dead_bro 0.2.35 → 0.2.36

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: 2544ca10e878c96153137ba46f40a543e157eff3c877b1bd3211eec15b01a9e7
4
- data.tar.gz: 046c6f63e058e9273e8f978f42a727164c4e366c65111169c1a3d86b29ebae43
3
+ metadata.gz: 7f33da7799522248140cc65b3637ae2a1dede4295b3696370bd3aabea0d09c7b
4
+ data.tar.gz: 376071ecd3eafe0e41695246074ccea3b3a2c479532d08eb24d46fa0d471c94e
5
5
  SHA512:
6
- metadata.gz: badda478e44e521b88d558413370bde506ecb7693c5a77864fc9953f9a082a97722c5c942386e16c8eeb3a15488e196d96da799a494ff9e9ee87cd304ea437bc
7
- data.tar.gz: 18f19c4e1dc982cb7e6ec422a11ff4e443f1759b73e9841757987c4d8e9b5b3c914dc6995c65cbccf0bdd48110e3214e357b62546585558a3e6cab7ee2de83a1
6
+ metadata.gz: ba9539bb294999bcd1d3b1f4bed9bc7612d4b9f3e66fe92769de92a0b50d8991ffadf2daf6e418b3951fcda7b8e1ae9afa5b6980c4de7af49d982ccd07aff6f2
7
+ data.tar.gz: 1e9abbd0ef17016ea6f639fa60d6d18edc0b481cdeec3d6495f52f5ae31e73a413db0bb7e7607b274a344d8ab1b33dda098dca608c617a024622aff3796fc8e0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+ - **`gc_pressure` now says whether the window's GC had finished.** `gc_state_at_end` is the GC's phase when a request or job window closes — `"none"`, `"marking"` or `"sweeping"`, from `GC.latest_gc_info(:state)` — sent with the other memory-tracking fields. Ruby marks and sweeps in steps between allocations, so a GC counted in the window is often still running when it closes, and until it finishes `heap_live_slots_growth` still counts garbage it hasn't freed. The dashboard uses it so a sample measured mid-GC isn't flagged as a possible leak. It's a single-key read; no hash is built.
5
+
6
+ ### Fixed
7
+ - **The in-process leak detector's `object_count` was always 0.** `gc_stats` never read `heap_live_slots`, so every `MemoryLeakDetector` sample recorded `object_count: 0` and the `object_stats` / `memory_efficiency` figures from `DeadBro::MemoryHelpers.analyze_memory` were meaningless. `gc_stats` now includes `heap_live_slots` — in request and job payloads alike — and the request's leak sample and payload share one `GC.stat` read. `average_memory_per_object_kb` is now actually in KB; it divided RSS in MB by the object count.
8
+
3
9
  ## [0.2.35] - 2026-10-21
4
10
 
5
11
  ### Fixed
@@ -29,14 +29,17 @@ module DeadBro
29
29
  # Memory-tracking enrichment (a few extra GC.stat reads). Only the base
30
30
  # GC pressure fields above are truly always-on.
31
31
  if memory_tracking_enabled?
32
- # Live heap slots — net retained objects. Comparing this delta against
33
- # allocated_objects separates transient churn from genuine retention.
32
+ # Live heap slots, process-wide. How to read their delta is explained
33
+ # in self.diff below.
34
34
  base[:heap_live_slots] = stat[:heap_live_slots] || 0
35
35
  # Bytes malloc'd outside the Ruby object heap (big strings/buffers, e.g.
36
36
  # parsed JSON response bodies). These are point-in-time gauges reset by
37
37
  # GC, so we report the request-end value rather than a diff.
38
38
  base[:malloc_increase_bytes] = stat[:malloc_increase_bytes] || 0
39
39
  base[:oldmalloc_increase_bytes] = stat[:oldmalloc_increase_bytes] || 0
40
+ # The GC's phase right after those counters were read: :none, :marking
41
+ # or :sweeping. See self.diff below.
42
+ base[:gc_state] = gc_state
40
43
  end
41
44
 
42
45
  base
@@ -44,6 +47,13 @@ module DeadBro
44
47
  {}
45
48
  end
46
49
 
50
+ # A single-key read: no hash is built. nil where the VM doesn't report it.
51
+ def self.gc_state
52
+ GC.latest_gc_info(:state) if GC.respond_to?(:latest_gc_info)
53
+ rescue
54
+ nil
55
+ end
56
+
47
57
  def self.diff(before, after)
48
58
  return {} if before.empty? || after.empty?
49
59
  gc_time_ms = if before[:gc_time_ns] && after[:gc_time_ns]
@@ -58,10 +68,19 @@ module DeadBro
58
68
 
