3. Visualizing results#

Install for this tutorial: pip install "scope-profiler[pproc]".

scope-profiler ships a growing set of chart types, all built on maxplotlib:

function

answers

plot_gantt

when did each call happen, on which rank?

plot_flame

how does time break down across nested regions?

plot_durations

which regions cost the most (total / avg / min / max)?

plot_speedup

how does a region scale with ranks or threads?

plot_weak_scaling

does runtime stay constant as the workload scales?

They need the pproc extra:

pip install "scope-profiler[pproc]"

Every function takes a results (or a list of runs), include / exclude filters, a filepath to save to, show=True to display, and a backend of either "matplotlib" (default) or "plotly" (interactive).

import tempfile
import time
from pathlib import Path

from scope_profiler import ProfileManager, read_h5

WORKDIR = Path(tempfile.mkdtemp(prefix="scope-profiler-tutorial-"))
DATA_FILE = WORKDIR / "profiling_data.h5"

ProfileManager.setup(file_path=str(DATA_FILE))

with ProfileManager.region("setup"):
    time.sleep(0.02)

for step in range(4):
    with ProfileManager.region("timestep"):
        with ProfileManager.region("assemble"):
            time.sleep(0.006)
        with ProfileManager.region("solve"):
            time.sleep(0.010)
        with ProfileManager.region("output"):
            time.sleep(0.002)

ProfileManager.finalize(verbose=False)
results = read_h5(DATA_FILE)
results.print_summary()
  ╭─────────────────────────────────╮
  │ region              total [s]   │
  ├─────────────────────────────────┤
  │ setup               0.020082    │
  │ timestep (4x)       0.073190    │
  │ └─ (own)               0.000301 │
  │ └─ assemble (4x)       0.024301 │
  │ └─ solve (4x)          0.040299 │
  │ └─ output (4x)         0.008290 │
  ╰─────────────────────────────────╯

  ╭─ Info ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
  │ Summary: ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5 (1 rank)                                      │
  │                                                                                                                                 │
  │ Explore:                                                                                                                        │
  │   Inspect: scope-profiler inspect ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5                      │
  │   TUI:     scope-profiler tui ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5                          │
  │                                                                                                                                 │
  │ Visualize and export:                                                                                                           │
  │   Plot:    scope-profiler plot default ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5 -o plots --show │
  │   Report:  scope-profiler report ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5 -o report.html        │
  │   Export:  scope-profiler export plot-data ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/profiling_data.h5 -o data     │
  │   Lines:   scope-profiler line-profile ../../../../../../tmp/scope-profiler-tutorial-b_4gvaky/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.                                                           │
  ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

In a notebook, pass show=True to display each chart directly. You can still pass filepath when you also want to save a chart. (PNG export with the default matplotlib backend needs nothing extra; the plotly backend writes self-contained .html, or .png if kaleido is installed.)

from scope_profiler import plot_durations, plot_flame, plot_gantt

Gantt chart — what ran when#

Each call becomes a bar on a timeline, with one lane per rank. This is the chart for spotting gaps, stragglers and serialization.

plot_gantt(results, show=True, verbose=False)

Flame chart — where the time goes#

The flame chart reconstructs the nesting from the timestamps: a region drawn on top of another ran inside it. It covers rank 0 by default, since it represents a single execution’s call stack; pass ranks=[...] for one chart per rank.

plot_flame(results, show=True, verbose=False)

Duration charts — which region is expensive#

plot_durations draws one bar chart per metric. By default it produces all of total, avg, min and max, writing one file per metric and returning the path it wrote; pass metric= to choose the statistic.

plot_durations(results, metric="total", show=True, verbose=False)
plot_durations(results, metric="avg", show=True, verbose=False)

Filtering and colors#

include / exclude are regexes matched against region names, and cmap picks any matplotlib colormap. Region colors are consistent across chart types, so a region keeps its color between the Gantt and flame views.

plot_gantt(
    results,
    include=["solve", "assemble"],
    cmap="viridis",
    show=True,
    verbose=False,
)

Speedup charts#

plot_speedup compares the same region across several files and plots speedup against a metadata field — num_ranks (default), omp_num_threads or total_cores. It needs at least two files, so it is normally fed a scaling study:

runs = [read_h5(f"run_{n}ranks.h5") for n in (1, 2, 4, 8)]
plot_speedup(runs, x_field="num_ranks", filepath="speedup.png")

To keep this notebook serial, the cell below writes three small HDF5 files by hand that imitate a 1/2/4-thread run — the shape of a real scaling study without needing the cores.

import h5py
import numpy as np

from scope_profiler import plot_speedup

scaling_files = []
for threads in (1, 2, 4):
    path = WORKDIR / f"scaling_{threads}.h5"
    duration_ns = int(0.4e9 / threads)  # perfect scaling, for illustration
    with h5py.File(path, "w") as handle:
        meta = handle.create_group("metadata")
        meta.attrs["omp_num_threads"] = threads
        meta.attrs["mpi_size"] = 1
        meta.attrs["total_cores"] = threads
        group = handle.create_group("rank0/regions/solve")
        group.create_dataset("start_times", data=np.array([0], dtype=np.int64))
        group.create_dataset("end_times", data=np.array([duration_ns], dtype=np.int64))
    scaling_files.append(path)

plot_speedup(
    [read_h5(path) for path in scaling_files],
    x_field="omp_num_threads",
    show=True,
    verbose=False,
)

Exporting the plotted numbers#

Every plotting function takes data_filepath (and data_format of "csv" or "json") to write out exactly the values it drew — handy when you want the chart in one tool and the numbers in another.

plot_durations(
    results,
    metric="total",
    filepath=str(WORKDIR / "durations_total.png"),
    data_filepath=WORKDIR / "durations.csv",
    data_format="csv",
    verbose=False,
)
print((WORKDIR / "durations.csv").read_text())
file,region,metric,value_seconds
profiling_data,setup,total,0.020081909
profiling_data,timestep,total,0.07319041400000001
profiling_data,assemble,total,0.024300772
profiling_data,solve,total,0.040298538999999994
profiling_data,output,total,0.008290463

The same thing from the command line#

Everything above is also available through the CLI, which is usually what you want on a cluster:

scope-profiler plot default profiling_data.h5 -o figures
scope-profiler plot default profiling_data.h5 -o figures --backend plotly --cmap viridis
scope-profiler plot weak_scaling 'run_*.h5' -o figures
scope-profiler export plot-data 'run_*.h5' -o figures --format json

plot default accepts several files (or a glob) and writes Gantt and duration charts and, for multiple files, a speedup chart, plus region_statistics.json.

Next#

4. Profiling modes covers the configuration options that decide what gets recorded in the first place.