brainzlab-rails 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7ef611a0687f342dd6ae9cbf4b556ed91a8d8c66a0b38654896e841989ef1976
4
+ data.tar.gz: 3947b85d71ddfd34125ead8424b7ff15c97ca00c365204f3dd572aa072faee0c
5
+ SHA512:
6
+ metadata.gz: 4d83e3d7ede72cd4e5fe53f3482c99f589b1e2125885dc3d5e1b23bce56a926c488a9f0daf9efa01cd27b1939392581954458b3da4c8832a0148f99d29bf07ef
7
+ data.tar.gz: 45d5f5a44d18119f68472dfe3f87d3b6665f6763a07f7aa94fd8fdc91e44e0cb3efe07540686d17d6e0f3b8004feb49b42af7e78433337c61098e6e7289b5856
data/CLAUDE.md ADDED
@@ -0,0 +1,144 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project: BrainzLab Rails Instrumentation
6
+
7
+ Rails-native observability powered by ActiveSupport::Notifications. This gem hooks into ALL Rails instrumentation events and routes them intelligently to BrainzLab products.
8
+
9
+ **Gem**: brainzlab-rails (on RubyGems.org)
10
+
11
+ **GitHub**: brainz-lab/brainzlab-rails
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌─────────────────────────────────────────────────────────────────┐
17
+ │ BRAINZLAB-RAILS GEM │
18
+ │ │
19
+ │ ┌──────────────────────────────────────────────────────────┐ │
20
+ │ │ ActiveSupport::Notifications │ │
21
+ │ │ (monotonic_subscribe for accurate timing) │ │
22
+ │ └──────────────────────────────────────────────────────────┘ │
23
+ │ │ │
24
+ │ ▼ │
25
+ │ ┌──────────────────────────────────────────────────────────┐ │
26
+ │ │ Event Router │ │
27
+ │ │ Routes events to appropriate products │ │
28
+ │ └──────────────────────────────────────────────────────────┘ │
29
+ │ │ │
30
+ │ ┌─────────────────────┼─────────────────────┐ │
31
+ │ │ │ │ │
32
+ │ ▼ ▼ ▼ │
33
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
34
+ │ │Collectors│ │Collectors│ │Analyzers │ │
35
+ │ │ AC, AV │ │ AR, AJ │ │ N+1, │ │
36
+ │ │ Cable │ │ Mailer │ │SlowQuery │ │
37
+ │ └──────────┘ └──────────┘ └──────────┘ │
38
+ │ │ │
39
+ │ ▼ │
40
+ │ ┌──────────────────────────────────────────────────────────┐ │
41
+ │ │ BrainzLab SDK │ │
42
+ │ │ Pulse • Recall • Reflex • Flux │ │
43
+ │ └──────────────────────────────────────────────────────────┘ │
44
+ └─────────────────────────────────────────────────────────────────┘
45
+ ```
46
+
47
+ ## Directory Structure
48
+
49
+ ```
50
+ lib/
51
+ ├── brainzlab-rails.rb # Main entry point
52
+ └── brainzlab/
53
+ └── rails/
54
+ ├── version.rb
55
+ ├── configuration.rb # Gem configuration
56
+ ├── subscriber.rb # Event subscription (monotonic)
57
+ ├── event_router.rb # Routes events to collectors
58
+ ├── railtie.rb # Rails auto-initialization
59
+ ├── collectors/ # Event processors
60
+ │ ├── base.rb
61
+ │ ├── action_controller.rb
62
+ │ ├── action_view.rb
63
+ │ ├── active_record.rb
64
+ │ ├── active_job.rb
65
+ │ ├── action_cable.rb
66
+ │ ├── action_mailer.rb
67
+ │ ├── active_storage.rb
68
+ │ └── cache.rb
69
+ └── analyzers/ # Intelligent analysis
70
+ ├── n_plus_one_detector.rb
71
+ ├── slow_query_analyzer.rb
72
+ └── cache_efficiency.rb
73
+ ```
74
+
75
+ ## Key Features
76
+
77
+ ### 1. Monotonic Subscribe
78
+ Uses `ActiveSupport::Notifications.monotonic_subscribe` for accurate timing instead of wall-clock time.
79
+
80
+ ### 2. Smart Event Routing
81
+ Each Rails event is routed to appropriate products:
82
+ - **Pulse**: APM spans for all performance-critical events
83
+ - **Recall**: Structured logs for requests, jobs, emails
84
+ - **Reflex**: Breadcrumbs and error context
85
+ - **Flux**: Metrics (counters, histograms, timing)
86
+ - **Nerve**: Job-specific monitoring
87
+
88
+ ### 3. Built-in Analyzers
89
+ - **N+1 Detection**: Automatically detects repeated queries
90
+ - **Slow Query Analyzer**: Identifies slow queries with suggestions
91
+ - **Cache Efficiency**: Tracks hit rates and efficiency
92
+
93
+ ## Rails Events Covered
94
+
95
+ | Component | Events |
96
+ |-----------|--------|
97
+ | Action Controller | 12 events |
98
+ | Action View | 4 events |
99
+ | Active Record | 5 events |
100
+ | Active Job | 8 events |
101
+ | Action Cable | 5 events |
102
+ | Action Mailer | 3 events |
103
+ | Active Storage | 12 events |
104
+ | Cache | 15 events |
105
+ | **Total** | **64+ events** |
106
+
107
+ ## Usage
108
+
109
+ ```ruby
110
+ # Gemfile
111
+ gem 'brainzlab-rails'
112
+
113
+ # config/initializers/brainzlab.rb
114
+ BrainzLab.configure do |config|
115
+ config.secret_key = ENV['BRAINZLAB_SECRET_KEY']
116
+ end
117
+
118
+ # That's it! Auto-starts via Railtie
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ ```ruby
124
+ # config/application.rb
125
+ config.brainzlab_rails.n_plus_one_detection = true
126
+ config.brainzlab_rails.slow_query_threshold_ms = 100
127
+ config.brainzlab_rails.sample_rate = 1.0
128
+ config.brainzlab_rails.ignored_actions = ['HealthController#check']
129
+ ```
130
+
131
+ ## Common Commands
132
+
133
+ ```bash
134
+ bundle install
135
+ bundle exec rspec
136
+ gem build brainzlab-rails.gemspec
137
+ gem push brainzlab-rails-*.gem
138
+ ```
139
+
140
+ ## Dependencies
141
+
142
+ - `brainzlab` gem (>= 0.1.4)
143
+ - `rails` (>= 7.0)
144
+ - `activesupport` (>= 7.0)
@@ -0,0 +1,370 @@
1
+ # BrainzLab Rails Instrumentation - Implementation Plan
2
+
3
+ ## Overview
4
+
5
+ This document outlines the complete implementation plan for the `brainzlab-rails` gem and required enhancements to the BrainzLab ecosystem.
6
+
7
+ ## Current State
8
+
9
+ ### Completed (Phase 1)
10
+
11
+ - [x] **Gem Structure** - Created `brainzlab-rails` gem with proper gemspec
12
+ - [x] **Subscriber** - Implemented `monotonic_subscribe` for accurate timing
13
+ - [x] **Event Router** - Routes 64+ Rails events to appropriate products
14
+ - [x] **Collectors** - All 8 collectors implemented:
15
+ - ActionController (12 events)
16
+ - ActionView (4 events)
17
+ - ActiveRecord (5 events)
18
+ - ActiveJob (8 events)
19
+ - ActionCable (5 events)
20
+ - ActionMailer (3 events)
21
+ - ActiveStorage (12 events)
22
+ - Cache (15 events)
23
+ - [x] **Analyzers** - Three intelligent analyzers:
24
+ - N+1 Query Detector
25
+ - Slow Query Analyzer
26
+ - Cache Efficiency Tracker
27
+ - [x] **Railtie** - Auto-initialization in Rails apps
28
+
29
+ ---
30
+
31
+ ## Phase 2: SDK Enhancements
32
+
33
+ ### 2.1 Enhanced Pulse Methods
34
+
35
+ Add specialized APM methods to the BrainzLab SDK:
36
+
37
+ ```ruby
38
+ # lib/brainzlab/pulse.rb
39
+ module BrainzLab
40
+ module Pulse
41
+ class << self
42
+ # Record a span with structured attributes
43
+ def record_span(name:, duration_ms:, category:, attributes: {}, timestamp: nil)
44
+ # Implementation
45
+ end
46
+
47
+ # Start a trace context
48
+ def start_trace(name, attributes = {})
49
+ # Returns trace context
50
+ end
51
+
52
+ # End current trace
53
+ def end_trace(trace_context)
54
+ # Finalizes and sends trace
55
+ end
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ **Files to modify:**
62
+ - `lib/brainzlab/pulse.rb` (add `record_span`, `start_trace`, `end_trace`)
63
+ - `lib/brainzlab/pulse/span.rb` (new - span data structure)
64
+ - `lib/brainzlab/pulse/trace.rb` (new - trace context)
65
+
66
+ ### 2.2 Enhanced Flux Methods
67
+
68
+ Add metric methods to the SDK:
69
+
70
+ ```ruby
71
+ # lib/brainzlab/flux.rb
72
+ module BrainzLab
73
+ module Flux
74
+ class << self
75
+ def increment(metric, value = 1, tags: {})
76
+ def gauge(metric, value, tags: {})
77
+ def histogram(metric, value, tags: {})
78
+ def timing(metric, value_ms, tags: {})
79
+ end
80
+ end
81
+ end
82
+ ```
83
+
84
+ **Files to modify:**
85
+ - `lib/brainzlab/flux.rb` (new module)
86
+ - `lib/brainzlab/flux/client.rb` (metrics client)
87
+ - `lib/brainzlab/flux/buffer.rb` (metric batching)
88
+
89
+ ### 2.3 Configuration Updates
90
+
91
+ ```ruby
92
+ # lib/brainzlab/configuration.rb
93
+ # Add:
94
+ attr_accessor :flux_enabled
95
+ attr_accessor :flux_url
96
+ attr_accessor :flux_api_key
97
+
98
+ def flux_effectively_enabled?
99
+ flux_enabled && flux_url.present?
100
+ end
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Phase 3: Server-Side Enhancements
106
+
107
+ ### 3.1 Pulse Dashboard Updates
108
+
109
+ The Pulse server needs to handle structured span data:
110
+
111
+ **New API Endpoints:**
112
+ ```
113
+ POST /api/v1/spans - Ingest spans with attributes
114
+ GET /api/v1/traces/:id - Get trace with spans
115
+ GET /api/v1/requests - Request list with breakdown
116
+ ```
117
+
118
+ **Database Schema:**
119
+ ```ruby
120
+ create_table :spans do |t|
121
+ t.references :trace, null: false
122
+ t.string :name, null: false
123
+ t.string :category
124
+ t.float :duration_ms
125
+ t.jsonb :attributes, default: {}
126
+ t.datetime :started_at
127
+ t.datetime :finished_at
128
+ t.timestamps
129
+ end
130
+
131
+ create_table :traces do |t|
132
+ t.references :project, null: false
133
+ t.string :trace_id, null: false
134
+ t.string :name
135
+ t.float :total_duration_ms
136
+ t.jsonb :metadata, default: {}
137
+ t.timestamps
138
+ end
139
+ ```
140
+
141
+ **Dashboard Features:**
142
+ - Request waterfall (like Chrome DevTools)
143
+ - SQL query breakdown with timing
144
+ - View rendering breakdown
145
+ - N+1 alerts
146
+ - Slow query highlighting
147
+
148
+ ### 3.2 Flux Product (New)
149
+
150
+ If Flux doesn't exist, create it:
151
+
152
+ **Key Features:**
153
+ - Time-series metrics storage (TimescaleDB)
154
+ - Real-time dashboards
155
+ - Custom metric definitions
156
+ - Anomaly detection
157
+ - Alerting integration
158
+
159
+ **Models:**
160
+ ```ruby
161
+ # Metric definition
162
+ class Metric < ApplicationRecord
163
+ belongs_to :project
164
+ has_many :data_points
165
+ end
166
+
167
+ # Time-series data (TimescaleDB hypertable)
168
+ class DataPoint < ApplicationRecord
169
+ belongs_to :metric
170
+ # time, value, tags (jsonb)
171
+ end
172
+ ```
173
+
174
+ ### 3.3 Recall Enhancements
175
+
176
+ **Structured Log Filtering:**
177
+ - Filter by Rails event type
178
+ - Filter by controller/action
179
+ - Filter by job class
180
+ - Search by payload attributes
181
+
182
+ **UI Updates:**
183
+ - Event type badges
184
+ - Expandable payload viewer
185
+ - Request correlation view
186
+
187
+ ### 3.4 Reflex Enhancements
188
+
189
+ **Breadcrumb Improvements:**
190
+ - Group by category (db, http, cache, job)
191
+ - Timeline visualization
192
+ - SQL queries before error
193
+ - Cache state at error time
194
+
195
+ **Error Context:**
196
+ - Attach request breakdown
197
+ - Show N+1 warnings
198
+ - Include slow queries
199
+
200
+ ---
201
+
202
+ ## Phase 4: Advanced Features
203
+
204
+ ### 4.1 N+1 Query Dashboard
205
+
206
+ Dedicated view for N+1 detection:
207
+
208
+ ```
209
+ /dashboard/performance/n-plus-one
210
+
211
+ Features:
212
+ - List of detected N+1 patterns
213
+ - Query frequency
214
+ - Affected controllers/actions
215
+ - Fix suggestions (eager loading)
216
+ - Track resolution status
217
+ ```
218
+
219
+ ### 4.2 Slow Query Analysis
220
+
221
+ ```
222
+ /dashboard/performance/slow-queries
223
+
224
+ Features:
225
+ - Slow query log with EXPLAIN
226
+ - Index recommendations
227
+ - Query patterns
228
+ - Performance trends
229
+ ```
230
+
231
+ ### 4.3 Cache Efficiency Dashboard
232
+
233
+ ```
234
+ /dashboard/performance/cache
235
+
236
+ Features:
237
+ - Overall hit rate
238
+ - Per-key statistics
239
+ - Miss patterns
240
+ - TTL analysis
241
+ - Size tracking
242
+ ```
243
+
244
+ ### 4.4 Action Cable Monitor (Unique Differentiator)
245
+
246
+ ```
247
+ /dashboard/websockets
248
+
249
+ Features:
250
+ - Active connections
251
+ - Subscription health
252
+ - Broadcast latency
253
+ - Message throughput
254
+ - Channel performance
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Phase 5: Testing & Documentation
260
+
261
+ ### 5.1 Test Suite
262
+
263
+ ```ruby
264
+ # spec/brainzlab/rails/subscriber_spec.rb
265
+ # spec/brainzlab/rails/event_router_spec.rb
266
+ # spec/brainzlab/rails/collectors/*_spec.rb
267
+ # spec/brainzlab/rails/analyzers/*_spec.rb
268
+ ```
269
+
270
+ ### 5.2 Integration Tests
271
+
272
+ ```ruby
273
+ # Test with real Rails app
274
+ # Verify all 64+ events are captured
275
+ # Verify accurate timing with monotonic_subscribe
276
+ # Test sampling
277
+ # Test filtering
278
+ ```
279
+
280
+ ### 5.3 Documentation
281
+
282
+ - README with quick start
283
+ - Configuration reference
284
+ - Event reference (all 64+ events)
285
+ - Dashboard usage guide
286
+ - Troubleshooting guide
287
+
288
+ ---
289
+
290
+ ## Implementation Priority
291
+
292
+ ### Week 1: Core SDK
293
+ 1. [ ] Add `Pulse.record_span` to SDK
294
+ 2. [ ] Add `Flux` module to SDK
295
+ 3. [ ] Update SDK configuration
296
+ 4. [ ] Publish SDK v0.1.5
297
+
298
+ ### Week 2: Gem Polish
299
+ 1. [ ] Add comprehensive tests
300
+ 2. [ ] Validate all event handlers
301
+ 3. [ ] Performance benchmarking
302
+ 4. [ ] Publish gem v0.1.0
303
+
304
+ ### Week 3: Pulse Updates
305
+ 1. [ ] Span ingestion API
306
+ 2. [ ] Trace aggregation
307
+ 3. [ ] Request waterfall UI
308
+ 4. [ ] N+1 detection UI
309
+
310
+ ### Week 4: Additional Products
311
+ 1. [ ] Flux metric ingestion
312
+ 2. [ ] Recall structured logs UI
313
+ 3. [ ] Reflex breadcrumb improvements
314
+ 4. [ ] Action Cable dashboard
315
+
316
+ ---
317
+
318
+ ## Event → Product Matrix
319
+
320
+ | Rails Event | Pulse | Recall | Reflex | Flux | Nerve |
321
+ |-------------|-------|--------|--------|------|-------|
322
+ | process_action.action_controller | ✓ | ✓ | ✓* | ✓ | |
323
+ | sql.active_record | ✓ | | | ✓ | |
324
+ | render_*.action_view | ✓ | | | ✓ | |
325
+ | *.active_job | ✓ | ✓ | ✓* | ✓ | ✓ |
326
+ | *.action_cable | ✓ | ✓ | ✓* | ✓ | |
327
+ | cache_*.active_support | ✓ | | | ✓ | |
328
+ | deliver.action_mailer | ✓ | ✓ | ✓* | ✓ | |
329
+ | service_*.active_storage | ✓ | ✓ | | ✓ | |
330
+
331
+ *✓ = Only when exception in payload*
332
+
333
+ ---
334
+
335
+ ## Success Metrics
336
+
337
+ 1. **Coverage**: 100% of Rails instrumentation events captured
338
+ 2. **Accuracy**: < 1ms timing variance vs wall-clock
339
+ 3. **Performance**: < 5% overhead in production
340
+ 4. **Adoption**: Zero-config installation via Railtie
341
+ 5. **Insights**: Automatic N+1 detection, slow query analysis
342
+
343
+ ---
344
+
345
+ ## Competitive Advantage
346
+
347
+ ```
348
+ ┌─────────────────────────────────────────────────────────────────┐
349
+ │ │
350
+ │ THEM (Datadog/New Relic): │
351
+ │ - Generic agents that monkey-patch │
352
+ │ - "HTTP 200 in 234ms" │
353
+ │ - Separate products, separate billing │
354
+ │ │
355
+ │ US (BrainzLab): │
356
+ │ - Native Rails via ActiveSupport::Notifications │
357
+ │ - "PostsController#index: 4 queries (N+1!), 3 cache hits" │
358
+ │ - One gem, all products, Rails-optimized │
359
+ │ │
360
+ └─────────────────────────────────────────────────────────────────┘
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Next Steps
366
+
367
+ 1. **Immediate**: Add Pulse.record_span to SDK
368
+ 2. **This Week**: Publish brainzlab-rails gem v0.1.0
369
+ 3. **Next Week**: Update Pulse dashboard for spans
370
+ 4. **Future**: Build Action Cable dashboard (differentiator)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/brainzlab/rails/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'brainzlab-rails'
7
+ spec.version = BrainzLab::Rails::VERSION
8
+ spec.authors = ['Brainz Lab']
9
+ spec.email = ['support@brainzlab.ai']
10
+
11
+ spec.summary = 'Rails-native observability powered by ActiveSupport::Notifications'
12
+ spec.description = 'Deep Rails instrumentation that routes events to Pulse (APM), Recall (Logs), Reflex (Errors), Flux (Metrics), and Nerve (Jobs). One gem, full Rails observability.'
13
+ spec.homepage = 'https://brainzlab.ai'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.1.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/brainz-lab/brainzlab-rails'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/brainz-lab/brainzlab-rails/blob/main/CHANGELOG.md'
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
25
+ end
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ # Dependencies
32
+ spec.add_dependency 'brainzlab', '>= 0.1.6'
33
+ spec.add_dependency 'rails', '>= 7.0'
34
+ spec.add_dependency 'activesupport', '>= 7.0'
35
+
36
+ # Development dependencies
37
+ spec.add_development_dependency 'bundler', '~> 2.0'
38
+ spec.add_development_dependency 'rake', '~> 13.0'
39
+ spec.add_development_dependency 'rspec', '~> 3.0'
40
+ spec.add_development_dependency 'rspec-rails', '~> 6.0'
41
+ spec.add_development_dependency 'rubocop', '~> 1.21'
42
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Rails
5
+ module Analyzers
6
+ # Tracks cache efficiency metrics and provides insights
7
+ class CacheEfficiency
8
+ WINDOW_SIZE = 1000 # Rolling window for efficiency calculation
9
+
10
+ def initialize
11
+ @hits = 0
12
+ @misses = 0
13
+ @reads = []
14
+ @writes = 0
15
+ @generates = 0
16
+ end
17
+
18
+ def track(event_data)
19
+ case event_data[:name]
20
+ when 'cache_read.active_support'
21
+ track_read(event_data)
22
+ when 'cache_read_multi.active_support'
23
+ track_read_multi(event_data)
24
+ when 'cache_write.active_support', 'cache_write_multi.active_support'
25
+ @writes += 1
26
+ when 'cache_generate.active_support'
27
+ @generates += 1
28
+ when 'cache_fetch_hit.active_support'
29
+ @hits += 1
30
+ end
31
+
32
+ # Trim old reads to maintain window
33
+ trim_reads if @reads.size > WINDOW_SIZE * 2
34
+ end
35
+
36
+ def hit_rate
37
+ total = @hits + @misses
38
+ return 0.0 if total == 0
39
+
40
+ (@hits.to_f / total * 100).round(2)
41
+ end
42
+
43
+ def efficiency_report
44
+ {
45
+ hit_rate: hit_rate,
46
+ total_hits: @hits,
47
+ total_misses: @misses,
48
+ total_writes: @writes,
49
+ total_generates: @generates,
50
+ recent_reads: recent_reads_stats
51
+ }
52
+ end
53
+
54
+ def reset!
55
+ @hits = 0
56
+ @misses = 0
57
+ @reads = []
58
+ @writes = 0
59
+ @generates = 0
60
+ end
61
+
62
+ private
63
+
64
+ def track_read(event_data)
65
+ payload = event_data[:payload]
66
+ hit = payload[:hit]
67
+
68
+ if hit
69
+ @hits += 1
70
+ else
71
+ @misses += 1
72
+ end
73
+
74
+ @reads << {
75
+ key: payload[:key],
76
+ hit: hit,
77
+ duration_ms: event_data[:duration_ms],
78
+ timestamp: Time.now
79
+ }
80
+ end
81
+
82
+ def track_read_multi(event_data)
83
+ payload = event_data[:payload]
84
+ keys = payload[:key] || []
85
+ hits = payload[:hits] || []
86
+
87
+ @hits += hits.size
88
+ @misses += (keys.size - hits.size)
89
+
90
+ @reads << {
91
+ key: "multi:#{keys.size}",
92
+ hit: hits.size == keys.size,
93
+ duration_ms: event_data[:duration_ms],
94
+ timestamp: Time.now,
95
+ multi: true,
96
+ hit_count: hits.size,
97
+ total_count: keys.size
98
+ }
99
+ end
100
+
101
+ def recent_reads_stats
102
+ return {} if @reads.empty?
103
+
104
+ recent = @reads.last(100)
105
+ hits = recent.count { |r| r[:hit] }
106
+ misses = recent.size - hits
107
+
108
+ {
109
+ count: recent.size,
110
+ hits: hits,
111
+ misses: misses,
112
+ hit_rate: (hits.to_f / recent.size * 100).round(2),
113
+ avg_duration_ms: (recent.sum { |r| r[:duration_ms] } / recent.size).round(3)
114
+ }
115
+ end
116
+
117
+ def trim_reads
118
+ @reads = @reads.last(WINDOW_SIZE)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end