chore(inputs.win_wmi): Cleanup and refactor code (#14965)

This commit is contained in:
Sven Rebhan 2024-03-12 15:45:26 +01:00 committed by GitHub
parent f7237170b9
commit 8183d4730c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 452 additions and 477 deletions

View File

@ -1,16 +1,16 @@
# Windows Management Instrumentation Input Plugin # Windows Management Instrumentation Input Plugin
This document presents the input plugin to read WMI classes on Windows This document presents the input plugin to read WMI classes on Windows
operating systems. With the win_wmi plugin, it is possible to operating systems. With the win_wmi plugin, it is possible to
capture and filter virtually any configuration or metric value exposed capture and filter virtually any configuration or metric value exposed
through the Windows Management Instrumentation ([WMI][WMIdoc]) through the Windows Management Instrumentation ([WMI][WMIdoc])
service. At minimum, the telegraf service user must have permission service. At minimum, the telegraf service user must have permission
to [read][ACL] the WMI namespace that is being queried. to [read][ACL] the WMI namespace that is being queried.
[ACL]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces [ACL]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
[WMIdoc]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page [WMIdoc]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page
## Global configuration options <!-- @/docs/includes/plugin_config.md --> ## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to additional global and plugin configuration settings. These settings are used to
@ -19,9 +19,9 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins [CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration ## Configuration
```toml @sample.conf ```toml @sample.conf
# Input plugin to query Windows Management Instrumentation # Input plugin to query Windows Management Instrumentation
# This plugin ONLY supports Windows # This plugin ONLY supports Windows
[[inputs.win_wmi]] [[inputs.win_wmi]]
@ -36,262 +36,262 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
filter = 'NOT Name LIKE "\\\\?\\%"' filter = 'NOT Name LIKE "\\\\?\\%"'
# WMI class properties which should be considered tags instead of fields # WMI class properties which should be considered tags instead of fields
tag_properties = ["Name"] tag_properties = ["Name"]
``` ```
### namespace ### namespace
A string representing the WMI namespace to be queried. For example, A string representing the WMI namespace to be queried. For example,
`root\\cimv2`. `root\\cimv2`.
### class_name ### class_name
A string representing the WMI class to be queried. For example, A string representing the WMI class to be queried. For example,
`Win32_Processor`. `Win32_Processor`.
### properties ### properties
An array of strings representing the properties of the WMI class to be queried. An array of strings representing the properties of the WMI class to be queried.
### filter ### filter
A string specifying a WHERE clause to use as a filter for the WMI Query A string specifying a WHERE clause to use as a filter for the WMI Query
Language (WQL). See [WHERE Clause][WHERE] for more information. Language (WQL). See [WHERE Clause][WHERE] for more information.
[WHERE]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/where-clause?source=recommendations [WHERE]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/where-clause?source=recommendations
### tag_properties ### tag_properties
Properties which should be considered tags instead of fields. Properties which should be considered tags instead of fields.
## Metrics ## Metrics
By default, a WMI class property's value is used as a metric field. If a class By default, a WMI class property's value is used as a metric field. If a class
property's value is specified in `tag_properties`, then the value is property's value is specified in `tag_properties`, then the value is
instead included with the metric as a tag. instead included with the metric as a tag.
## Troubleshooting ## Troubleshooting
### Errors ### Errors
If you are getting an error about an invalid WMI namespace, class, or property, If you are getting an error about an invalid WMI namespace, class, or property,
use the `Get-WmiObject` or `Get-CimInstance` PowerShell commands in order to use the `Get-WmiObject` or `Get-CimInstance` PowerShell commands in order to
verify their validity. For example: verify their validity. For example:
```powershell ```powershell
Get-WmiObject -Namespace root\cimv2 -Class Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"' Get-WmiObject -Namespace root\cimv2 -Class Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"'
``` ```
```powershell ```powershell
Get-CimInstance -Namespace root\cimv2 -ClassName Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"' Get-CimInstance -Namespace root\cimv2 -ClassName Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"'
``` ```
### Data types ### Data types
Some WMI classes will return the incorrect data type for a field. In those Some WMI classes will return the incorrect data type for a field. In those
cases, it is necessary to use a processor to convert the data type. For cases, it is necessary to use a processor to convert the data type. For
example, the Capacity and FreeSpace properties of the Win32_Volume class must example, the Capacity and FreeSpace properties of the Win32_Volume class must
be converted to integers: be converted to integers:
```toml ```toml
[[processors.converter]] [[processors.converter]]
namepass = ["win_wmi_Win32_Volume"] namepass = ["win_wmi_Win32_Volume"]
[processors.converter.fields] [processors.converter.fields]
integer = ["Capacity", "FreeSpace"] integer = ["Capacity", "FreeSpace"]
``` ```
## Example Output ## Example Output
### Physical Memory ### Physical Memory
This query provides metrics for the speed and capacity of each physical memory This query provides metrics for the speed and capacity of each physical memory
device, along with tags describing the manufacturer, part number, and device device, along with tags describing the manufacturer, part number, and device
locator of each device. locator of each device.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "root\\cimv2" namespace = "root\\cimv2"
class_name = "Win32_PhysicalMemory" class_name = "Win32_PhysicalMemory"
properties = [ properties = [
"Name", "Name",
"Capacity", "Capacity",
"DeviceLocator", "DeviceLocator",
"Manufacturer", "Manufacturer",
"PartNumber", "PartNumber",
"Speed", "Speed",
] ]
tag_properties = ["Name","DeviceLocator","Manufacturer","PartNumber"] tag_properties = ["Name","DeviceLocator","Manufacturer","PartNumber"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_Win32_PhysicalMemory,DeviceLocator=DIMM1,Manufacturer=80AD000080AD,Name=Physical\ Memory,PartNumber=HMA82GU6DJR8N-XN\ \ \ \ ,host=foo Capacity=17179869184i,Speed=3200i 1654269272000000000 win_wmi_Win32_PhysicalMemory,DeviceLocator=DIMM1,Manufacturer=80AD000080AD,Name=Physical\ Memory,PartNumber=HMA82GU6DJR8N-XN\ \ \ \ ,host=foo Capacity=17179869184i,Speed=3200i 1654269272000000000
``` ```
### Processor ### Processor
This query provides metrics for the number of cores in each physical processor. This query provides metrics for the number of cores in each physical processor.
Since the Name property of the WMI class is included by default, the metrics Since the Name property of the WMI class is included by default, the metrics
will also contain a tag value describing the model of each CPU. will also contain a tag value describing the model of each CPU.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "root\\cimv2" namespace = "root\\cimv2"
class_name = "Win32_Processor" class_name = "Win32_Processor"
properties = ["Name","NumberOfCores"] properties = ["Name","NumberOfCores"]
tag_properties = ["Name"] tag_properties = ["Name"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_Win32_Processor,Name=Intel(R)\ Core(TM)\ i9-10900\ CPU\ @\ 2.80GHz,host=foo NumberOfCores=10i 1654269272000000000 win_wmi_Win32_Processor,Name=Intel(R)\ Core(TM)\ i9-10900\ CPU\ @\ 2.80GHz,host=foo NumberOfCores=10i 1654269272000000000
``` ```
### Computer System ### Computer System
This query provides metrics for the number of socketted processors, number of This query provides metrics for the number of socketted processors, number of
logical cores on each processor, and the total physical memory in the computer. logical cores on each processor, and the total physical memory in the computer.
The metrics include tag values for the domain, manufacturer, and model of the The metrics include tag values for the domain, manufacturer, and model of the
computer. computer.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "root\\cimv2" namespace = "root\\cimv2"
class_name = "Win32_ComputerSystem" class_name = "Win32_ComputerSystem"
properties = [ properties = [
"Name", "Name",
"Domain", "Domain",
"Manufacturer", "Manufacturer",
"Model", "Model",
"NumberOfLogicalProcessors", "NumberOfLogicalProcessors",
"NumberOfProcessors", "NumberOfProcessors",
"TotalPhysicalMemory" "TotalPhysicalMemory"
] ]
tag_properties = ["Name","Domain","Manufacturer","Model"] tag_properties = ["Name","Domain","Manufacturer","Model"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_Win32_ComputerSystem,Domain=company.com,Manufacturer=Lenovo,Model=X1\ Carbon,Name=FOO,host=foo NumberOfLogicalProcessors=20i,NumberOfProcessors=1i,TotalPhysicalMemory=34083926016i 1654269272000000000 win_wmi_Win32_ComputerSystem,Domain=company.com,Manufacturer=Lenovo,Model=X1\ Carbon,Name=FOO,host=foo NumberOfLogicalProcessors=20i,NumberOfProcessors=1i,TotalPhysicalMemory=34083926016i 1654269272000000000
``` ```
### Operating System ### Operating System
This query provides metrics for the paging file's free space, the operating This query provides metrics for the paging file's free space, the operating
system's free virtual memory, the operating system SKU installed on the system's free virtual memory, the operating system SKU installed on the
computer, and the Windows product type. The OS architecture is included as a computer, and the Windows product type. The OS architecture is included as a
tagged value to describe whether the installation is 32-bit or 64-bit. tagged value to describe whether the installation is 32-bit or 64-bit.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
class_name = "Win32_OperatingSystem" class_name = "Win32_OperatingSystem"
namespace = "root\\cimv2" namespace = "root\\cimv2"
properties = [ properties = [
"Name", "Name",
"Caption", "Caption",
"FreeSpaceInPagingFiles", "FreeSpaceInPagingFiles",
"FreeVirtualMemory", "FreeVirtualMemory",
"OperatingSystemSKU", "OperatingSystemSKU",
"OSArchitecture", "OSArchitecture",
"ProductType" "ProductType"
] ]
tag_properties = ["Name","Caption","OSArchitecture"] tag_properties = ["Name","Caption","OSArchitecture"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_Win32_OperatingSystem,Caption=Microsoft\ Windows\ 10\ Enterprise,InstallationType=Client,Name=Microsoft\ Windows\ 10\ Enterprise|C:\WINDOWS|\Device\Harddisk0\Partition3,OSArchitecture=64-bit,host=foo FreeSpaceInPagingFiles=5203244i,FreeVirtualMemory=16194496i,OperatingSystemSKU=4i,ProductType=1i 1654269272000000000 win_wmi_Win32_OperatingSystem,Caption=Microsoft\ Windows\ 10\ Enterprise,InstallationType=Client,Name=Microsoft\ Windows\ 10\ Enterprise|C:\WINDOWS|\Device\Harddisk0\Partition3,OSArchitecture=64-bit,host=foo FreeSpaceInPagingFiles=5203244i,FreeVirtualMemory=16194496i,OperatingSystemSKU=4i,ProductType=1i 1654269272000000000
``` ```
### Failover Clusters ### Failover Clusters
This query provides a boolean metric describing whether Dynamic Quorum is This query provides a boolean metric describing whether Dynamic Quorum is
enabled for the cluster. The tag values for the metric also include the name of enabled for the cluster. The tag values for the metric also include the name of
the Windows Server Failover Cluster and the type of Quorum in use. the Windows Server Failover Cluster and the type of Quorum in use.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "root\\mscluster" namespace = "root\\mscluster"
class_name = "MSCluster_Cluster" class_name = "MSCluster_Cluster"
properties = [ properties = [
"Name", "Name",
"QuorumType", "QuorumType",
"DynamicQuorumEnabled" "DynamicQuorumEnabled"
] ]
tag_properties = ["Name","QuorumType"] tag_properties = ["Name","QuorumType"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_MSCluster_Cluster,Name=testcluster1,QuorumType=Node\ and\ File\ Share\ Majority,host=testnode1 DynamicQuorumEnabled=1i 1671553260000000000 win_wmi_MSCluster_Cluster,Name=testcluster1,QuorumType=Node\ and\ File\ Share\ Majority,host=testnode1 DynamicQuorumEnabled=1i 1671553260000000000
``` ```
### Bitlocker ### Bitlocker
This query provides a list of volumes which are eligible for bitlocker This query provides a list of volumes which are eligible for bitlocker
encryption and their compliance status. Because the MBAM_Volume class does not encryption and their compliance status. Because the MBAM_Volume class does not
include a Name property, the ExcludeNameKey configuration is included. The include a Name property, the ExcludeNameKey configuration is included. The
VolumeName property is included in the metric as a tagged value. VolumeName property is included in the metric as a tagged value.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "root\\Microsoft\\MBAM" namespace = "root\\Microsoft\\MBAM"
class_name = "MBAM_Volume" class_name = "MBAM_Volume"
properties = [ properties = [
"Compliant", "Compliant",
"VolumeName" "VolumeName"
] ]
tag_properties = ["VolumeName"] tag_properties = ["VolumeName"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_MBAM_Volume,VolumeName=C:,host=foo Compliant=1i 1654269272000000000 win_wmi_MBAM_Volume,VolumeName=C:,host=foo Compliant=1i 1654269272000000000
``` ```
### SQL Server ### SQL Server
This query provides metrics which contain tags describing the version and SKU This query provides metrics which contain tags describing the version and SKU
of SQL Server. These properties are useful for creating a dashboard of your SQL of SQL Server. These properties are useful for creating a dashboard of your SQL
Server inventory, which includes the patch level and edition of SQL Server that Server inventory, which includes the patch level and edition of SQL Server that
is installed. is installed.
```toml ```toml
[[inputs.win_wmi]] [[inputs.win_wmi]]
name_prefix = "win_wmi_" name_prefix = "win_wmi_"
[[inputs.win_wmi.query]] [[inputs.win_wmi.query]]
namespace = "Root\\Microsoft\\SqlServer\\ComputerManagement15" namespace = "Root\\Microsoft\\SqlServer\\ComputerManagement15"
class_name = "SqlServiceAdvancedProperty" class_name = "SqlServiceAdvancedProperty"
properties = [ properties = [
"PropertyName", "PropertyName",
"ServiceName", "ServiceName",
"PropertyStrValue", "PropertyStrValue",
"SqlServiceType" "SqlServiceType"
] ]
filter = "ServiceName LIKE 'MSSQLSERVER' AND SqlServiceType = 1 AND (PropertyName LIKE 'FILEVERSION' OR PropertyName LIKE 'SKUNAME')" filter = "ServiceName LIKE 'MSSQLSERVER' AND SqlServiceType = 1 AND (PropertyName LIKE 'FILEVERSION' OR PropertyName LIKE 'SKUNAME')"
tag_properties = ["PropertyName","ServiceName","PropertyStrValue"] tag_properties = ["PropertyName","ServiceName","PropertyStrValue"]
``` ```
Example Output: Example Output:
```text ```text
win_wmi_SqlServiceAdvancedProperty,PropertyName=FILEVERSION,PropertyStrValue=2019.150.4178.1,ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000 win_wmi_SqlServiceAdvancedProperty,PropertyName=FILEVERSION,PropertyStrValue=2019.150.4178.1,ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000
win_wmi_SqlServiceAdvancedProperty,PropertyName=SKUNAME,PropertyStrValue=Developer\ Edition\ (64-bit),ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000 win_wmi_SqlServiceAdvancedProperty,PropertyName=SKUNAME,PropertyStrValue=Developer\ Edition\ (64-bit),ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000
``` ```

View File

@ -0,0 +1,158 @@
//go:build windows
package win_wmi
import (
"errors"
"fmt"
"runtime"
"strings"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal"
)
// Query struct
type Query struct {
Namespace string `toml:"namespace"`
ClassName string `toml:"class_name"`
Properties []string `toml:"properties"`
Filter string `toml:"filter"`
TagPropertiesInclude []string `toml:"tag_properties"`
tagFilter filter.Filter
query string
}
func (q *Query) prepare() error {
// Compile the filter
f, err := filter.Compile(q.TagPropertiesInclude)
if err != nil {
return fmt.Errorf("compiling tag-filter failed: %w", err)
}
q.tagFilter = f
// Construct the overall query from the given parts
wql := fmt.Sprintf("SELECT %s FROM %s", strings.Join(q.Properties, ", "), q.ClassName)
if len(q.Filter) > 0 {
wql += " WHERE " + q.Filter
}
q.query = wql
return nil
}
func (q *Query) execute(acc telegraf.Accumulator) error {
// The only way to run WMI queries in parallel while being thread-safe is to
// ensure the CoInitialize[Ex]() call is bound to its current OS thread.
// Otherwise, attempting to initialize and run parallel queries across
// goroutines will result in protected memory errors.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// init COM
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
var oleCode *ole.OleError
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != sFalse {
return err
}
}
defer ole.CoUninitialize()
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
if err != nil {
return err
}
if unknown == nil {
return errors.New("failed to create WbemScripting.SWbemLocator, maybe WMI is broken")
}
defer unknown.Release()
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
return fmt.Errorf("failed to QueryInterface: %w", err)
}
defer wmi.Release()
// service is a SWbemServices
serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", nil, q.Namespace)
if err != nil {
return fmt.Errorf("failed calling method ConnectServer: %w", err)
}
service := serviceRaw.ToIDispatch()
defer serviceRaw.Clear()
// result is a SWBemObjectSet
resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q.query)
if err != nil {
return fmt.Errorf("failed calling method ExecQuery for query %s: %w", q.query, err)
}
result := resultRaw.ToIDispatch()
defer resultRaw.Clear()
countRaw, err := oleutil.GetProperty(result, "Count")
if err != nil {
return fmt.Errorf("failed getting Count: %w", err)
}
count := countRaw.Val
defer countRaw.Clear()
for i := int64(0); i < count; i++ {
itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
if err != nil {
return fmt.Errorf("failed calling method ItemIndex: %w", err)
}
if err := q.extractProperties(acc, itemRaw); err != nil {
return err
}
}
return nil
}
func (q *Query) extractProperties(acc telegraf.Accumulator, itemRaw *ole.VARIANT) error {
tags, fields := map[string]string{}, map[string]interface{}{}
item := itemRaw.ToIDispatch()
defer item.Release()
for _, name := range q.Properties {
propertyRaw, err := oleutil.GetProperty(item, name)
if err != nil {
return fmt.Errorf("getting property %q failed: %w", name, err)
}
value := propertyRaw.Value()
propertyRaw.Clear()
if q.tagFilter != nil && q.tagFilter.Match(name) {
s, err := internal.ToString(value)
if err != nil {
return fmt.Errorf("converting property %q failed: %w", s, err)
}
tags[name] = s
continue
}
switch v := value.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
fields[name] = v
case string:
fields[name] = v
case bool:
fields[name] = v
case []byte:
fields[name] = string(v)
case fmt.Stringer:
fields[name] = v.String()
case nil:
fields[name] = nil
default:
return fmt.Errorf("property %q of type \"%T\" unsupported", name, v)
}
}
acc.AddFields(q.ClassName, fields, tags)
return nil
}

View File

@ -1,235 +1,54 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build windows //go:build windows
// +build windows
package win_wmi package win_wmi
import ( import (
_ "embed" _ "embed"
"errors"
"fmt" "fmt"
"runtime"
"strings"
"sync" "sync"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
// Query struct
type Query struct {
query string
Namespace string `toml:"namespace"`
ClassName string `toml:"class_name"`
Properties []string `toml:"properties"`
Filter string `toml:"filter"`
TagPropertiesInclude []string `toml:"tag_properties"`
tagFilter filter.Filter
}
// Wmi struct // Wmi struct
type Wmi struct { type Wmi struct {
Queries []Query `toml:"query"` Queries []Query `toml:"query"`
Log telegraf.Logger Log telegraf.Logger `toml:"-"`
} }
// S_FALSE is returned by CoInitializeEx if it was already called on this thread. // S_FALSE is returned by CoInitializeEx if it was already called on this thread.
const sFalse = 0x00000001 const sFalse = 0x00000001
func oleInt64(item *ole.IDispatch, prop string) (int64, error) {
v, err := oleutil.GetProperty(item, prop)
if err != nil {
return 0, err
}
defer v.Clear()
return v.Val, nil
}
// Init function // Init function
func (s *Wmi) Init() error { func (w *Wmi) Init() error {
return compileInputs(s) for i := range w.Queries {
q := &w.Queries[i]
if err := q.prepare(); err != nil {
return fmt.Errorf("preparing query %q failed: %w", q.ClassName, err)
}
}
return nil
} }
// SampleConfig function // SampleConfig function
func (s *Wmi) SampleConfig() string { func (*Wmi) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func compileInputs(s *Wmi) error {
buildWqlStatements(s)
return compileTagFilters(s)
}
func compileTagFilters(s *Wmi) error {
for i, q := range s.Queries {
var err error
s.Queries[i].tagFilter, err = compileTagFilter(q)
if err != nil {
return err
}
}
return nil
}
func compileTagFilter(q Query) (filter.Filter, error) {
tagFilter, err := filter.NewIncludeExcludeFilterDefaults(q.TagPropertiesInclude, nil, false, false)
if err != nil {
return nil, fmt.Errorf("creating tag filter failed: %w", err)
}
return tagFilter, nil
}
// build a WMI query from input configuration
func buildWqlStatements(s *Wmi) {
for i, q := range s.Queries {
wql := fmt.Sprintf("SELECT %s FROM %s", strings.Join(q.Properties, ", "), q.ClassName)
if len(q.Filter) > 0 {
wql = fmt.Sprintf("%s WHERE %s", wql, q.Filter)
}
s.Queries[i].query = wql
}
}
func (q *Query) doQuery(acc telegraf.Accumulator) error {
// The only way to run WMI queries in parallel while being thread-safe is to
// ensure the CoInitialize[Ex]() call is bound to its current OS thread.
// Otherwise, attempting to initialize and run parallel queries across
// goroutines will result in protected memory errors.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// init COM
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
var oleCode *ole.OleError
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != sFalse {
return err
}
}
defer ole.CoUninitialize()
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
if err != nil {
return err
}
if unknown == nil {
return errors.New("failed to create WbemScripting.SWbemLocator, maybe WMI is broken")
}
defer unknown.Release()
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
return fmt.Errorf("failed to QueryInterface: %w", err)
}
defer wmi.Release()
// service is a SWbemServices
serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", nil, q.Namespace)
if err != nil {
return fmt.Errorf("failed calling method ConnectServer: %w", err)
}
service := serviceRaw.ToIDispatch()
defer serviceRaw.Clear()
// result is a SWBemObjectSet
resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q.query)
if err != nil {
return fmt.Errorf("failed calling method ExecQuery for query %s: %w", q.query, err)
}
result := resultRaw.ToIDispatch()
defer resultRaw.Clear()
count, err := oleInt64(result, "Count")
if err != nil {
return fmt.Errorf("failed getting Count: %w", err)
}
for i := int64(0); i < count; i++ {
// item is a SWbemObject
itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
if err != nil {
return fmt.Errorf("failed calling method ItemIndex: %w", err)
}
err = q.extractProperties(itemRaw, acc)
if err != nil {
return err
}
}
return nil
}
func (q *Query) extractProperties(itemRaw *ole.VARIANT, acc telegraf.Accumulator) error {
tags, fields := map[string]string{}, map[string]interface{}{}
item := itemRaw.ToIDispatch()
defer item.Release()
for _, wmiProperty := range q.Properties {
propertyValue, err := getPropertyValue(item, wmiProperty)
if err != nil {
return err
}
if q.tagFilter.Match(wmiProperty) {
valStr, err := internal.ToString(propertyValue)
if err != nil {
return fmt.Errorf("converting property %q failed: %w", wmiProperty, err)
}
tags[wmiProperty] = valStr
} else {
var fieldValue interface{}
switch v := propertyValue.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
fieldValue = v
case string:
fieldValue = v
case bool:
fieldValue = v
case []byte:
fieldValue = string(v)
case fmt.Stringer:
fieldValue = v.String()
case nil:
fieldValue = nil
default:
return fmt.Errorf("property %q of type \"%T\" unsupported", wmiProperty, v)
}
fields[wmiProperty] = fieldValue
}
}
acc.AddFields(q.ClassName, fields, tags)
return nil
}
func getPropertyValue(item *ole.IDispatch, wmiProperty string) (interface{}, error) {
prop, err := oleutil.GetProperty(item, wmiProperty)
if err != nil {
return nil, fmt.Errorf("failed GetProperty: %w", err)
}
defer prop.Clear()
return prop.Value(), nil
}
// Gather function // Gather function
func (s *Wmi) Gather(acc telegraf.Accumulator) error { func (w *Wmi) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup var wg sync.WaitGroup
for _, query := range s.Queries { for _, query := range w.Queries {
wg.Add(1) wg.Add(1)
go func(q Query) { go func(q Query) {
defer wg.Done() defer wg.Done()
err := q.doQuery(acc) acc.AddError(q.execute(acc))
if err != nil {
acc.AddError(err)
}
}(query) }(query)
} }
wg.Wait() wg.Wait()

View File

@ -21,11 +21,9 @@ func (w *Wmi) Init() error {
w.Log.Warn("current platform is not supported") w.Log.Warn("current platform is not supported")
return nil return nil
} }
func (w *Wmi) SampleConfig() string { return sampleConfig } func (*Wmi) SampleConfig() string { return sampleConfig }
func (w *Wmi) Gather(_ telegraf.Accumulator) error { return nil } func (*Wmi) Gather(_ telegraf.Accumulator) error { return nil }
func init() { func init() {
inputs.Add("win_wmi", func() telegraf.Input { inputs.Add("win_wmi", func() telegraf.Input { return &Wmi{} })
return &Wmi{}
})
} }