blob: bd6a90b94807ae3ef67a57925edab4c702c671dc [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package clock
import (
"math"
"time"
"v.io/x/lib/vlog"
"v.io/x/ref/services/syncbase/server/util"
"v.io/x/ref/services/syncbase/store"
)
// ProcessPeerClockData accepts PeerSyncData and local ClockData and updates
// local ClockData and persists it if peer's clock is more accurate. Peer's
// clock is more accurate if all of the following conditions are met:
// 1) diff between the two clocks is > 2 seconds (avoids constant tweaking).
// 2) peer synced with NTP and it was more recent than local.
// 3) peer has not rebooted since NTP or the peer has rebooted since NTP but
// the difference btw the two clocks is < 1 minute.
// 4) num hops for peer's NTP sync data is < 2, i.e. either the peer has
// synced with NTP itself or it synced with another peer that did NTP.
// Retruns true if the local clock was updated.
func (c *VClock) ProcessPeerClockData(tx store.Transaction, resp *PeerSyncData, localData *ClockData) bool {
offset := (resp.RecvTs.Sub(resp.MySendTs) + resp.SendTs.Sub(resp.MyRecvTs)) / 2
vlog.VI(2).Infof("clock: ProcessPeerClockData: offset between two clocks: %v", offset)
if math.Abs(float64(offset.Nanoseconds())) <= util.PeerSyncDiffThreshold {
vlog.VI(2).Info("clock: ProcessPeerClockData: the two clocks are synced within PeerSyncDiffThreshold.")
return false
}
if resp.LastNtpTs == nil {
vlog.VI(2).Info("clock: ProcessPeerClockData: peer clock has not synced to NTP. Ignoring peer's clock.")
return false
}
if (localData != nil) && !isPeerNtpSyncMoreRecent(localData.LastNtpTs, resp.LastNtpTs) {
vlog.VI(2).Info("clock: ProcessPeerClockData: peer NTP sync is less recent than local.")
return false
}
if isOverRebootTolerance(offset, resp.NumReboots) {
vlog.VI(2).Info("clock: ProcessPeerClockData: peer clock is over reboot tolerance.")
return false
}
if resp.NumHops >= util.HopTolerance {
vlog.VI(2).Info("clock: ProcessPeerClockData: peer clock is over hop tolerance.")
return false
}
vlog.VI(2).Info("clock: ProcessPeerClockData: peer's clock is more accurate than local clock. Syncing to peer's clock.")
return c.updateClockData(tx, resp, offset, localData)
}
// updateClockData updates the clock data for the local clock based on peer
// clock's data.
// Returs true if update succeeds.
func (c *VClock) updateClockData(tx store.Transaction, peerResp *PeerSyncData, offset time.Duration, localData *ClockData) bool {
systemTime := c.SysClock.Now()
elapsedTime, err := c.SysClock.ElapsedTime()
if err != nil {
vlog.Errorf("clock: ProcessPeerClockData: error while fetching elapsed time: %v", err)
return false
}
systemTimeAtBoot := systemTime.Add(-elapsedTime)
var skew time.Duration
if localData == nil {
// localData nil means that the local estimated UTC time is equal to
// the system time. Hence the offset was in reality between the local
// system clock and the remote estimated UTC time.
skew = offset
} else {
skew = time.Duration(localData.Skew) + offset
}
newClockData := &ClockData{
SystemTimeAtBoot: systemTimeAtBoot.UnixNano(),
Skew: skew.Nanoseconds(),
ElapsedTimeSinceBoot: elapsedTime.Nanoseconds(),
LastNtpTs: peerResp.LastNtpTs,
NumReboots: peerResp.NumReboots,
NumHops: (peerResp.NumHops + 1),
}
if err := c.SetClockData(tx, newClockData); err != nil {
vlog.Errorf("clock: ProcessPeerClockData: error while setting new clock data: %v", err)
return false
}
return true
}
func isPeerNtpSyncMoreRecent(localNtpTs, peerNtpTs *time.Time) bool {
if localNtpTs == nil {
return true
}
if peerNtpTs == nil {
return false
}
return peerNtpTs.After(*localNtpTs)
}
func isOverRebootTolerance(offset time.Duration, numReboots uint16) bool {
return (math.Abs(float64(offset.Nanoseconds())) > util.RebootTolerance) && (numReboots > 0)
}