summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/chrony
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:36:07 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:36:07 +0000
commite8c44275b9a1937b5948010a042294d580d36d7c (patch)
treee87c73e25556c3c9d5442f5ca4ba0cf46c64ec70 /src/go/plugin/go.d/modules/chrony
parentAdding upstream version 1.47.5. (diff)
downloadnetdata-upstream.tar.xz
netdata-upstream.zip
Adding upstream version 2.0.0.upstream/2.0.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/plugin/go.d/modules/chrony')
-rw-r--r--src/go/plugin/go.d/modules/chrony/charts.go143
-rw-r--r--src/go/plugin/go.d/modules/chrony/chrony.go60
-rw-r--r--src/go/plugin/go.d/modules/chrony/chrony_test.go66
-rw-r--r--src/go/plugin/go.d/modules/chrony/client.go152
-rw-r--r--src/go/plugin/go.d/modules/chrony/collect.go133
-rw-r--r--src/go/plugin/go.d/modules/chrony/exec.go46
-rw-r--r--src/go/plugin/go.d/modules/chrony/init.go33
-rw-r--r--src/go/plugin/go.d/modules/chrony/integrations/chrony.md9
-rw-r--r--src/go/plugin/go.d/modules/chrony/metadata.yaml23
9 files changed, 311 insertions, 354 deletions
diff --git a/src/go/plugin/go.d/modules/chrony/charts.go b/src/go/plugin/go.d/modules/chrony/charts.go
index 37a6fa3e..00f3d053 100644
--- a/src/go/plugin/go.d/modules/chrony/charts.go
+++ b/src/go/plugin/go.d/modules/chrony/charts.go
@@ -20,10 +20,8 @@ const (
prioRefMeasurementTime
prioLeapStatus
prioActivity
- //prioNTPPackets
- //prioCommandPackets
- //prioNKEConnections
- //prioClientLogRecords
+ prioNTPPackets
+ prioCommandPackets
)
var charts = module.Charts{
@@ -216,105 +214,42 @@ var (
}
)
-//var serverStatsVer1Charts = module.Charts{
-// ntpPacketsChart.Copy(),
-// commandPacketsChart.Copy(),
-// clientLogRecordsChart.Copy(),
-//}
-//
-//var serverStatsVer2Charts = module.Charts{
-// ntpPacketsChart.Copy(),
-// commandPacketsChart.Copy(),
-// clientLogRecordsChart.Copy(),
-// nkeConnectionChart.Copy(),
-//}
-//
-//var serverStatsVer3Charts = module.Charts{
-// ntpPacketsChart.Copy(),
-// commandPacketsChart.Copy(),
-// clientLogRecordsChart.Copy(),
-// nkeConnectionChart.Copy(),
-//}
-//
-//var serverStatsVer4Charts = module.Charts{
-// ntpPacketsChart.Copy(),
-// commandPacketsChart.Copy(),
-// clientLogRecordsChart.Copy(),
-// nkeConnectionChart.Copy(),
-//}
+var serverStatsCharts = module.Charts{
+ ntpPacketsChart.Copy(),
+ commandPacketsChart.Copy(),
+}
-// ServerStats charts
-//var (
-// ntpPacketsChart = module.Chart{
-// ID: "ntp_packets",
-// Title: "NTP packets",
-// Units: "packets/s",
-// Fam: "client requests",
-// Ctx: "chrony.ntp_packets",
-// Type: module.Stacked,
-// Priority: prioNTPPackets,
-// Dims: module.Dims{
-// {ID: "ntp_packets_received", Name: "received", Algo: module.Incremental},
-// {ID: "ntp_packets_dropped", Name: "dropped", Algo: module.Incremental},
-// },
-// }
-// commandPacketsChart = module.Chart{
-// ID: "command_packets",
-// Title: "Command packets",
-// Units: "packets/s",
-// Fam: "client requests",
-// Ctx: "chrony.command_packets",
-// Type: module.Stacked,
-// Priority: prioCommandPackets,
-// Dims: module.Dims{
-// {ID: "command_packets_received", Name: "received", Algo: module.Incremental},
-// {ID: "command_packets_dropped", Name: "dropped", Algo: module.Incremental},
-// },
-// }
-// nkeConnectionChart = module.Chart{
-// ID: "nke_connections",
-// Title: "NTS-KE connections",
-// Units: "connections/s",
-// Fam: "client requests",
-// Ctx: "chrony.nke_connections",
-// Type: module.Stacked,
-// Priority: prioNKEConnections,
-// Dims: module.Dims{
-// {ID: "nke_connections_accepted", Name: "accepted", Algo: module.Incremental},
-// {ID: "nke_connections_dropped", Name: "dropped", Algo: module.Incremental},
-// },
-// }
-// clientLogRecordsChart = module.Chart{
-// ID: "client_log_records",
-// Title: "Client log records",
-// Units: "records/s",
-// Fam: "client requests",
-// Ctx: "chrony.client_log_records",
-// Type: module.Stacked,
-// Priority: prioClientLogRecords,
-// Dims: module.Dims{
-// {ID: "client_log_records_dropped", Name: "dropped", Algo: module.Incremental},
-// },
-// }
-//)
+var (
+ ntpPacketsChart = module.Chart{
+ ID: "ntp_packets",
+ Title: "NTP packets",
+ Units: "packets/s",
+ Fam: "client requests",
+ Ctx: "chrony.ntp_packets",
+ Type: module.Line,
+ Priority: prioNTPPackets,
+ Dims: module.Dims{
+ {ID: "ntp_packets_received", Name: "received", Algo: module.Incremental},
+ {ID: "ntp_packets_dropped", Name: "dropped", Algo: module.Incremental},
+ },
+ }
+ commandPacketsChart = module.Chart{
+ ID: "command_packets",
+ Title: "Command packets",
+ Units: "packets/s",
+ Fam: "client requests",
+ Ctx: "chrony.command_packets",
+ Type: module.Line,
+ Priority: prioCommandPackets,
+ Dims: module.Dims{
+ {ID: "command_packets_received", Name: "received", Algo: module.Incremental},
+ {ID: "command_packets_dropped", Name: "dropped", Algo: module.Incremental},
+ },
+ }
+)
-//func (c *Chrony) addServerStatsCharts(stats *serverStats) {
-// var err error
-//
-// switch {
-// case stats.v1 != nil:
-// err = c.Charts().Add(*serverStatsVer1Charts.Copy()...)
-// case stats.v2 != nil:
-// err = c.Charts().Add(*serverStatsVer2Charts.Copy()...)
-// case stats.v3 != nil:
-// err = c.Charts().Add(*serverStatsVer3Charts.Copy()...)
-// case stats.v4 != nil:
-// err = c.Charts().Add(*serverStatsVer4Charts.Copy()...)
-// default:
-// err = errors.New("unknown stats chart")
-// }
-//
-// if err != nil {
-// c.Warning(err)
-// }
-//}
+func (c *Chrony) addServerStatsCharts() {
+ if err := c.Charts().Add(*serverStatsCharts.Copy()...); err != nil {
+ c.Warning(err)
+ }
+}
diff --git a/src/go/plugin/go.d/modules/chrony/chrony.go b/src/go/plugin/go.d/modules/chrony/chrony.go
index 0bdd3183..cfe3067c 100644
--- a/src/go/plugin/go.d/modules/chrony/chrony.go
+++ b/src/go/plugin/go.d/modules/chrony/chrony.go
@@ -5,13 +5,12 @@ package chrony
import (
_ "embed"
"errors"
+ "fmt"
"sync"
"time"
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
- "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
-
- "github.com/facebook/time/ntp/chrony"
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
)
//go:embed "config_schema.json"
@@ -29,38 +28,32 @@ func New() *Chrony {
return &Chrony{
Config: Config{
Address: "127.0.0.1:323",
- Timeout: web.Duration(time.Second),
+ Timeout: confopt.Duration(time.Second),
},
- charts: charts.Copy(),
- addStatsChartsOnce: &sync.Once{},
- newClient: newChronyClient,
+ charts: charts.Copy(),
+ addServerStatsChartsOnce: &sync.Once{},
+ newConn: newChronyConn,
}
}
type Config struct {
- UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
- Address string `yaml:"address" json:"address"`
- Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Address string `yaml:"address" json:"address"`
+ Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
}
-type (
- Chrony struct {
- module.Base
- Config `yaml:",inline" json:""`
+type Chrony struct {
+ module.Base
+ Config `yaml:",inline" json:""`
- charts *module.Charts
- addStatsChartsOnce *sync.Once
+ charts *module.Charts
+ addServerStatsChartsOnce *sync.Once
- client chronyClient
- newClient func(c Config) (chronyClient, error)
- }
- chronyClient interface {
- Tracking() (*chrony.ReplyTracking, error)
- Activity() (*chrony.ReplyActivity, error)
- ServerStats() (*serverStats, error)
- Close()
- }
-)
+ exec chronyBinary
+
+ conn chronyConn
+ newConn func(c Config) (chronyConn, error)
+}
func (c *Chrony) Configuration() any {
return c.Config
@@ -68,8 +61,12 @@ func (c *Chrony) Configuration() any {
func (c *Chrony) Init() error {
if err := c.validateConfig(); err != nil {
- c.Errorf("config validation: %v", err)
- return err
+ return fmt.Errorf("config validation: %v", err)
+ }
+
+ var err error
+ if c.exec, err = c.initChronycBinary(); err != nil {
+ c.Warningf("chronyc binary init failed: %v (serverstats metrics collection is disabled)", err)
}
return nil
@@ -78,7 +75,6 @@ func (c *Chrony) Init() error {
func (c *Chrony) Check() error {
mx, err := c.collect()
if err != nil {
- c.Error(err)
return err
}
if len(mx) == 0 {
@@ -105,8 +101,8 @@ func (c *Chrony) Collect() map[string]int64 {
}
func (c *Chrony) Cleanup() {
- if c.client != nil {
- c.client.Close()
- c.client = nil
+ if c.conn != nil {
+ c.conn.close()
+ c.conn = nil
}
}
diff --git a/src/go/plugin/go.d/modules/chrony/chrony_test.go b/src/go/plugin/go.d/modules/chrony/chrony_test.go
index 407724e7..dc380c20 100644
--- a/src/go/plugin/go.d/modules/chrony/chrony_test.go
+++ b/src/go/plugin/go.d/modules/chrony/chrony_test.go
@@ -155,11 +155,13 @@ func TestChrony_Collect(t *testing.T) {
prepare func() *Chrony
expected map[string]int64
}{
- "tracking: success, activity: success": {
+ "tracking: success, activity: success, serverstats: success": {
prepare: func() *Chrony { return prepareChronyWithMock(&mockClient{}) },
expected: map[string]int64{
"burst_offline_sources": 3,
"burst_online_sources": 4,
+ "command_packets_dropped": 1,
+ "command_packets_received": 652,
"current_correction": 154872,
"frequency": 51051185607,
"last_offset": 3095,
@@ -167,6 +169,8 @@ func TestChrony_Collect(t *testing.T) {
"leap_status_insert_second": 1,
"leap_status_normal": 0,
"leap_status_unsynchronised": 0,
+ "ntp_packets_dropped": 1,
+ "ntp_packets_received": 1,
"offline_sources": 2,
"online_sources": 8,
"ref_measurement_time": 63793323616,
@@ -219,12 +223,13 @@ func TestChrony_Collect(t *testing.T) {
c := test.prepare()
require.NoError(t, c.Init())
+ c.exec = &mockChronyc{}
_ = c.Check()
- collected := c.Collect()
- copyRefMeasurementTime(collected, test.expected)
+ mx := c.Collect()
+ copyRefMeasurementTime(mx, test.expected)
- assert.Equal(t, test.expected, collected)
+ assert.Equal(t, test.expected, mx)
})
}
}
@@ -232,13 +237,32 @@ func TestChrony_Collect(t *testing.T) {
func prepareChronyWithMock(m *mockClient) *Chrony {
c := New()
if m == nil {
- c.newClient = func(_ Config) (chronyClient, error) { return nil, errors.New("mock.newClient error") }
+ c.newConn = func(_ Config) (chronyConn, error) { return nil, errors.New("mock.newClient error") }
} else {
- c.newClient = func(_ Config) (chronyClient, error) { return m, nil }
+ c.newConn = func(_ Config) (chronyConn, error) { return m, nil }
}
return c
}
+type mockChronyc struct{}
+
+func (m *mockChronyc) serverStats() ([]byte, error) {
+ data := `
+NTP packets received : 1
+NTP packets dropped : 1
+Command packets received : 652
+Command packets dropped : 1
+Client log records dropped : 1
+NTS-KE connections accepted: 1
+NTS-KE connections dropped : 1
+Authenticated NTP packets : 1
+Interleaved NTP packets : 1
+NTP timestamps held : 1
+NTP timestamp span : 0
+`
+ return []byte(data), nil
+}
+
type mockClient struct {
errOnTracking bool
errOnActivity bool
@@ -246,7 +270,7 @@ type mockClient struct {
closeCalled bool
}
-func (m *mockClient) Tracking() (*chrony.ReplyTracking, error) {
+func (m *mockClient) tracking() (*chrony.ReplyTracking, error) {
if m.errOnTracking {
return nil, errors.New("mockClient.Tracking call error")
}
@@ -271,7 +295,7 @@ func (m *mockClient) Tracking() (*chrony.ReplyTracking, error) {
return &reply, nil
}
-func (m *mockClient) Activity() (*chrony.ReplyActivity, error) {
+func (m *mockClient) activity() (*chrony.ReplyActivity, error) {
if m.errOnActivity {
return nil, errors.New("mockClient.Activity call error")
}
@@ -287,31 +311,7 @@ func (m *mockClient) Activity() (*chrony.ReplyActivity, error) {
return &reply, nil
}
-func (m *mockClient) ServerStats() (*serverStats, error) {
- if m.errOnServerStats {
- return nil, errors.New("mockClient.ServerStats call error")
- }
-
- reply := serverStats{
- v3: &chrony.ServerStats3{
- NTPHits: 10,
- NKEHits: 10,
- CMDHits: 10,
- NTPDrops: 1,
- NKEDrops: 1,
- CMDDrops: 1,
- LogDrops: 1,
- NTPAuthHits: 10,
- NTPInterleavedHits: 10,
- NTPTimestamps: 0,
- NTPSpanSeconds: 0,
- },
- }
-
- return &reply, nil
-}
-
-func (m *mockClient) Close() {
+func (m *mockClient) close() {
m.closeCalled = true
}
diff --git a/src/go/plugin/go.d/modules/chrony/client.go b/src/go/plugin/go.d/modules/chrony/client.go
index 233e78f1..f07f4902 100644
--- a/src/go/plugin/go.d/modules/chrony/client.go
+++ b/src/go/plugin/go.d/modules/chrony/client.go
@@ -10,55 +10,40 @@ import (
"github.com/facebook/time/ntp/chrony"
)
-func newChronyClient(c Config) (chronyClient, error) {
- conn, err := net.DialTimeout("udp", c.Address, c.Timeout.Duration())
+type chronyConn interface {
+ tracking() (*chrony.ReplyTracking, error)
+ activity() (*chrony.ReplyActivity, error)
+ close()
+}
+
+func newChronyConn(cfg Config) (chronyConn, error) {
+ conn, err := net.DialTimeout("udp", cfg.Address, cfg.Timeout.Duration())
if err != nil {
return nil, err
}
- client := &simpleClient{
+ client := &chronyClient{
conn: conn,
- client: &chrony.Client{Connection: &connWithTimeout{
- Conn: conn,
- timeout: c.Timeout.Duration(),
- }},
+ client: &chrony.Client{
+ Connection: &connWithTimeout{
+ Conn: conn,
+ timeout: cfg.Timeout.Duration(),
+ },
+ },
}
return client, nil
}
-type connWithTimeout struct {
- net.Conn
- timeout time.Duration
-}
-
-func (c *connWithTimeout) Read(p []byte) (n int, err error) {
- if err := c.Conn.SetReadDeadline(c.deadline()); err != nil {
- return 0, err
- }
- return c.Conn.Read(p)
-}
-
-func (c *connWithTimeout) Write(p []byte) (n int, err error) {
- if err := c.Conn.SetWriteDeadline(c.deadline()); err != nil {
- return 0, err
- }
- return c.Conn.Write(p)
-}
-
-func (c *connWithTimeout) deadline() time.Time {
- return time.Now().Add(c.timeout)
-}
-
-type simpleClient struct {
+type chronyClient struct {
conn net.Conn
client *chrony.Client
}
-func (sc *simpleClient) Tracking() (*chrony.ReplyTracking, error) {
+func (c *chronyClient) tracking() (*chrony.ReplyTracking, error) {
req := chrony.NewTrackingPacket()
- reply, err := sc.client.Communicate(req)
+ reply, err := c.client.Communicate(req)
if err != nil {
return nil, err
}
@@ -67,13 +52,14 @@ func (sc *simpleClient) Tracking() (*chrony.ReplyTracking, error) {
if !ok {
return nil, fmt.Errorf("unexpected reply type, want=%T, got=%T", &chrony.ReplyTracking{}, reply)
}
+
return tracking, nil
}
-func (sc *simpleClient) Activity() (*chrony.ReplyActivity, error) {
+func (c *chronyClient) activity() (*chrony.ReplyActivity, error) {
req := chrony.NewActivityPacket()
- reply, err := sc.client.Communicate(req)
+ reply, err := c.client.Communicate(req)
if err != nil {
return nil, err
}
@@ -82,90 +68,36 @@ func (sc *simpleClient) Activity() (*chrony.ReplyActivity, error) {
if !ok {
return nil, fmt.Errorf("unexpected reply type, want=%T, got=%T", &chrony.ReplyActivity{}, reply)
}
+
return activity, nil
}
-type serverStats struct {
- v1 *chrony.ServerStats
- v2 *chrony.ServerStats2
- v3 *chrony.ServerStats3
- v4 *chrony.ServerStats4
+func (c *chronyClient) close() {
+ if c.conn != nil {
+ _ = c.conn.Close()
+ c.conn = nil
+ }
}
-func (sc *simpleClient) ServerStats() (*serverStats, error) {
- req := chrony.NewServerStatsPacket()
+type connWithTimeout struct {
+ net.Conn
+ timeout time.Duration
+}
- reply, err := sc.client.Communicate(req)
- if err != nil {
- return nil, err
+func (c *connWithTimeout) Read(p []byte) (n int, err error) {
+ if err := c.Conn.SetReadDeadline(c.deadline()); err != nil {
+ return 0, err
}
+ return c.Conn.Read(p)
+}
- var stats serverStats
-
- switch v := reply.(type) {
- case *chrony.ReplyServerStats:
- stats.v1 = &chrony.ServerStats{
- NTPHits: v.NTPHits,
- CMDHits: v.CMDHits,
- NTPDrops: v.NTPDrops,
- CMDDrops: v.CMDDrops,
- LogDrops: v.LogDrops,
- }
- case *chrony.ReplyServerStats2:
- stats.v2 = &chrony.ServerStats2{
- NTPHits: v.NTPHits,
- NKEHits: v.NKEHits,
- CMDHits: v.CMDHits,
- NTPDrops: v.NTPDrops,
- NKEDrops: v.NKEDrops,
- CMDDrops: v.CMDDrops,
- LogDrops: v.LogDrops,
- NTPAuthHits: v.NTPAuthHits,
- }
- case *chrony.ReplyServerStats3:
- stats.v3 = &chrony.ServerStats3{
- NTPHits: v.NTPHits,
- NKEHits: v.NKEHits,
- CMDHits: v.CMDHits,
- NTPDrops: v.NTPDrops,
- NKEDrops: v.NKEDrops,
- CMDDrops: v.CMDDrops,
- LogDrops: v.LogDrops,
- NTPAuthHits: v.NTPAuthHits,
- NTPInterleavedHits: v.NTPInterleavedHits,
- NTPTimestamps: v.NTPTimestamps,
- NTPSpanSeconds: v.NTPSpanSeconds,
- }
- case *chrony.ReplyServerStats4:
- stats.v4 = &chrony.ServerStats4{
- NTPHits: v.NTPHits,
- NKEHits: v.NKEHits,
- CMDHits: v.CMDHits,
- NTPDrops: v.NTPDrops,
- NKEDrops: v.NKEDrops,
- CMDDrops: v.CMDDrops,
- LogDrops: v.LogDrops,
- NTPAuthHits: v.NTPAuthHits,
- NTPInterleavedHits: v.NTPInterleavedHits,
- NTPTimestamps: v.NTPTimestamps,
- NTPSpanSeconds: v.NTPSpanSeconds,
- NTPDaemonRxtimestamps: v.NTPDaemonRxtimestamps,
- NTPDaemonTxtimestamps: v.NTPDaemonTxtimestamps,
- NTPKernelRxtimestamps: v.NTPKernelRxtimestamps,
- NTPKernelTxtimestamps: v.NTPKernelTxtimestamps,
- NTPHwRxTimestamps: v.NTPHwRxTimestamps,
- NTPHwTxTimestamps: v.NTPHwTxTimestamps,
- }
- default:
- return nil, fmt.Errorf("unexpected reply type, want=ReplyServerStats, got=%T", reply)
+func (c *connWithTimeout) Write(p []byte) (n int, err error) {
+ if err := c.Conn.SetWriteDeadline(c.deadline()); err != nil {
+ return 0, err
}
-
- return &stats, nil
+ return c.Conn.Write(p)
}
-func (sc *simpleClient) Close() {
- if sc.conn != nil {
- _ = sc.conn.Close()
- sc.conn = nil
- }
+func (c *connWithTimeout) deadline() time.Time {
+ return time.Now().Add(c.timeout)
}
diff --git a/src/go/plugin/go.d/modules/chrony/collect.go b/src/go/plugin/go.d/modules/chrony/collect.go
index 1a3a286f..c95b1b8a 100644
--- a/src/go/plugin/go.d/modules/chrony/collect.go
+++ b/src/go/plugin/go.d/modules/chrony/collect.go
@@ -3,19 +3,32 @@
package chrony
import (
+ "bufio"
+ "bytes"
+ "errors"
"fmt"
+ "strconv"
+ "strings"
"time"
)
const scaleFactor = 1000000000
+const (
+ // https://github.com/mlichvar/chrony/blob/7daf34675a5a2487895c74d1578241ca91a4eb70/ntp.h#L70-L75
+ leapStatusNormal = 0
+ leapStatusInsertSecond = 1
+ leapStatusDeleteSecond = 2
+ leapStatusUnsynchronised = 3
+)
+
func (c *Chrony) collect() (map[string]int64, error) {
- if c.client == nil {
- client, err := c.newClient(c.Config)
+ if c.conn == nil {
+ client, err := c.newConn(c.Config)
if err != nil {
return nil, err
}
- c.client = client
+ c.conn = client
}
mx := make(map[string]int64)
@@ -26,28 +39,20 @@ func (c *Chrony) collect() (map[string]int64, error) {
if err := c.collectActivity(mx); err != nil {
return mx, err
}
- //if strings.HasPrefix(c.Address, "/") {
- // TODO: Allowed only through the Unix domain socket (requires "_chrony" group membership).
- // See https://github.com/facebook/time/blob/18207c5d8ddc7242e8d4192985898b6dbe66932c/cmd/ntpcheck/checker/chrony.go#L38
- // ^^ For some reason doesn't work, Chrony doesn't respond. Additional configuration needed?
- //if err := c.collectServerStats(mx); err != nil {
- // return mx, err
- //}
- //}
+ if c.exec != nil {
+ if err := c.collectServerStats(mx); err != nil {
+ c.Warning(err)
+ c.exec = nil
+ } else {
+ c.addServerStatsChartsOnce.Do(c.addServerStatsCharts)
+ }
+ }
return mx, nil
}
-const (
- // https://github.com/mlichvar/chrony/blob/7daf34675a5a2487895c74d1578241ca91a4eb70/ntp.h#L70-L75
- leapStatusNormal = 0
- leapStatusInsertSecond = 1
- leapStatusDeleteSecond = 2
- leapStatusUnsynchronised = 3
-)
-
func (c *Chrony) collectTracking(mx map[string]int64) error {
- reply, err := c.client.Tracking()
+ reply, err := c.conn.tracking()
if err != nil {
return fmt.Errorf("error on collecting tracking: %v", err)
}
@@ -76,7 +81,7 @@ func (c *Chrony) collectTracking(mx map[string]int64) error {
}
func (c *Chrony) collectActivity(mx map[string]int64) error {
- reply, err := c.client.Activity()
+ reply, err := c.conn.activity()
if err != nil {
return fmt.Errorf("error on collecting activity: %v", err)
}
@@ -90,56 +95,42 @@ func (c *Chrony) collectActivity(mx map[string]int64) error {
return nil
}
-//func (c *Chrony) collectServerStats(mx map[string]int64) error {
-// stats, err := c.client.ServerStats()
-// if err != nil {
-// return fmt.Errorf("error on collecting server stats: %v", err)
-// }
-//
-// switch {
-// case stats.v4 != nil:
-// mx["ntp_packets_received"] = int64(stats.v4.NTPHits)
-// mx["ntp_packets_dropped"] = int64(stats.v4.NTPDrops)
-// mx["command_packets_received"] = int64(stats.v4.CMDHits)
-// mx["command_packets_dropped"] = int64(stats.v4.CMDDrops)
-// mx["client_log_records_dropped"] = int64(stats.v4.LogDrops)
-// mx["nke_connections_accepted"] = int64(stats.v4.NKEHits)
-// mx["nke_connections_dropped"] = int64(stats.v4.NKEDrops)
-// mx["authenticated_ntp_packets"] = int64(stats.v4.NTPAuthHits)
-// mx["interleaved_ntp_packets"] = int64(stats.v4.NTPInterleavedHits)
-// case stats.v3 != nil:
-// mx["ntp_packets_received"] = int64(stats.v3.NTPHits)
-// mx["ntp_packets_dropped"] = int64(stats.v3.NTPDrops)
-// mx["command_packets_received"] = int64(stats.v3.CMDHits)
-// mx["command_packets_dropped"] = int64(stats.v3.CMDDrops)
-// mx["client_log_records_dropped"] = int64(stats.v3.LogDrops)
-// mx["nke_connections_accepted"] = int64(stats.v3.NKEHits)
-// mx["nke_connections_dropped"] = int64(stats.v3.NKEDrops)
-// mx["authenticated_ntp_packets"] = int64(stats.v3.NTPAuthHits)
-// mx["interleaved_ntp_packets"] = int64(stats.v3.NTPInterleavedHits)
-// case stats.v2 != nil:
-// mx["ntp_packets_received"] = int64(stats.v2.NTPHits)
-// mx["ntp_packets_dropped"] = int64(stats.v2.NTPDrops)
-// mx["command_packets_received"] = int64(stats.v2.CMDHits)
-// mx["command_packets_dropped"] = int64(stats.v2.CMDDrops)
-// mx["client_log_records_dropped"] = int64(stats.v2.LogDrops)
-// mx["nke_connections_accepted"] = int64(stats.v2.NKEHits)
-// mx["nke_connections_dropped"] = int64(stats.v2.NKEDrops)
-// mx["authenticated_ntp_packets"] = int64(stats.v2.NTPAuthHits)
-// case stats.v1 != nil:
-// mx["ntp_packets_received"] = int64(stats.v1.NTPHits)
-// mx["ntp_packets_dropped"] = int64(stats.v1.NTPDrops)
-// mx["command_packets_received"] = int64(stats.v1.CMDHits)
-// mx["command_packets_dropped"] = int64(stats.v1.CMDDrops)
-// mx["client_log_records_dropped"] = int64(stats.v1.LogDrops)
-// default:
-// return errors.New("invalid server stats reply")
-// }
-//
-// //c.addStatsChartsOnce.Do(func() { c.addServerStatsCharts(stats) })
-//
-// return nil
-//}
+func (c *Chrony) collectServerStats(mx map[string]int64) error {
+ bs, err := c.exec.serverStats()
+ if err != nil {
+ return fmt.Errorf("error on collecting server stats: %v", err)
+ }
+
+ sc := bufio.NewScanner(bytes.NewReader(bs))
+ var n int
+
+ for sc.Scan() {
+ key, value, ok := strings.Cut(sc.Text(), ":")
+ if !ok {
+ continue
+ }
+
+ key, value = strings.TrimSpace(key), strings.TrimSpace(value)
+
+ switch key {
+ case "NTP packets received",
+ "NTP packets dropped",
+ "Command packets received",
+ "Command packets dropped":
+ if v, err := strconv.ParseInt(value, 10, 64); err == nil {
+ key = strings.ToLower(strings.ReplaceAll(key, " ", "_"))
+ mx[key] = v
+ n++
+ }
+ }
+ }
+
+ if n == 0 {
+ return errors.New("no server stats metrics found in the response")
+ }
+
+ return nil
+}
func boolToInt(v bool) int64 {
if v {
diff --git a/src/go/plugin/go.d/modules/chrony/exec.go b/src/go/plugin/go.d/modules/chrony/exec.go
new file mode 100644
index 00000000..c6792d84
--- /dev/null
+++ b/src/go/plugin/go.d/modules/chrony/exec.go
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package chrony
+
+import (
+ "context"
+ "fmt"
+ "os/exec"
+ "time"
+
+ "github.com/netdata/netdata/go/plugins/logger"
+)
+
+type chronyBinary interface {
+ serverStats() ([]byte, error)
+}
+
+func newChronycExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *chronycExec {
+ return &chronycExec{
+ Logger: log,
+ ndsudoPath: ndsudoPath,
+ timeout: timeout,
+ }
+}
+
+type chronycExec struct {
+ *logger.Logger
+
+ ndsudoPath string
+ timeout time.Duration
+}
+
+func (e *chronycExec) serverStats() ([]byte, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, e.ndsudoPath, "chronyc-serverstats")
+ e.Debugf("executing '%s'", cmd)
+
+ bs, err := cmd.Output()
+ if err != nil {
+ return nil, fmt.Errorf("error on '%s': %v", cmd, err)
+ }
+
+ return bs, nil
+}
diff --git a/src/go/plugin/go.d/modules/chrony/init.go b/src/go/plugin/go.d/modules/chrony/init.go
index 828112c9..2ad63ec6 100644
--- a/src/go/plugin/go.d/modules/chrony/init.go
+++ b/src/go/plugin/go.d/modules/chrony/init.go
@@ -4,6 +4,12 @@ package chrony
import (
"errors"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+
+ "github.com/netdata/netdata/go/plugins/pkg/executable"
)
func (c *Chrony) validateConfig() error {
@@ -12,3 +18,30 @@ func (c *Chrony) validateConfig() error {
}
return nil
}
+
+func (c *Chrony) initChronycBinary() (chronyBinary, error) {
+ host, _, err := net.SplitHostPort(c.Address)
+ if err != nil {
+ return nil, err
+ }
+
+ // 'serverstats' allowed only through the Unix domain socket
+ if !isLocalhost(host) {
+ return nil, nil
+ }
+
+ ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
+
+ if _, err := os.Stat(ndsudoPath); err != nil {
+ return nil, fmt.Errorf("ndsudo executable not found: %v", err)
+ }
+
+ chronyc := newChronycExec(ndsudoPath, c.Timeout.Duration(), c.Logger)
+
+ return chronyc, nil
+}
+
+func isLocalhost(host string) bool {
+ ip := net.ParseIP(host)
+ return host == "localhost" || (ip != nil && ip.IsLoopback())
+}
diff --git a/src/go/plugin/go.d/modules/chrony/integrations/chrony.md b/src/go/plugin/go.d/modules/chrony/integrations/chrony.md
index e9b9454d..6ef6cf18 100644
--- a/src/go/plugin/go.d/modules/chrony/integrations/chrony.md
+++ b/src/go/plugin/go.d/modules/chrony/integrations/chrony.md
@@ -23,7 +23,10 @@ Module: chrony
This collector monitors the system's clock performance and peers activity status
+
It collects metrics by sending UDP packets to chronyd using the Chrony communication protocol v6.
+Additionally, for data collection jobs that connect to localhost Chrony instances, it collects serverstats metrics (NTP packets, command packets received/dropped) by executing the 'chronyc serverstats' command.
+
This collector is supported on all platforms.
@@ -80,6 +83,8 @@ Metrics:
| chrony.ref_measurement_time | ref_measurement_time | seconds |
| chrony.leap_status | normal, insert_second, delete_second, unsynchronised | status |
| chrony.activity | online, offline, burst_online, burst_offline, unresolved | sources |
+| chrony.ntp_packets | received, dropped | packets/s |
+| chrony.command_packets | received, dropped | packets/s |
@@ -101,8 +106,8 @@ No action required.
The configuration file name for this integration is `go.d/chrony.conf`.
-You can edit the configuration file using the `edit-config` script from the
-Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
+You can edit the configuration file using the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#edit-a-configuration-file-using-edit-config) script from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
```bash
cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
diff --git a/src/go/plugin/go.d/modules/chrony/metadata.yaml b/src/go/plugin/go.d/modules/chrony/metadata.yaml
index 18f9152e..b7842aff 100644
--- a/src/go/plugin/go.d/modules/chrony/metadata.yaml
+++ b/src/go/plugin/go.d/modules/chrony/metadata.yaml
@@ -20,8 +20,11 @@ modules:
most_popular: false
overview:
data_collection:
- metrics_description: This collector monitors the system's clock performance and peers activity status
- method_description: It collects metrics by sending UDP packets to chronyd using the Chrony communication protocol v6.
+ metrics_description: |
+ This collector monitors the system's clock performance and peers activity status
+ method_description: |
+ It collects metrics by sending UDP packets to chronyd using the Chrony communication protocol v6.
+ Additionally, for data collection jobs that connect to localhost Chrony instances, it collects serverstats metrics (NTP packets, command packets received/dropped) by executing the 'chronyc serverstats' command.
supported_platforms:
include: []
exclude: []
@@ -206,3 +209,19 @@ modules:
- name: burst_online
- name: burst_offline
- name: unresolved
+ - name: chrony.ntp_packets
+ availability: []
+ description: NTP packets
+ unit: packets/s
+ chart_type: line
+ dimensions:
+ - name: received
+ - name: dropped
+ - name: chrony.command_packets
+ availability: []
+ description: Command packets
+ unit: packets/s
+ chart_type: line
+ dimensions:
+ - name: received
+ - name: dropped