Describe the bug
The update response carries the entire wuid_data subscriber-registration table on every poll. Since WUID_EXPIRY_TIME = 14400 (4 hours), that table grows with uptime and with how busy the system is, so the per-poll payload grows without any corresponding increase in useful information.
The web UI polls at 1 Hz (setInterval(do_update, 1000)), so a browser on a busy trunked system ends up parsing a large, mostly-unchanged JSON blob once or twice a second. The tab saturates a CPU core and outstanding requests accumulate faster than they drain. On the reporting user's desktop this degraded mouse and keyboard input across the whole OS, not just the tab.
The distinguishing symptom is that it gets worse the longer multi_rx has been running, and a restart temporarily resolves it.
Measurements
Live P25 Phase 1 simulcast, one control channel, 7 adjacent sites. Response composition at 44 minutes of uptime:
total response 164546 bytes
trunk_update 164090
wuid_data 159198 (771 entries) <-- 97% of the payload
frequency_data 1862 (12 entries)
channel_update 373
call_log 36
rx_update 39
Growth over 62 seconds on the same run:
| elapsed |
response bytes |
wuid entries |
| t+1s |
165,979 |
779 |
| t+31s |
167,153 |
785 |
| t+62s |
168,534 |
791 |
That is ~12 new registrations per minute, ~+1.2 KB per 30s. An earlier capture on the same system shortly after startup was 34,932 bytes total, versus 164,546 at 44 minutes.
Extrapolating (and this is an extrapolation, not a measurement): at ~12/min against a 4-hour expiry, this system would reach roughly 2,900 entries at equilibrium — on the order of 600 KB per response, delivered 1–2 times per second.
Client-side cost
update_sub_reg() iterates the whole table on every poll:
// main.js:991
if (!s || typeof s !== 'object' || !s.wuid_data || typeof s.wuid_data !== 'object') return;
// main.js:995
Object.values(s.wuid_data).forEach(function (obj) { ... });
// main.js:1188
update_sub_reg(d[nac]['wuid_data'], d[nac]['sysid']);
Notably, legacy-main.js does not reference wuid_data at all — so the legacy UI still receives the payload but never parses or renders it, which matches the observed difference in CPU cost between the two UIs.
Measured browser behaviour with the new UI: browser-process CPU stepped to ~95% of one core about 2.5 minutes after tab load and held there for 14 consecutive 30-second intervals, with all memory pools flat (browser RSS 412→427 MB over 9 min, NetworkService flat, renderer noisy with no trend). Sustained CPU with no memory growth is consistent with repeatedly parsing and re-rendering a large payload that is then discarded, rather than with anything accumulating.
Suggested fix
Any of these would address it; the first is probably the smallest change:
- Do not include
wuid_data in the routine update response. Serve it via its own command, fetched only when the subscriber-registration panel is actually open.
- Send deltas — new/changed/expired registrations since the client's last update — rather than the full table each time.
- Cap or paginate what is sent, since a UI panel cannot usefully display thousands of rows anyway.
There is currently no configuration option to disable registration tracking, so a user on a busy system has no workaround short of using the legacy UI or restarting multi_rx periodically.
System Information
- OP25: master
28f2c40
- Server: Raspbian 12 (bookworm), Raspberry Pi 4 Model B Rev 1.1, GNU Radio 3.10.5.1, Python 3.11.2
- Browser: Chrome on Windows 11, separate machine, reaching the host over a VPN
Relationship to #299
Separate defect, same victim. #299 is an unbounded get_full_config request storm caused by a missing site_alias; fixing that removed the extra request volume and the ERR_INSUFFICIENT_RESOURCES failures, and NetworkService memory went flat — but the sustained CPU remained, which is what led here. Both are reachable from a stock configuration.
Describe the bug
The
updateresponse carries the entirewuid_datasubscriber-registration table on every poll. SinceWUID_EXPIRY_TIME = 14400(4 hours), that table grows with uptime and with how busy the system is, so the per-poll payload grows without any corresponding increase in useful information.The web UI polls at 1 Hz (
setInterval(do_update, 1000)), so a browser on a busy trunked system ends up parsing a large, mostly-unchanged JSON blob once or twice a second. The tab saturates a CPU core and outstanding requests accumulate faster than they drain. On the reporting user's desktop this degraded mouse and keyboard input across the whole OS, not just the tab.The distinguishing symptom is that it gets worse the longer
multi_rxhas been running, and a restart temporarily resolves it.Measurements
Live P25 Phase 1 simulcast, one control channel, 7 adjacent sites. Response composition at 44 minutes of uptime:
Growth over 62 seconds on the same run:
That is ~12 new registrations per minute, ~+1.2 KB per 30s. An earlier capture on the same system shortly after startup was 34,932 bytes total, versus 164,546 at 44 minutes.
Extrapolating (and this is an extrapolation, not a measurement): at ~12/min against a 4-hour expiry, this system would reach roughly 2,900 entries at equilibrium — on the order of 600 KB per response, delivered 1–2 times per second.
Client-side cost
update_sub_reg()iterates the whole table on every poll:Notably,
legacy-main.jsdoes not referencewuid_dataat all — so the legacy UI still receives the payload but never parses or renders it, which matches the observed difference in CPU cost between the two UIs.Measured browser behaviour with the new UI: browser-process CPU stepped to ~95% of one core about 2.5 minutes after tab load and held there for 14 consecutive 30-second intervals, with all memory pools flat (browser RSS 412→427 MB over 9 min, NetworkService flat, renderer noisy with no trend). Sustained CPU with no memory growth is consistent with repeatedly parsing and re-rendering a large payload that is then discarded, rather than with anything accumulating.
Suggested fix
Any of these would address it; the first is probably the smallest change:
wuid_datain the routineupdateresponse. Serve it via its own command, fetched only when the subscriber-registration panel is actually open.There is currently no configuration option to disable registration tracking, so a user on a busy system has no workaround short of using the legacy UI or restarting
multi_rxperiodically.System Information
28f2c40Relationship to #299
Separate defect, same victim. #299 is an unbounded
get_full_configrequest storm caused by a missingsite_alias; fixing that removed the extra request volume and theERR_INSUFFICIENT_RESOURCESfailures, and NetworkService memory went flat — but the sustained CPU remained, which is what led here. Both are reachable from a stock configuration.