time: refactor collector (#1728)

This commit is contained in:
Jan-Otto Kröpke
2024-11-13 19:26:47 +01:00
committed by GitHub
parent b53f18bcc6
commit b4f50c542c
7 changed files with 159 additions and 89 deletions

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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)
}