mentortools/libs/: prometheus-tools-abm-5.1.60181a0 metadata and description
tools to collect metrics to prometheus
| author | Vasya Svintsov |
| author_email | v.svintsov@techokert.ru |
| classifiers |
|
| description_content_type | text/markdown |
| requires_dist |
|
| requires_python | >=3.11,<4.0 |
| File | Tox results | History |
|---|---|---|
prometheus_tools_abm-5.1.60181a0-py3-none-any.whl
|
|
|
prometheus_tools_abm-5.1.60181a0.tar.gz
|
|
Quick Start
EXAMPLE 1: Basic usage
Допустим, у нас есть http-сервер, бизнес-логика которого - отправка писем
Будем собирать количество отправленных писем
Для этого применим счётчик (counter)
import asyncio
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
async def send_email() -> None:
pass
async def main():
http_server = HttpServer(HttpServer.Config(port=9999), HttpServer.Context(instance_id='example'))
metrics = PrometheusController(PrometheusController.Config(), PrometheusController.Context(http_server))
await http_server.async_init() # запускаем http_server
async def do(): # эмулируем выполнение работы сервиса
await send_email() # отправляем письмо
metrics.counter('email_sent').inc() # инкрементируем счетчик писем
while True:
await do()
await asyncio.sleep(1)
asyncio.run(main())
Получение собранных метрик:
curl --location 'http://0.0.0.0:9999/metrics'
EXAMPLE 2: Basic usage with labels
Расширим прошлый пример:
Теперь отправка может быть успешной или провальной
Тогда имеет смысл собирать количество писем с учётом статуса отправки
Для этого используются метки(labels)
Метки представляют собой словарь, см. пример
import asyncio
import random
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
async def send_email() -> bool:
return bool(random.randint(0, 1)) # эмулируем статус отправки письма
async def main():
http_server = HttpServer(HttpServer.Config(port=9999), HttpServer.Context(instance_id='example'))
metrics = PrometheusController(PrometheusController.Config(), PrometheusController.Context(http_server))
await http_server.async_init()
async def do(): # эмулируем выполнение работы сервиса
sent_status = await send_email() # отправляем письмо
metrics.counter('email_sent', {'sent_status': sent_status}).inc() # инкрементируем счетчик писем
while True:
await do()
await asyncio.sleep(1)
asyncio.run(main())
ВАЖНО!
- Перечень ключей меток одной метрики всегда должен быть одинаковым. В примере выше всегда передаётся одна метка
sent_status- Перечень значений каждой метки должен быть ограничен. В примере выше у метки есть два варианта значений: true/false. Никогда нельзя использовать таймштемп в качестве значения метки
EXAMPLE 3: Track without labels
Расширим прошлый пример:
Теперь отправка писем занимает длительное время
Тогда имеет смысл так же собирать затраченное время
Для этого в библиотеке есть метод track
import asyncio
import random
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
async def send_email() -> bool:
await asyncio.sleep(0.5 + random.random()) # эмулируем длительное выполнение
return bool(random.randint(0, 1)) # эмулируем статус отправки письма
async def main():
http_server = HttpServer(HttpServer.Config(port=9999), HttpServer.Context(instance_id='example'))
metrics = PrometheusController(PrometheusController.Config(), PrometheusController.Context(http_server))
await http_server.async_init()
async def do(): # эмулируем выполнение работы сервиса
with metrics.track('email_sending'): # отслеживаем затраченное время и количество текущих отправок
sent_status = await send_email() # отправляем письмо
metrics.counter('email_sent', {'sent_status': sent_status}).inc() # инкрементируем счетчик писем
while True:
await do()
await asyncio.sleep(1)
asyncio.run(main())
EXAMPLE 4: Track with labels
Расширим прошлый пример:
Теперь отправка писем может быстро завершиться исключением
Тогда имеет смысл маркировать затраченное время фактом выполнения или возникновения исключения
Иначе метрики затраченного времени будут сильно искажены
import asyncio
import random
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
async def send_email() -> bool:
assert random.random() < 0.8 # эмулируем периодически возникающие исключения
await asyncio.sleep(0.5 + random.random()) # эмулируем длительное выполнение
return bool(random.randint(0, 1)) # эмулируем статус отправки письма
async def main():
http_server = HttpServer(HttpServer.Config(port=9999), HttpServer.Context(instance_id='example'))
metrics = PrometheusController(PrometheusController.Config(), PrometheusController.Context(http_server))
await http_server.async_init()
async def do(): # эмулируем выполнение работы сервиса
# поскольку перечень меток должен быть одинаковым, то все метки, которые будут проставлены внутри,
# должны быть указаны при создании трекера
with metrics.track('email_sending', {'sent_status': None}) as tracker:
sent_status = await send_email() # отправляем письмо
tracker.labels['sent_status'] = sent_status # проставляем трекеру значение метки
metrics.counter('email_sent', {'sent_status': sent_status}).inc() # инкрементируем счетчик писем
while True:
await do()
await asyncio.sleep(1)
asyncio.run(main())
EXAMPLE 5: Track with complex labels
Расширим прошлый пример:
Теперь отправка писем может завершиться разными исключениями, а отправка письма имеет тему
Тогда, имеет смысл добавить метку с типом письма и сохранять тип исключения: выделим его в отдельную метку
import asyncio
import random
from typing import Literal
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
async def send_email() -> bool:
if random.random() > 0.9: raise ValueError # эмулируем периодически возникающие первого исключения
if random.random() > 0.8: raise TypeError # эмулируем периодически возникающие второго исключения
await asyncio.sleep(0.5 + random.random()) # эмулируем длительное выполнение
return bool(random.randint(0, 1)) # эмулируем статус отправки письма
async def main():
http_server = HttpServer(HttpServer.Config(port=9999), HttpServer.Context(instance_id='example'))
metrics = PrometheusController(PrometheusController.Config(), PrometheusController.Context(http_server))
await http_server.async_init()
async def do(email_type: Literal['announcement'] | Literal['newsletter']): # эмулируем выполнение работы сервиса
with metrics.track('emails', {'type': email_type, 'status': None, 'error': None}) as tracker:
try:
sent_status = await send_email() # отправляем письмо
tracker.labels['status'] = sent_status # записываем в метку статус отправки письма
metrics.counter('email_sent', tracker.labels).inc() # инкрементируем счетчик писем
except Exception as er:
tracker.labels['error'] = type(er).__name__ # записываем в метку тип ошибки
while True:
for email_type in ('announcement', 'newsletter'): # эмулируем разные типы писем
await do(email_type)
await asyncio.sleep(1)
asyncio.run(main())
EXAMPLE 6: Initer
Этот пример показывает, как предполагается инициализировать данную библиотеку в сервисах, использующих Initer
from dataclasses import dataclass
import init_helpers
from aiohttp import ClientSession
from async_tools import AsyncInitable, AsyncDeinitable
from http_tools import HttpServer
from prometheus_tools.prometheus_controller import PrometheusController
@dataclass
class Initer:
@dataclass
class Config:
http_server: HttpServer.Config
metrics: PrometheusController.Config
config: Config
@dataclass
class Context(AsyncInitable, AsyncDeinitable):
instance_id: str = "Example"
http_server: HttpServer = None
metrics: PrometheusController = None
session: ClientSession = None
def __post_init__(self) -> None:
AsyncInitable.__init__(self)
AsyncDeinitable.__init__(self)
context: Context
def __init__(self) -> None:
self.config = init_helpers.parse_args(config_file=init_helpers.Arg.ini_file_to_dataclass(self.Config))
self.context = self.Context()
async def __aenter__(self) -> None:
self.context.http_server = HttpServer(self.config.http_server, self.context)
self.context.metrics = PrometheusController(self.config.metrics, self.context)
self.session = self.context.metrics.get_monitored_client_session()
await self.context.async_init()
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
if self.context.session is not None:
await self.context.session.close()
await self.context.async_deinit()