Skip to content

Feat/array summaries - #93

Open
ovalerio wants to merge 8 commits into
troycomi:mainfrom
mpinb:feat/array-summaries
Open

Feat/array summaries#93
ovalerio wants to merge 8 commits into
troycomi:mainfrom
mpinb:feat/array-summaries

Conversation

@ovalerio

@ovalerio ovalerio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

An array summaries new feature for reportseff following the feedback from @troycomi and @biermanr here: #92

The concept is mine and I've refined, implemented and regression test the new feature using and Agentic coding website (claude.ai/Sonnet 5).

ovalerio and others added 8 commits July 5, 2026 04:54
Append a summary after each job array (behind --array-summary), giving
at-a-glance insight into array progress and efficiency. Default output is
unchanged: with the feature disabled the rendered rows are byte-identical to
before, and summaries are suppressed in --parsable mode.

Each summary reports:
- completion progress (completed/total, percent) and per-state task counters
- min / mean / max for the displayed efficiency columns (completed tasks only)
- total accumulated task-time and per-state mean runtime
- shared values shown once vs. differing values listed (e.g. multiple partitions)
- compute nodes collapsed into Slurm-style hostlist ranges
  (e.g. somacpu[001-088,101-124],somagpu[001-093])
- a low-mean-efficiency marker, reusing the existing click color thresholds

An optional runtime histogram (minutes) is drawn for arrays larger than a
threshold, using Unicode block bars with an ASCII fallback for non-UTF output;
a compact one-line sparkline is available as an alternative.

New CLI options:
- --array-summary: enable the summary block
- --array-summary-hist: add a runtime histogram for large arrays
- --array-summary-hist-min-tasks (default 50): histogram size threshold
- --array-summary-sparkline: render a one-line sparkline instead of a histogram

Implementation:
- new src/reportseff/array_summary.py: grouping, completed-only aggregation,
  hostlist expand/compact, and histogram/sparkline builders (no color, no
  circular import with the renderer)
- output_renderer.py: RenderOptions fields and a colored summary block that
  reuses render_eff; block rendering only when enabled
- console.py: new flags; get_implementation now takes a RenderOptions
- parameters.py: four new fields
- README.md: documents the new options

Tests:
- tests/test_array_summary.py: grouping, stats, hostlist expand/compact,
  histogram binning/bars, sparkline, duration formatting
- tests/test_output_renderer.py: summary on/off (unchanged when off), ordering
  after each array, parsable suppression, histogram threshold, sparkline
- tests/test_reportseff.py: end-to-end CLI wiring
…tover

Foundation work for the `summarize` subcommand, with no behavior change
to the default `reportseff <jobs>` invocation (verified byte-identical
output against baseline for a representative query).

- Migrate the CLI entry point from a single click.command() to a
  click.group() (DefaultGroup), with a default-command shim so bare
  `reportseff <jobs>` / `reportseff --format=... jobs` keep working
  without a subcommand keyword. `--version` moves to the group level;
  `--help` at the top level now lists subcommands, while
  `reportseff report --help` reproduces the original detailed options
  text. Entry point in pyproject.toml updated to `console:cli`.
- Extract the "query sacct -> build/filter job collection" pipeline out
  of get_jobs() into fetch_job_collection(), reusable by both `report`
  and the future `summarize` command.
- Hard-remove the legacy --array-summary / --array-summary-hist /
  --array-summary-hist-min-tasks / --array-summary-sparkline flags and
  their ReportseffParameters/RenderOptions fields and rendering code
  path, per the agreed hard-cutover decision -- as if they never
  existed. array_summary.py (the underlying aggregation/rendering
  engine) is untouched and will be re-plumbed onto `summarize` in
  Phase 1.
- Update/add tests accordingly: remove now-obsolete array-summary
  rendering tests, add a hard-cutover check that the old flags are
  rejected, and add DefaultGroup dispatch coverage.
- Trim the now-stale --array-summary* section from README.md; the full
  `summarize` documentation lands in Phase 4.

Drafted with AI assistance (Claude) for the click.Group dispatch
boilerplate and the mechanical option/field removal; reviewed by the
requesting engineer before merge.
…d core

Adds the `summarize` subcommand: array/name grouping, per-task rows plus
a summary block per group, and runtime graphing (sparkline/histogram),
built on Phase 0's shared query pipeline and reusing array_summary.py's
aggregation/rendering engine unchanged.

