Draft: CI: Profile factory creations and SQL queries via EventProf
What does this MR do and why?
This is a proof of concept (hence Draft
) to investigate which specs create the most factory and run most SQL queries and thus are slow.
Related:
It tracks amount of factory creations and SQL queries in all RSpec tests via test-prof
's EventProf as JSON.
Results
{
"fields": [
{"key": "type", "label": "Type", "sortable": true},
{"key": "count", "label": "Factories created", "sortable": true},
{"key": "time", "label": "Factories runtime (s)", "sortable": true},
{"key": "run_time", "label": "Total runtime (s)", "sortable": true},
{"key": "percent", "label": "% spent in factories", "sortable": true},
{"key": "examples", "label": "Examples", "sortable": true},
{"key": "count_per_example", "label": "Factories / example", "sortable": true}
],
"items": [{"type":"uploader","count":692,"time":321.55,"run_time":401.87,"percent":80.01,"examples":789,"count_per_example":0.88},{"type":"strategy","count":70,"time":10.95,"run_time":13.98,"percent":78.29,"examples":23,"count_per_example":3.04},{"type":"replicator","count":633,"time":330.39,"run_time":439.36,"percent":75.2,"examples":951,"count_per_example":0.67},{"type":"model","count":38482,"time":10730.57,"run_time":15714.5,"percent":68.28,"examples":30377,"count_per_example":1.27},{"type":"finder","count":9528,"time":2020.16,"run_time":2984.05,"percent":67.7,"examples":3322,"count_per_example":2.87},{"type":"presenter","count":1367,"time":330.15,"run_time":489.38,"percent":67.46,"examples":1018,"count_per_example":1.34},{"type":"policy","count":5325,"time":1091.41,"run_time":1748.51,"percent":62.42,"examples":3212,"count_per_example":1.66},{"type":"worker","count":3802,"time":989.68,"run_time":1754.15,"percent":56.42,"examples":2878,"count_per_example":1.32},{"type":"serializer","count":2311,"time":624.42,"run_time":1114.73,"percent":56.02,"examples":1314,"count_per_example":1.76},{"type":"service","count":44013,"time":10688.93,"run_time":21038.68,"percent":50.81,"examples":20743,"count_per_example":2.12},{"type":"channel","count":9,"time":0.81,"run_time":1.6,"percent":50.74,"examples":11,"count_per_example":0.82},{"type":"helper","count":2260,"time":502.73,"run_time":1004.04,"percent":50.07,"examples":3522,"count_per_example":0.64},{"type":"graphql","count":4354,"time":878.21,"run_time":1755.02,"percent":50.04,"examples":3970,"count_per_example":1.1},{"type":"controller","count":15170,"time":3468.65,"run_time":6994.63,"percent":49.59,"examples":8077,"count_per_example":1.88},{"type":"mailer","count":1485,"time":275.24,"run_time":596.49,"percent":46.14,"examples":1350,"count_per_example":1.1},{"type":"request","count":50963,"time":7538.89,"run_time":16982.97,"percent":44.39,"examples":25464,"count_per_example":2.0},{"type":"view","count":1285,"time":307.48,"run_time":712.76,"percent":43.14,"examples":1198,"count_per_example":1.07},{"type":"lib","count":26973,"time":7323.0,"run_time":19383.18,"percent":37.78,"examples":35791,"count_per_example":0.75},{"type":"elastic_integration","count":91,"time":24.14,"run_time":82.95,"percent":29.1,"examples":9,"count_per_example":10.11},{"type":"experiment","count":25,"time":4.43,"run_time":15.31,"percent":28.95,"examples":74,"count_per_example":0.34},{"type":"elastic","count":80,"time":49.45,"run_time":200.27,"percent":24.69,"examples":104,"count_per_example":0.77},{"type":"routing","count":88,"time":16.93,"run_time":69.0,"percent":24.54,"examples":319,"count_per_example":0.28},{"type":"task","count":359,"time":115.95,"run_time":721.29,"percent":16.08,"examples":445,"count_per_example":0.81},{"type":"validator","count":37,"time":3.6,"run_time":26.65,"percent":13.51,"examples":178,"count_per_example":0.21},{"type":"component","count":71,"time":4.68,"run_time":38.17,"percent":12.27,"examples":305,"count_per_example":0.23},{"type":"feature","count":27713,"time":6503.11,"run_time":54689.28,"percent":11.89,"examples":10598,"count_per_example":2.61},{"type":"initializer","count":53,"time":4.75,"run_time":42.66,"percent":11.13,"examples":226,"count_per_example":0.23},{"type":"spam","count":1,"time":0.01,"run_time":0.22,"percent":4.38,"examples":1,"count_per_example":1.0},{"type":"sidekiq","count":1,"time":0.01,"run_time":0.47,"percent":2.58,"examples":1,"count_per_example":1.0},{"type":"rubocop_rspec","count":9,"time":0.08,"run_time":4.86,"percent":1.75,"examples":48,"count_per_example":0.19},{"type":"rubocop","count":108,"time":1.05,"run_time":86.95,"percent":1.2,"examples":939,"count_per_example":0.12},{"type":"support_spec","count":15,"time":0.14,"run_time":15.29,"percent":0.91,"examples":219,"count_per_example":0.07},{"type":"haml_lint","count":3,"time":0.03,"run_time":3.29,"percent":0.86,"examples":47,"count_per_example":0.06},{"type":"sidekiq_cluster","count":1,"time":0.01,"run_time":1.31,"percent":0.68,"examples":14,"count_per_example":0.07},{"type":"tooling","count":18,"time":0.17,"run_time":31.59,"percent":0.54,"examples":364,"count_per_example":0.05},{"type":"bin","count":2,"time":0.02,"run_time":14.1,"percent":0.12,"examples":43,"count_per_example":0.05},{"type":"metrics_server","count":1,"time":0.01,"run_time":12.37,"percent":0.09,"examples":37,"count_per_example":0.03},{"type":"db","count":10,"time":0.15,"run_time":197.17,"percent":0.08,"examples":3705,"count_per_example":0.0},{"type":"config","count":6,"time":0.05,"run_time":110.27,"percent":0.05,"examples":127,"count_per_example":0.05},{"type":"migration","count":255,"time":4.56,"run_time":11125.95,"percent":0.04,"examples":571,"count_per_example":0.45},{"type":"command","count":1,"time":0.01,"run_time":27.69,"percent":0.04,"examples":8,"count_per_example":0.13},{"type":"script","count":7,"time":0.06,"run_time":241.03,"percent":0.03,"examples":49,"count_per_example":0.14}],
"filter": true
}
How to run locally?
- Download data from https://gitlab.com/gitlab-org/gitlab/-/jobs/3073344776/artifacts/file/rspec/event_prof.json
- Run "Script" below like
FORMAT=json ruby event_prof.rb event_prof.json
Script
require 'json'
data = JSON(File.read("/home/peter/Downloads/event_prof.json"))
def time_percentage(time, total_time)
(time / total_time * 100).round(2)
end
def ff(fl)
fl.round(2)
end
def sum_by(key, set)
time = set.sum { |_, e| e.dig(key, 'time') }
run_time = set.sum { |_, e| e.dig(key, 'run_time') }
count = set.sum { |_, e| e.dig(key, 'count') }
{
time: time,
run_time: run_time,
count: count,
percent: time_percentage(time, run_time)
}
end
by_type = data.group_by { |k, v| v["type"] }
factory_create = by_type.to_h { |type, set| [type, sum_by('factory.create', set)] }
case ENV['FORMAT']
when 'json'
items = factory_create.sort_by { |_, v| -v[:percent] }.map do |type, v|
{
type: type,
count: v[:count],
time: ff(v[:time]),
run_time: ff(v[:run_time]),
percent: v[:percent]
}
end
puts <<~MD
```json:table
{
"fields": [
{"key": "type", "label": "Type", "sortable": true},
{"key": "count", "label": "Factories created", "sortable": true},
{"key": "time", "label": "Factories runtime (s)", "sortable": true},
{"key": "run_time", "label": "Total runtime (s)", "sortable": true},
{"key": "percent", "label": "% spent in factories", "sortable": true}
],
"items": #{JSON.generate(items)},
"filter": true
}
```
MD
else
puts <<~MD
| Type | Factories created | Factories runtime (s) | Total runtime (s) | Percent spent in factories |
| -------- | ---------: | -----------------: | -------------: | -------: |
MD
factory_create.sort_by { |_, v| -v[:percent] }.each do |type, v|
puts <<~MD
| #{type} | #{v[:count]} | #{ff v[:time]} | #{ff v[:run_time]} | #{v[:percent]} |
MD
end
end
Edited by Peter Leitzen