ddtrace 0.52.0 → 0.54.2

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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -11
  3. data/ddtrace.gemspec +6 -3
  4. data/docs/DevelopmentGuide.md +1 -6
  5. data/docs/GettingStarted.md +109 -18
  6. data/docs/ProfilingDevelopment.md +2 -2
  7. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +86 -0
  8. data/ext/ddtrace_profiling_native_extension/clock_id.h +4 -0
  9. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +52 -0
  10. data/ext/ddtrace_profiling_native_extension/clock_id_noop.c +14 -0
  11. data/ext/ddtrace_profiling_native_extension/extconf.rb +177 -8
  12. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +35 -0
  13. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
  14. data/ext/ddtrace_profiling_native_extension/profiling.c +6 -1
  15. data/lib/datadog/ci/contrib/cucumber/formatter.rb +1 -0
  16. data/lib/datadog/ci/contrib/rspec/example.rb +1 -0
  17. data/lib/datadog/ci/contrib/rspec/integration.rb +2 -2
  18. data/lib/datadog/ci/ext/environment.rb +64 -22
  19. data/lib/datadog/ci/ext/test.rb +1 -0
  20. data/lib/datadog/ci/test.rb +5 -1
  21. data/lib/datadog/contrib.rb +2 -0
  22. data/lib/datadog/core/environment/vm_cache.rb +46 -0
  23. data/lib/ddtrace/buffer.rb +28 -16
  24. data/lib/ddtrace/configuration/agent_settings_resolver.rb +131 -53
  25. data/lib/ddtrace/configuration/components.rb +1 -1
  26. data/lib/ddtrace/configuration/settings.rb +13 -3
  27. data/lib/ddtrace/context.rb +10 -2
  28. data/lib/ddtrace/contrib/action_cable/instrumentation.rb +46 -0
  29. data/lib/ddtrace/contrib/action_cable/patcher.rb +1 -0
  30. data/lib/ddtrace/contrib/action_mailer/configuration/settings.rb +32 -0
  31. data/lib/ddtrace/contrib/action_mailer/event.rb +50 -0
  32. data/lib/ddtrace/contrib/action_mailer/events/deliver.rb +54 -0
  33. data/lib/ddtrace/contrib/action_mailer/events/process.rb +41 -0
  34. data/lib/ddtrace/contrib/action_mailer/events.rb +31 -0
  35. data/lib/ddtrace/contrib/action_mailer/ext.rb +32 -0
  36. data/lib/ddtrace/contrib/action_mailer/integration.rb +45 -0
  37. data/lib/ddtrace/contrib/action_mailer/patcher.rb +27 -0
  38. data/lib/ddtrace/contrib/active_job/configuration/settings.rb +33 -0
  39. data/lib/ddtrace/contrib/active_job/event.rb +54 -0
  40. data/lib/ddtrace/contrib/active_job/events/discard.rb +46 -0
  41. data/lib/ddtrace/contrib/active_job/events/enqueue.rb +45 -0
  42. data/lib/ddtrace/contrib/active_job/events/enqueue_at.rb +45 -0
  43. data/lib/ddtrace/contrib/active_job/events/enqueue_retry.rb +47 -0
  44. data/lib/ddtrace/contrib/active_job/events/perform.rb +45 -0
  45. data/lib/ddtrace/contrib/active_job/events/retry_stopped.rb +46 -0
  46. data/lib/ddtrace/contrib/active_job/events.rb +39 -0
  47. data/lib/ddtrace/contrib/active_job/ext.rb +32 -0
  48. data/lib/ddtrace/contrib/active_job/integration.rb +46 -0
  49. data/lib/ddtrace/contrib/active_job/log_injection.rb +21 -0
  50. data/lib/ddtrace/contrib/active_job/patcher.rb +33 -0
  51. data/lib/ddtrace/contrib/auto_instrument.rb +0 -1
  52. data/lib/ddtrace/contrib/delayed_job/plugin.rb +2 -2
  53. data/lib/ddtrace/contrib/mongodb/instrumentation.rb +1 -1
  54. data/lib/ddtrace/contrib/mongodb/integration.rb +5 -0
  55. data/lib/ddtrace/contrib/rails/auto_instrument_railtie.rb +0 -1
  56. data/lib/ddtrace/contrib/rails/configuration/settings.rb +7 -0
  57. data/lib/ddtrace/contrib/rails/framework.rb +24 -1
  58. data/lib/ddtrace/contrib/rails/patcher.rb +19 -10
  59. data/lib/ddtrace/contrib/redis/instrumentation.rb +90 -0
  60. data/lib/ddtrace/contrib/redis/patcher.rb +2 -84
  61. data/lib/ddtrace/contrib/registerable.rb +0 -1
  62. data/lib/ddtrace/contrib/resque/integration.rb +1 -5
  63. data/lib/ddtrace/contrib/sidekiq/ext.rb +3 -0
  64. data/lib/ddtrace/contrib/sidekiq/integration.rb +10 -0
  65. data/lib/ddtrace/contrib/sidekiq/patcher.rb +26 -0
  66. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/heartbeat.rb +30 -0
  67. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/job_fetch.rb +30 -0
  68. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/scheduled_push.rb +29 -0
  69. data/lib/ddtrace/contrib/sinatra/env.rb +2 -1
  70. data/lib/ddtrace/contrib/sinatra/tracer.rb +15 -2
  71. data/lib/ddtrace/ext/git.rb +12 -0
  72. data/lib/ddtrace/ext/priority.rb +6 -4
  73. data/lib/ddtrace/ext/profiling.rb +8 -11
  74. data/lib/ddtrace/ext/runtime.rb +3 -0
  75. data/lib/ddtrace/ext/transport.rb +11 -0
  76. data/lib/ddtrace/metrics.rb +2 -2
  77. data/lib/ddtrace/profiling/collectors/stack.rb +112 -72
  78. data/lib/ddtrace/profiling/encoding/profile.rb +10 -2
  79. data/lib/ddtrace/profiling/events/stack.rb +13 -13
  80. data/lib/ddtrace/profiling/native_extension.rb +23 -1
  81. data/lib/ddtrace/profiling/pprof/builder.rb +8 -2
  82. data/lib/ddtrace/profiling/pprof/converter.rb +22 -9
  83. data/lib/ddtrace/profiling/pprof/stack_sample.rb +32 -9
  84. data/lib/ddtrace/profiling/pprof/template.rb +2 -2
  85. data/lib/ddtrace/profiling/scheduler.rb +20 -4
  86. data/lib/ddtrace/profiling/tasks/setup.rb +21 -13
  87. data/lib/ddtrace/profiling/trace_identifiers/ddtrace.rb +10 -9
  88. data/lib/ddtrace/profiling/trace_identifiers/helper.rb +5 -5
  89. data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +8 -15
  90. data/lib/ddtrace/profiling/transport/http.rb +8 -17
  91. data/lib/ddtrace/profiling.rb +0 -2
  92. data/lib/ddtrace/runtime/metrics.rb +14 -0
  93. data/lib/ddtrace/sampler.rb +18 -8
  94. data/lib/ddtrace/sampling/rule_sampler.rb +13 -1
  95. data/lib/ddtrace/span.rb +7 -19
  96. data/lib/ddtrace/tracer.rb +1 -1
  97. data/lib/ddtrace/transport/http/adapters/net.rb +13 -3
  98. data/lib/ddtrace/transport/http/adapters/test.rb +4 -2
  99. data/lib/ddtrace/transport/http/adapters/unix_socket.rb +23 -12
  100. data/lib/ddtrace/transport/http/builder.rb +13 -6
  101. data/lib/ddtrace/transport/http.rb +5 -11
  102. data/lib/ddtrace/utils/time.rb +11 -6
  103. data/lib/ddtrace/version.rb +2 -2
  104. data/lib/ddtrace/workers/{loop.rb → interval_loop.rb} +0 -16
  105. data/lib/ddtrace/workers/polling.rb +1 -1
  106. metadata +40 -10
  107. data/lib/ddtrace/profiling/ext/cpu.rb +0 -67
  108. data/lib/ddtrace/profiling/ext/cthread.rb +0 -156
