diff options
Diffstat (limited to 'lib/gitlab/graphql/tracers')
-rw-r--r-- | lib/gitlab/graphql/tracers/application_context_tracer.rb | 40 | ||||
-rw-r--r-- | lib/gitlab/graphql/tracers/logger_tracer.rb | 58 | ||||
-rw-r--r-- | lib/gitlab/graphql/tracers/metrics_tracer.rb | 48 | ||||
-rw-r--r-- | lib/gitlab/graphql/tracers/timer_tracer.rb | 31 |
4 files changed, 177 insertions, 0 deletions
diff --git a/lib/gitlab/graphql/tracers/application_context_tracer.rb b/lib/gitlab/graphql/tracers/application_context_tracer.rb new file mode 100644 index 00000000000..4193c46e321 --- /dev/null +++ b/lib/gitlab/graphql/tracers/application_context_tracer.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Tracers + # This graphql-ruby tracer sets up `ApplicationContext` for certain operations. + class ApplicationContextTracer + def self.use(schema) + schema.tracer(self.new) + end + + # See docs on expected interface for trace + # https://graphql-ruby.org/api-doc/1.12.17/GraphQL/Tracing + def trace(key, data) + case key + when "execute_query" + operation = known_operation(data) + + ::Gitlab::ApplicationContext.with_context(caller_id: operation.to_caller_id) do + yield + end + else + yield + end + end + + private + + def known_operation(data) + # The library guarantees that we should have :query for execute_query, but we're being defensive here + query = data.fetch(:query, nil) + + return ::Gitlab::Graphql::KnownOperations.UNKNOWN unless query + + ::Gitlab::Graphql::KnownOperations.default.from_query(query) + end + end + end + end +end diff --git a/lib/gitlab/graphql/tracers/logger_tracer.rb b/lib/gitlab/graphql/tracers/logger_tracer.rb new file mode 100644 index 00000000000..c7ba56824db --- /dev/null +++ b/lib/gitlab/graphql/tracers/logger_tracer.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Tracers + # This tracer writes logs for certain trace events. + # It reads duration metadata written by TimerTracer. + class LoggerTracer + def self.use(schema) + schema.tracer(self.new) + end + + def trace(key, data) + result = yield + + case key + when "execute_query" + log_execute_query(**data) + end + + result + end + + private + + def log_execute_query(query: nil, duration_s: 0) + # execute_query should always have :query, but we're just being defensive + return unless query + + analysis_info = query.context[:gl_analysis]&.transform_keys { |key| "query_analysis.#{key}" } + info = { + trace_type: 'execute_query', + query_fingerprint: query.fingerprint, + duration_s: duration_s, + operation_name: query.operation_name, + operation_fingerprint: query.operation_fingerprint, + is_mutation: query.mutation?, + variables: clean_variables(query.provided_variables), + query_string: query.query_string + } + + info.merge!(::Gitlab::ApplicationContext.current) + info.merge!(analysis_info) if analysis_info + + ::Gitlab::GraphqlLogger.info(info) + end + + def clean_variables(variables) + filtered = ActiveSupport::ParameterFilter + .new(::Rails.application.config.filter_parameters) + .filter(variables) + + filtered&.to_s + end + end + end + end +end diff --git a/lib/gitlab/graphql/tracers/metrics_tracer.rb b/lib/gitlab/graphql/tracers/metrics_tracer.rb new file mode 100644 index 00000000000..9fc001c0a6d --- /dev/null +++ b/lib/gitlab/graphql/tracers/metrics_tracer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Tracers + class MetricsTracer + def self.use(schema) + schema.tracer(self.new) + end + + # See https://graphql-ruby.org/api-doc/1.12.16/GraphQL/Tracing for full list of events + def trace(key, data) + result = yield + + case key + when "execute_query" + increment_query_sli(data) + end + + result + end + + private + + def increment_query_sli(data) + duration_s = data.fetch(:duration_s, nil) + query = data.fetch(:query, nil) + + # We're just being defensive here... + # duration_s comes from TimerTracer and we should be pretty much guaranteed it exists + return unless duration_s && query + + operation = ::Gitlab::Graphql::KnownOperations.default.from_query(query) + query_urgency = operation.query_urgency + + Gitlab::Metrics::RailsSlis.graphql_query_apdex.increment( + labels: { + endpoint_id: ::Gitlab::ApplicationContext.current_context_attribute(:caller_id), + feature_category: ::Gitlab::ApplicationContext.current_context_attribute(:feature_category), + query_urgency: query_urgency.name + }, + success: duration_s <= query_urgency.duration + ) + end + end + end + end +end diff --git a/lib/gitlab/graphql/tracers/timer_tracer.rb b/lib/gitlab/graphql/tracers/timer_tracer.rb new file mode 100644 index 00000000000..326620a22bc --- /dev/null +++ b/lib/gitlab/graphql/tracers/timer_tracer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Tracers + # This graphql-ruby tracer records duration for trace events and merges + # the duration into the trace event's metadata. This way, separate tracers + # can all use the same duration information. + # + # NOTE: TimerTracer should be applied last **after** other tracers, so + # that it runs first (similar to function composition) + class TimerTracer + def self.use(schema) + schema.tracer(self.new) + end + + def trace(key, data) + start_time = Gitlab::Metrics::System.monotonic_time + + result = yield + + duration_s = Gitlab::Metrics::System.monotonic_time - start_time + + data[:duration_s] = duration_s + + result + end + end + end + end +end |