catpm 0.6.2 → 0.6.4

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: eac4dbe3e1304875bb2ead1573dc51e3acd760b8dcc18901c122d8004b58e976
4
- data.tar.gz: c2a94022740f767cebdf44a98e737e495f182ada282528f145ab36deac28daca
3
+ metadata.gz: 3085a527e26332fa08ecca7de2efaa10b43cedc80b0505c88edae91edd7ec66f
4
+ data.tar.gz: 396130a945ff87931a1abea5c9b287b2529416f95a19aee47b5d8daa4e3812ca
5
5
  SHA512:
6
- metadata.gz: 499d38cbc6db1313bfb9f77a23476a450617024efbeb0de15c77c33819bf9844d45bbfe3525d8d1b849897a925113aeb4ca93e826f9da49bdd23d0d710bb8f6a
7
- data.tar.gz: 7db602ccd776bb8882e634c25845ac8792e50644afc72c7c17759ccc701eb4785664f84fc0ec17b46de97142df3a095247b18bbbed000a2f637cd68b12cdc315
6
+ metadata.gz: 4a043995497b5b3046cdee78037f0a89b9801d84f6d58df30a06b2380b866d7d9157c9d33f823e0a50c643b734d37339dcfab3f3a4b3d7cce70a381eb61c8432
7
+ data.tar.gz: 5e9be269fae7cc4dae79427c8d77284631bda64a25a10235da3755d8a9ec194eb85ff4e0fded2690aa633d1100295734081116d5484bd4be6ae6b69b1f7b21d4
@@ -81,9 +81,9 @@ module Catpm
81
81
  .joins(:bucket)
82
82
  .where(catpm_buckets: { kind: @kind, target: @target, operation: @operation })
83
83
 
84
- @slow_samples = endpoint_samples.where(sample_type: 'slow').order(duration: :desc).limit(10)
85
- @samples = endpoint_samples.where(sample_type: 'random').order(recorded_at: :desc).limit(10)
86
- @error_samples = endpoint_samples.where(sample_type: 'error').order(recorded_at: :desc).limit(10)
84
+ @slow_samples = endpoint_samples.where(sample_type: 'slow').order(duration: :desc)
85
+ @samples = endpoint_samples.where(sample_type: 'random').order(recorded_at: :desc)
86
+ @error_samples = endpoint_samples.where(sample_type: 'error').order(recorded_at: :desc)
87
87
 
88
88
  @pref = Catpm::EndpointPref.find_by(kind: @kind, target: @target, operation: @operation)
89
89
  @active_error_count = Catpm::ErrorRecord.unresolved.count
@@ -45,6 +45,7 @@ module Catpm
45
45
 
46
46
  if req_segments
47
47
  segments = segment_data[:segments]
48
+ collapse_code_wrappers(segments)
48
49
 
49
50
  # Inject root request segment with full duration
