Skip to content

Commit

Permalink
Process ts2phc events for multi-cards
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Ding <[email protected]>
  • Loading branch information
jzding committed Nov 16, 2023
1 parent 0faa983 commit 8b236fa
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ linters-settings:

gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 45
min-complexity: 48
goconst:
min-len: 3
min-occurrences: 4
Expand Down
47 changes: 47 additions & 0 deletions plugins/ptp_operator/metrics/logparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,53 @@ func extractRegularMetrics(processName, output string) (interfaceName string, pt
return
}

func extractExternalSourceMetrics(source, processName, output string) (interfaceName string, status, ptpOffset float64) {
// ts2phc[1699929121]:[ts2phc.0.config] ens2f0 nmea_status 0 offset 999999 s0
// ts2phc[1700086474]:[ts2phc.0.config] ens7fx offset 0 pps_status 1 s2
var err error
index := FindInLogForCfgFileIndex(output)
if index == -1 {
log.Errorf("config name is not found in log outpt")
return
}

output = strings.Replace(output, "path", "", 1)
replacer := strings.NewReplacer("[", " ", "]", " ", ":", " ", "phc", "", "sys", "")
output = replacer.Replace(output)

output = output[index:]
fields := strings.Fields(output)

// 0 1 2 3 4 5 6
// ts2phc.0.config ens2f0 nmea_status 0 offset 999999 s0
// ts2phc.0.config ens7fx offset 0 pps_status 1 s2
if len(fields) < 7 {
return
}

interfaceName = fields[1]

var fieldStatus, fieldOffset int

if source == NMEA {
fieldStatus = 3
fieldOffset = 5
} else {
fieldStatus = 5
fieldOffset = 3
}

status, err = strconv.ParseFloat(fields[fieldStatus], 64)
if err != nil {
log.Errorf("%s failed to parse %s status from master output %s error %v", processName, source, fields[fieldStatus], err)
}
ptpOffset, err = strconv.ParseFloat(fields[fieldOffset], 64)
if err != nil {
log.Errorf("%s failed to parse %s offset from master output %s error %v", processName, source, fields[fieldOffset], err)
}
return
}

// ExtractPTP4lEvent ... extract event form ptp4l logs
//
// "INITIALIZING to LISTENING on INIT_COMPLETE"
Expand Down
13 changes: 7 additions & 6 deletions plugins/ptp_operator/metrics/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,18 @@ func (p *PTPEventManager) PublishEvent(state ptp.SyncState, ptpOffset int64, sou
}

