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,447 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # E11y Performance Benchmark Suite
5
+ #
6
+ # Tests performance at 3 scale levels:
7
+ # - Small: 1K events/sec
8
+ # - Medium: 10K events/sec
9
+ # - Large: 100K events/sec
10
+ #
11
+ # Run:
12
+ # bundle exec ruby benchmarks/e11y_benchmarks.rb
13
+ #
14
+ # Run specific scale:
15
+ # SCALE=small bundle exec ruby benchmarks/e11y_benchmarks.rb
16
+ #
17
+ # ADR-001 §5: Performance Requirements
18
+
19
+ require "bundler/setup"
20
+ require "benchmark"
21
+ require "benchmark/ips"
22
+ require "memory_profiler"
23
+ require "e11y"
24
+
25
+ # ============================================================================
26
+ # Configuration
27
+ # ============================================================================
28
+
29
+ SCALE = (ENV["SCALE"] || "all").downcase
30
+ WARMUP_TIME = 2 # seconds
31
+ BENCHMARK_TIME = 5 # seconds
32
+
33
+ # Performance targets
34
+ TARGETS = {
35
+ small: {
36
+ name: "Small Scale (1K events/sec)",
37
+ track_latency_p99_us: 50, # <50μs p99
38
+ buffer_throughput: 10_000, # 10K events/sec
39
+ memory_mb: 100, # <100MB
40
+ cpu_percent: 5 # <5%
41
+ },
42
+ medium: {
43
+ name: "Medium Scale (10K events/sec)",
44
+ track_latency_p99_us: 1000, # <1ms p99
45
+ buffer_throughput: 50_000, # 50K events/sec
46
+ memory_mb: 500, # <500MB
47
+ cpu_percent: 10 # <10%
48
+ },
49
+ large: {
50
+ name: "Large Scale (100K events/sec)",
51
+ track_latency_p99_us: 5000, # <5ms p99
52
+ buffer_throughput: 100_000, # 100K events/sec (per process)
53
+ memory_mb: 2000, # <2GB
54
+ cpu_percent: 15 # <15%
55
+ }
56
+ }.freeze
57
+
58
+ # ============================================================================
59
+ # Test Event Classes
60
+ # ============================================================================
61
+
62
+ # BenchmarkEvent is a test event class for benchmarking purposes.
63
+ # It contains user action tracking with user_id, action, and timestamp fields.
64
+ class BenchmarkEvent < E11y::Event::Base
65
+ schema do
66
+ required(:user_id).filled(:string)
67
+ required(:action).filled(:string)
68
+ required(:timestamp).filled(:time)
69
+ end
70
+ end
71
+
72
+ # SimpleBenchmarkEvent is a minimal test event class for benchmarking.
73
+ # It contains only a single integer value field for performance testing.
74
+ class SimpleBenchmarkEvent < E11y::Event::Base
75
+ schema do
76
+ required(:value).filled(:integer)
77
+ end
78
+ end
79
+
80
+ # ============================================================================
81
+ # Helper Methods
82
+ # ============================================================================
83
+
84
+ def setup_e11y(_buffer_size: 10_000)
85
+ E11y.configure do |config|
86
+ config.enabled = true
87
+
88
+ # Use InMemory adapter for clean benchmarks (no I/O overhead)
89
+ config.adapters = [
90
+ E11y::Adapters::InMemory.new
91
+ ]
92
+ end
93
+ end
94
+
95
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
96
+ # Benchmark code prioritizes clarity and performance over complexity metrics
97
+ def measure_track_latency(event_class:, count:, _scale_name:)
98
+ latencies = []
99
+
100
+ count.times do
101
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
102
+ event_class.track(
103
+ user_id: "user_#{rand(1000)}",
104
+ action: "test_action",
105
+ timestamp: Time.now
106
+ )
107
+ finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
108
+ latencies << (finish - start)
109
+ end
110
+
111
+ latencies.sort!
112
+ p50_index = (count * 0.5).to_i
113
+ p99_index = (count * 0.99).to_i
114
+ p999_index = (count * 0.999).to_i
115
+
116
+ {
117
+ p50: latencies[p50_index],
118
+ p99: latencies[p99_index],
119
+ p999: latencies[p999_index],
120
+ min: latencies.first,
121
+ max: latencies.last,
122
+ mean: (latencies.sum / count.to_f)
123
+ }
124
+ end
125
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
126
+
127
+ def measure_buffer_throughput(event_class:, duration_sec:)
128
+ count = 0
129
+ start_time = Time.now
130
+
131
+ while Time.now - start_time < duration_sec
132
+ event_class.track(value: count)
133
+ count += 1
134
+ end
135
+
136
+ actual_duration = Time.now - start_time
137
+ throughput = (count / actual_duration).round
138
+
139
+ { count: count, duration: actual_duration, throughput: throughput }
140
+ end
141
+
142
+ def measure_memory_usage(event_count:)
143
+ GC.start # Clean slate
144
+
145
+ report = MemoryProfiler.report do
146
+ event_count.times do |i|
147
+ SimpleBenchmarkEvent.track(value: i)
148
+ end
149
+ end
150
+
151
+ memory_mb = (report.total_allocated_memsize / 1024.0 / 1024.0).round(2)
152
+ memory_per_event_kb = ((report.total_allocated_memsize / event_count.to_f) / 1024.0).round(2)
153
+
154
+ {
155
+ total_mb: memory_mb,
156
+ per_event_kb: memory_per_event_kb,
157
+ total_allocated: report.total_allocated,
158
+ total_retained: report.total_retained
159
+ }
160
+ end
161
+
162
+ def print_header(scale_name)
163
+ puts "\n"
164
+ puts "=" * 80
165
+ puts " #{TARGETS[scale_name][:name]}"
166
+ puts "=" * 80
167
+ puts ""
168
+ end
169
+
170
+ def print_result(name, value, unit, target, passed)
171
+ status = passed ? "✅ PASS" : "❌ FAIL"
172
+ target_str = target ? "(target: #{target}#{unit})" : ""
173
+ puts " #{name.ljust(30)} #{value}#{unit} #{target_str} #{status}"
174
+ end
175
+
176
+ # rubocop:disable Metrics/AbcSize
177
+ # Benchmark code prioritizes clarity over complexity metrics
178
+ def print_summary(results)
179
+ puts "\n"
180
+ puts "=" * 80
181
+ puts " SUMMARY"
182
+ puts "=" * 80
183
+
184
+ results.each do |scale, data|
185
+ puts "\n#{TARGETS[scale][:name]}:"
186
+ puts " Total checks: #{data[:total]}"
187
+ puts " Passed: #{data[:passed]} ✅"
188
+ puts " Failed: #{data[:failed]} ❌"
189
+ puts " Status: #{data[:passed] == data[:total] ? '✅ ALL PASS' : '❌ SOME FAILED'}"
190
+ end
191
+ end
192
+ # rubocop:enable Metrics/AbcSize
193
+
194
+ # ============================================================================
195
+ # Benchmark Suite
196
+ # ============================================================================
197
+
198
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
199
+ # Benchmark code prioritizes clarity and performance over complexity metrics
200
+ def run_small_scale_benchmark
201
+ scale = :small
202
+ print_header(scale)
203
+
204
+ setup_e11y(buffer_size: 1000)
205
+
206
+ results = { total: 0, passed: 0, failed: 0 }
207
+
208
+ # 1. track() Latency
209
+ puts "📊 Benchmark: track() Latency (1000 iterations)"
210
+ latency = measure_track_latency(
211
+ event_class: BenchmarkEvent,
212
+ count: 1000,
213
+ scale_name: scale
214
+ )
215
+
216
+ target_p99 = TARGETS[scale][:track_latency_p99_us]
217
+ passed_p99 = latency[:p99] <= target_p99
218
+
219
+ puts " p50: #{latency[:p50].round(2)}μs"
220
+ puts " p99: #{latency[:p99].round(2)}μs (target: <#{target_p99}μs) #{passed_p99 ? '✅' : '❌'}"
221
+ puts " p999: #{latency[:p999].round(2)}μs"
222
+ puts " mean: #{latency[:mean].round(2)}μs"
223
+
224
+ results[:total] += 1
225
+ passed_p99 ? results[:passed] += 1 : results[:failed] += 1
226
+
227
+ # 2. Buffer Throughput
228
+ puts "\n📊 Benchmark: Buffer Throughput (3 seconds)"
229
+ throughput = measure_buffer_throughput(
230
+ event_class: SimpleBenchmarkEvent,
231
+ duration_sec: 3
232
+ )
233
+
234
+ target_throughput = TARGETS[scale][:buffer_throughput]
235
+ passed_throughput = throughput[:throughput] >= target_throughput
236
+
237
+ print_result(
238
+ "Buffer Throughput",
239
+ throughput[:throughput],
240
+ " events/sec",
241
+ ">#{target_throughput}",
242
+ passed_throughput
243
+ )
244
+
245
+ results[:total] += 1
246
+ passed_throughput ? results[:passed] += 1 : results[:failed] += 1
247
+
248
+ # 3. Memory Usage
249
+ puts "\n📊 Benchmark: Memory Usage (1K events)"
250
+ memory = measure_memory_usage(event_count: 1000)
251
+
252
+ target_memory = TARGETS[scale][:memory_mb]
253
+ passed_memory = memory[:total_mb] <= target_memory
254
+
255
+ print_result(
256
+ "Memory Usage (1K events)",
257
+ memory[:total_mb],
258
+ " MB",
259
+ "<#{target_memory}",
260
+ passed_memory
261
+ )
262
+ puts " Memory per event: #{memory[:per_event_kb]} KB"
263
+
264
+ results[:total] += 1
265
+ passed_memory ? results[:passed] += 1 : results[:failed] += 1
266
+
267
+ # 4. CPU Overhead (informational, no strict check)
268
+ puts "\n📊 Benchmark: CPU Overhead (informational)"
269
+ puts " Note: CPU measurement is approximate"
270
+ puts " Target: <#{TARGETS[scale][:cpu_percent]}%"
271
+ puts " (Manual profiling recommended for accurate CPU %)"
272
+
273
+ results
274
+ end
275
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
276
+
277
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
278
+ # Benchmark code prioritizes clarity and performance over complexity metrics
279
+ def run_medium_scale_benchmark
280
+ scale = :medium
281
+ print_header(scale)
282
+
283
+ setup_e11y(buffer_size: 10_000)
284
+
285
+ results = { total: 0, passed: 0, failed: 0 }
286
+
287
+ # 1. track() Latency
288
+ puts "📊 Benchmark: track() Latency (10K iterations)"
289
+ latency = measure_track_latency(
290
+ event_class: BenchmarkEvent,
291
+ count: 10_000,
292
+ scale_name: scale
293
+ )
294
+
295
+ target_p99 = TARGETS[scale][:track_latency_p99_us]
296
+ passed_p99 = latency[:p99] <= target_p99
297
+
298
+ puts " p50: #{latency[:p50].round(2)}μs"
299
+ puts " p99: #{latency[:p99].round(2)}μs (target: <#{target_p99}μs) #{passed_p99 ? '✅' : '❌'}"
300
+ puts " p999: #{latency[:p999].round(2)}μs"
301
+
302
+ results[:total] += 1
303
+ passed_p99 ? results[:passed] += 1 : results[:failed] += 1
304
+
305
+ # 2. Buffer Throughput
306
+ puts "\n📊 Benchmark: Buffer Throughput (5 seconds)"
307
+ throughput = measure_buffer_throughput(
308
+ event_class: SimpleBenchmarkEvent,
309
+ duration_sec: 5
310
+ )
311
+
312
+ target_throughput = TARGETS[scale][:buffer_throughput]
313
+ passed_throughput = throughput[:throughput] >= target_throughput
314
+
315
+ print_result(
316
+ "Buffer Throughput",
317
+ throughput[:throughput],
318
+ " events/sec",
319
+ ">#{target_throughput}",
320
+ passed_throughput
321
+ )
322
+
323
+ results[:total] += 1
324
+ passed_throughput ? results[:passed] += 1 : results[:failed] += 1
325
+
326
+ # 3. Memory Usage
327
+ puts "\n📊 Benchmark: Memory Usage (10K events)"
328
+ memory = measure_memory_usage(event_count: 10_000)
329
+
330
+ target_memory = TARGETS[scale][:memory_mb]
331
+ passed_memory = memory[:total_mb] <= target_memory
332
+
333
+ print_result(
334
+ "Memory Usage (10K events)",
335
+ memory[:total_mb],
336
+ " MB",
337
+ "<#{target_memory}",
338
+ passed_memory
339
+ )
340
+
341
+ results[:total] += 1
342
+ passed_memory ? results[:passed] += 1 : results[:failed] += 1
343
+
344
+ results
345
+ end
346
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
347
+
348
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
349
+ # Benchmark code prioritizes clarity and performance over complexity metrics
350
+ def run_large_scale_benchmark
351
+ scale = :large
352
+ print_header(scale)
353
+
354
+ setup_e11y(buffer_size: 100_000)
355
+
356
+ results = { total: 0, passed: 0, failed: 0 }
357
+
358
+ # 1. track() Latency
359
+ puts "📊 Benchmark: track() Latency (100K iterations)"
360
+ latency = measure_track_latency(
361
+ event_class: BenchmarkEvent,
362
+ count: 100_000,
363
+ scale_name: scale
364
+ )
365
+
366
+ target_p99 = TARGETS[scale][:track_latency_p99_us]
367
+ passed_p99 = latency[:p99] <= target_p99
368
+
369
+ puts " p50: #{latency[:p50].round(2)}μs"
370
+ puts " p99: #{latency[:p99].round(2)}μs (target: <#{target_p99}μs) #{passed_p99 ? '✅' : '❌'}"
371
+ puts " p999: #{latency[:p999].round(2)}μs"
372
+
373
+ results[:total] += 1
374
+ passed_p99 ? results[:passed] += 1 : results[:failed] += 1
375
+
376
+ # 2. Buffer Throughput
377
+ puts "\n📊 Benchmark: Buffer Throughput (10 seconds)"
378
+ throughput = measure_buffer_throughput(
379
+ event_class: SimpleBenchmarkEvent,
380
+ duration_sec: 10
381
+ )
382
+
383
+ target_throughput = TARGETS[scale][:buffer_throughput]
384
+ passed_throughput = throughput[:throughput] >= target_throughput
385
+
386
+ print_result(
387
+ "Buffer Throughput",
388
+ throughput[:throughput],
389
+ " events/sec",
390
+ ">#{target_throughput}",
391
+ passed_throughput
392
+ )
393
+
394
+ results[:total] += 1
395
+ passed_throughput ? results[:passed] += 1 : results[:failed] += 1
396
+
397
+ # 3. Memory Usage
398
+ puts "\n📊 Benchmark: Memory Usage (100K events)"
399
+ memory = measure_memory_usage(event_count: 100_000)
400
+
401
+ target_memory = TARGETS[scale][:memory_mb]
402
+ passed_memory = memory[:total_mb] <= target_memory
403
+
404
+ print_result(
405
+ "Memory Usage (100K events)",
406
+ memory[:total_mb],
407
+ " MB",
408
+ "<#{target_memory}",
409
+ passed_memory
410
+ )
411
+
412
+ results[:total] += 1
413
+ passed_memory ? results[:passed] += 1 : results[:failed] += 1
414
+
415
+ results
416
+ end
417
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
418
+
419
+ # ============================================================================
420
+ # Main Runner
421
+ # ============================================================================
422
+
423
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
424
+ # Benchmark code prioritizes clarity over complexity metrics
425
+ def main
426
+ puts "🚀 E11y Performance Benchmark Suite"
427
+ puts "ADR-001 §5: Performance Requirements"
428
+ puts "Ruby: #{RUBY_VERSION}"
429
+ puts ""
430
+
431
+ all_results = {}
432
+
433
+ all_results[:small] = run_small_scale_benchmark if SCALE == "all" || SCALE == "small"
434
+
435
+ all_results[:medium] = run_medium_scale_benchmark if SCALE == "all" || SCALE == "medium"
436
+
437
+ all_results[:large] = run_large_scale_benchmark if SCALE == "all" || SCALE == "large"
438
+
439
+ print_summary(all_results)
440
+
441
+ # Exit with error code if any benchmark failed
442
+ failed_count = all_results.values.sum { |r| r[:failed] }
443
+ exit(failed_count.positive? ? 1 : 0)
444
+ end
445
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
446
+
447
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+
6
+ # Ruby Baseline Allocation Measurement
7
+ #
8
+ # Purpose: Measure Ruby's theoretical minimum allocations
9
+ # to establish baseline for E11y comparison.
10
+ #
11
+ # Usage: ruby benchmarks/ruby_baseline_allocations.rb
12
+
13
+ # ============================================================================
14
+ # Helper Functions
15
+ # ============================================================================
16
+
17
+ def measure_allocations
18
+ before = GC.stat(:total_allocated_objects)
19
+ yield
20
+ after = GC.stat(:total_allocated_objects)
21
+ after - before
22
+ end
23
+
24
+ # ============================================================================
25
+ # Test Cases
26
+ # ============================================================================
27
+
28
+ puts "=" * 80
29
+ puts " Ruby Baseline Allocation Measurement"
30
+ puts "=" * 80
31
+ puts "Ruby: #{RUBY_VERSION}"
32
+ puts ""
33
+
34
+ # Warmup (initialize GC stats)
35
+ 100.times { {} }
36
+ GC.start
37
+
38
+ puts "📊 Test 1: Empty method call"
39
+ puts "-" * 80
40
+
41
+ class EmptyClass
42
+ def self.empty_method
43
+ # Nothing
44
+ end
45
+ end
46
+
47
+ 10.times { EmptyClass.empty_method } # warmup
48
+
49
+ allocations = measure_allocations do
50
+ 1000.times { EmptyClass.empty_method }
51
+ end
52
+
53
+ puts "Empty method × 1000: #{allocations} allocations"
54
+ puts "Per call: #{(allocations / 1000.0).round(2)} allocations"
55
+ puts ""
56
+
57
+ # ============================================================================
58
+
59
+ puts "📊 Test 2: Method with keyword arguments (Hash allocation)"
60
+ puts "-" * 80
61
+
62
+ class KwargsClass
63
+ def self.kwargs_method(**payload)
64
+ payload # return the hash
65
+ end
66
+ end
67
+
68
+ 10.times { KwargsClass.kwargs_method(key: "value") } # warmup
69
+
70
+ allocations = measure_allocations do
71
+ 1000.times { KwargsClass.kwargs_method(key: "value") }
72
+ end
73
+
74
+ puts "Kwargs method × 1000: #{allocations} allocations"
75
+ puts "Per call: #{(allocations / 1000.0).round(2)} allocations"
76
+ puts "Expected minimum: 2.0 allocations (1 Hash for kwargs, 1 Hash return)"
77
+ puts ""
78
+
79
+ # ============================================================================
80
+
81
+ puts "📊 Test 3: Method returning Hash (E11y pattern)"
82
+ puts "-" * 80
83
+
84
+ class HashReturnClass
85
+ def self.return_hash(**payload)
86
+ {
87
+ event_name: "TestEvent",
88
+ payload: payload,
89
+ timestamp: Time.now.utc.iso8601(3)
90
+ }
91
+ end
92
+ end
93
+
94
+ 10.times { HashReturnClass.return_hash(key: "value") } # warmup
95
+
96
+ allocations = measure_allocations do
97
+ 1000.times { HashReturnClass.return_hash(key: "value") }
98
+ end
99
+
100
+ puts "Hash return × 1000: #{allocations} allocations"
101
+ puts "Per call: #{(allocations / 1000.0).round(2)} allocations"
102
+ puts ""
103
+
104
+ # ============================================================================
105
+
106
+ puts "📊 Test 4: Method with Time.now.utc (timestamp overhead)"
107
+ puts "-" * 80
108
+
109
+ class TimestampClass
110
+ def self.with_timestamp(**payload)
111
+ {
112
+ payload: payload,
113
+ timestamp: Time.now.utc
114
+ }
115
+ end
116
+ end
117
+
118
+ 10.times { TimestampClass.with_timestamp(key: "value") } # warmup
119
+
120
+ allocations = measure_allocations do
121
+ 1000.times { TimestampClass.with_timestamp(key: "value") }
122
+ end
123
+
124
+ puts "With timestamp × 1000: #{allocations} allocations"
125
+ puts "Per call: #{(allocations / 1000.0).round(2)} allocations"
126
+ puts ""
127
+
128
+ # ============================================================================
129
+
130
+ puts "📊 Test 5: Method with iso8601(3) (string conversion)"
131
+ puts "-" * 80
132
+
133
+ class ISOTimestampClass
134
+ def self.with_iso_timestamp(**payload)
135
+ {
136
+ payload: payload,
137
+ timestamp: Time.now.utc.iso8601(3)
138
+ }
139
+ end
140
+ end
141
+
142
+ 10.times { ISOTimestampClass.with_iso_timestamp(key: "value") } # warmup
143
+
144
+ allocations = measure_allocations do
145
+ 1000.times { ISOTimestampClass.with_iso_timestamp(key: "value") }
146
+ end
147
+
148
+ puts "With ISO timestamp × 1000: #{allocations} allocations"
149
+ puts "Per call: #{(allocations / 1000.0).round(2)} allocations"
150
+ puts ""
151
+
152
+ # ============================================================================
153
+ # Summary
154
+ # ============================================================================
155
+
156
+ puts "=" * 80
157
+ puts " SUMMARY: Ruby Allocation Baseline"
158
+ puts "=" * 80
159
+ puts ""
160
+ puts "Key Findings:"
161
+ puts "1. Empty method call: ~0 allocations (baseline)"
162
+ puts "2. Kwargs method: 2-3 allocations (Hash for params + return)"
163
+ puts "3. Hash return: 4-5 allocations (kwargs + return Hash + nested payload)"
164
+ puts "4. With timestamp: 5-7 allocations (+ Time object)"
165
+ puts "5. With ISO string: 6-8 allocations (+ String conversion)"
166
+ puts ""
167
+ puts "Expected E11y minimum: 6-8 allocations per event"
168
+ puts "For 1K events: 6,000-8,000 allocations"
169
+ puts ""
170
+ puts "DoD target: <100 allocations per 1K events"
171
+ puts "Conclusion: ❌ IMPOSSIBLE (60-80x too low)"
172
+ puts ""
173
+ puts "Realistic target: <10,000 allocations per 1K events (10 per event)"
174
+ puts " or <10 allocations per event"
175
+ puts ""
@@ -6,28 +6,16 @@
6
6
  # Run all benchmarks:
