working to analyze timing issues
This commit is contained in:
177
analyzer/tui/panels/detail_panel.py
Normal file
177
analyzer/tui/panels/detail_panel.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Right panel - Flow details with frame type table
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
import curses
|
||||
|
||||
from ...models import FlowStats, FrameTypeStats
|
||||
|
||||
|
||||
class DetailPanel:
|
||||
"""Right panel showing detailed flow information"""
|
||||
|
||||
def draw(self, stdscr, x_offset: int, y_offset: int, width: int,
|
||||
flows_list: List[FlowStats], selected_flow: int, max_height: Optional[int] = None):
|
||||
"""Draw detailed information panel for selected flow or frame type"""
|
||||
|
||||
if not flows_list:
|
||||
stdscr.addstr(y_offset, x_offset, "No flows available")
|
||||
return
|
||||
|
||||
# Get the selected flow and frame type
|
||||
flow, selected_frame_type = self._get_selected_flow_and_frame_type(flows_list, selected_flow)
|
||||
if not flow:
|
||||
stdscr.addstr(y_offset, x_offset, "No flow selected")
|
||||
return
|
||||
|
||||
if max_height is None:
|
||||
height, _ = stdscr.getmaxyx()
|
||||
max_lines = height - y_offset - 2
|
||||
else:
|
||||
max_lines = y_offset + max_height
|
||||
|
||||
try:
|
||||
# ALWAYS show flow details first
|
||||
stdscr.addstr(y_offset, x_offset, f"FLOW DETAILS: {flow.src_ip} -> {flow.dst_ip}", curses.A_BOLD)
|
||||
y_offset += 2
|
||||
|
||||
stdscr.addstr(y_offset, x_offset, f"Packets: {flow.frame_count} | Bytes: {flow.total_bytes:,}")
|
||||
y_offset += 1
|
||||
|
||||
# Frame types table
|
||||
if flow.frame_types and y_offset < max_lines:
|
||||
y_offset += 1
|
||||
stdscr.addstr(y_offset, x_offset, "Frame Types:", curses.A_BOLD)
|
||||
y_offset += 1
|
||||
|
||||
# Table header
|
||||
header = f"{'Type':<12} {'#Pkts':<6} {'Bytes':<8} {'Avg ΔT':<8} {'2σ Out':<6}"
|
||||
stdscr.addstr(y_offset, x_offset, header, curses.A_UNDERLINE)
|
||||
y_offset += 1
|
||||
|
||||
sorted_frame_types = sorted(flow.frame_types.items(), key=lambda x: x[1].count, reverse=True)
|
||||
for frame_type, ft_stats in sorted_frame_types:
|
||||
if y_offset >= max_lines:
|
||||
break
|
||||
|
||||
avg_str = f"{ft_stats.avg_inter_arrival:.3f}s" if ft_stats.avg_inter_arrival > 0 else "N/A"
|
||||
bytes_str = f"{ft_stats.total_bytes:,}" if ft_stats.total_bytes < 10000 else f"{ft_stats.total_bytes/1000:.1f}K"
|
||||
outliers_count = len(ft_stats.outlier_details) if ft_stats.outlier_details else 0
|
||||
|
||||
# Truncate frame type name if too long
|
||||
type_name = frame_type[:11] if len(frame_type) > 11 else frame_type
|
||||
|
||||
ft_line = f"{type_name:<12} {ft_stats.count:<6} {bytes_str:<8} {avg_str:<8} {outliers_count:<6}"
|
||||
stdscr.addstr(y_offset, x_offset, ft_line)
|
||||
y_offset += 1
|
||||
|
||||
# Timing statistics
|
||||
if y_offset < max_lines:
|
||||
y_offset += 1
|
||||
stdscr.addstr(y_offset, x_offset, "Timing:", curses.A_BOLD)
|
||||
y_offset += 1
|
||||
|
||||
if flow.avg_inter_arrival > 0:
|
||||
stdscr.addstr(y_offset, x_offset + 2, f"Avg: {flow.avg_inter_arrival:.6f}s")
|
||||
y_offset += 1
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset + 2, f"Std: {flow.std_inter_arrival:.6f}s")
|
||||
y_offset += 1
|
||||
else:
|
||||
stdscr.addstr(y_offset, x_offset + 2, "No timing data")
|
||||
y_offset += 1
|
||||
|
||||
# Display outlier frame details for each frame type
|
||||
if flow.frame_types and y_offset < max_lines:
|
||||
outlier_frame_types = [(frame_type, ft_stats) for frame_type, ft_stats in flow.frame_types.items()
|
||||
if ft_stats.outlier_details]
|
||||
|
||||
if outlier_frame_types:
|
||||
y_offset += 1
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset, "Outlier Frames:", curses.A_BOLD)
|
||||
y_offset += 1
|
||||
|
||||
for frame_type, ft_stats in outlier_frame_types:
|
||||
if y_offset >= max_lines:
|
||||
break
|
||||
|
||||
# Display frame type header
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset + 2, f"{frame_type}:", curses.A_UNDERLINE)
|
||||
y_offset += 1
|
||||
|
||||
# Display outlier details as individual table rows in format "frame# | deltaT"
|
||||
for frame_num, frame_inter_arrival_time in ft_stats.outlier_details:
|
||||
if y_offset >= max_lines:
|
||||
break
|
||||
outlier_line = f"{frame_num} | {frame_inter_arrival_time:.3f}s"
|
||||
stdscr.addstr(y_offset, x_offset + 4, outlier_line)
|
||||
y_offset += 1
|
||||
|
||||
# If a frame type is selected, show additional frame type specific details
|
||||
if selected_frame_type and selected_frame_type in flow.frame_types and y_offset < max_lines:
|
||||
ft_stats = flow.frame_types[selected_frame_type]
|
||||
|
||||
# Add separator
|
||||
y_offset += 2
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset, "─" * min(width-2, 40))
|
||||
y_offset += 1
|
||||
|
||||
# Frame type specific header
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset, f"FRAME TYPE: {selected_frame_type}", curses.A_BOLD)
|
||||
y_offset += 2
|
||||
|
||||
# Frame type specific info
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset, f"Count: {ft_stats.count}")
|
||||
y_offset += 1
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset, f"Bytes: {ft_stats.total_bytes:,}")
|
||||
y_offset += 1
|
||||
|
||||
# Frame type timing
|
||||
if y_offset < max_lines:
|
||||
y_offset += 1
|
||||
stdscr.addstr(y_offset, x_offset, "Timing:", curses.A_BOLD)
|
||||
y_offset += 1
|
||||
|
||||
if ft_stats.avg_inter_arrival > 0:
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset + 2, f"Avg: {ft_stats.avg_inter_arrival:.6f}s")
|
||||
y_offset += 1
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset + 2, f"Std: {ft_stats.std_inter_arrival:.6f}s")
|
||||
y_offset += 1
|
||||
else:
|
||||
if y_offset < max_lines:
|
||||
stdscr.addstr(y_offset, x_offset + 2, "No timing data")
|
||||
y_offset += 1
|
||||
|
||||
except curses.error:
|
||||
# Ignore curses errors from writing outside screen bounds
|
||||
pass
|
||||
|
||||
def _get_selected_flow_and_frame_type(self, flows_list: List[FlowStats],
|
||||
selected_flow: int) -> Tuple[Optional[FlowStats], Optional[str]]:
|
||||
"""Get the currently selected flow and frame type based on selection index"""
|
||||
current_item = 0
|
||||
|
||||
for flow in flows_list:
|
||||
if current_item == selected_flow:
|
||||
return flow, None # Selected the main flow
|
||||
current_item += 1
|
||||
|
||||
# Check frame types for this flow
|
||||
if flow.frame_types:
|
||||
sorted_frame_types = sorted(flow.frame_types.items(), key=lambda x: x[1].count, reverse=True)
|
||||
for frame_type, ft_stats in sorted_frame_types:
|
||||
if current_item == selected_flow:
|
||||
return flow, frame_type # Selected a frame type
|
||||
current_item += 1
|
||||
|
||||
# Fallback to first flow if selection is out of bounds
|
||||
return flows_list[0] if flows_list else None, None
|
||||
Reference in New Issue
Block a user