50
51
  root_segment = {
@@ -227,6 +228,7 @@ module Catpm
227
228
 
228
229
  if req_segments && segment_data
229
230
  segments = segment_data[:segments]
231
+ collapse_code_wrappers(segments)
230
232
 
231
233
  # Inject root request segment
232
234
  root_segment = {
@@ -331,6 +333,55 @@ module Catpm
331
333
 
332
334
  private
333
335
 
336
+ # Remove near-zero-duration "code" spans that merely wrap a "controller" span.
337
+ # This happens when CallTracer (TracePoint) captures a thin dispatch method
338
+ # (e.g. Telegram::WebhookController#process) whose :return fires before the
339
+ # ActiveSupport controller notification finishes.
340
+ # Mutates segments in place: removes the wrapper and re-indexes parent references.
341
+ def collapse_code_wrappers(segments)
342
+ # Identify code spans to collapse: near-zero duration wrapping a controller child
343
+ collapse = {}
344
+ segments.each_with_index do |seg, i|
345
+ next unless seg[:type] == 'code'
346
+ next unless (seg[:duration] || 0).to_f < 1.0
347
+
348
+ has_controller_child = segments.any? { |s| s[:parent_index] == i && s[:type] == 'controller' }
349
+ next unless has_controller_child
350
+
351
+ collapse[i] = seg[:parent_index]
352
+ end
353
+
354
+ return if collapse.empty?
355
+
356
+ # Reparent children of collapsed spans
357
+ segments.each do |seg|
358
+ pi = seg[:parent_index]
359
+ next unless pi && collapse.key?(pi)
360
+ new_parent = collapse[pi]
361
+ if new_parent.nil?
362
+ seg.delete(:parent_index)
363
+ else
364
+ seg[:parent_index] = new_parent
365
+ end
366
+ end
367
+
368
+ # Build old→new index mapping, remove collapsed spans
369
+ old_to_new = {}
370
+ kept = []
371
+ segments.each_with_index do |seg, i|
372
+ next if collapse.key?(i)
373
+ old_to_new[i] = kept.size
374
+ kept << seg
375
+ end
376
+
377
+ # Rewrite parent references to new indices
378
+ kept.each do |seg|
379
+ seg[:parent_index] = old_to_new[seg[:parent_index]] if seg[:parent_index]
380
+ end
381
+
382
+ segments.replace(kept)
383
+ end
384
+
334
385
  # Determine sample type at event creation time so only sampled events
335
386
  # carry full context in the buffer. Includes filling phase via
336
387
  # process-level counter (resets on restart — acceptable approximation).
@@ -355,6 +406,10 @@ module Catpm
355
406
  @random_sample_counts ||= Hash.new(0)
356
407
  end
357
408
 
409
+ def reset_sample_counts!
410
+ @random_sample_counts = nil
411
+ end
412
+
358
413
  def inject_gap_segments(segments, req_segments, gap, ctrl_idx, ctrl_seg)
359
414
  sampler_groups = req_segments&.sampler_segments || []
360
415
 
@@ -2,57 +2,66 @@
2
2
 
3
3
  module Catpm
4
4
  class Configuration
5
+ # Boolean / non-numeric settings — plain attr_accessor
5
6
  attr_accessor :enabled,
6
7
  :instrument_http,
7
8
  :instrument_jobs,
8
9
  :instrument_segments,
9
10
  :instrument_net_http,
10
11
  :instrument_stack_sampler,
11
- :max_segments_per_request,
12
- :segment_source_threshold,
13
- :max_sql_length,
14
- :slow_threshold,
12
+ :instrument_middleware_stack,
13
+ :instrument_call_tree,
15
14
  :slow_threshold_per_kind,
16
15
  :ignored_targets,
17
- :retention_period,
18
- :max_buffer_memory,
19
- :flush_interval,
20
- :flush_jitter,
21
- :max_error_contexts,
22
16
  :bucket_sizes,
23
17
  :error_handler,
24
18
  :http_basic_auth_user,
25
19
  :http_basic_auth_password,
26
20
  :access_policy,
27
21
  :additional_filter_parameters,
28
- :instrument_middleware_stack,
29
22
  :auto_instrument_methods,
30
23
  :service_base_classes,
31
- :random_sample_rate,
32
- :max_random_samples_per_endpoint,
33
- :max_slow_samples_per_endpoint,
34
- :max_error_samples_per_fingerprint,
35
- :cleanup_interval,
36
- :circuit_breaker_failure_threshold,
37
- :circuit_breaker_recovery_timeout,
38
- :sqlite_busy_timeout,
39
- :persistence_batch_size,
40
- :backtrace_lines,
41
- :shutdown_timeout,
42
24
  :events_enabled,
43
- :events_max_samples_per_name,
44
25
  :track_own_requests,
45
- :stack_sample_interval,
46
- :max_stack_samples_per_request,
47
26
  :downsampling_thresholds,
48
- :max_error_detail_length,
49
- :max_fingerprint_app_frames,
50
- :max_fingerprint_gem_frames,
51
- :cleanup_batch_size,
52
- :caller_scan_depth,
53
- :instrument_call_tree,
54
27
  :show_untracked_segments
55
28
 
29
+ # Numeric settings that must be positive numbers (nil not allowed)
30
+ REQUIRED_NUMERIC = %i[
31
+ max_sql_length slow_threshold max_buffer_memory flush_interval
32
+ flush_jitter max_error_contexts random_sample_rate cleanup_interval
33
+ circuit_breaker_failure_threshold circuit_breaker_recovery_timeout
34
+ sqlite_busy_timeout persistence_batch_size shutdown_timeout
35
+ events_max_samples_per_name stack_sample_interval
36
+ max_stack_samples_per_request max_error_detail_length
37
+ max_fingerprint_app_frames max_fingerprint_gem_frames
38
+ cleanup_batch_size caller_scan_depth segment_source_threshold
39
+ ].freeze
40
+
41
+ # Numeric settings where nil means "unlimited"
42
+ OPTIONAL_NUMERIC = %i[
43
+ max_segments_per_request retention_period backtrace_lines
44
+ max_random_samples_per_endpoint max_slow_samples_per_endpoint
45
+ max_error_samples_per_fingerprint
46
+ ].freeze
47
+
48
+ (REQUIRED_NUMERIC + OPTIONAL_NUMERIC).each do |attr|
49
+ attr_reader attr
50
+
51
+ define_method(:"#{attr}=") do |value|
52
+ if REQUIRED_NUMERIC.include?(attr)
53
+ unless value.is_a?(Numeric)
54
+ raise ArgumentError, "catpm config.#{attr} must be a number, got #{value.inspect}"
55
+ end
56
+ else
57
+ unless value.nil? || value.is_a?(Numeric)
58
+ raise ArgumentError, "catpm config.#{attr} must be a number or nil, got #{value.inspect}"
59
+ end
60
+ end
61
+ instance_variable_set(:"@#{attr}", value)
62
+ end
63
+ end
64
+
56
65
  def initialize
57
66
  @enabled = true
58
67
  @instrument_http = true
data/lib/catpm/event.rb CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  module Catpm
4
4
  class Event
5
- OBJECT_OVERHEAD = 40 # bytes, Ruby object header
6
- REF_SIZE = 8 # bytes, pointer on 64-bit
5
+ OBJECT_OVERHEAD = 40 # bytes, Ruby object header
6
+ REF_SIZE = 8 # bytes, pointer on 64-bit
7
+ HASH_ENTRY_SIZE = 80 # bytes, per key-value pair in a Hash (bucket + key obj + value obj)
7
8
  NUMERIC_FIELDS_SIZE = 64 # fixed numeric fields (duration, timestamps, etc.)
8
9
 
9
10
  attr_accessor :kind, :target, :operation, :duration, :started_at,
@@ -69,13 +70,28 @@ module Catpm
69
70
  def context_bytes
70
71
  return 0 if context.nil? || context.empty?
71
72
 
72
- context.to_json.bytesize + REF_SIZE
73
+ estimate_hash_bytes(context)
73
74
  end
74
75
 
75
76
  def metadata_bytes
76
77
  return 0 if metadata.nil? || metadata.empty?
77
78
 
78
- metadata.to_json.bytesize + REF_SIZE
79
+ estimate_hash_bytes(metadata)
80
+ end
81
+
82
+ def estimate_hash_bytes(obj)
83
+ case obj
84
+ when Hash
85
+ OBJECT_OVERHEAD + obj.sum { |k, v| HASH_ENTRY_SIZE + estimate_hash_bytes(k) + estimate_hash_bytes(v) }
86
+ when Array
87
+ OBJECT_OVERHEAD + obj.sum { |v| REF_SIZE + estimate_hash_bytes(v) }
88
+ when String
89
+ OBJECT_OVERHEAD + obj.bytesize
90
+ when Symbol, Integer, Float, TrueClass, FalseClass, NilClass
91
+ REF_SIZE
92
+ else
93
+ OBJECT_OVERHEAD + REF_SIZE
94
+ end
79
95
  end
80
96
  end
81
97
  end
data/lib/catpm/flusher.rb CHANGED
@@ -365,6 +365,7 @@ module Catpm
365
365
  @last_cleanup_at = Time.now
366
366
  downsample_buckets
367
367
  cleanup_expired_data if Catpm.config.retention_period
368
+ Collector.reset_sample_counts!
368
369
  end
369
370
 
370
371
  def downsample_buckets
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.6.2'
4
+ VERSION = '0.6.4'
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.6.2
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''