catpm 0.8.1 → 0.8.3

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: 5e79483c6546d21e4b6054d481f3625616eb0c4044f1251d11be907983dadeb7
4
- data.tar.gz: 555afaca6bfb09499fea0c24359ee7709d8b982759fb6717a577882d64c543cc
3
+ metadata.gz: 68fffa206b7baa5919309c1d54110c58205fe7b48bf37111724ba00b5ed07ef7
4
+ data.tar.gz: eba9254577cdbc6afa64d1ec352d2bdc55827e1668890dd0dbba2ff16696ac3c
5
5
  SHA512:
6
- metadata.gz: 2971732b88f54566048545753164728886136408fc0db9d82e20652c08b098973d0889a839fd66cd4a504def1a9c576cf2eab822dac9b381eda0f2e40d908123
7
- data.tar.gz: c8c7b98c039e7c483e8d4e3e3a9e8c9f06a58e7cd396d303da5bd37c9771d6b463d84d62c9da5c9f87e3e1f688ebefe7bb8b016d7eac477030588513b2e0988c
6
+ metadata.gz: d7c6016b9f7f3087638e0b1ab47ecb140bd3eff31b2c0988ab953590c435f162336b29e380cc231a5eb66d95c5ad4c7b93a77399fd513a99f8bcf0e44aface84
7
+ data.tar.gz: 9a923abb3d7f882c76b9665509adea19173368d0395ec1b99b73d6288901bfdff6d0d89336d9081e5f7b5ecea96f6274e59e0cf62d7f17f6be7ddc221bdff18c
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  gem build catpm.gemspec
2
- gem push catpm-0.7.0.gem
2
+ gem push catpm-0.8.0.gem
3
3
 
4
4
  # Catpm
5
5
 
@@ -144,6 +144,8 @@ module Catpm
144
144
  parent_index: error_parent
145
145
  }
146
146
  end
147
+
148
+ req_segments.release! # free internal state for GC
147
149
  end
148
150
 
149
151
  context = scrub(context)
@@ -314,6 +316,8 @@ module Catpm
314
316
  parent_index: error_parent
315
317
  }
316
318
  end
319
+
320
+ req_segments.release! # free internal state for GC
317
321
  end
318
322
 
319
323
  context = scrub(context)
@@ -32,6 +32,7 @@ module Catpm
32
32
  ensure
33
33
  if Catpm.config.instrument_segments
34
34
  req_segments&.stop_sampler
35
+ req_segments&.release!
35
36
  Thread.current[:catpm_request_segments] = nil
36
37
  end
37
38
  end
@@ -95,12 +95,25 @@ module Catpm
95
95
 
96
96
  def sampler_segments
97
97
  return [] if @call_tree # call tree mode produces segments via call_tree_segments
98
- @sampler&.to_segments(tracked_ranges: @tracked_ranges) || []
98
+ result = @sampler&.to_segments(tracked_ranges: @tracked_ranges) || []
99
+ @sampler&.clear_samples! # free raw backtrace data immediately
100
+ result
99
101
  end
100
102
 
101
103
  def call_tree_segments
102
104
  return [] unless @sampler && @call_tree
103
- @sampler.to_call_tree(tracked_ranges: @tracked_ranges)
105
+ result = @sampler.to_call_tree(tracked_ranges: @tracked_ranges)
106
+ @sampler.clear_samples! # free raw backtrace data immediately
107
+ result
108
+ end
109
+
110
+ # Release all internal state after Collector has consumed data.
111
+ # Helps GC reclaim memory sooner on small/constrained hosts.
112
+ def release!
113
+ @segments = []
114
+ @summary = {}
115
+ @tracked_ranges = []
116
+ @sampler = nil
104
117
  end
105
118
 
106
119
  def overflowed?
@@ -4,8 +4,8 @@ module Catpm
4
4
  class StackSampler
5
5
  MS_PER_SECOND = 1000.0
6
6
  MIN_SEGMENT_DURATION_MS = 1.0
7
- CALL_TREE_SAMPLE_INTERVAL = 0.001 # 1ms — higher resolution for call tree reconstruction
8
7
  SAMPLING_THREAD_PRIORITY = -1
8
+ HARD_SAMPLE_CAP = 500 # absolute max even when config allows unlimited — prevents heap bloat
9
9
 
10
10
  # Single global thread that samples all active requests.
11
11
  # Avoids creating a thread per request.
@@ -14,29 +14,32 @@ module Catpm
14
14
  @mutex = Mutex.new
15
15
  @samplers = []
16
16
  @thread = nil
