ddtrace 0.41.0 → 0.42.0

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.
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