@@ -30,8 +30,9 @@ module Datadog
30
30
  def initialize(*_)
31
31
  super
32
32
 
33
+ @most_recent_trace_samples = {}
33
34
  @processed_unique_stacks = 0
34
- @processed_with_trace_ids = 0
35
+ @processed_with_trace = 0
35
36
  end
36
37
 
37
38
  def add_events!(stack_samples)
@@ -40,9 +41,28 @@ module Datadog
40
41
  end
41
42
 
42
43
  def stack_sample_group_key(stack_sample)
44
+ # We want to make sure we have the most recent sample for any trace.
45
+ # (This is done here to save an iteration over all samples.)
46
+ update_most_recent_trace_sample(stack_sample)
47
+
43
48
  stack_sample.hash
44
49
  end
45
50
 
51
+ # Track the most recent sample for each trace (identified by root span id)
52
+ def update_most_recent_trace_sample(stack_sample)
53
+ return unless stack_sample.root_span_id && stack_sample.trace_resource
54
+
55
+ # Update trace resource with most recent value
56
+ if (most_recent_trace_sample = @most_recent_trace_samples[stack_sample.root_span_id])
57
+ if most_recent_trace_sample.timestamp < stack_sample.timestamp
58
+ @most_recent_trace_samples[stack_sample.root_span_id] = stack_sample
59
+ end
60
+ else
61
+ # Add trace resource
62
+ @most_recent_trace_samples[stack_sample.root_span_id] = stack_sample
63
+ end
64
+ end
65
+
46
66
  def build_samples(stack_samples)