func (p *PTPEventManager) publish(data ceevent.Data, eventSource string, eventType ptp.EventType) {
if p.mock {
return
}
if pubs, ok := p.publisherTypes[eventType]; ok {
e, err := common.CreateEvent(pubs.PubID, string(eventType), eventSource, data)
if err != nil {
log.Errorf("failed to create ptp event, %s", err)
return
}
if !p.mock {
if err = common.PublishEventViaAPI(p.scConfig, e); err != nil {
log.Errorf("failed to publish ptp event %v, %s", e, err)
return
}
if err = common.PublishEventViaAPI(p.scConfig, e); err != nil {
log.Errorf("failed to publish ptp event %v, %s", e, err)
return
}
} else {
log.Errorf("failed to publish ptp event due to missing publisher for type %s", string(eventType))
Expand Down Expand Up @@ -283,7 +284,7 @@ func (p *PTPEventManager) GenPTPEvent(ptpProfileName string, oStats *stats.Stats
case ptp.HOLDOVER:
// do nothing, the timeout will switch holdover to FREE-RUN
default: // not yet used states
log.Warnf("unknown %s sync state %s ,has last ptp state %s", eventResourceName, clockState, lastClockState)
log.Warnf("%s sync state %s, last ptp state is unknown: %s", eventResourceName, clockState, lastClockState)
if !isOffsetInRange(ptpOffset, threshold.MaxOffsetThreshold, threshold.MinOffsetThreshold) {
clockState = ptp.FREERUN
}
Expand Down
41 changes: 41 additions & 0 deletions plugins/ptp_operator/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ const (
PtpProcessDown int64 = 0
// PtpProcessUp process is up
PtpProcessUp int64 = 1

// NMEA ...
NMEA = "NMEA"
// PPS ...
PPS = "PPS"
// UNAVAILABLE Nmea and Pps status
UNAVAILABLE float64 = 0
// AVAILABLE Nmea and Pps status
AVAILABLE float64 = 1
)

// ExtractMetrics ... extract metrics from ptp logs.
Expand Down Expand Up @@ -167,6 +176,38 @@ func (p *PTPEventManager) ExtractMetrics(msg string) {
UpdatePTPMetrics(master, processName, alias, ptpOffset, maxPtpOffset, frequencyAdjustment, delay)
}
}
} else if (strings.Contains(output, "nmea_status") || strings.Contains(output, "pps_status")) &&
processName == ts2phcProcessName {
// ts2phc[1699929121]:[ts2phc.0.config] ens2f0 nmea_status 0 offset 999999 s0
// ts2phc[1700086474]:[ts2phc.0.config] ens7fx offset 9999 pps_status 0 s2
var source string
if strings.Contains(output, "nmea_status") {
source = NMEA
} else {
source = PPS
}
interfaceName, status, ptpOffset := extractExternalSourceMetrics(source, processName, output)

offsetSource := master
interfaceType := types.IFace(master)
// ts2phc return actual interface name unlike ptp4l
ptpInterface = ptp4lconf.PTPInterface{Name: interfaceName}

var alias string
r := []rune(interfaceName)
alias = string(r[:len(r)-1]) + "x"
ptpStats[master].SetAlias(alias)
masterResource := fmt.Sprintf("%s/%s", alias, MasterClockType)
if status == UNAVAILABLE {
p.GenPTPEvent(profileName, ptpStats[interfaceType], masterResource, int64(ptpOffset), ptp.FREERUN, ptp.PtpStateChange)
} else {
UpdatePTPOffsetMetrics(offsetSource, processName, alias, ptpOffset)
}
if source == NMEA {
UpdateNmeaStatusMetrics(processName, alias, status)
} else {
UpdatePpsStatusMetrics(processName, alias, status)
}
} else if strings.Contains(output, " offset ") &&
(processName != gnssProcessName && processName != dpllProcessName && processName != gmProcessName) {
// ptp4l[5196819.100]: [ptp4l.0.config] master offset -2162130 s2 freq +22451884 path delay 374976
Expand Down
128 changes: 128 additions & 0 deletions plugins/ptp_operator/metrics/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2023 The Cloud Native Events Authors
//
// 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.

//go:build unittests
// +build unittests

package metrics_test

import (
"os"
"testing"

"github.com/redhat-cne/cloud-event-proxy/pkg/common"
"github.com/redhat-cne/cloud-event-proxy/plugins/ptp_operator/config"
"github.com/redhat-cne/cloud-event-proxy/plugins/ptp_operator/metrics"
"github.com/redhat-cne/cloud-event-proxy/plugins/ptp_operator/ptp4lconf"
"github.com/redhat-cne/cloud-event-proxy/plugins/ptp_operator/stats"
"github.com/redhat-cne/cloud-event-proxy/plugins/ptp_operator/types"
"github.com/redhat-cne/sdk-go/pkg/event/ptp"
)

type metricsTestCase struct {
ptpLog string
}

var ptp4lConfig = &ptp4lconf.PTP4lConfig{
Name: "ptp4l.0.config",
Profile: "grandmaster",
Interfaces: []*ptp4lconf.PTPInterface{
{
Name: "ens2f0",
PortID: 1,
PortName: "port 1",
Role: 2,
},
{
Name: "ens7f0",
PortID: 2,
PortName: "port 3",
Role: 2,
},
},
}

var ptpEventManager *metrics.PTPEventManager
var scConfig *common.SCConfiguration
var resourcePrefix = ""

// InitPubSubTypes ... initialize types of publishers for ptp operator
func InitPubSubTypes() map[ptp.EventType]*types.EventPublisherType {
InitPubs := make(map[ptp.EventType]*types.EventPublisherType)
InitPubs[ptp.OsClockSyncStateChange] = &types.EventPublisherType{
EventType: ptp.OsClockSyncStateChange,
Resource: ptp.OsClockSyncState,
}
InitPubs[ptp.PtpClockClassChange] = &types.EventPublisherType{
EventType: ptp.PtpClockClassChange,
Resource: ptp.PtpClockClass,
}
InitPubs[ptp.PtpStateChange] = &types.EventPublisherType{
EventType: ptp.PtpStateChange,
Resource: ptp.PtpLockState,
}
InitPubs[ptp.GnssStateChange] = &types.EventPublisherType{
EventType: ptp.GnssStateChange,
Resource: ptp.GnssSyncStatus,
}
return InitPubs
}

var testCase = []string{
"GM[1699929086]:[ts2phc.0.config] ens2f0 T-GM-STATUS s0",
"ts2phc[1699929121]:[ts2phc.0.config] ens2f0 nmea_status 0 offset 999999 s0",
"ts2phc[1699929171]:[ts2phc.0.config] ens2fx nmea_status 1 offset 0 s2",
"ts2phc[1700086474]:[ts2phc.0.config] ens7fx offset 9999 pps_status 0 s2",
"ts2phc[1700086474]:[ts2phc.0.config] ens7fx offset 0 pps_status 1 s2",
"ts2phc[441664.291]: [ts2phc.0.config] ens2f0 master offset 0 s2 freq -0",
"ts2phc[441664.304]: [ts2phc.0.config] ens7f0 master offset 0 s2 freq +0",
}

func setup() {
ptpEventManager = metrics.NewPTPEventManager(resourcePrefix, InitPubSubTypes(), "tetsnode", scConfig)
ptpEventManager.MockTest(true)

ptpEventManager.AddPTPConfig(types.ConfigName(ptp4lConfig.Name), ptp4lConfig)

stats_master := stats.NewStats(ptp4lConfig.Name)
stats_master.SetOffsetSource("master")
stats_master.SetProcessName("ts2phc")
stats_master.SetAlias("ens2fx")

stats_slave := stats.NewStats(ptp4lConfig.Name)
stats_slave.SetOffsetSource("phc")
stats_slave.SetProcessName("phc2sys")
stats_slave.SetLastSyncState("LOCKED")
stats_slave.SetClockClass(0)

ptpEventManager.Stats[types.ConfigName(ptp4lConfig.Name)] = make(map[types.IFace]*stats.Stats)
ptpEventManager.Stats[types.ConfigName(ptp4lConfig.Name)][types.IFace("master")] = stats_master
ptpEventManager.Stats[types.ConfigName(ptp4lConfig.Name)][types.IFace("CLOCK_REALTIME")] = stats_slave
ptpEventManager.PtpConfigMapUpdates = config.NewLinuxPTPConfUpdate()
}

func teardown() {
}

func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}
func Test_ExtractMetrics(t *testing.T) {
for _, tc := range testCase {
ptpEventManager.ExtractMetrics(tc)
}
}
40 changes: 39 additions & 1 deletion plugins/ptp_operator/metrics/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ var (
Help: "0 = FREERUN, 1 = LOCKED, 2 = HOLDOVER",
}, []string{"process", "node", "iface"})

// NmeaStatus metrics to show current nmea status
NmeaStatus = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: ptpNamespace,
Subsystem: ptpSubsystem,
Name: "nmea_status",
Help: "0 = UNAVAILABLE, 1 = AVAILABLE",
}, []string{"process", "node", "iface"})

