dead_bro 0.2.8 → 0.2.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d8e1d14cc8e5511d143de228afefcb1a7557b8ce0308b7302ba40f4bc78bc90
4
- data.tar.gz: 1d215f5ff69110be5c10e965bcf2ac2f08f1f6cb9a2be72b602f143c3e3ac5e5
3
+ metadata.gz: 1ac04c555f3f82572e94327d09b90672869eb17c129deb87c78f746625cb5afd
4
+ data.tar.gz: 4639f701e7bd5dff8b36933e85b405dbbe43a2ccc664c94f16b6e2ea98987fda
5
5
  SHA512:
6
- metadata.gz: 9cab5ed48ea05f086512683b1d8d9fdb05030328f4fb4cb908884f074bc23b361ba7914774106bb5f1eba511435cdb506b123b2c4d2ece41634b539e925576a9
7
- data.tar.gz: '0695e7e7bc6f5835fde69586aa2bc03c134ca9a2515a4bda4e55a0c78242f776315e07018834a2ce66c9a828849c05d807c38e183f99ab77e00ac4d239144943'
6
+ metadata.gz: fee0d59a0362226babd057547b8138078d5ce3da4a75a3c5a2bbd175ee21b3043485e9f6c8e0b0a27a18c2373f10827fc7804f64c96888a41eea8d3c8eb8ea74
7
+ data.tar.gz: 1e044e7a275a5e52843119e00488a8a458e6d6f95420543014edd1074985bcdeebe1cbeef579e436a4eb31836eb6f9e24961c039851c4c82b2b0a76184cf23d2
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # DeadBro (Beta Version)
2
2
 
3
- Minimal APM for Rails apps. Automatically measures each controller action's total time, tracks SQL queries, monitors view rendering performance, tracks memory usage and detects leaks, monitors background jobs, and posts metrics to a remote endpoint with an API key read from your app's settings/credentials/env.
3
+ Minimal APM for Rails apps. Automatically measures each controller action's total time, tracks SQL queries, monitors view rendering performance, tracks memory usage and detects leaks, monitors background jobs, and posts metrics to DeadBro.
4
4
 
