catpm 0.6.4 → 0.6.6

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: 3085a527e26332fa08ecca7de2efaa10b43cedc80b0505c88edae91edd7ec66f
4
- data.tar.gz: 396130a945ff87931a1abea5c9b287b2529416f95a19aee47b5d8daa4e3812ca
3
+ metadata.gz: 584e9923c1e96355460ab405299df1d35e2116b4ca81b07bb350ae6d8ec773cf
4
+ data.tar.gz: c972d35aad27e3a6da12029192fbbb25e5674fd29ab4994b5c8f6baed0995c6f
5
5
  SHA512:
6
- metadata.gz: 4a043995497b5b3046cdee78037f0a89b9801d84f6d58df30a06b2380b866d7d9157c9d33f823e0a50c643b734d37339dcfab3f3a4b3d7cce70a381eb61c8432
7
- data.tar.gz: 5e9be269fae7cc4dae79427c8d77284631bda64a25a10235da3755d8a9ec194eb85ff4e0fded2690aa633d1100295734081116d5484bd4be6ae6b69b1f7b21d4
6
+ metadata.gz: 9fc17e982f9f39197c8398db38b601860c0977bb311b48204016fed327d7cc58c95bc897d74fe0b8e7bc72f75421eb95319bf64f007485348ef4b62179dbd2e8
7
+ data.tar.gz: 688688d4f266af7950c70bb7377d9846ebf3377550446d0761fc20426b7081ce5006d7ef567a6a589d79bc9cb9eb29a3b0b77c314832ce17c383f355f435e1f7
@@ -121,8 +121,9 @@
121
121
 
122
122
  <tr><td colspan="2" style="font-weight:600;color:var(--text-2);font-size:11px;text-transform:uppercase;letter-spacing:0.5px;padding-top:12px">Sampling</td></tr>
123
123
  <tr><td>Random Sample Rate</td><td class="mono">1 in <%= @config.random_sample_rate %></td></tr>
124
- <tr><td>Max Random Samples / Endpoint</td><td class="mono"><%= @config.max_random_samples_per_endpoint %></td></tr>
125
- <tr><td>Max Slow Samples / Endpoint</td><td class="mono"><%= @config.max_slow_samples_per_endpoint %></td></tr>
124
+ <tr><td>Max Random Samples / Endpoint</td><td class="mono"><%= @config.max_random_samples_per_endpoint || "unlimited" %></td></tr>
125
+ <tr><td>Max Slow Samples / Endpoint</td><td class="mono"><%= @config.max_slow_samples_per_endpoint || "unlimited" %></td></tr>
126
+ <tr><td>Max Error Samples / Fingerprint</td><td class="mono"><%= @config.max_error_samples_per_fingerprint || "unlimited" %></td></tr>
126
127
 
127
128
  <tr><td colspan="2" style="font-weight:600;color:var(--text-2);font-size:11px;text-transform:uppercase;letter-spacing:0.5px;padding-top:12px">Buffer & Flush</td></tr>
128
129
  <tr><td>Max Buffer Memory</td><td class="mono"><%= number_to_human_size(@config.max_buffer_memory) %></td></tr>
@@ -28,21 +28,21 @@ module Catpm
28
28
 
29
29
  # Numeric settings that must be positive numbers (nil not allowed)
30
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
31
+ slow_threshold max_buffer_memory flush_interval flush_jitter
32
+ random_sample_rate cleanup_interval
33
33
  circuit_breaker_failure_threshold circuit_breaker_recovery_timeout
34
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
35
+ stack_sample_interval segment_source_threshold
39
36
  ].freeze
40
37
 
41
- # Numeric settings where nil means "unlimited"
38
+ # Numeric settings where nil means "no limit" / "disabled"
42
39
  OPTIONAL_NUMERIC = %i[
43
40
  max_segments_per_request retention_period backtrace_lines
44
41
  max_random_samples_per_endpoint max_slow_samples_per_endpoint
45
- max_error_samples_per_fingerprint
42
+ max_error_samples_per_fingerprint max_sql_length max_error_contexts
43
+ events_max_samples_per_name max_stack_samples_per_request
44
+ max_error_detail_length max_fingerprint_app_frames
45
+ max_fingerprint_gem_frames cleanup_batch_size caller_scan_depth
46
46
  ].freeze
47
47
 
48
48
  (REQUIRED_NUMERIC + OPTIONAL_NUMERIC).each do |attr|
data/lib/catpm/flusher.rb CHANGED
@@ -83,8 +83,8 @@ module Catpm
83
83
  adapter.persist_buckets(buckets)
84
84
 
85
85
  bucket_map = build_bucket_map(buckets)
86
- samples = rotate_samples(samples)
87
86
  adapter.persist_samples(samples, bucket_map)
87
+ trim_samples(samples)
88
88
  adapter.persist_errors(errors)
89
89
  end
90
90
 
@@ -227,75 +227,43 @@ module Catpm
227
227
  end
228
228
 
229
229
 
230
- def rotate_samples(samples)
231
- return samples if samples.empty?
230
+ # Trim excess samples AFTER insert. Simpler and guaranteed correct —
231
+ # no stale-cache issues when a single flush batch crosses the limit.
232
+ def trim_samples(samples)
233
+ return if samples.empty?
232
234
 
233
- # Pre-fetch counts for all endpoints and types in bulk
234
235
  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
236
 
248
- error_counts = {}
249
- if error_fps.any?
250
- Catpm::Sample.where(sample_type: 'error', error_fingerprint: error_fps)
251
- .group(:error_fingerprint).count
252
- .each { |fp, cnt| error_counts[fp] = cnt }
237
+ endpoint_keys.each do |kind, target, operation|
238
+ endpoint_scope = Catpm::Sample.joins(:bucket)
239
+ .where(catpm_buckets: { kind: kind, target: target, operation: operation })
240
+
241
+ # Random: keep newest N
242
+ max_random = Catpm.config.max_random_samples_per_endpoint
243
+ trim_by_column(endpoint_scope.where(sample_type: 'random'), max_random, :recorded_at) if max_random
244
+
245
+ # Slow: keep highest-duration N
246
+ max_slow = Catpm.config.max_slow_samples_per_endpoint
247
+ trim_by_column(endpoint_scope.where(sample_type: 'slow'), max_slow, :duration) if max_slow
248
+
253
249
  end
254
250
 
255
- samples.each do |sample|
256
- kind, target, operation = sample[:bucket_key][0..2]
257
-
258
- case sample[:sample_type]
259
- when 'random'
260
- max_random = Catpm.config.max_random_samples_per_endpoint
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
251
+ # Errors: per-fingerprint cap (keep newest within each fingerprint)
252
+ max_err_fp = Catpm.config.max_error_samples_per_fingerprint
253
+ if max_err_fp
254
+ fps = samples.filter_map { |s| s[:error_fingerprint] }.uniq
255
+ fps.each do |fp|
256
+ trim_by_column(Catpm::Sample.where(sample_type: 'error', error_fingerprint: fp), max_err_fp, :recorded_at)
295
257
  end
296
258
  end
259
+ end
260
+
261
+ def trim_by_column(scope, max, keep_column)
262
+ count = scope.count
263
+ return if count <= max
297
264
 
298
- samples.reject { |s| s.delete(:_skip) }
265
+ excess_ids = scope.order(keep_column => :asc).limit(count - max).pluck(:id)
266
+ Catpm::Sample.where(id: excess_ids).delete_all if excess_ids.any?
299
267
  end
300
268
 
301
269
  def build_error_context(event)
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.4'
4
+ VERSION = '0.6.6'
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.4
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''