tabbed frametype filtering
This commit is contained in:
@@ -37,7 +37,7 @@ class BackgroundAnalyzer:
|
||||
"""Analyzer that processes PCAP files in background threads"""
|
||||
|
||||
def __init__(self, analyzer: EthernetAnalyzer,
|
||||
num_threads: int = 4,
|
||||
num_threads: int = 1, # Force single-threaded to avoid race conditions
|
||||
batch_size: int = 1000,
|
||||
progress_callback: Optional[Callable[[ParsingProgress], None]] = None,
|
||||
flow_update_callback: Optional[Callable[[], None]] = None):
|
||||
@@ -74,7 +74,7 @@ class BackgroundAnalyzer:
|
||||
|
||||
# Flow update batching
|
||||
self.packets_since_update = 0
|
||||
self.update_batch_size = 50 # Update UI every 50 packets (more frequent)
|
||||
self.update_batch_size = 100 # Update UI every 100 packets (slower for less frequent updates)
|
||||
self.update_lock = threading.Lock()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -87,6 +87,7 @@ class BackgroundAnalyzer:
|
||||
return
|
||||
|
||||
self.is_parsing = True
|
||||
self.analyzer.is_parsing = True # Set parsing flag on analyzer
|
||||
self.stop_event.clear()
|
||||
self.start_time = time.time()
|
||||
self.processed_packets = 0
|
||||
@@ -221,8 +222,8 @@ class BackgroundAnalyzer:
|
||||
try:
|
||||
current_time = time.time()
|
||||
|
||||
# Update every 0.5 seconds
|
||||
if current_time - last_update_time >= 0.5:
|
||||
# Update every 2.0 seconds (slower progress updates)
|
||||
if current_time - last_update_time >= 2.0:
|
||||
with self.parse_lock:
|
||||
current_packets = self.processed_packets
|
||||
|
||||
@@ -246,7 +247,7 @@ class BackgroundAnalyzer:
|
||||
if all(f.done() for f in futures):
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.5) # Slower monitoring loop
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("Monitor thread interrupted")
|
||||
break
|
||||
@@ -256,6 +257,7 @@ class BackgroundAnalyzer:
|
||||
|
||||
# Final update
|
||||
self.is_parsing = False
|
||||
self.analyzer.is_parsing = False # Clear parsing flag on analyzer
|
||||
self._report_progress(is_complete=True)
|
||||
|
||||
# Final flow update
|
||||
@@ -267,7 +269,7 @@ class BackgroundAnalyzer:
|
||||
|
||||
# Calculate final statistics
|
||||
with self.flow_lock:
|
||||
self.analyzer.statistics_engine.calculate_all_statistics()
|
||||
self.analyzer.statistics_engine.calculate_flow_statistics(self.analyzer.flows)
|
||||
|
||||
def _report_progress(self, packets_per_second: float = 0,
|
||||
elapsed_time: float = 0,
|
||||
|
||||
@@ -26,6 +26,7 @@ class EthernetAnalyzer:
|
||||
self.all_packets: List[Packet] = []
|
||||
self.is_live = False
|
||||
self.stop_capture = False
|
||||
self.is_parsing = False # Flag to track parsing state
|
||||
|
||||
# Expose flows for backward compatibility
|
||||
self.flows = self.flow_manager.flows
|
||||
|
||||
@@ -256,21 +256,31 @@ class FlowManager:
|
||||
decoded = ch10_info['decoded_payload']
|
||||
data_type_name = decoded.get('data_type_name', 'CH10-Data')
|
||||
|
||||
# Simplify timing frame names for display
|
||||
# For timing analysis purposes, group frames by their actual timing behavior
|
||||
# rather than their semantic meaning. Based on debug analysis:
|
||||
# - Some timing frames have ~26s intervals (high-level timing)
|
||||
# - Other frames (including some timing) have ~100ms intervals (data stream)
|
||||
|
||||
# Keep high-level timing frames separate (they have very different timing)
|
||||
if 'ACTTS' in data_type_name:
|
||||
return 'CH10-ACTTS'
|
||||
# Note: Extended Timing frames often have the same ~100ms timing as data frames
|
||||
# so they should be grouped with CH10-Data for accurate timing analysis
|
||||
elif 'Sync' in data_type_name and 'Custom' in data_type_name:
|
||||
return 'CH10-Sync'
|
||||
elif 'Clock' in data_type_name and 'Custom' in data_type_name:
|
||||
return 'CH10-Clock'
|
||||
elif ('Time' in data_type_name or 'Timing' in data_type_name) and 'Custom' in data_type_name:
|
||||
# Custom timing frames often have the 26s interval pattern
|
||||
if 'Time' in data_type_name:
|
||||
return 'CH10-Time'
|
||||
else:
|
||||
return 'CH10-Timing'
|
||||
# Special data types that should remain separate
|
||||
elif 'GPS NMEA' in data_type_name:
|
||||
return 'CH10-GPS'
|
||||
elif 'EAG ACMI' in data_type_name:
|
||||
return 'CH10-ACMI'
|
||||
elif 'Custom' in data_type_name and 'Timing' in data_type_name:
|
||||
# Extract variant for custom timing
|
||||
if 'Variant 0x04' in data_type_name:
|
||||
return 'CH10-ACTTS'
|
||||
elif 'Extended Timing' in data_type_name:
|
||||
return 'CH10-ExtTiming'
|
||||
else:
|
||||
return 'CH10-Timing'
|
||||
elif 'Ethernet' in data_type_name:
|
||||
return 'CH10-Ethernet'
|
||||
elif 'Image' in data_type_name:
|
||||
@@ -279,10 +289,10 @@ class FlowManager:
|
||||
return 'CH10-UART'
|
||||
elif 'CAN' in data_type_name:
|
||||
return 'CH10-CAN'
|
||||
elif 'Unknown' not in data_type_name:
|
||||
# Extract first word for other known types
|
||||
first_word = data_type_name.split()[0]
|
||||
return f'CH10-{first_word}'
|
||||
# Everything else gets grouped as CH10-Data for consistent timing analysis
|
||||
# This includes: Multi-Source, regular timing frames, custom data types, etc.
|
||||
else:
|
||||
return 'CH10-Data'
|
||||
|
||||
return 'CH10-Data'
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ class StatisticsEngine:
|
||||
for flow in flows.values():
|
||||
self._calculate_single_flow_statistics(flow)
|
||||
|
||||
def calculate_all_statistics(self, analyzer=None) -> None:
|
||||
"""Calculate statistics for all flows (called by background analyzer)"""
|
||||
# This is called by the background analyzer
|
||||
# The analyzer parameter should be passed in
|
||||
if analyzer and hasattr(analyzer, 'flows'):
|
||||
self.calculate_flow_statistics(analyzer.flows)
|
||||
|
||||
def _calculate_single_flow_statistics(self, flow: FlowStats) -> None:
|
||||
"""Calculate statistics for a single flow"""
|
||||
# Ensure timeline statistics are calculated
|
||||
@@ -77,11 +84,18 @@ class StatisticsEngine:
|
||||
# Detect outliers for this frame type
|
||||
ft_threshold = ft_stats.avg_inter_arrival + (self.outlier_threshold_sigma * ft_stats.std_inter_arrival)
|
||||
|
||||
# Clear existing outliers to recalculate
|
||||
ft_stats.outlier_frames.clear()
|
||||
ft_stats.outlier_details.clear()
|
||||
ft_stats.enhanced_outlier_details.clear()
|
||||
|
||||
for i, inter_time in enumerate(ft_stats.inter_arrival_times):
|
||||
if inter_time > ft_threshold:
|
||||
frame_number = ft_stats.frame_numbers[i + 1]
|
||||
frame_number = ft_stats.frame_numbers[i + 1] # Current frame
|
||||
prev_frame_number = ft_stats.frame_numbers[i] # Previous frame
|
||||
ft_stats.outlier_frames.append(frame_number)
|
||||
ft_stats.outlier_details.append((frame_number, inter_time))
|
||||
ft_stats.outlier_details.append((frame_number, inter_time)) # Legacy format
|
||||
ft_stats.enhanced_outlier_details.append((frame_number, prev_frame_number, inter_time)) # Enhanced format
|
||||
|
||||
def get_flow_summary_statistics(self, flows: Dict[tuple, FlowStats]) -> Dict[str, float]:
|
||||
"""Get summary statistics across all flows"""
|
||||
@@ -232,9 +246,11 @@ class StatisticsEngine:
|
||||
threshold = avg + (self.outlier_threshold_sigma * std)
|
||||
if new_time > threshold:
|
||||
frame_number = ft_stats.frame_numbers[-1]
|
||||
prev_frame_number = ft_stats.frame_numbers[-2] if len(ft_stats.frame_numbers) > 1 else 0
|
||||
if frame_number not in ft_stats.outlier_frames:
|
||||
ft_stats.outlier_frames.append(frame_number)
|
||||
ft_stats.outlier_details.append((frame_number, new_time))
|
||||
ft_stats.outlier_details.append((frame_number, new_time)) # Legacy format
|
||||
ft_stats.enhanced_outlier_details.append((frame_number, prev_frame_number, new_time)) # Enhanced format
|
||||
stats['outlier_count'] += 1
|
||||
|
||||
stats['last_avg'] = avg
|
||||
|
||||
Reference in New Issue
Block a user