ddtrace 0.41.0 → 0.42.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bbda933f09b8f29d20edc57d6825f6491158161b7e5164252deb006931e0e7d
4
- data.tar.gz: b3aaa6718e835fbfe97db64faa62f82b2f3ff76aefadb53cb27fb9b236b842e3
3
+ metadata.gz: 356d9acad651e21531cb323e2894fd187a02e91bd1c68973bf8030b2aa1512a3
4
+ data.tar.gz: e9997b324030189490fc43e67dc3a9e4ba4ec9c334e9fe7a08c14ea66b25145b
5
5
  SHA512:
6
- metadata.gz: 60d239de8c63091ede6a810127ad580629428b96181982cf543e77f45a89087dfdd238b98418fe72f0e27ebefae6d5f730208c5f6149541dfe1d1c6923579826
7
- data.tar.gz: 1d3657bc28458d0b50d0c2c68d3397b20ef6b2d4c2a0adf2ec834f701fd2c7701bcdac7f6fbce913743482e5eb2492a6409baf7c9c37c3467677ead705fd752e
6
+ metadata.gz: 2e276f69c9c8c39c63c6b5c8abc7572d18d635f549f0a152acd2fbbd9a3cfe4cb151112fb2a928f0785c054a416087d97fd6650ce057b199598123f89df43e57
7
+ data.tar.gz: edc8ad3cd84b445c276f8d26f76ab9d404ce234154ea916fac94c65122628ce2b6cd6f74df450af19e231fe1d20a7f21ed8ee8707c70a7647f58fee51ee16912
data/Appraisals CHANGED
@@ -539,6 +539,16 @@ elsif Gem::Version.new('2.3.0') <= Gem::Version.new(RUBY_VERSION) \
539
539
  gem 'lograge'
540
540
  end
541
541
 
542
+ appraise 'resque2-redis3' do
543
+ gem 'redis', '< 4.0'
544
+ gem 'resque', '>= 2.0'
545
+ end
546
+
547
+ appraise 'resque2-redis4' do
548
+ gem 'redis', '>= 4.0'
549
+ gem 'resque', '>= 2.0'
550
+ end
551
+
542
552
  appraise 'contrib' do
543
553
  gem 'actionpack'
544
554
  gem 'actionview'
@@ -569,7 +579,7 @@ elsif Gem::Version.new('2.3.0') <= Gem::Version.new(RUBY_VERSION) \
569
579
  gem 'rake', '>= 12.3'
570
580
  gem 'redis', '< 4.0'
571
581
  gem 'rest-client'
572
- gem 'resque', '< 2.0'
582
+ gem 'resque'
573
583
  gem 'ruby-kafka', '>= 0.7.10'
574
584
  gem 'sequel'
575
585
  gem 'shoryuken'
@@ -628,6 +638,16 @@ elsif Gem::Version.new('2.4.0') <= Gem::Version.new(RUBY_VERSION) \
628
638
  gem 'lograge'
629
639
  end
630
640
 
641
+ appraise 'resque2-redis3' do
642
+ gem 'redis', '< 4.0'
643
+ gem 'resque', '>= 2.0'
644
+ end
645
+
646
+ appraise 'resque2-redis4' do
647
+ gem 'redis', '>= 4.0'
648
+ gem 'resque', '>= 2.0'
649
+ end
650
+
631
651
  appraise 'contrib' do
632
652
  gem 'actionpack'
633
653
  gem 'actionview'
@@ -658,7 +678,7 @@ elsif Gem::Version.new('2.4.0') <= Gem::Version.new(RUBY_VERSION) \
658
678
  gem 'rake', '>= 12.3'
659
679
  gem 'redis', '< 4.0'
660
680
  gem 'rest-client'
661
- gem 'resque', '< 2.0'
681
+ gem 'resque'
662
682
  gem 'ruby-kafka', '>= 0.7.10'
663
683
  gem 'sequel'
664
684
  gem 'shoryuken'
@@ -765,6 +785,16 @@ elsif Gem::Version.new('2.5.0') <= Gem::Version.new(RUBY_VERSION) \
765
785
  gem 'lograge'
766
786
  end
767
787
 
788
+ appraise 'resque2-redis3' do
789
+ gem 'redis', '< 4.0'
790
+ gem 'resque', '>= 2.0'
791
+ end
792
+
793
+ appraise 'resque2-redis4' do
794
+ gem 'redis', '>= 4.0'
795
+ gem 'resque', '>= 2.0'
796
+ end
797
+
768
798
  appraise 'contrib' do
769
799
  gem 'actionpack'
770
800
  gem 'actionview'
@@ -796,7 +826,7 @@ elsif Gem::Version.new('2.5.0') <= Gem::Version.new(RUBY_VERSION) \
796
826
  gem 'rake', '>= 12.3'
797
827
  gem 'redis', '< 4.0'
798
828
  gem 'rest-client'
799
- gem 'resque', '< 2.0'
829
+ gem 'resque'
800
830
  gem 'ruby-kafka', '>= 0.7.10'
801
831
  gem 'sequel'
802
832
  gem 'shoryuken'
@@ -893,6 +923,16 @@ elsif Gem::Version.new('2.6.0') <= Gem::Version.new(RUBY_VERSION) \
893
923
  gem 'lograge'
894
924
  end
895
925
 
926
+ appraise 'resque2-redis3' do
927
+ gem 'redis', '< 4.0'
928
+ gem 'resque', '>= 2.0'
929
+ end
930
+
931
+ appraise 'resque2-redis4' do
932
+ gem 'redis', '>= 4.0'
933
+ gem 'resque', '>= 2.0'
934
+ end
935
+
896
936
  appraise 'contrib' do
897
937
  gem 'actionpack'
