summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/ntpd
diff options
context:
space:
mode:
authorAustin S. Hemmelgarn <austin@netdata.cloud>2024-02-13 06:56:20 -0500
committerGitHub <noreply@github.com>2024-02-13 06:56:20 -0500
commit3a29b66132f561c910d827e8c7ae82997f7c1f30 (patch)
treea9306156631b6b188de8877f7c1dbdbe8b067804 /src/go/collectors/go.d.plugin/modules/ntpd
parent57eec3da0e51baa400037ccc4b547cb839ab6ffa (diff)
Include Go plugin sources in main repository. (#16997)
* Include Go plugin sources in main repository. * Fix CI issues. * Rename source tree.
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/ntpd')
l---------src/go/collectors/go.d.plugin/modules/ntpd/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/charts.go346
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/client.go89
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/collect.go154
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/config_schema.json26
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/integrations/ntpd.md228
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/metadata.yaml260
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/ntpd.go111
-rw-r--r--src/go/collectors/go.d.plugin/modules/ntpd/ntpd_test.go351
9 files changed, 1566 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/README.md b/src/go/collectors/go.d.plugin/modules/ntpd/README.md
new file mode 120000
index 0000000000..bad92b03a2
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/README.md
@@ -0,0 +1 @@
+integrations/ntpd.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/charts.go b/src/go/collectors/go.d.plugin/modules/ntpd/charts.go
new file mode 100644
index 0000000000..dc9d183d04
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/charts.go
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ntpd
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/netdata/go.d.plugin/agent/module"
+)
+
+const (
+ prioSystemOffset = module.Priority + iota
+ prioSystemJitter
+ prioSystemFrequency
+ prioSystemWander
+ prioSystemRootDelay
+ prioSystemRootDispersion
+ prioSystemStratum
+ prioSystemTimeConstant
+ prioSystemPrecision
+
+ prioPeerOffset
+ prioPeerDelay
+ prioPeerDispersion
+ prioPeerJitter
+ prioPeerXleave
+ prioPeerRootDelay
+ prioPeerRootDispersion
+ prioPeerStratum
+ prioPeerHostMode
+ prioPeerPeerMode
+ prioPeerHostPoll
+ prioPeerPeerPoll
+ prioPeerPrecision
+)
+
+var (
+ systemCharts = module.Charts{
+ systemOffsetChart.Copy(),
+ systemJitterChart.Copy(),
+ systemFrequencyChart.Copy(),
+ systemWanderChart.Copy(),
+ systemRootDelayChart.Copy(),
+ systemRootDispersionChart.Copy(),
+ systemStratumChart.Copy(),
+ systemTimeConstantChart.Copy(),
+ systemPrecisionChart.Copy(),
+ }
+ systemOffsetChart = module.Chart{
+ ID: "sys_offset",
+ Title: "Combined offset of server relative to this host",
+ Units: "milliseconds",
+ Fam: "system",
+ Ctx: "ntpd.sys_offset",
+ Type: module.Area,
+ Priority: prioSystemOffset,
+ Dims: module.Dims{
+ {ID: "offset", Name: "offset", Div: precision},
+ },
+ }
+ systemJitterChart = module.Chart{
+ ID: "sys_jitter",
+ Title: "Combined system jitter and clock jitter",
+ Units: "milliseconds",
+ Fam: "system",
+ Ctx: "ntpd.sys_jitter",
+ Priority: prioSystemJitter,
+ Dims: module.Dims{
+ {ID: "sys_jitter", Name: "system", Div: precision},
+ {ID: "clk_jitter", Name: "clock", Div: precision},
+ },
+ }
+ systemFrequencyChart = module.Chart{
+ ID: "sys_frequency",
+ Title: "Frequency offset relative to hardware clock",
+ Units: "ppm",
+ Fam: "system",
+ Ctx: "ntpd.sys_frequency",
+ Type: module.Area,
+ Priority: prioSystemFrequency,
+ Dims: module.Dims{
+ {ID: "frequency", Name: "frequency", Div: precision},
+ },
+ }
+ systemWanderChart = module.Chart{
+ ID: "sys_wander",
+ Title: "Clock frequency wander",
+ Units: "ppm",
+ Fam: "system",
+ Ctx: "ntpd.sys_wander",
+ Type: module.Area,
+ Priority: prioSystemWander,
+ Dims: module.Dims{
+ {ID: "clk_wander", Name: "clock", Div: precision},
+ },
+ }
+ systemRootDelayChart = module.Chart{
+ ID: "sys_rootdelay",
+ Title: "Total roundtrip delay to the primary reference clock",
+ Units: "milliseconds",
+ Fam: "system",
+ Ctx: "ntpd.sys_rootdelay",
+ Type: module.Area,
+ Priority: prioSystemRootDelay,
+ Dims: module.Dims{
+ {ID: "rootdelay", Name: "delay", Div: precision},
+ },
+ }
+ systemRootDispersionChart = module.Chart{
+ ID: "sys_rootdisp",
+ Title: "Total root dispersion to the primary reference clock",
+ Units: "milliseconds",
+ Fam: "system",
+ Ctx: "ntpd.sys_rootdisp",
+ Type: module.Area,
+ Priority: prioSystemRootDispersion,
+ Dims: module.Dims{
+ {ID: "rootdisp", Name: "dispersion", Div: precision},
+ },
+ }
+ systemStratumChart = module.Chart{
+ ID: "sys_stratum",
+ Title: "Stratum",
+ Units: "stratum",
+ Fam: "system",
+ Ctx: "ntpd.sys_stratum",
+ Priority: prioSystemStratum,
+ Dims: module.Dims{
+ {ID: "stratum", Name: "stratum", Div: precision},
+ },
+ }
+ systemTimeConstantChart = module.Chart{
+ ID: "sys_tc",
+ Title: "Time constant and poll exponent",
+ Units: "log2",
+ Fam: "system",
+ Ctx: "ntpd.sys_tc",
+ Priority: prioSystemTimeConstant,
+ Dims: module.Dims{
+ {ID: "tc", Name: "current", Div: precision},
+ {ID: "mintc", Name: "minimum", Div: precision},
+ },
+ }
+ systemPrecisionChart = module.Chart{
+ ID: "sys_precision",
+ Title: "Precision",
+ Units: "log2",
+ Fam: "system",
+ Ctx: "ntpd.sys_precision",
+ Priority: prioSystemPrecision,
+ Dims: module.Dims{
+ {ID: "precision", Name: "precision", Div: precision},
+ },
+ }
+)
+
+var (
+ peerChartsTmpl = module.Charts{
+ peerOffsetChartTmpl.Copy(),
+ peerDelayChartTmpl.Copy(),
+ peerDispersionChartTmpl.Copy(),
+ peerJitterChartTmpl.Copy(),
+ peerXleaveChartTmpl.Copy(),
+ peerRootDelayChartTmpl.Copy(),
+ peerRootDispersionChartTmpl.Copy(),
+ peerStratumChartTmpl.Copy(),
+ peerHostModeChartTmpl.Copy(),
+ peerPeerModeChartTmpl.Copy(),
+ peerHostPollChartTmpl.Copy(),
+ peerPeerPollChartTmpl.Copy(),
+ peerPrecisionChartTmpl.Copy(),
+ }
+ peerOffsetChartTmpl = module.Chart{
+ ID: "peer_%s_offset",
+ Title: "Peer offset",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_offset",
+ Priority: prioPeerOffset,
+ Dims: module.Dims{
+ {ID: "peer_%s_offset", Name: "offset", Div: precision},
+ },
+ }
+ peerDelayChartTmpl = module.Chart{
+ ID: "peer_%s_delay",
+ Title: "Peer delay",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_delay",
+ Priority: prioPeerDelay,
+ Dims: module.Dims{
+ {ID: "peer_%s_delay", Name: "delay", Div: precision},
+ },
+ }
+ peerDispersionChartTmpl = module.Chart{
+ ID: "peer_%s_dispersion",
+ Title: "Peer dispersion",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_dispersion",
+ Priority: prioPeerDispersion,
+ Dims: module.Dims{
+ {ID: "peer_%s_dispersion", Name: "dispersion", Div: precision},
+ },
+ }
+ peerJitterChartTmpl = module.Chart{
+ ID: "peer_%s_jitter",
+ Title: "Peer jitter",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_jitter",
+ Priority: prioPeerJitter,
+ Dims: module.Dims{
+ {ID: "peer_%s_jitter", Name: "jitter", Div: precision},
+ },
+ }
+ peerXleaveChartTmpl = module.Chart{
+ ID: "peer_%s_xleave",
+ Title: "Peer interleave delay",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_xleave",
+ Priority: prioPeerXleave,
+ Dims: module.Dims{
+ {ID: "peer_%s_xleave", Name: "xleave", Div: precision},
+ },
+ }
+ peerRootDelayChartTmpl = module.Chart{
+ ID: "peer_%s_rootdelay",
+ Title: "Peer roundtrip delay to the primary reference clock",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_rootdelay",
+ Priority: prioPeerRootDelay,
+ Dims: module.Dims{
+ {ID: "peer_%s_rootdelay", Name: "rootdelay", Div: precision},
+ },
+ }
+ peerRootDispersionChartTmpl = module.Chart{
+ ID: "peer_%s_rootdisp",
+ Title: "Peer root dispersion to the primary reference clock",
+ Units: "milliseconds",
+ Fam: "peers",
+ Ctx: "ntpd.peer_rootdisp",
+ Priority: prioPeerRootDispersion,
+ Dims: module.Dims{
+ {ID: "peer_%s_rootdisp", Name: "dispersion", Div: precision},
+ },
+ }
+ peerStratumChartTmpl = module.Chart{
+ ID: "peer_%s_stratum",
+ Title: "Peer stratum",
+ Units: "stratum",
+ Fam: "peers",
+ Ctx: "ntpd.peer_stratum",
+ Priority: prioPeerStratum,
+ Dims: module.Dims{
+ {ID: "peer_%s_stratum", Name: "stratum", Div: precision},
+ },
+ }
+ peerHostModeChartTmpl = module.Chart{
+ ID: "peer_%s_hmode",
+ Title: "Peer host mode",
+ Units: "hmode",
+ Fam: "peers",
+ Ctx: "ntpd.peer_hmode",
+ Priority: prioPeerHostMode,
+ Dims: module.Dims{
+ {ID: "peer_%s_hmode", Name: "hmode", Div: precision},
+ },
+ }
+ peerPeerModeChartTmpl = module.Chart{
+ ID: "peer_%s_pmode",
+ Title: "Peer mode",
+ Units: "pmode",
+ Fam: "peers",
+ Ctx: "ntpd.peer_pmode",
+ Priority: prioPeerPeerMode,
+ Dims: module.Dims{
+ {ID: "peer_%s_pmode", Name: "pmode", Div: precision},
+ },
+ }
+ peerHostPollChartTmpl = module.Chart{
+ ID: "peer_%s_hpoll",
+ Title: "Peer host poll exponent",
+ Units: "log2",
+ Fam: "peers",
+ Ctx: "ntpd.peer_hpoll",
+ Priority: prioPeerHostPoll,
+ Dims: module.Dims{
+ {ID: "peer_%s_hpoll", Name: "hpoll", Div: precision},
+ },
+ }
+ peerPeerPollChartTmpl = module.Chart{
+ ID: "peer_%s_ppoll",
+ Title: "Peer poll exponent",
+ Units: "log2",
+ Fam: "peers",
+ Ctx: "ntpd.peer_ppoll",
+ Priority: prioPeerPeerPoll,
+ Dims: module.Dims{
+ {ID: "peer_%s_ppoll", Name: "hpoll", Div: precision},
+ },
+ }
+ peerPrecisionChartTmpl = module.Chart{
+ ID: "peer_%s_precision",
+ Title: "Peer precision",
+ Units: "log2",
+ Fam: "peers",
+ Ctx: "ntpd.peer_precision",
+ Priority: prioPeerPrecision,
+ Dims: module.Dims{
+ {ID: "peer_%s_precision", Name: "precision", Div: precision},
+ },
+ }
+)
+
+func (n *NTPd) addPeerCharts(addr string) {
+ charts := peerChartsTmpl.Copy()
+
+ for _, chart := range *charts {
+ chart.ID = fmt.Sprintf(chart.ID, strings.ReplaceAll(addr, ".", "_"))
+ chart.Labels = []module.Label{
+ {Key: "peer_address", Value: addr},
+ }
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf(dim.ID, addr)
+ }
+ }
+
+ if err := n.Charts().Add(*charts...); err != nil {
+ n.Warning(err)
+ }
+}
+
+func (n *NTPd) removePeerCharts(addr string) {
+ px := fmt.Sprintf("peer_%s", strings.ReplaceAll(addr, ".", "_"))
+
+ for _, chart := range *n.Charts() {
+ if strings.HasPrefix(chart.ID, px) {
+ chart.MarkRemove()
+ chart.MarkNotCreated()
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/client.go b/src/go/collectors/go.d.plugin/modules/ntpd/client.go
new file mode 100644
index 0000000000..5164c80e8b
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/client.go
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ntpd
+
+import (
+ "net"
+ "time"
+
+ "github.com/facebook/time/ntp/control"
+)
+
+func newNTPClient(c Config) (ntpConn, error) {
+ conn, err := net.DialTimeout("udp", c.Address, c.Timeout.Duration)
+ if err != nil {
+ return nil, err
+ }
+
+ client := &ntpClient{
+ conn: conn,
+ timeout: c.Timeout.Duration,
+ client: &control.NTPClient{Connection: conn},
+ }
+
+ return client, nil
+}
+
+type ntpClient struct {
+ conn net.Conn
+ timeout time.Duration
+ client *control.NTPClient
+}
+
+func (c *ntpClient) systemInfo() (map[string]string, error) {
+ return c.peerInfo(0)
+}
+
+func (c *ntpClient) peerInfo(id uint16) (map[string]string, error) {
+ msg := &control.NTPControlMsgHead{
+ VnMode: control.MakeVnMode(2, control.Mode),
+ REMOp: control.OpReadVariables,
+ AssociationID: id,
+ }
+
+ if err := c.conn.SetDeadline(time.Now().Add(c.timeout)); err != nil {
+ return nil, err
+ }
+
+ resp, err := c.client.Communicate(msg)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp.GetAssociationInfo()
+}
+
+func (c *ntpClient) peerIDs() ([]uint16, error) {
+ msg := &control.NTPControlMsgHead{
+ VnMode: control.MakeVnMode(2, control.Mode),
+ REMOp: control.OpReadStatus,
+ }
+
+ if err := c.conn.SetDeadline(time.Now().Add(c.timeout)); err != nil {
+ return nil, err
+ }
+
+ resp, err := c.client.Communicate(msg)
+ if err != nil {
+ return nil, err
+ }
+
+ peers, err := resp.GetAssociations()
+ if err != nil {
+ return nil, err
+ }
+
+ var ids []uint16
+ for id := range peers {
+ ids = append(ids, id)
+ }
+
+ return ids, nil
+}
+
+func (c *ntpClient) close() {
+ if c.conn != nil {
+ _ = c.conn.Close()
+ c.conn = nil
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/collect.go b/src/go/collectors/go.d.plugin/modules/ntpd/collect.go
new file mode 100644
index 0000000000..09553a65cf
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/collect.go
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package ntpd
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "time"
+)
+
+const (
+ precision = 1000000
+)
+
+func (n *NTPd) collect() (map[string]int64, error) {
+ if n.client == nil {
+ client, err := n.newClient(n.Config)
+ if err != nil {
+ return nil, fmt.Errorf("creating NTP client: %v", err)
+ }
+ n.client = client
+ }
+
+ mx := make(map[string]int64)
+
+ if err := n.collectInfo(mx); err != nil {
+ return nil, err
+ }
+
+ if n.CollectPeers {
+ if now := time.Now(); now.Sub(n.findPeersTime) > n.findPeersEvery {
+ n.findPeersTime = now
+ if err := n.findPeers(); err != nil {
+ n.Warning(err)
+ }
+ }
+ n.collectPeersInfo(mx)
+ }
+
+ return mx, nil
+}
+
+func (n *NTPd) collectInfo(mx map[string]int64) error {
+ info, err := n.client.systemInfo()
+ if err != nil {
+ return fmt.Errorf("error on querying system info: %v", err)
+ }
+
+ for k, v := range info {
+ switch k {
+ case
+ "offset",
+ "sys_jitter",
+ "clk_jitter",
+ "frequency",
+ "clk_wander",
+ "rootdelay",
+ "rootdisp",
+ "stratum",
+ "tc",
+ "mintc",
+ "precision":
+ if val, err := strconv.ParseFloat(v, 64); err == nil {
+ mx[k] = int64(val * precision)
+ }
+ }
+ }
+ return nil
+}
+
+func (n *NTPd) collectPeersInfo(mx map[string]int64) {
+ for _, id := range n.peerIDs {
+ info, err := n.client.peerInfo(id)
+ if err != nil {
+ n.Warningf("error on querying NTP peer info id='%d': %v", id, err)
+ continue
+ }
+
+ addr, ok := info["srcadr"]
+ if !ok {
+ continue
+ }
+
+ for k, v := range info {
+ switch k {
+ case
+ "offset",
+ "delay",
+ "dispersion",
+ "jitter",
+ "xleave",
+ "rootdelay",
+ "rootdisp",
+ "stratum",
+ "hmode",
+ "pmode",
+ "hpoll",
+ "ppoll",
+ "precision":
+ if val, err := strconv.ParseFloat(v, 64); err == nil {
+ mx["peer_"+addr+"_"+k] = int64(val * precision)
+ }
+ }
+ }
+ }
+}
+
+func (n *NTPd) findPeers() error {
+ n.peerIDs = n.peerIDs[:0]
+
+ n.Debug("querying NTP peers")
+ peers, err := n.client.peerIDs()
+ if err != nil {
+ return fmt.Errorf("querying NTP peers: %v", err)
+ }
+
+ n.Debugf("found %d NTP peers (ids: %v)", len(peers), peers)
+ seen := make(map[string]bool)
+
+ for _, id := range peers {
+ info, err := n.client.peerInfo(id)
+ if err != nil {
+ n.Debugf("error on querying NTP peer info id='%d': %v", id, err)
+ continue
+ }
+
+ addr, ok := info["srcadr"]
+ if ip := net.ParseIP(addr); !ok || ip == nil || n.peerIPAddrFilter.Contains(ip) {
+ n.Debugf("skipping NTP peer id='%d', srcadr='%s'", id, addr)
+ continue
+ }
+
+ seen[addr] = true
+
+ if !n.peerAddr[addr] {
+ n.peerAddr[addr] = true
+ n.Debugf("new NTP peer id='%d', srcadr='%s': creating charts", id, addr)
+ n.addPeerCharts(addr)
+ }
+
+ n.peerIDs = append(n.peerIDs, id)
+ }
+
+ for addr := range n.peerAddr {
+ if !seen[addr] {
+ delete(n.peerAddr, addr)
+ n.Debugf("stale NTP peer srcadr='%s': removing charts", addr)
+ n.removePeerCharts(addr)
+ }
+ }
+
+ return nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/config_schema.json b/src/go/collectors/go.d.plugin/modules/ntpd/config_schema.json
new file mode 100644
index 0000000000..ef360a7f95
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/config_schema.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "go.d/ntpd job configuration schema.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "address": {
+ "type": "string"
+ },
+ "timeout": {
+ "type": [
+ "string",
+ "integer"
+ ]
+ },
+ "collect_peers": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "name",
+ "address"
+ ]
+}
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/integrations/ntpd.md b/src/go/collectors/go.d.plugin/modules/ntpd/integrations/ntpd.md
new file mode 100644
index 0000000000..be765ae189
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/integrations/ntpd.md
@@ -0,0 +1,228 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/go.d.plugin/edit/master/modules/ntpd/README.md"
+meta_yaml: "https://github.com/netdata/go.d.plugin/edit/master/modules/ntpd/metadata.yaml"
+sidebar_label: "NTPd"
+learn_status: "Published"
+learn_rel_path: "Data Collection/System Clock and NTP"
+most_popular: False
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# NTPd
+
+
+<img src="https://netdata.cloud/img/ntp.png" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: ntpd
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors the system variables of the local `ntpd` daemon (optional incl. variables of the polled peers) using the NTP Control Message Protocol via UDP socket, similar to `ntpq`, the [standard NTP query program](https://doc.ntp.org/current-stable/ntpq.html).
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+The default configuration for this integration is not expected to impose a significant performance impact on the system.
+
+
+## Metrics
+
+Metrics grouped by *scope*.
+
+The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
+
+
+
+### Per NTPd instance
+
+These metrics refer to the entire monitored application.
+
+This scope has no labels.
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| ntpd.sys_offset | offset | milliseconds |
+| ntpd.sys_jitter | system, clock | milliseconds |
+| ntpd.sys_frequency | frequency | ppm |
+| ntpd.sys_wander | clock | ppm |
+| ntpd.sys_rootdelay | delay | milliseconds |
+| ntpd.sys_rootdisp | dispersion | milliseconds |
+| ntpd.sys_stratum | stratum | stratum |
+| ntpd.sys_tc | current, minimum | log2 |
+| ntpd.sys_precision | precision | log2 |
+
+### Per peer
+
+These metrics refer to the NTPd peer.
+
+Labels:
+
+| Label | Description |
+|:-----------|:----------------|
+| peer_address | peer's source IP address |
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| ntpd.peer_offset | offset | milliseconds |
+| ntpd.peer_delay | delay | milliseconds |
+| ntpd.peer_dispersion | dispersion | milliseconds |
+| ntpd.peer_jitter | jitter | milliseconds |
+| ntpd.peer_xleave | xleave | milliseconds |
+| ntpd.peer_rootdelay | rootdelay | milliseconds |
+| ntpd.peer_rootdisp | dispersion | milliseconds |
+| ntpd.peer_stratum | stratum | stratum |
+| ntpd.peer_hmode | hmode | hmode |
+| ntpd.peer_pmode | pmode | pmode |
+| ntpd.peer_hpoll | hpoll | log2 |
+| ntpd.peer_ppoll | ppoll | log2 |
+| ntpd.peer_precision | precision | log2 |
+
+
+
+## Alerts
+
+There are no alerts configured by default for this integration.
+
+
+## Setup
+
+### Prerequisites
+
+No action required.
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/ntpd.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/ntpd.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 1 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| address | Server address in IP:PORT format. | 127.0.0.1:123 | yes |
+| timeout | Connection/read/write timeout. | 3 | no |
+| collect_peers | Determines whether peer metrics will be collected. | no | no |
+
+</details>
+
+#### Examples
+
+##### Basic
+
+A basic example configuration.
+
+<details><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 127.0.0.1:123
+
+```
+</details>
+
+##### With peers metrics
+
+Collect peers metrics.
+
+<details><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 127.0.0.1:123
+ collect_peers: yes
+
+```
+</details>
+
+##### Multi-instance
+
+> **Note**: When you define multiple jobs, their names must be unique.
+
+Collecting metrics from local and remote instances.
+
+
+<details><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 127.0.0.1:123
+
+ - name: remote
+ address: 203.0.113.0:123
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `ntpd` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m ntpd
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/ntpd/metadata.yaml b/src/go/collectors/go.d.plugin/modules/ntpd/metadata.yaml
new file mode 100644
index 0000000000..3b968f20c8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/ntpd/metadata.yaml
@@ -0,0 +1,260 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-ntpd
+ plugin_name: go.d.plugin
+ module_name: ntpd
+ monitored_instance:
+ name: NTPd
+ link: https://www.ntp.org/documentation/4.2.8-series/ntpd
+ icon_filename: ntp.png
+ categories:
+ - data-collection.system-clock-and-ntp
+ keywords:
+ - ntpd
+ - ntp
+ - time
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: >
+ This collector monitors the system variables of the local `ntpd` daemon (optional incl. variables of the polled peers)
+ using the NTP Control Message Protocol via UDP socket, similar to `ntpq`,
+ the [standard NTP query program](https://doc.ntp.org/current-stable/ntpq.html).
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list: []
+ configuration:
+ file:
+ name: go.d/ntpd.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 1
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: address
+ description: Server address in IP:PORT format.
+ default_value: 127.0.0.1:123
+ required: true
+ - name: timeout
+ description: Connection/read/write timeout.
+ default_value: 3
+ required: false
+ - name: collect_peers
+ description: Determines whether peer metrics will be collected.
+ default_value: false
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: Basic
+ description: A basic example configuration.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:123
+ - name: With peers metrics
+ description: Collect peers metrics.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:123
+ collect_peers: yes
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Collecting metrics from local and remote instances.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:123
+
+ - name: remote
+ address: 203.0.113.0:123
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: global
+ description: These metrics refer to the entire monitored application.
+ labels: []
+ metrics:
+ - name: ntpd.sys_offset
+ description: Combined offset of server relative to this host
+ unit: milliseconds
+ chart_type: area
+ dimensions:
+ - name: offset
+ - name: ntpd.sys_jitter
+ description: Combined system jitter and clock jitter
+ unit: milliseconds
+ chart_type: line
+ dimensions:
+ - name: system
+ - name: clock
+ - name: ntpd.sys_frequency
+ description: Frequency offset relative to hardware clock
+ unit: ppm
+ chart_type: area
+ dimensions:
+ - name: frequency
+ - name: ntpd.sys_wander
+ description: Clock frequency wander
+ unit: ppm
+ chart_type: area
+ dimensions:
+ - name: clock
+ - name: ntpd.sys_rootdelay
+ description: Total roundtrip delay to the primary reference clock
+ unit: milliseconds
+ chart_type: area
+ dimensions:
+ - name: delay
+ - name: ntpd.sys_rootdisp
+ description: Total root dispersion to the primary reference clock
+ unit: milliseconds
+ chart_type: area
+ dimensions:
+ - name: dispersion
+ - name: ntpd.sys_stratum
+ description: Stratum
+ unit: stratum
+ chart_type: line
+ dimensions:
+ - name: stratum
+ - name: ntpd.sys_tc
+ descr