Что в метриках тебе моих?
О чём могут поведать цифры и графики в мониторинге и зачем вообще заморачиваться.
Андрей Новиков
RubyRussia 2018









🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥

Чего мы хотим?
- Знать, как быстро растут и разгребаются очереди
- Знать, какие задачи наиболее «проблемные»
- Понимать, когда это началось и кто виноват
- Понимать, помогли ли принятые нами меры

Чего мы хотим?
- Больше метрик
- Больше графиков
- Ещё больше графиков
- … и ещё два монитора

Недостающие метрики
- Время обработки задач
- Количество новых задач
Недостающие графики
- Динамика изменения размера очередей по отдельным очередям
- Динамика изменения времени выполнения задач по классам
Мониторинг веб-приложения
- Сервера: загрузка CPU, RAM, IO, LA, заполненность дисков, …
- Веб: количество запросов, время их обработки, queueing time, …
- Отложенные задачи: количество задач, время их обработки, количество ошибок, …
- Базы данных: количество запросов, время обработки, объём данных, …
- Внешние API: количество запросов, время обработки, ошибки…
- Сеть: задержки, DNS lookup time, throughput, потери пакетов, …
- Свои показатели: свой набор под каждую интеграцию
- Бизнес-метрики: количество пользователей, заказов, 🤑, …


А зачем?
Чтобы понять, откуда вдруг запахло жареным:
- 
	Откуда — какой участок приложения горит. 
- 
	Вдруг — когда загорелось. 5 минут назад, после деплоя или медленно, но верно разгоралось по мере роста сервиса? 
- 
	Запах — в чём именно проблема? ЦП, память, внешние сервисы? 



Теория
🤓
Мониторинг
Непрерывный процесс наблюдения и регистрации параметров системы в сравнении с заданными критериями
«Вы не можете контролировать то, что не можете измерить»
— Том ДеМарко
Метрики
- Параметры системы
- Численные значения, полученные при измерении этих параметров работающей системы
Временные ряды (time series)
Значения какой либо величины в порядке времени измерения
Для хранения разработаны специальные базы данных:
InfluxDB, Graphite, …

Ярлыки (labels)
Позволяют сегментировать значения и агрегировать их вновь
Для каждого уникального набора пишется свой временной ряд
Есть опасность комбинаторного взрыва (не стоит записывать в них id пользователей)

Счётчик
CRDT: G-Counter — монотонно возрастающий счётчик
Базовый тип для более сложных метрик: histogram, …
Позволяет уверенно детектировать рестарты и суммировать значения хоть с тысячи воркеров.
Gauge
Произвольное значение, без ограничений.
Используется для мониторинга состояния «внешнего мира»: Загрузка CPU, потребление памяти, количество процессов.

Histogram и Summary
Сложные типы для сбора статистики по времени выполнения или потребления ресурсов: перцентили, среднее, медиана, …
Histogram: набор счётчиков по «корзинам».
Summary: Предпросчёт требуемой статистики клиентом на ходу.
| Prometheus | Librato | NewRelic | |
|---|---|---|---|
| Типы метрик: (🔧 — настраивается) | histogram 🔧 summary 🔧 | summary 🔧(перцентили) | summary (min, max, сумма квадратов, …) | 
DYI: Prometheus
Регистрация метрик
prometheus = Prometheus::Client.registry
prometheus.counter   :sidekiq_jobs_executed_total,   "A counter of the total number of jobs sidekiq executed."
prometheus.counter   :sidekiq_jobs_success_total,    "A counter of the total number of jobs successfully processed by sidekiq."
prometheus.counter   :sidekiq_jobs_failed_total,     "A counter of the total number of jobs failed in sidekiq."
prometheus.histogram :sidekiq_job_runtime_seconds,   "A histogram of the job execution time."
prometheus.gauge     :sidekiq_jobs_waiting_count,    "The number of jobs waiting to process in sidekiq."
prometheus.gauge     :sidekiq_active_workers_count,  "The number of currently running machines with sidekiq workers."
prometheus.gauge     :sidekiq_jobs_scheduled_count,  "The number of jobs scheduled for later execution."
prometheus.gauge     :sidekiq_jobs_retry_count,      "The number of failed jobs waiting to be retried"
prometheus.gauge     :sidekiq_jobs_dead_count,       "The number of jobs exceeded their retry count."
prometheus.gauge     :sidekiq_active_processes,      "The number of active Sidekiq worker processes."DYI: Prometheus
Обновление метрик
class SidekiqWorkerPrometheusMiddleware
  def call(worker, job, queue)
    prometheus = Prometheus::Client.registry
    labels = { queue: queue, worker: worker.class }
    start = Time.now
    begin
      yield
      prometheus.get(:sidekiq_jobs_success_total).increment(labels)
    rescue Exception # rubocop: disable Lint/RescueException
      prometheus.get(:sidekiq_jobs_failed_total).increment(labels)
      raise
    ensure
      prometheus.get(:sidekiq_job_runtime_seconds).observe(labels, (Time.now - start).round(3))
      prometheus.get(:sidekiq_jobs_executed_total).increment(labels)
    end
  end
end
DYI: Prometheus
Обновление метрик
class SidekiqExporter < ::Prometheus::Middleware::Exporter
  def call(env)
    refresh_sidekiq_stats if env["REQUEST_PATH"].start_with?(path)
    super
  end
  def refresh_sidekiq_stats
    r = Prometheus.registry
    stats = ::Sidekiq::Stats.new
    stats.queues.each do |k, v|
      r.get(:sidekiq_jobs_waiting_count).set({ queue: k }, v)
    end
    r.get(:sidekiq_active_workers_count).set({}, stats.workers_size)
    r.get(:sidekiq_jobs_scheduled_count).set({}, stats.scheduled_size)
    r.get(:sidekiq_jobs_dead_count).set({}, stats.dead_size)
    r.get(:sidekiq_active_processes).set({}, stats.processes_size)
  end