47
67
  groups = group_events(stack_samples, &method(:stack_sample_group_key))
48
68
  groups.collect do |_group_key, group|
@@ -64,7 +84,7 @@ module Datadog
64
84
  )
65
85
  end
66
86
 
67
- def build_sample_values(stack_sample)
87
+ def build_event_values(stack_sample)
68
88
  no_value = Datadog::Ext::Profiling::Pprof::SAMPLE_VALUE_NO_VALUE
69
89
  values = super(stack_sample)
70
90
  values[sample_value_index(:cpu_time_ns)] = stack_sample.cpu_time_interval_ns || no_value
@@ -80,15 +100,15 @@ module Datadog
80
100
  )
81
101
  ]
82
102
 
83
- trace_id = stack_sample.trace_id || 0
103
+ root_span_id = stack_sample.root_span_id || 0
84
104
  span_id = stack_sample.span_id || 0
85
105
 
86
- if trace_id != 0 && span_id != 0
87
- @processed_with_trace_ids += 1
106
+ if root_span_id != 0 && span_id != 0
107
+ @processed_with_trace += 1
88
108
 
89
109
  labels << Perftools::Profiles::Label.new(
90
- key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_TRACE_ID),
91
- str: builder.string_table.fetch(trace_id.to_s)
110
+ key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_LOCAL_ROOT_SPAN_ID),
111
+ str: builder.string_table.fetch(root_span_id.to_s)
92
112
  )
93
113
 
94
114
  labels << Perftools::Profiles::Label.new(
@@ -96,7 +116,10 @@ module Datadog
96
116
  str: builder.string_table.fetch(span_id.to_s)
97
117
  )
98
118
 
99
- trace_resource = stack_sample.trace_resource_container && stack_sample.trace_resource_container.latest
119
+ # Use most up-to-date trace resource, if available.
120
+ # Otherwise, use the trace resource provided.
121
+ trace_resource = @most_recent_trace_samples.fetch(stack_sample.root_span_id, stack_sample).trace_resource
122
+
100
123
  if trace_resource && !trace_resource.empty?
101
124
  labels << Perftools::Profiles::Label.new(
102
125
  key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_TRACE_ENDPOINT),
@@ -109,7 +132,7 @@ module Datadog
109
132
  end
110
133
 
111
134
  def debug_statistics
112
- "unique stacks: #{@processed_unique_stacks}, of which had active traces: #{@processed_with_trace_ids}"
135
+ "unique stacks: #{@processed_unique_stacks}, of which had active traces: #{@processed_with_trace}"
113
136
  end
114
137
  end
115
138
  end
@@ -80,8 +80,8 @@ module Datadog
80
80
  converters.values.map(&:debug_statistics).join(', ')
81
81
  end
82
82
 
83
- def to_pprof
84
- profile = builder.build_profile
83
+ def to_pprof(start:, finish:)
84
+ profile = builder.build_profile(start: start, finish: finish)
85
85
  data = builder.encode_profile(profile)
86
86
  types = sample_type_mappings.keys
87
87
 
@@ -17,6 +17,10 @@ module Datadog
17
17
  # Profiles with duration less than this will not be reported
18
18
  PROFILE_DURATION_THRESHOLD_SECONDS = 1
19
19
 
20
+ # We sleep for at most this duration seconds before reporting data to avoid multi-process applications all
21
+ # reporting profiles at the exact same time
22
+ DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS = 3
23
+
20
24
  private_constant :DEFAULT_INTERVAL_SECONDS, :MINIMUM_INTERVAL_SECONDS, :PROFILE_DURATION_THRESHOLD_SECONDS
21
25
 
22
26
  attr_reader \
@@ -62,10 +66,6 @@ module Datadog
62
66
  end
63
67
  end
64
68
 
65
- def loop_back_off?
66
- false
67
- end
68
-
69
69
  def after_fork
70
70
  # Clear recorder's buffers by flushing events.
71
71
  # Objects from parent process will copy-on-write,
@@ -110,6 +110,22 @@ module Datadog
110
110
  return flush
111
111
  end
112
112
 
113
+ # Sleep for a bit to cause misalignment between profilers in multi-process applications
114
+ #
115
+ # When not being run in a loop, it means the scheduler has not been started or was stopped, and thus
116
+ # a) it's being shutting down (and is trying to report the last profile)
117
+ # b) it's being run as a one-shot, usually in a test
118
+ # ...so in those cases we don't sleep
119
+ #
120
+ # During PR review (https://github.com/DataDog/dd-trace-rb/pull/1807) we discussed the possible alternative of
121
+ # just sleeping before starting the scheduler loop. We ended up not going with that option to avoid the first
122
+ # profile containing up to DEFAULT_INTERVAL_SECONDS + DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS instead of the
123
+ # usual DEFAULT_INTERVAL_SECONDS size.
124
+ if run_loop?
125
+ jitter_seconds = rand * DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS # floating point number between (0.0...maximum)
126
+ sleep(jitter_seconds)
127
+ end
128
+
113
129
  # Send events to each exporter
