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 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: 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 if not timestamp: continue m = re.search(r'rms\s+(\d+).*freq\s+([+-]?\d+)', line) if m: t.append(timestamp) rms.append(int(m.group(1))) freq.append(int(m.group(2))) m = re.search(r'port \d+: (\w+) to (\w+)', line) if m: state_events.append((timestamp, f"{m.group(1)}→{m.group(2)}")) m = re.search( r'(selected best master clock|new foreign master)\s+([0-9a-fA-F\.:]+)', line ) if m: master_events.append((timestamp, m.group(2))) return t, rms, freq, state_events, master_events 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() # event lines 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 = [ 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) multi_day = any(d.date() != t[0].date() for d in t) 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() 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_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()