CLI reference
Install for this page:
pip install "scope-profiler[pproc]"enables the profiling, inspection, report, export, plot, and plotted-TUI examples. A text-only TUI can instead usescope-profiler[tui]; line profiling needsscope-profiler[line-profiler].
All subcommands live under the single scope-profiler executable (also
runnable as python -m scope_profiler).
pytest integration
Section titled “pytest integration”The package also exposes an opt-in pytest plugin. It writes a regular HDF5
profile with one region per selected test, named
pytest::<nodeid>::<phase>:
pytest --scope-profile --scope-profile-out pytest-profile.h5scope-profiler inspect pytest-profile.h5 --regions-only --sort totalscope-profiler plot durations pytest-profile.h5 -o pytest-plotsOnly the test call phase is measured by default. To include fixture setup
and teardown, pass --scope-profile-phases=all. A profiling TOML file can be
shared with other runs using --scope-profile-config profiling.toml.
The plugin deliberately uses an independent manager, so tests that configure
the process-wide ProfileManager do not interfere with it. It times complete
test phases rather than recursively tracing pytest internals. When using
pytest-xdist, omit --scope-profile: parallel workers cannot safely write
one HDF5 profile yet.
scope-profiler tui
Section titled “scope-profiler tui”Open an interactive browser for a profiling HDF5 file. The Plots section
reuses the plotting functions from scope-profiler plot:
scope-profiler tui profiling_data.h5Simple duration plots are rendered with Plotext inside the TUI when selected
and update automatically as settings change. Press t to refresh them
manually if needed. Press g to display a plot in a Matplotlib window, or p
to open it as an interactive Plotly figure in a browser. Press s to save the
Matplotlib version as PNG. Saved plots go to
<file-stem>_plots/ by default; use --plot-output DIR to choose another
directory.
For the Flame plot, press v to export the selected data to .prof format
and open it in Snakeviz (pip install snakeviz).
When a plot is selected, its settings panel shows the regions matched by the include/exclude filters as you type. It also supports rank ranges, colormap, log scale, duration metrics, duration sorting and top-N selection, histogram bin count, and imbalance metric. The include and exclude fields autocomplete region names from the loaded profile and accept comma-separated values. Press Apply settings before displaying or saving the plot.
Available TUI plot types include Gantt, flame chart, flame graph, durations, timeseries,
histogram, imbalance, and any LIKWID metrics recorded in the file. Only the
durations plot is rendered with Plotext inside the TUI; the other plots can be
shown with Matplotlib, opened with Plotly, or saved as files. Install the
optional TUI/plotting dependencies with pip install "scope-profiler[pproc]".
scope-profiler run
Section titled “scope-profiler run”Run a script with its explicit @profile and region() instrumentation
enabled. Recursive function tracing is opt-in with --recursive; add --all
to include standard-library and installed-package calls as well.
usage: scope-profiler run [-h] [-o OUTFILE | --no-output] [-q] [--recursive | --no-recursive] [--include GLOB] [--exclude GLOB] [--all] [--label LABEL] [--tag TAG] [--enable-tag TAG] [--entrypoint NAME] [--line-profile] [--memory-profile] [--buffer-limit BUFFER_LIMIT] script ...| Flag | Description |
|---|---|
-o, --outfile | Path of the output file (default: profiling_data.h5); the extension picks the format |
--no-output | Keep results in memory and do not write a profile file |
-q, --quiet | Suppress the per-region summary printed after the run |
--recursive | Trace every Python function in the script; explicit regions only by default |
--no-recursive | Disable recursive tracing, overriding the configuration file |
--all | Recursively trace all calls, including standard-library and installed-package calls |
--label | Store a short label with the run for comparison and reporting |
--tag | Record only explicit regions carrying the given tag; repeatable |
--enable-tag | Record the regions behind an opt-in tag, such as a library’s own instrumentation; * enables all; repeatable |
--entrypoint | Name the recursively traced entrypoint region |
--include | Include only recursively traced function names matching this glob; repeatable |
--exclude | Exclude recursively traced function names matching this glob; repeatable |
--line-profile | Also collect line-by-line timings via line_profiler (requires scope-profiler[line-profiler]) |
--memory-profile | Record allocations with Memray in an adjacent .memray.bin file (requires scope-profiler[extras]) |
--buffer-limit | Initial buffer capacity per region; grows as needed (default: 1024) |
scope-profiler run my_script.py [script args...]The runner performs setup before loading the script and finalizes it afterward. This means explicitly instrumented scripts do not need an in-source session:
import scope_profiler as sp
@sp.profile("main")def main(): with sp.region("iteration"): work()
main()python my_script.py leaves these instrumentation points disabled and writes
nothing; scope-profiler run my_script.py activates and records them. Use
scope-profiler run --recursive my_script.py for an uninstrumented script. The
explicit session(), setup(), and finalize() APIs remain unchanged.
Output formats
Section titled “Output formats”The extension of -o chooses what is written, and HDF5 remains the default:
scope-profiler run -o profile.h5 my_script.py # HDF5 (the default)scope-profiler run -o profile.json my_script.py # a JSON profilescope-profiler run -o profile.json.gz my_script.py # the same, gzip-compressedscope-profiler run -o report.html my_script.py # a rendered HTML reportUse --no-output when only an in-memory summary is needed; it suppresses the
default profiling_data.h5 file. --label stores a short run name in the
profile, which is useful when comparing several runs. --no-recursive can
override a configuration file that enables recursive tracing.
Repeat --tag to select explicitly defined regions by tag; a region is kept
when it has at least one of the requested tags. --include and --exclude
apply to recursively traced function names and accept shell-style globs.
Libraries can ship regions behind an opt-in tag (sp.register_opt_in_tag),
which stay off until a run enables them. --enable-tag mylib records them
alongside everything else, as does SCOPE_PROFILER_ENABLE_TAGS=mylib.
A JSON profile holds everything the HDF5 file does — every call’s
timestamps, the lane tables, LIKWID counters, line-profiler records — in
integer nanoseconds, so nothing is lost by choosing it. inspect, plot,
report, diff, tui and the exporters all read one wherever they read an
HDF5 file, and scope_profiler.read_json() (or the format-dispatching
read_profile()) loads it in Python. It is the format to reach for when the
consumer is not this package: a browser, jq, a code review, or a machine
without h5py. HDF5 stays the better choice for a long run: it is read back
column by column rather than whole (a summary read never touches the event
columns at all), and it is the only format the parallel and rank-by-rank
writers can produce. On size the two are closer than they look — for a run
of a few hundred thousand events, plain JSON is roughly a third larger than
the HDF5 file and --gzip is a few times smaller than it, but a gzipped
document has to be decompressed and parsed in full before the first number
comes out.
The run itself always writes HDF5 and the requested format is rendered from it
afterwards, so -o never changes what a run measures or costs. Under MPI the
conversion happens on rank 0, once the merged file exists.
scope-profiler inspect
Section titled “scope-profiler inspect”Print what is inside a profiling file — the run metadata in full, and an overall statistics line per region — without producing any plots. Useful for checking which environment a run came from, and where its time went, at a glance.
usage: scope-profiler inspect [-h] [--include INCLUDE [INCLUDE ...]] [--exclude EXCLUDE [EXCLUDE ...]] [--ranks RANKS [RANKS ...]] [--sort {total,calls,avg,min,max,first,last,std,p50,p95,p99,imbalance,name}] [--full] [--columns {region,ranks,calls,total,avg,min,max,first,last,std,p50,p95,p99,imbalance} [...]] [--source NAME [NAME ...]] [--metadata-only | --regions-only] files [files ...]| Flag | Description |
|---|---|
--include | Only report regions matching these regex patterns |
--exclude | Skip regions matching these regex patterns |
--ranks | Restrict region statistics to these ranks, e.g. 0 2 or 0-3 |
--sort | Order regions by any region-table statistic, or by name alphabetically |
--columns | Restrict the region table to selected columns; default is region ranks calls total avg |
--full | Print long metadata values (PATH, LD_LIBRARY_PATH, …) in full |
--source | Print the captured call-site source (the with block or decorated function that defines it) of these regions |
--export-metadata | Also write the metadata of every inspected file to this JSON file |
-q, --quiet | Suppress the printed summary (useful with --export-metadata) |
--metadata-only | Print only the metadata section |
--regions-only | Print only the region statistics |
scope-profiler inspect profiling_data.h5 --regions-onlyscope-profiler inspect 'run_*.h5' --regions-only --sort callsscope-profiler inspect profiling_data.h5 --regions-only --columns region ranks calls total avgscope-profiler inspect profiling_data.h5 --export-metadata metadata.json --quietscope-profiler inspect profiling_data.h5 --source solve assembleExample output:
==============================================================================profiling_data.h51 rank(s), 3 region(s), 0.05 MiB, 0.00148838 s wall clock, 0.0148986 s total (setup to finalize)==============================================================================
╭─────────────────────────────────────────────────────────╮ │ region % session total [s] │ ├─────────────────────────────────────────────────────────┤ │ scope_profiler.session 100.00% 0.001488 │ │ └─ (own) 41.40% 0.000616 │ │ └─ matrix_multiply (100x) 5.59% 0.000083 │ │ └─ time_step (1kx) 53.01% 0.000789 │ ╰─────────────────────────────────────────────────────────╯
╭─ Info ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Summary: Regions (3) │ │ │ │ Explore: │ │ Inspect: scope-profiler inspect ../../../../..profiling_data.h5 │ │ TUI: scope-profiler tui ../../../../..profiling_data.h5 │ │ │ │ Visualize and export: │ │ Plot: scope-profiler plot default ../../../../..profiling_data.h5 -o plots --show │ │ Report: scope-profiler report ../../../../..profiling_data.h5 -o report.html │ │ Export: scope-profiler export plot-data ../../../../..profiling_data.h5 -o data │ │ Lines: scope-profiler line-profile ../../../../..profiling_data.h5 │ │ │ │ Compare runs: │ │ Diff: scope-profiler diff BASE.h5 CANDIDATE.h5 │ │ Check: scope-profiler check BASE.h5 CANDIDATE.h5 │ │ │ │ Durations are in seconds. │ │ Regions may nest, so the summed total can exceed the wall-clock time. │ │ % session uses wall-clock coverage; overlapping recursive calls count once. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯Region durations are in seconds, aggregated over the selected ranks. See HDF5 output & post-processing from Python for what each metadata field means.
Showing a region’s source
Section titled “Showing a region’s source”A region can remember the with block or decorated function it was defined
with - captured once when the region is first created, but only if the run
was profiled with capture_region_source=True (off by default; see
Configuration for its cost and HDF5 output & post-processing from Python
for the Python API). --source prints it, after the region table:
scope-profiler inspect profiling_data.h5 --source solve assembleSource (2) solve (kernels.py:42) with ProfileManager.region("solve"): return solver.step(state) assemble (kernels.py:18) @ProfileManager.profile("assemble") def assemble(size): return build_matrix(size)A name with nothing captured (capture_region_source was left at its
default, the file predates this feature, or the region was only ever created
by the recursive tracer) prints “source not captured” instead of failing the
whole command; an unknown name prints the list of regions the file actually
has.
Exporting metadata to JSON
Section titled “Exporting metadata to JSON”--export-metadata writes the metadata of every inspected file to one JSON
document. Values are never clipped there, regardless of --full:
scope-profiler inspect profiling_data.h5 --export-metadata metadata.json --quiet{ "files": [ { "file_path": "/scratch/run/profiling_data.h5", "num_ranks": 2, "metadata": { "chip_information": "AMD EPYC 9654 96-Core Processor", "modules": ["profile/base", "gcc/12.3.0", "python/3.11.7"], "SLURM_JOB_ID": "9988776", "...": "..." } } ]}Several files (or a glob) produce one entry each, which makes the export convenient for comparing the environments of a set of runs. The same is available from Python:
from scope_profiler.inspection import collect_file_metadata, write_metadata_json
payload = write_metadata_json("profiling_data.h5", "metadata.json")payload = collect_file_metadata(["run_1.h5", "run_2.h5"]) # no file writtenscope-profiler report
Section titled “scope-profiler report”Write a self-contained HTML report containing the selected per-region statistics and the complete metadata for one or more profiling files. It has no plotting dependency, so it is suitable for copying from a compute node and opening later in a browser.
scope-profiler report profiling_data.h5 # writes report.htmlscope-profiler report 'run_*.h5' -o comparison.html --sort callsscope-profiler report profiling_data.h5 -o report.html --include solve --ranks 0-3-o/--output names the HTML file (default: report.html).
--include, --exclude, --ranks, --sort, and --columns use the same
meaning as scope-profiler inspect and control the region-statistics table.
One file gets a full report. It opens with the run’s file, recording time, host and scale, a row of headline numbers - wall time, ranks, regions, the share of the session inside regions, the top hotspot and, for MPI runs, the worst load imbalance - and a few findings worth acting on. Hotspots then ranks the leaves of the call tree by time: regions without nested regions, and the own time a parent spends outside its children, each with the call path it sits on. These add up to the session, so they name the code that costs the time rather than whatever encloses it.
The region-statistics table has the same layout as the terminal summary:
regions indented along the call tree, call counts after the name, and an
(own) row with each parent’s time excluding its children. Clicking a column
header re-sorts the table and drops the tree indentation. Collapse or expand a
region’s nested regions with the arrow beside it. Clicking a row expands it in
place to show the captured call-site source, tags, and a per-rank
calls/total/avg/min/max breakdown.
With more than one rank selected, Load balance lists each region’s own time
per rank - mean, min, max, the slowest rank, its imbalance over the mean, and
the excess time the slowest rank spends above the average, which orders the
table - and Rank comparison sets every rank side by side, coloring each
cell by how far that rank is from the region’s mean. Runs recorded with
line_profiler show each region’s line profile in its table row: clicking the
row shows its per-rank table and then the profiled function’s source as
written and syntax-highlighted, summed over the selected ranks, with the time
on each line and the hottest one highlighted. A long function scrolls inside a
box of limited height, which opens at the hottest line. Functions
whose region is not in the table - left out by --include, say - are listed
in a separate Line profile section instead.
Several files get a comparison report instead, with the first file as the
baseline. It lists the runs with their wall times, shows what changed in each
run against the baseline - the largest own-time improvements and regressions,
ignoring changes too small to be more than noise - and puts every region’s
duration in every run side by side, matched by call path, with a switch between
total, own, per-call time and calls and an ordering by largest change. A full
report for each run is written next to the comparison, as
<output>-<index>-<label>.html, and linked from its run table; pass
--no-individual-reports to skip them.
Metadata is always retained in the report. With scope-profiler[pproc]
installed, reports also embed interactive Plotly charts; pass --no-charts for
the compact table-only version. A single run opens on a region durations
chart, whose bars split each region’s total into its own time and the regions
it calls directly (plain bars for an aggregated profile, which has no
timeline), followed by a timeline, both unfolded, plus a rank-imbalance chart and rank heatmap when more than one
rank is selected. A bar at the top of the page stays there while you scroll.
It holds the region filter and two sliders that limit every chart and table to
the same regions: Regions keeps the N with the most time (it opens on 500 when there
are more; in a comparison, a region’s larger total over the runs counts), and
Depth hides regions nested more than the chosen number of levels below a
top-level call, along with the timeline’s deeper calls. Charts in collapsed panels are drawn when they are opened. For
flame graphs and flame charts, use scope-profiler plot flame_graph and
scope-profiler plot flame_chart. A comparison keeps the charts that compare
runs: grouped region durations and a percent-change chart for exactly two
runs. When the runs differ in MPI ranks or OpenMP threads (the
omp_num_threads metadata), scaling charts come first, for each of the eight
regions with the most time on the smallest run, over MPI ranks when they change
(even if threads change too), otherwise over threads. Speedup (smallest run’s mean call duration over
each run’s, against an ideal proportional to the scale) is for a
strong-scaling study, where every run solves the same total problem.
Weak scaling (the same ratio, read as an efficiency against an ideal of 1)
is for a weak-scaling study, where the problem grows with the run so each
rank or core has the same share. Nothing in a profile records the problem
size, so the report cannot tell the two apart: --scaling strong or
--scaling weak keeps the matching chart, and the default, --scaling both,
shows both with the speedup open and the weak-scaling chart collapsed.
Across run sizes the durations table compares time per rank and the bars show
the mean call, since times summed over ranks grow with the rank count even as
a run gets faster.
# Every run has the same work per rankscope-profiler report 'ranks_*.h5' -o weak.html --scaling weakBoth scaling charts share an x-axis. --speedup-x {auto,ranks,nodes,threads,cores} chooses it: MPI ranks, nodes (the number of
distinct hosts the ranks ran on), OpenMP threads, or cores (ranks × threads).
auto, the default, is the choice described above. A named axis has to be
recorded by every run - files from older scope-profiler versions have no node
count for a multi-rank run - or the command stops with an error naming the
runs that lack it; if every run has the same value, the report says so in
place of the charts. Buttons above each scaling chart switch it to any other
axis the runs differ in, without regenerating the report:
scope-profiler report nodes_*.h5 -o scaling.html --speedup-x nodesEach chart has an Open in new tab button that shows it, as currently filtered and highlighted, on a page of its own. If a profile contains LIKWID or built-in Linux perf events, the report adds hardware-counter tables automatically and plots the first available LIKWID metric. A contents bar, region search, summary links, and expand/collapse controls keep larger reports navigable.
Clicking a region in a table or any chart highlights that region throughout the
report. A chart click leaves the page where it is and offers a Show in
table jump instead; a hotspot click goes straight to the region’s row. Use
Clear highlight beside the search box to reset it.
The charts are built by the same versioned @scope-profiler/plotly code used
for export plot-data on the web. Both that builder and Plotly itself are
embedded in the report, so opening the file never requires a network connection.
That runtime is most of the file: a report is around 4.8 MB almost regardless
of the profile, of which ~4.7 MB is Plotly. Pass --charts-cdn to load Plotly
from https://cdn.plot.ly instead, which brings the same report down to about
80 kB - small enough to email or commit - at the cost of needing a network
connection to draw the charts. The version is pinned to the one this
scope-profiler would have embedded, so both modes render identically, and a
report that cannot reach the CDN says so in place of its charts rather than
failing silently. The tables, statistics and metadata never depend on it.
scope-profiler report profiling_data.h5 -o report.html --charts-cdnA search box at the top of the report filters which regions are shown, in both
the statistics tables and the charts at once. It takes a comma-separated list
of terms, each matched case-insensitively against any part of a region name —
solve, exchange keeps both - and a term prefixed with ^ is anchored to the
start, so ^field keeps field_energy but not apply_field_boundary. An
empty box shows everything. This is the same syntax the profiling-data site
uses, and the charts are refiltered by the builder that drew them rather than
by a second implementation.
scope-profiler site
Section titled “scope-profiler site”scope-profiler site builds a catalogue website from many profiling files:
it writes the pages and chart data of a catalogue into an Astro/Starlight
project, instead of standalone HTML reports. The site’s own build turns them
into a static website, so a code can publish its profiling history on GitHub
Pages with search, a sidebar and a dark mode. The
scope-profiler-catalog
repository is a template for such a site: fork it, fill in a manifest, and let
its workflow fetch the profiling files, run this command and deploy.
scope-profiler site catalogue.yaml -o docsscope-profiler site catalogue.yaml --check # only validate the manifestThe manifest lists cases, each a set of runs shown together:
title: Profiling runs of my codedescription: Profiling runs of the Poisson solver.base: /my-catalogue # the site's base path: "/" plus the repo namedefaults: {scaling: strong, speedup_x: auto}cases: - id: poisson-cube # default: the slug of the title title: Poisson cube, strong scaling description: The solver on a 256^3 cube. group: Poisson # optional: a sidebar group tags: [cpu, scaling] scaling: strong # strong, weak, both or none speedup_x: auto # auto, ranks, nodes, threads or cores info: {commit: 843a82cd, date: 2026-10-16} files: [scripts/run.py] # attached to every run of the case runs: - {path: data/np1.h5, label: 1 rank, files: [params/np1.yml]} - {path: "data/np*.h5", files: ["params/{stem}.yml"]} # globs, relative to the manifestbase is the base path the site is served from (/ plus the repository name on GitHub Pages,
"" at the root of a domain). The pages link to each other with it, because Starlight’s link
check rejects relative links; --base overrides it. Quote values YAML would read as something
else, such as a commit hash in info: commit: "0000000", not commit: 0000000, which is the
number 0.
Per case, include, exclude (region regular expressions), ranks and charts: false select
what the pages show, and metadata: all publishes every metadata field of the profiles (see
below); the same keys under defaults apply to every case. The manifest is checked as a whole
before anything is written, and an error names the case and the field it comes from.
files attaches scripts and parameter files to a run (under the run, or under the case for every
run of it); each is copied into the site, shown on the run’s page with syntax highlighting and a
copy button, and offered for download. In a glob of runs, {stem} stands for the run file’s name
without its extension, so params/{stem}.yml names each run’s own file. Text files up to 200 kB
are shown; larger or binary ones are only offered for download. What info says about a case (a
commit, a system) is shown as metadata, at the end of the case page and ahead of each run’s own
metadata.
A case that is run again and again, such as a nightly job, lists executions instead of runs:
- title: Poisson cube, nightly executions: - {id: "2026-10-16", date: "2026-10-16", info: {commit: d4e5f6a}, runs: [{path: "data/2026-10-16/np*.h5"}]} - {id: "2026-10-17", date: "2026-10-17", info: {commit: e5f6a7b}, runs: [{path: "data/2026-10-17/np*.h5"}]}Instead of listing them, a pattern can find the executions: path has {execution} where each
one’s directory name goes, and every directory holding runs is an execution.
executions: path: "data/poisson-nightly/{execution}/np*.h5"A meta.yml in an execution’s directory (the meta key names another file) may give its label,
date and info, so a nightly job only has to drop its files, and a meta.yml, into a new
directory.
Each execution has an id (default: its label or date), an optional label, date and
info, and its own runs. The case keeps one sidebar entry; its page is the latest execution
(by date when every execution has one, else the last listed), with a table of all executions and
a chart of each run’s wall time over them. Every earlier execution has a page of its own, with the
same comparison and charts, linked from that table. A run is followed over the executions by the MPI ranks it ran on (ranks and
OpenMP threads, or else its label, when an execution has two runs on as many ranks).
images puts images (png, jpg, gif, webp, svg) and PDFs on a page, as a grid of captioned
previews that open in a lightbox (a PDF opens in a new tab): under a run for its page ({stem} works
as for files), under the case for the case page. code shows code at the bottom of the case page,
each entry a path (shown with syntax highlighting), a link, or both:
code: - {path: scripts/run.py, url: "https://github.com/me/code/blob/main/run.py"} - {url: "https://github.com/me/code", label: The code}The command writes, below the -o directory (the Astro project):
src/content/docs/catalogue/: an index of the cases, a page per case and a page per run. Each page is an MDX file made of components, with a JSON data file next to it that holds what the page shows. A run page has everythingscope-profiler reportshows for one run: the headline numbers and findings, the ranked hotspots, the region statistics table (call tree, sortable columns, trend lines, and per region its call site, tags, per-rank breakdown and line profile), load balance and the rank comparison matrix, the line profiles of regions outside the table, hardware counters, the charts, and the metadata. A case page has what the report shows for a comparison: the runs table (folded after the first twelve runs), what changed between two runs (headline numbers and the largest improvements and regressions), the durations table of every region in them (switchable between total, own and per-call time and calls, in call tree order or by the size of the change), and the speedup, weak-scaling, change and duration charts, and the rank imbalance and rank heatmap charts over the compared runs. A study can have hundreds of runs, so the reader picks the two runs to compare, a baseline and a candidate (kept in the URL, so a comparison can be shared); a study of up to twelve runs can also show every run against the baseline, which is off by default. The pair is computed in the browser from a compact dataset on the page, and the charts of several runs draw only the compared ones. The scaling charts’ x-axis can be MPI ranks, nodes, OpenMP threads or their product, for the axes the runs differ in (speedup_xin the manifest picks the one a case opens on), and the durations chart of a run has a “stack children” checkbox, as inscope-profiler plot durations --stack-children.public/catalogue/: the chart data, as the plot-data JSON thatscope-profiler export plot-datawrites (a file per x-axis for the scaling charts), and anindex.jsonof the cases.
The text of the pages, the findings, the classification of changes and the hotspot ranking come
from the same code as the HTML report’s (scope_profiler.report_model), so the two cannot
drift apart. A study’s runs are not listed in the sidebar, which has one entry per case; the case page lists them, and each run
page links the runs either side. Every chart has linear, log2 and log10 scales for its numeric axes.
The Astro project draws the data with the components in
src/components/report/ (the template has them) and the charts with
@scope-profiler/plotly, in the colours
of the site’s theme. As in the report, the filter bar at the top of a page filters the regions of
every table and chart, a click on a region highlights it everywhere, and the Regions and Depth
sliders limit what the charts draw.
Both generated directories are replaced on every run, but only when a previous run wrote them
(they hold a .scope-profiler-site marker); any other content there is never deleted, and
--no-clean writes into the directories as they are. A published page shows only metadata that
identifies the run (time, platform, chip, versions, threads, ranks, nodes), never the host, user
or environment, unless the case sets metadata: all.
Reading the manifest needs PyYAML and the charts need scope-profiler[pproc];
pip install "scope-profiler[site]" installs just the former and
--no-charts writes pages without charts.
scope-profiler diff
Section titled “scope-profiler diff”Compare region statistics between two merged HDF5 profiling files, region by region, so a regression (or an improvement) between two runs - two commits, two configs, two job sizes - shows up as a single table.
usage: scope-profiler diff [-h] [--include INCLUDE [INCLUDE ...]] [--exclude EXCLUDE [EXCLUDE ...]] [--ranks RANKS [RANKS ...]] [--metric {total,avg,min,max,p50,p95,p99,imbalance,calls}] [--sort {delta,pct,name}] [--threshold PCT] file_a file_b| Flag | Description |
|---|---|
--include | Only compare regions matching these regex patterns |
--exclude | Skip regions matching these regex patterns |
--ranks | Restrict the statistics to these ranks, e.g. 0 2 or 0-3 |
--metric | Statistic to compare: total (default), avg, min, max, p50, p95, p99, imbalance or calls |
--sort | Order regions by descending ` |
--threshold | Only show regions whose absolute percent change is at least this many percent |
scope-profiler diff baseline.h5 candidate.h5scope-profiler diff baseline.h5 candidate.h5 --metric avg --threshold 5scope-profiler check
Section titled “scope-profiler check”Use check in CI to fail when a candidate exceeds a performance budget:
scope-profiler check baseline.h5 candidate.h5 --max-regression 5The command prints the comparison table and returns exit code 1 when any
region is more than 5% slower. Use --fail-on-new to treat newly appearing
regions as failures, and --metric p95 or --metric imbalance to enforce a
tail-latency or MPI-balance budget.
Example output:
Baseline: ../../../../..baseline.h5 (1 rank)Candidate: ../../../../..candidate.h5 (1 rank)Regression budget: +5% (total)Regression check: FAIL ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │ region total [s] (a) total [s] (b) delta delta [%] │ ├────────────────────────────────────────────────────────────────────────────────────────────┤ │ solve 0.00305883 0.00606233 +0.0030035 +98.19% │ │ scope_profiler.session 0.00419357 0.00826587 +0.00407229 +97.11% │ │ setup 0.00106451 0.00106334 -1.162e-06 -0.1092% │ │ teardown - 0.00106023 +0.00106023 - │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ Only in b: teardown
Budget violations: solve, scope_profiler.sessionA region present in only one file still gets a delta (treating the missing
side as 0 calls), but a percent change is only reported when the file it is
missing from is b - dropping out from a nonzero baseline in a is a
well-defined -100%, while a region appearing fresh in b has no baseline to
divide by, and is listed under “Only in b” instead. --threshold never drops
those regions, since there is nothing to compare against.
scope-profiler plot
Section titled “scope-profiler plot”Post-process one or more HDF5 profiling files and render a named plot or plot
preset. For text/JSON summaries (including LIKWID hardware counters), see
scope-profiler inspect above instead. For machine-readable exports without
rendering charts, see scope-profiler export below.
usage: scope-profiler plot [-h] {list,default,all,quick,gantt,flame_chart,flame_graph, durations,timeseries,speedup,histogram,imbalance,likwid} ...scope-profiler plot list prints the available plot kinds and presets.
Built-in Linux counter profiles can be plotted with a recorded event name or one of the derived metrics:
scope-profiler plot perf_events perf_events_example.h5 \ --metric ipc -o figures/perf_ipc.pngscope-profiler plot perf_events perf_events_example.h5 \ --metric cache-misses-per-ki -o figures/perf_cache_misses.pngPositional arguments
Section titled “Positional arguments”| Argument | Description |
|---|---|
kind | One of default, all, quick, or a plot kind |
files | Path(s) or glob patterns for profiling_data.h5 files |
default renders gantt and the total durations plot. quick renders
durations and speedup.
all renders every plot except likwid; pass --metric to include likwid.
Selecting data
Section titled “Selecting data”| Flag | Description |
|---|---|
--label | Override a file’s display label in the outputs (repeat once per file, in order) |
-i, --include | Region names to include (regex patterns) |
-e, --exclude | Region names to exclude (regex patterns) |
-r, --ranks | Ranks to include; supports ranges (e.g. 0-3,5) |
Choosing and rendering plots
Section titled “Choosing and rendering plots”| Flag | Description |
|---|---|
--show | Display the plot interactively (default: off) |
-o, --output | Directory to save generated outputs; for a single plot kind this may be a target .png, .html, or .txt file |
--backend | Renderer: matplotlib (default, writes .png), plotly (writes interactive .html), or plotext (writes terminal .txt for simple plots) |
--cmap | Matplotlib colormap used to color regions/files in all plots (default: tab20) |
Plot-specific options
Section titled “Plot-specific options”| Flag | Plot(s) | Description |
|---|---|---|
--metrics | durations | Duration statistics to draw as bar columns: any of avg, min, max, total, first, last (default: total). first and last are the chronologically first and last call’s duration, which separate one-off warmup - JIT, allocation, the first device transfer - from steady state |
--sort-by | durations, likwid | Order the bar chart’s regions by this statistic, descending (name sorts alphabetically). Default: order of first appearance |
--top-n | durations, likwid | Keep only the top N regions after --sort-by |
--combine-regions | durations | Merge several regions into one bar: NAME=PATTERN1,PATTERN2 (repeat once per group). Region names are matched against the comma-separated regexes like --include; a region matched by more than one group is claimed by whichever group is listed first |
--stack-children | durations | Split each bar into the region’s own (exclusive) time plus one stacked segment per region called directly from it, so a bar shows where its time went. Nesting is reconstructed from timestamps, as in the flame chart; children filtered out of the bars still get a segment. Only total and avg decompose this way |
--log-scale | durations, timeseries, histogram, imbalance, likwid | Logarithmic y-axis |
--bins | histogram | Number of duration bins (default: 30) |
--metric | imbalance | Per-call duration statistic plotted per rank: any of avg, min, max, total (default: total) |
--metric | likwid | Name of the LIKWID derived metric or raw event to plot, e.g. CPI, MFlops/s |
--x | speedup, weak_scaling, weak_scaling_efficiency, scaling_efficiency | X-axis: num_ranks (default), num_nodes, omp_num_threads, total_cores, or any other metadata field |
--work-per-rank | weak_scaling_efficiency | Work per rank in each file, in the order the files are given, in any unit (grid cells, particles, unknowns). Checked to be equal across the files: weak-scaling efficiency compares runs that each do the same work, so unequal values are an error rather than a misleading curve |
When -o/--output is supplied, the CLI saves one <name>_plot.png per plot
selected by the plot kind or preset (durations_plot.png becomes one
durations_plot_<metric>.png per metric when --metrics requests
several, e.g. durations_plot_avg.png, durations_plot_total.png), plus
region_statistics.json. speedup is skipped unless multiple files are
passed. With --backend plotly the plots are written as .html instead of
.png.
The plotext backend is available for simple plots (durations, timeseries,
speedup, weak_scaling, weak_scaling_efficiency, scaling_efficiency,
histogram, and imbalance).
It writes plain terminal renderings as .txt files. Complex plots such as
Gantt charts, flame charts, flame graphs, heatmaps, and LIKWID charts remain on the
Matplotlib or Plotly backends.
For multiple files, the JSON includes per-file region statistics and the set of common regions across all inputs.
scope-profiler export
Section titled “scope-profiler export”Export profiling data without rendering plot images.
usage: scope-profiler export [-h] {prof,speedscope,chrome-trace,json,plot-data} ...Plot data
Section titled “Plot data”plot-data writes the raw data behind selected charts as CSV or JSON, so plots
can be reconstructed later without the original HDF5 files:
scope-profiler export plot-data profiling_data.h5 -o data --format jsonscope-profiler export plot-data run_1.h5 run_2.h5 -o data \ --plots durations speedup --format jsonWith JSON, each data file includes a colors map matching the plot colors.
plot-data supports the same data-selection flags as plot, plus --plots,
--format, --metrics, --bins, --imbalance-metric, --likwid-metric,
and --x.
Exporting to .prof for snakeviz
Section titled “Exporting to .prof for snakeviz”export prof writes the profile in the format cProfile uses, so regions
can be browsed with any pstats-based viewer:
scope-profiler export prof profiling_data.h5 -o figuressnakeviz figures/profile_rank0.profOne file is written per exported rank (profile_rank0.prof, …; only the ranks
selected with -r/--ranks are exported, default rank 0), prefixed with the
input file’s stem when several HDF5 files are passed. Regions become
“functions”, cumtime is a region’s total wall time and tottime is that
minus the time spent in its nested regions, with a synthetic
<file rank N> frame as the root of the tree. Regions that captured a Python
source location are keyed by it, so viewers link back to the source line.
Since regions carry no call graph, the caller/callee relations are reconstructed from timestamp containment, exactly as the flame chart does. So:
- By default each distinct call path is its own entry, named
parent > child, so a region used below two different parents stays two nodes in the tree. Pass--no-call-pathsto merge every call of a region into one entry instead, which is how pstats keys a realcProfilerun; recursion is then reported as2/1-style call counts, likecProfile. - Regions must nest: intervals that only partially overlap (async work,
threads) are rejected with a
NestingErrorrather than being silently attributed to whichever region enclosed their start. - LIKWID counters have no place in the
.profformat and are left out.
The same data is available in memory, without writing a file, through
scope_profiler.to_pstats(), which returns a pstats.Stats per rank.
Exporting a flame graph as SVG
Section titled “Exporting a flame graph as SVG”export flamegraph renders the same reconstructed call tree as a standalone
SVG, through flameprof (part of the
pproc extra):
scope-profiler export flamegraph profiling_data.h5 -o figuresThis is the aggregated view — every call of a region below one parent is a
single frame, sized by cumulative time — the same thing plot flame_graph
draws, but as a self-contained document with no JavaScript and no plotting
stack, which is what a static site or an emailed report wants. Hovering a
frame reports its percentage, call count, tottime and cumtime.
One file is written per exported rank (flamegraph_rank0.svg, …; only the
ranks selected with -r/--ranks, default rank 0), prefixed with the input
file’s stem when several HDF5 files are passed. --no-call-paths merges every
call of a region into one frame, as for export prof.
By default the SVG carries a viewBox rather than a fixed pixel size, and no
XML prolog, so a page can inline it and scale it to whatever element holds it
— inlining being the only way the per-frame tooltips survive, since an
<img> renders the SVG without its hover handlers. Pass --fixed-width for a
standalone file instead, --width to change the aspect ratio and the point at
which frame labels are elided, and --threshold to drop frames below a share
of total time (default: 0.1%).
The same rendering is available from Python as
scope_profiler.export_flamegraph_svg().
Exporting to JSON
Section titled “Exporting to JSON”export json writes the whole run as a JSON document — the same data the
HDF5 file holds, in a format anything can read:
scope-profiler export json profiling_data.h5 -o exportsscope-profiler export json profiling_data.h5 -o exports --gzip --indent 2Unlike prof and speedscope, this is a lossless copy rather than a view for
a particular viewer: every rank is exported by default (-r/--ranks narrows
it), timestamps stay in the integer nanoseconds the run recorded, and
scope_profiler.read_json() rebuilds a ProfilingResults indistinguishable
from the one read_h5() gives back. --gzip writes profile.json.gz, which
is what a real run wants: the event columns are the bulk of the document and
compress by roughly an order of magnitude. --indent N pretty-prints it for
reading by eye or diffing in review. One file is written per input file, with
the run’s label added to the name when several are passed.
Exporting to speedscope
Section titled “Exporting to speedscope”export speedscope writes a speedscope JSON
file. Where .prof keeps aggregates per region, speedscope keeps every
individual call, so the timeline shows the run as it actually happened —
closest in spirit to the Gantt and flame charts, but interactive:
scope-profiler export speedscope profiling_data.h5 -o figuresThen open figures/profile.speedscope.json at https://www.speedscope.app
(the file never leaves the browser), or run npx speedscope figures/profile.speedscope.json. Its three views are all useful here: “Time
Order” is the run’s timeline, “Left Heavy” aggregates identical call paths
(the flame graph), and “Sandwich” ranks regions by self and total time.
One file is written per input HDF5 file, holding one profile per exported rank
(only the ranks selected with -r/--ranks, default rank 0) — the format
carries several profiles per file, and speedscope switches between them from
the dropdown in its top bar. Every profile in a file shares one time origin, so
ranks stay aligned with each other. The stem of the input file is added to the
name when several HDF5 files are passed.
Exporting to Chrome Trace / Perfetto
Section titled “Exporting to Chrome Trace / Perfetto”export chrome-trace writes Chrome Trace Event JSON. Each rank is a process;
tracked threads and asyncio tasks become separate lanes. Region durations are
complete events, with GPU and await measurements in event arguments. Aggregate
Linux perf-event values are emitted as counter events at the end of the rank
timeline.
scope-profiler export chrome-trace profiling_data.h5 -o figuresOpen figures/profile.trace.json in Perfetto or
chrome://tracing. The export is dependency-free and does not require the
optional plotting dependencies.
The call graph is reconstructed from timestamp containment, as for .prof,
with one extra consequence: speedscope replays the events as a stack machine,
so a region that starts inside another but ends after it is clipped to its
parent instead of overhanging it. The same caveats otherwise apply — regions
called from several places and recursion behave as described above.
Examples
Section titled “Examples”Save plots for a single file:
scope-profiler plot profiling_data.h5 -o figures/Omitting the plot kind selects the default preset (gantt and total
durations). The explicit equivalent is scope-profiler plot default profiling_data.h5 -o figures/.
Compare multiple files:
scope-profiler plot default run_1.h5 run_2.h5 run_4.h5 -o figures/Select files via wildcard patterns:
scope-profiler plot default files/*.h5 -o figures/scope-profiler plot default "files/file_*.h5" -o figures/Display interactively with region filtering:
scope-profiler plot profiling_data.h5 --show \ --include "solver.*" "rhs.*" \ --exclude "io"Select specific MPI ranks:
scope-profiler plot default profiling_data.h5 --show --ranks 0-3 8The --ranks flag accepts comma-separated values and dash ranges that
can be combined: 0,2,4-7 expands to ranks 0, 2, 4, 5, 6, 7.
Only export average and total duration plots:
scope-profiler plot durations profiling_data.h5 -o figures/ --metrics avg totalOnly generate the duration bar chart and speedup plot:
scope-profiler plot quick run_1.h5 run_2.h5 run_4.h5 -o figures/