33
44require 'json'
55require 'fileutils'
6- require 'digest'
76require 'benchmark'
87require 'optparse'
98
1413
1514options = { write_report : WRITE_REPORT_DEFAULT }
1615OptionParser . new do |opts |
17- opts . banner = 'Usage: ruby run_benchmarks.rb BENCHMARK_DIR [options]'
16+ opts . banner = 'Usage: ruby run_benchmarks.rb GLOB [options]'
1817 opts . on ( '--write-report=DEST' , 'console or path to .json/.svg report' ) do |dest |
1918 options [ :write_report ] = dest
2019 end
2120end . parse!
21+ pattern = ARGV . shift || abort ( 'Usage: ruby run_benchmarks.rb GLOB [options]' )
2222
23- benchmark_dir = ARGV . shift || abort ( 'Usage: ruby run_benchmarks.rb BENCHMARK_DIR [options]' )
24- unless Dir . exist? ( benchmark_dir )
25- abort ( "Benchmark directory not found: #{ benchmark_dir } " )
26- end
27-
28- # Collect benchmark names (file basenames without extension)
29- benchmarks = Dir . glob ( File . join ( benchmark_dir , '*.rb' ) ) . map { |f | File . basename ( f , '.rb' ) }
23+ # Collect benchmark names and match against the provided glob
24+ all_programs = Dir . glob ( File . join ( PROGRAMS_DIR , '*.rb' ) ) . map { |f | File . basename ( f , '.rb' ) }
25+ benchmarks = all_programs . select { |name | File . fnmatch? ( pattern , name ) }
3026if benchmarks . empty?
31- abort ( "No benchmark files (*.rb) found in directory : #{ benchmark_dir } " )
27+ abort ( "No benchmarks match pattern : #{ pattern } " )
3228end
3329
34- # Compare two files for identical content
35- def files_identical? ( a , b )
36- cmp_result = system ( 'cmp' , '-s' , a , b )
37- return $?. success? if !cmp_result . nil?
38- File . binread ( a ) == File . binread ( b )
30+ # Compare two trace files structurally
31+ def traces_equal? ( a , b )
32+ JSON . parse ( File . read ( a ) ) == JSON . parse ( File . read ( b ) )
3933end
4034
4135# Run a single benchmark by name
42- def run_benchmark ( name , benchmark_dir )
43- program = File . expand_path ( File . join ( benchmark_dir , "#{ name } .rb" ) )
44- fixture = File . join ( FIXTURES_DIR , "#{ name } _trace.json" )
45- output_dir = File . join ( TMP_DIR , name )
46-
47- FileUtils . mkdir_p ( output_dir )
36+ def run_benchmark ( name )
37+ program = File . join ( PROGRAMS_DIR , "#{ name } .rb" )
38+ fixture = File . join ( FIXTURES_DIR , "#{ name } _trace.json" )
4839 raise 'Reference trace unavailable' unless File . exist? ( fixture )
4940
41+ base_dir = File . join ( TMP_DIR , name )
42+ FileUtils . rm_rf ( base_dir )
43+
44+ results = { name : name }
45+
46+ elapsed = Benchmark . realtime do
47+ system ( 'ruby' , program )
48+ raise 'Program failed' unless $?. success?
49+ end
50+ results [ :ruby_ms ] = ( elapsed * 1000 ) . round
51+
52+ native_dir = File . join ( TMP_DIR , name , 'native' )
53+ FileUtils . mkdir_p ( native_dir )
54+ elapsed = Benchmark . realtime do
55+ system ( 'ruby' , File . expand_path ( '../../gems/native-tracer/lib/native_trace.rb' , __dir__ ) ,
56+ '--out-dir' , native_dir , program )
57+ raise 'Native trace failed' unless $?. success?
58+ end
59+ results [ :native_ms ] = ( elapsed * 1000 ) . round
60+ native_trace = File . join ( native_dir , 'trace.json' )
61+ results [ :native_ok ] = traces_equal? ( fixture , native_trace )
62+
63+ pure_dir = File . join ( TMP_DIR , name , 'pure' )
64+ FileUtils . mkdir_p ( pure_dir )
5065 elapsed = Benchmark . realtime do
5166 system ( 'ruby' , File . expand_path ( '../../gems/pure-ruby-tracer/lib/trace.rb' , __dir__ ) ,
52- '--out-dir' , output_dir ,
53- program )
54- raise 'Trace failed' unless $?. success?
67+ '--out-dir' , pure_dir , program )
68+ raise 'Pure trace failed' unless $?. success?
5569 end
56- runtime_ms = ( elapsed * 1000 ) . round
57- output_trace = File . join ( output_dir , 'trace.json' )
58- success = files_identical? ( fixture , output_trace )
59- size_bytes = File . size ( output_trace )
70+ results [ :pure_ms ] = ( elapsed * 1000 ) . round
71+ pure_trace = File . join ( pure_dir , 'trace.json' )
72+ results [ :pure_ok ] = traces_equal? ( fixture , pure_trace )
6073
61- { name : name , runtime_ms : runtime_ms , trace_size : size_bytes , success : success }
74+ results
6275end
6376
6477# Execute all benchmarks
65- results = benchmarks . map { |b | run_benchmark ( b , benchmark_dir ) }
78+ results = benchmarks . map { |b | run_benchmark ( b ) }
6679
6780# Reporting
6881if options [ :write_report ] == 'console'
6982 # Determine column widths
70- name_w = [ 'Benchmark' . length , *results . map { |r | r [ :name ] . length } ] . max
71- rt_w = [ 'Runtime' . length , *results . map { |r | r [ :runtime_ms ] . to_s . length } ] . max
72- ts_w = [ 'Trace Size' . length , *results . map { |r | r [ :trace_size ] . to_s . length } ] . max
83+ name_w = [ 'Benchmark' . length , *results . map { |r | r [ :name ] . length } ] . max
84+ ruby_w = [ 'Ruby' . length , *results . map { |r | "#{ r [ :ruby_ms ] } ms" . length } ] . max
85+ native_w = [ 'Native' . length , *results . map { |r | "#{ r [ :native_ok ] ? 'OK' : 'FAIL' } #{ r [ :native_ms ] } ms" . length } ] . max
86+ pure_w = [ 'Pure' . length , *results . map { |r | "#{ r [ :pure_ok ] ? 'OK' : 'FAIL' } #{ r [ :pure_ms ] } ms" . length } ] . max
7387
7488 # Header
75- printf "%-#{ name_w } s %#{ rt_w } s %#{ ts_w } s %s\n " , 'Benchmark' , 'Runtime ' , 'Trace Size ' , 'Status '
76- puts '-' * ( name_w + rt_w + ts_w + 10 )
89+ printf "%-#{ name_w } s %- #{ ruby_w } s %- #{ native_w } s %- #{ pure_w } s\n " , 'Benchmark' , 'Ruby ' , 'Native ' , 'Pure '
90+ puts '-' * ( name_w + ruby_w + native_w + pure_w + 6 )
7791
7892 # Rows
7993 results . each do |r |
80- status = r [ :success ] ? 'OK' : 'FAIL'
81- printf "%-#{ name_w } s %#{ rt_w } d ms %#{ ts_w } d %s\n " , r [ :name ] , r [ :runtime_ms ] , r [ :trace_size ] , status
94+ ruby_s = "#{ r [ :ruby_ms ] } ms"
95+ native_s = "#{ r [ :native_ok ] ? 'OK' : 'FAIL' } #{ r [ :native_ms ] } ms"
96+ pure_s = "#{ r [ :pure_ok ] ? 'OK' : 'FAIL' } #{ r [ :pure_ms ] } ms"
97+ printf "%-#{ name_w } s %-#{ ruby_w } s %-#{ native_w } s %-#{ pure_w } s\n " , r [ :name ] , ruby_s , native_s , pure_s
8298 end
8399
84100 # Exit with non-zero if any failed
85- exit 1 unless results . all? { |r | r [ :success ] }
101+ exit 1 unless results . all? { |r | r [ :native_ok ] && r [ :pure_ok ] }
86102else
87103 dest = options [ :write_report ]
88104 FileUtils . mkdir_p ( File . dirname ( dest ) )
89105
90106 case File . extname ( dest )
91107 when '.json'
92- data = results . map { |r | { benchmark : r [ :name ] , runtime_ms : r [ :runtime_ms ] , trace_bytes : r [ :trace_size ] } }
108+ data = results . map do |r |
109+ {
110+ benchmark : r [ :name ] ,
111+ ruby_ms : r [ :ruby_ms ] ,
112+ native_ms : r [ :native_ms ] ,
113+ native_ok : r [ :native_ok ] ,
114+ pure_ms : r [ :pure_ms ] ,
115+ pure_ok : r [ :pure_ok ]
116+ }
117+ end
93118 File . write ( dest , JSON . pretty_generate ( data ) )
94119 when '.svg'
95120 row_height = 25
@@ -98,11 +123,12 @@ def run_benchmark(name, benchmark_dir)
98123 svg << " <foreignObject width='100%' height='100%'>\n "
99124 svg << " <style>table{border-collapse:collapse;font-family:sans-serif;}td,th{border:1px solid #999;padding:4px;}</style>\n "
100125 svg << " <table>\n "
101- svg << " <thead><tr><th>Benchmark</th><th>Runtime (ms)</th><th>Trace size (bytes) </th><th>Status </th></tr></thead>\n "
126+ svg << " <thead><tr><th>Benchmark</th><th>Ruby (ms)</th><th>Native </th><th>Pure </th></tr></thead>\n "
102127 svg << " <tbody>\n "
103128 results . each do |r |
104- status = r [ :success ] ? 'OK' : 'FAIL'
105- svg << " <tr><td>#{ r [ :name ] } </td><td>#{ r [ :runtime_ms ] } </td><td>#{ r [ :trace_size ] } </td><td>#{ status } </td></tr>\n "
129+ native_s = r [ :native_ok ] ? 'OK' : 'FAIL'
130+ pure_s = r [ :pure_ok ] ? 'OK' : 'FAIL'
131+ svg << " <tr><td>#{ r [ :name ] } </td><td>#{ r [ :ruby_ms ] } </td><td>#{ native_s } #{ r [ :native_ms ] } </td><td>#{ pure_s } #{ r [ :pure_ms ] } </td></tr>\n "
106132 end
107133 svg << " </tbody>\n "
108134 svg << " </table>\n "
@@ -114,7 +140,7 @@ def run_benchmark(name, benchmark_dir)
114140 end
115141
116142 # Warn and exit if any failures
117- unless results . all? { |r | r [ :success ] }
143+ unless results . all? { |r | r [ :native_ok ] && r [ :pure_ok ] }
118144 warn 'One or more traces differ from reference!'
119145 exit 1
120146 end
0 commit comments