e11y 0.1.0 → 0.2.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +151 -13
  5. data/README.md +1138 -104
  6. data/RELEASE.md +254 -0
  7. data/Rakefile +377 -0
  8. data/benchmarks/OPTIMIZATION.md +246 -0
  9. data/benchmarks/README.md +103 -0
  10. data/benchmarks/allocation_profiling.rb +253 -0
  11. data/benchmarks/e11y_benchmarks.rb +447 -0
  12. data/benchmarks/ruby_baseline_allocations.rb +175 -0
  13. data/benchmarks/run_all.rb +9 -21
  14. data/docs/00-ICP-AND-TIMELINE.md +2 -2
  15. data/docs/ADR-001-architecture.md +1 -1
  16. data/docs/ADR-004-adapter-architecture.md +247 -0
  17. data/docs/ADR-009-cost-optimization.md +231 -115
  18. data/docs/ADR-017-multi-rails-compatibility.md +103 -0
  19. data/docs/ADR-INDEX.md +99 -0
  20. data/docs/CONTRIBUTING.md +312 -0
  21. data/docs/IMPLEMENTATION_PLAN.md +1 -1
  22. data/docs/QUICK-START.md +0 -6
  23. data/docs/use_cases/UC-019-retention-based-routing.md +584 -0
  24. data/e11y.gemspec +28 -17
  25. data/lib/e11y/adapters/adaptive_batcher.rb +3 -0
  26. data/lib/e11y/adapters/audit_encrypted.rb +10 -4
  27. data/lib/e11y/adapters/base.rb +15 -0
  28. data/lib/e11y/adapters/file.rb +4 -1
  29. data/lib/e11y/adapters/in_memory.rb +6 -0
  30. data/lib/e11y/adapters/loki.rb +9 -0
  31. data/lib/e11y/adapters/otel_logs.rb +11 -9
  32. data/lib/e11y/adapters/sentry.rb +9 -0
  33. data/lib/e11y/adapters/yabeda.rb +54 -10
  34. data/lib/e11y/buffers.rb +8 -8
  35. data/lib/e11y/console.rb +52 -60
  36. data/lib/e11y/event/base.rb +75 -10
  37. data/lib/e11y/event/value_sampling_config.rb +10 -4
  38. data/lib/e11y/events/rails/http/request.rb +1 -1
  39. data/lib/e11y/instruments/active_job.rb +6 -3
  40. data/lib/e11y/instruments/rails_instrumentation.rb +51 -28
  41. data/lib/e11y/instruments/sidekiq.rb +7 -7
  42. data/lib/e11y/logger/bridge.rb +24 -54
  43. data/lib/e11y/metrics/cardinality_protection.rb +257 -12
  44. data/lib/e11y/metrics/cardinality_tracker.rb +17 -0
  45. data/lib/e11y/metrics/registry.rb +6 -2
  46. data/lib/e11y/metrics/relabeling.rb +0 -56
  47. data/lib/e11y/metrics.rb +6 -1
  48. data/lib/e11y/middleware/audit_signing.rb +12 -9
  49. data/lib/e11y/middleware/pii_filter.rb +18 -10
  50. data/lib/e11y/middleware/request.rb +10 -4
  51. data/lib/e11y/middleware/routing.rb +117 -90
  52. data/lib/e11y/middleware/sampling.rb +47 -28
  53. data/lib/e11y/middleware/trace_context.rb +40 -11
  54. data/lib/e11y/middleware/validation.rb +20 -2
  55. data/lib/e11y/middleware/versioning.rb +1 -1
  56. data/lib/e11y/pii.rb +7 -7
  57. data/lib/e11y/railtie.rb +24 -20
  58. data/lib/e11y/reliability/circuit_breaker.rb +3 -0
  59. data/lib/e11y/reliability/dlq/file_storage.rb +16 -5
  60. data/lib/e11y/reliability/dlq/filter.rb +3 -0
  61. data/lib/e11y/reliability/retry_handler.rb +4 -0
  62. data/lib/e11y/sampling/error_spike_detector.rb +16 -5
  63. data/lib/e11y/sampling/load_monitor.rb +13 -4
  64. data/lib/e11y/self_monitoring/reliability_monitor.rb +3 -0
  65. data/lib/e11y/version.rb +1 -1
  66. data/lib/e11y.rb +86 -9
  67. metadata +83 -38
  68. data/docs/use_cases/UC-019-tiered-storage-migration.md +0 -562
  69. data/lib/e11y/middleware/pii_filtering.rb +0 -280
  70. data/lib/e11y/middleware/slo.rb +0 -168
