Skip to content

Commit

Permalink
time: refactor collector (#1728)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke authored Nov 13, 2024
1 parent b53f18b commit b4f50c5
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 89 deletions.
16 changes: 10 additions & 6 deletions docs/collector.time.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions internal/collector/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
10 changes: 10 additions & 0 deletions internal/collector/time/const.go
Original file line number Diff line number Diff line change
@@ -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"
)
142 changes: 95 additions & 47 deletions internal/collector/time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,30 +59,78 @@ func New(config *Config) *Collector {
config = &ConfigDefaults
}

if config.CollectorsEnabled == nil {
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
}

c := &Collector{
config: *config,
}

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 {
return Name
}

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",
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions internal/collector/time/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
24 changes: 12 additions & 12 deletions internal/collector/vmware/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Loading

0 comments on commit b4f50c5

Please sign in to comment.