sentry-ruby 5.10.0 → 5.26.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/.rspec +3 -1
- data/Gemfile +12 -13
- data/README.md +26 -11
- data/Rakefile +9 -11
- data/bin/console +2 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +11 -5
- data/lib/sentry/backpressure_monitor.rb +45 -0
- data/lib/sentry/backtrace.rb +12 -9
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +13 -6
- data/lib/sentry/check_in_event.rb +61 -0
- data/lib/sentry/client.rb +214 -25
- data/lib/sentry/configuration.rb +221 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +77 -0
- data/lib/sentry/cron/monitor_config.rb +53 -0
- data/lib/sentry/cron/monitor_schedule.rb +42 -0
- data/lib/sentry/dsn.rb +4 -4
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -68
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +28 -47
- data/lib/sentry/excon/middleware.rb +77 -0
- data/lib/sentry/excon.rb +10 -0
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +9 -0
- data/lib/sentry/hub.rb +138 -6
- data/lib/sentry/integrable.rb +10 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +8 -8
- data/lib/sentry/interfaces/single_exception.rb +13 -9
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +206 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +248 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +51 -0
- data/lib/sentry/metrics.rb +56 -0
- data/lib/sentry/net/http.rb +27 -44
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +41 -60
- data/lib/sentry/propagation_context.rb +135 -0
- data/lib/sentry/puma.rb +12 -5
- data/lib/sentry/rack/capture_exceptions.rb +17 -8
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +4 -15
- data/lib/sentry/redis.rb +10 -4
- data/lib/sentry/release_detector.rb +5 -5
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +75 -39
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +15 -43
- data/lib/sentry/span.rb +92 -8
- data/lib/sentry/std_lib_logger.rb +50 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +42 -13
- data/lib/sentry/threaded_periodic_worker.rb +39 -0
- data/lib/sentry/transaction.rb +44 -43
- data/lib/sentry/transaction_event.rb +10 -6
- data/lib/sentry/transport/configuration.rb +73 -1
- data/lib/sentry/transport/http_transport.rb +71 -41
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +53 -49
- data/lib/sentry/utils/argument_checking_helper.rb +12 -0
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +74 -0
- data/lib/sentry/utils/logging_helper.rb +10 -7
- data/lib/sentry/utils/real_ip.rb +2 -2
- data/lib/sentry/utils/request_id.rb +1 -1
- data/lib/sentry/utils/uuid.rb +13 -0
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +132 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +206 -35
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +15 -6
- metadata +61 -11
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class LocalAggregator
|
6
|
+
# exposed only for testing
|
7
|
+
attr_reader :buckets
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@buckets = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(key, value)
|
14
|
+
if @buckets[key]
|
15
|
+
@buckets[key].add(value)
|
16
|
+
else
|
17
|
+
@buckets[key] = GaugeMetric.new(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
return nil if @buckets.empty?
|
23
|
+
|
24
|
+
@buckets.map do |bucket_key, metric|
|
25
|
+
type, key, unit, tags = bucket_key
|
26
|
+
|
27
|
+
payload_key = "#{type}:#{key}@#{unit}"
|
28
|
+
payload_value = {
|
29
|
+
tags: deserialize_tags(tags),
|
30
|
+
min: metric.min,
|
31
|
+
max: metric.max,
|
32
|
+
count: metric.count,
|
33
|
+
sum: metric.sum
|
34
|
+
}
|
35
|
+
|
36
|
+
[payload_key, payload_value]
|
37
|
+
end.to_h
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def deserialize_tags(tags)
|
43
|
+
tags.inject({}) do |h, tag|
|
44
|
+
k, v = tag
|
45
|
+
old = h[k]
|
46
|
+
# make it an array if key repeats
|
47
|
+
h[k] = old ? (old.is_a?(Array) ? old << v : [old, v]) : v
|
48
|
+
h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class Metric
|
6
|
+
def add(value)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def weight
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
module Metrics
|
8
|
+
class SetMetric < Metric
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = Set[value]
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(value)
|
16
|
+
@value << value
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize
|
20
|
+
value.map { |x| x.is_a?(String) ? Zlib.crc32(x) : x.to_i }
|
21
|
+
end
|
22
|
+
|
23
|
+
def weight
|
24
|
+
value.size
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
module Timing
|
6
|
+
class << self
|
7
|
+
def nanosecond
|
8
|
+
time = Sentry.utc_now
|
9
|
+
time.to_i * (10 ** 9) + time.nsec
|
10
|
+
end
|
11
|
+
|
12
|
+
def microsecond
|
13
|
+
time = Sentry.utc_now
|
14
|
+
time.to_i * (10 ** 6) + time.usec
|
15
|
+
end
|
16
|
+
|
17
|
+
def millisecond
|
18
|
+
Sentry.utc_now.to_i * (10 ** 3)
|
19
|
+
end
|
20
|
+
|
21
|
+
def second
|
22
|
+
Sentry.utc_now.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def minute
|
26
|
+
Sentry.utc_now.to_i / 60.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def hour
|
30
|
+
Sentry.utc_now.to_i / 3600.0
|
31
|
+
end
|
32
|
+
|
33
|
+
def day
|
34
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def week
|
38
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
|
39
|
+
end
|
40
|
+
|
41
|
+
def duration_start
|
42
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
43
|
+
end
|
44
|
+
|
45
|
+
def duration_end(start)
|
46
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sentry/metrics/metric"
|
4
|
+
require "sentry/metrics/counter_metric"
|
5
|
+
require "sentry/metrics/distribution_metric"
|
6
|
+
require "sentry/metrics/gauge_metric"
|
7
|
+
require "sentry/metrics/set_metric"
|
8
|
+
require "sentry/metrics/timing"
|
9
|
+
require "sentry/metrics/aggregator"
|
10
|
+
|
11
|
+
module Sentry
|
12
|
+
module Metrics
|
13
|
+
DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
|
14
|
+
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
|
15
|
+
FRACTIONAL_UNITS = %w[ratio percent]
|
16
|
+
|
17
|
+
OP_NAME = "metric.timing"
|
18
|
+
SPAN_ORIGIN = "auto.metric.timing"
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
|
22
|
+
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
23
|
+
end
|
24
|
+
|
25
|
+
def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
|
26
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(key, value, unit: "none", tags: {}, timestamp: nil)
|
30
|
+
Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
31
|
+
end
|
32
|
+
|
33
|
+
def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
|
34
|
+
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
35
|
+
end
|
36
|
+
|
37
|
+
def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
|
38
|
+
return unless block_given?
|
39
|
+
return yield unless DURATION_UNITS.include?(unit)
|
40
|
+
|
41
|
+
result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
|
42
|
+
tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
|
43
|
+
|
44
|
+
start = Timing.send(unit.to_sym)
|
45
|
+
result = yield
|
46
|
+
value = Timing.send(unit.to_sym) - start
|
47
|
+
|
48
|
+
[result, value]
|
49
|
+
end
|
50
|
+
|
51
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/sentry/net/http.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "net/http"
|
4
|
+
require "resolv"
|
5
|
+
require "sentry/utils/http_tracing"
|
4
6
|
|
5
7
|
module Sentry
|
6
8
|
# @api private
|
7
9
|
module Net
|
8
10
|
module HTTP
|
11
|
+
include Utils::HttpTracing
|
12
|
+
|
9
13
|
OP_NAME = "http.client"
|
14
|
+
SPAN_ORIGIN = "auto.http.net_http"
|
10
15
|
BREADCRUMB_CATEGORY = "net.http"
|
16
|
+
URI_PARSER = URI.const_defined?("RFC2396_PARSER") ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
|
11
17
|
|
12
18
|
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
13
19
|
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
|
@@ -19,8 +25,7 @@ module Sentry
|
|
19
25
|
# req['connection'] ||= 'close'
|
20
26
|
# return request(req, body, &block) # <- request will be called for the second time from the first call
|
21
27
|
# }
|
22
|
-
# end
|
23
|
-
# # .....
|
28
|
+
# end # .....
|
24
29
|
# end
|
25
30
|
# ```
|
26
31
|
#
|
@@ -29,54 +34,29 @@ module Sentry
|
|
29
34
|
return super unless started? && Sentry.initialized?
|
30
35
|
return super if from_sentry_sdk?
|
31
36
|
|
32
|
-
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
|
33
|
-
|
34
|
-
|
35
|
-
super.tap do |res|
|
36
|
-
record_sentry_breadcrumb(req, res)
|
37
|
+
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
|
38
|
+
request_info = extract_request_info(req)
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
41
|
-
sentry_span.set_data('url', request_info[:url])
|
42
|
-
sentry_span.set_data('http.method', request_info[:method])
|
43
|
-
sentry_span.set_data('http.query', request_info[:query]) if request_info[:query]
|
44
|
-
sentry_span.set_data('status', res.code.to_i)
|
45
|
-
end
|
40
|
+
if propagate_trace?(request_info[:url])
|
41
|
+
set_propagation_headers(req)
|
46
42
|
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
43
|
|
52
|
-
|
53
|
-
|
44
|
+
res = super
|
45
|
+
response_status = res.code.to_i
|
54
46
|
|
55
|
-
|
47
|
+
if record_sentry_breadcrumb?
|
48
|
+
record_sentry_breadcrumb(request_info, response_status)
|
49
|
+
end
|
56
50
|
|
57
|
-
|
58
|
-
|
51
|
+
if sentry_span
|
52
|
+
set_span_info(sentry_span, request_info, response_status)
|
53
|
+
end
|
59
54
|
|
60
|
-
|
61
|
-
|
55
|
+
res
|
56
|
+
end
|
62
57
|
end
|
63
58
|
|
64
|
-
|
65
|
-
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
66
|
-
|
67
|
-
request_info = extract_request_info(req)
|
68
|
-
|
69
|
-
crumb = Sentry::Breadcrumb.new(
|
70
|
-
level: :info,
|
71
|
-
category: BREADCRUMB_CATEGORY,
|
72
|
-
type: :info,
|
73
|
-
data: {
|
74
|
-
status: res.code.to_i,
|
75
|
-
**request_info
|
76
|
-
}
|
77
|
-
)
|
78
|
-
Sentry.add_breadcrumb(crumb)
|
79
|
-
end
|
59
|
+
private
|
80
60
|
|
81
61
|
def from_sentry_sdk?
|
82
62
|
dsn = Sentry.configuration.dsn
|
@@ -84,7 +64,10 @@ module Sentry
|
|
84
64
|
end
|
85
65
|
|
86
66
|
def extract_request_info(req)
|
87
|
-
|
67
|
+
# IPv6 url could look like '::1/path', and that won't parse without
|
68
|
+
# wrapping it in square brackets.
|
69
|
+
hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
|
70
|
+
uri = req.uri || URI.parse(URI_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
|
88
71
|
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
89
72
|
|
90
73
|
result = { method: req.method, url: url }
|
@@ -100,4 +83,4 @@ module Sentry
|
|
100
83
|
end
|
101
84
|
end
|
102
85
|
|
103
|
-
Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
|
86
|
+
Sentry.register_patch(:http, Sentry::Net::HTTP, Net::HTTP)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Sentry
|
6
|
+
class Profiler
|
7
|
+
module Helpers
|
8
|
+
def in_app?(abs_path)
|
9
|
+
abs_path.match?(@in_app_pattern)
|
10
|
+
end
|
11
|
+
|
12
|
+
# copied from stacktrace.rb since I don't want to touch existing code
|
13
|
+
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
14
|
+
# the native extension
|
15
|
+
def compute_filename(abs_path, in_app)
|
16
|
+
return nil if abs_path.nil?
|
17
|
+
|
18
|
+
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
19
|
+
|
20
|
+
prefix =
|
21
|
+
if under_project_root && in_app
|
22
|
+
@project_root
|
23
|
+
else
|
24
|
+
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
25
|
+
|
26
|
+
if under_project_root
|
27
|
+
longest_load_path || @project_root
|
28
|
+
else
|
29
|
+
longest_load_path
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def split_module(name)
|
37
|
+
# last module plus class/instance method
|
38
|
+
i = name.rindex("::")
|
39
|
+
function = i ? name[(i + 2)..-1] : name
|
40
|
+
mod = i ? name[0...i] : nil
|
41
|
+
|
42
|
+
[function, mod]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/sentry/profiler.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "securerandom"
|
4
|
+
require_relative "profiler/helpers"
|
5
|
+
require "sentry/utils/uuid"
|
4
6
|
|
5
7
|
module Sentry
|
6
8
|
class Profiler
|
7
|
-
|
8
|
-
|
9
|
+
include Profiler::Helpers
|
10
|
+
|
11
|
+
VERSION = "1"
|
12
|
+
PLATFORM = "ruby"
|
9
13
|
# 101 Hz in microseconds
|
10
14
|
DEFAULT_INTERVAL = 1e6 / 101
|
11
15
|
MICRO_TO_NANO_SECONDS = 1e3
|
16
|
+
MIN_SAMPLES_REQUIRED = 2
|
12
17
|
|
13
18
|
attr_reader :sampled, :started, :event_id
|
14
19
|
|
15
20
|
def initialize(configuration)
|
16
|
-
@event_id =
|
21
|
+
@event_id = Utils.uuid
|
17
22
|
@started = false
|
18
23
|
@sampled = nil
|
19
24
|
|
20
25
|
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
21
26
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
22
27
|
@project_root = configuration.project_root
|
23
|
-
@app_dirs_pattern = configuration.app_dirs_pattern
|
28
|
+
@app_dirs_pattern = configuration.app_dirs_pattern
|
24
29
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
25
30
|
end
|
26
31
|
|
@@ -32,7 +37,7 @@ module Sentry
|
|
32
37
|
raw: true,
|
33
38
|
aggregate: false)
|
34
39
|
|
35
|
-
@started ? log(
|
40
|
+
@started ? log("Started") : log("Not started since running elsewhere")
|
36
41
|
end
|
37
42
|
|
38
43
|
def stop
|
@@ -40,7 +45,11 @@ module Sentry
|
|
40
45
|
return unless @started
|
41
46
|
|
42
47
|
StackProf.stop
|
43
|
-
log(
|
48
|
+
log("Stopped")
|
49
|
+
end
|
50
|
+
|
51
|
+
def active_thread_id
|
52
|
+
"0"
|
44
53
|
end
|
45
54
|
|
46
55
|
# Sets initial sampling decision of the profile.
|
@@ -53,14 +62,14 @@ module Sentry
|
|
53
62
|
|
54
63
|
unless transaction_sampled
|
55
64
|
@sampled = false
|
56
|
-
log(
|
65
|
+
log("Discarding profile because transaction not sampled")
|
57
66
|
return
|
58
67
|
end
|
59
68
|
|
60
69
|
case @profiles_sample_rate
|
61
70
|
when 0.0
|
62
71
|
@sampled = false
|
63
|
-
log(
|
72
|
+
log("Discarding profile because sample_rate is 0")
|
64
73
|
return
|
65
74
|
when 1.0
|
66
75
|
@sampled = true
|
@@ -69,28 +78,32 @@ module Sentry
|
|
69
78
|
@sampled = Random.rand < @profiles_sample_rate
|
70
79
|
end
|
71
80
|
|
72
|
-
log(
|
81
|
+
log("Discarding profile due to sampling decision") unless @sampled
|
73
82
|
end
|
74
83
|
|
75
84
|
def to_hash
|
76
|
-
|
85
|
+
unless @sampled
|
86
|
+
record_lost_event(:sample_rate)
|
87
|
+
return {}
|
88
|
+
end
|
89
|
+
|
77
90
|
return {} unless @started
|
78
91
|
|
79
92
|
results = StackProf.results
|
80
|
-
return {} unless results
|
81
|
-
return {} if results.empty?
|
82
|
-
return {} if results[:samples] == 0
|
83
|
-
return {} unless results[:raw]
|
84
93
|
|
85
|
-
|
94
|
+
if !results || results.empty? || results[:samples] == 0 || !results[:raw]
|
95
|
+
record_lost_event(:insufficient_data)
|
96
|
+
return {}
|
97
|
+
end
|
86
98
|
|
87
|
-
|
88
|
-
frame_id, frame_data = frame
|
99
|
+
frame_map = {}
|
89
100
|
|
101
|
+
frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
|
90
102
|
# need to map over stackprof frame ids to ours
|
91
103
|
frame_map[frame_id] = idx
|
92
104
|
|
93
105
|
file_path = frame_data[:file]
|
106
|
+
lineno = frame_data[:line]
|
94
107
|
in_app = in_app?(file_path)
|
95
108
|
filename = compute_filename(file_path, in_app)
|
96
109
|
function, mod = split_module(frame_data[:name])
|
@@ -103,7 +116,7 @@ module Sentry
|
|
103
116
|
}
|
104
117
|
|
105
118
|
frame_hash[:module] = mod if mod
|
106
|
-
frame_hash[:lineno] =
|
119
|
+
frame_hash[:lineno] = lineno if lineno && lineno >= 0
|
107
120
|
|
108
121
|
frame_hash
|
109
122
|
end
|
@@ -124,7 +137,7 @@ module Sentry
|
|
124
137
|
num_seen << results[:raw][idx + len]
|
125
138
|
idx += len + 1
|
126
139
|
|
127
|
-
log(
|
140
|
+
log("Unknown frame in stack") if stack.size != len
|
128
141
|
end
|
129
142
|
|
130
143
|
idx = 0
|
@@ -149,16 +162,17 @@ module Sentry
|
|
149
162
|
# Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
|
150
163
|
# we're profiling is idle/sleeping/waiting for IO etc.
|
151
164
|
# https://bugs.ruby-lang.org/issues/10602
|
152
|
-
thread_id:
|
165
|
+
thread_id: "0",
|
153
166
|
elapsed_since_start_ns: elapsed_since_start_ns.to_s
|
154
167
|
}
|
155
168
|
end
|
156
169
|
end
|
157
170
|
|
158
|
-
log(
|
171
|
+
log("Some samples thrown away") if samples.size != results[:samples]
|
159
172
|
|
160
|
-
if samples.size <=
|
161
|
-
log(
|
173
|
+
if samples.size <= MIN_SAMPLES_REQUIRED
|
174
|
+
log("Not enough samples, discarding profiler")
|
175
|
+
record_lost_event(:insufficient_data)
|
162
176
|
return {}
|
163
177
|
end
|
164
178
|
|
@@ -179,44 +193,11 @@ module Sentry
|
|
179
193
|
private
|
180
194
|
|
181
195
|
def log(message)
|
182
|
-
Sentry.
|
183
|
-
end
|
184
|
-
|
185
|
-
def in_app?(abs_path)
|
186
|
-
abs_path.match?(@in_app_pattern)
|
187
|
-
end
|
188
|
-
|
189
|
-
# copied from stacktrace.rb since I don't want to touch existing code
|
190
|
-
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
191
|
-
# the native extension
|
192
|
-
def compute_filename(abs_path, in_app)
|
193
|
-
return nil if abs_path.nil?
|
194
|
-
|
195
|
-
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
196
|
-
|
197
|
-
prefix =
|
198
|
-
if under_project_root && in_app
|
199
|
-
@project_root
|
200
|
-
else
|
201
|
-
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
202
|
-
|
203
|
-
if under_project_root
|
204
|
-
longest_load_path || @project_root
|
205
|
-
else
|
206
|
-
longest_load_path
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
196
|
+
Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
|
211
197
|
end
|
212
198
|
|
213
|
-
def
|
214
|
-
|
215
|
-
i = name.rindex('::')
|
216
|
-
function = i ? name[(i + 2)..-1] : name
|
217
|
-
mod = i ? name[0...i] : nil
|
218
|
-
|
219
|
-
[function, mod]
|
199
|
+
def record_lost_event(reason)
|
200
|
+
Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
|
220
201
|
end
|
221
202
|
end
|
222
203
|
end
|