This commit supports new alarms based upon monitoring ptp-instance.
instance-level alarm: gpsd daemon fault
device-level alarms:
GNSS signal loss/no lock
Number of satellites below configured threshold
Average SNR below configured threshold
This reads configured threshold values and devices from
/etc/linuxptp/ptpinstance/monitoring-*.conf and compares with
gpsd data, to trigger raise/clear alarms.
Unit tests has been added for testing gpsd protocol.
ptp_monitoring_cli.py added for testing gpsd data polling on live
system.
TEST PLAN:
PASS: Deploy on system where no gnss signal received
system ptp-instance-add test-monitor monitoring
system ptp-instance-parameter-add test-monitor satellite_count=12
system ptp-instance-parameter-add test-monitor signal_quality_db=30
system ptp-instance-parameter-add test-monitor devices="/dev/gnss0 /dev/gnss1"
system ptp-instance-parameter-add test-monitor cmdline_opts="-D 7"
system host-ptp-instance-assign controller-0 test-monitor
system host-update controller-0 clock_synchronization=ptp
system ptp-instance-apply
- Below alarms received for both device_path=/dev/gnss0 and
device_path=/dev/gnss1
controller-0 GNSS signal quality db below threshold state:
signal_quality_db 0 (expected: >= 30.0)
controller-0 GNSS satellite count below threshold state:
satellite count 0 (expected: >= 12)
controller-0 GNSS signal loss state: signal lock False (expected: True)
- gpsd.service process running and no alarm for this service
- "systemctl stop gpsd.service" triggers "gpsd.service enabled
but not running"
- "systemctl start gpsd.service" clears "gpsd.service enabled
but not running"
PASS: without devices, raises no device-specific alarms are reported,
no errors on collectd.log
system ptp-instance-parameter-delete test-monitor devices=
"/dev/gnss0 /dev/gnss1"
system ptp-instance-apply
- fm alarm-list # no alarms
- "systemctl stop gpsd.service" triggers "gpsd.service enabled but
not running" alarm
- "systemctl start gpsd.service" clears "gpsd.service enabled but
not running" alarm
PASS: Add wrong satellite_count value, only that alarm get excluded
system ptp-instance-parameter-add test-monitor devices=
"/dev/gnss0 /dev/gnss1"
system ptp-instance-parameter-add test-monitor 'satellite_count=x'
system ptp-instance-apply
collectd log: ptp plugin Reading satellite_count from monitoring
config file /etc/linuxptp/ptpinstance/monitoring-ptp.conf
failed. error: invalid literal for int() with base 10: 'x'
- only satellite_count alarm get excluded, working as expected
PASS: Add wrong signal_quality_db value, only that alarm get excluded
system ptp-instance-parameter-add test-monitor
'signal_quality_db=100.x'
system ptp-instance-apply
collectd log: ptp plugin Reading signal_quality_db from monitoring
config file /etc/linuxptp/ptpinstance/monitoring-ptp.conf failed.
error: could not convert string to float: '100.x'
- No traceback on collectd.log, signal_quality_db has no effect on
other alarms.
PASS: Test with single device and float value
system ptp-instance-parameter-add test-monitor devices="/dev/gnss0"
system ptp-instance-parameter-add test-monitor
'signal_quality_db=100.7'
system ptp-instance-apply
fm alarm-list
controller-0 GNSS signal quality db below threshold state:
signal_quality_db 0 (expected: >= 100.7)
controller-0 GNSS satellite count below threshold state:
satellite count 0 (expected: >= 5)
controller-0 GNSS signal loss state: signal lock False (expected: True)
PASS: Deploy on system where gnss signal received
Test with cli first: sudo python /usr/rootdirs/opt/
collectd/extensions/python/ptp_monitoring_cli.py
/dev/gnss0's gps_data: GpsData(gpsd_running=1, lock_state=1,
satellite_count=10,
signal_quality_db=SignalQualityDb(min=31.0, max=48.0, avg=43.6))
/dev/gnss1's gps_data: GpsData(gpsd_running=1, lock_state=1,
satellite_count=10,
signal_quality_db=SignalQualityDb(min=30.0, max=48.0, avg=43.5))
Error: ptp plugin /dev/gnssx is not being monitored by GPSD
/dev/gnssx's gps_data: GpsData(gpsd_running=1, lock_state=0,
satellite_count=0, signal_quality_db=SignalQualityDb
(min=0, max=0, avg=0))
This shows satellite count and signal_quality_db of devices, that
can be tested against on following tests.
system ptp-instance-add test-monitor monitoring
system ptp-instance-parameter-add test-monitor satellite_count=8
system ptp-instance-parameter-add test-monitor signal_quality_db=30
system ptp-instance-parameter-add test-monitor devices=
"/dev/gnss0 /dev/gnss1"
system ptp-instance-parameter-add test-monitor cmdline_opts="-D 7"
system host-ptp-instance-assign controller-0 test-monitor
system host-update controller-0 clock_synchronization=ptp
system ptp-instance-apply
- fm alarm-list # reports no alarms
- check collectd.log for actual GpsData:
info ptp plugin instance monitoring-ptp device /dev/gnss0 data: GpsData(..)
PASS: increase threshold to check device specific alarms triggered
system ptp-instance-parameter-add test-monitor satellite_count=100
system ptp-instance-parameter-add test-monitor signal_quality_db=300
system ptp-instance-apply
- fm alarm-list # reports both alarms on both devices
PASS: test with monitoring instance with other instances (except ts2phc)
PASS: remove monitoring instance, keep other instances
Story: 2011345
Task: 52521
Change-Id: I52d1451cd7cac364bcaeff850a424ddcc8e8de94
Signed-off-by: Tara Nath Subedi <tara.subedi@windriver.com>
357 lines
12 KiB
Python
357 lines
12 KiB
Python
#
|
|
# Copyright (c) 2025 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
|
|
# bypass 'import collectd' as it's C-based daemon, and cannot be directly imported.
|
|
sys.modules["collectd"] = mock_collectd = MagicMock()
|
|
sys.modules["gps"] = MagicMock()
|
|
from src import ptp_monitoring as ptp_monitoring
|
|
|
|
|
|
class TestPtpMonitoring(unittest.TestCase):
|
|
def test_parse_monitoring_config(self):
|
|
config_file_path = (
|
|
"./tests/test_input_files/monitoring-ptp.conf"
|
|
)
|
|
|
|
expected_devices = "/dev/gnss0 /dev/gnss1"
|
|
expected_satellite_count = "12"
|
|
expected_signal_quality_db = "30"
|
|
|
|
config = ptp_monitoring.parse_monitoring_config(config_file_path)
|
|
self.assertEqual(config["global"]["devices"], expected_devices)
|
|
self.assertEqual(config["global"]["satellite_count"], expected_satellite_count)
|
|
self.assertEqual(
|
|
config["global"]["signal_quality_db"], expected_signal_quality_db
|
|
)
|
|
|
|
def test_get_gps_data_by_session_empty_devices(self):
|
|
device_path = "/dev/gnss0"
|
|
session = [
|
|
{
|
|
"class": "VERSION",
|
|
"release": "3.22",
|
|
"rev": "3.22",
|
|
"proto_major": 3,
|
|
"proto_minor": 14,
|
|
},
|
|
{"class": "DEVICES", "devices": []},
|
|
{
|
|
"class": "WATCH",
|
|
"enable": True,
|
|
"json": True,
|
|
"nmea": False,
|
|
"raw": 0,
|
|
"scaled": False,
|
|
"timing": False,
|
|
"split24": False,
|
|
"pps": False,
|
|
},
|
|
]
|
|
data = ptp_monitoring.get_gps_data_by_session(session, device_path)
|
|
expected_data = ptp_monitoring.GpsData(
|
|
gpsd_running=1,
|
|
lock_state=0,
|
|
satellite_count=0,
|
|
signal_quality_db=ptp_monitoring.SignalQualityDb(min=0, max=0, avg=0),
|
|
)
|
|
expected_error_log = (
|
|
f"{ptp_monitoring.PLUGIN} {device_path} is not being monitored by GPSD"
|
|
)
|
|
mock_collectd.error.assert_called_with(expected_error_log)
|
|
self.assertEqual(
|
|
data,
|
|
expected_data,
|
|
msg=f"actual {data} not equal to expected {expected_data} ",
|
|
)
|
|
|
|
def test_get_gps_data_by_session_mode_1(self):
|
|
# NMEA mode: 0=unknown, 1=no fix, 2=2D, 3=3D.
|
|
# Until the sensor achieves satellite lock, the fixes (reports) will be
|
|
# "mode 1" - no valid data
|
|
device_path = "/dev/gnss0"
|
|
session = [
|
|
{
|
|
"class": "VERSION",
|
|
"release": "3.22",
|
|
"rev": "3.22",
|
|
"proto_major": 3,
|
|
"proto_minor": 14,
|
|
},
|
|
{
|
|
"class": "DEVICES",
|
|
"devices": [
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss0",
|
|
"activated": "2025-06-13T12:52:15.463Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss1",
|
|
"activated": "2025-06-13T12:52:19.637Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"class": "WATCH",
|
|
"enable": True,
|
|
"json": True,
|
|
"nmea": False,
|
|
"raw": 0,
|
|
"scaled": False,
|
|
"timing": False,
|
|
"split24": False,
|
|
"pps": False,
|
|
},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss0",
|
|
"driver": "NMEA0183",
|
|
"activated": "2025-06-13T12:52:19.637Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 1},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 1},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 1},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss1",
|
|
"driver": "NMEA0183",
|
|
"activated": "2025-06-13T12:52:20.168Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
{"class": "TPV", "device": "/dev/gnss1", "mode": 1},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss0",
|
|
"driver": "u-blox",
|
|
"subtype": "SW EXT CORE 1.00 (3fda8e),HW 00190000",
|
|
"subtype1": (
|
|
"ROM BASE 0x118B2060,FWVER=TIM 2.20,PROTVER=29.20,MOD=ZED-F9T,"
|
|
"GPS;GLO;GAL;BDS,SBAS;QZSS,NAVIC"
|
|
),
|
|
"activated": "2025-06-13T12:52:20.716Z",
|
|
"flags": 1,
|
|
"native": 1,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
"mincycle": 0.02,
|
|
},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 1},
|
|
]
|
|
data = ptp_monitoring.get_gps_data_by_session(session, device_path)
|
|
expected_data = ptp_monitoring.GpsData(
|
|
gpsd_running=1,
|
|
lock_state=0,
|
|
satellite_count=0,
|
|
signal_quality_db=ptp_monitoring.SignalQualityDb(min=0, max=0, avg=0),
|
|
)
|
|
expected_debug_log = (
|
|
f"{ptp_monitoring.PLUGIN} {device_path} "
|
|
f"have not achieved satellite lock: {session[4]}"
|
|
)
|
|
mock_collectd.debug.assert_called_with(expected_debug_log)
|
|
self.assertEqual(
|
|
data,
|
|
expected_data,
|
|
msg=f"actual {data} not equal to expected {expected_data} ",
|
|
)
|
|
|
|
def test_get_gps_data_by_session_valid(self):
|
|
device_path = "/dev/gnss0"
|
|
session = [
|
|
{
|
|
"class": "VERSION",
|
|
"release": "3.22",
|
|
"rev": "3.22",
|
|
"proto_major": 3,
|
|
"proto_minor": 14,
|
|
},
|
|
{
|
|
"class": "DEVICES",
|
|
"devices": [
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss0",
|
|
"activated": "2025-06-13T12:52:15.463Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss1",
|
|
"activated": "2025-06-13T12:52:19.637Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"class": "WATCH",
|
|
"enable": True,
|
|
"json": True,
|
|
"nmea": False,
|
|
"raw": 0,
|
|
"scaled": False,
|
|
"timing": False,
|
|
"split24": False,
|
|
"pps": False,
|
|
},
|
|
{
|
|
"class": "DEVICE",
|
|
"path": "/dev/gnss0",
|
|
"driver": "NMEA0183",
|
|
"activated": "2025-06-13T12:52:19.637Z",
|
|
"native": 0,
|
|
"bps": 9600,
|
|
"parity": "N",
|
|
"stopbits": 0,
|
|
"cycle": 1.0,
|
|
},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 2},
|
|
{"class": "TPV", "device": "/dev/gnss0", "mode": 3},
|
|
{
|
|
"class": "SKY",
|
|
"device": "/dev/gnss0",
|
|
"time": "2019-04-10T20:27:52.000Z",
|
|
"xdop": 0.69,
|
|
"ydop": 0.68,
|
|
"vdop": 1.33,
|
|
"tdop": 0.88,
|
|
"hdop": 0.97,
|
|
"gdop": 1.86,
|
|
"pdop": 1.64,
|
|
"nSat": 4,
|
|
"uSat": 3,
|
|
"satellites": [
|
|
{
|
|
"PRN": 5,
|
|
"el": 31.0,
|
|
"az": 86.0,
|
|
"ss": 45.2,
|
|
"used": True,
|
|
"gnssid": 0,
|
|
"svid": 5,
|
|
"health": 1,
|
|
},
|
|
{
|
|
"PRN": 10,
|
|
"el": 10.0,
|
|
"az": 278.0,
|
|
"ss": 40.0,
|
|
"used": False,
|
|
"gnssid": 0,
|
|
"svid": 10,
|
|
"health": 1,
|
|
},
|
|
{
|
|
"PRN": 13,
|
|
"el": 44.0,
|
|
"az": 53.0,
|
|
"ss": 47.4,
|
|
"used": True,
|
|
"gnssid": 0,
|
|
"svid": 13,
|
|
"health": 1,
|
|
},
|
|
{
|
|
"PRN": 15,
|
|
"el": 80.0,
|
|
"az": 68.0,
|
|
"ss": 48.8,
|
|
"used": True,
|
|
"gnssid": 0,
|
|
"svid": 15,
|
|
"health": 1,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
snr = [45.2, 47.4, 48.8]
|
|
avg = sum(snr) / len(snr)
|
|
trunc_avg = int(avg * 1000) / 1000
|
|
expected_signal_quality_db = ptp_monitoring.SignalQualityDb(
|
|
min=min(snr), max=max(snr), avg=trunc_avg
|
|
)
|
|
expected_data = ptp_monitoring.GpsData(
|
|
gpsd_running=1,
|
|
lock_state=1,
|
|
satellite_count=3,
|
|
signal_quality_db=expected_signal_quality_db,
|
|
)
|
|
|
|
data = ptp_monitoring.get_gps_data_by_session(session, device_path)
|
|
mock_collectd.assert_not_called()
|
|
self.assertEqual(
|
|
data,
|
|
expected_data,
|
|
msg=f"actual {data} not equal to expected {expected_data} ",
|
|
)
|
|
|
|
def test_get_gps_data_by_session_with_garbase_data(self):
|
|
device_path = "/dev/gnss0"
|
|
session = [
|
|
{
|
|
"GARBASE-class": "VERSION",
|
|
"release": "3.22",
|
|
"rev": "3.22",
|
|
"proto_major": 3,
|
|
"proto_minor": 14,
|
|
},
|
|
]
|
|
data = ptp_monitoring.get_gps_data_by_session(session, device_path)
|
|
expected_data = ptp_monitoring.GpsData(
|
|
gpsd_running=1,
|
|
lock_state=0,
|
|
satellite_count=0,
|
|
signal_quality_db=ptp_monitoring.SignalQualityDb(min=0, max=0, avg=0),
|
|
)
|
|
expected_error_log_1 = (
|
|
f"{ptp_monitoring.PLUGIN} Programming error occured: <class 'KeyError'>:'class'"
|
|
)
|
|
expected_error_log_substring_2 = "Traceback (most recent call last)"
|
|
self.assertEqual(mock_collectd.error.call_count, 2)
|
|
call_arg_1 = mock_collectd.error.call_args_list[0].args[0]
|
|
self.assertEqual(call_arg_1, expected_error_log_1)
|
|
call_arg_2 = mock_collectd.error.call_args_list[1].args[0]
|
|
self.assertIn(expected_error_log_substring_2, call_arg_2)
|
|
print(call_arg_2)
|
|
self.assertEqual(
|
|
data,
|
|
expected_data,
|
|
msg=f"actual {data} not equal to expected {expected_data} ",
|
|
)
|