ddtrace 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -3
  3. data/Appraisals +1 -0
  4. data/ddtrace.gemspec +3 -0
  5. data/docs/GettingStarted.md +31 -8
  6. data/gemfiles/rails32_postgres_redis.gemfile +1 -0
  7. data/lib/ddtrace.rb +20 -34
  8. data/lib/ddtrace/buffer.rb +1 -7
  9. data/lib/ddtrace/configurable.rb +77 -0
  10. data/lib/ddtrace/configuration.rb +35 -0
  11. data/lib/ddtrace/configuration/proxy.rb +29 -0
  12. data/lib/ddtrace/configuration/resolver.rb +24 -0
  13. data/lib/ddtrace/context.rb +55 -7
  14. data/lib/ddtrace/contrib/active_record/patcher.rb +4 -1
  15. data/lib/ddtrace/contrib/aws/patcher.rb +3 -0
  16. data/lib/ddtrace/contrib/base.rb +14 -0
  17. data/lib/ddtrace/contrib/dalli/patcher.rb +3 -0
  18. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +3 -0
  19. data/lib/ddtrace/contrib/faraday/middleware.rb +5 -6
  20. data/lib/ddtrace/contrib/faraday/patcher.rb +3 -0
  21. data/lib/ddtrace/contrib/grape/patcher.rb +3 -0
  22. data/lib/ddtrace/contrib/http/patcher.rb +22 -7
  23. data/lib/ddtrace/contrib/mongodb/patcher.rb +3 -0
  24. data/lib/ddtrace/contrib/rack/middlewares.rb +21 -35
  25. data/lib/ddtrace/contrib/rails/action_controller.rb +2 -2
  26. data/lib/ddtrace/contrib/rails/action_view.rb +2 -2
  27. data/lib/ddtrace/contrib/rails/active_record.rb +2 -2
  28. data/lib/ddtrace/contrib/rails/active_support.rb +2 -2
  29. data/lib/ddtrace/contrib/rails/framework.rb +36 -58
  30. data/lib/ddtrace/contrib/rails/middlewares.rb +1 -1
  31. data/lib/ddtrace/contrib/rails/patcher.rb +56 -0
  32. data/lib/ddtrace/contrib/rails/railtie.rb +18 -0
  33. data/lib/ddtrace/contrib/rails/utils.rb +1 -1
  34. data/lib/ddtrace/contrib/redis/patcher.rb +4 -0
  35. data/lib/ddtrace/contrib/redis/quantize.rb +1 -1
  36. data/lib/ddtrace/contrib/redis/tags.rb +1 -0
  37. data/lib/ddtrace/contrib/resque/patcher.rb +9 -0
  38. data/lib/ddtrace/contrib/resque/resque_job.rb +6 -6
  39. data/lib/ddtrace/contrib/sidekiq/tracer.rb +11 -11
  40. data/lib/ddtrace/contrib/sinatra/tracer.rb +23 -63
  41. data/lib/ddtrace/contrib/sucker_punch/patcher.rb +3 -0
  42. data/lib/ddtrace/ext/distributed.rb +2 -0
  43. data/lib/ddtrace/ext/redis.rb +6 -0
  44. data/lib/ddtrace/monkey.rb +20 -37
  45. data/lib/ddtrace/propagation/distributed_headers.rb +48 -0
  46. data/lib/ddtrace/propagation/http_propagator.rb +28 -0
  47. data/lib/ddtrace/registry.rb +42 -0
  48. data/lib/ddtrace/registry/registerable.rb +20 -0
  49. data/lib/ddtrace/sampler.rb +61 -1
  50. data/lib/ddtrace/sync_writer.rb +36 -0
  51. data/lib/ddtrace/tracer.rb +23 -21
  52. data/lib/ddtrace/transport.rb +52 -15
  53. data/lib/ddtrace/version.rb +2 -2
  54. data/lib/ddtrace/workers.rb +33 -31
  55. data/lib/ddtrace/writer.rb +20 -1
  56. metadata +42 -3
  57. data/lib/ddtrace/distributed.rb +0 -38
