feat(inputs.chrony): Remove chronyc dependency (#14629)
This commit is contained in:
parent
43fd26a7a0
commit
120167501b
|
|
@ -128,6 +128,7 @@ following works:
|
||||||
- github.com/eclipse/paho.golang [Eclipse Public License - v 2.0](https://github.com/eclipse/paho.golang/blob/master/LICENSE)
|
- github.com/eclipse/paho.golang [Eclipse Public License - v 2.0](https://github.com/eclipse/paho.golang/blob/master/LICENSE)
|
||||||
- github.com/eclipse/paho.mqtt.golang [Eclipse Public License - v 2.0](https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE)
|
- github.com/eclipse/paho.mqtt.golang [Eclipse Public License - v 2.0](https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE)
|
||||||
- github.com/emicklei/go-restful [MIT License](https://github.com/emicklei/go-restful/blob/v3/LICENSE)
|
- github.com/emicklei/go-restful [MIT License](https://github.com/emicklei/go-restful/blob/v3/LICENSE)
|
||||||
|
- github.com/facebook/time [Apache License 2.0](https://github.com/facebook/time/blob/main/LICENSE)
|
||||||
- github.com/fatih/color [MIT License](https://github.com/fatih/color/blob/master/LICENSE.md)
|
- github.com/fatih/color [MIT License](https://github.com/fatih/color/blob/master/LICENSE.md)
|
||||||
- github.com/felixge/httpsnoop [MIT License](https://github.com/felixge/httpsnoop/blob/master/LICENSE.txt)
|
- github.com/felixge/httpsnoop [MIT License](https://github.com/felixge/httpsnoop/blob/master/LICENSE.txt)
|
||||||
- github.com/form3tech-oss/jwt-go [MIT License](https://github.com/form3tech-oss/jwt-go/blob/master/LICENSE)
|
- github.com/form3tech-oss/jwt-go [MIT License](https://github.com/form3tech-oss/jwt-go/blob/master/LICENSE)
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -62,7 +62,7 @@ require (
|
||||||
github.com/compose-spec/compose-go v1.20.2
|
github.com/compose-spec/compose-go v1.20.2
|
||||||
github.com/coocood/freecache v1.2.3
|
github.com/coocood/freecache v1.2.3
|
||||||
github.com/coreos/go-semver v0.3.1
|
github.com/coreos/go-semver v0.3.1
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/couchbase/go-couchbase v0.1.1
|
github.com/couchbase/go-couchbase v0.1.1
|
||||||
github.com/datadope-io/go-zabbix/v2 v2.0.1
|
github.com/datadope-io/go-zabbix/v2 v2.0.1
|
||||||
|
|
@ -74,6 +74,7 @@ require (
|
||||||
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.5.0
|
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.5.0
|
||||||
github.com/eclipse/paho.golang v0.11.0
|
github.com/eclipse/paho.golang v0.11.0
|
||||||
github.com/eclipse/paho.mqtt.golang v1.4.3
|
github.com/eclipse/paho.mqtt.golang v1.4.3
|
||||||
|
github.com/facebook/time v0.0.0-20240125155343-557f84f4ad3e
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
github.com/go-logfmt/logfmt v0.6.0
|
github.com/go-logfmt/logfmt v0.6.0
|
||||||
|
|
|
||||||
5
go.sum
5
go.sum
|
|
@ -1010,8 +1010,9 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
|
@ -1108,6 +1109,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
|
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
|
||||||
|
github.com/facebook/time v0.0.0-20240125155343-557f84f4ad3e h1:PaVm1gMon1pkJdbzoyw2VUCn07cA6RDbGdTbsppjcY8=
|
||||||
|
github.com/facebook/time v0.0.0-20240125155343-557f84f4ad3e/go.mod h1:NBW7VY75BPCsOPrSsARAengTlgOICicHey4Bv70lCUI=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||||
github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4 h1:fP04zlkPjAGpsduG7xN3rRkxjAqkJaIQnnkNYYw/pAk=
|
github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4 h1:fP04zlkPjAGpsduG7xN3rRkxjAqkJaIQnnkNYYw/pAk=
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,9 @@
|
||||||
# chrony Input Plugin
|
# chrony Input Plugin
|
||||||
|
|
||||||
Get standard chrony metrics, requires chronyc executable.
|
This plugin queries metrics from a chrony NTP server. For details on the
|
||||||
|
meaning of the gathered fields please check the [chronyc manual][]
|
||||||
|
|
||||||
Below is the documentation of the various headers returned by `chronyc
|
[chronyc manual]: https://chrony-project.org/doc/4.4/chronyc.html
|
||||||
tracking`.
|
|
||||||
|
|
||||||
- Reference ID - This is the refid and name (or IP address) if available, of the
|
|
||||||
server to which the computer is currently synchronised. If this is 127.127.1.1
|
|
||||||
it means the computer is not synchronised to any external source and that you
|
|
||||||
have the ‘local’ mode operating (via the local command in chronyc (see section
|
|
||||||
local), or the local directive in the ‘/etc/chrony.conf’ file (see section
|
|
||||||
local)).
|
|
||||||
- Stratum - The stratum indicates how many hops away from a computer with an
|
|
||||||
attached reference clock we are. Such a computer is a stratum-1 computer, so
|
|
||||||
the computer in the example is two hops away (i.e. a.b.c is a stratum-2 and is
|
|
||||||
synchronised from a stratum-1).
|
|
||||||
- Ref time - This is the time (UTC) at which the last measurement from the
|
|
||||||
reference source was processed.
|
|
||||||
- System time - In normal operation, chronyd never steps the system clock,
|
|
||||||
because any jump in the timescale can have adverse consequences for certain
|
|
||||||
application programs. Instead, any error in the system clock is corrected by
|
|
||||||
slightly speeding up or slowing down the system clock until the error has been
|
|
||||||
removed, and then returning to the system clock’s normal speed. A consequence
|
|
||||||
of this is that there will be a period when the system clock (as read by other
|
|
||||||
programs using the gettimeofday() system call, or by the date command in the
|
|
||||||
shell) will be different from chronyd's estimate of the current true time
|
|
||||||
(which it reports to NTP clients when it is operating in server mode). The
|
|
||||||
value reported on this line is the difference due to this effect.
|
|
||||||
- Last offset - This is the estimated local offset on the last clock update.
|
|
||||||
- RMS offset - This is a long-term average of the offset value.
|
|
||||||
- Frequency - The ‘frequency’ is the rate by which the system’s clock would be
|
|
||||||
wrong if chronyd was not correcting it. It is expressed in ppm (parts per
|
|
||||||
million). For example, a value of 1ppm would mean that when the system’s
|
|
||||||
clock thinks it has advanced 1 second, it has actually advanced by 1.000001
|
|
||||||
seconds relative to true time.
|
|
||||||
- Residual freq - This shows the ‘residual frequency’ for the currently selected
|
|
||||||
reference source. This reflects any difference between what the measurements
|
|
||||||
from the reference source indicate the frequency should be and the frequency
|
|
||||||
currently being used. The reason this is not always zero is that a smoothing
|
|
||||||
procedure is applied to the frequency. Each time a measurement from the
|
|
||||||
reference source is obtained and a new residual frequency computed, the
|
|
||||||
estimated accuracy of this residual is compared with the estimated accuracy
|
|
||||||
(see ‘skew’ next) of the existing frequency value. A weighted average is
|
|
||||||
computed for the new frequency, with weights depending on these accuracies. If
|
|
||||||
the measurements from the reference source follow a consistent trend, the
|
|
||||||
residual will be driven to zero over time.
|
|
||||||
- Skew - This is the estimated error bound on the frequency.
|
|
||||||
- Root delay - This is the total of the network path delays to the stratum-1
|
|
||||||
computer from which the computer is ultimately synchronised. In certain
|
|
||||||
extreme situations, this value can be negative. (This can arise in a symmetric
|
|
||||||
peer arrangement where the computers’ frequencies are not tracking each other
|
|
||||||
and the network delay is very short relative to the turn-around time at each
|
|
||||||
computer.)
|
|
||||||
- Root dispersion - This is the total dispersion accumulated through all the
|
|
||||||
computers back to the stratum-1 computer from which the computer is ultimately
|
|
||||||
synchronised. Dispersion is due to system clock resolution, statistical
|
|
||||||
measurement variations etc.
|
|
||||||
- Leap status - This is the leap status, which can be Normal, Insert second,
|
|
||||||
Delete second or Not synchronised.
|
|
||||||
|
|
||||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||||
|
|
||||||
|
|
@ -73,7 +19,17 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||||
```toml @sample.conf
|
```toml @sample.conf
|
||||||
# Get standard chrony metrics, requires chronyc executable.
|
# Get standard chrony metrics, requires chronyc executable.
|
||||||
[[inputs.chrony]]
|
[[inputs.chrony]]
|
||||||
## If true, chronyc tries to perform a DNS lookup for the time server.
|
## Server address of chronyd with address scheme
|
||||||
|
## If empty or not set, the plugin will mimic the behavior of chronyc and
|
||||||
|
## check "unix:///run/chrony/chronyd.sock", "udp://127.0.0.1:323"
|
||||||
|
## and "udp://[::1]:323".
|
||||||
|
# server = ""
|
||||||
|
|
||||||
|
## Timeout for establishing the connection
|
||||||
|
# timeout = "5s"
|
||||||
|
|
||||||
|
## Try to resolve received addresses to host-names via DNS lookups
|
||||||
|
## Disabled by default to avoid DNS queries especially for slow DNS servers.
|
||||||
# dns_lookup = false
|
# dns_lookup = false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -100,5 +56,5 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||||
## Example Output
|
## Example Output
|
||||||
|
|
||||||
```text
|
```text
|
||||||
chrony,leap_status=normal,reference_id=192.168.1.1,stratum=3 frequency=-35.657,system_time=0.000027073,last_offset=-0.000013616,residual_freq=-0,rms_offset=0.000027073,root_delay=0.000644,root_dispersion=0.003444,skew=0.001,update_interval=1031.2 1463750789687639161
|
chrony,leap_status=not\ synchronized,reference_id=A29FC87B,stratum=3 frequency=-16.000999450683594,last_offset=0.000012651000361074694,residual_freq=0,rms_offset=0.000025576999178156257,root_delay=0.0016550000291317701,root_dispersion=0.00330700003542006,skew=0.006000000052154064,system_time=0.000020389999917824753,update_interval=507.1999816894531 1706271167571675297
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,30 @@ import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"net"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
fbchrony "github.com/facebook/time/ntp/chrony"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed sample.conf
|
//go:embed sample.conf
|
||||||
var sampleConfig string
|
var sampleConfig string
|
||||||
|
|
||||||
var (
|
|
||||||
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
|
||||||
)
|
|
||||||
|
|
||||||
type Chrony struct {
|
type Chrony struct {
|
||||||
|
Server string `toml:"server"`
|
||||||
|
Timeout config.Duration `toml:"timeout"`
|
||||||
DNSLookup bool `toml:"dns_lookup"`
|
DNSLookup bool `toml:"dns_lookup"`
|
||||||
path string
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
|
||||||
|
conn net.Conn
|
||||||
|
client *fbchrony.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Chrony) SampleConfig() string {
|
func (*Chrony) SampleConfig() string {
|
||||||
|
|
@ -32,98 +36,134 @@ func (*Chrony) SampleConfig() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chrony) Init() error {
|
func (c *Chrony) Init() error {
|
||||||
var err error
|
if c.Server != "" {
|
||||||
c.path, err = exec.LookPath("chronyc")
|
// Check the specified server address
|
||||||
|
u, err := url.Parse(c.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("chronyc not found: verify that chrony is installed and that chronyc is in your PATH")
|
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
|
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 {
|
func (c *Chrony) Gather(acc telegraf.Accumulator) error {
|
||||||
flags := []string{}
|
req := fbchrony.NewTrackingPacket()
|
||||||
if !c.DNSLookup {
|
resp, err := c.client.Communicate(req)
|
||||||
flags = append(flags, "-n")
|
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)
|
||||||
}
|
}
|
||||||
flags = append(flags, "tracking")
|
|
||||||
|
|
||||||
cmd := execCommand(c.path, flags...)
|
// according to https://github.com/mlichvar/chrony/blob/e11b518a1ffa704986fb1f1835c425844ba248ef/ntp.h#L70
|
||||||
out, err := internal.CombinedOutputTimeout(cmd, time.Second*5)
|
var leapStatus string
|
||||||
if err != nil {
|
switch tracking.LeapStatus {
|
||||||
return fmt.Errorf("failed to run command %q: %w - %s", strings.Join(cmd.Args, " "), err, string(out))
|
case 0:
|
||||||
|
leapStatus = "normal"
|
||||||
|
case 1:
|
||||||
|
leapStatus = "insert second"
|
||||||
|
case 2:
|
||||||
|
leapStatus = "delete second"
|
||||||
|
case 3:
|
||||||
|
leapStatus = "not synchronized"
|
||||||
}
|
}
|
||||||
fields, tags, err := processChronycOutput(string(out))
|
|
||||||
if err != nil {
|
tags := map[string]string{
|
||||||
return err
|
"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)
|
acc.AddFields("chrony", fields, tags)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processChronycOutput takes in a string output from the chronyc command, like:
|
|
||||||
//
|
|
||||||
// Reference ID : 192.168.1.22 (ntp.example.com)
|
|
||||||
// Stratum : 3
|
|
||||||
// Ref time (UTC) : Thu May 12 14:27:07 2016
|
|
||||||
// System time : 0.000020390 seconds fast of NTP time
|
|
||||||
// Last offset : +0.000012651 seconds
|
|
||||||
// RMS offset : 0.000025577 seconds
|
|
||||||
// Frequency : 16.001 ppm slow
|
|
||||||
// Residual freq : -0.000 ppm
|
|
||||||
// Skew : 0.006 ppm
|
|
||||||
// Root delay : 0.001655 seconds
|
|
||||||
// Root dispersion : 0.003307 seconds
|
|
||||||
// Update interval : 507.2 seconds
|
|
||||||
// Leap status : Normal
|
|
||||||
//
|
|
||||||
// The value on the left side of the colon is used as field name, if the first field on
|
|
||||||
// the right side is a float. If it cannot be parsed as float, it is a tag name.
|
|
||||||
//
|
|
||||||
// Ref time is ignored and all names are converted to snake case.
|
|
||||||
//
|
|
||||||
// It returns (<fields>, <tags>)
|
|
||||||
func processChronycOutput(out string) (map[string]interface{}, map[string]string, error) {
|
|
||||||
tags := map[string]string{}
|
|
||||||
fields := map[string]interface{}{}
|
|
||||||
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
stats := strings.Split(line, ":")
|
|
||||||
if len(stats) < 2 {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected output from chronyc, expected ':' in %s", out)
|
|
||||||
}
|
|
||||||
name := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(stats[0]), " ", "_"))
|
|
||||||
// ignore reference time
|
|
||||||
if strings.Contains(name, "ref_time") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
valueFields := strings.Fields(stats[1])
|
|
||||||
if len(valueFields) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected output from chronyc: %s", out)
|
|
||||||
}
|
|
||||||
if strings.Contains(strings.ToLower(name), "stratum") {
|
|
||||||
tags["stratum"] = valueFields[0]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(strings.ToLower(name), "reference_id") {
|
|
||||||
tags["reference_id"] = valueFields[0]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseFloat(valueFields[0], 64)
|
|
||||||
if err != nil {
|
|
||||||
tags[name] = strings.ToLower(strings.Join(valueFields, " "))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(stats[1], "slow") {
|
|
||||||
value = -value
|
|
||||||
}
|
|
||||||
fields[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields, tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("chrony", func() telegraf.Input {
|
inputs.Add("chrony", func() telegraf.Input {
|
||||||
return &Chrony{}
|
return &Chrony{Timeout: config.Duration(3 * time.Second)}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,74 @@
|
||||||
package chrony
|
package chrony
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"math"
|
||||||
"os/exec"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fbchrony "github.com/facebook/time/ntp/chrony"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/metric"
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGather(t *testing.T) {
|
func TestGather(t *testing.T) {
|
||||||
c := Chrony{
|
// Setup a mock server
|
||||||
path: "chronyc",
|
server := Server{
|
||||||
|
TrackingInfo: &fbchrony.Tracking{
|
||||||
|
RefID: 0xA29FC87B,
|
||||||
|
IPAddr: net.ParseIP("192.168.1.22"),
|
||||||
|
Stratum: 3,
|
||||||
|
LeapStatus: 3,
|
||||||
|
RefTime: time.Now(),
|
||||||
|
CurrentCorrection: 0.000020390,
|
||||||
|
LastOffset: 0.000012651,
|
||||||
|
RMSOffset: 0.000025577,
|
||||||
|
FreqPPM: -16.001,
|
||||||
|
ResidFreqPPM: 0.0,
|
||||||
|
SkewPPM: 0.006,
|
||||||
|
RootDelay: 0.001655,
|
||||||
|
RootDispersion: 0.003307,
|
||||||
|
LastUpdateInterval: 507.2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
// overwriting exec commands with mock commands
|
addr, err := server.Listen(t)
|
||||||
execCommand = fakeExecCommand
|
require.NoError(t, err)
|
||||||
defer func() { execCommand = exec.Command }()
|
defer server.Shutdown()
|
||||||
|
|
||||||
|
// Setup the plugin
|
||||||
|
plugin := &Chrony{
|
||||||
|
Server: "udp://" + addr,
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Start the plugin, do a gather and stop everything
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, plugin.Start(&acc))
|
||||||
|
defer plugin.Stop()
|
||||||
|
require.NoError(t, plugin.Gather(&acc))
|
||||||
|
plugin.Stop()
|
||||||
|
server.Shutdown()
|
||||||
|
|
||||||
err := c.Gather(&acc)
|
// Do the comparison
|
||||||
if err != nil {
|
expected := []telegraf.Metric{
|
||||||
t.Fatal(err)
|
metric.New(
|
||||||
}
|
"chrony",
|
||||||
|
map[string]string{
|
||||||
tags := map[string]string{
|
"reference_id": "A29FC87B",
|
||||||
"reference_id": "192.168.1.22",
|
|
||||||
"leap_status": "not synchronized",
|
"leap_status": "not synchronized",
|
||||||
"stratum": "3",
|
"stratum": "3",
|
||||||
}
|
},
|
||||||
fields := map[string]interface{}{
|
map[string]interface{}{
|
||||||
"system_time": 0.000020390,
|
"system_time": 0.000020390,
|
||||||
"last_offset": 0.000012651,
|
"last_offset": 0.000012651,
|
||||||
"rms_offset": 0.000025577,
|
"rms_offset": 0.000025577,
|
||||||
|
|
@ -38,75 +78,252 @@ func TestGather(t *testing.T) {
|
||||||
"root_delay": 0.001655,
|
"root_delay": 0.001655,
|
||||||
"root_dispersion": 0.003307,
|
"root_dispersion": 0.003307,
|
||||||
"update_interval": 507.2,
|
"update_interval": 507.2,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests on linux with go1.20 will add a warning about code coverage
|
options := []cmp.Option{
|
||||||
// due to the code coverage dir not being set
|
// tests on linux with go1.20 will add a warning about code coverage, ignore that tag
|
||||||
delete(acc.Metrics[0].Tags, "warning")
|
testutil.IgnoreTags("warning"),
|
||||||
|
testutil.IgnoreTime(),
|
||||||
|
cmpopts.EquateApprox(0.001, 0),
|
||||||
|
}
|
||||||
|
|
||||||
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
|
actual := acc.GetTelegrafMetrics()
|
||||||
|
testutil.RequireMetricsEqual(t, expected, actual, options...)
|
||||||
|
}
|
||||||
|
|
||||||
// test with dns lookup
|
func TestIntegration(t *testing.T) {
|
||||||
c.DNSLookup = true
|
if testing.Short() {
|
||||||
err = c.Gather(&acc)
|
t.Skip("Skipping integration test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the docker container
|
||||||
|
container := testutil.Container{
|
||||||
|
Image: "dockurr/chrony",
|
||||||
|
ExposedPorts: []string{"323/udp"},
|
||||||
|
Files: map[string]string{
|
||||||
|
"/etc/telegraf-chrony.conf": "testdata/chrony.conf",
|
||||||
|
"/start.sh": "testdata/start.sh",
|
||||||
|
},
|
||||||
|
Entrypoint: []string{"/start.sh"},
|
||||||
|
WaitingFor: wait.ForLog("Selected source"),
|
||||||
|
}
|
||||||
|
require.NoError(t, container.Start(), "failed to start container")
|
||||||
|
defer container.Terminate()
|
||||||
|
|
||||||
|
// Setup the plugin
|
||||||
|
plugin := &Chrony{
|
||||||
|
Server: "udp://" + container.Address + ":" + container.Ports["323"],
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Collect the metrics and compare
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, plugin.Start(&acc))
|
||||||
|
defer plugin.Stop()
|
||||||
|
require.NoError(t, plugin.Gather(&acc))
|
||||||
|
|
||||||
|
// Setup the expectations
|
||||||
|
expected := []telegraf.Metric{
|
||||||
|
metric.New(
|
||||||
|
"chrony",
|
||||||
|
map[string]string{
|
||||||
|
"leap_status": "normal",
|
||||||
|
"reference_id": "A29FC87B",
|
||||||
|
"stratum": "4",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"frequency": float64(0),
|
||||||
|
"last_offset": float64(0),
|
||||||
|
"residual_freq": float64(0),
|
||||||
|
"rms_offset": float64(0),
|
||||||
|
"root_delay": float64(0),
|
||||||
|
"root_dispersion": float64(0),
|
||||||
|
"skew": float64(0),
|
||||||
|
"system_time": float64(0),
|
||||||
|
"update_interval": float64(0),
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
options := []cmp.Option{
|
||||||
|
testutil.IgnoreTags("leap_status", "reference_id", "stratum"),
|
||||||
|
testutil.IgnoreTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := acc.GetTelegrafMetrics()
|
||||||
|
testutil.RequireMetricsStructureEqual(t, expected, actual, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
TrackingInfo *fbchrony.Tracking
|
||||||
|
|
||||||
|
conn net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Shutdown() {
|
||||||
|
if s.conn != nil {
|
||||||
|
s.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Listen(t *testing.T) (string, error) {
|
||||||
|
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
return "", err
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
|
s.conn = conn
|
||||||
|
addr := s.conn.LocalAddr().String()
|
||||||
|
|
||||||
|
go s.serve(t)
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeExecCommand is a helper function that mock
|
func (s *Server) serve(t *testing.T) {
|
||||||
// the exec.Command call (and call the test binary)
|
defer s.conn.Close()
|
||||||
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
|
||||||
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
|
||||||
cs = append(cs, args...)
|
|
||||||
cmd := exec.Command(os.Args[0], cs...)
|
|
||||||
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestHelperProcess isn't a real test. It's used to mock exec.Command
|
for {
|
||||||
// For example, if you run:
|
buf := make([]byte, 4096)
|
||||||
// GO_WANT_HELPER_PROCESS=1 go test -test.run=TestHelperProcess -- chrony tracking
|
n, addr, err := s.conn.ReadFrom(buf)
|
||||||
// it returns below mockData.
|
if err != nil {
|
||||||
func TestHelperProcess(_ *testing.T) {
|
|
||||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.Logf("mock server: received %d bytes from %q\n", n, addr.String())
|
||||||
|
|
||||||
lookup := "Reference ID : 192.168.1.22 (ntp.example.com)\n"
|
var header fbchrony.RequestHead
|
||||||
noLookup := "Reference ID : 192.168.1.22 (192.168.1.22)\n"
|
data := bytes.NewBuffer(buf)
|
||||||
mockData := `Stratum : 3
|
if err := binary.Read(data, binary.BigEndian, &header); err != nil {
|
||||||
Ref time (UTC) : Thu May 12 14:27:07 2016
|
t.Logf("mock server: reading request header failed: %v", err)
|
||||||
System time : 0.000020390 seconds fast of NTP time
|
return
|
||||||
Last offset : +0.000012651 seconds
|
|
||||||
RMS offset : 0.000025577 seconds
|
|
||||||
Frequency : 16.001 ppm slow
|
|
||||||
Residual freq : -0.000 ppm
|
|
||||||
Skew : 0.006 ppm
|
|
||||||
Root delay : 0.001655 seconds
|
|
||||||
Root dispersion : 0.003307 seconds
|
|
||||||
Update interval : 507.2 seconds
|
|
||||||
Leap status : Not synchronized
|
|
||||||
`
|
|
||||||
|
|
||||||
args := os.Args
|
|
||||||
|
|
||||||
// Previous arguments are tests stuff, that looks like :
|
|
||||||
// /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess --
|
|
||||||
cmd, args := args[3], args[4:]
|
|
||||||
|
|
||||||
if cmd != "chronyc" {
|
|
||||||
fmt.Fprint(os.Stdout, "command not found")
|
|
||||||
//nolint:revive // error code is important for this "test"
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if args[0] == "tracking" {
|
seqno := header.Sequence + 1
|
||||||
fmt.Fprint(os.Stdout, lookup+mockData)
|
|
||||||
|
switch header.Command {
|
||||||
|
case 33: // tracking
|
||||||
|
_, err := s.conn.WriteTo(s.encodeTrackingReply(seqno), addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("mock server [tracking]: writing reply failed: %v", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(os.Stdout, noLookup+mockData)
|
t.Log("mock server [tracking]: successfully wrote reply")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Logf("mock server: unhandled command %v", header.Command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) encodeTrackingReply(sequence uint32) []byte {
|
||||||
|
t := s.TrackingInfo
|
||||||
|
|
||||||
|
// Encode the header
|
||||||
|
buf := []byte{
|
||||||
|
0x06, // version 6
|
||||||
|
0x02, // packet type 2: tracking
|
||||||
|
0x00, // res1
|
||||||
|
0x00, // res2
|
||||||
|
0x00, 0x21, // command 33: tracking request
|
||||||
|
0x00, 0x05, // reply 5: tracking reply
|
||||||
|
0x00, 0x00, // status 0: success
|
||||||
|
0x00, 0x00, // pad1
|
||||||
|
0x00, 0x00, // pad2
|
||||||
|
0x00, 0x00, // pad3
|
||||||
|
}
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, sequence) // sequence number
|
||||||
|
buf = append(buf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // pad 4 & 5
|
||||||
|
|
||||||
|
// Encode data
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, t.RefID)
|
||||||
|
buf = append(buf, t.IPAddr.To16()...)
|
||||||
|
if len(t.IPAddr) == 4 {
|
||||||
|
buf = append(buf, 0x00, 0x01) // IPv4 address family
|
||||||
|
} else {
|
||||||
|
buf = append(buf, 0x00, 0x02) // IPv6 address family
|
||||||
|
}
|
||||||
|
buf = append(buf, 0x00, 0x00) // padding
|
||||||
|
buf = binary.BigEndian.AppendUint16(buf, t.Stratum)
|
||||||
|
buf = binary.BigEndian.AppendUint16(buf, t.LeapStatus)
|
||||||
|
sec := uint64(t.RefTime.Unix())
|
||||||
|
nsec := uint32(t.RefTime.UnixNano() % t.RefTime.Unix() * int64(time.Second))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, uint32(sec>>32)) // seconds high part
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, uint32(sec&0xffffffff)) // seconds low part
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, nsec) // nanoseconds
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.CurrentCorrection))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.LastOffset))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.RMSOffset))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.FreqPPM))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.ResidFreqPPM))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.SkewPPM))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.RootDelay))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.RootDispersion))
|
||||||
|
buf = binary.BigEndian.AppendUint32(buf, encodeFloat(t.LastUpdateInterval))
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified based on https://github.com/mlichvar/chrony/blob/master/util.c
|
||||||
|
const (
|
||||||
|
floatExpBits = int32(7)
|
||||||
|
floatCoeffBits = int32(25) // 32 - floatExpBits
|
||||||
|
floatExpMin = int32(-(1 << (floatExpBits - 1)))
|
||||||
|
floatExpMax = -floatExpMin - 1
|
||||||
|
floatCoefMin = int32(-(1 << (floatCoeffBits - 1)))
|
||||||
|
floatCoefMax = -floatCoefMin - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeFloat(x float64) uint32 {
|
||||||
|
var neg int32
|
||||||
|
|
||||||
|
if math.IsNaN(x) {
|
||||||
|
/* Save NaN as zero */
|
||||||
|
x = 0.0
|
||||||
|
} else if x < 0.0 {
|
||||||
|
x = -x
|
||||||
|
neg = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:revive // error code is important for this "test"
|
var exp, coef int32
|
||||||
os.Exit(0)
|
if x > 1.0e100 {
|
||||||
|
exp = floatExpMax
|
||||||
|
coef = floatCoefMax + neg
|
||||||
|
} else if x > 1.0e-100 {
|
||||||
|
exp = int32(math.Log2(x)) + 1
|
||||||
|
coef = int32(x*math.Pow(2.0, float64(-exp+floatCoeffBits)) + 0.5)
|
||||||
|
|
||||||
|
if coef <= 0 {
|
||||||
|
panic(fmt.Errorf("invalid coefficient %v for value %f", coef, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we may need to shift up to two bits down */
|
||||||
|
for coef > floatCoefMax+neg {
|
||||||
|
coef >>= 1
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp > floatExpMax {
|
||||||
|
/* overflow */
|
||||||
|
exp = floatExpMax
|
||||||
|
coef = floatCoefMax + neg
|
||||||
|
} else if exp < floatExpMin {
|
||||||
|
/* underflow */
|
||||||
|
if exp+floatCoeffBits >= floatExpMin {
|
||||||
|
coef >>= floatExpMin - exp
|
||||||
|
exp = floatExpMin
|
||||||
|
} else {
|
||||||
|
exp = 0
|
||||||
|
coef = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* negate back */
|
||||||
|
if neg != 0 {
|
||||||
|
coef = int32(uint32(-coef) % (1 << floatCoeffBits))
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32(exp<<floatCoeffBits) | uint32(coef)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
# Get standard chrony metrics, requires chronyc executable.
|
# Get standard chrony metrics, requires chronyc executable.
|
||||||
[[inputs.chrony]]
|
[[inputs.chrony]]
|
||||||
## If true, chronyc tries to perform a DNS lookup for the time server.
|
## Server address of chronyd with address scheme
|
||||||
|
## If empty or not set, the plugin will mimic the behavior of chronyc and
|
||||||
|
## check "unix:///run/chrony/chronyd.sock", "udp://127.0.0.1:323"
|
||||||
|
## and "udp://[::1]:323".
|
||||||
|
# server = ""
|
||||||
|
|
||||||
|
## Timeout for establishing the connection
|
||||||
|
# timeout = "5s"
|
||||||
|
|
||||||
|
## Try to resolve received addresses to host-names via DNS lookups
|
||||||
|
## Disabled by default to avoid DNS queries especially for slow DNS servers.
|
||||||
# dns_lookup = false
|
# dns_lookup = false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
server 0.pool.ntp.org iburst
|
||||||
|
server 1.pool.ntp.org iburst
|
||||||
|
server 2.pool.ntp.org iburst
|
||||||
|
server 3.pool.ntp.org iburst
|
||||||
|
|
||||||
|
driftfile /var/lib/chrony/chrony.drift
|
||||||
|
makestep 0.1 3
|
||||||
|
|
||||||
|
bindcmdaddress 0.0.0.0
|
||||||
|
cmdallow all
|
||||||
|
allow all
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# confirm correct permissions on chrony run directory
|
||||||
|
if [ -d /run/chrony ]; then
|
||||||
|
chown -R chrony:chrony /run/chrony
|
||||||
|
chmod o-rx /run/chrony
|
||||||
|
# remove previous pid file if it exist
|
||||||
|
rm -f /var/run/chrony/chronyd.pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
# confirm correct permissions on chrony variable state directory
|
||||||
|
if [ -d /var/lib/chrony ]; then
|
||||||
|
chown -R chrony:chrony /var/lib/chrony
|
||||||
|
fi
|
||||||
|
|
||||||
|
## startup chronyd in the foreground
|
||||||
|
exec /usr/sbin/chronyd -u chrony -d -x -f /etc/telegraf-chrony.conf
|
||||||
Loading…
Reference in New Issue