|
1
|
# frozen_string_literal: true
|
|
2
|
|
|
3
|
# Benchmark script for Group#user_added performance.
|
|
4
|
#
|
|
5
|
# Measures SQL query count and elapsed time when adding a user to a group
|
|
6
|
# that has memberships in multiple projects with inheriting subprojects.
|
|
7
|
#
|
|
8
|
# This script has no dependency on its file location.
|
|
9
|
# It can be run from anywhere:
|
|
10
|
#
|
|
11
|
# bin/rails runner /path/to/benchmark_group_user_added.rb
|
|
12
|
#
|
|
13
|
# Configuration:
|
|
14
|
# Adjust NUM_PROJECTS, NUM_ROLES, SUBPROJECT_DEPTH below.
|
|
15
|
|
|
16
|
require 'benchmark'
|
|
17
|
|
|
18
|
NUM_PROJECTS = 50
|
|
19
|
NUM_ROLES = 3
|
|
20
|
SUBPROJECT_DEPTH = 5
|
|
21
|
|
|
22
|
puts "=== Group#user_added benchmark ==="
|
|
23
|
puts "Projects: #{NUM_PROJECTS}, Roles: #{NUM_ROLES}, Subproject depth: #{SUBPROJECT_DEPTH}"
|
|
24
|
puts
|
|
25
|
|
|
26
|
seq = SecureRandom.hex(4)
|
|
27
|
|
|
28
|
# -- Setup --
|
|
29
|
group = Group.create!(:lastname => "BenchGroup_#{seq}")
|
|
30
|
|
|
31
|
user = User.new(
|
|
32
|
:login => "benchuser_#{seq}",
|
|
33
|
:firstname => 'Bench',
|
|
34
|
:lastname => 'User',
|
|
35
|
:mail => "benchuser_#{seq}@example.com"
|
|
36
|
)
|
|
37
|
user.password = 'password'
|
|
38
|
user.save!
|
|
39
|
|
|
40
|
roles = Role.where(:builtin => 0).limit(NUM_ROLES).to_a
|
|
41
|
raise "Need at least #{NUM_ROLES} non-builtin roles" if roles.size < NUM_ROLES
|
|
42
|
|
|
43
|
role_ids = roles.map(&:id)
|
|
44
|
|
|
45
|
projects = NUM_PROJECTS.times.map do |i|
|
|
46
|
root = Project.create!(
|
|
47
|
:name => "bench_#{seq}_#{i}",
|
|
48
|
:identifier => "bench-#{seq}-#{i}",
|
|
49
|
:is_public => false
|
|
50
|
)
|
|
51
|
Member.create!(:principal => group, :project => root, :role_ids => role_ids)
|
|
52
|
|
|
53
|
parent = root
|
|
54
|
SUBPROJECT_DEPTH.times do |d|
|
|
55
|
child = Project.new(
|
|
56
|
:name => "bench_#{seq}_#{i}_d#{d}",
|
|
57
|
:identifier => "bench-#{seq}-#{i}-d#{d}",
|
|
58
|
:is_public => false,
|
|
59
|
:inherit_members => true
|
|
60
|
)
|
|
61
|
child.parent = parent
|
|
62
|
child.save!
|
|
63
|
parent = child
|
|
64
|
end
|
|
65
|
root
|
|
66
|
end
|
|
67
|
|
|
68
|
total_projects = NUM_PROJECTS * (1 + SUBPROJECT_DEPTH)
|
|
69
|
puts "Setup complete: #{NUM_PROJECTS} root projects, each with #{SUBPROJECT_DEPTH} levels of inheriting subprojects"
|
|
70
|
puts "Total projects: #{total_projects}"
|
|
71
|
puts
|
|
72
|
|
|
73
|
# -- Count queries --
|
|
74
|
query_count = 0
|
|
75
|
query_details = Hash.new(0)
|
|
76
|
|
|
77
|
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
|
|
78
|
unless payload[:name] == 'SCHEMA' || payload[:sql].start_with?('PRAGMA')
|
|
79
|
query_count += 1
|
|
80
|
op = payload[:sql].split(' ', 2).first.upcase
|
|
81
|
query_details[op] += 1
|
|
82
|
end
|
|
83
|
end
|
|
84
|
|
|
85
|
elapsed = Benchmark.realtime do
|
|
86
|
group.users << user
|
|
87
|
end
|
|
88
|
|
|
89
|
ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
90
|
|
|
91
|
puts "--- Results ---"
|
|
92
|
puts "Time: #{(elapsed * 1000).round(1)} ms"
|
|
93
|
puts "Queries: #{query_count}"
|
|
94
|
puts
|
|
95
|
puts "Query breakdown:"
|
|
96
|
query_details.sort_by { |_, v| -v }.each do |op, count|
|
|
97
|
puts " #{op}: #{count}"
|
|
98
|
end
|
|
99
|
puts
|
|
100
|
|
|
101
|
# -- Verify correctness --
|
|
102
|
errors = []
|
|
103
|
projects.each do |root|
|
|
104
|
root.reload
|
|
105
|
Project.where("lft >= ? AND rgt <= ?", root.lft, root.rgt).each do |proj|
|
|
106
|
unless user.member_of?(proj)
|
|
107
|
errors << "User NOT a member of project #{proj.id} (#{proj.identifier})"
|
|
108
|
end
|
|
109
|
end
|
|
110
|
end
|
|
111
|
|
|
112
|
if errors.empty?
|
|
113
|
puts "Correctness: OK (user is a member of all #{total_projects} projects)"
|
|
114
|
else
|
|
115
|
puts "Correctness: FAILED"
|
|
116
|
errors.first(10).each { |e| puts " #{e}" }
|
|
117
|
puts " ... (#{errors.size} total)" if errors.size > 10
|
|
118
|
end
|
|
119
|
|
|
120
|
# -- Cleanup --
|
|
121
|
projects.each do |root|
|
|
122
|
root.reload
|
|
123
|
Project.where("lft >= ? AND rgt <= ?", root.lft, root.rgt).order(lft: :desc).each(&:destroy)
|
|
124
|
end
|
|
125
|
group.destroy
|
|
126
|
user.destroy
|
|
127
|
|
|
128
|
puts
|
|
129
|
puts "Cleanup complete."
|