ddtrace 0.47.0 → 0.48.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +4 -2
  3. data/.circleci/images/primary/Dockerfile-2.0.0 +11 -1
  4. data/.circleci/images/primary/Dockerfile-2.1.10 +11 -1
  5. data/.circleci/images/primary/Dockerfile-2.2.10 +11 -1
  6. data/.circleci/images/primary/Dockerfile-2.3.8 +10 -0
  7. data/.circleci/images/primary/Dockerfile-2.4.6 +10 -0
  8. data/.circleci/images/primary/Dockerfile-2.5.6 +10 -0
  9. data/.circleci/images/primary/Dockerfile-2.6.4 +10 -0
  10. data/.circleci/images/primary/Dockerfile-2.7.0 +10 -0
  11. data/.circleci/images/primary/Dockerfile-jruby-9.2-latest +10 -0
  12. data/.gitlab-ci.yml +18 -18
  13. data/.rubocop.yml +19 -0
  14. data/.rubocop_todo.yml +44 -3
  15. data/Appraisals +55 -1
  16. data/CHANGELOG.md +47 -1
  17. data/Gemfile +10 -0
  18. data/Rakefile +9 -0
  19. data/bin/ddtracerb +15 -0
  20. data/ddtrace.gemspec +4 -2
  21. data/docs/GettingStarted.md +36 -53
  22. data/docs/ProfilingDevelopment.md +88 -0
  23. data/integration/README.md +1 -2
  24. data/integration/apps/rack/Dockerfile +3 -0
  25. data/integration/apps/rack/script/build-images +1 -1
  26. data/integration/apps/rack/script/ci +1 -1
  27. data/integration/apps/rails-five/script/build-images +1 -1
  28. data/integration/apps/rails-five/script/ci +1 -1
  29. data/integration/apps/ruby/script/build-images +1 -1
  30. data/integration/apps/ruby/script/ci +1 -1
  31. data/integration/images/include/http-health-check +1 -1
  32. data/integration/images/wrk/scripts/entrypoint.sh +1 -1
  33. data/integration/script/build-images +1 -1
  34. data/lib/ddtrace.rb +1 -0
  35. data/lib/ddtrace/configuration.rb +39 -13
  36. data/lib/ddtrace/configuration/components.rb +85 -3
  37. data/lib/ddtrace/configuration/settings.rb +31 -0
  38. data/lib/ddtrace/contrib/active_record/configuration/makara_resolver.rb +30 -0
  39. data/lib/ddtrace/contrib/active_record/configuration/resolver.rb +9 -3
  40. data/lib/ddtrace/contrib/resque/configuration/settings.rb +17 -1
  41. data/lib/ddtrace/contrib/resque/patcher.rb +4 -4
  42. data/lib/ddtrace/contrib/resque/resque_job.rb +22 -1
  43. data/lib/ddtrace/contrib/shoryuken/configuration/settings.rb +1 -0
  44. data/lib/ddtrace/contrib/shoryuken/tracer.rb +7 -3
  45. data/lib/ddtrace/diagnostics/environment_logger.rb +1 -1
  46. data/lib/ddtrace/error.rb +2 -0
  47. data/lib/ddtrace/ext/profiling.rb +52 -0
  48. data/lib/ddtrace/ext/transport.rb +1 -0
  49. data/lib/ddtrace/metrics.rb +4 -0
  50. data/lib/ddtrace/profiling.rb +54 -0
  51. data/lib/ddtrace/profiling/backtrace_location.rb +32 -0
  52. data/lib/ddtrace/profiling/buffer.rb +41 -0
  53. data/lib/ddtrace/profiling/collectors/stack.rb +253 -0
  54. data/lib/ddtrace/profiling/encoding/profile.rb +31 -0
  55. data/lib/ddtrace/profiling/event.rb +13 -0
  56. data/lib/ddtrace/profiling/events/stack.rb +102 -0
  57. data/lib/ddtrace/profiling/exporter.rb +23 -0
  58. data/lib/ddtrace/profiling/ext/cpu.rb +54 -0
  59. data/lib/ddtrace/profiling/ext/cthread.rb +134 -0
  60. data/lib/ddtrace/profiling/ext/forking.rb +97 -0
  61. data/lib/ddtrace/profiling/flush.rb +41 -0
  62. data/lib/ddtrace/profiling/pprof/builder.rb +121 -0
  63. data/lib/ddtrace/profiling/pprof/converter.rb +85 -0
  64. data/lib/ddtrace/profiling/pprof/message_set.rb +12 -0
  65. data/lib/ddtrace/profiling/pprof/payload.rb +18 -0
  66. data/lib/ddtrace/profiling/pprof/pprof.proto +212 -0
  67. data/lib/ddtrace/profiling/pprof/pprof_pb.rb +81 -0
  68. data/lib/ddtrace/profiling/pprof/stack_sample.rb +90 -0
  69. data/lib/ddtrace/profiling/pprof/string_table.rb +10 -0
  70. data/lib/ddtrace/profiling/pprof/template.rb +114 -0
  71. data/lib/ddtrace/profiling/preload.rb +3 -0
  72. data/lib/ddtrace/profiling/profiler.rb +28 -0
  73. data/lib/ddtrace/profiling/recorder.rb +87 -0
  74. data/lib/ddtrace/profiling/scheduler.rb +84 -0
  75. data/lib/ddtrace/profiling/tasks/setup.rb +77 -0
  76. data/lib/ddtrace/profiling/transport/client.rb +12 -0
  77. data/lib/ddtrace/profiling/transport/http.rb +122 -0
  78. data/lib/ddtrace/profiling/transport/http/api.rb +43 -0
  79. data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +90 -0
  80. data/lib/ddtrace/profiling/transport/http/api/instance.rb +36 -0
  81. data/lib/ddtrace/profiling/transport/http/api/spec.rb +40 -0
  82. data/lib/ddtrace/profiling/transport/http/builder.rb +28 -0
  83. data/lib/ddtrace/profiling/transport/http/client.rb +33 -0
  84. data/lib/ddtrace/profiling/transport/http/response.rb +21 -0
  85. data/lib/ddtrace/profiling/transport/io.rb +30 -0
  86. data/lib/ddtrace/profiling/transport/io/client.rb +27 -0
  87. data/lib/ddtrace/profiling/transport/io/response.rb +16 -0
  88. data/lib/ddtrace/profiling/transport/parcel.rb +17 -0
  89. data/lib/ddtrace/profiling/transport/request.rb +15 -0
  90. data/lib/ddtrace/profiling/transport/response.rb +8 -0
  91. data/lib/ddtrace/runtime/container.rb +11 -3
  92. data/lib/ddtrace/sampling/rule_sampler.rb +3 -9
  93. data/lib/ddtrace/tasks/exec.rb +48 -0
  94. data/lib/ddtrace/tasks/help.rb +14 -0
  95. data/lib/ddtrace/tracer.rb +21 -0
  96. data/lib/ddtrace/transport/io/client.rb +15 -8
  97. data/lib/ddtrace/transport/parcel.rb +4 -0
  98. data/lib/ddtrace/version.rb +3 -1
  99. data/lib/ddtrace/workers/runtime_metrics.rb +14 -1
  100. metadata +70 -9
