pdh: convert all raw time counter types to seconds (#2776)

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Otto Kröpke
2026-10-16 22:54:44 +02:00
committed by GitHub
co-authored by Claude
parent a9d5bbef48
commit da7bebc6b3
23 changed files with 313 additions and 246 deletions
+5 -4
View File
@@ -141,10 +141,11 @@ exercised a live Windows role.
appropriate. Raw PDH values can be cumulative numerators, ticks or counts,
even when the Windows counter name says average, percentage or seconds.
Convert units explicitly; expose the base/operation count needed for ratios.
The raw value of a `PERF_AVERAGE_TIMER` counter is in ticks of its time
base: in new code, divide it by `pdh.Collector.Frequency` instead of a fixed
factor. The disk and `smbclient` collectors still use the fixed 1e-7
(10 MHz) factor.
With `pdh.CounterTypeRaw`, `internal/pdh` converts the time types to seconds:
the 100 ns timers and `PERF_COUNTER_100NS_QUEUELEN_TYPE` by dividing by
`pdh.TicksPerSecond`, and `PERF_AVERAGE_TIMER` by dividing by the counter's
own time base (its `,secondvalue` stays the operation count). Don't scale
these again in a collector.
Never publish an invalid sample as zero: that can look like a counter reset.
- Keep the typed PDH collectors and `perfdata` tags. `internal/pdh` passes
instance names through unchanged, so a `#N` in a name always comes from
+1 -1
View File
@@ -369,7 +369,7 @@ The `vm` label holds the instance name of the `Hyper-V Virtual SMB` performance
The `Latency`, `Lower Latency`, `Queue Length` and `Lower Queue Length` performance counters
are averages that Windows computes from two samples. Their raw values only grow: the sum of
all IO latencies in 100ns ticks, and the raw queue length values are the same as the raw
all IO latencies in ticks of the counter time base (usually 10 MHz), and the raw queue length values are the same as the raw
latency values. windows_exporter publishes them as the `*_io_latency_seconds_total` counters:
```promql
+18 -10
View File
@@ -218,16 +218,24 @@ keep counting up. If the multiplier is the reciprocal of an integer, like `0.001
the value is divided by that integer instead, which avoids rounding artifacts
such as `0.009000000000000001`. If not specified, the value is exported as is.
With the object `type` `raw`, the default, some counters are already converted
to seconds, so do not scale them again: `PERF_100NSEC_TIMER` and
`PERF_PRECISION_100NS_TIMER` counters such as `% Idle Time` are exported in
seconds, and `PERF_ELAPSED_TIME` counters such as `Process\Elapsed Time` as
seconds since the start. `PERF_100NSEC_TIMER_INV` counters such as
`% Processor Time` are not converted and arrive in 100 ns ticks of inactive time
(for `% Processor Time`, the idle time); a multiplier of `1e-7` converts them to
seconds. `PERF_AVERAGE_TIMER` counters such as `Avg. Disk sec/Read` count ticks
of the performance timer, whose frequency depends on the host, so a fixed
multiplier does not fit them; use the `formatted` type for them.
With the object `type` `raw`, the default, time counters are already converted
to seconds, so do not scale them again:
- the 100 ns timers (`PERF_100NSEC_TIMER`, `PERF_100NSEC_TIMER_INV`,
`PERF_100NSEC_MULTI_TIMER`, `PERF_100NSEC_MULTI_TIMER_INV` and
`PERF_PRECISION_100NS_TIMER`), such as `% Idle Time`;
- `PERF_COUNTER_100NS_QUEUELEN_TYPE` counters, such as `Avg. Data Queue Length`
of `SMB Client Shares`, as queue length times seconds, so their rate is the
average queue length;
- `PERF_AVERAGE_TIMER` counters, such as `Avg. Disk sec/Read`, as the summed
duration in seconds, using the time base of the counter. Their second value
is the number of operations;
- `PERF_ELAPSED_TIME` counters, such as `Process\Elapsed Time`, as seconds since
the start.
The raw value of an `_INV` counter is the inactive time: for `% Processor Time`,
that is the idle time, not the busy time. `MULTI_TIMER` values are summed over
all units of the counter, so they can grow faster than wall time.
This key is optional.
+3
View File
@@ -931,6 +931,9 @@ These metrics keep their names, but 0.31 reported wrong values. Graphs show a st
#### performancecounter and PDH-based collectors
- In the `performancecounter` collector, counters without an explicit `type` now always get the type of the underlying Windows counter. In 0.31 a mixed object could report counters as gauges, or the other way around, and the type could change between scrapes. If Prometheus scrapes with OpenMetrics, a counter that 0.31 reported as a gauge is now stored with a `_total` suffix, which the compatibility rules can't cover.
- Raw `PERF_100NSEC_TIMER_INV`, `PERF_100NSEC_MULTI_TIMER` and `PERF_100NSEC_MULTI_TIMER_INV` counters, such as `% Processor Time` read with the `raw` type, are exported in seconds, like the other 100 ns timers. In 0.31 they were exported in 100 ns ticks, so values read this way are 10,000,000 times smaller; remove any query that divided them by `1e7`. The raw value of an `_INV` counter such as `% Processor Time` is the inactive (idle) time, not the busy time; `PERF_100NSEC_MULTI_TIMER(_INV)` values are summed over all units of the counter.
- Raw `PERF_AVERAGE_TIMER` counters, such as `Avg. Disk sec/Read`, and raw `PERF_COUNTER_100NS_QUEUELEN_TYPE` counters, such as `Avg. Data Queue Length`, read with the `performancecounter` collector are exported in seconds and as counters. In 0.31 they were exported in ticks and as gauges. `PERF_AVERAGE_TIMER` uses the time base of the counter, which is 10 MHz on most hosts. The latencies of the `physical_disk`, `smbclient` and `hyperv` collectors now use it too; on a host with another time base, 0.31 reported them wrong.
- Values converted from 100 ns ticks to seconds, in the `performancecounter`, `cpu`, `process`, `physical_disk`, `logical_disk`, `smbclient`, `container` and `hyperv` collectors, are now rounded correctly and can differ from 0.31 in the last digit. A counter that doesn't move across the upgrade can drop by that last digit once, which `rate()` and `increase()` count as a reset for one window.
- Instances without a valid sample are left out. In 0.31 they were reported as `0`, which looked like a counter reset.
- An explicit instance list such as `instances: ["C:", "D:"]` now returns one series per instance. In 0.31 all listed instances were merged into one series.
- When Windows reports the same instance name more than once, for example every `svchost` process of the `Process` object, the `performancecounter`, `netframework`, `hyperv` and `exchange` (ADAccess processes) collectors publish each occurrence as a separate series. The first keeps the name, later ones get the suffix `#1`, `#2` and so on (`exchange` keeps its `_2`, `_3` label suffixes), which depends on the order of the sample and does not identify the instance across scrapes. The other PDH-based collectors read objects that Windows names uniquely; if one still reports a name twice, they publish the first instance with valid data and log a warning. In 0.31 the occurrences overwrote each other.
+3 -4
View File
@@ -21,10 +21,9 @@ one.
`PERF_AVERAGE_TIMER` (`AverageTimer32`). The raw value is the summed
duration in ticks of the counter's time base (`PdhGetCounterTimeBase`,
10 MHz on the test host, same as `QueryPerformanceFrequency`); the base
(`,secondvalue`) is the number of operations. `pdh.CounterTypeRaw` does not
convert this type; `internal/pdh` reads its time base, and the collector
divides the raw value by `pdh.Collector.Frequency` to get seconds. Do not
hardcode a scale factor.
(`,secondvalue`) is the number of operations. `internal/pdh` divides the
raw value by the time base, so the collector gets seconds. Do not scale it
again.
- Sample after about one hour: `Average Config Database Query Time` raw
11521176, base 306, which is 1.152 s in total, 3.8 ms per query.
`Federation Metadata Requests` was 1, all authentication counters 0.
+17 -23
View File
@@ -409,19 +409,13 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
return fmt.Errorf("failed to collect ADFS metrics: %w", types.ErrNoDataUnexpected)
}
// The query times are PERF_AVERAGE_TIMER counters: the raw value is the
// summed duration in ticks of the counter's time base.
artifactDBFrequency, _ := c.perfDataCollector.Frequency(counterArtifactDBQueryTime)
configDBFrequency, _ := c.perfDataCollector.Frequency(counterConfigDBQueryTime)
c.collectMetrics(ch, c.perfDataObject[0], artifactDBFrequency, configDBFrequency)
c.collectMetrics(ch, c.perfDataObject[0])
return nil
}
// collectMetrics sends the metrics of data. A query time is only sent if the
// frequency of its counter is known.
func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, data perfDataCounterValues, artifactDBFrequency, configDBFrequency int64) {
// collectMetrics sends the metrics of data.
func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, data perfDataCounterValues) {
ch <- prometheus.MustNewConstMetric(
c.adLoginConnectionFailures,
prometheus.CounterValue,
@@ -662,13 +656,13 @@ func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, data perfDataCou
data.ArtifactDBQueries,
)
if artifactDBFrequency > 0 {
ch <- prometheus.MustNewConstMetric(
c.artifactDBQueryTime,
prometheus.CounterValue,
data.ArtifactDBQueryTimeTicks/float64(artifactDBFrequency),
)
}
// PERF_AVERAGE_TIMER: the summed query time, converted to seconds by
// internal/pdh; the second value counts the queries.
ch <- prometheus.MustNewConstMetric(
c.artifactDBQueryTime,
prometheus.CounterValue,
data.ArtifactDBQueryTimeSeconds,
)
ch <- prometheus.MustNewConstMetric(
c.configDBFailures,
@@ -682,13 +676,13 @@ func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, data perfDataCou
data.ConfigDBQueries,
)
if configDBFrequency > 0 {
ch <- prometheus.MustNewConstMetric(
c.configDBQueryTime,
prometheus.CounterValue,
data.ConfigDBQueryTimeTicks/float64(configDBFrequency),
)
}
// PERF_AVERAGE_TIMER: the summed query time, converted to seconds by
// internal/pdh; the second value counts the queries.
ch <- prometheus.MustNewConstMetric(
c.configDBQueryTime,
prometheus.CounterValue,
data.ConfigDBQueryTimeSeconds,
)
ch <- prometheus.MustNewConstMetric(
c.federationMetadataRequests,
+31 -80
View File
@@ -25,100 +25,51 @@ import (
"github.com/stretchr/testify/require"
)
// TestCollectDatabaseQueryTimes checks that the raw ticks of the database
// query time counters are converted to seconds with the counter's time base,
// and that the query counts are exported.
// TestCollectDatabaseQueryTimes checks that the database query times, which
// internal/pdh converts to seconds, and the query counts are exported.
func TestCollectDatabaseQueryTimes(t *testing.T) {
t.Parallel()
data := perfDataCounterValues{
ArtifactDBQueryTimeTicks: 2_500_000,
ArtifactDBQueries: 5,
ConfigDBQueryTimeTicks: 11_521_176,
ConfigDBQueries: 306,
ArtifactDBQueryTimeSeconds: 0.25,
ArtifactDBQueries: 5,
ConfigDBQueryTimeSeconds: 1.1521176,
ConfigDBQueries: 306,
}
for _, tc := range []struct {
name string
artifactDBFrequency int64
configDBFrequency int64
expected map[string]float64
absent []string
}{
{
name: "10 MHz",
artifactDBFrequency: 10_000_000,
configDBFrequency: 10_000_000,
expected: map[string]float64{
"windows_adfs_db_artifact_query_time_seconds_total": 0.25,
"windows_adfs_db_artifact_queries_total": 5,
"windows_adfs_db_config_query_time_seconds_total": 1.1521176,
"windows_adfs_db_config_queries_total": 306,
},
},
{
name: "other frequency",
artifactDBFrequency: 2_500_000,
configDBFrequency: 3_000_000,
expected: map[string]float64{
"windows_adfs_db_artifact_query_time_seconds_total": 1,
"windows_adfs_db_config_query_time_seconds_total": 3.840392,
},
},
{
name: "unknown frequency",
expected: map[string]float64{
"windows_adfs_db_artifact_queries_total": 5,
"windows_adfs_db_config_queries_total": 306,
},
absent: []string{
"windows_adfs_db_artifact_query_time_seconds_total",
"windows_adfs_db_config_query_time_seconds_total",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
expected := map[string]float64{
"windows_adfs_db_artifact_query_time_seconds_total": 0.25,
"windows_adfs_db_artifact_queries_total": 5,
"windows_adfs_db_config_query_time_seconds_total": 1.1521176,
"windows_adfs_db_config_queries_total": 306,
}
c := New(nil)
c.buildDescriptors()
c := New(nil)
c.buildDescriptors()
registry := prometheus.NewPedanticRegistry()
require.NoError(t, registry.Register(metricCollector{
collector: c,
data: data,
artifactDBFrequency: tc.artifactDBFrequency,
configDBFrequency: tc.configDBFrequency,
}))
registry := prometheus.NewPedanticRegistry()
require.NoError(t, registry.Register(metricCollector{collector: c, data: data}))
families, err := registry.Gather()
require.NoError(t, err)
families, err := registry.Gather()
require.NoError(t, err)
byName := make(map[string]*dto.MetricFamily, len(families))
for _, family := range families {
byName[family.GetName()] = family
}
byName := make(map[string]*dto.MetricFamily, len(families))
for _, family := range families {
byName[family.GetName()] = family
}
for name, value := range tc.expected {
family, ok := byName[name]
require.True(t, ok, "missing metric %s", name)
require.Equal(t, dto.MetricType_COUNTER, family.GetType(), name)
require.Len(t, family.GetMetric(), 1, name)
require.InDelta(t, value, family.GetMetric()[0].GetCounter().GetValue(), 1e-9, name)
}
for _, name := range tc.absent {
require.NotContains(t, byName, name)
}
})
for name, value := range expected {
family, ok := byName[name]
require.True(t, ok, "missing metric %s", name)
require.Equal(t, dto.MetricType_COUNTER, family.GetType(), name)
require.Len(t, family.GetMetric(), 1, name)
require.InDelta(t, value, family.GetMetric()[0].GetCounter().GetValue(), 0, name)
}
}
type metricCollector struct {
collector *Collector
data perfDataCounterValues
artifactDBFrequency int64
configDBFrequency int64
collector *Collector
data perfDataCounterValues
}
func (c metricCollector) Describe(ch chan<- *prometheus.Desc) {
@@ -126,5 +77,5 @@ func (c metricCollector) Describe(ch chan<- *prometheus.Desc) {
}
func (c metricCollector) Collect(ch chan<- prometheus.Metric) {
c.collector.collectMetrics(ch, c.data, c.artifactDBFrequency, c.configDBFrequency)
c.collector.collectMetrics(ch, c.data)
}
+2 -9
View File
@@ -17,19 +17,12 @@
package adfs
// PERF_AVERAGE_TIMER counters. The raw value is the summed query time in ticks
// of the counter's time base, the second value the number of queries.
const (
counterArtifactDBQueryTime = "Average Artifact Database Query Time"
counterConfigDBQueryTime = "Average Config Database Query Time"
)
type perfDataCounterValues struct {
AdLoginConnectionFailures float64 `perfdata:"AD Login Connection Failures"`
ArtifactDBFailures float64 `perfdata:"Artifact Database Connection Failures"`
ArtifactDBQueryTimeTicks float64 `perfdata:"Average Artifact Database Query Time"`
ArtifactDBQueryTimeSeconds float64 `perfdata:"Average Artifact Database Query Time"`
ArtifactDBQueries float64 `perfdata:"Average Artifact Database Query Time,secondvalue"`
ConfigDBQueryTimeTicks float64 `perfdata:"Average Config Database Query Time"`
ConfigDBQueryTimeSeconds float64 `perfdata:"Average Config Database Query Time"`
ConfigDBQueries float64 `perfdata:"Average Config Database Query Time,secondvalue"`
CertificateAuthentications float64 `perfdata:"Certificate Authentications"`
ConfigDBFailures float64 `perfdata:"Configuration Database Connection Failures"`
+6 -6
View File
@@ -671,7 +671,7 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe
ch <- prometheus.MustNewConstMetric(
c.runtimeTotal,
prometheus.CounterValue,
float64(containerStats.Processor.TotalRuntime100ns)*pdh.TicksToSecondScaleFactor,
float64(containerStats.Processor.TotalRuntime100ns)/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
@@ -679,7 +679,7 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe
ch <- prometheus.MustNewConstMetric(
c.runtimeUser,
prometheus.CounterValue,
float64(containerStats.Processor.RuntimeUser100ns)*pdh.TicksToSecondScaleFactor,
float64(containerStats.Processor.RuntimeUser100ns)/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
@@ -687,7 +687,7 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe
ch <- prometheus.MustNewConstMetric(
c.runtimeKernel,
prometheus.CounterValue,
float64(containerStats.Processor.RuntimeKernel100ns)*pdh.TicksToSecondScaleFactor,
float64(containerStats.Processor.RuntimeKernel100ns)/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
@@ -939,7 +939,7 @@ func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID
ch <- prometheus.MustNewConstMetric(
c.runtimeTotal,
prometheus.CounterValue,
(float64(jobInfo.BasicInfo.TotalKernelTime)+float64(jobInfo.BasicInfo.TotalUserTime))*pdh.TicksToSecondScaleFactor,
(float64(jobInfo.BasicInfo.TotalKernelTime)+float64(jobInfo.BasicInfo.TotalUserTime))/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
@@ -947,7 +947,7 @@ func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID
ch <- prometheus.MustNewConstMetric(
c.runtimeUser,
prometheus.CounterValue,
float64(jobInfo.BasicInfo.TotalUserTime)*pdh.TicksToSecondScaleFactor,
float64(jobInfo.BasicInfo.TotalUserTime)/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
@@ -955,7 +955,7 @@ func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID
ch <- prometheus.MustNewConstMetric(
c.runtimeKernel,
prometheus.CounterValue,
float64(jobInfo.BasicInfo.TotalKernelTime)*pdh.TicksToSecondScaleFactor,
float64(jobInfo.BasicInfo.TotalKernelTime)/pdh.TicksPerSecond,
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
)
+3 -3
View File
@@ -18,9 +18,9 @@ Hyper-V guest with 12 logical processors in one processor group.
so `Collect` holds the previous value of `time_seconds_total` and
`cstate_seconds_total` for drops below `maxTimeDrop` (1 s).
- `% Processor Time` and `% Priority Time` are `PERF_100NSEC_TIMER_INV`. The
raw value is cumulative idle (non-busy) time in 100 ns ticks, not busy time,
and `internal/pdh` does not scale this type to seconds. In the sample, the
raw value of `% Processor Time` equals `% Idle Time`.
raw value is cumulative idle (non-busy) time in 100 ns ticks, not busy time.
`internal/pdh` scales it to seconds like the other 100 ns timers. In the
sample, the raw value of `% Processor Time` equals `% Idle Time`.
- `% Processor Utility`, `% Processor Performance` and `% Privileged Utility`
are `PERF_AVERAGE_BULK`. Raw and base are both cumulative. The displayed
value is delta raw / delta base and is already a percentage (it can exceed
+1 -1
View File
@@ -512,7 +512,7 @@ func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric, scrape
ch <- scrape.MustNewConstMetric(
c.gpuEngineRunningTime,
prometheus.CounterValue,
data.RunningTime/10_000_000,
data.RunningTime/pdh.TicksPerSecond,
instance.Pid, instance.Luid, device.ID, instance.Phys, instance.Eng, instance.Engtype,
)
}
@@ -205,19 +205,20 @@ func (c *Collector) publishVirtualStorageDevice(ch chan<- prometheus.Metric, scr
data.Name,
)
// The raw value of a PERF_AVERAGE_TIMER counter is the sum of all IO latencies in
// 100ns ticks; the operation count it is averaged over is Throughput.
// The value of a PERF_AVERAGE_TIMER counter is the sum of all IO latencies,
// converted to seconds by internal/pdh; the operation count it is averaged
// over is Throughput.
ch <- scrape.MustNewConstMetric(
c.virtualStorageDeviceIOLatency,
prometheus.CounterValue,
data.VirtualStorageDeviceLatency*pdh.TicksToSecondScaleFactor,
data.VirtualStorageDeviceLatency,
data.Name,
)
ch <- scrape.MustNewConstMetric(
c.virtualStorageDeviceLowerIOLatency,
prometheus.CounterValue,
data.VirtualStorageDeviceLowerLatency*pdh.TicksToSecondScaleFactor,
data.VirtualStorageDeviceLowerLatency,
data.Name,
)
}
+3 -5
View File
@@ -19,11 +19,9 @@ multi-instance. These notes were recorded on a Windows Server 2025
were all 9858832; the write counters were all 11319205, and
`Avg. Disk sec/Transfer` (21178037) is their sum. The raw value is the
cumulative busy time in 100 ns ticks. `internal/pdh` converts the
`PERF_PRECISION_100NS_TIMER` counters to seconds. The queue length and
`PERF_AVERAGE_TIMER` values arrive in ticks and need the 1e-7 factor; the
counter time base is 10 MHz here. The queue length counters duplicate the
busy time, so the collector no longer reads them; the average queue length is
`rate(windows_logical_disk_{read,write}_seconds_total[5m])`.
`PERF_PRECISION_100NS_TIMER` counters to seconds. The queue length counters
duplicate the busy time, so the collector no longer reads them; the average
queue length is `rate(windows_logical_disk_{read,write}_seconds_total[5m])`.
- `Avg. Disk sec/Read`, `/Write` and `/Transfer` are `PERF_AVERAGE_TIMER`.
Their base is the operation count and equals `Disk Reads/sec` (3073),
`Disk Writes/sec` (1698) and their sum (4771). Their raw values duplicate the
+2 -2
View File
@@ -17,8 +17,8 @@ multi-instance. These notes were recorded on a Windows Server 2025
value is the cumulative busy time in 100 ns ticks.
- `internal/pdh` converts the `PERF_PRECISION_100NS_TIMER` counters
(`% Disk Read Time`, `% Disk Write Time`, `% Idle Time`) to seconds.
`PERF_AVERAGE_TIMER` values arrive in ticks of the 10 MHz counter time base
and need the 1e-7 factor.
It also converts the `PERF_AVERAGE_TIMER` values (`Avg. Disk sec/*`) to
seconds with the counter time base, which was 10 MHz on JOK-PC.
- The second raw value of `% Idle Time` is the sample time as a UTC FILETIME;
PDH divides by its delta. Even with that divisor the idle delta of a busy
disk exceeded the elapsed time by up to 43% between 1 s samples on a
@@ -295,21 +295,21 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
ch <- prometheus.MustNewConstMetric(
c.readLatency,
prometheus.CounterValue,
data.AvgDiskSecPerRead*pdh.TicksToSecondScaleFactor,
data.AvgDiskSecPerRead,
disk_number,
)
ch <- prometheus.MustNewConstMetric(
c.writeLatency,
prometheus.CounterValue,
data.AvgDiskSecPerWrite*pdh.TicksToSecondScaleFactor,
data.AvgDiskSecPerWrite,
disk_number,
)
ch <- prometheus.MustNewConstMetric(
c.readWriteLatency,
prometheus.CounterValue,
data.AvgDiskSecPerTransfer*pdh.TicksToSecondScaleFactor,
data.AvgDiskSecPerTransfer,
disk_number,
)
}
+5 -6
View File
@@ -42,11 +42,10 @@ const Name = "process"
const (
// windowsEpoch is the FILETIME of the Unix epoch, in 100ns intervals since 1601.
windowsEpoch int64 = 116444736000000000
// ticksPerSecond is the number of 100ns intervals per second.
// ticksPerSecond is the number of 100ns intervals per second. Dividing by it,
// like internal/pdh does, keeps the CPU time values bit-identical to the
// Process counter set.
ticksPerSecond = 1e7
// secondsPerTick converts 100ns intervals to seconds. Multiplying by it, like the perflib
// reader did, keeps the CPU time values bit-identical to the Process counter set.
secondsPerTick = 1 / ticksPerSecond
)
type Config struct {
@@ -486,14 +485,14 @@ func (c *Collector) collectProcess(ch chan<- prometheus.Metric, scrape *labelcac
ch <- scrape.MustNewConstMetric(
c.cpuTimeTotal,
prometheus.CounterValue,
float64(process.kernelTime)*secondsPerTick,
float64(process.kernelTime)/ticksPerSecond,
name, pid, "privileged",
)
ch <- scrape.MustNewConstMetric(
c.cpuTimeTotal,
prometheus.CounterValue,
float64(process.userTime)*secondsPerTick,
float64(process.userTime)/ticksPerSecond,
name, pid, "user",
)
+9 -5
View File
@@ -13,12 +13,16 @@ host has connected to. These notes were recorded on a Windows Server 2025
- `Current Data Queue Length` is the only gauge ("the current number of read
or write requests outstanding"). The `/sec` counters are cumulative counts.
- `Avg. Data/Read/Write Queue Length` are `PERF_COUNTER_100NS_QUEUELEN_TYPE`:
the raw value is accumulated queue time in 100 ns ticks. `internal/pdh` does
not convert this type, so the collector applies the 1e-7 factor.
the raw value is accumulated queue time in 100 ns ticks, which
`internal/pdh` converts to seconds.
- `Avg. sec/Read`, `Avg. sec/Write` and `Avg. sec/Data Request` are
`PERF_AVERAGE_TIMER`. The first value is the summed latency in ticks of the
10 MHz counter time base and the base is the request count; the collector
reads only the first value.
`PERF_AVERAGE_TIMER`. The first value is the summed latency, which
`internal/pdh` converts to seconds with the counter time base, and the base
is the request count; the collector reads only the first value. The raw
sum is 32 bits, so the collector converts it back to ticks with
`pdh.Collector.Frequency`, extends it past the 2^32 wrap with
`utils.Counter32` and divides again. Keep that round trip: extending the
seconds directly truncates them and adds 2^32 seconds per wrap.
- `Data Bytes/sec`, `Read Bytes/sec` and `Write Bytes/sec` are bytes
(`PERF_COUNTER_BULK_COUNT`).
+43 -13
View File
@@ -20,6 +20,7 @@ package smbclient
import (
"fmt"
"log/slog"
"math"
"strings"
"time"
@@ -111,6 +112,12 @@ func restartSignals(data *perfDataCounterValues) [numRestartSignals]float64 {
}
}
// timerCounters32 names the PERF_AVERAGE_TIMER counters in the order of
// fields32.
//
//nolint:gochecknoglobals
var timerCounters32 = [numTimers32]string{"Avg. sec/Read", "Avg. sec/Write", "Avg. sec/Data Request"}
// fields32 returns the 32-bit counters: first the PERF_AVERAGE_TIMER
// numerators, then the request counters (PERF_COUNTER_COUNTER).
func fields32(data *perfDataCounterValues) [numTimers32 + numCounts32]*float64 {
@@ -275,6 +282,16 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
c.generation++
// internal/pdh converts the timer sums to seconds; extendCounters32 needs
// their ticks.
var frequencies [numTimers32]float64
for i, name := range timerCounters32 {
if frequency, ok := c.perfDataCollector.Frequency(name); ok {
frequencies[i] = float64(frequency)
}
}
for _, data := range c.perfDataObject {
serverValue, shareValue, ok := parseInstanceName(data.Name)
if !ok {
@@ -289,50 +306,50 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
continue
}
c.extendCounters32(&data)
c.extendCounters32(&data, frequencies)
// Request time spent on queue. Convert from ticks to seconds.
// Request time spent on queue, in seconds.
ch <- prometheus.MustNewConstMetric(
c.requestQueueSecsTotal,
prometheus.CounterValue,
data.AvgDataQueueLength*pdh.TicksToSecondScaleFactor,
data.AvgDataQueueLength,
serverValue, shareValue,
)
// Read time spent on queue. Convert from ticks to seconds.
// Read time spent on queue, in seconds.
ch <- prometheus.MustNewConstMetric(
c.readRequestQueueSecsTotal,
prometheus.CounterValue,
data.AvgReadQueueLength*pdh.TicksToSecondScaleFactor,
data.AvgReadQueueLength,
serverValue, shareValue,
)
ch <- prometheus.MustNewConstMetric(
c.readSecsTotal,
prometheus.CounterValue,
data.AvgSecPerRead*pdh.TicksToSecondScaleFactor,
data.AvgSecPerRead,
serverValue, shareValue,
)
ch <- prometheus.MustNewConstMetric(
c.writeSecsTotal,
prometheus.CounterValue,
data.AvgSecPerWrite*pdh.TicksToSecondScaleFactor,
data.AvgSecPerWrite,
serverValue, shareValue,
)
ch <- prometheus.MustNewConstMetric(
c.requestSecs,
prometheus.CounterValue,
data.AvgSecPerDataRequest*pdh.TicksToSecondScaleFactor,
data.AvgSecPerDataRequest,
serverValue, shareValue,
)
// Write time spent on queue. Convert from ticks to seconds.
// Write time spent on queue, in seconds.
ch <- prometheus.MustNewConstMetric(
c.writeRequestQueueSecsTotal,
prometheus.CounterValue,
data.AvgWriteQueueLength*pdh.TicksToSecondScaleFactor,
data.AvgWriteQueueLength,
serverValue, shareValue,
)
@@ -458,11 +475,18 @@ func (c *Collector) pruneCounters32() {
}
}
// extendCounters32 replaces the raw 32-bit counters with values that keep
// counting past 2^32.
func (c *Collector) extendCounters32(data *perfDataCounterValues) {
// extendCounters32 replaces the 32-bit counters with values that keep
// counting past 2^32. The timer sums arrive in seconds and wrap at 2^32 ticks
// of their time base in frequencies, so they are extended as ticks.
func (c *Collector) extendCounters32(data *perfDataCounterValues, frequencies [numTimers32]float64) {
fields := fields32(data)
for i, frequency := range frequencies {
if frequency > 0 {
*fields[i] = math.Round(*fields[i] * frequency)
}
}
signals := restartSignals(data)
counters, ok := c.counters32[data.Name]
@@ -493,6 +517,12 @@ func (c *Collector) extendCounters32(data *perfDataCounterValues) {
for i, field := range fields {
*field = counters.counters[i].Update(*field, reset)
}
for i, frequency := range frequencies {
if frequency > 0 {
*fields[i] /= frequency
}
}
}
// parseInstanceName splits an SMB Client Shares instance name such as
@@ -53,29 +53,36 @@ func TestExtendCounters32(t *testing.T) {
c := &Collector{counters32: make(map[string]*shareCounters32)}
// internal/pdh delivers the timer sums in seconds of a 10 MHz time base.
frequencies := [numTimers32]float64{1e7, 1e7, 1e7}
sample := func(data perfDataCounterValues) perfDataCounterValues {
c.generation++
data.Name = `\\server\share`
c.extendCounters32(&data)
c.extendCounters32(&data, frequencies)
c.pruneCounters32()
return data
}
got := sample(perfDataCounterValues{AvgSecPerRead: 1 << 20, ReadRequestsPerSec: 7, ReadBytesPerSec: 100})
require.Equal(t, float64(1<<20), got.AvgSecPerRead) //nolint:testifylint
require.Equal(t, 7.0, got.ReadRequestsPerSec) //nolint:testifylint
got := sample(perfDataCounterValues{AvgSecPerRead: (1 << 20) / 1e7, ReadRequestsPerSec: 7, ReadBytesPerSec: 100})
require.Equal(t, (1<<20)/1e7, got.AvgSecPerRead) //nolint:testifylint
require.Equal(t, 7.0, got.ReadRequestsPerSec) //nolint:testifylint
// A timer sum can wrap from anywhere: 2^32 ticks are about 430 s of
// summed request time.
got = sample(perfDataCounterValues{AvgSecPerRead: 1 << 10, ReadRequestsPerSec: 9, ReadBytesPerSec: 200})
require.Equal(t, float64(math.MaxUint32)+1+(1<<10), got.AvgSecPerRead) //nolint:testifylint
require.Equal(t, 9.0, got.ReadRequestsPerSec) //nolint:testifylint
got = sample(perfDataCounterValues{AvgSecPerRead: (1 << 10) / 1e7, ReadRequestsPerSec: 9, ReadBytesPerSec: 200})
require.Equal(t, (float64(math.MaxUint32)+1+(1<<10))/1e7, got.AvgSecPerRead) //nolint:testifylint
require.Equal(t, 9.0, got.ReadRequestsPerSec) //nolint:testifylint
// Fractions of a second survive the round trip through ticks.
got = sample(perfDataCounterValues{AvgSecPerRead: 0.1234567, ReadRequestsPerSec: 10, ReadBytesPerSec: 300})
require.Equal(t, (float64(math.MaxUint32)+1+1234567)/1e7, got.AvgSecPerRead) //nolint:testifylint
// A drop of a 64-bit byte counter shows a reconnect, even if DataBytes grew.
got = sample(perfDataCounterValues{AvgSecPerRead: 5, ReadRequestsPerSec: 1, ReadBytesPerSec: 50, DataBytesPerSec: 1000})
require.Equal(t, 5.0, got.AvgSecPerRead) //nolint:testifylint
got = sample(perfDataCounterValues{AvgSecPerRead: 5 / 1e7, ReadRequestsPerSec: 1, ReadBytesPerSec: 50, DataBytesPerSec: 1000})
require.Equal(t, 5/1e7, got.AvgSecPerRead) //nolint:testifylint
require.Equal(t, 1.0, got.ReadRequestsPerSec) //nolint:testifylint
// A metadata-only share has no byte counter to show the reconnect; a
+16
View File
@@ -32,6 +32,7 @@ var (
procKernelLocalFileTimeToFileTime = modkernel32.NewProc("LocalFileTimeToFileTime")
procOpenJobObject = modkernel32.NewProc("OpenJobObjectW")
procIsProcessInJob = modkernel32.NewProc("IsProcessInJob")
procQueryPerformanceFrequency = modkernel32.NewProc("QueryPerformanceFrequency")
// GetDiskSpaceInformationW is optional and checked by GetDiskSpaceInformation.
load = lazyproc.Once(modkernel32,
@@ -39,6 +40,7 @@ var (
procKernelLocalFileTimeToFileTime,
procOpenJobObject,
procIsProcessInJob,
procQueryPerformanceFrequency,
)
)
@@ -94,3 +96,17 @@ func LocalFileTimeToFileTime(localFileTime, utcFileTime *windows.Filetime) uint3
return uint32(ret)
}
// QueryPerformanceFrequency returns the frequency of the performance counter
// in counts per second.
// 📑 https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency
func QueryPerformanceFrequency() (int64, error) {
var frequency int64
ret, _, err := procQueryPerformanceFrequency.Call(uintptr(unsafe.Pointer(&frequency)))
if ret == 0 {
return 0, err
}
return frequency, nil
}
+31 -12
View File
@@ -31,6 +31,7 @@ import (
"sync"
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
"github.com/prometheus-community/windows_exporter/internal/osversion"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
@@ -487,10 +488,10 @@ func newCollector[T any](logger *slog.Logger, query *Query, resultType CounterTy
continue
}
case PERF_AVERAGE_TIMER:
// Only [Collector.Frequency] uses the time base of an average
// timer, so a missing one is not an error. Collect retries it.
if ret := GetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess {
counter.Frequency = 0
// The time base of an average timer is the performance counter
// frequency, so use that if PDH has none.
if ret := GetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess || counter.Frequency <= 0 {
counter.Frequency, _ = kernel32.QueryPerformanceFrequency()
}
}
}
@@ -569,11 +570,11 @@ func (c *Collector[T]) MetricType(counterName string) (prometheus.ValueType, boo
}
// Frequency returns the time base in ticks per second of the raw timer counter
// named counterName. The raw value of a PERF_AVERAGE_TIMER counter is the sum
// of the measured durations in these ticks; divide it by the frequency to get
// seconds. It returns false if the counter is unknown, is not such a timer or
// its time base is not known yet. [Collector.Collect] reads a missing time
// base again.
// named counterName. [Collector.Collect] already converts the values of these
// counters to seconds; a collector needs the time base only to work on the
// ticks, for example to extend a 32-bit counter past its wrap. It returns
// false if the counter is unknown, is not such a timer or its time base is not
// known yet.
func (c *Collector[T]) Frequency(counterName string) (int64, bool) {
if c == nil {
return 0, false
@@ -724,7 +725,8 @@ func hasTimeBase(counterType uint32) bool {
// the collector was created. Early after a reboot, PDH can report no data
// for it, e.g. for APP_POOL_WAS while WAS is still starting (#2313). Without a
// time base, an elapsed time counter has no valid value and every instance is
// omitted.
// omitted. An average timer falls back to QueryPerformanceFrequency at
// creation, so it only needs this if that failed too.
func (c *Collector[T]) loadTimeBase(counter *Counter) {
if c.resultType != CounterTypeRaw || !hasTimeBase(counter.Type) || counter.Frequency > 0 {
return
@@ -810,9 +812,26 @@ func (c *Collector[T]) setRawValue(row *T, counter *Counter, value RawCounter) b
if counter.FieldIndexValue != -1 {
c.rows.setValue(row, counter.FieldIndexValue, float64(value.SecondValue-value.FirstValue)/float64(counter.Frequency))
}
case PERF_100NSEC_TIMER, PERF_PRECISION_100NS_TIMER:
case PERF_100NSEC_TIMER, PERF_100NSEC_TIMER_INV, PERF_100NSEC_MULTI_TIMER, PERF_100NSEC_MULTI_TIMER_INV, PERF_PRECISION_100NS_TIMER,
PERF_COUNTER_100NS_QUEUELEN_TYPE:
// The queue length type sums the queue length times the elapsed 100 ns
// ticks, so in seconds its rate is the average queue length.
if counter.FieldIndexValue != -1 {
c.rows.setValue(row, counter.FieldIndexValue, float64(value.FirstValue)*TicksToSecondScaleFactor)
c.rows.setValue(row, counter.FieldIndexValue, float64(value.FirstValue)/TicksPerSecond)
}
case PERF_AVERAGE_TIMER:
// The first value sums the measured durations in ticks of the counter's
// time base, the second value counts the operations.
if counter.Frequency <= 0 {
return false
}
if counter.FieldIndexSecondValue != -1 {
c.rows.setValue(row, counter.FieldIndexSecondValue, float64(value.SecondValue))
}
if counter.FieldIndexValue != -1 {
c.rows.setValue(row, counter.FieldIndexValue, float64(value.FirstValue)/float64(counter.Frequency))
}
default:
if counter.FieldIndexSecondValue != -1 {
+64 -27
View File
@@ -385,6 +385,32 @@ func TestSetRawValueElapsedTime(t *testing.T) {
require.True(t, c.setRawValue(&values, counter, RawCounter{FirstValue: 1, SecondValue: 2}))
}
// TestSetRawValue100nsTimers checks that every 100 ns timer type, and the
// 100 ns queue length type, is exported in seconds. 115960642 * (1 / 1e7) is
// 11.596064199999999, so the test also catches a multiplication by the
// reciprocal.
func TestSetRawValue100nsTimers(t *testing.T) {
t.Parallel()
c := newRowSetTestCollector(false)
for _, counterType := range []uint32{
PERF_100NSEC_TIMER,
PERF_100NSEC_TIMER_INV,
PERF_100NSEC_MULTI_TIMER,
PERF_100NSEC_MULTI_TIMER_INV,
PERF_PRECISION_100NS_TIMER,
PERF_COUNTER_100NS_QUEUELEN_TYPE,
} {
var values rowSetValues
counter := &Counter{Type: counterType, FieldIndexValue: int(unsafe.Offsetof(values.A)), FieldIndexSecondValue: -1}
require.True(t, c.setRawValue(&values, counter, RawCounter{FirstValue: 115_960_642, SecondValue: 1}))
require.InDelta(t, 11.5960642, values.A, 0, "counter type %#x", counterType)
}
}
// TestCollectLoadsMissingTimeBase checks that an elapsed time counter whose time
// base was not available when the collector was created gets it on a later
// sample, instead of omitting every instance until a restart (#2313).
@@ -419,17 +445,15 @@ func TestCollectLoadsMissingTimeBase(t *testing.T) {
require.Positive(t, dst[0].SystemUpTime)
}
// TestFrequencyOfAverageTimer checks that the time base of a raw average timer
// is available, also when it was missing at startup, and that other counters
// have none.
func TestFrequencyOfAverageTimer(t *testing.T) {
// TestAverageTimerInSeconds checks that the raw value of an average timer is
// converted to seconds with the counter's time base, also when the time base
// was missing at startup, and that the second value keeps the operation count.
func TestAverageTimerInSeconds(t *testing.T) {
t.Parallel()
type physicalDisk struct {
AvgDiskSecPerRead float64 `perfdata:"Avg. Disk sec/Read"`
AvgDiskSecPerReadBase float64 `perfdata:"Avg. Disk sec/Read,secondvalue"`
DiskReadsPerSec float64 `perfdata:"Disk Reads/sec"`
CurrentDiskQueueLength float64 `perfdata:"Current Disk Queue Length"`
AvgDiskSecPerRead float64 `perfdata:"Avg. Disk sec/Read"`
AvgDiskSecPerReadBase float64 `perfdata:"Avg. Disk sec/Read,secondvalue"`
}
collector, err := NewCollector[physicalDisk](slog.New(slog.DiscardHandler), CounterTypeRaw, "PhysicalDisk", []string{"_Total"})
@@ -437,36 +461,45 @@ func TestFrequencyOfAverageTimer(t *testing.T) {
t.Cleanup(collector.Close)
frequency, ok := collector.Frequency("Avg. Disk sec/Read")
require.True(t, ok)
require.Positive(t, frequency)
for _, name := range []string{"Disk Reads/sec", "Current Disk Queue Length", "unknown"} {
_, ok = collector.Frequency(name)
require.False(t, ok, name)
}
// As if GetCounterTimeBase failed at startup.
idx := slices.IndexFunc(collector.counters, func(counter Counter) bool { return counter.Name == "Avg. Disk sec/Read" })
require.NotEqual(t, -1, idx)
collector.counters[idx].Frequency = 0
frequency := collector.counters[idx].Frequency
require.Positive(t, frequency)
_, ok = collector.Frequency("Avg. Disk sec/Read")
require.False(t, ok)
// As if GetCounterTimeBase failed at startup.
collector.counters[idx].Frequency = 0
var dst []physicalDisk
require.NoError(t, collector.Collect(&dst))
require.Equal(t, frequency, collector.counters[idx].Frequency)
require.Len(t, dst, 1)
require.GreaterOrEqual(t, dst[0].AvgDiskSecPerRead, 0.0)
}
reloaded, ok := collector.Frequency("Avg. Disk sec/Read")
require.True(t, ok)
require.Equal(t, frequency, reloaded)
// TestSetRawValueAverageTimer checks the conversion of an average timer and
// that a counter without a time base has no valid value.
func TestSetRawValueAverageTimer(t *testing.T) {
t.Parallel()
var nilCollector *Collector[physicalDisk]
c := newRowSetTestCollector(false)
_, ok = nilCollector.Frequency("Avg. Disk sec/Read")
require.False(t, ok)
var values rowSetValues
counter := &Counter{
Type: PERF_AVERAGE_TIMER,
Frequency: 2_500_000,
FieldIndexValue: int(unsafe.Offsetof(values.B)),
FieldIndexSecondValue: int(unsafe.Offsetof(values.BSecond)),
}
require.True(t, c.setRawValue(&values, counter, RawCounter{FirstValue: 5_000_000, SecondValue: 7}))
require.InDelta(t, 2.0, values.B, 0)
require.InDelta(t, 7.0, values.BSecond, 0)
counter.Frequency = 0
require.False(t, c.setRawValue(&values, counter, RawCounter{FirstValue: 1, SecondValue: 1}))
}
func TestMetricTypeForCounterType(t *testing.T) {
@@ -474,6 +507,10 @@ func TestMetricTypeForCounterType(t *testing.T) {
require.Equal(t, prometheus.GaugeValue, metricType(CounterTypeRaw, PERF_COUNTER_RAWCOUNT))
require.Equal(t, prometheus.CounterValue, metricType(CounterTypeRaw, PERF_COUNTER_BULK_COUNT))
require.Equal(t, prometheus.CounterValue, metricType(CounterTypeRaw, PERF_100NSEC_MULTI_TIMER))
require.Equal(t, prometheus.CounterValue, metricType(CounterTypeRaw, PERF_100NSEC_MULTI_TIMER_INV))
require.Equal(t, prometheus.CounterValue, metricType(CounterTypeRaw, PERF_AVERAGE_TIMER))
require.Equal(t, prometheus.CounterValue, metricType(CounterTypeRaw, PERF_COUNTER_100NS_QUEUELEN_TYPE))
require.Equal(t, prometheus.GaugeValue, metricType(CounterTypeRaw, 0xFFFFFFFF))
require.Equal(t, prometheus.GaugeValue, metricType(CounterTypeFormatted, PERF_COUNTER_BULK_COUNT))
}
+26 -19
View File
@@ -21,8 +21,11 @@ import "github.com/prometheus/client_golang/prometheus"
// Conversion factors.
const (
TicksToSecondScaleFactor = 1 / 1e7
WindowsEpoch int64 = 116444736000000000
// TicksPerSecond is the number of 100 ns ticks per second. Divide by it
// rather than multiply by its reciprocal: the quotient is correctly rounded,
// the product is not.
TicksPerSecond = 1e7
WindowsEpoch int64 = 116444736000000000
)
// Based on https://github.com/leoluk/perflib_exporter/blob/master/collector/mapper.go
@@ -74,21 +77,25 @@ const (
//nolint:gochecknoglobals
var SupportedCounterTypes = map[uint32]prometheus.ValueType{
PERF_COUNTER_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_DELTA: prometheus.CounterValue,
PERF_COUNTER_COUNTER: prometheus.CounterValue,
PERF_COUNTER_BULK_COUNT: prometheus.CounterValue,
PERF_RAW_FRACTION: prometheus.GaugeValue,
PERF_LARGE_RAW_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER: prometheus.CounterValue,
PERF_PRECISION_100NS_TIMER: prometheus.CounterValue,
PERF_SAMPLE_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER_INV: prometheus.CounterValue,
PERF_ELAPSED_TIME: prometheus.GaugeValue,
PERF_SAMPLE_BASE: prometheus.GaugeValue,
PERF_RAW_BASE: prometheus.GaugeValue,
PERF_LARGE_RAW_BASE: prometheus.GaugeValue,
PERF_COUNTER_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_DELTA: prometheus.CounterValue,
PERF_COUNTER_COUNTER: prometheus.CounterValue,
PERF_COUNTER_BULK_COUNT: prometheus.CounterValue,
PERF_RAW_FRACTION: prometheus.GaugeValue,
PERF_LARGE_RAW_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER: prometheus.CounterValue,
PERF_PRECISION_100NS_TIMER: prometheus.CounterValue,
PERF_SAMPLE_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER_INV: prometheus.CounterValue,
PERF_100NSEC_MULTI_TIMER: prometheus.CounterValue,
PERF_100NSEC_MULTI_TIMER_INV: prometheus.CounterValue,
PERF_ELAPSED_TIME: prometheus.GaugeValue,
PERF_AVERAGE_TIMER: prometheus.CounterValue,
PERF_COUNTER_100NS_QUEUELEN_TYPE: prometheus.CounterValue,
PERF_SAMPLE_BASE: prometheus.GaugeValue,
PERF_RAW_BASE: prometheus.GaugeValue,
PERF_LARGE_RAW_BASE: prometheus.GaugeValue,
}