catpm 0.6.5 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/controllers/catpm/application_controller.rb +15 -0
- data/app/controllers/catpm/system_controller.rb +4 -0
- data/app/helpers/catpm/application_helper.rb +142 -0
- data/app/views/catpm/system/index.html.erb +76 -479
- data/app/views/catpm/system/pipeline.html.erb +344 -0
- data/app/views/layouts/catpm/application.html.erb +40 -0
- data/app/views/layouts/catpm/pipeline.html.erb +79 -0
- data/config/routes.rb +1 -0
- data/lib/catpm/buffer.rb +22 -2
- data/lib/catpm/call_tracer.rb +32 -9
- data/lib/catpm/flusher.rb +44 -63
- data/lib/catpm/request_segments.rb +6 -1
- data/lib/catpm/version.rb +1 -1
- metadata +3 -1
data/lib/catpm/flusher.rb
CHANGED
|
@@ -16,6 +16,16 @@ module Catpm
|
|
|
16
16
|
@thread = nil
|
|
17
17
|
@pid = nil
|
|
18
18
|
@mutex = Mutex.new
|
|
19
|
+
@sleep_mutex = Mutex.new
|
|
20
|
+
@flush_signal = ConditionVariable.new
|
|
21
|
+
|
|
22
|
+
# When buffer reaches capacity, wake flusher immediately
|
|
23
|
+
@buffer.on_flush_needed { signal_flush }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Wake the flusher thread for an immediate flush cycle.
|
|
27
|
+
def signal_flush
|
|
28
|
+
@sleep_mutex.synchronize { @flush_signal.signal }
|
|
19
29
|
end
|
|
20
30
|
|
|
21
31
|
def start
|
|
@@ -32,7 +42,9 @@ module Catpm
|
|
|
32
42
|
@pid = Process.pid
|
|
33
43
|
@thread = Thread.new do
|
|
34
44
|
while @running
|
|
35
|
-
|
|
45
|
+
@sleep_mutex.synchronize do
|
|
46
|
+
@flush_signal.wait(@sleep_mutex, effective_interval)
|
|
47
|
+
end
|
|
36
48
|
flush_cycle if @running
|
|
37
49
|
end
|
|
38
50
|
rescue => e
|
|
@@ -61,6 +73,7 @@ module Catpm
|
|
|
61
73
|
@thread = nil
|
|
62
74
|
end
|
|
63
75
|
|
|
76
|
+
signal_flush # Wake flusher thread for clean exit
|
|
64
77
|
thread&.join(timeout)
|
|
65
78
|
flush_cycle # Final flush
|
|
66
79
|
end
|
|
@@ -83,8 +96,8 @@ module Catpm
|
|
|
83
96
|
adapter.persist_buckets(buckets)
|
|
84
97
|
|
|
85
98
|
bucket_map = build_bucket_map(buckets)
|
|
86
|
-
samples = rotate_samples(samples)
|
|
87
99
|
adapter.persist_samples(samples, bucket_map)
|
|
100
|
+
trim_samples(samples)
|
|
88
101
|
adapter.persist_errors(errors)
|
|
89
102
|
end
|
|
90
103
|
|
|
@@ -227,75 +240,43 @@ module Catpm
|
|
|
227
240
|
end
|
|
228
241
|
|
|
229
242
|
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
# Trim excess samples AFTER insert. Simpler and guaranteed correct —
|
|
244
|
+
# no stale-cache issues when a single flush batch crosses the limit.
|
|
245
|
+
def trim_samples(samples)
|
|
246
|
+
return if samples.empty?
|
|
232
247
|
|
|
233
|
-
# Pre-fetch counts for all endpoints and types in bulk
|
|
234
248
|
endpoint_keys = samples.map { |s| s[:bucket_key][0..2] }.uniq
|
|
235
|
-
error_fps = samples.filter_map { |s| s[:error_fingerprint] }.uniq
|
|
236
|
-
|
|
237
|
-
# Build counts cache: { [kind, target, op, type] => count }
|
|
238
|
-
counts_cache = {}
|
|
239
|
-
if endpoint_keys.any?
|
|
240
|
-
Catpm::Sample.joins(:bucket)
|
|
241
|
-
.where(catpm_buckets: { kind: endpoint_keys.map(&:first), target: endpoint_keys.map { |k| k[1] }, operation: endpoint_keys.map { |k| k[2] } })
|
|
242
|
-
.where(sample_type: %w[random slow])
|
|
243
|
-
.group('catpm_buckets.kind', 'catpm_buckets.target', 'catpm_buckets.operation', 'catpm_samples.sample_type')
|
|
244
|
-
.count
|
|
245
|
-
.each { |(kind, target, op, type), cnt| counts_cache[[kind, target, op, type]] = cnt }
|
|
246
|
-
end
|
|
247
249
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
endpoint_keys.each do |kind, target, operation|
|
|
251
|
+
endpoint_scope = Catpm::Sample.joins(:bucket)
|
|
252
|
+
.where(catpm_buckets: { kind: kind, target: target, operation: operation })
|
|
253
|
+
|
|
254
|
+
# Random: keep newest N
|
|
255
|
+
max_random = Catpm.config.max_random_samples_per_endpoint
|
|
256
|
+
trim_by_column(endpoint_scope.where(sample_type: 'random'), max_random, :recorded_at) if max_random
|
|
257
|
+
|
|
258
|
+
# Slow: keep highest-duration N
|
|
259
|
+
max_slow = Catpm.config.max_slow_samples_per_endpoint
|
|
260
|
+
trim_by_column(endpoint_scope.where(sample_type: 'slow'), max_slow, :duration) if max_slow
|
|
261
|
+
|
|
253
262
|
end
|
|
254
263
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if max_random
|
|
262
|
-
cache_key = [kind, target, operation, 'random']
|
|
263
|
-
if (counts_cache[cache_key] || 0) >= max_random
|
|
264
|
-
oldest = Catpm::Sample.joins(:bucket)
|
|
265
|
-
.where(catpm_buckets: { kind: kind, target: target, operation: operation })
|
|
266
|
-
.where(sample_type: 'random').order(recorded_at: :asc).first
|
|
267
|
-
oldest&.destroy
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
when 'slow'
|
|
271
|
-
max_slow = Catpm.config.max_slow_samples_per_endpoint
|
|
272
|
-
if max_slow
|
|
273
|
-
cache_key = [kind, target, operation, 'slow']
|
|
274
|
-
if (counts_cache[cache_key] || 0) >= max_slow
|
|
275
|
-
weakest = Catpm::Sample.joins(:bucket)
|
|
276
|
-
.where(catpm_buckets: { kind: kind, target: target, operation: operation })
|
|
277
|
-
.where(sample_type: 'slow').order(duration: :asc).first
|
|
278
|
-
if weakest && sample[:duration] > weakest.duration
|
|
279
|
-
weakest.destroy
|
|
280
|
-
else
|
|
281
|
-
sample[:_skip] = true
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
when 'error'
|
|
286
|
-
max_err = Catpm.config.max_error_samples_per_fingerprint
|
|
287
|
-
if max_err
|
|
288
|
-
fp = sample[:error_fingerprint]
|
|
289
|
-
if fp && (error_counts[fp] || 0) >= max_err
|
|
290
|
-
oldest = Catpm::Sample.where(sample_type: 'error', error_fingerprint: fp)
|
|
291
|
-
.order(recorded_at: :asc).first
|
|
292
|
-
oldest&.destroy
|
|
293
|
-
end
|
|
294
|
-
end
|
|
264
|
+
# Errors: per-fingerprint cap (keep newest within each fingerprint)
|
|
265
|
+
max_err_fp = Catpm.config.max_error_samples_per_fingerprint
|
|
266
|
+
if max_err_fp
|
|
267
|
+
fps = samples.filter_map { |s| s[:error_fingerprint] }.uniq
|
|
268
|
+
fps.each do |fp|
|
|
269
|
+
trim_by_column(Catpm::Sample.where(sample_type: 'error', error_fingerprint: fp), max_err_fp, :recorded_at)
|
|
295
270
|
end
|
|
296
271
|
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def trim_by_column(scope, max, keep_column)
|
|
275
|
+
count = scope.count
|
|
276
|
+
return if count <= max
|
|
297
277
|
|
|
298
|
-
|
|
278
|
+
excess_ids = scope.order(keep_column => :asc).limit(count - max).pluck(:id)
|
|
279
|
+
Catpm::Sample.where(id: excess_ids).delete_all if excess_ids.any?
|
|
299
280
|
end
|
|
300
281
|
|
|
301
282
|
def build_error_context(event)
|
|
@@ -65,7 +65,12 @@ module Catpm
|
|
|
65
65
|
def pop_span(index)
|
|
66
66
|
return unless index
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
# Pop from stack — typically it's the last element (LIFO)
|
|
69
|
+
if @span_stack.last == index
|
|
70
|
+
@span_stack.pop
|
|
71
|
+
else
|
|
72
|
+
@span_stack.delete(index)
|
|
73
|
+
end
|
|
69
74
|
segment = @segments[index]
|
|
70
75
|
return unless segment
|
|
71
76
|
|
data/lib/catpm/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
@@ -68,7 +68,9 @@ files:
|
|
|
68
68
|
- app/views/catpm/shared/_segments_waterfall.html.erb
|
|
69
69
|
- app/views/catpm/status/index.html.erb
|
|
70
70
|
- app/views/catpm/system/index.html.erb
|
|
71
|
+
- app/views/catpm/system/pipeline.html.erb
|
|
71
72
|
- app/views/layouts/catpm/application.html.erb
|
|
73
|
+
- app/views/layouts/catpm/pipeline.html.erb
|
|
72
74
|
- config/routes.rb
|
|
73
75
|
- db/migrate/20250601000001_create_catpm_tables.rb
|
|
74
76
|
- lib/catpm.rb
|