Prometheus метрики в Swift (iOS/macOS): мониторинг мобильных и десктопных приложений
Prometheus — это система мониторинга и оповещения с открытым исходным кодом, которая собирает метрики в виде временных рядов. В экосистеме Apple (iOS и macOS) использование Prometheus позволяет отслеживать производительность приложений, поведение пользователей, состояние сети и другие важные показатели. В этой статье мы рассмотрим, как внедрить Prometheus метрики в Swift-проекты, какие библиотеки для этого существуют и как настроить экспорт данных.
Зачем нужны Prometheus метрики в мобильных приложениях?
Мобильные и десктопные приложения часто работают в условиях ограниченных ресурсов и нестабильного соединения. Сбор метрик помогает:
- Выявлять узкие места производительности (задержки UI, утечки памяти)
- Отслеживать частоту и типы ошибок (crash, network errors)
- Анализировать пользовательское поведение (время сессии, экраны)
- Настраивать алерты на критические значения (например, время ответа API > 5 сек)
Основные типы метрик Prometheus
Prometheus поддерживает четыре основных типа метрик, которые можно реализовать в Swift:
- Counter (счётчик) — монотонно возрастающее значение (количество запросов, ошибок)
- Gauge (датчик) — произвольное значение, которое может увеличиваться и уменьшаться (текущая память, активные пользователи)
- Histogram (гистограмма) — распределение значений по заданным интервалам (время выполнения функций)
- Summary (суммарий) — распределение с квантилями (задержки)
Библиотеки для Swift
На данный момент наиболее популярная библиотека для работы с Prometheus в Swift — SwiftPrometheus. Она предоставляет простой API для создания и регистрации метрик, а также поддерживает экспорт в формате, совместимом с Prometheus.
Установка через Swift Package Manager:
// Package.swift
dependencies: [
.package(url: "https://github.com/reddavis/SwiftPrometheus.git", from: "1.0.0")
]
Примеры кода на Swift
1. Создание счётчика запросов к API
import SwiftPrometheus
let prometheus = Prometheus()
let apiRequests = prometheus.createCounter(
named: "api_requests_total",
help: "Total number of API requests",
labels: ["endpoint"]
)
// Инкремент при каждом запросе
apiRequests.inc(["endpoint": "/users"])
apiRequests.inc(["endpoint": "/posts"])
2. Gauge для отслеживания памяти
let memoryGauge = prometheus.createGauge(
named: "app_memory_bytes",
help: "Current memory usage in bytes"
)
// Обновление при изменении памяти
memoryGauge.set(Double(reportMemoryUsage()))
3. Гистограмма времени загрузки данных
let loadDuration = prometheus.createHistogram(
named: "data_load_duration_seconds",
help: "Histogram of data loading time",
buckets: [0.1, 0.5, 1.0, 2.0, 5.0]
)
let start = CFAbsoluteTimeGetCurrent()
// ... загрузка данных
let elapsed = CFAbsoluteTimeGetCurrent() - start
loadDuration.observe(elapsed)
Экспорт метрик в Prometheus
Чтобы Prometheus мог собирать метрики, ваше приложение должно предоставить эндпоинт, возвращающий данные в текстовом формате (plain text exposition format). Для iOS/macOS это можно сделать через локальный HTTP-сервер (например, используя Network.framework или сторонние библиотеки).
Пример простого HTTP-сервера для экспорта:
import SwiftPrometheus
import Network
class MetricsServer {
let prometheus: Prometheus
var listener: NWListener?
init(prometheus: Prometheus, port: UInt16 = 9090) {
self.prometheus = prometheus
startServer(port: port)
}
private func startServer(port: UInt16) {
let params = NWParameters.tcp
listener = try? NWListener(using: params, on: NWEndpoint.Port(rawValue: port)!)
listener?.newConnectionHandler = { [weak self] connection in
connection.start(queue: .global())
self?.handleConnection(connection)
}
listener?.start(queue: .global())
}
private func handleConnection(_ connection: NWConnection) {
// Формируем ответ с метриками в формате Prometheus
let metricsText = prometheus.outputString()
let response = """
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: \(metricsText.utf8.count)
\(metricsText)
"""
connection.send(content: response.data(using: .utf8), completion: .contentProcessed({ _ in
connection.cancel()
}