Skip to content

About

Lightweight scope-based Python profiler with colorful, hierarchical execution-time reports

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

21 Commits

Folders and files

Repository files navigation

License: MIT CI

scope-timer

Lightweight scope-based Python profiler with colorful, hierarchical execution-time reports.

scope-timer makes it easy to find performance bottlenecks in your Python code by timing specific sections and presenting the results in a beautiful, hierarchical tree. It's designed to be simple to use and have minimal overhead.

Key Features

  • Simple and Intuitive API: Use decorators (@ScopeTimer.profile_func(...)) or context managers (with ScopeTimer.profile_block(...)) to profile code blocks effortlessly.
  • Hierarchical Reports: Understand performance bottlenecks in nested function calls and scopes.
  • Multiple Outputs: View reports directly in the console, or save them as plain text or interactive HTML files.
  • Rich & Colorful: Powered by the rich library for beautiful and readable terminal output.
  • Lightweight & Predictable: Low memory overhead and linear performance scaling, suitable for complex applications.
  • Thread-Safe: Profile multi-threaded applications without interference between threads.

Installation

pip install scope-timer

Usage

You can easily profile functions and code blocks. Here is a simple example of a multi-stage pipeline:

from scope_timer import ScopeTimer
import time

@ScopeTimer.profile_func()
def preprocess():
    with ScopeTimer.profile_block("load_data"):
        time.sleep(0.01)
    with ScopeTimer.profile_block("clean_data"):
        time.sleep(0.015)

@ScopeTimer.profile_func()
def compute():
    for _ in range(10):
        with ScopeTimer.profile_block("matmul"):
            time.sleep(0.001)
        with ScopeTimer.profile_block("activation"):
            time.sleep(0.0005)

@ScopeTimer.profile_func()
def postprocess():
    with ScopeTimer.profile_block("save_results"):
        time.sleep(0.005)

# Profile the entire pipeline
with ScopeTimer.profile_block("pipeline"):
    preprocess()
    compute()
    postprocess()

# Print the summary to the console
ScopeTimer.summarize()

# You can also save the report to a file
ScopeTimer.save_html("timer_report.html")

Example Output

The following console output shows a structured report with elapsed time, number of calls, and percentage of parent scope.

ScopeTimer Console Output

Enabling / Disabling the Timer

By default, scope-timer is enabled. If you want to completely disable timing (e.g., in production or benchmarking mode), you can set the environment variable SCOPE_TIMER_ENABLE=0.

export SCOPE_TIMER_ENABLE=0  # disables all timing

This will skip all profiling calls with near-zero overhead, making scope-timer safe to leave in production code.

Note:

The environment variable must be set before scope_timer is imported.

This is because the internal switch is initialized at import time.

To disabling in code:

import os
os.environ["SCOPE_TIMER_ENABLE"] = "0"
from scope_timer import ScopeTimer

Limitations

Currently, scope-timer does not support asyncio.

This is because timer state is tracked per thread, but asyncio runs multiple tasks within a single thread, frequently switching execution contexts using await. As a result:

  • Profiling an async def function with @ScopeTimer.profile may produce incorrect timing or even raise ValueError.
  • Using ScopeTimer.profile(...) across an await boundary is not safe.

For now, scope-timer is intended for synchronous or multi-threaded code only.

Performance

scope-timer is designed to be lightweight with predictable performance. The following graphs show the memory usage and report generation time when profiling a realistic, multi-stage pipeline.

ScopeTimer Performance Graph

This benchmark simulates a typical pipeline structure consisting of the following:

  • preprocess: stage with 2 sub-tasks
  • compute: stage with 1 sub-task and 20 repeated prediction scopes
  • postprocess: stage with a result-saving scope

The full pipeline is executed multiple times (10–1000 iterations), and ScopeTimer.summarize() is called once at the end.

As shown:

  • Memory usage increases linearly and remains well under 3MB for 1000 pipeline runs.
  • Execution time for summarize() stays under 10ms for large workloads.

This ensures stable and low overhead behavior even in performance-critical applications.

API Overview

All methods are static and can be called directly from the ScopeTimer class.

Core Profiling Methods

  • ScopeTimer.profile_block(name: str)

    Profiles a block of code. It can be used as a context manager (with). It automatically handles starting and stopping the timer.

    Parameters:

    • name (str): The identifier for the scope.
  • ScopeTimer.profile_func(name: str = None)

    Profiles a function. It can be used as a decorator (@). It automatically handles starting and stopping the timer.

    Parameters:

    • name (str): The identifier for the scope. If not provided, the decorated function’s name will be used automatically.

Reporting Methods

  • ScopeTimer.summarize(time_unit="auto", precision="auto", divider="rule", verbose=False)

    Prints a formatted summary of timing results to the console.

    Parameters:

    • time_unit (str): The display unit for time. Can be 'auto', 's', 'ms', or 'us'. Defaults to 'auto'.
    • precision (int | str): The number of decimal places for time values. Defaults to 'auto'.
    • divider (str): The style of the separator between root scopes. Can be 'rule' or 'blank'. Defaults to 'rule'.
    • verbose (bool): If True, displays detailed statistics (min, max, avg, var). Defaults to False.
  • ScopeTimer.save_txt(file_path, **kwargs)

    Saves a summary of timing results as a plain text file.

    Parameters:

    • file_path (str | Path): The path to the output file.
    • **kwargs: Accepts the same arguments as summarize() (time_unit, precision, verbose).
  • ScopeTimer.save_html(file_path, **kwargs)

    Saves a summary of timing results as a themed HTML file.

    Parameters:

    • file_path (str | Path): The path to the output file.
    • **kwargs: Accepts the same arguments as summarize() (time_unit, precision, verbose).

Utility Methods

  • ScopeTimer.reset()

    Resets all recorded timer data, clearing all scopes and measurements. Use this to start a fresh set of measurements within the same process.

Release History

For a detailed history of changes, please see the CHANGELOG.md file.

License

This project is licensed under the MIT License.

About

Lightweight scope-based Python profiler with colorful, hierarchical execution-time reports

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages