time: expose clock source sync (#2058)

This commit is contained in:
Jan-Otto Kröpke
2025-05-30 19:52:27 +02:00
committed by GitHub
parent 89ac99e6a2
commit 298d820bd6
5 changed files with 106 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
# time collector
The time collector exposes the Windows Time Service metrics. Note that the Windows Time Service must be running, else metric collection will fail.
The time collector exposes the Windows Time Service and other time related metrics.
If the Windows Time Service is stopped after collection has started, collector metric values will reset to 0.
Please note the Time Service perflib counters are only available on [Windows Server 2016 or newer](https://docs.microsoft.com/en-us/windows-server/networking/windows-time-service/windows-server-2016-improvements).
@@ -32,9 +32,21 @@ Matching is case-sensitive.
| `windows_time_ntp_server_incoming_requests_total` | Total number of requests received by the NTP server. | counter | None |
| `windows_time_current_timestamp_seconds` | Current time as reported by the operating system, in [Unix time](https://en.wikipedia.org/wiki/Unix_time). See [time.Unix()](https://golang.org/pkg/time/#Unix) for details | gauge | None |
| `windows_time_timezone` | Current timezone as reported by the operating system. | gauge | `timezone` |
| `windows_time_clock_sync_source` | This value reflects the sync source of the system clock. | gauge | `type` |
### Example metric
_This collector does not yet have explained examples, we would appreciate your help adding them!_
```
# HELP windows_time_clock_sync_source This value reflects the sync source of the system clock.
# TYPE windows_time_clock_sync_source gauge
windows_time_clock_sync_source{type="AllSync"} 0
windows_time_clock_sync_source{type="Local CMOS Clock"} 0
windows_time_clock_sync_source{type="NT5DS"} 0
windows_time_clock_sync_source{type="NTP"} 1
windows_time_clock_sync_source{type="NoSync"} 0
# HELP windows_time_current_timestamp_seconds OperatingSystem.LocalDateTime
# TYPE windows_time_current_timestamp_seconds gauge
windows_time_current_timestamp_seconds 1.74862554e+09
```
## Useful queries
_This collector does not yet have any useful queries added, we would appreciate your help adding them!_

View File

@@ -33,13 +33,15 @@ import (
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
const (
Name = "time"
collectorSystemTime = "system_time"
collectorNTP = "ntp"
collectorSystemTime = "system_time"
collectorClockSource = "clock_source"
collectorNTP = "ntp"
)
type Config struct {
@@ -50,6 +52,7 @@ type Config struct {
var ConfigDefaults = Config{
CollectorsEnabled: []string{
collectorSystemTime,
collectorClockSource,
collectorNTP,
},
}
@@ -61,10 +64,13 @@ type Collector struct {
perfDataCollector *pdh.Collector
perfDataObject []perfDataCounterValues
logger *slog.Logger
ppbCounterPresent bool
currentTime *prometheus.Desc
timezone *prometheus.Desc
clockSource *prometheus.Desc
clockFrequencyAdjustment *prometheus.Desc
clockFrequencyAdjustmentPPB *prometheus.Desc
computedTimeOffset *prometheus.Desc
@@ -124,9 +130,11 @@ func (c *Collector) Close() error {
return nil
}
func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
c.logger = logger.With(slog.String("collector", Name))
for _, collector := range c.config.CollectorsEnabled {
if !slices.Contains([]string{collectorSystemTime, collectorNTP}, collector) {
if !slices.Contains([]string{collectorSystemTime, collectorClockSource, collectorNTP}, collector) {
return fmt.Errorf("unknown collector: %s", collector)
}
}
@@ -136,16 +144,22 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
c.currentTime = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "current_timestamp_seconds"),
"OperatingSystem.LocalDateTime",
"Current time as reported by the operating system, in unix time.",
nil,
nil,
)
c.timezone = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "timezone"),
"OperatingSystem.LocalDateTime",
"Current timezone as reported by the operating system.",
[]string{"timezone"},
nil,
)
c.clockSource = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "clock_sync_source"),
"This value reflects the sync source of the system clock.",
[]string{"type"},
nil,
)
c.clockFrequencyAdjustment = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "clock_frequency_adjustment"),
"This value reflects the adjustment made to the local system clock frequency by W32Time in nominal clock units. This counter helps visualize the finer adjustments being made by W32time to synchronize the local clock.",
@@ -189,11 +203,13 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
nil,
)
var err error
if slices.Contains(c.config.CollectorsEnabled, collectorNTP) {
var err error
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Windows Time Service", nil)
if err != nil {
return fmt.Errorf("failed to create Windows Time Service collector: %w", err)
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Windows Time Service", nil)
if err != nil {
return fmt.Errorf("failed to create Windows Time Service collector: %w", err)
}
}
return nil
@@ -206,7 +222,13 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
if slices.Contains(c.config.CollectorsEnabled, collectorSystemTime) {
if err := c.collectTime(ch); err != nil {
errs = append(errs, fmt.Errorf("failed collecting time metrics: %w", err))
errs = append(errs, fmt.Errorf("failed collecting operating system time metrics: %w", err))
}
}
if slices.Contains(c.config.CollectorsEnabled, collectorClockSource) {
if err := c.collectClockSource(ch); err != nil {
errs = append(errs, fmt.Errorf("failed collecting clock source metrics: %w", err))
}
}
@@ -244,6 +266,42 @@ func (c *Collector) collectTime(ch chan<- prometheus.Metric) error {
return nil
}
func (c *Collector) collectClockSource(ch chan<- prometheus.Metric) error {
keyPath := `SYSTEM\CurrentControlSet\Services\W32Time\Parameters`
key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.READ)
if err != nil {
return fmt.Errorf("failed to open registry key: %w", err)
}
val, _, err := key.GetStringValue("Type")
if err != nil {
return fmt.Errorf("failed to read 'Type' value: %w", err)
}
for _, validType := range []string{"NTP", "NT5DS", "AllSync", "NoSync", "Local CMOS Clock"} {
metricValue := 0.0
if val == validType {
metricValue = 1.0
}
ch <- prometheus.MustNewConstMetric(
c.clockSource,
prometheus.GaugeValue,
metricValue,
validType,
)
}
if err := key.Close(); err != nil {
c.logger.Debug("failed to close registry key",
slog.Any("err", err),
)
}
return nil
}
func (c *Collector) collectNTP(ch chan<- prometheus.Metric) error {
err := c.perfDataCollector.Collect(&c.perfDataObject)
if err != nil {

View File

@@ -208,20 +208,28 @@ func (c *Collection) collectCollector(ch chan<- prometheus.Metric, logger *slog.
return pending
}
if err != nil && !errors.Is(err, pdh.ErrNoData) && !errors.Is(err, types.ErrNoData) {
if errors.Is(err, pdh.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
err = fmt.Errorf("%w. Check application logs from initialization pharse for more information", err)
slogAttrs := make([]slog.Attr, 0)
if err != nil {
if !errors.Is(err, pdh.ErrNoData) && !errors.Is(err, types.ErrNoData) {
if errors.Is(err, pdh.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
err = fmt.Errorf("%w. Check application logs from initialization pharse for more information", err)
}
logger.LogAttrs(ctx, slog.LevelWarn,
fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
slog.Any("err", err),
)
return failed
}
logger.LogAttrs(ctx, slog.LevelWarn,
fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
slog.Any("err", err),
)
return failed
slogAttrs = append(slogAttrs, slog.Any("err", err))
}
logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics))
logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics),
slogAttrs...,
)
return success
}

