mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
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:
co-authored by
Claude
parent
a9d5bbef48
commit
da7bebc6b3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user