endDYI: Prometheus
Вывод метрик
Sidekiq.configure_server do |config|
  config.redis = redis_credentials
  config.server_middleware do |chain|
    chain.add SidekiqWorkerPrometheusMiddleware
  end
  # Start server to expose metrics for Prometheus collector
  Thread.new do
    Rack::Handler::WEBrick.run(
      Rack::Builder.new do
        use Rack::CommonLogger, ::Sidekiq.logger
        use SidekiqExporter, registry: Prometheus.registry
        run ->(_env) { [404, {"Content-Type" => "text/plain"}, ["Not Found\n"]] }
      end,
      Host: ENV["PROMETHEUS_EXPORTER_BIND"] || "0.0.0.0",
      Port: ENV.fetch("PROMETHEUS_EXPORTER_PORT", 9310),
      AccessLog: []
    )
  end
endDYI: Prometheus
Успех!
curl http://localhost:9091/metrics
# TYPE sidekiq_jobs_executed_total counter
# HELP sidekiq_jobs_executed_total A counter of the total number of jobs sidekiq executed.
sidekiq_jobs_executed_total{queue="utils",worker="EmptyJob"} 16
sidekiq_jobs_executed_total{queue="default",worker="WaitingJob"} 160
# TYPE sidekiq_jobs_waiting_count gauge
# HELP sidekiq_jobs_waiting_count The number of jobs waiting to process in sidekiq.
sidekiq_jobs_waiting_count{queue="utils"} 2190
sidekiq_jobs_waiting_count{queue="default"} 1490
# TYPE sidekiq_job_runtime_seconds histogram
# HELP sidekiq_job_runtime_seconds A histogram of the job execution time.
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="0.5"} 9.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="1"} 16.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="5"} 16.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="10"} 16.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="60"} 16.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="300"} 16.0
sidekiq_job_runtime_seconds_bucket{queue="utils",worker="EmptyJob",le="+Inf"} 16.0
sidekiq_job_runtime_seconds_sum{queue="utils",worker="EmptyJob"} 7.963
sidekiq_job_runtime_seconds_count{queue="utils",worker="EmptyJob"} 16.0


А теперь рисуем 🎨







Алерты!
Пусть роботы анализируют метрики
🔔
yabeda
Набор библиотек для удобной работы с метриками
# Gemfile
gem "yabeda-rails"
# Yay! Standard RoR metrics already tracked!
gem "yabeda-sidekiq"
# Yay! Sidekiq metrics are registered and tracked!
gem "yabeda-prometheus"
# Now you can export metrics to Prometheus
# But some configuration is still needed
gem "yabeda-newrelic"
# And to NewRelic at the same time (not yet released though)Пока WIP :-)
yabeda
DSL для описания метрик
Yabeda.configure do
  group :sidekiq
  counter(:jobs_total_count, comment: "Total number of jobs sidekiq executed.")
  counter(:jobs_success_count, comment: "Total number of jobs successfully processed by sidekiq.")
  counter(:jobs_failed_count, comment: "Total number of jobs failed in sidekiq.")
  histogram(:job_runtime, unit: :seconds, comment: "Job execution time.")
  gauge(:jobs_waiting_count, comment: "The number of jobs waiting to process in sidekiq.")
endyabeda
DSL для сбора метрик
# Явно в коде
Yabeda.things_duration.measure({type: :foo}, 10)
Yabeda.things_happened_total.increment({type: :foo})
Yabeda.configure do
  # Подписываемся на события в системе
  ActiveSupport::Notifications.subscribe 'occurence.custom_things' do |*args|
    things_duration.measure({type: :foo}, 10)
    things_happened_total.increment({type: :foo})
  end
  # Получаем текущие значения когда приходит время отдать накопленные метрики
  collect do
    things_alive_total.set({}, Thing.alive.count)
  end
endyabeda
Хуки для сбора собственных метрик
Yabeda::Rails.after_controller_action do |event|
  tags = {}
  external_service_requests_total.increment(tags)
  external_service_latency_seconds.measure(tags, event.duration)
end
Различия систем мониторинга
| Prometheus | Librato | NewRelic | |
|---|---|---|---|
| Метод сбора: | Опрос сервером клиентов | 1. Отправка с клиента на сервер 2. Периодический вывод в лог | Отправка с клиента на сервер | 
| Типы метрик: | counter gauge histogram summary | counter gauge summary | counter gauge histogram | 
yabeda
Набор библиотек для удобной работы с метриками
Попробовать в деле:
github.com/yabeda-rb/example-prometheus-sidekiq
И это всё?
- Наличие подробных метрик приложения и его окружения драматично упрощает отладку (если озаботиться заранее).
- Продакшен по прежнему падает, но время восстановления меньше
- Изменения и оптимизации больше не делаются в слепую.
- С одного взгляда можно ответить на вопрос «Ну чо, как?»
- Можно бесконечно смотреть на три вещи: огонь, воду и графики.
Кажись, всё
✅ Метрики в сайдкике разобрал
✅ Теорию про мониторинг рассказал
✅ Ruby-код показал
✅ Гемы пропиарил

Вопросы?
GitHub, Twitter, Telegram: @Envek
Нам нужны твои мозги!
Скоро опубликуем продолжение!

Что в метриках тебе моих?
By Andrey “Envek” Novikov
Что в метриках тебе моих?
О чём могут поведать цифры и графики в мониторинге и зачем вообще заморачиваться.
- 3,119


