summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/metrics
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /spec/lib/gitlab/metrics
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/lib/gitlab/metrics')
-rw-r--r--spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb9
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb342
-rw-r--r--spec/lib/gitlab/metrics/rails_slis_spec.rb58
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb187
-rw-r--r--spec/lib/gitlab/metrics/sli_spec.rb99
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb4
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb15
8 files changed, 355 insertions, 367 deletions
diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
index ce98c807e2e..9deaecbf41b 100644
--- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
@@ -30,6 +30,15 @@ RSpec.describe Gitlab::Metrics::Exporter::WebExporter do
expect(readiness_probe.json).to include(status: 'ok')
expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }])
end
+
+ it 'initializes request metrics', :prometheus do
+ expect(Gitlab::Metrics::RailsSlis).to receive(:initialize_request_slis_if_needed!).and_call_original
+
+ http = Net::HTTP.new(exporter.server.config[:BindAddress], exporter.server.config[:Port])
+ response = http.request(Net::HTTP::Get.new('/metrics'))
+
+ expect(response.body).to include('gitlab_sli:rails_request_apdex')
+ end
end
describe '#mark_as_not_running!' do
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
deleted file mode 100644
index b15e06a0861..00000000000
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ /dev/null
@@ -1,342 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Instrumentation do
- let(:env) { {} }
- let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
-
- before do
- @dummy = Class.new do
- def self.foo(text = 'foo')
- text
- end
-
- def self.wat(text = 'wat')
- text
- end
- private_class_method :wat
-
- class << self
- def buzz(text = 'buzz')
- text
- end
- private :buzz
-
- def flaky(text = 'flaky')
- text
- end
- protected :flaky
- end
-
- def bar(text = 'bar')
- text
- end
-
- def wadus(text = 'wadus')
- text
- end
- private :wadus
-
- def chaf(text = 'chaf')
- text
- end
- protected :chaf
- end
-
- allow(@dummy).to receive(:name).and_return('Dummy')
- end
-
- describe '.series' do
- it 'returns a String' do
- expect(described_class.series).to be_an_instance_of(String)
- end
- end
-
- describe '.configure' do
- it 'yields self' do
- described_class.configure do |c|
- expect(c).to eq(described_class)
- end
- end
- end
-
- describe '.instrument_method' do
- describe 'with metrics enabled' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
-
- described_class.instrument_method(@dummy, :foo)
- end
-
- it 'instruments the Class' do
- target = @dummy.singleton_class
-
- expect(described_class.instrumented?(target)).to eq(true)
- end
-
- it 'defines a proxy method' do
- mod = described_class.proxy_module(@dummy.singleton_class)
-
- expect(mod.method_defined?(:foo)).to eq(true)
- end
-
- it 'calls the instrumented method with the correct arguments' do
- expect(@dummy.foo).to eq('foo')
- end
-
- it 'tracks the call duration upon calling the method' do
- allow(Gitlab::Metrics).to receive(:method_call_threshold)
- .and_return(0)
-
- allow(described_class).to receive(:transaction)
- .and_return(transaction)
-
- expect_next_instance_of(Gitlab::Metrics::MethodCall) do |instance|
- expect(instance).to receive(:measure)
- end
-
- @dummy.foo
- end
-
- it 'does not track method calls below a given duration threshold' do
- allow(Gitlab::Metrics).to receive(:method_call_threshold)
- .and_return(100)
-
- expect(transaction).not_to receive(:add_metric)
-
- @dummy.foo
- end
-
- it 'generates a method with the correct arity when using methods without arguments' do
- dummy = Class.new do
- def self.test; end
- end
-
- described_class.instrument_method(dummy, :test)
-
- expect(dummy.method(:test).arity).to eq(0)
- end
-
- describe 'when a module is instrumented multiple times' do
- it 'calls the instrumented method with the correct arguments' do
- described_class.instrument_method(@dummy, :foo)
-
- expect(@dummy.foo).to eq('foo')
- end
- end
- end
-
- describe 'with metrics disabled' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
- end
-
- it 'does not instrument the method' do
- described_class.instrument_method(@dummy, :foo)
-
- target = @dummy.singleton_class
-
- expect(described_class.instrumented?(target)).to eq(false)
- end
- end
- end
-
- describe '.instrument_instance_method' do
- describe 'with metrics enabled' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
-
- described_class
- .instrument_instance_method(@dummy, :bar)
- end
-
- it 'instruments instances of the Class' do
- expect(described_class.instrumented?(@dummy)).to eq(true)
- end
-
- it 'defines a proxy method' do
- mod = described_class.proxy_module(@dummy)
-
- expect(mod.method_defined?(:bar)).to eq(true)
- end
-
- it 'calls the instrumented method with the correct arguments' do
- expect(@dummy.new.bar).to eq('bar')
- end
-
- it 'tracks the call duration upon calling the method' do
- allow(Gitlab::Metrics).to receive(:method_call_threshold)
- .and_return(0)
-
- allow(described_class).to receive(:transaction)
- .and_return(transaction)
-
- expect_next_instance_of(Gitlab::Metrics::MethodCall) do |instance|
- expect(instance).to receive(:measure)
- end
-
- @dummy.new.bar
- end
-
- it 'does not track method calls below a given duration threshold' do
- allow(Gitlab::Metrics).to receive(:method_call_threshold)
- .and_return(100)
-
- expect(transaction).not_to receive(:add_metric)
-
- @dummy.new.bar
- end
- end
-
- describe 'with metrics disabled' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
- end
-
- it 'does not instrument the method' do
- described_class
- .instrument_instance_method(@dummy, :bar)
-
- expect(described_class.instrumented?(@dummy)).to eq(false)
- end
- end
- end
-
- describe '.instrument_class_hierarchy' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
-
- @child1 = Class.new(@dummy) do
- def self.child1_foo; end
-
- def child1_bar; end
- end
-
- @child2 = Class.new(@child1) do
- def self.child2_foo; end
-
- def child2_bar; end
- end
- end
-
- it 'recursively instruments a class hierarchy' do
- described_class.instrument_class_hierarchy(@dummy)
-
- expect(described_class.instrumented?(@child1.singleton_class)).to eq(true)
- expect(described_class.instrumented?(@child2.singleton_class)).to eq(true)
-
- expect(described_class.instrumented?(@child1)).to eq(true)
- expect(described_class.instrumented?(@child2)).to eq(true)
- end
-
- it 'does not instrument the root module' do
- described_class.instrument_class_hierarchy(@dummy)
-
- expect(described_class.instrumented?(@dummy)).to eq(false)
- end
- end
-
- describe '.instrument_methods' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
- end
-
- it 'instruments all public class methods' do
- described_class.instrument_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
- expect(@dummy.method(:foo).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.public_methods).to include(:foo)
- end
-
- it 'instruments all protected class methods' do
- described_class.instrument_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
- expect(@dummy.method(:flaky).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.protected_methods).to include(:flaky)
- end
-
- it 'instruments all private class methods' do
- described_class.instrument_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
- expect(@dummy.method(:buzz).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.private_methods).to include(:buzz)
- expect(@dummy.private_methods).to include(:wat)
- end
-
- it 'only instruments methods directly defined in the module' do
- mod = Module.new do
- def kittens
- end
- end
-
- @dummy.extend(mod)
-
- described_class.instrument_methods(@dummy)
-
- expect(@dummy).not_to respond_to(:_original_kittens)
- end
-
- it 'can take a block to determine if a method should be instrumented' do
- described_class.instrument_methods(@dummy) do
- false
- end
-
- expect(@dummy).not_to respond_to(:_original_foo)
- end
- end
-
- describe '.instrument_instance_methods' do
- before do
- allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
- end
-
- it 'instruments all public instance methods' do
- described_class.instrument_instance_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy)).to eq(true)
- expect(@dummy.new.method(:bar).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.public_instance_methods).to include(:bar)
- end
-
- it 'instruments all protected instance methods' do
- described_class.instrument_instance_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy)).to eq(true)
- expect(@dummy.new.method(:chaf).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.protected_instance_methods).to include(:chaf)
- end
-
- it 'instruments all private instance methods' do
- described_class.instrument_instance_methods(@dummy)
-
- expect(described_class.instrumented?(@dummy)).to eq(true)
- expect(@dummy.new.method(:wadus).source_location.first).to match(/instrumentation\.rb/)
- expect(@dummy.private_instance_methods).to include(:wadus)
- end
-
- it 'only instruments methods directly defined in the module' do
- mod = Module.new do
- def kittens
- end
- end
-
- @dummy.include(mod)
-
- described_class.instrument_instance_methods(@dummy)
-
- expect(@dummy.new.method(:kittens).source_location.first).not_to match(/instrumentation\.rb/)
- end
-
- it 'can take a block to determine if a method should be instrumented' do
- described_class.instrument_instance_methods(@dummy) do
- false
- end
-
- expect(@dummy.new.method(:bar).source_location.first).not_to match(/instrumentation\.rb/)
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/rails_slis_spec.rb b/spec/lib/gitlab/metrics/rails_slis_spec.rb
new file mode 100644
index 00000000000..16fcb9d46a2
--- /dev/null
+++ b/spec/lib/gitlab/metrics/rails_slis_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::RailsSlis do
+ # Limit what routes we'll initialize so we don't have to load the entire thing
+ before do
+ api_route = API::API.routes.find do |route|
+ API::Base.endpoint_id_for_route(route) == "GET /api/:version/version"
+ end
+
+ allow(Gitlab::RequestEndpoints).to receive(:all_api_endpoints).and_return([api_route])
+ allow(Gitlab::RequestEndpoints).to receive(:all_controller_actions).and_return([[ProjectsController, 'show']])
+ end
+
+ describe '.initialize_request_slis_if_needed!' do
+ it "initializes the SLI for all possible endpoints if they weren't" do
+ possible_labels = [
+ {
+ endpoint_id: "GET /api/:version/version",
+ feature_category: :not_owned
+ },
+ {
+ endpoint_id: "ProjectsController#show",
+ feature_category: :projects
+ }
+ ]
+
+ expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { false }
+ expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:rails_request_apdex, array_including(*possible_labels)).and_call_original
+
+ described_class.initialize_request_slis_if_needed!
+ end
+
+ it 'does not initialize the SLI if they were initialized already' do
+ expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:rails_request_apdex) { true }
+ expect(Gitlab::Metrics::Sli).not_to receive(:initialize_sli)
+
+ described_class.initialize_request_slis_if_needed!
+ end
+
+ it 'does not initialize anything if the feature flag is disabled' do
+ stub_feature_flags(request_apdex_counters: false)
+
+ expect(Gitlab::Metrics::Sli).not_to receive(:initialize_sli)
+ expect(Gitlab::Metrics::Sli).not_to receive(:initialized?)
+
+ described_class.initialize_request_slis_if_needed!
+ end
+ end
+
+ describe '.request_apdex' do
+ it 'returns the initialized request apdex SLI object' do
+ described_class.initialize_request_slis_if_needed!
+
+ expect(described_class.request_apdex).to be_initialized
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 9d5c4bdf9e2..5870f9a8f68 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it 'tracks request count and duration' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
subject.call(env)
end
@@ -70,7 +71,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect(described_class).not_to receive(:http_health_requests_total)
expect(described_class)
.to receive_message_chain(:http_request_duration_seconds, :observe)
- .with({ method: 'get' }, a_positive_execution_time)
+ .with({ method: 'get' }, a_positive_execution_time)
subject.call(env)
end
@@ -82,9 +83,10 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
context '@app.call returns an error code' do
let(:status) { '500' }
- it 'tracks count but not duration' do
+ it 'tracks count but not duration or apdex' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '500', feature_category: 'unknown')
expect(described_class).not_to receive(:http_request_duration_seconds)
+ expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex)
subject.call(env)
end
@@ -104,20 +106,23 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'unknown')
expect(described_class.http_request_duration_seconds).not_to receive(:observe)
+ expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex)
expect { subject.call(env) }.to raise_error(StandardError)
end
end
- context 'feature category header' do
- context 'when a feature category context is present' do
+ context 'application context' do
+ context 'when a context is present' do
before do
- ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
+ ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking', caller_id: 'IssuesController#show')
end
- it 'adds the feature category to the labels for http_requests_total' do
+ it 'adds the feature category to the labels for required metrics' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'issue_tracking')
expect(described_class).not_to receive(:http_health_requests_total)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex)
+ .to receive(:increment).with(labels: { feature_category: 'issue_tracking', endpoint_id: 'IssuesController#show' }, success: true)
subject.call(env)
end
@@ -127,6 +132,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect(described_class).to receive_message_chain(:http_health_requests_total, :increment).with(method: 'get', status: '200')
expect(described_class).not_to receive(:http_requests_total)
+ expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex)
subject.call(env)
end
@@ -140,19 +146,180 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it 'adds the feature category to the labels for http_requests_total' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'issue_tracking')
+ expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex)
expect { subject.call(env) }.to raise_error(StandardError)
end
end
- context 'when the feature category context is not available' do
- it 'sets the feature category to unknown' do
+ context 'when the context is not available' do
+ it 'sets the required labels to unknown' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).not_to receive(:http_health_requests_total)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
subject.call(env)
end
end
+
+ context 'SLI satisfactory' do
+ where(:request_urgency_name, :duration, :success) do
+ [
+ [:high, 0.1, true],
+ [:high, 0.25, false],
+ [:high, 0.3, false],
+ [:medium, 0.3, true],
+ [:medium, 0.5, false],
+ [:medium, 0.6, false],
+ [:default, 0.6, true],
+ [:default, 1.0, false],
+ [:default, 1.2, false],
+ [:low, 4.5, true],
+ [:low, 5.0, false],
+ [:low, 6, false]
+ ]
+ end
+
+ with_them do
+ context 'Grape API handler having expected duration setup' do
+ let(:api_handler) do
+ request_urgency = request_urgency_name
+ Class.new(::API::Base) do
+ feature_category :hello_world, ['/projects/:id/archive']
+ urgency request_urgency, ['/projects/:id/archive']
+ end
+ end
+
+ let(:endpoint) do
+ route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
+ double(:endpoint, route: route,
+ options: { for: api_handler, path: [":id/archive"] },
+ namespace: "/projects")
+ end
+
+ let(:env) { { 'api.endpoint' => endpoint, 'REQUEST_METHOD' => 'GET' } }
+
+ before do
+ ::Gitlab::ApplicationContext.push(feature_category: 'hello_world', caller_id: 'GET /projects/:id/archive')
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100 + duration)
+ end
+
+ it "captures SLI metrics" do
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'hello_world', endpoint_id: 'GET /projects/:id/archive' },
+ success: success
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'Rails controller having expected duration setup' do
+ let(:controller) do
+ request_urgency = request_urgency_name
+ Class.new(ApplicationController) do
+ feature_category :hello_world, [:index, :show]
+ urgency request_urgency, [:index, :show]
+ end
+ end
+
+ let(:env) do
+ controller_instance = controller.new
+ controller_instance.action_name = :index
+ { 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
+ end
+
+ before do
+ ::Gitlab::ApplicationContext.push(feature_category: 'hello_world', caller_id: 'AnonymousController#index')
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100 + duration)
+ end
+
+ it "captures SLI metrics" do
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'hello_world', endpoint_id: 'AnonymousController#index' },
+ success: success
+ )
+ subject.call(env)
+ end
+ end
+ end
+
+ context 'Grape API without expected duration' do
+ let(:endpoint) do
+ route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
+ double(:endpoint, route: route,
+ options: { for: api_handler, path: [":id/archive"] },
+ namespace: "/projects")
+ end
+
+ let(:env) { { 'api.endpoint' => endpoint, 'REQUEST_METHOD' => 'GET' } }
+
+ let(:api_handler) { Class.new(::API::Base) }
+
+ it "falls back request's expectation to medium (1 second)" do
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: true
+ )
+ subject.call(env)
+
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'Rails controller without expected duration' do
+ let(:controller) { Class.new(ApplicationController) }
+
+ let(:env) do
+ controller_instance = controller.new
+ controller_instance.action_name = :index
+ { 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
+ end
+
+ it "falls back request's expectation to medium (1 second)" do
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: true
+ )
+ subject.call(env)
+
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'An unknown request' do
+ let(:env) do
+ { 'REQUEST_METHOD' => 'GET' }
+ end
+
+ it "falls back request's expectation to medium (1 second)" do
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: true
+ )
+ subject.call(env)
+
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+ end
end
describe '.initialize_metrics', :prometheus do
@@ -181,8 +348,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
it 'has every label in config/feature_categories.yml' do
- defaults = [described_class::FEATURE_CATEGORY_DEFAULT, 'not_owned']
- feature_categories = YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:strip) + defaults
+ defaults = [::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT, 'not_owned']
+ feature_categories = Gitlab::FeatureCategories.default.categories + defaults
expect(described_class::FEATURE_CATEGORIES_TO_INITIALIZE).to all(be_in(feature_categories))
end
diff --git a/spec/lib/gitlab/metrics/sli_spec.rb b/spec/lib/gitlab/metrics/sli_spec.rb
new file mode 100644
index 00000000000..8ba4bf29568
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sli_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Metrics::Sli do
+ let(:prometheus) { double("prometheus") }
+
+ before do
+ stub_const("Gitlab::Metrics", prometheus)
+ end
+
+ describe 'Class methods' do
+ before do
+ described_class.instance_variable_set(:@known_slis, nil)
+ end
+
+ describe '.[]' do
+ it 'warns about an uninitialized SLI but returns and stores a new one' do
+ sli = described_class[:bar]
+
+ expect(described_class[:bar]).to be(sli)
+ end
+
+ it 'returns the same object for multiple accesses' do
+ sli = described_class.initialize_sli(:huzzah, [])
+
+ 2.times do
+ expect(described_class[:huzzah]).to be(sli)
+ end
+ end
+ end
+
+ describe '.initialized?' do
+ before do
+ fake_total_counter(:boom)
+ fake_success_counter(:boom)
+ end
+
+ it 'is true when an SLI was initialized with labels' do
+ expect { described_class.initialize_sli(:boom, [{ hello: :world }]) }
+ .to change { described_class.initialized?(:boom) }.from(false).to(true)
+ end
+
+ it 'is false when an SLI was not initialized with labels' do
+ expect { described_class.initialize_sli(:boom, []) }
+ .not_to change { described_class.initialized?(:boom) }.from(false)
+ end
+ end
+ end
+
+ describe '#initialize_counters' do
+ it 'initializes counters for the passed label combinations' do
+ counters = [fake_total_counter(:hey), fake_success_counter(:hey)]
+
+ described_class.new(:hey).initialize_counters([{ foo: 'bar' }, { foo: 'baz' }])
+
+ expect(counters).to all(have_received(:get).with({ foo: 'bar' }))
+ expect(counters).to all(have_received(:get).with({ foo: 'baz' }))
+ end
+ end
+
+ describe "#increment" do
+ let!(:sli) { described_class.new(:heyo) }
+ let!(:total_counter) { fake_total_counter(:heyo) }
+ let!(:success_counter) { fake_success_counter(:heyo) }
+
+ it 'increments both counters for labels successes' do
+ sli.increment(labels: { hello: "world" }, success: true)
+
+ expect(total_counter).to have_received(:increment).with({ hello: 'world' })
+ expect(success_counter).to have_received(:increment).with({ hello: 'world' })
+ end
+
+ it 'only increments the total counters for labels when not successful' do
+ sli.increment(labels: { hello: "world" }, success: false)
+
+ expect(total_counter).to have_received(:increment).with({ hello: 'world' })
+ expect(success_counter).not_to have_received(:increment).with({ hello: 'world' })
+ end
+ end
+
+ def fake_prometheus_counter(name)
+ fake_counter = double("prometheus counter: #{name}")
+
+ allow(fake_counter).to receive(:get)
+ allow(fake_counter).to receive(:increment)
+ allow(prometheus).to receive(:counter).with(name.to_sym, anything).and_return(fake_counter)
+
+ fake_counter
+ end
+
+ def fake_total_counter(name)
+ fake_prometheus_counter("gitlab_sli:#{name}:total")
+ end
+
+ def fake_success_counter(name)
+ fake_prometheus_counter("gitlab_sli:#{name}:success_total")
+ end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 3ffbcbea03c..a8e4f039da4 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:env) { {} }
let(:subscriber) { described_class.new }
- let(:connection) { ActiveRecord::Base.connection }
+ let(:connection) { ActiveRecord::Base.retrieve_connection }
let(:db_config_name) { ::Gitlab::Database.db_config_name(connection) }
describe '#transaction' do
@@ -135,7 +135,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
it_behaves_like 'record ActiveRecord metrics'
- it_behaves_like 'store ActiveRecord info in RequestStore'
+ it_behaves_like 'store ActiveRecord info in RequestStore', :primary
end
end
@@ -195,10 +195,6 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
with_them do
let(:payload) { { name: name, sql: sql(sql_query, comments: comments), connection: connection } }
- before do
- allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
- end
-
context 'query using a connection to a replica' do
before do
allow(Gitlab::Database::LoadBalancing).to receive(:db_role_for_connection).and_return(:replica)
diff --git a/spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb b/spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb
index 21a6573c6fd..bc6effd0438 100644
--- a/spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb
@@ -5,10 +5,6 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Subscribers::LoadBalancing, :request_store do
let(:subscriber) { described_class.new }
- before do
- allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
- end
-
describe '#caught_up_replica_pick' do
shared_examples 'having payload result value' do |result, counter_name|
subject { subscriber.caught_up_replica_pick(event) }
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 5261d04c879..9e22dccb2a2 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
it 'measures with correct labels and value' do
value = 1
- expect(prometheus_metric).to receive(metric_method).with({ controller: 'TestController', action: 'show', feature_category: '' }, value)
+ expect(prometheus_metric).to receive(metric_method).with({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT }, value)
transaction.send(metric_method, :bau, value)
end
@@ -105,6 +105,9 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
namespace: "/projects")
env['api.endpoint'] = endpoint
+
+ # This is needed since we're not actually making a request, which would trigger the controller pushing to the context
+ ::Gitlab::ApplicationContext.push(feature_category: 'projects')
end
it 'provides labels with the method and path of the route in the grape endpoint' do
@@ -129,7 +132,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
include_context 'ActionController request'
it 'tags a transaction with the name and action of a controller' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
end
it 'contains only the labels defined for transactions' do
@@ -140,7 +143,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: :json)) }
it 'appends the mime type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: '' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
end
end
@@ -148,13 +151,15 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
it 'does not append the MIME type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
end
end
context 'when the feature category is known' do
it 'includes it in the feature category label' do
- expect(controller_class).to receive(:feature_category_for_action).with('show').and_return(:source_code_management)
+ # This is needed since we're not actually making a request, which would trigger the controller pushing to the context
+ ::Gitlab::ApplicationContext.push(feature_category: 'source_code_management')
+
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
end
end