ddtrace 0.47.0 → 0.48.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +4 -2
  3. data/.circleci/images/primary/Dockerfile-2.0.0 +11 -1
  4. data/.circleci/images/primary/Dockerfile-2.1.10 +11 -1
  5. data/.circleci/images/primary/Dockerfile-2.2.10 +11 -1
  6. data/.circleci/images/primary/Dockerfile-2.3.8 +10 -0
  7. data/.circleci/images/primary/Dockerfile-2.4.6 +10 -0
  8. data/.circleci/images/primary/Dockerfile-2.5.6 +10 -0
  9. data/.circleci/images/primary/Dockerfile-2.6.4 +10 -0
  10. data/.circleci/images/primary/Dockerfile-2.7.0 +10 -0
  11. data/.circleci/images/primary/Dockerfile-jruby-9.2-latest +10 -0
  12. data/.gitlab-ci.yml +18 -18
  13. data/.rubocop.yml +19 -0
  14. data/.rubocop_todo.yml +44 -3
  15. data/Appraisals +55 -1
  16. data/CHANGELOG.md +47 -1
  17. data/Gemfile +10 -0
  18. data/Rakefile +9 -0
  19. data/bin/ddtracerb +15 -0
  20. data/ddtrace.gemspec +4 -2
  21. data/docs/GettingStarted.md +36 -53
  22. data/docs/ProfilingDevelopment.md +88 -0
  23. data/integration/README.md +1 -2
  24. data/integration/apps/rack/Dockerfile +3 -0
  25. data/integration/apps/rack/script/build-images +1 -1
  26. data/integration/apps/rack/script/ci +1 -1
  27. data/integration/apps/rails-five/script/build-images +1 -1
  28. data/integration/apps/rails-five/script/ci +1 -1
  29. data/integration/apps/ruby/script/build-images +1 -1
  30. data/integration/apps/ruby/script/ci +1 -1
  31. data/integration/images/include/http-health-check +1 -1
  32. data/integration/images/wrk/scripts/entrypoint.sh +1 -1
  33. data/integration/script/build-images +1 -1
  34. data/lib/ddtrace.rb +1 -0
  35. data/lib/ddtrace/configuration.rb +39 -13
  36. data/lib/ddtrace/configuration/components.rb +85 -3
  37. data/lib/ddtrace/configuration/settings.rb +31 -0
  38. data/lib/ddtrace/contrib/active_record/configuration/makara_resolver.rb +30 -0
  39. data/lib/ddtrace/contrib/active_record/configuration/resolver.rb +9 -3
  40. data/lib/ddtrace/contrib/resque/configuration/settings.rb +17 -1
  41. data/lib/ddtrace/contrib/resque/patcher.rb +4 -4
  42. data/lib/ddtrace/contrib/resque/resque_job.rb +22 -1
  43. data/lib/ddtrace/contrib/shoryuken/configuration/settings.rb +1 -0
  44. data/lib/ddtrace/contrib/shoryuken/tracer.rb +7 -3
  45. data/lib/ddtrace/diagnostics/environment_logger.rb +1 -1
  46. data/lib/ddtrace/error.rb +2 -0
  47. data/lib/ddtrace/ext/profiling.rb +52 -0
  48. data/lib/ddtrace/ext/transport.rb +1 -0
  49. data/lib/ddtrace/metrics.rb +4 -0
  50. data/lib/ddtrace/profiling.rb +54 -0
  51. data/lib/ddtrace/profiling/backtrace_location.rb +32 -0
  52. data/lib/ddtrace/profiling/buffer.rb +41 -0
  53. data/lib/ddtrace/profiling/collectors/stack.rb +253 -0
  54. data/lib/ddtrace/profiling/encoding/profile.rb +31 -0
  55. data/lib/ddtrace/profiling/event.rb +13 -0
  56. data/lib/ddtrace/profiling/events/stack.rb +102 -0
  57. data/lib/ddtrace/profiling/exporter.rb +23 -0
  58. data/lib/ddtrace/profiling/ext/cpu.rb +54 -0
  59. data/lib/ddtrace/profiling/ext/cthread.rb +134 -0
  60. data/lib/ddtrace/profiling/ext/forking.rb +97 -0
  61. data/lib/ddtrace/profiling/flush.rb +41 -0
  62. data/lib/ddtrace/profiling/pprof/builder.rb +121 -0
  63. data/lib/ddtrace/profiling/pprof/converter.rb +85 -0
  64. data/lib/ddtrace/profiling/pprof/message_set.rb +12 -0
  65. data/lib/ddtrace/profiling/pprof/payload.rb +18 -0
  66. data/lib/ddtrace/profiling/pprof/pprof.proto +212 -0
  67. data/lib/ddtrace/profiling/pprof/pprof_pb.rb +81 -0
  68. data/lib/ddtrace/profiling/pprof/stack_sample.rb +90 -0
  69. data/lib/ddtrace/profiling/pprof/string_table.rb +10 -0
  70. data/lib/ddtrace/profiling/pprof/template.rb +114 -0
  71. data/lib/ddtrace/profiling/preload.rb +3 -0
  72. data/lib/ddtrace/profiling/profiler.rb +28 -0
  73. data/lib/ddtrace/profiling/recorder.rb +87 -0
  74. data/lib/ddtrace/profiling/scheduler.rb +84 -0
  75. data/lib/ddtrace/profiling/tasks/setup.rb +77 -0
  76. data/lib/ddtrace/profiling/transport/client.rb +12 -0
  77. data/lib/ddtrace/profiling/transport/http.rb +122 -0
  78. data/lib/ddtrace/profiling/transport/http/api.rb +43 -0
  79. data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +90 -0
  80. data/lib/ddtrace/profiling/transport/http/api/instance.rb +36 -0
  81. data/lib/ddtrace/profiling/transport/http/api/spec.rb +40 -0
  82. data/lib/ddtrace/profiling/transport/http/builder.rb +28 -0
  83. data/lib/ddtrace/profiling/transport/http/client.rb +33 -0
  84. data/lib/ddtrace/profiling/transport/http/response.rb +21 -0
  85. data/lib/ddtrace/profiling/transport/io.rb +30 -0
  86. data/lib/ddtrace/profiling/transport/io/client.rb +27 -0
  87. data/lib/ddtrace/profiling/transport/io/response.rb +16 -0
  88. data/lib/ddtrace/profiling/transport/parcel.rb +17 -0
  89. data/lib/ddtrace/profiling/transport/request.rb +15 -0
  90. data/lib/ddtrace/profiling/transport/response.rb +8 -0
  91. data/lib/ddtrace/runtime/container.rb +11 -3
  92. data/lib/ddtrace/sampling/rule_sampler.rb +3 -9
  93. data/lib/ddtrace/tasks/exec.rb +48 -0
  94. data/lib/ddtrace/tasks/help.rb +14 -0
  95. data/lib/ddtrace/tracer.rb +21 -0
  96. data/lib/ddtrace/transport/io/client.rb +15 -8
  97. data/lib/ddtrace/transport/parcel.rb +4 -0
  98. data/lib/ddtrace/version.rb +3 -1
  99. data/lib/ddtrace/workers/runtime_metrics.rb +14 -1
  100. metadata +70 -9
