Skip to content

feat: add FastAPI middleware for per-request emissions tracking - #1203

Open
davidberenstein1957 wants to merge 1 commit into
masterfrom
feat/add-fastapi-middleware
Open

feat: add FastAPI middleware for per-request emissions tracking#1203
davidberenstein1957 wants to merge 1 commit into
masterfrom
feat/add-fastapi-middleware

Conversation

@davidberenstein1957

@davidberenstein1957 davidberenstein1957 commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds optional codecarbon[fastapi] integration: middleware to track CO₂ emissions per HTTP request on FastAPI/Starlette apps
  • Measurement runs after the response is sent, so clients are not blocked on hardware sampling
  • Includes create_codecarbon_lifespan for a shared app-level tracker, route include/exclude filters, optional save_to_api, and opt-in response_headers
  • Rebased on v3.2.9 (latest release)
  • Measured on a real HF embedder workload (paraphrase-MiniLM-L3-v2): default deferred middleware ~+4 ms per request; sync headers cost more. See docs/how-to/fastapi.md.

Test plan

  • uv run pytest tests/integrations/ -v
  • Manual smoke: uv run --extra fastapi uvicorn examples.fastapi_middleware:app --reload then curl localhost:8000/predict

Usage

from fastapi import FastAPI
from codecarbon.integrations.fastapi import add_codecarbon_middleware, create_codecarbon_lifespan

app = FastAPI(lifespan=create_codecarbon_lifespan(app, project_name="my-api"))
add_codecarbon_middleware(app)

Full guide: docs/how-to/fastapi.md

@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.79769% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.54%. Comparing base (3ec31a0) to head (e293e0c).

Files with missing lines Patch % Lines
codecarbon/integrations/fastapi/attribution.py 94.94% 5 Missing ⚠️
codecarbon/emissions_tracker.py 90.00% 2 Missing ⚠️
codecarbon/integrations/fastapi/middleware.py 96.07% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1203      +/-   ##
==========================================
+ Coverage   91.43%   91.54%   +0.11%     
==========================================
  Files          49       52       +3     
  Lines        5057     5230     +173     
==========================================
+ Hits         4624     4788     +164     
- Misses        433      442       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@benoit-cty

Copy link
Copy Markdown
Contributor

Hello, thanks for this. There is a problem with your branch : there are many changes that are already merged. Can you do a rebase ?

@davidberenstein1957
davidberenstein1957 force-pushed the feat/add-fastapi-middleware branch from f44740f to 15c0ccf Compare May 20, 2026 09:31
@davidberenstein1957
davidberenstein1957 marked this pull request as ready for review May 20, 2026 09:33
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner May 20, 2026 09:33
@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

@benoit-cty I have reached out to some people at FastAPI, if they would be interested in a quick review :) For visibility, we could also consider deploying it as a standalone integration, but let's see if people like it.

@davidberenstein1957
davidberenstein1957 force-pushed the feat/add-fastapi-middleware branch 2 times, most recently from eabd526 to bb6995c Compare May 20, 2026 16:03

@SaboniAmine SaboniAmine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks David this a great PR! Left a few questions, and saw that you have already prepared a benchmark script, do you have any numbers / graph already computed to share ?

Comment thread codecarbon/integrations/fastapi/_headers.py Outdated
Comment thread codecarbon/integrations/fastapi/_headers.py Outdated
Comment thread codecarbon/integrations/fastapi/_headers.py Outdated
Comment thread codecarbon/integrations/fastapi/_routing.py Outdated
Comment thread codecarbon/integrations/fastapi/_routing.py Outdated
Comment thread codecarbon/integrations/fastapi/middleware.py Outdated
Comment thread codecarbon/integrations/fastapi/middleware.py Outdated
Comment thread codecarbon/integrations/fastapi/middleware.py Outdated
Comment thread docs/how-to/fastapi.md Outdated
Comment thread examples/fastapi_middleware.py Outdated
@davidberenstein1957
davidberenstein1957 force-pushed the feat/add-fastapi-middleware branch 2 times, most recently from deba2ce to 86cc355 Compare July 20, 2026 07:38
@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

@inimaz This is ready for review. Rebased on v3.2.9 and benchmarked on a real HF embedder workload — middleware adds about 3 ms per request (~24 ms → ~27 ms). Details in the updated PR description and docs/how-to/fastapi.md.

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

