Skip to content

munjed-ab/nanowatch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nanowatch

High-precision Python performance measurement toolkit. Nanosecond accuracy. Zero dependencies. Minimal output.

Tests Publish


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

About

High-precision Python performance measurement toolkit

Resources

License

Stars

Watchers

Forks

Contributors

Languages