1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-26 01:35:28 +03:00

Suggestions per Coderabbit

This commit is contained in:
Mark Haslinghuis 2025-06-12 19:09:37 +02:00
parent 07ca99ad0d
commit 12d3ddfcaa
8 changed files with 258 additions and 184 deletions

View file

@ -132,7 +132,8 @@ function startProcess() {
console.log(`Libraries: jQuery - ${$.fn.jquery}, three.js - ${THREE.REVISION}`);
// Check if this is the first visit
if (getConfig("firstRun").firstRun === undefined) {
const firstRunCfg = getConfig("firstRun") ?? {};
if (firstRunCfg.firstRun === undefined) {
setConfig({ firstRun: true });
import("./tabs/static_tab.js").then(({ staticTab }) => {
staticTab.initialize("options", () => {

View file

@ -51,11 +51,14 @@ const MSP = {
message_buffer: null,
message_buffer_uint8_view: null,
message_checksum: 0,
crcError: false,
callbacks: [],
packet_error: 0,
unsupported: 0,
TIMEOUT: 1000,
last_received_timestamp: null,
listeners: [],
@ -65,8 +68,6 @@ const MSP = {
cli_output: [],
cli_callback: null,
TIMEOUT: 1000,
read(readInfo) {
if (CONFIGURATOR.virtualMode) {
return;
@ -377,7 +378,6 @@ const MSP = {
return false;
}
// Create unique key combining code and data
const requestExists = this.callbacks.some((instance) => instance.code === code);
const bufferOut = code <= 254 ? this.encode_message_v1(code, data) : this.encode_message_v2(code, data);
@ -390,7 +390,6 @@ const MSP = {
start: performance.now(),
};
// Track only the first outstanding request for a given key
if (!requestExists) {
obj.timer = setTimeout(() => {
console.warn(
@ -404,9 +403,7 @@ const MSP = {
// We should probably give up connection if the request takes too long ?
if (executionTime > 5000) {
console.warn(
`MSP: data request took too long: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} TIMEOUT: ${
this.timeout
} EXECUTION TIME: ${executionTime}ms`,
`MSP: data request took too long: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} EXECUTION TIME: ${executionTime}ms`,
);
}

View file

@ -48,62 +48,82 @@ import('./src/js/msp_debug_tools.js');
**Start monitoring:**
```javascript
MSPTestRunner.startQuickMonitor();
MSPDebug.startMonitoring();
```
**Show visual dashboard:**
```javascript
MSPTestRunner.showDashboard();
MSPDebug.show();
// Or press Ctrl+Shift+M
```
**Quick health check:**
**Quick test of alert system:**
```javascript
MSPTestRunner.quickHealthCheck();
MSPDebug.testAlerts();
```
**Run stress tests:**
```javascript
// Run specific test
MSPTestRunner.runTest('queue-flooding');
MSPDebug.runTests();
// Run full test suite
MSPTestRunner.runFullSuite();
// Run complete stress test suite with detailed console output
MSPDebug.runFullSuite();
// Run individual test by name
MSPDebug.runTest('queue-flooding');
// Quick health check
MSPDebug.quickHealthCheck();
// Run stress scenario
MSPDebug.stressScenario('high-frequency');
```
## Available Commands
The MSP Debug Tools provide two APIs:
- **MSPDebug**: Modern, simplified API (recommended)
- **MSPTestRunner**: Legacy API with additional methods
Both APIs provide the same core functionality. Use `MSPDebug` for new code.
### Monitoring Commands
| Command | Description |
|---------|-------------|
| `MSPTestRunner.startQuickMonitor()` | Start monitoring with console output |
| `MSPTestRunner.stopMonitor()` | Stop monitoring |
| `MSPTestRunner.getStatus()` | Get current MSP status |
| `MSPTestRunner.analyzeQueue()` | Analyze current queue contents |
| `MSPDebug.startMonitoring()` | Start monitoring with console output |
| `MSPDebug.stopMonitoring()` | Stop monitoring |
| `MSPDebug.getStatus()` | Get current MSP status |
| `MSPDebug.monitor.getStatus()` | Get current MSP status (alternative) |
| `MSPDebug.analyze()` | Analyze current queue contents |
### Testing Commands
| Command | Description |
|---------|-------------|
| `MSPTestRunner.runTest('test-name')` | Run specific stress test |
| `MSPTestRunner.runFullSuite()` | Run complete test suite |
| `MSPTestRunner.quickHealthCheck()` | Quick health validation |
| `MSPDebug.runTests()` | Run stress test suite |
| `MSPDebug.runFullSuite()` | Run complete stress test suite with detailed output |
| `MSPDebug.runTest('test-name')` | Run a specific test by name |
| `MSPDebug.quickHealthCheck()` | Run a quick MSP health check |
| `MSPDebug.stressScenario('scenario')` | Run specific stress test scenario |
| `MSPDebug.testAlerts()` | Test alert system |
| `MSPDebug.triggerTestAlerts()` | Manually trigger alerts |
### Stress Scenarios
### Alert Testing
| Command | Description |
|---------|-------------|
| `MSPTestRunner.stressScenario('high-frequency')` | High frequency request test |
| `MSPTestRunner.stressScenario('queue-overflow')` | Queue overflow handling test |
| `MSPTestRunner.stressScenario('mixed-load')` | Mixed request types test |
| `MSPDebug.setTestThresholds()` | Lower thresholds for easier testing |
| `MSPDebug.setNormalThresholds()` | Restore normal thresholds |
| `MSPDebug.testAlerts()` | Complete alert system test |
### Visual Tools
| Command | Description |
|---------|-------------|
| `MSPTestRunner.showDashboard()` | Show visual debug dashboard |
| `MSPTestRunner.generateReport()` | Generate and download report |
| `MSPDebug.show()` | Show visual debug dashboard |
| `MSPDebug.report()` | Generate and download report |
## Dashboard Interactions
@ -139,15 +159,27 @@ The visual dashboard includes smart interaction handling to ensure a smooth user
## Available Tests
1. **Queue Flooding** - Tests queue limits with many simultaneous requests
2. **Rapid Fire Requests** - Tests high-frequency request handling
3. **Duplicate Request Handling** - Validates duplicate request management
4. **Timeout Recovery** - Tests timeout and retry mechanisms
5. **Memory Leak Detection** - Checks for proper cleanup of completed requests
6. **Concurrent Mixed Requests** - Tests various request types simultaneously
7. **Queue Overflow Handling** - Tests behavior when queue reaches capacity
8. **Connection Disruption** - Simulates connection issues
9. **Performance Under Load** - Tests sustained load performance
### Individual Test Names (for `runTest`)
1. **queue-flooding** - Tests queue limits with many simultaneous requests
2. **rapid-fire** - Tests high-frequency request handling
3. **duplicates** - Validates duplicate request management
4. **timeout-recovery** - Tests timeout and retry mechanisms
5. **memory-leaks** - Checks for proper cleanup of completed requests
6. **concurrent-mixed** - Tests various request types simultaneously
7. **queue-overflow** - Tests behavior when queue reaches capacity
8. **connection-disruption** - Simulates connection issues
9. **performance-load** - Tests sustained load performance
### Stress Scenarios (for `stressScenario`)
- **high-frequency** - High-frequency requests every 10ms for 5 seconds
- **queue-overflow** - Floods queue beyond capacity
- **mixed-load** - Various request types and sizes
### Full Test Suite
The `runFullSuite()` command runs all individual tests in sequence with detailed console output and generates a comprehensive report.
## Monitoring Metrics
@ -190,37 +222,50 @@ The visual dashboard provides:
### Development Testing
```javascript
// Start monitoring during development
MSPTestRunner.startQuickMonitor();
MSPDebug.startMonitoring();
// Run quick health check after changes
MSPTestRunner.quickHealthCheck();
// Quick health check
MSPDebug.quickHealthCheck();
// Test specific functionality
MSPTestRunner.runTest('timeout-recovery');
// Test the alert system
MSPDebug.testAlerts();
// Show visual dashboard
MSPDebug.show();
```
### Performance Analysis
```javascript
// Show dashboard for visual monitoring
MSPTestRunner.showDashboard();
MSPDebug.show();
// Run performance stress test
MSPTestRunner.stressScenario('high-frequency');
// Run complete stress tests with detailed output
MSPDebug.runFullSuite();
// Test specific scenarios
MSPDebug.stressScenario('high-frequency');
// Generate detailed report
MSPTestRunner.generateReport();
MSPDebug.report();
```
### Issue Debugging
```javascript
// Get current status
MSPDebug.getStatus();
// Analyze current queue state
MSPTestRunner.analyzeQueue();
MSPDebug.analyze();
// Check for memory leaks
MSPTestRunner.runTest('memory-leaks');
// Test specific problematic scenario
MSPDebug.runTest('memory-leaks');
// Run full diagnostic
MSPTestRunner.runFullSuite();
// Check for alerts with low thresholds
MSPDebug.setTestThresholds();
MSPDebug.triggerTestAlerts();
// Generate diagnostic report
MSPDebug.report();
```
## Integration with Existing Code
@ -232,6 +277,41 @@ The debug tools are designed to be non-intrusive:
- No performance impact when not actively monitoring
- Original MSP behavior is preserved
### Auto-loading
The debug tools auto-load when `msp_debug_tools.js` is imported. They detect the presence of the global MSP object and initialize automatically.
### Keyboard Shortcuts
- `Ctrl+Shift+M`: Toggle debug dashboard
## Implementation Status
### ✅ Current Features
#### Alert System
- Enhanced debug logging with reduced console noise
- Test infrastructure: `triggerTestAlerts()`, `setTestThresholds()`, `setNormalThresholds()`
- Visual alert display in dashboard
- Smart threshold management for testing
#### Interactive Dashboard
- Smart update pausing during user interactions
- Clickable test results with detailed information
- Enhanced interaction handling for all UI elements
- Visual feedback with updates pause indicator
#### Complete API
- Dual API support: `MSPDebug` (modern) and `MSPTestRunner` (legacy)
- All documented commands implemented and verified
- Comprehensive testing methods (9 test types + 3 stress scenarios)
- Real-time monitoring with alert detection
### ✅ Verified Working
- Alert system triggers correctly when thresholds exceeded
- Dashboard displays alerts visually without update interference
- Test results provide comprehensive detailed information
- All API commands function as documented
- Auto-loading works in development environment
## File Structure
```

View file

@ -70,6 +70,9 @@ export class MSPDebugDashboard {
<!-- Alerts Section -->
<div class="alerts-section">
<h4>🚨 Alerts</h4>
<div class="alerts-header">
<button id="clear-alerts" style="float: right; padding: 2px 6px; font-size: 10px; background: #666; color: white; border: none; border-radius: 2px; cursor: pointer;">Clear Alerts</button>
</div>
<div id="alerts-container" class="alerts-container">
<div class="no-alerts">No active alerts</div>
</div>
@ -88,7 +91,7 @@ export class MSPDebugDashboard {
<!-- Live Chart -->
<div class="chart-section">
<h4>📈 Live Metrics</h4>
<canvas id="msp-metrics-chart" width="400" height="200"></canvas>
<canvas id="msp-metrics-chart"></canvas>
</div>
<!-- Request Details -->
@ -304,6 +307,11 @@ export class MSPDebugDashboard {
border-bottom: none;
}
.queue-item-empty {
opacity: 0.3;
font-style: italic;
}
.test-results {
background: #2a2a2a;
padding: 10px;
@ -344,6 +352,7 @@ export class MSPDebugDashboard {
height: 150px;
background: #2a2a2a;
border-radius: 3px;
display: block;
}
.test-result-item {
@ -421,6 +430,8 @@ export class MSPDebugDashboard {
this.runStressTest();
} else if (e.target.id === "msp-clear-metrics") {
this.clearMetrics();
} else if (e.target.id === "clear-alerts") {
this.clearAlerts();
} else if (e.target.id === "msp-close-dashboard") {
this.hide();
} else if (e.target.id === "analyze-queue") {
@ -444,6 +455,14 @@ export class MSPDebugDashboard {
this.toggle();
}
});
// Handle window resize to redraw canvas with correct dimensions
window.addEventListener("resize", () => {
if (this.isVisible) {
// Delay redraw to ensure layout is updated
setTimeout(() => this.drawChart(), 100);
}
});
}
/**
@ -606,6 +625,13 @@ export class MSPDebugDashboard {
this.updateDisplay();
}
/**
* Clear alerts only
*/
clearAlerts() {
mspQueueMonitor.clearAlerts();
}
/**
* Update display with current status
*/
@ -752,23 +778,37 @@ export class MSPDebugDashboard {
const container = document.getElementById("queue-contents");
if (!container) return;
if (!queueContents || queueContents.length === 0) {
container.innerHTML = '<div style="text-align: center; color: #888; padding: 20px;">Queue is empty</div>';
return;
// Always show exactly 5 slots to prevent layout shifts
const maxSlots = 5;
const items = queueContents || [];
const slotsHtml = [];
// Add actual queue items
for (let i = 0; i < maxSlots; i++) {
if (i < items.length) {
const item = items[i];
slotsHtml.push(`
<div class="queue-item">
<span>Code: ${item.code}</span>
<span>Age: ${Math.round(item.age)}ms</span>
<span>Attempts: ${item.attempts}</span>
<span style="color: ${item.hasTimer ? "#00ff00" : "#ff4444"}">${item.hasTimer ? "✓" : "✗"}</span>
</div>
`);
} else {
// Add empty slot placeholder
slotsHtml.push(`
<div class="queue-item queue-item-empty">
<span style="color: #555;"></span>
<span style="color: #555;"></span>
<span style="color: #555;"></span>
<span style="color: #555;"></span>
</div>
`);
}
}
container.innerHTML = queueContents
.map(
(item) => `
<div class="queue-item">
<span>Code: ${item.code}</span>
<span>Age: ${Math.round(item.age)}ms</span>
<span>Attempts: ${item.attempts}</span>
<span style="color: ${item.hasTimer ? "#00ff00" : "#ff4444"}">${item.hasTimer ? "✓" : "✗"}</span>
</div>
`,
)
.join("");
container.innerHTML = slotsHtml.join("");
}
/**
@ -805,8 +845,28 @@ export class MSPDebugDashboard {
if (!canvas) return;
const ctx = canvas.getContext("2d");
const width = canvas.width;
const height = canvas.height;
// Get the display size (CSS pixels)
const rect = canvas.getBoundingClientRect();
const displayWidth = rect.width;
const displayHeight = rect.height;
// Get the device pixel ratio, falling back to 1
const devicePixelRatio = window.devicePixelRatio || 1;
// Set the internal canvas size to actual pixels for Hi-DPI displays
canvas.width = displayWidth * devicePixelRatio;
canvas.height = displayHeight * devicePixelRatio;
// Scale the canvas back down using CSS
canvas.style.width = `${displayWidth }px`;
canvas.style.height = `${displayHeight }px`;
// Scale the drawing context so everything draws at the correct size
ctx.scale(devicePixelRatio, devicePixelRatio);
const width = displayWidth;
const height = displayHeight;
// Clear canvas
ctx.fillStyle = "#2a2a2a";
@ -834,7 +894,7 @@ export class MSPDebugDashboard {
ctx.stroke();
// Draw labels
// Draw labels with proper font scaling
ctx.fillStyle = "#ffffff";
ctx.font = "10px monospace";
ctx.fillText("Queue Size", 5, 15);
@ -1010,10 +1070,17 @@ window.MSPDebug = {
startMonitoring: () => mspQueueMonitor.startMonitoring(),
stopMonitoring: () => mspQueueMonitor.stopMonitoring(),
runTests: () => mspStressTest.runStressTestSuite(),
runFullSuite: () => mspStressTest.runStressTestSuite(),
analyze: () => mspQueueMonitor.analyzeQueue(),
report: () => mspQueueMonitor.generateReport(),
showTestDetails: (index) => mspDebugDashboard.showTestDetails(index),
// Individual test methods
runTest: (testName) => mspStressTest.runSpecificTest(testName),
quickHealthCheck: () => window.MSPTestRunner.quickHealthCheck(),
stressScenario: (scenario) => window.MSPTestRunner.stressScenario(scenario),
getStatus: () => mspQueueMonitor.getStatus(),
// Alert testing methods
triggerTestAlerts: () => mspQueueMonitor.triggerTestAlerts(),
setTestThresholds: () => mspQueueMonitor.setTestThresholds(),

View file

@ -29,7 +29,7 @@ export class MSPQueueMonitor {
};
this.thresholds = {
maxQueueSize: 40, // Alert when queue > 80% of MAX_QUEUE_SIZE
maxQueueSize: Math.floor((this.msp.MAX_QUEUE_SIZE || 50) * 0.8), // Alert when queue > 80% of MAX_QUEUE_SIZE
maxAvgResponseTime: 2000, // Alert when avg response > 2s
maxTimeoutRate: 0.1, // Alert when timeout rate > 10%
memoryLeakThreshold: 100, // Alert when callbacks grow beyond expected
@ -83,7 +83,7 @@ export class MSPQueueMonitor {
this.metrics.requestsByCode.set(code, count + 1);
// Check for queue size peaks
const currentQueueSize = this.msp.callbacks.length;
const currentQueueSize = this.msp.callbacks?.length ?? 0;
if (currentQueueSize > this.metrics.queuePeakSize) {
this.metrics.queuePeakSize = currentQueueSize;
}
@ -339,7 +339,7 @@ export class MSPQueueMonitor {
}
/**
* Reset metrics
* Reset metrics only (keep alerts intact)
*/
resetMetrics() {
this.metrics = {
@ -356,12 +356,30 @@ export class MSPQueueMonitor {
errorsByType: new Map(),
};
// Note: Alerts are NOT reset here - they should only be cleared when conditions are no longer true
// or when explicitly requested via clearAlerts() method
}
/**
* Clear alerts (separate from metrics)
*/
clearAlerts() {
console.log("🔄 Clearing all alerts...");
this.alerts = {
queueFull: false,
highTimeout: false,
slowResponses: false,
memoryLeak: false,
};
this._notifyListeners();
}
/**
* Reset both metrics and alerts (complete reset)
*/
resetAll() {
this.resetMetrics();
this.clearAlerts();
}
/**
@ -516,7 +534,7 @@ export class MSPQueueMonitor {
setNormalThresholds() {
console.log("🔧 Resetting to normal thresholds...");
this.thresholds = {
maxQueueSize: 40, // Alert when queue > 80% of MAX_QUEUE_SIZE
maxQueueSize: Math.floor((this.msp.MAX_QUEUE_SIZE || 50) * 0.8), // Alert when queue > 80% of MAX_QUEUE_SIZE
maxAvgResponseTime: 2000, // Alert when avg response > 2s
maxTimeoutRate: 0.1, // Alert when timeout rate > 10%
memoryLeakThreshold: 100, // Alert when callbacks grow beyond expected

View file

@ -60,7 +60,7 @@ export class MSPStressTest {
try {
console.log(`\n📋 Running: ${testDef.name}`);
this.currentTest = testDef.name;
this.monitor.resetMetrics();
this.monitor.resetAll(); // Reset both metrics and alerts for clean test start
const startTime = performance.now();
const result = await testDef.test();
@ -547,7 +547,7 @@ export class MSPStressTest {
console.log(`🧪 Running specific test: ${testName}`);
this.monitor.startMonitoring(100);
this.monitor.resetMetrics();
this.monitor.resetAll(); // Reset both metrics and alerts for clean test start
try {
const result = await testMethod();

View file

@ -20,13 +20,22 @@ import { mspStressTest } from "./msp_stress_test.js";
import { mspDebugDashboard } from "./msp_debug_dashboard.js";
export const MSPTestRunner = {
// Store the listener function so it can be removed later
_quickMonitorListener: null,
/**
* Start quick monitoring with console output
*/
startQuickMonitor() {
console.log("🚀 Starting MSP Quick Monitor...");
mspQueueMonitor.addListener((status) => {
// Remove any existing listener first
if (this._quickMonitorListener) {
mspQueueMonitor.removeListener(this._quickMonitorListener);
}
// Define the listener function so it can be referenced for removal
this._quickMonitorListener = (status) => {
if (status.alerts && Object.values(status.alerts).some((alert) => alert)) {
console.warn("🚨 MSP Alert:", status.alerts);
}
@ -37,8 +46,9 @@ export const MSPTestRunner = {
`📊 MSP Status: Queue=${status.currentQueueSize}/${status.maxQueueSize}, Requests=${status.metrics.totalRequests}, AvgTime=${Math.round(status.metrics.avgResponseTime)}ms`,
);
}
});
};
mspQueueMonitor.addListener(this._quickMonitorListener);
mspQueueMonitor.startMonitoring(1000);
console.log("✅ Quick monitor started. Use MSPTestRunner.stopMonitor() to stop.");
@ -54,6 +64,13 @@ export const MSPTestRunner = {
*/
stopMonitor() {
mspQueueMonitor.stopMonitoring();
// Remove the listener to prevent duplicate logs
if (this._quickMonitorListener) {
mspQueueMonitor.removeListener(this._quickMonitorListener);
this._quickMonitorListener = null;
}
console.log("⏹️ MSP Monitor stopped");
},

View file

@ -1,106 +0,0 @@
/**
* Alert System Test Script
* Run this in the browser console to test the MSP debug alert system
*/
console.log("🧪 Starting MSP Alert System Test...");
// Test function to check alert system
async function testAlertSystem() {
console.log("1. Checking if MSPDebug is available...");
if (typeof window.MSPDebug === "undefined") {
console.error("❌ MSPDebug not found! Debug tools may not be loaded.");
return;
}
console.log("✅ MSPDebug is available");
console.log("2. Checking dashboard...");
const dashboard = window.MSPDebug.dashboard;
if (!dashboard) {
console.error("❌ Dashboard not found!");
return;
}
console.log("✅ Dashboard is available");
console.log("3. Checking monitor...");
const monitor = window.MSPDebug.monitor;
if (!monitor) {
console.error("❌ Monitor not found!");
return;
}
console.log("✅ Monitor is available");
console.log("4. Showing dashboard...");
dashboard.show();
console.log("5. Starting monitoring...");
monitor.startMonitoring(500);
console.log("6. Checking alert container exists...");
const alertContainer = document.getElementById("alerts-container");
if (!alertContainer) {
console.error("❌ Alert container not found in DOM!");
return;
}
console.log("✅ Alert container found:", alertContainer);
console.log("7. Getting current status...");
const status = monitor.getStatus();
console.log("Current status:", status);
console.log("Current alerts:", status.alerts);
console.log("8. Testing alert display directly...");
dashboard.updateAlerts({
queueFull: true,
highTimeout: false,
slowResponses: true,
memoryLeak: false,
});
console.log("9. Checking alert container content after manual update...");
console.log("Alert container HTML:", alertContainer.innerHTML);
console.log("10. Triggering test alerts...");
const testAlerts = monitor.triggerTestAlerts();
console.log("Test alerts triggered:", testAlerts);
console.log("11. Waiting 2 seconds and checking again...");
setTimeout(() => {
const newStatus = monitor.getStatus();
console.log("Status after test alerts:", newStatus);
console.log("Alerts after test:", newStatus.alerts);
console.log("Alert container HTML after test:", alertContainer.innerHTML);
// Test the complete update flow
console.log("12. Testing complete update flow...");
dashboard.updateDisplay(newStatus);
console.log("Alert container HTML after updateDisplay:", alertContainer.innerHTML);
console.log("🏁 Alert system test complete!");
}, 2000);
}
// Run the test
testAlertSystem();
// Also provide manual test functions
window.testAlertSystem = testAlertSystem;
window.checkAlerts = () => {
const container = document.getElementById("alerts-container");
console.log("Alert container:", container);
console.log("Alert container HTML:", container?.innerHTML);
const status = window.MSPDebug?.monitor?.getStatus();
console.log("Current alerts:", status?.alerts);
};
window.manualTestAlert = () => {
console.log("Testing alert display manually...");
const dashboard = window.MSPDebug.dashboard;
dashboard.updateAlerts({
queueFull: true,
highTimeout: true,
slowResponses: false,
memoryLeak: true,
});
console.log("Manual test alerts set");
};