@@ -0,0 +1,246 @@
1
+ # E11y Performance Optimization Strategies
2
+
3
+ **Status:** Conditional (apply only if benchmarks fail)
4
+
5
+ This document outlines optimization strategies to apply if performance benchmarks don't meet targets.
6
+
7
+ ## šŸ“Š Benchmark First
8
+
9
+ **ALWAYS run benchmarks before optimizing:**
10
+
11
+ ```bash
12
+ bundle exec ruby benchmarks/e11y_benchmarks.rb
13
+ ```
14
+
15
+ If all targets are met → **no optimization needed** āœ…
16
+
17
+ If some targets fail → apply strategies below āš™ļø
18
+
19
+ ## šŸŽÆ Optimization Strategies
20
+
21
+ ### 1. Memory Optimization
22
+
23
+ **If memory usage exceeds targets:**
24
+
25
+ #### 1.1 Reduce Object Allocations
26
+
27
+ ```ruby
28
+ # BAD: Creates new hash on every call
29
+ def event_data
30
+ { name: @name, value: @value, timestamp: Time.now }
31
+ end
32
+
33
+ # GOOD: Reuse hash
34
+ def event_data
35
+ @event_data ||= {}
36
+ @event_data[:name] = @name
37
+ @event_data[:value] = @value
38
+ @event_data[:timestamp] = Time.now
39
+ @event_data
40
+ end
41
+ ```
42
+
43
+ #### 1.2 Optimize Buffer Size
44
+
45
+ Current default: 10,000 events
46
+
47
+ ```ruby
48
+ # Tune buffer size based on scale
49
+ E11y.configure do |config|
50
+ config.buffer_size = case scale
51
+ when :small then 1_000
52
+ when :medium then 5_000
53
+ when :large then 10_000
54
+ end
55
+ end
56
+ ```
57
+
58
+ #### 1.3 Pool Reusable Objects
59
+
60
+ ```ruby
61
+ # Event object pooling
62
+ class EventPool
63
+ def initialize(size: 100)
64
+ @pool = Array.new(size) { E11y::Event::Base.new }
65
+ @mutex = Mutex.new
66
+ end
67
+
68
+ def checkout
69
+ @mutex.synchronize { @pool.pop || E11y::Event::Base.new }
70
+ end
71
+
72
+ def checkin(event)
73
+ @mutex.synchronize { @pool.push(event) if @pool.size < 100 }
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### 2. CPU Optimization
79
+
80
+ **If CPU overhead exceeds targets:**
81
+
82
+ #### 2.1 Reduce Regex Matches
83
+
84
+ ```ruby
85
+ # BAD: Regex on hot path
86
+ def matches_pattern?(name)
87
+ name =~ /^user\./
88
+ end
89
+
90
+ # GOOD: String prefix check
91
+ def matches_pattern?(name)
92
+ name.start_with?("user.")
93
+ end
94
+ ```
95
+
96
+ #### 2.2 Cache Pattern Compilation
97
+
98
+ ```ruby
99
+ # BAD: Compile regex every time
100
+ def filter_events(events)
101
+ events.select { |e| e.name =~ /^(user|admin)\./ }
102
+ end
103
+
104
+ # GOOD: Compile once
105
+ PATTERN = /^(user|admin)\./
106
+
107
+ def filter_events(events)
108
+ events.select { |e| e.name =~ PATTERN }
109
+ end
110
+ ```
111
+
112
+ #### 2.3 Optimize JSON Serialization
113
+
114
+ ```ruby
115
+ # Use Oj gem for faster JSON
116
+ require "oj"
117
+
118
+ module E11y
119
+ module Adapters
120
+ class File < Base
121
+ def format_event(event_data)
122
+ Oj.dump(event_data, mode: :compat)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ # Add to gemspec:
129
+ # spec.add_dependency "oj", "~> 3.16"
130
+ ```
131
+
132
+ ### 3. I/O Optimization
133
+
134
+ **If adapter latency exceeds targets:**
135
+
136
+ #### 3.1 Increase Batching
137
+
138
+ ```ruby
139
+ E11y.configure do |config|
140
+ # Increase batch size for better I/O efficiency
141
+ config.flush_threshold = 1000 # Flush every 1000 events
142
+ config.flush_interval = 5.0 # Or every 5 seconds
143
+ end
144
+ ```
145
+
146
+ #### 3.2 Connection Pooling Tuning
147
+
148
+ ```ruby
149
+ # For LokiAdapter
150
+ E11y::Adapters::Loki.new(
151
+ url: "http://loki:3100",
152
+ connection_pool_size: 10, # Increase for high throughput
153
+ timeout: 5
154
+ )
155
+ ```
156
+
157
+ #### 3.3 Compression Optimization
158
+
159
+ ```ruby
160
+ # Use faster compression level
161
+ E11y.configure do |config|
162
+ config.compression = {
163
+ enabled: true,
164
+ algorithm: :zstd, # Faster than gzip
165
+ level: 1 # Fast compression (vs level 3)
166
+ }
167
+ end
168
+ ```
169
+
170
+ ### 4. Profiling Tools
171
+
172
+ **Use Ruby profilers to find bottlenecks:**
173
+
174
+ #### 4.1 Memory Profiler
175
+
176
+ ```ruby
177
+ require "memory_profiler"
178
+
179
+ report = MemoryProfiler.report do
180
+ 10_000.times { MyEvent.track(user_id: "123") }
181
+ end
182
+
183
+ report.pretty_print
184
+ ```
185
+
186
+ #### 4.2 Stackprof (CPU profiler)
187
+
188
+ ```bash
189
+ gem install stackprof
190
+
191
+ # Add to benchmark:
192
+ require "stackprof"
193
+
194
+ StackProf.run(mode: :cpu, out: "tmp/stackprof.dump") do
195
+ # Your benchmark code
196
+ end
197
+
198
+ # Analyze:
199
+ stackprof tmp/stackprof.dump --text
200
+ ```
201
+
202
+ #### 4.3 RubyProf
203
+
204
+ ```ruby
205
+ require "ruby-prof"
206
+
207
+ result = RubyProf.profile do
208
+ # Your benchmark code
209
+ end
210
+
211
+ printer = RubyProf::FlatPrinter.new(result)
212
+ printer.print(STDOUT)
213
+ ```
214
+
215
+ ## šŸ”§ Implementation Checklist
216
+
217
+ If benchmarks fail, apply optimizations in this order:
218
+
219
+ 1. **Identify bottleneck** (use profilers)
220
+ 2. **Apply targeted fix** (don't optimize everything)
221
+ 3. **Re-run benchmarks** (verify improvement)
222
+ 4. **Repeat** (until targets met)
223
+
224
+ ## šŸ“ˆ Performance Targets Reminder
225
+
226
+ - **Small (1K/sec)**: track() <50μs p99, Buffer 10K/sec, Memory <100MB
227
+ - **Medium (10K/sec)**: track() <1ms p99, Buffer 50K/sec, Memory <500MB
228
+ - **Large (100K/sec)**: track() <5ms p99, Buffer 200K/sec, Memory <2GB
229
+
230
+ ## āš ļø Anti-Patterns
231
+
232
+ **Don't optimize prematurely:**
233
+ - āŒ Don't apply optimizations "just in case"
234
+ - āŒ Don't micro-optimize without profiling
235
+ - āœ… Measure first, optimize second
236
+
237
+ **Don't sacrifice readability:**
238
+ - āŒ Don't make code unreadable for 1% gain
239
+ - āœ… Keep code maintainable, profile-driven optimizations only
240
+
241
+ ## šŸ“š Additional Resources
242
+
243
+ - [Ruby Performance Optimization (Book)](https://pragprog.com/titles/adrpo/ruby-performance-optimization/)
244
+ - [benchmark-ips gem](https://github.com/evanphx/benchmark-ips)
245
+ - [memory_profiler gem](https://github.com/SamSaffron/memory_profiler)
246
+ - [stackprof gem](https://github.com/tmm1/stackprof)
@@ -0,0 +1,103 @@
1
+ # E11y Performance Benchmarks
2
+
3
+ Performance benchmark suite for E11y gem, testing at 3 scale levels.
4
+
5
+ ## šŸŽÆ Performance Targets
6
+
7
+ Based on **ADR-001 §5: Performance Requirements**
8
+
9
+ ### Small Scale (1K events/sec)
10
+ - `track()` latency: **<50μs** (p99)
11
+ - Buffer throughput: **10K events/sec**
12
+ - Memory usage: **<100MB**
13
+ - CPU overhead: **<5%**
14
+
15
+ ### Medium Scale (10K events/sec)
16
+ - `track()` latency: **<1ms** (p99)
17
+ - Buffer throughput: **50K events/sec**
18
+ - Memory usage: **<500MB**
19
+ - CPU overhead: **<10%**
20
+
21
+ ### Large Scale (100K events/sec)
22
+ - `track()` latency: **<5ms** (p99)
23
+ - Buffer throughput: **200K events/sec**
24
+ - Memory usage: **<2GB**
25
+ - CPU overhead: **<15%**
26
+
27
+ ## šŸš€ Running Benchmarks
28
+
29
+ ### Run all scales
30
+
31
+ ```bash
32
+ bundle exec ruby benchmarks/e11y_benchmarks.rb
33
+ ```
34
+
35
+ ### Run specific scale
36
+
37
+ ```bash
38
+ # Small scale (1K events/sec)
39
+ SCALE=small bundle exec ruby benchmarks/e11y_benchmarks.rb
40
+
41
+ # Medium scale (10K events/sec)
42
+ SCALE=medium bundle exec ruby benchmarks/e11y_benchmarks.rb
43
+
44
+ # Large scale (100K events/sec)
45
+ SCALE=large bundle exec ruby benchmarks/e11y_benchmarks.rb
46
+ ```
47
+
48
+ ### Run via runner
49
+
50
+ ```bash
51
+ bundle exec ruby benchmarks/run_all.rb
52
+ ```
53
+
54
+ ## šŸ“Š Metrics Collected
55
+
56
+ 1. **track() Latency**
57
+ - p50, p99, p999 percentiles (microseconds)
58
+ - Min, max, mean values
59
+
60
+ 2. **Buffer Throughput**
61
+ - Events per second
62
+ - Measured over sustained period (3-10 seconds)
63
+
64
+ 3. **Memory Usage**
65
+ - Total allocated memory (MB)
66
+ - Memory per event (KB)
67
+ - Object allocations and retentions
68
+
69
+ 4. **CPU Overhead**
70
+ - Informational only (manual profiling recommended)
71
+
72
+ ## āœ… Success Criteria
73
+
74
+ Benchmarks **PASS** if all metrics meet targets for each scale.
75
+
76
+ Exit codes:
77
+ - `0` - All benchmarks passed āœ…
78
+ - `1` - Some benchmarks failed āŒ
79
+
80
+ ## šŸ”§ Dependencies
81
+
82
+ ```ruby
83
+ # Required gems (already in gemspec)
84
+ gem "benchmark-ips", "~> 2.13"
85
+ gem "memory_profiler", "~> 1.0"
86
+ ```
87
+
88
+ ## šŸ“ Notes
89
+
90
+ - Benchmarks use `InMemory` adapter to eliminate I/O overhead
91
+ - GC is triggered before memory profiling for clean measurements
92
+ - CPU percentage is approximate (use external profilers for accuracy)
93
+ - Results may vary based on Ruby version and hardware
94
+
95
+ ## šŸŽ“ CI Integration
96
+
97
+ In CI/CD:
98
+
99
+ ```yaml
100
+ - name: Run performance benchmarks
101
+ run: bundle exec ruby benchmarks/e11y_benchmarks.rb
102
+ # Fails CI if benchmarks don't meet targets
103
+ ```
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # E11y Allocation Profiling Script
5
+ #
6
+ # Purpose: Measure actual object allocations per event and verify
7
+ # we're meeting Ruby's best practices for minimal allocations.
8
+ #
9
+ # Usage:
10
+ # bundle exec ruby benchmarks/allocation_profiling.rb
11
+ #
12
+ # Expected results:
13
+ # - Ruby minimum: 2 allocations per event (kwargs Hash + return Hash)
14
+ # - E11y target: Close to theoretical minimum (2-5 allocations/event)
15
+ # - For 1K events: 2K-5K allocations total
16
+
17
+ require "bundler/setup"
18
+ require "memory_profiler"
19
+ require "e11y"
20
+
21
+ # ============================================================================
22
+ # Configuration
23
+ # ============================================================================
24
+
25
+ # Test with different event counts
26
+ TEST_CASES = [
27
+ { count: 10, name: "10 events (warmup)" },
28
+ { count: 100, name: "100 events" },
29
+ { count: 1000, name: "1K events (DoD requirement)" },
30
+ { count: 10_000, name: "10K events" }
31
+ ].freeze
32
+
33
+ # ============================================================================
34
+ # Test Event Class
35
+ # ============================================================================
36
+
37
+ class AllocationTestEvent < E11y::Event::Base
38
+ # Minimal schema to isolate allocation sources
39
+ schema do
40
+ required(:value).filled(:integer)
41
+ end
42
+
43
+ # Skip validation for pure allocation measurement
44
+ validation_mode :never
45
+ end
46
+
47
+ # ============================================================================
48
+ # Setup E11y (minimal config)
49
+ # ============================================================================
50
+
51
+ E11y.configure do |config|
52
+ config.enabled = true
53
+ config.adapters = [E11y::Adapters::InMemory.new]
54
+ end
55
+
56
+ # ============================================================================
57
+ # Profiling Functions
58
+ # ============================================================================
59
+
60
+ def measure_allocations(event_count:)
61
+ # Warmup (avoid cold start allocations)
62
+ 10.times { AllocationTestEvent.track(value: 1) }
63
+
64
+ # Force GC to start clean
65
+ GC.start
66
+
67
+ # Profile allocations
68
+ report = MemoryProfiler.report do
69
+ event_count.times do |i|
70
+ AllocationTestEvent.track(value: i)
71
+ end
72
+ end
73
+
74
+ {
75
+ total_allocated: report.total_allocated,
76
+ total_retained: report.total_retained,
77
+ total_allocated_memsize: report.total_allocated_memsize,
78
+ total_retained_memsize: report.total_retained_memsize,
79
+ per_event_allocated: (report.total_allocated.to_f / event_count).round(2),
80
+ per_event_retained: (report.total_retained.to_f / event_count).round(2),
81
+ per_event_memsize_bytes: (report.total_allocated_memsize.to_f / event_count).round(2)
82
+ }
83
+ end
84
+
85
+ def detailed_allocation_report(event_count:)
86
+ puts "\n#{'=' * 80}"
87
+ puts " DETAILED ALLOCATION REPORT (#{event_count} events)"
88
+ puts "=" * 80
89
+
90
+ # Warmup
91
+ 10.times { AllocationTestEvent.track(value: 1) }
92
+ GC.start
93
+
94
+ report = MemoryProfiler.report do
95
+ event_count.times do |i|
96
+ AllocationTestEvent.track(value: i)
97
+ end
98
+ end
99
+
100
+ puts "\nšŸ“Š Allocation Summary:"
101
+ puts " Total allocated: #{report.total_allocated} objects"
102
+ puts " Total retained: #{report.total_retained} objects"
103
+ puts " Memory size: #{(report.total_allocated_memsize / 1024.0).round(2)} KB"
104
+ puts ""
105
+ puts " Per-event allocated: #{(report.total_allocated.to_f / event_count).round(2)} objects"
106
+ puts " Per-event retained: #{(report.total_retained.to_f / event_count).round(2)} objects"
107
+ puts " Per-event memory: #{(report.total_allocated_memsize.to_f / event_count).round(2)} bytes"
108
+
109
+ puts "\nšŸ“ Top Allocation Sources (by object count):"
110
+ report.allocated_memory_by_location.first(10).each_with_index do |(location, data), index|
111
+ puts " #{index + 1}. #{location}"
112
+ puts " Objects: #{data[:count]}"
113
+ end
114
+
115
+ puts "\nšŸ” Allocations by Class:"
116
+ report.allocated_memory_by_class.first(10).each_with_index do |(klass, data), index|
117
+ puts " #{index + 1}. #{klass}: #{data[:count]} objects"
118
+ end
119
+
120
+ # Leak detection
121
+ if report.total_retained.positive?
122
+ puts "\nāš ļø MEMORY LEAK WARNING:"
123
+ puts " #{report.total_retained} objects retained (not garbage collected)"
124
+ puts ""
125
+ puts " Retained objects by class:"
126
+ report.retained_memory_by_class.first(5).each_with_index do |(klass, data), index|
127
+ puts " #{index + 1}. #{klass}: #{data[:count]} objects"
128
+ end
129
+ else
130
+ puts "\nāœ… No memory leaks detected (0 retained objects)"
131
+ end
132
+
133
+ report
134
+ end
135
+
136
+ # ============================================================================
137
+ # Verification Functions
138
+ # ============================================================================
139
+
140
+ def verify_allocation_target(result:, event_count:)
141
+ per_event = result[:per_event_allocated]
142
+ total = result[:total_allocated]
143
+
144
+ puts "\nšŸŽÆ Target Verification:"
145
+ puts ""
146
+
147
+ # Ruby theoretical minimum: 2 allocations per event
148
+ ruby_minimum = 2.0
149
+ is_near_minimum = per_event <= (ruby_minimum * 2.5) # 5 allocations = 2.5x minimum (reasonable overhead)
150
+
151
+ puts " Ruby theoretical minimum: #{ruby_minimum} allocations/event"
152
+ puts " E11y actual: #{per_event} allocations/event"
153
+ puts " Overhead: #{(((per_event / ruby_minimum) * 100) - 100).round(1)}%"
154
+ puts ""
155
+
156
+ if is_near_minimum
157
+ puts " āœ… GOOD: Near Ruby's theoretical minimum (< 2.5x)"
158
+ elsif per_event <= 10
159
+ puts " āš ļø ACCEPTABLE: Within reasonable range (< 10 allocations/event)"
160
+ else
161
+ puts " āŒ CONCERN: High allocation count (> 10 allocations/event)"
162
+ end
163
+
164
+ # DoD requirement check (for context)
165
+ dod_target = 100 # <100 allocations per 1K events
166
+ dod_actual = (total.to_f / event_count * 1000).round
167
+
168
+ puts ""
169
+ puts " DoD requirement (1K events): < #{dod_target} allocations"
170
+ puts " E11y extrapolated (1K): #{dod_actual} allocations"
171
+
172
+ if dod_actual <= dod_target
173
+ puts " āœ… MEETS DoD requirement"
174
+ else
175
+ puts " āŒ EXCEEDS DoD requirement (#{dod_actual - dod_target} over)"
176
+ puts " Note: DoD target may be unrealistic for Ruby (see audit findings)"
177
+ end
178
+ end
179
+
180
+ def check_for_leaks(result:)
181
+ retained = result[:total_retained]
182
+
183
+ puts "\nšŸ” Memory Leak Check:"
184
+ if retained.zero?
185
+ puts " āœ… PASS: No memory leaks (0 retained objects)"
186
+ else
187
+ puts " āŒ FAIL: #{retained} objects retained (potential memory leak)"
188
+ end
189
+ end
190
+
191
+ # ============================================================================
192
+ # Main Execution
193
+ # ============================================================================
194
+
195
+ def main
196
+ puts "šŸš€ E11y Allocation Profiling"
197
+ puts "Audit: FEAT-4918 - Zero-Allocation Pattern Verification"
198
+ puts "Ruby: #{RUBY_VERSION}"
199
+ puts ""
200
+
201
+ results = {}
202
+
203
+ # Run all test cases
204
+ TEST_CASES.each do |test_case|
205
+ count = test_case[:count]
206
+ name = test_case[:name]
207
+
208
+ puts "\n#{'=' * 80}"
209
+ puts " Testing: #{name}"
210
+ puts "=" * 80
211
+
212
+ result = measure_allocations(event_count: count)
213
+ results[count] = result
214
+
215
+ puts ""
216
+ puts " Total allocated: #{result[:total_allocated]} objects"
217
+ puts " Total retained: #{result[:total_retained]} objects"
218
+ puts " Per-event alloc: #{result[:per_event_allocated]} objects"
219
+ puts " Per-event memory: #{result[:per_event_memsize_bytes]} bytes"
220
+ end
221
+
222
+ # Detailed report for 1K events (DoD requirement)
223
+ detailed_allocation_report(event_count: 1000)
224
+
225
+ # Verification for 1K events
226
+ verify_allocation_target(result: results[1000], event_count: 1000)
227
+ check_for_leaks(result: results[1000])
228
+
229
+ # Summary
230
+ puts "\n#{'=' * 80}"
231
+ puts " SUMMARY"
232
+ puts "=" * 80
233
+ puts ""
234
+
235
+ result_1k = results[1000]
236
+ per_event = result_1k[:per_event_allocated]
237
+
238
+ if per_event <= 5 && result_1k[:total_retained].zero?
239
+ puts " āœ… EXCELLENT: Near-optimal allocations, no leaks"
240
+ exit 0
241
+ elsif per_event <= 10 && result_1k[:total_retained].zero?
242
+ puts " āš ļø ACCEPTABLE: Reasonable allocations, no leaks"
243
+ exit 0
244
+ elsif result_1k[:total_retained].positive?
245
+ puts " āŒ FAIL: Memory leak detected"
246
+ exit 1
247
+ else
248
+ puts " āŒ FAIL: High allocation count"
249
+ exit 1
250
+ end
251
+ end
252
+
253
+ main if __FILE__ == $PROGRAM_NAME