elkrb 1.0.0

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +11 -0
  4. data/Gemfile +13 -0
  5. data/README.adoc +1028 -0
  6. data/Rakefile +64 -0
  7. data/benchmarks/README.md +172 -0
  8. data/benchmarks/elkjs_benchmark.js +140 -0
  9. data/benchmarks/elkrb_benchmark.rb +145 -0
  10. data/benchmarks/fixtures/graphs.json +10777 -0
  11. data/benchmarks/generate_report.rb +241 -0
  12. data/benchmarks/generate_test_graphs.rb +154 -0
  13. data/benchmarks/results/elkrb_results.json +280 -0
  14. data/benchmarks/results/elkrb_summary.json +285 -0
  15. data/elkrb.gemspec +39 -0
  16. data/examples/dot_export_demo.rb +133 -0
  17. data/examples/hierarchical_graph.rb +19 -0
  18. data/examples/layout_constraints_demo.rb +272 -0
  19. data/examples/port_constraints_demo.rb +291 -0
  20. data/examples/self_loop_demo.rb +391 -0
  21. data/examples/simple_graph.rb +50 -0
  22. data/examples/spline_routing_demo.rb +235 -0
  23. data/exe/elkrb +8 -0
  24. data/lib/elkrb/cli.rb +224 -0
  25. data/lib/elkrb/commands/batch_command.rb +66 -0
  26. data/lib/elkrb/commands/convert_command.rb +130 -0
  27. data/lib/elkrb/commands/diagram_command.rb +208 -0
  28. data/lib/elkrb/commands/render_command.rb +52 -0
  29. data/lib/elkrb/commands/validate_command.rb +241 -0
  30. data/lib/elkrb/errors.rb +30 -0
  31. data/lib/elkrb/geometry/bezier.rb +163 -0
  32. data/lib/elkrb/geometry/dimension.rb +32 -0
  33. data/lib/elkrb/geometry/point.rb +68 -0
  34. data/lib/elkrb/geometry/rectangle.rb +86 -0
  35. data/lib/elkrb/geometry/vector.rb +67 -0
  36. data/lib/elkrb/graph/edge.rb +95 -0
  37. data/lib/elkrb/graph/graph.rb +90 -0
  38. data/lib/elkrb/graph/label.rb +45 -0
  39. data/lib/elkrb/graph/layout_options.rb +247 -0
  40. data/lib/elkrb/graph/node.rb +79 -0
  41. data/lib/elkrb/graph/node_constraints.rb +107 -0
  42. data/lib/elkrb/graph/port.rb +104 -0
  43. data/lib/elkrb/graphviz_wrapper.rb +133 -0
  44. data/lib/elkrb/layout/algorithm_registry.rb +57 -0
  45. data/lib/elkrb/layout/algorithms/base_algorithm.rb +208 -0
  46. data/lib/elkrb/layout/algorithms/box.rb +47 -0
  47. data/lib/elkrb/layout/algorithms/disco.rb +206 -0
  48. data/lib/elkrb/layout/algorithms/fixed.rb +32 -0
  49. data/lib/elkrb/layout/algorithms/force.rb +165 -0
  50. data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +86 -0
  51. data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +96 -0
  52. data/lib/elkrb/layout/algorithms/layered/node_placer.rb +77 -0
  53. data/lib/elkrb/layout/algorithms/layered.rb +49 -0
  54. data/lib/elkrb/layout/algorithms/libavoid.rb +389 -0
  55. data/lib/elkrb/layout/algorithms/mrtree.rb +144 -0
  56. data/lib/elkrb/layout/algorithms/radial.rb +64 -0
  57. data/lib/elkrb/layout/algorithms/random.rb +43 -0
  58. data/lib/elkrb/layout/algorithms/rectpacking.rb +93 -0
  59. data/lib/elkrb/layout/algorithms/spore_compaction.rb +139 -0
  60. data/lib/elkrb/layout/algorithms/spore_overlap.rb +117 -0
  61. data/lib/elkrb/layout/algorithms/stress.rb +176 -0
  62. data/lib/elkrb/layout/algorithms/topdown_packing.rb +183 -0
  63. data/lib/elkrb/layout/algorithms/vertiflex.rb +174 -0
  64. data/lib/elkrb/layout/constraints/alignment_constraint.rb +150 -0
  65. data/lib/elkrb/layout/constraints/base_constraint.rb +72 -0
  66. data/lib/elkrb/layout/constraints/constraint_processor.rb +134 -0
  67. data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +87 -0
  68. data/lib/elkrb/layout/constraints/layer_constraint.rb +71 -0
  69. data/lib/elkrb/layout/constraints/relative_position_constraint.rb +110 -0
  70. data/lib/elkrb/layout/edge_router.rb +935 -0
  71. data/lib/elkrb/layout/hierarchical_processor.rb +299 -0
  72. data/lib/elkrb/layout/label_placer.rb +338 -0
  73. data/lib/elkrb/layout/layout_engine.rb +170 -0
  74. data/lib/elkrb/layout/port_constraint_processor.rb +173 -0
  75. data/lib/elkrb/options/elk_padding.rb +94 -0
  76. data/lib/elkrb/options/k_vector.rb +100 -0
  77. data/lib/elkrb/options/k_vector_chain.rb +135 -0
  78. data/lib/elkrb/parsers/elkt_parser.rb +248 -0
  79. data/lib/elkrb/serializers/dot_serializer.rb +339 -0
  80. data/lib/elkrb/serializers/elkt_serializer.rb +236 -0
  81. data/lib/elkrb/version.rb +5 -0
  82. data/lib/elkrb.rb +509 -0
  83. data/sig/elkrb/constraints.rbs +114 -0
  84. data/sig/elkrb/geometry.rbs +61 -0
  85. data/sig/elkrb/graph.rbs +112 -0
  86. data/sig/elkrb/layout.rbs +107 -0
  87. data/sig/elkrb/options.rbs +81 -0
  88. data/sig/elkrb.rbs +32 -0
  89. metadata +179 -0
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+
10
+ namespace :benchmark do
11
+ desc "Generate test graphs for benchmarking"
12
+ task :generate_graphs do
13
+ ruby "benchmarks/generate_test_graphs.rb"
14
+ end
15
+
16
+ desc "Run ElkRb benchmarks"
17
+ task elkrb: :generate_graphs do
18
+ ruby "benchmarks/elkrb_benchmark.rb"
19
+ end
20
+
21
+ desc "Run elkjs benchmarks (requires Node.js and elkjs)"
22
+ task elkjs: :generate_graphs do
23
+ sh "node benchmarks/elkjs_benchmark.js"
24
+ end
25
+
26
+ desc "Generate performance report"
27
+ task :report do
28
+ ruby "benchmarks/generate_report.rb"
29
+ end
30
+
31
+ desc "Run all benchmarks and generate report"
32
+ task all: %i[elkrb report] do
33
+ puts "\nAll benchmarks completed!"
34
+ puts "Note: Run 'rake benchmark:elkjs' separately if elkjs is installed"
35
+ end
36
+ end
37
+
38
+ namespace :validate do
39
+ desc "Import test cases from elkjs"
40
+ task :import_elkjs do
41
+ ruby "spec/cross_validation/elkjs_test_importer.rb"
42
+ end
43
+
44
+ desc "Import test cases from Java ELK"
45
+ task :import_java_elk do
46
+ ruby "spec/cross_validation/java_elk_test_importer.rb"
47
+ end
48
+
49
+ desc "Import all test cases from elkjs and Java ELK"
50
+ task import_all: %i[import_elkjs import_java_elk]
51
+
52
+ desc "Run cross-validation tests"
53
+ task :run do
54
+ ruby "spec/cross_validation/validation_runner.rb"
55
+ end
56
+
57
+ desc "Import and run cross-validation (full pipeline)"
58
+ task all: %i[import_all run]
59
+
60
+ desc "Generate validation report (AsciiDoc)"
61
+ task :report do
62
+ ruby "spec/cross_validation/generate_validation_report.rb"
63
+ end
64
+ end
@@ -0,0 +1,172 @@
1
+ # ElkRb Performance Benchmarks
2
+
3
+ This directory contains the performance benchmarking suite for ElkRb, comparing it against elkjs and potentially Java ELK.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ benchmarks/
9
+ ├── fixtures/
10
+ │ └── graphs.json # Test graphs in JSON format
11
+ ├── results/
12
+ │ ├── elkrb_results.json # Raw ElkRb benchmark results
13
+ │ ├── elkrb_summary.json # ElkRb summary with metadata
14
+ │ ├── elkjs_results.json # Raw elkjs benchmark results (if run)
15
+ │ └── elkjs_summary.json # elkjs summary with metadata (if run)
16
+ ├── generate_test_graphs.rb # Generates test graphs
17
+ ├── elkrb_benchmark.rb # ElkRb benchmark runner
18
+ ├── elkjs_benchmark.js # elkjs benchmark runner (requires Node.js)
19
+ ├── generate_report.rb # Generates performance report
20
+ └── README.md # This file
21
+ ```
22
+
23
+ ## Running Benchmarks
24
+
25
+ ### Prerequisites
26
+
27
+ * Ruby 3.0+ with ElkRb installed
28
+ * Node.js 14+ (optional, for elkjs comparison)
29
+ * elkjs npm package (optional, install with `npm install elkjs`)
30
+
31
+ ### Quick Start
32
+
33
+ Run all ElkRb benchmarks and generate report:
34
+
35
+ ```bash
36
+ rake benchmark:all
37
+ ```
38
+
39
+ ### Individual Commands
40
+
41
+ **Generate test graphs:**
42
+ ```bash
43
+ rake benchmark:generate_graphs
44
+ # or
45
+ ruby benchmarks/generate_test_graphs.rb
46
+ ```
47
+
48
+ **Run ElkRb benchmarks:**
49
+ ```bash
50
+ rake benchmark:elkrb
51
+ # or
52
+ ruby benchmarks/elkrb_benchmark.rb
53
+ ```
54
+
55
+ **Run elkjs benchmarks** (requires Node.js and elkjs):
56
+ ```bash
57
+ rake benchmark:elkjs
58
+ # or
59
+ node benchmarks/elkjs_benchmark.js
60
+ ```
61
+
62
+ **Generate performance report:**
63
+ ```bash
64
+ rake benchmark:report
65
+ # or
66
+ ruby benchmarks/generate_report.rb
67
+ ```
68
+
69
+ ## Test Graphs
70
+
71
+ The benchmark suite uses four test graphs of varying complexity:
72
+
73
+ 1. **Small Simple** (10 nodes, 15 edges)
74
+ - Simple graph for basic performance testing
75
+ - Fast execution, ideal for algorithm correctness verification
76
+
77
+ 2. **Medium Hierarchical** (50 nodes, 75 edges, 3 levels)
78
+ - Hierarchical structure with nested containers
79
+ - Tests handling of parent-child relationships
80
+
81
+ 3. **Large Complex** (200 nodes, 400 edges)
82
+ - Large flat graph with many nodes
83
+ - Tests scalability of algorithms
84
+
85
+ 4. **Dense Network** (100 nodes, 500 edges)
86
+ - Dense connectivity between nodes
87
+ - Stresses edge routing and overlap prevention
88
+
89
+ ## Benchmark Methodology
90
+
91
+ * **Iterations**: Each algorithm runs 10 times with a warm-up run
92
+ * **Timing**: Average execution time in milliseconds
93
+ * **Timeout**: 5 seconds per algorithm to prevent hangs
94
+ * **Error Handling**: Algorithms that fail are marked with error messages
95
+ * **Consistency**: Same test graphs used across all implementations
96
+
97
+ ## Performance Metrics
98
+
99
+ The benchmarks collect:
100
+
101
+ * **Average Time**: Mean execution time across 10 runs
102
+ * **Min/Max Time**: Best and worst execution times
103
+ * **Memory Usage**: Memory delta during execution (ElkRb only)
104
+ * **Errors**: Stack overflows, timeouts, or other failures
105
+
106
+ ## Algorithms Benchmarked
107
+
108
+ * `layered` - Hierarchical layered layout
109
+ * `force` - Force-directed layout
110
+ * `stress` - Stress minimization
111
+ * `box` - Simple box layout
112
+ * `random` - Random positioning
113
+ * `fixed` - Fixed node positions
114
+ * `mrtree` - Tree layout
115
+ * `radial` - Radial/circular layout
116
+ * `rectpacking` - Rectangle packing
117
+ * `disco` - Disconnected graph handling
118
+ * `topdownpacking` - Top-down packing
119
+ * `libavoid` - Orthogonal routing (libavoid-inspired)
120
+ * `vertiflex` - Vertical flex layout
121
+
122
+ Note: `sporeOverlap` and `sporeCompaction` are not yet implemented in ElkRb.
123
+
124
+ ## Performance Report
125
+
126
+ The generated performance report (`docs/PERFORMANCE.adoc`) includes:
127
+
128
+ * Benchmark environment details
129
+ * Methodology description
130
+ * Performance comparison tables
131
+ * Algorithm performance analysis
132
+ * Recommendations for different use cases
133
+ * Future optimization opportunities
134
+
135
+ ## Known Issues
136
+
137
+ * **Cyclic Graphs**: Some algorithms (e.g., MRTree) don't handle cyclic graphs well
138
+ * **Hierarchical Graphs**: Nested container support varies by algorithm
139
+ * **Timeouts**: Complex algorithms may timeout on very large graphs
140
+ * **Memory**: Ruby uses more memory than JavaScript due to interpreter overhead
141
+
142
+ ## Tips for Running Benchmarks
143
+
144
+ 1. **Close other applications** to ensure consistent performance
145
+ 2. **Run multiple times** if you need statistical significance
146
+ 3. **Adjust timeout** in `elkrb_benchmark.rb` if needed (default: 5s)
147
+ 4. **Check logs** in `benchmarks/results/` for detailed data
148
+ 5. **Compare carefully** - different environments may have different results
149
+
150
+ ## Interpreting Results
151
+
152
+ * **< 10ms**: Fast, suitable for real-time/interactive use
153
+ * **10-50ms**: Medium speed, good for most applications
154
+ * **50-200ms**: Slower, suitable for batch processing
155
+ * **> 200ms**: Slow, consider simpler algorithms or optimization
156
+ * **Timeout**: Algorithm doesn't scale well for this graph size
157
+
158
+ ## Contributing
159
+
160
+ To add new benchmark graphs:
161
+
162
+ 1. Edit `generate_test_graphs.rb`
163
+ 2. Add a new graph generation method
164
+ 3. Include it in `generate_all`
165
+ 4. Re-run benchmarks
166
+
167
+ To benchmark against Java ELK:
168
+
169
+ 1. Create `java_elk_benchmark.java` or `.sh` script
170
+ 2. Output results in same JSON format
171
+ 3. Update `generate_report.rb` to include Java results
172
+ 4. Add rake task for Java benchmarks
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ // elkjs Performance Benchmark
3
+
4
+ const fs = require('fs');
5
+
6
+ // Import elkjs - we'll check if it's available
7
+ let ELK;
8
+ try {
9
+ ELK = require('elkjs');
10
+ } catch (e) {
11
+ console.error('ERROR: elkjs not installed. Install it with: npm install elkjs');
12
+ process.exit(1);
13
+ }
14
+
15
+ class ElkjsBenchmark {
16
+ constructor() {
17
+ this.elk = new ELK();
18
+ this.graphs = this.loadGraphs();
19
+ this.results = {};
20
+ }
21
+
22
+ loadGraphs() {
23
+ const data = fs.readFileSync('benchmarks/fixtures/graphs.json', 'utf8');
24
+ return JSON.parse(data);
25
+ }
26
+
27
+ async run() {
28
+ console.log('elkjs Performance Benchmark');
29
+ console.log('='.repeat(60));
30
+ console.log(`Node Version: ${process.version}`);
31
+
32
+ try {
33
+ const pkg = require('elkjs/package.json');
34
+ console.log(`elkjs Version: ${pkg.version}`);
35
+ } catch (e) {
36
+ console.log('elkjs Version: unknown');
37
+ }
38
+
39
+ console.log();
40
+
41
+ for (const [name, data] of Object.entries(this.graphs)) {
42
+ console.log(`Graph: ${name} - ${data.description}`);
43
+ await this.benchmarkGraph(name, data.graph);
44
+ console.log();
45
+ }
46
+
47
+ this.saveResults();
48
+ }
49
+
50
+ async benchmarkGraph(name, graphData) {
51
+ this.results[name] = {};
52
+
53
+ const algorithms = [
54
+ 'layered', 'force', 'stress', 'box', 'random', 'fixed',
55
+ 'mrtree', 'radial', 'disco'
56
+ ];
57
+
58
+ for (const algorithm of algorithms) {
59
+ try {
60
+ const times = [];
61
+
62
+ // Clone graph data for each test
63
+ const testGraph = JSON.parse(JSON.stringify(graphData));
64
+ testGraph.layoutOptions = { 'elk.algorithm': algorithm };
65
+
66
+ // Warm-up run
67
+ await this.elk.layout(testGraph);
68
+
69
+ // Benchmark runs (10 iterations)
70
+ for (let i = 0; i < 10; i++) {
71
+ const testGraphCopy = JSON.parse(JSON.stringify(graphData));
72
+ testGraphCopy.layoutOptions = { 'elk.algorithm': algorithm };
73
+
74
+ const start = process.hrtime.bigint();
75
+ await this.elk.layout(testGraphCopy);
76
+ const end = process.hrtime.bigint();
77
+
78
+ times.push(Number(end - start) / 1_000_000); // Convert to ms
79
+ }
80
+
81
+ const avg = times.reduce((a, b) => a + b) / times.length;
82
+ const min = Math.min(...times);
83
+ const max = Math.max(...times);
84
+
85
+ this.results[name][algorithm] = { avg, min, max };
86
+
87
+ console.log(` ${algorithm.padEnd(20)}: ${this.formatTime(avg)}`);
88
+ } catch (error) {
89
+ console.log(` ${algorithm.padEnd(20)}: ERROR - ${error.message}`);
90
+ this.results[name][algorithm] = { error: error.message };
91
+ }
92
+ }
93
+ }
94
+
95
+ formatTime(ms) {
96
+ if (ms < 1) return `${(ms * 1000).toFixed(2)}µs`;
97
+ if (ms < 1000) return `${ms.toFixed(2)}ms`;
98
+ return `${(ms / 1000).toFixed(2)}s`;
99
+ }
100
+
101
+ saveResults() {
102
+ const summary = {
103
+ timestamp: new Date().toISOString(),
104
+ node_version: process.version,
105
+ elkjs_version: this.getElkjsVersion(),
106
+ results: this.results
107
+ };
108
+
109
+ fs.writeFileSync(
110
+ 'benchmarks/results/elkjs_results.json',
111
+ JSON.stringify(this.results, null, 2)
112
+ );
113
+
114
+ fs.writeFileSync(
115
+ 'benchmarks/results/elkjs_summary.json',
116
+ JSON.stringify(summary, null, 2)
117
+ );
118
+
119
+ console.log('='.repeat(60));
120
+ console.log('Results saved to:');
121
+ console.log(' - benchmarks/results/elkjs_results.json');
122
+ console.log(' - benchmarks/results/elkjs_summary.json');
123
+ }
124
+
125
+ getElkjsVersion() {
126
+ try {
127
+ const pkg = require('elkjs/package.json');
128
+ return pkg.version;
129
+ } catch (e) {
130
+ return 'unknown';
131
+ }
132
+ }
133
+ }
134
+
135
+ // Run benchmark
136
+ if (require.main === module) {
137
+ new ElkjsBenchmark().run().catch(console.error);
138
+ }
139
+
140
+ module.exports = ElkjsBenchmark;
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "elkrb"
6
+ require "json"
7
+ require "benchmark"
8
+
9
+ # ElkRb Performance Benchmark
10
+ class ElkrbBenchmark
11
+ ALGORITHMS = %w[
12
+ layered force stress box random fixed
13
+ mrtree radial rectpacking disco
14
+ sporeOverlap sporeCompaction
15
+ topdownpacking libavoid vertiflex
16
+ ].freeze
17
+
18
+ def initialize
19
+ @graphs = load_graphs
20
+ @results = {}
21
+ end
22
+
23
+ def run
24
+ puts "ElkRb Performance Benchmark"
25
+ puts "=" * 60
26
+ puts "Ruby Version: #{RUBY_VERSION}"
27
+ puts "ElkRb Version: #{Elkrb::VERSION}"
28
+ puts
29
+
30
+ @graphs.each do |name, data|
31
+ puts "Graph: #{name} - #{data['description']}"
32
+ benchmark_graph(name, data["graph"])
33
+ puts
34
+ end
35
+
36
+ save_results
37
+ generate_report
38
+ end
39
+
40
+ private
41
+
42
+ def load_graphs
43
+ JSON.parse(File.read("benchmarks/fixtures/graphs.json"))
44
+ end
45
+
46
+ def benchmark_graph(name, graph_data)
47
+ @results[name] = {}
48
+
49
+ ALGORITHMS.each do |algorithm|
50
+ times = []
51
+ memory_before = get_memory_usage
52
+
53
+ # Warm-up run with timeout
54
+ result = run_with_timeout(5) do
55
+ Elkrb.layout(graph_data, algorithm: algorithm)
56
+ end
57
+
58
+ if result.nil?
59
+ puts " #{algorithm.ljust(20)}: TIMEOUT (> 5s)"
60
+ @results[name][algorithm] = { error: "Timeout" }
61
+ next
62
+ end
63
+
64
+ # Benchmark runs (10 iterations)
65
+ 10.times do
66
+ time = Benchmark.realtime do
67
+ Elkrb.layout(graph_data, algorithm: algorithm)
68
+ end
69
+ times << time
70
+ end
71
+
72
+ memory_after = get_memory_usage
73
+ memory_delta = memory_after - memory_before
74
+
75
+ avg_time = times.sum / times.size
76
+ min_time = times.min
77
+ max_time = times.max
78
+
79
+ @results[name][algorithm] = {
80
+ avg: avg_time * 1000, # Convert to ms
81
+ min: min_time * 1000,
82
+ max: max_time * 1000,
83
+ memory: memory_delta,
84
+ }
85
+
86
+ puts " #{algorithm.ljust(20)}: #{format_time(avg_time * 1000)}"
87
+ rescue StandardError, SystemStackError => e
88
+ error_msg = e.is_a?(SystemStackError) ? "Stack overflow (graph has cycles)" : e.message
89
+ puts " #{algorithm.ljust(20)}: ERROR - #{error_msg}"
90
+ @results[name][algorithm] = { error: error_msg }
91
+ end
92
+ end
93
+
94
+ def run_with_timeout(seconds, &)
95
+ require "timeout"
96
+ Timeout.timeout(seconds, &)
97
+ rescue Timeout::Error
98
+ nil
99
+ end
100
+
101
+ def get_memory_usage
102
+ `ps -o rss= -p #{Process.pid}`.to_i
103
+ end
104
+
105
+ def format_time(ms)
106
+ if ms < 1
107
+ "#{(ms * 1000).round(2)}µs"
108
+ elsif ms < 1000
109
+ "#{ms.round(2)}ms"
110
+ else
111
+ "#{(ms / 1000).round(2)}s"
112
+ end
113
+ end
114
+
115
+ def save_results
116
+ File.write(
117
+ "benchmarks/results/elkrb_results.json",
118
+ JSON.pretty_generate(@results),
119
+ )
120
+ end
121
+
122
+ def generate_report
123
+ summary = {
124
+ timestamp: Time.now.iso8601,
125
+ ruby_version: RUBY_VERSION,
126
+ elkrb_version: Elkrb::VERSION,
127
+ results: @results,
128
+ }
129
+
130
+ File.write(
131
+ "benchmarks/results/elkrb_summary.json",
132
+ JSON.pretty_generate(summary),
133
+ )
134
+
135
+ puts "=" * 60
136
+ puts "Results saved to:"
137
+ puts " - benchmarks/results/elkrb_results.json"
138
+ puts " - benchmarks/results/elkrb_summary.json"
139
+ end
140
+ end
141
+
142
+ # Run benchmark when executed directly
143
+ if __FILE__ == $PROGRAM_NAME
144
+ ElkrbBenchmark.new.run
145
+ end