From 387c64424b568293d04ac1f8ae90539cf4141301 Mon Sep 17 00:00:00 2001 From: Chris Batey Date: Wed, 1 Jun 2016 08:56:20 +0100 Subject: [PATCH] Use the offset calculation that includes round trip time in the ntp collector Previously the raw time difference was used which includes the network trip time between the node and the ntp server. This makes setting alerts off the value troublesome as it depends on the latency as well as the clock offset. --- collector/ntp.go | 9 +- vendor/github.com/beevik/ntp/CONTRIBUTORS | 4 + vendor/github.com/beevik/ntp/README.md | 3 + vendor/github.com/beevik/ntp/ntp.go | 159 +++++++++++++++++----- vendor/vendor.json | 5 +- 5 files changed, 140 insertions(+), 40 deletions(-) create mode 100644 vendor/github.com/beevik/ntp/CONTRIBUTORS diff --git a/collector/ntp.go b/collector/ntp.go index f9a1e1a5..12426f91 100644 --- a/collector/ntp.go +++ b/collector/ntp.go @@ -18,7 +18,6 @@ package collector import ( "flag" "fmt" - "time" "github.com/beevik/ntp" "github.com/prometheus/client_golang/prometheus" @@ -58,13 +57,13 @@ func NewNtpCollector() (Collector, error) { } func (c *ntpCollector) Update(ch chan<- prometheus.Metric) (err error) { - t, err := ntp.TimeV(*ntpServer, byte(*ntpProtocolVersion)) + resp, err := ntp.Query(*ntpServer, *ntpProtocolVersion) if err != nil { return fmt.Errorf("couldn't get NTP drift: %s", err) } - drift := t.Sub(time.Now()) - log.Debugf("Set ntp_drift_seconds: %f", drift.Seconds()) - c.drift.Set(drift.Seconds()) + driftSeconds := resp.ClockOffset.Seconds() + log.Debugf("Set ntp_drift_seconds: %f", driftSeconds) + c.drift.Set(driftSeconds) c.drift.Collect(ch) return err } diff --git a/vendor/github.com/beevik/ntp/CONTRIBUTORS b/vendor/github.com/beevik/ntp/CONTRIBUTORS new file mode 100644 index 00000000..7b3b2e22 --- /dev/null +++ b/vendor/github.com/beevik/ntp/CONTRIBUTORS @@ -0,0 +1,4 @@ +Brett Vickers (beevik) +Mikhail Salosin (AlphaB) +Anton Tolchanov (knyar) +Christopher Batey (chbatey) diff --git a/vendor/github.com/beevik/ntp/README.md b/vendor/github.com/beevik/ntp/README.md index 17457522..ef86b0de 100644 --- a/vendor/github.com/beevik/ntp/README.md +++ b/vendor/github.com/beevik/ntp/README.md @@ -1,3 +1,6 @@ +[![Build Status](https://travis-ci.org/beevik/ntp.svg?branch=master)](https://travis-ci.org/beevik/ntp) +[![GoDoc](https://godoc.org/github.com/beevik/ntp?status.svg)](https://godoc.org/github.com/beevik/ntp) + ntp === diff --git a/vendor/github.com/beevik/ntp/ntp.go b/vendor/github.com/beevik/ntp/ntp.go index e6c2efc0..d0575d71 100644 --- a/vendor/github.com/beevik/ntp/ntp.go +++ b/vendor/github.com/beevik/ntp/ntp.go @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package ntp provides a simple mechanism for querying the current time -// from a remote NTP server. This package only supports NTP client mode -// behavior and version 4 of the NTP protocol. See RFC 5905. -// Approach inspired by go-nuts post by Michael Hofmann: +// Package ntp provides a simple mechanism for querying the current time from +// a remote NTP server. This package only supports NTP client mode behavior +// and version 4 of the NTP protocol. See RFC 5905. Approach inspired by go- +// nuts post by Michael Hofmann: +// // https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/FlcdMU5fkLQ package ntp @@ -28,16 +29,43 @@ const ( reservedPrivate ) +const ( + maxStratum = 16 + nanoPerSec = 1000000000 +) + +var ( + timeout = 5 * time.Second + ntpEpoch = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC) +) + type ntpTime struct { Seconds uint32 Fraction uint32 } -func (t ntpTime) UTC() time.Time { - nsec := uint64(t.Seconds)*1e9 + (uint64(t.Fraction) * 1e9 >> 32) - return time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)) +func (t ntpTime) Time() time.Time { + return ntpEpoch.Add(t.sinceEpoch()) } +// sinceEpoch converts the ntpTime record t into a duration since the NTP +// epoch time (Jan 1, 1900). +func (t ntpTime) sinceEpoch() time.Duration { + sec := time.Duration(t.Seconds) * time.Second + frac := time.Duration(uint64(t.Fraction) * nanoPerSec >> 32) + return sec + frac +} + +// toNtpTime converts the time value t into an ntpTime representation. +func toNtpTime(t time.Time) ntpTime { + nsec := uint64(t.Sub(ntpEpoch)) + return ntpTime{ + Seconds: uint32(nsec / nanoPerSec), + Fraction: uint32((nsec % nanoPerSec) << 32 / nanoPerSec), + } +} + +// msg is an internal representation of an NTP packet. type msg struct { LiVnMode byte // Leap Indicator (2) + Version (3) + Mode (3) Stratum byte @@ -45,69 +73,134 @@ type msg struct { Precision byte RootDelay uint32 RootDispersion uint32 - ReferenceId uint32 + ReferenceID uint32 ReferenceTime ntpTime OriginTime ntpTime ReceiveTime ntpTime TransmitTime ntpTime } -// SetVersion sets the NTP protocol version on the message. -func (m *msg) SetVersion(v byte) { - m.LiVnMode = (m.LiVnMode & 0xc7) | v<<3 +// setVersion sets the NTP protocol version on the message. +func (m *msg) setVersion(v int) { + m.LiVnMode = (m.LiVnMode & 0xc7) | uint8(v)<<3 } -// SetMode sets the NTP protocol mode on the message. -func (m *msg) SetMode(md mode) { +// setMode sets the NTP protocol mode on the message. +func (m *msg) setMode(md mode) { m.LiVnMode = (m.LiVnMode & 0xf8) | byte(md) } -// Time returns the "receive time" from the remote NTP server -// specifed as host. NTP client mode is used. -func getTime(host string, version byte) (time.Time, error) { +// A Response contains time data, some of which is returned by the NTP server +// and some of which is calculated by the client. +type Response struct { + Time time.Time // receive time reported by the server + RTT time.Duration // round-trip time between client and server + ClockOffset time.Duration // local clock offset relative to server + Stratum uint8 // stratum level of NTP server's clock +} + +// Query returns information from the remote NTP server specifed as host. NTP +// client mode is used. +func Query(host string, version int) (*Response, error) { + m, err := getTime(host, version) + now := toNtpTime(time.Now()) + if err != nil { + return nil, err + } + + r := &Response{ + Time: m.ReceiveTime.Time(), + RTT: rtt(m.OriginTime, m.ReceiveTime, m.TransmitTime, now), + ClockOffset: offset(m.OriginTime, m.ReceiveTime, m.TransmitTime, now), + Stratum: m.Stratum, + } + + // https://tools.ietf.org/html/rfc5905#section-7.3 + if r.Stratum == 0 { + r.Stratum = maxStratum + } + + return r, nil +} + +// Time returns the "receive time" from the remote NTP server specifed as +// host. NTP client mode is used. +func getTime(host string, version int) (*msg, error) { if version < 2 || version > 4 { panic("ntp: invalid version number") } raddr, err := net.ResolveUDPAddr("udp", host+":123") if err != nil { - return time.Now(), err + return nil, err } con, err := net.DialUDP("udp", nil, raddr) if err != nil { - return time.Now(), err + return nil, err } defer con.Close() - con.SetDeadline(time.Now().Add(5 * time.Second)) + con.SetDeadline(time.Now().Add(timeout)) m := new(msg) - m.SetMode(client) - m.SetVersion(version) + m.setMode(client) + m.setVersion(version) + m.TransmitTime = toNtpTime(time.Now()) err = binary.Write(con, binary.BigEndian, m) if err != nil { - return time.Now(), err + return nil, err } err = binary.Read(con, binary.BigEndian, m) if err != nil { - return time.Now(), err + return nil, err } - t := m.ReceiveTime.UTC().Local() - return t, nil + return m, nil } -// TimeV returns the "receive time" from the remote NTP server -// specifed as host. Use the NTP client mode with the requested -// version number (2, 3, or 4). -func TimeV(host string, version byte) (time.Time, error) { - return getTime(host, version) +// TimeV returns the "receive time" from the remote NTP server specifed as +// host. Use the NTP client mode with the requested version number (2, 3, or +// 4). +func TimeV(host string, version int) (time.Time, error) { + m, err := getTime(host, version) + if err != nil { + return time.Now(), err + } + return m.ReceiveTime.Time().Local(), nil } -// Time returns the "receive time" from the remote NTP server -// specifed as host. NTP client mode version 4 is used. +// Time returns the "receive time" from the remote NTP server specifed as +// host. NTP client mode version 4 is used. func Time(host string) (time.Time, error) { - return getTime(host, 4) + return TimeV(host, 4) +} + +func rtt(t1, t2, t3, t4 ntpTime) time.Duration { + // round trip delay time (https://tools.ietf.org/html/rfc5905#section-8) + // T1 = client send time + // T2 = server receive time + // T3 = server reply time + // T4 = client receive time + // + // RTT d: + // d = (T4-T1) - (T3-T2) + a := t4.Time().Sub(t1.Time()) + b := t3.Time().Sub(t2.Time()) + return a - b +} + +func offset(t1, t2, t3, t4 ntpTime) time.Duration { + // local offset equation (https://tools.ietf.org/html/rfc5905#section-8) + // T1 = client send time + // T2 = server receive time + // T3 = server reply time + // T4 = client receive time + // + // Local clock offset t: + // t = ((T2-T1) + (T3-T4)) / 2 + a := t2.Time().Sub(t1.Time()) + b := t3.Time().Sub(t4.Time()) + return (a + b) / time.Duration(2) } diff --git a/vendor/vendor.json b/vendor/vendor.json index edd27bb7..9d394a99 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -8,9 +8,10 @@ "revisionTime": "2016-01-18T19:00:32-05:00" }, { + "checksumSHA1": "JhaYHdVIj52Fpdcb7DuDjr/gk0Q=", "path": "github.com/beevik/ntp", - "revision": "283ed9d548825a1dae0994311560e8dbf8efac68", - "revisionTime": "2015-11-09T15:30:19-08:00" + "revision": "f0545e6f2c3cb0d0a2ed115b88c539d8e5247ef3", + "revisionTime": "2016-05-31T23:09:58Z" }, { "path": "github.com/beorn7/perks/quantile",