cmd/ptpcheck/checker/checker.go (112 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 checker
import (
"fmt"
"net"
"os"
"path"
"time"
log "github.com/sirupsen/logrus"
ptp "github.com/facebook/time/ptp/protocol"
)
// PTPCheckResult is selected parts of various stats we expose to users, abstracting away protocol implementation
type PTPCheckResult struct {
OffsetFromMasterNS float64
GrandmasterPresent bool
StepsRemoved int
MeanPathDelayNS float64
ClockIdentity string
GrandmasterIdentity string
IngressTimeNS int64
PortStatsTX map[string]uint64
PortStatsRX map[string]uint64
}
// Run will talk over conn and return PTPCheckResult
func Run(c *ptp.MgmtClient) (*PTPCheckResult, error) {
var err error
currentDataSet, err := c.CurrentDataSet()
if err != nil {
return nil, err
}
log.Debugf("CurrentDataSet: %+v", currentDataSet)
defaultDataSet, err := c.DefaultDataSet()
if err != nil {
return nil, err
}
log.Debugf("DefaultDataSet: %+v", defaultDataSet)
parentDataSet, err := c.ParentDataSet()
if err != nil {
return nil, err
}
log.Debugf("ParentDataSet: %+v", parentDataSet)
/* gmPresent calculation from non-standard TIME_STATUS_NP
if (cid_eq(&c->dad.pds.grandmasterIdentity, &c->dds.clockIdentity))
tsn->gmPresent = 0;
else
tsn->gmPresent = 1;
where dad.pds is PARENT_DATA_SET and dds is DEFAULT_DATA_SET
*/
gmPresent := defaultDataSet.ClockIdentity != parentDataSet.GrandmasterIdentity
result := &PTPCheckResult{
OffsetFromMasterNS: currentDataSet.OffsetFromMaster.Nanoseconds(),
GrandmasterPresent: gmPresent,
MeanPathDelayNS: currentDataSet.MeanPathDelay.Nanoseconds(),
StepsRemoved: int(currentDataSet.StepsRemoved),
ClockIdentity: defaultDataSet.ClockIdentity.String(),
GrandmasterIdentity: parentDataSet.GrandmasterIdentity.String(),
PortStatsTX: map[string]uint64{},
PortStatsRX: map[string]uint64{},
}
portStats, err := c.PortStatsNP()
// it's a non-standard ptp4l thing, might be missing
if err != nil {
log.Warningf("couldn't get PortStatsNP: %v", err)
} else {
log.Debugf("PortStatsNP: %+v", portStats)
for k, v := range ptp.MessageTypeToString {
result.PortStatsRX[v] = portStats.PortStats.RXMsgType[k]
result.PortStatsTX[v] = portStats.PortStats.TXMsgType[k]
}
}
timeStatus, err := c.TimeStatusNP()
// it's a non-standard ptp4l thing, might be missing
if err != nil {
log.Warningf("couldn't get TimeStatusNP: %v", err)
} else {
log.Debugf("TimeStatusNP: %+v", timeStatus)
result.IngressTimeNS = timeStatus.IngressTimeNS
}
return result, nil
}
// PrepareClient creates a ptp.MgmtClient with connection to ptp4l over unix socket
func PrepareClient(address string) (c *ptp.MgmtClient, cleanup func(), err error) {
timeout := 5 * time.Second
base, _ := path.Split(address)
var conn *net.UnixConn
local := path.Join(base, fmt.Sprintf("ptpcheck.%d.sock", os.Getpid()))
// cleanup
cleanup = func() {
if conn != nil {
if err := conn.Close(); err != nil {
log.Warningf("closing connection: %v", err)
}
}
if err := os.RemoveAll(local); err != nil {
log.Warningf("removing socket: %v", err)
}
}
deadline := time.Now().Add(timeout)
addr, err := net.ResolveUnixAddr("unixgram", address)
if err != nil {
return nil, cleanup, err
}
localAddr, _ := net.ResolveUnixAddr("unixgram", local)
conn, err = net.DialUnix("unixgram", localAddr, addr)
if err != nil {
return nil, cleanup, err
}
if err := os.Chmod(local, 0666); err != nil {
return nil, cleanup, err
}
if err := conn.SetReadDeadline(deadline); err != nil {
return nil, cleanup, err
}
return &ptp.MgmtClient{
Connection: conn,
}, cleanup, err
}
// RunCheck is a simple wrapper to connect to address and run Run()
func RunCheck(address string) (*PTPCheckResult, error) {
c, cleanup, err := PrepareClient(address)
defer cleanup()
if err != nil {
return nil, err
}
log.Debugf("connected to %s", address)
return Run(c)
}