Update PTPLogger/PTPLogger_Graphing.py

This commit is contained in:
2026-06-09 11:37:31 +10:00
parent 91d1d498cc
commit 3c5b9cb96b
+149 -41
View File
@@ -1,85 +1,193 @@
import re
import datetime as dt
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import filedialog, messagebox
import matplotlib.dates as mdates
from matplotlib.lines import Line2D
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
# ===========================
# PARSE LOG
# ===========================
def parse_log(file_path):
t = []
rms = []
freq = []
t, rms, freq = [], [], []
state_events = []
master_events = []
last_timestamp = None
with open(file_path, encoding='utf-8', errors='ignore') as f:
for line in f:
# Match only valid ptp4l stat lines
match = re.search(
r'^(\w+ \d+ \d+:\d+:\d+).*rms (\d+).*freq ([+-]?\d+)',
line
)
if match:
# --- Try syslog timestamp ---
ts_match = re.match(r'^(\w+ \d+ \d+:\d+:\d+)', line)
if ts_match:
try:
# Add year (needed for parsing)
timestr = match.group(1) + " 2026"
timestamp = dt.datetime.strptime(
timestr, "%b %d %H:%M:%S %Y"
)
t.append(timestamp)
rms.append(int(match.group(2)))
freq.append(int(match.group(3)))
except Exception:
# Skip malformed lines safely
timestr = ts_match.group(1) + " 2026"
last_timestamp = dt.datetime.strptime(timestr, "%b %d %H:%M:%S %Y")
except:
pass
return t, rms, freq
timestamp = last_timestamp # fallback for ptp4l lines
# --- RMS/FREQ ---
m = re.search(r'rms\s+(\d+).*freq\s+([+-]?\d+)', line)
if m and timestamp:
t.append(timestamp)
rms.append(int(m.group(1)))
freq.append(int(m.group(2)))
# --- STATE CHANGE ---
m = re.search(r'port \d+: (\w+) to (\w+)', line)
if m and timestamp:
state_events.append((timestamp, f"{m.group(1)}{m.group(2)}"))
# --- MASTER CHANGE ---
m = re.search(
r'(selected best master clock|new foreign master)\s+([0-9a-fA-F\.:]+)',
line
)
if m and timestamp:
master_events.append((timestamp, m.group(2)))
return t, rms, freq, state_events, master_events
def plot_data(t, rms, freq):
# ===========================
# PLOT
# ===========================
def plot_data(t, rms, freq, state_events, master_events, detail):
if not t:
messagebox.showerror("Error", "No valid PTP lines found.")
messagebox.showerror("Error", "No valid RMS/freq data found.")
return
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(t, rms)
ax1.plot(t, rms, color='black')
ax1.set_ylabel("RMS (ns)")
ax1.set_title("PTP RMS Offset")
ax1.grid()
ax2.plot(t, freq)
ax2.plot(t, freq, color='black')
ax2.set_ylabel("Frequency deviation")
ax2.set_xlabel("Time")
ax2.set_title("PTP Frequency Deviation")
ax2.grid()
plt.xticks(rotation=30)
# --- EVENTS ---
for ts, _ in state_events:
ax1.axvline(ts, color='blue', linestyle='--', alpha=0.7)
ax2.axvline(ts, color='blue', linestyle='--', alpha=0.7)
for ts, _ in master_events:
ax1.axvline(ts, color='red', linestyle='--', alpha=0.7)
ax2.axvline(ts, color='red', linestyle='--', alpha=0.7)
# --- LEGEND ---
legend = [
Line2D([0], [0], color='black', label='RMS/Freq'),
Line2D([0], [0], color='blue', linestyle='--', label='State'),
Line2D([0], [0], color='red', linestyle='--', label='Master'),
]
ax1.legend(handles=legend)
# --- SAFE DATE FORMAT ---
if len(t) > 0:
multi_day = any(d.date() != t[0].date() for d in t)
else:
multi_day = False
if multi_day:
formatter = mdates.DateFormatter('%d %H:%M')
else:
formatter = mdates.DateFormatter('%H:%M:%S')
if detail == "Seconds":
locator = mdates.SecondLocator(interval=1)
elif detail == "5 sec":
locator = mdates.SecondLocator(interval=5)
elif detail == "10 sec":
locator = mdates.SecondLocator(interval=10)
elif detail == "30 sec":
locator = mdates.SecondLocator(interval=30)
elif detail == "1 min":
locator = mdates.MinuteLocator(interval=1)
else:
locator = mdates.AutoDateLocator(maxticks=20)
ax2.xaxis.set_major_locator(locator)
ax2.xaxis.set_major_formatter(formatter)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
plt.show(block=False)
# ===========================
# LOAD FILE
# ===========================
def open_file():
file_path = filedialog.askopenfilename(
title="Select PTP log file",
title="Select log",
filetypes=[("Log files", "*.log *.txt"), ("All files", "*.*")]
)
if not file_path:
return
t, rms, freq = parse_log(file_path)
detail = detail_var.get()
plot_data(t, rms, freq)
t, rms, freq, state_events, master_events = parse_log(file_path)
plot_data(t, rms, freq, state_events, master_events, detail)
# --- EVENT LIST ---
event_box.delete(1.0, tk.END)
events = []
for ts, label in state_events:
events.append((ts, f"{ts} | STATE {label}"))
for ts, label in master_events:
events.append((ts, f"{ts} | MASTER {label}"))
events.sort(key=lambda x: x[0])
if events:
for _, line in events:
event_box.insert(tk.END, line + "\n")
else:
event_box.insert(tk.END, "No events found.\n")
# --- GUI setup ---
# ===========================
# GUI
# ===========================
root = tk.Tk()
root.title("PTP Log Viewer")
root.geometry("750x550")
root.geometry("300x150")
frame = tk.Frame(root)
frame.pack(pady=5)
label = tk.Label(root, text="PTP Log Plotter", font=("Arial", 14))
label.pack(pady=10)
tk.Button(frame, text="Open Log", command=open_file).pack(side=tk.LEFT, padx=10)
btn = tk.Button(root, text="Open Log File", command=open_file)
btn.pack(pady=10)
detail_var = tk.StringVar(value="Auto")
tk.OptionMenu(
frame,
detail_var,
"Auto",
"Seconds",
"5 sec",
"10 sec",
"30 sec",
"1 min"
).pack(side=tk.LEFT)
event_box = scrolledtext.ScrolledText(root)
event_box.pack(fill=tk.BOTH, expand=True)
root.mainloop()