Commit 16991831 authored by Michael Kozono's avatar Michael Kozono

Merge branch '344803-expose-vsa-aggregation-metadata' into 'master'

Expose VSA incremental aggregation metadata

See merge request gitlab-org/gitlab!82303
parents 40e4d936 89186c7e
......@@ -10,11 +10,41 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
scope :priority_order, -> { order('last_incremental_run_at ASC NULLS FIRST') }
scope :enabled, -> { where('enabled IS TRUE') }
def estimated_next_run_at
return unless enabled
return if last_incremental_run_at.nil?
estimation = (last_incremental_run_at - earliest_last_run_at) + average_aggregation_duration
estimation < 1 ? nil : estimation.from_now
end
def self.safe_create_for_group(group)
top_level_group = group.root_ancestor
return if Analytics::CycleAnalytics::Aggregation.exists?(group_id: top_level_group.id)
aggregation = find_by(group_id: top_level_group.id)
return aggregation if aggregation.present?
insert({ group_id: top_level_group.id }, unique_by: :group_id)
find_by(group_id: top_level_group.id)
end
private
def average_aggregation_duration
return 0.seconds if incremental_runtimes_in_seconds.empty?
average = incremental_runtimes_in_seconds.sum.fdiv(incremental_runtimes_in_seconds.size)
average.seconds
end
def earliest_last_run_at
max = self.class.select(:last_incremental_run_at)
.where(enabled: true)
.where.not(last_incremental_run_at: nil)
.priority_order
.limit(1)
.to_sql
connection.select_value("(#{max})")
end
def self.load_batch(last_run_at, batch_size = 100)
......
......@@ -230,4 +230,16 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do
expect(data_attributes[:direction]).to eq('asc')
end
end
describe 'aggregation params' do
it 'exposes the aggregation params' do
data_collector_params = subject.to_data_attributes
expect(data_collector_params[:aggregation]).to eq({
enabled: 'true',
last_run_at: nil,
next_run_at: nil
})
end
end
end
......@@ -86,6 +86,7 @@ module Gitlab
def to_data_attributes
{}.tap do |attrs|
attrs[:aggregation] = aggregation_attributes if group
attrs[:group] = group_data_attributes if group
attrs[:value_stream] = value_stream_data_attributes.to_json if value_stream
attrs[:created_after] = created_after.to_date.iso8601
......@@ -103,6 +104,15 @@ module Gitlab
private
def aggregation_attributes
aggregation = ::Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group)
{
enabled: aggregation.enabled.to_s,
last_run_at: aggregation.last_incremental_run_at,
next_run_at: aggregation.estimated_next_run_at
}
end
def group_data_attributes
{
id: group.id,
......
......@@ -25,18 +25,16 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
let_it_be(:subgroup) { create(:group, parent: group) }
it 'creates the aggregation record' do
described_class.safe_create_for_group(group)
record = described_class.safe_create_for_group(group)
record = described_class.find_by(group_id: group)
expect(record).to be_present
expect(record).to be_persisted
end
context 'when non top-level group is given' do
it 'creates the aggregation record for the top-level group' do
described_class.safe_create_for_group(subgroup)
record = described_class.safe_create_for_group(subgroup)
record = described_class.find_by(group_id: group)
expect(record).to be_present
expect(record).to be_persisted
end
end
......@@ -75,4 +73,56 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
expect(last_two).to eq([aggregation5, aggregation2])
end
end
describe '#estimated_next_run_at' do
around do |example|
freeze_time { example.run }
end
context 'when aggregation was not yet executed for the given group' do
let(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: nil) }
it { expect(aggregation.estimated_next_run_at).to eq(nil) }
end
context 'when aggregation was already run' do
let!(:other_aggregation1) { create(:cycle_analytics_aggregation, last_incremental_run_at: 10.minutes.ago) }
let!(:other_aggregation2) { create(:cycle_analytics_aggregation, last_incremental_run_at: 15.minutes.ago) }
let!(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: 5.minutes.ago) }
it 'returns the duration between the previous run timestamp and the earliest last_incremental_run_at' do
expect(aggregation.estimated_next_run_at).to eq(10.minutes.from_now)
end
context 'when the aggregation has persisted previous runtimes' do
before do
aggregation.update!(incremental_runtimes_in_seconds: [30, 60, 90])
end
it 'adds the runtime to the estimation' do
expect(aggregation.estimated_next_run_at).to eq((10.minutes.seconds + 60.seconds).from_now)
end
end
end
context 'when no records are present in the DB' do
it 'returns nil' do
expect(described_class.new.estimated_next_run_at).to eq(nil)
end
end
context 'when only one aggregation record present' do
let!(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: 5.minutes.ago) }
it 'returns nil' do
expect(aggregation.estimated_next_run_at).to eq(nil)
end
end
context 'when the aggregation is disabled' do
it 'returns nil' do
expect(described_class.new(enabled: false).estimated_next_run_at).to eq(nil)
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