prometheusmetricshost-metricsmachine-metricsnode-metricsprocfsprometheus-exportersystem-informationsystem-metrics
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.
208 lines
6.6 KiB
208 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" |
|
"log/slog" |
|
"os" |
|
|
|
"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 *slog.Logger |
|
} |
|
|
|
func init() { |
|
registerCollector("timex", defaultEnabled, NewTimexCollector) |
|
} |
|
|
|
// NewTimexCollector returns a new Collector exposing adjtime(3) stats. |
|
func NewTimexCollector(logger *slog.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) { |
|
c.logger.Debug("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 |
|
}
|
|
|