- parameters.py: SummarizeParameters, plus a new BaseQueryParameters
  shared by both ReportseffParameters and SummarizeParameters so the
  "querying slurm" field list isn't duplicated. Validates --graph-format
  eagerly in __post_init__.
- console.py: shared_query_options decorator factors the ~15 options
  common to `report` and `summarize` out of two separate @click.option
  chains. `summarize` adds --group-by (array/name, default array),
  --graph-style (sparkline/histogram/none), --graph-format (comma-
  separated, case-insensitive), --min-tasks (default 50), and
  --ascii-fallback.
- array_summary.py: group_jobs_by_array refactored onto a shared
  _group_jobs_by_key helper; adds group_jobs_by_name (falls back to a
  task's own base id when JobName wasn't captured -- e.g. a PENDING
  job, whose fields are mostly uncached by Job.update() -- rather than
  merging unrelated tasks under an empty key). Adds parse_graph_format
  and GRAPH_FORMAT_VOCABULARY = runtime/cpueff/memeff/energy/gpueff/
  gpumem (energy, not power; gpu metrics included, per the agreed plan).
- output_renderer.py: add_required_column() lets `summarize --group-by
  name` pull JobName into the sacct query without displaying it.
  format_grouped_summary() is the new rendering entry point, re-plumbing
  the array-summary block logic Phase 0 removed onto the new command's
  options instead of the deleted legacy RenderOptions fields. Only
  `runtime` currently draws a graph -- extending --graph-format to the
  other vocabulary entries needs raw per-task series retained for them,
  which is Phase 3, not this one; requesting them is still validated,
  they just don't graph yet (marked with a TODO at the call site).
  Also fixes a gap found during verification: the summary block wasn't
  being suppressed in --parsable mode (per-task rows now correctly
  remain parsable while the decorative block is dropped, matching the
  original prototype's own safeguard).
- Tests: group_jobs_by_name (including the fallback/mixed-availability
  cases) and parse_graph_format in test_array_summary.py;
  format_grouped_summary and add_required_column end-to-end in
  test_output_renderer.py; full `summarize` CLI coverage (both grouping
  modes, graph-style/format/min-tasks/ascii-fallback, parsable
  suppression, bad --graph-format rejection, singleton jobs) in
  test_reportseff.py.

Drafted with AI assistance (Claude) for the CLI/renderer scaffolding
and the bulk of the test suite; reviewed by the requesting engineer
before merge.
…block

Replaces the per-metric bullet/middot line ("CPUEff: min 90.0 . mean
95.0 . max 99.0") with an aligned Metric/Min/Mean/Max table, per
troycomi's original review comment, reusing ColumnFormatter's width/
alignment/coloring machinery instead of hand-rolled string joins --
the same approach the main report table already uses, so summarize's
output now shares its visual language.

- _format_metric_line replaced by _format_metrics_table, which builds
  four ColumnFormatter instances (Metric left-aligned, Min/Mean/Max
  right-aligned), sizes each from its own header/cell contents, and
  reuses format_title()/format_entry() for rendering -- no new parsing
  or alignment logic.
- Drops the separate "(low)"/warning-glyph marker that flagged
  below-threshold values in the old line format. The color coding
  (render_eff, same thresholds, same as the main report table) already
  flags this per-cell -- now on each of min/mean/max independently
  rather than a single mean-based check -- which is what troycomi's
  review comment on the original prototype asked for. If this reads
  as a step too far, it's a one-line-per-cell change to reinstate.
- No unicode is introduced by the table (no box-drawing characters,
  same as the main report table's own style), so --ascii-fallback
  needs no table-specific handling; the existing bullet/histogram
  ascii_only behavior elsewhere in the block is unaffected.
- Total task-time already used format_duration() as of Phase 1 (found
  during that phase's verification pass), so nothing to change there.
- Tests: table header/alignment (ANSI-stripped width comparison, since
  colored cells add invisible escape bytes), confirms the old inline
  "TITLE: min X . mean Y . max Z" text is gone, confirms no extra "low"
  text/glyph, empty-metrics edge case. Two Phase 1 tests that asserted
  on the old "CPUEff:" text are updated to match the new table cells.

Drafted with AI assistance (Claude) for the table-rendering rewrite and
its tests; reviewed by the requesting engineer before merge.
test_format_metrics_table_low_efficiency_uses_color_not_extra_text
called _summary_job(..., total_cpu=...), but the helper hard-coded
TotalCPU to "00:09:00" and never exposed it as a parameter -- caught
by the real pytest run (TypeError: unexpected keyword argument
'total_cpu'), not by my own verification, which had drifted into
using a hand-retyped local reimplementation of the helper instead of
the actual committed one.

Adds the missing parameter, matching the pattern test_array_summary.py's
_make_job already uses. Re-verified this time by extracting the actual
function source via ast from the committed file and executing it
directly, rather than retyping it -- to close this specific class of
gap, not just this one instance of it.

Drafted with AI assistance (Claude); reviewed by the requesting
engineer before merge.
…tric

Generalizes graphing beyond the runtime special case: --graph-format
cpueff/memeff/energy/gpueff/gpumem now actually draw a sparkline or
histogram, not just get summarized in the metrics table. Validation
and --ascii-fallback wiring were already in place as of Phase 1; this
closes the remaining gap the plan called out explicitly.

- array_summary.py: MetricStat gains a `values: list[float]` field,
  populated in build_array_summary alongside min/mean/max, so any
  summarized metric's raw per-completed-task series is retained (not
  just runtime's, via the pre-existing elapsed_minutes). Backward
  compatible -- defaults to an empty list for direct construction.
  render_histogram gains a `label` parameter (default "Runtime", so
  every existing call site/test is unaffected) so the same renderer
  can be reused for any metric's histogram, not just runtime's.
- output_renderer.py: the runtime-only graph dispatch in
  _format_summary_block is replaced by a loop over every metric named
  in --graph-format, extracted into a new _format_metric_graph helper
  shared between the sparkline and histogram code paths. Percent-style
  metrics (cpueff/memeff/gpueff/gpumem) get a "%" unit; Energy gets
  "J" (Joules, per Slurm's TRES energy accounting convention);
  everything still respects --min-tasks, --graph-style=none, and
  --ascii-fallback uniformly, not just for runtime.
- Tests: MetricStat.values retention, render_histogram's label/unit
  handling (including the empty-unit-omits-parens case), multi-metric
  graphing (min-tasks gating, graph-style=none, ascii-fallback, and
  sparkline mode all applied across several simultaneously-graphed
  metrics), and Energy's Joules unit specifically. Two tests whose
  premise Phase 3 made obsolete (asserting a non-runtime metric
  *doesn't* graph) are rewritten to assert the new, correct behavior.

Drafted with AI assistance (Claude) for the generalization and its
tests; reviewed by the requesting engineer before merge.
Completes the array-summary feature per the implementation plan: user
docs for `summarize`, the last testing-strategy item not yet covered
by an earlier phase, and a commit-hygiene note for contributors.

- README.md: new "The `summarize` Subcommand" section under Usage,
  covering --group-by/--graph-style/--graph-format/--min-tasks/
  --ascii-fallback, with three real, freshly-captured examples (array
  grouping, name grouping, histogram style) rather than hand-typed
  ones, matching how the rest of the README documents behavior. Also
  adds the AI-assistance-disclosure + small-commits note (agreed
  earlier in this project's design discussion) to the Contributions
  section, since there's no separate CONTRIBUTING.md to put it in.
- Tests: the plan's testing strategy explicitly called for "graceful
  no-graph behavior for gpueff/gpumem when jobstat-caching data isn't
  present" -- this was implicitly correct already (an all-"---" column
  never accumulates a MetricStat to graph) but wasn't exercised by any
  existing test. Added at both the renderer level
  (test_output_renderer.py) and the CLI level (test_reportseff.py):
  requesting --graph-format gpueff/gpumem on non-GPU jobs succeeds,
  draws nothing, and doesn't otherwise disturb the summary.

  Everything else the plan asked Phase 4 to cover -- name-grouping and
  raw-series-retention tests, click.group dispatch and hard-cutover
  tests -- already landed in Phases 0, 1, and 3 respectively, as unit
  tests were written alongside each phase rather than deferred here.

Drafted with AI assistance (Claude) for the README section and the
GPU-absence tests; reviewed by the requesting engineer before merge.
@troycomi

Copy link
Copy Markdown
Owner

Let me know if you want me to review this or wait until the checks are all passing.

@ovalerio

Copy link
Copy Markdown
Contributor Author

Hello @troycomi👋

I'm on vacation and I've already started with the cleanup (coverage & lint issues).

Of course you can start looking at it if you have time. I'm not planning to do any big changes just fixing the CI stuff that is currently preventing the merge.

Then I will sent you a review request when I get back from vacation. In around a week time.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants