Functools.track_stats decorator for lightweight performance monitoring

Hello everyone,

I would like to propose a new addition to the `functools` module: a decorator called `@track_stats`.

**Motivation:**

Currently, developers often rewrite boilerplate code to monitor function performance (call counts and execution time). While specialized profilers exist, they are often too “heavy” for simple, per-function monitoring. A lightweight, built-in way to track basic stats would be highly beneficial for quick debugging and monitoring in production environments without external dependencies.

**Proposed API:**

```python

@functools.track_stats

def my_task():

\# process data

pass

my_task()

stats = my_task.get_stats()

print(f"Calls: {stats.calls}")

print(f"Avg Time: {stat

s.average_time}")

Implementation Details:

The decorator uses functools.wraps to preserve metadata and time.perf_counter() for high-precision timing. It returns a FunctionStats object (using a dataclass with slots for performance).

​I have developed a working prototype and verified its utility in several test cases, including web (Flask) and local applications. I am looking for feedback from the community on whether this fits the scope of functools.

​Best regards,

Hans

I feel like this is easy enough to implement in just a few lines of code on your own. Having a builtin decorator for it would be a bit counterproductive too since “stats” could be different for any given situation.

Are you tracking arguments? Are you tracking metadata of specific arguments? Are you just tracking runtime? Are you passing a stats object through the runs that the stat tracker is inspecting and mutating?

We already have the timeit module and the %timeit magic method in Jupyter sessions for just perf testing.

1 Like

Hello Hayden, thank you for your feedback!

​You raise some very good points. My goal with this proposal is not to replace full-featured profilers or specialized tools like timeit, but rather to provide a standard, lightweight “in-place” monitoring tool.

​To answer your questions:

  1. Scope: This is intended to track only basic execution time and call counts. Tracking specific arguments or metadata would indeed add too much overhead, which is why I prefer a minimalist approach.

  2. Why built-in? While it’s true that it can be implemented in a few lines, having it in functools ensures an optimized, standard implementation (using slots, perf_counter, and proper wraps handling) that developers can rely on without reinventing the wheel in every project.

  3. Difference from timeit: timeit is excellent for benchmarking snippets. However, @track_stats is designed for active code. It allows a developer to check how a function is performing in a live environment (like a Flask route or a background task) by simply calling .get_stats().

​My prototype focuses on being non-intrusive and highly efficient. I believe there is a gap between “no monitoring” and “full profiling” where this decorator fits perfectly.

​Best regards,

Hans

Idk how i feel about the substance of the proposal, but I definitely think it would need a more specific name. Is there a way to describe what it tracks that’s not just “stats”?

Hello Andrew, thank you for your feedback!

​You are right, “stats” might be too broad. The goal is to track execution performance specifically.

​Based on your suggestion, here are some more specific name alternatives that might fit better within the functools philosophy:

  1. ​@functools.track_performance

  2. ​@functools.profile_calls

  3. ​@functools.execution_stats

  4. ​@functools.call_metrics

​Personally, I think @track_performance or @call_metrics describes the intent more clearly: providing a lightweight way to see how many times a function was called and its average latency.

​What does the community think about these options? I am open to suggestions that align with Python’s naming conventions.

​Best regards,

Hans

Python already includes profilers: The Python Profilers — Python 3.14.4 documentation

And since you keep bringing it up, Flask (through Werkzeug) has ProfilerMiddleware that works for any WSGI app, not just Flask.

Hello David, thank you very much for joining the discussion. I truly appreciate your perspective, especially given your work on Flask and Werkzeug.

​You are absolutely right that Python has excellent profiling tools and Flask offers great middleware for WSGI applications. However, my proposal aims for a different “developer experience”:

  1. Granularity: While a Middleware profiles the entire request/application, @track_performance (or the chosen name) allows a developer to target one specific, critical function deep in the business logic with zero configuration.

  2. Low Barrier: It’s intended for those “quick and dirty” moments where you don’t want to spin up a full profiler or middleware, but you need to know if a specific function is the bottleneck in production.

  3. Programmatic Access: The key advantage is being able to call .get_stats() from within the code to, for example, log metrics to a custom dashboard or trigger alerts if a function exceeds a latency threshold, all in a very “Pythonic” way.

​I see this as a bridge between “nothing” and “full profiling”—a lightweight tool for the functools toolbox that is always there when you need it.

​Best regards,

Hans

Your writing style here feels very like the responses of an AI chatbot. Are you putting people’s comments into an AI and pasting the responses here? Because if so, can you please refrain from doing so, and reply in your own words?

If you’re not a native English speaker and need assistance translating your comments into English, that’s fine - but we don’t mind if the English isn’t perfect, as long as what you’re posting is your words.

6 Likes

I’m sorry, you’re right. Since I am not a native English speaker, I have been using an AI to help me organize my thoughts and make my comments sound more formal and clearer to avoid misunderstandings.

​From now on, I will write my comments myself. My English might not be perfect, but I want to share my ideas directly with you.

​My main goal is just to contribute a tool that I found very useful in my daily work. Sorry for the robotic tone!

3 Likes

Have you read the link provided by David above ?

You can do this :

import cProfile

with cProfile.Profile() as pr:
    # ... do something ...

    pr.print_stats()

This is fully granular and quick-and-dirty compliant… About the output mode, there might be several options (print it once, log it into some file, extract some part of it to be printed, process it in a more complex way, etc…). There are too many orthogonal possibilities with none to be favored over another one in the python implementation. The semantic complexity quickly balloons into a combinatorial mess with no definable “correct default behavior”. Thus you won’t get anything more quick-and-dirty than this in the builtins.

Thanks for the example. I agree cProfile is powerful, but it requires more boilerplate code for a quick check.

​My point is about simplicity. Writing @track_performance is faster when you just want to see call counts and time during development or in a quick script, without setting up a with block and printing stats manually.

​I understand there are many ways to handle the output, but I thought a basic, standard default could be useful for many developers. However, I see your point about the complexity of defining a “correct” default for everyone.

I think if something like this were to be seriously considered, it would be have too many needs/wants from too many people to be that useful. For example, something that you haven’t mentioned (and would be related) is logging, but that would be a HUGE can or worms to make it useful for people.

Looking at a similar construct [lru_]cache.cache_info(), it’s only used a few hundred times (looking through github; https://grep.app/search?f.lang=Python&q=.cache_info()) and most of those are in tests, so I don’t know how much people would really use something like this (I know this looks like a contradiction to what I just said, but I think what people would want in a vacuum and what they’ll use in practice is different for this).

I don’t think this feature would be that useful to include in functools, but it might be worth it for you to make a package that has these decorators. I don’t think anything here would need cpython changes.

Hi Daniel,

​Thank you for the feedback.

​I understand your perspective regarding why this might not fit into the CPython core or functools. However, I believe there is a gap between what a core developer considers “pure code” and what a regular developer needs to build functional applications quickly. Not everyone is at the level of a core developer, and many people rely on libraries to simplify complex logic and make their code more readable.

​Instead of debating the inclusion in the standard library, I decided to take your advice and package these tools myself. I have just published flowlens-py on PyPI.

​My goal with this library is to provide simple, ready-to-use decorators and tools that I’ve personally found useful throughout my path as a programmer. I want to help those who are still learning or who simply need to build things without reinventing the wheel every time.

​I appreciate your time and the discussion.

​Best regards,

​Hans Saldias

1 Like