clockdrift

package
v0.0.0-...-a6686b8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package clockdrift collects NTP/chrony time-synchronization data from the host for emission as PCI-DSS 10.6 audit evidence.

Index

Constants

View Source
const (
	BackendChrony = "chrony"
)

BackendName identifies a TimeSource implementation. It is exposed as a label value on the clock_drift_reference_info gauge so dashboards can distinguish backends if more than one ever ships (e.g., w32tm).

View Source
const DefaultCacheTTL = 0

DefaultCacheTTL controls result reuse between collection ticks. Zero disables caching: chronyc is invoked on every tick (typically every 15s), keeping clock drift metrics at the same cadence as all other system metrics and ensuring a lost-sync event surfaces within one tick rather than up to five minutes later.

Variables

LeapStatusValues lists every well-known LeapStatus, in dashboard display order. It is exported so callers (e.g., the Prometheus sender) can iterate without hardcoding the set in two places.

Functions

This section is empty.

Types

type ChronyBackend

type ChronyBackend struct {
	// contains filtered or unexported fields
}

ChronyBackend implements TimeSource by executing `chronyc tracking`.

Behavior:

  • exec is wrapped in the caller-supplied context (5s timeout in production) so a wedged chronyd cannot stall the metrics agent.
  • Locale and timezone are pinned via LANG=C, LC_ALL=C, TZ=UTC so a reconfigured stemcell cannot accidentally rename chrony's field keys out from under the parser.
  • Successful results are cached for cacheTTL to avoid forking chronyc more often than the audit cadence demands.

func NewChronyBackend

func NewChronyBackend(opts ...ChronyOption) *ChronyBackend

NewChronyBackend constructs a ChronyBackend with sensible defaults that can be selectively overridden via options.

func (*ChronyBackend) Collect

func (c *ChronyBackend) Collect(ctx context.Context) (*TimeSyncData, error)

Collect returns the latest TimeSyncData, honoring the supplied context's deadline. The result may come from an in-memory cache if the previous call succeeded within cacheTTL.

func (*ChronyBackend) Name

func (c *ChronyBackend) Name() string

Name returns the backend identifier ("chrony").

type ChronyOption

type ChronyOption func(*ChronyBackend)

ChronyOption configures a ChronyBackend at construction time.

func WithCacheTTL

func WithCacheTTL(d time.Duration) ChronyOption

WithCacheTTL overrides the duration for which a successful snapshot is reused. A non-positive value disables caching and forces every Collect call to invoke chronyc.

func WithCmdRunner

func WithCmdRunner(r func(ctx context.Context) (string, error)) ChronyOption

WithCmdRunner overrides the function used to execute `chronyc tracking`. Tests inject a fake to return canned output without forking a process.

func WithLogger

func WithLogger(l *log.Logger) ChronyOption

WithLogger sets the logger used for non-fatal parse warnings. If unset, warnings are silently discarded so the package never spams the agent log.

type LeapStatus

type LeapStatus string

LeapStatus is the chrony-reported leap-second state.

const (
	LeapNormal          LeapStatus = "Normal"
	LeapInsertSecond    LeapStatus = "Insert second"
	LeapDeleteSecond    LeapStatus = "Delete second"
	LeapNotSynchronised LeapStatus = "Not synchronised"
	LeapUnknown         LeapStatus = "unknown"
)

func ParseLeapStatus

func ParseLeapStatus(s string) LeapStatus

ParseLeapStatus maps a chronyc leap-status string to a typed LeapStatus. Matching is case-insensitive and accepts both British ("Not synchronised") and American ("Not synchronized") spellings so a future chrony release that normalizes the spelling cannot silently drop the unsync signal.

type TimeDirection

type TimeDirection string

TimeDirection describes whether the local clock is running slow or fast relative to the upstream NTP source.

const (
	DirectionSlow    TimeDirection = "slow"
	DirectionFast    TimeDirection = "fast"
	DirectionUnknown TimeDirection = "unknown"
)

func ParseTimeDirection

func ParseTimeDirection(s string) TimeDirection

ParseTimeDirection maps a chronyc direction word to a typed TimeDirection. Unknown inputs (including empty string and uppercase variants) return DirectionUnknown rather than panicking.

type TimeSource

type TimeSource interface {
	// Name returns the backend identifier (see BackendChrony).
	Name() string

	// Collect returns the latest time-synchronization snapshot or an error.
	// A nil error and non-nil *TimeSyncData indicates a successful read; the
	// caller may still observe NaN values on individual fields when the
	// underlying tool emitted unparseable output for that field.
	Collect(ctx context.Context) (*TimeSyncData, error)
}

TimeSource collects time-synchronization data from the system.

Implementations are NOT required to be safe for concurrent use; the Collector calls Collect sequentially from a single goroutine. Collect MUST honor the supplied context's deadline/cancellation so a wedged time daemon cannot stall the broader metrics collection cycle.

type TimeSyncData

type TimeSyncData struct {
	ReferenceID         string        `json:"reference_id"`
	ReferenceHost       string        `json:"reference_host"`
	Stratum             *int          `json:"stratum,omitempty"`
	RefTimeUTC          string        `json:"ref_time_utc"`
	SystemTimeOffsetSec float64       `json:"system_time_offset_sec"`
	SystemTimeDirection TimeDirection `json:"system_time_direction"`
	LastOffsetSec       float64       `json:"last_offset_sec"`
	FrequencyPPM        float64       `json:"frequency_ppm"`
	FrequencyDirection  TimeDirection `json:"frequency_direction"`
	RootDelaySec        float64       `json:"root_delay_sec"`
	LeapStatus          LeapStatus    `json:"leap_status"`
}

TimeSyncData is one snapshot of the local clock's NTP-sync state.

Float fields use math.NaN() as a sentinel for "the underlying tool emitted a value we could not parse", which the sender translates to a missing gauge data point rather than a misleading zero. Stratum uses *int with nil signaling missing/invalid (NTP stratum 0 is a valid sentinel meaning "unspecified" and we must not collide with it). RefTimeUnixSec uses *float64 because float zero would render as Jan 1 1970 UTC on dashboards.

Sign convention for offset and PPM fields: when the chrony direction word is "slow" the parser stores the value as negative; "fast" stays positive. The companion Direction enum preserves the original word so consumers can disambiguate without relying on the sign.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL