brainzlab 0.1.1 → 0.1.2
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/README.md +8 -0
- data/lib/brainzlab/beacon/client.rb +209 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +341 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +141 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +227 -0
- data/lib/brainzlab/dendrite/client.rb +232 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
- data/lib/brainzlab/devtools/assets/devtools.js +322 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +70 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/active_record.rb +18 -1
- data/lib/brainzlab/instrumentation/aws.rb +179 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/resque.rb +115 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
- data/lib/brainzlab/instrumentation/stripe.rb +164 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
- data/lib/brainzlab/instrumentation.rb +72 -0
- data/lib/brainzlab/nerve/client.rb +217 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/instrumentation.rb +35 -2
- data/lib/brainzlab/pulse/propagation.rb +1 -1
- data/lib/brainzlab/pulse/tracer.rb +1 -1
- data/lib/brainzlab/pulse.rb +1 -1
- data/lib/brainzlab/rails/log_subscriber.rb +1 -2
- data/lib/brainzlab/rails/railtie.rb +36 -3
- data/lib/brainzlab/recall/provisioner.rb +17 -0
- data/lib/brainzlab/recall.rb +6 -1
- data/lib/brainzlab/reflex.rb +2 -2
- data/lib/brainzlab/sentinel/client.rb +218 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +62 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +290 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
- data/lib/brainzlab/utilities/health_check.rb +296 -0
- data/lib/brainzlab/utilities/log_formatter.rb +256 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +198 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +268 -0
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +128 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +157 -0
- data/lib/brainzlab.rb +101 -0
- metadata +60 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainzLab DevTools - Stimulus Controller
|
|
3
|
+
*/
|
|
4
|
+
(function() {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// Load Stimulus if not available
|
|
8
|
+
let stimulusApp = null;
|
|
9
|
+
let StimulusController = null;
|
|
10
|
+
|
|
11
|
+
async function loadStimulus() {
|
|
12
|
+
if (stimulusApp) return { app: stimulusApp, Controller: StimulusController };
|
|
13
|
+
|
|
14
|
+
// Check if already loading
|
|
15
|
+
if (window._stimulusLoading) return window._stimulusLoading;
|
|
16
|
+
|
|
17
|
+
window._stimulusLoading = new Promise((resolve) => {
|
|
18
|
+
const script = document.createElement('script');
|
|
19
|
+
script.src = 'https://unpkg.com/@hotwired/stimulus@3.2.2/dist/stimulus.umd.js';
|
|
20
|
+
script.onload = () => {
|
|
21
|
+
StimulusController = window.Stimulus.Controller;
|
|
22
|
+
stimulusApp = window.Stimulus.Application.start();
|
|
23
|
+
resolve({ app: stimulusApp, Controller: StimulusController });
|
|
24
|
+
};
|
|
25
|
+
document.head.appendChild(script);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return window._stimulusLoading;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Stimulus Controller Definition
|
|
32
|
+
const DevtoolsController = {
|
|
33
|
+
static: {
|
|
34
|
+
targets: ['panel', 'tab', 'pane', 'toast']
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Lifecycle
|
|
38
|
+
connect() {
|
|
39
|
+
this.loadState();
|
|
40
|
+
this.bindKeyboardShortcuts();
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
disconnect() {
|
|
44
|
+
this.unbindKeyboardShortcuts();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Actions
|
|
48
|
+
togglePanel() {
|
|
49
|
+
this.element.classList.toggle('collapsed');
|
|
50
|
+
this.saveState();
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
switchTab(event) {
|
|
54
|
+
event.stopPropagation();
|
|
55
|
+
const tabName = event.currentTarget.dataset.tab;
|
|
56
|
+
|
|
57
|
+
// Update tab buttons
|
|
58
|
+
this.element.querySelectorAll('[data-devtools-target="tab"]').forEach(t => {
|
|
59
|
+
t.classList.toggle('active', t.dataset.tab === tabName);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Update content panes
|
|
63
|
+
this.element.querySelectorAll('[data-devtools-target="pane"]').forEach(p => {
|
|
64
|
+
p.classList.toggle('active', p.dataset.pane === tabName);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.saveState();
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
copyToAi(event) {
|
|
71
|
+
event.stopPropagation();
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
|
|
74
|
+
const button = event.currentTarget;
|
|
75
|
+
const issueType = button.dataset.issueType;
|
|
76
|
+
const prompt = this.buildPrompt(issueType, button);
|
|
77
|
+
|
|
78
|
+
this.copyToClipboard(prompt);
|
|
79
|
+
this.showToast('Copied to clipboard');
|
|
80
|
+
|
|
81
|
+
// Visual feedback
|
|
82
|
+
button.classList.add('copied');
|
|
83
|
+
setTimeout(() => button.classList.remove('copied'), 1500);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
copySql(event) {
|
|
87
|
+
const cell = event.currentTarget;
|
|
88
|
+
const sql = cell.getAttribute('title') || cell.textContent;
|
|
89
|
+
this.copyToClipboard(sql);
|
|
90
|
+
this.showToast('SQL copied');
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Prompt Builders
|
|
94
|
+
buildPrompt(issueType, button) {
|
|
95
|
+
const builders = {
|
|
96
|
+
n_plus_one: () => this.buildN1Prompt(button),
|
|
97
|
+
slow_query: () => this.buildSlowQueryPrompt(button),
|
|
98
|
+
too_many_queries: () => this.buildTooManyQueriesPrompt(button),
|
|
99
|
+
slow_view: () => this.buildSlowViewPrompt(button),
|
|
100
|
+
high_memory: () => this.buildHighMemoryPrompt(button)
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return builders[issueType]?.() || 'Unknown issue type';
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
buildN1Prompt(btn) {
|
|
107
|
+
const { n1Count, n1Duration, n1Source, n1Query, n1Pattern } = btn.dataset;
|
|
108
|
+
return `Fix this N+1 query issue in my Rails application:
|
|
109
|
+
|
|
110
|
+
## Problem
|
|
111
|
+
Detected ${n1Count || 'unknown'}x similar queries (${n1Duration || 'unknown'}ms total) from:
|
|
112
|
+
\`${n1Source || 'unknown location'}\`
|
|
113
|
+
|
|
114
|
+
## Sample Query
|
|
115
|
+
\`\`\`sql
|
|
116
|
+
${n1Query || ''}
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
## Pattern
|
|
120
|
+
\`\`\`
|
|
121
|
+
${n1Pattern || ''}
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
## Instructions
|
|
125
|
+
1. Find the source code at the location mentioned above
|
|
126
|
+
2. Identify why this query is being executed multiple times
|
|
127
|
+
3. Suggest a fix using eager loading (includes/preload/eager_load) or other optimization
|
|
128
|
+
4. Show me the before and after code`;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
buildSlowQueryPrompt(btn) {
|
|
132
|
+
const { queryDuration, querySql, querySource, queryName } = btn.dataset;
|
|
133
|
+
return `Optimize this slow database query in my Rails application:
|
|
134
|
+
|
|
135
|
+
## Problem
|
|
136
|
+
Query taking ${queryDuration || 'unknown'}ms (threshold: 100ms)
|
|
137
|
+
Source: \`${querySource || 'unknown location'}\`
|
|
138
|
+
Name: ${queryName || 'SQL'}
|
|
139
|
+
|
|
140
|
+
## Query
|
|
141
|
+
\`\`\`sql
|
|
142
|
+
${querySql || ''}
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
## Instructions
|
|
146
|
+
1. Analyze why this query might be slow
|
|
147
|
+
2. Check if there are missing database indexes
|
|
148
|
+
3. Suggest query optimizations or restructuring
|
|
149
|
+
4. If applicable, suggest caching strategies
|
|
150
|
+
5. Show me the recommended changes`;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
buildTooManyQueriesPrompt(btn) {
|
|
154
|
+
const { queryCount, controllerName, actionName } = btn.dataset;
|
|
155
|
+
return `Reduce the number of database queries in my Rails application:
|
|
156
|
+
|
|
157
|
+
## Problem
|
|
158
|
+
${queryCount || 'unknown'} queries executed in a single request (threshold: 20)
|
|
159
|
+
Controller: ${controllerName || 'unknown'}#${actionName || 'unknown'}
|
|
160
|
+
|
|
161
|
+
## Instructions
|
|
162
|
+
1. Look at the controller action and identify what data is being loaded
|
|
163
|
+
2. Find opportunities to use eager loading (includes/preload/eager_load)
|
|
164
|
+
3. Identify if any queries can be combined or eliminated
|
|
165
|
+
4. Check for queries inside loops (potential N+1)
|
|
166
|
+
5. Suggest caching for frequently accessed data
|
|
167
|
+
6. Show me the before and after code`;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
buildSlowViewPrompt(btn) {
|
|
171
|
+
const { viewDuration, viewTemplate, viewType } = btn.dataset;
|
|
172
|
+
return `Optimize this slow view render in my Rails application:
|
|
173
|
+
|
|
174
|
+
## Problem
|
|
175
|
+
${viewType || 'template'} taking ${viewDuration || 'unknown'}ms to render (threshold: 50ms)
|
|
176
|
+
Template: ${viewTemplate || 'unknown'}
|
|
177
|
+
|
|
178
|
+
## Instructions
|
|
179
|
+
1. Look at the template and identify expensive operations
|
|
180
|
+
2. Check for complex logic that should be moved to helpers or presenters
|
|
181
|
+
3. Look for database queries being made in the view
|
|
182
|
+
4. Identify if fragment caching could help
|
|
183
|
+
5. Check for unnecessary partial renders or loops
|
|
184
|
+
6. Suggest optimizations and show me the recommended changes`;
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
buildHighMemoryPrompt(btn) {
|
|
188
|
+
const { memoryDelta, memoryBefore, memoryAfter, controllerName, actionName } = btn.dataset;
|
|
189
|
+
return `Investigate high memory usage in my Rails application:
|
|
190
|
+
|
|
191
|
+
## Problem
|
|
192
|
+
Request allocated +${memoryDelta || 'unknown'}MB of memory (threshold: 50MB)
|
|
193
|
+
Memory before: ${memoryBefore || 'unknown'}MB
|
|
194
|
+
Memory after: ${memoryAfter || 'unknown'}MB
|
|
195
|
+
Controller: ${controllerName || 'unknown'}#${actionName || 'unknown'}
|
|
196
|
+
|
|
197
|
+
## Instructions
|
|
198
|
+
1. Look at the controller action and identify what data is being loaded
|
|
199
|
+
2. Check for loading large datasets into memory (use find_each/find_in_batches)
|
|
200
|
+
3. Look for creating many objects in loops
|
|
201
|
+
4. Check if large files or blobs are being processed
|
|
202
|
+
5. Identify if streaming responses could help
|
|
203
|
+
6. Suggest memory optimizations and show me the recommended changes`;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// Utilities
|
|
207
|
+
copyToClipboard(text) {
|
|
208
|
+
if (navigator.clipboard?.writeText) {
|
|
209
|
+
navigator.clipboard.writeText(text);
|
|
210
|
+
} else {
|
|
211
|
+
const textarea = document.createElement('textarea');
|
|
212
|
+
textarea.value = text;
|
|
213
|
+
textarea.style.cssText = 'position:fixed;opacity:0';
|
|
214
|
+
document.body.appendChild(textarea);
|
|
215
|
+
textarea.select();
|
|
216
|
+
document.execCommand('copy');
|
|
217
|
+
document.body.removeChild(textarea);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
showToast(message) {
|
|
222
|
+
// Remove existing toast
|
|
223
|
+
const existing = document.querySelector('.brainz-toast');
|
|
224
|
+
if (existing) existing.remove();
|
|
225
|
+
|
|
226
|
+
const toast = document.createElement('div');
|
|
227
|
+
toast.className = 'brainz-toast';
|
|
228
|
+
toast.textContent = message;
|
|
229
|
+
document.body.appendChild(toast);
|
|
230
|
+
|
|
231
|
+
requestAnimationFrame(() => toast.classList.add('visible'));
|
|
232
|
+
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
toast.classList.remove('visible');
|
|
235
|
+
setTimeout(() => toast.remove(), 200);
|
|
236
|
+
}, 2000);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// State Management
|
|
240
|
+
saveState() {
|
|
241
|
+
try {
|
|
242
|
+
const activeTab = this.element.querySelector('[data-devtools-target="tab"].active');
|
|
243
|
+
sessionStorage.setItem('brainz-devtools-state', JSON.stringify({
|
|
244
|
+
collapsed: this.element.classList.contains('collapsed'),
|
|
245
|
+
activeTab: activeTab?.dataset.tab || 'request'
|
|
246
|
+
}));
|
|
247
|
+
} catch (e) { /* ignore */ }
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
loadState() {
|
|
251
|
+
try {
|
|
252
|
+
const state = JSON.parse(sessionStorage.getItem('brainz-devtools-state'));
|
|
253
|
+
if (state?.collapsed) this.element.classList.add('collapsed');
|
|
254
|
+
if (state?.activeTab) {
|
|
255
|
+
const tab = this.element.querySelector(`[data-tab="${state.activeTab}"]`);
|
|
256
|
+
if (tab) tab.click();
|
|
257
|
+
}
|
|
258
|
+
} catch (e) { /* ignore */ }
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// Keyboard Shortcuts
|
|
262
|
+
bindKeyboardShortcuts() {
|
|
263
|
+
this._keyHandler = (e) => {
|
|
264
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'B') {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
this.togglePanel();
|
|
267
|
+
}
|
|
268
|
+
if (e.key === 'Escape' && !this.element.classList.contains('collapsed')) {
|
|
269
|
+
this.togglePanel();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
document.addEventListener('keydown', this._keyHandler);
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
unbindKeyboardShortcuts() {
|
|
276
|
+
if (this._keyHandler) {
|
|
277
|
+
document.removeEventListener('keydown', this._keyHandler);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Register Stimulus controller
|
|
283
|
+
async function initialize() {
|
|
284
|
+
const panel = document.querySelector('.brainz-debug-panel');
|
|
285
|
+
if (!panel) return;
|
|
286
|
+
|
|
287
|
+
// Load Stimulus and register controller
|
|
288
|
+
const { app, Controller } = await loadStimulus();
|
|
289
|
+
|
|
290
|
+
// Register the devtools controller
|
|
291
|
+
app.register('devtools', class extends Controller {
|
|
292
|
+
static targets = ['tab', 'pane'];
|
|
293
|
+
|
|
294
|
+
connect() { DevtoolsController.connect.call(this); }
|
|
295
|
+
disconnect() { DevtoolsController.disconnect.call(this); }
|
|
296
|
+
togglePanel() { DevtoolsController.togglePanel.call(this); }
|
|
297
|
+
switchTab(e) { DevtoolsController.switchTab.call(this, e); }
|
|
298
|
+
copyToAi(e) { DevtoolsController.copyToAi.call(this, e); }
|
|
299
|
+
copySql(e) { DevtoolsController.copySql.call(this, e); }
|
|
300
|
+
|
|
301
|
+
buildPrompt(...args) { return DevtoolsController.buildPrompt.call(this, ...args); }
|
|
302
|
+
buildN1Prompt(...args) { return DevtoolsController.buildN1Prompt.call(this, ...args); }
|
|
303
|
+
buildSlowQueryPrompt(...args) { return DevtoolsController.buildSlowQueryPrompt.call(this, ...args); }
|
|
304
|
+
buildTooManyQueriesPrompt(...args) { return DevtoolsController.buildTooManyQueriesPrompt.call(this, ...args); }
|
|
305
|
+
buildSlowViewPrompt(...args) { return DevtoolsController.buildSlowViewPrompt.call(this, ...args); }
|
|
306
|
+
buildHighMemoryPrompt(...args) { return DevtoolsController.buildHighMemoryPrompt.call(this, ...args); }
|
|
307
|
+
copyToClipboard(...args) { return DevtoolsController.copyToClipboard.call(this, ...args); }
|
|
308
|
+
showToast(...args) { return DevtoolsController.showToast.call(this, ...args); }
|
|
309
|
+
saveState() { DevtoolsController.saveState.call(this); }
|
|
310
|
+
loadState() { DevtoolsController.loadState.call(this); }
|
|
311
|
+
bindKeyboardShortcuts() { DevtoolsController.bindKeyboardShortcuts.call(this); }
|
|
312
|
+
unbindKeyboardShortcuts() { DevtoolsController.unbindKeyboardShortcuts.call(this); }
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Initialize on DOM ready
|
|
317
|
+
if (document.readyState === 'loading') {
|
|
318
|
+
document.addEventListener('DOMContentLoaded', initialize);
|
|
319
|
+
} else {
|
|
320
|
+
initialize();
|
|
321
|
+
}
|
|
322
|
+
})();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="32" height="32" rx="8" fill="#FF6B35"/>
|
|
3
|
+
<path d="M8 12C8 10.8954 8.89543 10 10 10H22C23.1046 10 24 10.8954 24 12V13C24 13.5523 23.5523 14 23 14H9C8.44772 14 8 13.5523 8 13V12Z" fill="white"/>
|
|
4
|
+
<path d="M8 17C8 16.4477 8.44772 16 9 16H23C23.5523 16 24 16.4477 24 17V18C24 18.5523 23.5523 19 23 19H9C8.44772 19 8 18.5523 8 18V17Z" fill="white" fill-opacity="0.8"/>
|
|
5
|
+
<path d="M8 22C8 21.4477 8.44772 21 9 21H18C18.5523 21 19 21.4477 19 22C19 22.5523 18.5523 23 18 23H9C8.44772 23 8 22.5523 8 22Z" fill="white" fill-opacity="0.6"/>
|
|
6
|
+
</svg>
|