fix: spectrum analysis crash on long logs (Freq/PSD vs Throttle/RPM)#923
Conversation
Freq/PSD vs Throttle and vs RPM threw 'Cannot read properties of undefined (reading 0)' and failed to open for long logs (betaflight#922). Spectrum sample buffers were sized from a _blackBoxRate estimate, but the real frame count comes from an index-based chunk range decoupled from that rate. On long logs the actual frame count exceeds the estimate, the vsValues buffers overflow, and the out-of-range reads return undefined -> NaN, poisoning minValue/maxValue. The existing minValue > maxValue guard does not catch NaN, so the bin index became Math.round(NaN) = NaN and matrixFftOutput[NaN][0] threw. - Size buffers from the real frame count (sum of chunk.frames.length) in _getFlightSamplesFreqVsX and _getFlightSamplesFreq. The latter uses Math.max(frameCount, fftBufferSize) to keep the 2048 FFT floor and zero-padding for short logs. - NaN-aware range guard: reject non-finite and equal bounds, fall back to a safe 0..100 range (also covers constant-VS divide-by-zero). - Clamp the VS bin index to [0, NUM_VS_BINS-1] in both VsX functions. - Empty-window guards in dataLoadPSD and _dataLoadPowerSpectralDensityVsX.
WalkthroughThis PR fixes spectrum analysis failures (Freq. vs throttle, Freq. vs RPM) reported in issue ChangesSpectrum Analysis Bounds and Sizing Repairs
🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
Preview URL: https://pr923.betaflight-blackbox.pages.dev |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/graph_spectrum_calc.js (1)
686-691:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSame rate-estimate undersizing exists here and can mis-size on long logs.
piderror/setpointare sized from a_blackBoxRateestimate (300 * _blackBoxRate) rather than the actual frame count. On long logs where the real frame count exceeds the estimate, writes past the typed-array length are silently dropped whilesamplesCountkeeps incrementing, so the returnedcountovershoots the sliced buffer length and the consumer loop readsundefined→NaNsetpoints. This is the same class of bug just fixed in_getFlightSamplesFreq/_getFlightSamplesFreqVsX, and is noted as a follow-up in the PR description.🛡️ Proposed fix to size from actual frame count
const FIELD_SETPOINT_INDEX = this._flightLog.getMainFieldIndexByName( `setpoint[${axisIndex}]`, ); - const piderror = new Int16Array( - (MAX_ANALYSER_LENGTH / (1000 * 1000)) * this._blackBoxRate, - ); - const setpoint = new Int16Array( - (MAX_ANALYSER_LENGTH / (1000 * 1000)) * this._blackBoxRate, - ); + let frameCount = 0; + for (const chunk of allChunks) { + frameCount += chunk.frames.length; + } + const piderror = new Int16Array(frameCount); + const setpoint = new Int16Array(frameCount);Want me to open a follow-up issue to track this PID-error sizing fix?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/graph_spectrum_calc.js` around lines 686 - 691, The piderror and setpoint typed arrays are sized from the _blackBoxRate estimate which can undersize them; instead size them from the actual frame count used when populating samples (use the same actual frame/sample count variable used elsewhere — e.g. samplesCount, totalFrames or frameCount — rather than (MAX_ANALYSER_LENGTH / (1000 * 1000)) * this._blackBoxRate). Update the allocation for piderror and setpoint to use that actual frame count (or Math.min(actualFrameCount, MAX_ANALYSER_LENGTH) as a safety cap) and ensure any returned count is clamped to the arrays' length so writes cannot silently drop and consumers never read beyond the buffer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/graph_spectrum_calc.js`:
- Around line 686-691: The piderror and setpoint typed arrays are sized from the
_blackBoxRate estimate which can undersize them; instead size them from the
actual frame count used when populating samples (use the same actual
frame/sample count variable used elsewhere — e.g. samplesCount, totalFrames or
frameCount — rather than (MAX_ANALYSER_LENGTH / (1000 * 1000)) *
this._blackBoxRate). Update the allocation for piderror and setpoint to use that
actual frame count (or Math.min(actualFrameCount, MAX_ANALYSER_LENGTH) as a
safety cap) and ensure any returned count is clamped to the arrays' length so
writes cannot silently drop and consumers never read beyond the buffer.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f2c1226f-5b41-4a57-b335-76ba57624c09
📒 Files selected for processing (1)
src/graph_spectrum_calc.js


fix: spectrum analysis crash on long logs (Freq/PSD vs Throttle/RPM)
Summary
Fixes #922 — "Freq. vs Throttle" / "Freq. vs RPM" (and the PSD variants) threw
TypeError: Cannot read properties of undefined (reading '0')and failed to open forlong (e.g. 15-minute) logs, while plain Frequency and v3.6.0 worked.
Root cause: the spectrum sample buffers in
graph_spectrum_calc.jswere sized from a_blackBoxRateestimate ((MAX_ANALYSER_LENGTH / 1e6) * _blackBoxRate), but the realframe count comes from an index-based chunk range that is decoupled from that rate. On a
long log the actual frame count exceeds the estimate, so the per-field
vsValuesbuffersoverflow. The out-of-range reads in the min/max pass returned
undefined→NaN, whichpoisoned
minValue/maxValue; the existingminValue > maxValueguard does not catchNaN, so the bin index becameMath.round(NaN) = NaNandmatrixFftOutput[NaN][0]threw.Changes (
src/graph_spectrum_calc.js)chunk.frames.length) instead of therate estimate, in
_getFlightSamplesFreqVsXand_getFlightSamplesFreq. This removesthe overflow at its source.
_getFlightSamplesFrequsesMath.max(frameCount, fftBufferSize)so the
MIN_SPECTRUM_SAMPLES_COUNT(2048) FFT floor and zero-padding are preserved forshort logs.
bounds and the equal-bounds case (
!Number.isFinite(...) || minValue >= maxValue),falling back to a safe
0..100range. This also covers a constant-VS log(
maxValue === minValue) that would otherwise divide by zero.[0, NUM_VS_BINS - 1]in_dataLoadFrequencyVsXand_dataLoadPowerSpectralDensityVsXas a second line of defense against an out-of-rangematrix-row dereference.
dataLoadPSDwhen there are no samples(
getNearPower2Value(0)→FFTComplex(0)would throw), and an empty-matrix fallback in_dataLoadPowerSpectralDensityVsXsofftOutputis neverundefined.Test plan
No automated test suite exists for this module (manual testing per the project setup).
30-05-first-long-flight.zip); confirm Freq vsThrottle, Freq vs RPM, PSD vs Throttle and PSD vs RPM all render with no console error.
0..100axis without crashing.npm run lintpasses;prettier --checkclean.Notes
_getFlightSamplesPidErrorVsSetpointstill uses the samerate-estimate sizing. It writes by index and slices to the sample count, so it cannot
crash like the heat-map paths, but it shares the latent undersize and is worth a separate fix.
Summary by CodeRabbit
Release Notes