114
130
  if flush.event_count > 0
115
131
  exporters.each do |exporter|
@@ -1,22 +1,20 @@
1
1
  # typed: false
2
- require 'ddtrace'
3
2
  require 'ddtrace/utils/only_once'
4
3
  require 'ddtrace/profiling'
5
- require 'ddtrace/profiling/ext/cpu'
6
4
  require 'ddtrace/profiling/ext/forking'
7
5
 
8
6
  module Datadog
9
7
  module Profiling
10
8
  module Tasks
11
- # Takes care of loading our extensions/monkey patches to handle fork() and CPU profiling.
9
+ # Takes care of loading our extensions/monkey patches to handle fork() and validating if CPU-time profiling is usable
12
10
  class Setup
13
11
  ACTIVATE_EXTENSIONS_ONLY_ONCE = Datadog::Utils::OnlyOnce.new
14
12
 
15
13
  def run
16
14
  ACTIVATE_EXTENSIONS_ONLY_ONCE.run do
17
15
  begin
16
+ check_if_cpu_time_profiling_is_supported
18
17
  activate_forking_extensions
19
- activate_cpu_extensions
20
18
  setup_at_fork_hooks
21
19
  rescue StandardError, ScriptError => e
22
20
  Datadog.logger.warn do
@@ -40,19 +38,15 @@ module Datadog
40
38
  end
41
39
  end
42
40
 
43
- def activate_cpu_extensions
44
- if Ext::CPU.supported?
45
- Ext::CPU.apply!
46
- elsif Datadog.configuration.profiling.enabled
41
+ def check_if_cpu_time_profiling_is_supported
42
+ unsupported = cpu_time_profiling_unsupported_reason
43
+
44
+ if unsupported
47
45
  Datadog.logger.info do
48
46
  'CPU time profiling skipped because native CPU time is not supported: ' \
49
- "#{Ext::CPU.unsupported_reason}. Profiles containing Wall time will still be reported."
47
+ "#{unsupported}. Profiles containing 'Wall time' data will still be reported."
50
48
  end
51
49
  end
52
- rescue StandardError, ScriptError => e
53
- Datadog.logger.warn do
54
- "Profiler CPU profiling extensions unavailable. Cause: #{e.message} Location: #{Array(e.backtrace).first}"
55
- end
56
50
  end
57
51
 
58
52
  def setup_at_fork_hooks
@@ -76,6 +70,20 @@ module Datadog
76
70
  end
77
71
  end
78
72
  end
73
+
74
+ def cpu_time_profiling_unsupported_reason
75
+ # NOTE: Only the first matching reason is returned, so try to keep a nice order on reasons
76
+
77
+ if RUBY_ENGINE == 'jruby'
78
+ 'JRuby is not supported'
79
+ elsif RUBY_PLATFORM.include?('darwin')
80
+ 'Feature requires Linux; macOS is not supported'
81
+ elsif RUBY_PLATFORM =~ /(mswin|mingw)/
82
+ 'Feature requires Linux; Windows is not supported'
83
+ elsif !RUBY_PLATFORM.include?('linux')
84
+ "Feature requires Linux; #{RUBY_PLATFORM} is not supported"
85
+ end
86
+ end
79
87
  end
80
88
  end
81
89
  end
@@ -6,10 +6,10 @@ require 'ddtrace/ext/http'
6
6
  module Datadog
7
7
  module Profiling
8
8
  module TraceIdentifiers
9
- # Used by Datadog::Profiling::TraceIdentifiers::Helper to get the trace identifiers (trace id and span id) for a
10
- # given thread, if there is an active trace for that thread in Datadog.tracer.
9
+ # Used by Datadog::Profiling::TraceIdentifiers::Helper to get the trace identifiers (root span id and span id)
10
+ # for a given thread, if there is an active trace for that thread in the supplied tracer object.
11
11
  class Ddtrace
12
- def initialize(tracer: nil)
12
+ def initialize(tracer:)
13
13
  @tracer = (tracer if tracer.respond_to?(:call_context))
14
14
  end
15
15
 
@@ -19,10 +19,13 @@ module Datadog
19
19
  context = @tracer.call_context(thread)
20
20
  return unless context
21
21
 
22
- trace_id = context.trace_id || 0
23
- span_id = context.span_id || 0
22
+ span, root_span = context.current_span_and_root_span
23
+ return unless span && root_span
24
24
 
