Repository navigation
fix(diskio): alert on the bitrate, and decorate the field the WebUI reads - #3684
Merged
Merged
Conversation
…eads The rx/tx thresholds describe a bitrate, but update_views measured them against read_bytes / write_bytes -- the lifetime counters psutil returns. Those only grow, so on any machine with uptime a configured threshold latched on within seconds and never came back down, whatever the disk was doing. The network plugin next door already alerts on bytes_recv_rate_per_sec. The decoration also went only to the counter keys. The TUI prints the rate and reads read_bytes' decoration, so it showed a colour derived from the wrong quantity; the WebUI asks for read_bytes_rate_per_sec, which nothing set, so it showed no colour at all. Both keys now carry it, matching network again. The rate view exists only once there is a timespan to divide by, so the rate key is set when present -- setting it unconditionally raises KeyError on the first sample, which the existing view tests catch.
Owner
|
Thanks for the PR @ntdatt812 Merged into develop. |
nicolargo
pushed a commit
that referenced
this pull request
Aug 27, 2026
`update_views` skipped the whole interface when either direction measured
0 bytes/s:
if not i.get('bytes_recv_rate_per_sec') or not i.get('bytes_sent_rate_per_sec'):
continue
The guard is `or`, so one idle direction suppressed the alert for *both*. A
send-only interface saturating its uplink went undecorated because its rx rate
happened to be exactly 0, and a receive-only one — a monitor/SPAN port — the
same way. 0 bytes/s is a real measurement, not a missing one.
The rates now default to 0 instead of skipping, which is what the diskio plugin
already does: it dropped this same guard in #3684 and reads the rate through
`or 0` for the same reason.
The WebUI needs no change — `plugin-network.vue` renders `getDecoration(...)`
straight from the server-supplied view, so it inherits this.
Tests: four cases in `TestNetworkPluginZeroRateAlerts`. Three fail before this
change (saturated tx with idle rx, saturated rx with idle tx, and a fully idle
interface reading DEFAULT instead of OK); the fourth — both directions busy —
passes before and after, which is the path that already worked.
`tests/test_plugin_network.py` 39 passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things, one line apart, and the network plugin next door already does both correctly.
The alert measured a lifetime counter against a bitrate threshold
read_bytescarriesrate: Trueinfields_description, which is what producesread_bytes_rate_per_sec—read_bytesitself is psutil's counter since boot. It only ever grows, so on any machine with real uptime a configured..._rx_criticalwas crossed within seconds of the first read and stayed crossed for the life of the process, regardless of what the disk was doing. An operator who set thresholds got a permanently red column; one who set none saw nothing, which is why this could sit unnoticed.The network plugin is the shape this should have had:
The decoration went to a key one interface reads and the other does not
update_viewsset it only onread_bytes/write_bytes:read_bytes_rate_per_sec(auto_unit(i.get('read_bytes_rate_per_sec'))) but readsread_bytes' decoration — a colour computed from a different quantity than the number beside it;getDecoration(disk.name, 'read_bytes_rate_per_sec'), which nothing ever set, so it rendered no colour at all.Network sets both keys for exactly this reason:
Diskio now does the same, guarded: the
_rate_per_secview only exists once there is a timespan to divide by, and setting it unconditionally raisesKeyErroron the first sample — the repo's existingTestDiskioPluginViewscases catch that, which is how I found it.Tests
tests/test_plugin_diskio.py, newTestDiskioBitrateAlert, four cases over injected stats carrying a large lifetime counter and a separate rate:OK— this is the case the old code got wrongCRITICALread_bytes_rate_per_secdecoratedCRITICALwhilewrite_bytes_rate_per_secisOK— the WebUI's key, and asymmetric so a blanket assignment cannot pass itKeyErrorThresholds in the fixture are named
diskio_sda_rx_*:get_stat_name()composes plugin +action_key+ header, so a per-disk threshold carries the disk name, exactly as the network plugin's docstring documents withnetwork_wlan0_rx_careful.Mutation-checked, one per defect:
i['read_bytes']again*_rate_per_sec(37 before this PR, plus the 4 added.) Python only, so no WebUI rebuild — the bundle already reads whatever the plugin publishes.