View File

@@ -125,7 +125,6 @@ windows_exporter_collector_success{collector="os"} 1
windows_exporter_collector_success{collector="pagefile"} 1
windows_exporter_collector_success{collector="performancecounter"} 1
windows_exporter_collector_success{collector="physical_disk"} 1
windows_exporter_collector_success{collector="printer"} 1
windows_exporter_collector_success{collector="process"} 1
windows_exporter_collector_success{collector="scheduled_task"} 1
windows_exporter_collector_success{collector="service"} 1
@@ -148,7 +147,6 @@ windows_exporter_collector_timeout{collector="os"} 0
windows_exporter_collector_timeout{collector="pagefile"} 0
windows_exporter_collector_timeout{collector="performancecounter"} 0
windows_exporter_collector_timeout{collector="physical_disk"} 0
windows_exporter_collector_timeout{collector="printer"} 0
windows_exporter_collector_timeout{collector="process"} 0
windows_exporter_collector_timeout{collector="scheduled_task"} 0
windows_exporter_collector_timeout{collector="service"} 0
@@ -355,10 +353,6 @@ windows_exporter_collector_timeout{collector="udp"} 0
# TYPE windows_physical_disk_write_seconds_total counter
# HELP windows_physical_disk_writes_total The number of write operations on the disk (PhysicalDisk.DiskWritesPerSec)
# TYPE windows_physical_disk_writes_total counter
# HELP windows_printer_job_count Number of jobs processed by the printer since the last reset
# TYPE windows_printer_job_count counter
# HELP windows_printer_status Printer status
# TYPE windows_printer_status gauge
# HELP windows_scheduled_task_last_result The result that was returned the last time the registered task was run
# TYPE windows_scheduled_task_last_result gauge
windows_scheduled_task_last_result{task="/Microsoft/Windows/PLA/GAEvents"} 0
@@ -433,13 +427,15 @@ windows_service_state{name="Themes",state="stopped"} 0
# TYPE windows_tcp_segments_total counter
# HELP windows_textfile_mtime_seconds Unixtime mtime of textfiles successfully read.
# TYPE windows_textfile_mtime_seconds gauge
# HELP windows_time_clock_sync_source This value reflects the sync source of the system clock.
# TYPE windows_time_clock_sync_source gauge
# HELP windows_time_clock_frequency_adjustment This value reflects the adjustment made to the local system clock frequency by W32Time in nominal clock units. This counter helps visualize the finer adjustments being made by W32time to synchronize the local clock.
# TYPE windows_time_clock_frequency_adjustment gauge
# HELP windows_time_clock_frequency_adjustment_ppb This value reflects the adjustment made to the local system clock frequency by W32Time in Parts Per Billion (PPB) units. 1 PPB adjustment imples the system clock was adjusted at a rate of 1 nanosecond per second. The smallest possible adjustment can vary and can be expected to be in the order of 100&apos;s of PPB. This counter helps visualize the finer actions being taken by W32time to synchronize the local clock.
# TYPE windows_time_clock_frequency_adjustment_ppb gauge
# HELP windows_time_computed_time_offset_seconds Absolute time offset between the system clock and the chosen time source, in seconds
# TYPE windows_time_computed_time_offset_seconds gauge
# HELP windows_time_current_timestamp_seconds OperatingSystem.LocalDateTime
# HELP windows_time_current_timestamp_seconds Current time as reported by the operating system, in unix time.
# TYPE windows_time_current_timestamp_seconds gauge
# HELP windows_time_ntp_client_time_sources Active number of NTP Time sources being used by the client
# TYPE windows_time_ntp_client_time_sources gauge
@@ -449,7 +445,7 @@ windows_service_state{name="Themes",state="stopped"} 0
# TYPE windows_time_ntp_server_incoming_requests_total counter
# HELP windows_time_ntp_server_outgoing_responses_total Total number of requests responded to by NTP server
# TYPE windows_time_ntp_server_outgoing_responses_total counter
# HELP windows_time_timezone OperatingSystem.LocalDateTime
# HELP windows_time_timezone Current timezone as reported by the operating system.
# TYPE windows_time_timezone gauge
# HELP windows_udp_datagram_no_port_total Number of received UDP datagrams for which there was no application at the destination port
# TYPE windows_udp_datagram_no_port_total counter

View File

@@ -25,7 +25,7 @@ $skip_re = "^(go_|windows_exporter_build_info|windows_exporter_collector_duratio
$exporter_proc = Start-Process `
-PassThru `
-FilePath ..\windows_exporter.exe `
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,printer,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
--collector.performancecounter.objects="[{\"name\":\"cpu\",\"object\":\"Processor Information\",\"instances\":[\"*\"],\"instance_label\":\"core\",\"counters\":[{\"name\":\"% Processor Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"active\"}},{\"name\":\"% Idle Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"idle\"}}]},{\"name\":\"memory\",\"object\":\"Memory\",\"counters\":[{\"name\":\"Cache Faults/sec\",\"type\":\"counter\"}]}]"
"@ `
-WindowStyle Hidden `