How to Use the Profiler and Timer for Performance Measurement in OpenMed

OpenMed provides lightweight, zero-overhead performance measurement tools in openmed/utils/profiling.py, including a contextual Profiler for aggregated timing reports and a simple Timer for ad-hoc measurements, both utilizing time.perf_counter() for nanosecond precision.

OpenMed includes a sophisticated yet easy-to-use profiling toolkit designed to help developers identify bottlenecks in medical ML pipelines. The openmed/utils/profiling.py module offers two complementary approaches: a stateful Profiler that aggregates multiple timing blocks into detailed reports, and a lightweight Timer for single-shot measurements. These utilities are implemented in pure Python and can be enabled or disabled globally without code changes, making them ideal for both development debugging and production monitoring.

Profiler Architecture and Core Components

The Profiler class is the primary instrument for collecting performance metrics across complex workflows. According to the source code in openmed/utils/profiling.py, the architecture relies on three main abstractions that work together to provide zero-overhead profiling when disabled.

TimingResult and ProfileReport

At the foundation, the TimingResult class (defined at lines 21-32) stores individual measurements including the operation name, duration, and optional metadata dictionary. These results are aggregated by the ProfileReport class (lines 39-84), which provides helper methods like total_duration(), summary(), and format_report() to analyze collected timings.

The Profiler Class and measure() Context Manager

The Profiler class definition begins at line 122 and serves as the main entry point for performance sessions. It provides the measure() method (implemented as a context manager around lines 60-86) that records elapsed time for wrapped code blocks. When enabled, measure() creates a TimingResult for each block; when disabled, it yields control immediately without recording, ensuring zero runtime overhead.

Global Profiler State Management

For library-wide instrumentation without passing instances, the module provides global helpers (lines 233-292): get_profiler(), enable_profiling(), disable_profiling(), and the @profile decorator. These functions manage a singleton _global_profiler variable, enabling thread-safe access from any module without explicit object passing.

Timer Implementation and Usage

For simple, single-shot measurements, OpenMed provides the Timer class starting at line 405. Unlike the stateful Profiler, Timer operates as a minimalistic stopwatch with start(), stop(), elapsed_ms, and elapsed_s properties.

Direct Timer Usage

You can instantiate and control a Timer manually for precise measurement of specific code sections. This is useful when you need the elapsed time as a return value rather than an aggregated report.

The timed Context Manager

The timed context manager (defined at line 554) offers a convenience wrapper around the Timer class. It automatically starts timing on entry, stops on exit, and logs the duration at the DEBUG level, making it ideal for quick diagnostic logging without report generation.

Practical Implementation Examples

The following examples demonstrate common patterns for integrating OpenMed's performance tools into your codebase.

Basic Profiling with Explicit Profiler Instances

Create a dedicated Profiler instance when you need isolated measurement sessions for specific components:

from openmed.utils import Profiler

# Initialize profiler (enabled=True by default)

profiler = Profiler(enabled=True)

# Measure model loading

with profiler.measure("load_model"):
    model = load_my_model()

# Measure inference

with profiler.measure("inference"):
    result = model.predict(data)

# Generate formatted report

print(profiler.report().format_report())

This produces a structured output showing total duration and percentage breakdowns for each named operation.

Using the Global Profiler Across Modules

For application-wide profiling, enable the global singleton and access it from any function:

from openmed.utils import enable_profiling, get_profiler

# Enable once at application startup

enable_profiling()

def process_batch(data):
    with get_profiler().measure("batch_processing"):
        return expensive_transform(data)

# Called from anywhere in your codebase

process_batch(x)
process_batch(y)

# Retrieve aggregated report

report = get_profiler().report()
print(report.format_report(include_metadata=True))

Decorating Functions with @profile

The @profile decorator provides non-intrusive instrumentation for existing functions:

from openmed.utils import profile, enable_profiling

enable_profiling()

@profile("data_preprocessing")
def clean_data(raw):
    return heavy_cleaning_operation(raw)

clean_data(dataset)
print(get_profiler().report().format_report())

The decorator automatically wraps the function with the global profiler's measure() context, recording timings under the specified name.

Single-Shot Timing with Timer

For quick measurements without report aggregation:

from openmed.utils import Timer, timed

# Direct usage

timer = Timer().start()
run_algorithm()
milliseconds = timer.stop()
print(f"Algorithm took {milliseconds:.2f} ms")

# Context-manager with automatic logging

with timed("database_query"):
    results = db.fetch_large_table()

Combining Profiler and Timer

You can nest Timer measurements inside Profiler blocks for granular diagnostics:

from openmed.utils import Profiler, timed

profiler = Profiler(enabled=True)

with profiler.measure("end_to_end"):
    with timed("initialization"):
        setup_environment()
    with timed("execution"):
        run_main_logic()

print(profiler.report().format_report())

Here, the Profiler captures the total end_to_end duration while the timed context managers emit debug logs for sub-steps.

Summary

  • OpenMed's profiling toolkit resides in openmed/utils/profiling.py and provides both aggregated and single-shot timing utilities.
  • The Profiler class uses measure() as a context manager to collect TimingResult objects into ProfileReport summaries, with global singleton access via get_profiler().
  • The Timer class and timed context manager offer lightweight alternatives for ad-hoc measurements using time.perf_counter().
  • Zero-overhead operation is achieved by setting enabled=False or calling disable_profiling(), which causes context managers to skip recording entirely.
  • Source file locations include openmed/utils/profiling.py (implementation), tests/unit/test_profiling.py (validation), and openmed/__init__.py (top-level exports).

Frequently Asked Questions

How do I disable profiling in production without removing instrumentation code?

Call disable_profiling() at application startup or ensure the Profiler is initialized with enabled=False. When disabled, the measure() context manager yields immediately without recording, and the @profile decorator executes the wrapped function directly. This design ensures zero runtime overhead in production environments while preserving instrumentation for development.

What is the difference between the Profiler and Timer classes?

The Profiler (line 122) maintains state across multiple measurements, aggregating them into a ProfileReport with statistical summaries and percentage breakdowns. The Timer (line 405) is a minimal stopwatch for single measurements that returns raw duration values without persistence. Use Profiler for benchmarking complex workflows; use Timer for simple, one-off timing checks or logging.

Can I attach custom metadata to specific timing measurements?

Yes. The Profiler.measure() method accepts an optional metadata parameter as a dictionary. When provided, this data is stored in the TimingResult object and can be rendered in reports via format_report(include_metadata=True). This is useful for tracking parameters like batch sizes or input shapes alongside timing data.

Is the global profiler thread-safe for multi-threaded applications?

The global profiler singleton stored in _global_profiler is designed for thread-safe access. The enable_profiling() and disable_profiling() functions operate atomically, and the get_profiler() function returns the same instance across threads. However, individual TimingResult collections are not automatically thread-local; concurrent measurements from different threads will aggregate into the same report, which is typically desirable for total application profiling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →