From 8c75b7b33347ad1f480bdab5da1766248b8e0386 Mon Sep 17 00:00:00 2001 From: Jeremy Prevost Date: Mon, 26 Feb 2024 10:32:56 -0500 Subject: [PATCH] Adds prometheus metrics collection and endpoint Why are these changes being introduced: * The ability to understand how how timdex api is being used, including which fields are being used and whether any deprecated fields are being requested will help us have necessary information to make decisions going forward * A culture of assessment is part of our discovery strategic plan. Intentional, specific metric collection is an important part of assessing our platform. How does this address that need: * Adds a feature flag, controlled by ENV that enables collection of a few intial metrics and exposes them for collection by prometheus servers Document any side effects to this change: * the metrics endpoint is open to anyone wanting to grab metrics --- Gemfile | 1 + Gemfile.lock | 2 ++ README.md | 1 + app/controllers/graphql_controller.rb | 9 ++++++--- app/graphql/timdex_field_usage_analyzer.rb | 21 +++++++++++++++++++++ config.ru | 10 ++++++++++ config/features.rb | 4 ++++ config/initializers/prometheus_metrics.rb | 14 ++++++++++++++ 8 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 config/initializers/prometheus_metrics.rb diff --git a/Gemfile b/Gemfile index 09a16dbd..a50c4401 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,7 @@ gem 'jwt' gem 'lograge' gem 'mitlibraries-theme', git: 'https://github.com/mitlibraries/mitlibraries-theme', tag: 'v1.2' gem 'opensearch-ruby' +gem 'prometheus-client' gem 'puma' gem 'rack-attack' gem 'rack-cors' diff --git a/Gemfile.lock b/Gemfile.lock index ecfe8d54..6dd301ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -248,6 +248,7 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) pg (1.5.4) + prometheus-client (4.2.2) psych (5.1.2) stringio public_suffix (5.0.4) @@ -433,6 +434,7 @@ DEPENDENCIES mitlibraries-theme! opensearch-ruby pg + prometheus-client puma rack-attack rack-cors diff --git a/README.md b/README.md index 5b8a2595..0f08a77b 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ locally. - `PREFERRED_DOMAIN` - set this to the domain you would like to to use. Any other requests that come to the app will redirect to the root of this domain. This is useful to prevent access to herokuapp.com domains. +- `PROMETHEUS` - If present, enables the Prometheus metrics endpoint and the feature flag to capture metrics - `REQUESTS_PER_PERIOD` - requests allowed before throttling. Default is 100. - `REQUEST_PERIOD` - number of minutes for the period in `REQUESTS_PER_PERIOD`. Default is 1. diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 37ea8b30..f5c2e53c 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -10,9 +10,12 @@ def execute # current_user: current_user, tracers: [request_tracer] } - result = TimdexSchema.execute(query, variables: variables, - context: context, - operation_name: operation_name) + result = TimdexSchema.execute(query, variables:, + context:, + operation_name:) + + Timdex::GraphqlQueriesTotal.increment if Flipflop.enabled?(:prometheus) + render json: result rescue StandardError => e raise e unless Rails.env.development? diff --git a/app/graphql/timdex_field_usage_analyzer.rb b/app/graphql/timdex_field_usage_analyzer.rb index aca1131b..98af4c5d 100644 --- a/app/graphql/timdex_field_usage_analyzer.rb +++ b/app/graphql/timdex_field_usage_analyzer.rb @@ -8,6 +8,27 @@ def result Rails.logger.info("GraphQL used fields: #{@used_fields.to_a}") Rails.logger.info("GraphQL used deprecated fields: #{@used_deprecated_fields.to_a}") Rails.logger.info("GraphQL used deprecated arguments: #{@used_deprecated_arguments.to_a}") + + if Flipflop.enabled?(:prometheus) + # Increment deprecated field usage counter per field + @used_deprecated_fields.each do |field| + Timdex::GraphqlDeprecatedFieldUsage.increment(labels: { field: }) + end + + # Increment field usage counter per field + @used_fields.each do |field| + next if @used_deprecated_fields.include?(field) + next if field.start_with?('_') + + Timdex::GraphqlFieldUsage.increment(labels: { field: }) + end + + # Increment deprecated argument counter per argument + @used_deprecated_arguments.each do |field| + Timdex::GraphqlDeprecatedArguments.increment(labels: { field: }) + end + end + { used_fields: @used_fields.to_a, used_deprecated_fields: @used_deprecated_fields.to_a, diff --git a/config.ru b/config.ru index ad1fbf29..3e65b446 100644 --- a/config.ru +++ b/config.ru @@ -4,3 +4,13 @@ require_relative 'config/environment' run Rails.application Rails.application.load_server + +if ENV.fetch('PROMETHEUS', false).present? + require 'rack' + require 'prometheus/middleware/collector' + require 'prometheus/middleware/exporter' + + use Rack::Deflater + use Prometheus::Middleware::Collector + use Prometheus::Middleware::Exporter +end diff --git a/config/features.rb b/config/features.rb index 744fcdf2..801d5a23 100644 --- a/config/features.rb +++ b/config/features.rb @@ -2,4 +2,8 @@ # Strategies will be used in the order listed here. strategy :session strategy :default + + feature :prometheus, + default: ENV.fetch('PROMETHEUS', false), + description: "Enable prometheus metrics endpoint" end diff --git a/config/initializers/prometheus_metrics.rb b/config/initializers/prometheus_metrics.rb new file mode 100644 index 00000000..a57ec343 --- /dev/null +++ b/config/initializers/prometheus_metrics.rb @@ -0,0 +1,14 @@ +# flipflip features are available to use in initializers so we cheat a bit here. +return unless ENV.fetch('PROMETHEUS', false).present? + +require 'prometheus/client' +require 'prometheus/client/push' + +prometheus = Prometheus::Client.registry +Timdex::GraphqlQueriesTotal = prometheus.counter(:graphql_queries_total, docstring: 'A counter of GraphQL requests made') + +Timdex::GraphqlFieldUsage = prometheus.counter(:graphql_field_usage, docstring: 'A counter of GraphQL fields requested', labels: [:service, :field], preset_labels: { service: "timdex-api" }) + +Timdex::GraphqlDeprecatedFieldUsage = prometheus.counter(:graphql_deprecated_field_usage, docstring: 'A counter of deprecated GraphQL fields requested', labels: [:service, :field], preset_labels: { service: "timdex-api" }) + +Timdex::GraphqlDeprecatedArguments = prometheus.counter(:graphql_deprecated_argument_usage, docstring: 'A counter of deprecated GraphQL arguments requested', labels: [:service, :field], preset_labels: { service: "timdex-api" })