mentortools/libs/: prometheus-tools-abm-5.1.60181a0 metadata and description

Simple index Stable version available

tools to collect metrics to prometheus

author Vasya Svintsov
author_email v.svintsov@techokert.ru
classifiers
  • Programming Language :: Python :: 3
  • Programming Language :: Python :: 3.11
description_content_type text/markdown
requires_dist
  • http-tools-abm (>=5.3.59932,<6.0.0)
  • more-itertools (>=10.2.0,<11.0.0)
  • prometheus-client (>=0.17.0,<1)
requires_python >=3.11,<4.0
File Tox results History
prometheus_tools_abm-5.1.60181a0-py3-none-any.whl
Size
10 KB
Type
Python Wheel
Python
3
prometheus_tools_abm-5.1.60181a0.tar.gz
Size
9 KB
Type
Source

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()