""" Chapter 10 Timing Analysis Plugin Demonstrates how to use exposed CH10 frame variables for advanced analysis """ from typing import Dict, Any, List, Optional from dataclasses import dataclass import statistics @dataclass class TimingAnalysisResult: """Results from timing analysis""" packet_timestamp: float internal_timestamp: Optional[float] time_delta: Optional[float] clock_drift_ppm: Optional[float] timing_quality: str anomaly_detected: bool confidence_score: float class Chapter10TimingAnalysisPlugin: """Analysis plugin demonstrating use of exposed CH10 variables""" def __init__(self): self.flow_timing_history: Dict[str, List[TimingAnalysisResult]] = {} self.reference_time_base: Optional[float] = None @property def plugin_name(self) -> str: return "CH10_Advanced_Timing_Analysis" @property def required_fields(self) -> List[str]: """Fields required from the enhanced CH10 decoder""" return [ 'packet_timestamp', # From packet capture 'internal_timestamp', # From CH10 internal time 'internal_seconds', # CH10 seconds component 'internal_nanoseconds', # CH10 nanoseconds component 'channel_id', # Channel identifier 'sequence_number', # Packet sequence 'relative_time_counter', # RTC value 'data_type', # Data type for context 'rtc_sync_error', # RTC sync status 'time_source_confidence', # Decoder's confidence assessment 'frame_quality_score' # Overall frame quality ] def analyze_frame(self, frame_data, flow_context: Dict[str, Any]) -> TimingAnalysisResult: """Analyze timing for a single CH10 frame""" # Extract timing information packet_time = frame_data.get_field('packet_timestamp') internal_time = frame_data.get_field('internal_timestamp') if internal_time is None: return TimingAnalysisResult( packet_timestamp=packet_time, internal_timestamp=None, time_delta=None, clock_drift_ppm=None, timing_quality="no_internal_time", anomaly_detected=False, confidence_score=0.0 ) # Calculate basic time delta time_delta = packet_time - internal_time # Analyze timing quality timing_quality = self._assess_timing_quality(frame_data, time_delta) # Calculate clock drift in PPM clock_drift_ppm = self._calculate_clock_drift_ppm( time_delta, flow_context.get('flow_duration', 1.0) ) # Detect anomalies anomaly_detected = self._detect_timing_anomaly(frame_data, time_delta) # Calculate confidence score confidence_score = self._calculate_confidence_score(frame_data, time_delta) result = TimingAnalysisResult( packet_timestamp=packet_time, internal_timestamp=internal_time, time_delta=time_delta, clock_drift_ppm=clock_drift_ppm, timing_quality=timing_quality, anomaly_detected=anomaly_detected, confidence_score=confidence_score ) # Store in flow history for trend analysis flow_key = flow_context.get('flow_key', 'unknown') if flow_key not in self.flow_timing_history: self.flow_timing_history[flow_key] = [] self.flow_timing_history[flow_key].append(result) return result def analyze_flow_timing_trends(self, flow_key: str) -> Dict[str, Any]: """Analyze timing trends across an entire flow""" if flow_key not in self.flow_timing_history: return {'error': 'No timing data available for flow'} timing_results = self.flow_timing_history[flow_key] valid_results = [r for r in timing_results if r.time_delta is not None] if not valid_results: return {'error': 'No valid timing data in flow'} # Extract time deltas for statistical analysis time_deltas = [r.time_delta for r in valid_results] drift_values = [r.clock_drift_ppm for r in valid_results if r.clock_drift_ppm is not None] # Calculate comprehensive statistics analysis = { 'frame_count': len(valid_results), 'timing_statistics': { 'mean_delta': statistics.mean(time_deltas), 'median_delta': statistics.median(time_deltas), 'std_deviation': statistics.stdev(time_deltas) if len(time_deltas) > 1 else 0.0, 'min_delta': min(time_deltas), 'max_delta': max(time_deltas), 'delta_range': max(time_deltas) - min(time_deltas) }, 'drift_analysis': {}, 'quality_metrics': { 'anomaly_rate': sum(1 for r in valid_results if r.anomaly_detected) / len(valid_results), 'avg_confidence': statistics.mean([r.confidence_score for r in valid_results]), 'quality_trend': self._analyze_quality_trend(valid_results) }, 'timing_stability': self._assess_timing_stability(time_deltas), 'recommendations': [] } # Drift analysis if drift_values: analysis['drift_analysis'] = { 'mean_drift_ppm': statistics.mean(drift_values), 'drift_stability': statistics.stdev(drift_values) if len(drift_values) > 1 else 0.0, 'drift_trend': self._calculate_drift_trend(drift_values), 'max_drift_ppm': max(abs(d) for d in drift_values) } # Generate recommendations analysis['recommendations'] = self._generate_recommendations(analysis) return analysis def compare_channel_timing(self, flow_data: Dict[str, List]) -> Dict[str, Any]: """Compare timing across multiple CH10 channels""" channel_analysis = {} for flow_key, timing_results in self.flow_timing_history.items(): # Extract channel ID from first result (simplified) if timing_results: # Would need to extract channel_id from frame_data # This is a simplified example channel_analysis[flow_key] = { 'timing_variance': self._calculate_timing_variance(timing_results), 'sync_quality': self._assess_sync_quality(timing_results), 'relative_drift': self._calculate_relative_drift(timing_results) } # Cross-channel correlation analysis correlation_matrix = self._calculate_channel_correlations(channel_analysis) return { 'channel_individual_analysis': channel_analysis, 'cross_channel_correlations': correlation_matrix, 'sync_status': self._assess_overall_sync_status(channel_analysis), 'timing_reference_quality': self._assess_reference_quality() } def _assess_timing_quality(self, frame_data, time_delta: float) -> str: """Assess the quality of timing data""" # Check for RTC sync errors if frame_data.get_field('rtc_sync_error', False): return "rtc_sync_error" # Check frame quality frame_quality = frame_data.get_field('frame_quality_score', 0) if frame_quality < 50: return "poor_frame_quality" # Check time delta magnitude abs_delta = abs(time_delta) if abs_delta > 1.0: # More than 1 second difference return "large_time_discrepancy" elif abs_delta > 0.1: # More than 100ms difference return "moderate_time_discrepancy" elif abs_delta > 0.001: # More than 1ms difference return "small_time_discrepancy" else: return "excellent" def _calculate_clock_drift_ppm(self, time_delta: float, flow_duration: float) -> float: """Calculate clock drift in parts per million""" if flow_duration <= 0: return 0.0 # Simplified drift calculation # In practice, would need more sophisticated analysis drift_fraction = time_delta / flow_duration return drift_fraction * 1_000_000 # Convert to PPM def _detect_timing_anomaly(self, frame_data, time_delta: float) -> bool: """Detect timing anomalies in this frame""" # Multiple criteria for anomaly detection anomaly_indicators = [ abs(time_delta) > 0.1, # Large time discrepancy frame_data.get_field('rtc_sync_error', False), # RTC sync error frame_data.get_field('format_error', False), # Format error frame_data.get_field('overflow_error', False), # Overflow error frame_data.get_field('frame_quality_score', 100) < 70 # Poor frame quality ] return any(anomaly_indicators) def _calculate_confidence_score(self, frame_data, time_delta: float) -> float: """Calculate confidence in timing analysis""" confidence_factors = [] # Frame quality factor frame_quality = frame_data.get_field('frame_quality_score', 0) / 100.0 confidence_factors.append(frame_quality) # Time source confidence from decoder time_confidence = frame_data.get_field('time_source_confidence', 0.5) confidence_factors.append(time_confidence) # Time delta reasonableness abs_delta = abs(time_delta) delta_confidence = max(0.0, 1.0 - (abs_delta / 10.0)) # Decreases with larger deltas confidence_factors.append(delta_confidence) # Error flags (reduce confidence) error_penalty = 0.0 if frame_data.get_field('rtc_sync_error', False): error_penalty += 0.3 if frame_data.get_field('format_error', False): error_penalty += 0.2 if frame_data.get_field('overflow_error', False): error_penalty += 0.2 base_confidence = statistics.mean(confidence_factors) final_confidence = max(0.0, base_confidence - error_penalty) return final_confidence def _analyze_quality_trend(self, timing_results: List[TimingAnalysisResult]) -> str: """Analyze trend in timing quality over time""" if len(timing_results) < 10: return "insufficient_data" # Split into segments and compare segment_size = len(timing_results) // 3 early_segment = timing_results[:segment_size] late_segment = timing_results[-segment_size:] early_confidence = statistics.mean([r.confidence_score for r in early_segment]) late_confidence = statistics.mean([r.confidence_score for r in late_segment]) if late_confidence > early_confidence + 0.1: return "improving" elif late_confidence < early_confidence - 0.1: return "degrading" else: return "stable" def _assess_timing_stability(self, time_deltas: List[float]) -> Dict[str, Any]: """Assess the stability of timing measurements""" if len(time_deltas) < 2: return {'stability': 'insufficient_data'} std_dev = statistics.stdev(time_deltas) mean_abs_dev = statistics.mean([abs(d) for d in time_deltas]) # Coefficient of variation mean_delta = statistics.mean(time_deltas) cv = std_dev / abs(mean_delta) if mean_delta != 0 else float('inf') # Stability classification if std_dev < 0.001: # Less than 1ms standard deviation stability = "excellent" elif std_dev < 0.01: # Less than 10ms standard deviation stability = "good" elif std_dev < 0.1: # Less than 100ms standard deviation stability = "moderate" else: stability = "poor" return { 'stability': stability, 'standard_deviation': std_dev, 'mean_absolute_deviation': mean_abs_dev, 'coefficient_of_variation': cv, 'drift_consistency': 'stable' if cv < 0.1 else 'variable' } def _calculate_drift_trend(self, drift_values: List[float]) -> str: """Calculate the trend in clock drift""" if len(drift_values) < 5: return "insufficient_data" # Simple linear trend analysis x = list(range(len(drift_values))) y = drift_values # Calculate correlation coefficient as trend indicator n = len(x) sum_x = sum(x) sum_y = sum(y) sum_xy = sum(x[i] * y[i] for i in range(n)) sum_x2 = sum(x[i] * x[i] for i in range(n)) denominator = (n * sum_x2 - sum_x * sum_x) if denominator == 0: return "stable" slope = (n * sum_xy - sum_x * sum_y) / denominator if slope > 1.0: # Increasing drift return "increasing" elif slope < -1.0: # Decreasing drift return "decreasing" else: return "stable" def _generate_recommendations(self, analysis: Dict[str, Any]) -> List[str]: """Generate recommendations based on timing analysis""" recommendations = [] # Check anomaly rate anomaly_rate = analysis.get('quality_metrics', {}).get('anomaly_rate', 0) if anomaly_rate > 0.1: # More than 10% anomalies recommendations.append("High anomaly rate detected - check timing source stability") # Check drift drift_analysis = analysis.get('drift_analysis', {}) max_drift = drift_analysis.get('max_drift_ppm', 0) if max_drift > 100: # More than 100 PPM drift recommendations.append("Significant clock drift detected - calibrate timing reference") # Check timing stability stability_info = analysis.get('timing_stability', {}) if stability_info.get('stability') in ['poor', 'moderate']: recommendations.append("Timing instability detected - investigate noise sources") # Check confidence avg_confidence = analysis.get('quality_metrics', {}).get('avg_confidence', 1.0) if avg_confidence < 0.7: recommendations.append("Low timing confidence - verify CH10 frame integrity") if not recommendations: recommendations.append("Timing analysis shows good quality - no issues detected") return recommendations def _calculate_timing_variance(self, timing_results: List[TimingAnalysisResult]) -> float: """Calculate timing variance for a channel""" deltas = [r.time_delta for r in timing_results if r.time_delta is not None] return statistics.variance(deltas) if len(deltas) > 1 else 0.0 def _assess_sync_quality(self, timing_results: List[TimingAnalysisResult]) -> str: """Assess synchronization quality for a channel""" if not timing_results: return "no_data" avg_confidence = statistics.mean([r.confidence_score for r in timing_results]) if avg_confidence > 0.9: return "excellent" elif avg_confidence > 0.7: return "good" elif avg_confidence > 0.5: return "moderate" else: return "poor" def _calculate_relative_drift(self, timing_results: List[TimingAnalysisResult]) -> float: """Calculate relative drift compared to reference""" # Simplified calculation - would compare against reference channel deltas = [r.time_delta for r in timing_results if r.time_delta is not None] return statistics.stdev(deltas) if len(deltas) > 1 else 0.0 def _calculate_channel_correlations(self, channel_analysis: Dict) -> Dict[str, float]: """Calculate correlations between channels""" # Placeholder for cross-channel correlation analysis return {"correlation_analysis": "not_implemented_in_demo"} def _assess_overall_sync_status(self, channel_analysis: Dict) -> str: """Assess overall synchronization status across channels""" sync_qualities = [info.get('sync_quality', 'poor') for info in channel_analysis.values()] excellent_count = sync_qualities.count('excellent') good_count = sync_qualities.count('good') total_count = len(sync_qualities) if total_count == 0: return "no_data" elif excellent_count / total_count > 0.8: return "excellent" elif (excellent_count + good_count) / total_count > 0.6: return "good" else: return "poor" def _assess_reference_quality(self) -> str: """Assess the quality of the timing reference""" # Placeholder for reference quality assessment return "analysis_pending"