From b4f50c542cc77006b61089560f3504ca631a295a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Wed, 13 Nov 2024 19:26:47 +0100 Subject: [PATCH] time: refactor collector (#1728) --- docs/collector.time.md | 16 ++- internal/collector/service/service_test.go | 4 + internal/collector/time/const.go | 10 ++ internal/collector/time/time.go | 142 ++++++++++++++------- internal/collector/time/time_test.go | 4 + internal/collector/vmware/const.go | 24 ++-- internal/collector/vmware/vmware.go | 48 +++---- 7 files changed, 159 insertions(+), 89 deletions(-) create mode 100644 internal/collector/time/const.go diff --git a/docs/collector.time.md b/docs/collector.time.md index 214d4fa1..37b8c889 100644 --- a/docs/collector.time.md +++ b/docs/collector.time.md @@ -5,15 +5,19 @@ If the Windows Time Service is stopped after collection has started, collector m 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). -| | | -|---------------------|---------| -| Metric name prefix | `time` | -| Data source | Perflib | -| Enabled by default? | No | +| | | +|---------------------|--------| +| Metric name prefix | `time` | +| Data source | PDH | +| Enabled by default? | No | ## Flags -None +### `--collectors.time.enabled` +Comma-separated list of collectors to use, for example: `--collectors.time.enabled=ntp,system_time`. +Matching is case-sensitive. + + ## Metrics diff --git a/internal/collector/service/service_test.go b/internal/collector/service/service_test.go index d19a9cfa..7eb3608f 100644 --- a/internal/collector/service/service_test.go +++ b/internal/collector/service/service_test.go @@ -10,3 +10,7 @@ import ( func BenchmarkCollector(b *testing.B) { testutils.FuncBenchmarkCollector(b, service.Name, service.NewWithFlags) } + +func TestCollector(t *testing.T) { + testutils.TestCollector(t, service.New, nil) +} diff --git a/internal/collector/time/const.go b/internal/collector/time/const.go new file mode 100644 index 00000000..83cf0c2b --- /dev/null +++ b/internal/collector/time/const.go @@ -0,0 +1,10 @@ +package time + +const ( + ClockFrequencyAdjustmentPPBTotal = "Clock Frequency Adjustment (ppb)" + ComputedTimeOffset = "Computed Time Offset" + NTPClientTimeSourceCount = "NTP Client Time Source Count" + NTPRoundTripDelay = "NTP Roundtrip Delay" + NTPServerIncomingRequestsTotal = "NTP Server Incoming Requests" + NTPServerOutgoingResponsesTotal = "NTP Server Outgoing Responses" +) diff --git a/internal/collector/time/time.go b/internal/collector/time/time.go index 8c0ee5b3..59c08a21 100644 --- a/internal/collector/time/time.go +++ b/internal/collector/time/time.go @@ -4,28 +4,46 @@ package time import ( "errors" + "fmt" "log/slog" + "slices" + "strings" "time" "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/kernel32" "github.com/prometheus-community/windows_exporter/internal/mi" - v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" + "github.com/prometheus-community/windows_exporter/internal/perfdata" + "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) -const Name = "time" +const ( + Name = "time" -type Config struct{} + collectorSystemTime = "system_time" + collectorNTP = "ntp" +) -var ConfigDefaults = Config{} +type Config struct { + CollectorsEnabled []string `yaml:"collectors_enabled"` +} + +var ConfigDefaults = Config{ + CollectorsEnabled: []string{ + collectorSystemTime, + collectorNTP, + }, +} // Collector is a Prometheus Collector for Perflib counter metrics. type Collector struct { config Config + perfDataCollector perfdata.Collector + currentTime *prometheus.Desc timezone *prometheus.Desc clockFrequencyAdjustmentPPBTotal *prometheus.Desc @@ -41,6 +59,10 @@ func New(config *Config) *Collector { config = &ConfigDefaults } + if config.CollectorsEnabled == nil { + config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled + } + c := &Collector{ config: *config, } @@ -48,8 +70,26 @@ func New(config *Config) *Collector { return c } -func NewWithFlags(_ *kingpin.Application) *Collector { - return &Collector{} +func NewWithFlags(app *kingpin.Application) *Collector { + c := &Collector{ + config: ConfigDefaults, + } + c.config.CollectorsEnabled = make([]string, 0) + + var collectorsEnabled string + + app.Flag( + "collector.time.enabled", + "Comma-separated list of collectors to use. Defaults to all, if not specified. ntp may not available on all systems.", + ).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled) + + app.Action(func(*kingpin.ParseContext) error { + c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",") + + return nil + }) + + return c } func (c *Collector) GetName() string { @@ -57,14 +97,40 @@ func (c *Collector) GetName() string { } func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) { - return []string{"Windows Time Service"}, nil + return []string{}, nil } func (c *Collector) Close(_ *slog.Logger) error { + if slices.Contains(c.config.CollectorsEnabled, collectorNTP) { + c.perfDataCollector.Close() + } + return nil } func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { + for _, collector := range c.config.CollectorsEnabled { + if !slices.Contains([]string{collectorSystemTime, collectorNTP}, collector) { + return fmt.Errorf("unknown collector: %s", collector) + } + } + + counters := []string{ + ClockFrequencyAdjustmentPPBTotal, + ComputedTimeOffset, + NTPClientTimeSourceCount, + NTPRoundTripDelay, + NTPServerIncomingRequestsTotal, + NTPServerOutgoingResponsesTotal, + } + + var err error + + c.perfDataCollector, err = perfdata.NewCollector(perfdata.V2, "Windows Time Service", nil, counters) + if err != nil { + return fmt.Errorf("failed to create Windows Time Service collector: %w", err) + } + c.currentTime = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "current_timestamp_seconds"), "OperatingSystem.LocalDateTime", @@ -119,40 +185,24 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { // Collect sends the metric values for each metric // to the provided prometheus Metric channel. -func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { - logger = logger.With(slog.String("collector", Name)) - +func (c *Collector) Collect(_ *types.ScrapeContext, _ *slog.Logger, ch chan<- prometheus.Metric) error { errs := make([]error, 0, 2) - if err := c.collectTime(ch); err != nil { - logger.Error("failed collecting time metrics", - slog.Any("err", err), - ) - - errs = append(errs, err) + 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)) + } } - if err := c.collectNTP(ctx, logger, ch); err != nil { - logger.Error("failed collecting time ntp metrics", - slog.Any("err", err), - ) - - errs = append(errs, err) + if slices.Contains(c.config.CollectorsEnabled, collectorNTP) { + if err := c.collectNTP(ch); err != nil { + errs = append(errs, fmt.Errorf("failed collecting time ntp metrics: %w", err)) + } } return errors.Join(errs...) } -// Perflib "Windows Time Service". -type windowsTime struct { - ClockFrequencyAdjustmentPPBTotal float64 `perflib:"Clock Frequency Adjustment (PPB)"` - ComputedTimeOffset float64 `perflib:"Computed Time Offset"` - NTPClientTimeSourceCount float64 `perflib:"NTP Client Time Source Count"` - NTPRoundTripDelay float64 `perflib:"NTP Roundtrip Delay"` - NTPServerIncomingRequestsTotal float64 `perflib:"NTP Server Incoming Requests"` - NTPServerOutgoingResponsesTotal float64 `perflib:"NTP Server Outgoing Responses"` -} - func (c *Collector) collectTime(ch chan<- prometheus.Metric) error { ch <- prometheus.MustNewConstMetric( c.currentTime, @@ -178,48 +228,46 @@ func (c *Collector) collectTime(ch chan<- prometheus.Metric) error { return nil } -func (c *Collector) collectNTP(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { - logger = logger.With(slog.String("collector", Name)) - - var dst []windowsTime // Single-instance class, array is required but will have single entry. - - if err := v1.UnmarshalObject(ctx.PerfObjects["Windows Time Service"], &dst, logger); err != nil { - return err +func (c *Collector) collectNTP(ch chan<- prometheus.Metric) error { + perfData, err := c.perfDataCollector.Collect() + if err != nil { + return fmt.Errorf("failed to collect VM Memory metrics: %w", err) } - if len(dst) == 0 { - return errors.New("no data returned for Windows Time Service") + data, ok := perfData[perftypes.EmptyInstance] + if !ok { + return errors.New("query for Windows Time Service returned empty result set") } ch <- prometheus.MustNewConstMetric( c.clockFrequencyAdjustmentPPBTotal, prometheus.CounterValue, - dst[0].ClockFrequencyAdjustmentPPBTotal, + data[ClockFrequencyAdjustmentPPBTotal].FirstValue, ) ch <- prometheus.MustNewConstMetric( c.computedTimeOffset, prometheus.GaugeValue, - dst[0].ComputedTimeOffset/1000000, // microseconds -> seconds + data[ComputedTimeOffset].FirstValue/1000000, // microseconds -> seconds ) ch <- prometheus.MustNewConstMetric( c.ntpClientTimeSourceCount, prometheus.GaugeValue, - dst[0].NTPClientTimeSourceCount, + data[NTPClientTimeSourceCount].FirstValue, ) ch <- prometheus.MustNewConstMetric( c.ntpRoundTripDelay, prometheus.GaugeValue, - dst[0].NTPRoundTripDelay/1000000, // microseconds -> seconds + data[NTPRoundTripDelay].FirstValue/1000000, // microseconds -> seconds ) ch <- prometheus.MustNewConstMetric( c.ntpServerIncomingRequestsTotal, prometheus.CounterValue, - dst[0].NTPServerIncomingRequestsTotal, + data[NTPServerIncomingRequestsTotal].FirstValue, ) ch <- prometheus.MustNewConstMetric( c.ntpServerOutgoingResponsesTotal, prometheus.CounterValue, - dst[0].NTPServerOutgoingResponsesTotal, + data[NTPServerOutgoingResponsesTotal].FirstValue, ) return nil diff --git a/internal/collector/time/time_test.go b/internal/collector/time/time_test.go index 1d42456a..f5ece161 100644 --- a/internal/collector/time/time_test.go +++ b/internal/collector/time/time_test.go @@ -10,3 +10,7 @@ import ( func BenchmarkCollector(b *testing.B) { testutils.FuncBenchmarkCollector(b, time.Name, time.NewWithFlags) } + +func TestCollector(t *testing.T) { + testutils.TestCollector(t, time.New, nil) +} diff --git a/internal/collector/vmware/const.go b/internal/collector/vmware/const.go index a19bf8e3..4056bf16 100644 --- a/internal/collector/vmware/const.go +++ b/internal/collector/vmware/const.go @@ -9,16 +9,16 @@ const ( cpuStolenMs = "CPU stolen time" // \VM Processor(*)\CPU stolen time cpuTimePercents = "% Processor Time" // \VM Processor(*)\% Processor Time - MemActiveMB = "MemActiveMB" // \VM Memory\Memory Active in MB - MemBalloonedMB = "MemBalloonedMB" // \VM Memory\Memory Ballooned in MB - MemLimitMB = "MemLimitMB" // \VM Memory\Memory Limit in MB - MemMappedMB = "MemMappedMB" // \VM Memory\Memory Mapped in MB - MemOverheadMB = "MemOverheadMB" // \VM Memory\Memory Overhead in MB - MemReservationMB = "MemReservationMB" // \VM Memory\Memory Reservation in MB - MemSharedMB = "MemSharedMB" // \VM Memory\Memory Shared in MB - MemSharedSavedMB = "MemSharedSavedMB" // \VM Memory\Memory Shared Saved in MB - MemShares = "MemShares" // \VM Memory\Memory Shares - MemSwappedMB = "MemSwappedMB" // \VM Memory\Memory Swapped in MB - MemTargetSizeMB = "MemTargetSizeMB" // \VM Memory\Memory Target Size - MemUsedMB = "MemUsedMB" // \VM Memory\Memory Used in MB + memActiveMB = "MemActiveMB" // \VM Memory\Memory Active in MB + memBalloonedMB = "MemBalloonedMB" // \VM Memory\Memory Ballooned in MB + memLimitMB = "MemLimitMB" // \VM Memory\Memory Limit in MB + memMappedMB = "MemMappedMB" // \VM Memory\Memory Mapped in MB + memOverheadMB = "MemOverheadMB" // \VM Memory\Memory Overhead in MB + memReservationMB = "MemReservationMB" // \VM Memory\Memory Reservation in MB + memSharedMB = "MemSharedMB" // \VM Memory\Memory Shared in MB + memSharedSavedMB = "MemSharedSavedMB" // \VM Memory\Memory Shared Saved in MB + memShares = "MemShares" // \VM Memory\Memory Shares + memSwappedMB = "MemSwappedMB" // \VM Memory\Memory Swapped in MB + memTargetSizeMB = "MemTargetSizeMB" // \VM Memory\Memory Target Size + memUsedMB = "MemUsedMB" // \VM Memory\Memory Used in MB ) diff --git a/internal/collector/vmware/vmware.go b/internal/collector/vmware/vmware.go index 7ba9d468..85bf69fc 100644 --- a/internal/collector/vmware/vmware.go +++ b/internal/collector/vmware/vmware.go @@ -143,18 +143,18 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { ) counters = []string{ - MemActiveMB, - MemBalloonedMB, - MemLimitMB, - MemMappedMB, - MemOverheadMB, - MemReservationMB, - MemSharedMB, - MemSharedSavedMB, - MemShares, - MemSwappedMB, - MemTargetSizeMB, - MemUsedMB, + memActiveMB, + memBalloonedMB, + memLimitMB, + memMappedMB, + memOverheadMB, + memReservationMB, + memSharedMB, + memSharedSavedMB, + memShares, + memSwappedMB, + memTargetSizeMB, + memUsedMB, } c.perfDataCollectorMemory, err = perfdata.NewCollector(perfdata.V2, "VM Memory", nil, counters) @@ -268,73 +268,73 @@ func (c *Collector) collectMem(ch chan<- prometheus.Metric) error { ch <- prometheus.MustNewConstMetric( c.memActive, prometheus.GaugeValue, - utils.MBToBytes(data[MemActiveMB].FirstValue), + utils.MBToBytes(data[memActiveMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memBallooned, prometheus.GaugeValue, - utils.MBToBytes(data[MemBalloonedMB].FirstValue), + utils.MBToBytes(data[memBalloonedMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memLimit, prometheus.GaugeValue, - utils.MBToBytes(data[MemLimitMB].FirstValue), + utils.MBToBytes(data[memLimitMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memMapped, prometheus.GaugeValue, - utils.MBToBytes(data[MemMappedMB].FirstValue), + utils.MBToBytes(data[memMappedMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memOverhead, prometheus.GaugeValue, - utils.MBToBytes(data[MemOverheadMB].FirstValue), + utils.MBToBytes(data[memOverheadMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memReservation, prometheus.GaugeValue, - utils.MBToBytes(data[MemReservationMB].FirstValue), + utils.MBToBytes(data[memReservationMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memShared, prometheus.GaugeValue, - utils.MBToBytes(data[MemSharedMB].FirstValue), + utils.MBToBytes(data[memSharedMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memSharedSaved, prometheus.GaugeValue, - utils.MBToBytes(data[MemSharedSavedMB].FirstValue), + utils.MBToBytes(data[memSharedSavedMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memShares, prometheus.GaugeValue, - data[MemShares].FirstValue, + data[memShares].FirstValue, ) ch <- prometheus.MustNewConstMetric( c.memSwapped, prometheus.GaugeValue, - utils.MBToBytes(data[MemSwappedMB].FirstValue), + utils.MBToBytes(data[memSwappedMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memTargetSize, prometheus.GaugeValue, - utils.MBToBytes(data[MemTargetSizeMB].FirstValue), + utils.MBToBytes(data[memTargetSizeMB].FirstValue), ) ch <- prometheus.MustNewConstMetric( c.memUsed, prometheus.GaugeValue, - utils.MBToBytes(data[MemUsedMB].FirstValue), + utils.MBToBytes(data[memUsedMB].FirstValue), ) return nil