5
- To use the gem you need to have a free account with [DeadBro - Rails APM](https://www.deadbro.com)
5
+ **Current versions:** almost everything (sampling, exclusions, SQL EXPLAIN, memory toggles, control-plane collectors, enable/disable, and more) is configured in the [DeadBro](https://www.deadbro.com) web app. The API returns those settings on successful requests and the gem applies them in memory—no need to mirror them in Ruby unless you want optional code-based overrides.
6
+
7
+ To use the gem you need a free account with [DeadBro - Rails APM](https://www.deadbro.com).
6
8
 
7
9
  ## Installation
8
10
 
@@ -16,21 +18,33 @@ gem "dead_bro", git: "https://github.com/rubydevro/dead_bro.git"
16
18
 
17
19
  By default, if Rails is present, DeadBro auto-subscribes to `process_action.action_controller` and posts metrics asynchronously.
18
20
 
19
- ### Configuration settings
20
-
21
- You can set via an initializer:
21
+ ### Required: API key in your app
22
22
 
23
+ Add an initializer (for example `config/initializers/dead_bro.rb`) and set your API key from the environment, [Rails credentials](https://guides.rubyonrails.org/security.html#custom-credentials), or another secret store:
23
24
 
24
25
  ```ruby
25
26
  DeadBro.configure do |config|
26
27
  config.api_key = ENV["DEAD_BRO_API_KEY"]
27
- config.enabled = true
28
28
  end
29
29
  ```
30
30
 
31
+ That is enough to start shipping metrics. Create or copy the key from your DeadBro account, then wire it into `DEAD_BRO_API_KEY` (or assign `config.api_key` directly).
32
+
33
+ ### Dashboard configuration
34
+
35
+ Use the DeadBro UI to turn features on or off, set sample rates, define controller/job inclusions and exclusions, tune slow-query EXPLAIN, enable queue and system metrics, and adjust related limits. After you deploy the initializer above, those choices take effect when the gem receives them from the API (typically on the next successful metric or heartbeat response).
36
+
37
+ ### Optional local flags
38
+
39
+ You can still set `config.enabled` in Ruby if you need to force the integration off in a given environment before any remote settings arrive; otherwise the dashboard can control `enabled` like other remote settings.
40
+
41
+ ## Optional: configuration in Ruby
42
+
43
+ The sections below describe the same knobs you can manage in the DeadBro app. Use them only when you want values in source control, per-environment initializer logic, or other overrides outside the UI.
44
+
31
45
  ## Request Sampling
32
46
 
33
- DeadBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications.
47
+ DeadBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications. Prefer setting this in the DeadBro app; use Ruby if you need a local override.
34
48
 
35
49
  ### Configuration
36
50
 
@@ -70,22 +84,20 @@ end
70
84
 
71
85
  ## Excluding Controllers and Jobs
72
86
 
73
- You can exclude specific controllers and jobs from APM tracking.
87
+ You can exclude specific controllers and jobs from APM tracking (dashboard first; Ruby optional).
74
88
 
75
89
  ### Configuration
76
90
 
77
91
 
78
92
  ```ruby
79
93
  DeadBro.configure do |config|
94
+ # Controller-only or controller#action patterns in one list (wildcards supported)
80
95
  config.excluded_controllers = [
81
96
  "HealthChecksController",
82
- "Admin::*" # wildcard supported
83
- ]
84
-
85
- config.excluded_controller_actions = [
97
+ "Admin::*",
86
98
  "UsersController#show",
87
99
  "Admin::ReportsController#index",
88
- "Admin::*#*" # wildcard supported for controller and action
100
+ "Admin::*#*"
89
101
  ]
90
102
 
91
103
  config.excluded_jobs = [
@@ -96,39 +108,38 @@ end
96
108
  ```
97
109
 
98
110
  Notes:
99
- - Wildcards `*` are supported for controller and action (e.g., `Admin::*#*`).
100
- - Matching is done against full names like `UsersController`, `Admin::ReportsController#index`, `MyJob`.
111
+ - Wildcards `*` are supported (e.g., `Admin::*`, `Admin::*#*`).
112
+ - Matching uses full names like `UsersController`, `Admin::ReportsController#index`, `MyJob`.
101
113
 
102
114
  ## Exclusive Tracking (Whitelist Mode)
103
115
 
104
- You can configure DeadBro to **only** track specific controllers, actions, or jobs. This is useful when you want to focus monitoring on a subset of your application.
116
+ You can configure DeadBro to **only** track specific controllers, actions, or jobs. Prefer the dashboard; use Ruby for overrides.
105
117
 
106
118
  ### Configuration
107
119
 
108
120
  ```ruby
109
121
  DeadBro.configure do |config|
110
- # Only track these specific controller actions
111
- config.exclusive_controller_actions = [
122
+ # Only track these controllers/actions (patterns can include #action or wildcards)
123
+ config.exclusive_controllers = [
112
124
  "UsersController#show",
113
125
  "UsersController#index",
114
- "Admin::ReportsController#*", # all actions in this controller
115
- "Api::*#*" # all actions in all Api controllers
126
+ "Admin::ReportsController#*",
127
+ "Api::*#*"
116
128
  ]
117
129
 
118
- # Only track these specific jobs
119
130
  config.exclusive_jobs = [
120
131
  "PaymentProcessingJob",
121
132
  "EmailDeliveryJob",
122
- "Admin::*" # all jobs in Admin namespace
133
+ "Admin::*"
123
134
  ]
124
135
  end
125
136
  ```
126
137
 
127
138
  ### How It Works
128
139
 
129
- - **If `exclusive_controller_actions` or `exclusive_jobs` is empty/not defined**: All controllers/actions/jobs are tracked (default behavior)
130
- - **If `exclusive_controller_actions` or `exclusive_jobs` is defined with values**: Only matching controllers/actions/jobs are tracked
131
- - **Exclusion takes precedence**: If something is in both `excluded_*` and `exclusive_*`, it will be excluded (exclusion is checked first)
140
+ - **If `exclusive_controllers` or `exclusive_jobs` is empty/not defined**: All controllers/actions/jobs are tracked (default behavior)
141
+ - **If `exclusive_controllers` or `exclusive_jobs` is defined with values**: Only matching controllers/actions/jobs are tracked
142
+ - **Exclusion takes precedence**: If something matches both `excluded_*` and `exclusive_*`, it is excluded (exclusion is checked first)
132
143
 
133
144
  ### Use Cases
134
145
 
@@ -137,18 +148,6 @@ end
137
148
  - **Debugging**: Temporarily focus on specific controllers/jobs during investigation
138
149
  - **Compliance**: Track only operations that require monitoring for compliance reasons
139
150
 
140
- ### Environment Variables
141
-
142
- You can also configure exclusive tracking via environment variables:
143
-
144
- ```bash
145
- # Comma-separated list of controller#action patterns
146
- dead_bro_EXCLUSIVE_CONTROLLER_ACTIONS="UsersController#show,Admin::*#*"
147
-
148
- # Comma-separated list of job patterns
149
- dead_bro_EXCLUSIVE_JOBS="PaymentProcessingJob,EmailDeliveryJob"
150
- ```
151
-
152
151
  ## SQL Query Tracking
153
152
 
154
153
  DeadBro automatically tracks SQL queries executed during each request and job. Each request will include a `sql_queries` array containing:
@@ -173,6 +172,8 @@ DeadBro can automatically run `EXPLAIN ANALYZE` on slow SQL queries to help you
173
172
 
174
173
  ### Configuration
175
174
 
175
+ These options are usually set in the DeadBro UI. In Ruby:
176
+
176
177
  - **`explain_analyze_enabled`** (default: `false`) - Set to `true` to enable automatic EXPLAIN ANALYZE
177
178
  - **`slow_query_threshold_ms`** (default: `500`) - Queries taking longer than this threshold will have their execution plan captured
178
179
 
@@ -180,13 +181,10 @@ DeadBro can automatically run `EXPLAIN ANALYZE` on slow SQL queries to help you
180
181
 
181
182
  ```ruby
182
183
  DeadBro.configure do |config|
183
- config.api_key = ENV['DEAD_BRO_API_KEY']
184
- config.enabled = true
185
-
186
184
  # Enable EXPLAIN ANALYZE for queries slower than 500ms
187
185
  config.explain_analyze_enabled = true
188
186
  config.slow_query_threshold_ms = 500
189
-
187
+
190
188
  # Or use a higher threshold for production
191
189
  # config.slow_query_threshold_ms = 1000 # Only explain queries > 1 second
192
190
  end
@@ -232,8 +230,9 @@ By default, DeadBro uses **lightweight memory tracking** that has minimal perfor
232
230
 
233
231
  ### Configuration Options
234
232
 
233
+ Usually managed in the dashboard; Ruby example:
234
+
235
235
  ```ruby
236
- # In your Rails configuration
237
236
  DeadBro.configure do |config|
238
237
  config.memory_tracking_enabled = true # Enable lightweight memory tracking (default: true)
239
238
  config.allocation_tracking_enabled = false # Enable detailed allocation tracking (default: false)
@@ -279,7 +278,7 @@ Everything is **best effort** and designed to be **safe and low overhead**:
279
278
 
280
279
  ### Configuration
281
280
 
282
- You can enable or disable individual collectors and tune basic options via the standard `DeadBro.configure` block:
281
+ Enable or disable collectors in the DeadBro app, or use `DeadBro.configure` for code-based overrides:
283
282
 
284
283
  ```ruby
285
284
  DeadBro.configure do |config|
@@ -389,6 +388,69 @@ The control plane job sends a single JSON payload roughly shaped like:
389
388
  Not all fields will be present in all environments; unsupported or unavailable metrics may be `null` or omitted, and any hard failures are captured in `error_class` / `error_message` fields per section.
390
389
 
391
390
 
391
+ ## One-Off Analysis with `DeadBro.analyze`
392
+
393
+ Use `DeadBro.analyze` to profile any block of code inline — useful in the Rails console, rake tasks, or debug sessions. It tracks execution time, SQL queries, and memory usage without sending data to the DeadBro backend.
394
+
395
+ ```ruby
396
+ result = DeadBro.analyze("load active users") do
397
+ User.where(active: true).includes(:profile).to_a
398
+ end
399
+ ```
400
+
401
+ Returns a `DeadBro::AnalysisResult` struct.
402
+
403
+ ### Return Value
404
+
405
+ `DeadBro::AnalysisResult` exposes:
406
+
407
+ | Member | Type | Description |
408
+ |---|---|---|
409
+ | `label` | String | Label passed to the block |
410
+ | `total_time_ms` | Float | Wall time of the block |
411
+ | `sql_count` | Integer | Number of SQL queries executed |
412
+ | `sql_time_ms` | Float | Total SQL execution time |
413
+ | `sql_queries` | Array | Per-query breakdown (see below) |
414
+ | `memory_before_mb` | Float | RSS before block |
415
+ | `memory_after_mb` | Float | RSS after block |
416
+ | `memory_delta_mb` | Float | Memory change |
417
+ | `memory_details` | Hash | GC stats, new objects, heap pages |
418
+
419
+ **`sql_queries` is intentionally excluded from `inspect`/`to_s`** to avoid flooding the console with a long array. Access it explicitly when needed:
420
+
421
+ ```ruby
422
+ result # => #<DeadBro::AnalysisResult label="load active users" total_time_ms=42.3 ...>
423
+ result.sql_queries # => [{sql: "SELECT ...", query_type: "SELECT", count: 3, total_time_ms: 12.1}, ...]
424
+ ```
425
+
426
+ ### Options
427
+
428
+ ```ruby
429
+ DeadBro.analyze("my block", verbose: true) do
430
+ # verbose: true lowers the Rails log level to DEBUG and enables
431
+ # ActiveRecord.verbose_query_logs for the duration of the block
432
+ MyService.call
433
+ end
434
+ ```
435
+
436
+ ### Helper methods
437
+
438
+ ```ruby
439
+ result.most_queries # top 5 query patterns by execution count
440
+ result.longest_queries # top 5 query patterns by total_time_ms
441
+ ```
442
+
443
+ Both return an array of query hashes (same shape as `sql_queries`):
444
+
445
+ - `sql` — normalized SQL (literals replaced, whitespace collapsed)
446
+ - `query_type` — e.g. `"SELECT"`, `"INSERT"`, `"UPDATE"`
447
+ - `count` — how many times this pattern ran
448
+ - `total_time_ms` — combined time across all occurrences
449
+
450
+ ### `sql_queries` fields
451
+
452
+ `result.sql_queries` returns the full list. Each entry is a hash with the same fields as above.
453
+
392
454
  ## Development
393
455
 
394
456
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -25,15 +25,17 @@ module DeadBro
25
25
  @failure_count = 0
26
26
  @last_failure_time = nil
27
27
  @last_success_time = nil
28
+ @mutex = Mutex.new
28
29
  end
29
30
 
30
31
  def call(&block)
31
- case @state
32
+ state = @mutex.synchronize { @state }
33
+ case state
32
34
  when CLOSED
33
35
  execute_with_monitoring(&block)
34
36
  when OPEN
35
37
  if should_attempt_reset?
36
- @state = HALF_OPEN
38
+ @mutex.synchronize { @state = HALF_OPEN }
37
39
  execute_with_monitoring(&block)
38
40
  else
39
41
  :circuit_open
@@ -43,75 +45,93 @@ module DeadBro
43
45
  end
44
46
  end
45
47
 
46
- attr_reader :state
48
+ def state
49
+ @mutex.synchronize { @state }
50
+ end
47
51
 
48
- attr_reader :failure_count
52
+ def failure_count
53
+ @mutex.synchronize { @failure_count }
54
+ end
49
55
 
50
- attr_reader :last_failure_time
56
+ def last_failure_time
57
+ @mutex.synchronize { @last_failure_time }
58
+ end
51
59
 
52
- attr_reader :last_success_time
60
+ def last_success_time
61
+ @mutex.synchronize { @last_success_time }
62
+ end
53
63
 
54
64
  def reset!
55
- @state = CLOSED
56
- @failure_count = 0
57
- @last_failure_time = nil
65
+ @mutex.synchronize do
66
+ @state = CLOSED
67
+ @failure_count = 0
68
+ @last_failure_time = nil
69
+ end
58
70
  end
59
71
 
60
72
  def open!
61
- @state = OPEN
62
- @last_failure_time = Time.now
73
+ @mutex.synchronize do
74
+ @state = OPEN
75
+ @last_failure_time = Time.now
76
+ end
63
77
  end
64
78
 
65
79
  def transition_to_half_open!
66
- @state = HALF_OPEN
80
+ @mutex.synchronize { @state = HALF_OPEN }
67
81
  end
68
82
 
69
83
  def should_attempt_reset?
70
- return false unless @last_failure_time
84
+ @mutex.synchronize do
85
+ return false unless @last_failure_time
86
+ (Time.now - @last_failure_time) >= @recovery_timeout
87
+ end
88
+ end
89
+
90
+ # Public entry points for callers that already know the outcome (e.g. the
91
+ # HTTP dispatcher thread). Preferred over `call(&block)` when the caller
92
+ # is doing its own error handling.
93
+ def record_success
94
+ @mutex.synchronize do
95
+ @failure_count = 0
96
+ @last_success_time = Time.now
97
+ @state = CLOSED
98
+ end
99
+ end
71
100
 
72
- # Try to reset after recovery timeout
73
- elapsed = Time.now - @last_failure_time
74
- elapsed >= @recovery_timeout
101
+ def record_failure
102
+ @mutex.synchronize do
103
+ @failure_count += 1
104
+ @last_failure_time = Time.now
105
+ if @state == HALF_OPEN || @failure_count >= @failure_threshold
106
+ @state = OPEN
107
+ end
108
+ end
75
109
  end
76
110
 
77
111
  private
78
112
 
113
+ # Historical names kept as private aliases so existing specs and any
114
+ # internal callers that reach in via `send(:on_success)` still work.
115
+ alias_method :on_success, :record_success
116
+ alias_method :on_failure, :record_failure
117
+
79
118
  def execute_with_monitoring(&block)
80
119
  result = block.call
81
120
 
82
121
  if success?(result)
83
- on_success
122
+ record_success
84
123
  result
85
124
  else
86
- on_failure
125
+ record_failure
87
126
  result
88
127
  end
89
128
  rescue => e
90
- on_failure
129
+ record_failure
91
130
  raise e
92
131
  end
93
132
 
94
133
  def success?(result)
95
- # Consider 2xx status codes as success
96
134
  result.is_a?(Net::HTTPSuccess)
97
135
  end
98
-
99
- def on_success
100
- @failure_count = 0
101
- @last_success_time = Time.now
102
- @state = CLOSED
103
- end
104
-
105
- def on_failure
106
- @failure_count += 1
107
- @last_failure_time = Time.now
108
-
109
- # If we're in half-open state and get a failure, go back to open
110
- if @state == HALF_OPEN
111
- @state = OPEN
112
- elsif @failure_count >= @failure_threshold
113
- @state = OPEN
114
- end
115
- end
116
136
  end
117
137
  end