load_limits split a config list on ',' and kept the spaces, so 'list=cpu, mem,
load' became ['cpu', ' mem', ' load']. glances.conf writes lists that way in its
own comments, so following the shipped documentation produced a config that did
not work.
The loudest symptom is show/hide: every item is used as a re.fullmatch pattern,
and a leading space makes the pattern match nothing. 'hide=sda2, loop.*' hid
sda2 and silently kept showing every loop device.
Quicklook also answered a bad list by falling back to AVAILABLE_STATS_LIST -
more than the user asked for, and its two GPU entries flip the gpu_stats polling
flags, so a typo started polling the GPU. It now falls back to the documented
DEFAULT_STATS_LIST and names the offending entries in the warning.
`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.
sort_stats wraps the specific sort helpers in try/except and falls back to
cpu_percent for the whole list. _sort_cpu_times indexed cpu_times['user'] and
_sort_io_counters indexed io_counters[0..3], so one row with an empty value
raised and silently reordered every other row while the header still read TIME
or IOR/IOW.
Those empty values are expected rather than corrupt: programs.py builds a
program with p['cpu_times'] or {} and list(p['io_counters'] or NO_IO_COUNTERS),
and its own comment says some values can be None on macOS system processes.
Each helper now reads a missing value as zero, so the degenerate row sorts last
and the rest keep their order.
Codacy's Prospector/pydocstyle profile flagged 23 issues on the new file: the
multi-line docstrings all put a description straight under the summary line
(D205, D209, D213, D400, D415). Keeping the summary to one line and putting the
reasoning in comments below says the same thing and leaves the file clean.
No test logic changed: still 7 red on origin/develop, 9 green with the fix.
io_counters is a fixed five-slot list -
[read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag] - and
update_program_dict merged it with '+=', which concatenates lists rather than
adding them. Three things followed:
- Readers index the list, so a program showed only its first process's disk
IO. Two processes reading 100 and 1000 bytes displayed 90 B/s where the
sum is 990 B/s.
- '+=' extends in place, and create_program_dict stored the process's own
list, so aggregating wrote back into the process list.
- Re-aggregating the same processes - which a re-sort does - appended again
every time, growing the list without bound.
Sum the four byte slots and OR the io_tag: processlist only displays a rate
when the tag is exactly 1, so adding it would blank the columns instead.
WifiPlugin.get_alert chained its three comparisons, so the first undefined level
was compared against None, raised TypeError, and the blanket handler dropped the
result to DEFAULT. Because 'critical' is tested first, leaving it out silenced
the other two: a config defining only wifi_careful produced no alert at all, and
even a strong signal came back undecorated instead of OK.
GlancesPluginModel.get_alert already guards each level separately, and the
sensors plugin has tests pinning that partial thresholds work. This brings wifi
in line. `is not None` rather than truthiness because these limits are negative
dBm values, where `if threshold` would discard a 0.
The TypeError handler is kept for a non-numeric signal level (issue #1373) but
narrowed to that case, which is now the only way it can fire.
The WebUI decided degradation with `diskData.used < diskData.available` and
nothing else, while the authoritative RaidPlugin.raid_alert applies three rules
before that comparison: raid0 is always OK, an inactive array is CRITICAL, and
an unknown device count is left undecorated.
Two of those omissions are visible:
- A raid0 array reporting fewer used than available devices was drawn as
"Degraded mode" in warning colour. raid0 has no redundancy, so that comparison
carries no meaning there — msg_curse excludes raid0 from the same banner.
- `used` is None whenever the parser cannot read the device count, and in
JavaScript `null < 5` is true, so an unreadable array was reported as degraded.
The TUI leaves it undecorated.
Comparing both implementations across seven arrays, four disagreed before and
none after.
One asymmetry is preserved rather than fixed: raid_alert returns OK for an
inactive raid0 because the raid0 branch runs first. This change matches that so
the two views agree; whether the Python ordering itself is right is a separate
question, and the inactive line is rendered regardless.
`update_views` guarded the alert loop with `if not i['value']: continue`, which
skips a genuine reading of 0 along with the absent ones.
The battery alert is computed on `100 - value`, so an empty battery is the most
critical reading there is — yet a battery at 0% was left undecorated while one at
3%, a strictly better state, was flagged CRITICAL. A fan reporting 0 RPM was
likewise never evaluated.
Tests for a usable number instead. Sensors reporting a placeholder — no battery
is an empty list, hddtemp uses b'ERR'/b'SLP'/b'UNK'/b'NOS' — keep the DEFAULT
decoration the parent update_views() already assigns, so nothing that was skipped
before starts being decorated now. It also keeps non-numbers away from the
`100 - value` subtraction, which raises TypeError on them today.
`summarize_all_cpus_not_displayed` sliced `percpu_list[0 : max_cpu_display]` —
the exact slice `msg_curse` has just printed one line above. So the CPU* row
averaged the cores already on screen instead of the ones that did not fit.
`manage_max_cpu_to_display` sorts by total descending whenever the list
overflows, so the displayed slice is the busiest cores. On an 8-core box showing
4, with four cores at ~87% and four idle at 2%, the CPU* row read 87.0% — a
figure for cores the user can already see, and 43x the load of the group it
claims to represent. The busier the top cores, the more misleading the summary.
Slices the tail instead, so the displayed slice and the summarized slice
partition the list.
Per-CPU stats checked for guest_nice but returned the steal field, so systems exposing both values reported incorrect guest_nice usage. Read the matching psutil field and cover the conversion with distinct fixture values.
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.
update_views called get_alert(header='nf_conntrack_percent') without a value,
so it measured get_alert's default current=0 on every refresh. The tracked
connection percentage was drawn OK at any fill level, and the thresholds
conf/glances.conf ships for it -- careful 70, warning 80, critical 90 -- could
never fire. A conntrack table close to nf_conntrack_max drops new connections,
which is exactly what those thresholds exist to warn about.
Both interfaces read this one decoration (the WebUI through
getDecoration('nf_conntrack_percent')), so both were silent.
build_title appends the pieces of the header to one list, and the last
append sits outside the branch that produces its message:
if not self.views['show_engine_name']:
msg = f' (served by {self.stats[0].get("engine", "")})'
ret.append(self.curse_add_line(msg))
With one engine the branch runs and the append is correct. With several -
Docker and Podman on the same host, which is the only case
show_engine_name is True - the branch is skipped, msg still holds the
previous fragment, and appending it again repeats it.
Measured against the real build_title:
2 containers, 2 engines -> 'CONTAINERS 2 sorted by CPU consumption sorted by CPU consumption'
1 container, 2 engines -> 'CONTAINERSCONTAINERS'
2 containers, 1 engine -> 'CONTAINERS 2 sorted by CPU consumption (served by docker)'
1 container, 1 engine -> 'CONTAINERS (served by docker)'
Move the append inside the branch. The engine name genuinely has nothing
to add in the multi-engine case: maybe_add_engine_name_or_pod_line adds a
per-row Engine column there instead.
Two problems on the same argument.
Windows 'ping -w' is a per-reply timeout in **milliseconds**, not
seconds, so a configured 'timeout = 3' became a 3 ms deadline. Measured
against two hosts that answer well within 3 seconds:
200.160.2.3 (350 ms) ping -n 1 -w 3 -> exit 1
200.160.2.3 (350 ms) ping -n 1 -w 3000 -> exit 0
139.130.4.5 (168 ms) ping -n 1 -w 3 -> exit 1
139.130.4.5 (168 ms) ping -n 1 -w 3000 -> exit 0
Windows clamps the wait to about 50 ms, so a host on the LAN still
answers in time and the bug hides; anything further away is reported
offline. Multiply by 1000 on Windows and leave -W/-t in seconds.
The timeout was also passed through _resolv_name(), which runs
socket.gethostbyname() on it. It is a number of seconds, not a host: the
lookup can only fail, and it logs a misleading 'Cannot convert 3 to IP
address' on every ICMP check.
update() guarded the per-folder timer with `i in self.timer_folders`,
which asks whether the integer i is one of the Timer objects in the list.
Timer does not implement __eq__, so that test is always False: the skip
branch was unreachable and the reset branch never ran.
The consequence is that folder_N_refresh has no effect. Every folder is
re-walked on every refresh cycle (2s by default) rather than on its own
schedule, and folder_size() is a full recursive walk - which is exactly
why the setting exists.
Use the index bound the code meant, `i < len(self.timer_folders)`. The
guard still earns its place: the folder list lives on the class, so it
can outgrow the per-instance timer list.
`_sum_stats(key, sub_key)` guarded the accumulation with `sub_key in p[key]`.
That is a mapping test, and `sub_key` is only a mapping key for `memory_info`.
For `io_counters` it is an index — the value is the list
[read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
so `0 in p['io_counters']` asked whether the counters *contain the number zero*.
A process doing real IO was skipped, and one whose counters happened to hold the
literal 0/1/2/3 was summed instead:
[[5000, 700, 2000, 300, 1], _sum_stats('io_counters', 0) -> 0
[3000, 400, 1000, 200, 1]] (expected 8000)
[[5000, 700, 0, 300, 1]] _sum_stats('io_counters', 0) -> 5000
(summed only because 0 is in the list)
So the R/s and W/s totals on the process-list summary row — rendered whenever a
process filter is active — were not merely zero but arbitrary. The VIRT and RES
totals beside them were correct, because `memory_info` really is a mapping.
Branch on the container type, and bounds-check the index so a short list is
skipped rather than raising.
`GlancesFilter.filter` split the input on every colon and kept only the second
field, so everything after the second colon was discarded:
cmdline:C:/Program Files/.* -> key=cmdline regex=C
name:foo:bar -> key=name regex=foo
The truncation is silent. The leading fragment is usually still a valid regex —
`C` compiles — so the compile guard never fires, the filter matches nothing, and
the process list comes back empty with no error and only a debug log line.
On Windows that is every absolute path, since each one has a drive colon. It
also truncates URLs, IPv6 literals and any `foo:bar` process name.
`split(':', 1)` rather than the repo's `split_esc`: that helper escapes on
backslash and strips the escape characters from its result, which would mangle a
Windows path regex. The separator here needs no escaping — the key never
contains a colon.
psutil reports the Win32 priority *class* for `nice` on Windows, not a nice
value. Those numbers are neither ordered nor small — 32 is normal while 32768 is
*above* normal — so the NI column showed a five-digit number that is meaningless
as a nice value and does not fit its 3-character width.
Render the six classes as the short labels Windows itself uses, in the TUI and
the WebUI alike:
256 RT 128 Hi 32768 AN 32 No 16384 BN 64 Lo
Only the rendering changes. The API keeps the raw value, so sorting and any
consumer of `/api/*/processlist` are untouched, and `get_nice_alert()` still
receives the number: the `nice_*` limits in glances.conf are POSIX nice values,
and matching a label against them would silence them. An unmapped class falls
through to its number rather than disappearing, so a class Windows adds later
stays visible.
ProgramlistPlugin inherits `_get_process_curses_nice`, so the program view is
covered by the same change.
Closes#3672.
These three plugins expose a list of stats but inherited the base class's
stats_init_value default of {}. get_init_value() is what update() returns when
it has nothing to report, and what reset() installs, so /api/4/containers
answered {} instead of [] whenever no container engine library was importable -
the empty-dict-for-empty-list the issue reports.
containers and vms simply never passed stats_init_value. smart declared
stats_init_value=[] as its own parameter default and then dropped it on the way
to super().__init__(), so it read as correct while behaving the same way; a grep
for the keyword finds it, only running it does not.
Every other list plugin already passes stats_init_value=[]. tests/test_restful.py
carries a workaround for this exact symptom, added in 4c92e1b as "allow empty
dicts for list plugins" - it was read as test flakiness rather than a bug.
Fixes#3582
_get_network_stats() read the container network counters from a single
hardcoded interface. The Docker stats endpoint returns one entry per
container interface, so a container attached to several networks gets
eth0, eth1... and everything past eth0 was silently dropped, making the
reported RX/TX rates too low.
Sum rx_bytes/tx_bytes across all the interfaces instead. Loopback is
excluded, since Docker-API-compatible runtimes have been observed to
report it and its traffic would inflate the totals. Interfaces with
missing counters are skipped rather than aborting the whole method, so
one malformed interface no longer discards the valid ones. None is still
returned when nothing usable remains, keeping --network host containers
unchanged.
The output contract is untouched: same keys, same types, same units.
Fixes#3669