datadog 2.26.0 → 2.27.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/CHANGELOG.md +31 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +7 -6
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
- data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
- data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
- data/ext/datadog_profiling_native_extension/profiling.c +20 -15
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
- data/ext/libdatadog_api/crashtracker.c +5 -8
- data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
- data/ext/libdatadog_api/ddsketch.c +4 -8
- data/ext/libdatadog_api/feature_flags.c +5 -5
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +4 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/context.rb +3 -3
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
- data/lib/datadog/appsec/ext.rb +2 -0
- data/lib/datadog/appsec/metrics/collector.rb +8 -3
- data/lib/datadog/appsec/metrics/exporter.rb +7 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
- data/lib/datadog/appsec/metrics.rb +5 -5
- data/lib/datadog/appsec/remote.rb +4 -4
- data/lib/datadog/appsec.rb +7 -1
- data/lib/datadog/core/configuration/settings.rb +17 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/telemetry/logger.rb +2 -0
- data/lib/datadog/core/telemetry/logging.rb +20 -2
- data/lib/datadog/profiling/component.rb +13 -0
- data/lib/datadog/profiling/exporter.rb +4 -0
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/flush.rb +3 -0
- data/lib/datadog/profiling/profiler.rb +3 -5
- data/lib/datadog/profiling/scheduler.rb +8 -7
- data/lib/datadog/profiling/tag_builder.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- metadata +6 -4
|
@@ -13,27 +13,62 @@ module Datadog
|
|
|
13
13
|
# AppSec Middleware for Excon
|
|
14
14
|
class SSRFDetectionMiddleware < ::Excon::Middleware::Base
|
|
15
15
|
def request_call(data)
|
|
16
|
-
|
|
16
|
+
context = AppSec.active_context
|
|
17
|
+
return super unless context && AppSec.rasp_enabled?
|
|
18
|
+
|
|
19
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
20
|
+
ephemeral_data = {
|
|
21
|
+
'server.io.net.url' => request_url(data),
|
|
22
|
+
'server.io.net.request.method' => data[:method].to_s.upcase,
|
|
23
|
+
'server.io.net.request.headers' => normalize_headers(data[:headers])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
|
|
27
|
+
handle(result, context: context) if result.match?
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
17
31
|
|
|
32
|
+
def response_call(data)
|
|
18
33
|
context = AppSec.active_context
|
|
34
|
+
return super unless context && AppSec.rasp_enabled?
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
ephemeral_data = {
|
|
36
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
37
|
+
ephemeral_data = {
|
|
38
|
+
'server.io.net.response.status' => data.dig(:response, :status).to_s,
|
|
39
|
+
'server.io.net.response.headers' => normalize_headers(data.dig(:response, :headers))
|
|
40
|
+
}
|
|
22
41
|
|
|
23
|
-
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data,
|
|
42
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
|
|
43
|
+
handle(result, context: context) if result.match?
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
TraceKeeper.keep!(context.trace) if result.keep?
|
|
45
|
+
super
|
|
46
|
+
end
|
|
28
47
|
|
|
29
|
-
|
|
30
|
-
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
31
|
-
)
|
|
48
|
+
private
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
def request_url(data)
|
|
51
|
+
klass = (data[:scheme] == 'https') ? URI::HTTPS : URI::HTTP
|
|
52
|
+
klass.build(host: data[:host], path: data[:path], query: data[:query]).to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize_headers(headers)
|
|
56
|
+
return {} if headers.nil? || headers.empty?
|
|
57
|
+
|
|
58
|
+
headers.each_with_object({}) do |(key, value), memo|
|
|
59
|
+
memo[key.downcase] = value.is_a?(Array) ? value.join(', ') : value
|
|
34
60
|
end
|
|
61
|
+
end
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
def handle(result, context:)
|
|
64
|
+
AppSec::Event.tag(context, result)
|
|
65
|
+
TraceKeeper.keep!(context.trace) if result.keep?
|
|
66
|
+
|
|
67
|
+
context.events.push(
|
|
68
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
AppSec::ActionsHandler.handle(result.actions)
|
|
37
72
|
end
|
|
38
73
|
end
|
|
39
74
|
end
|
|
@@ -10,33 +10,50 @@ module Datadog
|
|
|
10
10
|
module Faraday
|
|
11
11
|
# AppSec SSRF detection Middleware for Faraday
|
|
12
12
|
class SSRFDetectionMiddleware < ::Faraday::Middleware
|
|
13
|
-
def call(
|
|
13
|
+
def call(env)
|
|
14
14
|
context = AppSec.active_context
|
|
15
|
+
return @app.call(env) unless context && AppSec.rasp_enabled?
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
18
18
|
ephemeral_data = {
|
|
19
|
-
'server.io.net.url' =>
|
|
19
|
+
'server.io.net.url' => env.url.to_s,
|
|
20
|
+
'server.io.net.request.method' => env.method.to_s.upcase,
|
|
21
|
+
'server.io.net.request.headers' => env.request_headers.transform_keys(&:downcase)
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data,
|
|
24
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
|
|
25
|
+
handle(result, context: context) if result.match?
|
|
26
|
+
|
|
27
|
+
@app.call(env).on_complete { |response_env| on_complete(response_env, context: context) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def on_complete(env, context:)
|
|
33
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
response_headers = env.response_headers || {}
|
|
36
|
+
ephemeral_data = {
|
|
37
|
+
'server.io.net.response.status' => env.status.to_s,
|
|
38
|
+
'server.io.net.response.headers' => response_headers.transform_keys(&:downcase)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
|
|
42
|
+
handle(result, context: context) if result.match?
|
|
43
|
+
end
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
def handle(result, context:)
|
|
46
|
+
AppSec::Event.tag(context, result)
|
|
47
|
+
TraceKeeper.keep!(context.trace) if result.keep?
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
context.events.push(
|
|
50
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
51
|
+
)
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
AppSec::ActionsHandler.handle(result.actions)
|
|
36
54
|
end
|
|
37
55
|
end
|
|
38
56
|
end
|
|
39
57
|
end
|
|
40
58
|
end
|
|
41
59
|
end
|
|
42
|
-
# rubocop:enable Naming/FileName
|
|
@@ -11,29 +11,65 @@ module Datadog
|
|
|
11
11
|
# Module that adds SSRF detection to RestClient::Request#execute
|
|
12
12
|
module RequestSSRFDetectionPatch
|
|
13
13
|
def execute(&block)
|
|
14
|
-
return super unless AppSec.rasp_enabled? && AppSec.active_context
|
|
15
|
-
|
|
16
14
|
context = AppSec.active_context
|
|
15
|
+
return super unless context && AppSec.rasp_enabled?
|
|
16
|
+
|
|
17
|
+
timeout = Datadog.configuration.appsec.waf_timeout
|
|
18
|
+
ephemeral_data = {
|
|
19
|
+
'server.io.net.url' => url,
|
|
20
|
+
'server.io.net.request.method' => method.to_s.upcase,
|
|
21
|
+
'server.io.net.request.headers' => normalize_request_headers
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_REQUEST_PHASE)
|
|
25
|
+
handle(result, context: context) if result.match?
|
|
26
|
+
|
|
27
|
+
response = super
|
|
28
|
+
|
|
29
|
+
ephemeral_data = {
|
|
30
|
+
'server.io.net.response.status' => response.code.to_s,
|
|
31
|
+
'server.io.net.response.headers' => normalize_response_headers(response)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, timeout, phase: Ext::RASP_RESPONSE_PHASE)
|
|
35
|
+
handle(result, context: context) if result.match?
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
response
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# NOTE: Starting version 2.1.0 headers are already normalized via internal
|
|
43
|
+
# variable `@processed_headers_lowercase`. In case it's available,
|
|
44
|
+
# we use it to avoid unnecessary transformation.
|
|
45
|
+
def normalize_request_headers
|
|
46
|
+
return @processed_headers_lowercase if defined?(@processed_headers_lowercase)
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
48
|
+
processed_headers.transform_keys(&:downcase)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# NOTE: Headers values are always an `Array` in `Net::HTTPResponse`,
|
|
52
|
+
# but we want to avoid accidents and will wrap them in no-op
|
|
53
|
+
# `Array` call just in case of a breaking change in the future
|
|
54
|
+
#
|
|
55
|
+
# FIXME: Steep has issues with `transform_values!` modifying the original
|
|
56
|
+
# type and it failed with "Cannot allow block body" error
|
|
57
|
+
def normalize_response_headers(response) # steep:ignore MethodBodyTypeMismatch
|
|
58
|
+
response.net_http_res.to_hash.transform_values! { |value| Array(value).join(', ') } # steep:ignore BlockBodyTypeMismatch
|
|
59
|
+
end
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
def handle(result, context:)
|
|
62
|
+
AppSec::Event.tag(context, result)
|
|
63
|
+
TraceKeeper.keep!(context.trace) if result.keep?
|
|
28
64
|
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
context.events.push(
|
|
66
|
+
AppSec::SecurityEvent.new(result, trace: context.trace, span: context.span)
|
|
67
|
+
)
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
AppSec::ActionsHandler.handle(result.actions)
|
|
33
70
|
end
|
|
34
71
|
end
|
|
35
72
|
end
|
|
36
73
|
end
|
|
37
74
|
end
|
|
38
75
|
end
|
|
39
|
-
# rubocop:enable Naming/FileName
|
data/lib/datadog/appsec/ext.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Datadog
|
|
|
13
13
|
:duration_ns,
|
|
14
14
|
:duration_ext_ns,
|
|
15
15
|
:inputs_truncated,
|
|
16
|
+
:downstream_requests,
|
|
16
17
|
keyword_init: true
|
|
17
18
|
)
|
|
18
19
|
|
|
@@ -22,10 +23,13 @@ module Datadog
|
|
|
22
23
|
@mutex = Mutex.new
|
|
23
24
|
|
|
24
25
|
@waf = Store.new(
|
|
25
|
-
evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
|
|
26
|
+
evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
|
|
27
|
+
duration_ext_ns: 0, inputs_truncated: 0, downstream_requests: 0
|
|
26
28
|
)
|
|
29
|
+
|
|
27
30
|
@rasp = Store.new(
|
|
28
|
-
evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
|
|
31
|
+
evals: 0, matches: 0, errors: 0, timeouts: 0, duration_ns: 0,
|
|
32
|
+
duration_ext_ns: 0, inputs_truncated: 0, downstream_requests: 0
|
|
29
33
|
)
|
|
30
34
|
end
|
|
31
35
|
|
|
@@ -41,7 +45,7 @@ module Datadog
|
|
|
41
45
|
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
def record_rasp(result)
|
|
48
|
+
def record_rasp(result, type:, phase: nil)
|
|
45
49
|
@mutex.synchronize do
|
|
46
50
|
@rasp.evals += 1
|
|
47
51
|
@waf.matches += 1 if result.match?
|
|
@@ -50,6 +54,7 @@ module Datadog
|
|
|
50
54
|
@rasp.duration_ns += result.duration_ns
|
|
51
55
|
@rasp.duration_ext_ns += result.duration_ext_ns
|
|
52
56
|
@rasp.inputs_truncated += 1 if result.input_truncated?
|
|
57
|
+
@rasp.downstream_requests += 1 if type == Ext::RASP_SSRF && phase == Ext::RASP_REQUEST_PHASE
|
|
53
58
|
end
|
|
54
59
|
end
|
|
55
60
|
end
|
|
@@ -22,6 +22,13 @@ module Datadog
|
|
|
22
22
|
span.set_tag('_dd.appsec.rasp.timeout', 1) unless metrics.timeouts.zero?
|
|
23
23
|
span.set_tag('_dd.appsec.rasp.duration', convert_ns_to_us(metrics.duration_ns))
|
|
24
24
|
span.set_tag('_dd.appsec.rasp.duration_ext', convert_ns_to_us(metrics.duration_ext_ns))
|
|
25
|
+
|
|
26
|
+
# NOTE: In case of downstream requests being analyzed additionally
|
|
27
|
+
# with `Context.run_waf` method, we would need to share it
|
|
28
|
+
# between two exporting methods
|
|
29
|
+
unless metrics.downstream_requests.zero?
|
|
30
|
+
span.set_tag('_dd.appsec.downstream_request', metrics.downstream_requests)
|
|
31
|
+
end
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
# private
|
|
@@ -7,10 +7,15 @@ module Datadog
|
|
|
7
7
|
module Telemetry
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
def report_rasp(type, result)
|
|
10
|
+
def report_rasp(type, result, phase: nil)
|
|
11
11
|
return if result.error?
|
|
12
12
|
|
|
13
|
-
tags = {rule_type: type, waf_version:
|
|
13
|
+
tags = {rule_type: type, waf_version: WAF::VERSION::BASE_STRING}
|
|
14
|
+
tags[:rule_variant] = phase if phase
|
|
15
|
+
|
|
16
|
+
context = AppSec.active_context
|
|
17
|
+
tags[:event_rules_version] = context.waf_runner_ruleset_version if context
|
|
18
|
+
|
|
14
19
|
namespace = Ext::TELEMETRY_METRICS_NAMESPACE
|
|
15
20
|
|
|
16
21
|
AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'metrics/collector'
|
|
4
|
+
require_relative 'metrics/exporter'
|
|
5
|
+
require_relative 'metrics/telemetry'
|
|
6
|
+
require_relative 'metrics/telemetry_exporter'
|
|
7
|
+
|
|
3
8
|
module Datadog
|
|
4
9
|
module AppSec
|
|
5
10
|
# This namespace contains classes related to metrics collection and exportation.
|
|
@@ -7,8 +12,3 @@ module Datadog
|
|
|
7
12
|
end
|
|
8
13
|
end
|
|
9
14
|
end
|
|
10
|
-
|
|
11
|
-
require_relative 'metrics/collector'
|
|
12
|
-
require_relative 'metrics/exporter'
|
|
13
|
-
require_relative 'metrics/telemetry'
|
|
14
|
-
require_relative 'metrics/telemetry_exporter'
|
|
@@ -75,7 +75,8 @@ module Datadog
|
|
|
75
75
|
|
|
76
76
|
matcher = Core::Remote::Dispatcher::Matcher::Product.new(ASM_PRODUCTS)
|
|
77
77
|
receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
|
|
78
|
-
|
|
78
|
+
engine = AppSec.security_engine
|
|
79
|
+
next unless engine
|
|
79
80
|
|
|
80
81
|
changes.each do |change|
|
|
81
82
|
content = repository[change.path]
|
|
@@ -84,11 +85,10 @@ module Datadog
|
|
|
84
85
|
case change.type
|
|
85
86
|
when :insert, :update
|
|
86
87
|
# @type var content: Core::Remote::Configuration::Content
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
engine.add_or_update_config(parse_content(content), path: change.path.to_s)
|
|
89
89
|
content.applied
|
|
90
90
|
when :delete
|
|
91
|
-
|
|
91
|
+
engine.remove_config_at_path(change.path.to_s)
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
data/lib/datadog/appsec.rb
CHANGED
|
@@ -22,8 +22,14 @@ module Datadog
|
|
|
22
22
|
Datadog::AppSec::Context.active
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# NOTE: This is a temporary workaround for type checking.
|
|
26
|
+
#
|
|
27
|
+
# We want to move from possible nil-component to the disabled-component
|
|
28
|
+
# on an initialization error. Technically, telemetry will be never
|
|
29
|
+
# used if AppSec was not able to initialize, so it's safe to assume
|
|
30
|
+
# that telemetry will never be used and will be nil at the same time.
|
|
25
31
|
def telemetry
|
|
26
|
-
components.appsec&.telemetry
|
|
32
|
+
components.appsec&.telemetry || components.telemetry
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
def security_engine
|
|
@@ -436,6 +436,23 @@ module Datadog
|
|
|
436
436
|
o.default true
|
|
437
437
|
end
|
|
438
438
|
|
|
439
|
+
# The profiler gathers data by sending `SIGPROF` unix signals to Ruby application threads.
|
|
440
|
+
#
|
|
441
|
+
# When using `Kernel#exec` on Linux, it can happen that a signal sent before calling `exec` arrives after
|
|
442
|
+
# the new process is running, causing it to fail with the `Profiling timer expired` error message.
|
|
443
|
+
# To avoid this, the profiler installs a monkey patch on `Kernel#exec` to stop profiling before actually
|
|
444
|
+
# calling `exec`.
|
|
445
|
+
# This monkey patch is available for Ruby 2.7+; let us know if you need it on earlier Rubies.
|
|
446
|
+
# For more details see https://github.com/DataDog/dd-trace-rb/issues/5101 .
|
|
447
|
+
#
|
|
448
|
+
# @default `DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED` environment variable as a boolean,
|
|
449
|
+
# otherwise `true`
|
|
450
|
+
option :shutdown_on_exec_enabled do |o|
|
|
451
|
+
o.env 'DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED'
|
|
452
|
+
o.type :bool
|
|
453
|
+
o.default true
|
|
454
|
+
end
|
|
455
|
+
|
|
439
456
|
# Configures how much wall-time overhead the profiler targets. The profiler will dynamically adjust the
|
|
440
457
|
# interval between samples it takes so as to try and maintain the property that it spends no longer than
|
|
441
458
|
# this amount of wall-clock time profiling. For example, with the default value of 2%, the profiler will
|
|
@@ -81,6 +81,7 @@ module Datadog
|
|
|
81
81
|
"DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED",
|
|
82
82
|
"DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE",
|
|
83
83
|
"DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED",
|
|
84
|
+
"DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED",
|
|
84
85
|
"DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED",
|
|
85
86
|
"DD_PROFILING_SKIP_MYSQL2_CHECK",
|
|
86
87
|
"DD_PROFILING_TIMELINE_ENABLED",
|
|
@@ -14,10 +14,12 @@ module Datadog
|
|
|
14
14
|
# read: lib/datadog/core/telemetry/logging.rb
|
|
15
15
|
module Logger
|
|
16
16
|
class << self
|
|
17
|
+
# (see Datadog::Core::Telemetry::Logging#report)
|
|
17
18
|
def report(exception, level: :error, description: nil)
|
|
18
19
|
instance&.report(exception, level: level, description: description)
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
# (see Datadog::Core::Telemetry::Logging#error)
|
|
21
23
|
def error(description)
|
|
22
24
|
instance&.error(description)
|
|
23
25
|
end
|
|
@@ -45,11 +45,20 @@ module Datadog
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# @param exception [Exception] The exception to report
|
|
49
|
+
# @param level [Symbol] The log level (:error, :warn, :info, :debug)
|
|
50
|
+
# @param description [String, nil] A low cardinality description, without dynamic data
|
|
48
51
|
def report(exception, level: :error, description: nil)
|
|
49
52
|
# Anonymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
|
|
50
|
-
message = +
|
|
53
|
+
message = +(exception.class.name || exception.class.inspect)
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
telemetry_message = message_for_telemetry(exception)
|
|
56
|
+
|
|
57
|
+
if description || telemetry_message
|
|
58
|
+
message << ':'
|
|
59
|
+
message << " #{description}" if description
|
|
60
|
+
message << " (#{telemetry_message})" if telemetry_message
|
|
61
|
+
end
|
|
53
62
|
|
|
54
63
|
event = Event::Log.new(
|
|
55
64
|
message: message,
|
|
@@ -65,6 +74,15 @@ module Datadog
|
|
|
65
74
|
|
|
66
75
|
log!(event)
|
|
67
76
|
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# A constant string representing the exception
|
|
81
|
+
def message_for_telemetry(exception)
|
|
82
|
+
return unless exception.instance_variable_defined?(:@telemetry_message)
|
|
83
|
+
|
|
84
|
+
exception.instance_variable_get(:@telemetry_message)
|
|
85
|
+
end
|
|
68
86
|
end
|
|
69
87
|
end
|
|
70
88
|
end
|
|
@@ -82,6 +82,10 @@ module Datadog
|
|
|
82
82
|
Datadog::Profiling::Ext::DirMonkeyPatches.apply!
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
if can_apply_exec_monkey_patch?(settings)
|
|
86
|
+
Datadog::Profiling::Ext::ExecMonkeyPatch.apply!
|
|
87
|
+
end
|
|
88
|
+
|
|
85
89
|
[profiler, {profiling_enabled: true}]
|
|
86
90
|
end
|
|
87
91
|
|
|
@@ -434,6 +438,15 @@ module Datadog
|
|
|
434
438
|
settings.profiling.advanced.dir_interruption_workaround_enabled
|
|
435
439
|
end
|
|
436
440
|
|
|
441
|
+
private_class_method def self.can_apply_exec_monkey_patch?(settings)
|
|
442
|
+
return false if RUBY_VERSION < "2.7"
|
|
443
|
+
|
|
444
|
+
# This file is 2.7+ only so we only require it here once we've checked the Ruby version
|
|
445
|
+
require "datadog/profiling/ext/exec_monkey_patch"
|
|
446
|
+
|
|
447
|
+
settings.profiling.advanced.shutdown_on_exec_enabled
|
|
448
|
+
end
|
|
449
|
+
|
|
437
450
|
private_class_method def self.enable_gvl_profiling?(settings, logger)
|
|
438
451
|
return false if RUBY_VERSION < "3.2"
|
|
439
452
|
|
|
@@ -70,6 +70,9 @@ module Datadog
|
|
|
70
70
|
|
|
71
71
|
uncompressed_code_provenance = code_provenance_collector.refresh.generate_json if code_provenance_collector
|
|
72
72
|
|
|
73
|
+
process_tags = Datadog.configuration.experimental_propagate_process_tags_enabled ?
|
|
74
|
+
Core::Environment::Process.serialized : ''
|
|
75
|
+
|
|
73
76
|
Flush.new(
|
|
74
77
|
start: start,
|
|
75
78
|
finish: finish,
|
|
@@ -80,6 +83,7 @@ module Datadog
|
|
|
80
83
|
settings: Datadog.configuration,
|
|
81
84
|
profile_seq: sequence_tracker.get_next,
|
|
82
85
|
).to_a,
|
|
86
|
+
process_tags: process_tags,
|
|
83
87
|
internal_metadata: internal_metadata.merge(
|
|
84
88
|
{
|
|
85
89
|
worker_stats: worker_stats,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Profiling
|
|
5
|
+
module Ext
|
|
6
|
+
# The profiler gathers data by sending `SIGPROF` unix signals to Ruby application threads.
|
|
7
|
+
#
|
|
8
|
+
# When using `Kernel#exec` on Linux, it can happen that a signal sent before calling `exec` arrives after
|
|
9
|
+
# the new process is running, causing it to fail with the `Profiling timer expired` error message.
|
|
10
|
+
# To avoid this, the profiler installs a monkey patch on `Kernel#exec` to stop profiling before actually
|
|
11
|
+
# calling `exec`.
|
|
12
|
+
# This monkey patch is available for Ruby 2.7+; let us know if you need it on earlier Rubies.
|
|
13
|
+
# For more details see https://github.com/DataDog/dd-trace-rb/issues/5101 .
|
|
14
|
+
module ExecMonkeyPatch
|
|
15
|
+
def self.apply!
|
|
16
|
+
::Object.prepend(ObjectMonkeyPatch)
|
|
17
|
+
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module ObjectMonkeyPatch
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def exec(...)
|
|
25
|
+
Datadog.send(:components, allow_initialization: false)&.profiler&.shutdown!(report_last_profile: false)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -13,6 +13,7 @@ module Datadog
|
|
|
13
13
|
:code_provenance_file_name,
|
|
14
14
|
:code_provenance_data,
|
|
15
15
|
:tags_as_array,
|
|
16
|
+
:process_tags,
|
|
16
17
|
:internal_metadata_json,
|
|
17
18
|
:info_json
|
|
18
19
|
|
|
@@ -23,6 +24,7 @@ module Datadog
|
|
|
23
24
|
code_provenance_file_name:,
|
|
24
25
|
code_provenance_data:,
|
|
25
26
|
tags_as_array:,
|
|
27
|
+
process_tags:,
|
|
26
28
|
internal_metadata:,
|
|
27
29
|
info_json:
|
|
28
30
|
)
|
|
@@ -32,6 +34,7 @@ module Datadog
|
|
|
32
34
|
@code_provenance_file_name = code_provenance_file_name
|
|
33
35
|
@code_provenance_data = code_provenance_data
|
|
34
36
|
@tags_as_array = tags_as_array
|
|
37
|
+
@process_tags = process_tags
|
|
35
38
|
@internal_metadata_json = JSON.generate(internal_metadata)
|
|
36
39
|
@info_json = info_json
|
|
37
40
|
end
|
|
@@ -31,10 +31,11 @@ module Datadog
|
|
|
31
31
|
scheduler.start(on_failure_proc: proc { component_failed(:scheduler) })
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def shutdown!
|
|
34
|
+
def shutdown!(report_last_profile: true)
|
|
35
35
|
Datadog.logger.debug("Shutting down profiler")
|
|
36
36
|
|
|
37
37
|
stop_worker
|
|
38
|
+
scheduler.disable_reporting unless report_last_profile
|
|
38
39
|
stop_scheduler
|
|
39
40
|
end
|
|
40
41
|
|
|
@@ -57,11 +58,8 @@ module Datadog
|
|
|
57
58
|
Datadog::Core::Telemetry::Logger
|
|
58
59
|
.error("Detected issue with profiler (#{failed_component} component), stopping profiling")
|
|
59
60
|
|
|
60
|
-
# We explicitly not stop the crash tracker in this situation, under the assumption that, if a component failed,
|
|
61
|
-
# we're operating in a degraded state and crash tracking may still be helpful.
|
|
62
|
-
|
|
63
61
|
if failed_component == :worker
|
|
64
|
-
scheduler.
|
|
62
|
+
scheduler.disable_reporting
|
|
65
63
|
stop_scheduler
|
|
66
64
|
elsif failed_component == :scheduler
|
|
67
65
|
stop_worker
|
|
@@ -24,7 +24,7 @@ module Datadog
|
|
|
24
24
|
attr_reader \
|
|
25
25
|
:exporter,
|
|
26
26
|
:transport,
|
|
27
|
-
:
|
|
27
|
+
:reporting_disabled
|
|
28
28
|
|
|
29
29
|
public
|
|
30
30
|
|
|
@@ -36,7 +36,7 @@ module Datadog
|
|
|
36
36
|
)
|
|
37
37
|
@exporter = exporter
|
|
38
38
|
@transport = transport
|
|
39
|
-
@
|
|
39
|
+
@reporting_disabled = false
|
|
40
40
|
@stop_requested = false
|
|
41
41
|
|
|
42
42
|
# Workers::Async::Thread settings
|
|
@@ -83,14 +83,15 @@ module Datadog
|
|
|
83
83
|
true
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
# if there is data to be flushed
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
# Called by the Profiler when it wants to prevent any further reporting,
|
|
87
|
+
# even if there is data to be flushed.
|
|
88
|
+
# Used when the profiler failed or we're otherwise in a hurry to shut down.
|
|
89
|
+
def disable_reporting
|
|
90
|
+
@reporting_disabled = true
|
|
90
91
|
end
|
|
91
92
|
|
|
92
93
|
def work_pending?
|
|
93
|
-
!
|
|
94
|
+
!reporting_disabled && exporter.can_flush? && (run_loop? || !stop_requested?)
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def reset_after_fork
|
|
@@ -50,6 +50,7 @@ module Datadog
|
|
|
50
50
|
FORM_FIELD_TAG_PROFILER_VERSION => profiler_version,
|
|
51
51
|
'profile_seq' => profile_seq.to_s,
|
|
52
52
|
)
|
|
53
|
+
|
|
53
54
|
user_tag_keys = settings.tags.keys
|
|
54
55
|
hash.keep_if { |tag| user_tag_keys.include?(tag) || ALLOWED_TAGS.include?(tag) }
|
|
55
56
|
Core::Utils.encode_tags(hash)
|