Files
RiedelScripts/PTPLogger/PTPLogger_Graphing.py
T

193 lines
5.1 KiB
Python

import re
import datetime as dt
import matplotlib.pyplot as plt
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 = [], [], []
state_events = []
master_events = []
last_timestamp = None
with open(file_path, encoding='utf-8', errors='ignore') as f:
for line in f:
# --- Try syslog timestamp ---
ts_match = re.match(r'^(\w+ \d+ \d+:\d+:\d+)', line)
if ts_match:
try:
timestr = ts_match.group(1) + " 2026"
last_timestamp = dt.datetime.strptime(timestr, "%b %d %H:%M:%S %Y")
except:
pass
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
# ===========================
# PLOT
# ===========================
def plot_data(t, rms, freq, state_events, master_events, detail):
if not t:
messagebox.showerror("Error", "No valid RMS/freq data found.")
return
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(t, rms, color='black')
ax1.set_ylabel("RMS (ns)")
ax1.grid()
ax2.plot(t, freq, color='black')
ax2.set_ylabel("Frequency deviation")
ax2.set_xlabel("Time")
ax2.grid()
# --- 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(block=False)
# ===========================
# LOAD FILE
# ===========================
def open_file():
file_path = filedialog.askopenfilename(
title="Select log",
filetypes=[("Log files", "*.log *.txt"), ("All files", "*.*")]
)
if not file_path:
return
detail = detail_var.get()
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
# ===========================
root = tk.Tk()
root.title("PTP Log Viewer")
root.geometry("750x550")
frame = tk.Frame(root)
frame.pack(pady=5)
tk.Button(frame, text="Open Log", command=open_file).pack(side=tk.LEFT, padx=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()