From 163a22826510db197d4858474125ae50e0f607bf Mon Sep 17 00:00:00 2001 From: "elastic-vault-github-plugin-prod[bot]" <150874479+elastic-vault-github-plugin-prod[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:14:08 +0100 Subject: [PATCH 1/3] [Release] Update docs for the 9.1.0 release (#42495) * docs: update docs * Update heartbeat-kubernetes.yaml --------- Co-authored-by: elasticmachine Co-authored-by: Julien Lind --- deploy/kubernetes/auditbeat-kubernetes.yaml | 2 +- deploy/kubernetes/filebeat-kubernetes.yaml | 2 +- deploy/kubernetes/heartbeat-kubernetes.yaml | 2 +- deploy/kubernetes/metricbeat-kubernetes.yaml | 2 +- libbeat/docs/version.asciidoc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/kubernetes/auditbeat-kubernetes.yaml b/deploy/kubernetes/auditbeat-kubernetes.yaml index 23c940ad4e0a..b29e8126d2d4 100644 --- a/deploy/kubernetes/auditbeat-kubernetes.yaml +++ b/deploy/kubernetes/auditbeat-kubernetes.yaml @@ -209,7 +209,7 @@ spec: dnsPolicy: ClusterFirstWithHostNet containers: - name: auditbeat - image: docker.elastic.co/beats/auditbeat-wolfi:9.0.0 + image: docker.elastic.co/beats/auditbeat-wolfi:9.1.0 args: [ "-c", "/etc/auditbeat.yml", "-e", diff --git a/deploy/kubernetes/filebeat-kubernetes.yaml b/deploy/kubernetes/filebeat-kubernetes.yaml index f028322c1aca..25cd4b913047 100644 --- a/deploy/kubernetes/filebeat-kubernetes.yaml +++ b/deploy/kubernetes/filebeat-kubernetes.yaml @@ -183,7 +183,7 @@ spec: dnsPolicy: ClusterFirstWithHostNet containers: - name: filebeat - image: docker.elastic.co/beats/filebeat-wolfi:9.0.0 + image: docker.elastic.co/beats/filebeat-wolfi:9.1.0 args: [ "-c", "/etc/filebeat.yml", "-e", diff --git a/deploy/kubernetes/heartbeat-kubernetes.yaml b/deploy/kubernetes/heartbeat-kubernetes.yaml index 280c243d305b..dfd7a0988c3a 100644 --- a/deploy/kubernetes/heartbeat-kubernetes.yaml +++ b/deploy/kubernetes/heartbeat-kubernetes.yaml @@ -171,7 +171,7 @@ spec: dnsPolicy: ClusterFirstWithHostNet containers: - name: heartbeat - image: docker.elastic.co/beats/heartbeat-wolfi:9.0.0 + image: docker.elastic.co/beats/heartbeat-wolfi:9.1.0 args: [ "-c", "/etc/heartbeat.yml", "-e", diff --git a/deploy/kubernetes/metricbeat-kubernetes.yaml b/deploy/kubernetes/metricbeat-kubernetes.yaml index 0afc6438b65e..5bc4f476b7b5 100644 --- a/deploy/kubernetes/metricbeat-kubernetes.yaml +++ b/deploy/kubernetes/metricbeat-kubernetes.yaml @@ -291,7 +291,7 @@ spec: dnsPolicy: ClusterFirstWithHostNet containers: - name: metricbeat - image: docker.elastic.co/beats/metricbeat-wolfi:9.0.0 + image: docker.elastic.co/beats/metricbeat-wolfi:9.1.0 args: [ "-c", "/etc/metricbeat.yml", "-e", diff --git a/libbeat/docs/version.asciidoc b/libbeat/docs/version.asciidoc index 41ee1fc8e231..d374efed893f 100644 --- a/libbeat/docs/version.asciidoc +++ b/libbeat/docs/version.asciidoc @@ -1,4 +1,4 @@ -:stack-version: 9.0.0 +:stack-version: 9.1.0 :doc-branch: main :go-version: 1.22.10 :release-state: unreleased From 35fbdcec594eec482bbb8e82458153256dd0bba8 Mon Sep 17 00:00:00 2001 From: Mirko Bez Date: Thu, 30 Jan 2025 16:24:39 +0100 Subject: [PATCH 2/3] [Metricbeat] Windows Module add wmi metricset (#42017) * Add stub for wmi module * Execute mage update * Add first draft of wmi metricset * Add first draft of wmi windows module * Add config unit test * Add unit test for the config class * Add license notice mage fmt * Add License to wmi/wmi.go * Update example * Use wrapping fromat verb for fmt.Errorf * Fix mispelled comment * Add first draft of the documentation * Add the config reference and config file * Bump microsfot/wmi to 0.25.1 * Make sure the wmi metricset is only used on windows * Run mage update * Add License for microsoft/wmi library in Notice.txt * Add Timeout configuration * Introduce the ExecuteGuardedQueryInstances to wait for at most a timeout for the result * Add invokation of CloseAllInstances() to make sure to free resources after need * Add license header to utils.go * Rename timeout to warning_threshold to conceive the message that the query is not actually cancelled * Refactor the ExecuteGuardedQueryInstances to use the context.WithTimeout * Add unit test for ExecuteGuardedQueryInstances Add an interface WmiQueryInterface to allow mocking the session object * Add parameter IncludeEmptyString. Create function to check skip conditions and add a test * Add namespace at the query level and add a structure to index queries on the namespace. Add logic to create a single connection per Namespace * Rename the config method to be more explicit * Add Primitives to deal with the type convrsion for strings * Add unit test for the conversion function * Add heuristic to determine if fetching the CIMType is needed * Add type conversion * Improve comments to explicitly state what are the config parameters used for * Add license header to wmi and utils test * Run mage fmt * Update the reference config after the final implementation * Remove the dummy field definition * Add sample data in the data.json file * Fix go.mod and change NOTICE.txt to reflect the fact that go-ole is now directly used in the WMI module * Make sure that the wmi tests run only on windows * Improve the error message to prepare a Troubleshooting Guide * Fix test to use right function * Add doc.go file * Add pragma to avoid compiling utils.go on platforms other than windows * Run make update BEATS=metricbeat * Use Metricset Logger instead of generic one in wmi.go * Make sure that we are using the Metricset logger everywhere. Address linting error caused by not checking the return of rawResult.Clear() * Run make updates BEATS=metricbeat * Get rid of duplicated rawResult.Clear() * Review: remove redundant multiplication by time.Second * Review: Fix grammar of error * Review: Get rid of the panic * Review: Remove redundant check * Review: Make the namespace settings easier to understand * Review: Honor the query-level namespace in the output document and move the field type caching at the query level rather than namespace level * Add a warning if a given query is not producing results * Fix golint warning * add results of 'make update' * Add changelog entry for the wmi metricset * Fix pull request number * Add an explicit variable for the converted field and double check if the cast is safe * Make warning easier to read * Add empty newline in the config.yml * Add codeowners entry for wmi * Add windows.yml.disabled * Modify codeowners at the dataset level * Fix test after renaming of the error message * Update metricbeat/docs/modules/windows.asciidoc Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> * Fix config reference to generate correct doc * Update xpack metricbeat.reference.yml --------- Co-authored-by: tommyers-elastic <106530686+tommyers-elastic@users.noreply.github.com> Co-authored-by: Ishleen Kaur <102962586+ishleenk17@users.noreply.github.com> Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- .github/CODEOWNERS | 1 + CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 93 ++++--- go.mod | 3 +- go.sum | 2 + metricbeat/docs/fields.asciidoc | 6 + metricbeat/docs/modules/windows.asciidoc | 28 ++ metricbeat/docs/modules/windows/wmi.asciidoc | 28 ++ metricbeat/docs/modules_list.asciidoc | 3 +- metricbeat/include/list_common.go | 1 + metricbeat/metricbeat.reference.yml | 24 ++ .../module/windows/_meta/config.reference.yml | 24 ++ metricbeat/module/windows/_meta/config.yml | 19 ++ metricbeat/module/windows/fields.go | 2 +- metricbeat/module/windows/wmi/_meta/data.json | 27 ++ .../module/windows/wmi/_meta/docs.asciidoc | 44 +++ .../module/windows/wmi/_meta/fields.yml | 6 + metricbeat/module/windows/wmi/config.go | 127 +++++++++ metricbeat/module/windows/wmi/config_test.go | 251 ++++++++++++++++++ metricbeat/module/windows/wmi/doc.go | 18 ++ metricbeat/module/windows/wmi/utils.go | 201 ++++++++++++++ metricbeat/module/windows/wmi/utils_test.go | 218 +++++++++++++++ metricbeat/module/windows/wmi/wmi.go | 220 +++++++++++++++ metricbeat/module/windows/wmi/wmi_test.go | 102 +++++++ metricbeat/modules.d/windows.yml.disabled | 19 ++ x-pack/metricbeat/metricbeat.reference.yml | 24 ++ 26 files changed, 1458 insertions(+), 34 deletions(-) create mode 100644 metricbeat/docs/modules/windows/wmi.asciidoc create mode 100644 metricbeat/module/windows/wmi/_meta/data.json create mode 100644 metricbeat/module/windows/wmi/_meta/docs.asciidoc create mode 100644 metricbeat/module/windows/wmi/_meta/fields.yml create mode 100644 metricbeat/module/windows/wmi/config.go create mode 100644 metricbeat/module/windows/wmi/config_test.go create mode 100644 metricbeat/module/windows/wmi/doc.go create mode 100644 metricbeat/module/windows/wmi/utils.go create mode 100644 metricbeat/module/windows/wmi/utils_test.go create mode 100644 metricbeat/module/windows/wmi/wmi.go create mode 100644 metricbeat/module/windows/wmi/wmi_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1a8fd0a00882..feccb80bf282 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -105,6 +105,7 @@ CHANGELOG* /metricbeat/module/redis @elastic/obs-infraobs-integrations /metricbeat/module/system/ @elastic/elastic-agent-data-plane /metricbeat/module/vsphere @elastic/obs-infraobs-integrations +/metricbeat/module/windows/wmi @elastic/obs-infraobs-integrations /metricbeat/module/zookeeper @elastic/obs-infraobs-integrations /packetbeat/ @elastic/sec-linux-platform /script/ @elastic/elastic-agent-data-plane diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 883174a20316..91e75266659d 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -460,6 +460,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] - Add support of additional `collstats` metrics in mongodb module. {pull}42171[42171] - Preserve queries for debugging when `merge_results: true` in SQL module {pull}42271[42271] - Collect more fields from ES node/stats metrics and only those that are necessary {pull}42421[42421] +- Add new metricset wmi for the windows module. {pull}42017[42017] *Metricbeat* - Add benchmark module {pull}41801[41801] diff --git a/NOTICE.txt b/NOTICE.txt index 9b42ca2af1f8..f1784c76dc6d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -16404,6 +16404,37 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +Dependency : github.com/go-ole/go-ole +Version: v1.2.6 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/go-ole/go-ole@v1.2.6/LICENSE: + +The MIT License (MIT) + +Copyright © 2013-2017 Yasuhiro Matsumoto, + +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. + + -------------------------------------------------------------------------------- Dependency : github.com/go-sql-driver/mysql Version: v1.6.0 @@ -20790,6 +20821,37 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +Dependency : github.com/microsoft/wmi +Version: v0.25.1 +Licence type (autodetected): MIT +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/microsoft/wmi@v0.25.1/LICENSE: + + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 + + -------------------------------------------------------------------------------- Dependency : github.com/miekg/dns Version: v1.1.61 @@ -44630,37 +44692,6 @@ 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. --------------------------------------------------------------------------------- -Dependency : github.com/go-ole/go-ole -Version: v1.2.6 -Licence type (autodetected): MIT --------------------------------------------------------------------------------- - -Contents of probable licence file $GOMODCACHE/github.com/go-ole/go-ole@v1.2.6/LICENSE: - -The MIT License (MIT) - -Copyright © 2013-2017 Yasuhiro Matsumoto, - -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. - - -------------------------------------------------------------------------------- Dependency : github.com/go-openapi/jsonpointer Version: v0.20.2 diff --git a/go.mod b/go.mod index f28a7fa8a6be..88c0fabc38da 100644 --- a/go.mod +++ b/go.mod @@ -189,6 +189,7 @@ require ( github.com/elastic/toutoumomoma v0.0.0-20240626215117-76e39db18dfb github.com/foxcpp/go-mockdns v0.0.0-20201212160233-ede2f9158d15 github.com/go-ldap/ldap/v3 v3.4.6 + github.com/go-ole/go-ole v1.2.6 github.com/gofrs/uuid/v5 v5.2.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/cel-go v0.19.0 @@ -202,6 +203,7 @@ require ( github.com/klauspost/compress v1.17.11 github.com/meraki/dashboard-api-go/v3 v3.0.9 github.com/microsoft/go-mssqldb v1.7.2 + github.com/microsoft/wmi v0.25.1 github.com/open-telemetry/opentelemetry-collector-contrib/exporter/elasticsearchexporter v0.114.0 github.com/otiai10/copy v1.12.0 github.com/pierrec/lz4/v4 v4.1.21 @@ -307,7 +309,6 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect diff --git a/go.sum b/go.sum index 7716ae78a52b..6bab262fcf18 100644 --- a/go.sum +++ b/go.sum @@ -726,6 +726,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/microsoft/wmi v0.25.1 h1:sQv9hCEHtW5K6yEVL78T6XGRMGxk4aTpcJwCiB5rLN0= +github.com/microsoft/wmi v0.25.1/go.mod h1:1zbdSF0A+5OwTUII5p3hN7/K6KF2m3o27pSG6Y51VU8= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 0e5a1d381a77..58ee16f2c838 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -69582,6 +69582,12 @@ format: duration -- +[float] +=== wmi + +wmi + + [[exported-fields-zookeeper]] == ZooKeeper fields diff --git a/metricbeat/docs/modules/windows.asciidoc b/metricbeat/docs/modules/windows.asciidoc index cf30e3e9675c..9a9552be72c0 100644 --- a/metricbeat/docs/modules/windows.asciidoc +++ b/metricbeat/docs/modules/windows.asciidoc @@ -52,6 +52,30 @@ metricbeat.modules: metricsets: ["service"] enabled: true period: 60s + +- module: windows + metricsets: ["wmi"] + period: 60s + wmi: + include_null: false # Exclude fields with null values from the output + include_queries: false # Do not include the query string in the output + include_empty_string: false # Exclude fields with empty string values from the output + warning_threshold: 30s # Maximum time to wait for a query result before logging a warning (defaults to period) + # Default WMI namespace for all queries (if not specified per query) + # Uncomment to override the default, which is "root\\cimv2". + # namespace: "root\\cimv2" + queries: + - class: Win32_OperatingSystem # FROM: Class to fetch + fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties + - FreePhysicalMemory + - FreeSpaceInPagingFiles + - FreeVirtualMemory + - LocalDateTime + - NumberOfUsers + where: "" # Optional WHERE clause to filter query results + # Override the WMI namespace for this specific query (optional). + # If set, this takes precedence over the default namespace above. + # namespace: "root\\cimv2" # Overrides the metric ---- [float] @@ -63,8 +87,12 @@ The following metricsets are available: * <> +* <> + include::windows/perfmon.asciidoc[] include::windows/service.asciidoc[] +include::windows/wmi.asciidoc[] + :edit_url!: diff --git a/metricbeat/docs/modules/windows/wmi.asciidoc b/metricbeat/docs/modules/windows/wmi.asciidoc new file mode 100644 index 000000000000..72ed437918f0 --- /dev/null +++ b/metricbeat/docs/modules/windows/wmi.asciidoc @@ -0,0 +1,28 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// +:edit_url: https://github.com/elastic/beats/edit/main/metricbeat/module/windows/wmi/_meta/docs.asciidoc + + +[[metricbeat-metricset-windows-wmi]] +=== Windows wmi metricset + +beta[] + +include::../../../module/windows/wmi/_meta/docs.asciidoc[] + + +:edit_url: + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/windows/wmi/_meta/data.json[] +---- +:edit_url!: \ No newline at end of file diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index ae81c25e5fc9..1acf29688dc7 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -330,8 +330,9 @@ This file is generated! See scripts/mage/docs_collector.go |<> beta[] |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.2+| .2+| |<> +.3+| .3+| |<> |<> +|<> beta[] |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | .3+| .3+| |<> |<> diff --git a/metricbeat/include/list_common.go b/metricbeat/include/list_common.go index 3cbb38284730..c8957a4443a0 100644 --- a/metricbeat/include/list_common.go +++ b/metricbeat/include/list_common.go @@ -191,6 +191,7 @@ import ( _ "github.com/elastic/beats/v7/metricbeat/module/windows" _ "github.com/elastic/beats/v7/metricbeat/module/windows/perfmon" _ "github.com/elastic/beats/v7/metricbeat/module/windows/service" + _ "github.com/elastic/beats/v7/metricbeat/module/windows/wmi" _ "github.com/elastic/beats/v7/metricbeat/module/zookeeper" _ "github.com/elastic/beats/v7/metricbeat/module/zookeeper/connection" _ "github.com/elastic/beats/v7/metricbeat/module/zookeeper/mntr" diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 767d947670e4..0d7ceb34057c 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -1091,6 +1091,30 @@ metricbeat.modules: enabled: true period: 60s +- module: windows + metricsets: ["wmi"] + period: 60s + wmi: + include_null: false # Exclude fields with null values from the output + include_queries: false # Do not include the query string in the output + include_empty_string: false # Exclude fields with empty string values from the output + warning_threshold: 30s # Maximum time to wait for a query result before logging a warning (defaults to period) + # Default WMI namespace for all queries (if not specified per query) + # Uncomment to override the default, which is "root\\cimv2". + # namespace: "root\\cimv2" + queries: + - class: Win32_OperatingSystem # FROM: Class to fetch + fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties + - FreePhysicalMemory + - FreeSpaceInPagingFiles + - FreeVirtualMemory + - LocalDateTime + - NumberOfUsers + where: "" # Optional WHERE clause to filter query results + # Override the WMI namespace for this specific query (optional). + # If set, this takes precedence over the default namespace above. + # namespace: "root\\cimv2" # Overrides the metric + #------------------------------ ZooKeeper Module ------------------------------ - module: zookeeper enabled: true diff --git a/metricbeat/module/windows/_meta/config.reference.yml b/metricbeat/module/windows/_meta/config.reference.yml index ecb94de595ed..d033f457c48f 100644 --- a/metricbeat/module/windows/_meta/config.reference.yml +++ b/metricbeat/module/windows/_meta/config.reference.yml @@ -17,3 +17,27 @@ metricsets: ["service"] enabled: true period: 60s + +- module: windows + metricsets: ["wmi"] + period: 60s + wmi: + include_null: false # Exclude fields with null values from the output + include_queries: false # Do not include the query string in the output + include_empty_string: false # Exclude fields with empty string values from the output + warning_threshold: 30s # Maximum time to wait for a query result before logging a warning (defaults to period) + # Default WMI namespace for all queries (if not specified per query) + # Uncomment to override the default, which is "root\\cimv2". + # namespace: "root\\cimv2" + queries: + - class: Win32_OperatingSystem # FROM: Class to fetch + fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties + - FreePhysicalMemory + - FreeSpaceInPagingFiles + - FreeVirtualMemory + - LocalDateTime + - NumberOfUsers + where: "" # Optional WHERE clause to filter query results + # Override the WMI namespace for this specific query (optional). + # If set, this takes precedence over the default namespace above. + # namespace: "root\\cimv2" # Overrides the metric diff --git a/metricbeat/module/windows/_meta/config.yml b/metricbeat/module/windows/_meta/config.yml index 4618435daf3a..45a9a709d725 100644 --- a/metricbeat/module/windows/_meta/config.yml +++ b/metricbeat/module/windows/_meta/config.yml @@ -15,3 +15,22 @@ # field: cpu_usage # format: "float" # - name: "Thread Count" + +# - module: windows +# metricsets: +# - wmi +# period: 60s +# wmi: +# warning_threshold: 30s +# # namespace: "root\\cimv2" +# queries: +# - class: Win32_OperatingSystem # FROM: Class to fetch +# fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties +# - FreePhysicalMemory +# - FreeSpaceInPagingFiles +# - FreeVirtualMemory +# - LocalDateTime +# - NumberOfUsers +# where: "" # Optional WHERE clause to filter query results +# # Namespace for this specific query. Uncomment to override the default namespace. +# # namespace: "root\\cimv2" diff --git a/metricbeat/module/windows/fields.go b/metricbeat/module/windows/fields.go index 74d55663717a..e8d35afb3712 100644 --- a/metricbeat/module/windows/fields.go +++ b/metricbeat/module/windows/fields.go @@ -32,5 +32,5 @@ func init() { // AssetWindows returns asset data. // This is the base64 encoded zlib format compressed contents of module/windows. func AssetWindows() string { - return "eJysVl1v2zYUffevuMhLgSIx1vZpfiiQxcvmYUmLJoUxIIDFkFfWXShS5b20Y2A/fqAkO5Y/YscoHwKFpM49555D0RfwhIsBzMkZP+cegJBYHMDZuJk56wEYZB2oEvJuAJ97AAA33kSLkPsA49WrXPggE+1dTtMB5Moy9gACWlSMA5iqHkBOaA0PapALcKrE9eJpyKJKm4OPVTuzo34XaB2swpCX3q3mdwGm0aW1HDtLNWMbeJPCCwlyLMpp7AA0PJ5wMffBdFY6Vf/rLAGMWiyYKRuxv6NaiRJIc/99//2Ogv7xX9TSWWimJs16br3auzwpVVWRm7Z7z96f7Sf+eYP4TU2roc0QUGJwaF4EbFnHGGbUadpu616pmrUYGWjvRJFjkAKBRUnk9bgui3H/YCa2bV4z2myI3m8yAD6rskqHqxh/uv3rWn+4rDZ2vNpPgEuIjn5EhNGw1lJLa3T0YSRADAoKxQX4vF4slS7I4TuGP76PhqCcSdNbuC1GrWmHP+uC099TJI9R80y/Te79i7xjqBniyqrF5GSKbTJ+n6ETuPLWohYf3s65JVLTWjqxtOl1CSwqNAfvDQKO6WLCjVUNs8mo3lB5Znq0uDytKiBkl1F8qYR0dr6Fmv3mvWTnkA2J1aNFk55vlIvKZud10LK7BQuW2VGST/Xs9h4uv9//+eXb6P6fh7+9VvZu6yNyRI8utfbRSeNYdAYDzAvSBahVAEN0fEBKpaQ4WcnV4GE8uh1+Gd89cN24Tx8feKYLz9LHZ4SLJ1jXBxdv/HZcR2sX8CMqSzmhqcmC+DoKOVkEKZQAJTIlOuH1jGzbT07baMhNQYVprF847LP85FQrLVHZBvn4VF95J+QiuemuWH9Vkeul5rHJ9bfoXDt5l+K6evb15bgKfPofzaHE4zOlX0nmJ3bj2oeX8qurDaQgTpdCagyG4AOkso3Tq1xj5YPwFuS8QNcczuSyeOBWbWotMczJWnjEGnuKDtNVv3G3bmGucYjOIndCBlXwMzLJpuXUBVeoKSe99uahM7jnRrbeTfcdvQ+//PrxhH4vU7G734rZa1KSjlrwOon9OhoeYB8roRL75aYdezXkPpRKBmBiUInsxjK5Kspkuakka4lRe2c2Cxx/E7/jliW05qABch3sfu//AAAA//8t/Vii" + return "eJysVsFu4zYQvfsrBrksECRGd/dUHxZI46Z10WQXmyyMoiksmhpZ01CkljO0Y6AfX1CSHcuyY8dYHQKFpN68N+/R5CU84XIAC7KpW3APQEgMDuBsXI+c9QBSZO2pFHJ2AJ96AAC3Lg0GIXMexutPOXdeJtrZjGYDyJRh7AF4NKgYBzBTPYCM0KQ8qEAuwaoCN4vHR5ZlXOxdKJuRHfXbQJtgJfqscHY9vgswPm1aq2dnqfrpAm9TeCFBlkVZjS2AmscTLhfOp62ZVtX/WlMAowYL5soE7O+oVqB40tw/75/vKOim/6KW1kQ9NKnnM+PU3ulJocqS7KxZe3Z+tp/4py3itxWtmjaDRwneYvoioGMdo59Tq2m7rXulatJgJKCdFUWWQXIEFiWBN+O6Ksb9g5no2rxhdLoler/JAPisijJurnz88e6PG/3+qtxa8Wo/Aa4gWPoeEEbDSkslrdbRh5EAMSjIFefgsmqyUDoni+8Yfvs2GoKyaRzu4DYYlaYd/mwKjn9PkTxGzXP9NrkPL/KOoZYSl0YtJydTbJLx6xytwLUzBrU4/3bODZGK1sqJlU2vS2BRvt54bxBwTBcjbigrmG1G1YLSMdPU4Gq3Ko+QXAVxhRLSyUUHNfnFOUkuIBkSq6nBNL7fKhuUSS6qoCX3SxYskqMkn+rZ3QNcfXv4/fPX0cNfj386rcx950fkiB5dae2CldqxYFP0sMhJ56DWAfTB8gEppZL8ZCXXg8fx6G74eXz/yFXjPn545LnOHUsfnxEun2BTH1y+8bfjJhizhO9BGcoI04osiKuikJFBkFwJUCRToBXezEjXfrLahJTsDJSfheqDwz7LD0610hKUqZGPT/W1s0I2kJ3tivUXFbiaql/rXH8N1jaD9zGu63dXHY7rwMf/MT2UeHymeEtKf2A3bpx/Kb8+2kBy4ngoxMag985DLFs7vc41ls4LdyAXOdp6c0aXxQE3amNriWFBxsAUK+wZWoxH/dbZ2sHc4BCsQW6FDErv5pRGm1ZDl1yipoz0xpeH9uCeE9k4O9u39d7/9POHE/q9SsXufitmp0lJ3Gre6Sj2y2h4gH0ohQrsF9t27NWQOV8oGUAavIpkt6bJlkEmq0UFGUOM2tl0u8DxJ/E7blhCYw6mQLaF3e/c7BYFHX8hn6IceSVvwzb3NPj7n97/AQAA//8jYncl" } diff --git a/metricbeat/module/windows/wmi/_meta/data.json b/metricbeat/module/windows/wmi/_meta/data.json new file mode 100644 index 000000000000..f8f6205791d3 --- /dev/null +++ b/metricbeat/module/windows/wmi/_meta/data.json @@ -0,0 +1,27 @@ +{ + "@timestamp": "2024-12-12T15:46:39.622Z", + "event": { + "dataset": "windows.wmi", + "duration": 58982500, + "module": "windows" + }, + "metricset": { + "name": "wmi", + "period": 10000 + }, + "service": { + "type": "windows" + }, + "windows": { + "wmi": { + "FreePhysicalMemory": 7537796, + "FreeSpaceInPagingFiles": 2257908, + "FreeVirtualMemory": 9694064, + "LocalDateTime": "2024-12-12T15:46:39.62Z", + "NumberOfUsers": 1, + "class": "Win32_OperatingSystem", + "host": "localhost", + "namespace": "root\\cimv2" + } + } +} \ No newline at end of file diff --git a/metricbeat/module/windows/wmi/_meta/docs.asciidoc b/metricbeat/module/windows/wmi/_meta/docs.asciidoc new file mode 100644 index 000000000000..94dc73b67f0b --- /dev/null +++ b/metricbeat/module/windows/wmi/_meta/docs.asciidoc @@ -0,0 +1,44 @@ +The `wmi` metricset of the Windows module reads metrics via Windows Management Instrumentation link:https://learn.microsoft.com/en-us/windows/win32/wmisdk/about-wmi[(WMI)], a core management technology in the Windows Operating system. + +By leveraging WMI Query Language (WQL), this metricset allows you to extract detailed +system information and metrics to monitor the health and performance of Windows +Systems. + +This metricset leverages the link:https://github.com/microsoft/wmi[Microsoft WMI], library a +convenient wrapper around the link:https://github.com/go-ole[GO-OLE] library which allows to +invoke the WMI Api. + +=== WMI Query Language (WQL) Support + +This metricset supports the execution of link:https://learn.microsoft.com/en-us/windows/win32/wmisdk/wql-sql-for-wmi[WQL] queries, a SQL-like query language for retrieving information from WMI namespaces. + +As of now, we only support and execute queries with `SELECT`, `FROM` and `WHERE` clauses. + +=== Configuration + +[source,yaml] +---- +- module: windows + metricsets: ["wmi"] + period: 60s + namespace: "root\\cimv2" # Namespace + queries: + - class: Win32_OperatingSystem + fields: + - FreePhysicalMemory + - FreeSpaceInPaginFiles + - NumberOfUsers + # Where Clasue + where: "" +---- + +[float] +=== Compatibility + +This module has been tested on the following platform: + +- Operating System: Microsoft Windows Server 2019 Datacenter +- Architecture: x86 + +Other Windows versions and architectures may also work but have not been explicitly tested. + diff --git a/metricbeat/module/windows/wmi/_meta/fields.yml b/metricbeat/module/windows/wmi/_meta/fields.yml new file mode 100644 index 000000000000..5b2ce49c0304 --- /dev/null +++ b/metricbeat/module/windows/wmi/_meta/fields.yml @@ -0,0 +1,6 @@ +- name: wmi + type: group + release: beta + description: > + wmi + fields: [] \ No newline at end of file diff --git a/metricbeat/module/windows/wmi/config.go b/metricbeat/module/windows/wmi/config.go new file mode 100644 index 000000000000..ad0e4d02df9a --- /dev/null +++ b/metricbeat/module/windows/wmi/config.go @@ -0,0 +1,127 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Config is put into a different package to prevent cyclic imports in case +// it is needed in several locations + +//go:build windows + +package wmi + +import ( + "fmt" + "strings" + "time" + + wmiquery "github.com/microsoft/wmi/pkg/base/query" +) + +type Config struct { + IncludeQueries bool `config:"wmi.include_queries"` // Determines if the query string should be included in the output document + IncludeNull bool `config:"wmi.include_null"` // Specifies whether to include fields with nil values in the final document + IncludeEmptyString bool `config:"wmi.include_empty_string"` // Specifies whether to include fields with empty string values in the final document + Host string `config:"wmi.host"` // Hostname or IP address of the remote WMI server + User string `config:"wmi.username"` // Username for authentication on the remote WMI server + Password string `config:"wmi.password"` // Password for authentication on the remote WMI server + Namespace string `config:"wmi.namespace"` // Default WMI namespace for executing queries, used if not overridden by individual query configurations + Queries []QueryConfig `config:"wmi.queries"` // List of WMI query configurations + WarningThreshold time.Duration `config:"wmi.warning_threshold"` // Maximum duration to wait for query results before logging a warning. The query will continue running in WMI but will no longer be awaited + NamespaceQueryIndex map[string][]QueryConfig // Internal structure indexing queries by namespace to reduce the number of WMI connections required per execution +} + +type QueryConfig struct { + QueryStr string // The compiled query string generated internally (not user-configurable) + Class string `config:"class"` // WMI class to query (used in the FROM clause) + Fields []string `config:"fields"` // List of properties to retrieve (used in the SELECT clause). If omitted, all properties of the class are fetched + Where string `config:"where"` // Custom WHERE clause to filter query results. The provided string is used directly in the query + Namespace string `config:"namespace"` // WMI namespace for the query. This takes precedence over the globally configured namespace +} + +func NewDefaultConfig() Config { + return Config{ + IncludeQueries: false, + IncludeNull: false, + Host: "localhost", + Namespace: WMIDefaultNamespace, + } +} + +func (c *Config) ValidateConnectionParameters() error { + if c.User != "" && c.Password == "" { + return fmt.Errorf("if user is set, password should be set") + } else if c.User == "" && c.Password != "" { + return fmt.Errorf("if password is set, user should be set") + } + return nil +} + +func (qc *QueryConfig) compileQuery() { + // Let us normalize the case where the array is ['*'] + // To the Empty Array + if len(qc.Fields) == 1 && qc.Fields[0] == "*" { + qc.Fields = []string{} + } + + query := wmiquery.NewWmiQueryWithSelectList(qc.Class, qc.Fields, []string{}...) + queryStr := query.String() + // Concatenating the where clause manually, because the library supports only a subset of where clauses + // while we want to leverage all filtering capabilities + if qc.Where != "" { + queryStr += " WHERE " + qc.Where + } + qc.QueryStr = queryStr +} + +func (qc *QueryConfig) applyDefaultNamespace(defaultNamespace string) { + if qc.Namespace == "" { + qc.Namespace = defaultNamespace + } +} + +func (c *Config) CompileQueries() error { + if len(c.Queries) == 0 { + return fmt.Errorf("at least one query is needed") + } + + for i := range c.Queries { + c.Queries[i].compileQuery() + } + return nil +} + +func (c *Config) ApplyDefaultNamespaceToQueries(defaultNamespace string) error { + for i := range c.Queries { + c.Queries[i].applyDefaultNamespace(defaultNamespace) + } + + return nil +} + +func (c *Config) BuildNamespaceQueryIndex() { + c.NamespaceQueryIndex = make(map[string][]QueryConfig) + for _, q := range c.Queries { + // WMI namespaces are case-insensitive. We are building a case-insensitive map + // to ensure that different variations of the namespace (e.g., "root\\cimv2" and "ROOT\\CIMV2") + // are treated as the same and grouped together. + namespace := strings.ToLower(q.Namespace) + _, ok := c.NamespaceQueryIndex[namespace] + if !ok { + c.NamespaceQueryIndex[namespace] = []QueryConfig{} + } + c.NamespaceQueryIndex[namespace] = append(c.NamespaceQueryIndex[namespace], q) + } +} diff --git a/metricbeat/module/windows/wmi/config_test.go b/metricbeat/module/windows/wmi/config_test.go new file mode 100644 index 000000000000..443e05e5e35e --- /dev/null +++ b/metricbeat/module/windows/wmi/config_test.go @@ -0,0 +1,251 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build windows + +package wmi + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestNewDefaultConfig verifies the default values for the Config struct. +func TestNewDefaultConfig(t *testing.T) { + cfg := NewDefaultConfig() + + assert.False(t, cfg.IncludeQueries, "IncludeQueries should default to false") + assert.False(t, cfg.IncludeNull, "IncludeNull should default to false") + assert.Equal(t, WMIDefaultNamespace, cfg.Namespace, "Namespace should default to WMIDefaultNamespace") + assert.Empty(t, cfg.Queries, "Queries should default to an empty slice") +} + +// TestValidateConnectionParameters checks the validation logic for user and password. +func TestValidateConnectionParameters(t *testing.T) { + tests := []struct { + name string + config Config + expectedError string + }{ + { + name: "Valid user and password", + config: Config{User: "admin", Password: "password"}, + expectedError: "", + }, + { + name: "User without password", + config: Config{User: "admin"}, + expectedError: "if user is set, password should be set", + }, + { + name: "Password without user", + config: Config{Password: "password"}, + expectedError: "if password is set, user should be set", + }, + { + name: "No user and no password", + config: Config{}, + expectedError: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.ValidateConnectionParameters() + if tt.expectedError == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.expectedError) + } + }) + } +} + +// TestCompileQueries ensures queries are properly compiled. +func TestCompileQueries(t *testing.T) { + tests := []struct { + name string + config Config + expectedError string + }{ + { + name: "Valid queries", + config: Config{ + Queries: []QueryConfig{ + { + Class: "Win32_Process", + Fields: []string{"Name", "ID"}, + Where: "Name LIKE 'chrome%'", + }, + }, + }, + expectedError: "", + }, + { + name: "No queries defined", + config: Config{}, + expectedError: "at least one query is needed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.CompileQueries() + if tt.expectedError == "" { + assert.NoError(t, err) + assert.NotEmpty(t, tt.config.Queries[0].QueryStr, "QueryStr should be compiled and not empty") + } else { + assert.EqualError(t, err, tt.expectedError) + } + }) + } +} + +// TestQueryCompile ensures individual query compilation works correctly. +func TestQueryCompile(t *testing.T) { + tests := []struct { + name string + queryConfig QueryConfig + expectedQuery string + expectedFieldsLength int + }{ + { + name: "Simple query with WHERE clause", + queryConfig: QueryConfig{ + Class: "Win32_Process", + Fields: []string{"Name", "ProcessId"}, + Where: "Name = 'notepad.exe'", + }, + expectedQuery: "SELECT Name,ProcessId FROM Win32_Process WHERE Name = 'notepad.exe'", + expectedFieldsLength: 2, + }, + { + name: "Query with multiple fields and no WHERE clause", + queryConfig: QueryConfig{ + Class: "Win32_Service", + Fields: []string{"Name", "State", "StartMode"}, + Where: "", + }, + expectedQuery: "SELECT Name,State,StartMode FROM Win32_Service", + expectedFieldsLength: 3, + }, + { + name: "Query with wildcard (*) for fields", + queryConfig: QueryConfig{ + Class: "Win32_ComputerSystem", + Fields: []string{}, + Where: "Manufacturer = 'Dell'", + }, + expectedQuery: "SELECT * FROM Win32_ComputerSystem WHERE Manufacturer = 'Dell'", + expectedFieldsLength: 0, + }, + { + name: "Query with wildcard (*) and no WHERE clause", + queryConfig: QueryConfig{ + Class: "Win32_BIOS", + Fields: []string{"*"}, + Where: "", + }, + expectedQuery: "SELECT * FROM Win32_BIOS", + expectedFieldsLength: 0, // The normalization process make sure that ['*'] and [] are the same case + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.queryConfig.compileQuery() + assert.Equal(t, tt.expectedQuery, tt.queryConfig.QueryStr, "QueryStr should match the expected query string") + assert.Equal(t, tt.expectedFieldsLength, len(tt.queryConfig.Fields)) + }) + } +} + +func TestBuildNamespaceQueryIndex(t *testing.T) { + + defaultNamespace := "root\\cimv2" + upperCaseDefaultNamespace := "ROOT\\CIMV2" + + tests := []struct { + name string + queries []QueryConfig + expectedIndex map[string][]QueryConfig + description string + }{ + { + name: "Single query, single namespace", + queries: []QueryConfig{ + {Namespace: defaultNamespace}, + }, + expectedIndex: map[string][]QueryConfig{ + WMIDefaultNamespace: { + {Namespace: defaultNamespace}, + }, + }, + description: "Should build an index with a single query in the 'default' namespace", + }, + { + name: "Multiple queries, same namespace, different spells", + queries: []QueryConfig{ + {Namespace: defaultNamespace}, + {Namespace: upperCaseDefaultNamespace}, + }, + expectedIndex: map[string][]QueryConfig{ + defaultNamespace: { + {Namespace: defaultNamespace}, + {Namespace: upperCaseDefaultNamespace}, + }, + }, + description: "Should correctly handle multiple queries in the same namespace", + }, + { + name: "Multiple queries, different namespaces", + queries: []QueryConfig{ + {Namespace: "default"}, + {Namespace: "custom"}, + }, + expectedIndex: map[string][]QueryConfig{ + "default": { + {Namespace: "default"}, + }, + "custom": { + {Namespace: "custom"}, + }, + }, + description: "Should correctly build separate indices for different namespaces", + }, + { + name: "Empty queries", + queries: []QueryConfig{}, + expectedIndex: map[string][]QueryConfig{}, + description: "Should return an empty index when no queries are provided", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the config and assign queries + config := &Config{Queries: tt.queries} + + // Build the namespace index + config.BuildNamespaceQueryIndex() + + // Assert that the namespace index matches the expected result + assert.Equal(t, tt.expectedIndex, config.NamespaceQueryIndex, tt.description) + }) + } +} diff --git a/metricbeat/module/windows/wmi/doc.go b/metricbeat/module/windows/wmi/doc.go new file mode 100644 index 000000000000..6077fac1cb56 --- /dev/null +++ b/metricbeat/module/windows/wmi/doc.go @@ -0,0 +1,18 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package wmi diff --git a/metricbeat/module/windows/wmi/utils.go b/metricbeat/module/windows/wmi/utils.go new file mode 100644 index 000000000000..e43f59567b67 --- /dev/null +++ b/metricbeat/module/windows/wmi/utils.go @@ -0,0 +1,201 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build windows + +package wmi + +import ( + "context" + "fmt" + "strconv" + "time" + "unicode" + + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" + base "github.com/microsoft/wmi/go/wmi" + wmi "github.com/microsoft/wmi/pkg/wmiinstance" + + "github.com/elastic/elastic-agent-libs/logp" +) + +// Utilities related to Type conversion + +// WmiStringConversionFunction defines a function type for converting string values +// into other data types, such as integers or timestamps. +type WmiStringConversionFunction func(string) (interface{}, error) + +func ConvertUint64(v string) (interface{}, error) { + return strconv.ParseUint(v, 10, 64) +} + +func ConvertSint64(v string) (interface{}, error) { + return strconv.ParseInt(v, 10, 64) +} + +func ConvertDatetime(v string) (interface{}, error) { + layout := "20060102150405.999999-0700" + return time.Parse(layout, v+"0") +} + +func ConvertString(v string) (interface{}, error) { + return v, nil +} + +// Function that determines if a given value requires additional conversion +// This holds true for strings that encode uint64, sint64 and datetime format +func RequiresExtraConversion(fieldValue interface{}) bool { + stringValue, isString := fieldValue.(string) + if !isString { + return false + } + isEmptyString := len(stringValue) == 0 + + // Heuristic to avoid fetching the raw properties for every string property + // If the string is empty, no need to convert the string + // If the string does not end with a digit, it's not an uint64, sint64, datetime + return !isEmptyString && unicode.IsDigit(rune(stringValue[len(stringValue)-1])) +} + +// Given a Property it returns its CIM Type Qualifier +// https://learn.microsoft.com/en-us/windows/win32/wmisdk/cimtype-qualifier +func getPropertyType(property *ole.IDispatch) (base.WmiType, error) { + rawType := oleutil.MustGetProperty(property, "CIMType") + + value, err := wmi.GetVariantValue(rawType) + if err != nil { + return base.WmiType(0), err + } + + return base.WmiType(value.(int32)), nil +} + +// Returns the "raw" SWbemProperty containing type information for a given field. +// +// The microsoft/wmi library does not have a function that given an instance and a property name +// returns the wmi.wmiProperty object. This function mimics the behavior of the `GetSystemProperty` +// method in the wmi.wmiInstance struct and applies it on the Properties_ field +// https://github.com/microsoft/wmi/blob/v0.25.2/pkg/wmiinstance/WmiInstance.go#L87 +// +// Note: We are not instantiating a wmi.wmiProperty because of this issue +// https://github.com/microsoft/wmi/issues/150 +// Once this issue is resolved, we can instantiate a wmi.WmiProperty and eliminate +// the need for the "getPropertyType" function. +func getProperty(instance *wmi.WmiInstance, propertyName string, logger *logp.Logger) (*ole.IDispatch, error) { + // Documentation: https://learn.microsoft.com/en-us/windows/win32/wmisdk/swbemobject-properties- + rawResult, err := oleutil.GetProperty(instance.GetIDispatch(), "Properties_") + if err != nil { + return nil, err + } + + // SWbemObjectEx.Properties_ returns + // an SWbemPropertySet object that contains the collection + // of properties for the c class + sWbemObjectExAsIDispatch := rawResult.ToIDispatch() + defer func() { + if cerr := rawResult.Clear(); cerr != nil { + logger.Debugf("failed to release connection: %w", err) + } + }() + + // Get the property + sWbemProperty, err := oleutil.CallMethod(sWbemObjectExAsIDispatch, "Item", propertyName) + if err != nil { + return nil, err + } + + return sWbemProperty.ToIDispatch(), nil +} + +// Given an instance and a property Name, it returns the appropriate conversion function +func GetConvertFunction(instance *wmi.WmiInstance, propertyName string, logger *logp.Logger) (WmiStringConversionFunction, error) { + rawProperty, err := getProperty(instance, propertyName, logger) + if err != nil { + return nil, err + } + propType, err := getPropertyType(rawProperty) + if err != nil { + return nil, fmt.Errorf("could not fetch CIMType for property '%s' with error %w", propertyName, err) + } + + var f WmiStringConversionFunction + + switch propType { + case base.WbemCimtypeDatetime: + f = ConvertDatetime + case base.WbemCimtypeUint64: + f = ConvertUint64 + case base.WbemCimtypeSint64: + f = ConvertSint64 + default: // For all other types we return the identity function + f = ConvertString + } + return f, err +} + +// Utilities related to Warning Threshold + +// Define an interface to allow unit-testing long-running queries +// *wmi.wmiSession is an implementation of this interface +type WmiQueryInterface interface { + QueryInstances(query string) ([]*wmi.WmiInstance, error) +} + +// Wrapper of the session.QueryInstances function that execute a query for at most a timeout +// after which we stop actively waiting. +// Note that the underlying query will continue to run, until the query completes or the WMI Arbitrator stops the query +// https://learn.microsoft.com/en-us/troubleshoot/windows-server/system-management-components/new-wmi-arbitrator-behavior-in-windows-server +func ExecuteGuardedQueryInstances(session WmiQueryInterface, query string, timeout time.Duration, logger *logp.Logger) ([]*wmi.WmiInstance, error) { + var rows []*wmi.WmiInstance + var err error + done := make(chan error) + timedout := false + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + go func() { + start_time := time.Now() + rows, err = session.QueryInstances(query) + if !timedout { + done <- err + } else { + timeSince := time.Since(start_time) + baseMessage := fmt.Sprintf("The query '%s' that exceeded the warning threshold terminated after %s", query, timeSince) + var tailMessage string + // We eventually fetched the documents, let us free them + if err == nil { + tailMessage = "successfully. The result will be ignored" + wmi.CloseAllInstances(rows) + } else { + tailMessage = fmt.Sprintf("with an error %v", err) + } + logger.Warn("%s %s", baseMessage, tailMessage) + } + }() + + select { + case <-ctx.Done(): + err = fmt.Errorf("the execution of the query '%s' exceeded the warning threshold of %s", query, timeout) + timedout = true + close(done) + case <-done: + // Query completed in time either successfully or with an error + } + return rows, err +} diff --git a/metricbeat/module/windows/wmi/utils_test.go b/metricbeat/module/windows/wmi/utils_test.go new file mode 100644 index 000000000000..54f66e4d90a3 --- /dev/null +++ b/metricbeat/module/windows/wmi/utils_test.go @@ -0,0 +1,218 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build windows + +package wmi + +import ( + "fmt" + "testing" + "time" + + wmi "github.com/microsoft/wmi/pkg/wmiinstance" + "github.com/stretchr/testify/assert" + + "github.com/elastic/elastic-agent-libs/logp" +) + +type MockWmiSession struct { +} + +const MockTimeout = time.Second * 5 + +// Mock Implementation of QueryInstances function +// This simulate a long-running query +func (c *MockWmiSession) QueryInstances(queryExpression string) ([]*wmi.WmiInstance, error) { + time.Sleep(MockTimeout) + return []*wmi.WmiInstance{}, nil +} + +func TestExecuteGuardedQueryInstances(t *testing.T) { + mockSession := new(MockWmiSession) + query := "SELECT * FROM Win32_OpeartingSystem" + timeout := 200 * time.Millisecond + + startTime := time.Now() + expectedError := fmt.Errorf("the execution of the query '%s' exceeded the warning threshold of %s", query, timeout) + _, err := ExecuteGuardedQueryInstances(mockSession, query, timeout, logp.NewLogger("wmi")) + // Make sure the return time is less than the MockTimeout + assert.Less(t, time.Since(startTime), MockTimeout, "The return time should be less than the sleep time") + // Make sure the error returned is the expected one + assert.Equal(t, err, expectedError, "Expected the returned error to match the expected error") +} + +func Test_RequiresExtraConversion(t *testing.T) { + tests := []struct { + name string + fieldValue interface{} + expected bool + description string + }{ + { + name: "Valid numeric string - ends with a digit", + fieldValue: "12345", + expected: true, + description: "Should require conversion as the string ends with a digit", + }, + { + name: "Empty string", + fieldValue: "", + expected: false, + description: "Should not require conversion as the string is empty", + }, + { + name: "Non-numeric string - no digits", + fieldValue: "abcdef", + expected: false, + description: "Should not require conversion as the string does not end with a digit", + }, + { + name: "Mixed string - ends with a digit. Let us fetch the type", + fieldValue: "abc123", + expected: true, + description: "Should require conversion as the string ends with a digit", + }, + { + name: "String ending with a non-digit", + fieldValue: "123abc", + expected: false, + description: "Should not require conversion as the string ends with a non-digit", + }, + { + name: "Nil input", + fieldValue: nil, + expected: false, + description: "Should not require conversion as the input is nil", + }, + { + name: "Non-string input", + fieldValue: 12345, + expected: false, + description: "Should not require conversion as the input is not a string", + }, + { + name: "Datetime input - requires a conversion", + fieldValue: "20240925192747.000000+000", + expected: true, + description: "Should not require conversion as the input is not a string", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := RequiresExtraConversion(tt.fieldValue) + assert.Equal(t, tt.expected, result, tt.description) + }) + } +} + +func Test_ConversionFunctions(t *testing.T) { + tests := []struct { + name string + conversion WmiStringConversionFunction + input string + expected interface{} + expectErr bool + description string + }{ + // Test cases for ConvertUint64 + { + name: "ConvertUint64 - valid input", + conversion: ConvertUint64, + input: "12345", + expected: uint64(12345), + expectErr: false, + description: "Should convert string to uint64", + }, + { + name: "ConvertUint64 - invalid input", + conversion: ConvertUint64, + input: "notANumber", + expected: nil, + expectErr: true, + description: "Should return error for invalid uint64 string", + }, + + // Test cases for ConvertSint64 + { + name: "ConvertSint64 - valid input", + conversion: ConvertSint64, + input: "-12345", + expected: int64(-12345), + expectErr: false, + description: "Should convert string to sint64", + }, + { + name: "ConvertSint64 - invalid input", + conversion: ConvertSint64, + input: "notANumber", + expected: nil, + expectErr: true, + description: "Should return error for invalid sint64 string", + }, + + // Test cases for ConvertDatetime + { + name: "ConvertDatetime - valid input", + conversion: ConvertDatetime, + input: "20231224093045.123456-000", + expected: mustParseTime("20060102150405.999999-0700", "20231224093045.123456-0000"), + expectErr: false, + description: "Should convert string to time.Time", + }, + { + name: "ConvertDatetime - invalid input", + conversion: ConvertDatetime, + input: "invalidDatetime", + expected: nil, + expectErr: true, + description: "Should return error for invalid datetime string", + }, + // Test cases for ConvertString + { + name: "ConvertString - valid input", + conversion: ConvertString, + input: "test string", + expected: "test string", + expectErr: false, + description: "Should return the same string", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := tt.conversion(tt.input) + + if tt.expectErr { + assert.Error(t, err, tt.description) + } else { + assert.NoError(t, err, tt.description) + assert.Equal(t, tt.expected, result, tt.description) + } + }) + } +} + +// Helper function to parse time +func mustParseTime(layout, value string) time.Time { + parsed, err := time.Parse(layout, value) + if err != nil { + panic(err) + } + return parsed +} diff --git a/metricbeat/module/windows/wmi/wmi.go b/metricbeat/module/windows/wmi/wmi.go new file mode 100644 index 000000000000..26f9b8a30404 --- /dev/null +++ b/metricbeat/module/windows/wmi/wmi.go @@ -0,0 +1,220 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build windows + +package wmi + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + wmi "github.com/microsoft/wmi/pkg/wmiinstance" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet("windows", "wmi", New) +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + config Config +} + +const WMIDefaultNamespace = "root\\cimv2" + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The windows wmi metricset is beta.") + + config := NewDefaultConfig() + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + err := config.ValidateConnectionParameters() + if err != nil { + return nil, err + } + + err = config.CompileQueries() + if err != nil { + return nil, err + } + + err = config.ApplyDefaultNamespaceToQueries(config.Namespace) + if err != nil { + return nil, err + } + + config.BuildNamespaceQueryIndex() + + if config.WarningThreshold == 0 { + config.WarningThreshold = base.Module().Config().Period + } + + m := &MetricSet{ + BaseMetricSet: base, + config: config, + } + + return m, nil +} + +// This function handles the skip conditions +func (m *MetricSet) shouldSkipNilOrEmptyValue(fieldValue interface{}) bool { + if fieldValue == nil { + if !m.config.IncludeNull { + return true // Skip if it's nil and IncludeNull is false + } + } else if stringValue, ok := fieldValue.(string); ok { + if len(stringValue) == 0 && !m.config.IncludeEmptyString { + return true // Skip if it's an empty string and IncludeEmptyString is false + } + } + return false +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(report mb.ReporterV2) error { + + sm := wmi.NewWmiSessionManager() + defer sm.Dispose() + + // To optimize performance and reduce overhead, we create a single session + // for each unique WMI namespace. This minimizes the number of session creations + for namespace, queries := range m.config.NamespaceQueryIndex { + + session, err := sm.GetSession(namespace, m.config.Host, "", m.config.User, m.config.Password) + + if err != nil { + return fmt.Errorf("could not initialize session %w", err) + } + _, err = session.Connect() + if err != nil { + return fmt.Errorf("could not connect session %w", err) + } + defer session.Dispose() + + for _, queryConfig := range queries { + + // We create a shared conversion table for entries with the same schema. + // This avoids repeatedly fetching the schema for each individual entry, improving efficiency. + conversionTable := make(map[string]WmiStringConversionFunction) + + query := queryConfig.QueryStr + + rows, err := ExecuteGuardedQueryInstances(session, query, m.config.WarningThreshold, m.Logger()) + + if err != nil { + m.Logger().Warn("Could not execute query: %v", err) + continue + } + + if len(rows) == 0 { + m.Logger().Warnf("The query '%s' did not produce results. This can be expected, but it can also be the result of querying an invalid field. Make sure all required fields do exist or check the WMI-Activity Operational Log for more information.", query) + } + + defer wmi.CloseAllInstances(rows) + + for _, instance := range rows { + event := mb.Event{ + MetricSetFields: mapstr.M{ + "class": queryConfig.Class, + "namespace": namespace, + "host": m.config.Host, + }, + } + + if m.config.IncludeQueries { + event.MetricSetFields.Put("query", query) + } + + // Get only the required properties + properties := queryConfig.Fields + + // If the Fields array is empty we retrieve all fields + if len(queryConfig.Fields) == 0 { + properties = instance.GetClass().GetPropertiesNames() + } + + for _, fieldName := range properties { + fieldValue, err := instance.GetProperty(fieldName) + if err != nil { + m.Logger().Error("Unable to get propery by name: %v", err) + continue + } + + if m.shouldSkipNilOrEmptyValue(fieldValue) { + continue + } + + // The default case, we simply return what we got + finalValue := fieldValue + + // The script API of WMI returns strings for uint64, sint64, datetime + // Link: https://learn.microsoft.com/en-us/windows/win32/wmisdk/querying-wmi + // As a user, I want to have the right CIM_TYPE in the final document + // + // Example: in the query: SELECT * FROM Win32_OperatingSystem + // FreePhysicalMemory is a string, but it should be an uint64 + if RequiresExtraConversion(fieldValue) { + convertFun, ok := conversionTable[fieldName] + // If the function is not found let us fetch it and cache it + if !ok { + convertFun, err = GetConvertFunction(instance, fieldName, m.Logger()) + if err != nil { + m.Logger().Warn("Skipping addition of field %s: Unable to retrieve the conversion function: %v", fieldName, err) + continue + } + conversionTable[fieldName] = convertFun + } + // Perform the conversion at this point it's safe to cast to string. + fieldValueString, ok := fieldValue.(string) + if !ok { + m.Logger().Warn("Skipping addition of field %s: expected a string found %v", fieldName, fieldValue) + continue + } + convertedValue, err := convertFun(fieldValueString) + if err != nil { + m.Logger().Warn("Skipping addition of field %s. Cannot convert: %v", fieldName, err) + continue + } + finalValue = convertedValue + } + event.MetricSetFields.Put(fieldName, finalValue) + } + report.Event(event) + } + } + } + return nil +} diff --git a/metricbeat/module/windows/wmi/wmi_test.go b/metricbeat/module/windows/wmi/wmi_test.go new file mode 100644 index 000000000000..e9cb2e35be2e --- /dev/null +++ b/metricbeat/module/windows/wmi/wmi_test.go @@ -0,0 +1,102 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build windows + +package wmi + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestShouldSkipNilOrEmptyValue(t *testing.T) { + tests := []struct { + key string + fieldValue interface{} + includeNull bool + includeEmptyString bool + expectedShouldSkip bool + }{ + // Test Case 1: fieldValue is nil, and IncludeNull is false + { + key: "Skip nil value with IncludeNull false", + fieldValue: nil, + includeNull: false, + includeEmptyString: false, + expectedShouldSkip: true, // Should skip because IncludeNull is false + }, + + // Test Case 2: fieldValue is an empty string, and IncludeEmptyString is false + { + key: "Skip Empty string with IncludeEmptyString false", + fieldValue: "", + includeNull: false, + includeEmptyString: false, + expectedShouldSkip: true, // Should skip because IncludeEmptyString is false + }, + + // Test Case 3: fieldValue is nil, and IncludeNull is true + { + key: "Don't skip Nil value with IncludeNull true", + fieldValue: nil, + includeNull: true, + includeEmptyString: false, + expectedShouldSkip: false, // Should not skip because IncludeNull is true + }, + + // Test Case 4: fieldValue is a non-empty string, and IncludeEmptyString is false + { + key: "Don't skip Non-empty string with IncludeEmptyString false", + fieldValue: "non-empty", + includeNull: false, + includeEmptyString: false, + expectedShouldSkip: false, // Should not skip because the string is non-empty + }, + + // Test Case 5: fieldValue is a non-empty string, and IncludeEmptyString is true + { + key: "Non-empty string with IncludeEmptyString true", + fieldValue: "non-empty", + includeNull: true, + includeEmptyString: true, + expectedShouldSkip: false, // Should not skip because IncludeEmptyString is true + }, + } + + for _, test := range tests { + t.Run(test.key, func(t *testing.T) { + + // Arrange: Create a MetricSet with the configuration based on the test case + config := Config{ + IncludeNull: test.includeNull, + IncludeEmptyString: test.includeEmptyString, + } + + metricSet := &MetricSet{ + config: config, + } + + // Act: Call shouldSkipNilOrEmptyValue with the test case fieldValue + result := metricSet.shouldSkipNilOrEmptyValue(test.fieldValue) + + // Assert: Check if the result matches the expected result + assert.Equal(t, test.expectedShouldSkip, result) + }) + } +} diff --git a/metricbeat/modules.d/windows.yml.disabled b/metricbeat/modules.d/windows.yml.disabled index afe1af593116..c917800c1915 100644 --- a/metricbeat/modules.d/windows.yml.disabled +++ b/metricbeat/modules.d/windows.yml.disabled @@ -18,3 +18,22 @@ # field: cpu_usage # format: "float" # - name: "Thread Count" + +# - module: windows +# metricsets: +# - wmi +# period: 60s +# wmi: +# warning_threshold: 30s +# # namespace: "root\\cimv2" +# queries: +# - class: Win32_OperatingSystem # FROM: Class to fetch +# fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties +# - FreePhysicalMemory +# - FreeSpaceInPagingFiles +# - FreeVirtualMemory +# - LocalDateTime +# - NumberOfUsers +# where: "" # Optional WHERE clause to filter query results +# # Namespace for this specific query. Uncomment to override the default namespace. +# # namespace: "root\\cimv2" diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 3cfc78380b28..dfb8246bc5ea 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -1722,6 +1722,30 @@ metricbeat.modules: enabled: true period: 60s +- module: windows + metricsets: ["wmi"] + period: 60s + wmi: + include_null: false # Exclude fields with null values from the output + include_queries: false # Do not include the query string in the output + include_empty_string: false # Exclude fields with empty string values from the output + warning_threshold: 30s # Maximum time to wait for a query result before logging a warning (defaults to period) + # Default WMI namespace for all queries (if not specified per query) + # Uncomment to override the default, which is "root\\cimv2". + # namespace: "root\\cimv2" + queries: + - class: Win32_OperatingSystem # FROM: Class to fetch + fields: # SELECT: Fields to retrieve for this WMI class. Omit the setting to fetch all properties + - FreePhysicalMemory + - FreeSpaceInPagingFiles + - FreeVirtualMemory + - LocalDateTime + - NumberOfUsers + where: "" # Optional WHERE clause to filter query results + # Override the WMI namespace for this specific query (optional). + # If set, this takes precedence over the default namespace above. + # namespace: "root\\cimv2" # Overrides the metric + #------------------------------ ZooKeeper Module ------------------------------ - module: zookeeper enabled: true From d4471e0d98c48756e3c66573dadbf43057f328a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Alvarez=20Pi=C3=B1eiro?= <95703246+emilioalvap@users.noreply.github.com> Date: Thu, 30 Jan 2025 18:46:24 +0100 Subject: [PATCH 3/3] Upgrade NodeJS to latest v18 LTS (#42478) Upgrade HB Nodejs to latest LTS version (v18.20.6). --- dev-tools/packaging/templates/docker/Dockerfile.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-tools/packaging/templates/docker/Dockerfile.tmpl b/dev-tools/packaging/templates/docker/Dockerfile.tmpl index 64b6a155e1eb..1c81a7001f9a 100644 --- a/dev-tools/packaging/templates/docker/Dockerfile.tmpl +++ b/dev-tools/packaging/templates/docker/Dockerfile.tmpl @@ -130,7 +130,7 @@ USER root # Install required dependencies from wolfi repository RUN for iter in {1..10}; do \ apk update && \ - apk add --no-interactive --no-progress --no-cache nodejs-18=18.20.4-r0 npm=10.8.3-r0 glib dbus-glib libatk-1.0 \ + apk add --no-interactive --no-progress --no-cache nodejs-18=18.20.6-r0 npm=11.1.0-r0 glib dbus-glib libatk-1.0 \ libatk-bridge-2.0 cups-libs libxcomposite libxdamage libxrandr libxkbcommon pango alsa-lib \ font-opensans fontconfig gtk icu-data-full libnss mesa font-noto-cjk font-noto-emoji && \ exit_code=0 && break || exit_code=$? && echo "apk error: retry $iter in 10s" && sleep 10; \ @@ -169,7 +169,7 @@ RUN echo \ # Setup synthetics env vars ENV ELASTIC_SYNTHETICS_CAPABLE=true ENV TZ=UTC -ENV NODE_VERSION=18.20.4 +ENV NODE_VERSION=18.20.6 ENV PATH="$NODE_PATH/node/bin:$PATH" # Install the latest version of @elastic/synthetics forcefully ignoring the previously # cached node_modules, heartbeat then calls the global executable to run test suites