Compare commits
No commits in common. "master" and "v.0.0.0.1" have entirely different histories.
BIN
NetAnalyzer.ico
BIN
NetAnalyzer.ico
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 0 B |
@ -1,5 +1,39 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
block_cipher = None
|
block_cipher = None
|
||||||
a = Analysis(pathex=['netanalyzer', '.'], binaries=[], datas=[('NetAnalyzer.ico', '.')], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, scripts=['netanalyzer\\__main__.py'])
|
|
||||||
|
a = Analysis(
|
||||||
|
['netanalyzer/__main__.py'],
|
||||||
|
pathex=['.'],
|
||||||
|
binaries=[],
|
||||||
|
# Usa project_icon_filename nella sezione datas
|
||||||
|
datas=[('NetAnalyzer.ico', '.')],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False
|
||||||
|
)
|
||||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='NetAnalyzer', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, icon='NetAnalyzer.ico', exclude_binaries=True)
|
|
||||||
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='NetAnalyzer')
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='NetAnalyzer',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
# Usa project_icon_filename per l'opzione icon
|
||||||
|
icon='NetAnalyzer.ico'
|
||||||
|
)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from gui.main_view import MainView
|
from .gui.main_view import MainView
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# File generated by PyInstaller GUI Wrapper. DO NOT EDIT MANUALLY.
|
|
||||||
# Contains build-time information scraped from Git (if available)
|
|
||||||
# and a helper function to format version strings.
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
# --- Version Data (Generated) ---
|
|
||||||
__version__ = "v.0.0.0.2-0-gd4e6f34-dirty"
|
|
||||||
GIT_COMMIT_HASH = "d4e6f3487d230e5b2b1df98cf35f2e853bc2f38e"
|
|
||||||
GIT_BRANCH = "master"
|
|
||||||
BUILD_TIMESTAMP = "2025-11-19T13:05:49.512611+00:00"
|
|
||||||
IS_GIT_REPO = True
|
|
||||||
|
|
||||||
# --- Default Values (for comparison or fallback) ---
|
|
||||||
DEFAULT_VERSION = "0.0.0+unknown"
|
|
||||||
DEFAULT_COMMIT = "Unknown"
|
|
||||||
DEFAULT_BRANCH = "Unknown"
|
|
||||||
|
|
||||||
# --- Helper Function ---
|
|
||||||
def get_version_string(format_string=None):
|
|
||||||
"""
|
|
||||||
Returns a formatted string based on the build version information.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
format_string (str, optional): A format string using placeholders.
|
|
||||||
Defaults to "{version} ({branch}/{commit_short})" if None.
|
|
||||||
Placeholders:
|
|
||||||
{{version}}: Full version string (e.g., 'v1.0.0-5-gabcdef-dirty')
|
|
||||||
{{tag}}: Clean tag part if exists (e.g., 'v1.0.0'), else DEFAULT_VERSION.
|
|
||||||
{{commit}}: Full Git commit hash.
|
|
||||||
{{commit_short}}: Short Git commit hash (7 chars).
|
|
||||||
{{branch}}: Git branch name.
|
|
||||||
{{dirty}}: '-dirty' if the repo was dirty, empty otherwise.
|
|
||||||
{{timestamp}}: Full build timestamp (ISO 8601 UTC).
|
|
||||||
{{timestamp_short}}: Build date only (YYYY-MM-DD).
|
|
||||||
{{is_git}}: 'Git' if IS_GIT_REPO is True, 'Unknown' otherwise.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The formatted version string, or an error message if formatting fails.
|
|
||||||
"""
|
|
||||||
if format_string is None:
|
|
||||||
format_string = "{version} ({branch}/{commit_short})" # Default format
|
|
||||||
|
|
||||||
replacements = {}
|
|
||||||
try:
|
|
||||||
replacements['version'] = __version__ if __version__ else DEFAULT_VERSION
|
|
||||||
replacements['commit'] = GIT_COMMIT_HASH if GIT_COMMIT_HASH else DEFAULT_COMMIT
|
|
||||||
replacements['commit_short'] = GIT_COMMIT_HASH[:7] if GIT_COMMIT_HASH and len(GIT_COMMIT_HASH) >= 7 else DEFAULT_COMMIT
|
|
||||||
replacements['branch'] = GIT_BRANCH if GIT_BRANCH else DEFAULT_BRANCH
|
|
||||||
replacements['timestamp'] = BUILD_TIMESTAMP if BUILD_TIMESTAMP else "Unknown"
|
|
||||||
replacements['timestamp_short'] = BUILD_TIMESTAMP.split('T')[0] if BUILD_TIMESTAMP and 'T' in BUILD_TIMESTAMP else "Unknown"
|
|
||||||
replacements['is_git'] = "Git" if IS_GIT_REPO else "Unknown"
|
|
||||||
replacements['dirty'] = "-dirty" if __version__ and __version__.endswith('-dirty') else ""
|
|
||||||
|
|
||||||
tag = DEFAULT_VERSION
|
|
||||||
if __version__ and IS_GIT_REPO:
|
|
||||||
match = re.match(r'^(v?([0-9]+(?:\.[0-9]+)*))', __version__)
|
|
||||||
if match:
|
|
||||||
tag = match.group(1)
|
|
||||||
replacements['tag'] = tag
|
|
||||||
|
|
||||||
output_string = format_string
|
|
||||||
for placeholder, value in replacements.items():
|
|
||||||
pattern = re.compile(r'{{\s*' + re.escape(placeholder) + r'\s*}}')
|
|
||||||
output_string = pattern.sub(str(value), output_string)
|
|
||||||
|
|
||||||
if re.search(r'{\s*\w+\s*}', output_string):
|
|
||||||
pass # Or log a warning: print(f"Warning: Unreplaced placeholders found: {output_string}")
|
|
||||||
|
|
||||||
return output_string
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return f"[Formatting Error: {e}]"
|
|
||||||
@ -1,5 +1,3 @@
|
|||||||
# NetPulseAnalyzer/core/analyzer.py
|
|
||||||
|
|
||||||
import scapy.all as scapy
|
import scapy.all as scapy
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
@ -33,6 +31,7 @@ class PacketAnalyzer:
|
|||||||
if not self.packets:
|
if not self.packets:
|
||||||
raise ValueError("No packets loaded. Call load_pcap() first.")
|
raise ValueError("No packets loaded. Call load_pcap() first.")
|
||||||
|
|
||||||
|
# Extract timestamps for packets on port A and port B (destination ports)
|
||||||
times_a = [p.time for p in self.packets if p.haslayer(scapy.UDP) and p[scapy.UDP].dport == port_a]
|
times_a = [p.time for p in self.packets if p.haslayer(scapy.UDP) and p[scapy.UDP].dport == port_a]
|
||||||
times_b = [p.time for p in self.packets if p.haslayer(scapy.UDP) and p[scapy.UDP].dport == port_b]
|
times_b = [p.time for p in self.packets if p.haslayer(scapy.UDP) and p[scapy.UDP].dport == port_b]
|
||||||
|
|
||||||
@ -43,8 +42,13 @@ class PacketAnalyzer:
|
|||||||
if min_len == 0:
|
if min_len == 0:
|
||||||
return {"error": "No matching A/B packet pairs found."}
|
return {"error": "No matching A/B packet pairs found."}
|
||||||
|
|
||||||
|
# Calculate the time deltas in milliseconds
|
||||||
processing_times_ms = (pd.Series(times_b[:min_len]) - pd.Series(times_a[:min_len])) * 1000
|
processing_times_ms = (pd.Series(times_b[:min_len]) - pd.Series(times_a[:min_len])) * 1000
|
||||||
|
|
||||||
|
# Use pandas to get a comprehensive set of statistics
|
||||||
stats = processing_times_ms.describe(percentiles=[.25, .5, .75, .95, .99]).to_dict()
|
stats = processing_times_ms.describe(percentiles=[.25, .5, .75, .95, .99]).to_dict()
|
||||||
|
|
||||||
|
# Add raw data points for plotting
|
||||||
stats['data_points'] = processing_times_ms.tolist()
|
stats['data_points'] = processing_times_ms.tolist()
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
@ -56,15 +60,21 @@ class PacketAnalyzer:
|
|||||||
if not self.packets:
|
if not self.packets:
|
||||||
raise ValueError("No packets loaded.")
|
raise ValueError("No packets loaded.")
|
||||||
|
|
||||||
|
# Filter packets for the specified port (either source or destination)
|
||||||
times = [p.time for p in self.packets if p.haslayer(scapy.UDP) and (p[scapy.UDP].sport == port or p[scapy.UDP].dport == port)]
|
times = [p.time for p in self.packets if p.haslayer(scapy.UDP) and (p[scapy.UDP].sport == port or p[scapy.UDP].dport == port)]
|
||||||
|
|
||||||
if len(times) < 2:
|
if len(times) < 2:
|
||||||
return {"error": f"Fewer than 2 packets found on port {port}. Cannot calculate interval."}
|
return {"error": f"Fewer than 2 packets found on port {port}. Cannot calculate interval."}
|
||||||
|
|
||||||
|
# Calculate the difference between each timestamp and the previous one
|
||||||
gaps_ms = pd.Series(times).diff().dropna() * 1000
|
gaps_ms = pd.Series(times).diff().dropna() * 1000
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
stats = gaps_ms.describe(percentiles=[.25, .5, .75, .95, .99]).to_dict()
|
stats = gaps_ms.describe(percentiles=[.25, .5, .75, .95, .99]).to_dict()
|
||||||
|
|
||||||
|
# Jitter is often defined as the standard deviation of the packet delay variation
|
||||||
stats['jitter'] = stats.get('std', 0.0)
|
stats['jitter'] = stats.get('std', 0.0)
|
||||||
|
|
||||||
stats['data_points'] = gaps_ms.tolist()
|
stats['data_points'] = gaps_ms.tolist()
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
@ -1,11 +1,14 @@
|
|||||||
|
# NetPulseAnalyzer/gui/main_view.py
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, filedialog, messagebox
|
from tkinter import ttk, filedialog, messagebox
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk # NUOVO IMPORT
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from typing import List, Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
|
||||||
from ..core.analyzer import PacketAnalyzer
|
from ..core.analyzer import PacketAnalyzer
|
||||||
|
|
||||||
@ -26,6 +29,7 @@ class MainView(tk.Tk):
|
|||||||
main_frame = ttk.Frame(self, padding=10)
|
main_frame = ttk.Frame(self, padding=10)
|
||||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# --- Top Controls Section ---
|
||||||
controls_frame = ttk.LabelFrame(main_frame, text="File Controls", padding=10)
|
controls_frame = ttk.LabelFrame(main_frame, text="File Controls", padding=10)
|
||||||
controls_frame.pack(fill=tk.X, pady=(0, 10))
|
controls_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
|
|
||||||
@ -34,15 +38,18 @@ class MainView(tk.Tk):
|
|||||||
self.load_button = ttk.Button(controls_frame, text="Open .pcapng File...", command=self._open_file)
|
self.load_button = ttk.Button(controls_frame, text="Open .pcapng File...", command=self._open_file)
|
||||||
self.load_button.pack(side=tk.RIGHT, padx=5)
|
self.load_button.pack(side=tk.RIGHT, padx=5)
|
||||||
|
|
||||||
|
# --- Notebook for different analysis types ---
|
||||||
self.results_notebook = ttk.Notebook(main_frame)
|
self.results_notebook = ttk.Notebook(main_frame)
|
||||||
self.results_notebook.pack(fill=tk.BOTH, expand=True)
|
self.results_notebook.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Create the two tabs
|
||||||
self.tab_processing_time = ttk.Frame(self.results_notebook)
|
self.tab_processing_time = ttk.Frame(self.results_notebook)
|
||||||
self.tab_jitter = ttk.Frame(self.results_notebook)
|
self.tab_jitter = ttk.Frame(self.results_notebook)
|
||||||
|
|
||||||
self.results_notebook.add(self.tab_processing_time, text=" Processing Time (A -> B) ")
|
self.results_notebook.add(self.tab_processing_time, text=" Processing Time (A -> B) ")
|
||||||
self.results_notebook.add(self.tab_jitter, text=" Interval / Jitter Analysis ")
|
self.results_notebook.add(self.tab_jitter, text=" Interval / Jitter Analysis ")
|
||||||
|
|
||||||
|
# Populate each tab with its specific widgets
|
||||||
self._create_processing_time_tab(self.tab_processing_time)
|
self._create_processing_time_tab(self.tab_processing_time)
|
||||||
self._create_jitter_tab(self.tab_jitter)
|
self._create_jitter_tab(self.tab_jitter)
|
||||||
|
|
||||||
@ -79,55 +86,29 @@ class MainView(tk.Tk):
|
|||||||
|
|
||||||
self.proc_fig = plt.Figure(figsize=(5, 4), dpi=100)
|
self.proc_fig = plt.Figure(figsize=(5, 4), dpi=100)
|
||||||
self.proc_ax = self.proc_fig.add_subplot(111)
|
self.proc_ax = self.proc_fig.add_subplot(111)
|
||||||
|
|
||||||
self.proc_canvas = FigureCanvasTkAgg(self.proc_fig, master=plot_frame)
|
self.proc_canvas = FigureCanvasTkAgg(self.proc_fig, master=plot_frame)
|
||||||
|
|
||||||
# --- NUOVA PARTE: Toolbar per il grafico ---
|
|
||||||
toolbar = NavigationToolbar2Tk(self.proc_canvas, plot_frame)
|
|
||||||
toolbar.update()
|
|
||||||
# --- FINE NUOVA PARTE ---
|
|
||||||
|
|
||||||
self.proc_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
self.proc_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
def _create_jitter_tab(self, parent_tab):
|
def _create_jitter_tab(self, parent_tab):
|
||||||
controls = ttk.LabelFrame(parent_tab, text="Analysis Parameters", padding=10)
|
controls = ttk.LabelFrame(parent_tab, text="Analysis Parameters", padding=10)
|
||||||
controls.pack(fill=tk.X, padx=10, pady=10)
|
controls.pack(fill=tk.X, padx=10, pady=10)
|
||||||
|
|
||||||
ports_frame = ttk.Frame(controls)
|
ttk.Label(controls, text="Port to Analyze:").grid(row=0, column=0, padx=5, sticky='w')
|
||||||
ports_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
self.jitter_port_var = tk.StringVar(value="60012") # Default to server command port
|
||||||
|
ttk.Entry(controls, textvariable=self.jitter_port_var, width=10).grid(row=0, column=1)
|
||||||
ttk.Label(ports_frame, text="Ports to Analyze (use Ctrl/Shift to multi-select):").pack(anchor='w')
|
|
||||||
|
|
||||||
self.jitter_ports_listbox = tk.Listbox(ports_frame, selectmode=tk.EXTENDED, height=4)
|
|
||||||
for port in ["60012", "55001", "55002"]:
|
|
||||||
self.jitter_ports_listbox.insert(tk.END, port)
|
|
||||||
self.jitter_ports_listbox.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=5)
|
|
||||||
|
|
||||||
self.jitter_ports_listbox.selection_set(0)
|
|
||||||
self.jitter_ports_listbox.selection_set(2)
|
|
||||||
self.jitter_ports_listbox.selection_set(3)
|
|
||||||
|
|
||||||
add_port_frame = ttk.Frame(ports_frame)
|
|
||||||
add_port_frame.pack(side=tk.LEFT, padx=10, fill='y')
|
|
||||||
self.jitter_custom_port_var = tk.StringVar()
|
|
||||||
custom_port_entry = ttk.Entry(add_port_frame, textvariable=self.jitter_custom_port_var, width=10)
|
|
||||||
custom_port_entry.pack()
|
|
||||||
ttk.Button(add_port_frame, text="Add Port", command=self._add_jitter_port).pack(pady=5)
|
|
||||||
|
|
||||||
self.jitter_analyze_button = ttk.Button(controls, text="Analyze Jitter", command=self._run_jitter_analysis, state=tk.DISABLED)
|
self.jitter_analyze_button = ttk.Button(controls, text="Analyze Jitter", command=self._run_jitter_analysis, state=tk.DISABLED)
|
||||||
self.jitter_analyze_button.pack(side=tk.LEFT, padx=20, anchor='center')
|
self.jitter_analyze_button.grid(row=0, column=2, padx=20)
|
||||||
|
|
||||||
pane = ttk.PanedWindow(parent_tab, orient=tk.HORIZONTAL)
|
pane = ttk.PanedWindow(parent_tab, orient=tk.HORIZONTAL)
|
||||||
pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
|
pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
|
||||||
|
|
||||||
stats_frame = ttk.LabelFrame(pane, text="Interval Statistics (ms)", padding=10)
|
stats_frame = ttk.LabelFrame(pane, text="Interval Statistics (ms)", padding=10)
|
||||||
pane.add(stats_frame, weight=2)
|
pane.add(stats_frame, weight=1)
|
||||||
|
|
||||||
self.jitter_stats_tree = ttk.Treeview(stats_frame, columns=("Port", "Metric", "Value"), show="headings")
|
self.jitter_stats_tree = ttk.Treeview(stats_frame, columns=("Metric", "Value"), show="headings")
|
||||||
self.jitter_stats_tree.heading("Port", text="Port")
|
|
||||||
self.jitter_stats_tree.heading("Metric", text="Metric")
|
self.jitter_stats_tree.heading("Metric", text="Metric")
|
||||||
self.jitter_stats_tree.heading("Value", text="Value (ms)")
|
self.jitter_stats_tree.heading("Value", text="Value (ms)")
|
||||||
self.jitter_stats_tree.column("Port", width=80, anchor='center')
|
|
||||||
self.jitter_stats_tree.column("Metric", width=120)
|
self.jitter_stats_tree.column("Metric", width=120)
|
||||||
self.jitter_stats_tree.column("Value", width=100, anchor='e')
|
self.jitter_stats_tree.column("Value", width=100, anchor='e')
|
||||||
self.jitter_stats_tree.pack(fill=tk.BOTH, expand=True)
|
self.jitter_stats_tree.pack(fill=tk.BOTH, expand=True)
|
||||||
@ -138,31 +119,8 @@ class MainView(tk.Tk):
|
|||||||
self.jitter_fig = plt.Figure(figsize=(5, 4), dpi=100)
|
self.jitter_fig = plt.Figure(figsize=(5, 4), dpi=100)
|
||||||
self.jitter_ax = self.jitter_fig.add_subplot(111)
|
self.jitter_ax = self.jitter_fig.add_subplot(111)
|
||||||
self.jitter_canvas = FigureCanvasTkAgg(self.jitter_fig, master=plot_frame)
|
self.jitter_canvas = FigureCanvasTkAgg(self.jitter_fig, master=plot_frame)
|
||||||
|
|
||||||
# --- NUOVA PARTE: Toolbar per il grafico jitter ---
|
|
||||||
toolbar = NavigationToolbar2Tk(self.jitter_canvas, plot_frame)
|
|
||||||
toolbar.update()
|
|
||||||
# --- FINE NUOVA PARTE ---
|
|
||||||
|
|
||||||
self.jitter_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
self.jitter_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
def _add_jitter_port(self):
|
|
||||||
port_str = self.jitter_custom_port_var.get().strip()
|
|
||||||
if not port_str:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
port_num = int(port_str)
|
|
||||||
if not (0 < port_num < 65536):
|
|
||||||
raise ValueError("Port out of range")
|
|
||||||
|
|
||||||
if port_str not in self.jitter_ports_listbox.get(0, tk.END):
|
|
||||||
self.jitter_ports_listbox.insert(tk.END, port_str)
|
|
||||||
|
|
||||||
self.jitter_custom_port_var.set("")
|
|
||||||
except ValueError:
|
|
||||||
messagebox.showerror("Invalid Port", "Please enter a valid port number (1-65535).")
|
|
||||||
|
|
||||||
# --- Le funzioni di caricamento file rimangono invariate ---
|
|
||||||
def _open_file(self):
|
def _open_file(self):
|
||||||
filepath = filedialog.askopenfilename(
|
filepath = filedialog.askopenfilename(
|
||||||
title="Select a capture file",
|
title="Select a capture file",
|
||||||
@ -184,6 +142,7 @@ class MainView(tk.Tk):
|
|||||||
).start()
|
).start()
|
||||||
|
|
||||||
def _create_progress_dialog(self, filepath: str) -> tk.Toplevel:
|
def _create_progress_dialog(self, filepath: str) -> tk.Toplevel:
|
||||||
|
"""Creates a modal dialog with an indeterminate progress bar."""
|
||||||
dialog = tk.Toplevel(self)
|
dialog = tk.Toplevel(self)
|
||||||
dialog.title("Loading...")
|
dialog.title("Loading...")
|
||||||
dialog.geometry("400x150")
|
dialog.geometry("400x150")
|
||||||
@ -200,6 +159,7 @@ class MainView(tk.Tk):
|
|||||||
|
|
||||||
ttk.Label(dialog, text="Loading capture file:", padding=10).pack()
|
ttk.Label(dialog, text="Loading capture file:", padding=10).pack()
|
||||||
ttk.Label(dialog, text=f"{filename} {size_str}", font=('Helvetica', 10, 'bold')).pack()
|
ttk.Label(dialog, text=f"{filename} {size_str}", font=('Helvetica', 10, 'bold')).pack()
|
||||||
|
|
||||||
ttk.Label(dialog, text="This may take a moment...", padding=5).pack()
|
ttk.Label(dialog, text="This may take a moment...", padding=5).pack()
|
||||||
|
|
||||||
progress_bar = ttk.Progressbar(dialog, mode='indeterminate', length=350)
|
progress_bar = ttk.Progressbar(dialog, mode='indeterminate', length=350)
|
||||||
@ -214,6 +174,9 @@ class MainView(tk.Tk):
|
|||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
def _load_file_thread(self, filepath: str, progress_dialog: tk.Toplevel):
|
def _load_file_thread(self, filepath: str, progress_dialog: tk.Toplevel):
|
||||||
|
"""
|
||||||
|
Background thread function to load the file. Closes the dialog when done.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
total_packets = self.analyzer.load_pcap(filepath)
|
total_packets = self.analyzer.load_pcap(filepath)
|
||||||
self.after(0, self._on_load_complete, filepath, total_packets)
|
self.after(0, self._on_load_complete, filepath, total_packets)
|
||||||
@ -225,6 +188,7 @@ class MainView(tk.Tk):
|
|||||||
self.after(0, self.load_button.config, {"state": tk.NORMAL})
|
self.after(0, self.load_button.config, {"state": tk.NORMAL})
|
||||||
|
|
||||||
def _on_load_complete(self, filepath, total_packets):
|
def _on_load_complete(self, filepath, total_packets):
|
||||||
|
"""GUI thread callback for when file loading is complete."""
|
||||||
self.filepath_label.config(text=f"File: {os.path.basename(filepath)} ({total_packets} packets)")
|
self.filepath_label.config(text=f"File: {os.path.basename(filepath)} ({total_packets} packets)")
|
||||||
self.proc_analyze_button.config(state=tk.NORMAL)
|
self.proc_analyze_button.config(state=tk.NORMAL)
|
||||||
self.jitter_analyze_button.config(state=tk.NORMAL)
|
self.jitter_analyze_button.config(state=tk.NORMAL)
|
||||||
@ -237,45 +201,35 @@ class MainView(tk.Tk):
|
|||||||
if "error" in stats:
|
if "error" in stats:
|
||||||
messagebox.showerror("Analysis Error", stats["error"])
|
messagebox.showerror("Analysis Error", stats["error"])
|
||||||
return
|
return
|
||||||
self._update_single_port_stats_table(self.proc_stats_tree, stats)
|
self._update_stats_table(self.proc_stats_tree, stats)
|
||||||
self._update_single_series_plot(self.proc_ax, self.proc_fig, self.proc_canvas, stats['data_points'], "Processing Time", "Time (ms)")
|
self._update_plot(self.proc_ax, self.proc_fig, self.proc_canvas, stats['data_points'], "Processing Time", "Time (ms)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"Analysis failed: {e}")
|
messagebox.showerror("Error", f"Analysis failed: {e}")
|
||||||
|
|
||||||
def _run_jitter_analysis(self):
|
def _run_jitter_analysis(self):
|
||||||
selected_indices = self.jitter_ports_listbox.curselection()
|
try:
|
||||||
if not selected_indices:
|
port = int(self.jitter_port_var.get())
|
||||||
messagebox.showwarning("No Selection", "Please select one or more ports to analyze.")
|
stats = self.analyzer.calculate_inter_packet_gap_stats(port)
|
||||||
return
|
if "error" in stats:
|
||||||
|
messagebox.showerror("Analysis Error", stats["error"])
|
||||||
|
return
|
||||||
|
|
||||||
selected_ports = [self.jitter_ports_listbox.get(i) for i in selected_indices]
|
# CORREZIONE: Usiamo la chiave 'std' per 'Jitter'
|
||||||
|
stats['Jitter'] = stats.get('std', 0.0)
|
||||||
|
|
||||||
all_stats: Dict[int, Dict[str, Any]] = {}
|
self._update_stats_table(self.jitter_stats_tree, stats)
|
||||||
all_data_points: Dict[int, List[float]] = {}
|
self._update_plot(self.jitter_ax, self.jitter_fig, self.jitter_canvas, stats['data_points'], f"Packet Interval (Port {port})", "Interval (ms)")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Analysis failed: {e}")
|
||||||
|
|
||||||
for port_str in selected_ports:
|
def _update_stats_table(self, tree: ttk.Treeview, stats: Dict[str, Any]):
|
||||||
try:
|
|
||||||
port = int(port_str)
|
|
||||||
stats = self.analyzer.calculate_inter_packet_gap_stats(port)
|
|
||||||
if "error" not in stats:
|
|
||||||
all_stats[port] = stats
|
|
||||||
all_data_points[port] = stats['data_points']
|
|
||||||
else:
|
|
||||||
messagebox.showwarning("Analysis Warning", f"Could not analyze port {port}:\n{stats['error']}")
|
|
||||||
except Exception as e:
|
|
||||||
messagebox.showerror("Error", f"Analysis for port {port} failed: {e}")
|
|
||||||
|
|
||||||
if all_stats:
|
|
||||||
self._update_multi_port_stats_table(all_stats)
|
|
||||||
self._update_multi_series_plot(all_data_points)
|
|
||||||
|
|
||||||
def _update_single_port_stats_table(self, tree: ttk.Treeview, stats: Dict[str, Any]):
|
|
||||||
for item in tree.get_children():
|
for item in tree.get_children():
|
||||||
tree.delete(item)
|
tree.delete(item)
|
||||||
|
|
||||||
|
# CORREZIONE: Usiamo 'std' ma lo etichettiamo come Jitter per chiarezza
|
||||||
metric_map = {
|
metric_map = {
|
||||||
'count': 'Sample Count', 'mean': 'Mean', 'std': 'Std Dev',
|
'count': 'Sample Count', 'mean': 'Mean', 'std': 'Std Dev (Jitter)',
|
||||||
'min': 'Min', '25%': '25th Percentile', '50%': 'Median',
|
'min': 'Min', '25%': '25th Percentile', '50%': 'Median (50%)',
|
||||||
'75%': '75th Percentile', '95%': '95th Percentile', '99%': '99th Percentile',
|
'75%': '75th Percentile', '95%': '95th Percentile', '99%': '99th Percentile',
|
||||||
'max': 'Max'
|
'max': 'Max'
|
||||||
}
|
}
|
||||||
@ -288,35 +242,14 @@ class MainView(tk.Tk):
|
|||||||
else:
|
else:
|
||||||
tree.insert('', 'end', values=(name, f"{value:.4f}"))
|
tree.insert('', 'end', values=(name, f"{value:.4f}"))
|
||||||
|
|
||||||
# --- NUOVA FUNZIONE per la tabella multi-porta ---
|
def _update_plot(self, ax, fig, canvas, data_points, title, ylabel):
|
||||||
def _update_multi_port_stats_table(self, all_stats: Dict[int, Dict[str, Any]]):
|
|
||||||
tree = self.jitter_stats_tree
|
|
||||||
for item in tree.get_children():
|
|
||||||
tree.delete(item)
|
|
||||||
|
|
||||||
metric_map = {'count': 'Samples', 'mean': 'Mean', 'std': 'Std Dev (Jitter)', 'min': 'Min', 'max': 'Max', '50%': 'Median'}
|
|
||||||
|
|
||||||
for port, stats in sorted(all_stats.items()):
|
|
||||||
# Add a header row for the port
|
|
||||||
tree.insert('', 'end', values=(port, '', ''), tags=('header',))
|
|
||||||
for key, name in metric_map.items():
|
|
||||||
if key in stats:
|
|
||||||
value = stats[key]
|
|
||||||
if key == 'count':
|
|
||||||
tree.insert('', 'end', values=('', name, f"{int(value)}"))
|
|
||||||
else:
|
|
||||||
tree.insert('', 'end', values=('', name, f"{value:.4f}"))
|
|
||||||
|
|
||||||
tree.tag_configure('header', font=('Helvetica', 10, 'bold'), background='#eee')
|
|
||||||
|
|
||||||
|
|
||||||
def _update_single_series_plot(self, ax, fig, canvas, data_points, title, ylabel):
|
|
||||||
ax.clear()
|
ax.clear()
|
||||||
ax.plot(data_points, marker='.', linestyle='-', markersize=4)
|
ax.plot(data_points, marker='.', linestyle='-', markersize=4)
|
||||||
|
|
||||||
if data_points:
|
if data_points:
|
||||||
s = pd.Series(data_points)
|
s = pd.Series(data_points)
|
||||||
mean_val, p99_val = s.mean(), s.quantile(0.99)
|
mean_val = s.mean()
|
||||||
|
p99_val = s.quantile(0.99)
|
||||||
ax.axhline(y=mean_val, color='g', linestyle='--', label=f'Mean ({mean_val:.2f} ms)')
|
ax.axhline(y=mean_val, color='g', linestyle='--', label=f'Mean ({mean_val:.2f} ms)')
|
||||||
ax.axhline(y=p99_val, color='r', linestyle='--', label=f'99th Percentile ({p99_val:.2f} ms)')
|
ax.axhline(y=p99_val, color='r', linestyle='--', label=f'99th Percentile ({p99_val:.2f} ms)')
|
||||||
|
|
||||||
@ -327,26 +260,3 @@ class MainView(tk.Tk):
|
|||||||
ax.legend()
|
ax.legend()
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
canvas.draw()
|
canvas.draw()
|
||||||
|
|
||||||
# --- NUOVA FUNZIONE per il grafico multi-serie ---
|
|
||||||
def _update_multi_series_plot(self, all_data_points: Dict[int, List[float]]):
|
|
||||||
ax, fig, canvas = self.jitter_ax, self.jitter_fig, self.jitter_canvas
|
|
||||||
ax.clear()
|
|
||||||
|
|
||||||
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
|
|
||||||
|
|
||||||
for i, (port, data_points) in enumerate(sorted(all_data_points.items())):
|
|
||||||
if data_points:
|
|
||||||
color = colors[i % len(colors)]
|
|
||||||
ax.plot(data_points, marker='.', linestyle='-', markersize=3, label=f'Port {port}', color=color, alpha=0.8)
|
|
||||||
|
|
||||||
ax.set_title("Packet Interval Comparison")
|
|
||||||
ax.set_xlabel("Packet Sequence")
|
|
||||||
ax.set_ylabel("Interval (ms)")
|
|
||||||
ax.grid(True, which='both', linestyle='--', linewidth=0.5)
|
|
||||||
|
|
||||||
if all_data_points:
|
|
||||||
ax.legend()
|
|
||||||
|
|
||||||
fig.tight_layout()
|
|
||||||
canvas.draw()
|
|
||||||
Loading…
Reference in New Issue
Block a user