mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-22 20:56:36 +00:00
21
pkg/perflib/LICENSE
Normal file
21
pkg/perflib/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Leopold Schabel / The perflib_exporter authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
10
pkg/perflib/const.go
Normal file
10
pkg/perflib/const.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package perflib
|
||||
|
||||
// Based on https://github.com/leoluk/perflib_exporter/blob/master/collector/mapper.go
|
||||
|
||||
const (
|
||||
PERF_COUNTER_COUNTER = 0x10410400
|
||||
PERF_100NSEC_TIMER = 0x20510500
|
||||
PERF_PRECISION_100NS_TIMER = 0x20570500
|
||||
PERF_ELAPSED_TIME = 0x30240500
|
||||
)
|
||||
81
pkg/perflib/nametable.go
Normal file
81
pkg/perflib/nametable.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Initialize global name tables
|
||||
// TODO: profiling, add option to disable name tables if necessary
|
||||
// Not sure if we should resolve the names at all or just have the caller do it on demand
|
||||
// (for many use cases the index is sufficient)
|
||||
|
||||
var CounterNameTable = *QueryNameTable("Counter 009")
|
||||
|
||||
func (p *perfObjectType) LookupName() string {
|
||||
return CounterNameTable.LookupString(p.ObjectNameTitleIndex)
|
||||
}
|
||||
|
||||
type NameTable struct {
|
||||
once sync.Once
|
||||
|
||||
name string
|
||||
|
||||
table struct {
|
||||
index map[uint32]string
|
||||
string map[string]uint32
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NameTable) LookupString(index uint32) string {
|
||||
t.initialize()
|
||||
return t.table.index[index]
|
||||
}
|
||||
|
||||
func (t *NameTable) LookupIndex(str string) uint32 {
|
||||
t.initialize()
|
||||
return t.table.string[str]
|
||||
}
|
||||
|
||||
// QueryNameTable Query a perflib name table from the registry. Specify the type and the language
|
||||
// code (i.e. "Counter 009" or "Help 009") for English language.
|
||||
func QueryNameTable(tableName string) *NameTable {
|
||||
return &NameTable{
|
||||
name: tableName,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NameTable) initialize() {
|
||||
t.once.Do(func() {
|
||||
t.table.index = make(map[uint32]string)
|
||||
t.table.string = make(map[string]uint32)
|
||||
|
||||
buffer, err := queryRawData(t.name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r := bytes.NewReader(buffer)
|
||||
for {
|
||||
index, err := readUTF16String(r)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
desc, err := readUTF16String(r)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Invalid index ", index))
|
||||
}
|
||||
|
||||
indexInt, _ := strconv.Atoi(index)
|
||||
|
||||
t.table.index[uint32(indexInt)] = desc
|
||||
t.table.string[desc] = uint32(indexInt)
|
||||
}
|
||||
})
|
||||
}
|
||||
467
pkg/perflib/perflib.go
Normal file
467
pkg/perflib/perflib.go
Normal file
@@ -0,0 +1,467 @@
|
||||
package perflib
|
||||
|
||||
/*
|
||||
Go bindings for the HKEY_PERFORMANCE_DATA perflib / Performance Counters interface.
|
||||
|
||||
# Overview
|
||||
|
||||
HKEY_PERFORMANCE_DATA is a low-level alternative to the higher-level PDH library and WMI.
|
||||
It operates on blocks of counters and only returns raw values without calculating rates
|
||||
or formatting them, which is exactly what you want for, say, a Prometheus exporter
|
||||
(not so much for a GUI like Windows Performance Monitor).
|
||||
|
||||
Its overhead is much lower than the high-level libraries.
|
||||
|
||||
It operates on the same set of perflib providers as PDH and WMI. See this document
|
||||
for more details on the relationship between the different libraries:
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/aa371643(v=vs.85).aspx
|
||||
|
||||
Example C++ source code:
|
||||
https://msdn.microsoft.com/de-de/library/windows/desktop/aa372138(v=vs.85).aspx
|
||||
|
||||
For now, the API is not stable and is probably going to change in future
|
||||
perflib_exporter releases. If you want to use this library, send the author an email
|
||||
so we can discuss your requirements and stabilize the API.
|
||||
|
||||
# Names
|
||||
|
||||
Counter names and help texts are resolved by looking up an index in a name table.
|
||||
Since Microsoft loves internalization, both names and help texts can be requested
|
||||
any locally available language.
|
||||
|
||||
The library automatically loads the name tables and resolves all identifiers
|
||||
in English ("Name" and "HelpText" struct members). You can manually resolve
|
||||
identifiers in a different language by using the NameTable API.
|
||||
|
||||
# Performance Counters intro
|
||||
|
||||
Windows has a system-wide performance counter mechanism. Most performance counters
|
||||
are stored as actual counters, not gauges (with some exceptions).
|
||||
There's additional metadata which defines how the counter should be presented to the user
|
||||
(for example, as a calculated rate). This library disregards all of the display metadata.
|
||||
|
||||
At the top level, there's a number of performance counter objects.
|
||||
Each object has counter definitions, which contain the metadata for a particular
|
||||
counter, and either zero or multiple instances. We hide the fact that there are
|
||||
objects with no instances, and simply return a single null instance.
|
||||
|
||||
There's one counter per counter definition and instance (or the object itself, if
|
||||
there are no instances).
|
||||
|
||||
Behind the scenes, every perflib DLL provides one or more objects.
|
||||
Perflib has a registry where DLLs are dynamically registered and
|
||||
unregistered. Some third party applications like VMWare provide their own counters,
|
||||
but this is, sadly, a rare occurrence.
|
||||
|
||||
Different Windows releases have different numbers of counters.
|
||||
|
||||
Objects and counters are identified by well-known indices.
|
||||
|
||||
Here's an example object with one instance:
|
||||
|
||||
4320 WSMan Quota Statistics [7 counters, 1 instance(s)]
|
||||
`-- "WinRMService"
|
||||
`-- Total Requests/Second [4322] = 59
|
||||
`-- User Quota Violations/Second [4324] = 0
|
||||
`-- System Quota Violations/Second [4326] = 0
|
||||
`-- Active Shells [4328] = 0
|
||||
`-- Active Operations [4330] = 0
|
||||
`-- Active Users [4332] = 0
|
||||
`-- Process ID [4334] = 928
|
||||
|
||||
All "per second" metrics are counters, the rest are gauges.
|
||||
|
||||
Another example, with no instance:
|
||||
|
||||
4600 Network QoS Policy [6 counters, 1 instance(s)]
|
||||
`-- (default)
|
||||
`-- Packets transmitted [4602] = 1744
|
||||
`-- Packets transmitted/sec [4604] = 4852
|
||||
`-- Bytes transmitted [4606] = 4853
|
||||
`-- Bytes transmitted/sec [4608] = 180388626632
|
||||
`-- Packets dropped [4610] = 0
|
||||
`-- Packets dropped/sec [4612] = 0
|
||||
|
||||
You can access the same values using PowerShell's Get-Counter cmdlet
|
||||
or the Performance Monitor.
|
||||
|
||||
> Get-Counter '\WSMan Quota Statistics(WinRMService)\Process ID'
|
||||
|
||||
Timestamp CounterSamples
|
||||
--------- --------------
|
||||
1/28/2018 10:18:00 PM \\DEV\wsman quota statistics(winrmservice)\process id :
|
||||
928
|
||||
|
||||
> (Get-Counter '\Process(Idle)\% Processor Time').CounterSamples[0] | Format-List *
|
||||
[..detailed output...]
|
||||
|
||||
Data for some of the objects is also available through WMI:
|
||||
|
||||
> Get-CimInstance Win32_PerfRawData_Counters_WSManQuotaStatistics
|
||||
|
||||
Name : WinRMService
|
||||
[...]
|
||||
ActiveOperations : 0
|
||||
ActiveShells : 0
|
||||
ActiveUsers : 0
|
||||
ProcessID : 928
|
||||
SystemQuotaViolationsPerSecond : 0
|
||||
TotalRequestsPerSecond : 59
|
||||
UserQuotaViolationsPerSecond : 0
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// TODO: There's a LittleEndian field in the PERF header - we ought to check it
|
||||
var bo = binary.LittleEndian
|
||||
|
||||
const averageCount64Type = 1073874176
|
||||
|
||||
// PerfObject Top-level performance object (like "Process").
|
||||
type PerfObject struct {
|
||||
Name string
|
||||
// NameIndex Same index you pass to QueryPerformanceData
|
||||
NameIndex uint
|
||||
Instances []*PerfInstance
|
||||
CounterDefs []*PerfCounterDef
|
||||
|
||||
Frequency int64
|
||||
|
||||
rawData *perfObjectType
|
||||
}
|
||||
|
||||
// PerfInstance Each object can have multiple instances. For example,
|
||||
// In case the object has no instances, we return one single PerfInstance with an empty name.
|
||||
type PerfInstance struct {
|
||||
// *not* resolved using a name table
|
||||
Name string
|
||||
Counters []*PerfCounter
|
||||
|
||||
rawData *perfInstanceDefinition
|
||||
rawCounterBlock *perfCounterBlock
|
||||
}
|
||||
|
||||
type PerfCounterDef struct {
|
||||
Name string
|
||||
NameIndex uint
|
||||
|
||||
// For debugging - subject to removal. CounterType is a perflib
|
||||
// implementation detail (see perflib.h) and should not be used outside
|
||||
// of this package. We export it so we can show it on /dump.
|
||||
CounterType uint32
|
||||
|
||||
// PERF_TYPE_COUNTER (otherwise, it's a gauge)
|
||||
IsCounter bool
|
||||
// PERF_COUNTER_BASE (base value of a multi-value fraction)
|
||||
IsBaseValue bool
|
||||
// PERF_TIMER_100NS
|
||||
IsNanosecondCounter bool
|
||||
HasSecondValue bool
|
||||
|
||||
rawData *perfCounterDefinition
|
||||
}
|
||||
|
||||
type PerfCounter struct {
|
||||
Value int64
|
||||
Def *PerfCounterDef
|
||||
SecondValue int64
|
||||
}
|
||||
|
||||
var (
|
||||
bufLenGlobal = uint32(400000)
|
||||
bufLenCostly = uint32(2000000)
|
||||
)
|
||||
|
||||
// queryRawData Queries the performance counter buffer using RegQueryValueEx, returning raw bytes. See:
|
||||
// https://msdn.microsoft.com/de-de/library/windows/desktop/aa373219(v=vs.85).aspx
|
||||
func queryRawData(query string) ([]byte, error) {
|
||||
var (
|
||||
valType uint32
|
||||
buffer []byte
|
||||
bufLen uint32
|
||||
)
|
||||
|
||||
switch query {
|
||||
case "Global":
|
||||
bufLen = bufLenGlobal
|
||||
case "Costly":
|
||||
bufLen = bufLenCostly
|
||||
default:
|
||||
// TODO: depends on the number of values requested
|
||||
// need make an educated guess
|
||||
numCounters := len(strings.Split(query, " "))
|
||||
bufLen = uint32(150000 * numCounters)
|
||||
}
|
||||
|
||||
buffer = make([]byte, bufLen)
|
||||
|
||||
name, err := syscall.UTF16PtrFromString(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode query string: %v", err)
|
||||
}
|
||||
|
||||
for {
|
||||
bufLen := uint32(len(buffer))
|
||||
|
||||
err := syscall.RegQueryValueEx(
|
||||
syscall.HKEY_PERFORMANCE_DATA,
|
||||
name,
|
||||
nil,
|
||||
&valType,
|
||||
(*byte)(unsafe.Pointer(&buffer[0])),
|
||||
&bufLen)
|
||||
|
||||
if err == error(syscall.ERROR_MORE_DATA) {
|
||||
newBuffer := make([]byte, len(buffer)+16384)
|
||||
copy(newBuffer, buffer)
|
||||
buffer = newBuffer
|
||||
continue
|
||||
} else if err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
return nil, fmt.Errorf("ReqQueryValueEx failed: %v errno %d", err, uint(errno))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer = buffer[:bufLen]
|
||||
|
||||
switch query {
|
||||
case "Global":
|
||||
if bufLen > bufLenGlobal {
|
||||
bufLenGlobal = bufLen
|
||||
}
|
||||
case "Costly":
|
||||
if bufLen > bufLenCostly {
|
||||
bufLenCostly = bufLen
|
||||
}
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
QueryPerformanceData Query all performance counters that match a given query.
|
||||
|
||||
The query can be any of the following:
|
||||
|
||||
- "Global" (all performance counters except those Windows marked as costly)
|
||||
|
||||
- "Costly" (only the costly ones)
|
||||
|
||||
- One or more object indices, separated by spaces ("238 2 5")
|
||||
|
||||
Many objects have dependencies - if you query one of them, you often get back
|
||||
more than you asked for.
|
||||
*/
|
||||
func QueryPerformanceData(query string) ([]*PerfObject, error) {
|
||||
buffer, err := queryRawData(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buffer)
|
||||
|
||||
// Read global header
|
||||
|
||||
header := new(perfDataBlock)
|
||||
err = header.BinaryReadFrom(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for "PERF" signature
|
||||
if header.Signature != [4]uint16{80, 69, 82, 70} {
|
||||
panic("Invalid performance block header")
|
||||
}
|
||||
|
||||
// Parse the performance data
|
||||
|
||||
numObjects := int(header.NumObjectTypes)
|
||||
objects := make([]*PerfObject, numObjects)
|
||||
|
||||
objOffset := int64(header.HeaderLength)
|
||||
|
||||
for i := 0; i < numObjects; i++ {
|
||||
_, err := r.Seek(objOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj := new(perfObjectType)
|
||||
err = obj.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numCounterDefs := int(obj.NumCounters)
|
||||
numInstances := int(obj.NumInstances)
|
||||
|
||||
// Perf objects can have no instances. The perflib differentiates
|
||||
// between objects with instances and without, but we just create
|
||||
// an empty instance in order to simplify the interface.
|
||||
if numInstances <= 0 {
|
||||
numInstances = 1
|
||||
}
|
||||
|
||||
instances := make([]*PerfInstance, numInstances)
|
||||
counterDefs := make([]*PerfCounterDef, numCounterDefs)
|
||||
|
||||
objects[i] = &PerfObject{
|
||||
Name: obj.LookupName(),
|
||||
NameIndex: uint(obj.ObjectNameTitleIndex),
|
||||
Instances: instances,
|
||||
CounterDefs: counterDefs,
|
||||
Frequency: obj.PerfFreq,
|
||||
rawData: obj,
|
||||
}
|
||||
|
||||
for i := 0; i < numCounterDefs; i++ {
|
||||
def := new(perfCounterDefinition)
|
||||
err := def.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counterDefs[i] = &PerfCounterDef{
|
||||
Name: def.LookupName(),
|
||||
NameIndex: uint(def.CounterNameTitleIndex),
|
||||
rawData: def,
|
||||
|
||||
CounterType: def.CounterType,
|
||||
|
||||
IsCounter: def.CounterType&0x400 == 0x400,
|
||||
IsBaseValue: def.CounterType&0x00030000 == 0x00030000,
|
||||
IsNanosecondCounter: def.CounterType&0x00100000 == 0x00100000,
|
||||
HasSecondValue: def.CounterType == averageCount64Type,
|
||||
}
|
||||
}
|
||||
|
||||
if obj.NumInstances <= 0 {
|
||||
blockOffset := objOffset + int64(obj.DefinitionLength)
|
||||
_, err := r.Seek(blockOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, counters, err := parseCounterBlock(buffer, r, blockOffset, counterDefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances[0] = &PerfInstance{
|
||||
Name: "",
|
||||
Counters: counters,
|
||||
rawData: nil,
|
||||
rawCounterBlock: nil,
|
||||
}
|
||||
} else {
|
||||
instOffset := objOffset + int64(obj.DefinitionLength)
|
||||
|
||||
for i := 0; i < numInstances; i++ {
|
||||
_, err := r.Seek(instOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inst := new(perfInstanceDefinition)
|
||||
err = inst.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, _ := readUTF16StringAtPos(r, instOffset+int64(inst.NameOffset), inst.NameLength)
|
||||
pos := instOffset + int64(inst.ByteLength)
|
||||
offset, counters, err := parseCounterBlock(buffer, r, pos, counterDefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances[i] = &PerfInstance{
|
||||
Name: name,
|
||||
Counters: counters,
|
||||
rawData: inst,
|
||||
}
|
||||
|
||||
instOffset = pos + offset
|
||||
}
|
||||
}
|
||||
|
||||
// Next perfObjectType
|
||||
objOffset += int64(obj.TotalByteLength)
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func parseCounterBlock(b []byte, r io.ReadSeeker, pos int64, defs []*PerfCounterDef) (int64, []*PerfCounter, error) {
|
||||
_, err := r.Seek(pos, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
block := new(perfCounterBlock)
|
||||
err = block.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
counters := make([]*PerfCounter, len(defs))
|
||||
|
||||
for i, def := range defs {
|
||||
valueOffset := pos + int64(def.rawData.CounterOffset)
|
||||
value := convertCounterValue(def.rawData, b, valueOffset)
|
||||
secondValue := int64(0)
|
||||
|
||||
if def.HasSecondValue {
|
||||
secondValue = convertCounterValue(def.rawData, b, valueOffset+8)
|
||||
}
|
||||
|
||||
counters[i] = &PerfCounter{
|
||||
Value: value,
|
||||
Def: def,
|
||||
SecondValue: secondValue,
|
||||
}
|
||||
}
|
||||
|
||||
return int64(block.ByteLength), counters, nil
|
||||
}
|
||||
|
||||
func convertCounterValue(counterDef *perfCounterDefinition, buffer []byte, valueOffset int64) (value int64) {
|
||||
/*
|
||||
We can safely ignore the type since we're not interested in anything except the raw value.
|
||||
We also ignore all of the other attributes (timestamp, presentation, multi counter values...)
|
||||
|
||||
See also: winperf.h.
|
||||
|
||||
Here's the most common value for CounterType:
|
||||
|
||||
65536 32bit counter
|
||||
65792 64bit counter
|
||||
272696320 32bit rate
|
||||
272696576 64bit rate
|
||||
|
||||
*/
|
||||
|
||||
switch counterDef.CounterSize {
|
||||
case 4:
|
||||
value = int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
|
||||
case 8:
|
||||
value = int64(bo.Uint64(buffer[valueOffset:(valueOffset + 8)]))
|
||||
default:
|
||||
value = int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
41
pkg/perflib/perflib_test.go
Normal file
41
pkg/perflib/perflib_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ExampleQueryPerformanceData() {
|
||||
objects, err := QueryPerformanceData("2")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, object := range objects {
|
||||
fmt.Printf("%d %s [%d counters, %d instances]\n",
|
||||
object.NameIndex, object.Name, len(object.CounterDefs), len(object.Instances))
|
||||
|
||||
for _, instance := range object.Instances {
|
||||
if !((instance.Name == "_Total") || (instance.Name == "")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if instance.Name == "" {
|
||||
fmt.Println("No instance.", instance.Name)
|
||||
} else {
|
||||
fmt.Println("Instance:", instance.Name)
|
||||
}
|
||||
|
||||
for _, counter := range instance.Counters {
|
||||
fmt.Printf(" -> %s %d\n", counter.Def.Name, counter.Def.NameIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQueryPerformanceData(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = QueryPerformanceData("Global")
|
||||
}
|
||||
}
|
||||
172
pkg/perflib/raw_types.go
Normal file
172
pkg/perflib/raw_types.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
/*
|
||||
perfDataBlock
|
||||
See: https://msdn.microsoft.com/de-de/library/windows/desktop/aa373157(v=vs.85).aspx
|
||||
|
||||
typedef struct _PERF_DATA_BLOCK {
|
||||
WCHAR Signature[4];
|
||||
DWORD LittleEndian;
|
||||
DWORD Version;
|
||||
DWORD Revision;
|
||||
DWORD TotalByteLength;
|
||||
DWORD HeaderLength;
|
||||
DWORD NumObjectTypes;
|
||||
DWORD DefaultObject;
|
||||
SYSTEMTIME SystemTime;
|
||||
LARGE_INTEGER PerfTime;
|
||||
LARGE_INTEGER PerfFreq;
|
||||
LARGE_INTEGER PerfTime100nSec;
|
||||
DWORD SystemNameLength;
|
||||
DWORD SystemNameOffset;
|
||||
} PERF_DATA_BLOCK;
|
||||
*/
|
||||
type perfDataBlock struct {
|
||||
Signature [4]uint16
|
||||
LittleEndian uint32
|
||||
Version uint32
|
||||
Revision uint32
|
||||
TotalByteLength uint32
|
||||
HeaderLength uint32
|
||||
NumObjectTypes uint32
|
||||
DefaultObject int32
|
||||
SystemTime syscall.Systemtime
|
||||
_ uint32 // TODO
|
||||
PerfTime int64
|
||||
PerfFreq int64
|
||||
PerfTime100nSec int64
|
||||
SystemNameLength uint32
|
||||
SystemNameOffset uint32
|
||||
}
|
||||
|
||||
func (p *perfDataBlock) BinaryReadFrom(r io.Reader) error {
|
||||
return binary.Read(r, bo, p)
|
||||
}
|
||||
|
||||
/*
|
||||
perfObjectType
|
||||
See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373160(v=vs.85).aspx
|
||||
|
||||
typedef struct _PERF_OBJECT_TYPE {
|
||||
DWORD TotalByteLength;
|
||||
DWORD DefinitionLength;
|
||||
DWORD HeaderLength;
|
||||
DWORD ObjectNameTitleIndex;
|
||||
LPWSTR ObjectNameTitle;
|
||||
DWORD ObjectHelpTitleIndex;
|
||||
LPWSTR ObjectHelpTitle;
|
||||
DWORD DetailLevel;
|
||||
DWORD NumCounters;
|
||||
DWORD DefaultCounter;
|
||||
DWORD NumInstances;
|
||||
DWORD CodePage;
|
||||
LARGE_INTEGER PerfTime;
|
||||
LARGE_INTEGER PerfFreq;
|
||||
} PERF_OBJECT_TYPE;
|
||||
*/
|
||||
type perfObjectType struct {
|
||||
TotalByteLength uint32
|
||||
DefinitionLength uint32
|
||||
HeaderLength uint32
|
||||
ObjectNameTitleIndex uint32
|
||||
ObjectNameTitle uint32
|
||||
ObjectHelpTitleIndex uint32
|
||||
ObjectHelpTitle uint32
|
||||
DetailLevel uint32
|
||||
NumCounters uint32
|
||||
DefaultCounter int32
|
||||
NumInstances int32
|
||||
CodePage uint32
|
||||
PerfTime int64
|
||||
PerfFreq int64
|
||||
}
|
||||
|
||||
func (p *perfObjectType) BinaryReadFrom(r io.Reader) error {
|
||||
return binary.Read(r, bo, p)
|
||||
}
|
||||
|
||||
/*
|
||||
perfCounterDefinition
|
||||
See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373150(v=vs.85).aspx
|
||||
|
||||
typedef struct _PERF_COUNTER_DEFINITION {
|
||||
DWORD ByteLength;
|
||||
DWORD CounterNameTitleIndex;
|
||||
LPWSTR CounterNameTitle;
|
||||
DWORD CounterHelpTitleIndex;
|
||||
LPWSTR CounterHelpTitle;
|
||||
LONG DefaultScale;
|
||||
DWORD DetailLevel;
|
||||
DWORD CounterType;
|
||||
DWORD CounterSize;
|
||||
DWORD CounterOffset;
|
||||
} PERF_COUNTER_DEFINITION;
|
||||
*/
|
||||
type perfCounterDefinition struct {
|
||||
ByteLength uint32
|
||||
CounterNameTitleIndex uint32
|
||||
CounterNameTitle uint32
|
||||
CounterHelpTitleIndex uint32
|
||||
CounterHelpTitle uint32
|
||||
DefaultScale int32
|
||||
DetailLevel uint32
|
||||
CounterType uint32
|
||||
CounterSize uint32
|
||||
CounterOffset uint32
|
||||
}
|
||||
|
||||
func (p *perfCounterDefinition) BinaryReadFrom(r io.Reader) error {
|
||||
return binary.Read(r, bo, p)
|
||||
}
|
||||
|
||||
func (p *perfCounterDefinition) LookupName() string {
|
||||
return CounterNameTable.LookupString(p.CounterNameTitleIndex)
|
||||
}
|
||||
|
||||
/*
|
||||
perfCounterBlock
|
||||
See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373147(v=vs.85).aspx
|
||||
|
||||
typedef struct _PERF_COUNTER_BLOCK {
|
||||
DWORD ByteLength;
|
||||
} PERF_COUNTER_BLOCK;
|
||||
*/
|
||||
type perfCounterBlock struct {
|
||||
ByteLength uint32
|
||||
}
|
||||
|
||||
func (p *perfCounterBlock) BinaryReadFrom(r io.Reader) error {
|
||||
return binary.Read(r, bo, p)
|
||||
}
|
||||
|
||||
/*
|
||||
perfInstanceDefinition
|
||||
See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373159(v=vs.85).aspx
|
||||
|
||||
typedef struct _PERF_INSTANCE_DEFINITION {
|
||||
DWORD ByteLength;
|
||||
DWORD ParentObjectTitleIndex;
|
||||
DWORD ParentObjectInstance;
|
||||
DWORD UniqueID;
|
||||
DWORD NameOffset;
|
||||
DWORD NameLength;
|
||||
} PERF_INSTANCE_DEFINITION;
|
||||
*/
|
||||
type perfInstanceDefinition struct {
|
||||
ByteLength uint32
|
||||
ParentObjectTitleIndex uint32
|
||||
ParentObjectInstance uint32
|
||||
UniqueID uint32
|
||||
NameOffset uint32
|
||||
NameLength uint32
|
||||
}
|
||||
|
||||
func (p *perfInstanceDefinition) BinaryReadFrom(r io.Reader) error {
|
||||
return binary.Read(r, bo, p)
|
||||
}
|
||||
111
pkg/perflib/unmarshal.go
Normal file
111
pkg/perflib/unmarshal.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
)
|
||||
|
||||
// Conversion factors
|
||||
const (
|
||||
TicksToSecondScaleFactor = 1 / 1e7
|
||||
WindowsEpoch = 116444736000000000
|
||||
)
|
||||
|
||||
func UnmarshalObject(obj *PerfObject, vs interface{}, logger log.Logger) error {
|
||||
if obj == nil {
|
||||
return fmt.Errorf("counter not found")
|
||||
}
|
||||
rv := reflect.ValueOf(vs)
|
||||
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
||||
return fmt.Errorf("%v is nil or not a pointer to slice", reflect.TypeOf(vs))
|
||||
}
|
||||
ev := rv.Elem()
|
||||
if ev.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("%v is not slice", reflect.TypeOf(vs))
|
||||
}
|
||||
|
||||
// Ensure sufficient length
|
||||
if ev.Cap() < len(obj.Instances) {
|
||||
nvs := reflect.MakeSlice(ev.Type(), len(obj.Instances), len(obj.Instances))
|
||||
ev.Set(nvs)
|
||||
}
|
||||
|
||||
for idx, instance := range obj.Instances {
|
||||
target := ev.Index(idx)
|
||||
rt := target.Type()
|
||||
|
||||
counters := make(map[string]*PerfCounter, len(instance.Counters))
|
||||
for _, ctr := range instance.Counters {
|
||||
if ctr.Def.IsBaseValue && !ctr.Def.IsNanosecondCounter {
|
||||
counters[ctr.Def.Name+"_Base"] = ctr
|
||||
} else {
|
||||
counters[ctr.Def.Name] = ctr
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < target.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
tag := f.Tag.Get("perflib")
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
secondValue := false
|
||||
|
||||
st := strings.Split(tag, ",")
|
||||
tag = st[0]
|
||||
|
||||
for _, t := range st {
|
||||
if t == "secondvalue" {
|
||||
secondValue = true
|
||||
}
|
||||
}
|
||||
|
||||
ctr, found := counters[tag]
|
||||
if !found {
|
||||
_ = level.Debug(logger).Log("msg", fmt.Sprintf("missing counter %q, have %v", tag, counterMapKeys(counters)))
|
||||
continue
|
||||
}
|
||||
if !target.Field(i).CanSet() {
|
||||
return fmt.Errorf("tagged field %v cannot be written to", f.Name)
|
||||
}
|
||||
if fieldType := target.Field(i).Type(); fieldType != reflect.TypeOf((*float64)(nil)).Elem() {
|
||||
return fmt.Errorf("tagged field %v has wrong type %v, must be float64", f.Name, fieldType)
|
||||
}
|
||||
|
||||
if secondValue {
|
||||
if !ctr.Def.HasSecondValue {
|
||||
return fmt.Errorf("tagged field %v expected a SecondValue, which was not present", f.Name)
|
||||
}
|
||||
target.Field(i).SetFloat(float64(ctr.SecondValue))
|
||||
continue
|
||||
}
|
||||
|
||||
switch ctr.Def.CounterType {
|
||||
case PERF_ELAPSED_TIME:
|
||||
target.Field(i).SetFloat(float64(ctr.Value-WindowsEpoch) / float64(obj.Frequency))
|
||||
case PERF_100NSEC_TIMER, PERF_PRECISION_100NS_TIMER:
|
||||
target.Field(i).SetFloat(float64(ctr.Value) * TicksToSecondScaleFactor)
|
||||
default:
|
||||
target.Field(i).SetFloat(float64(ctr.Value))
|
||||
}
|
||||
}
|
||||
|
||||
if instance.Name != "" && target.FieldByName("Name").CanSet() {
|
||||
target.FieldByName("Name").SetString(instance.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func counterMapKeys(m map[string]*PerfCounter) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
49
pkg/perflib/utf16.go
Normal file
49
pkg/perflib/utf16.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// readUTF16StringAtPos Read an unterminated UTF16 string at a given position, specifying its length
|
||||
func readUTF16StringAtPos(r io.ReadSeeker, absPos int64, length uint32) (string, error) {
|
||||
value := make([]uint16, length/2)
|
||||
_, err := r.Seek(absPos, io.SeekStart)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = binary.Read(r, bo, value)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return syscall.UTF16ToString(value), nil
|
||||
}
|
||||
|
||||
// readUTF16String Reads a null-terminated UTF16 string at the current offset
|
||||
func readUTF16String(r io.Reader) (string, error) {
|
||||
var err error
|
||||
|
||||
b := make([]byte, 2)
|
||||
out := make([]uint16, 0, 100)
|
||||
|
||||
for i := 0; err == nil; i += 2 {
|
||||
_, err = r.Read(b)
|
||||
|
||||
if b[0] == 0 && b[1] == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
out = append(out, bo.Uint16(b))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return syscall.UTF16ToString(out), nil
|
||||
}
|
||||
22
pkg/perflib/utils.go
Normal file
22
pkg/perflib/utils.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func MapCounterToIndex(name string) string {
|
||||
return strconv.Itoa(int(CounterNameTable.LookupIndex(name)))
|
||||
}
|
||||
|
||||
func GetPerflibSnapshot(objNames string) (map[string]*PerfObject, error) {
|
||||
objects, err := QueryPerformanceData(objNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexed := make(map[string]*PerfObject)
|
||||
for _, obj := range objects {
|
||||
indexed[obj.Name] = obj
|
||||
}
|
||||
return indexed, nil
|
||||
}
|
||||
127
pkg/perflib/utils_test.go
Normal file
127
pkg/perflib/utils_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package perflib
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
)
|
||||
|
||||
type simple struct {
|
||||
ValA float64 `perflib:"Something"`
|
||||
ValB float64 `perflib:"Something Else"`
|
||||
ValC float64 `perflib:"Something Else,secondvalue"`
|
||||
}
|
||||
|
||||
func TestUnmarshalPerflib(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
obj *PerfObject
|
||||
|
||||
expectedOutput []simple
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "nil check",
|
||||
obj: nil,
|
||||
expectedOutput: []simple{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Simple",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: []simple{{ValA: 123}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple properties",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 123,
|
||||
},
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something Else",
|
||||
CounterType: PERF_COUNTER_COUNTER,
|
||||
HasSecondValue: true,
|
||||
},
|
||||
Value: 256,
|
||||
SecondValue: 222,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: []simple{{ValA: 123, ValB: 256, ValC: 222}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple instances",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 321,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 231,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: []simple{{ValA: 321}, {ValA: 231}},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
output := make([]simple, 0)
|
||||
err := UnmarshalObject(c.obj, &output, log.NewNopLogger())
|
||||
if err != nil && !c.expectError {
|
||||
t.Errorf("Did not expect error, got %q", err)
|
||||
}
|
||||
if err == nil && c.expectError {
|
||||
t.Errorf("Expected an error, but got ok")
|
||||
}
|
||||
|
||||
if err == nil && !reflect.DeepEqual(output, c.expectedOutput) {
|
||||
t.Errorf("Output mismatch, expected %+v, got %+v", c.expectedOutput, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user