25
- [trace_id, span_id, maybe_extract_resource(context.current_root_span)] if trace_id != 0 && span_id != 0
25
+ root_span_id = root_span.span_id || 0
26
+ span_id = span.span_id || 0
27
+
28
+ [root_span_id, span_id, maybe_extract_resource(root_span)] if root_span_id != 0 && span_id != 0
26
29
  end
27
30
 
28
31
  private
@@ -31,9 +34,7 @@ module Datadog
31
34
  # Resources MUST NOT include personal identifiable information (PII); this should not be the case with
32
35
  # ddtrace integrations, but worth mentioning just in case :)
33
36
  def maybe_extract_resource(root_span)
34
- return unless root_span
35
-
36
- root_span.resource_container if root_span.span_type == Datadog::Ext::HTTP::TYPE_INBOUND
37
+ root_span.resource if root_span.span_type == Datadog::Ext::HTTP::TYPE_INBOUND
37
38
  end
38
39
  end
39
40
  end
@@ -6,7 +6,7 @@ require 'ddtrace/profiling/trace_identifiers/ddtrace'
6
6
  module Datadog
7
7
  module Profiling
8
8
  module TraceIdentifiers
9
- # Helper used to retrieve the trace identifiers (trace id and span id) for a given thread,
9
+ # Helper used to retrieve the trace identifiers (root span id and span id) for a given thread,
10
10
  # if there is an active trace for that thread for the supported tracing APIs.
11
11
  #
12
12
  # This data is used to connect profiles to the traces -- samples in a profile will be tagged with this data and
@@ -20,21 +20,21 @@ module Datadog
20
20
  def initialize(
21
21
  tracer:,
22
22
  # If this is disabled, the helper will strip the optional trace_resource_container even if provided by the api
23
- extract_trace_resource:,
23
+ endpoint_collection_enabled:,
24
24
  supported_apis: DEFAULT_SUPPORTED_APIS.map { |api| api.new(tracer: tracer) }
25
25
  )
26
- @extract_trace_resource = extract_trace_resource
26
+ @endpoint_collection_enabled = endpoint_collection_enabled
27
27
  @supported_apis = supported_apis
28
28
  end
29
29
 
30
30
  # Expected output of the #trace_identifiers_for
31
- # duck type is [trace_id, span_id, (optional trace_resource_container)]
31
+ # duck type is [root_span_id, span_id, (optional trace_resource_container)]
32
32
  def trace_identifiers_for(thread)
33
33
  @supported_apis.each do |api|
34
34
  trace_identifiers = api.trace_identifiers_for(thread)
35
35
 
36
36
  if trace_identifiers
37
- return @extract_trace_resource ? trace_identifiers : trace_identifiers[0..1]
37
+ return @endpoint_collection_enabled ? trace_identifiers : trace_identifiers[0..1]
38
38
  end
39
39
  end
40
40
 
@@ -41,11 +41,10 @@ module Datadog
41
41
 
42
42
  def build_form(env)
43
43
  flush = env.request.parcel.data
44
- pprof_file, types = build_pprof(flush)
44
+ pprof_file = build_pprof(flush)
45
45
 
46
46
  form = {
47
- # NOTE: Redundant w/ 'runtime-id' tag below; may want to remove this later.
48
- FORM_FIELD_RUNTIME_ID => flush.runtime_id,
47
+ FORM_FIELD_INTAKE_VERSION => '3', # Aka 1.3 intake format
49
48
  FORM_FIELD_RECORDING_START => flush.start.utc.iso8601,
50
49
  FORM_FIELD_RECORDING_END => flush.finish.utc.iso8601,
51
50
  FORM_FIELD_TAGS => [
@@ -64,14 +63,10 @@ module Datadog
64
63
  .reject { |tag_key| TAGS_TO_IGNORE_IN_TAGS_HASH.include?(tag_key) }
65
64
  .map { |tag_key, tag_value| "#{tag_key}:#{tag_value}" }
66
65
  ],
67
- FORM_FIELD_DATA => pprof_file,
68
- FORM_FIELD_RUNTIME => flush.language,
69
- FORM_FIELD_FORMAT => FORM_FIELD_FORMAT_PPROF
66
+ FORM_FIELD_PPROF_DATA => pprof_file,
67
+ FORM_FIELD_FAMILY => flush.language,
70
68
  }
71
69
 
72
- # Add types
73
- form[FORM_FIELD_TYPES] = types.join(',')
74
-
75
70
  # Optional fields
76
71
  form[FORM_FIELD_TAGS] << "#{FORM_FIELD_TAG_SERVICE}:#{flush.service}" unless flush.service.nil?
77
72
  form[FORM_FIELD_TAGS] << "#{FORM_FIELD_TAG_ENV}:#{flush.env}" unless flush.env.nil?
@@ -83,15 +78,13 @@ module Datadog
83
78
  def build_pprof(flush)
