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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +151 -13
- data/README.md +1138 -104
- data/RELEASE.md +254 -0
- data/Rakefile +377 -0
- data/benchmarks/OPTIMIZATION.md +246 -0
- data/benchmarks/README.md +103 -0
- data/benchmarks/allocation_profiling.rb +253 -0
- data/benchmarks/e11y_benchmarks.rb +447 -0
- data/benchmarks/ruby_baseline_allocations.rb +175 -0
- data/benchmarks/run_all.rb +9 -21
- data/docs/00-ICP-AND-TIMELINE.md +2 -2
- data/docs/ADR-001-architecture.md +1 -1
- data/docs/ADR-004-adapter-architecture.md +247 -0
- data/docs/ADR-009-cost-optimization.md +231 -115
- data/docs/ADR-017-multi-rails-compatibility.md +103 -0
- data/docs/ADR-INDEX.md +99 -0
- data/docs/CONTRIBUTING.md +312 -0
- data/docs/IMPLEMENTATION_PLAN.md +1 -1
- data/docs/QUICK-START.md +0 -6
- data/docs/use_cases/UC-019-retention-based-routing.md +584 -0
- data/e11y.gemspec +28 -17
- data/lib/e11y/adapters/adaptive_batcher.rb +3 -0
- data/lib/e11y/adapters/audit_encrypted.rb +10 -4
- data/lib/e11y/adapters/base.rb +15 -0
- data/lib/e11y/adapters/file.rb +4 -1
- data/lib/e11y/adapters/in_memory.rb +6 -0
- data/lib/e11y/adapters/loki.rb +9 -0
- data/lib/e11y/adapters/otel_logs.rb +11 -9
- data/lib/e11y/adapters/sentry.rb +9 -0
- data/lib/e11y/adapters/yabeda.rb +54 -10
- data/lib/e11y/buffers.rb +8 -8
- data/lib/e11y/console.rb +52 -60
- data/lib/e11y/event/base.rb +75 -10
- data/lib/e11y/event/value_sampling_config.rb +10 -4
- data/lib/e11y/events/rails/http/request.rb +1 -1
- data/lib/e11y/instruments/active_job.rb +6 -3
- data/lib/e11y/instruments/rails_instrumentation.rb +51 -28
- data/lib/e11y/instruments/sidekiq.rb +7 -7
- data/lib/e11y/logger/bridge.rb +24 -54
- data/lib/e11y/metrics/cardinality_protection.rb +257 -12
- data/lib/e11y/metrics/cardinality_tracker.rb +17 -0
- data/lib/e11y/metrics/registry.rb +6 -2
- data/lib/e11y/metrics/relabeling.rb +0 -56
- data/lib/e11y/metrics.rb +6 -1
- data/lib/e11y/middleware/audit_signing.rb +12 -9
- data/lib/e11y/middleware/pii_filter.rb +18 -10
- data/lib/e11y/middleware/request.rb +10 -4
- data/lib/e11y/middleware/routing.rb +117 -90
- data/lib/e11y/middleware/sampling.rb +47 -28
- data/lib/e11y/middleware/trace_context.rb +40 -11
- data/lib/e11y/middleware/validation.rb +20 -2
- data/lib/e11y/middleware/versioning.rb +1 -1
- data/lib/e11y/pii.rb +7 -7
- data/lib/e11y/railtie.rb +24 -20
- data/lib/e11y/reliability/circuit_breaker.rb +3 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +16 -5
- data/lib/e11y/reliability/dlq/filter.rb +3 -0
- data/lib/e11y/reliability/retry_handler.rb +4 -0
- data/lib/e11y/sampling/error_spike_detector.rb +16 -5
- data/lib/e11y/sampling/load_monitor.rb +13 -4
- data/lib/e11y/self_monitoring/reliability_monitor.rb +3 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y.rb +86 -9
- metadata +83 -38
- data/docs/use_cases/UC-019-tiered-storage-migration.md +0 -562
- data/lib/e11y/middleware/pii_filtering.rb +0 -280
- 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
|