From 7d71285347c57f02b7b57a6387cc51112fe64aa9 Mon Sep 17 00:00:00 2001 From: Jakub Sikorski Date: Mon, 11 Sep 2023 16:38:58 +0200 Subject: [PATCH] feat(inputs): New Intel PMT plugin (#13801) --- docs/LICENSE_OF_DEPENDENCIES.md | 1 + go.mod | 1 + go.sum | 4 + plugins/inputs/all/intel_pmt.go | 5 + plugins/inputs/intel_pmt/README.md | 414 +++++++++++ plugins/inputs/intel_pmt/filtering.go | 180 +++++ plugins/inputs/intel_pmt/filtering_test.go | 654 ++++++++++++++++++ plugins/inputs/intel_pmt/intel_pmt.go | 400 +++++++++++ .../intel_pmt/intel_pmt_notamd64linux.go | 31 + plugins/inputs/intel_pmt/intel_pmt_test.go | 586 ++++++++++++++++ plugins/inputs/intel_pmt/sample.conf | 18 + plugins/inputs/intel_pmt/tags_extraction.go | 34 + .../inputs/intel_pmt/tags_extraction_test.go | 155 +++++ plugins/inputs/intel_pmt/xml_parser.go | 277 ++++++++ plugins/inputs/intel_pmt/xml_parser_test.go | 155 +++++ 15 files changed, 2915 insertions(+) create mode 100644 plugins/inputs/all/intel_pmt.go create mode 100644 plugins/inputs/intel_pmt/README.md create mode 100644 plugins/inputs/intel_pmt/filtering.go create mode 100644 plugins/inputs/intel_pmt/filtering_test.go create mode 100644 plugins/inputs/intel_pmt/intel_pmt.go create mode 100644 plugins/inputs/intel_pmt/intel_pmt_notamd64linux.go create mode 100644 plugins/inputs/intel_pmt/intel_pmt_test.go create mode 100644 plugins/inputs/intel_pmt/sample.conf create mode 100644 plugins/inputs/intel_pmt/tags_extraction.go create mode 100644 plugins/inputs/intel_pmt/tags_extraction_test.go create mode 100644 plugins/inputs/intel_pmt/xml_parser.go create mode 100644 plugins/inputs/intel_pmt/xml_parser_test.go diff --git a/docs/LICENSE_OF_DEPENDENCIES.md b/docs/LICENSE_OF_DEPENDENCIES.md index 5e39a97f2..1235c1751 100644 --- a/docs/LICENSE_OF_DEPENDENCIES.md +++ b/docs/LICENSE_OF_DEPENDENCIES.md @@ -32,6 +32,7 @@ following works: - github.com/Masterminds/sprig [MIT License](https://github.com/Masterminds/sprig/blob/master/LICENSE.txt) - github.com/Mellanox/rdmamap [Apache License 2.0](https://github.com/Mellanox/rdmamap/blob/master/LICENSE) - github.com/Microsoft/go-winio [MIT License](https://github.com/Microsoft/go-winio/blob/master/LICENSE) +- github.com/PaesslerAG/gval [BSD 3-Clause "New" or "Revised" License](https://github.com/PaesslerAG/gval/blob/master/LICENSE) - github.com/Shopify/sarama [MIT License](https://github.com/IBM/sarama/blob/master/LICENSE.md) - github.com/aerospike/aerospike-client-go [Apache License 2.0](https://github.com/aerospike/aerospike-client-go/blob/master/LICENSE) - github.com/alecthomas/participle [MIT License](https://github.com/alecthomas/participle/blob/master/COPYING) diff --git a/go.mod b/go.mod index c61f590ef..b951cad01 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/Masterminds/sprig/v3 v3.2.3 github.com/Mellanox/rdmamap v1.1.0 + github.com/PaesslerAG/gval v1.2.2 github.com/Shopify/sarama v1.38.1 github.com/aerospike/aerospike-client-go/v5 v5.11.0 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 diff --git a/go.sum b/go.sum index bd587eb2b..a57a1b85b 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,10 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E= +github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac= +github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= +github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= diff --git a/plugins/inputs/all/intel_pmt.go b/plugins/inputs/all/intel_pmt.go new file mode 100644 index 000000000..bd7be3cc8 --- /dev/null +++ b/plugins/inputs/all/intel_pmt.go @@ -0,0 +1,5 @@ +//go:build !custom || inputs || inputs.intel_pmt + +package all + +import _ "github.com/influxdata/telegraf/plugins/inputs/intel_pmt" // register plugin diff --git a/plugins/inputs/intel_pmt/README.md b/plugins/inputs/intel_pmt/README.md new file mode 100644 index 000000000..62a5737e7 --- /dev/null +++ b/plugins/inputs/intel_pmt/README.md @@ -0,0 +1,414 @@ +# Intel® Platform Monitoring Technology (Intel® PMT) Input Plugin + +This plugin collects metrics via the Linux kernel driver for +Intel® Platform Monitoring Technology (Intel® PMT). +Intel® PMT is an architecture capable of enumerating +and accessing hardware monitoring capabilities on a supported device. + +Support has been added to the mainline Linux kernel under the +platform driver (`drivers/platform/x86/intel/pmt`) which exposes +the Intel PMT telemetry space as a sysfs entry at +`/sys/class/intel_pmt/`. Each discovered telemetry aggregator is +exposed as a directory (with a `telem` prefix) containing a `guid` +identifying the unique PMT space. This file is associated with a +set of XML specification files which can be found in the +[Intel-PMT Repository]. + +This plugin discovers and parses the telemetry data exposed by +the kernel driver using the specification inside the XML files. +Furthermore, the plugin then reads low level samples/counters +and evaluates high level samples/counters according to +transformation formulas, and then reports the collected values. + +## Global configuration options + +In addition to the plugin-specific configuration settings, plugins support +additional global and plugin configuration settings. These settings are used to +modify metrics, tags, and field or create aliases and configure ordering, etc. +See the [CONFIGURATION.md][CONFIGURATION.md] for more details. + +[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins + +## Requirements + +Intel PMT Input Plugin requires an XML specification as `spec`. +It can be provided as a filepath. + +The provided filepath should be an absolute path to `pmt.xml` within +local copies of XML files from the cloned [Intel-PMT Repository]. + +## Configuration + +```toml @sample.conf +# Intel Platform Monitoring Technology plugin exposes Intel PMT metrics available through the Intel PMT kernel space. +# This plugin ONLY supports Linux. +[[inputs.intel_pmt]] + ## Filepath to PMT XML within local copies of XML files from PMT repository. + ## The filepath should be absolute. + spec = "/home/telegraf/Intel-PMT/xml/pmt.xml" + + ## Enable metrics by their datatype. + ## See the Enabling Metrics section in README for more details. + ## If empty, all metrics are enabled. + ## When used, the alternative option samples_enabled should NOT be used. + # datatypes_enabled = [] + + ## Enable metrics by their name. + ## See the Enabling Metrics section in README for more details. + ## If empty, all metrics are enabled. + ## When used, the alternative option datatypes_enabled should NOT be used. + # samples_enabled = [] +``` + +## Example Configuration: C-State residency and temperature with a datatype metric filter + +This configuration allows getting only a subset of metrics +with the use of a datatype filter: + +```toml +[[inputs.intel_pmt]] + spec = "/home/telegraf/Intel-PMT/xml/pmt.xml" + datatypes_enabled = ["tbandwidth_28b","ttemperature"] +``` + +## Example Configuration: C-State residency and temperature with a sample metric filter + +This configuration allows getting only a subset of metrics +with the use of a sample filter: + +```toml +[[inputs.intel_pmt]] + spec = "/home/telegraf/Intel-PMT/xml/pmt.xml" + samples_enabled = ["C0Residency","C1Residency", "Cx_TEMP"] +``` + +## Prerequisites + +Minimum Linux kernel version 5.11 with + +- the `intel_pmt_telemetry` module loaded (on kernels 5.11-5.14) +- the `intel_pmt` module loaded (on kernels 5.14+) + +Intel PMT is exposed on a limited number of devices, e.g. + +- 4th Generation Intel® Xeon® Scalable Processors +(codenamed Sapphire Rapids / SPR) +- 6th Generation Intel® Xeon® Scalable Processors +(codenamed Granite Rapids / GNR) + +PMT space is located in `/sys/class/intel_pmt` with `telem` files requiring +root privileges to read. + +### If Telegraf is not running as a root user + +By default, the `telem` binary file requires root privileges to be read. + +To avoid running Telegraf as a root, +add the following capability to the Telegraf executable: + +```sh +sudo setcap cap_dac_read_search+ep /usr/bin/telegraf +``` + +## Metrics + +All metrics have the following tags: + +- `guid` (unique id of an Intel PMT space). +- `numa_node` (NUMA node the sample is collected from). +- `sample_name` (name of the gathered sample). +- `sample_group` (name of a group to which the sample belongs). +- `datatype_idref` (datatype to which the sample belongs). + +`sample_name` prefixed in XMLs with `Cx_` where `x` +is the core number also have the following tag: + +- `core` (core to which the metric relates). + +`sample_name` prefixed in XMLs with `CHAx_` where `x` +is the CHA number also have the following tag: + +- `cha` (Caching and Home Agent to which the metric relates). + +## Enabling metrics + +By default, the plugin collects all available metrics. + +To limit the metrics collected by the plugin, +two options are available for selecting metrics: + +- enable by datatype (groups of metrics), +- enable by name. + +It's important to note that only one enabling option +should be chosen at a time. + +See the table below for available datatypes and related metrics: + +| Datatype | Metric name | Description | +|-------------------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| `txtal_strap` | `XTAL_FREQ` | Clock rate of the crystal oscillator on this silicon | +| `tdram_energy` | `DRAM_ENERGY_LOW` | DRAM energy consumed by all DIMMS in all Channels (uJ) | +| | `DRAM_ENERGY_HIGH` | DRAM energy consumed by all DIMMS in all Channels (uJ) | +| `tbandwidth_32b` | `C2U_BW` | Core to Uncore Bandwidth (per core and per uncore) | +| | `U2C_BW` | Uncore to Core Bandwidth (per core and per uncore) | +| | `PC2_LOW` | Time spent in the Package C-State 2 (PC2) | +| | `PC2_HIGH` | Time spent in the Package C-State 2 (PC2) | +| | `PC6_LOW` | Time spent in the Package C-State 6 (PC6) | +| | `PC6_HIGH` | Time spent in the Package C-State 6 (PC6) | +| | `MEM_RD_BW` | Memory Read Bandwidth (per channel) | +| | `MEM_WR_BW` | Memory Write Bandwidth (per channel) | +| | `DDRT_READ_BW` | DDRT Read Bandwidth (per channel) | +| | `DDRT_WR_BW` | DDRT Write Bandwidth (per channel) | +| | `THRT_COUNT` | Number of clock ticks when throttling occurred on IMC channel (per channel) | +| | `PMSUM` | Energy accumulated by IMC channel (per channel) | +| | `CMD_CNT_CH0` | Command count for IMC channel subchannel 0 (per channel) | +| | `CMD_CNT_CH1` | Command count for IMC channel subchannel 1 (per channel) | +| `tU32.0` | `PEM_ANY` | Duration for which a core frequency excursion occurred due to a listed or unlisted reason | +| | `PEM_THERMAL` | Duration for which a core frequency excursion occurred due to EMTTM | +| | `PEM_EXT_PROCHOT` | Duration for which a core frequency excursion occurred due to an external PROCHOT assertion | +| | `PEM_PBM` | Duration for which a core frequency excursion occurred due to PBM | +| | `PEM_PL1` | Duration for which a core frequency excursion occurred due to PL1 | +| | `PEM_RESERVED` | PEM Reserved Counter | +| | `PEM_PL2` | Duration for which a core frequency excursion occurred due to PL2 | +| | `PEM_PMAX` | Duration for which a core frequency excursion occurred due to PMAX | +| `tbandwidth_28b` | `C0Residency` | Core C0 Residency (per core) | +| | `C1Residency` | Core C1 Residency (per core) | +| `tratio` | `FET` | Current Frequency Excursion Threshold. Ratio of the core frequency. | +| `tbandwidth_24b` | `UFS_MAX_RING_TRAFFIC` | IO Bandwidth for DMI or PCIE port (per port) | +| `ttemperature` | `TEMP` | Current temperature of a core (per core) | +| `tU8.0` | `VERSION` | For SPR, it's 0. New feature versions will uprev this. | +| `tebb_energy` | `FIVR_HBM_ENERGY` | FIVR HBM Energy in uJ (per HBM) | +| `tBOOL` | `OOB_PEM_ENABLE` | 0x0 (Default)=Inband interface for PEM is enabled. 0x1=OOB interface for PEM is enabled. | +| | `ENABLE_PEM` | 0 (Default): Disable PEM. 1: Enable PEM | +| | `ANY` | Set if a core frequency excursion occurs due to a listed or unlisted reason | +| | `THERMAL` | Set if a core frequency excursion occurs due to any thermal event in core/uncore | +| | `EXT_PROCHOT` | Set if a core frequency excursion occurs due to external PROCHOT assertion | +| | `PBM` | Set if a core frequency excursion occurs due to a power limit (socket RAPL and/or platform RAPL) | +| | `PL1` | Set if a core frequency excursion occurs due to PL1 input from any interfaces | +| | `PL2` | Set if a core frequency excursion occurs due to PL2 input from any interfaces | +| | `PMAX` | Set if a core frequency excursion occurs due to PMAX | +| `ttsc` | `ART` | TSC Delta HBM (per HBM) | +| `tproduct_id` | `PRODUCT_ID` | Product ID | +| `tstring` | `LOCAL_REVISION` | Local Revision ID for this product | +| | `RECORD_TYPE` | Record Type | +| `tcore_state` | `EN` | Core x is enabled (per core) | +| `thist_counter` | `FREQ_HIST_R0` | Frequency histogram range 0 (core in C6) counter (per core) | +| | `FREQ_HIST_R1` | Frequency histogram range 1 (16.67-800 MHz) counter (per core) | +| | `FREQ_HIST_R2` | Frequency histogram range 2 (801-1200 MHz) counter (per core) | +| | `FREQ_HIST_R3` | Frequency histogram range 3 (1201-1600 MHz) counter (per core) | +| | `FREQ_HIST_R4` | Frequency histogram range 4 (1601-2000 MHz) counter (per core) | +| | `FREQ_HIST_R5` | Frequency histogram range 5 (2001-2400 MHz) counter (per core) | +| | `FREQ_HIST_R6` | Frequency histogram range 6 (2401-2800 MHz) counter (per core) | +| | `FREQ_HIST_R7` | Frequency histogram range 7 (2801-3200 MHz) counter (per core) | +| | `FREQ_HIST_R8` | Frequency histogram range 8 (3201-3600 MHz) counter (per core) | +| | `FREQ_HIST_R9` | Frequency histogram range 9 (3601-4000 MHz) counter (per core) | +| | `FREQ_HIST_R10` | Frequency histogram range 10 (4001-4400 MHz) counter (per core) | +| | `FREQ_HIST_R11` | Frequency histogram range 11 (greater then 4400 MHz) (per core) | +| | `VOLT_HIST_R0` | Voltage histogram range 0 (less then 602 mV) counter (per core) | +| | `VOLT_HIST_R1` | Voltage histogram range 1 (602.5-657 mV) counter (per core) | +| | `VOLT_HIST_R2` | Voltage histogram range 2 (657.5-712 mV) counter (per core) | +| | `VOLT_HIST_R3` | Voltage histogram range 3 (712.5-767 mV) counter (per core) | +| | `VOLT_HIST_R4` | Voltage histogram range 4 (767.5-822 mV) counter (per core) | +| | `VOLT_HIST_R5` | Voltage histogram range 5 (822.5-877 mV) counter (per core) | +| | `VOLT_HIST_R6` | Voltage histogram range 6 (877.5-932 mV) counter (per core) | +| | `VOLT_HIST_R7` | Voltage histogram range 7 (932.5-987 mV) counter (per core) | +| | `VOLT_HIST_R8` | Voltage histogram range 8 (987.5-1042 mV) counter (per core) | +| | `VOLT_HIST_R9` | Voltage histogram range 9 (1042.5-1097 mV) counter (per core) | +| | `VOLT_HIST_R10` | Voltage histogram range 10 (1097.5-1152 mV) counter (per core) | +| | `VOLT_HIST_R11` | Voltage histogram range 11 (greater then 1152 mV) counter (per core) | +| | `TEMP_HIST_R0` | Temperature histogram range 0 (less then 20°C) counter | +| | `TEMP_HIST_R1` | Temperature histogram range 1 (20.5-27.5°C) counter | +| | `TEMP_HIST_R2` | Temperature histogram range 2 (28-35°C) counter | +| | `TEMP_HIST_R3` | Temperature histogram range 3 (35.5-42.5°C) counter | +| | `TEMP_HIST_R4` | Temperature histogram range 4 (43-50°C) counter | +| | `TEMP_HIST_R5` | Temperature histogram range 5 (50.5-57.5°C) counter | +| | `TEMP_HIST_R6` | Temperature histogram range 6 (58-65°C) counter | +| | `TEMP_HIST_R7` | Temperature histogram range 7 (65.5-72.5°C) counter | +| | `TEMP_HIST_R8` | Temperature histogram range 8 (73-80°C) counter | +| | `TEMP_HIST_R9` | Temperature histogram range 9 (80.5-87.5°C) counter | +| | `TEMP_HIST_R10` | Temperature histogram range 10 (88-95°C) counter | +| | `TEMP_HIST_R11` | Temperature histogram range 11 (greater then 95°C) counter | +| `tpvp_throttle_counter` | `PVP_THROTTLE_64` | Counter indicating the number of times the core x was throttled in the last 64 cycles window | +| | `PVP_THROTTLE_1024` | Counter indicating the number of times the core x was throttled in the last 1024 cycles window | +| `tpvp_level_res` | `PVP_LEVEL_RES_128_L0` | Counter indicating the percentage of residency during the last 2 ms measurement for level 0 of this type of CPU instruction | +| | `PVP_LEVEL_RES_128_L1` | Counter indicating the percentage of residency during the last 2 ms measurement for level 1 of this type of CPU instruction | +| | `PVP_LEVEL_RES_128_L2` | Counter indicating the percentage of residency during the last 2 ms measurement for level 2 of this type of CPU instruction | +| | `PVP_LEVEL_RES_128_L3` | Counter indicating the percentage of residency during the last 2 ms measurement for level 3 of this type of CPU instruction | +| | `PVP_LEVEL_RES_256_L0` | Counter indicating the percentage of residency during the last 2 ms measurement for level 0 of AVX256 CPU instructions | +| | `PVP_LEVEL_RES_256_L1` | Counter indicating the percentage of residency during the last 2 ms measurement for level 1 of AVX256 CPU instructions | +| | `PVP_LEVEL_RES_256_L2` | Counter indicating the percentage of residency during the last 2 ms measurement for level 2 of AVX256 CPU instructions | +| | `PVP_LEVEL_RES_256_L3` | Counter indicating the percentage of residency during the last 2 ms measurement for level 3 of AVX256 CPU instructions | +| | `PVP_LEVEL_RES_512_L0` | Counter indicating the percentage of residency during the last 2 ms measurement for level 0 of AVX512 CPU instructions | +| | `PVP_LEVEL_RES_512_L1` | Counter indicating the percentage of residency during the last 2 ms measurement for level 1 of AVX512 CPU instructions | +| | `PVP_LEVEL_RES_512_L2` | Counter indicating the percentage of residency during the last 2 ms measurement for level 2 of AVX512 CPU instructions | +| | `PVP_LEVEL_RES_512_L3` | Counter indicating the percentage of residency during the last 2 ms measurement for level 3 of AVX512 CPU instructions | +| | `PVP_LEVEL_RES_TMUL_L0` | Counter indicating the percentage of residency during the last 2 ms measurement for level 0 of TMUL CPU instructions | +| | `PVP_LEVEL_RES_TMUL_L1` | Counter indicating the percentage of residency during the last 2 ms measurement for level 1 of TMUL CPU instructions | +| | `PVP_LEVEL_RES_TMUL_L2` | Counter indicating the percentage of residency during the last 2 ms measurement for level 2 of TMUL CPU instructions | +| | `PVP_LEVEL_RES_TMUL_L3` | Counter indicating the percentage of residency during the last 2 ms measurement for level 3 of TMUL CPU instructions | +| `ttsc_timer` | `TSC_TIMER` | OOBMSM TSC (Time Stamp Counter) value | +| `tnum_en_cha` | `NUM_EN_CHA` | Number of enabled CHAs | +| `trmid_usage_counter` | `RMID0_RDT_CMT` | CHA x RMID 0 LLC cache line usage counter (per CHA) | +| | `RMID1_RDT_CMT` | CHA x RMID 1 LLC cache line usage counter (per CHA) | +| | `RMID2_RDT_CMT` | CHA x RMID 2 LLC cache line usage counter (per CHA) | +| | `RMID3_RDT_CMT` | CHA x RMID 3 LLC cache line usage counter (per CHA) | +| | `RMID4_RDT_CMT` | CHA x RMID 4 LLC cache line usage counter (per CHA) | +| | `RMID5_RDT_CMT` | CHA x RMID 5 LLC cache line usage counter (per CHA) | +| | `RMID6_RDT_CMT` | CHA x RMID 6 LLC cache line usage counter (per CHA) | +| | `RMID7_RDT_CMT` | CHA x RMID 7 LLC cache line usage counter (per CHA) | +| | `RMID0_RDT_MBM_TOTAL` | CHA x RMID 0 total memory transactions counter (per CHA) | +| | `RMID1_RDT_MBM_TOTAL` | CHA x RMID 1 total memory transactions counter (per CHA) | +| | `RMID2_RDT_MBM_TOTAL` | CHA x RMID 2 total memory transactions counter (per CHA) | +| | `RMID3_RDT_MBM_TOTAL` | CHA x RMID 3 total memory transactions counter (per CHA) | +| | `RMID4_RDT_MBM_TOTAL` | CHA x RMID 4 total memory transactions counter (per CHA) | +| | `RMID5_RDT_MBM_TOTAL` | CHA x RMID 5 total memory transactions counter (per CHA) | +| | `RMID6_RDT_MBM_TOTAL` | CHA x RMID 6 total memory transactions counter (per CHA) | +| | `RMID7_RDT_MBM_TOTAL` | CHA x RMID 7 total memory transactions counter (per CHA) | +| | `RMID0_RDT_MBM_LOCAL` | CHA x RMID 0 local memory transactions counter (per CHA) | +| | `RMID1_RDT_MBM_LOCAL` | CHA x RMID 1 local memory transactions counter (per CHA) | +| | `RMID2_RDT_MBM_LOCAL` | CHA x RMID 2 local memory transactions counter (per CHA) | +| | `RMID3_RDT_MBM_LOCAL` | CHA x RMID 3 local memory transactions counter (per CHA) | +| | `RMID4_RDT_MBM_LOCAL` | CHA x RMID 4 local memory transactions counter (per CHA) | +| | `RMID5_RDT_MBM_LOCAL` | CHA x RMID 5 local memory transactions counter (per CHA) | +| | `RMID6_RDT_MBM_LOCAL` | CHA x RMID 6 local memory transactions counter (per CHA) | +| | `RMID7_RDT_MBM_LOCAL` | CHA x RMID 7 local memory transactions counter (per CHA) | +| `ttw_unit` | `TW` | Time window. Valid TW range is 0 to 17. The unit is calculated as `2.3 * 2^TW` ms (e.g. `2.3 * 2^17` ms = ~302 seconds). | +| `tcore_stress_level` | `STRESS_LEVEL` | Accumulating counter indicating relative stress level for a core (per core) | + +## Example Output + +Example output with `tpvp_throttle_counter` as a datatype metric filter: + +```text +intel_pmt,core=0,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C0_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1886465i 1693766334000000000 +intel_pmt,core=1,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C1_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=2,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C2_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=3,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C3_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=4,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C4_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1357578i 1693766334000000000 +intel_pmt,core=5,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C5_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=6,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C6_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2024801i 1693766334000000000 +intel_pmt,core=7,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C7_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=8,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C8_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1390741i 1693766334000000000 +intel_pmt,core=9,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C9_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=10,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C10_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1536483i 1693766334000000000 +intel_pmt,core=11,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C11_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=12,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C12_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=13,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C13_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=14,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C14_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1604964i 1693766334000000000 +intel_pmt,core=15,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C15_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1168673i 1693766334000000000 +intel_pmt,core=16,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C16_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=17,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C17_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=18,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C18_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1276588i 1693766334000000000 +intel_pmt,core=19,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C19_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1139005i 1693766334000000000 +intel_pmt,core=20,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C20_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=21,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C21_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=22,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C22_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=970698i 1693766334000000000 +intel_pmt,core=23,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C23_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=24,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C24_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=25,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C25_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1178462i 1693766334000000000 +intel_pmt,core=26,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C26_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=27,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C27_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2093384i 1693766334000000000 +intel_pmt,core=28,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C28_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=29,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C29_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=30,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C30_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=31,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C31_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=32,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C32_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2825174i 1693766334000000000 +intel_pmt,core=33,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C33_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2592279i 1693766334000000000 +intel_pmt,core=34,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C34_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=35,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C35_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=36,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C36_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1960662i 1693766334000000000 +intel_pmt,core=37,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C37_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1821914i 1693766334000000000 +intel_pmt,core=38,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C38_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=39,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C39_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=40,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C40_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=41,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C41_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2654651i 1693766334000000000 +intel_pmt,core=42,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C42_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2230984i 1693766334000000000 +intel_pmt,core=43,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C43_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=44,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C44_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=45,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C45_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=46,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C46_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2325520i 1693766334000000000 +intel_pmt,core=47,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C47_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=48,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C48_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=49,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C49_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=50,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C50_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=51,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C51_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=52,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C52_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1468880i 1693766334000000000 +intel_pmt,core=53,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C53_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2151919i 1693766334000000000 +intel_pmt,core=54,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C54_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=55,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C55_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=2065994i 1693766334000000000 +intel_pmt,core=56,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C56_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=57,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C57_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=58,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C58_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1553691i 1693766334000000000 +intel_pmt,core=59,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C59_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=1624177i 1693766334000000000 +intel_pmt,core=60,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C60_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=61,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C61_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=62,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C62_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=63,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C63_PVP_THROTTLE_64,sample_name=PVP_THROTTLE_64 value=0i 1693766334000000000 +intel_pmt,core=0,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C0_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=12977949i 1693766334000000000 +intel_pmt,core=1,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C1_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=2,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C2_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=3,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C3_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=4,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C4_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=7180524i 1693766334000000000 +intel_pmt,core=5,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C5_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=6,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C6_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=8667263i 1693766334000000000 +intel_pmt,core=7,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C7_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=8,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C8_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=5945851i 1693766334000000000 +intel_pmt,core=9,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C9_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=10,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C10_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6669829i 1693766334000000000 +intel_pmt,core=11,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C11_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=12,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C12_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=13,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C13_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=14,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C14_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6579832i 1693766334000000000 +intel_pmt,core=15,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C15_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6101856i 1693766334000000000 +intel_pmt,core=16,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C16_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=17,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C17_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=18,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C18_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=7796183i 1693766334000000000 +intel_pmt,core=19,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C19_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6849098i 1693766334000000000 +intel_pmt,core=20,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C20_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=21,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C21_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=22,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C22_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=12378942i 1693766334000000000 +intel_pmt,core=23,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C23_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=24,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C24_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=25,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C25_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=8299231i 1693766334000000000 +intel_pmt,core=26,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C26_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=27,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C27_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=7986390i 1693766334000000000 +intel_pmt,core=28,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C28_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=29,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C29_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=30,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C30_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=31,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C31_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=32,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C32_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=9876325i 1693766334000000000 +intel_pmt,core=33,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C33_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=8547471i 1693766334000000000 +intel_pmt,core=34,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C34_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=35,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C35_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=36,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C36_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=9231744i 1693766334000000000 +intel_pmt,core=37,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C37_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=8133031i 1693766334000000000 +intel_pmt,core=38,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C38_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=39,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C39_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=40,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C40_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=41,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C41_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6136417i 1693766334000000000 +intel_pmt,core=42,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C42_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6091019i 1693766334000000000 +intel_pmt,core=43,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C43_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=44,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C44_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=45,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C45_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=46,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C46_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=5804639i 1693766334000000000 +intel_pmt,core=47,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C47_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=48,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C48_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=49,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C49_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=50,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C50_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=51,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C51_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=52,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C52_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=5738491i 1693766334000000000 +intel_pmt,core=53,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C53_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=6058504i 1693766334000000000 +intel_pmt,core=54,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C54_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=55,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C55_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=5987093i 1693766334000000000 +intel_pmt,core=56,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C56_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=57,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C57_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=58,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C58_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=10384909i 1693766334000000000 +intel_pmt,core=59,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C59_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=7305786i 1693766334000000000 +intel_pmt,core=60,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C60_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=61,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C61_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=62,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C62_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +intel_pmt,core=63,datatype_idref=tpvp_throttle_counter,guid=0x87b6fef1,pmt,numa_node=0,sample_group=C63_PVP_THROTTLE_1024,sample_name=PVP_THROTTLE_1024 value=0i 1693766334000000000 +``` + +[Intel-PMT repository]: https://github.com/intel/Intel-PMT diff --git a/plugins/inputs/intel_pmt/filtering.go b/plugins/inputs/intel_pmt/filtering.go new file mode 100644 index 000000000..48d867e07 --- /dev/null +++ b/plugins/inputs/intel_pmt/filtering.go @@ -0,0 +1,180 @@ +//go:build linux && amd64 + +package intel_pmt + +import ( + "errors" + "regexp" + + "golang.org/x/exp/slices" +) + +var metricPatternRegex = regexp.MustCompile(`(?P(C|CHA))\d+_(?P[A-Z0-9_]+)$`) + +// verifyNoEmpty checks if all pmt XMLs are not empty. +// +// Data for different GUIDs can be empty +// but data for at least one GUID cannot be empty. +// +// Returns: +// - nil if at least one pair of XMLs for GUID is not empty. +// - an error if all XMLs are empty. +func (p *IntelPMT) verifyNoEmpty() error { + emptyAggInterface := true + for guid := range p.pmtTelemetryFiles { + if len(p.pmtAggregatorInterface[guid].AggregatorSamples.AggregatorSample) != 0 { + emptyAggInterface = false + break + } + } + if emptyAggInterface { + return errors.New("all aggregator interface XMLs are empty") + } + emptyAgg := true + for guid := range p.pmtTelemetryFiles { + if len(p.pmtAggregator[guid].SampleGroup) != 0 { + emptyAgg = false + break + } + } + if emptyAgg { + return errors.New("all aggregator XMLs are empty") + } + return nil +} + +// filterAggregatorByDatatype filters Aggregator XML by provided datatypes. +// +// Every sample group in aggregator XML consists of several samples. +// Every sample in the group has a datatype assigned. +// This function filters the samples based on their datatype. +// +// Parameters: +// +// datatypes: string slice of datatypes to include in filtered XML. +func (a *aggregator) filterAggregatorByDatatype(datatypes []string) { + var newSampleGroup []sampleGroup + for _, group := range a.SampleGroup { + var tmpAgg []sample + for _, aggSample := range group.Sample { + if slices.Contains(datatypes, aggSample.DatatypeIDRef) { + tmpAgg = append(tmpAgg, aggSample) + } + } + if len(tmpAgg) > 0 { + // groupSample can have samples with different datatypeIDRef inside + // so new groupSample needs to be created + // containing all needed information and filtered samples only. + newGroup := sampleGroup{} + newGroup.SampleID = group.SampleID + newGroup.Sample = tmpAgg + newSampleGroup = append(newSampleGroup, newGroup) + } + } + a.SampleGroup = newSampleGroup +} + +// filterAggregatorBySampleName filters Aggregator XML by provided sample names. +// +// Every sample has a name specified in the XML. +// This function filters the samples based on their names. +// The match can be exact or can be based on regex match. +// +// Parameters: +// +// sampleNames: string slice of sample names to include in filtered XML. +func (a *aggregator) filterAggregatorBySampleName(sampleNames []string) { + var newSampleGroup []sampleGroup + for _, group := range a.SampleGroup { + var tmpAgg []sample + for _, aggSample := range group.Sample { + if shouldAddSample(aggSample, sampleNames) { + tmpAgg = append(tmpAgg, aggSample) + } + } + + if len(tmpAgg) > 0 { + newGroup := sampleGroup{} + newGroup.SampleID = group.SampleID + newGroup.Sample = tmpAgg + newSampleGroup = append(newSampleGroup, newGroup) + } + } + a.SampleGroup = newSampleGroup +} + +// shouldAddSample is a helper function for filterAggregatorBySampleName +// that checks if the sample should be added to the sample group. +func shouldAddSample(s sample, sampleNames []string) bool { + matches := metricPatternRegex.FindStringSubmatch(s.SampleName) + for _, v := range sampleNames { + if s.SampleName == v { + return true + } + if len(matches) == 4 { + if matches[3] == v { + return true + } + } + } + return false +} + +// filterAggInterfaceByDatatype filter aggregator interface XML by provided datatypes. +// +// Aggregator interface XML contains many aggregator samples inside, each with datatype assigned. +// This function filters aggregator samples based on their datatype. +// +// Parameters: +// +// datatypes: string slice of datatypes to include in filtered XML. +// dtMetricsFound: a map of found datatypes for all GUIDs. +func (a *aggregatorInterface) filterAggInterfaceByDatatype(datatypes []string, dtMetricsFound map[string]bool) { + newAggSample := aggregatorSamples{} + for _, s := range a.AggregatorSamples.AggregatorSample { + if slices.Contains(datatypes, s.DatatypeIDRef) { + dtMetricsFound[s.DatatypeIDRef] = true + newAggSample.AggregatorSample = append(newAggSample.AggregatorSample, s) + } + } + a.AggregatorSamples = newAggSample +} + +// filterAggInterfaceBySampleName filters aggregator interface XML by sample names. +// +// This function filters aggregator samples based on the provided sampleNames. +// When the name for the sample is unique the match is exact. +// When the name is per resource (i.e. Cx_) the match is regex based. +// +// Parameters: +// +// sampleNames: string slice of sample names to include in filtered XML. +// sMetricsFound: a map of found metric names for all GUIDs. +func (a *aggregatorInterface) filterAggInterfaceBySampleName(sampleNames []string, sMetricsFound map[string]bool) { + newAggSample := aggregatorSamples{} + for _, s := range a.AggregatorSamples.AggregatorSample { + if shouldAddAggregatorSample(s, sampleNames, sMetricsFound) { + newAggSample.AggregatorSample = append(newAggSample.AggregatorSample, s) + } + } + a.AggregatorSamples = newAggSample +} + +// shouldAddAggregatorSample is a helper function for filterAggInterfaceBySampleName +// that checks if if the sample should be added to the aggregator samples. +func shouldAddAggregatorSample(s aggregatorSample, sampleNames []string, sMetricsFound map[string]bool) bool { + matches := metricPatternRegex.FindStringSubmatch(s.SampleName) + for _, userMetricInput := range sampleNames { + if s.SampleName == userMetricInput { + sMetricsFound[userMetricInput] = true + return true + } + if len(matches) == 4 { + if matches[3] == userMetricInput { + sMetricsFound[userMetricInput] = true + return true + } + } + } + return false +} diff --git a/plugins/inputs/intel_pmt/filtering_test.go b/plugins/inputs/intel_pmt/filtering_test.go new file mode 100644 index 000000000..744d02e25 --- /dev/null +++ b/plugins/inputs/intel_pmt/filtering_test.go @@ -0,0 +1,654 @@ +//go:build linux && amd64 + +package intel_pmt + +import ( + "testing" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +func TestFilterAggregatorByDatatype(t *testing.T) { + t.Run("Filter aggregator, 1 sample group, 2 samples with different DataTypes", func(t *testing.T) { + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + { + DatatypeIDRef: "missing", + Msb: 0, + Lsb: 0, + SampleID: "missing", + }, + }, + }, + }, + } + p := IntelPMT{ + DatatypeFilter: []string{"test-datatype"}, + } + expected := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + agg.filterAggregatorByDatatype(p.DatatypeFilter) + require.Equal(t, expected, agg) + }) + + t.Run("Filter Aggregator, 2 sample groups, only 1 sample group has expected datatype", func(t *testing.T) { + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + { + SampleID: uint64(2), + Sample: []sample{ + { + DatatypeIDRef: "missing", + Msb: 0, + Lsb: 0, + SampleID: "missing", + }, + }, + }, + }, + } + p := IntelPMT{ + DatatypeFilter: []string{"test-datatype"}, + } + expected := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + agg.filterAggregatorByDatatype(p.DatatypeFilter) + require.Equal(t, expected, agg) + }) +} + +func TestFilterAggregatorInterfaceByDatatype(t *testing.T) { + t.Run("Filter agg interface, 2 Agg samples, only 1 should remain", func(t *testing.T) { + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + { + SampleName: "missing", + SampleGroup: "missing", + DatatypeIDRef: "missing", + TransformREF: "missing", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "missing", + SampleIDREF: "missing", + }, + }, + }, + }, + }, + }, + } + expected := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + + p := IntelPMT{ + DatatypeFilter: []string{"test-datatype"}, + Log: testutil.Logger{}, + } + aggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, make(map[string]bool)) + require.Equal(t, expected, aggInterface) + }) +} + +func TestFilterAggregatorBySampleName(t *testing.T) { + t.Run("Filter aggregator, 2 sample names, with the same datatype, 1 sample name matches exactly", func(t *testing.T) { + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "exists", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + { + SampleName: "missing", + DatatypeIDRef: "test-datatype", + Msb: 0, + Lsb: 0, + SampleID: "missing", + }, + }, + }, + }, + } + p := IntelPMT{ + SampleFilter: []string{"exists"}, + } + expected := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "exists", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + agg.filterAggregatorBySampleName(p.SampleFilter) + require.Equal(t, expected, agg) + }) + + t.Run("Filter aggregator, 2 sample names, with the same datatype, 1 sample name matches by regex", func(t *testing.T) { + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "C61_TEMP", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + { + SampleName: "C61_TEMP_test", + DatatypeIDRef: "test-datatype", + Msb: 0, + Lsb: 0, + SampleID: "missing", + }, + }, + }, + }, + } + p := IntelPMT{ + SampleFilter: []string{"TEMP"}, + } + expected := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "C61_TEMP", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + agg.filterAggregatorBySampleName(p.SampleFilter) + require.Equal(t, expected, agg) + }) +} + +func TestFilterAggregatorInterfaceBySampleName(t *testing.T) { + t.Run("Filter agg interface, 2 Agg samples, 1 sample name matches exactly", func(t *testing.T) { + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "C36_PVP_LEVEL_RES_128_L1", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + { + SampleName: "missing", + SampleGroup: "missing", + DatatypeIDRef: "test-datatype", + TransformREF: "missing", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "missing", + SampleIDREF: "missing", + }, + }, + }, + }, + }, + }, + } + expected := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "C36_PVP_LEVEL_RES_128_L1", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + + p := IntelPMT{ + SampleFilter: []string{"PVP_LEVEL_RES_128_L1"}, + Log: testutil.Logger{}, + } + aggInterface.filterAggInterfaceBySampleName(p.SampleFilter, make(map[string]bool)) + require.Equal(t, expected, aggInterface) + }) + + t.Run("Filter agg interface, 2 Agg samples, 1 sample name matches by regex", func(t *testing.T) { + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + { + SampleName: "missing", + SampleGroup: "missing", + DatatypeIDRef: "test-datatype", + TransformREF: "missing", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "missing", + SampleIDREF: "missing", + }, + }, + }, + }, + }, + }, + } + expected := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + + p := IntelPMT{ + SampleFilter: []string{"test-sample"}, + Log: testutil.Logger{}, + } + aggInterface.filterAggInterfaceBySampleName(p.SampleFilter, make(map[string]bool)) + require.Equal(t, expected, aggInterface) + }) +} + +func TestVerifyNoEmpty(t *testing.T) { + t.Run("Correct XMLs, no filtering by user", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregator: map[string]aggregator{ + "test-guid": { + SampleGroup: []sampleGroup{{}}, + }, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{{}}, + }, + }, + }, + } + p.pmtTelemetryFiles = map[string]pmtFileInfo{ + "test-guid": []fileInfo{{}}, + } + require.NoError(t, p.verifyNoEmpty()) + }) + + t.Run("Incorrect XMLs, filtering by datatype that doesn't exist", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregator: map[string]aggregator{ + "test-guid": {}, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": {}, + }, + DatatypeFilter: []string{"doesn't-exist"}, + Log: testutil.Logger{}, + pmtTelemetryFiles: map[string]pmtFileInfo{"test-guid": []fileInfo{{}}}, + } + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + aggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, make(map[string]bool)) + p.pmtAggregatorInterface["test-guid"] = aggInterface + require.ErrorContains(t, p.verifyNoEmpty(), "all aggregator interface XMLs are empty") + }) + + t.Run("Incorrect XMLs, user provided sample names that don't exist", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": {}, + }, + SampleFilter: []string{"doesn't-exist"}, + Log: testutil.Logger{}, + pmtTelemetryFiles: map[string]pmtFileInfo{"test-guid": []fileInfo{{}}}, + } + + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + aggInterface.filterAggInterfaceBySampleName(p.SampleFilter, make(map[string]bool)) + p.pmtAggregatorInterface["test-guid"] = aggInterface + require.ErrorContains(t, p.verifyNoEmpty(), "XMLs are empty") + }) + t.Run("Correct XMLs, user provided correct sample names", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregator: map[string]aggregator{ + "test-guid": {}, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": {}, + }, + SampleFilter: []string{"test-sample"}, + pmtTelemetryFiles: map[string]pmtFileInfo{"test-guid": []fileInfo{{}}}, + } + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "test-sample", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + agg.filterAggregatorBySampleName(p.SampleFilter) + aggInterface.filterAggInterfaceBySampleName(p.SampleFilter, make(map[string]bool)) + p.pmtAggregator["test-guid"] = agg + p.pmtAggregatorInterface["test-guid"] = aggInterface + require.NoError(t, p.verifyNoEmpty()) + }) + + t.Run("Correct XMLs, user provided correct datatype names", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregator: map[string]aggregator{ + "test-guid": {}, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": {}, + }, + DatatypeFilter: []string{"test-datatype"}, + pmtTelemetryFiles: map[string]pmtFileInfo{"test-guid": []fileInfo{{}}}, + } + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "test-sample", + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + agg.filterAggregatorByDatatype(p.DatatypeFilter) + aggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, make(map[string]bool)) + p.pmtAggregator["test-guid"] = agg + p.pmtAggregatorInterface["test-guid"] = aggInterface + require.NoError(t, p.verifyNoEmpty()) + }) + + t.Run("Incorrect XMLs, no datatype metrics found in aggregator sample XML", func(t *testing.T) { + p := &IntelPMT{ + pmtAggregator: map[string]aggregator{ + "test-guid": {}, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": {}, + }, + DatatypeFilter: []string{"test-datatype"}, + pmtTelemetryFiles: map[string]pmtFileInfo{"test-guid": []fileInfo{{}}}, + } + agg := aggregator{ + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + SampleName: "test-sample", + // DatatypeIDREF is wrong + DatatypeIDRef: "wrong", + Msb: 4, + Lsb: 4, + SampleID: "test-sample-ref", + }, + }, + }, + }, + } + aggInterface := aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + } + agg.filterAggregatorByDatatype(p.DatatypeFilter) + aggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, make(map[string]bool)) + p.pmtAggregator["test-guid"] = agg + p.pmtAggregatorInterface["test-guid"] = aggInterface + require.ErrorContains(t, p.verifyNoEmpty(), "all aggregator XMLs are empty") + }) +} diff --git a/plugins/inputs/intel_pmt/intel_pmt.go b/plugins/inputs/intel_pmt/intel_pmt.go new file mode 100644 index 000000000..84a19050c --- /dev/null +++ b/plugins/inputs/intel_pmt/intel_pmt.go @@ -0,0 +1,400 @@ +//go:generate ../../../tools/readme_config_includer/generator +//go:build linux && amd64 + +package intel_pmt + +import ( + _ "embed" + "encoding/binary" + "errors" + "fmt" + "html" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/PaesslerAG/gval" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +//go:embed sample.conf +var sampleConfig string + +var hexToDecRegex = regexp.MustCompile(`0x[0-9a-fA-F]+`) + +const ( + defaultPmtBasePath = "/sys/class/intel_pmt" + pluginName = "intel_pmt" +) + +type pmtFileInfo []fileInfo + +type fileInfo struct { + path string + numaNode string +} + +type IntelPMT struct { + PmtSpec string `toml:"spec"` + DatatypeFilter []string `toml:"datatypes_enabled"` + SampleFilter []string `toml:"samples_enabled"` + Log telegraf.Logger `toml:"-"` + + pmtBasePath string + reader sourceReader + pmtTelemetryFiles map[string]pmtFileInfo + pmtMetadata *pmt + pmtAggregator map[string]aggregator + pmtAggregatorInterface map[string]aggregatorInterface + pmtTransformations map[string]map[string]transformation +} + +// SampleConfig returns a sample configuration (See sample.conf). +func (p *IntelPMT) SampleConfig() string { + return sampleConfig +} + +// Init performs one time setup of the plugin +func (p *IntelPMT) Init() error { + err := p.checkPmtSpec() + if err != nil { + return err + } + + err = p.explorePmtInSysfs() + if err != nil { + return fmt.Errorf("error while exploring pmt sysfs: %w", err) + } + + return p.parseXMLs() +} + +// Gather collects the plugin's metrics. +func (p *IntelPMT) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + var hasError atomic.Bool + for guid := range p.pmtTelemetryFiles { + wg.Add(1) + go func(guid string, fileInfo []fileInfo) { + defer wg.Done() + for _, info := range fileInfo { + data, err := os.ReadFile(info.path) + if err != nil { + hasError.Store(true) + acc.AddError(fmt.Errorf("gathering metrics failed: %w", err)) + return + } + + err = p.aggregateSamples(acc, guid, data, info.numaNode) + if err != nil { + hasError.Store(true) + acc.AddError(fmt.Errorf("gathering metrics failed: %w", err)) + return + } + } + }(guid, p.pmtTelemetryFiles[guid]) + } + wg.Wait() + + if hasError.Load() { + return errors.New("error(s) occurred while gathering metrics") + } + return nil +} + +// checkPmtSpec checks if provided PmtSpec is correct and readable. +// +// PmtSpec is expected to be an absolute filepath. +// +// Returns: +// +// error - error if PmtSpec is invalid, not readable, or not absolute. +func (p *IntelPMT) checkPmtSpec() error { + if p.PmtSpec == "" { + return errors.New("pmt spec is empty") + } + + if !isFileReadable(p.PmtSpec) { + return fmt.Errorf("provided pmt spec is not readable %q", p.PmtSpec) + } + + lastSlash := strings.LastIndex(p.PmtSpec, "/") + // if PmtSpec contains no "/" + if lastSlash == -1 { + return errors.New("provided pmt spec is not an absolute path") + } + p.pmtBasePath = p.PmtSpec[:lastSlash] + p.reader = fileReader{} + + return nil +} + +// explorePmtInSysfs finds necessary paths in pmt sysfs. +// +// This method finds "telem" files, used to retrieve telemetry values +// and saves them under their corresponding GUID. +// It also finds which NUMA node the samples belong to. +// +// Returns: +// +// error - error if any of the operations failed. +func (p *IntelPMT) explorePmtInSysfs() error { + pmtDirectories, err := os.ReadDir(defaultPmtBasePath) + if err != nil { + return fmt.Errorf("error reading pmt directory: %w", err) + } + p.pmtTelemetryFiles = make(map[string]pmtFileInfo) + for _, dir := range pmtDirectories { + if !strings.HasPrefix(dir.Name(), "telem") { + continue + } + telemDirPath := filepath.Join(defaultPmtBasePath, dir.Name()) + symlinkInfo, err := os.Stat(telemDirPath) + if err != nil { + return fmt.Errorf("error resolving symlink for directory %q: %w", telemDirPath, err) + } + if !symlinkInfo.IsDir() { + continue + } + + pmtGUIDPath := filepath.Join(telemDirPath, "guid") + rawGUID, err := os.ReadFile(pmtGUIDPath) + if err != nil { + return fmt.Errorf("cannot read GUID: %w", err) + } + // cut the newline char + tID := strings.TrimSpace(string(rawGUID)) + + telemPath := filepath.Join(telemDirPath, "telem") + if !isFileReadable(telemPath) { + p.Log.Warnf("telem file is not readable %q", telemPath) + continue + } + + numaNodePath := filepath.Join(telemDirPath, "device", "numa_node") + numaNodeSymlink, err := filepath.EvalSymlinks(numaNodePath) + if err != nil { + return fmt.Errorf("error while evaluating symlink %q: %w", numaNodePath, err) + } + + numaNode, err := os.ReadFile(numaNodeSymlink) + if err != nil { + return fmt.Errorf("error while reading symlink %q: %w", numaNodeSymlink, err) + } + numaNodeString := strings.TrimSpace(string(numaNode)) + if numaNodeString == "" { + return fmt.Errorf("numa_node file %q is empty", numaNodeSymlink) + } + + fi := fileInfo{ + path: telemPath, + numaNode: numaNodeString, + } + p.pmtTelemetryFiles[tID] = append(p.pmtTelemetryFiles[tID], fi) + } + if len(p.pmtTelemetryFiles) == 0 { + return errors.New("no telemetry sources found - current platform doesn't support PMT or proper permissions needed to read them") + } + return nil +} + +func isFileReadable(path string) bool { + if _, err := os.Stat(path); err != nil { + return false + } + + file, err := os.Open(path) + if err != nil { + return false + } + file.Close() + + return true +} + +// getSampleValues reads all sample values for all sample groups. +// +// This method reads all telemetry samples for given GUID from given data +// and saves it in results map. +// +// Parameters: +// +// guid - GUID saying which Aggregator XML will be read. +// data - data read from "telem" file. +// +// Returns: +// +// map[string]uint64 - results map with read data. +// error - error if getting any of the values failed. +func (p *IntelPMT) getSampleValues(guid string, data []byte) (map[string]uint64, error) { + results := make(map[string]uint64) + for _, group := range p.pmtAggregator[guid].SampleGroup { + // Determine starting position of the Sample Group. + // Each Sample Group occupies 8 bytes. + offset := 8 * group.SampleID + for _, sample := range group.Sample { + var err error + results[sample.SampleID], err = getTelemSample(sample, data, offset) + if err != nil { + return nil, err + } + } + } + return results, nil +} + +// getTelemSample extracts a telemetry sample from a given buffer. +// +// This function uses offset as a starting position. +// Then it uses LSB and MSB from sample to determine which bits +// to read from the given buffer. +// +// Parameters: +// +// s - sample from Aggregator XML containing LSB and MSB info. +// buf - the byte buffer containing the telemetry data. +// offset - the starting position (in bytes) in the buffer. +// +// Returns: +// +// uint64 - the extracted sample as a 64-bit unsigned integer. +// error - error if offset+8 exceeds the size of the buffer. +func getTelemSample(s sample, buf []byte, offset uint64) (uint64, error) { + if len(buf) < int(offset+8) { + return 0, fmt.Errorf("error reading telemetry sample: insufficient bytes from offset %d in buffer of size %d", offset, len(buf)) + } + data := binary.LittleEndian.Uint64(buf[offset : offset+8]) + + // Apply mask and shift right + value := (data & s.mask) >> s.Lsb + return value, nil +} + +// aggregateSamples outputs transformed metrics to Telegraf. +// +// This method transforms low level samples +// into high-level samples with appropriate transformation equation. +// Then it creates fields and tags and adds them to Telegraf Accumulator. +// +// Parameters: +// +// guid - GUID saying which Aggregator Interface will be read. +// data - contents of the "telem" file. +// numaNode - which NUMA node this sample belongs to. +// acc - Telegraf Accumulator. +// +// Returns: +// +// error - error if getting values has failed, if sample IDref is missing or if equation evaluation has failed. +func (p *IntelPMT) aggregateSamples(acc telegraf.Accumulator, guid string, data []byte, numaNode string) error { + results, err := p.getSampleValues(guid, data) + if err != nil { + return err + } + for _, sample := range p.pmtAggregatorInterface[guid].AggregatorSamples.AggregatorSample { + parameters := make(map[string]interface{}) + for _, input := range sample.TransformInputs.TransformInput { + if _, ok := results[input.SampleIDREF]; !ok { + return fmt.Errorf("sample with IDREF %q has not been found", input.SampleIDREF) + } + parameters[input.VarName] = results[input.SampleIDREF] + } + eq := transformEquation(p.pmtTransformations[guid][sample.TransformREF].Transform) + res, err := eval(eq, parameters) + if err != nil { + return fmt.Errorf("error during eval of sample %q: %w", sample.SampleName, err) + } + fields := map[string]interface{}{ + "value": res, + } + tags := map[string]string{ + "guid": guid, + "numa_node": numaNode, + "sample_name": sample.SampleName, + "sample_group": sample.SampleGroup, + "datatype_idref": sample.DatatypeIDRef, + } + if sample.core != "" { + tags["core"] = sample.core + } + if sample.cha != "" { + tags["cha"] = sample.cha + } + + acc.AddFields(pluginName, fields, tags) + } + return nil +} + +// transformEquation changes the equation string to be ready for eval. +// +// This function removes "$" signs, which prefixes every parameter in equations. +// Then escapes special characters from XML +// like "<" into "<", "&" into "&" and ">" into ">" +// so they can be used in evaluation. +// +// Parameters: +// +// eq - string which should be transformed. +// +// Returns: +// +// string - transformed string. +func transformEquation(eq string) string { + withoutDollar := strings.ReplaceAll(eq, "$", "") + decoded := html.UnescapeString(withoutDollar) + return decoded +} + +// eval calculates the value of given equation for given parameters. +// +// This function evaluates arbitrary equations with parameters. +// It substitutes the parameters in the equation with their values +// and calculates its value. +// Example: equation "a + b", with params: a: 2, b: 3. +// a and b will be substituted with their values so the equation becomes "2 + 3". +// If any of the parameters are missing then the equation is invalid and returns an error. +// Parameters: +// +// eq - equation which should be calculated. +// params - parameters to substitute in the equation. +// +// Returns: +// +// interface - the value of calculation. +// error - error if the equation is empty, if hex to dec conversion failed or if the equation is invalid. +func eval(eq string, params map[string]interface{}) (interface{}, error) { + if eq == "" { + return nil, errors.New("no transformation equation found") + } + // gval doesn't support hexadecimals + eq = hexToDecRegex.ReplaceAllStringFunc(eq, hexToDec) + if eq == "" { + return nil, fmt.Errorf("error during hex to decimal conversion") + } + result, err := gval.Evaluate(eq, params) + if err != nil { + return nil, err + } + return result, nil +} + +func hexToDec(hexStr string) string { + dec, err := strconv.ParseInt(hexStr, 0, 64) + if err != nil { + return "" + } + return strconv.FormatInt(dec, 10) +} + +func init() { + inputs.Add(pluginName, func() telegraf.Input { + return &IntelPMT{} + }) +} diff --git a/plugins/inputs/intel_pmt/intel_pmt_notamd64linux.go b/plugins/inputs/intel_pmt/intel_pmt_notamd64linux.go new file mode 100644 index 000000000..e2441457c --- /dev/null +++ b/plugins/inputs/intel_pmt/intel_pmt_notamd64linux.go @@ -0,0 +1,31 @@ +//go:generate ../../../tools/readme_config_includer/generator +//go:build !linux || !amd64 + +package intel_pmt + +import ( + _ "embed" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +//go:embed sample.conf +var sampleConfig string + +type IntelPMT struct { + Log telegraf.Logger `toml:"-"` +} + +func (p *IntelPMT) Init() error { + p.Log.Warn("Current platform is not supported") + return nil +} +func (*IntelPMT) SampleConfig() string { return sampleConfig } +func (*IntelPMT) Gather(_ telegraf.Accumulator) error { return nil } + +func init() { + inputs.Add("intel_pmt", func() telegraf.Input { + return &IntelPMT{} + }) +} diff --git a/plugins/inputs/intel_pmt/intel_pmt_test.go b/plugins/inputs/intel_pmt/intel_pmt_test.go new file mode 100644 index 000000000..58378001c --- /dev/null +++ b/plugins/inputs/intel_pmt/intel_pmt_test.go @@ -0,0 +1,586 @@ +//go:build linux && amd64 + +package intel_pmt + +import ( + _ "embed" + "os" + "testing" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +func createTempFile(t *testing.T, dir string, pattern string, data []byte) (*os.File, os.FileInfo) { + tempFile, err := os.CreateTemp(dir, pattern) + if err != nil { + t.Fatalf("error creating a temporary file %v: %v", tempFile.Name(), err) + } + _, err = tempFile.Write(data) + if err != nil { + t.Fatalf("error writing buffer to file %v: %v", tempFile.Name(), err) + } + fileInfo, err := tempFile.Stat() + if err != nil { + t.Fatalf("failed to stat a temporary file %v: %v", tempFile.Name(), err) + } + + return tempFile, fileInfo +} + +func TestTransformEquation(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "No changes", + input: "abc", + expected: "abc", + }, + { + name: "Remove $ sign", + input: "a$b$c", + expected: "abc", + }, + { + name: "Decode HTML entities", + input: "a&b", + expected: "a&b", + }, + { + name: "Remove $ and decode HTML entities", + input: "$a&b$c", + expected: "a&bc", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := transformEquation(tt.input) + require.Equal(t, tt.expected, output) + }) + } +} + +func TestEval(t *testing.T) { + tests := []struct { + name string + eq string + params map[string]interface{} + expected interface{} + err bool + }{ + { + name: "empty equation", + eq: "", + params: nil, + expected: nil, + err: true, + }, + { + name: "Valid equation", + eq: "2 + 2", + params: nil, + expected: float64(4), + err: false, + }, + { + name: "Valid equation with params, valid params", + eq: "a + b", + params: map[string]interface{}{ + "a": 2, + "b": 3, + }, + expected: float64(5), + err: false, + }, + { + name: "Valid equation with params, invalid params", + eq: "a + b", + params: map[string]interface{}{ + "a": 2, + // "b" is missing + }, + expected: nil, + err: true, + }, + { + name: "Invalid equation", + eq: "2 +", + params: nil, + expected: nil, + err: true, + }, + { + name: "Real equation from PMT - temperature of unused core", + eq: "( ( parameter_0 >> 8 ) & 0xff ) + ( ( parameter_0 & 0xff ) / ( 2 ** 8 ) ) - 64", + params: map[string]interface{}{ + "parameter_0": 0, + }, + expected: float64(-64), + err: false, + }, + { + name: "Real equation from PMT - temperature of working core", + eq: "( ( parameter_0 >> 8 ) & 0xff ) + ( ( parameter_0 & 0xff ) / ( 2 ** 8 ) ) - 64", + params: map[string]interface{}{ + "parameter_0": 23600, + }, + expected: float64(28.1875), + err: false, + }, + { + name: "Badly parsed real equation from PMT - temperature of working core", + eq: "( ( parameter_0 >> 8 ) & 0xff ) + ( ( parameter_0 & 0xff ) / ( 2 ** 8 ) ) - 64", + params: map[string]interface{}{ + "parameter_0": 23600, + }, + expected: nil, + err: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := eval(tt.eq, tt.params) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, result) + } + }) + } +} + +func TestGetTelemSample(t *testing.T) { + tests := []struct { + name string + s sample + buf []byte + offset uint64 + expected uint64 + err bool + }{ + { + name: "All bits set", + s: sample{Msb: 7, Lsb: 0, mask: 255}, + buf: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + offset: 0, + expected: 255, + }, + { + name: "Middle bits set", + s: sample{Msb: 5, Lsb: 2, mask: 60}, + buf: []byte{0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x3c = 00111100 in binary + offset: 0, + expected: 15, + }, + { + name: "Non-zero offset", + s: sample{Msb: 7, Lsb: 0, mask: 255}, + buf: []byte{0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + offset: 3, + expected: 255, + }, + { + name: "Single bit set", + s: sample{Msb: 4, Lsb: 4, mask: 16}, + buf: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x10 = 00010000 in binary + offset: 0, + expected: 1, + }, + { + name: "Two bytes set", + s: sample{Msb: 14, Lsb: 0, mask: 32767}, + buf: []byte{0x30, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x5c30 = 23600 in decimal + offset: 0, + expected: 23600, + }, + { + name: "Offset larger than buffer size", + s: sample{Msb: 7, Lsb: 0, mask: 255}, + buf: []byte{0x00}, + offset: 5, + expected: 0, + err: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := getTelemSample(tt.s, tt.buf, tt.offset) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, result) + } + }) + } +} + +func TestInit(t *testing.T) { + t.Run("No PmtSpec", func(t *testing.T) { + p := &IntelPMT{ + PmtSpec: "", + } + err := p.Init() + require.ErrorContains(t, err, "pmt spec is empty") + }) + + t.Run("Incorrect filepath PmtSpec", func(t *testing.T) { + p := &IntelPMT{ + PmtSpec: "/this/path/doesntexist", + } + err := p.Init() + require.ErrorContains(t, err, "provided pmt spec is not readable") + }) + + t.Run("Incorrect PmtSpec, random letters", func(t *testing.T) { + p := &IntelPMT{ + PmtSpec: "loremipsum", + } + err := p.Init() + require.ErrorContains(t, err, "provided pmt spec is not readable") + }) + + t.Run("Correct filepath PmtSpec, no pmt/can't read pmt in sysfs", func(t *testing.T) { + tmp := t.TempDir() + testFile, _ := createTempFile(t, tmp, "test-file", []byte("")) + defer testFile.Close() + + p := &IntelPMT{ + PmtSpec: testFile.Name(), + Log: testutil.Logger{}, + } + err := p.Init() + require.ErrorContains(t, err, "error while exploring pmt sysfs") + }) +} + +func TestGather(t *testing.T) { + type fields struct { + PmtSpec string + Log telegraf.Logger + pmtTelemetryFiles map[string]pmtFileInfo + pmtAggregator map[string]aggregator + pmtAggregatorInterface map[string]aggregatorInterface + pmtTransformations map[string]map[string]transformation + } + type testFile struct { + guid string + content []byte + numaNode string + } + tests := []struct { + name string + fields fields + files []testFile + expected []telegraf.Metric + wantErr bool + }{ + { + name: "Incorrect gather, results map has no value for sample", + fields: fields{ + pmtAggregator: map[string]aggregator{ + "test-guid": { + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + mask: 16, + SampleID: "test-sample-ref", + }, + }, + }, + }, + }, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + // missing sampleIDREF + }, + }, + }, + }, + }, + }, + }, + }, + }, + files: []testFile{ + {guid: "test-guid", content: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, numaNode: "0"}, + }, + wantErr: true, + }, + { + name: "Failed Gather, no equation for gathered sample", + fields: fields{ + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + {SampleName: "test-sample"}, + }, + }, + }, + }, + }, + files: []testFile{ + {guid: "test-guid", content: []byte{}}, + }, + wantErr: true, + }, + { + name: "Correct gather, 2 guids, 2 metrics returned", + fields: fields{ + pmtAggregator: map[string]aggregator{ + "test-guid": { + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + mask: 16, + SampleID: "test-sample-ref", + }, + }, + }, + }, + }, + "test-guid2": { + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype2", + Msb: 14, + Lsb: 0, + mask: 32767, + SampleID: "test-sample-ref2", + }, + }, + }, + }, + }, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + "test-guid2": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample2", + SampleGroup: "test-group2", + DatatypeIDRef: "test-datatype2", + TransformREF: "test-transform-ref2", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testv", + SampleIDREF: "test-sample-ref2", + }, + }, + }, + }, + }, + }, + }, + }, + pmtTransformations: map[string]map[string]transformation{ + "test-guid": { + "test-transform-ref": { + Transform: "testvar + 2", + }, + }, + "test-guid2": { + "test-transform-ref2": { + Transform: "( ( $testv >> 8 ) & 0xff ) + ( ( $testv & 0xff ) / ( 2 ** 8 ) ) - 64", + }, + }, + }, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "intel_pmt", + map[string]string{ + "guid": "test-guid", + "numa_node": "0", + "sample_name": "test-sample", + "sample_group": "test-group", + "datatype_idref": "test-datatype", + }, + map[string]interface{}{ + // 1 from buffer, 2 from equation + "value": float64(3), + }, + time.Time{}, + ), + testutil.MustMetric( + "intel_pmt", + map[string]string{ + "guid": "test-guid2", + "numa_node": "1", + "sample_name": "test-sample2", + "sample_group": "test-group2", + "datatype_idref": "test-datatype2", + }, + map[string]interface{}{ + "value": float64(28.1875), + }, + time.Time{}, + ), + }, + files: []testFile{ + {guid: "test-guid", content: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, numaNode: "0"}, + {guid: "test-guid2", content: []byte{0x30, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, numaNode: "1"}, + }, + wantErr: false, + }, + { + name: "Correct gather, 1 value returned", + fields: fields{ + pmtAggregator: map[string]aggregator{ + "test-guid": { + SampleGroup: []sampleGroup{ + { + SampleID: uint64(0), + Sample: []sample{ + { + DatatypeIDRef: "test-datatype", + Msb: 4, + Lsb: 4, + mask: 16, + SampleID: "test-sample-ref", + }, + }, + }, + }, + }, + }, + pmtAggregatorInterface: map[string]aggregatorInterface{ + "test-guid": { + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test-sample", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + }, + pmtTransformations: map[string]map[string]transformation{ + "test-guid": { + "test-transform-ref": { + Transform: "testvar + 2", + }, + }, + }, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "intel_pmt", + map[string]string{ + "guid": "test-guid", + "numa_node": "0", + "sample_name": "test-sample", + "sample_group": "test-group", + "datatype_idref": "test-datatype", + }, + map[string]interface{}{ + // 1 from buffer, 2 from equation + "value": float64(3), + }, + time.Time{}, + ), + }, + files: []testFile{ + {guid: "test-guid", content: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, numaNode: "0"}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &IntelPMT{ + PmtSpec: tt.fields.PmtSpec, + Log: testutil.Logger{}, + pmtAggregator: tt.fields.pmtAggregator, + pmtTelemetryFiles: tt.fields.pmtTelemetryFiles, + pmtAggregatorInterface: tt.fields.pmtAggregatorInterface, + pmtTransformations: tt.fields.pmtTransformations, + } + var acc testutil.Accumulator + telemetryFiles := make(map[string]pmtFileInfo) + tmp := t.TempDir() + for _, file := range tt.files { + testFile, _ := createTempFile(t, tmp, "test-file", file.content) + telemetryFiles[file.guid] = append(telemetryFiles[file.guid], fileInfo{ + path: testFile.Name(), + numaNode: file.numaNode, + }) + } + p.pmtTelemetryFiles = telemetryFiles + if tt.wantErr { + require.Error(t, acc.GatherError(p.Gather)) + } else { + require.NoError(t, acc.GatherError(p.Gather)) + testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime(), testutil.SortMetrics()) + } + }) + } +} diff --git a/plugins/inputs/intel_pmt/sample.conf b/plugins/inputs/intel_pmt/sample.conf new file mode 100644 index 000000000..c49da28d0 --- /dev/null +++ b/plugins/inputs/intel_pmt/sample.conf @@ -0,0 +1,18 @@ +# Intel Platform Monitoring Technology plugin exposes Intel PMT metrics available through the Intel PMT kernel space. +# This plugin ONLY supports Linux. +[[inputs.intel_pmt]] + ## Filepath to PMT XML within local copies of XML files from PMT repository. + ## The filepath should be absolute. + spec = "/home/telegraf/Intel-PMT/xml/pmt.xml" + + ## Enable metrics by their datatype. + ## See the Enabling Metrics section in README for more details. + ## If empty, all metrics are enabled. + ## When used, the alternative option samples_enabled should NOT be used. + # datatypes_enabled = [] + + ## Enable metrics by their name. + ## See the Enabling Metrics section in README for more details. + ## If empty, all metrics are enabled. + ## When used, the alternative option datatypes_enabled should NOT be used. + # samples_enabled = [] diff --git a/plugins/inputs/intel_pmt/tags_extraction.go b/plugins/inputs/intel_pmt/tags_extraction.go new file mode 100644 index 000000000..1d12530e3 --- /dev/null +++ b/plugins/inputs/intel_pmt/tags_extraction.go @@ -0,0 +1,34 @@ +//go:build linux && amd64 + +package intel_pmt + +import "regexp" + +var ( + // core in sample name - like C5_ + coreRegex = regexp.MustCompile("^C([0-9]+)_") + // CHA in sample name - like CHA43_ + chaRegex = regexp.MustCompile("^CHA([0-9]+)_") +) + +func (a *aggregatorInterface) extractTagsFromSample() { + newAggSample := aggregatorSamples{} + for _, sample := range a.AggregatorSamples.AggregatorSample { + matches := coreRegex.FindStringSubmatch(sample.SampleName) + if len(matches) == 2 { + // matches[0] is the exact match in the code + // matches[1] is the captured number (in parentheses) + sample.core = matches[1] + sample.SampleName = coreRegex.ReplaceAllString(sample.SampleName, "") + newAggSample.AggregatorSample = append(newAggSample.AggregatorSample, sample) + continue + } + matches = chaRegex.FindStringSubmatch(sample.SampleName) + if len(matches) == 2 { + sample.cha = matches[1] + sample.SampleName = chaRegex.ReplaceAllString(sample.SampleName, "") + } + newAggSample.AggregatorSample = append(newAggSample.AggregatorSample, sample) + } + a.AggregatorSamples = newAggSample +} diff --git a/plugins/inputs/intel_pmt/tags_extraction_test.go b/plugins/inputs/intel_pmt/tags_extraction_test.go new file mode 100644 index 000000000..5fffb9cf3 --- /dev/null +++ b/plugins/inputs/intel_pmt/tags_extraction_test.go @@ -0,0 +1,155 @@ +//go:build linux && amd64 + +package intel_pmt + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExtractTagsFromSample(t *testing.T) { + tests := []struct { + name string + input aggregatorInterface + expected aggregatorInterface + }{ + { + name: "Extract core number from sampleName", + input: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "C34_test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + expected: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + core: "34", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Extract cha number from sample", + input: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "CHA34_test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + expected: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + cha: "34", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "SampleName doesn't contain any matched patterns, no change in sample", + input: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + expected: aggregatorInterface{ + AggregatorSamples: aggregatorSamples{ + AggregatorSample: []aggregatorSample{ + { + SampleName: "test", + SampleGroup: "test-group", + DatatypeIDRef: "test-datatype", + TransformREF: "test-transform-ref", + TransformInputs: transformInputs{ + TransformInput: []transformInput{ + { + VarName: "testvar", + SampleIDREF: "test-sample-ref", + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.input.extractTagsFromSample() + require.Equal(t, tt.expected, tt.input) + }) + } +} diff --git a/plugins/inputs/intel_pmt/xml_parser.go b/plugins/inputs/intel_pmt/xml_parser.go new file mode 100644 index 000000000..79329dfbb --- /dev/null +++ b/plugins/inputs/intel_pmt/xml_parser.go @@ -0,0 +1,277 @@ +//go:build linux && amd64 + +package intel_pmt + +import ( + "encoding/xml" + "errors" + "fmt" + "io" + "os" + "path/filepath" +) + +type pmt struct { + XMLName xml.Name `xml:"pmt"` + Mappings mappings `xml:"mappings"` +} +type mappings struct { + XMLName xml.Name `xml:"mappings"` + Mapping []mapping `xml:"mapping"` +} + +type mapping struct { + XMLName xml.Name `xml:"mapping"` + GUID string `xml:"guid,attr"` + XMLSet xmlset `xml:"xmlset"` +} + +type xmlset struct { + XMLName xml.Name `xml:"xmlset"` + Basedir string `xml:"basedir"` + Aggregator string `xml:"aggregator"` + AggregatorInterface string `xml:"aggregatorinterface"` +} + +type aggregator struct { + XMLName xml.Name `xml:"Aggregator"` + Name string `xml:"name"` + SampleGroup []sampleGroup `xml:"SampleGroup"` +} + +type sampleGroup struct { + XMLName xml.Name `xml:"SampleGroup"` + SampleID uint64 `xml:"sampleID,attr"` + Sample []sample `xml:"sample"` +} + +type sample struct { + XMLName xml.Name `xml:"sample"` + SampleName string `xml:"name,attr"` + DatatypeIDRef string `xml:"datatypeIDREF,attr"` + SampleID string `xml:"sampleID,attr"` + Lsb uint64 `xml:"lsb"` + Msb uint64 `xml:"msb"` + + mask uint64 +} + +type aggregatorInterface struct { + XMLName xml.Name `xml:"AggregatorInterface"` + Transformations transformations `xml:"TransFormations"` + AggregatorSamples aggregatorSamples `xml:"AggregatorSamples"` +} + +type transformations struct { + XMLName xml.Name `xml:"TransFormations"` + Transformation []transformation `xml:"TransFormation"` +} + +type transformation struct { + XMLName xml.Name `xml:"TransFormation"` + Name string `xml:"name,attr"` + TransformID string `xml:"transformID,attr"` + Transform string `xml:"transform"` +} + +type aggregatorSamples struct { + XMLName xml.Name `xml:"AggregatorSamples"` + AggregatorSample []aggregatorSample `xml:"T_AggregatorSample"` +} + +type aggregatorSample struct { + XMLName xml.Name `xml:"T_AggregatorSample"` + SampleName string `xml:"sampleName,attr"` + SampleGroup string `xml:"sampleGroup,attr"` + DatatypeIDRef string `xml:"datatypeIDREF,attr"` + TransformInputs transformInputs `xml:"TransFormInputs"` + TransformREF string `xml:"transformREF"` + + core string + cha string +} + +type transformInputs struct { + XMLName xml.Name `xml:"TransFormInputs"` + TransformInput []transformInput `xml:"TransFormInput"` +} + +type transformInput struct { + XMLName xml.Name `xml:"TransFormInput"` + VarName string `xml:"varName,attr"` + SampleIDREF string `xml:"sampleIDREF"` +} + +type sourceReader interface { + getReadCloser(source string) (io.ReadCloser, error) +} + +type fileReader struct{} + +func (fileReader) getReadCloser(source string) (io.ReadCloser, error) { + return os.Open(source) +} + +// parseXMLs reads and parses PMT XMLs. +// +// This method retrieves all metadata about known GUIDs from PmtSpec. +// Then, it explores PMT sysfs to find all readable "telem" files and their GUIDs. +// It then matches found (readable) system GUIDs with GUIDs from metadata and +// reads corresponding sets of XMLs. +// +// Returns: +// +// error - if PMT spec is empty, if exploring PMT sysfs fails, or if reading XMLs fails. +func (p *IntelPMT) parseXMLs() error { + err := parseXML(p.PmtSpec, p.reader, &p.pmtMetadata) + if err != nil { + return err + } + if len(p.pmtMetadata.Mappings.Mapping) == 0 { + return errors.New("pmt XML provided contains no mappings") + } + + err = p.readXMLs() + if err != nil { + return err + } + + p.pmtTransformations = make(map[string]map[string]transformation) + for guid := range p.pmtTelemetryFiles { + p.pmtTransformations[guid] = make(map[string]transformation) + for _, transform := range p.pmtAggregatorInterface[guid].Transformations.Transformation { + p.pmtTransformations[guid][transform.TransformID] = transform + } + } + return nil +} + +// readXMLs function reads all XMLs for found GUIDs. +// +// This method reads two required XMLs for each found GUID, +// checks if any of the provided filtering metrics were not found, +// and checks if there is at least one non-empty XML set. +// +// Returns: +// +// error - error if reading operation failed or if all XMLs are empty. +func (p *IntelPMT) readXMLs() error { + p.pmtAggregator = make(map[string]aggregator) + p.pmtAggregatorInterface = make(map[string]aggregatorInterface) + dtMetricsFound := make(map[string]bool) + sampleFilterFound := make(map[string]bool) + for guid := range p.pmtTelemetryFiles { + err := p.getAllXMLData(guid, dtMetricsFound, sampleFilterFound) + if err != nil { + return fmt.Errorf("failed reading XMLs: %w", err) + } + } + for _, dt := range p.DatatypeFilter { + if _, ok := dtMetricsFound[dt]; !ok { + p.Log.Warnf("Configured datatype metric %q has not been found", dt) + } + } + for _, sm := range p.SampleFilter { + if _, ok := sampleFilterFound[sm]; !ok { + p.Log.Warnf("Configured sample metric %q has not been found", sm) + } + } + err := p.verifyNoEmpty() + if err != nil { + return fmt.Errorf("XMLs empty: %w", err) + } + return nil +} + +// getAllXMLData retrieves two XMLs for given GUID. +// +// This method reads where to find the Aggregator and Aggregator interface XMLs +// from pmt metadata and reads found XMLs. +// This method also filters read XMLs before saving them +// and extracts additional tags from the data. +// +// Parameters: +// +// guid - GUID saying which XMLs should be read. +// dtMetricsFound - a map of found datatype metrics for all GUIDs. +// smFound - a map of found sample names for all GUIDs. +// +// Returns: +// +// error - if reading XML has failed. +func (p *IntelPMT) getAllXMLData(guid string, dtMetricsFound map[string]bool, smFound map[string]bool) error { + for _, mapping := range p.pmtMetadata.Mappings.Mapping { + if mapping.GUID == guid { + basedir := mapping.XMLSet.Basedir + guid := mapping.GUID + var aggSource, aggInterfaceSource string + + aggSource = filepath.Join(p.pmtBasePath, basedir, mapping.XMLSet.Aggregator) + aggInterfaceSource = filepath.Join(p.pmtBasePath, basedir, mapping.XMLSet.AggregatorInterface) + + tAgg := aggregator{} + tAggInterface := aggregatorInterface{} + + err := parseXML(aggSource, p.reader, &tAgg) + if err != nil { + return fmt.Errorf("failed reading aggregator XML: %w", err) + } + err = parseXML(aggInterfaceSource, p.reader, &tAggInterface) + if err != nil { + return fmt.Errorf("failed reading aggregator interface XML: %w", err) + } + if len(p.DatatypeFilter) > 0 { + tAgg.filterAggregatorByDatatype(p.DatatypeFilter) + tAggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, dtMetricsFound) + } + if len(p.SampleFilter) > 0 { + tAgg.filterAggregatorBySampleName(p.SampleFilter) + tAggInterface.filterAggInterfaceBySampleName(p.SampleFilter, smFound) + } + tAgg.calculateMasks() + p.pmtAggregator[guid] = tAgg + tAggInterface.extractTagsFromSample() + p.pmtAggregatorInterface[guid] = tAggInterface + } + } + return nil +} + +func (a *aggregator) calculateMasks() { + for i := range a.SampleGroup { + for j, sample := range a.SampleGroup[i].Sample { + mask := computeMask(sample.Msb, sample.Lsb) + a.SampleGroup[i].Sample[j].mask = mask + } + } +} + +func computeMask(msb uint64, lsb uint64) uint64 { + msbMask := uint64(0xffffffffffffffff) & ((1 << (msb + 1)) - 1) + lsbMask := uint64(0xffffffffffffffff) & (1<John30`), err: nil}, + v: &Person{}, + err: false, + }, + { + name: "Empty XML", + source: "test", + sr: mockReader{data: []byte(``), err: nil}, + v: &Person{}, + err: true, + }, + { + name: "Nil interface parameter", + source: "test", + sr: mockReader{data: []byte(`John30`), err: nil}, + v: nil, + err: true, + }, + { + name: "Error from SourceReader", + source: "test", + sr: mockReader{data: nil, err: errors.New("mock error")}, + v: &Person{}, + err: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := parseXML(tt.source, tt.sr, tt.v) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestReadXMLs(t *testing.T) { + t.Run("Test single PMT GUID, no XMLs found", func(t *testing.T) { + p := &IntelPMT{ + pmtMetadata: &pmt{ + Mappings: mappings{ + Mapping: []mapping{ + {GUID: "abc"}, + }, + }, + }, + pmtTelemetryFiles: map[string]pmtFileInfo{ + "abc": []fileInfo{{path: "doesn't-exist"}}, + }, + reader: fileReader{}, + } + err := p.readXMLs() + require.Error(t, err) + require.ErrorContains(t, err, "failed reading XMLs") + }) + + t.Run("Test single PMT GUID, aggregator interface empty", func(t *testing.T) { + tmp := t.TempDir() + + bufAgg := []byte("") + testAgg, aggName := createTempFile(t, tmp, "test-agg", bufAgg) + defer testAgg.Close() + + bufAggInterface := []byte("") + testAggInterface, aggInterfaceName := createTempFile(t, tmp, "test-aggInterface", bufAggInterface) + defer testAggInterface.Close() + + p := &IntelPMT{ + pmtBasePath: tmp, + pmtMetadata: &pmt{ + Mappings: mappings{ + Mapping: []mapping{ + { + GUID: "abc", + XMLSet: xmlset{ + Aggregator: aggName.Name(), + AggregatorInterface: aggInterfaceName.Name(), + }, + }, + }, + }, + }, + // This is done just so we enter the loop + pmtTelemetryFiles: map[string]pmtFileInfo{ + "abc": []fileInfo{{path: testAgg.Name()}}, + }, + reader: fileReader{}, + } + + err := p.readXMLs() + require.ErrorContains(t, err, "all aggregator interface XMLs are empty") + }) +}