84
79
  pprof = encoder.encode(flush)
85
80
 
86
- # Wrap pprof as a gzipped file
87
- gzipped_data = Datadog::Utils::Compression.gzip(pprof.data)
88
- pprof_file = Datadog::Vendor::Multipart::Post::UploadIO.new(
89
- StringIO.new(gzipped_data),
81
+ gzipped_pprof_data = Datadog::Utils::Compression.gzip(pprof.data)
82
+
83
+ Datadog::Vendor::Multipart::Post::UploadIO.new(
84
+ StringIO.new(gzipped_pprof_data),
90
85
  HEADER_CONTENT_TYPE_OCTET_STREAM,
91
86
  PPROF_DEFAULT_FILENAME
92
87
  )
93
-
94
- [pprof_file, [FORM_FIELD_TYPES_AUTO]]
95
88
  end
96
89
  end
97
90
  end
@@ -64,22 +64,10 @@ module Datadog
64
64
  end
65
65
  end
66
66
 
67
- private_class_method def self.default_adapter
68
- :net_http
69
- end
70
-
71
67
  private_class_method def self.configure_for_agent(transport, profiling_upload_timeout_seconds:, agent_settings:)
72
68
  apis = API.agent_defaults
73
69
 
74
- transport.adapter(
75
- default_adapter,
76
- agent_settings.hostname,
77
- agent_settings.port,
78
- # We explictly use profiling_upload_timeout_seconds instead of agent_settings.timeout because profile
79
- # uploads are bigger and thus we employ a separate configuration.
80
- timeout: profiling_upload_timeout_seconds,
81
- ssl: agent_settings.ssl
82
- )
70
+ transport.adapter(agent_settings.merge(timeout_seconds: profiling_upload_timeout_seconds))
83
71
  transport.api(API::V1, apis[API::V1], default: true)
84
72
 
85
73
  # NOTE: This proc, when it exists, usually overrides the transport specified above
@@ -96,7 +84,7 @@ module Datadog
96
84
  port = site_uri.port
97
85
 
98
86
  transport.adapter(
99
- default_adapter,
87
+ Datadog::Ext::Transport::HTTP::ADAPTER,
100
88
  hostname,
101
89
  port,
102
90
  timeout: profiling_upload_timeout_seconds,
@@ -111,9 +99,12 @@ module Datadog
111
99
  end
112
100
 
113
101
  # Add adapters to registry
114
- Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::Net, :net_http)
115
- Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::Test, :test)
116
- Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::UnixSocket, :unix)
102
+ Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::Net,
103
+ Datadog::Ext::Transport::HTTP::ADAPTER)
104
+ Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::Test,
105
+ Datadog::Ext::Transport::Test::ADAPTER)
106
+ Datadog::Transport::HTTP::Builder::REGISTRY.set(Datadog::Transport::HTTP::Adapters::UnixSocket,
107
+ Datadog::Ext::Transport::UnixSocket::ADAPTER)
117
108
  end
118
109
  end
119
110
  end
@@ -128,9 +128,7 @@ module Datadog
128
128
  private_class_method def self.load_profiling
129
129
  return false unless supported?
130
130
 
131
- require 'ddtrace/profiling/ext/cpu'
132
131
  require 'ddtrace/profiling/ext/forking'
133
-
134
132
  require 'ddtrace/profiling/collectors/stack'
135
133
  require 'ddtrace/profiling/exporter'
136
134
  require 'ddtrace/profiling/recorder'
@@ -7,6 +7,7 @@ require 'datadog/core/environment/class_count'
7
7
  require 'datadog/core/environment/gc'
8
8
  require 'datadog/core/environment/identity'
9
9
  require 'datadog/core/environment/thread_count'
10
+ require 'datadog/core/environment/vm_cache'
10
11
 
11
12
  module Datadog
12
13
  module Runtime
@@ -58,12 +59,25 @@ module Datadog
58
59
  gauge(Ext::Runtime::Metrics::METRIC_CLASS_COUNT, Core::Environment::ClassCount.value)
59
60
  end
60
61
  end
62
+
61
63
  try_flush do
62
64
  if Core::Environment::ThreadCount.available?
63
65
  gauge(Ext::Runtime::Metrics::METRIC_THREAD_COUNT, Core::Environment::ThreadCount.value)
64
66
  end
65
67
  end
68
+
66
69
  try_flush { gc_metrics.each { |metric, value| gauge(metric, value) } if Core::Environment::GC.available? }
70
+
71
+ try_flush do
72
+ if Core::Environment::VMCache.available?
73
+ gauge(Ext::Runtime::Metrics::METRIC_GLOBAL_CONSTANT_STATE, Core::Environment::VMCache.global_constant_state)
74
+
75
+ # global_method_state is not available since Ruby >= 3.0,
76
+ # as method caching was moved to a per-class basis.
77
+ global_method_state = Core::Environment::VMCache.global_method_state
78
+ gauge(Ext::Runtime::Metrics::METRIC_GLOBAL_METHOD_STATE, global_method_state) if global_method_state
79
+ end
80
+ end
67
81
  end