7
7
  # bundle exec ruby benchmarks/run_all.rb
8
8
  #
9
- # Run with specific iterations:
10
- # ITERATIONS=10000 bundle exec ruby benchmarks/run_all.rb
9
+ # Run with specific scale:
10
+ # SCALE=small bundle exec ruby benchmarks/run_all.rb
11
+ # SCALE=medium bundle exec ruby benchmarks/run_all.rb
12
+ # SCALE=large bundle exec ruby benchmarks/run_all.rb
11
13
 
12
14
  require "bundler/setup"
13
- require "benchmark"
14
- require "e11y"
15
15
 
16
- ITERATIONS = (ENV["ITERATIONS"] || 1000).to_i
16
+ # Load main benchmark suite
17
+ load File.expand_path("e11y_benchmarks.rb", __dir__)
17
18
 
18
- puts "🚀 E11y Benchmarks (#{ITERATIONS} iterations)"
19
- puts "=" * 60
20
-
21
- # Benchmarks will be implemented in Phase 1+
22
- # Examples:
23
- # - Event creation (zero-allocation goal)
24
- # - Buffer operations (push/flush)
25
- # - Middleware chain execution
26
- # - Adapter send performance
27
-
28
- puts "\n⚠️ Benchmarks will be implemented in Phase 1+"
29
- puts "Expected metrics (from ADR-009):"
30
- puts " - Event creation: < 10µs per event (zero-allocation)"
31
- puts " - Buffer push: < 1µs (lock-free)"
32
- puts " - Middleware: < 5µs per middleware"
33
- puts " - Memory: < 100KB baseline, < 10MB under load"
19
+ puts "\n✅ All benchmarks completed"
20
+ puts "\nFor detailed benchmarks, run:"
21
+ puts " bundle exec ruby benchmarks/e11y_benchmarks.rb"
@@ -228,7 +228,7 @@
228
228
  - [ ] Migration guide (from Rails.logger, other gems)
229
229
  - [ ] Video tutorials (YouTube, Quick Start)
230
230
  - [ ] Blog posts (announcement, case studies)
231
- - [ ] RubyGems v1.0.0 release
231
+ - [ ] RubyGems v0.1.0 release
232
232
  - [ ] HackerNews / Reddit launch
233
233
 
234
234
  **Success Criteria:**
@@ -270,7 +270,7 @@
270
270
 
271
271
  ---
272
272
 
273
- ### v1.0.0 (End of Phase 5) - July 2025
273
+ ### v0.1.0 (End of Phase 5) - July 2025
274
274
  **Focus:** Production-grade, battle-tested
275
275
 
276
276
  **Polish:**
@@ -2167,7 +2167,7 @@ graph TB
2167
2167
  Version Format: MAJOR.MINOR.PATCH
2168
2168
 
2169
2169
  Examples:
2170
- - 1.0.0 - Initial release (all 22 use cases)
2170
+ - 0.1.0 - Initial release (all 22 use cases)
2171
2171
  - 1.1.0 - New adapter (backward compatible)
2172
2172
  - 1.0.1 - Bug fix
2173
2173
  - 2.0.0 - Breaking change (API change, Rails 9 support)