Skip to content

Commit c364676

Browse files
authored
Merge pull request #16 from autometrics-dev/fp-3168-autometrics-py-feature-add-slo-support
Add slo support
2 parents 1ceee20 + 0d8b65a commit c364676

13 files changed

+765
-109
lines changed

.github/workflows/build.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
cache: "poetry"
2121
- name: Install dependencies
2222
run: poetry install --no-interaction --no-root --with dev
23-
- uses: psf/black@stable
24-
- name: Run pyright
23+
- name: Check code formatting
24+
run: poetry run black .
25+
- name: Lint code
2526
run: poetry run pyright
27+
- name: Run tests
28+
run: poetry run pytest

README.md

+90-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
![GitHub_headerImage](https://user-images.githubusercontent.com/3262610/221191767-73b8a8d9-9f8b-440e-8ab6-75cb3c82f2bc.png)
2+
13
# autometrics-py
24

3-
A Python decorator that makes it easy to understand the error rate, response time, and production usage of any function in your code. Jump straight from your IDE to live Prometheus charts for each HTTP/RPC handler, database method, or other piece of application logic.
5+
A Python library that exports a decorator that makes it easy to understand the error rate, response time, and production usage of any function in your code. Jump straight from your IDE to live Prometheus charts for each HTTP/RPC handler, database method, or other piece of application logic.
46

57
Autometrics for Python provides:
68

@@ -18,8 +20,7 @@ See [Why Autometrics?](https://github.com/autometrics-dev#why-autometrics) for m
1820
- 🔗 Create links to live Prometheus charts directly into each functions docstrings (with tooltips coming soon!)
1921
- 📊 (Coming Soon!) Grafana dashboard showing the performance of all
2022
instrumented functions
21-
- 🚨 (Coming Soon!) Generates Prometheus alerting rules using SLO best practices
22-
from simple annotations in your code
23+
- 🚨 Enable Prometheus alerts using SLO best practices from simple annotations in your code
2324
- ⚡ Minimal runtime overhead
2425

2526
## Using autometrics-py
@@ -45,15 +46,96 @@ def sayHello:
4546

4647
> Note that we cannot support tooltips without a VSCode extension due to behavior of the [static analyzer](https://github.com/davidhalter/jedi/issues/1921) used in VSCode.
4748
49+
## Alerts / SLOs
50+
51+
Autometrics makes it easy to add Prometheus alerts using Service-Level Objectives (SLOs) to a function or group of functions.
52+
53+
In order to receive alerts you need to add a set of rules to your Prometheus set up. You can find out more about those rules here: [Prometheus alerting rules](https://github.com/autometrics-dev/autometrics-shared#prometheus-recording--alerting-rules). Once added, most of the recording rules are dormant. They are enabled by specific metric labels that can be automatically attached by autometrics.
54+
55+
To use autometrics SLOs and alerts, create one or multiple `Objective`s based on the function(s) success rate and/or latency, as shown below. The `Objective` can be passed as an argument to the `autometrics` macro to include the given function in that objective.
56+
57+
```python
58+
from autometrics import autometrics
59+
from autometrics.objectives import Objective, ObjectiveLatency, ObjectivePercentile
60+
61+
API_SLO = Objective(
62+
"random",
63+
success_rate=ObjectivePercentile.P99_9,
64+
latency=(ObjectiveLatency.Ms250, ObjectivePercentile.P99),
65+
)
66+
67+
@autometrics(objective=API_SLO)
68+
def api_handler():
69+
# ...
70+
```
71+
72+
Autometrics by default will try to store information on which function calls a decorated function. As such you may want to place the autometrics in the top/first decorator, as otherwise you may get `inner` or `wrapper` as the caller function.
73+
74+
So instead of writing:
75+
76+
```py
77+
from functools import wraps
78+
from typing import Any, TypeVar, Callable
79+
80+
R = TypeVar("R")
81+
82+
def noop(func: Callable[..., R]) -> Callable[..., R]:
83+
"""A noop decorator that does nothing."""
84+
85+
@wraps(func)
86+
def inner(*args: Any, **kwargs: Any) -> Any:
87+
return func(*args, **kwargs)
88+
89+
return inner
90+
91+
@noop
92+
@autometrics
93+
def api_handler():
94+
# ...
95+
```
96+
97+
You may want to switch the order of the decorator
98+
99+
```py
100+
@autometrics
101+
@noop
102+
def api_handler():
103+
# ...
104+
```
105+
48106
## Development of the package
49107

50108
This package uses [poetry](https://python-poetry.org) as a package manager, with all dependencies separated into three groups:
51-
- root level dependencies, required
52-
- `dev`, everything that is needed for development or in ci
53-
- `examples`, dependencies of everything in `examples/` directory
109+
110+
- root level dependencies, required
111+
- `dev`, everything that is needed for development or in ci
112+
- `examples`, dependencies of everything in `examples/` directory
54113

55114
By default, poetry will only install required dependencies, if you want to run examples, install using this command:
56115

57-
`poetry install --with examples`
116+
```sh
117+
poetry install --with examples
118+
```
119+
120+
Code in this repository is:
121+
122+
- formatted using [black](https://black.readthedocs.io/en/stable/).
123+
- contains type definitions (which are linted by [pyright](https://microsoft.github.io/pyright/))
124+
- tested using [pytest](https://docs.pytest.org/)
125+
126+
In order to run these tools locally you have to install them, you can install them using poetry:
58127

59-
Code in this repository is formatted using [black](https://black.readthedocs.io/en/stable/) and contains type definitions (which are linted by [pyright](https://microsoft.github.io/pyright/))
128+
```sh
129+
poetry install --with dev
130+
```
131+
132+
After that you can run the tools individually
133+
134+
```sh
135+
# Formatting using black
136+
poetry run black .
137+
# Lint using pyright
138+
poetry run pyright
139+
# Run the tests using pytest
140+
poetry run pytest
141+
```

examples/example.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from prometheus_client import start_http_server
2-
from autometrics import autometrics
31
import time
42
import random
3+
from prometheus_client import start_http_server
4+
from autometrics import autometrics
5+
from autometrics.objectives import Objective, ObjectiveLatency, ObjectivePercentile
56

67

78
# Defines a class called `Operations`` that has two methods:
@@ -36,6 +37,24 @@ def div_unhandled(num1, num2):
3637
return result
3738

3839

40+
RANDOM_SLO = Objective(
41+
"random",
42+
success_rate=ObjectivePercentile.P99_9,
43+
latency=(ObjectiveLatency.Ms250, ObjectivePercentile.P99),
44+
)
45+
46+
47+
@autometrics(objective=RANDOM_SLO)
48+
def random_error():
49+
"""This function will randomly return an error or ok."""
50+
51+
result = random.choice(["ok", "error"])
52+
if result == "error":
53+
time.sleep(1)
54+
raise RuntimeError("random error")
55+
return result
56+
57+
3958
ops = Operations()
4059

4160
# Show the docstring (with links to prometheus metrics) for the `add` method
@@ -59,3 +78,4 @@ def div_unhandled(num1, num2):
5978
time.sleep(2)
6079
# Call `div_unhandled` such that it raises an error
6180
div_unhandled(2, 0)
81+
random_error()

0 commit comments

Comments
 (0)