phc/offset.go (89 lines of code) (raw):
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package phc
import (
"fmt"
"golang.org/x/sys/unix"
"os"
"time"
)
// SysoffResult is a result of PHC time measurement with related data
type SysoffResult struct {
Offset time.Duration
Delay time.Duration
SysTime time.Time
PHCTime time.Time
}
// based on calculate_offset from ptp4l phc_ctl.c
func sysoffEstimateBasic(ts1, rt, ts2 time.Time) SysoffResult {
interval := ts2.Sub(ts1)
sysTime := ts1.Add(interval / 2)
offset := ts2.Sub(rt) - (interval / 2)
return SysoffResult{
SysTime: sysTime,
PHCTime: rt,
Delay: ts2.Sub(ts1),
Offset: offset,
}
}
// loosely based on sysoff_estimate from ptp4l sysoff.c
func sysoffEstimateExtended(extended *PTPSysOffsetExtended) SysoffResult {
t1 := extended.TS[0][0].Time()
tp := extended.TS[0][1].Time()
t2 := extended.TS[0][2].Time()
shortestInterval := t2.Sub(t1)
bestSysTS := t1.Add(shortestInterval / 2)
bestPhcTS := tp
bestOffset := bestSysTS.Sub(tp)
for i := 1; i < int(extended.NSamples); i++ {
t1 := extended.TS[i][0].Time()
tp := extended.TS[i][1].Time()
t2 := extended.TS[i][2].Time()
interval := t2.Sub(t1)
timestamp := t1.Add(interval / 2)
offset := timestamp.Sub(tp)
if interval < shortestInterval {
shortestInterval = interval
bestSysTS = timestamp
bestOffset = offset
bestPhcTS = tp
}
}
return SysoffResult{
SysTime: bestSysTS,
PHCTime: bestPhcTS,
Delay: shortestInterval,
Offset: bestOffset,
}
}
// TimeAndOffset returns time we got from network card + offset
func TimeAndOffset(iface string, method TimeMethod) (SysoffResult, error) {
info, err := IfaceInfo(iface)
if err != nil {
return SysoffResult{}, fmt.Errorf("getting interface info: %w", err)
}
if info.PHCIndex < 0 {
return SysoffResult{}, fmt.Errorf("%s doesn't support PHC", iface)
}
device := fmt.Sprintf("/dev/ptp%d", info.PHCIndex)
return TimeAndOffsetFromDevice(device, method)
}
// TimeAndOffsetFromDevice returns time we got from phc device + offset
func TimeAndOffsetFromDevice(device string, method TimeMethod) (SysoffResult, error) {
switch method {
case MethodSyscallClockGettime:
f, err := os.Open(device)
if err != nil {
return SysoffResult{}, err
}
defer f.Close()
var ts unix.Timespec
ts1 := time.Now()
err = unix.ClockGettime(fdToClockID(f.Fd()), &ts)
ts2 := time.Now()
if err != nil {
return SysoffResult{}, fmt.Errorf("failed clock_gettime: %w", err)
}
return sysoffEstimateBasic(ts1, time.Unix(ts.Unix()), ts2), nil
case MethodIoctlSysOffsetExtended:
extended, err := ReadPTPSysOffsetExtended(device, 5)
if err != nil {
return SysoffResult{}, err
}
return sysoffEstimateExtended(extended), nil
}
return SysoffResult{}, fmt.Errorf("unknown method to get PHC time %q", method)
}