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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +13 -0
- data/README.adoc +1028 -0
- data/Rakefile +64 -0
- data/benchmarks/README.md +172 -0
- data/benchmarks/elkjs_benchmark.js +140 -0
- data/benchmarks/elkrb_benchmark.rb +145 -0
- data/benchmarks/fixtures/graphs.json +10777 -0
- data/benchmarks/generate_report.rb +241 -0
- data/benchmarks/generate_test_graphs.rb +154 -0
- data/benchmarks/results/elkrb_results.json +280 -0
- data/benchmarks/results/elkrb_summary.json +285 -0
- data/elkrb.gemspec +39 -0
- data/examples/dot_export_demo.rb +133 -0
- data/examples/hierarchical_graph.rb +19 -0
- data/examples/layout_constraints_demo.rb +272 -0
- data/examples/port_constraints_demo.rb +291 -0
- data/examples/self_loop_demo.rb +391 -0
- data/examples/simple_graph.rb +50 -0
- data/examples/spline_routing_demo.rb +235 -0
- data/exe/elkrb +8 -0
- data/lib/elkrb/cli.rb +224 -0
- data/lib/elkrb/commands/batch_command.rb +66 -0
- data/lib/elkrb/commands/convert_command.rb +130 -0
- data/lib/elkrb/commands/diagram_command.rb +208 -0
- data/lib/elkrb/commands/render_command.rb +52 -0
- data/lib/elkrb/commands/validate_command.rb +241 -0
- data/lib/elkrb/errors.rb +30 -0
- data/lib/elkrb/geometry/bezier.rb +163 -0
- data/lib/elkrb/geometry/dimension.rb +32 -0
- data/lib/elkrb/geometry/point.rb +68 -0
- data/lib/elkrb/geometry/rectangle.rb +86 -0
- data/lib/elkrb/geometry/vector.rb +67 -0
- data/lib/elkrb/graph/edge.rb +95 -0
- data/lib/elkrb/graph/graph.rb +90 -0
- data/lib/elkrb/graph/label.rb +45 -0
- data/lib/elkrb/graph/layout_options.rb +247 -0
- data/lib/elkrb/graph/node.rb +79 -0
- data/lib/elkrb/graph/node_constraints.rb +107 -0
- data/lib/elkrb/graph/port.rb +104 -0
- data/lib/elkrb/graphviz_wrapper.rb +133 -0
- data/lib/elkrb/layout/algorithm_registry.rb +57 -0
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +208 -0
- data/lib/elkrb/layout/algorithms/box.rb +47 -0
- data/lib/elkrb/layout/algorithms/disco.rb +206 -0
- data/lib/elkrb/layout/algorithms/fixed.rb +32 -0
- data/lib/elkrb/layout/algorithms/force.rb +165 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +86 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +96 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +77 -0
- data/lib/elkrb/layout/algorithms/layered.rb +49 -0
- data/lib/elkrb/layout/algorithms/libavoid.rb +389 -0
- data/lib/elkrb/layout/algorithms/mrtree.rb +144 -0
- data/lib/elkrb/layout/algorithms/radial.rb +64 -0
- data/lib/elkrb/layout/algorithms/random.rb +43 -0
- data/lib/elkrb/layout/algorithms/rectpacking.rb +93 -0
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +139 -0
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +117 -0
- data/lib/elkrb/layout/algorithms/stress.rb +176 -0
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +183 -0
- data/lib/elkrb/layout/algorithms/vertiflex.rb +174 -0
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +150 -0
- data/lib/elkrb/layout/constraints/base_constraint.rb +72 -0
- data/lib/elkrb/layout/constraints/constraint_processor.rb +134 -0
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +87 -0
- data/lib/elkrb/layout/constraints/layer_constraint.rb +71 -0
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +110 -0
- data/lib/elkrb/layout/edge_router.rb +935 -0
- data/lib/elkrb/layout/hierarchical_processor.rb +299 -0
- data/lib/elkrb/layout/label_placer.rb +338 -0
- data/lib/elkrb/layout/layout_engine.rb +170 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +173 -0
- data/lib/elkrb/options/elk_padding.rb +94 -0
- data/lib/elkrb/options/k_vector.rb +100 -0
- data/lib/elkrb/options/k_vector_chain.rb +135 -0
- data/lib/elkrb/parsers/elkt_parser.rb +248 -0
- data/lib/elkrb/serializers/dot_serializer.rb +339 -0
- data/lib/elkrb/serializers/elkt_serializer.rb +236 -0
- data/lib/elkrb/version.rb +5 -0
- data/lib/elkrb.rb +509 -0
- data/sig/elkrb/constraints.rbs +114 -0
- data/sig/elkrb/geometry.rbs +61 -0
- data/sig/elkrb/graph.rbs +112 -0
- data/sig/elkrb/layout.rbs +107 -0
- data/sig/elkrb/options.rbs +81 -0
- data/sig/elkrb.rbs +32 -0
- 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
|