Project

General

Profile

Defect #43838 » bench_mentions.rb

Marius BĂLTEANU, 2026-02-21 18:19

 
1
require 'benchmark'
2

    
3
users_count = (ENV['USERS_COUNT'] || '10000').to_i
4
iterations = (ENV['ITER'] || '10').to_i
5
mentions_count = (ENV['MENTIONS_COUNT'] || '100').to_i
6

    
7
Setting.text_formatting = 'common_mark'
8
Setting.cache_formatted_text = 0
9

    
10
puts "ruby=#{RUBY_VERSION} db=#{ActiveRecord::Base.connection.adapter_name} users=#{users_count} mentions_per_text=#{mentions_count} iter=#{iterations}"
11

    
12
class SimpleHelper
13
  include ApplicationHelper
14
  include ActionView::Helpers::TagHelper
15
  include ActionView::Helpers::AssetTagHelper
16
  include ActionView::Helpers::UrlHelper
17
  include Rails.application.routes.url_helpers
18

    
19
  def controller
20
    nil
21
  end
22

    
23
  def current_menu_item
24
    nil
25
  end
26
end
27

    
28
helper = SimpleHelper.new
29

    
30
ActiveRecord::Base.transaction do
31
  puts "Inserting users..."
32
  total_inserted = 0
33

    
34
  users_count.times.each_slice(10000) do |slice|
35
    users_attrs = slice.map do |i|
36
      {
37
        login: "bench_user_#{i}",
38
        firstname: "Bench",
39
        lastname: "User #{i}",
40
        status: Principal::STATUS_ACTIVE,
41
        type: 'User',
42
        language: 'en',
43
        created_on: Time.now,
44
        updated_on: Time.now
45
      }
46
    end
47
    User.insert_all(users_attrs)
48
    total_inserted += users_attrs.size
49
    print "."
50
  end
51
  puts "\nUsers inserted: #{total_inserted}"
52

    
53
  sample_users = User.where("login LIKE 'bench_user_%'").limit(mentions_count).to_a
54

    
55
  if sample_users.length < mentions_count
56
    puts "Warning: Only #{sample_users.length} users generated"
57
  end
58

    
59
  project = Project.find(1)
60
  role = Role.givable.first
61

    
62
  puts "Assigning sample users to project..."
63
  # Only add the sampled users to the project for speed
64
  member_attrs = sample_users.map do |u|
65
    {
66
      user_id: u.id,
67
      project_id: project.id,
68
      created_on: Time.now
69
    }
70
  end
71

    
72
  Member.insert_all(member_attrs) if member_attrs.any?
73

    
74
  # Also need member_roles
75
  members = Member.where(user_id: sample_users.map(&:id)).pluck(:id)
76
  member_role_attrs = members.map do |member_id|
77
    {
78
      member_id: member_id,
79
      role_id: role.id
80
    }
81
  end
82

    
83
  MemberRole.insert_all(member_role_attrs) if member_role_attrs.any?
84

    
85
  text_login = sample_users.map { |u| "@#{u.login}" }.join(" ")
86
  text_user_link = sample_users.map { |u| "user##{u.id}" }.join(" ")
87

    
88
  # Worst case: mentions that look valid but don't exist in the DB,
89
  # forcing a sequential scan across all rows
90
  text_invalid_login = (1..mentions_count).map { |i| "@missing_user_#{i}" }.join(" ")
91

    
92
  prefix = "Here are some mentions: "
93
  text_login = prefix + text_login
94
  text_user_link = prefix + text_user_link
95
  text_invalid_login = prefix + text_invalid_login
96

    
97
  # Warm up
98
  helper.textilizable(text_login, project: project)
99
  helper.textilizable(text_user_link, project: project)
100
  helper.textilizable(text_invalid_login, project: project)
101

    
102
  puts "\nBenchmarking @login..."
103
  bm_login = Benchmark.realtime do
104
    ApplicationRecord.uncached do
105
      iterations.times { helper.textilizable(text_login, project: project) }
106
    end
107
  end
108
  puts "login_ms=#{(bm_login*1000).round(2)} avg_login=#{(bm_login*1000/iterations).round(2)}"
109

    
110
  puts "\nBenchmarking user#id..."
111
  bm_user_link = Benchmark.realtime do
112
    ApplicationRecord.uncached do
113
      iterations.times { helper.textilizable(text_user_link, project: project) }
114
    end
115
  end
116
  puts "user_link_ms=#{(bm_user_link*1000).round(2)} avg_user_link=#{(bm_user_link*1000/iterations).round(2)}"
117

    
118
  puts "\nBenchmarking @login (worst-case non-existent, forces full scan)..."
119
  bm_invalid_login = Benchmark.realtime do
120
    ApplicationRecord.uncached do
121
      iterations.times { helper.textilizable(text_invalid_login, project: project) }
122
    end
123
  end
124
  puts "invalid_login_ms=#{(bm_invalid_login*1000).round(2)} avg_invalid_login=#{(bm_invalid_login*1000/iterations).round(2)}"
125

    
126
  raise ActiveRecord::Rollback
127
end
(1-1/2)