ddtrace 1.22.0 → 1.23.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +8 -20
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +18 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +7 -5
- data/ext/datadog_profiling_native_extension/heap_recorder.c +38 -3
- data/ext/datadog_profiling_native_extension/heap_recorder.h +5 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +46 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +156 -55
- data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration.rb +3 -17
- data/lib/datadog/core/telemetry/component.rb +66 -0
- data/lib/datadog/core/telemetry/emitter.rb +1 -1
- data/lib/datadog/core/telemetry/event.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
- data/lib/datadog/core/telemetry/http/response.rb +4 -0
- data/lib/datadog/core/telemetry/worker.rb +158 -0
- data/lib/datadog/core/utils/only_once_successful.rb +76 -0
- data/lib/datadog/profiling/exporter.rb +6 -3
- data/lib/datadog/profiling/stack_recorder.rb +6 -2
- data/lib/ddtrace/version.rb +2 -2
- metadata +18 -7
- data/lib/datadog/core/telemetry/client.rb +0 -95
- data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'event'
|
4
|
+
|
5
|
+
require_relative '../utils/only_once_successful'
|
6
|
+
require_relative '../workers/polling'
|
7
|
+
require_relative '../workers/queue'
|
8
|
+
|
9
|
+
module Datadog
|
10
|
+
module Core
|
11
|
+
module Telemetry
|
12
|
+
# Accumulates events and sends them to the API at a regular interval, including heartbeat event.
|
13
|
+
class Worker
|
14
|
+
include Core::Workers::Queue
|
15
|
+
include Core::Workers::Polling
|
16
|
+
|
17
|
+
DEFAULT_BUFFER_MAX_SIZE = 1000
|
18
|
+
APP_STARTED_EVENT_RETRIES = 10
|
19
|
+
|
20
|
+
TELEMETRY_STARTED_ONCE = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
|
21
|
+
|
22
|
+
def initialize(
|
23
|
+
heartbeat_interval_seconds:,
|
24
|
+
emitter:,
|
25
|
+
dependency_collection:,
|
26
|
+
enabled: true,
|
27
|
+
shutdown_timeout: Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT,
|
28
|
+
buffer_size: DEFAULT_BUFFER_MAX_SIZE
|
29
|
+
)
|
30
|
+
@emitter = emitter
|
31
|
+
@dependency_collection = dependency_collection
|
32
|
+
|
33
|
+
# Workers::Polling settings
|
34
|
+
self.enabled = enabled
|
35
|
+
# Workers::IntervalLoop settings
|
36
|
+
self.loop_base_interval = heartbeat_interval_seconds
|
37
|
+
self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
|
38
|
+
|
39
|
+
@shutdown_timeout = shutdown_timeout
|
40
|
+
@buffer_size = buffer_size
|
41
|
+
|
42
|
+
self.buffer = buffer_klass.new(@buffer_size)
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
return if !enabled? || forked?
|
47
|
+
|
48
|
+
# starts async worker
|
49
|
+
perform
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop(force_stop = false, timeout = @shutdown_timeout)
|
53
|
+
buffer.close if running?
|
54
|
+
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def enqueue(event)
|
59
|
+
return if !enabled? || forked?
|
60
|
+
|
61
|
+
buffer.push(event)
|
62
|
+
end
|
63
|
+
|
64
|
+
def sent_started_event?
|
65
|
+
TELEMETRY_STARTED_ONCE.success?
|
66
|
+
end
|
67
|
+
|
68
|
+
def failed_to_start?
|
69
|
+
TELEMETRY_STARTED_ONCE.failed?
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def perform(*events)
|
75
|
+
return if !enabled? || forked?
|
76
|
+
|
77
|
+
started! unless sent_started_event?
|
78
|
+
|
79
|
+
heartbeat!
|
80
|
+
|
81
|
+
flush_events(events)
|
82
|
+
end
|
83
|
+
|
84
|
+
def flush_events(events)
|
85
|
+
return if events.nil?
|
86
|
+
return if !enabled? || !sent_started_event?
|
87
|
+
|
88
|
+
Datadog.logger.debug { "Sending #{events.count} telemetry events" }
|
89
|
+
events.each do |event|
|
90
|
+
send_event(event)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def heartbeat!
|
95
|
+
return if !enabled? || !sent_started_event?
|
96
|
+
|
97
|
+
send_event(Event::AppHeartbeat.new)
|
98
|
+
end
|
99
|
+
|
100
|
+
def started!
|
101
|
+
return unless enabled?
|
102
|
+
|
103
|
+
if failed_to_start?
|
104
|
+
Datadog.logger.debug('Telemetry app-started event exhausted retries, disabling telemetry worker')
|
105
|
+
self.enabled = false
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
TELEMETRY_STARTED_ONCE.run do
|
110
|
+
res = send_event(Event::AppStarted.new)
|
111
|
+
|
112
|
+
if res.ok?
|
113
|
+
Datadog.logger.debug('Telemetry app-started event is successfully sent')
|
114
|
+
|
115
|
+
send_event(Event::AppDependenciesLoaded.new) if @dependency_collection
|
116
|
+
|
117
|
+
true
|
118
|
+
else
|
119
|
+
Datadog.logger.debug('Error sending telemetry app-started event, retry after heartbeat interval...')
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def send_event(event)
|
126
|
+
res = @emitter.request(event)
|
127
|
+
|
128
|
+
disable_on_not_found!(res)
|
129
|
+
|
130
|
+
res
|
131
|
+
end
|
132
|
+
|
133
|
+
def dequeue
|
134
|
+
buffer.pop
|
135
|
+
end
|
136
|
+
|
137
|
+
def work_pending?
|
138
|
+
run_loop? || !buffer.empty?
|
139
|
+
end
|
140
|
+
|
141
|
+
def buffer_klass
|
142
|
+
if Core::Environment::Ext::RUBY_ENGINE == 'ruby'
|
143
|
+
Core::Buffer::CRuby
|
144
|
+
else
|
145
|
+
Core::Buffer::ThreadSafe
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def disable_on_not_found!(response)
|
150
|
+
return unless response.not_found?
|
151
|
+
|
152
|
+
Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
|
153
|
+
self.enabled = false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'only_once'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module Core
|
7
|
+
module Utils
|
8
|
+
# Helper class to execute something with only one success.
|
9
|
+
#
|
10
|
+
# This is useful for cases where we want to ensure that a block of code is only executed once, and only if it
|
11
|
+
# succeeds. One such example is sending app-started telemetry event.
|
12
|
+
#
|
13
|
+
# Successful execution is determined by the return value of the block: any truthy value is considered success.
|
14
|
+
#
|
15
|
+
# Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class).
|
16
|
+
#
|
17
|
+
# Note: In its current state, this class is not Ractor-safe.
|
18
|
+
# In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives,
|
19
|
+
# including an alternative implementation that is Ractor-safe once spent.
|
20
|
+
class OnlyOnceSuccessful < OnlyOnce
|
21
|
+
def initialize(limit = 0)
|
22
|
+
super()
|
23
|
+
|
24
|
+
@limit = limit
|
25
|
+
@failed = false
|
26
|
+
@retries = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
@mutex.synchronize do
|
31
|
+
return if @ran_once
|
32
|
+
|
33
|
+
result = yield
|
34
|
+
@ran_once = !!result
|
35
|
+
|
36
|
+
if !@ran_once && limited?
|
37
|
+
@retries += 1
|
38
|
+
check_limit!
|
39
|
+
end
|
40
|
+
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def success?
|
46
|
+
@mutex.synchronize { @ran_once && !@failed }
|
47
|
+
end
|
48
|
+
|
49
|
+
def failed?
|
50
|
+
@mutex.synchronize { @ran_once && @failed }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def check_limit!
|
56
|
+
if @retries >= @limit
|
57
|
+
@failed = true
|
58
|
+
@ran_once = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def limited?
|
63
|
+
!@limit.nil? && @limit > 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_ran_once_state_for_tests
|
67
|
+
@mutex.synchronize do
|
68
|
+
@ran_once = false
|
69
|
+
@failed = false
|
70
|
+
@retries = 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -52,10 +52,11 @@ module Datadog
|
|
52
52
|
|
53
53
|
def flush
|
54
54
|
worker_stats = @worker.stats_and_reset_not_thread_safe
|
55
|
-
|
56
|
-
|
55
|
+
serialization_result = pprof_recorder.serialize
|
56
|
+
return if serialization_result.nil?
|
57
57
|
|
58
|
-
|
58
|
+
start, finish, compressed_pprof, profile_stats = serialization_result
|
59
|
+
@last_flush_finish_at = finish
|
59
60
|
|
60
61
|
if duration_below_threshold?(start, finish)
|
61
62
|
Datadog.logger.debug('Skipped exporting profiling events as profile duration is below minimum')
|
@@ -75,6 +76,8 @@ module Datadog
|
|
75
76
|
internal_metadata: internal_metadata.merge(
|
76
77
|
{
|
77
78
|
worker_stats: worker_stats,
|
79
|
+
profile_stats: profile_stats,
|
80
|
+
recorder_stats: pprof_recorder.stats,
|
78
81
|
gc: GC.stat,
|
79
82
|
}
|
80
83
|
),
|
@@ -31,11 +31,11 @@ module Datadog
|
|
31
31
|
status, result = @no_concurrent_synchronize_mutex.synchronize { self.class._native_serialize(self) }
|
32
32
|
|
33
33
|
if status == :ok
|
34
|
-
start, finish, encoded_pprof = result
|
34
|
+
start, finish, encoded_pprof, profile_stats = result
|
35
35
|
|
36
36
|
Datadog.logger.debug { "Encoded profile covering #{start.iso8601} to #{finish.iso8601}" }
|
37
37
|
|
38
|
-
[start, finish, encoded_pprof]
|
38
|
+
[start, finish, encoded_pprof, profile_stats]
|
39
39
|
else
|
40
40
|
error_message = result
|
41
41
|
|
@@ -62,6 +62,10 @@ module Datadog
|
|
62
62
|
def reset_after_fork
|
63
63
|
self.class._native_reset_after_fork(self)
|
64
64
|
end
|
65
|
+
|
66
|
+
def stats
|
67
|
+
self.class._native_stats(self)
|
68
|
+
end
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
data/lib/ddtrace/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ddtrace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.23.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -81,6 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.8.1
|
83
83
|
description: |
|
84
|
+
NOTICE: This gem has been renamed to `datadog` since 2.0.0. Please use `datadog` instead of `ddtrace`.
|
84
85
|
ddtrace is Datadog's tracing client for Ruby. It is used to trace requests
|
85
86
|
as they flow across web servers, databases and microservices so that developers
|
86
87
|
have great visiblity into bottlenecks and troublesome requests.
|
@@ -295,17 +296,17 @@ files:
|
|
295
296
|
- lib/datadog/core/remote/worker.rb
|
296
297
|
- lib/datadog/core/runtime/ext.rb
|
297
298
|
- lib/datadog/core/runtime/metrics.rb
|
298
|
-
- lib/datadog/core/telemetry/
|
299
|
+
- lib/datadog/core/telemetry/component.rb
|
299
300
|
- lib/datadog/core/telemetry/emitter.rb
|
300
301
|
- lib/datadog/core/telemetry/event.rb
|
301
302
|
- lib/datadog/core/telemetry/ext.rb
|
302
|
-
- lib/datadog/core/telemetry/heartbeat.rb
|
303
303
|
- lib/datadog/core/telemetry/http/adapters/net.rb
|
304
304
|
- lib/datadog/core/telemetry/http/env.rb
|
305
305
|
- lib/datadog/core/telemetry/http/ext.rb
|
306
306
|
- lib/datadog/core/telemetry/http/response.rb
|
307
307
|
- lib/datadog/core/telemetry/http/transport.rb
|
308
308
|
- lib/datadog/core/telemetry/request.rb
|
309
|
+
- lib/datadog/core/telemetry/worker.rb
|
309
310
|
- lib/datadog/core/transport/ext.rb
|
310
311
|
- lib/datadog/core/transport/http/adapters/net.rb
|
311
312
|
- lib/datadog/core/transport/http/adapters/registry.rb
|
@@ -326,6 +327,7 @@ files:
|
|
326
327
|
- lib/datadog/core/utils/hash.rb
|
327
328
|
- lib/datadog/core/utils/network.rb
|
328
329
|
- lib/datadog/core/utils/only_once.rb
|
330
|
+
- lib/datadog/core/utils/only_once_successful.rb
|
329
331
|
- lib/datadog/core/utils/safe_dup.rb
|
330
332
|
- lib/datadog/core/utils/sequence.rb
|
331
333
|
- lib/datadog/core/utils/time.rb
|
@@ -876,10 +878,19 @@ files:
|
|
876
878
|
homepage: https://github.com/DataDog/dd-trace-rb
|
877
879
|
licenses:
|
878
880
|
- BSD-3-Clause
|
881
|
+
- Apache-2.0
|
879
882
|
metadata:
|
880
883
|
allowed_push_host: https://rubygems.org
|
881
|
-
changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/
|
882
|
-
|
884
|
+
changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v1.23.3/CHANGELOG.md
|
885
|
+
source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v1.23.3
|
886
|
+
post_install_message: |2
|
887
|
+
Thank you for installing ddtrace. We have released our next major version!
|
888
|
+
|
889
|
+
As of version 2, `ddtrace` gem has been renamed to `datadog`.
|
890
|
+
The 1.x series will now only receive maintenance updates for security and critical bug fixes.
|
891
|
+
|
892
|
+
To upgrade, please replace gem `ddtrace` with gem `datadog`.
|
893
|
+
For detailed instructions on migration, see: https://dtdg.co/ruby-v2-upgrade
|
883
894
|
rdoc_options: []
|
884
895
|
require_paths:
|
885
896
|
- lib
|
@@ -897,7 +908,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
897
908
|
- !ruby/object:Gem::Version
|
898
909
|
version: 2.0.0
|
899
910
|
requirements: []
|
900
|
-
rubygems_version: 3.
|
911
|
+
rubygems_version: 3.4.21
|
901
912
|
signing_key:
|
902
913
|
specification_version: 4
|
903
914
|
summary: Datadog tracing code for your Ruby applications
|
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'emitter'
|
4
|
-
require_relative 'event'
|
5
|
-
require_relative 'heartbeat'
|
6
|
-
require_relative '../utils/forking'
|
7
|
-
|
8
|
-
module Datadog
|
9
|
-
module Core
|
10
|
-
module Telemetry
|
11
|
-
# Telemetry entrypoint, coordinates sending telemetry events at various points in app lifecycle.
|
12
|
-
class Client
|
13
|
-
attr_reader \
|
14
|
-
:enabled,
|
15
|
-
:unsupported
|
16
|
-
|
17
|
-
include Core::Utils::Forking
|
18
|
-
|
19
|
-
# @param enabled [Boolean] Determines whether telemetry events should be sent to the API
|
20
|
-
# @param heartbeat_interval_seconds [Float] How frequently heartbeats will be reported, in seconds.
|
21
|
-
# @param [Boolean] dependency_collection Whether to send the `app-dependencies-loaded` event
|
22
|
-
def initialize(heartbeat_interval_seconds:, dependency_collection:, enabled: true)
|
23
|
-
@enabled = enabled
|
24
|
-
@emitter = Emitter.new
|
25
|
-
@stopped = false
|
26
|
-
@unsupported = false
|
27
|
-
@started = false
|
28
|
-
@dependency_collection = dependency_collection
|
29
|
-
|
30
|
-
@worker = Telemetry::Heartbeat.new(enabled: @enabled, heartbeat_interval_seconds: heartbeat_interval_seconds) do
|
31
|
-
next unless @started # `started!` should be the first event, thus ensure that `heartbeat!` is not sent first.
|
32
|
-
|
33
|
-
heartbeat!
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def disable!
|
38
|
-
@enabled = false
|
39
|
-
@worker.enabled = false
|
40
|
-
end
|
41
|
-
|
42
|
-
def started!
|
43
|
-
return if !@enabled || forked?
|
44
|
-
|
45
|
-
res = @emitter.request(Event::AppStarted.new)
|
46
|
-
|
47
|
-
if res.not_found? # Telemetry is only supported by agent versions 7.34 and up
|
48
|
-
Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
|
49
|
-
disable!
|
50
|
-
@unsupported = true # Prevent telemetry from getting re-enabled
|
51
|
-
return res
|
52
|
-
end
|
53
|
-
|
54
|
-
@emitter.request(Event::AppDependenciesLoaded.new) if @dependency_collection
|
55
|
-
|
56
|
-
@started = true
|
57
|
-
end
|
58
|
-
|
59
|
-
def emit_closing!
|
60
|
-
return if !@enabled || forked?
|
61
|
-
|
62
|
-
@emitter.request(Event::AppClosing.new)
|
63
|
-
end
|
64
|
-
|
65
|
-
def stop!
|
66
|
-
return if @stopped
|
67
|
-
|
68
|
-
@worker.stop(true, 0)
|
69
|
-
@stopped = true
|
70
|
-
end
|
71
|
-
|
72
|
-
def integrations_change!
|
73
|
-
return if !@enabled || forked?
|
74
|
-
|
75
|
-
@emitter.request(Event::AppIntegrationsChange.new)
|
76
|
-
end
|
77
|
-
|
78
|
-
# Report configuration changes caused by Remote Configuration.
|
79
|
-
def client_configuration_change!(changes)
|
80
|
-
return if !@enabled || forked?
|
81
|
-
|
82
|
-
@emitter.request(Event::AppClientConfigurationChange.new(changes, 'remote_config'))
|
83
|
-
end
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
def heartbeat!
|
88
|
-
return if !@enabled || forked?
|
89
|
-
|
90
|
-
@emitter.request(Event::AppHeartbeat.new)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../worker'
|
4
|
-
require_relative '../workers/polling'
|
5
|
-
|
6
|
-
module Datadog
|
7
|
-
module Core
|
8
|
-
module Telemetry
|
9
|
-
# Periodically (every DEFAULT_INTERVAL_SECONDS) sends a heartbeat event to the telemetry API.
|
10
|
-
class Heartbeat < Core::Worker
|
11
|
-
include Core::Workers::Polling
|
12
|
-
|
13
|
-
def initialize(heartbeat_interval_seconds:, enabled: true, &block)
|
14
|
-
# Workers::Polling settings
|
15
|
-
self.enabled = enabled
|
16
|
-
# Workers::IntervalLoop settings
|
17
|
-
self.loop_base_interval = heartbeat_interval_seconds
|
18
|
-
self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
|
19
|
-
super(&block)
|
20
|
-
start
|
21
|
-
end
|
22
|
-
|
23
|
-
def loop_wait_before_first_iteration?; end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def start
|
28
|
-
perform
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|