@SaboniAmine Thanks again for the thorough review — I've addressed the inline threads in the latest pushes:

  • Core enums for emission fields / header presets / HTTP methods (codecarbon/core/emission_fields.py)
  • Simpler resolve_header_mapping + package-level Starlette import guard
  • Concurrent task naming documented/tested (stable route labels + UUID uniqueness on the lifespan path)
  • Measurement model docs + regression test (sync measure on tracker worker before callback)
  • compose_lifespans() for stacking with user-owned startup/shutdown
  • Benchmarks vs raw FastAPI + Logfire (--with-logfire); ~3 ms CodeCarbon overhead on the HF embedder workload

Local uv run task test-package: 552 passed. Ready for another look when you have a moment.

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

Ponytail cleanup on this PR:

  • Removed unused _headers.py / core emission_fields enums (never wired to middleware)
  • Added opt-in response_headers= (sync measure → X-CodeCarbon-*; costs client latency)
  • Added include_background_tasks (default True; set False to finalize at end-of-body)
  • Docs: BackgroundTasks are included by default; WebSockets stay unsupported (no implementation)

See docs/how-to/fastapi.md Limitations + config section.

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

Added a sync response_headers row to the Performance section (and --with-headers on the benchmark script):

Mode Rough client latency (mocked 20 ms measure)
Deferred default ~+3 ms (embedder table)
response_headers=True, c=1 55 ms (+25 ms vs ~30 ms baseline)
response_headers=True, c=4 ~95 ms (tracker worker serializes sync measures)

Reproduce: uv run --extra fastapi python scripts/benchmark_fastapi_middleware.py --quick --with-headers

@davidberenstein1957

Copy link
Copy Markdown
Collaborator Author

@inimaz @SaboniAmine — ready for another look when you have time.

This commit finishes the HTTP perf work:

  • REQUEST lane for mark_http_request_start (no thread-pool hop)
  • Cached cloud metadata + emissions template on HTTP finalize
  • Docs updated with live HF embedder benchmarks only (--realistic --with-headers)

Deferred middleware stays in the same ballpark as baseline on the live benchmark (~30–32 ms vs ~42 ms baseline mean on Darwin arm64).

Adds an ASGI middleware that gives each HTTP request its share of a
long-running tracker's energy, plus the attribution model behind it.

One tracker runs for the app's lifetime. Each completed sampling window
(t_prev, t_now, dE) is split across the requests in flight during it,
weighted by their overlap with the window and normalised by the sum of the
weights. Windows with nothing in flight are recorded as unattributed. The
invariant attributed + unattributed == settled holds exactly after every
window, and is what the concurrency test pins down.

Why not per-request start/stop energy snapshots: with N requests in flight
each request observes the whole machine's delta, so the sum overcounts by
roughly N - measured up to 88x at 100 concurrent requests. Fair-share
weighting is the only split that conserves the run total.

A request's share is only known one or more sampling windows after its
response was sent, so results are reported then, via a callback. A request
that never covered a completed window reports energy_kwh=None rather than
zero: there is no honest number for it.

Tracker side: add_energy_window_observer / remove_energy_window_observer
expose the sampling windows, and http_request_emissions() scales the run's
EmissionsData down to one attributed share using the run's accumulated
component ratios and carbon intensity.

Depends on #1374 (duration int -> float in the emissions schemas, and
dropping the duration < 1 send guard) and #1375 (scheduler pause handling
around tasks). Both are carried by their own PRs rather than duplicated
here, so this should merge after them.

Deliberately left out, to keep the diff reviewable: hardware-tier gating of
which backends can resolve a sampling window, include/exclude path filtering
(endpoint labelling is two lines inline), idle-baseline subtraction,
per-endpoint aggregation, routing per-request rows into the tracker's own
CSV/API output handlers, a lifespan helper, and a dedicated docs page. Each
is additive on top of this and can follow if there is demand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@davidberenstein1957
davidberenstein1957 force-pushed the feat/add-fastapi-middleware branch from d1e7a95 to e293e0c Compare August 19, 2026 15:17
@github-actions github-actions Bot added size/XS and removed size/XL labels Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants