closeyourit-ruby 0.3.3 → 0.3.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 +4 -4
- data/lib/closeyourit/configuration.rb +15 -1
- data/lib/closeyourit/events/performance_issue_event.rb +42 -0
- data/lib/closeyourit/events/slow_query_event.rb +2 -0
- data/lib/closeyourit/performance/request_profile.rb +51 -0
- data/lib/closeyourit/performance/rollup.rb +76 -0
- data/lib/closeyourit/rails/net_http_patch.rb +60 -0
- data/lib/closeyourit/rails/railtie.rb +37 -1
- data/lib/closeyourit/scope.rb +8 -0
- data/lib/closeyourit/subscribers/request_performance.rb +38 -0
- data/lib/closeyourit/subscribers/slow_query.rb +22 -1
- data/lib/closeyourit/version.rb +1 -1
- data/lib/closeyourit-ruby.rb +5 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fce4d4eb3a92009bf70daeb903091ec30b4274d926b52d5b8df44644a808c35e
|
|
4
|
+
data.tar.gz: be83377677c8567c700d8ba5d6a47aa1664474db87aa750ab8a61dbed85fadb2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4533f36e714d2c8ac1706a8bdce8652beed02384f0ce09f5965679c0d76a5e098b5ed006530bc88bbc4c92ed982ed373bd9dd5d5db04cf015770949deebe3e00
|
|
7
|
+
data.tar.gz: 3500e6f71ea42ba8bb900d78dabde73941913e5b03459ab0f2e570b27c26b6637df27a786d5ea1eb6e63b7ffcb8c8b84d07b6299d5553d5711ea3700543449dd
|
|
@@ -24,7 +24,10 @@ module CloseYourIt
|
|
|
24
24
|
:breadcrumbs_enabled, :max_breadcrumbs, :sample_rate,
|
|
25
25
|
:capture_handled_errors, :report_active_job_errors,
|
|
26
26
|
:logs_enabled, :logs_sample_rate, :logs_batch_size, :logs_flush_interval,
|
|
27
|
-
:capture_rails_logs, :logs_min_level
|
|
27
|
+
:capture_rails_logs, :logs_min_level,
|
|
28
|
+
:detect_performance_issues, :n_plus_one_threshold, :query_count_threshold,
|
|
29
|
+
:query_time_threshold_ms, :slow_request_threshold_ms, :slow_external_threshold_ms,
|
|
30
|
+
:capture_external_http
|
|
28
31
|
attr_writer :release
|
|
29
32
|
attr_reader :excluded_exceptions, :filter_parameters, :scrub_message_patterns
|
|
30
33
|
|
|
@@ -76,6 +79,17 @@ module CloseYourIt
|
|
|
76
79
|
@capture_rails_logs = false
|
|
77
80
|
@logs_min_level = :info
|
|
78
81
|
|
|
82
|
+
# Performance issue detection (verdetti aggregati: N+1, slow request, HTTP esterne lente).
|
|
83
|
+
# OPT-IN, default OFF: profila OGNI query della richiesta → overhead non trascurabile, va attivato
|
|
84
|
+
# consapevolmente per-app. Le soglie sono conservative (poco rumore). Vedi Performance::Rollup.
|
|
85
|
+
@detect_performance_issues = false
|
|
86
|
+
@n_plus_one_threshold = 10 # stesso fingerprint+call-site eseguito > N volte in una richiesta
|
|
87
|
+
@query_count_threshold = 100 # troppe query totali in una richiesta
|
|
88
|
+
@query_time_threshold_ms = 500 # tempo DB totale per richiesta oltre cui = high_query_count
|
|
89
|
+
@slow_request_threshold_ms = 1000 # durata totale della richiesta
|
|
90
|
+
@slow_external_threshold_ms = 1000 # singola chiamata HTTP esterna
|
|
91
|
+
@capture_external_http = true # strumenta Net::HTTP (solo se detect_performance_issues)
|
|
92
|
+
|
|
79
93
|
@filter_parameters = []
|
|
80
94
|
@scrub_message_patterns = []
|
|
81
95
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../event"
|
|
5
|
+
|
|
6
|
+
module CloseYourIt
|
|
7
|
+
# Payload `kind=performance_issue` per la pipeline metriche (`/api/v1/projects/:id/metrics`).
|
|
8
|
+
# È un VERDETTO aggregato (N+1, slow request, slow external HTTP), non una metrica grezza:
|
|
9
|
+
# `subtype` lo qualifica, `trace_id` lo correla a log/errori della stessa richiesta. Lo SQL è già
|
|
10
|
+
# offuscato (è il fingerprint del profilo). I campi nil vengono omessi (slow_request non ha sql).
|
|
11
|
+
class PerformanceIssueEvent < Event
|
|
12
|
+
def initialize(attrs, configuration)
|
|
13
|
+
super(configuration)
|
|
14
|
+
@attrs = attrs
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
compact(
|
|
19
|
+
"kind" => "performance_issue",
|
|
20
|
+
"subtype" => @attrs[:subtype],
|
|
21
|
+
"sample_id" => SecureRandom.uuid,
|
|
22
|
+
"duration_ms" => @attrs[:duration_ms]&.round(2),
|
|
23
|
+
"occurred_at" => @occurred_at,
|
|
24
|
+
"environment" => environment,
|
|
25
|
+
"trace_id" => @attrs[:trace_id],
|
|
26
|
+
"sql" => @attrs[:sql],
|
|
27
|
+
"source" => @attrs[:source],
|
|
28
|
+
"route" => @attrs[:route],
|
|
29
|
+
"http_host" => @attrs[:http_host],
|
|
30
|
+
"http_url" => @attrs[:http_url],
|
|
31
|
+
"query_count" => @attrs[:query_count],
|
|
32
|
+
"total_query_time_ms" => @attrs[:total_query_time_ms]&.round(2),
|
|
33
|
+
"count_in_request" => @attrs[:count_in_request],
|
|
34
|
+
"sdk" => sdk
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ingest_path(project_id)
|
|
39
|
+
"/api/v1/projects/#{project_id}/metrics"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require_relative "../event"
|
|
5
5
|
require_relative "../scrubber"
|
|
6
|
+
require_relative "../scope"
|
|
6
7
|
|
|
7
8
|
module CloseYourIt
|
|
8
9
|
# Payload `kind=slow_query` per la pipeline metriche (`/api/v1/projects/:id/metrics`).
|
|
@@ -27,6 +28,7 @@ module CloseYourIt
|
|
|
27
28
|
"cached" => @payload.fetch(:cached, false),
|
|
28
29
|
"db_system" => db_system,
|
|
29
30
|
"source" => @payload[:source],
|
|
31
|
+
"trace_id" => CloseYourIt::Scope.current.trace_id,
|
|
30
32
|
"bindings" => bindings,
|
|
31
33
|
"sdk" => sdk
|
|
32
34
|
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloseYourIt
|
|
4
|
+
module Performance
|
|
5
|
+
# Accumulatore per-richiesta (vive nello Scope, resettato a fine richiesta). Conta le query, le
|
|
6
|
+
# raggruppa per [fingerprint SQL offuscato, call-site] (il pattern prosopite per l'N+1) e tiene le
|
|
7
|
+
# chiamate HTTP esterne. Puro stato in memoria: il verdetto lo calcola Performance::Rollup.
|
|
8
|
+
class RequestProfile
|
|
9
|
+
# Guard di memoria: cap ai gruppi/chiamate distinti tracciati (il conteggio totale resta esatto).
|
|
10
|
+
MAX_GROUPS = 1000
|
|
11
|
+
MAX_EXTERNAL = 500
|
|
12
|
+
|
|
13
|
+
attr_reader :query_count, :total_query_time_ms, :query_groups, :external_calls
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@query_count = 0
|
|
17
|
+
@total_query_time_ms = 0.0
|
|
18
|
+
@query_groups = {}
|
|
19
|
+
@external_calls = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Una query non di sistema. Le query da cache non sono round-trip DB → non contano per l'N+1.
|
|
23
|
+
def add_query(fingerprint:, source:, duration_ms:, cached: false)
|
|
24
|
+
return if cached
|
|
25
|
+
|
|
26
|
+
ms = duration_ms.to_f
|
|
27
|
+
@query_count += 1
|
|
28
|
+
@total_query_time_ms += ms
|
|
29
|
+
|
|
30
|
+
key = "#{fingerprint}\n#{source}"
|
|
31
|
+
group = @query_groups[key]
|
|
32
|
+
if group
|
|
33
|
+
group[:count] += 1
|
|
34
|
+
group[:duration_ms] += ms
|
|
35
|
+
elsif @query_groups.size < MAX_GROUPS
|
|
36
|
+
@query_groups[key] = { count: 1, duration_ms: ms, sql: fingerprint, source: source }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_external(host:, path:, duration_ms:)
|
|
41
|
+
return if @external_calls.size >= MAX_EXTERNAL
|
|
42
|
+
|
|
43
|
+
@external_calls << { host: host, path: path, duration_ms: duration_ms.to_f }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty?
|
|
47
|
+
@query_count.zero? && @external_calls.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../events/performance_issue_event"
|
|
4
|
+
|
|
5
|
+
module CloseYourIt
|
|
6
|
+
module Performance
|
|
7
|
+
# Trasforma un RequestProfile (+ durata/route della richiesta) in 0..N verdetti PerformanceIssueEvent.
|
|
8
|
+
# Le soglie vivono nella Configuration. Detection lato client; dedup/alert lato backend.
|
|
9
|
+
class Rollup
|
|
10
|
+
def self.call(...) = new(...).call
|
|
11
|
+
|
|
12
|
+
def initialize(profile:, configuration:, route: nil, request_duration_ms: nil, trace_id: nil)
|
|
13
|
+
@profile = profile
|
|
14
|
+
@config = configuration
|
|
15
|
+
@route = route
|
|
16
|
+
@request_duration_ms = request_duration_ms
|
|
17
|
+
@trace_id = trace_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
events = []
|
|
22
|
+
events.concat(n_plus_one_events)
|
|
23
|
+
events << high_query_count_event if high_query_count?
|
|
24
|
+
events << slow_request_event if slow_request?
|
|
25
|
+
events.concat(slow_external_events)
|
|
26
|
+
events.compact
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Un verdetto per ogni gruppo [fingerprint, call-site] che ha girato più di n_plus_one_threshold volte.
|
|
32
|
+
def n_plus_one_events
|
|
33
|
+
@profile.query_groups.values.filter_map do |group|
|
|
34
|
+
next unless group[:count] > @config.n_plus_one_threshold
|
|
35
|
+
|
|
36
|
+
build(subtype: "n_plus_one", duration_ms: group[:duration_ms], sql: group[:sql],
|
|
37
|
+
source: group[:source], query_count: group[:count], count_in_request: group[:count])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def high_query_count?
|
|
42
|
+
(@config.query_count_threshold && @profile.query_count > @config.query_count_threshold) ||
|
|
43
|
+
(@config.query_time_threshold_ms && @profile.total_query_time_ms > @config.query_time_threshold_ms)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def high_query_count_event
|
|
47
|
+
build(subtype: "high_query_count", duration_ms: @profile.total_query_time_ms,
|
|
48
|
+
query_count: @profile.query_count, total_query_time_ms: @profile.total_query_time_ms,
|
|
49
|
+
route: @route)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def slow_request?
|
|
53
|
+
@request_duration_ms && @config.slow_request_threshold_ms &&
|
|
54
|
+
@request_duration_ms > @config.slow_request_threshold_ms
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def slow_request_event
|
|
58
|
+
build(subtype: "slow_request", duration_ms: @request_duration_ms, route: @route,
|
|
59
|
+
query_count: @profile.query_count, total_query_time_ms: @profile.total_query_time_ms)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def slow_external_events
|
|
63
|
+
@profile.external_calls.filter_map do |call|
|
|
64
|
+
next unless call[:duration_ms] > @config.slow_external_threshold_ms
|
|
65
|
+
|
|
66
|
+
build(subtype: "slow_external_http", duration_ms: call[:duration_ms],
|
|
67
|
+
http_host: call[:host], http_url: call[:path])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build(attrs)
|
|
72
|
+
PerformanceIssueEvent.new(attrs.merge(trace_id: @trace_id), @config)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require_relative "../scope"
|
|
5
|
+
|
|
6
|
+
module CloseYourIt
|
|
7
|
+
module Rails
|
|
8
|
+
# Prepended a Net::HTTP: cronometra ogni chiamata esterna e la spinge nel RequestProfile dello
|
|
9
|
+
# Scope, così la finestra della richiesta può rilevare le HTTP esterne lente. Trasparente
|
|
10
|
+
# (restituisce la risposta originale) e difensivo (no-op se la telemetria è off; mai solleva per
|
|
11
|
+
# colpa del profiling). Esclude le chiamate verso l'endpoint CloseYourIt stesso (niente loop).
|
|
12
|
+
module NetHTTPPatch
|
|
13
|
+
def request(req, body = nil, &block)
|
|
14
|
+
config = CloseYourIt.configuration
|
|
15
|
+
return super unless config.detect_performance_issues && config.capture_external_http
|
|
16
|
+
|
|
17
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
18
|
+
begin
|
|
19
|
+
super
|
|
20
|
+
ensure
|
|
21
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000.0
|
|
22
|
+
record_external(config, req, duration_ms)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def record_external(config, req, duration_ms)
|
|
29
|
+
host = address
|
|
30
|
+
return if host.nil? || own_endpoint?(config, host)
|
|
31
|
+
|
|
32
|
+
CloseYourIt::Scope.current.performance_profile.add_external(
|
|
33
|
+
host: host, path: templatize_path(req), duration_ms: duration_ms
|
|
34
|
+
)
|
|
35
|
+
rescue StandardError
|
|
36
|
+
# Il profiling non deve mai disturbare la chiamata ospite.
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def own_endpoint?(config, host)
|
|
41
|
+
endpoint = config.endpoint_url
|
|
42
|
+
return false if endpoint.nil?
|
|
43
|
+
|
|
44
|
+
URI.parse(endpoint).host == host
|
|
45
|
+
rescue URI::InvalidURIError
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Path senza query string, con uuid e run di ≥3 cifre → placeholder (stessa rotta = stessa
|
|
50
|
+
# signature). La soglia ≥3 cifre preserva le versioni API tipo "/v1" e templatizza gli id reali
|
|
51
|
+
# ("/v1/charges/ch_12345" → "/v1/charges/ch_<n>", "/users/123" → "/users/<n>").
|
|
52
|
+
def templatize_path(req)
|
|
53
|
+
path = req.respond_to?(:path) ? req.path.to_s : ""
|
|
54
|
+
path.split("?", 2).first.to_s
|
|
55
|
+
.gsub(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i, "<uuid>")
|
|
56
|
+
.gsub(/\d{3,}/, "<n>")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -7,7 +7,9 @@ require_relative "error_subscriber"
|
|
|
7
7
|
require_relative "../sidekiq/error_handler"
|
|
8
8
|
require_relative "query_source"
|
|
9
9
|
require_relative "log_broadcast"
|
|
10
|
+
require_relative "net_http_patch"
|
|
10
11
|
require_relative "../subscribers/slow_query"
|
|
12
|
+
require_relative "../subscribers/request_performance"
|
|
11
13
|
|
|
12
14
|
module CloseYourIt
|
|
13
15
|
module Rails
|
|
@@ -45,9 +47,39 @@ module CloseYourIt
|
|
|
45
47
|
duration_ms: event.duration,
|
|
46
48
|
cached: event.payload.fetch(:cached, false)
|
|
47
49
|
)
|
|
50
|
+
# Accumula la query nel profilo per-richiesta (detection N+1 a fine richiesta).
|
|
51
|
+
subscriber.profile(
|
|
52
|
+
name: event.payload[:name],
|
|
53
|
+
sql: event.payload[:sql],
|
|
54
|
+
duration_ms: event.duration,
|
|
55
|
+
cached: event.payload.fetch(:cached, false),
|
|
56
|
+
source: CloseYourIt::Rails::QuerySource.from_caller
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# A fine richiesta: trasforma il profilo accumulato in verdetti performance_issue (N+1, slow
|
|
62
|
+
# request, HTTP esterne lente). Il subscriber è no-op se detect_performance_issues è OFF.
|
|
63
|
+
initializer "closeyourit.subscribe_request_performance" do
|
|
64
|
+
perf = CloseYourIt::Subscribers::RequestPerformance.new
|
|
65
|
+
|
|
66
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
|
|
67
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
68
|
+
payload = event.payload
|
|
69
|
+
perf.record(
|
|
70
|
+
route: "#{payload[:controller]}##{payload[:action]}",
|
|
71
|
+
duration_ms: event.duration
|
|
72
|
+
)
|
|
48
73
|
end
|
|
49
74
|
end
|
|
50
75
|
|
|
76
|
+
# Strumenta le chiamate HTTP esterne (Net::HTTP) per rilevare quelle lente nella finestra della
|
|
77
|
+
# richiesta. Il patch è no-op (chiama super) se la detection è OFF → overhead trascurabile.
|
|
78
|
+
initializer "closeyourit.instrument_net_http" do
|
|
79
|
+
require "net/http"
|
|
80
|
+
::Net::HTTP.prepend(CloseYourIt::Rails::NetHTTPPatch) unless ::Net::HTTP.ancestors.include?(CloseYourIt::Rails::NetHTTPPatch)
|
|
81
|
+
end
|
|
82
|
+
|
|
51
83
|
# Cattura gli errori di ActiveJob/Solid Queue (around_perform).
|
|
52
84
|
initializer "closeyourit.active_job" do
|
|
53
85
|
ActiveSupport.on_load(:active_job) do
|
|
@@ -64,7 +96,11 @@ module CloseYourIt
|
|
|
64
96
|
|
|
65
97
|
# Broadcast opt-in di Rails.logger → CloseYourIt.log (config.capture_rails_logs, default OFF).
|
|
66
98
|
# Spedisce solo i log dell'app ≥ logs_min_level. Richiede BroadcastLogger (Rails 7.1+).
|
|
67
|
-
|
|
99
|
+
# `after: :load_config_initializers`: config.capture_rails_logs è impostato in
|
|
100
|
+
# config/initializers/closeyourit.rb (CloseYourIt.init), che gira DOPO gli initializer dei
|
|
101
|
+
# railtie. Senza questo `after:` il check leggerebbe il default (false) e il broadcast non
|
|
102
|
+
# verrebbe mai agganciato → i log dell'app non arriverebbero a CloseYourIt.
|
|
103
|
+
initializer "closeyourit.capture_rails_logs", after: :load_config_initializers do
|
|
68
104
|
config = CloseYourIt.configuration
|
|
69
105
|
if config.capture_rails_logs && ::Rails.logger.respond_to?(:broadcast_to)
|
|
70
106
|
::Rails.logger.broadcast_to(CloseYourIt::Rails::LogBroadcast.new(config.logs_min_level))
|
data/lib/closeyourit/scope.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "breadcrumb_buffer"
|
|
4
|
+
require_relative "performance/request_profile"
|
|
4
5
|
|
|
5
6
|
module CloseYourIt
|
|
6
7
|
# Contesto per-richiesta (o per-job) isolato per execution-context (Fiber storage):
|
|
@@ -65,6 +66,12 @@ module CloseYourIt
|
|
|
65
66
|
@breadcrumbs.add(breadcrumb)
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
# Profilo di performance per-richiesta (query + HTTP esterne). Lazy: creato al primo accesso,
|
|
70
|
+
# azzerato da #clear a fine richiesta. Il verdetto lo calcola Subscribers::RequestPerformance.
|
|
71
|
+
def performance_profile
|
|
72
|
+
@performance_profile ||= Performance::RequestProfile.new
|
|
73
|
+
end
|
|
74
|
+
|
|
68
75
|
def clear
|
|
69
76
|
@user = {}
|
|
70
77
|
@tags = {}
|
|
@@ -73,6 +80,7 @@ module CloseYourIt
|
|
|
73
80
|
@request = nil
|
|
74
81
|
@trace_id = nil
|
|
75
82
|
@breadcrumbs = BreadcrumbBuffer.new(CloseYourIt.configuration.max_breadcrumbs)
|
|
83
|
+
@performance_profile = nil
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
# Sottoinsieme non vuoto in forma evento Sentry (user/tags/extra/contexts/request),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../scope"
|
|
4
|
+
require_relative "../performance/rollup"
|
|
5
|
+
|
|
6
|
+
module CloseYourIt
|
|
7
|
+
module Subscribers
|
|
8
|
+
# A fine richiesta (process_action.action_controller) trasforma il RequestProfile accumulato nello
|
|
9
|
+
# Scope in verdetti performance_issue e li spedisce (fire-and-forget). Lo Scope — e quindi il
|
|
10
|
+
# profilo — viene azzerato subito dopo da RequestContext#call (ensure). Logica pura: il wiring ad
|
|
11
|
+
# ActiveSupport::Notifications vive nel Railtie.
|
|
12
|
+
class RequestPerformance
|
|
13
|
+
def initialize(configuration = nil)
|
|
14
|
+
@configuration = configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def record(route:, duration_ms:)
|
|
18
|
+
config = @configuration || CloseYourIt.configuration
|
|
19
|
+
return unless config.detect_performance_issues
|
|
20
|
+
|
|
21
|
+
profile = CloseYourIt::Scope.current.performance_profile
|
|
22
|
+
return if profile.empty? && !slow_request?(config, duration_ms)
|
|
23
|
+
|
|
24
|
+
events = CloseYourIt::Performance::Rollup.call(
|
|
25
|
+
profile: profile, configuration: config, route: route,
|
|
26
|
+
request_duration_ms: duration_ms, trace_id: CloseYourIt::Scope.current.trace_id
|
|
27
|
+
)
|
|
28
|
+
events.each { |event| CloseYourIt.capture_event(event) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def slow_request?(config, duration_ms)
|
|
34
|
+
duration_ms && config.slow_request_threshold_ms && duration_ms > config.slow_request_threshold_ms
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../events/slow_query_event"
|
|
4
4
|
require_relative "../scrubber"
|
|
5
|
+
require_relative "../scope"
|
|
5
6
|
|
|
6
7
|
module CloseYourIt
|
|
7
8
|
module Subscribers
|
|
@@ -40,13 +41,33 @@ module CloseYourIt
|
|
|
40
41
|
CloseYourIt.add_breadcrumb(
|
|
41
42
|
category: "query",
|
|
42
43
|
type: "query",
|
|
43
|
-
message:
|
|
44
|
+
message: scrubber(config).obfuscate_sql(sql),
|
|
44
45
|
data: { "name" => name, "duration_ms" => duration_ms.to_f.round(2), "cached" => cached }
|
|
45
46
|
)
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
# Spinge OGNI query non di sistema nel RequestProfile dello Scope (per la detection N+1 a fine
|
|
50
|
+
# richiesta). Solo se detect_performance_issues: fingerprint = SQL offuscato + call-site.
|
|
51
|
+
def profile(name:, sql:, duration_ms:, cached: false, source: nil)
|
|
52
|
+
config = @configuration || CloseYourIt.configuration
|
|
53
|
+
return unless config.detect_performance_issues
|
|
54
|
+
return if ignored_name?(name)
|
|
55
|
+
|
|
56
|
+
CloseYourIt::Scope.current.performance_profile.add_query(
|
|
57
|
+
fingerprint: scrubber(config).obfuscate_sql(sql),
|
|
58
|
+
source: source, duration_ms: duration_ms, cached: cached
|
|
59
|
+
)
|
|
60
|
+
rescue StandardError
|
|
61
|
+
# La telemetria non deve mai disturbare la query ospite.
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
48
65
|
private
|
|
49
66
|
|
|
67
|
+
def scrubber(config)
|
|
68
|
+
@scrubber ||= Scrubber.new(config)
|
|
69
|
+
end
|
|
70
|
+
|
|
50
71
|
def ignored_name?(name)
|
|
51
72
|
name.nil? || IGNORED_NAMES.include?(name)
|
|
52
73
|
end
|
data/lib/closeyourit/version.rb
CHANGED
data/lib/closeyourit-ruby.rb
CHANGED
|
@@ -16,15 +16,20 @@ require_relative "closeyourit/events/message_event"
|
|
|
16
16
|
require_relative "closeyourit/events/slow_query_event"
|
|
17
17
|
require_relative "closeyourit/events/slow_method_event"
|
|
18
18
|
require_relative "closeyourit/events/log_event"
|
|
19
|
+
require_relative "closeyourit/events/performance_issue_event"
|
|
20
|
+
require_relative "closeyourit/performance/request_profile"
|
|
21
|
+
require_relative "closeyourit/performance/rollup"
|
|
19
22
|
require_relative "closeyourit/log_device"
|
|
20
23
|
require_relative "closeyourit/log_buffer"
|
|
21
24
|
require_relative "closeyourit/subscribers/slow_query"
|
|
25
|
+
require_relative "closeyourit/subscribers/request_performance"
|
|
22
26
|
require_relative "closeyourit/instrumenter"
|
|
23
27
|
require_relative "closeyourit/monitor"
|
|
24
28
|
require_relative "closeyourit/client"
|
|
25
29
|
require_relative "closeyourit/rails/capture_exceptions"
|
|
26
30
|
require_relative "closeyourit/rails/request_context"
|
|
27
31
|
require_relative "closeyourit/rails/log_broadcast"
|
|
32
|
+
require_relative "closeyourit/rails/net_http_patch"
|
|
28
33
|
require_relative "closeyourit/rails/active_job_extension"
|
|
29
34
|
require_relative "closeyourit/rails/error_subscriber"
|
|
30
35
|
require_relative "closeyourit/sidekiq/error_handler"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: closeyourit-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessio Bussolari
|
|
@@ -43,16 +43,20 @@ files:
|
|
|
43
43
|
- lib/closeyourit/events/error_event.rb
|
|
44
44
|
- lib/closeyourit/events/log_event.rb
|
|
45
45
|
- lib/closeyourit/events/message_event.rb
|
|
46
|
+
- lib/closeyourit/events/performance_issue_event.rb
|
|
46
47
|
- lib/closeyourit/events/slow_method_event.rb
|
|
47
48
|
- lib/closeyourit/events/slow_query_event.rb
|
|
48
49
|
- lib/closeyourit/instrumenter.rb
|
|
49
50
|
- lib/closeyourit/log_buffer.rb
|
|
50
51
|
- lib/closeyourit/log_device.rb
|
|
51
52
|
- lib/closeyourit/monitor.rb
|
|
53
|
+
- lib/closeyourit/performance/request_profile.rb
|
|
54
|
+
- lib/closeyourit/performance/rollup.rb
|
|
52
55
|
- lib/closeyourit/rails/active_job_extension.rb
|
|
53
56
|
- lib/closeyourit/rails/capture_exceptions.rb
|
|
54
57
|
- lib/closeyourit/rails/error_subscriber.rb
|
|
55
58
|
- lib/closeyourit/rails/log_broadcast.rb
|
|
59
|
+
- lib/closeyourit/rails/net_http_patch.rb
|
|
56
60
|
- lib/closeyourit/rails/query_source.rb
|
|
57
61
|
- lib/closeyourit/rails/railtie.rb
|
|
58
62
|
- lib/closeyourit/rails/request_context.rb
|
|
@@ -60,6 +64,7 @@ files:
|
|
|
60
64
|
- lib/closeyourit/scrubber.rb
|
|
61
65
|
- lib/closeyourit/sidekiq/error_handler.rb
|
|
62
66
|
- lib/closeyourit/stats.rb
|
|
67
|
+
- lib/closeyourit/subscribers/request_performance.rb
|
|
63
68
|
- lib/closeyourit/subscribers/slow_query.rb
|
|
64
69
|
- lib/closeyourit/transport.rb
|
|
65
70
|
- lib/closeyourit/version.rb
|