Releases: alubbock/microbench
v2.0.0
Microbench v2 is a significant upgrade with many new features versus v1.1.0.
Be sure to review the breaking changes before upgrading.
New features
-
Command-line interface (
microbench -- COMMAND): wrap any external
command and record host metadata alongside timing without writing Python
code. Useful for SLURM jobs, shell scripts, and compiled executables.- Records
command,returncode(list, one per timed iteration),
alongside the standard timing fields. Mixins are specified by short
kebab-case names without theMBprefix (e.g.host-info);
original MB-prefixed names are also accepted. - Use
--mixin MIXIN [MIXIN ...]to select metadata to capture (defaults to
host-info,slurm-info,loaded-modules,python-info
andworking-dir) - Use
--show-mixinsto
list all available mixins with descriptions; use--field KEY=VALUEto
attach extra labels - Use
--iterations Nand--warmup Nfor repeat
timing - Use
--stdout[=suppress]and--stderr[=suppress]to capture
subprocess output into the record (output is re-printed to the terminal
unless=suppressis given) - Use
--monitor-interval SECONDSto sample
child process CPU and memory over time. - Some mixins expose
their own configuration flags (shown in--show-mixinsand--help) - Capture failures are non-fatal by default
(capture_optional = True), making the CLI safe across heterogeneous
cluster nodes. - The process exits with the first non-zero returncode seen
across all timed iterations if present, or zero (success) otherwise.
- Records
-
summary(results)/bench.summary(): prints min / mean / median /
max / stdev ofcall.durationsacross all results. No dependencies required
beyond the Python standard library.bench.summary()is a one-liner
convenience that callsbench.get_results()internally. The module-level
summary(results)accepts any list of dicts and can be composed with other
results-processing steps.from microbench import MicroBench, summary bench = MicroBench() @bench def my_function(): ... for _ in range(10): my_function() bench.summary() # n=10 min=0.000031 mean=0.000038 median=0.000036 max=0.000059 stdev=0.000008 # or with explicit results list: summary(bench.get_results())
-
bench.time(name)sub-timing: [Python API] label phases inside a single benchmark
record with named timing sections. Sub-timings accumulate incall.timingsas
[{"name": ..., "duration": ...}, ...]in call order. Compatible with
bench.record(),bench.arecord(),@bench(sync and async), and
bench.record_on_exit(). Calling outside an active benchmark is a silent
no-op;call.timingsis absent whenbench.time()is never called.with bench.record('pipeline'): with bench.time('parse'): data = parse(raw) with bench.time('transform'): result = transform(data)
-
Async support: [Python API] the
@benchdecorator now detectsasync deffunctions
and returns anasync defwrapper that must be awaited. A new
bench.arecord(name)method provides the async counterpart of
bench.record()for use withasync with. All mixins, static fields,
output sinks,iterations, andwarmupwork identically to the sync path.
MBLineProfilerraisesNotImplementedErrorat decoration time when used
with an async function (line profiling of coroutines is not supported).@bench async def fetch(): await asyncio.sleep(0.01) asyncio.run(fetch()) async with bench.arecord('load'): await load_data()
Note: elapsed time includes event-loop interleaving from other concurrent
tasks; run in an otherwise-idle event loop for repeatable results. -
bench.record_on_exit(name, handle_sigterm=True): [Python API]
registers a
process-exit handler that writes one benchmark record when the script
terminates. Captures wall-clock duration from the call site to exit plus
all mixin fields. Designed for SLURM jobs and batch scripts where
restructuring code around a decorator is impractical. Key behaviours:- By default installs a SIGTERM handler (main thread only) that writes
the record, chains to any existing SIGTERM handler, then re-delivers
SIGTERM so the process exits with the conventional code 143 (128 + 15). - Wraps
sys.excepthookto capture unhandled exceptions into an
exceptionfield before the process exits. - Adds an
exit_signalfield when the exit was triggered by SIGTERM. - Falls back to writing the record to
sys.stderrif the primary output
sink raises (e.g. filesystem unmounted at exit time). - Calling a second time on the same instance replaces the first
registration and resets the start time.
- By default installs a SIGTERM handler (main thread only) that writes
-
bench.record(name)context manager: [Python API] times an arbitrary code block
and writes one record, without requiring the code to be in a named
function. All mixins, static fields, and output sinks behave identically
to the decorator form. -
Exception capture: [Python API] when a benchmarked block raises — via
bench.record()or a@bench-decorated function — the record is
written before the exception propagates. Anexceptionfield is added
containing{"type": ..., "message": ...}. The exception is always
re-raised. With--iterations N, timing stops at the first exception. -
MBPythonInfomixin replacesMBPythonVersion: records apythondict
withversion,prefix(sys.prefix), andexecutable(sys.executable),
giving a complete picture of the running interpreter in one field.MBPythonVersion
has been removed (see breaking changes above).MBPythonInfois included
inMicroBenchby default (Python API) and in the CLI default mixin set;
--no-mixinsuppresses it on the CLI as usual. -
MBLoadedModulesmixin: captures the loaded Lmod / Environment
Modules software stack into aloaded_modulesdict mapping module name
to version string (e.g.{"gcc": "12.2.0", "openmpi": "4.1.5"}). Reads
the standardLOADEDMODULESenvironment variable — no subprocess, no
extra dependencies. Empty dict when no modules are loaded. Included in
the CLI defaults alongsideMBHostInfo,MBSlurmInfo, andMBWorkingDir. -
MBWorkingDirmixin: captures the absolute path of the working
directory at benchmark time intocall.working_dir. No dependencies.
Included in the CLI defaults — useful for reproducibility when comparing
results across nodes or directories. -
MBGitInfomixin: captures the repository root path, current commit
hash, branch name, and dirty flag (uncommitted changes present) via
git≥ 2.11 on PATH. Stored ingit. Setgit_repoto inspect
a specific repository directory. -
MBPeakMemorymixin: captures peak Python memory allocation during the
benchmarked function ascall.peak_memory_bytes(bytes), using
tracemallocfrom the standard library. No extra dependencies required. -
MBSlurmInfomixin: captures allSLURM_*environment variables into
aslurmdict (keys lowercased,SLURM_prefix stripped). Empty dict
when running outside a SLURM job. Supersedes the manual
env_vars = ('SLURM_JOB_ID', ...)pattern. -
MBFileHashmixin: records a cryptographic checksum of specified files
in thefile_hashesfield (a dict mapping path to hex digest). Defaults to
hashingsys.argv[0]— the running script. Sethash_filesto an iterable
of paths to hash specific files instead. Sethash_algorithmto any
algorithm accepted byhashlib.new(default:'sha256'). -
MBCgroupLimitsmixin: captures the CPU quota and memory limit enforced by
the Linux cgroup filesystem. Works for SLURM jobs and Kubernetes pods (cgroup
v1 and v2). Fields incgroups:cpu_cores_limit(float — quota ÷ period,
ornullif unlimited),memory_bytes_limit(int ornullif unlimited),
version(1 or 2). Returns{}on non-Linux systems or when the
cgroup filesystem is unavailable.class Bench(MicroBench, MBSlurmInfo, MBCgroupLimits): pass
{ "cgroups": { "cpu_cores_limit": 4.0, "memory_bytes_limit": 17179869184, "version": 2 } } -
MBCondaPackagesimprovements:- Queries the environment identified by
CONDA_PREFIX(the shell's active conda
environment) rather thansys.prefix. Falls back tosys.prefixwhen
CONDA_PREFIXis not set. - Falls back to
CONDA_EXEifcondais not onPATH(common in non-interactive
SLURM batch scripts where conda is activated but itsbin/is not onPATH). - Replaces the separate
conda_versionsfield with a unifiedcondadict
containingname(CONDA_DEFAULT_ENV),path(CONDA_PREFIX), and
packages(the version dict). Either ofname/pathmay beNoneif
the corresponding variable is unset. Withget_results(flat=True)these
expand toconda.name,conda.path,conda.packages.<pkg>etc.
- Queries the environment identified by
-
MicroBenchBase: the core benchmarking machinery is now exposed as
MicroBenchBase(no default mixins).MicroBenchinherits from both
MicroBenchBaseandMBPythonInfo. SubclassMicroBenchBasedirectly when
you need a completely bare benchmark class with no automatic captures. -
warmupparameter: passwarmup=Nto run the functionNtimes
before timing begins, priming caches or JIT compilation without affecting
results. Warmup calls are unrecorded and do not interact with the monitor
thread or capture triggers. -
Multi-sink output architecture (#52): Results can now be written to
multiple d...
v1.1.0
What's new
mb_run_idandmb_versionfields added to every record — no configuration required.mb_run_id— UUID generated once at import time, shared by allMicroBenchinstances in the same process. Usegroupby('mb_run_id')to correlate records from independent bench suites within the same run.mb_version— version of themicrobenchpackage that produced the record; useful for long-running studies where benchmark code evolves.
Upgrading from v1.0
No changes required. The two new fields appear automatically in all existing benchmark suites.
v1.0.0
v1.0.0
First stable release of microbench.
Highlights since last PyPI release
Bug fixes
- Fix MBCondaPackages double-concatenation of version strings (#28)
- Fix inverted set difference in MBNvidiaSmi attribute validation (#29)
- Fix test_multi_iterations using wrong timezone parameter name (#30)
- Fix
selfinstead ofclsin MBLineProfiler classmethod (#27) - Fix MBFunctionCall double-encoding args/kwargs as JSON strings (#34)
- Fix TelemetryThread crash when called from non-main thread (#32)
- Fix CSS syntax error in diff.py (#26)
- Fix nvidia_gpus rejecting integer GPU indexes (#43)
- Fix MicroBenchRedis.get_results() returning empty DataFrame (#45)
- Fix psutil.cpu_count() returning None on macOS with psutil 7.x (#49)
- Remove overly restrictive nvidia attribute allowlist (#48)
- Drop internal conda.testing API, use subprocess (#44)
Features
- Implement get_results() for MicroBenchRedis (#45)
- Capture both logical and physical CPU core counts (#49)
Code quality
- Migrate to pyproject.toml + setuptools-scm (#39)
- Remove Python 2 patterns (#38)
- Add ruff linter/formatter with pre-commit hooks (#41)
- Add
__all__export list (#37) - Fix LiveStream exit condition (#36)
- Robust stack-frame walking in MBGlobalPackages (#35)
- Add pickle security warning for MBLineProfiler (#33)
- Expand test coverage from 78% to 88% (#42)
- Add explicit workflow permissions (#40)
- Remove dead Python <3.9 fallback (#46)
Documentation
Requirements
- Python >= 3.10
- No required dependencies (pandas recommended for results analysis)
v1.0.0-rc.1
v1.0.0 Release Candidate 1
First release candidate for microbench v1.0.
Highlights since last release
Bug fixes
- Fix MBCondaPackages double-concatenation of version strings (#28)
- Fix inverted set difference in MBNvidiaSmi attribute validation (#29)
- Fix test_multi_iterations using wrong timezone parameter name (#30)
- Fix
selfinstead ofclsin MBLineProfiler classmethod (#27) - Fix MBFunctionCall double-encoding args/kwargs as JSON strings (#34)
- Fix TelemetryThread crash when called from non-main thread (#32)
- Fix CSS syntax error in diff.py (#26)
- Fix typo "fuctionality" (#25)
- Fix nvidia_gpus rejecting integer GPU indexes (#43)
- Fix MicroBenchRedis.get_results() returning empty DataFrame (#45)
- Fix psutil.cpu_count() returning None on macOS with psutil 7.x (#49)
- Remove overly restrictive nvidia attribute allowlist (#48)
- Drop internal conda.testing API, use subprocess (#44)
Features
- Implement get_results() for MicroBenchRedis (#45)
- Capture both logical and physical CPU core counts (#49)
Code quality
- Migrate to pyproject.toml + setuptools-scm (#39)
- Remove Python 2 patterns (#38)
- Add ruff linter/formatter with pre-commit hooks (#41)
- Add all export list (#37)
- Fix LiveStream exit condition (#36)
- Robust stack-frame walking in MBGlobalPackages (#35)
- Add pickle security warning for MBLineProfiler (#33)
- Expand test coverage from 78% to 88% (#42)
- Add explicit workflow permissions (#40)
- Remove dead Python <3.9 fallback (#46)
Documentation
Requirements
- Python >= 3.10
- No required dependencies (pandas recommended for results analysis)
v0.9.1
This release should fix installation of microbench from pypi with python 3.12.
What's Changed
- chore: Configure Renovate by @renovate in #11
- chore(deps): update actions/checkout action to v4 by @renovate in #12
- chore(deps): update actions/setup-python action to v5 by @renovate in #13
- chore: upgrade versioneer to support python 3.12 by @alubbock in #16
New Contributors
Full Changelog: v0.9...v0.9.1
v0.9
What's Changed
- feat: multiple iterations of functions by @alubbock in #7
the wrapped function can now be evaluated multiple times, with timings given in a newrun_durationsfield. - feat: customisable duration timer functions by @alubbock in #10
the newrun_durationsfield usestime.perf_counterby default, but the function used is customisable. - feat: use importlib instead of pkg_resources by @alubbock in #6
pkg_resources is deprecated, so use importlib instead. - fix: calls to deprecated conda api by @alubbock in #5
- fix: line_profiler hook by @alubbock in #8
this was not storing timings properly. - fix: update pandas.read_json syntax by @alubbock in #9
another deprecation fix.
For further details on how to use these new features, see the README.
Full Changelog: v0.8...v0.9