Commit 6c24fa0b authored by Nick Thomas's avatar Nick Thomas

Merge branch '332954-build-usage-data-queries-using-instrumentation-classes' into 'master'

Build usage data queries and usage data non sql metrics using instrumentation classes

See merge request gitlab-org/gitlab!67049
parents 255b93e9 4ac90389
...@@ -15,7 +15,7 @@ RSpec.describe Gitlab::UsageDataNonSqlMetrics do ...@@ -15,7 +15,7 @@ RSpec.describe Gitlab::UsageDataNonSqlMetrics do
described_class.uncached_data described_class.uncached_data
end end
expect(recorder.count).to eq(55) expect(recorder.count).to eq(56)
end end
end end
end end
...@@ -3,40 +3,43 @@ ...@@ -3,40 +3,43 @@
module Gitlab module Gitlab
module Usage module Usage
class Metric class Metric
include ActiveModel::Model attr_reader :definition
InvalidMetricError = Class.new(RuntimeError) def initialize(definition)
@definition = definition
attr_accessor :key_path, :value end
validates :key_path, presence: true class << self
def all
@all ||= Gitlab::Usage::MetricDefinition.with_instrumentation_class.map do |definition|
self.new(definition)
end
end
end
def definition def with_value
self.class.definitions[key_path] unflatten_key_path(intrumentation_object.value)
end end
def unflatten_key_path def with_instrumentation
unflatten(key_path.split('.'), value) unflatten_key_path(intrumentation_object.instrumentation)
end end
class << self private
def definitions
@definitions ||= Gitlab::Usage::MetricDefinition.definitions
end
def dictionary def unflatten_key_path(value)
definitions.map { |key, definition| definition.to_dictionary } ::Gitlab::Usage::Metrics::KeyPathProcessor.process(definition.key_path, value)
end
end end
private def instrumentation_class
"Gitlab::Usage::Metrics::Instrumentations::#{definition.instrumentation_class}"
end
def unflatten(keys, value) def intrumentation_object
loop do instrumentation_class.constantize.new(
value = { keys.pop.to_sym => value } time_frame: definition.time_frame,
break if keys.blank? options: definition.attributes[:options]
end )
value
end end
end end
end end
......
...@@ -7,6 +7,8 @@ module Gitlab ...@@ -7,6 +7,8 @@ module Gitlab
BASE_REPO_PATH = 'https://gitlab.com/gitlab-org/gitlab/-/blob/master' BASE_REPO_PATH = 'https://gitlab.com/gitlab-org/gitlab/-/blob/master'
SKIP_VALIDATION_STATUSES = %w[deprecated removed].to_set.freeze SKIP_VALIDATION_STATUSES = %w[deprecated removed].to_set.freeze
InvalidError = Class.new(RuntimeError)
attr_reader :path attr_reader :path
attr_reader :attributes attr_reader :attributes
...@@ -48,7 +50,7 @@ module Gitlab ...@@ -48,7 +50,7 @@ module Gitlab
Metric file: #{path} Metric file: #{path}
ERROR_MSG ERROR_MSG
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Usage::Metric::InvalidMetricError.new(error_message)) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(InvalidError.new(error_message))
end end
end end
end end
...@@ -69,6 +71,10 @@ module Gitlab ...@@ -69,6 +71,10 @@ module Gitlab
@all ||= definitions.map { |_key_path, definition| definition } @all ||= definitions.map { |_key_path, definition| definition }
end end
def with_instrumentation_class
all.select { |definition| definition.attributes[:instrumentation_class].present? }
end
def schemer def schemer
@schemer ||= ::JSONSchemer.schema(Pathname.new(METRIC_SCHEMA_PATH)) @schemer ||= ::JSONSchemer.schema(Pathname.new(METRIC_SCHEMA_PATH))
end end
...@@ -92,7 +98,7 @@ module Gitlab ...@@ -92,7 +98,7 @@ module Gitlab
self.new(path, definition).tap(&:validate!) self.new(path, definition).tap(&:validate!)
rescue StandardError => e rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Usage::Metric::InvalidMetricError.new(e.message)) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(InvalidError.new(e.message))
end end
def load_all_from_path!(definitions, glob_path) def load_all_from_path!(definitions, glob_path)
...@@ -100,7 +106,7 @@ module Gitlab ...@@ -100,7 +106,7 @@ module Gitlab
definition = load_from_file(path) definition = load_from_file(path)
if previous = definitions[definition.key] if previous = definitions[definition.key]
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(Gitlab::Usage::Metric::InvalidMetricError.new("Metric '#{definition.key}' is already defined in '#{previous.path}'")) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(InvalidError.new("Metric '#{definition.key}' is already defined in '#{previous.path}'"))
end end
definitions[definition.key] = definition definitions[definition.key] = definition
......
...@@ -5,26 +5,7 @@ module Gitlab ...@@ -5,26 +5,7 @@ module Gitlab
class << self class << self
# Build the Usage Ping JSON payload from metrics YAML definitions which have instrumentation class set # Build the Usage Ping JSON payload from metrics YAML definitions which have instrumentation class set
def uncached_data def uncached_data
::Gitlab::Usage::MetricDefinition.all.map do |definition| ::Gitlab::Usage::Metric.all.map(&:with_value).reduce({}, :deep_merge)
instrumentation_class = definition.attributes[:instrumentation_class]
options = definition.attributes[:options]
if instrumentation_class.present?
metric_value = "Gitlab::Usage::Metrics::Instrumentations::#{instrumentation_class}".constantize.new(
time_frame: definition.attributes[:time_frame],
options: options).value
metric_payload(definition.key_path, metric_value)
else
{}
end
end.reduce({}, :deep_merge)
end
private
def metric_payload(key_path, value)
::Gitlab::Usage::Metrics::KeyPathProcessor.process(key_path, value)
end end
end end
end end
......
...@@ -5,6 +5,10 @@ module Gitlab ...@@ -5,6 +5,10 @@ module Gitlab
SQL_METRIC_DEFAULT = -3 SQL_METRIC_DEFAULT = -3
class << self class << self
def uncached_data
super.with_indifferent_access.deep_merge(instrumentation_metrics_queries.with_indifferent_access)
end
def add_metric(metric, time_frame: 'none') def add_metric(metric, time_frame: 'none')
metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
...@@ -43,6 +47,12 @@ module Gitlab ...@@ -43,6 +47,12 @@ module Gitlab
projects_jira_cloud_active: 0 projects_jira_cloud_active: 0
} }
end end
private
def instrumentation_metrics_queries
::Gitlab::Usage::Metric.all.map(&:with_instrumentation).reduce({}, :deep_merge)
end
end end
end end
end end
...@@ -5,6 +5,10 @@ module Gitlab ...@@ -5,6 +5,10 @@ module Gitlab
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41091 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41091
class UsageDataQueries < UsageData class UsageDataQueries < UsageData
class << self class << self
def uncached_data
super.with_indifferent_access.deep_merge(instrumentation_metrics_queries.with_indifferent_access)
end
def add_metric(metric, time_frame: 'none') def add_metric(metric, time_frame: 'none')
metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
...@@ -64,6 +68,12 @@ module Gitlab ...@@ -64,6 +68,12 @@ module Gitlab
def epics_deepest_relationship_level def epics_deepest_relationship_level
{ epics_deepest_relationship_level: 0 } { epics_deepest_relationship_level: 0 }
end end
private
def instrumentation_metrics_queries
::Gitlab::Usage::Metric.all.map(&:with_instrumentation).reduce({}, :deep_merge)
end
end end
end end
end end
...@@ -87,14 +87,14 @@ RSpec.describe Gitlab::Usage::MetricDefinition do ...@@ -87,14 +87,14 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
end end
it 'raise exception' do it 'raise exception' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError)) expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes).validate! described_class.new(path, attributes).validate!
end end
context 'with skip_validation' do context 'with skip_validation' do
it 'raise exception if skip_validation: false' do it 'raise exception if skip_validation: false' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError)) expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes.merge( { skip_validation: false } )).validate! described_class.new(path, attributes.merge( { skip_validation: false } )).validate!
end end
...@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do ...@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
attributes[:status] = 'broken' attributes[:status] = 'broken'
attributes.delete(:repair_issue_url) attributes.delete(:repair_issue_url)
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError)) expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
described_class.new(path, attributes).validate! described_class.new(path, attributes).validate!
end end
...@@ -173,7 +173,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do ...@@ -173,7 +173,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
write_metric(metric1, path, yaml_content) write_metric(metric1, path, yaml_content)
write_metric(metric2, path, yaml_content) write_metric(metric2, path, yaml_content)
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError)) expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
subject subject
end end
......
...@@ -3,27 +3,46 @@ ...@@ -3,27 +3,46 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Usage::Metric do RSpec.describe Gitlab::Usage::Metric do
describe '#definition' do let!(:issue) { create(:issue) }
it 'returns key_path metric definiton' do
expect(described_class.new(key_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition) let(:attributes) do
end {
data_category: "Operational",
key_path: "counts.issues",
description: "Count of Issues created",
product_section: "dev",
product_stage: "plan",
product_group: "group::plan",
product_category: "issue_tracking",
value_type: "number",
status: "data_available",
time_frame: "all",
data_source: "database",
instrumentation_class: "CountIssuesMetric",
distribution: %w(ce ee),
tier: %w(free premium ultimate)
}
end end
describe '#unflatten_default_path' do let(:issue_count_metric_definiton) do
using RSpec::Parameterized::TableSyntax double(:issue_count_metric_definiton,
attributes.merge({ attributes: attributes })
)
end
where(:key_path, :value, :expected_hash) do before do
'uuid' | nil | { uuid: nil } allow(ApplicationRecord.connection).to receive(:transaction_open?).and_return(false)
'uuid' | '1111' | { uuid: '1111' } end
'counts.issues' | nil | { counts: { issues: nil } }
'counts.issues' | 100 | { counts: { issues: 100 } }
'usage_activity_by_stage.verify.ci_builds' | 100 | { usage_activity_by_stage: { verify: { ci_builds: 100 } } }
end
with_them do describe '#with_value' do
subject { described_class.new(key_path: key_path, value: value).unflatten_key_path } it 'returns key_path metric with the corresponding value' do
expect(described_class.new(issue_count_metric_definiton).with_value).to eq({ counts: { issues: 1 } })
end
end
it { is_expected.to eq(expected_hash) } describe '#with_instrumentation' do
it 'returns key_path metric with the corresponding generated query' do
expect(described_class.new(issue_count_metric_definiton).with_instrumentation).to eq({ counts: { issues: "SELECT COUNT(\"issues\".\"id\") FROM \"issues\"" } })
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment