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,500 @@
|
|
|
1
|
+
<div class="brainz-debug-panel<%= ' collapsed' unless @expand_by_default %>" data-controller="devtools">
|
|
2
|
+
<%# Calculate issues count and performance score %>
|
|
3
|
+
<%-
|
|
4
|
+
issues_count = 0
|
|
5
|
+
n_plus_ones = @database[:n_plus_ones] || []
|
|
6
|
+
slow_queries = (@database[:queries] || []).select { |q| (q[:duration] || 0) > 100 }
|
|
7
|
+
slow_views = (@views[:templates] || []).select { |v| (v[:duration] || 0) > 50 }
|
|
8
|
+
query_count = @database[:total_count] || 0
|
|
9
|
+
memory_delta = @memory[:delta_mb] || 0
|
|
10
|
+
|
|
11
|
+
issues_count += n_plus_ones.length
|
|
12
|
+
issues_count += slow_queries.length
|
|
13
|
+
issues_count += slow_views.length
|
|
14
|
+
issues_count += 1 if query_count > 20
|
|
15
|
+
issues_count += 1 if memory_delta > 50
|
|
16
|
+
|
|
17
|
+
# Calculate performance score (0-100)
|
|
18
|
+
score = 100
|
|
19
|
+
duration = @timing[:duration_ms] || 0
|
|
20
|
+
|
|
21
|
+
# Response time penalties
|
|
22
|
+
score -= 10 if duration > 500
|
|
23
|
+
score -= 10 if duration > 1000
|
|
24
|
+
score -= 10 if duration > 2000
|
|
25
|
+
|
|
26
|
+
# N+1 queries (major issue)
|
|
27
|
+
score -= n_plus_ones.length * 15
|
|
28
|
+
|
|
29
|
+
# Slow queries
|
|
30
|
+
score -= slow_queries.length * 5
|
|
31
|
+
|
|
32
|
+
# Query count penalties
|
|
33
|
+
score -= 10 if query_count > 20
|
|
34
|
+
score -= 10 if query_count > 50
|
|
35
|
+
|
|
36
|
+
# Memory penalties
|
|
37
|
+
score -= 10 if memory_delta > 50
|
|
38
|
+
score -= 10 if memory_delta > 100
|
|
39
|
+
|
|
40
|
+
# Slow views
|
|
41
|
+
score -= slow_views.length * 5
|
|
42
|
+
|
|
43
|
+
# Clamp to 0-100
|
|
44
|
+
score = [[score, 0].max, 100].min
|
|
45
|
+
|
|
46
|
+
# Score grade
|
|
47
|
+
score_grade = case score
|
|
48
|
+
when 90..100 then 'excellent'
|
|
49
|
+
when 70..89 then 'good'
|
|
50
|
+
when 50..69 then 'warning'
|
|
51
|
+
else 'poor'
|
|
52
|
+
end
|
|
53
|
+
-%>
|
|
54
|
+
|
|
55
|
+
<!-- Toolbar (always visible) -->
|
|
56
|
+
<div class="brainz-debug-toolbar" data-action="click->devtools#togglePanel">
|
|
57
|
+
<img src="<%= asset_url('logo.svg') %>" alt="BrainzLab" class="brainz-debug-logo">
|
|
58
|
+
|
|
59
|
+
<!-- Performance Score -->
|
|
60
|
+
<div class="brainz-score <%= score_grade %>">
|
|
61
|
+
<svg class="brainz-score-ring" viewBox="0 0 36 36">
|
|
62
|
+
<circle class="brainz-score-bg" cx="18" cy="18" r="16" fill="none" stroke-width="3"/>
|
|
63
|
+
<circle class="brainz-score-progress" cx="18" cy="18" r="16" fill="none" stroke-width="3"
|
|
64
|
+
stroke-dasharray="<%= score %>, 100"
|
|
65
|
+
transform="rotate(-90 18 18)"/>
|
|
66
|
+
</svg>
|
|
67
|
+
<span class="brainz-score-value"><%= score %></span>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<span class="brainz-debug-stat <%= duration_class(@timing[:duration_ms]) %>">
|
|
71
|
+
<strong><%= format_duration(@timing[:duration_ms]) %></strong>
|
|
72
|
+
</span>
|
|
73
|
+
|
|
74
|
+
<span class="brainz-debug-stat<%= ' warning' if query_count > 20 %>">
|
|
75
|
+
<strong><%= query_count %></strong> queries
|
|
76
|
+
(<%= format_duration(@database[:total_duration_ms]) %>)
|
|
77
|
+
</span>
|
|
78
|
+
|
|
79
|
+
<%- if n_plus_ones.any? -%>
|
|
80
|
+
<span class="brainz-debug-stat error">
|
|
81
|
+
<strong><%= n_plus_ones.length %></strong> N+1
|
|
82
|
+
</span>
|
|
83
|
+
<%- end -%>
|
|
84
|
+
|
|
85
|
+
<span class="brainz-debug-stat<%= memory_class(@memory[:delta_mb]) %>">
|
|
86
|
+
<strong><%= sprintf('%.1f', memory_delta) %>MB</strong>
|
|
87
|
+
</span>
|
|
88
|
+
|
|
89
|
+
<span class="brainz-debug-stat <%= status_class(@response[:status]) %>">
|
|
90
|
+
<strong><%= @response[:status] || 200 %></strong>
|
|
91
|
+
</span>
|
|
92
|
+
|
|
93
|
+
<span class="brainz-keyboard-hint">Ctrl+Shift+B</span>
|
|
94
|
+
|
|
95
|
+
<span class="brainz-debug-expand">
|
|
96
|
+
<svg width="12" height="12" viewBox="0 0 12 12">
|
|
97
|
+
<path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
98
|
+
</svg>
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Tabs -->
|
|
103
|
+
<div class="brainz-debug-tabs">
|
|
104
|
+
<button class="brainz-debug-tab active" data-tab="request" data-devtools-target="tab" data-action="click->devtools#switchTab">Request</button>
|
|
105
|
+
<button class="brainz-debug-tab" data-tab="queries" data-devtools-target="tab" data-action="click->devtools#switchTab">
|
|
106
|
+
Queries <span class="badge"><%= query_count %></span>
|
|
107
|
+
</button>
|
|
108
|
+
<button class="brainz-debug-tab" data-tab="views" data-devtools-target="tab" data-action="click->devtools#switchTab">
|
|
109
|
+
Views <span class="badge"><%= (@views[:templates] || []).length %></span>
|
|
110
|
+
</button>
|
|
111
|
+
<button class="brainz-debug-tab<%= ' has-issues' if issues_count > 0 %>" data-tab="issues" data-devtools-target="tab" data-action="click->devtools#switchTab">
|
|
112
|
+
Issues <span class="badge<%= ' error' if issues_count > 0 %>"><%= issues_count %></span>
|
|
113
|
+
</button>
|
|
114
|
+
<button class="brainz-debug-tab" data-tab="timeline" data-devtools-target="tab" data-action="click->devtools#switchTab">Timeline</button>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Content Panes -->
|
|
118
|
+
<div class="brainz-debug-content">
|
|
119
|
+
<!-- Request Pane -->
|
|
120
|
+
<div class="brainz-debug-pane active" data-pane="request" data-devtools-target="pane">
|
|
121
|
+
<table class="brainz-info-table">
|
|
122
|
+
<tr>
|
|
123
|
+
<th>Method</th>
|
|
124
|
+
<td><%= h(@request[:method]) %></td>
|
|
125
|
+
</tr>
|
|
126
|
+
<tr>
|
|
127
|
+
<th>Path</th>
|
|
128
|
+
<td><%= h(@request[:path]) %></td>
|
|
129
|
+
</tr>
|
|
130
|
+
<%- if @controller[:name] -%>
|
|
131
|
+
<tr>
|
|
132
|
+
<th>Controller</th>
|
|
133
|
+
<td><%= h(@controller[:name]) %>#<%= h(@controller[:action]) %></td>
|
|
134
|
+
</tr>
|
|
135
|
+
<%- end -%>
|
|
136
|
+
<tr>
|
|
137
|
+
<th>Status</th>
|
|
138
|
+
<td><span class="<%= status_class(@response[:status]) %>"><%= @response[:status] %></span></td>
|
|
139
|
+
</tr>
|
|
140
|
+
<tr>
|
|
141
|
+
<th>Request ID</th>
|
|
142
|
+
<td><code><%= h(@request[:request_id]) %></code></td>
|
|
143
|
+
</tr>
|
|
144
|
+
<tr>
|
|
145
|
+
<th>Duration</th>
|
|
146
|
+
<td><%= format_duration(@timing[:duration_ms]) %></td>
|
|
147
|
+
</tr>
|
|
148
|
+
</table>
|
|
149
|
+
|
|
150
|
+
<%- if @request[:params] && !@request[:params].empty? -%>
|
|
151
|
+
<h4 style="margin: 1rem 0 0.5rem; font-size: 0.8125rem; color: #718096;">Parameters</h4>
|
|
152
|
+
<pre class="brainz-code-block"><%= json_pretty(@request[:params]) %></pre>
|
|
153
|
+
<%- end -%>
|
|
154
|
+
|
|
155
|
+
<%- if @user && !@user.empty? -%>
|
|
156
|
+
<h4 style="margin: 1rem 0 0.5rem; font-size: 0.8125rem; color: #718096;">User</h4>
|
|
157
|
+
<pre class="brainz-code-block"><%= json_pretty(@user) %></pre>
|
|
158
|
+
<%- end -%>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<!-- Queries Pane -->
|
|
162
|
+
<div class="brainz-debug-pane" data-pane="queries" data-devtools-target="pane">
|
|
163
|
+
<%- queries = @database[:queries] || [] -%>
|
|
164
|
+
<%- if queries.any? -%>
|
|
165
|
+
<table class="brainz-query-table">
|
|
166
|
+
<thead>
|
|
167
|
+
<tr>
|
|
168
|
+
<th width="80">Time</th>
|
|
169
|
+
<th width="100">Name</th>
|
|
170
|
+
<th>Query</th>
|
|
171
|
+
<th width="150">Source</th>
|
|
172
|
+
</tr>
|
|
173
|
+
</thead>
|
|
174
|
+
<tbody>
|
|
175
|
+
<%- queries.each do |query| -%>
|
|
176
|
+
<tr class="brainz-query-row<%= ' cached' if query[:cached] %><%= ' slow' if (query[:duration] || 0) > 100 %>">
|
|
177
|
+
<td class="brainz-query-duration <%= query_duration_class(query[:duration]) %>">
|
|
178
|
+
<%= sprintf('%.2f', query[:duration] || 0) %>ms
|
|
179
|
+
</td>
|
|
180
|
+
<td><%= h(query[:name] || 'SQL') %></td>
|
|
181
|
+
<td class="brainz-query-sql" title="<%= h(query[:sql]) %>">
|
|
182
|
+
<%= h(truncate(query[:sql], 80)) %>
|
|
183
|
+
</td>
|
|
184
|
+
<td style="font-size: 0.75rem; color: #A0AEC0;"><%= h(query[:source]) %></td>
|
|
185
|
+
</tr>
|
|
186
|
+
<%- end -%>
|
|
187
|
+
</tbody>
|
|
188
|
+
</table>
|
|
189
|
+
<%- else -%>
|
|
190
|
+
<p style="color: #718096; font-size: 0.875rem;">No SQL queries recorded.</p>
|
|
191
|
+
<%- end -%>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<!-- Views Pane -->
|
|
195
|
+
<div class="brainz-debug-pane" data-pane="views" data-devtools-target="pane">
|
|
196
|
+
<%- templates = @views[:templates] || [] -%>
|
|
197
|
+
<%- if templates.any? -%>
|
|
198
|
+
<table class="brainz-view-table">
|
|
199
|
+
<thead>
|
|
200
|
+
<tr>
|
|
201
|
+
<th width="80">Time</th>
|
|
202
|
+
<th width="80">Type</th>
|
|
203
|
+
<th>Template</th>
|
|
204
|
+
</tr>
|
|
205
|
+
</thead>
|
|
206
|
+
<tbody>
|
|
207
|
+
<%- templates.each do |view| -%>
|
|
208
|
+
<tr class="<%= 'slow' if (view[:duration] || 0) > 50 %>">
|
|
209
|
+
<td style="font-family: var(--brainz-mono); font-size: 0.75rem;" class="<%= 'brainz-slow' if (view[:duration] || 0) > 50 %>">
|
|
210
|
+
<%= sprintf('%.2f', view[:duration] || 0) %>ms
|
|
211
|
+
</td>
|
|
212
|
+
<td><%= h(view[:type]) %></td>
|
|
213
|
+
<td style="font-family: var(--brainz-mono); font-size: 0.75rem;"><%= h(view[:template]) %></td>
|
|
214
|
+
</tr>
|
|
215
|
+
<%- end -%>
|
|
216
|
+
</tbody>
|
|
217
|
+
</table>
|
|
218
|
+
<%- else -%>
|
|
219
|
+
<p style="color: #718096; font-size: 0.875rem;">No view renders recorded.</p>
|
|
220
|
+
<%- end -%>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<!-- Issues Pane -->
|
|
224
|
+
<div class="brainz-debug-pane" data-pane="issues" data-devtools-target="pane">
|
|
225
|
+
<%- if issues_count == 0 -%>
|
|
226
|
+
<div class="brainz-no-issues">
|
|
227
|
+
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
228
|
+
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" stroke-linecap="round" stroke-linejoin="round"/>
|
|
229
|
+
</svg>
|
|
230
|
+
<p>Looking good!</p>
|
|
231
|
+
<span>No performance issues detected in this request. Your code is running efficiently.</span>
|
|
232
|
+
</div>
|
|
233
|
+
<%- else -%>
|
|
234
|
+
<div class="brainz-issues-container">
|
|
235
|
+
|
|
236
|
+
<%# N+1 Queries %>
|
|
237
|
+
<%- if n_plus_ones.any? -%>
|
|
238
|
+
<div class="brainz-issue-section">
|
|
239
|
+
<div class="brainz-issue-header error">
|
|
240
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
241
|
+
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" stroke-linecap="round" stroke-linejoin="round"/>
|
|
242
|
+
</svg>
|
|
243
|
+
<span>N+1 Query Detection</span>
|
|
244
|
+
<span class="issue-count"><%= n_plus_ones.length %> found</span>
|
|
245
|
+
</div>
|
|
246
|
+
<%- n_plus_ones.each do |n1| -%>
|
|
247
|
+
<div class="brainz-issue-item">
|
|
248
|
+
<div class="brainz-issue-content">
|
|
249
|
+
<span class="brainz-issue-metric critical"><%= n1[:count] %>x</span>
|
|
250
|
+
similar queries totaling <strong><%= format_duration(n1[:total_duration_ms]) %></strong>
|
|
251
|
+
<%- if n1[:source] -%>
|
|
252
|
+
<span class="brainz-issue-source"><%= h(n1[:source]) %></span>
|
|
253
|
+
<%- end -%>
|
|
254
|
+
<code><%= h(truncate(n1[:sample_query], 100)) %></code>
|
|
255
|
+
</div>
|
|
256
|
+
<button class="brainz-copy-to-ai-btn" type="button"
|
|
257
|
+
data-action="click->devtools#copyToAi"
|
|
258
|
+
data-issue-type="n_plus_one"
|
|
259
|
+
data-n1-count="<%= n1[:count] %>"
|
|
260
|
+
data-n1-duration="<%= n1[:total_duration_ms] %>"
|
|
261
|
+
data-n1-source="<%= h(n1[:source] || '') %>"
|
|
262
|
+
data-n1-query="<%= h(n1[:sample_query] || '') %>"
|
|
263
|
+
data-n1-pattern="<%= h(n1[:pattern] || '') %>">
|
|
264
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
265
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
266
|
+
</svg>
|
|
267
|
+
Copy
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
<%- end -%>
|
|
271
|
+
</div>
|
|
272
|
+
<%- end -%>
|
|
273
|
+
|
|
274
|
+
<%# Slow Queries %>
|
|
275
|
+
<%- if slow_queries.any? -%>
|
|
276
|
+
<div class="brainz-issue-section">
|
|
277
|
+
<div class="brainz-issue-header warning">
|
|
278
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
279
|
+
<circle cx="12" cy="12" r="10"/>
|
|
280
|
+
<path d="M12 6v6l4 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
281
|
+
</svg>
|
|
282
|
+
<span>Slow Queries</span>
|
|
283
|
+
<span class="issue-count"><%= slow_queries.length %> over 100ms</span>
|
|
284
|
+
</div>
|
|
285
|
+
<%- slow_queries.each do |query| -%>
|
|
286
|
+
<div class="brainz-issue-item">
|
|
287
|
+
<div class="brainz-issue-content">
|
|
288
|
+
<span class="brainz-issue-metric slow"><%= sprintf('%.0f', query[:duration]) %>ms</span>
|
|
289
|
+
<span class="brainz-issue-name"><%= h(query[:name] || 'SQL') %></span>
|
|
290
|
+
<%- if query[:source] -%>
|
|
291
|
+
<span class="brainz-issue-source"><%= h(query[:source]) %></span>
|
|
292
|
+
<%- end -%>
|
|
293
|
+
<code><%= h(truncate(query[:sql], 100)) %></code>
|
|
294
|
+
</div>
|
|
295
|
+
<button class="brainz-copy-to-ai-btn" type="button"
|
|
296
|
+
data-action="click->devtools#copyToAi"
|
|
297
|
+
data-issue-type="slow_query"
|
|
298
|
+
data-query-duration="<%= query[:duration] %>"
|
|
299
|
+
data-query-sql="<%= h(query[:sql] || '') %>"
|
|
300
|
+
data-query-source="<%= h(query[:source] || '') %>"
|
|
301
|
+
data-query-name="<%= h(query[:name] || '') %>">
|
|
302
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
303
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
304
|
+
</svg>
|
|
305
|
+
Copy
|
|
306
|
+
</button>
|
|
307
|
+
</div>
|
|
308
|
+
<%- end -%>
|
|
309
|
+
</div>
|
|
310
|
+
<%- end -%>
|
|
311
|
+
|
|
312
|
+
<%# Too Many Queries %>
|
|
313
|
+
<%- if query_count > 20 -%>
|
|
314
|
+
<div class="brainz-issue-section">
|
|
315
|
+
<div class="brainz-issue-header warning">
|
|
316
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
317
|
+
<path d="M4 7v10c0 2 1 3 3 3h10c2 0 3-1 3-3V7c0-2-1-3-3-3H7c-2 0-3 1-3 3z"/>
|
|
318
|
+
<path d="M9 12h6M12 9v6" stroke-linecap="round"/>
|
|
319
|
+
</svg>
|
|
320
|
+
<span>Excessive Database Calls</span>
|
|
321
|
+
<span class="issue-count"><%= query_count %> queries</span>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="brainz-issue-item">
|
|
324
|
+
<div class="brainz-issue-content">
|
|
325
|
+
<span class="brainz-issue-metric slow"><%= query_count %></span>
|
|
326
|
+
queries executed in this request
|
|
327
|
+
<span class="brainz-issue-hint">Use eager loading or caching to reduce database round-trips.</span>
|
|
328
|
+
</div>
|
|
329
|
+
<button class="brainz-copy-to-ai-btn" type="button"
|
|
330
|
+
data-action="click->devtools#copyToAi"
|
|
331
|
+
data-issue-type="too_many_queries"
|
|
332
|
+
data-query-count="<%= query_count %>"
|
|
333
|
+
data-controller-name="<%= h(@controller[:name] || '') %>"
|
|
334
|
+
data-action-name="<%= h(@controller[:action] || '') %>">
|
|
335
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
336
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
337
|
+
</svg>
|
|
338
|
+
Copy
|
|
339
|
+
</button>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
<%- end -%>
|
|
343
|
+
|
|
344
|
+
<%# Slow Views %>
|
|
345
|
+
<%- if slow_views.any? -%>
|
|
346
|
+
<div class="brainz-issue-section">
|
|
347
|
+
<div class="brainz-issue-header warning">
|
|
348
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
349
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
350
|
+
<path d="M3 9h18M9 21V9" stroke-linecap="round"/>
|
|
351
|
+
</svg>
|
|
352
|
+
<span>Slow View Rendering</span>
|
|
353
|
+
<span class="issue-count"><%= slow_views.length %> over 50ms</span>
|
|
354
|
+
</div>
|
|
355
|
+
<%- slow_views.each do |view| -%>
|
|
356
|
+
<div class="brainz-issue-item">
|
|
357
|
+
<div class="brainz-issue-content">
|
|
358
|
+
<span class="brainz-issue-metric slow"><%= sprintf('%.0f', view[:duration]) %>ms</span>
|
|
359
|
+
<span class="brainz-issue-name"><%= h(view[:type]) %></span>
|
|
360
|
+
<code><%= h(view[:template]) %></code>
|
|
361
|
+
</div>
|
|
362
|
+
<button class="brainz-copy-to-ai-btn" type="button"
|
|
363
|
+
data-action="click->devtools#copyToAi"
|
|
364
|
+
data-issue-type="slow_view"
|
|
365
|
+
data-view-duration="<%= view[:duration] %>"
|
|
366
|
+
data-view-template="<%= h(view[:template] || '') %>"
|
|
367
|
+
data-view-type="<%= h(view[:type] || '') %>">
|
|
368
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
369
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
370
|
+
</svg>
|
|
371
|
+
Copy
|
|
372
|
+
</button>
|
|
373
|
+
</div>
|
|
374
|
+
<%- end -%>
|
|
375
|
+
</div>
|
|
376
|
+
<%- end -%>
|
|
377
|
+
|
|
378
|
+
<%# High Memory Usage %>
|
|
379
|
+
<%- if memory_delta > 50 -%>
|
|
380
|
+
<div class="brainz-issue-section">
|
|
381
|
+
<div class="brainz-issue-header warning">
|
|
382
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
383
|
+
<path d="M22 12h-4l-3 9L9 3l-3 9H2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
384
|
+
</svg>
|
|
385
|
+
<span>High Memory Allocation</span>
|
|
386
|
+
<span class="issue-count">+<%= sprintf('%.0f', memory_delta) %>MB</span>
|
|
387
|
+
</div>
|
|
388
|
+
<div class="brainz-issue-item">
|
|
389
|
+
<div class="brainz-issue-content">
|
|
390
|
+
<span class="brainz-issue-metric critical">+<%= sprintf('%.0f', memory_delta) %>MB</span>
|
|
391
|
+
memory allocated during this request
|
|
392
|
+
<span class="brainz-issue-hint">Check for large data loads or inefficient object creation.</span>
|
|
393
|
+
</div>
|
|
394
|
+
<button class="brainz-copy-to-ai-btn" type="button"
|
|
395
|
+
data-action="click->devtools#copyToAi"
|
|
396
|
+
data-issue-type="high_memory"
|
|
397
|
+
data-memory-delta="<%= memory_delta %>"
|
|
398
|
+
data-memory-before="<%= @memory[:before_mb] %>"
|
|
399
|
+
data-memory-after="<%= @memory[:after_mb] %>"
|
|
400
|
+
data-controller-name="<%= h(@controller[:name] || '') %>"
|
|
401
|
+
data-action-name="<%= h(@controller[:action] || '') %>">
|
|
402
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
403
|
+
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
404
|
+
</svg>
|
|
405
|
+
Copy
|
|
406
|
+
</button>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
<%- end -%>
|
|
410
|
+
|
|
411
|
+
</div>
|
|
412
|
+
<%- end -%>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<!-- Timeline Pane -->
|
|
416
|
+
<div class="brainz-debug-pane" data-pane="timeline" data-devtools-target="pane">
|
|
417
|
+
<%- total = (@timing[:duration_ms] || 1).to_f -%>
|
|
418
|
+
<%- db_ms = @database[:total_duration_ms] || 0 -%>
|
|
419
|
+
<%- views_ms = @views[:total_duration_ms] || 0 -%>
|
|
420
|
+
<%- other_ms = [total - db_ms - views_ms, 0].max -%>
|
|
421
|
+
<%- db_pct = (db_ms / total * 100).round -%>
|
|
422
|
+
<%- views_pct = (views_ms / total * 100).round -%>
|
|
423
|
+
<%- other_pct = [100 - db_pct - views_pct, 0].max -%>
|
|
424
|
+
|
|
425
|
+
<!-- Total Time Header -->
|
|
426
|
+
<div class="timeline-header">
|
|
427
|
+
<span class="timeline-total"><%= format_duration(@timing[:duration_ms]) %></span>
|
|
428
|
+
<span class="timeline-label">total request time</span>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<!-- Timeline Bar -->
|
|
432
|
+
<div class="timeline-bar">
|
|
433
|
+
<%- if db_pct > 0 -%>
|
|
434
|
+
<div class="timeline-segment db" style="width: <%= [db_pct, 5].max %>%">
|
|
435
|
+
<span class="timeline-segment-label">DB</span>
|
|
436
|
+
</div>
|
|
437
|
+
<%- end -%>
|
|
438
|
+
<%- if views_pct > 0 -%>
|
|
439
|
+
<div class="timeline-segment views" style="width: <%= [views_pct, 5].max %>%">
|
|
440
|
+
<span class="timeline-segment-label">Views</span>
|
|
441
|
+
</div>
|
|
442
|
+
<%- end -%>
|
|
443
|
+
<%- if other_pct > 5 -%>
|
|
444
|
+
<div class="timeline-segment other" style="width: <%= other_pct %>%">
|
|
445
|
+
<span class="timeline-segment-label">Other</span>
|
|
446
|
+
</div>
|
|
447
|
+
<%- end -%>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<!-- Stats Grid -->
|
|
451
|
+
<div class="timeline-stats">
|
|
452
|
+
<div class="timeline-stat">
|
|
453
|
+
<div class="timeline-stat-icon db">
|
|
454
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
455
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
456
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
|
|
457
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
|
|
458
|
+
</svg>
|
|
459
|
+
</div>
|
|
460
|
+
<div class="timeline-stat-content">
|
|
461
|
+
<div class="timeline-stat-value"><%= format_duration(db_ms) %></div>
|
|
462
|
+
<div class="timeline-stat-label">Database · <%= @database[:total_count] || 0 %> queries</div>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="timeline-stat-pct"><%= db_pct %>%</div>
|
|
465
|
+
</div>
|
|
466
|
+
|
|
467
|
+
<div class="timeline-stat">
|
|
468
|
+
<div class="timeline-stat-icon views">
|
|
469
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
470
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
471
|
+
<path d="M3 9h18M9 21V9"/>
|
|
472
|
+
</svg>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="timeline-stat-content">
|
|
475
|
+
<div class="timeline-stat-value"><%= format_duration(views_ms) %></div>
|
|
476
|
+
<div class="timeline-stat-label">Views · <%= (@views[:templates] || []).length %> templates</div>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="timeline-stat-pct"><%= views_pct %>%</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div class="timeline-stat">
|
|
482
|
+
<div class="timeline-stat-icon other">
|
|
483
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
484
|
+
<circle cx="12" cy="12" r="10"/>
|
|
485
|
+
<path d="M12 6v6l4 2"/>
|
|
486
|
+
</svg>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="timeline-stat-content">
|
|
489
|
+
<div class="timeline-stat-value"><%= format_duration(other_ms) %></div>
|
|
490
|
+
<div class="timeline-stat-label">Ruby · controller, middleware</div>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="timeline-stat-pct"><%= other_pct %>%</div>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<link rel="stylesheet" href="<%= asset_url('devtools.css') %>">
|
|
500
|
+
<script src="<%= asset_url('devtools.js') %>"></script>
|