elastic-apm 3.1.0 → 3.2.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 +4 -4
- data/.ci/.jenkins_exclude.yml +47 -0
- data/.ci/.jenkins_framework.yml +4 -0
- data/.ci/.jenkins_master_framework.yml +1 -0
- data/.ci/.jenkins_ruby.yml +1 -0
- data/.ci/downstreamTests.groovy +1 -1
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/CHANGELOG.asciidoc +24 -0
- data/Dockerfile +43 -0
- data/Gemfile +34 -15
- data/README.md +30 -1
- data/bin/dev +54 -0
- data/bin/run-tests +27 -0
- data/docker-compose.yml +32 -0
- data/docs/api.asciidoc +13 -2
- data/docs/configuration.asciidoc +30 -0
- data/docs/getting-started-rack.asciidoc +24 -0
- data/docs/release-notes.asciidoc +1 -1
- data/lib/elastic_apm.rb +12 -1
- data/lib/elastic_apm/agent.rb +15 -3
- data/lib/elastic_apm/central_config.rb +39 -19
- data/lib/elastic_apm/child_durations.rb +42 -0
- data/lib/elastic_apm/config.rb +27 -11
- data/lib/elastic_apm/context/request/socket.rb +1 -1
- data/lib/elastic_apm/context_builder.rb +1 -1
- data/lib/elastic_apm/error.rb +10 -0
- data/lib/elastic_apm/error/exception.rb +7 -0
- data/lib/elastic_apm/grape.rb +48 -0
- data/lib/elastic_apm/instrumenter.rb +77 -4
- data/lib/elastic_apm/logging.rb +0 -2
- data/lib/elastic_apm/metrics.rb +39 -26
- data/lib/elastic_apm/metrics/breakdown_set.rb +14 -0
- data/lib/elastic_apm/metrics/{cpu_mem.rb → cpu_mem_set.rb} +62 -54
- data/lib/elastic_apm/metrics/metric.rb +117 -0
- data/lib/elastic_apm/metrics/set.rb +106 -0
- data/lib/elastic_apm/metrics/span_scoped_set.rb +39 -0
- data/lib/elastic_apm/metrics/transaction_set.rb +11 -0
- data/lib/elastic_apm/metrics/vm_set.rb +44 -0
- data/lib/elastic_apm/metricset.rb +31 -4
- data/lib/elastic_apm/normalizers.rb +6 -0
- data/lib/elastic_apm/normalizers/grape.rb +5 -0
- data/lib/elastic_apm/normalizers/grape/endpoint_run.rb +47 -0
- data/lib/elastic_apm/normalizers/rails/active_record.rb +16 -5
- data/lib/elastic_apm/opentracing.rb +4 -4
- data/lib/elastic_apm/rails.rb +12 -2
- data/lib/elastic_apm/railtie.rb +1 -5
- data/lib/elastic_apm/sinatra.rb +1 -1
- data/lib/elastic_apm/span.rb +15 -10
- data/lib/elastic_apm/spies.rb +0 -1
- data/lib/elastic_apm/sql_summarizer.rb +8 -6
- data/lib/elastic_apm/subscriber.rb +4 -1
- data/lib/elastic_apm/transaction.rb +6 -6
- data/lib/elastic_apm/transport/base.rb +7 -0
- data/lib/elastic_apm/transport/connection.rb +11 -69
- data/lib/elastic_apm/transport/connection/http.rb +43 -35
- data/lib/elastic_apm/transport/connection/proxy_pipe.rb +0 -3
- data/lib/elastic_apm/transport/headers.rb +62 -0
- data/lib/elastic_apm/transport/serializers.rb +0 -2
- data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +19 -6
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
- data/lib/elastic_apm/transport/user_agent.rb +31 -0
- data/lib/elastic_apm/transport/worker.rb +1 -2
- data/lib/elastic_apm/version.rb +1 -1
- metadata +20 -6
- data/lib/elastic_apm/metrics/vm.rb +0 -60
- data/lib/elastic_apm/util/prefixed_logger.rb +0 -18
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Metrics
|
5
|
+
# @api private
|
6
|
+
class Metric
|
7
|
+
def initialize(
|
8
|
+
key,
|
9
|
+
initial_value: nil,
|
10
|
+
tags: nil,
|
11
|
+
reset_on_collect: false
|
12
|
+
)
|
13
|
+
@key = key
|
14
|
+
@initial_value = initial_value
|
15
|
+
@value = initial_value
|
16
|
+
@tags = tags
|
17
|
+
@reset_on_collect = reset_on_collect
|
18
|
+
@mutex = Mutex.new
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :key, :initial_value, :tags, :value
|
22
|
+
|
23
|
+
def value=(value)
|
24
|
+
@mutex.synchronize { @value = value }
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset!
|
28
|
+
self.value = initial_value
|
29
|
+
end
|
30
|
+
|
31
|
+
def tags?
|
32
|
+
!!tags&.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_on_collect?
|
36
|
+
@reset_on_collect
|
37
|
+
end
|
38
|
+
|
39
|
+
def collect
|
40
|
+
collected = value
|
41
|
+
|
42
|
+
self.value = initial_value if reset_on_collect?
|
43
|
+
|
44
|
+
return nil if reset_on_collect? && collected == 0
|
45
|
+
|
46
|
+
collected
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
class NoopMetric
|
52
|
+
def value; end
|
53
|
+
|
54
|
+
def value=(_); end
|
55
|
+
|
56
|
+
def collect; end
|
57
|
+
|
58
|
+
def reset!; end
|
59
|
+
|
60
|
+
def tags?; end
|
61
|
+
|
62
|
+
def reset_on_collect?; end
|
63
|
+
|
64
|
+
def inc!; end
|
65
|
+
|
66
|
+
def dec!; end
|
67
|
+
|
68
|
+
def update(_, delta: nil); end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
class Counter < Metric
|
73
|
+
def initialize(key, initial_value: 0, **args)
|
74
|
+
super(key, initial_value: initial_value, **args)
|
75
|
+
end
|
76
|
+
|
77
|
+
def inc!
|
78
|
+
@value += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
def dec!
|
82
|
+
@value -= 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
class Gauge < Metric
|
88
|
+
def initialize(key, **args)
|
89
|
+
super(key, initial_value: 0, **args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
class Timer < Metric
|
95
|
+
def initialize(key, **args)
|
96
|
+
super(key, initial_value: 0, **args)
|
97
|
+
@count = 0
|
98
|
+
end
|
99
|
+
|
100
|
+
attr_accessor :count
|
101
|
+
|
102
|
+
def update(duration, delta: 0)
|
103
|
+
@mutex.synchronize do
|
104
|
+
@value += duration
|
105
|
+
@count += delta
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def reset!
|
110
|
+
@mutex.synchronize do
|
111
|
+
@value = 0
|
112
|
+
@count = 0
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
# @api private
|
5
|
+
module Metrics
|
6
|
+
NOOP = NoopMetric.new
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
class Set
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
DISTINCT_LABEL_LIMIT = 1000
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
16
|
+
@metrics = {}
|
17
|
+
@disabled = false
|
18
|
+
@lock = Mutex.new
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :metrics
|
22
|
+
|
23
|
+
def disable!
|
24
|
+
@disabled = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def disabled?
|
28
|
+
@disabled
|
29
|
+
end
|
30
|
+
|
31
|
+
def counter(key, tags: nil, **args)
|
32
|
+
metric(Counter, key, tags: tags, **args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def gauge(key, tags: nil, **args)
|
36
|
+
metric(Gauge, key, tags: tags, **args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def timer(key, tags: nil, **args)
|
40
|
+
metric(Timer, key, tags: tags, **args)
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
44
|
+
def metric(kls, key, tags: nil, **args)
|
45
|
+
key = key_with_tags(key, tags)
|
46
|
+
return metrics[key] if metrics[key]
|
47
|
+
|
48
|
+
@lock.synchronize do
|
49
|
+
return metrics[key] if metrics[key]
|
50
|
+
|
51
|
+
metrics[key] =
|
52
|
+
if metrics.length < DISTINCT_LABEL_LIMIT
|
53
|
+
kls.new(key, tags: tags, **args)
|
54
|
+
else
|
55
|
+
unless @label_limit_logged
|
56
|
+
warn(
|
57
|
+
'The limit of %d metricsets has been reached, no new ' \
|
58
|
+
'metricsets will be created.', DISTINCT_LABEL_LIMIT
|
59
|
+
)
|
60
|
+
@label_limit_logged = true
|
61
|
+
end
|
62
|
+
|
63
|
+
NOOP
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
68
|
+
|
69
|
+
# rubocop:disable Metrics/MethodLength
|
70
|
+
def collect
|
71
|
+
return if disabled?
|
72
|
+
|
73
|
+
@lock.synchronize do
|
74
|
+
metrics.each_with_object({}) do |(key, metric), sets|
|
75
|
+
next unless (value = metric.collect)
|
76
|
+
|
77
|
+
# metrics have a key of name and flat array of key-value pairs
|
78
|
+
# eg [name, key, value, key, value]
|
79
|
+
# they can be sent in the same metricset but not if they have
|
80
|
+
# differing tags. So, we split the resulting sets by tags first.
|
81
|
+
name, *tags = key
|
82
|
+
sets[tags] ||= Metricset.new
|
83
|
+
|
84
|
+
# then we set the `samples` value for the metricset
|
85
|
+
set = sets[tags]
|
86
|
+
set.samples[name] = value
|
87
|
+
|
88
|
+
# and finally we copy the tags from the Metric to the Metricset
|
89
|
+
set.merge_tags! metric.tags
|
90
|
+
end.values
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# rubocop:enable Metrics/MethodLength
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def key_with_tags(key, tags)
|
98
|
+
return key unless tags
|
99
|
+
|
100
|
+
tuple = tags.keys.zip(tags.values)
|
101
|
+
tuple.flatten!
|
102
|
+
tuple.unshift(key)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Metrics
|
5
|
+
# @api private
|
6
|
+
class SpanScopedSet < Set
|
7
|
+
def collect
|
8
|
+
super.tap do |sets|
|
9
|
+
return unless sets
|
10
|
+
|
11
|
+
sets.each do |set|
|
12
|
+
move_transaction(set)
|
13
|
+
move_span(set)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def move_transaction(set)
|
21
|
+
name = set.tags&.delete(:'transaction.name')
|
22
|
+
type = set.tags&.delete(:'transaction.type')
|
23
|
+
return unless name || type
|
24
|
+
|
25
|
+
set.transaction = { name: name, type: type }
|
26
|
+
set.tags = nil if set.tags.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def move_span(set)
|
30
|
+
type = set.tags&.delete(:'span.type')
|
31
|
+
subtype = set.tags&.delete(:'span.subtype')
|
32
|
+
return unless type
|
33
|
+
|
34
|
+
set.span = { type: type, subtype: subtype }
|
35
|
+
set.tags = nil if set.tags.empty?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Metrics
|
5
|
+
# @api private
|
6
|
+
class VMSet < Set
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def collect
|
10
|
+
read!
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
15
|
+
def read!
|
16
|
+
return if disabled?
|
17
|
+
|
18
|
+
stat = GC.stat
|
19
|
+
|
20
|
+
gauge(:'ruby.gc.count').value = stat[:count]
|
21
|
+
gauge(:'ruby.threads').value = Thread.list.count
|
22
|
+
gauge(:'ruby.heap.slots.live').value = stat[:heap_live_slots]
|
23
|
+
|
24
|
+
gauge(:'ruby.heap.slots.free').value = stat[:heap_free_slots]
|
25
|
+
gauge(:'ruby.heap.allocations.total').value =
|
26
|
+
stat[:total_allocated_objects]
|
27
|
+
|
28
|
+
return unless GC::Profiler.enabled?
|
29
|
+
|
30
|
+
@total_time ||= 0
|
31
|
+
@total_time += GC::Profiler.total_time
|
32
|
+
GC::Profiler.clear
|
33
|
+
gauge(:'ruby.gc.time').value = @total_time
|
34
|
+
rescue TypeError => e
|
35
|
+
error 'VM metrics encountered error: %s', e
|
36
|
+
debug('Backtrace:') { e.backtrace.join("\n") }
|
37
|
+
|
38
|
+
disable!
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -3,17 +3,44 @@
|
|
3
3
|
module ElasticAPM
|
4
4
|
# @api private
|
5
5
|
class Metricset
|
6
|
-
def initialize(
|
6
|
+
def initialize(
|
7
|
+
timestamp: Util.micros,
|
8
|
+
tags: nil,
|
9
|
+
transaction: nil,
|
10
|
+
span: nil,
|
11
|
+
**samples
|
12
|
+
)
|
7
13
|
@timestamp = timestamp
|
8
|
-
@
|
14
|
+
@tags = tags
|
15
|
+
@transaction = transaction
|
16
|
+
@span = span
|
9
17
|
@samples = samples
|
10
18
|
end
|
11
19
|
|
12
|
-
attr_accessor :timestamp
|
13
|
-
attr_reader :samples
|
20
|
+
attr_accessor :timestamp, :transaction, :span, :tags
|
21
|
+
attr_reader :samples
|
22
|
+
|
23
|
+
def merge_tags!(tags)
|
24
|
+
return unless tags
|
25
|
+
|
26
|
+
@tags ||= {}
|
27
|
+
@tags.merge! tags
|
28
|
+
end
|
29
|
+
|
30
|
+
def tags?
|
31
|
+
tags&.any?
|
32
|
+
end
|
14
33
|
|
15
34
|
def empty?
|
16
35
|
samples.empty?
|
17
36
|
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"<ElasticAPM::Metricset timestamp:#{timestamp}" \
|
40
|
+
" transaction:#{transaction.inspect}" \
|
41
|
+
" span:#{span.inspect}" \
|
42
|
+
" tags:#{tags.inspect}" \
|
43
|
+
" samples:#{samples.inspect}>"
|
44
|
+
end
|
18
45
|
end
|
19
46
|
end
|
@@ -12,6 +12,8 @@ module ElasticAPM # :nodoc:
|
|
12
12
|
def self.register(name)
|
13
13
|
Normalizers.register(name, self)
|
14
14
|
end
|
15
|
+
|
16
|
+
def backtrace(payload); end
|
15
17
|
end
|
16
18
|
|
17
19
|
def self.register(name, klass)
|
@@ -54,6 +56,10 @@ module ElasticAPM # :nodoc:
|
|
54
56
|
def normalize(transaction, name, payload)
|
55
57
|
self.for(name).normalize(transaction, name, payload)
|
56
58
|
end
|
59
|
+
|
60
|
+
def backtrace(name, payload)
|
61
|
+
self.for(name).backtrace(payload)
|
62
|
+
end
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ElasticAPM
|
4
|
+
module Normalizers
|
5
|
+
module Grape
|
6
|
+
# @api private
|
7
|
+
class EndpointRun < Normalizer
|
8
|
+
register 'endpoint_run.grape'
|
9
|
+
|
10
|
+
TYPE = 'app'
|
11
|
+
SUBTYPE = 'resource'
|
12
|
+
|
13
|
+
FRAMEWORK_NAME = 'Grape'
|
14
|
+
|
15
|
+
def normalize(transaction, _name, payload)
|
16
|
+
transaction.name = endpoint(payload[:env])
|
17
|
+
|
18
|
+
if transaction_from_host_app?(transaction)
|
19
|
+
transaction.context.set_service(
|
20
|
+
framework_name: FRAMEWORK_NAME,
|
21
|
+
framework_version: ::Grape::VERSION
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
[transaction.name, TYPE, SUBTYPE, nil, nil]
|
26
|
+
end
|
27
|
+
|
28
|
+
def backtrace(payload)
|
29
|
+
source_location = payload[:endpoint].source.source_location
|
30
|
+
["#{source_location[0]}:#{source_location[1]}"]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def transaction_from_host_app?(transaction)
|
36
|
+
transaction.config.framework_name != FRAMEWORK_NAME
|
37
|
+
end
|
38
|
+
|
39
|
+
def endpoint(env)
|
40
|
+
route_name = env['api.endpoint']&.routes&.first&.pattern&.origin ||
|
41
|
+
env['REQUEST_PATH']
|
42
|
+
[env['REQUEST_METHOD'], route_name].join(' ')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -11,31 +11,42 @@ module ElasticAPM
|
|
11
11
|
|
12
12
|
TYPE = 'db'
|
13
13
|
ACTION = 'sql'
|
14
|
+
SKIP_NAMES = %w[SCHEMA CACHE].freeze
|
15
|
+
UNKNOWN = 'unknown'
|
14
16
|
|
15
17
|
def initialize(*args)
|
16
18
|
super
|
17
19
|
|
18
|
-
@subtype = lookup_adapter || 'unknown'
|
19
20
|
@summarizer = SqlSummarizer.new
|
21
|
+
@adapters = {}
|
20
22
|
end
|
21
23
|
|
22
24
|
def normalize(_transaction, _name, payload)
|
23
|
-
return :skip if
|
25
|
+
return :skip if SKIP_NAMES.include?(payload[:name])
|
24
26
|
|
25
27
|
name = summarize(payload[:sql]) || payload[:name]
|
26
28
|
context =
|
27
29
|
Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
|
28
|
-
[name, TYPE,
|
30
|
+
[name, TYPE, subtype(payload), ACTION, context]
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
32
34
|
|
35
|
+
def subtype(payload)
|
36
|
+
cached_adapter_name(
|
37
|
+
payload[:connection] ? payload[:connection].adapter_name :
|
38
|
+
::ActiveRecord::Base.connection_config[:adapter]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
33
42
|
def summarize(sql)
|
34
43
|
@summarizer.summarize(sql)
|
35
44
|
end
|
36
45
|
|
37
|
-
def
|
38
|
-
|
46
|
+
def cached_adapter_name(adapter_name)
|
47
|
+
return UNKNOWN if adapter_name.nil? || adapter_name.empty?
|
48
|
+
@adapters[adapter_name] ||
|
49
|
+
(@adapters[adapter_name] = adapter_name.downcase)
|
39
50
|
rescue StandardError
|
40
51
|
nil
|
41
52
|
end
|