chore(inputs.win_wmi): Cleanup and refactor code (#14965)
This commit is contained in:
parent
f7237170b9
commit
8183d4730c
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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{}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue