wmi: add generic wmi collector (#2530)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-10-07 23:22:11 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a67a64c55c
commit 88034b818e
16 changed files with 2309 additions and 32 deletions
+159 -5
View File
@@ -21,6 +21,8 @@ import (
"errors"
"fmt"
"math"
"strconv"
"time"
"unsafe"
"golang.org/x/sys/windows"
@@ -65,10 +67,166 @@ const (
ValueTypeARRAY ValueType = 16
)
// flagNull is MI_FLAG_NULL. MI_Instance_GetElement sets it in the returned
// flags when the element has no value.
const flagNull uint32 = 0x20000000
type Element struct {
value uintptr
arrayLen uint32
valueType ValueType
flags uint32
// raw holds the complete MI_Value union. Scalars and pointers live in the
// first word, but MI_Datetime is stored inline and spans 36 bytes.
raw [5]uint64
}
// IsNull reports whether the element has no value.
func (e *Element) IsNull() bool {
return e.flags&flagNull != 0
}
// Float64 converts a boolean, numeric or datetime element to a float64.
// Booleans become 0 or 1, timestamps become seconds since the Unix epoch and
// intervals become seconds.
func (e *Element) Float64() (float64, error) {
switch e.valueType {
case ValueTypeBOOLEAN:
if e.value == 1 {
return 1, nil
}
return 0, nil
case ValueTypeUINT8:
return float64(uint8(e.value)), nil
case ValueTypeSINT8:
return float64(int8(e.value)), nil
case ValueTypeUINT16:
return float64(uint16(e.value)), nil
case ValueTypeSINT16:
return float64(int16(e.value)), nil
case ValueTypeUINT32:
return float64(uint32(e.value)), nil
case ValueTypeSINT32:
return float64(int32(e.value)), nil
case ValueTypeUINT64:
return float64(uint64(e.value)), nil
case ValueTypeSINT64:
return float64(int64(e.value)), nil
case ValueTypeREAL32:
return float64(math.Float32frombits(uint32(e.value))), nil
case ValueTypeREAL64:
return math.Float64frombits(uint64(e.value)), nil
case ValueTypeDATETIME:
dt := e.datetime()
if dt.IsTimestamp {
return float64(dt.Timestamp.Time().UnixMicro()) / 1e6, nil
}
return dt.Interval.TotalSeconds(), nil
default:
return 0, fmt.Errorf("unsupported value type for numeric conversion: %d", e.valueType)
}
}
// String converts a string, boolean or integer element to its string form.
func (e *Element) String() (string, error) {
switch e.valueType {
case ValueTypeSTRING:
if e.value == 0 {
return "", nil
}
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(e.value))), nil
case ValueTypeBOOLEAN:
return strconv.FormatBool(e.value == 1), nil
case ValueTypeUINT8:
return strconv.FormatUint(uint64(uint8(e.value)), 10), nil
case ValueTypeUINT16:
return strconv.FormatUint(uint64(uint16(e.value)), 10), nil
case ValueTypeUINT32:
return strconv.FormatUint(uint64(uint32(e.value)), 10), nil
case ValueTypeUINT64:
return strconv.FormatUint(uint64(e.value), 10), nil
case ValueTypeSINT8:
return strconv.FormatInt(int64(int8(e.value)), 10), nil
case ValueTypeSINT16:
return strconv.FormatInt(int64(int16(e.value)), 10), nil
case ValueTypeSINT32:
return strconv.FormatInt(int64(int32(e.value)), 10), nil
case ValueTypeSINT64:
return strconv.FormatInt(int64(e.value), 10), nil
default:
return "", fmt.Errorf("unsupported value type for string conversion: %d", e.valueType)
}
}
// datetime decodes the inline MI_Datetime: an MI_Uint32 isTimestamp followed
// by an MI_Timestamp or an MI_Interval of eight 32-bit words.
func (e *Element) datetime() Datetime {
words := (*[9]uint32)(unsafe.Pointer(&e.raw))
if words[0] != 0 {
return Datetime{
IsTimestamp: true,
Timestamp: &Timestamp{
Year: words[1],
Month: words[2],
Day: words[3],
Hour: words[4],
Minute: words[5],
Second: words[6],
Microseconds: words[7],
UTC: int32(words[8]),
},
}
}
return Datetime{
Interval: &Interval{
Days: words[1],
Hours: words[2],
Minutes: words[3],
Seconds: words[4],
Microseconds: words[5],
},
}
}
// Time converts the timestamp to a time.Time. UTC is the offset of the
// recorded local time from UTC, in minutes.
func (t *Timestamp) Time() time.Time {
return time.Date(
int(t.Year), time.Month(t.Month), int(t.Day),
int(t.Hour), int(t.Minute), int(t.Second), int(t.Microseconds)*int(time.Microsecond),
time.FixedZone("", int(t.UTC)*60),
)
}
// TotalSeconds returns the length of the interval in seconds. Unlike
// [Interval.Duration], it covers the full range of an MI_Interval, including
// the CIM "infinite" interval of 99999999 days.
func (i *Interval) TotalSeconds() float64 {
return float64(i.Days)*86400 +
float64(i.Hours)*3600 +
float64(i.Minutes)*60 +
float64(i.Seconds) +
float64(i.Microseconds)/1e6
}
// Duration converts the interval to a time.Duration. Intervals longer than
// about 292 years exceed time.Duration and are capped at its maximum.
func (i *Interval) Duration() time.Duration {
if i.TotalSeconds() >= float64(math.MaxInt64)/float64(time.Second) {
return time.Duration(math.MaxInt64)
}
return time.Duration(i.Days)*24*time.Hour +
time.Duration(i.Hours)*time.Hour +
time.Duration(i.Minutes)*time.Minute +
time.Duration(i.Seconds)*time.Second +
time.Duration(i.Microseconds)*time.Microsecond
}
func (e *Element) GetValue() (any, error) {
@@ -98,11 +256,7 @@ func (e *Element) GetValue() (any, error) {
case ValueTypeCHAR16:
return uint16(e.value), nil
case ValueTypeDATETIME:
if e.value == 0 {
return nil, errors.New("invalid pointer: value is nil")
}
return *(*Datetime)(unsafe.Pointer(e.value)), nil
return e.datetime(), nil
case ValueTypeSTRING:
if e.value == 0 {
return nil, errors.New("invalid pointer: value is nil")