68
82
 
69
83
  def gc_metrics
@@ -194,6 +194,12 @@ module Datadog
194
194
  class PrioritySampler
195
195
  extend Forwardable
196
196
 
197
+ # NOTE: We do not advise using a pre-sampler. It can save resources,
198
+ # but pre-sampling at rates < 100% may result in partial traces, unless
199
+ # the pre-sampler knows exactly how to drop a span without dropping its ancestors.
200
+ #
201
+ # Additionally, as service metrics are calculated in the Datadog Agent,
202
+ # the service's throughput will be underestimated.
197
203
  attr_reader :pre_sampler, :priority_sampler
198
204
 
199
205
  SAMPLE_RATE_METRIC_KEY = '_sample_rate'.freeze
@@ -209,17 +215,21 @@ module Datadog
209
215
 
210
216
  def sample!(span)
211
217
  # If pre-sampling is configured, do it first. (By default, this will sample at 100%.)
212
- # NOTE: Pre-sampling at rates < 100% may result in partial traces; not recommended.
213
218
  span.sampled = pre_sample?(span) ? @pre_sampler.sample!(span) : true
214
219
 
215
220
  if span.sampled
216
- # If priority sampling has already been applied upstream, use that, otherwise...
217
- unless priority_assigned_upstream?(span)
218
- # Roll the dice and determine whether how we set the priority.
219
- priority = priority_sample!(span) ? Datadog::Ext::Priority::AUTO_KEEP : Datadog::Ext::Priority::AUTO_REJECT
221
+ # If priority sampling has already been applied upstream, use that value.
222
+ return true if priority_assigned?(span)
220
223
 
221
- assign_priority!(span, priority)
222
- end
224
+ # Check with post sampler how we set the priority.
225
+ sample = priority_sample!(span)
226
+
227
+ # Check if post sampler has already assigned a priority.
228
+ return true if priority_assigned?(span)
229
+
230
+ # If not, use agent priority values.
231
+ priority = sample ? Datadog::Ext::Priority::AUTO_KEEP : Datadog::Ext::Priority::AUTO_REJECT
232
+ assign_priority!(span, priority)
223
233
  else
224
234
  # If discarded by pre-sampling, set "reject" priority, so other
225
235
  # services for the same trace don't sample needlessly.
@@ -244,7 +254,7 @@ module Datadog
244
254
  end
245
255
  end
246
256
 
247
- def priority_assigned_upstream?(span)
257
+ def priority_assigned?(span)
248
258
  span.context && !span.context.sampling_priority.nil?
249
259
  end
250
260
 
@@ -97,11 +97,13 @@ module Datadog
97
97
  sampled = rule.sample?(span)
98
98
  sample_rate = rule.sample_rate(span)
99
99
 
100
+ set_priority(span, sampled)
100
101
  set_rule_metrics(span, sample_rate)
101
102
 
102
103
  return false unless sampled
103
104
 
104
- rate_limiter.allow?(1).tap do
105
+ rate_limiter.allow?(1).tap do |allowed|
106
+ set_priority(span, allowed)
105
107
  set_limiter_metrics(span, rate_limiter.effective_rate)
106
108
  end
107
109
  rescue StandardError => e
@@ -109,6 +111,16 @@ module Datadog
109
111
  yield(span)
110
112
  end
111
113
 
114
+ # Span priority should only be set when the {RuleSampler}
115
+ # was responsible for the sampling decision.
116
+ def set_priority(span, sampled)
117
+ if sampled
118
+ ForcedTracing.keep(span)
119
+ else
120
+ ForcedTracing.drop(span)
121
+ end
122
+ end
123
+
112
124
  def set_rule_metrics(span, sample_rate)
113
125
  span.set_metric(Ext::Sampling::RULE_SAMPLE_RATE, sample_rate)
114
126
  end
data/lib/ddtrace/span.rb CHANGED
@@ -53,12 +53,7 @@ module Datadog
53
53
  Ext::NET::TAG_HOSTNAME => true
54
54
  }.freeze
55
55
 
56
- # Simple indirection used to contain the latest resource name for a span.
57
- # The profiler keeps a reference to these objects while sampling so it can extract the latest resource after the
58
- # fact, as some integrations only set the correct name at the end of the span.
59
- ResourceContainer = Struct.new(:latest)
60
-
61
- attr_accessor :name, :service, :span_type,
56
+ attr_accessor :name, :service, :resource, :span_type,
62
57
  :span_id, :trace_id, :parent_id,
63
58
  :status, :sampled,
