findbug 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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +8 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +375 -0
  6. data/Rakefile +12 -0
  7. data/app/controllers/findbug/application_controller.rb +105 -0
  8. data/app/controllers/findbug/dashboard_controller.rb +93 -0
  9. data/app/controllers/findbug/errors_controller.rb +129 -0
  10. data/app/controllers/findbug/performance_controller.rb +80 -0
  11. data/app/jobs/findbug/alert_job.rb +40 -0
  12. data/app/jobs/findbug/cleanup_job.rb +132 -0
  13. data/app/jobs/findbug/persist_job.rb +158 -0
  14. data/app/models/findbug/error_event.rb +197 -0
  15. data/app/models/findbug/performance_event.rb +237 -0
  16. data/app/views/findbug/dashboard/index.html.erb +199 -0
  17. data/app/views/findbug/errors/index.html.erb +137 -0
  18. data/app/views/findbug/errors/show.html.erb +185 -0
  19. data/app/views/findbug/performance/index.html.erb +168 -0
  20. data/app/views/findbug/performance/show.html.erb +203 -0
  21. data/app/views/layouts/findbug/application.html.erb +601 -0
  22. data/lib/findbug/alerts/channels/base.rb +75 -0
  23. data/lib/findbug/alerts/channels/discord.rb +155 -0
  24. data/lib/findbug/alerts/channels/email.rb +179 -0
  25. data/lib/findbug/alerts/channels/slack.rb +149 -0
  26. data/lib/findbug/alerts/channels/webhook.rb +143 -0
  27. data/lib/findbug/alerts/dispatcher.rb +126 -0
  28. data/lib/findbug/alerts/throttler.rb +110 -0
  29. data/lib/findbug/background_persister.rb +142 -0
  30. data/lib/findbug/capture/context.rb +301 -0
  31. data/lib/findbug/capture/exception_handler.rb +141 -0
  32. data/lib/findbug/capture/exception_subscriber.rb +228 -0
  33. data/lib/findbug/capture/message_handler.rb +104 -0
  34. data/lib/findbug/capture/middleware.rb +247 -0
  35. data/lib/findbug/configuration.rb +381 -0
  36. data/lib/findbug/engine.rb +109 -0
  37. data/lib/findbug/performance/instrumentation.rb +336 -0
  38. data/lib/findbug/performance/transaction.rb +193 -0
  39. data/lib/findbug/processing/data_scrubber.rb +163 -0
  40. data/lib/findbug/rails/controller_methods.rb +152 -0
  41. data/lib/findbug/railtie.rb +222 -0
  42. data/lib/findbug/storage/circuit_breaker.rb +223 -0
  43. data/lib/findbug/storage/connection_pool.rb +134 -0
  44. data/lib/findbug/storage/redis_buffer.rb +285 -0
  45. data/lib/findbug/tasks/findbug.rake +167 -0
  46. data/lib/findbug/version.rb +5 -0
  47. data/lib/findbug.rb +216 -0
  48. data/lib/generators/findbug/install_generator.rb +67 -0
  49. data/lib/generators/findbug/templates/POST_INSTALL +41 -0
  50. data/lib/generators/findbug/templates/create_findbug_error_events.rb +44 -0
  51. data/lib/generators/findbug/templates/create_findbug_performance_events.rb +47 -0
  52. data/lib/generators/findbug/templates/initializer.rb +157 -0
  53. data/sig/findbug.rbs +4 -0
  54. metadata +251 -0
@@ -0,0 +1,203 @@
1
+ <div style="margin-bottom: 1.5rem;">
2
+ <h1 style="margin-bottom: 0.5rem;"><%= @transaction_name %></h1>
3
+ <p class="text-muted text-sm">
4
+ <%= link_to "← Back to Performance", findbug.performance_index_path %>
5
+ </p>
6
+ </div>
7
+
8
+ <% if @stats %>
9
+ <%# Stats overview %>
10
+ <div class="stats-grid" style="grid-template-columns: repeat(6, 1fr);">
11
+ <div class="stat-card">
12
+ <div class="stat-label">Requests</div>
13
+ <div class="stat-value"><%= @stats[:count] %></div>
14
+ </div>
15
+
16
+ <div class="stat-card">
17
+ <div class="stat-label">Average</div>
18
+ <div class="stat-value"><%= @stats[:avg_duration_ms].round %>ms</div>
19
+ </div>
20
+
21
+ <div class="stat-card">
22
+ <div class="stat-label">P95</div>
23
+ <div class="stat-value"><%= @stats[:p95_duration_ms].round %>ms</div>
24
+ </div>
25
+
26
+ <div class="stat-card">
27
+ <div class="stat-label">P99</div>
28
+ <div class="stat-value"><%= @stats[:p99_duration_ms].round %>ms</div>
29
+ </div>
30
+
31
+ <div class="stat-card">
32
+ <div class="stat-label">Avg Queries</div>
33
+ <div class="stat-value"><%= @stats[:avg_query_count] %></div>
34
+ </div>
35
+
36
+ <div class="stat-card">
37
+ <div class="stat-label">N+1 Issues</div>
38
+ <div class="stat-value <%= @stats[:n_plus_one_count] > 0 ? 'warning' : 'success' %>">
39
+ <%= @stats[:n_plus_one_count] %>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="grid-2">
45
+ <%# Slowest Requests %>
46
+ <div class="card">
47
+ <div class="card-header">
48
+ <h2 class="card-title">Slowest Requests</h2>
49
+ </div>
50
+
51
+ <% if @slowest_requests.any? %>
52
+ <table class="table">
53
+ <thead>
54
+ <tr>
55
+ <th>Duration</th>
56
+ <th>Queries</th>
57
+ <th>When</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ <% @slowest_requests.each do |req| %>
62
+ <tr>
63
+ <td>
64
+ <span class="badge badge-<%= req.duration_ms > 1000 ? 'error' : 'warning' %>">
65
+ <%= req.duration_ms.round %>ms
66
+ </span>
67
+ </td>
68
+ <td>
69
+ <%= req.query_count %> queries
70
+ <% if req.has_n_plus_one %>
71
+ <span class="badge badge-warning" style="margin-left: 0.25rem;">N+1</span>
72
+ <% end %>
73
+ </td>
74
+ <td class="text-muted text-sm">
75
+ <%= time_ago_in_words(req.captured_at) %> ago
76
+ </td>
77
+ </tr>
78
+ <% end %>
79
+ </tbody>
80
+ </table>
81
+ <% else %>
82
+ <div class="empty-state">
83
+ <p>No slow requests found</p>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+
88
+ <%# N+1 Issues %>
89
+ <div class="card">
90
+ <div class="card-header">
91
+ <h2 class="card-title">N+1 Issues</h2>
92
+ </div>
93
+
94
+ <% if @n_plus_one_requests.any? %>
95
+ <div class="card-content">
96
+ <% @n_plus_one_requests.each_with_index do |req, idx| %>
97
+ <div>
98
+ <div style="display: flex; justify-content: space-between; align-items: center;">
99
+ <span class="badge badge-warning"><%= req.query_count %> queries</span>
100
+ <span class="text-muted text-sm"><%= req.duration_ms.round %>ms</span>
101
+ </div>
102
+ <% if req.n_plus_one_queries.present? %>
103
+ <% req.n_plus_one_queries.first(2).each do |nq| %>
104
+ <div style="margin-top: 0.5rem;">
105
+ <div class="text-muted text-xs">
106
+ Repeated <%= nq['count'] || nq[:count] %> times
107
+ </div>
108
+ <div class="code-block" style="margin-top: 0.25rem;">
109
+ <%= truncate((nq['example'] || nq[:example]).to_s, length: 200) %>
110
+ </div>
111
+ </div>
112
+ <% end %>
113
+ <% end %>
114
+ </div>
115
+ <% unless idx == @n_plus_one_requests.length - 1 %><div class="separator"></div><% end %>
116
+ <% end %>
117
+ </div>
118
+ <% else %>
119
+ <div class="empty-state">
120
+ <div class="empty-state-icon">✓</div>
121
+ <p>No N+1 issues found</p>
122
+ </div>
123
+ <% end %>
124
+ </div>
125
+ </div>
126
+
127
+ <%# Duration breakdown %>
128
+ <div class="card">
129
+ <div class="card-header">
130
+ <h2 class="card-title">Duration Distribution</h2>
131
+ </div>
132
+ <div class="card-content">
133
+ <div style="display: grid; gap: 0.75rem;">
134
+ <div style="display: flex; justify-content: space-between; align-items: center;">
135
+ <span class="text-muted text-sm">Min</span>
136
+ <div style="flex: 1; margin: 0 1rem;">
137
+ <div class="progress">
138
+ <div class="progress-bar info" style="width: <%= (@stats[:min_duration_ms] / [@stats[:max_duration_ms], 1].max * 100).round %>%;"></div>
139
+ </div>
140
+ </div>
141
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:min_duration_ms].round %>ms</span>
142
+ </div>
143
+
144
+ <div style="display: flex; justify-content: space-between; align-items: center;">
145
+ <span class="text-muted text-sm">P50</span>
146
+ <div style="flex: 1; margin: 0 1rem;">
147
+ <div class="progress">
148
+ <div class="progress-bar info" style="width: <%= (@stats[:p50_duration_ms] / [@stats[:max_duration_ms], 1].max * 100).round %>%;"></div>
149
+ </div>
150
+ </div>
151
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:p50_duration_ms].round %>ms</span>
152
+ </div>
153
+
154
+ <div style="display: flex; justify-content: space-between; align-items: center;">
155
+ <span class="text-muted text-sm">Avg</span>
156
+ <div style="flex: 1; margin: 0 1rem;">
157
+ <div class="progress">
158
+ <div class="progress-bar info" style="width: <%= (@stats[:avg_duration_ms] / [@stats[:max_duration_ms], 1].max * 100).round %>%;"></div>
159
+ </div>
160
+ </div>
161
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:avg_duration_ms].round %>ms</span>
162
+ </div>
163
+
164
+ <div style="display: flex; justify-content: space-between; align-items: center;">
165
+ <span class="text-muted text-sm">P95</span>
166
+ <div style="flex: 1; margin: 0 1rem;">
167
+ <div class="progress">
168
+ <div class="progress-bar warning" style="width: <%= (@stats[:p95_duration_ms] / [@stats[:max_duration_ms], 1].max * 100).round %>%;"></div>
169
+ </div>
170
+ </div>
171
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:p95_duration_ms].round %>ms</span>
172
+ </div>
173
+
174
+ <div style="display: flex; justify-content: space-between; align-items: center;">
175
+ <span class="text-muted text-sm">P99</span>
176
+ <div style="flex: 1; margin: 0 1rem;">
177
+ <div class="progress">
178
+ <div class="progress-bar warning" style="width: <%= (@stats[:p99_duration_ms] / [@stats[:max_duration_ms], 1].max * 100).round %>%;"></div>
179
+ </div>
180
+ </div>
181
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:p99_duration_ms].round %>ms</span>
182
+ </div>
183
+
184
+ <div style="display: flex; justify-content: space-between; align-items: center;">
185
+ <span class="text-muted text-sm">Max</span>
186
+ <div style="flex: 1; margin: 0 1rem;">
187
+ <div class="progress">
188
+ <div class="progress-bar error" style="width: 100%;"></div>
189
+ </div>
190
+ </div>
191
+ <span class="text-sm" style="min-width: 60px; text-align: right;"><%= @stats[:max_duration_ms].round %>ms</span>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <% else %>
197
+ <div class="card">
198
+ <div class="empty-state">
199
+ <div class="empty-state-icon">📊</div>
200
+ <p>No data found for this transaction.</p>
201
+ </div>
202
+ </div>
203
+ <% end %>