// PpsStatus metrics to show current pps status
PpsStatus = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: ptpNamespace,
Subsystem: ptpSubsystem,
Name: "pps_status",
Help: "0 = UNAVAILABLE, 1 = AVAILABLE",
}, []string{"process", "node", "iface"})

// Threshold metrics to show current ptp threshold
Threshold = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Expand Down Expand Up @@ -113,6 +131,8 @@ func RegisterMetrics(nodeName string) {
prometheus.MustRegister(PtpFrequencyAdjustment)
prometheus.MustRegister(PtpDelay)
prometheus.MustRegister(SyncState)
prometheus.MustRegister(NmeaStatus)
prometheus.MustRegister(PpsStatus)
prometheus.MustRegister(Threshold)
prometheus.MustRegister(InterfaceRole)
prometheus.MustRegister(ClockClassMetrics)
Expand Down Expand Up @@ -142,6 +162,12 @@ func UpdatePTPMetrics(metricsType, process, eventResourceName string, offset, ma
"process": process, "node": ptpNodeName, "iface": eventResourceName}).Set(delay)
}

// UpdatePTPOffsetMetrics ... update ptp offset metrics
func UpdatePTPOffsetMetrics(metricsType, process, eventResourceName string, offset float64) {
PtpOffset.With(prometheus.Labels{"from": metricsType,
"process": process, "node": ptpNodeName, "iface": eventResourceName}).Set(offset)
}

// DeletedPTPMetrics ... update metrics for deleted ptp config
func DeletedPTPMetrics(clockType, processName, eventResourceName string) {
PtpOffset.Delete(prometheus.Labels{"from": clockType,
Expand Down Expand Up @@ -178,13 +204,25 @@ func UpdateSyncStateMetrics(process, iface string, state ptp.SyncState) {
}
// prevent reporting wrong metrics
if iface == master && process == phc2sysProcessName {
log.Errorf("wrong meterics are processed, ignoring interface %s with process %s", iface, process)
log.Errorf("wrong metrics are processed, ignoring interface %s with process %s", iface, process)
return
}
SyncState.With(prometheus.Labels{
"process": process, "node": ptpNodeName, "iface": iface}).Set(clockState)
}

// UpdateNmeaStatusMetrics ... update nmea status metrics
func UpdateNmeaStatusMetrics(process, iface string, status float64) {
NmeaStatus.With(prometheus.Labels{
"process": process, "node": ptpNodeName, "iface": iface}).Set(status)
}

// UpdatePpsStatusMetrics ... update pps status metrics
func UpdatePpsStatusMetrics(process, iface string, status float64) {
PpsStatus.With(prometheus.Labels{
"process": process, "node": ptpNodeName, "iface": iface}).Set(status)
}

// UpdateInterfaceRoleMetrics ... update interface role metrics
func UpdateInterfaceRoleMetrics(process, ptpInterface string, role types.PtpPortRole) {
InterfaceRole.With(prometheus.Labels{
Expand Down

0 comments on commit 8b236fa

Please sign in to comment.