You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
6.6 KiB
210 lines
6.6 KiB
// 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
|
|
}
|