17
+ @stop = false
17
18
  end
18
19
 
19
20
  def register(sampler)
20
21
  @mutex.synchronize do
21
22
  @samplers << sampler
23
+ @stop = false
22
24
  start_thread unless @thread&.alive?
23
25
  end
24
26
  end
25
27
 
26
28
  def unregister(sampler)
27
- @mutex.synchronize { @samplers.delete(sampler) }
29
+ @mutex.synchronize do
30
+ @samplers.delete(sampler)
31
+ @stop = true if @samplers.empty?
32
+ end
28
33
  end
29
34
 
30
35
  private
31
36
 
32
37
  def start_thread
38
+ @stop = false
33
39
  @thread = Thread.new do
40
+ interval = Catpm.config.stack_sample_interval
34
41
  loop do
35
- interval = if Catpm.config.instrument_call_tree
36
- [CALL_TREE_SAMPLE_INTERVAL, Catpm.config.stack_sample_interval].min
37
- else
38
- Catpm.config.stack_sample_interval
39
- end
42
+ break if @stop
40
43
  sleep(interval)
41
44
  sample_all
42
45
  end
@@ -45,8 +48,10 @@ module Catpm
45
48
  end
46
49
 
47
50
  def sample_all
48
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
51
  targets = @mutex.synchronize { @samplers.dup }
52
+ return if targets.empty?
53
+
54
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
55
  targets.each { |s| s.capture(now) }
51
56
  end
52
57
  end
@@ -70,14 +75,21 @@ module Catpm
70
75
 
71
76
  def stop
72
77
  self.class.loop.unregister(self)
78
+ @target = nil # release thread reference for GC
79
+ end
80
+
81
+ # Free raw backtrace data after segments have been extracted.
82
+ def clear_samples!
83
+ @samples = []
73
84
  end
74
85
 
75
86
  # Called by SamplingLoop from the global thread
76
87
  def capture(now)
77
88
  max = Catpm.config.max_stack_samples_per_request
78
- return if max && @samples.size >= max
89
+ cap = max ? [max, HARD_SAMPLE_CAP].min : HARD_SAMPLE_CAP
90
+ return if @samples.size >= cap
79
91
 
80
- locs = @target.backtrace_locations
92
+ locs = @target&.backtrace_locations
81
93
  @samples << [now, locs] if locs
82
94
  end
83
95
 
@@ -239,9 +251,7 @@ module Catpm
239
251
  end
240
252
 
241
253
  def call_tree_node_duration(node)
242
- interval = Catpm.config.instrument_call_tree ?
243
- [CALL_TREE_SAMPLE_INTERVAL, Catpm.config.stack_sample_interval].min :
244
- Catpm.config.stack_sample_interval
254
+ interval = Catpm.config.stack_sample_interval
245
255
  [
246
256
  (node[:last_time] - node[:first_time]) * MS_PER_SECOND,
247
257
  node[:count] * interval * MS_PER_SECOND
data/lib/catpm/trace.rb CHANGED
@@ -76,18 +76,15 @@ module Catpm
76
76
  owns_segments = false
77
77
 
78
78
  if req_segments.nil? && config.instrument_segments
79
+ use_sampler = config.instrument_stack_sampler || config.instrument_call_tree
79
80
  req_segments = RequestSegments.new(
80
81
  max_segments: config.max_segments_per_request,
81
82
  request_start: Process.clock_gettime(Process::CLOCK_MONOTONIC),
82
- stack_sample: config.instrument_stack_sampler
83
+ stack_sample: use_sampler,
84
+ call_tree: config.instrument_call_tree
83
85
  )
84
86
  Thread.current[:catpm_request_segments] = req_segments
85
87
  owns_segments = true
86
-
87
- if config.instrument_call_tree
88
- call_tracer = CallTracer.new(request_segments: req_segments)
89
- call_tracer.start
90
- end
91
88
  end
92
89
 
93
90
  if req_segments
@@ -105,7 +102,6 @@ module Catpm
105
102
  raise
106
103
  ensure
107
104
  duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0
108
- call_tracer&.stop
109
105
  req_segments&.pop_span(ctrl_idx) if ctrl_idx
110
106
  req_segments&.stop_sampler
111
107
 
@@ -116,6 +112,7 @@ module Catpm
116
112
  )
117
113
 
118
114
  if owns_segments
115
+ req_segments&.release!
119
116
  Thread.current[:catpm_request_segments] = nil
120
117
  end
121
118
  end
data/lib/catpm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Catpm
4
- VERSION = '0.8.1'
4
+ VERSION = '0.8.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: catpm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''