@@ -0,0 +1,48 @@
1
+ require 'ddtrace/span'
2
+ require 'ddtrace/ext/distributed'
3
+
4
+ module Datadog
5
+ # DistributedHeaders provides easy access and validation to headers
6
+ class DistributedHeaders
7
+ include Ext::DistributedTracing
8
+
9
+ def initialize(env)
10
+ @env = env
11
+ end
12
+
13
+ def valid?
14
+ # Sampling priority is optional.
15
+ trace_id && parent_id
16
+ end
17
+
18
+ def trace_id
19
+ value = header(HTTP_HEADER_TRACE_ID).to_i
20
+ return if value <= 0 || value >= Span::MAX_ID
21
+ value
22
+ end
23
+
24
+ def parent_id
25
+ value = header(HTTP_HEADER_PARENT_ID).to_i
26
+ return if value <= 0 || value >= Span::MAX_ID
27
+ value
28
+ end
29
+
30
+ def sampling_priority
31
+ hdr = header(HTTP_HEADER_SAMPLING_PRIORITY)
32
+ # It's important to make a difference between no header,
33
+ # and a header defined to zero.
34
+ return unless hdr
35
+ value = hdr.to_i
36
+ return if value < 0
37
+ value
38
+ end
39
+
40
+ private
41
+
42
+ def header(name)
43
+ rack_header = "http-#{name}".upcase!.tr('-', '_')
44
+
45
+ @env[rack_header]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ require 'ddtrace/context'
2
+ require 'ddtrace/ext/distributed'
3
+ require 'ddtrace/propagation/distributed_headers'
4
+
5
+ module Datadog
6
+ # HTTPPropagator helps extracting and injecting HTTP headers.
7
+ module HTTPPropagator
8
+ include Ext::DistributedTracing
9
+
10
+ # inject! popolates the env with span ID, trace ID and sampling priority
11
+ def self.inject!(context, env)
12
+ env[HTTP_HEADER_TRACE_ID] = context.trace_id.to_s
13
+ env[HTTP_HEADER_PARENT_ID] = context.span_id.to_s
14
+ env[HTTP_HEADER_SAMPLING_PRIORITY] = context.sampling_priority.to_s
15
+ env.delete(HTTP_HEADER_SAMPLING_PRIORITY) unless context.sampling_priority
16
+ end
17
+
18
+ # extract returns a context containing the span ID, trace ID and
19
+ # sampling priority defined in env.
20
+ def self.extract(env)
21
+ headers = DistributedHeaders.new(env)
22
+ return Datadog::Context.new unless headers.valid?
23
+ Datadog::Context.new(trace_id: headers.trace_id,
24
+ span_id: headers.parent_id,
25
+ sampling_priority: headers.sampling_priority)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'registry/registerable'
2
+
3
+ module Datadog
4
+ # Registry provides insertion/retrieval capabilities for integrations
5
+ class Registry
6
+ include Enumerable
7
+
8
+ Entry = Struct.new(:name, :klass, :auto_patch)
9
+
10
+ def initialize
11
+ @data = {}
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def add(name, klass, auto_patch = false)
16
+ @mutex.synchronize do
17
+ @data[name] = Entry.new(name, klass, auto_patch).freeze
18
+ end
19
+ end
20
+
21
+ def each
22
+ @mutex.synchronize do
23
+ @data.each { |_, entry| yield(entry) }
24
+ end
25
+ end
26
+
27
+ def [](name)
28
+ @mutex.synchronize do
29
+ entry = @data[name]
30
+ entry.klass if entry
31
+ end
32
+ end
33
+
34
+ def to_h
35
+ @mutex.synchronize do
36
+ @data.each_with_object({}) do |(_, entry), hash|
37
+ hash[entry.name] = entry.auto_patch
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ module Datadog
2
+ class Registry
3
+ # Registerable provides a convenience method for self-registering
4
+ module Registerable
5
+ def self.included(base)
6
+ base.singleton_class.send(:include, ClassMethods)
7
+ end
8
+
9
+ # ClassMethods
10
+ module ClassMethods
11
+ def register_as(name, options = {})
12
+ registry = options.fetch(:registry, Datadog.registry)
13
+ auto_patch = options.fetch(:auto_patch, false)
14
+
15
+ registry.add(name, self, auto_patch)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Datadog
2
4
  # \Sampler performs client-side trace sampling.
