catpm 0.9.7 → 0.10.1
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 +22 -2
- data/app/controllers/catpm/endpoints_controller.rb +15 -1
- data/app/controllers/catpm/errors_controller.rb +11 -8
- data/app/controllers/catpm/events_controller.rb +5 -1
- data/app/helpers/catpm/application_helper.rb +50 -11
- data/app/views/catpm/endpoints/_sample_table.html.erb +4 -1
- data/app/views/catpm/endpoints/show.html.erb +9 -3
- data/app/views/catpm/errors/show.html.erb +4 -2
- data/app/views/catpm/events/show.html.erb +3 -1
- data/app/views/catpm/shared/not_found.html.erb +24 -0
- data/app/views/catpm/shared/record_not_found.html.erb +24 -0
- data/app/views/layouts/catpm/application.html.erb +46 -2
- data/config/routes.rb +1 -0
- data/lib/catpm/collector.rb +82 -5
- data/lib/catpm/configuration.rb +72 -2
- data/lib/catpm/flusher.rb +16 -10
- data/lib/catpm/middleware.rb +2 -1
- data/lib/catpm/stack_sampler.rb +1 -3
- data/lib/catpm/trace.rb +5 -3
- data/lib/catpm/version.rb +1 -1
- data/lib/catpm.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5dab2a97464afff0ad59e3913f7f1aca84505d3ca109966a611eed911d5bb1a7
|
|
4
|
+
data.tar.gz: aa4c892ce8a39fb2ba5ceacf1aef5f1b9c6c867a682536f67c895d887d93607b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbfa610b0355bf1f99b41c6be9476f622e4f756c75c79764abee15721bb2a729113425b6b81416e86e09f734d253301c36f8b0f577b8b15075c584d0e0728dae
|
|
7
|
+
data.tar.gz: ebaef872bc07283485e72ef2ccb5b9f14b836f581c66053ed0f7329621e83a5b7e3b58ef7e021cb43c5bdece61d20dda39867c85a73382bef055364424481f16
|
data/README.md
CHANGED
|
@@ -2,23 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module Catpm
|
|
4
4
|
class ApplicationController < ActionController::Base
|
|
5
|
+
SAMPLES_PER_PAGE = 15
|
|
6
|
+
SAMPLES_PER_PAGE_OPTIONS = [10, 15, 25, 50].freeze
|
|
7
|
+
|
|
5
8
|
before_action :authenticate!
|
|
9
|
+
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
|
10
|
+
|
|
11
|
+
def not_found
|
|
12
|
+
render 'catpm/shared/not_found', layout: 'catpm/application', status: :not_found
|
|
13
|
+
end
|
|
6
14
|
|
|
7
15
|
private
|
|
8
16
|
|
|
17
|
+
def render_not_found
|
|
18
|
+
respond_to do |format|
|
|
19
|
+
format.html { render 'catpm/shared/record_not_found', layout: 'catpm/application', status: :not_found }
|
|
20
|
+
format.json { render json: { error: 'not_found' }, status: :not_found }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
9
24
|
def authenticate!
|
|
10
25
|
if Catpm.config.access_policy
|
|
11
26
|
unless Catpm.config.access_policy.call(request)
|
|
12
|
-
render plain:
|
|
27
|
+
render plain: 'Unauthorized', status: :unauthorized
|
|
13
28
|
end
|
|
14
29
|
elsif Catpm.config.http_basic_auth_user.present? && Catpm.config.http_basic_auth_password.present?
|
|
15
|
-
authenticate_or_request_with_http_basic(
|
|
30
|
+
authenticate_or_request_with_http_basic('Catpm') do |username, password|
|
|
16
31
|
ActiveSupport::SecurityUtils.secure_compare(username, Catpm.config.http_basic_auth_user) &
|
|
17
32
|
ActiveSupport::SecurityUtils.secure_compare(password, Catpm.config.http_basic_auth_password)
|
|
18
33
|
end
|
|
19
34
|
end
|
|
20
35
|
end
|
|
21
36
|
|
|
37
|
+
def sanitized_per_page(param_key = :per_page)
|
|
38
|
+
val = params[param_key].to_i
|
|
39
|
+
SAMPLES_PER_PAGE_OPTIONS.include?(val) ? val : SAMPLES_PER_PAGE
|
|
40
|
+
end
|
|
41
|
+
|
|
22
42
|
def remembered_range
|
|
23
43
|
if params[:range].present?
|
|
24
44
|
cookies[:catpm_range] = { value: params[:range], expires: 1.year.from_now }
|
|
@@ -81,9 +81,23 @@ module Catpm
|
|
|
81
81
|
.joins(:bucket)
|
|
82
82
|
.where(catpm_buckets: { kind: @kind, target: @target, operation: @operation })
|
|
83
83
|
|
|
84
|
+
@error_per_page = sanitized_per_page(:error_per_page)
|
|
85
|
+
@error_page = [params[:error_page].to_i, 1].max
|
|
86
|
+
@error_total = endpoint_samples.where(sample_type: 'error').count
|
|
87
|
+
@error_samples = endpoint_samples.where(sample_type: 'error').order(recorded_at: :desc)
|
|
88
|
+
.offset((@error_page - 1) * @error_per_page).limit(@error_per_page)
|
|
89
|
+
|
|
90
|
+
@slow_per_page = sanitized_per_page(:slow_per_page)
|
|
91
|
+
@slow_page = [params[:slow_page].to_i, 1].max
|
|
92
|
+
@slow_total = endpoint_samples.where(sample_type: 'slow').count
|
|
84
93
|
@slow_samples = endpoint_samples.where(sample_type: 'slow').order(duration: :desc)
|
|
94
|
+
.offset((@slow_page - 1) * @slow_per_page).limit(@slow_per_page)
|
|
95
|
+
|
|
96
|
+
@samples_per_page = sanitized_per_page(:samples_per_page)
|
|
97
|
+
@samples_page = [params[:samples_page].to_i, 1].max
|
|
98
|
+
@samples_total = endpoint_samples.where(sample_type: 'random').count
|
|
85
99
|
@samples = endpoint_samples.where(sample_type: 'random').order(recorded_at: :desc)
|
|
86
|
-
|
|
100
|
+
.offset((@samples_page - 1) * @samples_per_page).limit(@samples_per_page)
|
|
87
101
|
|
|
88
102
|
@pref = Catpm::EndpointPref.find_by(kind: @kind, target: @target, operation: @operation)
|
|
89
103
|
@active_error_count = Catpm::ErrorRecord.unresolved.count
|
|
@@ -38,23 +38,26 @@ module Catpm
|
|
|
38
38
|
|
|
39
39
|
@range, period, bucket_seconds = helpers.parse_range(remembered_range)
|
|
40
40
|
|
|
41
|
-
# Samples table:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.limit(Catpm.config.max_error_samples_per_fingerprint)
|
|
41
|
+
# Samples table: linked by fingerprint
|
|
42
|
+
samples_scope = Catpm::Sample.where(error_fingerprint: @error.fingerprint)
|
|
43
|
+
.order(recorded_at: :desc)
|
|
45
44
|
|
|
46
45
|
# Fallback: match error samples by recorded_at from contexts
|
|
47
|
-
if
|
|
46
|
+
if !samples_scope.exists? && @contexts.any?
|
|
48
47
|
occurred_times = @contexts.filter_map { |c|
|
|
49
48
|
Time.parse(c['occurred_at'] || c[:occurred_at]) rescue nil
|
|
50
49
|
}
|
|
51
50
|
if occurred_times.any?
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.limit(Catpm.config.max_error_samples_per_fingerprint)
|
|
51
|
+
samples_scope = Catpm::Sample.where(sample_type: 'error', kind: @error.kind, recorded_at: occurred_times)
|
|
52
|
+
.order(recorded_at: :desc)
|
|
55
53
|
end
|
|
56
54
|
end
|
|
57
55
|
|
|
56
|
+
@samples_per_page = sanitized_per_page
|
|
57
|
+
@samples_page = [params[:page].to_i, 1].max
|
|
58
|
+
@samples_total = samples_scope.count
|
|
59
|
+
@samples = samples_scope.offset((@samples_page - 1) * @samples_per_page).limit(@samples_per_page)
|
|
60
|
+
|
|
58
61
|
# Chart from occurrence_buckets (multi-resolution, no dependency on samples)
|
|
59
62
|
ob = @error.parsed_occurrence_buckets
|
|
60
63
|
|
|
@@ -114,7 +114,11 @@ module Catpm
|
|
|
114
114
|
@chart_times = 60.times.map { |i| Time.at(now_slot - (59 - i) * bucket_seconds).strftime('%H:%M') }
|
|
115
115
|
|
|
116
116
|
# Recent samples
|
|
117
|
-
|
|
117
|
+
samples_scope = Catpm::EventSample.by_name(@name).order(recorded_at: :desc)
|
|
118
|
+
@samples_per_page = sanitized_per_page
|
|
119
|
+
@samples_page = [params[:page].to_i, 1].max
|
|
120
|
+
@samples_total = samples_scope.count
|
|
121
|
+
@samples = samples_scope.offset((@samples_page - 1) * @samples_per_page).limit(@samples_per_page)
|
|
118
122
|
|
|
119
123
|
@pref = Catpm::EventPref.find_by(name: @name)
|
|
120
124
|
@active_error_count = Catpm::ErrorRecord.unresolved.count
|
|
@@ -78,6 +78,7 @@ module Catpm
|
|
|
78
78
|
|
|
79
79
|
# ── Sampling ──
|
|
80
80
|
random_sample_rate: { group: 'Sampling', label: 'Random Sample Rate', desc: '1-in-N requests are sampled randomly for detailed traces', fmt: :one_in_n },
|
|
81
|
+
always_sample_targets: { group: 'Sampling', label: 'Always Sample Targets', desc: 'Endpoint patterns that are always sampled regardless of random rate', fmt: :list },
|
|
81
82
|
max_random_samples_per_endpoint: { group: 'Sampling', label: 'Max Random / Endpoint', desc: 'Random samples retained per endpoint', fmt: :nullable_int },
|
|
82
83
|
max_slow_samples_per_endpoint: { group: 'Sampling', label: 'Max Slow / Endpoint', desc: 'Slow samples retained per endpoint', fmt: :nullable_int },
|
|
83
84
|
max_error_samples_per_fingerprint: { group: 'Sampling', label: 'Max Error / Fingerprint', desc: 'Error samples retained per error fingerprint', fmt: :nullable_int },
|
|
@@ -308,7 +309,7 @@ module Catpm
|
|
|
308
309
|
new_dir = active && current_dir == 'asc' ? 'desc' : 'asc'
|
|
309
310
|
arrow = active ? (current_dir == 'asc' ? ' ▲' : ' ▼') : ''
|
|
310
311
|
params_hash = extra_params.merge(sort: column, dir: new_dir)
|
|
311
|
-
url = '?' +
|
|
312
|
+
url = '?' + Rack::Utils.build_query(params_hash)
|
|
312
313
|
%(<a href="#{url}" class="sort-link#{active ? ' active' : ''}">#{label}#{arrow}</a>).html_safe
|
|
313
314
|
end
|
|
314
315
|
|
|
@@ -350,31 +351,69 @@ module Catpm
|
|
|
350
351
|
(span / 60.0).ceil
|
|
351
352
|
end
|
|
352
353
|
|
|
353
|
-
def pagination_nav(current_page, total_count, per_page, extra_params: {})
|
|
354
|
+
def pagination_nav(current_page, total_count, per_page, extra_params: {}, page_param: :page, anchor: nil)
|
|
354
355
|
total_pages = (total_count.to_f / per_page).ceil
|
|
355
356
|
return '' if total_pages <= 1
|
|
356
357
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
prev_url = '?' +
|
|
360
|
-
next_url = '?' +
|
|
358
|
+
fragment = anchor ? "##{anchor}" : ''
|
|
359
|
+
|
|
360
|
+
prev_url = '?' + Rack::Utils.build_query(extra_params.merge(page_param => current_page - 1).compact) + fragment
|
|
361
|
+
next_url = '?' + Rack::Utils.build_query(extra_params.merge(page_param => current_page + 1).compact) + fragment
|
|
361
362
|
|
|
362
363
|
html = +'<div class="pagination">'
|
|
363
364
|
if current_page > 1
|
|
364
|
-
html << %(<a href="#{prev_url}" class="btn"
|
|
365
|
+
html << %(<a href="#{prev_url}" class="page-btn">‹</a>)
|
|
365
366
|
else
|
|
366
|
-
html << '<span class="btn
|
|
367
|
+
html << '<span class="page-btn disabled">‹</span>'
|
|
367
368
|
end
|
|
368
|
-
html << %(<span class="pagination-info"
|
|
369
|
+
html << %(<span class="pagination-info">#{current_page} / #{total_pages}</span>)
|
|
369
370
|
if current_page < total_pages
|
|
370
|
-
html << %(<a href="#{next_url}" class="btn"
|
|
371
|
+
html << %(<a href="#{next_url}" class="page-btn">›</a>)
|
|
371
372
|
else
|
|
372
|
-
html << '<span class="btn
|
|
373
|
+
html << '<span class="page-btn disabled">›</span>'
|
|
373
374
|
end
|
|
374
375
|
html << '</div>'
|
|
375
376
|
html.html_safe
|
|
376
377
|
end
|
|
377
378
|
|
|
379
|
+
def samples_pagination(current_page, total_count, per_page, extra_params: {}, page_param: :page, anchor: nil, storage_key:, per_page_param:)
|
|
380
|
+
total_pages = (total_count.to_f / per_page).ceil
|
|
381
|
+
fragment = anchor ? "##{anchor}" : ''
|
|
382
|
+
|
|
383
|
+
html = +'<div class="pagination">'
|
|
384
|
+
|
|
385
|
+
if total_pages > 1
|
|
386
|
+
prev_url = '?' + Rack::Utils.build_query(extra_params.merge(page_param => current_page - 1).compact) + fragment
|
|
387
|
+
next_url = '?' + Rack::Utils.build_query(extra_params.merge(page_param => current_page + 1).compact) + fragment
|
|
388
|
+
|
|
389
|
+
if current_page > 1
|
|
390
|
+
html << %(<a href="#{prev_url}" class="page-btn">‹</a>)
|
|
391
|
+
else
|
|
392
|
+
html << '<span class="page-btn disabled">‹</span>'
|
|
393
|
+
end
|
|
394
|
+
html << %(<span class="pagination-info">#{current_page} / #{total_pages}</span>)
|
|
395
|
+
if current_page < total_pages
|
|
396
|
+
html << %(<a href="#{next_url}" class="page-btn">›</a>)
|
|
397
|
+
else
|
|
398
|
+
html << '<span class="page-btn disabled">›</span>'
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Per-page select — right-aligned
|
|
403
|
+
html << '<select class="per-page-select" data-storage-key="'
|
|
404
|
+
html << ERB::Util.html_escape(storage_key)
|
|
405
|
+
html << '" data-page-param="' << ERB::Util.html_escape(page_param)
|
|
406
|
+
html << '" data-per-page-param="' << ERB::Util.html_escape(per_page_param) << '">'
|
|
407
|
+
Catpm::ApplicationController::SAMPLES_PER_PAGE_OPTIONS.each do |opt|
|
|
408
|
+
sel = opt == per_page ? ' selected' : ''
|
|
409
|
+
html << %(<option value="#{opt}"#{sel}>#{opt}</option>)
|
|
410
|
+
end
|
|
411
|
+
html << '</select>'
|
|
412
|
+
|
|
413
|
+
html << '</div>'
|
|
414
|
+
html.html_safe
|
|
415
|
+
end
|
|
416
|
+
|
|
378
417
|
def trend_indicator(error)
|
|
379
418
|
return '' unless error.last_occurred_at
|
|
380
419
|
if error.last_occurred_at > 1.hour.ago
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
<% anchor_id = "samples-#{page_param.to_s.delete_suffix('_page')}" %>
|
|
2
|
+
<h2 id="<%= anchor_id %>"><%= title %> (<%= total_count %>)</h2>
|
|
2
3
|
<% if samples.any? %>
|
|
3
4
|
<div class="table-scroll">
|
|
4
5
|
<table>
|
|
@@ -36,6 +37,8 @@
|
|
|
36
37
|
</tbody>
|
|
37
38
|
</table>
|
|
38
39
|
</div>
|
|
40
|
+
<% base_params = request.query_parameters.symbolize_keys.except(page_param.to_sym) %>
|
|
41
|
+
<%= samples_pagination(page, total_count, per_page, extra_params: base_params, page_param: page_param.to_sym, anchor: anchor_id, storage_key: storage_key, per_page_param: per_page_param) %>
|
|
39
42
|
<% else %>
|
|
40
43
|
<div class="empty-state">
|
|
41
44
|
<div class="empty-hint"><%= empty_msg %></div>
|
|
@@ -147,6 +147,12 @@
|
|
|
147
147
|
</div>
|
|
148
148
|
<% end %>
|
|
149
149
|
|
|
150
|
-
<%= render "catpm/endpoints/sample_table", title: "Error Requests", samples: @error_samples, empty_msg: "No errors for this endpoint."
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
<%= render "catpm/endpoints/sample_table", title: "Error Requests", samples: @error_samples, empty_msg: "No errors for this endpoint.",
|
|
151
|
+
page: @error_page, total_count: @error_total, per_page: @error_per_page,
|
|
152
|
+
page_param: "error_page", per_page_param: "error_per_page", storage_key: "catpm_per_page_errors" %>
|
|
153
|
+
<%= render "catpm/endpoints/sample_table", title: "Slowest Requests", samples: @slow_samples, empty_msg: "No slow requests captured yet.",
|
|
154
|
+
page: @slow_page, total_count: @slow_total, per_page: @slow_per_page,
|
|
155
|
+
page_param: "slow_page", per_page_param: "slow_per_page", storage_key: "catpm_per_page_slow" %>
|
|
156
|
+
<%= render "catpm/endpoints/sample_table", title: "Sample Requests", samples: @samples, empty_msg: "No samples captured yet.",
|
|
157
|
+
page: @samples_page, total_count: @samples_total, per_page: @samples_per_page,
|
|
158
|
+
page_param: "samples_page", per_page_param: "samples_per_page", storage_key: "catpm_per_page_requests" %>
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
</div>
|
|
95
95
|
|
|
96
96
|
<%# ─── Samples ─── %>
|
|
97
|
-
<% if @samples.any? %>
|
|
98
|
-
<h2>Recent Samples</h2>
|
|
97
|
+
<% if @samples.any? || @samples_page > 1 %>
|
|
98
|
+
<h2 id="samples-error">Recent Samples (<%= @samples_total %>)</h2>
|
|
99
99
|
<%= section_description("Linked request samples for this error. Click to view full details.") %>
|
|
100
100
|
<div class="table-scroll">
|
|
101
101
|
<table>
|
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
</tbody>
|
|
130
130
|
</table>
|
|
131
131
|
</div>
|
|
132
|
+
<% base_params = request.query_parameters.symbolize_keys.except(:page) %>
|
|
133
|
+
<%= samples_pagination(@samples_page, @samples_total, @samples_per_page, extra_params: base_params, anchor: "samples-error", storage_key: "catpm_per_page_errors", per_page_param: "per_page") %>
|
|
132
134
|
<% end %>
|
|
133
135
|
|
|
134
136
|
<%# ─── Legacy Occurrences (for errors without linked samples) ─── %>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<% end %>
|
|
56
56
|
|
|
57
57
|
<%# ─── Recent Samples ─── %>
|
|
58
|
-
<h2>Recent Samples</h2>
|
|
58
|
+
<h2 id="samples-events">Recent Samples (<%= @samples_total %>)</h2>
|
|
59
59
|
<%= section_description("Most recent event payloads captured.") %>
|
|
60
60
|
|
|
61
61
|
<% if @samples.any? %>
|
|
@@ -99,6 +99,8 @@
|
|
|
99
99
|
<% end %>
|
|
100
100
|
</tbody>
|
|
101
101
|
</table>
|
|
102
|
+
<% base_params = request.query_parameters.symbolize_keys.except(:page) %>
|
|
103
|
+
<%= samples_pagination(@samples_page, @samples_total, @samples_per_page, extra_params: base_params, anchor: "samples-events", storage_key: "catpm_per_page_requests", per_page_param: "per_page") %>
|
|
102
104
|
<% else %>
|
|
103
105
|
<div class="empty-state">
|
|
104
106
|
<div class="empty-title">No samples recorded</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<% content_for :head_extra do %>
|
|
2
|
+
<style>
|
|
3
|
+
body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
4
|
+
.not-found-content { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
5
|
+
.not-found-inner { text-align: center; padding: 24px; }
|
|
6
|
+
.not-found-cat {
|
|
7
|
+
font-family: var(--font-mono); font-size: 14px; line-height: 1.3;
|
|
8
|
+
color: var(--accent); white-space: pre; margin-bottom: 20px; user-select: none;
|
|
9
|
+
}
|
|
10
|
+
.not-found-code { font-size: 13px; color: var(--text-2); font-weight: 500; letter-spacing: 1px; margin-bottom: 24px; }
|
|
11
|
+
.not-found-link { font-size: 13px; color: var(--accent); }
|
|
12
|
+
.not-found-link:hover { text-decoration: underline; }
|
|
13
|
+
</style>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<div class="not-found-content">
|
|
17
|
+
<div class="not-found-inner">
|
|
18
|
+
<div class="not-found-cat"> /\_/\
|
|
19
|
+
( o.o )
|
|
20
|
+
> ^ <</div>
|
|
21
|
+
<div class="not-found-code">404</div>
|
|
22
|
+
<a href="<%= catpm.status_index_path %>" class="not-found-link">← Dashboard</a>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<% content_for :head_extra do %>
|
|
2
|
+
<style>
|
|
3
|
+
body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
4
|
+
.not-found-content { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
5
|
+
.not-found-inner { text-align: center; padding: 24px; }
|
|
6
|
+
.not-found-paws { font-size: 40px; color: var(--accent); margin-bottom: 20px; letter-spacing: 6px; }
|
|
7
|
+
.not-found-title { font-size: 15px; font-weight: 600; color: var(--text-0); margin-bottom: 6px; }
|
|
8
|
+
.not-found-hint { font-size: 13px; color: var(--text-2); max-width: 380px; margin: 0 auto 24px; line-height: 1.6; }
|
|
9
|
+
.not-found-link { font-size: 13px; color: var(--accent); }
|
|
10
|
+
.not-found-link:hover { text-decoration: underline; }
|
|
11
|
+
</style>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<div class="not-found-content">
|
|
15
|
+
<div class="not-found-inner">
|
|
16
|
+
<div class="not-found-paws">🐾</div>
|
|
17
|
+
<div class="not-found-title">This record is no longer available</div>
|
|
18
|
+
<p class="not-found-hint">
|
|
19
|
+
catpm rotates older performance data to keep memory usage low.
|
|
20
|
+
This resource was likely purged during a routine cleanup.
|
|
21
|
+
</p>
|
|
22
|
+
<a href="<%= catpm.status_index_path %>" class="not-found-link">← Dashboard</a>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -183,8 +183,12 @@
|
|
|
183
183
|
.empty-state .empty-hint { font-size: 13px; }
|
|
184
184
|
|
|
185
185
|
/* ─── Pagination ─── */
|
|
186
|
-
.pagination { display: flex; align-items: center; gap:
|
|
187
|
-
.pagination-info {
|
|
186
|
+
.pagination { display: flex; align-items: center; gap: 6px; margin: 8px 0; font-size: 12px; }
|
|
187
|
+
.pagination-info { color: var(--text-2); min-width: 32px; text-align: center; }
|
|
188
|
+
.page-btn { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 4px; color: var(--text-1); font-size: 14px; line-height: 1; }
|
|
189
|
+
.page-btn:hover { background: var(--bg-1); color: var(--text-0); text-decoration: none; }
|
|
190
|
+
.page-btn.disabled { opacity: 0.3; cursor: default; pointer-events: none; }
|
|
191
|
+
.per-page-select { margin-left: 6px; padding: 2px 4px; border: 1px solid var(--border); border-radius: 4px; font-size: 12px; background: var(--bg-0); color: var(--text-1); cursor: pointer; }
|
|
188
192
|
|
|
189
193
|
/* ─── Code / Backtrace ─── */
|
|
190
194
|
.source { color: var(--green); font-size: 12px; font-family: var(--font-mono); }
|
|
@@ -602,6 +606,46 @@
|
|
|
602
606
|
/* Close menu on outside click */
|
|
603
607
|
closeActionMenu();
|
|
604
608
|
});
|
|
609
|
+
|
|
610
|
+
/* ─── Per-Page Selector: persist in localStorage, update URL ─── */
|
|
611
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
612
|
+
var selects = document.querySelectorAll('.per-page-select');
|
|
613
|
+
var urlParams = new URLSearchParams(window.location.search);
|
|
614
|
+
var needsRedirect = false;
|
|
615
|
+
|
|
616
|
+
// Batch: apply all stored per_page values missing from URL
|
|
617
|
+
selects.forEach(function(sel) {
|
|
618
|
+
var storageKey = sel.dataset.storageKey;
|
|
619
|
+
var perPageParam = sel.dataset.perPageParam;
|
|
620
|
+
var pageParam = sel.dataset.pageParam;
|
|
621
|
+
if (!urlParams.has(perPageParam) && storageKey) {
|
|
622
|
+
var stored = localStorage.getItem(storageKey);
|
|
623
|
+
if (stored && stored !== sel.value) {
|
|
624
|
+
urlParams.set(perPageParam, stored);
|
|
625
|
+
urlParams.delete(pageParam);
|
|
626
|
+
needsRedirect = true;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
if (needsRedirect) { window.location.search = urlParams.toString(); return; }
|
|
631
|
+
|
|
632
|
+
// Change handler: persist and reload
|
|
633
|
+
selects.forEach(function(sel) {
|
|
634
|
+
sel.addEventListener('change', function() {
|
|
635
|
+
var storageKey = sel.dataset.storageKey;
|
|
636
|
+
if (storageKey) localStorage.setItem(storageKey, sel.value);
|
|
637
|
+
var params = new URLSearchParams(window.location.search);
|
|
638
|
+
params.set(sel.dataset.perPageParam, sel.value);
|
|
639
|
+
params.delete(sel.dataset.pageParam);
|
|
640
|
+
// Find heading anchor above this pagination bar
|
|
641
|
+
var el = sel.closest('.pagination');
|
|
642
|
+
while (el && !el.id) el = el.previousElementSibling;
|
|
643
|
+
if (!el) { el = sel.closest('.pagination')?.parentElement; while (el && !el.id) el = el.previousElementSibling; }
|
|
644
|
+
var hash = el?.id ? '#' + el.id : '';
|
|
645
|
+
window.location.href = window.location.pathname + '?' + params.toString() + hash;
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
});
|
|
605
649
|
</script>
|
|
606
650
|
</body>
|
|
607
651
|
</html>
|
data/config/routes.rb
CHANGED
data/lib/catpm/collector.rb
CHANGED
|
@@ -6,6 +6,10 @@ module Catpm
|
|
|
6
6
|
MIN_GAP_MS = 1.0
|
|
7
7
|
DEFAULT_ERROR_STATUS = 500
|
|
8
8
|
DEFAULT_SUCCESS_STATUS = 200
|
|
9
|
+
|
|
10
|
+
@instrumentation_mutex = Mutex.new
|
|
11
|
+
@active_instrumented_count = 0
|
|
12
|
+
|
|
9
13
|
class << self
|
|
10
14
|
def process_action_controller(event)
|
|
11
15
|
return unless Catpm.enabled?
|
|
@@ -118,6 +122,7 @@ module Catpm
|
|
|
118
122
|
seg[:parent_index] = tree_parent ? (tree_parent + base_idx) : (ctrl_idx || 0)
|
|
119
123
|
segments << seg
|
|
120
124
|
end
|
|
125
|
+
reparent_under_call_tree(segments, ctrl_idx)
|
|
121
126
|
end
|
|
122
127
|
end
|
|
123
128
|
|
|
@@ -295,6 +300,7 @@ module Catpm
|
|
|
295
300
|
seg[:parent_index] = tree_parent ? (tree_parent + base_idx) : (ctrl_idx || 0)
|
|
296
301
|
segments << seg
|
|
297
302
|
end
|
|
303
|
+
reparent_under_call_tree(segments, ctrl_idx)
|
|
298
304
|
end
|
|
299
305
|
end
|
|
300
306
|
|
|
@@ -389,16 +395,86 @@ module Catpm
|
|
|
389
395
|
|
|
390
396
|
# For HTTP middleware where endpoint is unknown at start.
|
|
391
397
|
def should_instrument_request?
|
|
392
|
-
rand(Catpm.config.random_sample_rate) == 0
|
|
398
|
+
rand(Catpm.config.random_sample_rate) == 0 && try_start_instrumentation
|
|
393
399
|
end
|
|
394
400
|
|
|
395
401
|
# For track_request where endpoint is known at start.
|
|
396
|
-
|
|
397
|
-
|
|
402
|
+
# Always instruments targets matched by always_sample_targets.
|
|
403
|
+
def should_instrument?(_kind, target, _operation)
|
|
404
|
+
if Catpm.config.always_sample?(target)
|
|
405
|
+
try_start_instrumentation
|
|
406
|
+
else
|
|
407
|
+
rand(Catpm.config.random_sample_rate) == 0 && try_start_instrumentation
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Reset the concurrency counter (for tests and config reloads).
|
|
412
|
+
def reset_instrumentation_counter!
|
|
413
|
+
@instrumentation_mutex.synchronize { @active_instrumented_count = 0 }
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Release an instrumentation slot. Must be called in ensure block
|
|
417
|
+
# for every request where try_start_instrumentation returned true.
|
|
418
|
+
def end_instrumentation
|
|
419
|
+
@instrumentation_mutex.synchronize do
|
|
420
|
+
@active_instrumented_count = [@active_instrumented_count - 1, 0].max
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Atomically claim an instrumentation slot if within budget.
|
|
425
|
+
# Returns true if the slot was acquired, false if at capacity.
|
|
426
|
+
# Already-running instrumented requests are not affected.
|
|
427
|
+
def try_start_instrumentation
|
|
428
|
+
@instrumentation_mutex.synchronize do
|
|
429
|
+
max = Catpm.config.effective_max_concurrent_instrumented
|
|
430
|
+
return false if @active_instrumented_count >= max
|
|
431
|
+
|
|
432
|
+
@active_instrumented_count += 1
|
|
433
|
+
true
|
|
434
|
+
end
|
|
398
435
|
end
|
|
399
436
|
|
|
400
437
|
private
|
|
401
438
|
|
|
439
|
+
# Re-parent non-code segments (sql, cache, etc.) under call tree code segments
|
|
440
|
+
# when their offset falls within the code segment's time range.
|
|
441
|
+
# This gives proper nesting: code → sql, instead of both being siblings under controller.
|
|
442
|
+
def reparent_under_call_tree(segments, ctrl_idx)
|
|
443
|
+
# Build index of code segments with their time ranges: [index, offset, end]
|
|
444
|
+
code_nodes = []
|
|
445
|
+
segments.each_with_index do |seg, i|
|
|
446
|
+
next unless seg[:type] == 'code' && seg[:offset] && seg[:duration]
|
|
447
|
+
code_nodes << [i, seg[:offset].to_f, seg[:offset].to_f + seg[:duration].to_f]
|
|
448
|
+
end
|
|
449
|
+
return if code_nodes.empty?
|
|
450
|
+
|
|
451
|
+
segments.each_with_index do |seg, i|
|
|
452
|
+
# Only reparent direct children of controller that aren't code segments
|
|
453
|
+
next if seg[:type] == 'code' || seg[:type] == 'controller' || seg[:type] == 'request'
|
|
454
|
+
next unless seg[:parent_index] == ctrl_idx
|
|
455
|
+
next unless seg[:offset]
|
|
456
|
+
|
|
457
|
+
seg_offset = seg[:offset].to_f
|
|
458
|
+
|
|
459
|
+
# Find the innermost (deepest-nested) code node that contains this segment.
|
|
460
|
+
# Innermost = the one with the smallest duration among all containing nodes.
|
|
461
|
+
best_idx = nil
|
|
462
|
+
best_dur = Float::INFINITY
|
|
463
|
+
|
|
464
|
+
code_nodes.each do |code_i, code_start, code_end|
|
|
465
|
+
next unless seg_offset >= code_start && seg_offset < code_end
|
|
466
|
+
|
|
467
|
+
dur = code_end - code_start
|
|
468
|
+
if dur < best_dur
|
|
469
|
+
best_dur = dur
|
|
470
|
+
best_idx = code_i
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
seg[:parent_index] = best_idx if best_idx
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
402
478
|
# Remove near-zero-duration "code" spans that merely wrap a "controller" span.
|
|
403
479
|
# This happens when CallTracer (TracePoint) captures a thin dispatch method
|
|
404
480
|
# (e.g. Telegram::WebhookController#process) whose :return fires before the
|
|
@@ -456,8 +532,9 @@ module Catpm
|
|
|
456
532
|
# carry full context in the buffer.
|
|
457
533
|
# Non-instrumented requests have no segments — skip sample creation.
|
|
458
534
|
def early_sample_type(error:, duration:, kind:, target:, operation:, instrumented: true)
|
|
459
|
-
|
|
460
|
-
return
|
|
535
|
+
always = Catpm.config.always_sample?(target)
|
|
536
|
+
return 'error' if error && (instrumented || always)
|
|
537
|
+
return nil unless instrumented || always
|
|
461
538
|
return 'slow' if duration >= Catpm.config.slow_threshold_for(kind.to_sym)
|
|
462
539
|
|
|
463
540
|
'random'
|
data/lib/catpm/configuration.rb
CHANGED
|
@@ -9,6 +9,18 @@ module Catpm
|
|
|
9
9
|
BUFFER_MEMORY_SHARE = 0.5 # 50% of max_memory for event buffer
|
|
10
10
|
CACHE_ENTRIES_PER_MB = 10_000 # ~100 bytes/entry in path_cache
|
|
11
11
|
PATH_CACHE_BUDGET_SHARE = 0.05 # 5% of max_memory for path_cache
|
|
12
|
+
INSTRUMENTATION_BUDGET_SHARE = 0.30 # 30% of max_memory for concurrent instrumented requests
|
|
13
|
+
|
|
14
|
+
# Estimated per-request memory for instrumentation budget
|
|
15
|
+
ESTIMATED_BYTES_PER_STACK_SAMPLE = 10_000 # ~10 KB: backtrace_locations array (~100 frames × ~100 bytes)
|
|
16
|
+
ESTIMATED_BYTES_PER_SEGMENT = 500 # segment hash with strings
|
|
17
|
+
|
|
18
|
+
# Within per-request instrumentation budget, split between stack samples and segments
|
|
19
|
+
STACK_SAMPLES_BUDGET_SHARE = 0.7
|
|
20
|
+
SEGMENT_BUDGET_SHARE = 0.3
|
|
21
|
+
MIN_EFFECTIVE_STACK_SAMPLES = 10
|
|
22
|
+
MIN_EFFECTIVE_SEGMENTS = 20
|
|
23
|
+
MAX_EFFECTIVE_SEGMENTS = 1000
|
|
12
24
|
|
|
13
25
|
# Boolean / non-numeric settings — plain attr_accessor
|
|
14
26
|
attr_accessor :enabled,
|
|
@@ -30,7 +42,8 @@ module Catpm
|
|
|
30
42
|
:events_enabled,
|
|
31
43
|
:track_own_requests,
|
|
32
44
|
:downsampling_thresholds,
|
|
33
|
-
:show_untracked_segments
|
|
45
|
+
:show_untracked_segments,
|
|
46
|
+
:always_sample_targets
|
|
34
47
|
|
|
35
48
|
# Numeric settings that must be positive numbers (nil not allowed)
|
|
36
49
|
REQUIRED_NUMERIC = %i[
|
|
@@ -122,6 +135,7 @@ module Catpm
|
|
|
122
135
|
@caller_scan_depth = 50
|
|
123
136
|
@instrument_call_tree = false
|
|
124
137
|
@show_untracked_segments = false
|
|
138
|
+
@always_sample_targets = []
|
|
125
139
|
end
|
|
126
140
|
|
|
127
141
|
# Buffer gets BUFFER_MEMORY_SHARE of max_memory, scaled by thread count
|
|
@@ -135,12 +149,56 @@ module Catpm
|
|
|
135
149
|
(max_memory * CACHE_ENTRIES_PER_MB * PATH_CACHE_BUDGET_SHARE).to_i
|
|
136
150
|
end
|
|
137
151
|
|
|
152
|
+
# Max concurrent instrumented requests derived from max_memory.
|
|
153
|
+
# Each instrumented request holds stack samples + segments in memory
|
|
154
|
+
# until the request completes. This limits how many can run at once
|
|
155
|
+
# so total live instrumentation data stays within budget.
|
|
156
|
+
def effective_max_concurrent_instrumented
|
|
157
|
+
budget_bytes = max_memory * 1_048_576 * INSTRUMENTATION_BUDGET_SHARE
|
|
158
|
+
(budget_bytes / estimated_instrumented_request_bytes).clamp(1, 1000).to_i
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Stack samples per request, auto-derived from max_memory when not explicitly set.
|
|
162
|
+
# Ensures per-request memory fits within the instrumentation budget.
|
|
163
|
+
def effective_max_stack_samples_per_request
|
|
164
|
+
if max_stack_samples_per_request
|
|
165
|
+
[max_stack_samples_per_request, StackSampler::HARD_SAMPLE_CAP].min
|
|
166
|
+
else
|
|
167
|
+
return StackSampler::HARD_SAMPLE_CAP unless instrument_stack_sampler || instrument_call_tree
|
|
168
|
+
|
|
169
|
+
per_request_budget = max_memory * 1_048_576 * INSTRUMENTATION_BUDGET_SHARE
|
|
170
|
+
stack_budget = per_request_budget * STACK_SAMPLES_BUDGET_SHARE
|
|
171
|
+
(stack_budget / ESTIMATED_BYTES_PER_STACK_SAMPLE).to_i.clamp(MIN_EFFECTIVE_STACK_SAMPLES, StackSampler::HARD_SAMPLE_CAP)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Segments per request, auto-derived from max_memory when not explicitly set.
|
|
176
|
+
def effective_max_segments_per_request
|
|
177
|
+
return max_segments_per_request if max_segments_per_request
|
|
178
|
+
|
|
179
|
+
per_request_budget = max_memory * 1_048_576 * INSTRUMENTATION_BUDGET_SHARE
|
|
180
|
+
use_sampler = instrument_stack_sampler || instrument_call_tree
|
|
181
|
+
segment_share = use_sampler ? SEGMENT_BUDGET_SHARE : 0.9
|
|
182
|
+
segment_budget = per_request_budget * segment_share
|
|
183
|
+
(segment_budget / ESTIMATED_BYTES_PER_SEGMENT).to_i.clamp(MIN_EFFECTIVE_SEGMENTS, MAX_EFFECTIVE_SEGMENTS)
|
|
184
|
+
end
|
|
185
|
+
|
|
138
186
|
def slow_threshold_for(kind)
|
|
139
187
|
slow_threshold_per_kind.fetch(kind.to_sym, slow_threshold)
|
|
140
188
|
end
|
|
141
189
|
|
|
142
190
|
def ignored?(target)
|
|
143
|
-
ignored_targets
|
|
191
|
+
match_target?(ignored_targets, target)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def always_sample?(target)
|
|
195
|
+
match_target?(always_sample_targets, target)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def match_target?(patterns, target)
|
|
201
|
+
patterns.any? do |pattern|
|
|
144
202
|
case pattern
|
|
145
203
|
when Regexp then pattern.match?(target)
|
|
146
204
|
when String
|
|
@@ -152,5 +210,17 @@ module Catpm
|
|
|
152
210
|
end
|
|
153
211
|
end
|
|
154
212
|
end
|
|
213
|
+
|
|
214
|
+
def estimated_instrumented_request_bytes
|
|
215
|
+
bytes = 0
|
|
216
|
+
|
|
217
|
+
if instrument_stack_sampler || instrument_call_tree
|
|
218
|
+
bytes += effective_max_stack_samples_per_request * ESTIMATED_BYTES_PER_STACK_SAMPLE
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
bytes += effective_max_segments_per_request * ESTIMATED_BYTES_PER_SEGMENT
|
|
222
|
+
|
|
223
|
+
[bytes, 50_000].max # minimum 50 KB estimate per request
|
|
224
|
+
end
|
|
155
225
|
end
|
|
156
226
|
end
|
data/lib/catpm/flusher.rb
CHANGED
|
@@ -243,6 +243,22 @@ module Catpm
|
|
|
243
243
|
# Trim excess samples AFTER insert. Simpler and guaranteed correct —
|
|
244
244
|
# no stale-cache issues when a single flush batch crosses the limit.
|
|
245
245
|
def trim_samples(samples)
|
|
246
|
+
# Errors: per-fingerprint cap (keep newest within each fingerprint)
|
|
247
|
+
# Query DB for all fingerprints that exceed the limit — the current batch
|
|
248
|
+
# may not contain error samples, so this runs regardless of batch contents.
|
|
249
|
+
max_err_fp = Catpm.config.max_error_samples_per_fingerprint
|
|
250
|
+
if max_err_fp
|
|
251
|
+
over_limit_fps = Catpm::Sample
|
|
252
|
+
.where(sample_type: 'error')
|
|
253
|
+
.group(:error_fingerprint)
|
|
254
|
+
.having('COUNT(*) > ?', max_err_fp)
|
|
255
|
+
.pluck(:error_fingerprint)
|
|
256
|
+
|
|
257
|
+
over_limit_fps.each do |fp|
|
|
258
|
+
trim_by_column(Catpm::Sample.where(sample_type: 'error', error_fingerprint: fp), max_err_fp, :recorded_at)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
246
262
|
return if samples.empty?
|
|
247
263
|
|
|
248
264
|
endpoint_keys = samples.map { |s| s[:bucket_key][0..2] }.uniq
|
|
@@ -258,16 +274,6 @@ module Catpm
|
|
|
258
274
|
# Slow: keep highest-duration N
|
|
259
275
|
max_slow = Catpm.config.max_slow_samples_per_endpoint
|
|
260
276
|
trim_by_column(endpoint_scope.where(sample_type: 'slow'), max_slow, :duration) if max_slow
|
|
261
|
-
|
|
262
|
-
end
|
|
263
|
-
|
|
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)
|
|
270
|
-
end
|
|
271
277
|
end
|
|
272
278
|
end
|
|
273
279
|
|
data/lib/catpm/middleware.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Catpm
|
|
|
17
17
|
if Catpm.config.instrument_segments && Collector.should_instrument_request?
|
|
18
18
|
use_sampler = Catpm.config.instrument_stack_sampler || Catpm.config.instrument_call_tree
|
|
19
19
|
req_segments = RequestSegments.new(
|
|
20
|
-
max_segments: Catpm.config.
|
|
20
|
+
max_segments: Catpm.config.effective_max_segments_per_request,
|
|
21
21
|
request_start: env['catpm.request_start'],
|
|
22
22
|
stack_sample: use_sampler,
|
|
23
23
|
call_tree: Catpm.config.instrument_call_tree
|
|
@@ -33,6 +33,7 @@ module Catpm
|
|
|
33
33
|
ensure
|
|
34
34
|
req_segments&.stop_sampler
|
|
35
35
|
req_segments&.release!
|
|
36
|
+
Collector.end_instrumentation if req_segments
|
|
36
37
|
Thread.current[:catpm_request_segments] = nil
|
|
37
38
|
Thread.current[:catpm_request_start] = nil
|
|
38
39
|
Thread.current[:catpm_tracked_instrumented] = nil
|
data/lib/catpm/stack_sampler.rb
CHANGED
|
@@ -85,9 +85,7 @@ module Catpm
|
|
|
85
85
|
|
|
86
86
|
# Called by SamplingLoop from the global thread
|
|
87
87
|
def capture(now)
|
|
88
|
-
|
|
89
|
-
cap = max ? [max, HARD_SAMPLE_CAP].min : HARD_SAMPLE_CAP
|
|
90
|
-
return if @samples.size >= cap
|
|
88
|
+
return if @samples.size >= Catpm.config.effective_max_stack_samples_per_request
|
|
91
89
|
|
|
92
90
|
locs = @target&.backtrace_locations
|
|
93
91
|
@samples << [now, locs] if locs
|
data/lib/catpm/trace.rb
CHANGED
|
@@ -75,18 +75,19 @@ module Catpm
|
|
|
75
75
|
# process_update(...)
|
|
76
76
|
# end
|
|
77
77
|
#
|
|
78
|
-
def self.track_request(kind: :http, target:, operation: '', context: {}, metadata: {})
|
|
78
|
+
def self.track_request(kind: :http, target:, operation: '', context: {}, metadata: {}, always_sample: false)
|
|
79
79
|
return yield unless enabled?
|
|
80
80
|
|
|
81
81
|
req_segments = Thread.current[:catpm_request_segments]
|
|
82
82
|
owns_segments = false
|
|
83
83
|
|
|
84
84
|
if req_segments.nil? && config.instrument_segments
|
|
85
|
-
|
|
85
|
+
force = always_sample || config.always_sample?(target)
|
|
86
|
+
if force ? Collector.try_start_instrumentation : Collector.should_instrument?(kind, target, operation)
|
|
86
87
|
use_sampler = config.instrument_stack_sampler || config.instrument_call_tree
|
|
87
88
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
88
89
|
req_segments = RequestSegments.new(
|
|
89
|
-
max_segments: config.
|
|
90
|
+
max_segments: config.effective_max_segments_per_request,
|
|
90
91
|
request_start: start_time,
|
|
91
92
|
stack_sample: use_sampler,
|
|
92
93
|
call_tree: config.instrument_call_tree
|
|
@@ -122,6 +123,7 @@ module Catpm
|
|
|
122
123
|
|
|
123
124
|
if owns_segments
|
|
124
125
|
req_segments&.release!
|
|
126
|
+
Collector.end_instrumentation
|
|
125
127
|
Thread.current[:catpm_request_segments] = nil
|
|
126
128
|
# Mark that this request was already instrumented and processed by
|
|
127
129
|
# track_request. Without this, process_action_controller would see
|
data/lib/catpm/version.rb
CHANGED
data/lib/catpm.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.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
@@ -66,6 +66,8 @@ files:
|
|
|
66
66
|
- app/views/catpm/samples/show.html.erb
|
|
67
67
|
- app/views/catpm/shared/_page_nav.html.erb
|
|
68
68
|
- app/views/catpm/shared/_segments_waterfall.html.erb
|
|
69
|
+
- app/views/catpm/shared/not_found.html.erb
|
|
70
|
+
- app/views/catpm/shared/record_not_found.html.erb
|
|
69
71
|
- app/views/catpm/status/index.html.erb
|
|
70
72
|
- app/views/catpm/system/index.html.erb
|
|
71
73
|
- app/views/catpm/system/pipeline.html.erb
|