From 139988435f296fd5a4bef09a82898e15d66dde24 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 00:45:00 +0300 Subject: [PATCH 1/6] Make overview page display 1 benchmark data per week --- .gitignore | 1 + app/models/repo.rb | 91 ++++++++++++++++++++++++++------ test/models/repo_test.rb | 109 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 181 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 959f01ca..2e1dfacf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ vendor/bundle *.swp *.swo +.ruby-version diff --git a/app/models/repo.rb b/app/models/repo.rb index 666b68a5..a76bcded 100644 --- a/app/models/repo.rb +++ b/app/models/repo.rb @@ -17,23 +17,80 @@ def generate_sparkline_data charts = {} - self.benchmark_types.map do |benchmark_type| - benchmark_type.benchmark_result_types.each do |benchmark_result_type| - benchmark_runs = BenchmarkRun.select(:initiator_id, :result, :initiator_type).fetch_commit_benchmark_runs( - benchmark_type.category, - benchmark_result_type, - 2000 - ) - - runs = benchmark_runs.sort_by { |run| run.initiator.created_at } - chart_builder = ChartBuilder.new(runs, benchmark_result_type).build_columns - - charts[benchmark_type.category] ||= [] - charts[benchmark_type.category] << { - benchmark_result_type: benchmark_result_type.name, - columns: chart_builder.columns - } - end + query = <<~SQL + WITH min_max_dates AS ( + SELECT MIN(date_trunc('week', created_at)) AS start_week, + MAX(date_trunc('week', created_at)) AS end_week + FROM ( + SELECT created_at + FROM commits + WHERE repo_id = #{self.id} + ) AS subq + ), + weeks AS ( + SELECT generate_series(start_week, end_week, '7 days') AS weekstart + FROM min_max_dates + ) + SELECT + subquery.* + FROM ( + SELECT + benchmark_result_type_id, + benchmark_type_id, + array_to_json(array_agg(br.id)) AS ids + FROM ( + SELECT id FROM ( + SELECT + ROW_NUMBER() OVER(PARTITION BY w.weekstart ORDER BY c.created_at) AS row_num, + id + FROM weeks w + INNER JOIN commits c + ON w.weekstart = date_trunc('week', c.created_at) AND c.repo_id = #{self.id} + ) x + WHERE row_num = 1 + ) cw + INNER JOIN benchmark_runs br + ON cw.id = br.initiator_id AND br.initiator_type = 'Commit' + INNER JOIN benchmark_types bt + ON bt.id = br.benchmark_type_id AND bt.repo_id = #{self.id} + GROUP BY benchmark_type_id, benchmark_result_type_id + ) AS subquery + INNER JOIN benchmark_types + ON benchmark_types.id = subquery.benchmark_type_id + INNER JOIN benchmark_result_types + ON benchmark_result_types.id = subquery.benchmark_result_type_id + ORDER BY category, name + SQL + + results = self.class.connection.execute(query).to_a + results.each do |row| + row['ids'] = JSON.parse(row['ids']) + end + + types = self.benchmark_types + .where(id: results.map { |row| row['benchmark_type_id'] }.uniq) + .map { |type| [type.id, type] }.to_h + result_types = BenchmarkResultType + .where(id: results.map { |row| row['benchmark_result_type_id'] }.uniq) + .map { |res_type| [res_type.id, res_type] }.to_h + all_runs = BenchmarkRun + .select(:id, :initiator_id, :result, :initiator_type, 'c.created_at') + .joins("JOIN commits c ON c.id = benchmark_runs.initiator_id AND benchmark_runs.initiator_type = 'Commit'") + .where(id: results.map { |row| row['ids'] }.flatten.uniq) + .map { |run| [run.id, run] }.to_h + + results.each do |res| + type = types[res['benchmark_type_id']] + result_type = result_types[res['benchmark_result_type_id']] + runs = all_runs.values_at(*res['ids']).sort_by(&:created_at) + next if !type || !result_type || runs.size == 0 + + chart_builder = ChartBuilder.new(runs, result_type).build_columns + charts[type.category] ||= [] + charts[type.category] << { + benchmark_result_type: result_type.name, + columns: chart_builder.columns + } end $redis.setex("sparklines:#{self.id}", 1800, charts.to_msgpack) diff --git a/test/models/repo_test.rb b/test/models/repo_test.rb index 27e9a5de..2168293e 100644 --- a/test/models/repo_test.rb +++ b/test/models/repo_test.rb @@ -1,7 +1,110 @@ require 'test_helper' class RepoTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test '#generate_sparkline_data picks first commit from every week' do + repo = create(:repo) + + mem_res_type = create(:benchmark_result_type, name: 'Memory', unit: 'rss') + ips_res_type = create(:benchmark_result_type, name: 'Ips', unit: 'i/s') + + type1 = create(:benchmark_type, repo: repo, category: 'Array map') + type2 = create(:benchmark_type, repo: repo, category: 'String to_i') + + # Week of Mon 18-05-2020 to Sun 24-05-2020 + c1 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 24, 1)) + c2 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 24, 1, 1)) + c3 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 24, 1, 1, 1)) + + # Week of Mon 25-05-2020 to Sun 31-05-2020 + c4 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 26)) + c5 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 29)) + c6 = create(:commit, repo: repo, created_at: Time.utc(2020, 5, 30)) + + # Week of Mon 01-06-2020 to Sun 07-06-2020 + c7 = create(:commit, repo: repo, created_at: Time.utc(2020, 6, 1)) + c8 = create(:commit, repo: repo, created_at: Time.utc(2020, 6, 2)) + c9 = create(:commit, repo: repo, created_at: Time.utc(2020, 6, 7)) + + commits = [c1, c2, c3, c4, c5, c6, c7, c8, c9] + commits.each_with_index do |commit, index| + [type1, type2].each do |type| + create( + :benchmark_run, + initiator_id: commit.id, + initiator_type: 'Commit', + result: { rss_kb: commit.id }, + benchmark_result_type_id: mem_res_type.id, + benchmark_type_id: type.id + ) + create( + :benchmark_run, + initiator_id: commit.id, + initiator_type: 'Commit', + result: { bench_1: commit.id, bench_2: commit.id + 1 }, + benchmark_result_type_id: ips_res_type.id, + benchmark_type_id: type.id + ) + end + end + + # assert_equal([0.0, 3.0, 7.0], [index_of_c1, index_of_c4, index_of_c7]) + # We should pick the first commit from each week. + # Since the commits are spread over a period of 3 + # weeks, we should have 3 commits. These commits + # should be c1, c4 and c7. + # We will then pick the benchmark_runs records whose + # initiator_ids are the commits we picked up earlier. + data = repo.generate_sparkline_data + assert_equal( + data, + 'Array map' => [ + { + benchmark_result_type: 'Ips', + columns: [ + { + name: 'bench_1', + data: [c1.id, c4.id, c7.id] + }, + { + name: 'bench_2', + data: [c1.id + 1, c4.id + 1, c7.id + 1] + } + ], + }, + { + benchmark_result_type: 'Memory', + columns: [ + { + name: 'rss_kb', + data: [c1.id, c4.id, c7.id] + } + ] + } + ], + 'String to_i' => [ + { + benchmark_result_type: 'Ips', + columns: [ + { + name: 'bench_1', + data: [c1.id, c4.id, c7.id] + }, + { + name: 'bench_2', + data: [c1.id + 1, c4.id + 1, c7.id + 1] + } + ], + }, + { + benchmark_result_type: 'Memory', + columns: [ + { + name: 'rss_kb', + data: [c1.id, c4.id, c7.id] + } + ] + } + ] + ) + end end From 4d499478a9a717212dc5132fc902cd50328e9091 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 00:48:04 +0300 Subject: [PATCH 2/6] Remove commented line --- test/models/repo_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/models/repo_test.rb b/test/models/repo_test.rb index 2168293e..57cdccae 100644 --- a/test/models/repo_test.rb +++ b/test/models/repo_test.rb @@ -47,7 +47,6 @@ class RepoTest < ActiveSupport::TestCase end end - # assert_equal([0.0, 3.0, 7.0], [index_of_c1, index_of_c4, index_of_c7]) # We should pick the first commit from each week. # Since the commits are spread over a period of 3 # weeks, we should have 3 commits. These commits From b954768039d1e925c7fc191be40c9c552fe0e326 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 00:51:01 +0300 Subject: [PATCH 3/6] Fix indentation --- test/models/repo_test.rb | 96 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/test/models/repo_test.rb b/test/models/repo_test.rb index 57cdccae..b6aafb13 100644 --- a/test/models/repo_test.rb +++ b/test/models/repo_test.rb @@ -56,54 +56,54 @@ class RepoTest < ActiveSupport::TestCase data = repo.generate_sparkline_data assert_equal( data, - 'Array map' => [ - { - benchmark_result_type: 'Ips', - columns: [ - { - name: 'bench_1', - data: [c1.id, c4.id, c7.id] - }, - { - name: 'bench_2', - data: [c1.id + 1, c4.id + 1, c7.id + 1] - } - ], - }, - { - benchmark_result_type: 'Memory', - columns: [ - { - name: 'rss_kb', - data: [c1.id, c4.id, c7.id] - } - ] - } - ], - 'String to_i' => [ - { - benchmark_result_type: 'Ips', - columns: [ - { - name: 'bench_1', - data: [c1.id, c4.id, c7.id] - }, - { - name: 'bench_2', - data: [c1.id + 1, c4.id + 1, c7.id + 1] - } - ], - }, - { - benchmark_result_type: 'Memory', - columns: [ - { - name: 'rss_kb', - data: [c1.id, c4.id, c7.id] - } - ] - } - ] + 'Array map' => [ + { + benchmark_result_type: 'Ips', + columns: [ + { + name: 'bench_1', + data: [c1.id, c4.id, c7.id] + }, + { + name: 'bench_2', + data: [c1.id + 1, c4.id + 1, c7.id + 1] + } + ], + }, + { + benchmark_result_type: 'Memory', + columns: [ + { + name: 'rss_kb', + data: [c1.id, c4.id, c7.id] + } + ] + } + ], + 'String to_i' => [ + { + benchmark_result_type: 'Ips', + columns: [ + { + name: 'bench_1', + data: [c1.id, c4.id, c7.id] + }, + { + name: 'bench_2', + data: [c1.id + 1, c4.id + 1, c7.id + 1] + } + ], + }, + { + benchmark_result_type: 'Memory', + columns: [ + { + name: 'rss_kb', + data: [c1.id, c4.id, c7.id] + } + ] + } + ] ) end end From d13a5d05d831b0670fe130bd4d3ae0f9337e2f8b Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 11:34:29 +0300 Subject: [PATCH 4/6] Imrove performance --- app/models/repo.rb | 96 ++++++++++++++++------------------- app/services/chart_builder.rb | 25 +++++++++ 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/app/models/repo.rb b/app/models/repo.rb index a76bcded..0993e8eb 100644 --- a/app/models/repo.rb +++ b/app/models/repo.rb @@ -32,65 +32,59 @@ def generate_sparkline_data FROM min_max_dates ) SELECT - subquery.* + benchmark_result_type_id, + benchmark_type_id, + hstore_to_json(br.result) AS result, + category FROM ( - SELECT - benchmark_result_type_id, - benchmark_type_id, - array_to_json(array_agg(br.id)) AS ids - FROM ( - SELECT id FROM ( - SELECT - ROW_NUMBER() OVER(PARTITION BY w.weekstart ORDER BY c.created_at) AS row_num, - id - FROM weeks w - INNER JOIN commits c - ON w.weekstart = date_trunc('week', c.created_at) AND c.repo_id = #{self.id} - ) x - WHERE row_num = 1 - ) cw - INNER JOIN benchmark_runs br - ON cw.id = br.initiator_id AND br.initiator_type = 'Commit' - INNER JOIN benchmark_types bt - ON bt.id = br.benchmark_type_id AND bt.repo_id = #{self.id} - GROUP BY benchmark_type_id, benchmark_result_type_id - ) AS subquery - INNER JOIN benchmark_types - ON benchmark_types.id = subquery.benchmark_type_id - INNER JOIN benchmark_result_types - ON benchmark_result_types.id = subquery.benchmark_result_type_id - ORDER BY category, name + SELECT id, commit_date FROM ( + SELECT + ROW_NUMBER() OVER(PARTITION BY w.weekstart ORDER BY c.created_at) AS row_num, + id, + created_at AS commit_date + FROM weeks w + INNER JOIN commits c + ON w.weekstart = date_trunc('week', c.created_at) AND c.repo_id = #{self.id} + ) x + WHERE row_num = 1 + ) cw + INNER JOIN benchmark_runs br + ON cw.id = br.initiator_id AND br.initiator_type = 'Commit' + INNER JOIN benchmark_types bt + ON bt.id = br.benchmark_type_id AND bt.repo_id = #{self.id} + ORDER BY category, commit_date SQL - results = self.class.connection.execute(query).to_a - results.each do |row| - row['ids'] = JSON.parse(row['ids']) + raw_results = self.class.connection.execute(query).to_a + raw_results.each do |row| + row['result'] = JSON.parse(row['result']) end - types = self.benchmark_types - .where(id: results.map { |row| row['benchmark_type_id'] }.uniq) - .map { |type| [type.id, type] }.to_h result_types = BenchmarkResultType - .where(id: results.map { |row| row['benchmark_result_type_id'] }.uniq) + .where(id: raw_results.map { |row| row['benchmark_result_type_id'] }.uniq) .map { |res_type| [res_type.id, res_type] }.to_h - all_runs = BenchmarkRun - .select(:id, :initiator_id, :result, :initiator_type, 'c.created_at') - .joins("JOIN commits c ON c.id = benchmark_runs.initiator_id AND benchmark_runs.initiator_type = 'Commit'") - .where(id: results.map { |row| row['ids'] }.flatten.uniq) - .map { |run| [run.id, run] }.to_h - - results.each do |res| - type = types[res['benchmark_type_id']] - result_type = result_types[res['benchmark_result_type_id']] - runs = all_runs.values_at(*res['ids']).sort_by(&:created_at) - next if !type || !result_type || runs.size == 0 - chart_builder = ChartBuilder.new(runs, result_type).build_columns - charts[type.category] ||= [] - charts[type.category] << { - benchmark_result_type: result_type.name, - columns: chart_builder.columns - } + results = {} + raw_results.each do |res| + results[res['benchmark_type_id']] ||= {} + results[res['benchmark_type_id']]['category'] = res['category'] + hash = results[res['benchmark_type_id']]['res_types'] ||= {} + arr = hash[result_types[res['benchmark_result_type_id']].name] ||= [] + arr << res['result'] + hash.sort_by { |k| k } + results[res['benchmark_type_id']]['res_types'] = hash.sort_by { |k, v| k }.to_h + end + results.values.each do |res| + category = res['category'] + next unless category + res['res_types'].each do |name, runs| + chart_builder = ChartBuilder.new(runs, nil).build_columns_hash + charts[category] ||= [] + charts[category] << { + benchmark_result_type: name, + columns: chart_builder.columns + } + end end $redis.setex("sparklines:#{self.id}", 1800, charts.to_msgpack) diff --git a/app/services/chart_builder.rb b/app/services/chart_builder.rb index a341b600..46a59896 100644 --- a/app/services/chart_builder.rb +++ b/app/services/chart_builder.rb @@ -60,4 +60,29 @@ def build_columns @columns = new_columns self end + + def build_columns_hash + keys = @benchmark_runs.map { |run| run.keys }.flatten.uniq + @benchmark_runs.each do |benchmark_run| + if block_given? + version = yield(benchmark_run) + @categories ||= [] + @categories << version + end + + keys.each do |key| + @columns[key] ||= [] + @columns[key] << benchmark_run[key]&.to_f + end + end + + new_columns = [] + + @columns.each do |name, data| + new_columns << { name: name, data: data } + end + + @columns = new_columns + self + end end From 139e282472a9aff403e3224cfc7295f2c09c17e2 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 12:46:42 +0300 Subject: [PATCH 5/6] Add overview link to orgs page --- app/views/organizations/index.html.haml | 15 +++++++++------ config/locales/en.yml | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/views/organizations/index.html.haml b/app/views/organizations/index.html.haml index 1de20bab..19cbbc0f 100644 --- a/app/views/organizations/index.html.haml +++ b/app/views/organizations/index.html.haml @@ -15,12 +15,15 @@ %td - organization.repos.each do |repo| %ul.list-inline - %li - - if !repo.releases.empty? - = link_to "#{repo.title} #{t('.releases_benchmark')}", + - if !repo.releases.empty? + %li + = link_to t('.releases_benchmark'), releases_path(organization_name: organization.name, repo_name: repo.name) - %li - - if !repo.commits.empty? - = link_to "#{repo.title} #{t('.commits_benchmark')}", + - if !repo.commits.empty? + %li + = link_to t('.commits_benchmark'), commits_path(organization_name: organization.name, repo_name: repo.name), data: { no_turbolink: true } + %li + = link_to t('.overview'), + overview_path(organization_name: organization.name, repo_name: repo.name), data: { no_turbolink: true } diff --git a/config/locales/en.yml b/config/locales/en.yml index 47345831..3d2d2b33 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -44,6 +44,7 @@ en: title: Benchmarks commits_benchmark: Commits Benchmarks releases_benchmark: Releases Benchmarks + overview: Overview repos: select_benchmark: &select_benchmark From af703ab411104618d3a6b2d69c147e8a5f149155 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Mon, 8 Jun 2020 17:15:21 +0300 Subject: [PATCH 6/6] Fix travis --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dd53fd3b..93d6ba22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_install: tar -xvf $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" + - gem install bundler:1.17.3 script: - bundle exec rubocop - bundle exec rake test @@ -41,5 +41,14 @@ after_success: fi addons: postgresql: "9.3" + apt: + packages: + - postgresql-9.3 + - postgresql-client-9.3 + - postgresql-contrib-9.3 services: - redis-server + - xvfb +env: + global: + - PGPORT=5433