# PTP Prometheus Exporters Python Prometheus exporters for the PTP topology: ```text GT-U7 │ \/ RPi5 Grandmaster Grandmaster │ \/ Linux boundary clock │ │ \/ \/ NEXUS / C93180LC-EX Nexus boundary clock │ \/ Linux downstream clients ``` There are two exporter programs: - `linux_ptp_exporter.py` — runs locally on Linux grandmasters, Linux boundary clocks, and Linux clients. - `nexus_ptp_exporter.py` — runs on a Linux monitoring/Prometheus host and polls the Nexus over SSH. The Linux exporter uses the **read-only** `ptp4l` management socket (`/var/run/ptp4lro`) and `pmc`. It does not alter PTP state. ## Metrics worth graphing For Linux PTP nodes: ```text ptp_master_offset_nanoseconds ptp_mean_path_delay_nanoseconds ptp_steps_removed ptp_gm_present ptp_port_state ptp_port_messages_total ptp_port_service_events_total ptp_phc2sys_offset_nanoseconds ptp_service_up ``` On the RPi5 grandmaster, additionally: ```text ptp_gm_chrony_system_time_offset_seconds ptp_gm_chrony_last_offset_seconds ptp_gm_chrony_rms_offset_seconds ptp_gm_chrony_root_dispersion_seconds ptp_gm_chrony_source ``` For NYATER: ```text ptp_nexus_clock_locked ptp_nexus_offset_from_master_nanoseconds ptp_nexus_mean_path_delay_nanoseconds ptp_nexus_steps_removed ptp_nexus_port_state ptp_nexus_port_messages_total ptp_nexus_parent_info ``` ## 1. Install on a Linux PTP node Debian/Raspberry Pi OS/Proxmox example: ```bash sudo apt update sudo apt install python3-venv linuxptp sudo useradd --system --home /opt/ptp-exporter --shell /usr/sbin/nologin ptp-exporter sudo mkdir -p /opt/ptp-exporter sudo chown ptp-exporter:ptp-exporter /opt/ptp-exporter sudo -u ptp-exporter python3 -m venv /opt/ptp-exporter/venv sudo -u ptp-exporter /opt/ptp-exporter/venv/bin/pip install prometheus-client sudo install -m 0755 linux_ptp_exporter.py /opt/ptp-exporter/linux_ptp_exporter.py ``` linuxptp's default `/var/run/ptp4lro` is a read-only management socket intended for monitoring and is normally mode `0666`, so the exporter does not need write access to `/var/run/ptp4l`. If `ptp4l` on your distro uses `/var/run/ptp/ptp4lro` instead, pass that with `--uds`. ### RPi5 grandmaster Install: ```bash sudo install -m 0644 systemd/ptp-exporter-grandmaster.service \ /etc/systemd/system/ptp-exporter.service sudo systemctl daemon-reload sudo systemctl enable --now ptp-exporter ``` The unit runs: ```bash linux_ptp_exporter.py \ --role grandmaster \ --chrony \ --ptp4l-service ptp4l-gm.service \ --phc2sys-service phc2sys-gm.service ``` This exports both the PTP side and the GNSS/PPS -> chrony side. ### Linux boundary clock Install: ```bash sudo install -m 0644 systemd/ptp-exporter-boundary.service \ /etc/systemd/system/ptp-exporter.service sudo systemctl daemon-reload sudo systemctl enable --now ptp-exporter ``` ### Downstream Linux client Install: ```bash sudo install -m 0644 systemd/ptp-exporter-client.service \ /etc/systemd/system/ptp-exporter.service sudo systemctl daemon-reload sudo systemctl enable --now ptp-exporter ``` ## 2. Journal permissions The exact `pmc` metrics do not require journal access. `ptp_phc2sys_*` metrics are parsed from the latest `phc2sys -m` systemd journal lines. The included services use: ```text SupplementaryGroups=systemd-journal ``` so the exporter can read those lines without running as root. If your distribution does not have the `systemd-journal` group, remove that line. The exporter will continue working; only the `ptp_phc2sys_*` metrics will be absent. ## 3. Test a Linux exporter ```bash curl http://127.0.0.1:9559/metrics | grep '^ptp_' ``` Useful direct checks: ```bash pmc -u -s /var/run/ptp4lro -b 0 'GET CURRENT_DATA_SET' pmc -u -s /var/run/ptp4lro -b 0 'GET TIME_STATUS_NP' pmc -u -s /var/run/ptp4lro -b 0 'GET PORT_PROPERTIES_NP' pmc -u -s /var/run/ptp4lro -b 0 'GET PORT_STATS_NP' pmc -u -s /var/run/ptp4lro -b 0 'GET PORT_SERVICE_STATS_NP' ``` `PORT_STATS_NP` is particularly useful because it exposes real per-port Sync, Follow_Up, Delay_Req, Delay_Resp, Announce, Management, etc. RX/TX counters. ## 4. Nexus exporter Python cannot run directly on NX-OS, so `nexus_ptp_exporter.py` should run on a Linux monitoring host. Install: ```bash sudo useradd --system --home /opt/ptp-exporter --shell /usr/sbin/nologin ptp-exporter sudo mkdir -p /opt/ptp-exporter sudo chown ptp-exporter:ptp-exporter /opt/ptp-exporter sudo -u ptp-exporter python3 -m venv /opt/ptp-exporter/venv sudo -u ptp-exporter /opt/ptp-exporter/venv/bin/pip install prometheus-client paramiko sudo install -m 0755 nexus_ptp_exporter.py /opt/ptp-exporter/nexus_ptp_exporter.py ``` Use SSH key authentication if possible. The account only needs permission to run: ```text show ptp clock show ptp parent show ptp brief show ptp counters all ``` Example manual start: ```bash /opt/ptp-exporter/venv/bin/python \ /opt/ptp-exporter/nexus_ptp_exporter.py \ --host 10.255.254.255 \ --target-name NYATER \ --username prometheus \ --key-file /opt/ptp-exporter/.ssh/id_ed25519 \ --port 9560 ``` `10.255.254.255` above is only an example based on the PTP source/loopback shown in the current topology. Use whichever management-reachable address you actually want SSH to connect to. The exporter checks the system SSH known-hosts database by default. Do not use `--accept-new-host-key` for a permanent deployment unless you explicitly want trust-on-first-use behavior. ### Password authentication If required: ```bash export NEXUS_PTP_PASSWORD='...' nexus_ptp_exporter.py \ --host \ --username \ --password-env NEXUS_PTP_PASSWORD ``` Do not put the password directly in `ExecStart=`. ## 5. Prometheus scrape configuration See `examples/prometheus.yml`. Typical jobs: ```yaml scrape_configs: - job_name: ptp-linux static_configs: - targets: - GrandMaster:9559 - BoundaryClock:9559 - server-a:9559 - server-b:9559 - job_name: ptp-nexus static_configs: - targets: - prometheus-host:9560 ``` The Nexus target is the **exporter host**, not NEXUS itself. ## 6. Suggested PromQL ### Offset from immediate master ```promql ptp_master_offset_nanoseconds ``` or Nexus: ```promql ptp_nexus_offset_from_master_nanoseconds ``` ### Absolute value of offset ```promql abs(ptp_master_offset_nanoseconds) ``` ### 5-minute worst-case Linux offset ```promql max_over_time(abs(ptp_master_offset_nanoseconds)[5m]) ``` ### 5-minute RMS-like standard deviation ```promql stddev_over_time(ptp_master_offset_nanoseconds[5m]) ``` ### Ensure GM is present ```promql ptp_gm_present == 1 ``` ### Ensure Nexus is locked ```promql ptp_nexus_clock_locked == 1 ``` ### Check WOLF / client port states ```promql ptp_port_state{state="client"} == 1 ``` ### Check Nexus upstream redundancy ```promql ptp_nexus_port_state{state=~"slave|passive"} ``` ### PTP packet rate ```promql rate(ptp_port_messages_total[5m]) ``` For NEXUS: ```promql rate(ptp_nexus_port_messages_total[5m]) ``` ## 8. Security / firewall The exporters listen on all addresses by default. Restrict TCP/9559 and TCP/9560 to the Prometheus server with the host firewall, or start them with a specific management address: ```text --listen ``` The HTTP endpoint has no authentication; network-level restriction is recommended. ## 9. Notes about PTP semantics `ptp_master_offset_nanoseconds` is the local clock's offset from its **immediate PTP master**, not a proof of absolute UTC accuracy. For example: ```text MOMI GNSS/PPS -> RPi CLOCK_REALTIME -> RPi PHC -> WOLF eno4 PHC -> WOLF Mellanox PHC -> NYATER -> final client ``` Each stage can contribute error. Grafana should therefore show both the per-hop PTP offsets and the RPi chrony/GNSS health.