59
69
  # Present only when the enrichment was captured (memory tracking enabled).
60
70
  if after.key?(:heap_live_slots) || before.key?(:heap_live_slots)
61
- # Net change in live slots over the request. A small value alongside a
62
- # large allocated_objects means the memory was transient (reclaimed by
63
- # GC); a large value means objects were retained — the real leak signal.
71
+ # Signed net change in live slots over the request. Like allocated_objects
72
+ # it is process-wide: every thread's allocations and frees count. It only
73
+ # separates transient churn from retention once a GC in the window has
74
+ # finished: without one, slots are freed only by the lazy sweep of an
75
+ # earlier GC, and a GC still running when the window closes hasn't freed
76
+ # all its garbage yet. It goes negative when more is freed than
77
+ # allocated. Read it together with minor_gc_runs/major_gc_runs and
78
+ # gc_state_at_end.
64
79
  result[:heap_live_slots_growth] = (after[:heap_live_slots] || 0) - (before[:heap_live_slots] || 0)
80
+ # The GC's phase when the window closed: "none" once the last GC has
81
+ # finished, "marking" or "sweeping" while it's still running (Ruby marks
82
+ # and sweeps in steps between allocations).
83
+ result[:gc_state_at_end] = after[:gc_state].to_s if after[:gc_state]
65
84
  # Off-heap malloc pressure pending at request end (see snapshot).
66
85
  result[:malloc_increase_bytes] = after[:malloc_increase_bytes] || 0
67
86
  result[:oldmalloc_increase_bytes] = after[:oldmalloc_increase_bytes] || 0
@@ -307,6 +307,7 @@ module DeadBro
307
307
  count: stats[:count] || 0,
308
308
  heap_allocated_pages: stats[:heap_allocated_pages] || 0,
309
309
  heap_sorted_pages: stats[:heap_sorted_pages] || 0,
310
+ heap_live_slots: stats[:heap_live_slots] || 0,
310
311
  total_allocated_objects: stats[:total_allocated_objects] || 0
311
312
  }
312
313
  else
@@ -141,8 +141,9 @@ module DeadBro
141
141
  def calculate_memory_efficiency(samples)
142
142
  return {} if samples.length < 2
143
143
 
144
+ # memory_usage is RSS in MB; object_count is live heap slots.
144
145
  memory_per_object = samples.map do |sample|
145
- (sample[:object_count] > 0) ? sample[:memory_usage] / sample[:object_count] : 0
146
+ (sample[:object_count] > 0) ? sample[:memory_usage] * 1024.0 / sample[:object_count] : 0
146
147
  end
147
148
 
148
149
  gc_efficiency = []
@@ -139,13 +139,16 @@ module DeadBro
139
139
  }
140
140
  end
141
141
 
142
+ # One GC.stat read for both the leak-detection sample and the payload.
143
+ end_gc_stats = gc_stats
144
+
142
145
  # Record memory sample for leak detection (only if memory tracking enabled)
143
146
  if DeadBro.configuration.memory_tracking_enabled
144
147
  DeadBro::MemoryLeakDetector.record_memory_sample({
145
148
  memory_usage: memory_usage_mb,
146
- gc_count: gc_stats[:count],
147
- heap_pages: gc_stats[:heap_allocated_pages],
148
- object_count: gc_stats[:heap_live_slots],
149
+ gc_count: end_gc_stats[:count],
150
+ heap_pages: end_gc_stats[:heap_allocated_pages],
151
+ object_count: end_gc_stats[:heap_live_slots],
149
152
  request_id: data[:request_id],
150
153
  controller: data[:controller],
151
154
  action: data[:action]
@@ -162,7 +165,7 @@ module DeadBro
162
165
  view_runtime_ms: data[:view_runtime],
163
166
  db_runtime_ms: data[:db_runtime],
164
167
  memory_usage: memory_usage_mb,
165
- gc_stats: gc_stats,
168
+ gc_stats: end_gc_stats,
166
169
  sql_count: sql_count(data),
167
170
  sql_queries: sql_queries,
168
171
  transaction_events: transaction_events,
@@ -459,6 +462,7 @@ module DeadBro
459
462
  count: stats[:count] || 0,
460
463
  heap_allocated_pages: stats[:heap_allocated_pages] || 0,
461
464
  heap_sorted_pages: stats[:heap_sorted_pages] || 0,
465
+ heap_live_slots: stats[:heap_live_slots] || 0,
462
466
  total_allocated_objects: stats[:total_allocated_objects] || 0
463
467
  }
464
468
  else
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadBro
4
- VERSION = "0.2.35"
4
+ VERSION = "0.2.36"
5
5
  end
6
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dead_bro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.35
4
+ version: 0.2.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuel Comsa