3
5
  class Sampler
@@ -42,8 +44,66 @@ module Datadog
42
44
  end
43
45
 
44
46
  def sample(span)
45
- span.sampled = ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
46
47
  span.set_metric(SAMPLE_RATE_METRIC_KEY, @sample_rate)
48
+ span.sampled = ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
49
+ end
50
+ end
51
+
52
+ # \RateByServiceSampler samples different services at different rates
53
+ class RateByServiceSampler < Sampler
54
+ DEFAULT_KEY = 'service:,env:'.freeze
55
+
56
+ def initialize(rate = 1.0, opts = {})
57
+ @env = opts.fetch(:env, Datadog.tracer.tags[:env])
58
+ @mutex = Mutex.new
59
+ @fallback = RateSampler.new(rate)
60
+ @sampler = { DEFAULT_KEY => @fallback }
61
+ end
62
+
63
+ def sample(span)
64
+ key = key_for(span)
65
+
66
+ @mutex.synchronize do
67
+ @sampler.fetch(key, @fallback).sample(span)
68
+ end
69
+ end
70
+
71
+ def update(rate_by_service)
72
+ @mutex.synchronize do
73
+ @sampler.delete_if { |key, _| key != DEFAULT_KEY && !rate_by_service.key?(key) }
74
+
75
+ rate_by_service.each do |key, rate|
76
+ @sampler[key] ||= RateSampler.new(rate)
77
+ @sampler[key].sample_rate = rate
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def key_for(span)
85
+ "service:#{span.service},env:#{@env}"
86
+ end
87
+ end
88
+
89
+ # \PrioritySampler
90
+ class PrioritySampler
91
+ extend Forwardable
92
+
93
+ def initialize(opts = {})
94
+ @base_sampler = opts[:base_sampler] || RateSampler.new
95
+ @post_sampler = opts[:post_sampler] || RateByServiceSampler.new
47
96
  end
97
+
98
+ def sample(span)
99
+ span.context.sampling_priority = 0 if span.context
100
+ return unless @base_sampler.sample(span)
101
+ return unless @post_sampler.sample(span)
102
+ span.context.sampling_priority = 1 if span.context
103
+
104
+ true
105
+ end
106
+
107
+ def_delegators :@post_sampler, :update
48
108
  end
49
109
  end
@@ -0,0 +1,36 @@
1
+ module Datadog
2
+ # SyncWriter flushes both services and traces synchronously
3
+ class SyncWriter
4
+ attr_reader :transport
5
+
6
+ def initialize(options = {})
7
+ @transport = options.fetch(:transport) do
8
+ HTTPTransport.new(Writer::HOSTNAME, Writer::PORT)
9
+ end
10
+ end
11
+
12
+ def write(trace, services)
13
+ perform_concurrently(
14
+ proc { flush_services(services) },
15
+ proc { flush_trace(trace) }
16
+ )
17
+ rescue => e
18
+ Tracer.log.debug(e)
19
+ end
20
+
21
+ private
22
+
23
+ def perform_concurrently(*tasks)
24
+ tasks.map { |task| Thread.new(&task) }.each(&:join)
25
+ end
26
+
27
+ def flush_services(services)
28
+ transport.send(:services, services)
29
+ end
30
+
31
+ def flush_trace(trace)
32
+ processed_traces = Pipeline.process!([trace])
33
+ transport.send(:traces, processed_traces)
34
+ end
35
+ end
36
+ end
@@ -18,8 +18,8 @@ module Datadog
18
18
  # of these function calls and sub-requests would be encapsulated within a single trace.
19
19
  # rubocop:disable Metrics/ClassLength
20
20
  class Tracer
21
- attr_reader :writer, :sampler, :services, :tags, :provider
22
- attr_accessor :enabled
21
+ attr_reader :sampler, :services, :tags, :provider
22
+ attr_accessor :enabled, :writer
23
23
  attr_writer :default_service
24
24
 
25
25
  # Global, memoized, lazy initialized instance of a logger that is used within the the Datadog
@@ -71,7 +71,7 @@ module Datadog
71
71
  #
72
72
  def shutdown!
