diff options
Diffstat (limited to 'lib/gitlab/usage/metrics')
13 files changed, 253 insertions, 50 deletions
diff --git a/lib/gitlab/usage/metrics/aggregates/aggregate.rb b/lib/gitlab/usage/metrics/aggregates/aggregate.rb index 11e2fd22638..cd72f16d46d 100644 --- a/lib/gitlab/usage/metrics/aggregates/aggregate.rb +++ b/lib/gitlab/usage/metrics/aggregates/aggregate.rb @@ -13,62 +13,72 @@ module Gitlab end def all_time_data - aggregated_metrics_data(start_date: nil, end_date: nil, time_frame: Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME) + aggregated_metrics_data(Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME) end def monthly_data - aggregated_metrics_data(**monthly_time_range.merge(time_frame: Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME)) + aggregated_metrics_data(Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME) end def weekly_data - aggregated_metrics_data(**weekly_time_range.merge(time_frame: Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME)) + aggregated_metrics_data(Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME) + end + + def calculate_count_for_aggregation(aggregation:, time_frame:) + with_validate_configuration(aggregation, time_frame) do + source = SOURCES[aggregation[:source]] + + if aggregation[:operator] == UNION_OF_AGGREGATED_METRICS + source.calculate_metrics_union(**time_constraints(time_frame).merge(metric_names: aggregation[:events], recorded_at: recorded_at)) + else + source.calculate_metrics_intersections(**time_constraints(time_frame).merge(metric_names: aggregation[:events], recorded_at: recorded_at)) + end + end + rescue Gitlab::UsageDataCounters::HLLRedisCounter::EventError, AggregatedMetricError => error + failure(error) end private attr_accessor :aggregated_metrics, :recorded_at - def aggregated_metrics_data(start_date:, end_date:, time_frame:) + def aggregated_metrics_data(time_frame) aggregated_metrics.each_with_object({}) do |aggregation, data| next if aggregation[:feature_flag] && Feature.disabled?(aggregation[:feature_flag], type: :development) next unless aggregation[:time_frame].include?(time_frame) - case aggregation[:source] - when REDIS_SOURCE - if time_frame == Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME - data[aggregation[:name]] = Gitlab::Utils::UsageData::FALLBACK - Gitlab::ErrorTracking - .track_and_raise_for_dev_exception( - DisallowedAggregationTimeFrame.new("Aggregation time frame: 'all' is not allowed for aggregation with source: '#{REDIS_SOURCE}'") - ) - else - data[aggregation[:name]] = calculate_count_for_aggregation(aggregation: aggregation, start_date: start_date, end_date: end_date) - end - when DATABASE_SOURCE - data[aggregation[:name]] = calculate_count_for_aggregation(aggregation: aggregation, start_date: start_date, end_date: end_date) - else - Gitlab::ErrorTracking - .track_and_raise_for_dev_exception(UnknownAggregationSource.new("Aggregation source: '#{aggregation[:source]}' must be included in #{SOURCES.keys}")) - - data[aggregation[:name]] = Gitlab::Utils::UsageData::FALLBACK - end + data[aggregation[:name]] = calculate_count_for_aggregation(aggregation: aggregation, time_frame: time_frame) end end - def calculate_count_for_aggregation(aggregation:, start_date:, end_date:) - source = SOURCES[aggregation[:source]] - - case aggregation[:operator] - when UNION_OF_AGGREGATED_METRICS - source.calculate_metrics_union(metric_names: aggregation[:events], start_date: start_date, end_date: end_date, recorded_at: recorded_at) - when INTERSECTION_OF_AGGREGATED_METRICS - source.calculate_metrics_intersections(metric_names: aggregation[:events], start_date: start_date, end_date: end_date, recorded_at: recorded_at) - else - Gitlab::ErrorTracking - .track_and_raise_for_dev_exception(UnknownAggregationOperator.new("Events should be aggregated with one of operators #{ALLOWED_METRICS_AGGREGATIONS}")) - Gitlab::Utils::UsageData::FALLBACK + def with_validate_configuration(aggregation, time_frame) + source = aggregation[:source] + + unless ALLOWED_METRICS_AGGREGATIONS.include?(aggregation[:operator]) + return failure( + UnknownAggregationOperator + .new("Events should be aggregated with one of operators #{ALLOWED_METRICS_AGGREGATIONS}") + ) end - rescue Gitlab::UsageDataCounters::HLLRedisCounter::EventError, AggregatedMetricError => error + + unless SOURCES[source] + return failure( + UnknownAggregationSource + .new("Aggregation source: '#{source}' must be included in #{SOURCES.keys}") + ) + end + + if time_frame == Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME && source == REDIS_SOURCE + return failure( + DisallowedAggregationTimeFrame + .new("Aggregation time frame: 'all' is not allowed for aggregation with source: '#{REDIS_SOURCE}'") + ) + end + + yield + end + + def failure(error) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error) Gitlab::Utils::UsageData::FALLBACK end @@ -82,6 +92,17 @@ module Gitlab def load_yaml_from_path(path) YAML.safe_load(File.read(path), aliases: true)&.map(&:with_indifferent_access) end + + def time_constraints(time_frame) + case time_frame + when Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME + monthly_time_range + when Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME + weekly_time_range + when Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME + { start_date: nil, end_date: nil } + end + end end end end diff --git a/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb b/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb new file mode 100644 index 00000000000..63ead5a8cb0 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + # Usage example + # + # In metric YAML definition: + # + # instrumentation_class: AggregatedMetric + # data_source: redis_hll + # options: + # aggregate: + # operator: OR + # attribute: user_id + # events: + # - 'incident_management_alert_status_changed' + # - 'incident_management_alert_assigned' + # - 'incident_management_alert_todo' + # - 'incident_management_alert_create_incident' + + class AggregatedMetric < BaseMetric + FALLBACK = -1 + + def initialize(metric_definition) + super + @source = parse_data_source_to_legacy_value(metric_definition) + @aggregate = options.fetch(:aggregate, {}) + end + + def value + alt_usage_data(fallback: FALLBACK) do + Aggregates::Aggregate + .new(Time.current) + .calculate_count_for_aggregation( + aggregation: aggregate_config, + time_frame: time_frame + ) + end + end + + def suggested_name + Gitlab::Usage::Metrics::NameSuggestion.for(:alt) + end + + private + + attr_accessor :source, :aggregate + + # TODO: This method is a temporary measure that + # handles backwards compatibility until + # point 5 from is resolved https://gitlab.com/gitlab-org/gitlab/-/issues/370963#implementation + def parse_data_source_to_legacy_value(metric_definition) + return 'redis' if metric_definition[:data_source] == 'redis_hll' + + metric_definition[:data_source] + end + + def aggregate_config + { + source: source, + events: options[:events], + operator: aggregate[:operator] + } + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/base_metric.rb b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb index 5e20766b1b4..55da2315e45 100644 --- a/lib/gitlab/usage/metrics/instrumentations/base_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb @@ -23,9 +23,9 @@ module Gitlab attr_reader :metric_available end - def initialize(time_frame:, options: {}) - @time_frame = time_frame - @options = options + def initialize(metric_definition) + @time_frame = metric_definition.fetch(:time_frame) + @options = metric_definition.fetch(:options, {}) end def instrumentation diff --git a/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb index 67dc1455b23..642b67a3b02 100644 --- a/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb @@ -7,7 +7,7 @@ module Gitlab class CountBulkImportsEntitiesMetric < DatabaseMetric operation :count - def initialize(time_frame:, options: {}) + def initialize(metric_definition) super if source_type.present? && !source_type.in?(allowed_source_types) diff --git a/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb index c5498ce530f..d485e8b4f72 100644 --- a/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb @@ -7,7 +7,7 @@ module Gitlab class CountImportedProjectsMetric < DatabaseMetric operation :count - def initialize(time_frame:, options: {}) + def initialize(metric_definition) super raise ArgumentError, "import_type options attribute is required" unless import_type.present? diff --git a/lib/gitlab/usage/metrics/instrumentations/distinct_count_projects_with_expiration_policy_disabled_metric.rb b/lib/gitlab/usage/metrics/instrumentations/distinct_count_projects_with_expiration_policy_disabled_metric.rb new file mode 100644 index 00000000000..0c421dc3311 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/distinct_count_projects_with_expiration_policy_disabled_metric.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class DistinctCountProjectsWithExpirationPolicyDisabledMetric < DatabaseMetric + operation :distinct_count, column: :project_id + + start { Project.minimum(:id) } + finish { Project.maximum(:id) } + + cache_start_and_finish_as :project_id + + relation { ::ContainerExpirationPolicy.where(enabled: false) } + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb index 0f4b903b99c..7c646281598 100644 --- a/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb @@ -28,9 +28,8 @@ module Gitlab end end - def initialize(time_frame: 'none', options: {}) - @time_frame = time_frame - @options = options + def initialize(metric_definition) + super(metric_definition.reverse_merge(time_frame: 'none')) end def value diff --git a/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_direct_installations_count_metric.rb b/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_direct_installations_count_metric.rb new file mode 100644 index 00000000000..f22ee2aa3f5 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_direct_installations_count_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class GitlabForJiraAppDirectInstallationsCountMetric < DatabaseMetric + operation :count + + relation { JiraConnectInstallation.direct_installations } + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_proxy_installations_count_metric.rb b/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_proxy_installations_count_metric.rb new file mode 100644 index 00000000000..222a69faf8b --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/gitlab_for_jira_app_proxy_installations_count_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class GitlabForJiraAppProxyInstallationsCountMetric < DatabaseMetric + operation :count + + relation { JiraConnectInstallation.proxy_installations } + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/merge_request_widget_extension_metric.rb b/lib/gitlab/usage/metrics/instrumentations/merge_request_widget_extension_metric.rb new file mode 100644 index 00000000000..e2fdb3462c5 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/merge_request_widget_extension_metric.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + # Usage example + # + # In metric YAML definition: + # + # instrumentation_class: MergeRequestWidgetExtensionMetric + # options: + # event: expand + # widget: terraform + # + class MergeRequestWidgetExtensionMetric < RedisMetric + extend ::Gitlab::Utils::Override + + def validate_options! + raise ArgumentError, "'event' option is required" unless metric_event.present? + raise ArgumentError, "'widget' option is required" unless widget_name.present? + end + + def widget_name + options[:widget] + end + + override :prefix + def prefix + 'i_code_review_merge_request_widget' + end + + private + + override :redis_key + def redis_key + "#{USAGE_PREFIX}#{prefix}_#{widget_name}_count_#{metric_event}".upcase + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb index bb27cca1bb9..17009f7638e 100644 --- a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb @@ -12,7 +12,7 @@ module Gitlab # events: # - g_analytics_valuestream # end - def initialize(time_frame:, options: {}) + def initialize(metric_definition) super raise ArgumentError, "options events are required" unless metric_events.present? diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb index 26d963e2407..ae3326fa845 100644 --- a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb @@ -17,12 +17,17 @@ module Gitlab include Gitlab::UsageDataCounters::RedisCounter USAGE_PREFIX = "USAGE_" + OPTIONS_PREFIX_KEY = :prefix - def initialize(time_frame:, options: {}) + def initialize(metric_definition) super + validate_options! + end + + def validate_options! raise ArgumentError, "'event' option is required" unless metric_event.present? - raise ArgumentError, "'prefix' option is required" unless prefix.present? + raise ArgumentError, "'prefix' option is required" unless options.has_key?(OPTIONS_PREFIX_KEY) end def metric_event @@ -30,7 +35,7 @@ module Gitlab end def prefix - options[:prefix] + options[OPTIONS_PREFIX_KEY] end def include_usage_prefix? @@ -50,9 +55,10 @@ module Gitlab private def redis_key - key = "#{prefix}_#{metric_event}".upcase + key = metric_event.dup + key.prepend("#{prefix}_") if prefix key.prepend(USAGE_PREFIX) if include_usage_prefix? - key + key.upcase end end end diff --git a/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb b/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb new file mode 100644 index 00000000000..d045265495a --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class WorkItemsActivityAggregatedMetric < AggregatedMetric + available? { Feature.enabled?(:track_work_items_activity) } + end + end + end + end +end |