Files
RPi5-PTP-server/exporters/README.md

8.5 KiB

PTP Prometheus Exporters

Python Prometheus exporters for the PTP topology:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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

curl http://127.0.0.1:9559/metrics | grep '^ptp_'

Useful direct checks:

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:

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:

show ptp clock
show ptp parent
show ptp brief
show ptp counters all

Example manual start:

/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:

export NEXUS_PTP_PASSWORD='...'

nexus_ptp_exporter.py \
  --host <NYATER-address> \
  --username <user> \
  --password-env NEXUS_PTP_PASSWORD

Do not put the password directly in ExecStart=.

5. Prometheus scrape configuration

See examples/prometheus.yml.

Typical jobs:

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

ptp_master_offset_nanoseconds

or Nexus:

ptp_nexus_offset_from_master_nanoseconds

Absolute value of offset

abs(ptp_master_offset_nanoseconds)

5-minute worst-case Linux offset

max_over_time(abs(ptp_master_offset_nanoseconds)[5m])

5-minute RMS-like standard deviation

stddev_over_time(ptp_master_offset_nanoseconds[5m])

Ensure GM is present

ptp_gm_present == 1

Ensure Nexus is locked

ptp_nexus_clock_locked == 1

Check WOLF / client port states

ptp_port_state{state="client"} == 1

Check Nexus upstream redundancy

ptp_nexus_port_state{state=~"slave|passive"}

PTP packet rate

rate(ptp_port_messages_total[5m])

For NEXUS:

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:

--listen <management-IP>

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:

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.