From 5977eb3beddd01f04260008a963f11381fa6dbe4 Mon Sep 17 00:00:00 2001 From: Niklas van Schrick Date: Thu, 22 Jan 2026 21:50:10 +0100 Subject: [PATCH] Add performance tracking to graphql --- app/controllers/graphql_controller.rb | 49 ++++++++++++++++++- .../graphql_performance_tracing.rb | 18 +++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 config/initializers/graphql_performance_tracing.rb diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 89217fa3..d7fb533a 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -26,7 +26,10 @@ def execute } Code0::ZeroTrack::Context.with_context(user: { id: current_user&.id, username: current_user&.username }) do - result = SagittariusSchema.execute(query, variables: variables, context: context, operation_name: operation_name) + result = with_performance_tracking(current_user) do + SagittariusSchema.execute(query, variables: variables, context: context, operation_name: operation_name) + end + render json: result rescue StandardError => e logger.error message: e.message, backtrace: e.backtrace, exception_class: e.class @@ -89,4 +92,48 @@ def anonymous_mutation? usersPasswordReset ].include?(mutation_name) end + + def with_performance_tracking(current_user) + return yield unless current_user&.admin + + PerformanceCollector.start_performance_tracking + + result = yield + + queries = PerformanceCollector.sql_queries || [] + total_duration = ((Sagittarius::Utils.monotonic_time - PerformanceCollector.query_start_time) * 1000).round(2) + + include_queries = request.headers['X-Sagittarius-Performance-Details']&.include?('queries') + + result['extensions'] ||= {} + result['extensions']['performance'] = { + total_duration_ms: total_duration, + query_count: queries.size, + query_duration_ms: queries.sum { |q| q[:duration_ms] }.round(2), + queries: include_queries ? queries : nil, + }.compact + + PerformanceCollector.end_performance_tracking + + result + end + + class PerformanceCollector < ActiveSupport::CurrentAttributes + attribute :sql_queries, :query_start_time + + def self.start_performance_tracking + self.sql_queries = [] + self.query_start_time = Sagittarius::Utils.monotonic_time + end + + def self.end_performance_tracking + self.sql_queries = nil + self.query_start_time = nil + end + + def self.add_query(sql:, duration_ms:, name:, cached:) + self.sql_queries ||= [] + self.sql_queries << { sql: sql, duration_ms: duration_ms, name: name, cached: !!cached } + end + end end diff --git a/config/initializers/graphql_performance_tracing.rb b/config/initializers/graphql_performance_tracing.rb new file mode 100644 index 00000000..87807f58 --- /dev/null +++ b/config/initializers/graphql_performance_tracing.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +Rails.application.config.after_initialize do + ActiveSupport::Notifications.subscribe('sql.active_record') do |*args| + event = ActiveSupport::Notifications::Event.new(*args) + + next if event.payload[:name] == 'SCHEMA' + next if event.payload[:name] == 'CACHE' + next unless GraphqlController::PerformanceCollector.sql_queries # Only track if initialized + + GraphqlController::PerformanceCollector.add_query( + sql: event.payload[:sql], + duration_ms: event.duration.round(2), + name: event.payload[:name], + cached: event.payload[:cached] + ) + end +end