xmi 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +7 -7
- data/lib/tasks/performance.rake +6 -8
- data/lib/xmi/performance/comparator.rb +81 -0
- data/lib/xmi/performance/helpers.rb +226 -0
- data/lib/xmi/performance/runner.rb +262 -0
- data/lib/xmi/performance.rb +15 -0
- data/lib/xmi/uml/association_class.rb +1 -1
- data/lib/xmi/uml/component.rb +1 -1
- data/lib/xmi/uml/dependency.rb +1 -1
- data/lib/xmi/uml/extension.rb +1 -1
- data/lib/xmi/uml/owned_comment.rb +3 -2
- data/lib/xmi/uml/owned_element.rb +2 -2
- data/lib/xmi/uml/packaged_element.rb +15 -9
- data/lib/xmi/uml/signal.rb +1 -1
- data/lib/xmi/uml/stereotype.rb +1 -1
- data/lib/xmi/uml/uml_class.rb +1 -1
- data/lib/xmi/uml/usage.rb +1 -1
- data/lib/xmi/uml/value_specification.rb +10 -4
- data/lib/xmi/version.rb +1 -1
- data/lib/xmi.rb +1 -0
- metadata +6 -5
- data/lib/tasks/benchmark_runner.rb +0 -274
- data/lib/tasks/performance_comparator.rb +0 -88
- data/lib/tasks/performance_helpers.rb +0 -238
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "open3"
|
|
5
|
-
require "tmpdir"
|
|
6
|
-
require "fileutils"
|
|
7
|
-
|
|
8
|
-
module PerformanceHelpers
|
|
9
|
-
# ANSI color codes for terminal output
|
|
10
|
-
CLEAR = "\e[0m"
|
|
11
|
-
BOLD = "\e[1m"
|
|
12
|
-
DIM = "\e[2m"
|
|
13
|
-
CYAN = "\e[36m"
|
|
14
|
-
GREEN = "\e[32m"
|
|
15
|
-
YELLOW = "\e[33m"
|
|
16
|
-
RED = "\e[31m"
|
|
17
|
-
GRAY = "\e[90m"
|
|
18
|
-
MAGENTA = "\e[35m"
|
|
19
|
-
|
|
20
|
-
# Terminal formatting helpers
|
|
21
|
-
module Term
|
|
22
|
-
extend self
|
|
23
|
-
|
|
24
|
-
HL = "─"
|
|
25
|
-
VL = "│"
|
|
26
|
-
TL = "┌"
|
|
27
|
-
TR = "┐"
|
|
28
|
-
BL = "└"
|
|
29
|
-
BR = "┘"
|
|
30
|
-
|
|
31
|
-
def header(title, color: PerformanceHelpers::CYAN)
|
|
32
|
-
width = 78
|
|
33
|
-
line = HL * width
|
|
34
|
-
puts
|
|
35
|
-
puts "#{color}#{TL}#{line}#{TR}#{CLEAR}"
|
|
36
|
-
puts "#{color}#{VL}#{CLEAR} #{BOLD}#{color}#{title}#{CLEAR}#{' ' * (width - title.length - 4)}#{color}#{VL}#{CLEAR}"
|
|
37
|
-
puts "#{color}#{BL}#{line}#{BR}#{CLEAR}"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def sep(char: HL, width: 78)
|
|
41
|
-
puts "#{DIM}#{char * width}#{CLEAR}"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
module Base
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
module Current
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
class << self
|
|
52
|
-
def load_into_namespace(module_obj, file_path)
|
|
53
|
-
content = File.read(file_path)
|
|
54
|
-
module_obj.module_eval(content, file_path)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def ruby_exec(cmd, env: {})
|
|
58
|
-
Open3.capture3(env, cmd)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def current_branch
|
|
62
|
-
stdout, = ruby_exec("git rev-parse --abbrev-ref HEAD")
|
|
63
|
-
stdout.strip
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Clone base branch into a temp dir and return its path
|
|
67
|
-
def clone_base_repo(base, performance_dir, script)
|
|
68
|
-
puts "#{DIM}Cloning base #{base}...#{CLEAR}"
|
|
69
|
-
safe_ref = base.gsub(/[^0-9A-Za-z._-]/, "-")
|
|
70
|
-
clone_dir = File.join(performance_dir, "base-#{safe_ref}")
|
|
71
|
-
FileUtils.rm_rf(clone_dir)
|
|
72
|
-
|
|
73
|
-
repo_url, = ruby_exec("git config --get remote.origin.url")
|
|
74
|
-
repo_url = repo_url.strip
|
|
75
|
-
|
|
76
|
-
stdout, stderr, status = ruby_exec("git clone --branch #{safe_ref} --single-branch #{repo_url} #{clone_dir}")
|
|
77
|
-
raise "git clone failed: #{stderr}\n#{stdout}" unless status.success?
|
|
78
|
-
|
|
79
|
-
Dir.chdir(clone_dir) do
|
|
80
|
-
stdout, stderr, status = ruby_exec("bundle install --quiet")
|
|
81
|
-
raise "bundle install failed: #{stderr}\n#{stdout}" unless status.success?
|
|
82
|
-
|
|
83
|
-
bench_copy_dir = File.join(clone_dir, "lib", "tasks")
|
|
84
|
-
FileUtils.mkdir_p(bench_copy_dir)
|
|
85
|
-
bench_copy = File.join(bench_copy_dir, "benchmark_runner.rb")
|
|
86
|
-
File.write(bench_copy, File.read(script))
|
|
87
|
-
load_into_namespace(Base, bench_copy)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def run_benchmarks(base_runner, current_runner, threshold, all_base,
|
|
92
|
-
all_current)
|
|
93
|
-
base_results = base_runner.run_benchmarks
|
|
94
|
-
curr_results = current_runner.run_benchmarks
|
|
95
|
-
|
|
96
|
-
all_base.merge!(base_results)
|
|
97
|
-
all_current.merge!(curr_results)
|
|
98
|
-
|
|
99
|
-
# Collect comparison results
|
|
100
|
-
comparison_rows = []
|
|
101
|
-
|
|
102
|
-
curr_results.each do |label, result|
|
|
103
|
-
base_result = base_results[label]
|
|
104
|
-
cmp = compare_metrics(label, result, base_result, threshold)
|
|
105
|
-
comparison_rows << cmp
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
print_comparison_table(comparison_rows, threshold)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def print_comparison_table(comparison_rows, threshold)
|
|
112
|
-
rows = comparison_rows.map do |cmp|
|
|
113
|
-
{
|
|
114
|
-
benchmark: cmp[:label],
|
|
115
|
-
base_ips: cmp[:base_ips]&.round(1),
|
|
116
|
-
curr_ips: cmp[:curr_ips]&.round(1),
|
|
117
|
-
change: cmp[:change] ? "#{(cmp[:change] * 100).round(1)}%" : "N/A",
|
|
118
|
-
status: if cmp[:base_ips].nil?
|
|
119
|
-
"NEW"
|
|
120
|
-
elsif cmp[:change] < -threshold
|
|
121
|
-
"REGRESSED"
|
|
122
|
-
else
|
|
123
|
-
"OK"
|
|
124
|
-
end,
|
|
125
|
-
}
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
return if rows.empty?
|
|
129
|
-
|
|
130
|
-
puts " #{'Benchmark'.ljust(40)} #{'Base IPS'.rjust(12)} #{'Curr IPS'.rjust(12)} #{'Change'.rjust(10)} #{'Status'.rjust(10)}"
|
|
131
|
-
puts " #{DIM}#{'─' * 86}#{CLEAR}"
|
|
132
|
-
|
|
133
|
-
rows.each do |row|
|
|
134
|
-
status_color = case row[:status]
|
|
135
|
-
when "REGRESSED" then RED
|
|
136
|
-
when "NEW" then YELLOW
|
|
137
|
-
else GREEN
|
|
138
|
-
end
|
|
139
|
-
row[:status] == "REGRESSED" ? RED : DIM
|
|
140
|
-
|
|
141
|
-
puts " #{row[:benchmark].ljust(40)} #{format('%-12.1f',
|
|
142
|
-
row[:base_ips] || 0)} #{format('%-12.1f',
|
|
143
|
-
row[:curr_ips] || 0)} #{format('%-10s', row[:change]).gsub('%',
|
|
144
|
-
'%%')} #{status_color}#{row[:status].rjust(10)}#{CLEAR}"
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
puts
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def compare_metrics(label, curr, base, threshold)
|
|
151
|
-
unless base
|
|
152
|
-
return { label: label, base_ips: nil, curr_ips: nil, change: nil,
|
|
153
|
-
regressed: false }
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
base_ips = base.fetch(:lower)
|
|
157
|
-
curr_ips = curr.fetch(:upper)
|
|
158
|
-
change = (curr_ips - base_ips) / base_ips.to_f
|
|
159
|
-
|
|
160
|
-
{
|
|
161
|
-
label: label,
|
|
162
|
-
base_ips: base_ips,
|
|
163
|
-
curr_ips: curr_ips,
|
|
164
|
-
change: change,
|
|
165
|
-
regressed: change < -threshold,
|
|
166
|
-
}
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def summary_report(current_results, base_results, base, run_time, threshold)
|
|
170
|
-
summary = {
|
|
171
|
-
run_time: run_time,
|
|
172
|
-
threshold: threshold,
|
|
173
|
-
branch: current_branch,
|
|
174
|
-
base: base,
|
|
175
|
-
regressions: [],
|
|
176
|
-
new_benchmarks: [],
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
current_results.each do |label, metrics|
|
|
180
|
-
base_result = base_results[label]
|
|
181
|
-
cmp = compare_metrics(label, metrics, base_result, threshold)
|
|
182
|
-
|
|
183
|
-
# Track new benchmarks that don't exist in base
|
|
184
|
-
if base_result.nil?
|
|
185
|
-
summary[:new_benchmarks] << label
|
|
186
|
-
next
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
next unless cmp[:regressed]
|
|
190
|
-
|
|
191
|
-
summary[:regressions] << {
|
|
192
|
-
label: label,
|
|
193
|
-
base_ips: cmp[:base_ips],
|
|
194
|
-
curr_ips: cmp[:curr_ips],
|
|
195
|
-
delta_fraction: cmp[:change],
|
|
196
|
-
}
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
log_regressions(summary[:regressions], threshold)
|
|
200
|
-
log_new_benchmarks(summary[:new_benchmarks])
|
|
201
|
-
summary
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def log_new_benchmarks(new_benchmarks)
|
|
205
|
-
return if new_benchmarks.empty?
|
|
206
|
-
|
|
207
|
-
puts
|
|
208
|
-
puts "#{YELLOW}🆕 New benchmarks (not in base branch):#{CLEAR}"
|
|
209
|
-
new_benchmarks.each do |label|
|
|
210
|
-
puts " • #{label}"
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def log_regressions(regressions, threshold)
|
|
215
|
-
return if regressions.empty?
|
|
216
|
-
|
|
217
|
-
puts
|
|
218
|
-
puts "#{RED}⚠️ Performance Regressions Detected#{CLEAR}"
|
|
219
|
-
puts "#{RED} (< -#{(threshold * 100).round(2)}% IPS)#{CLEAR}"
|
|
220
|
-
puts
|
|
221
|
-
regressions.each do |regression|
|
|
222
|
-
delta = regression[:delta_fraction]
|
|
223
|
-
base_ips = regression[:base_ips]
|
|
224
|
-
curr_ips = regression[:curr_ips]
|
|
225
|
-
|
|
226
|
-
delta_str = delta ? format("%+0.2f%%", delta * 100) : "N/A"
|
|
227
|
-
base_str = base_ips ? format("%.2f", base_ips) : "N/A"
|
|
228
|
-
curr_str = curr_ips ? format("%.2f", curr_ips) : "N/A"
|
|
229
|
-
|
|
230
|
-
puts " #{BOLD}#{regression[:label]}#{CLEAR}"
|
|
231
|
-
puts " #{GRAY}base: #{base_str} IPS#{CLEAR}"
|
|
232
|
-
puts " #{RED}curr: #{curr_str} IPS#{CLEAR}"
|
|
233
|
-
puts " #{RED}change: #{delta_str}#{CLEAR}"
|
|
234
|
-
puts
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|