898
938
  gem 'actionview'
@@ -922,7 +962,7 @@ elsif Gem::Version.new('2.6.0') <= Gem::Version.new(RUBY_VERSION) \
922
962
  gem 'rake', '>= 12.3'
923
963
  gem 'redis', '< 4.0'
924
964
  gem 'rest-client'
925
- gem 'resque', '< 2.0'
965
+ gem 'resque'
926
966
  gem 'ruby-kafka', '>= 0.7.10'
927
967
  gem 'sequel'
928
968
  gem 'shoryuken'
@@ -1023,6 +1063,16 @@ elsif Gem::Version.new('2.7.0') <= Gem::Version.new(RUBY_VERSION)
1023
1063
  gem 'lograge'
1024
1064
  end
1025
1065
 
1066
+ appraise 'resque2-redis3' do
1067
+ gem 'redis', '< 4.0'
1068
+ gem 'resque', '>= 2.0'
1069
+ end
1070
+
1071
+ appraise 'resque2-redis4' do
1072
+ gem 'redis', '>= 4.0'
1073
+ gem 'resque', '>= 2.0'
1074
+ end
1075
+
1026
1076
  appraise 'contrib' do
1027
1077
  gem 'actionpack'
1028
1078
  gem 'actionview'
@@ -1051,7 +1101,7 @@ elsif Gem::Version.new('2.7.0') <= Gem::Version.new(RUBY_VERSION)
1051
1101
  gem 'rake', '>= 12.3'
1052
1102
  gem 'redis', '< 4.0'
1053
1103
  gem 'rest-client'
1054
- gem 'resque', '< 2.0'
1104
+ gem 'resque'
1055
1105
  gem 'ruby-kafka', '>= 0.7.10'
1056
1106
  gem 'sequel'