73
73
  return if !@enabled || @writer.worker.nil?
74
- @writer.worker.shutdown!
74
+ @writer.worker.stop
75
75
  end
76
76
 
77
77
  # Return the current active \Context for this traced execution. This method is
@@ -118,11 +118,18 @@ module Datadog
118
118
  hostname = options.fetch(:hostname, nil)
119
119
  port = options.fetch(:port, nil)
120
120
  sampler = options.fetch(:sampler, nil)
121
+ priority_sampling = options[:priority_sampling]
121
122
 
122
123
  @enabled = enabled unless enabled.nil?
124
+ @sampler = sampler unless sampler.nil?
125
+
126
+ if priority_sampling
127
+ @sampler = PrioritySampler.new(base_sampler: @sampler)
128
+ @writer = Writer.new(priority_sampler: @sampler)
129
+ end
130
+
123
131
  @writer.transport.hostname = hostname unless hostname.nil?
124
132
  @writer.transport.port = port unless port.nil?
125
- @sampler = sampler unless sampler.nil?
126
133
  end
127
134
 
128
135
  # Set the information about the given service. A valid example is:
@@ -162,24 +169,15 @@ module Datadog
162
169
  end
163
170
 
164
171
  # Guess context and parent from child_of entry.
165
- def guess_context_and_parent(options = {})
166
- child_of = options.fetch(:child_of, nil) # can be context or span
167
-
168
- ctx = nil
169
- parent = nil
170
- unless child_of.nil?
171
- if child_of.respond_to?(:current_span)
172
- ctx = child_of
173
- parent = child_of.current_span
174
- elsif child_of.is_a?(Datadog::Span)
175
- parent = child_of
176
- ctx = child_of.context
177
- end
178
- end
172
+ def guess_context_and_parent(child_of)
173
+ # call_context should not be in this code path, as start_span
174
+ # should never try and pick an existing context, but only get
175
+ # it from the parameters passed to it (child_of)
176
+ return [Datadog::Context.new, nil] unless child_of
179
177
 
180
- ctx ||= call_context
178
+ return [child_of, child_of.current_span] if child_of.is_a?(Context)
181
179
 
182
- [ctx, parent]
180
+ [child_of.context, child_of]
183
181
  end
184
182
 
185
183
  # Return a span that will trace an operation called \name. This method allows
@@ -202,7 +200,7 @@ module Datadog
202
200
  [:service, :resource, :span_type].include?(k)
203
201
  end
204
202
 
205
- ctx, parent = guess_context_and_parent(options)
203
+ ctx, parent = guess_context_and_parent(options[:child_of])
206
204
  opts[:context] = ctx unless ctx.nil?
207
205
 
208
206
  span = Span.new(self, name, opts)
@@ -210,6 +208,10 @@ module Datadog
210
208
  # root span
211
209
  @sampler.sample(span)
212
210
  span.set_tag('system.pid', Process.pid)
211
+ if ctx && ctx.trace_id && ctx.span_id
212
+ span.trace_id = ctx.trace_id
213
+ span.parent_id = ctx.span_id
214
+ end
213
215
  else
214
216
  # child span
215
217
  span.parent = parent # sets service, trace_id, parent_id, sampled
@@ -19,13 +19,36 @@ module Datadog
19
19
  TRACE_COUNT_HEADER = 'X-Datadog-Trace-Count'.freeze
20
20
  RUBY_INTERPRETER = RUBY_VERSION > '1.9' ? RUBY_ENGINE + '-' + RUBY_PLATFORM : 'ruby-' + RUBY_PLATFORM
21
21
 
22
+ API = {
23
+ V4 = 'v0.4'.freeze => {
24
+ traces_endpoint: '/v0.4/traces'.freeze,
25
+ services_endpoint: '/v0.4/services'.freeze,
26
+ encoder: Encoding::MsgpackEncoder,
27
+ fallback: 'v0.3'.freeze
28
+ }.freeze,
29
+ V3 = 'v0.3'.freeze => {
30
+ traces_endpoint: '/v0.3/traces'.freeze,
31
+ services_endpoint: '/v0.3/services'.freeze,
32
+ encoder: Encoding::MsgpackEncoder,
33
+ fallback: 'v0.2'.freeze
34
+ }.freeze,
35
+ V2 = 'v0.2'.freeze => {
36
+ traces_endpoint: '/v0.2/traces'.freeze,
37
+ services_endpoint: '/v0.2/services'.freeze,
38
+ encoder: Encoding::JSONEncoder
39
+ }.freeze
40
+ }.freeze
41
+
42
+ private_constant :API
43
+
22
44
  def initialize(hostname, port, options = {})
