// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build linux && !notimex // +build linux,!notimex package collector import ( "errors" "fmt" "os" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/unix" ) const ( // The system clock is not synchronized to a reliable // server (TIME_ERROR). timeError = 5 // The timex.Status time resolution bit (STA_NANO), // 0 = microsecond, 1 = nanoseconds. staNano = 0x2000 // 1 second in nanoSeconds = 1000000000 microSeconds = 1000000 // See NOTES in adjtimex(2). ppm16frac = 1000000.0 * 65536.0 ) type timexCollector struct { offset, freq, maxerror, esterror, status, constant, tick, ppsfreq, jitter, shift, stabil, jitcnt, calcnt, errcnt, stbcnt, tai, syncStatus typedDesc logger log.Logger } func init() { registerCollector("timex", defaultEnabled, NewTimexCollector) } // NewTimexCollector returns a new Collector exposing adjtime(3) stats. func NewTimexCollector(logger log.Logger) (Collector, error) { const subsystem = "timex" return &timexCollector{ offset: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "offset_seconds"), "Time offset in between local system and reference clock.", nil, nil, ), prometheus.GaugeValue}, freq: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "frequency_adjustment_ratio"), "Local clock frequency adjustment.", nil, nil, ), prometheus.GaugeValue}, maxerror: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "maxerror_seconds"), "Maximum error in seconds.", nil, nil, ), prometheus.GaugeValue}, esterror: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "estimated_error_seconds"), "Estimated error in seconds.", nil, nil, ), prometheus.GaugeValue}, status: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "status"), "Value of the status array bits.", nil, nil, ), prometheus.GaugeValue}, constant: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "loop_time_constant"), "Phase-locked loop time constant.", nil, nil, ), prometheus.GaugeValue}, tick: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "tick_seconds"), "Seconds between clock ticks.", nil, nil, ), prometheus.GaugeValue}, ppsfreq: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_frequency_hertz"), "Pulse per second frequency.", nil, nil, ), prometheus.GaugeValue}, jitter: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_jitter_seconds"), "Pulse per second jitter.", nil, nil, ), prometheus.GaugeValue}, shift: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_shift_seconds"), "Pulse per second interval duration.", nil, nil, ), prometheus.GaugeValue}, stabil: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_stability_hertz"), "Pulse per second stability, average of recent frequency changes.", nil, nil, ), prometheus.GaugeValue}, jitcnt: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_jitter_total"), "Pulse per second count of jitter limit exceeded events.", nil, nil, ), prometheus.CounterValue}, calcnt: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_calibration_total"), "Pulse per second count of calibration intervals.", nil, nil, ), prometheus.CounterValue}, errcnt: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_error_total"), "Pulse per second count of calibration errors.", nil, nil, ), prometheus.CounterValue}, stbcnt: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "pps_stability_exceeded_total"), "Pulse per second count of stability limit exceeded events.", nil, nil, ), prometheus.CounterValue}, tai: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "tai_offset_seconds"), "International Atomic Time (TAI) offset.", nil, nil, ), prometheus.GaugeValue}, syncStatus: typedDesc{prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "sync_status"), "Is clock synchronized to a reliable server (1 = yes, 0 = no).", nil, nil, ), prometheus.GaugeValue}, logger: logger, }, nil } func (c *timexCollector) Update(ch chan<- prometheus.Metric) error { var syncStatus float64 var divisor float64 var timex = new(unix.Timex) status, err := unix.Adjtimex(timex) if err != nil { if errors.Is(err, os.ErrPermission) { level.Debug(c.logger).Log("msg", "Not collecting timex metrics", "err", err) return ErrNoData } return fmt.Errorf("failed to retrieve adjtimex stats: %w", err) } if status == timeError { syncStatus = 0 } else { syncStatus = 1 } if (timex.Status & staNano) != 0 { divisor = nanoSeconds } else { divisor = microSeconds } ch <- c.syncStatus.mustNewConstMetric(syncStatus) ch <- c.offset.mustNewConstMetric(float64(timex.Offset) / divisor) ch <- c.freq.mustNewConstMetric(1 + float64(timex.Freq)/ppm16frac) ch <- c.maxerror.mustNewConstMetric(float64(timex.Maxerror) / microSeconds) ch <- c.esterror.mustNewConstMetric(float64(timex.Esterror) / microSeconds) ch <- c.status.mustNewConstMetric(float64(timex.Status)) ch <- c.constant.mustNewConstMetric(float64(timex.Constant)) ch <- c.tick.mustNewConstMetric(float64(timex.Tick) / microSeconds) ch <- c.ppsfreq.mustNewConstMetric(float64(timex.Ppsfreq) / ppm16frac) ch <- c.jitter.mustNewConstMetric(float64(timex.Jitter) / divisor) ch <- c.shift.mustNewConstMetric(float64(timex.Shift)) ch <- c.stabil.mustNewConstMetric(float64(timex.Stabil) / ppm16frac) ch <- c.jitcnt.mustNewConstMetric(float64(timex.Jitcnt)) ch <- c.calcnt.mustNewConstMetric(float64(timex.Calcnt)) ch <- c.errcnt.mustNewConstMetric(float64(timex.Errcnt)) ch <- c.stbcnt.mustNewConstMetric(float64(timex.Stbcnt)) ch <- c.tai.mustNewConstMetric(float64(timex.Tai)) return nil }