diff options
Diffstat (limited to 'spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb')
-rw-r--r-- | spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb | 434 |
1 files changed, 82 insertions, 352 deletions
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb index 76eec2755df..1f00f7bbec3 100644 --- a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb +++ b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb @@ -3,403 +3,133 @@ require 'spec_helper' RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redis_shared_state do - let(:entity1) { 'dfb9d2d2-f56c-4c77-8aeb-6cddc4a1f857' } - let(:entity2) { '1dd9afb2-a3ee-4de1-8ae3-a405579c8584' } - let(:entity3) { '34rfjuuy-ce56-sa35-ds34-dfer567dfrf2' } - let(:entity4) { '8b9a2671-2abf-4bec-a682-22f6a8f7bf31' } let(:end_date) { Date.current } - let(:sources) { Gitlab::Usage::Metrics::Aggregates::Sources } let(:namespace) { described_class.to_s.deconstantize.constantize } + let(:sources) { Gitlab::Usage::Metrics::Aggregates::Sources } let_it_be(:recorded_at) { Time.current.to_i } - def aggregated_metric(name:, time_frame:, source: "redis", events: %w[event1 event2 event3], operator: "OR", feature_flag: nil) - { - name: name, - source: source, - events: events, - operator: operator, - time_frame: time_frame, - feature_flag: feature_flag - }.compact.with_indifferent_access - end - - context 'aggregated_metrics_data' do - shared_examples 'aggregated_metrics_data' do - context 'no aggregated metric is defined' do - it 'returns empty hash' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics).and_return([]) - end - - expect(aggregated_metrics_data).to eq({}) - end + describe '.calculate_count_for_aggregation' do + using RSpec::Parameterized::TableSyntax + + context 'with valid configuration' do + where(:number_of_days, :operator, :datasource, :expected_method) do + 28 | 'AND' | 'redis_hll' | :calculate_metrics_intersections + 7 | 'AND' | 'redis_hll' | :calculate_metrics_intersections + 28 | 'AND' | 'database' | :calculate_metrics_intersections + 7 | 'AND' | 'database' | :calculate_metrics_intersections + 28 | 'OR' | 'redis_hll' | :calculate_metrics_union + 7 | 'OR' | 'redis_hll' | :calculate_metrics_union + 28 | 'OR' | 'database' | :calculate_metrics_union + 7 | 'OR' | 'database' | :calculate_metrics_union end - context 'there are aggregated metrics defined' do - let(:aggregated_metrics) do - [ - aggregated_metric(name: "gmau_1", source: datasource, time_frame: time_frame, operator: operator) - ] - end - - let(:results) { { 'gmau_1' => 5 } } + with_them do + let(:time_frame) { "#{number_of_days}d" } + let(:start_date) { number_of_days.days.ago.to_date } let(:params) { { start_date: start_date, end_date: end_date, recorded_at: recorded_at } } - - before do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics) - end - end - - context 'with OR operator' do - let(:operator) { Gitlab::Usage::Metrics::Aggregates::UNION_OF_AGGREGATED_METRICS } - - it 'returns the number of unique events occurred for any metric in aggregate', :aggregate_failures do - expect(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2 event3])).and_return(5) - expect(aggregated_metrics_data).to eq(results) - end + let(:aggregate) do + { + source: datasource, + operator: operator, + events: %w[event1 event2] + } end - context 'with AND operator' do - let(:operator) { Gitlab::Usage::Metrics::Aggregates::INTERSECTION_OF_AGGREGATED_METRICS } - - it 'returns the number of unique events that occurred for all of metrics in the aggregate', :aggregate_failures do - expect(namespace::SOURCES[datasource]).to receive(:calculate_metrics_intersections).with(params.merge(metric_names: %w[event1 event2 event3])).and_return(5) - expect(aggregated_metrics_data).to eq(results) - end - end - - context 'hidden behind feature flag' do - let(:enabled_feature_flag) { 'test_ff_enabled' } - let(:disabled_feature_flag) { 'test_ff_disabled' } - let(:aggregated_metrics) do - params = { source: datasource, time_frame: time_frame } - [ - # represents stable aggregated metrics that has been fully released - aggregated_metric(**params.merge(name: "gmau_without_ff")), - # represents new aggregated metric that is under performance testing on gitlab.com - aggregated_metric(**params.merge(name: "gmau_enabled", feature_flag: enabled_feature_flag)), - # represents aggregated metric that is under development and shouldn't be yet collected even on gitlab.com - aggregated_metric(**params.merge(name: "gmau_disabled", feature_flag: disabled_feature_flag)) - ] - end - - it 'does not calculate data for aggregates with ff turned off' do - skip_feature_flags_yaml_validation - skip_default_enabled_yaml_check - stub_feature_flags(enabled_feature_flag => true, disabled_feature_flag => false) - allow(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).and_return(6) - - expect(aggregated_metrics_data).to eq('gmau_without_ff' => 6, 'gmau_enabled' => 6) - end - end - end - - context 'error handling' do - context 'development and test environment' do - it 'raises error when unknown aggregation operator is used' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: datasource, operator: "SUM", time_frame: time_frame)]) - end - - expect { aggregated_metrics_data }.to raise_error namespace::UnknownAggregationOperator - end - - it 'raises error when unknown aggregation source is used' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: 'whoami', time_frame: time_frame)]) - end - - expect { aggregated_metrics_data }.to raise_error namespace::UnknownAggregationSource - end - - it 'raises error when union is missing' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: datasource, time_frame: time_frame)]) - end - allow(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).and_raise(sources::UnionNotAvailable) - - expect { aggregated_metrics_data }.to raise_error sources::UnionNotAvailable - end + subject(:calculate_count_for_aggregation) do + described_class + .new(recorded_at) + .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) end - context 'production' do - before do - stub_rails_env('production') - end - - it 'rescues unknown aggregation operator error' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: datasource, operator: "SUM", time_frame: time_frame)]) - end - - expect(aggregated_metrics_data).to eq('gmau_1' => -1) - end - - it 'rescues unknown aggregation source error' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: 'whoami', time_frame: time_frame)]) - end - - expect(aggregated_metrics_data).to eq('gmau_1' => -1) - end - - it 'rescues error when union is missing' do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics) - .and_return([aggregated_metric(name: 'gmau_1', source: datasource, time_frame: time_frame)]) - end - allow(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).and_raise(sources::UnionNotAvailable) - - expect(aggregated_metrics_data).to eq('gmau_1' => -1) - end + it 'returns the number of unique events for aggregation', :aggregate_failures do + expect(namespace::SOURCES[datasource]) + .to receive(expected_method) + .with(params.merge(metric_names: %w[event1 event2])) + .and_return(5) + expect(calculate_count_for_aggregation).to eq(5) end end end - shared_examples 'database_sourced_aggregated_metrics' do - let(:datasource) { namespace::DATABASE_SOURCE } - - it_behaves_like 'aggregated_metrics_data' - end - - shared_examples 'redis_sourced_aggregated_metrics' do - let(:datasource) { namespace::REDIS_SOURCE } - - it_behaves_like 'aggregated_metrics_data' do - context 'error handling' do - let(:aggregated_metrics) { [aggregated_metric(name: 'gmau_1', source: datasource, time_frame: time_frame)] } - let(:error) { Gitlab::UsageDataCounters::HLLRedisCounter::EventError } - - before do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics) - end - allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_raise(error) - end - - context 'development and test environment' do - it 're raises Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do - expect { aggregated_metrics_data }.to raise_error error - end - end - - context 'production' do - it 'rescues Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do - stub_rails_env('production') - - expect(aggregated_metrics_data).to eq('gmau_1' => -1) - end - end - end + context 'with invalid configuration' do + where(:time_frame, :operator, :datasource, :expected_error) do + '28d' | 'SUM' | 'redis_hll' | namespace::UnknownAggregationOperator + '7d' | 'AND' | 'mongodb' | namespace::UnknownAggregationSource + 'all' | 'AND' | 'redis_hll' | namespace::DisallowedAggregationTimeFrame end - end - - describe '.aggregated_metrics_all_time_data' do - subject(:aggregated_metrics_data) { described_class.new(recorded_at).all_time_data } - let(:start_date) { nil } - let(:end_date) { nil } - let(:time_frame) { ['all'] } - - it_behaves_like 'database_sourced_aggregated_metrics' - - context 'redis sourced aggregated metrics' do - let(:aggregated_metrics) { [aggregated_metric(name: 'gmau_1', time_frame: time_frame)] } + with_them do + let(:aggregate) do + { + source: datasource, + operator: operator, + events: %w[event1 event2] + } + end - before do - allow_next_instance_of(described_class) do |instance| - allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics) - end + subject(:calculate_count_for_aggregation) do + described_class + .new(recorded_at) + .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) end - context 'development and test environment' do - it 'raises Gitlab::Usage::Metrics::Aggregates::DisallowedAggregationTimeFrame' do - expect { aggregated_metrics_data }.to raise_error namespace::DisallowedAggregationTimeFrame + context 'with non prod environment' do + it 'raises error' do + expect { calculate_count_for_aggregation }.to raise_error expected_error end end - context 'production env' do - it 'returns fallback value for unsupported time frame' do + context 'with prod environment' do + before do stub_rails_env('production') + end - expect(aggregated_metrics_data).to eq('gmau_1' => -1) + it 'returns fallback value' do + expect(calculate_count_for_aggregation).to be(-1) end end end end - context 'legacy aggregated metrics configuration' do - let(:temp_dir) { Dir.mktmpdir } - let(:temp_file) { Tempfile.new(%w[common .yml], temp_dir) } - - before do - stub_const("#{namespace}::AGGREGATED_METRICS_PATH", File.expand_path('*.yml', temp_dir)) - File.open(temp_file.path, "w+b") do |file| - file.write [aggregated_metric(name: "gmau_1", time_frame: '7d')].to_yaml - end - end - - after do - temp_file.unlink - FileUtils.remove_entry(temp_dir) if Dir.exist?(temp_dir) + context 'when union data is not available' do + subject(:calculate_count_for_aggregation) do + described_class + .new(recorded_at) + .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) end - it 'allows for YAML aliases in aggregated metrics configs' do - expect(YAML).to receive(:safe_load).with(kind_of(String), aliases: true).at_least(:once) - - described_class.new(recorded_at) + where(:time_frame, :operator, :datasource) do + '28d' | 'OR' | 'redis_hll' + '7d' | 'OR' | 'database' end - end - - describe '.aggregated_metrics_weekly_data' do - subject(:aggregated_metrics_data) { described_class.new(recorded_at).weekly_data } - let(:start_date) { 7.days.ago.to_date } - let(:time_frame) { ['7d'] } - - it_behaves_like 'database_sourced_aggregated_metrics' - it_behaves_like 'redis_sourced_aggregated_metrics' - end - - describe '.aggregated_metrics_monthly_data' do - subject(:aggregated_metrics_data) { described_class.new(recorded_at).monthly_data } - - let(:start_date) { 4.weeks.ago.to_date } - let(:time_frame) { ['28d'] } - - it_behaves_like 'database_sourced_aggregated_metrics' - it_behaves_like 'redis_sourced_aggregated_metrics' - end - - describe '.calculate_count_for_aggregation' do - using RSpec::Parameterized::TableSyntax - - context 'with valid configuration' do - where(:number_of_days, :operator, :datasource, :expected_method) do - 28 | 'AND' | 'redis' | :calculate_metrics_intersections - 7 | 'AND' | 'redis' | :calculate_metrics_intersections - 28 | 'AND' | 'database' | :calculate_metrics_intersections - 7 | 'AND' | 'database' | :calculate_metrics_intersections - 28 | 'OR' | 'redis' | :calculate_metrics_union - 7 | 'OR' | 'redis' | :calculate_metrics_union - 28 | 'OR' | 'database' | :calculate_metrics_union - 7 | 'OR' | 'database' | :calculate_metrics_union - end - - with_them do - let(:time_frame) { "#{number_of_days}d" } - let(:start_date) { number_of_days.days.ago.to_date } - let(:params) { { start_date: start_date, end_date: end_date, recorded_at: recorded_at } } - let(:aggregate) do - { - source: datasource, - operator: operator, - events: %w[event1 event2] - } - end - - subject(:calculate_count_for_aggregation) do - described_class - .new(recorded_at) - .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) - end - - it 'returns the number of unique events for aggregation', :aggregate_failures do - expect(namespace::SOURCES[datasource]) - .to receive(expected_method) - .with(params.merge(metric_names: %w[event1 event2])) - .and_return(5) - expect(calculate_count_for_aggregation).to eq(5) - end + with_them do + before do + allow(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).and_raise(sources::UnionNotAvailable) end - end - context 'with invalid configuration' do - where(:time_frame, :operator, :datasource, :expected_error) do - '28d' | 'SUM' | 'redis' | namespace::UnknownAggregationOperator - '7d' | 'AND' | 'mongodb' | namespace::UnknownAggregationSource - 'all' | 'AND' | 'redis' | namespace::DisallowedAggregationTimeFrame + let(:aggregate) do + { + source: datasource, + operator: operator, + events: %w[event1 event2] + } end - with_them do - let(:aggregate) do - { - source: datasource, - operator: operator, - events: %w[event1 event2] - } - end - - subject(:calculate_count_for_aggregation) do - described_class - .new(recorded_at) - .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) - end - - context 'with non prod environment' do - it 'raises error' do - expect { calculate_count_for_aggregation }.to raise_error expected_error - end - end - - context 'with prod environment' do - before do - stub_rails_env('production') - end - - it 'returns fallback value' do - expect(calculate_count_for_aggregation).to be(-1) - end + context 'with non prod environment' do + it 'raises error' do + expect { calculate_count_for_aggregation }.to raise_error sources::UnionNotAvailable end end - end - - context 'when union data is not available' do - subject(:calculate_count_for_aggregation) do - described_class - .new(recorded_at) - .calculate_count_for_aggregation(aggregation: aggregate, time_frame: time_frame) - end - where(:time_frame, :operator, :datasource) do - '28d' | 'OR' | 'redis' - '7d' | 'OR' | 'database' - end - - with_them do + context 'with prod environment' do before do - allow(namespace::SOURCES[datasource]).to receive(:calculate_metrics_union).and_raise(sources::UnionNotAvailable) - end - - let(:aggregate) do - { - source: datasource, - operator: operator, - events: %w[event1 event2] - } - end - - context 'with non prod environment' do - it 'raises error' do - expect { calculate_count_for_aggregation }.to raise_error sources::UnionNotAvailable - end + stub_rails_env('production') end - context 'with prod environment' do - before do - stub_rails_env('production') - end - - it 'returns fallback value' do - expect(calculate_count_for_aggregation).to be(-1) - end + it 'returns fallback value' do + expect(calculate_count_for_aggregation).to be(-1) end end end |