45
+ api_version = options.fetch(:api_version, V3)
46
+
23
47
  @hostname = hostname
24
48
  @port = port
25
- @traces_endpoint = '/v0.3/traces'.freeze
26
- @services_endpoint = '/v0.3/services'.freeze
27
- @compatibility_mode = false
28
- @encoder = options.fetch(:encoder, Datadog::Encoding::MsgpackEncoder.new())
49
+ @api = API.fetch(api_version)
50
+ @encoder = options[:encoder] || @api[:encoder].new
51
+ @response_callback = options[:response_callback]
29
52
 
30
53
  # overwrite the Content-type with the one chosen in the Encoder
31
54
  @headers = options.fetch(:headers, {})
@@ -48,21 +71,19 @@ module Datadog
48
71
  case endpoint
49
72
  when :services
50
73
  payload = @encoder.encode_services(data)
51
- status_code = post(@services_endpoint, payload)
74
+ status_code = post(@api[:services_endpoint], payload)
52
75
  when :traces
53
76
  count = data.length
54
77
  payload = @encoder.encode_traces(data)
55
- status_code = post(@traces_endpoint, payload, count)
78
+ status_code = post(@api[:traces_endpoint], payload, count)
56
79
  else
57
80
  Datadog::Tracer.log.error("Unsupported endpoint: #{endpoint}")
58
81
  return nil
59
82
  end
60
83
 
61
- return status_code unless downgrade?(status_code) && !@compatibility_mode
84
+ downgrade! && send(endpoint, data) if downgrade?(status_code)
62
85
 
63
- # the API endpoint is not available so we should downgrade the connection and re-try the call
64
- downgrade!
65
- send(endpoint, data)
86
+ status_code
66
87
  end
67
88
 
68
89
  # send data to the trace-agent; the method is thread-safe
@@ -84,11 +105,13 @@ module Datadog
84
105
  # this method should target a stable API that works whatever is the agent
85
106
  # or the tracing client versions.
86
107
  def downgrade!
87
- @compatibility_mode = true
88
- @traces_endpoint = '/v0.2/traces'.freeze
89
- @services_endpoint = '/v0.2/services'.freeze
90
- @encoder = Datadog::Encoding::JSONEncoder.new()
91
- @headers['Content-Type'] = @encoder.content_type
108
+ @mutex.synchronize do
109
+ fallback_version = @api.fetch(:fallback)
110
+
111
+ @api = API.fetch(fallback_version)
112
+ @encoder = @api[:encoder].new
113
+ @headers['Content-Type'] = @encoder.content_type
114
+ end
92
115
  end
93
116
 
94
117
  def informational?(code)
@@ -119,6 +142,8 @@ module Datadog
119
142
  # endpoint. In both cases, we're going to downgrade the transporter encoder so that
120
143
  # it will target a stable API.
121
144
  def downgrade?(code)
145
+ return unless @api[:fallback]
146
+
122
147
  code == 404 || code == 415
123
148
  end
124
149
 
@@ -141,6 +166,8 @@ module Datadog
141
166
  @mutex.synchronize { @count_server_error += 1 }
142
167
  end
143
168
 
169
+ process_callback(response)
170
+
144
171
  status_code
145
172
  rescue StandardError => e
146
173
  Datadog::Tracer.log.error(e.message)
@@ -158,5 +185,15 @@ module Datadog
158
185
  }
159
186
  end
160
187
  end
188
+
189
+ private
190
+
191
+ def process_callback(response)
192
+ return unless @response_callback && @response_callback.respond_to?(:call)
193
+
194
+ @response_callback.call(response)
195
+ rescue => e
196
+ Tracer.log.debug("Error processing callback: #{e}")
197
+ end
161
198
  end
162
199
  end