High-precision Python performance measurement toolkit
Project description
nanowatch
High-precision Python performance measurement toolkit. Nanosecond accuracy. Zero dependencies. Minimal output.
Install
pip install nanowatch
Or from source:
pip install -e .
Interfaces
1. Function Decorator
from nanowatch import watch
@watch
def compute(n):
return sum(range(n))
# With a custom label
@watch("heavy computation")
def compute(n):
return sum(range(n))
# Async functions work identically
@watch
async def fetch_data(url):
...
Output:
compute 1.243 ms
2. Code Block Context Manager
from nanowatch import watch_block
with watch_block("db query"):
results = db.execute(sql)
3. Inline Call (no decorator needed)
from nanowatch import watch_call
data = watch_call(json.loads, raw_string, name="parse response")
4. Class Mixin (auto-instruments all public methods)
from nanowatch import WatchedMixin
class UserService(WatchedMixin):
_watch_prefix = "UserService" # optional, defaults to class name
def fetch_user(self, user_id):
...
def save_user(self, user):
...
def _internal_helper(self): # skipped (underscore prefix)
...
Every call to fetch_user or save_user is automatically timed.
Custom collector via DI:
from nanowatch import WatchedMixin, Collector
my_collector = Collector()
class OrderService(WatchedMixin):
_watch_collector = my_collector
_watch_prefix = "OrderService"
def create_order(self, data):
...
5. WSGI Middleware (Flask, Django, etc.)
from nanowatch import WsgiMiddleware
# Flask
app.wsgi_app = WsgiMiddleware(app.wsgi_app)
# Django (in wsgi.py)
application = WsgiMiddleware(get_wsgi_application())
Output per request:
HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
6. ASGI Middleware (FastAPI, Starlette, etc.)
from nanowatch import AsgiMiddleware
# FastAPI
app.add_middleware(AsgiMiddleware)
# Or manually wrap
app = AsgiMiddleware(app)
7. Line Profiler (checkpoint-based)
Measures time between named points inside a function.
from nanowatch import LineProfiler
def process_order(order):
prof = LineProfiler("process_order")
validate(order)
prof.mark("validated")
result = db.save(order)
prof.mark("saved to db")
notify(order)
prof.mark("notified")
prof.finish()
Output:
process_order | validated 312 us [session=process_order, checkpoint=validated]
process_order | saved to db 2.841 ms [session=process_order, checkpoint=saved to db]
process_order | notified 1.102 ms [session=process_order, checkpoint=notified]
[nanowatch] session 'process_order' complete (3 checkpoints)
------------------------------------------------------------------------
Reports
Print summary to stdout
import nanowatch
# ... run your code ...
nanowatch.summary()
Output:
========================================================================
nanowatch | Performance Summary
2025-06-01 14:32:10
========================================================================
UserService.fetch_user
calls : 48
min : 812 us
max : 4.231 ms
avg : 1.103 ms
total : 52.944 ms
------------------------------------------------------------------------
HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
------------------------------------------------------------------------
Total tracked time : 1.204 s
Total measurements : 57
========================================================================
Save to JSON file
nanowatch.save("perf_results.json")
{
"generated_at": "2025-06-01T14:32:10.123456",
"total_measurements": 57,
"records": [
{
"name": "UserService.fetch_user",
"duration_ns": 1103000,
"duration_us": 1103.0,
"duration_ms": 1.103,
"duration_s": 0.001103,
"context": {}
}
],
"groups": {
"UserService.fetch_user": {
"count": 48,
"min_ns": 812000,
"max_ns": 4231000,
"avg_ns": 1103000,
"total_ns": 52944000
}
}
}
Custom Collector (Isolation / Testing)
All interfaces accept an optional collector parameter for DI:
from nanowatch import watch, Collector
test_collector = Collector()
@watch(collector=test_collector)
def my_fn():
...
my_fn()
print(test_collector.stats("my_fn"))
Reset
nanowatch.reset() # clears the global collector
Precision
All measurements use time.perf_counter_ns, Python's highest-resolution
monotonic clock. Results are stored as raw integers (nanoseconds) and
converted only for display.
Project Structure
src/nanowatch/
core/
timer.py # Timer, TimingRecord
collector.py # Collector, default_collector
interfaces/
decorators.py # @watch, watch_block, watch_call
mixin.py # WatchedMixin
middleware.py # WsgiMiddleware, AsgiMiddleware
line_profiler.py # LineProfiler
output/
formatter.py # console + file output
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nanowatch-0.1.2.tar.gz.
File metadata
- Download URL: nanowatch-0.1.2.tar.gz
- Upload date:
- Size: 15.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72009c9691f9184c29f2e3814b575376672323c66207703031c6e637741fbd0b
|
|
| MD5 |
0497c7042ea7e38c8657cfefb4a1462b
|
|
| BLAKE2b-256 |
a1fa258f228ccc7cbf60fcbce25d965b9a605b705f4a57ca85f13aef7c581428
|
Provenance
The following attestation bundles were made for nanowatch-0.1.2.tar.gz:
Publisher:
publish.yml on munjed-ab/nanowatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nanowatch-0.1.2.tar.gz -
Subject digest:
72009c9691f9184c29f2e3814b575376672323c66207703031c6e637741fbd0b - Sigstore transparency entry: 976412382
- Sigstore integration time:
-
Permalink:
munjed-ab/nanowatch@e9bcbc4a7c1e08e7cf942b84c1936ce947e8da3a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/munjed-ab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e9bcbc4a7c1e08e7cf942b84c1936ce947e8da3a -
Trigger Event:
push
-
Statement type:
File details
Details for the file nanowatch-0.1.2-py3-none-any.whl.
File metadata
- Download URL: nanowatch-0.1.2-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2a4910ccf17816c178b6afa1b867bef86e7fb13a2222c0e32b733daa4a60a60
|
|
| MD5 |
d021bec3c2d08b3e5c2daa4d1b052bda
|
|
| BLAKE2b-256 |
1f5f90057a7d2911294df205ddb072c28f9ff2b59df3b7d26f327315b583f1fe
|
Provenance
The following attestation bundles were made for nanowatch-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on munjed-ab/nanowatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nanowatch-0.1.2-py3-none-any.whl -
Subject digest:
a2a4910ccf17816c178b6afa1b867bef86e7fb13a2222c0e32b733daa4a60a60 - Sigstore transparency entry: 976412384
- Sigstore integration time:
-
Permalink:
munjed-ab/nanowatch@e9bcbc4a7c1e08e7cf942b84c1936ce947e8da3a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/munjed-ab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e9bcbc4a7c1e08e7cf942b84c1936ce947e8da3a -
Trigger Event:
push
-
Statement type: