BigW Consortium Gitlab

Commit a7b3f11e by Tiago Botelho

Adds get all medians to Cycle Analytics model

parent 4371f845
...@@ -6,6 +6,12 @@ class CycleAnalytics ...@@ -6,6 +6,12 @@ class CycleAnalytics
@options = options @options = options
end end
def self.all_medians_per_stage(projects, options)
STAGES.each_with_object({}) do |stage_name, hsh|
hsh[stage_name] = Gitlab::CycleAnalytics::Stage[stage_name].new(projects: projects, options: options).medians&.values || []
end
end
def summary def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project, @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project,
from: @options[:from], from: @options[:from],
......
...@@ -14,7 +14,7 @@ module Gitlab ...@@ -14,7 +14,7 @@ module Gitlab
def stage_query def stage_query
query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id]))
.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.where(issue_table[:project_id].eq(@project.id)) # rubocop:disable Gitlab/ModuleWithInstanceVariables .where(issue_table[:project_id].in(project_ids)) # rubocop:disable Gitlab/ModuleWithInstanceVariables
.where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
# Load merge_requests # Load merge_requests
...@@ -22,9 +22,14 @@ module Gitlab ...@@ -22,9 +22,14 @@ module Gitlab
.on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])) .on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id]))
.join(mr_metrics_table) .join(mr_metrics_table)
.on(mr_table[:id].eq(mr_metrics_table[:merge_request_id])) .on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
.project(issue_table[:project_id].as("project_id"))
query query
end end
def project_ids
@projects.map(&:id)
end
end end
end end
end end
...@@ -3,8 +3,8 @@ module Gitlab ...@@ -3,8 +3,8 @@ module Gitlab
class BaseStage class BaseStage
include BaseQuery include BaseQuery
def initialize(project:, options:) def initialize(projects:, options:)
@project = project @projects = projects
@options = options @options = options
end end
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
raise NotImplementedError.new("Expected #{self.name} to implement title") raise NotImplementedError.new("Expected #{self.name} to implement title")
end end
def median def medians
cte_table = Arel::Table.new("cte_table_for_#{name}") cte_table = Arel::Table.new("cte_table_for_#{name}")
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
...@@ -31,7 +31,7 @@ module Gitlab ...@@ -31,7 +31,7 @@ module Gitlab
cte_table, cte_table,
subtract_datetimes(base_query.dup, start_time_attrs, end_time_attrs, name.to_s)) subtract_datetimes(base_query.dup, start_time_attrs, end_time_attrs, name.to_s))
median_datetime(cte_table, interval_query, name) median_datetimes(cte_table, interval_query, name)
end end
def name def name
......
module Gitlab
module CycleAnalytics
class UsageData
PROJECTS_LIMIT = 10
attr_reader :projects, :options
def initialize(projects, options)
@projects = projects
@options = options
end
def to_json
total = 0
values = {}
medians_per_stage.each do |stage_name, medians|
medians = medians.compact
stage_values = {
average: calc_average(medians),
sd: standard_deviation(medians),
missing: projects.length - medians.length
}
total += stage_values.values.compact.sum
values[stage_name] = stage_values
end
values[:total] = total
{ avg_cycle_analytics: values }
end
private
def medians_per_stage
@medians_per_stage ||= ::CycleAnalytics.all_medians_per_stage(projects, options)
end
def calc_average(values)
return if values.empty?
(values.sum / values.length).to_i
end
def sample_variance(values)
return 0 if values.length <= 1
avg = calc_average(values)
sum = values.inject(0) do |acc, val|
acc + (val - avg)**2
end
sum / (values.length - 1)
end
def standard_deviation(values)
Math.sqrt(sample_variance(values)).to_i
end
end
end
end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Gitlab module Gitlab
module Database module Database
module Median module Median
def median_datetime(arel_table, query_so_far, column_sym) def median_datetimes(arel_table, query_so_far, column_sym)
median_queries = median_queries =
if Gitlab::Database.postgresql? if Gitlab::Database.postgresql?
pg_median_datetime_sql(arel_table, query_so_far, column_sym) pg_median_datetime_sql(arel_table, query_so_far, column_sym)
...@@ -13,16 +13,16 @@ module Gitlab ...@@ -13,16 +13,16 @@ module Gitlab
results = Array.wrap(median_queries).map do |query| results = Array.wrap(median_queries).map do |query|
ActiveRecord::Base.connection.execute(query) ActiveRecord::Base.connection.execute(query)
end end
extract_median(results).presence extract_medians(results).presence
end end
def extract_median(results) def extract_medians(results)
result = results.compact.first result = results.compact.first
if Gitlab::Database.postgresql? if Gitlab::Database.postgresql?
result = result.first.presence result.values.map do |id, median|
median = result['median'] if result [id, median&.to_f]
median.to_f if median end.to_h
elsif Gitlab::Database.mysql? elsif Gitlab::Database.mysql?
result.to_a.flatten.first result.to_a.flatten.first
end end
...@@ -69,17 +69,20 @@ module Gitlab ...@@ -69,17 +69,20 @@ module Gitlab
cte_table, cte_table,
arel_table arel_table
.project( .project(
arel_table[:project_id],
arel_table[column_sym].as(column_sym.to_s), arel_table[column_sym].as(column_sym.to_s),
Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("row_number", []), Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("rank", []),
Arel::Nodes::Window.new.order(arel_table[column_sym])).as('row_id'), Arel::Nodes::Window.new.partition(arel_table[:project_id])
arel_table.project("COUNT(1)").as('ct')). .order(arel_table[column_sym])).as('row_id'),
arel_table.from(arel_table.alias).project("COUNT(*)").where(arel_table[:project_id].eq(arel_table.alias[:project_id])).as('ct')).
# Disallow negative values # Disallow negative values
where(arel_table[column_sym].gteq(zero_interval))) where(arel_table[column_sym].gteq(zero_interval)))
# From the CTE, select either the middle row or the middle two rows (this is accomplished # From the CTE, select either the middle row or the middle two rows (this is accomplished
# by 'where cte.row_id between cte.ct / 2.0 AND cte.ct / 2.0 + 1'). Find the average of the # by 'where cte.row_id between cte.ct / 2.0 AND cte.ct / 2.0 + 1'). Find the average of the
# selected rows, and this is the median value. # selected rows, and this is the median value.
cte_table.project(average([extract_epoch(cte_table[column_sym])], "median")) cte_table.project(cte_table[:project_id])
.project(average([extract_epoch(cte_table[column_sym])], "median"))
.where( .where(
Arel::Nodes::Between.new( Arel::Nodes::Between.new(
cte_table[:row_id], cte_table[:row_id],
...@@ -90,6 +93,8 @@ module Gitlab ...@@ -90,6 +93,8 @@ module Gitlab
) )
) )
.with(query_so_far, cte) .with(query_so_far, cte)
.group(cte_table[:project_id])
.order(cte_table[:project_id])
.to_sql .to_sql
end end
......
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
license_usage_data.merge(system_usage_data) license_usage_data.merge(system_usage_data)
.merge(features_usage_data) .merge(features_usage_data)
.merge(components_usage_data) .merge(components_usage_data)
.merge(cycle_analytics_usage_data)
end end
def to_json(force_refresh: false) def to_json(force_refresh: false)
...@@ -71,6 +72,12 @@ module Gitlab ...@@ -71,6 +72,12 @@ module Gitlab
} }
end end
def cycle_analytics_usage_data
projects = Project.sorted_by_activity.limit(Gitlab::CycleAnalytics::UsageData::PROJECTS_LIMIT)
Gitlab::CycleAnalytics::UsageData.new(projects, { from: 7.days.ago }).to_json
end
def features_usage_data def features_usage_data
features_usage_data_ce features_usage_data_ce
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