Add Prometheus metrics endpoint with HTTP Basic Auth - #437
Conversation
- New endpoint GET /metrics with Prometheus Text Exposition Format - HTTP Basic Auth using PROMETHEUS_USERNAME/PROMETHEUS_PASSWORD env vars - Returns 503 if credentials not configured - All metrics cached with 5-minute TTL using Rails.cache - Implemented services: * Metrics::S95Collector - main metrics aggregator * Metrics::LocationHealthCalculator - health score calculations * Metrics::VolunteerBusFactorCalculator - bus factor calculations - Metrics implemented: * Event metrics (s95_events_total, s95_activities_total, etc.) * Athlete metrics (s95_athletes_total, s95_athletes_with_user_total, etc.) * Result metrics (s95_activity_average_time_seconds, s95_activity_median_time_seconds, etc.) * Volunteer metrics (s95_volunteers_total, s95_volunteer_bus_factor, etc.) * Location health metrics (s95_location_health_score, active_community, etc.) * Data quality metrics (s95_activity_correct, s95_incorrect_results_total, etc.) - Comprehensive test coverage with RSpec - Documentation added to docs/prometheus_metrics.md
vol1ura
left a comment
There was a problem hiding this comment.
А где будет инстанс прометеуса?
Remove the unused prometheus-client gem and initializer so CI no longer fails due to Gemfile/Gemfile.lock mismatch in deployment mode. Co-authored-by: Cursor <cursoragent@cursor.com>
Use nested module style, sum, explicit parentheses, and trailing comma to satisfy CI lint checks. Co-authored-by: Cursor <cursoragent@cursor.com>
Вариантов масса - пока была мысль забрать своим, построить дашборды и показать как это работает. А там либо рядом поднять в каком-то виде, или на отдельной VDS. В том же клауд.ру бесплатную взять - её хватит |
Switch metrics auth to Authorization header token, remove unnecessary CSRF skip and docs file, and fix LocationHealthCalculator indentation so lint-ruby passes. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Поправил по ревью:
По вопросу про инстанс Prometheus: в этом PR только endpoint scrape_configs:
- job_name: sat_9am_5km
metrics_path: /metrics
authorization:
credentials: <PROMETHEUS_TOKEN>
static_configs:
- targets: ['app.example.com']Если нужно, могу отдельным PR добавить deploy-конфиг для Prometheus. |
3ccb1af to
91c241c
Compare
| private | ||
|
|
||
| def calculate_activity_frequency_factor | ||
| six_months_ago = Date.current - 6.months |
There was a problem hiding this comment.
можно без переменной, сразу 6.months.ago.to_date
| def calculate_activity_frequency_factor | ||
| six_months_ago = Date.current - 6.months | ||
| recent_activities = Activity.published.where(event: @event, date: six_months_ago..).count | ||
| (recent_activities.to_f / 26.0 * 100).clamp(0, 100) |
| factors << calculate_activity_frequency_factor | ||
| factors << calculate_volunteer_consistency_factor | ||
| factors << calculate_athlete_retention_factor | ||
| factors << calculate_results_quality_factor |
There was a problem hiding this comment.
предлагаю все эти методы называть без префикса calculate_
| recent_volunteers = Volunteer.joins(:activity) | ||
| .where(activity: { event: @event, published: true, date: (Date.current - 6.months).. }) | ||
| .distinct.count(:athlete_id) | ||
| total_volunteers = Volunteer.joins(:activity) |
There was a problem hiding this comment.
Тут и выше можно сократить за счёт скоупа
Volunteer.published.where(activity: { event: @event }).distinct.count(:athlete_id)
и аналогично с результатами Result.published...
|
|
||
| def calculate_athlete_retention_factor | ||
| six_months_ago = Date.current - 6.months | ||
| total_athletes = @event.athletes.count |
There was a problem hiding this comment.
это число участников, поставивших это мероприятие домашним забегом - точно именно это имелось в виду?
| total = recent_results.count | ||
| return 100 if total.zero? | ||
|
|
||
| incorrect = recent_results.count { |result| !result.correct? } |
There was a problem hiding this comment.
А зачем это? Мне кажется, не особо актуально. Забег не получится опубликовать, если есть некорректный результат. Единственное, кто-то может в опубликованном забеге сделать правку, но это редкость (сейчас в проде ни одного некорректного результата нет).
Тут разве что может оказаться полезен алерт (возможно)... Но тогда наверно нет смысла за 3 месяца смотреть, достаточно 3 недель (или сколько там сейчас разрешено редактировать старые + 1).
|
|
||
| def metrics_data | ||
| Rails.cache.fetch('s95_metrics', expires_in: Metrics::S95Collector::TTL) do | ||
| Metrics::S95Collector.call |
There was a problem hiding this comment.
Попробовал локально в дев консоли запустить на продовой базе - тут гигантское количество sql запросов, у меня на маке минут 10-15 выполнялось, я не дождался и прервал (с выводом запросов в консоль, т.е. на проде будет конечно быстрее, тем не менее - с ростом базы время будет только расти). Если кеш пустой, серия таких запросов может положить сайт
Ещё было видно, что где-то есть N+1 по атлетам
|
|
||
| get 'up', to: 'rails/health#show' | ||
|
|
||
| get '/metrics', to: 'metrics#show' |
|
|
||
| module Metrics | ||
| class VolunteerBusFactorCalculator < ApplicationService | ||
| ROLES = %w[ |
There was a problem hiding this comment.
тут имя у константы я бы сделал более говорящим, тут же не все возможные роли Volunteer.roles.keys - только те из них,которые имеют значение для проведения
|
|
||
| next if volunteers.zero? | ||
|
|
||
| volunteers * 1.5 |
| ENV['PROMETHEUS_TOKEN'] = 'test-token' | ||
| end | ||
|
|
||
| it 'returns 503 Service Unavailable' do |
There was a problem hiding this comment.
кмк правильнее отдавать 4xx код, например, 401, 403 или 404(типа не существует такого для внешнего мира)
|
@vol1ura cпасибо за ревью и разумные замечания по перфомансу) Что поменял:
|
47b9265 to
0f18e53
Compare
Summary
Metrics Implemented
Event Metrics
Athlete Metrics
Result Metrics
Volunteer Metrics
Location Health Metrics
Data Quality Metrics
Configuration
Set these environment variables to enable the metrics endpoint:
Testing
Run tests with: bundle exec rspec
Documentation
See docs/prometheus_metrics.md for detailed metric descriptions, labels, and Prometheus configuration examples.