diff --git a/common/generate_combined_report.py b/common/generate_combined_report.py index 1328f64..9722bde 100755 --- a/common/generate_combined_report.py +++ b/common/generate_combined_report.py @@ -7,6 +7,7 @@ from plotly.subplots import make_subplots from datetime import datetime import json +import html def read_system_info(info_file): """Read and parse system information file.""" @@ -52,13 +53,10 @@ def read_system_info(info_file): def generate_gpu_graph(csv_file): """Generate GPU performance graph from GPU data.""" try: - # Read GPU CSV data df = pd.read_csv(csv_file) - if df.empty or 'gpu_load' not in df.columns: return None, 0 - # Create GPU load trace with frequency in hover gpu_hover = [] for _, row in df.iterrows(): text = f"GPU Load: {row['gpu_load']:.0f}%
" @@ -77,15 +75,41 @@ def generate_gpu_graph(csv_file): text=gpu_hover ) - # Calculate test duration duration = df['seconds'].iloc[-1] / 60 if len(df) > 0 else 1 - return trace, duration except Exception as e: print(f"Error processing GPU data: {e}") return None, 0 +def generate_network_graph(csv_file): + """Generate Network Latency graph from test metrics.""" + try: + df = pd.read_csv(csv_file) + if df.empty or 'Latency_ms' not in df.columns: + return None + + network_hover = [] + for _, row in df.iterrows(): + text = f"Status: {row['Interface_Status']}
" + text += f"Time: {row['seconds']}s
" + text += f"Latency: {row['Latency_ms']:.2f} ms
" + network_hover.append(text) + + trace = go.Scatter( + x=df['seconds'], + y=df['Latency_ms'], + mode='lines+markers', + name='Network Latency (ms)', + line=dict(color='#00A8FF', width=2), + hovertemplate='%{text}', + text=network_hover + ) + return trace + except Exception as e: + print(f"Error processing network data: {e}") + return None + def generate_temperature_graph(csv_file): """Generate temperature graph for backward compatibility.""" if not os.path.exists(csv_file): @@ -127,51 +151,28 @@ def generate_temperature_graph(csv_file): def generate_system_graphs(csv_file): """Generate system monitoring graphs with temperature, CPU load, and CPU frequency.""" - if not os.path.exists(csv_file): return [], None try: - # Read CSV, skipping metadata line if present - with open(csv_file, 'r') as f: - first_line = f.readline() - metadata = {} - if first_line.startswith('# METADATA:'): - import json - metadata_str = first_line.replace('# METADATA:', '').strip() - metadata = json.loads(metadata_str) - - # Read the actual data df = pd.read_csv(csv_file, comment='#') - - if df.empty: - return [], None - - # Use seconds column for time axis - if 'seconds' in df.columns: - df['time'] = df['seconds'] - time_column = 'time' - else: + if df.empty or 'seconds' not in df.columns: return [], None + time_column = 'seconds' traces = [] - # 1. Temperature trace (average with hover details) + # 1. Temperature trace temp_columns = [col for col in df.columns if col.startswith('temp_')] if temp_columns: - # Convert to numeric for col in temp_columns: df[col] = pd.to_numeric(df[col], errors='coerce') - - # Calculate average df['avg_temp'] = df[temp_columns].mean(axis=1) - # Create hover text temp_hover = [] for idx, row in df.iterrows(): text = f"Avg Temperature: {row['avg_temp']:.1f}°C
" - text += f"Time: {row[time_column]:.1f}s
" - text += "
Sensors:
" + text += f"Time: {row[time_column]:.1f}s

Sensors:
" for col in temp_columns: sensor_name = col.replace('temp_', '').replace('_', ' ').title() if pd.notna(row[col]): @@ -179,33 +180,22 @@ def generate_system_graphs(csv_file): temp_hover.append(text) traces.append(go.Scatter( - x=df[time_column], - y=df['avg_temp'], - mode='lines', - name='Temperature (°C)', - line=dict(color='#FF6B6B', width=2), - hovertemplate='%{text}', - text=temp_hover, - visible=True, - yaxis='y' + x=df[time_column], y=df['avg_temp'], mode='lines', + name='Temperature (°C)', line=dict(color='#FF6B6B', width=2), + hovertemplate='%{text}', text=temp_hover, yaxis='y' )) - # 2. CPU Load trace (average with hover details) + # 2. CPU Load trace load_columns = [col for col in df.columns if col.startswith('load_')] if load_columns: - # Convert to numeric for col in load_columns: df[col] = pd.to_numeric(df[col], errors='coerce') - - # Calculate average df['avg_load'] = df[load_columns].mean(axis=1) - # Create hover text load_hover = [] for idx, row in df.iterrows(): text = f"Avg CPU Load: {row['avg_load']:.1f}%
" - text += f"Time: {row[time_column]:.1f}s
" - text += "
Per Core:
" + text += f"Time: {row[time_column]:.1f}s

Per Core:
" for col in sorted(load_columns): core_num = col.replace('load_cpu', '') if pd.notna(row[col]): @@ -213,34 +203,23 @@ def generate_system_graphs(csv_file): load_hover.append(text) traces.append(go.Scatter( - x=df[time_column], - y=df['avg_load'], - mode='lines', - name='CPU Load (%)', - line=dict(color='#4ECDC4', width=2), - hovertemplate='%{text}', - text=load_hover, - visible=True, - yaxis='y2' + x=df[time_column], y=df['avg_load'], mode='lines', + name='CPU Load (%)', line=dict(color='#4ECDC4', width=2), + hovertemplate='%{text}', text=load_hover, yaxis='y2' )) - # 3. CPU Frequency trace (average with hover details) + # 3. CPU Frequency trace freq_columns = [col for col in df.columns if col.startswith('freq_')] if freq_columns: - # Convert to numeric for col in freq_columns: df[col] = pd.to_numeric(df[col], errors='coerce') - - # Calculate average df['avg_freq'] = df[freq_columns].mean(axis=1) - # Create hover text freq_hover = [] for idx, row in df.iterrows(): text = f"Avg CPU Freq: {row['avg_freq']:.0f} MHz
" - text += f"Time: {row[time_column]:.1f}s
" - text += "
Per Core:
" - for i, col in enumerate(sorted(freq_columns)): + text += f"Time: {row[time_column]:.1f}s

Per Core:
" + for col in sorted(freq_columns): core_num = col.replace('freq_cpu', '') if pd.notna(row[col]): core_type = "LITTLE" if int(core_num) < 4 else "BIG" @@ -248,36 +227,23 @@ def generate_system_graphs(csv_file): freq_hover.append(text) traces.append(go.Scatter( - x=df[time_column], - y=df['avg_freq'], - mode='lines', - name='CPU Frequency (MHz)', - line=dict(color='#95E77E', width=2), - hovertemplate='%{text}', - text=freq_hover, - visible=True, - yaxis='y3' + x=df[time_column], y=df['avg_freq'], mode='lines', + name='CPU Frequency (MHz)', line=dict(color='#95E77E', width=2), + hovertemplate='%{text}', text=freq_hover, yaxis='y3' )) - - # Calculate test duration - duration = df[time_column].iloc[-1] / 60 # Convert seconds to minutes - + duration = df[time_column].iloc[-1] / 60 return traces, max(0.1, duration) except Exception as e: print(f"Error processing system data: {e}") - import traceback - traceback.print_exc() return [], None def generate_combined_report(results_dir): """Generate a combined HTML report for all tests.""" - - # Read system information system_info = read_system_info(os.path.join(results_dir, 'system_info.txt')) - # Check for CPU stress test marker + # Check for CPU stress markers and log cpu_stress_status = None cpu_stress_log_content = "" cpu_stress_marker_file = os.path.join(results_dir, 'cpu_stress_marker.txt') @@ -285,18 +251,16 @@ def generate_combined_report(results_dir): with open(cpu_stress_marker_file, 'r') as f: marker_content = f.read() if 'CPU_STRESS=RUNNING' in marker_content or 'PID=' in marker_content: - cpu_stress_status = 'success' # Assume success if marker exists + cpu_stress_status = 'success' elif 'STATUS=FAILED' in marker_content: cpu_stress_status = 'failed' - - # Read CPU stress log content + cpu_stress_log_file = os.path.join(results_dir, 'cpu_stress.log') if os.path.exists(cpu_stress_log_file): with open(cpu_stress_log_file, 'r') as f: - import html cpu_stress_log_content = html.escape(f.read()) - # Check for GPU stress test marker and read log + # Check for GPU stress markers and log gpu_stress_status = None gpu_stress_log_content = "" gpu_info_content = "" @@ -305,19 +269,15 @@ def generate_combined_report(results_dir): with open(gpu_stress_marker_file, 'r') as f: marker_content = f.read() if 'GPU_STRESS=RUNNING' in marker_content or 'PID=' in marker_content: - gpu_stress_status = 'success' # Assume success if marker exists + gpu_stress_status = 'success' elif 'STATUS=FAILED' in marker_content: gpu_stress_status = 'failed' - - # Read GPU stress log content + gpu_stress_log_file = os.path.join(results_dir, 'gpu_stress.log') if os.path.exists(gpu_stress_log_file): with open(gpu_stress_log_file, 'r') as f: - import html full_log = f.read() gpu_stress_log_content = html.escape(full_log) - - # Extract GPU info from glmark2 header if 'OpenGL Information' in full_log: lines = full_log.split('\n') in_header = False @@ -333,149 +293,75 @@ def generate_combined_report(results_dir): gpu_info_lines.append(line.strip()) gpu_info_content = html.escape('\n'.join(gpu_info_lines)) - # Read full system info for debug section system_info_text = "" system_info_file = os.path.join(results_dir, 'system_info.txt') if os.path.exists(system_info_file): with open(system_info_file, 'r') as f: - import html system_info_text = html.escape(f.read()) - - # Try new system data format first, fall back to old temperature format + traces = [] test_duration = None - # Check for new system_data.csv if os.path.exists(os.path.join(results_dir, 'system_data.csv')): traces, test_duration = generate_system_graphs(os.path.join(results_dir, 'system_data.csv')) - # Fall back to old temperature_data.csv elif os.path.exists(os.path.join(results_dir, 'temperature_data.csv')): - # Use old temperature-only graph for backward compatibility temp_trace, test_duration = generate_temperature_graph(os.path.join(results_dir, 'temperature_data.csv')) - if temp_trace: - traces = [temp_trace] - - # Generate separate GPU graph if available + if temp_trace: traces = [temp_trace] + gpu_graph_html = "" if os.path.exists(os.path.join(results_dir, 'gpu_data.csv')): gpu_trace, gpu_duration = generate_gpu_graph(os.path.join(results_dir, 'gpu_data.csv')) if gpu_trace: - # Create separate GPU figure - gpu_fig = go.Figure() - gpu_fig.add_trace(gpu_trace) + gpu_fig = go.Figure(data=[gpu_trace]) gpu_fig.update_layout( - title={ - 'text': 'GPU Performance', - 'x': 0.5, - 'xanchor': 'center', - 'font': {'size': 18, 'family': 'Arial, sans-serif'} - }, + title={'text': 'GPU Performance', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 18}}, xaxis=dict(title="Time (seconds)"), - yaxis=dict( - title="GPU Load (%)", - range=[0, 100], - titlefont=dict(color="#FF9800"), - tickfont=dict(color="#FF9800") - ), - height=400, - margin=dict(l=60, r=60, t=60, b=60), - plot_bgcolor='#f8f9fa', - paper_bgcolor='white', - hovermode='x' + yaxis=dict(title="GPU Load (%)", range=[0, 100], titlefont=dict(color="#FF9800"), tickfont=dict(color="#FF9800")), + height=400, margin=dict(l=60, r=60, t=60, b=60), plot_bgcolor='#f8f9fa', paper_bgcolor='white', hovermode='x' ) gpu_graph_html = gpu_fig.to_html(include_plotlyjs=False).replace('
', '
', 1) - - # Create figure with multiple y-axes + + # Dynamic Network Graph Generation + network_graph_html = "" + network_csv = os.path.join(results_dir, 'network_data.csv') + if os.path.exists(network_csv): + net_trace = generate_network_graph(network_csv) + if net_trace: + net_fig = go.Figure(data=[net_trace]) + net_fig.update_layout( + title={'text': 'Network Stability & Latency', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 18}}, + xaxis=dict(title="Time (seconds)"), + yaxis=dict(title="Round Trip Time (ms)", titlefont=dict(color="#00A8FF"), tickfont=dict(color="#00A8FF")), + height=400, margin=dict(l=60, r=60, t=60, b=60), plot_bgcolor='#f8f9fa', paper_bgcolor='white', hovermode='x' + ) + network_graph_html = net_fig.to_html(include_plotlyjs=False).replace('
', '
', 1) + fig = go.Figure() + for trace in traces: fig.add_trace(trace) - for trace in traces: - fig.add_trace(trace) - - # Update layout with multiple y-axes fig.update_layout( - title={ - 'text': 'System Monitoring', - 'x': 0.5, - 'xanchor': 'center', - 'font': {'size': 20, 'family': 'Arial, sans-serif'} - }, - xaxis=dict( - title="Time (seconds)" - ), - yaxis=dict( - title="Temperature (°C)", - titlefont=dict(color="#FF6B6B"), - tickfont=dict(color="#FF6B6B"), - side='left' - ), - yaxis2=dict( - titlefont=dict(color="#4ECDC4"), - tickfont=dict(color="#4ECDC4"), - anchor="x", - overlaying="y", - side="right", - range=[0, 100], # Fixed scale 0-100% - showticklabels=False, # Hide tick labels - showgrid=False, # Also hide grid for cleaner look - title=None # Remove title to save space - ), - yaxis3=dict( - titlefont=dict(color="#95E77E"), - tickfont=dict(color="#95E77E"), - anchor="x", - overlaying="y", - side="right", - showticklabels=False, # Hide tick labels - showgrid=False, # Also hide grid for cleaner look - title=None # Remove title to save space - ), - hovermode='x', - height=520, - showlegend=True, - legend=dict( - orientation="h", - yanchor="bottom", - y=-0.15, - xanchor="center", - x=0.5 - ), - margin=dict(l=60, r=60, t=60, b=100), - plot_bgcolor='#f8f9fa', - paper_bgcolor='white', + title={'text': 'System Monitoring', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 20}}, + xaxis=dict(title="Time (seconds)"), + yaxis=dict(title="Temperature (°C)", titlefont=dict(color="#FF6B6B"), tickfont=dict(color="#FF6B6B"), side='left'), + yaxis2=dict(anchor="x", overlaying="y", side="right", range=[0, 100], showticklabels=False, showgrid=False, title=None), + yaxis3=dict(anchor="x", overlaying="y", side="right", showticklabels=False, showgrid=False, title=None), + hovermode='x', height=520, showlegend=True, + legend=dict(orientation="h", yanchor="bottom", y=-0.15, xanchor="center", x=0.5), + margin=dict(l=60, r=60, t=60, b=100), plot_bgcolor='#f8f9fa', paper_bgcolor='white' ) + fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='#e0e0e0', rangeslider_visible=True, + rangeselector=dict(buttons=list([ + dict(count=1, label="1m", step="minute", stepmode="backward"), + dict(count=5, label="5m", step="minute", stepmode="backward"), + dict(count=30, label="30m", step="minute", stepmode="backward"), + dict(count=1, label="1h", step="hour", stepmode="backward"), + dict(step="all", label="All") + ]))) + fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='#e0e0e0') - # Update axes with range selector - fig.update_xaxes( - showgrid=True, - gridwidth=1, - gridcolor='#e0e0e0', - rangeslider_visible=True, - rangeselector=dict( - buttons=list([ - dict(count=1, label="1m", step="minute", stepmode="backward"), - dict(count=5, label="5m", step="minute", stepmode="backward"), - dict(count=30, label="30m", step="minute", stepmode="backward"), - dict(count=1, label="1h", step="hour", stepmode="backward"), - dict(step="all", label="All") - ]) - ) - ) + duration_str = f"{test_duration:.1f}" if test_duration else "0.0" - fig.update_yaxes( - showgrid=True, - gridwidth=1, - gridcolor='#e0e0e0' - ) - - # Format test duration - if test_duration: - duration_str = f"{test_duration:.1f}" - else: - duration_str = "0.0" - - # Create HTML content - html_content = f""" - + html_content = f""" @@ -483,382 +369,77 @@ def generate_combined_report(results_dir): {system_info['board_model']} Test Report

📊 {system_info['board_model']} Test Report

- {f'

{system_info["test_name"]}

' if system_info.get('test_name') else ''} - + {f'

{system_info["test_name"]}

' if system_info.get('test_name') else ''}
-
- Hostname: - {system_info['hostname']} -
-
- Kernel: - {system_info['kernel_version']} -
-
- Distribution: - {system_info['linux_distro']} -
+
Hostname:{system_info['hostname']}
+
Kernel:{system_info['kernel_version']}
+
Distribution:{system_info['linux_distro']}
-
-
-

📊 System Monitoring

-
-

- Click on legend items or use buttons below to show/hide metrics -

-
- - - - | - - -
-
-
-
-
+
- -

🎮 GPU Performance

- {'
✓ GPU Stress Test Executed Successfully
GPU stress test with glmark2 completed
' if gpu_stress_status == 'success' else '
✗ GPU Stress Test Failed
The GPU stress test encountered an error during execution
' if gpu_stress_status == 'failed' else '
GPU stress test was not run
'} - {gpu_graph_html if gpu_graph_html else ''} - {f'

GPU Information:

{gpu_info_content}
' if gpu_info_content else ''} + {f'
✓ GPU Stress Test Executed Successfully
' if gpu_stress_status == 'success' else ''} + {gpu_graph_html if gpu_graph_html else '
GPU stress test was not run
'}
- - -
-

⚡ Power Consumption

-
- Power consumption test not yet implemented -
-
- - -
-

💻 CPU Stress

- {'
✓ CPU Stress Test Executed Successfully
All CPU cores were loaded for the full test duration
' if cpu_stress_status == 'success' else '
✗ CPU Stress Test Failed
The stress test encountered an error during execution
' if cpu_stress_status == 'failed' else '
CPU stress test was not run
'} - {f'

Stress Test Log:

{cpu_stress_log_content}
' if cpu_stress_log_content else ''} -
- -

🌐 Network Performance

-
- Network performance test not yet implemented -
+ {network_graph_html if network_graph_html else '
Network metrics were not recorded or interface was offline
'}
- -
-

💾 Disk I/O Performance

-
- Disk I/O test not yet implemented -
+

💻 CPU Stress

+ {f'
✓ CPU Stress Test Executed Successfully
' if cpu_stress_status == 'success' else '
CPU stress test was not run
'} + {f'
{cpu_stress_log_content}
' if cpu_stress_log_content else ''}
- - -
-

📋 System Information (Debug)

-
{system_info_text}
-
- - +
- - -""" +""" - # Write HTML file - output_file = os.path.join(results_dir, 'report.html') - with open(output_file, 'w') as f: + with open(os.path.join(results_dir, 'report.html'), 'w') as f: f.write(html_content) - - # Silent - no output return True if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: python generate_combined_report.py ") - sys.exit(1) - - results_dir = sys.argv[1] - - if not os.path.exists(results_dir): - print(f"Error: Results directory '{results_dir}' not found") - sys.exit(1) - - success = generate_combined_report(results_dir) - sys.exit(0 if success else 1) \ No newline at end of file + if len(sys.argv) > 1: + generate_combined_report(sys.argv[1]) diff --git a/network/network_test.sh b/network/network_test.sh index 6c098b1..9d193d2 100755 --- a/network/network_test.sh +++ b/network/network_test.sh @@ -1,8 +1,61 @@ #!/bin/bash -# Network performance test (MOCK) -echo "Network performance test - NOT YET IMPLEMENTED" -echo "This is a placeholder for future network benchmarking functionality" -echo "Duration: $1 minutes" -echo "Results directory: $2" -exit 0 \ No newline at end of file +# Network Monitoring Stress-Test Module for Flipper One (RK3576) +# Usage: ./network_test.sh [interval_seconds] [output_directory] + +# Exit safely if the user terminates the script prematurely +trap "echo -e '\nStopping network monitoring...'; exit 0" SIGINT SIGTERM + +# 1. Parse Input Arguments with Defaults +INTERVAL=${1:-1} # Default to 1-second intervals +RESULTS_DIR=${2:-"results/debug"} # Default output directory if run standalone + +# Ensure the results directory exists +mkdir -p "$RESULTS_DIR" +OUTPUT_CSV="$RESULTS_DIR/network_data.csv" + +# 2. Write CSV Headers (Ensuring contract compatibility with Pandas) +if [ ! -f "$OUTPUT_CSV" ]; then + echo "seconds,Timestamp,Latency_ms,Interface_Status" > "$OUTPUT_CSV" +fi + +# 3. Detect Active Network Interface +INTERFACE=$(ip -o link show up | awk -F': ' '{print $2}' | grep -v 'lo' | head -n 1) +if [ -z "$INTERFACE" ]; then + INTERFACE="none" +fi + +echo "Starting network telemetry loop on interface: $INTERFACE" +echo "Logging metrics to: $OUTPUT_CSV" + +# 4. Telemetry Loop +SECONDS_COUNTER=0 + +while true; do + TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S") + + # Run a defensive 1-packet ping with a strict 1-second timeout + PING_OUT=$(ping -c 1 -W 1 8.8.8.8 2>/dev/null) + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + # Successfully connected! Extract raw millisecond value + LATENCY=$(echo "$PING_OUT" | grep 'bytes from' | awk -F'time=' '{print $2}' | cut -d' ' -f1) + + # Fallback if text parsing fails unexpectedly + if [ -z "$LATENCY" ]; then LATENCY="0.00"; fi + STATUS="${INTERFACE}_online" + else + # Host unreachable or interface dropped offline + LATENCY="0.00" + STATUS="${INTERFACE}_offline" + fi + + # 5. Append cleanly formatted row to CSV + echo "$SECONDS_COUNTER,$TIMESTAMP,$LATENCY,$STATUS" >> "$OUTPUT_CSV" + + # Rest and increment time step + sleep "$INTERVAL" + ((SECONDS_COUNTER+=INTERVAL)) +done +