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 use scope-profiler[tui]; line profiling needs scope-profiler[line-profiler].

All subcommands live under the single scope-profiler executable (also runnable as python -m scope_profiler).

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.h5
scope-profiler inspect pytest-profile.h5 --regions-only --sort total
scope-profiler plot durations pytest-profile.h5 -o pytest-plots

Only 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#

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.h5

Simple 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#

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] [--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

--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#

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 profile
scope-profiler run -o profile.json.gz my_script.py  # the same, gzip-compressed
scope-profiler run -o report.html my_script.py      # a rendered HTML report

Use --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.

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#

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-only
scope-profiler inspect 'run_*.h5' --regions-only --sort calls
scope-profiler inspect profiling_data.h5 --regions-only --columns region ranks calls total avg
scope-profiler inspect profiling_data.h5 --export-metadata metadata.json --quiet
scope-profiler inspect profiling_data.h5 --source solve assemble

Example output:

==============================================================================
profiling_data.h5
1 rank(s), 3 region(s), 0.05 MiB, 0.00287576 s wall clock, 0.0041239 s total (setup to finalize)
==============================================================================

  ╭─────────────────────────────────────────────────────────╮
  │ region                       % session      total [s]   │
  ├─────────────────────────────────────────────────────────┤
  │ scope_profiler.session       100.00%        0.002876    │
  │ └─ (own)                        45.51%         0.001309 │
  │ └─ matrix_multiply (100x)       5.03%          0.000145 │
  │ └─ time_step (1kx)              49.46%         0.001422 │
  ╰─────────────────────────────────────────────────────────╯

  ╭─ 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#

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 assemble
Source (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#

--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 written

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.html
scope-profiler report 'run_*.h5' -o comparison.html --sort calls
scope-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 rank
scope-profiler report 'ranks_*.h5' -o weak.html --scaling weak

Both 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 nodes

Each 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-cdn

A 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 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.h5
scope-profiler diff baseline.h5 candidate.h5 --metric avg --threshold 5

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 5

The 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.00306822      │ 0.00606759      │ +0.00299937 │ +97.76%     │
  │ scope_profiler.session │ 0.00432668      │ 0.00846089      │ +0.0041342  │ +95.55%     │
  │ setup                  │ 0.00108717      │ 0.00107669      │ -1.0479e-05 │ -0.9639%    │
  │ teardown               │ -               │ 0.00107405      │ +0.00107405 │ -           │
  ╰────────────────────────┴─────────────────┴─────────────────┴─────────────┴─────────────╯
  Only in b: teardown

Budget violations: solve, scope_profiler.session

A 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#

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.png
scope-profiler plot perf_events perf_events_example.h5 \
  --metric cache-misses-per-ki -o figures/perf_cache_misses.png

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#

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#

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#

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#

Export profiling data without rendering plot images.

usage: scope-profiler export [-h] {prof,speedscope,chrome-trace,json,plot-data} ...

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 json
scope-profiler export plot-data run_1.h5 run_2.h5 -o data \
    --plots durations speedup --format json

With 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#

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 figures
snakeviz figures/profile_rank0.prof

One 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-paths to merge every call of a region into one entry instead, which is how pstats keys a real cProfile run; recursion is then reported as 2/1-style call counts, like cProfile.

  • Regions must nest: intervals that only partially overlap (async work, threads) are rejected with a NestingError rather than being silently attributed to whichever region enclosed their start.

  • LIKWID counters have no place in the .prof format 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#

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 figures

This 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#

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 exports
scope-profiler export json profiling_data.h5 -o exports --gzip --indent 2

Unlike 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#

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 figures

Then 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#

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 figures

Open 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#

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 8

The --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 total

Only generate the duration bar chart and speedup plot:

scope-profiler plot quick run_1.h5 run_2.h5 run_4.h5 -o figures/