ddtrace 0.47.0 → 0.48.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +4 -2
- data/.circleci/images/primary/Dockerfile-2.0.0 +11 -1
- data/.circleci/images/primary/Dockerfile-2.1.10 +11 -1
- data/.circleci/images/primary/Dockerfile-2.2.10 +11 -1
- data/.circleci/images/primary/Dockerfile-2.3.8 +10 -0
- data/.circleci/images/primary/Dockerfile-2.4.6 +10 -0
- data/.circleci/images/primary/Dockerfile-2.5.6 +10 -0
- data/.circleci/images/primary/Dockerfile-2.6.4 +10 -0
- data/.circleci/images/primary/Dockerfile-2.7.0 +10 -0
- data/.circleci/images/primary/Dockerfile-jruby-9.2-latest +10 -0
- data/.gitlab-ci.yml +18 -18
- data/.rubocop.yml +19 -0
- data/.rubocop_todo.yml +44 -3
- data/Appraisals +55 -1
- data/CHANGELOG.md +47 -1
- data/Gemfile +10 -0
- data/Rakefile +9 -0
- data/bin/ddtracerb +15 -0
- data/ddtrace.gemspec +4 -2
- data/docs/GettingStarted.md +36 -53
- data/docs/ProfilingDevelopment.md +88 -0
- data/integration/README.md +1 -2
- data/integration/apps/rack/Dockerfile +3 -0
- data/integration/apps/rack/script/build-images +1 -1
- data/integration/apps/rack/script/ci +1 -1
- data/integration/apps/rails-five/script/build-images +1 -1
- data/integration/apps/rails-five/script/ci +1 -1
- data/integration/apps/ruby/script/build-images +1 -1
- data/integration/apps/ruby/script/ci +1 -1
- data/integration/images/include/http-health-check +1 -1
- data/integration/images/wrk/scripts/entrypoint.sh +1 -1
- data/integration/script/build-images +1 -1
- data/lib/ddtrace.rb +1 -0
- data/lib/ddtrace/configuration.rb +39 -13
- data/lib/ddtrace/configuration/components.rb +85 -3
- data/lib/ddtrace/configuration/settings.rb +31 -0
- data/lib/ddtrace/contrib/active_record/configuration/makara_resolver.rb +30 -0
- data/lib/ddtrace/contrib/active_record/configuration/resolver.rb +9 -3
- data/lib/ddtrace/contrib/resque/configuration/settings.rb +17 -1
- data/lib/ddtrace/contrib/resque/patcher.rb +4 -4
- data/lib/ddtrace/contrib/resque/resque_job.rb +22 -1
- data/lib/ddtrace/contrib/shoryuken/configuration/settings.rb +1 -0
- data/lib/ddtrace/contrib/shoryuken/tracer.rb +7 -3
- data/lib/ddtrace/diagnostics/environment_logger.rb +1 -1
- data/lib/ddtrace/error.rb +2 -0
- data/lib/ddtrace/ext/profiling.rb +52 -0
- data/lib/ddtrace/ext/transport.rb +1 -0
- data/lib/ddtrace/metrics.rb +4 -0
- data/lib/ddtrace/profiling.rb +54 -0
- data/lib/ddtrace/profiling/backtrace_location.rb +32 -0
- data/lib/ddtrace/profiling/buffer.rb +41 -0
- data/lib/ddtrace/profiling/collectors/stack.rb +253 -0
- data/lib/ddtrace/profiling/encoding/profile.rb +31 -0
- data/lib/ddtrace/profiling/event.rb +13 -0
- data/lib/ddtrace/profiling/events/stack.rb +102 -0
- data/lib/ddtrace/profiling/exporter.rb +23 -0
- data/lib/ddtrace/profiling/ext/cpu.rb +54 -0
- data/lib/ddtrace/profiling/ext/cthread.rb +134 -0
- data/lib/ddtrace/profiling/ext/forking.rb +97 -0
- data/lib/ddtrace/profiling/flush.rb +41 -0
- data/lib/ddtrace/profiling/pprof/builder.rb +121 -0
- data/lib/ddtrace/profiling/pprof/converter.rb +85 -0
- data/lib/ddtrace/profiling/pprof/message_set.rb +12 -0
- data/lib/ddtrace/profiling/pprof/payload.rb +18 -0
- data/lib/ddtrace/profiling/pprof/pprof.proto +212 -0
- data/lib/ddtrace/profiling/pprof/pprof_pb.rb +81 -0
- data/lib/ddtrace/profiling/pprof/stack_sample.rb +90 -0
- data/lib/ddtrace/profiling/pprof/string_table.rb +10 -0
- data/lib/ddtrace/profiling/pprof/template.rb +114 -0
- data/lib/ddtrace/profiling/preload.rb +3 -0
- data/lib/ddtrace/profiling/profiler.rb +28 -0
- data/lib/ddtrace/profiling/recorder.rb +87 -0
- data/lib/ddtrace/profiling/scheduler.rb +84 -0
- data/lib/ddtrace/profiling/tasks/setup.rb +77 -0
- data/lib/ddtrace/profiling/transport/client.rb +12 -0
- data/lib/ddtrace/profiling/transport/http.rb +122 -0
- data/lib/ddtrace/profiling/transport/http/api.rb +43 -0
- data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +90 -0
- data/lib/ddtrace/profiling/transport/http/api/instance.rb +36 -0
- data/lib/ddtrace/profiling/transport/http/api/spec.rb +40 -0
- data/lib/ddtrace/profiling/transport/http/builder.rb +28 -0
- data/lib/ddtrace/profiling/transport/http/client.rb +33 -0
- data/lib/ddtrace/profiling/transport/http/response.rb +21 -0
- data/lib/ddtrace/profiling/transport/io.rb +30 -0
- data/lib/ddtrace/profiling/transport/io/client.rb +27 -0
- data/lib/ddtrace/profiling/transport/io/response.rb +16 -0
- data/lib/ddtrace/profiling/transport/parcel.rb +17 -0
- data/lib/ddtrace/profiling/transport/request.rb +15 -0
- data/lib/ddtrace/profiling/transport/response.rb +8 -0
- data/lib/ddtrace/runtime/container.rb +11 -3
- data/lib/ddtrace/sampling/rule_sampler.rb +3 -9
- data/lib/ddtrace/tasks/exec.rb +48 -0
- data/lib/ddtrace/tasks/help.rb +14 -0
- data/lib/ddtrace/tracer.rb +21 -0
- data/lib/ddtrace/transport/io/client.rb +15 -8
- data/lib/ddtrace/transport/parcel.rb +4 -0
- data/lib/ddtrace/version.rb +3 -1
- data/lib/ddtrace/workers/runtime_metrics.rb +14 -1
- 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,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,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
|