@@ -0,0 +1,33 @@
1
+ require 'ddtrace/transport/http/client'
2
+ require 'ddtrace/profiling/transport/client'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ module Transport
7
+ module HTTP
8
+ # Routes, encodes, and sends tracer data to the trace agent via HTTP.
9
+ class Client < Datadog::Transport::HTTP::Client
10
+ include Transport::Client
11
+
12
+ def send_profiling_flush(flush)
13
+ # Build a request
14
+ request = Profiling::Transport::Request.new(flush)
15
+ send_payload(request).tap do |response|
16
+ if response.ok?
17
+ Datadog.logger.debug('Successfully reported profiling data')
18
+ else
19
+ Datadog.logger.debug { "Failed to report profiling data -- #{response.inspect}" }
20
+ end
21
+ end
22
+ end
23
+
24
+ def send_payload(request)
25
+ send_request(request) do |api, env|
26
+ api.send_profiling_flush(env)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'ddtrace/transport/http/response'
2
+ require 'ddtrace/profiling/transport/response'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ module Transport
7
+ # HTTP transport behavior for profiling
8
+ module HTTP
9
+ # Response from HTTP transport for profiling
10
+ class Response
11
+ include Datadog::Transport::HTTP::Response
12
+ include Profiling::Transport::Response
13
+
14
+ def initialize(http_response, options = {})
15
+ super(http_response)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ require 'ddtrace/profiling/transport/io/client'
2
+ require 'ddtrace/profiling/encoding/profile'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ module Transport
7
+ # Namespace for profiling IO transport components
8
+ module IO
9
+ module_function
10
+
11
+ # Builds a new Profiling::Transport::IO::Client
12
+ def new(out, encoder, options = {})
13
+ Client.new(out, encoder, options)
14
+ end
15
+
16
+ # Builds a new Profiling::Transport::IO::Client with default settings
17
+ # Pass options to override any settings.
18
+ def default(options = {})
19
+ options = options.dup
20
+
21
+ new(
22
+ options.delete(:out) || $stdout,
23
+ options.delete(:encoder) || Profiling::Encoding::Profile::Protobuf,
24
+ options
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'ddtrace/transport/io/client'
2
+ require 'ddtrace/profiling/transport/client'
3
+ require 'ddtrace/profiling/transport/request'
4
+ require 'ddtrace/profiling/transport/io/response'
5
+
6
+ module Datadog
7
+ module Profiling
8
+ module Transport
9
+ module IO
10
+ # IO transport for profiling
11
+ class Client < Datadog::Transport::IO::Client
12
+ include Transport::Client
13
+
14
+ def send_profiling_flush(flush)
15
+ # Build a request
16
+ request = Profiling::Transport::Request.new(flush)
17
+ send_request(request)
18
+ end
19
+
20
+ def build_response(_request, _data, result)
21
+ Profiling::Transport::IO::Response.new(result)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ require 'ddtrace/transport/io/response'
2
+ require 'ddtrace/profiling/transport/response'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ module Transport
7
+ # IO transport behavior for profiling
8
+ module IO
9
+ # Response from IO transport for profiling
10
+ class Response < Datadog::Transport::IO::Response
11
+ include Profiling::Transport::Response
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'ddtrace/transport/parcel'
2
+
3
+ module Datadog
4
+ module Profiling
5
+ module Transport
6
+ # Data transfer object for profiling data
7
+ class Parcel
8
+ include Datadog::Transport::Parcel
9
+
10
+ def encode_with(encoder)
11
+ # TODO: Determine encoding behavior
12
+ encoder.encode(data)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'ddtrace/transport/request'
2
+ require 'ddtrace/profiling/transport/parcel'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ module Transport
7
+ # Profiling request
8
+ class Request < Datadog::Transport::Request
9
+ def initialize(flush)
10
+ super(Parcel.new(flush))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module Datadog
2
+ module Profiling
3
+ module Transport
4
+ # Profiling response
5
+ module Response; end
6
+ end
7
+ end
8
+ end
@@ -10,6 +10,7 @@ module Datadog
10
10
 
11
11
  POD_REGEX = /(pod)?(#{UUID_PATTERN})(?:.slice)?$/.freeze
12
12
  CONTAINER_REGEX = /(#{UUID_PATTERN}|#{CONTAINER_PATTERN})(?:.scope)?$/.freeze
13
+ FARGATE_14_CONTAINER_REGEX = /[0-9a-f]{32}-[0-9]{10}/.freeze
13
14
 
14
15
  Descriptor = Struct.new(
15
16
  :platform,
@@ -42,12 +43,19 @@ module Datadog
42
43
  # Split path into parts
43
44
  parts = path.split('/')
44
45
  parts.shift # Remove leading empty part
45
- next if parts.length < 2
46
46
 
47
47
  # Read info from path
48
48
  platform = parts[0]
49
- container_id = parts[-1][CONTAINER_REGEX]
50
- task_uid = parts[-2][POD_REGEX]
49
+ container_id, task_uid = nil
50
+
51
+ case parts.length
52
+ when 2
53
+ container_id = parts[-1][CONTAINER_REGEX] || parts[-1][FARGATE_14_CONTAINER_REGEX]
54
+ else
55
+ platform = parts[0]
56
+ container_id = parts[-1][CONTAINER_REGEX]
57
+ task_uid = parts[-2][POD_REGEX]
58
+ end
51
59
 
52
60
  # If container ID wasn't found, ignore.
53
61
  # Path might describe a non-container environment.
@@ -5,6 +5,7 @@ require 'ddtrace/ext/priority'
5
5
  require 'ddtrace/ext/sampling'
6
6
  require 'ddtrace/sampler'
7
7
  require 'ddtrace/sampling/rate_limiter'
8
+ require 'ddtrace/sampling/rule'
8
9
 
9
10
  module Datadog
10
11
  module Sampling
@@ -45,15 +46,8 @@ module Datadog
45
46
  @default_sampler = if default_sampler
46
47
  default_sampler
47
48
  elsif default_sample_rate
48
- # We want to allow 0.0 to drop all traces, but \RateSampler
49
- # considers 0.0 an invalid rate and falls back to 100% sampling.
50
- #
51
- # We address that here by not setting the rate in the constructor,
52
- # but using the setter method.
53
- #
54
- # We don't want to make this change directly to \RateSampler
55
- # because it breaks its current contract to existing users.
56
- Datadog::RateSampler.new.tap { |s| s.sample_rate = default_sample_rate }
49
+ # Add to the end of the rule list a rule always matches any span
50
+ @rules << SimpleRule.new(sample_rate: default_sample_rate)
57
51
  else
58
52
  RateByServiceSampler.new(1.0, env: -> { Datadog.tracer.tags[:env] })
59
53
  end
@@ -0,0 +1,48 @@
1
+ module Datadog
2
+ module Tasks
3
+ # Wraps command with Datadog tracing
4
+ class Exec
5
+ attr_reader :args
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ end
10
+
11
+ def run
12
+ set_rubyopt!
13
+ exec_with_error_handling(args)
14
+ end
15
+
16
+ def rubyopts
17
+ [
18
+ '-rddtrace/profiling/preload'
19
+ ]
20
+ end
21
+
22
+ private
23
+
24
+ def set_rubyopt!
25
+ if ENV.key?('RUBYOPT')
26
+ ENV['RUBYOPT'] += " #{rubyopts.join(' ')}"
27
+ else
28
+ ENV['RUBYOPT'] = rubyopts.join(' ')
29
+ end
30
+ end
31
+
32
+ # If there's an error here, rather than throwing a cryptic stack trace, let's instead have clearer messages, and
33
+ # follow the same status codes as the shell uses
34
+ # See also:
35
+ # * https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
36
+ # * https://github.com/rubygems/rubygems/blob/dd93966cac224532035deda533cba2685dfa30cc/bundler/lib/bundler/cli/exec.rb#L45
37
+ def exec_with_error_handling(args)
38
+ Kernel.exec(*args)
39
+ rescue Errno::ENOENT => e
40
+ Kernel.warn "ddtracerb exec failed: #{e.message} (command was '#{args.join(' ')}')"
41
+ Kernel.exit 127
42
+ rescue Errno::EACCES, Errno::ENOEXEC => e
43
+ Kernel.warn "ddtracerb exec failed: #{e.message} (command was '#{args.join(' ')}')"
44
+ Kernel.exit 126
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ module Datadog
2
+ module Tasks
3
+ # Prints help message for usage of `ddtrace`
4
+ class Help
5
+ def run
6
+ puts %(
7
+ Usage: ddtrace [command] [arguments]
8
+ exec [command]: Executes command with tracing & profiling preloaded.
9
+ help: Prints this help message.
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -10,6 +10,7 @@ require 'ddtrace/runtime/identity'
10
10
  require 'ddtrace/sampler'
11
11
  require 'ddtrace/sampling'
12
12
  require 'ddtrace/correlation'
13
+ require 'ddtrace/event'
13
14
  require 'ddtrace/utils/only_once'
14
15
 
15
16
  # \Datadog global namespace that includes all tracing functionality for Tracer and Span classes.
@@ -315,6 +316,10 @@ module Datadog
315
316
  end
316
317
  end
317
318
 
319
+ def trace_completed
320
+ @trace_completed ||= TraceCompleted.new
321
+ end
322
+
318
323
  # Record the given +context+. For compatibility with previous versions,
319
324
  # +context+ can also be a span. It is similar to the +child_of+ argument,
320
325
  # method will figure out what to do, submitting a +span+ for recording
@@ -366,6 +371,22 @@ module Datadog
366
371
  end
367
372
 
368
373
  @writer.write(trace)
374
+ trace_completed.publish(trace)
375
+ end
376
+
377
+ # Triggered whenever a trace is completed
378
+ class TraceCompleted < Datadog::Event
379
+ def initialize
380
+ super(:trace_completed)
381
+ end
382
+
383
+ # NOTE: Ignore Rubocop rule. This definition allows for
384
+ # description of and constraints on arguments.
385
+ # rubocop:disable Lint/UselessMethodDefinition
386
+ def publish(trace)
387
+ super(trace)
388
+ end
389
+ # rubocop:enable Lint/UselessMethodDefinition
369
390
  end
370
391
 
371
392
  # TODO: Move this kind of configuration building out of the tracer.
@@ -12,19 +12,24 @@ module Datadog
12
12
  :encoder,
13
13
  :out
14
14
 
15
- def initialize(out, encoder)
15
+ def initialize(out, encoder, options = {})
16
16
  @out = out
17
17
  @encoder = encoder
18
+
19
+ @request_block = options.fetch(:request, method(:send_default_request))
20
+ @encode_block = options.fetch(:encode, method(:encode_data))
21
+ @write_block = options.fetch(:write, method(:write_data))
22
+ @response_block = options.fetch(:response, method(:build_response))
18
23
  end
19
24
 
20
25
  def send_request(request)
21
26
  # Write data to IO
22
27
  # If block is given, allow it to handle writing
23
- # Otherwise use default encoding.
28
+ # Otherwise do a standard encode/write/response.
24
29
  response = if block_given?
25
30
  yield(out, request)
26
31
  else
27
- send_default_request(out, request)
32
+ @request_block.call(out, request)
28
33
  end
29
34
 
30
35
  # Update statistics
@@ -48,8 +53,6 @@ module Datadog
48
53
  InternalErrorResponse.new(e)
49
54
  end
50
55
 
51
- protected
52
-
53
56
  def encode_data(encoder, request)
54
57
  request.parcel.encode_with(encoder)
55
58
  end
@@ -58,17 +61,21 @@ module Datadog
58
61
  out.puts(data)
59
62
  end
60
63
 
64
+ def build_response(_request, _data, result)
65
+ IO::Response.new(result)
66
+ end
67
+
61
68
  private
62
69
 
63
70
  def send_default_request(out, request)
64
71
  # Encode data
65
- data = encode_data(encoder, request)
72
+ data = @encode_block.call(encoder, request)
66
73
 
67
74
  # Write to IO
68
- result = write_data(out, data)
75
+ result = @write_block.call(out, data)
69
76
 
70
77
  # Generate a response
71
- IO::Response.new(result)
78
+ @response_block.call(request, data, result)
72
79
  end
73
80
  end
74
81
  end
@@ -8,6 +8,10 @@ module Datadog
8
8
  def initialize(data)
9
9
  @data = data
10
10
  end
11
+
12
+ def encode_with(encoder)
13
+ raise NotImplementedError
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -1,12 +1,14 @@
1
1
  module Datadog
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 47
4
+ MINOR = 48
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
9
9
 
10
+ # Support for Ruby < 2.1 is currently deprecated in the tracer.
11
+ # Support will be dropped in the near future.
10
12
  MINIMUM_RUBY_VERSION = '2.0.0'.freeze
11
13
  end
12
14
  end
@@ -20,7 +20,7 @@ module Datadog
20
20
  :metrics
21
21
 
22
22
  def initialize(options = {})
23
- @metrics = options.fetch(:metrics, Runtime::Metrics.new)
23
+ @metrics = options.fetch(:metrics) { Runtime::Metrics.new }
24
24
 
25
25
  # Workers::Async::Thread settings
26
26
  self.fork_policy = options.fetch(:fork_policy, Workers::Async::Thread::FORK_POLICY_STOP)
@@ -43,6 +43,19 @@ module Datadog
43
43
  metrics.associate_with_span(*args).tap { perform }
44
44
  end
45
45
 
46
+ # TODO: `close_metrics` is only needed because
47
+ # Datadog::Components directly manipulates the lifecycle of
48
+ # Runtime::Metrics.statsd instances.
49
+ # This should be avoided, as it prevents this class from
50
+ # ensuring correct resource decommission of its internal
51
+ # dependencies.
52
+ def stop(*args, close_metrics: true)
53
+ self.enabled = false
54
+ result = super(*args)
55
+ @metrics.close if close_metrics
56
+ result
57
+ end
58
+
46
59
  def_delegators \
47
60
  :metrics,
48
61
  :register_service