Описание
Мы используем CDN для раздачи статичных файлов и хотим иметь статистику по этой раздаче (коды ответов, rps, полосу пропускания)
Первый и самый главный вопрос - откуда получить эти данные? Конечно же из CDN API.
Ищем… Ага! Статистика по конкретному аккаунту с разбивкой по ресурсам
Пример успешного ответа realtimestat
[
{
"http_status_percent": {
"200": 8.3333,
"304": 91.6667
},
"account": "aoprc3vtj4",
"cache_status_percent_by_requests": {
"HIT": 0,
"MISS": 100
},
"cache_status_percent_by_volume": {
"HIT": 0,
"MISS": 100
},
"requests_per_second": 1.1903,
"bandwidth_out_bits_per_second": 3656.8472,
"resource": "u7qy39gwbj"
},
{
"http_status_percent": {
"200": 9.1443,
"304": 90.8557
},
"account": "aoprc3vtj4",
"cache_status_percent_by_requests": {
"HIT": 5.5746,
"MISS": 94.4254
},
"cache_status_percent_by_volume": {
"HIT": 99.7363,
"MISS": 0.2637
},
"requests_per_second": 101.3832,
"bandwidth_out_bits_per_second": 129778356.037,
"resource": "bgc2555isn"
}
]
В ответе есть, всё, что надо:
- http_status_percent. Коды ответов в процентном соотношении от общего числа ответов
- requests_per_second. Запросы в секунду
- bandwidth_out_bits_per_second. Исходящий канал
Код
Нет смысла копировать весь код в статью. Исходники можно найти здесь
Разберём основные моменты.
main.py
Запускаем http сервер с хэндлером из прометеевской либы, который уже умеет отдавать при запросе все добавленные в коде метрики.
class HttpHandler(MetricsHandler)
HTTPServer(("0.0.0.0", settings.get("WEB_PORT", 8080)), HttpHandler).serve_forever()
При этом в нашем хэндлере мы:
-
Инициализируем класс для обновления метрик
metrics = Metrics()
-
Переопределяем метод
do_GET
, чтобы прямо при обработке запроса на путь/metrics
обновлять метрики с помощьюself.metrics.refresh_metrics()
. При этом не забываем вызвать родительский методdo_GET()
черезsuper()
, чтобы отдать метрики в формате прометея.def do_GET(self): if self.path == "/metrics": self.metrics.refresh_metrics() super().do_GET() else: self.send_error(404)
metrics.py
В этом файле описан класс для непосредственного обновления метрик прометея
class Metrics():
def __init__(self):
self.cdn = CDN(
username=settings.get("CDN_USERNAME"),
password=settings.get("CDN_PASSWORD"),
account_name=settings.get("CDN_ACCOUNT_NAME"),
url=settings.get("CDN_URL"))
self.resources_map = {}
self.labels_map = {
"http_status_percent": "code",
"cache_status_percent_by_requests": "status",
"cache_status_percent_by_volume": "status"
}
self.gauges = {}
self.update_errors_count = 0
self.update_resources()
-
При инициализации создаём инстанс
cdn = CDN(...)
, чтобы через него работать с CDN API -
В
resources_map
при инициализации вызовомupdate_resources()
мы сохраняем соответствие id ресурса и его имениdef update_resources(self): for resource in self.cdn.get_resource(): self.resources_map[resource["id"]] = resource["name"]
Это нужно для того, чтобы в лейблах метрик было читаемое название ресурса, а не id, который отдаётся в ответе API (см. выше Пример успешного ответа realtimestat).
-
С помощью
labels_map
мы преобразуем ключи полей ответов в значения соответсвующих лейблов. К примеру, чтобы из"http_status_percent": { "200": 8.3333, "304": 91.6667 }
получитьhttp_status_percent{code="200"} 8.3333
иhttp_status_percent{code="304"} 91.6667
-
В
gauges
по соответствующим ключам мы храним объекты метрик Gauge, которые обновляются в методеupdate_metrics()
Метрики обновляем через метод refresh_metrics()
def refresh_metrics(self):
self.clear_metrics()
try:
self.update_metrics()
self.update_errors_count = 0
except HTTPError as e:
if self.update_errors_count > 2:
log.error(f'I can not update metrics. Error "{e}". Bye!')
sys.exit(1)
self.update_errors_count += 1
log.info(f'#{self.update_errors_count} Update metrics error "{e}". I will try to update token and continue')
self.metrics.cdn._refresh_token()
self.refresh_metrics()
-
Метрики мы сбрасываем перед обновлением методом
clear_metrics()
, т.к. коды ответов мы получаем уже в процентном соотношении к общему числу ответов и не хотим накапливать старые данные в gauge’ахdef clear_metrics(self): for gauge in self.gauges.values(): gauge.clear()
-
Т.к. мы не следим за свежестью токена, то просто делаем пару ретраев при попытке обновить метрики. В том числе пробуем обновить токен
Результат
Пример отдаваемых метрик
# HELP http_status_percent http_status_percent
# TYPE http_status_percent gauge
http_status_percent{account="aoprc3vtj4",code="304",resource_id="u7qy39gwbj",resource_name="static_example_com"} 100.0
http_status_percent{account="aoprc3vtj4",code="200",resource_id="bgc2555isn",resource_name="images_example_com"} 6.6667
http_status_percent{account="aoprc3vtj4",code="404",resource_id="bgc2555isn",resource_name="images_example_com"} 1.5385
http_status_percent{account="aoprc3vtj4",code="304",resource_id="bgc2555isn",resource_name="images_example_com"} 91.2821
http_status_percent{account="aoprc3vtj4",code="502",resource_id="bgc2555isn",resource_name="images_example_com"} 0.5128
# HELP cache_status_percent_by_requests cache_status_percent_by_requests
# TYPE cache_status_percent_by_requests gauge
cache_status_percent_by_requests{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com",status="HIT"} 0.0
cache_status_percent_by_requests{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com",status="MISS"} 100.0
cache_status_percent_by_requests{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com",status="HIT"} 3.0769
cache_status_percent_by_requests{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com",status="MISS"} 96.9231
# HELP cache_status_percent_by_volume cache_status_percent_by_volume
# TYPE cache_status_percent_by_volume gauge
cache_status_percent_by_volume{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com",status="HIT"} 0.0
cache_status_percent_by_volume{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com",status="MISS"} 100.0
cache_status_percent_by_volume{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com",status="HIT"} 99.7153
cache_status_percent_by_volume{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com",status="MISS"} 0.2847
# HELP requests_per_second requests_per_second
# TYPE requests_per_second gauge
requests_per_second{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com"} 0.0493
requests_per_second{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com"} 9.6546
# HELP bandwidth_out_bits_per_second bandwidth_out_bits_per_second
# TYPE bandwidth_out_bits_per_second gauge
bandwidth_out_bits_per_second{account="aoprc3vtj4",resource_id="u7qy39gwbj",resource_name="static_example_com"} 118.7743
bandwidth_out_bits_per_second{account="aoprc3vtj4",resource_id="bgc2555isn",resource_name="images_example_com"} 7.6798019124e+06