1057
1107
  gem 'shoryuken'
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.42.0] - 2020-10-21
6
+
7
+ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.42.0
8
+
9
+ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.41.0...v0.42.0
10
+
11
+ ### Added
12
+
13
+ - Increase Resque support to include 2.0 (#1213) (@erict-square)
14
+
15
+ - Improve gRPC Propagator to support metadata array values (#1203) (@mdehoog)
16
+
17
+ - Add CPU benchmarks, diagnostics to tests (#1188, #1198)
18
+
19
+ - Access active correlation by Thread (#1200)
20
+
21
+ - Improve delayed_job instrumentation (#1187) (@norbertnytko)
22
+
23
+ ### Changed
24
+
25
+ ### Fixed
26
+
27
+ - Improve Rails `log_injection` option to support more Lograge formats (#1210) (@Supy)
28
+
29
+ - Fix Changelog (#1199) (@y-yagi)
30
+
31
+ ### Refactored
32
+
33
+ - Refactor Trace buffer into multiple components (#1195)
34
+
5
35
  ## [0.41.0] - 2020-09-30
6
36
 
7
37
  Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.41.0
@@ -17,7 +47,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.40.0...v0.41.0
17
47
  - Add peer.service tag to external services and skip tagging external services with language tag for runtime metrics (#934, #935, #1180)
18
48
  - This helps support the way runtime metrics are associated with spans in the UI.
19
49
  - Faster TraceBuffer for CRuby (#1172)
20
- - Reduce memory usage during gem statup (#1090)
50
+ - Reduce memory usage during gem startup (#1090)
21
51
  - Reduce memory usage of the HTTP transport (#1165)
22
52
 
23
53
  ### Fixed
@@ -56,7 +56,7 @@ We welcome code contributions to the library, which you can [submit as a pull re
56
56
  2. **Make any changes** for your patch.
57
57
  3. **Write tests** that demonstrate how the feature works or how the bug is fixed.
58
58
  4. **Update any documentation** such as `docs/GettingStarted.md`, especially for new features.
59
- 5. **Submit the pull request** from your fork back to https://github.com/DataDog/dd-trace-rb. Bugs should merge back to `master`, and new features should merge back to the latest `dev` branch (e.g. `0.24-dev`).
59
+ 5. **Submit the pull request** from your fork back to the latest revision of the `master` branch on https://github.com/DataDog/dd-trace-rb.
60
60
 
61
61
  The pull request will be run through our CI pipeline, and a project member will review the changes with you. At a minimum, to be accepted and merged, pull requests must:
62
62
 
data/Rakefile CHANGED
@@ -464,6 +464,10 @@ task :ci do
464
464
  declare 'bundle exec appraisal rails4-postgres rake spec:rails'
465
465
  declare 'bundle exec appraisal rails5-mysql2 rake spec:rails'
466
466
  declare 'bundle exec appraisal rails5-postgres rake spec:rails'
467
+
468
+ # explicitly test resque-2x compatability
469
+ declare 'bundle exec appraisal resque2-redis3 rake spec:resque'
470
+ declare 'bundle exec appraisal resque2-redis4 rake spec:resque'
467
471
  end
468
472
  elsif Gem::Version.new('2.4.0') <= Gem::Version.new(RUBY_VERSION) \
469
473
  && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
@@ -525,6 +529,10 @@ task :ci do
525
529
  # Rails specs
526
530
  declare 'bundle exec appraisal rails5-mysql2 rake spec:rails'
527
531
  declare 'bundle exec appraisal rails5-postgres rake spec:rails'
532
+
533
+ # explicitly test resque-2x compatability
534
+ declare 'bundle exec appraisal resque2-redis3 rake spec:resque'
535
+ declare 'bundle exec appraisal resque2-redis4 rake spec:resque'
528
536
  end
529
537
  elsif Gem::Version.new('2.5.0') <= Gem::Version.new(RUBY_VERSION) \
530
538
  && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6.0')
@@ -594,6 +602,10 @@ task :ci do
594
602
  # declare 'bundle exec appraisal rails6-mysql2 rake spec:action_cable' # TODO: Hangs CI jobs... fix and re-enable.
595
603
  declare 'bundle exec appraisal rails6-mysql2 rake spec:rails'
596
604
  declare 'bundle exec appraisal rails6-postgres rake spec:rails'
605
+
606
+ # explicitly test resque-2x compatability
607
+ declare 'bundle exec appraisal resque2-redis3 rake spec:resque'
608
+ declare 'bundle exec appraisal resque2-redis4 rake spec:resque'
597
609
  elsif Gem::Version.new('2.6.0') <= Gem::Version.new(RUBY_VERSION) \
598
610
  && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0')
599
611
  # Main library
@@ -664,6 +676,10 @@ task :ci do
664
676
  # declare 'bundle exec appraisal rails6-mysql2 rake spec:action_cable' # TODO: Hangs CI jobs... fix and re-enable.
665
677
  declare 'bundle exec appraisal rails6-mysql2 rake spec:rails'
666
678
  declare 'bundle exec appraisal rails6-postgres rake spec:rails'
679
+
680
+ # explicitly test resque-2x compatability
681
+ declare 'bundle exec appraisal resque2-redis3 rake spec:resque'
682
+ declare 'bundle exec appraisal resque2-redis4 rake spec:resque'
667
683
  end
668
684
  elsif Gem::Version.new('2.7.0') <= Gem::Version.new(RUBY_VERSION)
669
685
  # Main library
@@ -732,6 +748,10 @@ task :ci do
732
748
  declare 'bundle exec appraisal rails5-postgres rake spec:rails'
733
749
  declare 'bundle exec appraisal rails6-mysql2 rake spec:rails'
734
750
  declare 'bundle exec appraisal rails6-postgres rake spec:rails'
751
+
752
+ # explicitly test resque-2x compatability
753
+ declare 'bundle exec appraisal resque2-redis3 rake spec:resque'
754
+ declare 'bundle exec appraisal resque2-redis4 rake spec:resque'
735
755
  end
736
756
  end
737
757
  end
@@ -46,6 +46,7 @@ Gem::Specification.new do |spec|
46
46
  spec.add_development_dependency 'rubocop', '= 0.49.1' if RUBY_VERSION >= '2.1.0'
47
47
  spec.add_development_dependency 'rspec', '~> 3.0'
48
48
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1.1'
49
+ spec.add_development_dependency 'ruby-prof', '~> 1.4' if RUBY_PLATFORM != 'java' && RUBY_VERSION >= '2.4.0'
49
50
  spec.add_development_dependency 'minitest', '= 5.10.1'
50
51
  spec.add_development_dependency 'minitest-around', '0.5.0'
51
52
  spec.add_development_dependency 'minitest-stub_any_instance', '1.0.2'
@@ -124,6 +124,16 @@ The trace library uses Rubocop to enforce [code style](https://github.com/bbatso
124
124
  $ bundle exec rake rubocop
125
125
  ```
126
126
 
127
+ ### Running benchmarks
128
+
129
+ If your changes can have a measurable performance impact, we recommend running our benchmark suite:
130
+
131
+ ```
132
+ $ bundle exec rake spec:benchmark
133
+ ```
134
+
135
+ Results are printed to STDOUT as well as written to the `./tmp/benchmark/` directory.
136
+
127
137
  ## Appendix
128
138
 
129
139
  ### Writing new integrations
@@ -359,7 +359,7 @@ For a list of available integrations, and their configuration options, please re
359
359
  | Rails | `rails` | `>= 3.0` | `>= 3.0` | *[Link](#rails)* | *[Link](https://github.com/rails/rails)* |
360
360
  | Rake | `rake` | `>= 12.0` | `>= 12.0` | *[Link](#rake)* | *[Link](https://github.com/ruby/rake)* |
361
361
  | Redis | `redis` | `>= 3.2` | `>= 3.2` | *[Link](#redis)* | *[Link](https://github.com/redis/redis-rb)* |
362
- | Resque | `resque` | `>= 1.0, < 2.0` | `>= 1.0, < 2.0` | *[Link](#resque)* | *[Link](https://github.com/resque/resque)* |
362
+ | Resque | `resque` | `>= 1.0` | `>= 1.0` | *[Link](#resque)* | *[Link](https://github.com/resque/resque)* |
363
363
  | Rest Client | `rest-client` | `>= 1.8` | `>= 1.8` | *[Link](#rest-client)* | *[Link](https://github.com/rest-client/rest-client)* |
364
364
  | Sequel | `sequel` | `>= 3.41` | `>= 3.41` | *[Link](#sequel)* | *[Link](https://github.com/jeremyevans/sequel)* |
365
365
  | Shoryuken | `shoryuken` | `>= 3.2` | `>= 3.2` | *[Link](#shoryuken)* | *[Link](https://github.com/phstc/shoryuken)* |
@@ -617,7 +617,7 @@ Where `options` is an optional `Hash` that accepts the following parameters:
617
617
 
618
618
  ### DelayedJob
619
619
 
620
- The DelayedJob integration uses lifecycle hooks to trace the job executions.
620
+ The DelayedJob integration uses lifecycle hooks to trace the job executions and enqueues.
621
621
 
622
622
  You can enable it through `Datadog.configure`:
623
623
 
@@ -635,6 +635,7 @@ Where `options` is an optional `Hash` that accepts the following parameters:
635
635
  | --- | ----------- | ------- |
636
636
  | `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` |
637
637
  | `service_name` | Service name used for `DelayedJob` instrumentation | `'delayed_job'` |
638
+ | `client_service_name` | Service name used for client-side `DelayedJob` instrumentation | `'delayed_job-client'` |
638
639
 
639
640
  ### Elasticsearch
640
641
 
@@ -2045,6 +2046,8 @@ Datadog.configure do |c|
2045
2046
  end
2046
2047
  ```
2047
2048
 
2049
+ _Note:_ For `lograge` users who have also defined `lograge.custom_options` in an `initializers/lograge.rb` configuration file, due to the order that Rails loads initializers (alphabetical), automatic trace correlation may not take effect, since `initializers/datadog.rb` would be overwritten by the `initializers/lograge.rb` initializer. To support automatic trace correlation with _existing_ `lograge.custom_options`, use the [Manual (Lograge)](#manual-lograge) configuration below.
2050
+
2048
2051
  ##### Manual (Lograge)
2049
2052
 
2050
2053
  After [setting up Lograge in a Rails application](https://docs.datadoghq.com/logs/log_collection/ruby/), manually modify the `custom_options` block in your environment configuration file (e.g. `config/environments/production.rb`) to add the trace IDs.
@@ -5,132 +5,175 @@ require 'ddtrace/runtime/object_space'
5
5
  # Trace buffer that accumulates traces for a consumer.
6
6
  # Consumption can happen from a different thread.
7
7
  module Datadog
8
- # Aggregate metrics:
9
- # They reflect buffer activity since last #pop.
10
- # These may not be as accurate or as granular, but they
11
- # don't use as much network traffic as live stats.
12
- class MeasuredBuffer
13
- def initialize
14
- @buffer_accepted = 0
15
- @buffer_accepted_lengths = 0
16
- @buffer_dropped = 0
17
- @buffer_spans = 0
8
+ # Buffer that stores objects. The buffer has a maximum size and when
9
+ # the buffer is full, a random object is discarded.
10
+ class Buffer
11
+ def initialize(max_size)
12
+ @max_size = max_size
13
+ @items = []
14
+ @closed = false
18
15
  end
19
16
 
20
- def measure_accept(trace)
21
- @buffer_accepted += 1
22
- @buffer_accepted_lengths += trace.length
17
+ # Add a new ``item`` in the local queue. This method doesn't block the execution
18
+ # even if the buffer is full. In that case, a random item is discarded.
19
+ def push(item)
20
+ return if closed?
21
+ full? ? replace!(item) : add!(item)
22
+ item
23
+ end
23
24
 
24
- @buffer_spans += trace.length
25
- rescue StandardError => e
26
- Datadog.logger.debug("Failed to measure queue accept. Cause: #{e.message} Source: #{e.backtrace.first}")
25
+ # A bulk push alternative to +#push+. Use this method if
26
+ # pushing more than one item for efficiency.
27
+ def concat(items)
28
+ return if closed?
29
+
30
+ # Segment items into underflow and overflow
31
+ underflow, overflow = overflow_segments(items)
32
+
33
+ # Concatenate items do not exceed capacity.
34
+ add_all!(underflow) unless underflow.nil?
35
+
36
+ # Iteratively replace items, to ensure pseudo-random replacement.
37
+ overflow.each { |item| replace!(item) } unless overflow.nil?
27
38
  end
28
39
 
29
- def measure_drop(trace)
30
- @buffer_dropped += 1
40
+ # Stored items are returned and the local buffer is reset.
41
+ def pop
42
+ drain!
43
+ end
31
44
 
32
- @buffer_spans -= trace.length
33
- rescue StandardError => e
34
- Datadog.logger.debug("Failed to measure queue drop. Cause: #{e.message} Source: #{e.backtrace.first}")
45
+ # Return the current number of stored traces.
46
+ def length
47
+ @items.length
35
48
  end
36
49
 
37
- def measure_pop(traces)
38
- # Accepted, cumulative totals
39
- Datadog.health_metrics.queue_accepted(@buffer_accepted)
40
- Datadog.health_metrics.queue_accepted_lengths(@buffer_accepted_lengths)
50
+ # Return if the buffer is empty.
51
+ def empty?
52
+ @items.empty?
53
+ end
41
54
 
42
- # Dropped, cumulative totals
43
- Datadog.health_metrics.queue_dropped(@buffer_dropped)
44
- # TODO: are we missing a +queue_dropped_lengths+ metric?
55
+ # Closes this buffer, preventing further pushing.
56
+ # Draining is still allowed.
57
+ def close
58
+ @closed = true
59
+ end
45
60
 
46
- # Queue gauges, current values
47
- Datadog.health_metrics.queue_max_length(@max_size)
48
- Datadog.health_metrics.queue_spans(@buffer_spans)
49
- Datadog.health_metrics.queue_length(traces.length)
61
+ def closed?
62
+ @closed
63
+ end
50
64
 
51
- # Reset aggregated metrics
52
- @buffer_accepted = 0
53
- @buffer_accepted_lengths = 0
54
- @buffer_dropped = 0
55
- @buffer_spans = 0
56
- rescue StandardError => e
57
- Datadog.logger.debug("Failed to measure queue. Cause: #{e.message} Source: #{e.backtrace.first}")
65
+ protected
66
+
67
+ # Segment items into two distinct segments: underflow and overflow.
68
+ # Underflow are items that will fit into buffer.
69
+ # Overflow are items that will exceed capacity, after underflow is added.
70
+ # Returns each array, and nil if there is no underflow/overflow.
71
+ def overflow_segments(items)
72
+ underflow = nil
73
+ overflow = nil
74
+
75
+ overflow_size = @max_size > 0 ? (@items.length + items.length) - @max_size : 0
76
+
77
+ if overflow_size > 0
78
+ # Items will overflow
79
+ if overflow_size < items.length
80
+ # Partial overflow
81
+ underflow_end_index = items.length - overflow_size - 1
82
+ underflow = items[0..underflow_end_index]
83
+ overflow = items[(underflow_end_index + 1)..-1]
84
+ else
85
+ # Total overflow
86
+ overflow = items
87
+ end
88
+ else
89
+ # Items do not exceed capacity.
90
+ underflow = items
91
+ end
92
+
93
+ [underflow, overflow]
94
+ end
95
+
96
+ def full?
97
+ @max_size > 0 && @items.length >= @max_size
98
+ end
99
+
100
+ def add_all!(items)
101
+ @items.concat(items)
102
+ end
103
+
104
+ def add!(item)
105
+ @items << item
106
+ end
107
+
108
+ def replace!(item)
109
+ # Choose random item to be replaced
110
+ replace_index = rand(@items.length)
111
+
112
+ # Replace random item
113
+ discarded_item = @items[replace_index]
114
+ @items[replace_index] = item
115
+
116
+ # Return discarded item
117
+ discarded_item
118
+ end
119
+
120
+ def drain!
121
+ items = @items
122
+ @items = []
123
+ items
58
124
  end
59
125
  end
60
126
 
61
- # Trace buffer that stores application traces and
127
+ # Buffer that stores objects, has a maximum size, and
62
128
  # can be safely used concurrently on any environment.
63
129
  #
64
130
  # This implementation uses a {Mutex} around public methods, incurring
65
- # overhead in order to ensure full thread-safety.
131
+ # overhead in order to ensure thread-safety.
66
132
  #
67
133
  # This is implementation is recommended for non-CRuby environments.
68
- # If using CRuby, {Datadog::CRubyTraceBuffer} is a faster implementation with minimal compromise.
69
- class ThreadSafeBuffer < MeasuredBuffer
134
+ # If using CRuby, {Datadog::CRubyBuffer} is a faster implementation with minimal compromise.
135
+ class ThreadSafeBuffer < Buffer
70
136
  def initialize(max_size)
71
- super()
72
-
73
- @max_size = max_size
137
+ super
74
138
 
75
- @mutex = Mutex.new()
76
- @traces = []
77
- @closed = false
139
+ @mutex = Mutex.new
78
140
  end
79
141
 
80
- # Add a new ``trace`` in the local queue. This method doesn't block the execution
81
- # even if the buffer is full. In that case, a random trace is discarded.
82
- def push(trace)
83
- @mutex.synchronize do
84
- return if @closed
85
- len = @traces.length
86
- if len < @max_size || @max_size <= 0
87
- @traces << trace
88
- else
89
- # we should replace a random trace with the new one
90
- replace_index = rand(len)
91
- replaced_trace = @traces[replace_index]
92
- @traces[replace_index] = trace
93
- measure_drop(replaced_trace)
94
- end
142
+ # Add a new ``item`` in the local queue. This method doesn't block the execution
143
+ # even if the buffer is full. In that case, a random item is discarded.
144
+ def push(item)
145
+ synchronize { super }
146
+ end
95
147
 
96
- measure_accept(trace)
97
- end
148
+ def concat(items)
149
+ synchronize { super }
98
150
  end
99
151
 
100
152
  # Return the current number of stored traces.
101
153
  def length
102
- @mutex.synchronize do
103
- return @traces.length
104
- end
154
+ synchronize { super }
105
155
  end
106
156
 
107
157
  # Return if the buffer is empty.
108
158
  def empty?
109
- @mutex.synchronize do
110
- return @traces.empty?
111
- end
159
+ synchronize { super }
112
160
  end
113
161
 
114
162
  # Stored traces are returned and the local buffer is reset.
115
163
  def pop
116
- @mutex.synchronize do
117
- traces = @traces
118
- @traces = []
119
-
120
- measure_pop(traces)
121
-
122
- return traces
123
- end
164
+ synchronize { super }
124
165
  end
125
166
 
126
167
  def close
127
- @mutex.synchronize do
128
- @closed = true
129
- end
168
+ synchronize { super }
169
+ end
170
+
171
+ def synchronize
172
+ @mutex.synchronize { yield }
130
173
  end
131
174
  end
132
175
 
133
- # Trace buffer that stores application traces and
176
+ # Buffer that stores objects, has a maximum size, and
134
177
  # can be safely used concurrently with CRuby.
135
178
  #
136
179
  # Under extreme concurrency scenarios, this class can exceed
@@ -151,80 +194,133 @@ module Datadog
151
194
  #
152
195
  # @see spec/ddtrace/benchmark/buffer_benchmark_spec.rb Buffer benchmarks
153
196
  # @see https://github.com/ruby-concurrency/concurrent-ruby/blob/c1114a0c6891d9634f019f1f9fe58dcae8658964/lib/concurrent-ruby/concurrent/array.rb#L23-L27
154
- class CRubyTraceBuffer < MeasuredBuffer
155
- def initialize(max_size)
156
- super()
197
+ class CRubyBuffer < Buffer
198
+ # Add a new ``trace`` in the local queue. This method doesn't block the execution
199
+ # even if the buffer is full. In that case, a random trace is discarded.
200
+ def replace!(item)
201
+ # we should replace a random trace with the new one
202
+ replace_index = rand(@items.size)
203
+ replaced_trace = @items.delete_at(replace_index)
204
+ @items << item
205
+
206
+ # We might have deleted an element right when the buffer
207
+ # was drained, thus +replaced_trace+ will be +nil+.
208
+ # In that case, nothing was replaced, and this method
209
+ # performed a simple insertion into the buffer.
210
+ replaced_trace
211
+ end
212
+ end
157
213
 
158
- @max_size = max_size
214
+ # Health metrics for trace buffers.
215
+ module MeasuredBuffer
216
+ def initialize(*_)
217
+ super
159
218
 
160
- @traces = []
161
- @closed = false
219
+ @buffer_accepted = 0
220
+ @buffer_accepted_lengths = 0
221
+ @buffer_dropped = 0
222
+ @buffer_spans = 0
162
223
  end
163
224
 
164
- # Add a new ``trace`` in the local queue. This method doesn't block the execution
165
- # even if the buffer is full. In that case, a random trace is discarded.
166
- def push(trace)
167
- return if @closed
168
- len = @traces.length
169
- if len < @max_size || @max_size <= 0
170
- @traces << trace
171
- else
172
- # we should replace a random trace with the new one
173
- replace_index = rand(len)
174
- replaced_trace = @traces.delete_at(replace_index)
175
- @traces << trace
176
-
177
- # Check if we deleted the element right when the buffer
178
- # was popped. In that case we didn't actually delete anything,
179
- # we just inserted into a newly cleared buffer instead.
180
- measure_drop(replaced_trace) if replaced_trace
181
- end
225
+ def add!(trace)
226
+ super
182
227
 
228
+ # Emit health metrics
183
229
  measure_accept(trace)
184
230
  end
185
231
 
186
- # Return the current number of stored traces.
187
- def length
188
- @traces.length
189
- end
232
+ def add_all!(traces)
233
+ super
190
234
 
191
- # Return if the buffer is empty.
192
- def empty?
193
- @traces.empty?
235
+ # Emit health metrics
236
+ traces.each { |trace| measure_accept(trace) }
194
237
  end
195
238
 
196
- # Return all traces stored and reset buffer.
197
- def pop
198
- traces = @traces.pop(VERY_LARGE_INTEGER)
239
+ def replace!(trace)
240
+ discarded_trace = super
199
241
 
200
- measure_pop(traces)
242
+ # Emit health metrics
243
+ measure_accept(trace)
244
+ measure_drop(discarded_trace) if discarded_trace
245
+
246
+ discarded_trace
247
+ end
201
248
 
249
+ # Stored traces are returned and the local buffer is reset.
250
+ def drain!
251
+ traces = super
252
+ measure_pop(traces)
202
253
  traces
203
254
  end
204
255
 
205
- # Very large value, to ensure that we drain the whole buffer.
206
- # 1<<62-1 happens to be the largest integer that can be stored inline in CRuby.
207
- VERY_LARGE_INTEGER = 1 << 62 - 1
256
+ def measure_accept(trace)
257
+ @buffer_accepted += 1
258
+ @buffer_accepted_lengths += trace.length
208
259
 
209
- def close
210
- @closed = true
260
+ @buffer_spans += trace.length
261
+ rescue StandardError => e
262
+ Datadog.logger.debug("Failed to measure queue accept. Cause: #{e.message} Source: #{e.backtrace.first}")
211
263
  end
264
+
265
+ def measure_drop(trace)
266
+ @buffer_dropped += 1
267
+
268
+ @buffer_spans -= trace.length
269
+ rescue StandardError => e
270
+ Datadog.logger.debug("Failed to measure queue drop. Cause: #{e.message} Source: #{e.backtrace.first}")
271
+ end
272
+
273
+ def measure_pop(traces)
274
+ # Accepted, cumulative totals
275
+ Datadog.health_metrics.queue_accepted(@buffer_accepted)
276
+ Datadog.health_metrics.queue_accepted_lengths(@buffer_accepted_lengths)
277
+
278
+ # Dropped, cumulative totals
279
+ Datadog.health_metrics.queue_dropped(@buffer_dropped)
280
+ # TODO: are we missing a +queue_dropped_lengths+ metric?
281
+
282
+ # Queue gauges, current values
283
+ Datadog.health_metrics.queue_max_length(@max_size)
284
+ Datadog.health_metrics.queue_spans(@buffer_spans)
285
+ Datadog.health_metrics.queue_length(traces.length)
286
+
287
+ # Reset aggregated metrics
288
+ @buffer_accepted = 0
289
+ @buffer_accepted_lengths = 0
290
+ @buffer_dropped = 0
291
+ @buffer_spans = 0
292
+ rescue StandardError => e
293
+ Datadog.logger.debug("Failed to measure queue. Cause: #{e.message} Source: #{e.backtrace.first}")
294
+ end
295
+ end
296
+
297
+ # Trace buffer that stores application traces, has a maximum size, and
298
+ # can be safely used concurrently on any environment.
299
+ #
300
+ # @see {Datadog::ThreadSafeBuffer}
301
+ class ThreadSafeTraceBuffer < ThreadSafeBuffer
302
+ prepend MeasuredBuffer
212
303
  end
213
304
 
214
- # Choose default TraceBuffer implementation for current platform.
215
- BUFFER_IMPLEMENTATION = if Datadog::Ext::Runtime::RUBY_ENGINE == 'ruby'
216
- CRubyTraceBuffer
217
- else
218
- ThreadSafeBuffer
219
- end
220
- private_constant :BUFFER_IMPLEMENTATION
305
+ # Trace buffer that stores application traces, has a maximum size, and
306
+ # can be safely used concurrently with CRuby.
307
+ #
308
+ # @see {Datadog::CRubyBuffer}
309
+ class CRubyTraceBuffer < CRubyBuffer
310
+ prepend MeasuredBuffer
311
+ end
221
312
 
222
313
  # Trace buffer that stores application traces. The buffer has a maximum size and when
223
314
  # the buffer is full, a random trace is discarded. This class is thread-safe and is used
224
315
  # automatically by the ``Tracer`` instance when a ``Span`` is finished.
225
316
  #
317
+ # We choose the default TraceBuffer implementation for current platform dynamically here.
318
+ #
226
319
  # TODO We should restructure this module, so that classes are not declared at top-level ::Datadog.
227
320
  # TODO Making such a change is potentially breaking for users manually configuring the tracer.
228
- class TraceBuffer < BUFFER_IMPLEMENTATION
229
- end
321
+ TraceBuffer = if Datadog::Ext::Runtime::RUBY_ENGINE == 'ruby' # rubocop:disable Style/ConstantName
322
+ CRubyTraceBuffer
323
+ else
324
+ ThreadSafeTraceBuffer
325
+ end
230
326
  end
@@ -13,9 +13,9 @@ module Datadog
13
13
  @context.local = ctx
14
14
  end
15
15
 
16
- # Return the current context.
17
- def context
18
- @context.local
16
+ # Return the local context.
17
+ def context(key = nil)
18
+ key.nil? ? @context.local : @context.local(key)
19
19
  end
20
20
  end
21
21
 
@@ -43,8 +43,9 @@ module Datadog
43
43
  end
44
44
 
45
45
  # Return the thread-local context.
46
- def local
47
- Thread.current[@key] ||= Datadog::Context.new
46
+ def local(thread = Thread.current)
47
+ raise ArgumentError, '\'thread\' must be a Thread.' unless thread.is_a?(Thread)
48
+ thread[@key] ||= Datadog::Context.new
48
49
  end
49
50
  end
50
51
  end
@@ -23,6 +23,7 @@ module Datadog
23
23
  end
24
24
 
25
25
  option :service_name, default: Ext::SERVICE_NAME
26
+ option :client_service_name, default: Ext::CLIENT_SERVICE_NAME
26
27
  end
27
28
  end
28
29
  end
@@ -10,7 +10,9 @@ module Datadog
10
10
  ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_DELAYED_JOB_ANALYTICS_SAMPLE_RATE'.freeze
11
11
  ENV_ANALYTICS_SAMPLE_RATE_OLD = 'DD_DELAYED_JOB_ANALYTICS_SAMPLE_RATE'.freeze
12
12
  SERVICE_NAME = 'delayed_job'.freeze
13
+ CLIENT_SERVICE_NAME = 'delayed_job-client'.freeze
13
14
  SPAN_JOB = 'delayed_job'.freeze
15
+ SPAN_ENQUEUE = 'delayed_job.enqueue'.freeze
14
16
  TAG_ATTEMPTS = 'delayed_job.attempts'.freeze
15
17
  TAG_ID = 'delayed_job.id'.freeze
16
18
  TAG_PRIORITY = 'delayed_job.priority'.freeze
@@ -7,22 +7,11 @@ module Datadog
7
7
  module DelayedJob
8
8
  # DelayedJob plugin that instruments invoke_job hook
9
9
  class Plugin < Delayed::Plugin
10
- def self.instrument(job, &block)
10
+ def self.instrument_invoke(job, &block)
11
11
  return block.call(job) unless tracer && tracer.enabled
12
12
 
13
- # When DelayedJob is used through ActiveJob, we need to parse the payload differentely
14
- # to get the actual job name
15
- job_name = if job.payload_object.respond_to?(:job_data)
16
- job.payload_object.job_data['job_class']
17
- else
18
- job.name
19
- end
20
-
21
- tracer.trace(Ext::SPAN_JOB, service: configuration[:service_name], resource: job_name) do |span|
22
- # Set analytics sample rate
23
- if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
24
- Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
25
- end
13
+ tracer.trace(Ext::SPAN_JOB, service: configuration[:service_name], resource: job_name(job)) do |span|
14
+ set_sample_rate(span)
26
15
 
27
16
  # Measure service stats
28
17
  Contrib::Analytics.set_measured(span)
@@ -37,6 +26,23 @@ module Datadog
37
26
  end
38
27
  end
39
28
 
29
+ def self.instrument_enqueue(job, &block)
30
+ return block.call(job) unless tracer && tracer.enabled
31
+
32
+ tracer.trace(Ext::SPAN_ENQUEUE, service: configuration[:client_service_name], resource: job_name(job)) do |span|
33
+ set_sample_rate(span)
34
+
35
+ # Measure service stats
36
+ Contrib::Analytics.set_measured(span)
37
+
38
+ span.set_tag(Ext::TAG_QUEUE, job.queue) if job.queue
39
+ span.set_tag(Ext::TAG_PRIORITY, job.priority)
40
+ span.span_type = Datadog::Ext::AppTypes::WORKER
41
+
42
+ yield job
43
+ end
44
+ end
45
+
40
46
  def self.flush(worker, &block)
41
47
  yield worker
42
48
 
@@ -51,8 +57,24 @@ module Datadog
51
57
  configuration[:tracer]
52
58
  end
53
59
 
60
+ def self.job_name(job)
61
+ # When DelayedJob is used through ActiveJob, we need to parse the payload differentely
62
+ # to get the actual job name
63
+ return job.payload_object.job_data['job_class'] if job.payload_object.respond_to?(:job_data)
64
+
65
+ job.name
66
+ end
67
+
68
+ def self.set_sample_rate(span)
69
+ # Set analytics sample rate
70
+ if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
71
+ Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
72
+ end
73
+ end
74
+
54
75
  callbacks do |lifecycle|
55
- lifecycle.around(:invoke_job, &method(:instrument))
76
+ lifecycle.around(:invoke_job, &method(:instrument_invoke))
77
+ lifecycle.around(:enqueue, &method(:instrument_enqueue))
56
78
  lifecycle.around(:execute, &method(:flush))
57
79
  end
58
80
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module GRPC
9
9
  module DatadogInterceptor
10
10
  # The DatadogInterceptor::Client implements the tracing strategy
11
- # for gRPC client-side endpoitns. This middleware compoent will
11
+ # for gRPC client-side endpoints. This middleware component will
12
12
  # inject trace context information into gRPC metadata prior to
13
13
  # sending the request to the server.
14
14
  class Client < Base
@@ -54,14 +54,25 @@ module Datadog
54
54
 
55
55
  def add_logger(app)
56
56
  # check if lograge key exists
57
- if app.config.respond_to?(:lograge) && app.config.lograge.enabled
57
+ # Note: Rails executes initializers sequentially based on alphabetical order,
58
+ # and lograge config could occur after dd config.
59
+ # Checking for `app.config.lograge.enabled` may yield a false negative.
60
+ # Instead we should naively add custom options if `config.lograge` exists from the lograge Railtie,
61
+ # since the custom options get ignored without lograge explicitly being enabled.
62
+ # See: https://github.com/roidrage/lograge/blob/1729eab7956bb95c5992e4adab251e4f93ff9280/lib/lograge/railtie.rb#L7-L12
63
+ if app.config.respond_to?(:lograge)
58
64
  Datadog::Contrib::Rails::LogInjection.add_lograge_logger(app)
59
- # if lograge isn't set, check if tagged logged is enabed.
65
+ end
66
+
67
+ # if lograge isn't set, check if tagged logged is enabled.
60
68
  # if so, add proc that injects trace identifiers for tagged logging.
61
- elsif (logger = app.config.logger) && logger.is_a?(::ActiveSupport::TaggedLogging)
69
+ if (logger = app.config.logger) &&
70
+ defined?(::ActiveSupport::TaggedLogging) &&
71
+ logger.is_a?(::ActiveSupport::TaggedLogging)
72
+
62
73
  Datadog::Contrib::Rails::LogInjection.add_as_tagged_logging_logger(app)
63
74
  else
64
- Datadog.logger.warn("Unabe to enable Datadog Trace context, Logger #{logger} is not supported")
75
+ Datadog.logger.warn("Unable to enable Datadog Trace context, Logger #{logger} is not supported")
65
76
  end
66
77
  end
67
78
 
@@ -11,7 +11,7 @@ module Datadog
11
11
 
12
12
  MINIMUM_VERSION = Gem::Version.new('1.0')
13
13
  # Maximum is first version it's NOT compatible with (not inclusive)
14
- MAXIMUM_VERSION = Gem::Version.new('2.0')
14
+ MAXIMUM_VERSION = Gem::Version.new('3.0')
15
15
 
16
16
  register_as :resque, auto_patch: true
17
17
 
@@ -38,24 +38,36 @@ module Datadog
38
38
  end
39
39
 
40
40
  def trace_id
41
- value = @metadata[GRPC_METADATA_TRACE_ID].to_i
41
+ value = metadata_for_key(GRPC_METADATA_TRACE_ID).to_i
42
42
  value if (1..Span::EXTERNAL_MAX_ID).cover? value
43
43
  end
44
44
 
45
45
  def parent_id
46
- value = @metadata[GRPC_METADATA_PARENT_ID].to_i
46
+ value = metadata_for_key(GRPC_METADATA_PARENT_ID).to_i
47
47
  value if (1..Span::EXTERNAL_MAX_ID).cover? value
48
48
  end
49
49
 
50
50
  def sampling_priority
51
- value = @metadata[GRPC_METADATA_SAMPLING_PRIORITY]
51
+ value = metadata_for_key(GRPC_METADATA_SAMPLING_PRIORITY)
52
52
  value && value.to_i
53
53
  end
54
54
 
55
55
  def origin
56
- value = @metadata[GRPC_METADATA_ORIGIN]
56
+ value = metadata_for_key(GRPC_METADATA_ORIGIN)
57
57
  value if value != ''
58
58
  end
59
+
60
+ private
61
+
62
+ def metadata_for_key(key)
63
+ # metadata values can be arrays (multiple headers with the same key)
64
+ value = @metadata[key]
65
+ if value.is_a?(Array)
66
+ value.first
67
+ else
68
+ value
69
+ end
70
+ end
59
71
  end
60
72
  end
61
73
  end
@@ -59,8 +59,8 @@ module Datadog
59
59
  #
60
60
  # This method makes use of a \ContextProvider that is automatically set during the tracer
61
61
  # initialization, or while using a library instrumentation.
62
- def call_context
63
- @provider.context
62
+ def call_context(key = nil)
63
+ @provider.context(key)
64
64
  end
65
65
 
66
66
  # Initialize a new \Tracer used to create, sample and submit spans that measure the
@@ -324,18 +324,18 @@ module Datadog
324
324
  end
325
325
 
326
326
  # Return the current active span or +nil+.
327
- def active_span
328
- call_context.current_span
327
+ def active_span(key = nil)
328
+ call_context(key).current_span
329
329
  end
330
330
 
331
331
  # Return the current active root span or +nil+.
332
- def active_root_span
333
- call_context.current_root_span
332
+ def active_root_span(key = nil)
333
+ call_context(key).current_root_span
334
334
  end
335
335
 
336
336
  # Return a CorrelationIdentifier for active span
337
- def active_correlation
338
- Datadog::Correlation.identifier_from_context(call_context)
337
+ def active_correlation(key = nil)
338
+ Datadog::Correlation.identifier_from_context(call_context(key))
339
339
  end
340
340
 
341
341
  # Send the trace to the writer to enqueue the spans list in the agent
@@ -1,7 +1,7 @@
1
1
  module Datadog
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 41
4
+ MINOR = 42
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
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: 0.41.0
4
+ version: 0.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-30 00:00:00.000000000 Z
11
+ date: 2020-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.1'
125
+ - !ruby/object:Gem::Dependency
126
+ name: ruby-prof
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.4'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.4'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: minitest
127
141
  requirement: !ruby/object:Gem::Requirement