Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/metrics')
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb38
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb141
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb136
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb30
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb40
5 files changed, 203 insertions, 182 deletions
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 1f7daaa308d..9d5c4bdf9e2 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -7,8 +7,16 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
subject { described_class.new(app) }
+ around do |example|
+ # Simulate application context middleware
+ # In fact, this middleware cleans up the contexts after a request lifecycle
+ ::Gitlab::ApplicationContext.with_context({}) do
+ example.run
+ end
+ end
+
describe '#call' do
- let(:status) { 100 }
+ let(:status) { 200 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] }
@@ -71,6 +79,17 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
end
+ context '@app.call returns an error code' do
+ let(:status) { '500' }
+
+ it 'tracks count but not duration' 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)
+
+ subject.call(env)
+ end
+ end
+
context '@app.call throws exception' do
let(:http_request_duration_seconds) { double('http_request_duration_seconds') }
let(:http_requests_total) { double('http_requests_total') }
@@ -91,9 +110,9 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
context 'feature category header' do
- context 'when a feature category header is present' do
+ context 'when a feature category context is present' do
before do
- allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => 'issue_tracking' }, nil])
+ ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
end
it 'adds the feature category to the labels for http_requests_total' do
@@ -113,11 +132,20 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
end
- context 'when the feature category header is an empty string' do
+ context 'when application raises an exception when the feature category context is present' do
before do
- allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => '' }, nil])
+ ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
+ allow(app).to receive(:call).and_raise(StandardError)
end
+ 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 { 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
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)
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
deleted file mode 100644
index 7971a7cabd5..00000000000
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Samplers::UnicornSampler do
- subject { described_class.new(1.second) }
-
- it_behaves_like 'metrics sampler', 'UNICORN_SAMPLER'
-
- describe '#sample' do
- let(:unicorn) { Module.new }
- let(:raindrops) { double('raindrops') }
- let(:stats) { double('stats') }
-
- before do
- stub_const('Unicorn', unicorn)
- stub_const('Raindrops::Linux', raindrops)
- allow(raindrops).to receive(:unix_listener_stats).and_return({})
- allow(raindrops).to receive(:tcp_listener_stats).and_return({})
- end
-
- context 'unicorn listens on unix sockets' do
- let(:socket_address) { '/some/sock' }
- let(:sockets) { [socket_address] }
-
- before do
- allow(unicorn).to receive(:listener_names).and_return(sockets)
- end
-
- it 'samples socket data' do
- expect(raindrops).to receive(:unix_listener_stats).with(sockets)
-
- subject.sample
- end
-
- context 'stats collected' do
- before do
- allow(stats).to receive(:active).and_return('active')
- allow(stats).to receive(:queued).and_return('queued')
- allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
- end
-
- it 'updates metrics type unix and with addr' do
- labels = { socket_type: 'unix', socket_address: socket_address }
-
- expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
- expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
-
- subject.sample
- end
- end
- end
-
- context 'unicorn listens on tcp sockets' do
- let(:tcp_socket_address) { '0.0.0.0:8080' }
- let(:tcp_sockets) { [tcp_socket_address] }
-
- before do
- allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
- end
-
- it 'samples socket data' do
- expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
-
- subject.sample
- end
-
- context 'stats collected' do
- before do
- allow(stats).to receive(:active).and_return('active')
- allow(stats).to receive(:queued).and_return('queued')
- allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
- end
-
- it 'updates metrics type unix and with addr' do
- labels = { socket_type: 'tcp', socket_address: tcp_socket_address }
-
- expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
- expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
-
- subject.sample
- end
- end
- end
-
- context 'unicorn workers' do
- before do
- allow(unicorn).to receive(:listener_names).and_return([])
- end
-
- context 'without http server' do
- it "does set unicorn_workers to 0" do
- expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 0)
-
- subject.sample
- end
- end
-
- context 'with http server' do
- let(:http_server_class) { Struct.new(:worker_processes) }
- let!(:http_server) { http_server_class.new(5) }
-
- before do
- stub_const('Unicorn::HttpServer', http_server_class)
- end
-
- it "sets additional metrics" do
- expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 5)
-
- subject.sample
- end
- end
- end
- end
-
- describe '#start' do
- context 'when enabled' do
- before do
- allow(subject).to receive(:enabled?).and_return(true)
- end
-
- it 'creates new thread' do
- expect(Thread).to receive(:new)
-
- subject.start
- end
- end
-
- context 'when disabled' do
- before do
- allow(subject).to receive(:enabled?).and_return(false)
- end
-
- it "doesn't create new thread" do
- expect(Thread).not_to receive(:new)
-
- subject.start
- end
- end
- 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 6bfcfa21289..cffa62c3a52 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -150,4 +150,140 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
it_behaves_like 'track generic sql events'
end
end
+
+ context 'Database Load Balancing enabled' do
+ let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10', connection: connection } }
+
+ let(:event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ duration: 2,
+ payload: payload
+ )
+ end
+
+ # Emulate Marginalia pre-pending comments
+ def sql(query, comments: true)
+ if comments && !%w[BEGIN COMMIT].include?(query)
+ "/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
+ else
+ query
+ end
+ end
+
+ shared_examples 'track sql events for each role' do
+ where(:name, :sql_query, :record_query, :record_write_query, :record_cached_query, :record_wal_query) do
+ 'SQL' | 'SELECT * FROM users WHERE id = 10' | true | false | false | false
+ 'SQL' | 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' | true | false | false | false
+ 'SQL' | 'SELECT * FROM users WHERE id = 10 FOR UPDATE' | true | true | false | false
+ 'SQL' | 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' | true | true | false | false
+ 'SQL' | 'DELETE FROM users where id = 10' | true | true | false | false
+ 'SQL' | 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' | true | true | false | false
+ 'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false | false
+ 'SQL' | 'SELECT pg_current_wal_insert_lsn()::text AS location' | true | false | false | true
+ 'SQL' | 'SELECT pg_last_wal_replay_lsn()::text AS location' | true | false | false | true
+ 'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true | false
+ 'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false | false
+ nil | 'BEGIN' | false | false | false | false
+ nil | 'COMMIT' | false | false | false | false
+ end
+
+ 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)
+ end
+
+ it 'queries connection db role' do
+ subscriber.sql(event)
+
+ if record_query
+ expect(Gitlab::Database::LoadBalancing).to have_received(:db_role_for_connection).with(connection)
+ end
+ end
+
+ it_behaves_like 'record ActiveRecord metrics', :replica
+ it_behaves_like 'store ActiveRecord info in RequestStore', :replica
+ end
+
+ context 'query using a connection to a primary' do
+ before do
+ allow(Gitlab::Database::LoadBalancing).to receive(:db_role_for_connection).and_return(:primary)
+ end
+
+ it 'queries connection db role' do
+ subscriber.sql(event)
+
+ if record_query
+ expect(Gitlab::Database::LoadBalancing).to have_received(:db_role_for_connection).with(connection)
+ end
+ end
+
+ it_behaves_like 'record ActiveRecord metrics', :primary
+ it_behaves_like 'store ActiveRecord info in RequestStore', :primary
+ end
+
+ context 'query using a connection to an unknown source' do
+ let(:transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ before do
+ allow(Gitlab::Database::LoadBalancing).to receive(:db_role_for_connection).and_return(nil)
+
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(nil)
+
+ allow(transaction).to receive(:increment)
+ allow(transaction).to receive(:observe)
+ end
+
+ it 'does not record DB role metrics' do
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_primary_count_total".to_sym, any_args)
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_replica_count_total".to_sym, any_args)
+
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_primary_cached_count_total".to_sym, any_args)
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_replica_cached_count_total".to_sym, any_args)
+
+ expect(transaction).not_to receive(:observe).with("gitlab_sql_primary_duration_seconds".to_sym, any_args)
+ expect(transaction).not_to receive(:observe).with("gitlab_sql_replica_duration_seconds".to_sym, any_args)
+
+ subscriber.sql(event)
+ end
+
+ it 'does not store DB roles into into RequestStore' do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
+
+ expect(described_class.db_counter_payload).to include(
+ db_primary_cached_count: 0,
+ db_primary_count: 0,
+ db_primary_duration_s: 0,
+ db_replica_cached_count: 0,
+ db_replica_count: 0,
+ db_replica_duration_s: 0
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context 'without Marginalia comments' do
+ let(:comments) { false }
+
+ it_behaves_like 'track sql events for each role'
+ end
+
+ context 'with Marginalia comments' do
+ let(:comments) { true }
+
+ it_behaves_like 'track sql events for each role'
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index d4e5a1a94f2..2ff8efcd7cb 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -12,32 +12,6 @@ RSpec.describe Gitlab::Metrics::Transaction do
}
end
- describe '#duration' do
- it 'returns the duration of a transaction in seconds' do
- transaction.run { }
-
- expect(transaction.duration).to be > 0
- end
- end
-
- describe '#run' do
- it 'yields the supplied block' do
- expect { |b| transaction.run(&b) }.to yield_control
- end
-
- it 'stores the transaction in the current thread' do
- transaction.run do
- expect(described_class.current).to eq(transaction)
- end
- end
-
- it 'removes the transaction from the current thread upon completion' do
- transaction.run { }
-
- expect(described_class.current).to be_nil
- end
- end
-
describe '#method_call_for' do
it 'returns a MethodCall' do
method = transaction.method_call_for('Foo#bar', :Foo, '#bar')
@@ -46,6 +20,10 @@ RSpec.describe Gitlab::Metrics::Transaction do
end
end
+ describe '#run' do
+ specify { expect { transaction.run }.to raise_error(NotImplementedError) }
+ end
+
describe '#add_event' do
let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, increment: nil, base_labels: {}) }
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 6ee9564ef75..5261d04c879 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -38,16 +38,6 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
end
- describe '#duration' do
- include_context 'transaction observe metrics'
-
- it 'returns the duration of a transaction in seconds' do
- transaction.run { sleep(0.5) }
-
- expect(transaction.duration).to be >= 0.5
- end
- end
-
describe '#run' do
include_context 'transaction observe metrics'
@@ -58,6 +48,9 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
it 'stores the transaction in the current thread' do
transaction.run do
expect(Thread.current[described_class::THREAD_KEY]).to eq(transaction)
+ expect(described_class.current).to eq(transaction)
+
+ ['200', {}, '']
end
end
@@ -65,6 +58,33 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
transaction.run { }
expect(Thread.current[described_class::THREAD_KEY]).to be_nil
+ expect(described_class.current).to be_nil
+ end
+
+ it 'records the duration of the transaction if the request was successful' do
+ expect(transaction).to receive(:observe).with(:gitlab_transaction_duration_seconds, instance_of(Float))
+
+ transaction.run { ['200', {}, ''] }
+ end
+
+ it 'does not record the duration of the transaction if the request failed' do
+ expect(transaction).not_to receive(:observe).with(:gitlab_transaction_duration_seconds, instance_of(Float))
+
+ transaction.run { ['500', {}, ''] }
+ end
+
+ it 'does not record the duration of the transaction if it raised' do
+ expect(transaction).not_to receive(:observe).with(:gitlab_transaction_duration_seconds, instance_of(Float))
+
+ expect do
+ transaction.run { raise 'broken' }
+ end.to raise_error('broken')
+ end
+
+ it 'returns the rack response' do
+ response = ['500', {}, '']
+
+ expect(transaction.run { response }).to eq(response)
end
end