64
59
  :tracer, :context
@@ -71,7 +66,8 @@ module Datadog
71
66
  # and then <tt>finish()</tt> once the tracer operation is over.
72
67
  #
73
68
  # * +service+: the service name for this span
74
- # * +resource+: the resource this span refers, or +name+ if it's missing
69
+ # * +resource+: the resource this span refers, or +name+ if it's missing.
70
+ # +nil+ can be used as a placeholder, when the resource value is not yet known at +#initialize+ time.
75
71
  # * +span_type+: the type of the span (such as +http+, +db+ and so on)
76
72
  # * +parent_id+: the identifier of the parent span
77
73
  # * +trace_id+: the identifier of the root span for this trace
@@ -81,7 +77,7 @@ module Datadog
81
77
 
82
78
  @name = name
83
79
  @service = options.fetch(:service, nil)
84
- @resource_container = ResourceContainer.new(options.fetch(:resource, name))
80
+ @resource = options.fetch(:resource, name)
85
81
  @span_type = options.fetch(:span_type, nil)
86
82
 
87
83
  @span_id = Datadog::Utils.next_id
@@ -289,7 +285,7 @@ module Datadog
289
285
  trace_id: @trace_id,
290
286
  name: @name,
291
287
  service: @service,
292
- resource: resource,
288
+ resource: @resource,
293
289
  type: @span_type,
294
290
  meta: @meta,
295
291
  metrics: @metrics,
@@ -343,7 +339,7 @@ module Datadog
343
339
  packer.write('service')
344
340
  packer.write(@service)
345
341
  packer.write('resource')
346
- packer.write(resource)
342
+ packer.write(@resource)
347
343
  packer.write('type')
348
344
  packer.write(@span_type)
349
345
  packer.write('meta')
@@ -375,7 +371,7 @@ module Datadog
375
371
  q.text "Trace ID: #{@trace_id}\n"
376
372
  q.text "Type: #{@span_type}\n"
377
373
  q.text "Service: #{@service}\n"
378
- q.text "Resource: #{resource}\n"
374
+ q.text "Resource: #{@resource}\n"
379
375
  q.text "Error: #{@status}\n"
380
376
  q.text "Start: #{start_time}\n"
381
377
  q.text "End: #{end_time}\n"
@@ -414,14 +410,6 @@ module Datadog
414
410
  end
415
411
  end
416
412
 
417
- def resource
418
- @resource_container.latest
419
- end
420
-
421
- def resource=(resource)
422
- @resource_container.latest = resource
423
- end
424
-
425
413
  private
426
414
 
427
415
  def duration_marker
@@ -200,7 +200,7 @@ module Datadog
200
200
  if parent.nil?
201
201
  # root span
202
202
  @sampler.sample!(span)
203
- span.set_tag('system.pid', Process.pid)
203
+ span.set_tag(Datadog::Ext::Runtime::TAG_PID, Process.pid)
204
204
  span.set_tag(Datadog::Ext::Runtime::TAG_ID, Datadog::Core::Environment::Identity.id)
205
205
 
206
206
  if ctx && ctx.trace_id
@@ -16,13 +16,23 @@ module Datadog
16
16
 
17
17
  DEFAULT_TIMEOUT = 30
18
18
 
19
- def initialize(hostname, port, options = {})
20
- @hostname = hostname
21
- @port = port
19
+ # @deprecated Positional parameters are deprecated. Use named parameters instead.
20
+ def initialize(hostname = nil, port = nil, **options)
21
+ @hostname = hostname || options.fetch(:hostname)
22
+ @port = port || options.fetch(:port)
22
23
  @timeout = options[:timeout] || DEFAULT_TIMEOUT
23
24
  @ssl = options.key?(:ssl) ? options[:ssl] == true : false
24
25
  end
25
26
 
27
+ def self.build(agent_settings)
28
+ new(
29
+ hostname: agent_settings.hostname,
30
+ port: agent_settings.port,
31
+ timeout: agent_settings.timeout_seconds,
32
+ ssl: agent_settings.ssl
33
+ )
34
+ end
35
+
26
36
  def open(&block)
27
37
  # DEV Initializing +Net::HTTP+ directly help us avoid expensive
28
38
  # options processing done in +Net::HTTP.start+:
@@ -11,8 +11,10 @@ module Datadog
11
11
  :buffer,
12
12
  :status
13
13
 
14
- def initialize(buffer = nil)
15
- @buffer = buffer
14
+ # @param buffer [Array] an optional array that will capture all spans sent to this adapter, defaults to +nil+
15
+ # @deprecated Positional parameters are deprecated. Use named parameters instead.
16
+ def initialize(buffer = nil, **options)
17
+ @buffer = buffer || options[:buffer]
16
18
  @mutex = Mutex.new
17
19
  @status = 200
18
20
  end