170 lines
4.4 KiB
Go
170 lines
4.4 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
package chrony
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
fbchrony "github.com/facebook/time/ntp/chrony"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/config"
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
)
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
type Chrony struct {
|
|
Server string `toml:"server"`
|
|
Timeout config.Duration `toml:"timeout"`
|
|
DNSLookup bool `toml:"dns_lookup"`
|
|
Log telegraf.Logger `toml:"-"`
|
|
|
|
conn net.Conn
|
|
client *fbchrony.Client
|
|
}
|
|
|
|
func (*Chrony) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (c *Chrony) Init() error {
|
|
if c.Server != "" {
|
|
// Check the specified server address
|
|
u, err := url.Parse(c.Server)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing server address failed: %w", err)
|
|
}
|
|
switch u.Scheme {
|
|
case "unix":
|
|
// Keep the server unmodified
|
|
case "udp":
|
|
// Check if we do have a port and add the default port if we don't
|
|
if u.Port() == "" {
|
|
u.Host += ":323"
|
|
}
|
|
// We cannot have path elements in an UDP address
|
|
if u.Path != "" {
|
|
return fmt.Errorf("path detected in UDP address %q", c.Server)
|
|
}
|
|
u = &url.URL{Scheme: "udp", Host: u.Host}
|
|
default:
|
|
return errors.New("unknown or missing address scheme")
|
|
}
|
|
c.Server = u.String()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Chrony) Start(_ telegraf.Accumulator) error {
|
|
if c.Server != "" {
|
|
// Create a connection
|
|
u, err := url.Parse(c.Server)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing server address failed: %w", err)
|
|
}
|
|
switch u.Scheme {
|
|
case "unix":
|
|
conn, err := net.DialTimeout("unix", u.Path, time.Duration(c.Timeout))
|
|
if err != nil {
|
|
return fmt.Errorf("dialing %q failed: %w", c.Server, err)
|
|
}
|
|
c.conn = conn
|
|
case "udp":
|
|
conn, err := net.DialTimeout("udp", u.Host, time.Duration(c.Timeout))
|
|
if err != nil {
|
|
return fmt.Errorf("dialing %q failed: %w", c.Server, err)
|
|
}
|
|
c.conn = conn
|
|
}
|
|
} else {
|
|
// If no server is given, reproduce chronyc's behavior
|
|
if conn, err := net.DialTimeout("unix", "/run/chrony/chronyd.sock", time.Duration(c.Timeout)); err == nil {
|
|
c.Server = "unix:///run/chrony/chronyd.sock"
|
|
c.conn = conn
|
|
} else if conn, err := net.DialTimeout("udp", "127.0.0.1:323", time.Duration(c.Timeout)); err == nil {
|
|
c.Server = "udp://127.0.0.1:323"
|
|
c.conn = conn
|
|
} else {
|
|
conn, err := net.DialTimeout("udp", "[::1]:323", time.Duration(c.Timeout))
|
|
if err != nil {
|
|
return fmt.Errorf("dialing server failed: %w", err)
|
|
}
|
|
c.Server = "udp://[::1]:323"
|
|
c.conn = conn
|
|
}
|
|
}
|
|
c.Log.Debugf("Connected to %q...", c.Server)
|
|
|
|
// Initialize the client
|
|
c.client = &fbchrony.Client{Connection: c.conn}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Chrony) Stop() {
|
|
if c.conn != nil {
|
|
if err := c.conn.Close(); err != nil {
|
|
c.Log.Errorf("Closing connection to %q failed: %v", c.Server, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Chrony) Gather(acc telegraf.Accumulator) error {
|
|
req := fbchrony.NewTrackingPacket()
|
|
resp, err := c.client.Communicate(req)
|
|
if err != nil {
|
|
return fmt.Errorf("querying tracking data failed: %w", err)
|
|
}
|
|
tracking, ok := resp.(*fbchrony.ReplyTracking)
|
|
if !ok {
|
|
return fmt.Errorf("got unexpected response type %T while waiting for tracking data", resp)
|
|
}
|
|
|
|
// according to https://github.com/mlichvar/chrony/blob/e11b518a1ffa704986fb1f1835c425844ba248ef/ntp.h#L70
|
|
var leapStatus string
|
|
switch tracking.LeapStatus {
|
|
case 0:
|
|
leapStatus = "normal"
|
|
case 1:
|
|
leapStatus = "insert second"
|
|
case 2:
|
|
leapStatus = "delete second"
|
|
case 3:
|
|
leapStatus = "not synchronized"
|
|
}
|
|
|
|
tags := map[string]string{
|
|
"leap_status": leapStatus,
|
|
"reference_id": strings.ToUpper(strconv.FormatUint(uint64(tracking.RefID), 16)),
|
|
"stratum": strconv.FormatUint(uint64(tracking.Stratum), 10),
|
|
}
|
|
fields := map[string]interface{}{
|
|
"frequency": tracking.FreqPPM,
|
|
"system_time": tracking.CurrentCorrection,
|
|
"last_offset": tracking.LastOffset,
|
|
"residual_freq": tracking.ResidFreqPPM,
|
|
"rms_offset": tracking.RMSOffset,
|
|
"root_delay": tracking.RootDelay,
|
|
"root_dispersion": tracking.RootDispersion,
|
|
"skew": tracking.SkewPPM,
|
|
"update_interval": tracking.LastUpdateInterval,
|
|
}
|
|
acc.AddFields("chrony", fields, tags)
|
|
|
|
return nil
|
|
}
|
|
func init() {
|
|
inputs.Add("chrony", func() telegraf.Input {
|
|
return &Chrony{Timeout: config.Duration(3 * time.Second)}
|
|
})
|
|
}
|