Skip to content
Merged
5 changes: 5 additions & 0 deletions plugins/inputs/all/timex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.timex

package all

import _ "github.com/influxdata/telegraf/plugins/inputs/timex" // register plugin
63 changes: 63 additions & 0 deletions plugins/inputs/timex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Timex Input Plugin

This plugin gathers metrics on system time using the Linux Kernel [adjtimex syscall][timex].

The call gets the information of the kernel time variables that are controlled
by the ntpd, systemd-timesyncd, chrony or other time synchronization services.

⭐ Telegraf v1.37.0
🏷️ hardware, system
💻 linux

[timex]: https://man7.org/linux/man-pages/man2/adjtimex.2.html

## Global configuration options <!-- @/docs/includes/plugin_config.md -->

In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.

[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins

## Configuration

```toml @sample.conf
# Read time metrics from linux timex interface.
[[inputs.timex]]
## No input configuration
```

## Metrics

- timex
- tags:
- status (string) - Clock command/status.
- fields:
- offset_seconds (int64) - The offset from local and reference clock.
- frequency (int64) - Local clock frequency offset.
- maxerror_ns (int64) - The maximum error in nanoseconds.
- estimated_error_ns (int64) - The estimated error in nanoseconds.
- loop_time_constant (int64) - Phase-locked loop time constant.
- tick_ns (int64) - Nanoseconds between clock ticks.
- pps_frequency_hertz (float) - Pulse-per-second frequency in hertz.
- pps_jitter_ns (int64) - Pulse-per-second jitter in nanoseconds.
- pps_shift_seconds (int64) - Pulse-per-second interval duration in
seconds.
- pps_stability_hertz (float) - Pulse-per-second stability, average of
relative.
- pps_jitter_total (int64) - Pulse-per-second per second count of jitter
limit.
- pps_calibration_total (int64) - Pulse-per-second count of calibration
intervals.
- pps_error_total (int64) - Pulse-per-second count of calibration errors.
- pps_stability_exceeded_total (int64) - Pulse-per-second total stability.
- tai_offset_seconds (int64) - TAI offset in seconds.
- sync_status (boolean) - Is clock synchronized with a server.
- status (int) - Clock command/status.

## Example Output

```text
timex,host=testvm,status=ok maxerror_ns=1516000i,estimated_error_ns=4000i,tick_ns=10000000i,loop_time_constant=2i,pps_jitter_total=0i,sync_status=true,offset_ns=0i,frequency=885043i,pps_shift_seconds=0i,pps_stability_hertz=0,tai_offset_seconds=37i,status=0i,pps_frequency_hertz=0,pps_jitter_ns=0i,pps_calibration_total=0i,pps_error_total=0i,pps_stability_exceeded_total=0i 1761121800000000000
```
3 changes: 3 additions & 0 deletions plugins/inputs/timex/sample.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Read time metrics from linux timex interface.
[[inputs.timex]]
## No input configuration
101 changes: 101 additions & 0 deletions plugins/inputs/timex/timex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build linux

package timex

import (
_ "embed"
"fmt"
"time"

"golang.org/x/sys/unix"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

//go:embed sample.conf
var sampleConfig string

type Timex struct {
Log telegraf.Logger `toml:"-"`
}

func (*Timex) SampleConfig() string {
return sampleConfig
}

func (*Timex) Gather(acc telegraf.Accumulator) error {
var timex unix.Timex
status, err := unix.Adjtimex(&timex)
if err != nil {
return fmt.Errorf("failed to get time adjtimex stats: %w", err)
}

// Check the return status for clock state
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/timex.h
synced := status != unix.TIME_ERROR

// https://man7.org/linux/man-pages/man2/adjtimex.2.html
// Notes for frequency adjustment ppm
ppm16 := float64(65536 * time.Second / time.Microsecond)

// https://man7.org/linux/man-pages/man2/adjtimex.2.html
// validate the status to determine if the time is in nanoseconds or microseconds
// STA_NANO (0x2000): time is in nanoseconds
// STA_MICRO (0x4000): time is in microseconds
multiplier := int64(1000)
if (timex.Status & unix.STA_NANO) != 0 {
multiplier = int64(1)
}

statusOutput := ""
switch status {
case unix.TIME_OK:
statusOutput = "ok"
case unix.TIME_INS:
statusOutput = "insert"
case unix.TIME_DEL:
statusOutput = "delete"
case unix.TIME_OOP:
statusOutput = "progress"
case unix.TIME_WAIT:
statusOutput = "wait"
case unix.TIME_ERROR:
statusOutput = "error"
}

tags := map[string]string{
"status": statusOutput,
}

fields := map[string]interface{}{
"offset_ns": int64(timex.Offset) * multiplier, //nolint:unconvert // Conversion needed for some architectures
"frequency": timex.Freq,
"maxerror_ns": timex.Maxerror * 1000,
"estimated_error_ns": timex.Esterror * 1000,
"status": timex.Status,
"loop_time_constant": timex.Constant,
"tick_ns": timex.Tick * 1000,
"pps_frequency_hertz": float64(timex.Ppsfreq) / ppm16,
"pps_jitter_ns": int64(timex.Jitter) * multiplier, //nolint:unconvert // Conversion needed for some architectures
"pps_shift_seconds": timex.Shift,
"pps_stability_hertz": float64(timex.Stabil) / ppm16,
"pps_jitter_total": timex.Jitcnt,
"pps_calibration_total": timex.Calcnt,
"pps_error_total": timex.Errcnt,
"pps_stability_exceeded_total": timex.Stbcnt,
"tai_offset_seconds": timex.Tai,
"sync_status": synced,
}

acc.AddGauge("timex", fields, tags)

return nil
}

func init() {
inputs.Add("timex", func() telegraf.Input {
return &Timex{}
})
}
35 changes: 35 additions & 0 deletions plugins/inputs/timex/timex_notlinux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !linux

package timex

import (
_ "embed"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

//go:embed sample.conf
var sampleConfig string

type Timex struct {
Log telegraf.Logger `toml:"-"`
}

func (*Timex) SampleConfig() string {
return sampleConfig
}

func (tx *Timex) Init() error {
tx.Log.Warn("Current platform is not supported")
return nil
}

func (*Timex) Gather(_ telegraf.Accumulator) error { return nil }

func init() {
inputs.Add("timex", func() telegraf.Input {
return &Timex{}
})
}
59 changes: 59 additions & 0 deletions plugins/inputs/timex/timex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build linux

package timex

import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)

func TestMetricStructure(t *testing.T) {
plugin := &Timex{
Log: &testutil.Logger{},
}

expected := []telegraf.Metric{
metric.New(
"timex",
map[string]string{
"status": "error",
},
map[string]interface{}{
"offset_ns": int64(0),
"frequency": int64(0),
"maxerror_ns": int64(0),
"estimated_error_ns": int64(0),
"status": int32(0),
"loop_time_constant": int64(0),
"tick_ns": int64(0),
"pps_frequency_hertz": float64(0),
"pps_jitter_ns": int64(0),
"pps_shift_seconds": int32(0),
"pps_stability_hertz": float64(0),
"pps_jitter_total": int64(0),
"pps_calibration_total": int64(0),
"pps_error_total": int64(0),
"pps_stability_exceeded_total": int64(0),
"tai_offset_seconds": int32(0),
"sync_status": bool(false),
},
time.Unix(0, 0),
telegraf.Gauge,
),
}

var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
actual := acc.GetTelegrafMetrics()

testutil.RequireMetricsStructureEqual(t, expected, actual, testutil.IgnoreTime(), testutil.IgnoreTags("status"))

// validate the status tag separately bacause the value could be different based on the system.
require.NotEmpty(t, actual[0].Tags()["status"])
}
Loading