diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /spec/lib/gitlab/metrics | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) | |
download | gitlab-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.rb | 9 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/instrumentation_spec.rb | 342 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/rails_slis_spec.rb | 58 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb | 187 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/sli_spec.rb | 99 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 8 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/subscribers/load_balancing_spec.rb | 4 | ||||
-rw-r--r-- | spec/lib/gitlab/metrics/web_transaction_spec.rb | 15 |
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 |