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 +4 -4
- data/README.md +1 -1
- data/lib/catpm/collector.rb +4 -0
- data/lib/catpm/middleware.rb +1 -0
- data/lib/catpm/request_segments.rb +15 -2
- data/lib/catpm/stack_sampler.rb +23 -13
- data/lib/catpm/trace.rb +4 -7
- data/lib/catpm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68fffa206b7baa5919309c1d54110c58205fe7b48bf37111724ba00b5ed07ef7
|
|
4
|
+
data.tar.gz: eba9254577cdbc6afa64d1ec352d2bdc55827e1668890dd0dbba2ff16696ac3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7c6016b9f7f3087638e0b1ab47ecb140bd3eff31b2c0988ab953590c435f162336b29e380cc231a5eb66d95c5ad4c7b93a77399fd513a99f8bcf0e44aface84
|
|
7
|
+
data.tar.gz: 9a923abb3d7f882c76b9665509adea19173368d0395ec1b99b73d6288901bfdff6d0d89336d9081e5f7b5ecea96f6274e59e0cf62d7f17f6be7ddc221bdff18c
|
data/README.md
CHANGED
data/lib/catpm/collector.rb
CHANGED
|
@@ -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)
|
data/lib/catpm/middleware.rb
CHANGED
|
@@ -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?
|
data/lib/catpm/stack_sampler.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
cap = max ? [max, HARD_SAMPLE_CAP].min : HARD_SAMPLE_CAP
|
|
90
|
+
return if @samples.size >= cap
|
|
79
91
|
|
|
80
|
-
locs = @target
|
|
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.
|
|
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:
|
|
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