@@ -0,0 +1,81 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: lib/ddtrace/profiling/pprof/pprof.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "perftools.profiles.Profile" do
8
+ repeated :sample_type, :message, 1, "perftools.profiles.ValueType"
9
+ repeated :sample, :message, 2, "perftools.profiles.Sample"
10
+ repeated :mapping, :message, 3, "perftools.profiles.Mapping"
11
+ repeated :location, :message, 4, "perftools.profiles.Location"
12
+ repeated :function, :message, 5, "perftools.profiles.Function"
13
+ repeated :string_table, :string, 6
14
+ optional :drop_frames, :int64, 7
15
+ optional :keep_frames, :int64, 8
16
+ optional :time_nanos, :int64, 9
17
+ optional :duration_nanos, :int64, 10
18
+ optional :period_type, :message, 11, "perftools.profiles.ValueType"
19
+ optional :period, :int64, 12
20
+ repeated :comment, :int64, 13
21
+ optional :default_sample_type, :int64, 14
22
+ end
23
+ add_message "perftools.profiles.ValueType" do
24
+ optional :type, :int64, 1
25
+ optional :unit, :int64, 2
26
+ end
27
+ add_message "perftools.profiles.Sample" do
28
+ repeated :location_id, :uint64, 1
29
+ repeated :value, :int64, 2
30
+ repeated :label, :message, 3, "perftools.profiles.Label"
31
+ end
32
+ add_message "perftools.profiles.Label" do
33
+ optional :key, :int64, 1
34
+ optional :str, :int64, 2
35
+ optional :num, :int64, 3
36
+ optional :num_unit, :int64, 4
37
+ end
38
+ add_message "perftools.profiles.Mapping" do
39
+ optional :id, :uint64, 1
40
+ optional :memory_start, :uint64, 2
41
+ optional :memory_limit, :uint64, 3
42
+ optional :file_offset, :uint64, 4
43
+ optional :filename, :int64, 5
44
+ optional :build_id, :int64, 6
45
+ optional :has_functions, :bool, 7
46
+ optional :has_filenames, :bool, 8
47
+ optional :has_line_numbers, :bool, 9
48
+ optional :has_inline_frames, :bool, 10
49
+ end
50
+ add_message "perftools.profiles.Location" do
51
+ optional :id, :uint64, 1
52
+ optional :mapping_id, :uint64, 2
53
+ optional :address, :uint64, 3
54
+ repeated :line, :message, 4, "perftools.profiles.Line"
55
+ optional :is_folded, :bool, 5
56
+ end
57
+ add_message "perftools.profiles.Line" do
58
+ optional :function_id, :uint64, 1
59
+ optional :line, :int64, 2
60
+ end
61
+ add_message "perftools.profiles.Function" do
62
+ optional :id, :uint64, 1
63
+ optional :name, :int64, 2
64
+ optional :system_name, :int64, 3
65
+ optional :filename, :int64, 4
66
+ optional :start_line, :int64, 5
67
+ end
68
+ end
69
+
70
+ module Perftools
71
+ module Profiles
72
+ Profile = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Profile").msgclass
73
+ ValueType = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.ValueType").msgclass
74
+ Sample = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Sample").msgclass
75
+ Label = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Label").msgclass
76
+ Mapping = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Mapping").msgclass
77
+ Location = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Location").msgclass
78
+ Line = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Line").msgclass
79
+ Function = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Function").msgclass
80
+ end
81
+ end
@@ -0,0 +1,90 @@
1
+ require 'ddtrace/ext/profiling'
2
+ require 'ddtrace/profiling/events/stack'
3
+ require 'ddtrace/profiling/pprof/builder'
4
+ require 'ddtrace/profiling/pprof/converter'
5
+
6
+ module Datadog
7
+ module Profiling
8
+ module Pprof
9
+ # Builds a profile from a StackSample
10
+ class StackSample < Converter
11
+ SAMPLE_TYPES = {
12
+ cpu_time_ns: [
13
+ Datadog::Ext::Profiling::Pprof::VALUE_TYPE_CPU,
14
+ Datadog::Ext::Profiling::Pprof::VALUE_UNIT_NANOSECONDS
15
+ ],
16
+ wall_time_ns: [
17
+ Datadog::Ext::Profiling::Pprof::VALUE_TYPE_WALL,
18
+ Datadog::Ext::Profiling::Pprof::VALUE_UNIT_NANOSECONDS
19
+ ]
20
+ }.freeze
21
+
22
+ def self.sample_value_types
23
+ SAMPLE_TYPES
24
+ end
25
+
26
+ def add_events!(stack_samples)
27
+ new_samples = build_samples(stack_samples)
28
+ builder.samples.concat(new_samples)
29
+ end
30
+
31
+ def stack_sample_group_key(stack_sample)
32
+ stack_sample.hash
33
+ end
34
+
35
+ def build_samples(stack_samples)
36
+ groups = group_events(stack_samples, &method(:stack_sample_group_key))
37
+ groups.collect do |_group_key, group|
38
+ build_sample(group.sample, group.values)
39
+ end
40
+ end
41
+
42
+ def build_sample(stack_sample, values)
43
+ locations = builder.build_locations(
44
+ stack_sample.frames,
45
+ stack_sample.total_frame_count
46
+ )
47
+
48
+ Perftools::Profiles::Sample.new(
49
+ location_id: locations.collect(&:id),
50
+ value: values,
51
+ label: build_sample_labels(stack_sample)
52
+ )
53
+ end
54
+
55
+ def build_sample_values(stack_sample)
56
+ no_value = Datadog::Ext::Profiling::Pprof::SAMPLE_VALUE_NO_VALUE
57
+ values = super(stack_sample)
58
+ values[sample_value_index(:cpu_time_ns)] = stack_sample.cpu_time_interval_ns || no_value
59
+ values[sample_value_index(:wall_time_ns)] = stack_sample.wall_time_interval_ns || no_value
60
+ values
61
+ end
62
+
63
+ def build_sample_labels(stack_sample)
64
+ labels = [
65
+ Perftools::Profiles::Label.new(
66
+ key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_THREAD_ID),
67
+ str: builder.string_table.fetch(stack_sample.thread_id.to_s)
68
+ )
69
+ ]
70
+
71
+ unless stack_sample.trace_id.nil? || stack_sample.trace_id.zero?
72
+ labels << Perftools::Profiles::Label.new(
73
+ key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_TRACE_ID),
74
+ str: builder.string_table.fetch(stack_sample.trace_id.to_s)
75
+ )
76
+ end
77
+
78
+ unless stack_sample.span_id.nil? || stack_sample.span_id.zero?
79
+ labels << Perftools::Profiles::Label.new(
80
+ key: builder.string_table.fetch(Datadog::Ext::Profiling::Pprof::LABEL_KEY_SPAN_ID),
81
+ str: builder.string_table.fetch(stack_sample.span_id.to_s)
82
+ )
83
+ end
84
+
85
+ labels
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,10 @@
1
+ require 'ddtrace/utils/string_table'
2
+
3
+ module Datadog
4
+ module Profiling
5
+ module Pprof
6
+ # Tracks strings and returns IDs
7
+ class StringTable < Utils::StringTable; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,114 @@
1
+ require 'ddtrace/profiling/pprof/payload'
2
+ require 'ddtrace/profiling/pprof/message_set'
3
+ require 'ddtrace/profiling/pprof/builder'
4
+
5
+ require 'ddtrace/profiling/events/stack'
6
+ require 'ddtrace/profiling/pprof/stack_sample'
7
+
8
+ module Datadog
9
+ module Profiling
10
+ module Pprof
11
+ # Converts a collection of profiling events into a Perftools::Profiles::Profile
12
+ class Template
13
+ DEFAULT_MAPPINGS = {
14
+ Events::StackSample => Pprof::StackSample
15
+ }.freeze
16
+
17
+ attr_reader \
18
+ :builder,
19
+ :converters,
20
+ :sample_type_mappings
21
+
22
+ def self.for_event_classes(event_classes)
23
+ # Build a map of event class --> converter class
24
+ mappings = event_classes.each_with_object({}) do |event_class, m|
25
+ converter_class = DEFAULT_MAPPINGS[event_class]
26
+ raise NoProfilingEventConversionError, event_class unless converter_class
27
+
28
+ m[event_class] = converter_class
29
+ end
30
+
31
+ new(mappings)
32
+ end
33
+
34
+ def initialize(mappings)
35
+ @builder = Builder.new
36
+ @converters = Hash.new { |_h, event_class| raise NoProfilingEventConversionError, event_class }
37
+ @sample_type_mappings = Hash.new { |_h, type| raise UnknownSampleTypeMappingError, type }
38
+
39
+ # Add default mapping
40
+ builder.mappings.fetch($PROGRAM_NAME, &builder.method(:build_mapping))
41
+
42
+ # Combine all sample types from each converter class
43
+ types = mappings.values.each_with_object({}) do |converter_class, t|
44
+ t.merge!(converter_class.sample_value_types)
45
+ end
46
+
47
+ # Build the sample types into sample type objects
48
+ types.each do |type_name, type_args|
49
+ index = nil
50
+
51
+ sample_type = builder.sample_types.fetch(*type_args) do |id, type, unit|
52
+ index = id
53
+ builder.build_value_type(type, unit)
54
+ end
55
+
56
+ # Create mapping between the type and index to which its assigned.
57
+ # Do this for faster lookup while building profile sample values.
58
+ sample_type_mappings[type_name] = index || builder.sample_types.messages.index(sample_type)
59
+ end
60
+
61
+ # Freeze them so they can't be modified.
62
+ # We don't want the number of sample types to vary between samples within the same profile.
63
+ builder.sample_types.freeze
64
+ sample_type_mappings.freeze
65
+
66
+ # Add converters
67
+ mappings.each do |event_class, converter_class|
68
+ converters[event_class] = converter_class.new(builder, sample_type_mappings)
69
+ end
70
+
71
+ converters.freeze
72
+ end
73
+
74
+ def add_events!(event_class, events)
75
+ converters[event_class].add_events!(events)
76
+ end
77
+
78
+ def to_pprof
79
+ profile = builder.build_profile
80
+ data = builder.encode_profile(profile)
81
+ types = sample_type_mappings.keys
82
+
83
+ Payload.new(data, types)
84
+ end
85
+
86
+ # Error when an unknown event type is given to be converted
87
+ class NoProfilingEventConversionError < ArgumentError
88
+ attr_reader :type
89
+
90
+ def initialize(type)
91
+ @type = type
92
+ end
93
+
94
+ def message
95
+ "Profiling event type '#{type}' cannot be converted to pprof."
96
+ end
97
+ end
98
+
99
+ # Error when the mapping of a sample type to value index is unknown
100
+ class UnknownSampleTypeMappingError < ArgumentError
101
+ attr_reader :type
102
+
103
+ def initialize(type)
104
+ @type = type
105
+ end
106
+
107
+ def message
108
+ "Mapping for sample value type '#{type}' is unknown."
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ require 'ddtrace'
2
+
3
+ Datadog.profiler.start if Datadog.profiler
@@ -0,0 +1,28 @@
1
+ module Datadog
2
+ # Profiling entry point, which coordinates collectors and a scheduler
3
+ class Profiler
4
+ attr_reader \
5
+ :collectors,
6
+ :scheduler
7
+
8
+ def initialize(collectors, scheduler)
9
+ @collectors = collectors
10
+ @scheduler = scheduler
11
+ end
12
+
13
+ def start
14
+ collectors.each(&:start)
15
+ scheduler.start
16
+ end
17
+
18
+ def shutdown!
19
+ collectors.each do |collector|
20
+ collector.enabled = false
21
+ collector.stop(true)
22
+ end
23
+
24
+ scheduler.enabled = false
25
+ scheduler.stop(true)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,87 @@
1
+ require 'ddtrace/profiling/buffer'
2
+ require 'ddtrace/profiling/flush'
3
+
4
+ module Datadog
5
+ module Profiling
6
+ # Stores profiling events gathered by `Collector`s
7
+ class Recorder
8
+ attr_reader :max_size
9
+
10
+ def initialize(event_classes, max_size)
11
+ @buffers = {}
12
+ @last_flush_time = Time.now.utc
13
+ @max_size = max_size
14
+
15
+ # Add a buffer for each class
16
+ event_classes.each do |event_class|
17
+ @buffers[event_class] = Profiling::Buffer.new(max_size)
18
+ end
19
+ end
20
+
21
+ def [](event_class)
22
+ @buffers[event_class]
23
+ end
24
+
25
+ def push(events)
26
+ if events.is_a?(Array)
27
+ # Push multiple events
28
+ event_class = events.first.class
29
+ raise UnknownEventError, event_class unless @buffers.key?(event_class)
30
+
31
+ @buffers[event_class].concat(events)
32
+ else
33
+ # Push single event
34
+ event_class = events.class
35
+ raise UnknownEventError, event_class unless @buffers.key?(event_class)
36
+
37
+ @buffers[event_class].push(events)
38
+ end
39
+ end
40
+
41
+ def flush
42
+ event_count = 0
43
+
44
+ event_groups, start, finish = update_time do
45
+ @buffers.collect do |event_class, buffer|
46
+ events = buffer.pop
47
+ next if events.empty?
48
+
49
+ event_count += events.length
50
+ EventGroup.new(event_class, events)
51
+ end.compact
52
+ end
53
+
54
+ Flush.new(
55
+ start,
56
+ finish,
57
+ event_groups,
58
+ event_count
59
+ )
60
+ end
61
+
62
+ # Error when event of an unknown type is used with the Recorder
63
+ class UnknownEventError < StandardError
64
+ attr_reader :event_class
65
+
66
+ def initialize(event_class)
67
+ @event_class = event_class
68
+ end
69
+
70
+ def message
71
+ @message ||= "Unknown event class '#{event_class}' for profiling recorder."
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def update_time
78
+ start = @last_flush_time
79
+ result = yield
80
+ @last_flush_time = Time.now.utc
81
+
82
+ # Return event groups, start time, finish time
83
+ [result, start, @last_flush_time]
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,84 @@
1
+ require 'ddtrace/utils/time'
2
+
3
+ require 'ddtrace/worker'
4
+ require 'ddtrace/workers/polling'
5
+
6
+ module Datadog
7
+ module Profiling
8
+ # Periodically (every DEFAULT_INTERVAL seconds) takes data from the `Recorder` and pushes them to all configured
9
+ # `Exporter`s. Runs on its own background thread.
10
+ class Scheduler < Worker
11
+ include Workers::Polling
12
+
13
+ DEFAULT_INTERVAL = 60
14
+ MIN_INTERVAL = 0
15
+
16
+ attr_reader \
17
+ :exporters,
18
+ :recorder
19
+
20
+ def initialize(recorder, exporters, options = {})
21
+ @recorder = recorder
22
+ @exporters = [exporters].flatten
23
+
24
+ # Workers::Async::Thread settings
25
+ # Restart in forks by default
26
+ self.fork_policy = options[:fork_policy] || Workers::Async::Thread::FORK_POLICY_RESTART
27
+
28
+ # Workers::IntervalLoop settings
29
+ self.loop_base_interval = options[:interval] || DEFAULT_INTERVAL
30
+
31
+ # Workers::Polling settings
32
+ self.enabled = options.key?(:enabled) ? options[:enabled] == true : true
33
+ end
34
+
35
+ def start
36
+ perform
37
+ end
38
+
39
+ def perform
40
+ flush_and_wait
41
+ end
42
+
43
+ def loop_back_off?
44
+ false
45
+ end
46
+
47
+ def after_fork
48
+ # Clear recorder's buffers by flushing events.
49
+ # Objects from parent process will copy-on-write,
50
+ # and we don't want to send events for the wrong process.
51
+ recorder.flush
52
+ end
53
+
54
+ def flush_and_wait
55
+ run_time = Datadog::Utils::Time.measure do
56
+ flush_events
57
+ end
58
+
59
+ # Update wait time to try to wake consistently on time.
60
+ # Don't drop below the minimum interval.
61
+ self.loop_wait_time = [loop_base_interval - run_time, MIN_INTERVAL].max
62
+ end
63
+
64
+ def flush_events
65
+ # Get events from recorder
66
+ flush = recorder.flush
67
+
68
+ # Send events to each exporter
69
+ if flush.event_count > 0
70
+ exporters.each do |exporter|
71
+ begin
72
+ exporter.export(flush)
73
+ rescue StandardError => e
74
+ error_details = "Cause: #{e} Location: #{e.backtrace.first}"
75
+ Datadog.logger.error("Unable to export #{flush.event_count} profiling events. #{error_details}")
76
+ end
77
+ end
78
+ end
79
+
80
+ flush
81
+ end
82
+ end
83
+ end
84
+ end