Merge pull request #250 from sky-uk/ntp-rtt

Use the offset calculation that includes round trip time in the ntp collector
This commit is contained in:
Julius Volz 2016-06-01 19:44:53 -05:00
commit d4799999fc
6 changed files with 145 additions and 40 deletions

5
.gitignore vendored
View file

@ -26,3 +26,8 @@ dependencies-stamp
/.deps /.deps
/.release /.release
/.tarballs /.tarballs
# Intellij
/.idea
*.iml

View file

@ -18,7 +18,6 @@ package collector
import ( import (
"flag" "flag"
"fmt" "fmt"
"time"
"github.com/beevik/ntp" "github.com/beevik/ntp"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -58,13 +57,13 @@ func NewNtpCollector() (Collector, error) {
} }
func (c *ntpCollector) Update(ch chan<- prometheus.Metric) (err 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 { if err != nil {
return fmt.Errorf("couldn't get NTP drift: %s", err) return fmt.Errorf("couldn't get NTP drift: %s", err)
} }
drift := t.Sub(time.Now()) driftSeconds := resp.ClockOffset.Seconds()
log.Debugf("Set ntp_drift_seconds: %f", drift.Seconds()) log.Debugf("Set ntp_drift_seconds: %f", driftSeconds)
c.drift.Set(drift.Seconds()) c.drift.Set(driftSeconds)
c.drift.Collect(ch) c.drift.Collect(ch)
return err return err
} }

4
vendor/github.com/beevik/ntp/CONTRIBUTORS generated vendored Normal file
View file

@ -0,0 +1,4 @@
Brett Vickers (beevik)
Mikhail Salosin (AlphaB)
Anton Tolchanov (knyar)
Christopher Batey (chbatey)

View file

@ -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 ntp
=== ===

159
vendor/github.com/beevik/ntp/ntp.go generated vendored
View file

@ -2,10 +2,11 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package ntp provides a simple mechanism for querying the current time // Package ntp provides a simple mechanism for querying the current time from
// from a remote NTP server. This package only supports NTP client mode // a remote NTP server. This package only supports NTP client mode behavior
// behavior and version 4 of the NTP protocol. See RFC 5905. // and version 4 of the NTP protocol. See RFC 5905. Approach inspired by go-
// Approach inspired by go-nuts post by Michael Hofmann: // nuts post by Michael Hofmann:
//
// https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/FlcdMU5fkLQ // https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/FlcdMU5fkLQ
package ntp package ntp
@ -28,16 +29,43 @@ const (
reservedPrivate 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 { type ntpTime struct {
Seconds uint32 Seconds uint32
Fraction uint32 Fraction uint32
} }
func (t ntpTime) UTC() time.Time { func (t ntpTime) Time() time.Time {
nsec := uint64(t.Seconds)*1e9 + (uint64(t.Fraction) * 1e9 >> 32) return ntpEpoch.Add(t.sinceEpoch())
return time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec))
} }
// 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 { type msg struct {
LiVnMode byte // Leap Indicator (2) + Version (3) + Mode (3) LiVnMode byte // Leap Indicator (2) + Version (3) + Mode (3)
Stratum byte Stratum byte
@ -45,69 +73,134 @@ type msg struct {
Precision byte Precision byte
RootDelay uint32 RootDelay uint32
RootDispersion uint32 RootDispersion uint32
ReferenceId uint32 ReferenceID uint32
ReferenceTime ntpTime ReferenceTime ntpTime
OriginTime ntpTime OriginTime ntpTime
ReceiveTime ntpTime ReceiveTime ntpTime
TransmitTime ntpTime TransmitTime ntpTime
} }
// SetVersion sets the NTP protocol version on the message. // setVersion sets the NTP protocol version on the message.
func (m *msg) SetVersion(v byte) { func (m *msg) setVersion(v int) {
m.LiVnMode = (m.LiVnMode & 0xc7) | v<<3 m.LiVnMode = (m.LiVnMode & 0xc7) | uint8(v)<<3
} }
// SetMode sets the NTP protocol mode on the message. // setMode sets the NTP protocol mode on the message.
func (m *msg) SetMode(md mode) { func (m *msg) setMode(md mode) {
m.LiVnMode = (m.LiVnMode & 0xf8) | byte(md) m.LiVnMode = (m.LiVnMode & 0xf8) | byte(md)
} }
// Time returns the "receive time" from the remote NTP server // A Response contains time data, some of which is returned by the NTP server
// specifed as host. NTP client mode is used. // and some of which is calculated by the client.
func getTime(host string, version byte) (time.Time, error) { 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 { if version < 2 || version > 4 {
panic("ntp: invalid version number") panic("ntp: invalid version number")
} }
raddr, err := net.ResolveUDPAddr("udp", host+":123") raddr, err := net.ResolveUDPAddr("udp", host+":123")
if err != nil { if err != nil {
return time.Now(), err return nil, err
} }
con, err := net.DialUDP("udp", nil, raddr) con, err := net.DialUDP("udp", nil, raddr)
if err != nil { if err != nil {
return time.Now(), err return nil, err
} }
defer con.Close() defer con.Close()
con.SetDeadline(time.Now().Add(5 * time.Second)) con.SetDeadline(time.Now().Add(timeout))
m := new(msg) m := new(msg)
m.SetMode(client) m.setMode(client)
m.SetVersion(version) m.setVersion(version)
m.TransmitTime = toNtpTime(time.Now())
err = binary.Write(con, binary.BigEndian, m) err = binary.Write(con, binary.BigEndian, m)
if err != nil { if err != nil {
return time.Now(), err return nil, err
} }
err = binary.Read(con, binary.BigEndian, m) err = binary.Read(con, binary.BigEndian, m)
if err != nil { if err != nil {
return time.Now(), err return nil, err
} }
t := m.ReceiveTime.UTC().Local() return m, nil
return t, nil
} }
// TimeV returns the "receive time" from the remote NTP server // TimeV returns the "receive time" from the remote NTP server specifed as
// specifed as host. Use the NTP client mode with the requested // host. Use the NTP client mode with the requested version number (2, 3, or
// version number (2, 3, or 4). // 4).
func TimeV(host string, version byte) (time.Time, error) { func TimeV(host string, version int) (time.Time, error) {
return getTime(host, version) 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 // Time returns the "receive time" from the remote NTP server specifed as
// specifed as host. NTP client mode version 4 is used. // host. NTP client mode version 4 is used.
func Time(host string) (time.Time, error) { 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)
} }

5
vendor/vendor.json vendored
View file

@ -8,9 +8,10 @@
"revisionTime": "2016-01-18T19:00:32-05:00" "revisionTime": "2016-01-18T19:00:32-05:00"
}, },
{ {
"checksumSHA1": "JhaYHdVIj52Fpdcb7DuDjr/gk0Q=",
"path": "github.com/beevik/ntp", "path": "github.com/beevik/ntp",
"revision": "283ed9d548825a1dae0994311560e8dbf8efac68", "revision": "f0545e6f2c3cb0d0a2ed115b88c539d8e5247ef3",
"revisionTime": "2015-11-09T15:30:19-08:00" "revisionTime": "2016-05-31T23:09:58Z"
}, },
{ {
"path": "github.com/beorn7/perks/quantile", "path": "github.com/beorn7/perks/quantile",