Repository navigation
Conversation
update() sorted the raw as_dict() rows at line 689, before the per-process loop ran get_io_counters() and the namedtuple->dict conversion. At that point io_counters is still a pio namedtuple (indexes 2/3 are read/write *counts*, not the previous-cycle byte totals the sort lambda subtracts) and cpu_times a pcputimes namedtuple without .get() — so --sort-processes io_counters ordered on garbage that pushes the busiest process to the bottom of /api/5/processlist, and cpu_times sorting raised inside sorted() and silently fell back to cpu_percent order. Do a first pass that materializes the metadata and converts the fields the sort keys can point at (io_counters list, cpu_times dict, cached username/cmdline), then sort on that. The extended-stats loop keeps running after the sort so position still matches the displayed order (issue nicolargo#3400). update_processcount moves ahead of the metadata pass since it counts the raw status strings. Fixes nicolargo#3830
|
Thanks for the clear diagnosis and the regression tests. Summary. The sort used to run on raw psutil rows, before the per-process conversion. CPU impact: negligible.
Minor, non-blocking:
LGTM overall. Generated by Claude Code |
Review on nicolargo#3831: since the pre-sort pass merges the cached attrs (cmdline, username) before the extended grab, the processlist model comment claiming cmdline was merged after was stale, and the post-loop copy into extended_process was redundant -- namedtuple_to_dict(proc) already carries it. Update the comment in model.py (and the same claim in the test docstring) and delete the dead copy.
|
Done in 141cb10 — thanks for the close read. You asked for the stale-comment fix: Since you flagged the post-loop copy as redundant, I removed it rather than leaving dead code — Regression check: |
|
Thanks for the PR @santhiprakash Just merged into develop. |
Description
Problem. With
--sort-processes io_counters,/api/5/processlistreturns the process with the most disk IO at the bottom of the list; with--sort-processes cpu_timesthe list comes back in CPU% order. Reported and diagnosed in #3830.Root cause.
GlancesProcesses.update()sorts atprocesses.py:689on the rawas_dict()rows, before the per-process loop runsget_io_counters()and the namedtuple→dict conversion. At sort timeio_countersis still apionamedtuple — psutil'spio(read_count, write_count, read_bytes, write_bytes, …)puts the syscall counts where_sort_io_countersexpects the previous-cycle byte totals, so a heavy writer scorescount − bytes ≪ 0and sinks to the bottom.cpu_timesis apcputimesnamedtuple with no.get(), so_sort_cpu_timesraises insidesorted()andsort_statssilently falls back tocpu_percentfor the whole list.The TUI hides this because
_apply_live_sortre-sorts the post-conversion snapshot every cycle; the REST API and WebUI serve the engine order directly (get_list()), so they expose the pre-conversion sort.Fix. In
update():get_pid_time_and_status,get_io_counters,maybe_add_cached_stats(cachedusername/cmdlinealso only exist after this pass), andcpu_times._asdict()— beforesort_statsruns.update_processcountmoves ahead of that pass since it counts the raw status strings (the pass truncatesstatusto one letter).positionkeeps matching the displayed order (why the early sort exists, Process widget is broken in Homepage since a few dev release #3400).is_extended_pid(v5 pin) is order-independent.Verification.
tests/test_processes_sort_order.py(4 tests): fakebuild_process_listrows in the raw psutil shapes. All 4 fail on the pre-fix code (writer last / CPU% order) and pass after.glances -w --sort-processes io_counters:/api/5/processlistis strictly monotonic desc onread−read_old+write−write_oldacross 371 rows; a busy writer rises to position 0 within one cycle.POST /api/5/processes/sort/cpu_times→ monotonic desc onuser+system(Spearman vs cpu% order 0.992, no longer identical).usernamealso monotonic.pytest tests/test_processes_extended.py test_sort_missing_values.py test_processes_sort_order.py test_alerts.py test_routes.py test_plugin_processlist.py test_program_aggregation.py test_curses.py→ 569 passed. Fullpytest tests/→ 4223 passed; the only failures are pre-existing environment gaps (test_plugin_containersneeds thedockerpackage;test_webui_browserneeds a Playwright browser), both red on a clean tree.pre-commit runon the touched files: all hooks pass.Risks. One extra pass over the process list per cycle (~O(n) dict ops, trivial next to the
psutilcalls).get_io_countersstill runs exactly once per process per cycle, soio_oldaccounting is unchanged.Resume
AI-assisted. I wrote and verified this change.