diff --git a/Gemfile b/Gemfile index 1b14f588..fb0afd81 100644 --- a/Gemfile +++ b/Gemfile @@ -117,3 +117,5 @@ gem "openssl", "~> 3.1" # mail 2.8.0 breaks sendmail usage: https://github.com/mikel/mail/issues/1538 gem "mail", "~> 2.7.1" + +gem "prometheus-client", "~> 4.0" diff --git a/Gemfile.lock b/Gemfile.lock index 8fd5df6d..f7d3b596 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,6 +279,7 @@ GEM pg (1.4.5) pghero (3.1.0) activerecord (>= 6) + prometheus-client (4.0.0) public_suffix (4.0.7) puma (6.1.0) nio4r (~> 2.0) @@ -522,6 +523,7 @@ DEPENDENCIES openssl (~> 3.1) pg pghero + prometheus-client (~> 4.0) puma pundit (~> 2.3) questiongenerator (~> 1.1) diff --git a/app/controllers/inbox_controller.rb b/app/controllers/inbox_controller.rb index d849dafc..16c47099 100644 --- a/app/controllers/inbox_controller.rb +++ b/app/controllers/inbox_controller.rb @@ -37,6 +37,7 @@ class InboxController < ApplicationController user: current_user) inbox = Inbox.create!(user: current_user, question_id: question.id, new: true) + increment_metric respond_to do |format| format.turbo_stream do @@ -85,4 +86,14 @@ class InboxController < ApplicationController # using .dup to not modify @inbox -- useful in tests @inbox&.dup&.update_all(new: false) # rubocop:disable Rails/SkipsModelValidations end + + def increment_metric + Retrospring::Metrics::QUESTIONS_ASKED.increment( + labels: { + anonymous: true, + followers: false, + generated: true, + } + ) + end end diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb new file mode 100644 index 00000000..dae673a6 --- /dev/null +++ b/app/controllers/metrics_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "prometheus/client/formats/text" + +class MetricsController < ActionController::API + include ActionController::MimeResponds + + def show + render plain: metrics + end + + private + + def metrics + Prometheus::Client::Formats::Text.marshal(Prometheus::Client.registry) + end +end diff --git a/config/routes.rb b/config/routes.rb index 744a8d83..fc210a32 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,6 +49,9 @@ Rails.application.routes.draw do get "/linkfilter", to: "link_filter#index", as: :linkfilter get "/manifest.json", to: "manifests#show", as: :webapp_manifest + # TODO: limit this endpoint + get "/metrics", to: "metrics#show" + # Devise routes devise_for :users, path: "user", skip: %i[sessions registrations] as :user do diff --git a/lib/retrospring/metrics.rb b/lib/retrospring/metrics.rb new file mode 100644 index 00000000..0072fd81 --- /dev/null +++ b/lib/retrospring/metrics.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Retrospring + module Metrics + PROMETHEUS = Prometheus::Client.registry + + # avoid re-registering metrics to make autoreloader happy: + class << self + %i[counter gauge histogram summary].each do |meth| + define_method meth do |name, *args, **kwargs| + PROMETHEUS.public_send(meth, name, *args, **kwargs) + rescue Prometheus::Client::Registry::AlreadyRegisteredError + raise unless Rails.env.development? + + PROMETHEUS.unregister name + retry + end + end + end + + VERSION_INFO = gauge( + :retrospring_version_info, + docstring: "Information about the currently running version", + labels: [:version], + preset_labels: { + version: Retrospring::Version.to_s, + } + ).tap { _1.set 1 } + + QUESTIONS_ASKED = counter( + :retrospring_questions_asked_total, + docstring: "How many questions got asked", + labels: %i[anonymous followers generated] + ) + end +end diff --git a/lib/use_case/question/create.rb b/lib/use_case/question/create.rb index 5cc8f174..a7c1345d 100644 --- a/lib/use_case/question/create.rb +++ b/lib/use_case/question/create.rb @@ -18,6 +18,7 @@ module UseCase return if filtered?(question) increment_asked_count + increment_metric inbox = ::Inbox.create!(user: target_user, question:, new: true) notify(inbox) @@ -92,6 +93,16 @@ module UseCase source_user.save end + def increment_metric + Retrospring::Metrics::QUESTIONS_ASKED.increment( + labels: { + anonymous:, + followers: false, + generated: false, + } + ) + end + def filtered?(question) target_user.mute_rules.any? { |rule| rule.applies_to? question } || (anonymous && AnonymousBlock.where(identifier: question.author_identifier, user_id: [target_user.id, nil]).any?) || diff --git a/lib/use_case/question/create_followers.rb b/lib/use_case/question/create_followers.rb index 21c2e89d..a6bddb0f 100644 --- a/lib/use_case/question/create_followers.rb +++ b/lib/use_case/question/create_followers.rb @@ -17,6 +17,7 @@ module UseCase ) increment_asked_count + increment_metric QuestionWorker.perform_async(source_user_id, question.id) @@ -33,6 +34,16 @@ module UseCase source_user.save end + def increment_metric + Retrospring::Metrics::QUESTIONS_ASKED.increment( + labels: { + anonymous: false, + followers: true, + generated: false, + } + ) + end + def source_user @source_user ||= ::User.find(source_user_id) end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb new file mode 100644 index 00000000..c278e308 --- /dev/null +++ b/spec/controllers/metrics_controller_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe MetricsController, type: :controller do + describe "#show" do + subject { get :show } + + it "returns the metrics" do + # ensure we have at least a metric set + Retrospring::Metrics::VERSION_INFO.set 1 + + expect(subject.body).to include "retrospring_version_info" + end + end +end