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 +7 -0
- data/CLAUDE.md +144 -0
- data/IMPLEMENTATION_PLAN.md +370 -0
- data/Rakefile +8 -0
- data/brainzlab-rails.gemspec +42 -0
- data/lib/brainzlab/rails/analyzers/cache_efficiency.rb +123 -0
- data/lib/brainzlab/rails/analyzers/n_plus_one_detector.rb +90 -0
- data/lib/brainzlab/rails/analyzers/slow_query_analyzer.rb +118 -0
- data/lib/brainzlab/rails/collectors/action_cable.rb +212 -0
- data/lib/brainzlab/rails/collectors/action_controller.rb +299 -0
- data/lib/brainzlab/rails/collectors/action_mailer.rb +187 -0
- data/lib/brainzlab/rails/collectors/action_view.rb +176 -0
- data/lib/brainzlab/rails/collectors/active_job.rb +374 -0
- data/lib/brainzlab/rails/collectors/active_record.rb +250 -0
- data/lib/brainzlab/rails/collectors/active_storage.rb +306 -0
- data/lib/brainzlab/rails/collectors/base.rb +129 -0
- data/lib/brainzlab/rails/collectors/cache.rb +384 -0
- data/lib/brainzlab/rails/configuration.rb +121 -0
- data/lib/brainzlab/rails/event_router.rb +67 -0
- data/lib/brainzlab/rails/railtie.rb +98 -0
- data/lib/brainzlab/rails/subscriber.rb +164 -0
- data/lib/brainzlab/rails/version.rb +7 -0
- data/lib/brainzlab-rails.rb +72 -0
- metadata +178 -0
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,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
|