Skip to content

Commit 72b3be3

Browse files
committed
Add fastapi prometheus example
1 parent 2ea9fde commit 72b3be3

File tree

9 files changed

+177
-8
lines changed

9 files changed

+177
-8
lines changed

.github/workflows/lint_peripherals.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ concurrency:
77
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
88

99
env:
10-
POETRY_GROUPS: stress_test,code_quality
10+
POETRY_GROUPS: stress_test,code_quality,examples
1111

1212
jobs:
1313
test:
@@ -53,4 +53,7 @@ jobs:
5353
run: make lint TARGET_DIR=stress_tests
5454

5555
- name: Lint tests code
56-
run: make lint TARGET_DIR=tests
56+
run: make lint TARGET_DIR=tests
57+
58+
- name: Lint tests examples
59+
run: make lint TARGET_DIR=examples

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TARGET_DIR = .
2-
POETRY_GROUPS = dev,stress_test,code_quality
2+
POETRY_GROUPS = dev,stress_test,code_quality,examples
33

44
install:
55
poetry install --with ${POETRY_GROUPS} -E uvloop

README.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,23 @@ With every call you will receive an [IoLoopMonitorState](monitored_ioloop/monito
5252
- `cpu_loop_duration`: The time it took to execute the current step of the event loop in CPU time.
5353
- `handles_count`: The amount of handles (think about them as tasks) that the IO loop is currently handling.
5454

55-
5655
## Performance impact
57-
As many of you might be concerned about the performance impact of this library, I have run some benchmarks to measure the performance impact of this library.
58-
You can find the results in the [following README](stress_tests/results/README.md) folder.
56+
As many of you might be concerned about the performance impact of this library, I have run some benchmarks to measure the performance impact of this library.
57+
In summary the performance impact is negligible for most use cases.
58+
You can find the more detailed information in the following [README.md](stress_tests/results/README.md).
59+
60+
## Usage examples
61+
You can find examples projects showing potential use cases in the [examples](examples) folder.
62+
Currently there is only the [fastapi with prometheus exporter example](examples/fastapi_with_prometheus) but more will be added in the future.
63+
5964

6065
## Roadmap
6166
- [x] Add support for the amount of `Handle`'s on the event loop
67+
- [x] Add an examples folder
6268
- [ ] Add visibility into which `Handle` are making the event loop slower
6369
- [ ] Add easier integration with popular monitoring tools like Prometheus
6470
- [ ] Add easier integration with `uvicorn`
65-
- [ ] Add an examples folder
71+
6672

6773
## Credits
6874
I took a lot of inspiration from the [uvloop](https://github.com/MagicStack/uvloop) project with everythin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
run:
2+
poetry run python server.py
3+
create-trafiic:
4+
poetry run python create_traffic.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import random
2+
3+
from httpx import AsyncClient
4+
from httpx import Limits, Timeout
5+
import asyncio
6+
7+
URLS = [
8+
"http://localhost:1441/ping",
9+
"http://localhost:1441/async_slow?sleep_for=5&coroutines_number=10",
10+
"http://localhost:1441/blocking_slow?sleep_for=1",
11+
]
12+
13+
14+
async def main() -> None:
15+
async with AsyncClient(
16+
limits=Limits(max_connections=20), timeout=Timeout(timeout=30)
17+
) as client:
18+
await asyncio.gather(*[client.get(random.choice(URLS)) for _ in range(10)])
19+
20+
21+
if __name__ == "__main__":
22+
asyncio.run(main())
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import asyncio
2+
import time
3+
4+
from monitored_ioloop.monitored_asyncio import MonitoredAsyncIOEventLoopPolicy
5+
from monitored_ioloop.monitoring import IoLoopMonitorState
6+
from fastapi import FastAPI
7+
from prometheus_client import start_http_server, Histogram
8+
from uvicorn import Server, Config
9+
10+
ioloop_execution_time_histogram = Histogram(
11+
"ioloop_execution_time_histogram",
12+
"Histogram of the ioloop execution time",
13+
buckets=[0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1, 2, 4],
14+
)
15+
16+
app = FastAPI()
17+
18+
19+
@app.get("/ping")
20+
async def ping() -> str:
21+
return "pong"
22+
23+
24+
@app.get("/async_slow")
25+
async def async_slow(sleep_for: int, coroutines_number: int) -> str:
26+
await asyncio.gather(*[asyncio.sleep(sleep_for) for _ in range(coroutines_number)])
27+
return f"slept for {sleep_for} seconds and created {coroutines_number} coroutines"
28+
29+
30+
@app.get("/blocking_slow")
31+
async def blocking_slow(sleep_for: int) -> str:
32+
time.sleep(sleep_for)
33+
return f"slept for {sleep_for} seconds"
34+
35+
36+
def monitor_ioloop(ioloop_monitor_state: IoLoopMonitorState) -> None:
37+
ioloop_execution_time_histogram.observe(ioloop_monitor_state.wall_loop_duration)
38+
39+
40+
def main() -> None:
41+
"""
42+
Current because uvloop does not support settings the ioloop to a custom implementation, we need to
43+
run the server manually and not from the CLI.
44+
I am currently working on allowing passing an import string to a custom event loop policy which
45+
will considerably simplify the fastapi example.
46+
"""
47+
asyncio.set_event_loop_policy(MonitoredAsyncIOEventLoopPolicy(monitor_ioloop))
48+
config = Config(app=app, host="localhost", port=1441, loop="asyncio")
49+
server = Server(config)
50+
start_http_server(1551)
51+
asyncio.run(server.serve())
52+
53+
54+
if __name__ == "__main__":
55+
main()

monitored_ioloop/monioted_ioloop_base.py

+11
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@ def __init__(
1010
):
1111
super().__init__()
1212
self._monitor_callback = monitor_callback
13+
14+
if typing.TYPE_CHECKING:
15+
# EventLoopPolicy doesn't implement these, but since they are marked
16+
# as abstract in typeshed, we have to put them in so mypy thinks
17+
# the base methods are overridden. This is the same approach taken
18+
# for the Windows event loop policy classes in typeshed.
19+
def get_child_watcher(self) -> typing.NoReturn:
20+
...
21+
22+
def set_child_watcher(self, watcher: typing.Any) -> typing.NoReturn:
23+
...

poetry.lock

+60-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+9
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ uvicorn = "^0.27.1"
3939
click = "^8.1.7"
4040
locust = "^2.23.1"
4141

42+
[tool.poetry.group.examples]
43+
optional = true
44+
45+
[tool.poetry.group.examples.dependencies]
46+
fastapi = "^0.109.2"
47+
prometheus-client = "^0.20.0"
48+
httpx = "^0.27.0"
49+
50+
4251
[build-system]
4352
requires = ["poetry-core"]
4453
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
 (0)