Commit 3eaf18fc authored by Paul Slaughter's avatar Paul Slaughter Committed by Bob Van Landuyt

Step 3 - Add MetricsTracer to Graphql Schema

parent b378ddef
...@@ -12,6 +12,7 @@ class GitlabSchema < GraphQL::Schema ...@@ -12,6 +12,7 @@ class GitlabSchema < GraphQL::Schema
# Tracers (order is important) # Tracers (order is important)
use Gitlab::Graphql::Tracers::ApplicationContextTracer use Gitlab::Graphql::Tracers::ApplicationContextTracer
use Gitlab::Graphql::Tracers::MetricsTracer
use Gitlab::Graphql::Tracers::LoggerTracer use Gitlab::Graphql::Tracers::LoggerTracer
use Gitlab::Graphql::GenericTracing # Old tracer which will be removed eventually use Gitlab::Graphql::GenericTracing # Old tracer which will be removed eventually
use Gitlab::Graphql::Tracers::TimerTracer use Gitlab::Graphql::Tracers::TimerTracer
......
...@@ -7,6 +7,11 @@ module Gitlab ...@@ -7,6 +7,11 @@ module Gitlab
def to_caller_id def to_caller_id
"graphql:#{name}" "graphql:#{name}"
end end
def query_urgency
# We'll be able to actually correlate query_urgency with https://gitlab.com/gitlab-org/gitlab/-/issues/345141
::Gitlab::EndpointAttributes::DEFAULT_URGENCY
end
end end
ANONYMOUS = Operation.new("anonymous").freeze ANONYMOUS = Operation.new("anonymous").freeze
......
# 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
...@@ -5,17 +5,31 @@ module Gitlab ...@@ -5,17 +5,31 @@ module Gitlab
module RailsSlis module RailsSlis
class << self class << self
def initialize_request_slis_if_needed! def initialize_request_slis_if_needed!
return if Gitlab::Metrics::Sli.initialized?(:rails_request_apdex) Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels) unless Gitlab::Metrics::Sli.initialized?(:rails_request_apdex)
Gitlab::Metrics::Sli.initialize_sli(:graphql_query_apdex, possible_graphql_query_labels) unless Gitlab::Metrics::Sli.initialized?(:graphql_query_apdex)
Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels)
end end
def request_apdex def request_apdex
Gitlab::Metrics::Sli[:rails_request_apdex] Gitlab::Metrics::Sli[:rails_request_apdex]
end end
def graphql_query_apdex
Gitlab::Metrics::Sli[:graphql_query_apdex]
end
private private
def possible_graphql_query_labels
::Gitlab::Graphql::KnownOperations.default.operations.map do |op|
{
endpoint_id: op.to_caller_id,
# We'll be able to correlate feature_category with https://gitlab.com/gitlab-org/gitlab/-/issues/328535
feature_category: nil,
query_urgency: op.query_urgency.name
}
end
end
def possible_request_labels def possible_request_labels
possible_controller_labels + possible_api_labels possible_controller_labels + possible_api_labels
end end
......
...@@ -54,6 +54,14 @@ RSpec.describe Gitlab::Graphql::KnownOperations do ...@@ -54,6 +54,14 @@ RSpec.describe Gitlab::Graphql::KnownOperations do
end end
end end
describe "Opeartion#query_urgency" do
it "returns the associated query urgency" do
query = ::GraphQL::Query.new(fake_schema, "query foo { helloWorld }")
expect(subject.from_query(query).query_urgency).to equal(::Gitlab::EndpointAttributes::DEFAULT_URGENCY)
end
end
describe ".default" do describe ".default" do
it "returns a memoization of values from webpack", :aggregate_failures do it "returns a memoization of values from webpack", :aggregate_failures do
# .default could have been referenced in another spec, so we need to clean it up here # .default could have been referenced in another spec, so we need to clean it up here
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
require "support/graphql/fake_query_type"
RSpec.describe Gitlab::Graphql::Tracers::MetricsTracer do
using RSpec::Parameterized::TableSyntax
let(:default_known_operations) { ::Gitlab::Graphql::KnownOperations.new(%w(lorem foo bar)) }
let(:fake_schema) do
Class.new(GraphQL::Schema) do
use Gitlab::Graphql::Tracers::ApplicationContextTracer
use Gitlab::Graphql::Tracers::MetricsTracer
use Gitlab::Graphql::Tracers::TimerTracer
query Graphql::FakeQueryType
end
end
around do |example|
::Gitlab::ApplicationContext.with_context(feature_category: 'test_feature_category') do
example.run
end
end
before do
allow(::Gitlab::Graphql::KnownOperations).to receive(:default).and_return(default_known_operations)
end
describe 'when used as tracer and query is executed' do
where(:duration, :expected_success) do
0.1 | true
0.1 + ::Gitlab::EndpointAttributes::DEFAULT_URGENCY.duration | false
end
with_them do
it 'increments sli' do
# Trigger initialization
fake_schema
# setup timer
current_time = 0
allow(Gitlab::Metrics::System).to receive(:monotonic_time) { current_time += duration }
expect(Gitlab::Metrics::RailsSlis.graphql_query_apdex).to receive(:increment).with(
labels: {
endpoint_id: 'graphql:lorem',
feature_category: 'test_feature_category',
query_urgency: ::Gitlab::EndpointAttributes::DEFAULT_URGENCY.name
},
success: expected_success
)
fake_schema.execute("query lorem { helloWorld }")
end
end
end
end
...@@ -10,10 +10,11 @@ RSpec.describe Gitlab::Metrics::RailsSlis do ...@@ -10,10 +10,11 @@ RSpec.describe Gitlab::Metrics::RailsSlis do
allow(Gitlab::RequestEndpoints).to receive(:all_api_endpoints).and_return([api_route]) allow(Gitlab::RequestEndpoints).to receive(:all_api_endpoints).and_return([api_route])
allow(Gitlab::RequestEndpoints).to receive(:all_controller_actions).and_return([[ProjectsController, 'show']]) allow(Gitlab::RequestEndpoints).to receive(:all_controller_actions).and_return([[ProjectsController, 'show']])
allow(Gitlab::Graphql::KnownOperations).to receive(:default).and_return(Gitlab::Graphql::KnownOperations.new(%w(foo bar)))
end end
describe '.initialize_request_slis_if_needed!' do describe '.initialize_request_slis_if_needed!' do
it "initializes the SLI for all possible endpoints if they weren't" do it "initializes the SLI for all possible endpoints if they weren't", :aggregate_failures do
possible_labels = [ possible_labels = [
{ {
endpoint_id: "GET /api/:version/version", endpoint_id: "GET /api/:version/version",
...@@ -27,14 +28,25 @@ RSpec.describe Gitlab::Metrics::RailsSlis do ...@@ -27,14 +28,25 @@ RSpec.describe Gitlab::Metrics::RailsSlis do
} }
] ]
possible_graphql_labels = ['graphql:foo', 'graphql:bar', 'graphql:unknown', 'graphql:anonymous'].map do |endpoint_id|
{
endpoint_id: endpoint_id,
feature_category: nil,
query_urgency: ::Gitlab::EndpointAttributes::DEFAULT_URGENCY.name
}
end
expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { false } expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { false }
expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:graphql_query_apdex) { false }
expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:rails_request_apdex, array_including(*possible_labels)).and_call_original expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:rails_request_apdex, array_including(*possible_labels)).and_call_original
expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:graphql_query_apdex, array_including(*possible_graphql_labels)).and_call_original
described_class.initialize_request_slis_if_needed! described_class.initialize_request_slis_if_needed!
end end
it 'does not initialize the SLI if they were initialized already' do it 'does not initialize the SLI if they were initialized already', :aggregate_failures do
expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { true } expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { true }
expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:graphql_query_apdex) { true }
expect(Gitlab::Metrics::Sli).not_to receive(:initialize_sli) expect(Gitlab::Metrics::Sli).not_to receive(:initialize_sli)
described_class.initialize_request_slis_if_needed! described_class.initialize_request_slis_if_needed!
...@@ -48,4 +60,12 @@ RSpec.describe Gitlab::Metrics::RailsSlis do ...@@ -48,4 +60,12 @@ RSpec.describe Gitlab::Metrics::RailsSlis do
expect(described_class.request_apdex).to be_initialized expect(described_class.request_apdex).to be_initialized
end end
end end
describe '.graphql_query_apdex' do
it 'returns the initialized request apdex SLI object' do
described_class.initialize_request_slis_if_needed!
expect(described_class.graphql_query_apdex).to be_initialized
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment