Skip to content

Commit 45d1b6e

Browse files
committed
Use JSON output for bin/benchmark for CI
1 parent 715bbc6 commit 45d1b6e

File tree

2 files changed

+98
-70
lines changed

2 files changed

+98
-70
lines changed

.github/workflows/benchmark.yml

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,97 +24,93 @@ jobs:
2424

2525
- name: Run benchmarks with YJIT
2626
run: |
27-
bundle exec ruby --yjit bin/benchmark | tee benchmark_output.txt
27+
bundle exec ruby --yjit bin/benchmark --json > benchmark_results.json
2828
29-
- name: Parse and format results
29+
- name: Format results as markdown
3030
run: |
31-
cat > parse_benchmark.rb << 'EOF'
31+
cat > format_results.rb << 'EOF'
3232
#!/usr/bin/env ruby
33+
require 'json'
3334
34-
input = File.read('benchmark_output.txt')
35+
data = JSON.parse(File.read('benchmark_results.json'))
3536
36-
def parse_section(text, title)
37-
lines = text.lines.map(&:chomp)
38-
results = []
39-
comparisons = []
40-
in_comparison = false
41-
in_calculating = false
42-
43-
lines.each do |line|
44-
# Start of calculation section
45-
if line =~ /^Calculating/
46-
in_calculating = true
47-
next
48-
end
49-
50-
# Match result lines after "Calculating" section
51-
# Format: " Ruby CSV 463.779 (± 2.4%) i/s (2.16 ms/i) - ..."
52-
if in_calculating && line =~ /^\s*(.+?)\s\s+(\d+\.?\d*)\s+\(.+?\)\s+i\/s/
53-
name = $1.strip
54-
ips = $2
55-
results << { name: name, ips: ips }
56-
elsif line =~ /^Comparison:/
57-
in_comparison = true
58-
in_calculating = false
59-
elsif in_comparison && line.strip =~ /^(.+?):\s+(.+)/
60-
comparisons << "- **#{$1.strip}**: #{$2.strip}"
61-
end
62-
end
63-
64-
return nil if results.empty?
37+
output = "## 🚀 Benchmark Results\n\n"
38+
output += "_Benchmarks run with YJIT enabled on Ruby #{data['ruby_version']}_\n\n"
39+
40+
# Writing benchmark
41+
if data['benchmarks']['writing']
42+
output += "### 📝 CSV Writing Performance\n\n"
43+
output += "| Library | Iterations/sec | Std Dev |\n"
44+
output += "|---------|----------------|----------|\n"
6545
66-
# Build markdown table
67-
table = "### #{title}\n\n"
68-
table += "| Library | Iterations/sec |\n"
69-
table += "|---------|---------------|\n"
46+
writing = data['benchmarks']['writing']
47+
sorted_writing = writing.sort_by { |_, v| -v['ips'] }
7048
71-
results.each do |result|
72-
table += "| #{result[:name]} | #{result[:ips]} |\n"
49+
sorted_writing.each do |name, stats|
50+
output += "| #{name} | #{stats['ips'].round(1)} | ±#{stats['stddev_percentage'].round(1)}% |\n"
7351
end
7452
75-
# Add comparison info
76-
if !comparisons.empty?
77-
table += "\n**Comparison:**\n\n"
78-
table += comparisons.join("\n") + "\n"
53+
# Add comparison
54+
if sorted_writing.length > 1
55+
fastest = sorted_writing.first
56+
output += "\n**Comparison:**\n\n"
57+
sorted_writing.each do |name, stats|
58+
if name == fastest[0]
59+
output += "- **#{name}**: #{stats['ips'].round(1)} i/s (fastest)\n"
60+
else
61+
slowdown = fastest[1]['ips'] / stats['ips']
62+
output += "- **#{name}**: #{stats['ips'].round(1)} i/s - #{slowdown.round(2)}x slower\n"
63+
end
64+
end
7965
end
80-
81-
table
66+
output += "\n"
8267
end
8368
84-
output = "## 🚀 Benchmark Results\n\n"
85-
output += "_Benchmarks run with YJIT enabled on Ruby #{RUBY_VERSION}_\n\n"
86-
87-
# Split by === markers to get sections
88-
sections = input.split(/^=== /)
89-
90-
sections.each do |section|
91-
if section.include?("CSV Writing Benchmark")
92-
result = parse_section(section, "📝 CSV Writing Performance")
93-
output += result + "\n" if result
94-
elsif section.include?("CSV Reading Benchmark")
95-
result = parse_section(section, "📖 CSV Reading Performance")
96-
output += result + "\n" if result
69+
# Reading benchmark
70+
if data['benchmarks']['reading']
71+
output += "### 📖 CSV Reading Performance\n\n"
72+
output += "| Library | Iterations/sec | Std Dev |\n"
73+
output += "|---------|----------------|----------|\n"
74+
75+
reading = data['benchmarks']['reading']
76+
sorted_reading = reading.sort_by { |_, v| -v['ips'] }
77+
78+
sorted_reading.each do |name, stats|
79+
output += "| #{name} | #{stats['ips'].round(1)} | ±#{stats['stddev_percentage'].round(1)}% |\n"
80+
end
81+
82+
# Add comparison
83+
if sorted_reading.length > 1
84+
fastest = sorted_reading.first
85+
output += "\n**Comparison:**\n\n"
86+
sorted_reading.each do |name, stats|
87+
if name == fastest[0]
88+
output += "- **#{name}**: #{stats['ips'].round(1)} i/s (fastest)\n"
89+
else
90+
slowdown = fastest[1]['ips'] / stats['ips']
91+
output += "- **#{name}**: #{stats['ips'].round(1)} i/s - #{slowdown.round(2)}x slower\n"
92+
end
93+
end
9794
end
95+
output += "\n"
9896
end
9997
10098
# Write to GitHub Step Summary
10199
if ENV['GITHUB_STEP_SUMMARY']
102100
File.write(ENV['GITHUB_STEP_SUMMARY'], output)
103101
puts "✅ Results written to GitHub Actions summary"
104-
else
105-
puts "⚠️ GITHUB_STEP_SUMMARY not available, printing to stdout:"
106102
end
107103
108104
# Also print to stdout
109-
puts "\n" + output
105+
puts output
110106
EOF
111107
112-
ruby parse_benchmark.rb
108+
ruby format_results.rb
113109
114-
- name: Upload benchmark output
110+
- name: Upload benchmark results
115111
uses: actions/upload-artifact@v4
116112
if: always()
117113
with:
118114
name: benchmark-results
119-
path: benchmark_output.txt
115+
path: benchmark_results.json
120116
retention-days: 30

bin/benchmark

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ require 'rscsv'
1414
require 'csv'
1515
require 'osv'
1616
require 'stringio'
17+
require 'json'
18+
19+
# Parse command line arguments
20+
json_output = ARGV.include?('--json')
21+
results = { ruby_version: RUBY_VERSION, yjit_enabled: defined?(RubyVM::YJIT), benchmarks: {} }
1722

1823
rows = (0...1000).map do
1924
(0...10).map { SecureRandom.hex }
@@ -23,8 +28,8 @@ csv_string = CSV.generate do |csv|
2328
rows.each { |row| csv << row }
2429
end
2530

26-
puts "\n=== CSV Writing Benchmark ==="
27-
Benchmark.ips do |x|
31+
puts "\n=== CSV Writing Benchmark ===" unless json_output
32+
write_report = Benchmark.ips do |x|
2833
x.report('Ruby CSV') do |times|
2934
times.times do
3035
CSV.generate do |csv|
@@ -37,11 +42,22 @@ Benchmark.ips do |x|
3742
times.times { Rscsv::Writer.generate_lines(rows) }
3843
end
3944

40-
x.compare!
45+
x.compare! unless json_output
4146
end
4247

43-
puts "\n=== CSV Reading Benchmark ==="
44-
Benchmark.ips do |x|
48+
if json_output
49+
write_results = {}
50+
write_report.entries.each do |entry|
51+
write_results[entry.label] = {
52+
ips: entry.stats.central_tendency,
53+
stddev_percentage: entry.stats.error_percentage
54+
}
55+
end
56+
results[:benchmarks][:writing] = write_results
57+
end
58+
59+
puts "\n=== CSV Reading Benchmark ===" unless json_output
60+
read_report = Benchmark.ips do |x|
4561
x.report('Ruby CSV') do |times|
4662
times.times do
4763
CSV.parse(csv_string)
@@ -58,5 +74,21 @@ Benchmark.ips do |x|
5874
end
5975
end
6076

61-
x.compare!
77+
x.compare! unless json_output
78+
end
79+
80+
if json_output
81+
read_results = {}
82+
read_report.entries.each do |entry|
83+
read_results[entry.label] = {
84+
ips: entry.stats.central_tendency,
85+
stddev_percentage: entry.stats.error_percentage
86+
}
87+
end
88+
results[:benchmarks][:reading] = read_results
89+
end
90+
91+
# Output JSON if requested
92+
if json_output
93+
puts JSON.pretty_generate(results)
6294
end

0 commit comments

Comments
 (0)