Commit 0bdd20eb authored by Rémy Coutable's avatar Rémy Coutable

ci: Distribute static-analysis tasks more intelligently

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent a762ccc6
......@@ -19,18 +19,18 @@ class StaticAnalysis
# file that is generated by an EE installation, which can
# contain values that a FOSS installation won't find. To work
# around this we will only enable this task on EE installations.
TASKS_BY_DURATIONS_SECONDS_DESC = {
TASKS_WITH_DURATIONS_SECONDS = {
%w[bin/rake lint:haml] => 800,
# We need to disable the cache for this cop since it creates files under tmp/feature_flags/*.used,
# the cache would prevent these files from being created.
%w[bundle exec rubocop --only Gitlab/MarkUsedFeatureFlags --cache false] => 600,
(Gitlab.ee? ? %w[bin/rake gettext:updated_check] : nil) => 360,
%w[yarn run lint:eslint:all] => 312,
%w[bundle exec rubocop --parallel] => 300,
%w[yarn run lint:prettier] => 162,
%w[bin/rake gettext:lint] => 65,
%w[bundle exec license_finder] => 61,
%w[bin/rake lint:static_verification] => 45,
%w[bundle exec rubocop --parallel] => 40,
%w[bin/rake config_lint] => 26,
%w[bin/rake gitlab:sidekiq:all_queues_yml:check] => 15,
(Gitlab.ee? ? %w[bin/rake gitlab:sidekiq:sidekiq_queues_yml:check] : nil) => 11,
......@@ -40,14 +40,21 @@ class StaticAnalysis
%w[scripts/lint-rugged] => 1,
%w[scripts/gemfile_lock_changed.sh] => 1,
%w[scripts/frontend/check_no_partial_karma_jest.sh] => 1
}.reject { |k| k.nil? }.sort_by { |a| -a[1] }.to_h.keys.freeze
}.reject { |k| k.nil? }.freeze
def run_tasks!
tasks = tasks_to_run((ENV['CI_NODE_INDEX'] || 1).to_i, (ENV['CI_NODE_TOTAL'] || 1).to_i)
StaticAnalysisTasks = Struct.new(:tasks, :duration)
def run_tasks!(options = {})
node_tasks = tasks_to_run((ENV['CI_NODE_TOTAL'] || 1).to_i, debug: options[:debug])[(ENV['CI_NODE_INDEX'] || 1).to_i - 1]
if options[:dry_run]
puts "Dry-run mode!"
return
end
static_analysis = Gitlab::Popen::Runner.new
static_analysis.run(tasks) do |cmd, &run|
static_analysis.run(node_tasks.tasks) do |cmd, &run|
puts
puts "$ #{cmd.join(' ')}"
......@@ -107,16 +114,62 @@ class StaticAnalysis
.count { |result| !ALLOWED_WARNINGS.include?(result.stderr.strip) }
end
def tasks_to_run(node_index, node_total)
tasks = []
TASKS_BY_DURATIONS_SECONDS_DESC.each_with_index do |task, i|
tasks << task if i % node_total == (node_index - 1)
def tasks_to_run(node_total, debug: false)
tasks_per_node = Array.new(node_total) { StaticAnalysisTasks.new([], 0) }
total_time = TASKS_WITH_DURATIONS_SECONDS.values.sum.to_f
ideal_time_per_job = total_time / node_total
tasks_by_duration_desc = TASKS_WITH_DURATIONS_SECONDS.sort_by { |a| -a[1] }.to_h
p "total_time: #{total_time}" if debug
p "ideal_time_per_job: #{ideal_time_per_job}" if debug
tasks_by_duration_desc.each_with_index do |(task, duration), i|
puts "Assigning #{task}..." if debug
(0...node_total).each do |node_index|
puts "Current node: #{node_index}..." if debug
# Task is already longer than the ideal time
if duration >= ideal_time_per_job && tasks_per_node[node_index].tasks.empty?
puts "Assigning #{task} to node #{node_index} (#{duration}s)." if debug
assign_task_to_node(tasks_by_duration_desc, tasks_per_node[node_index], task, duration)
break
elsif tasks_per_node[node_index].duration + duration <= ideal_time_per_job
puts "Assigning #{task} to node #{node_index} (#{duration}s)." if debug
assign_task_to_node(tasks_by_duration_desc, tasks_per_node[node_index], task, duration)
break
else
puts "Node #{node_index} is already full (#{tasks_per_node[node_index]})" if debug
end
end
end
tasks
raise "There are unassigned tasks: #{tasks_by_duration_desc}" unless tasks_by_duration_desc.empty?
tasks_per_node.each_with_index do |node, i|
puts "\nExpected duration for node #{i + 1}: #{node.duration}"
node.tasks.each { |task| puts "- #{task.join(' ')}" }
end
tasks_per_node
end
def assign_task_to_node(remaining_tasks, node, task_name, duration)
node.tasks << task_name
node.duration += duration
remaining_tasks.delete(task_name)
end
end
if $0 == __FILE__
StaticAnalysis.new.run_tasks!
options = {}
if ARGV.include?('--dry-run')
options[:dry_run] = true
end
if ARGV.include?('--debug')
options[:debug] = true
end
StaticAnalysis.new.run_tasks!(options)
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