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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_exclude.yml +47 -0
  3. data/.ci/.jenkins_framework.yml +4 -0
  4. data/.ci/.jenkins_master_framework.yml +1 -0
  5. data/.ci/.jenkins_ruby.yml +1 -0
  6. data/.ci/downstreamTests.groovy +1 -1
  7. data/.gitignore +2 -1
  8. data/.rspec +1 -0
  9. data/CHANGELOG.asciidoc +24 -0
  10. data/Dockerfile +43 -0
  11. data/Gemfile +34 -15
  12. data/README.md +30 -1
  13. data/bin/dev +54 -0
  14. data/bin/run-tests +27 -0
  15. data/docker-compose.yml +32 -0
  16. data/docs/api.asciidoc +13 -2
  17. data/docs/configuration.asciidoc +30 -0
  18. data/docs/getting-started-rack.asciidoc +24 -0
  19. data/docs/release-notes.asciidoc +1 -1
  20. data/lib/elastic_apm.rb +12 -1
  21. data/lib/elastic_apm/agent.rb +15 -3
  22. data/lib/elastic_apm/central_config.rb +39 -19
  23. data/lib/elastic_apm/child_durations.rb +42 -0
  24. data/lib/elastic_apm/config.rb +27 -11
  25. data/lib/elastic_apm/context/request/socket.rb +1 -1
  26. data/lib/elastic_apm/context_builder.rb +1 -1
  27. data/lib/elastic_apm/error.rb +10 -0
  28. data/lib/elastic_apm/error/exception.rb +7 -0
  29. data/lib/elastic_apm/grape.rb +48 -0
  30. data/lib/elastic_apm/instrumenter.rb +77 -4
  31. data/lib/elastic_apm/logging.rb +0 -2
  32. data/lib/elastic_apm/metrics.rb +39 -26
  33. data/lib/elastic_apm/metrics/breakdown_set.rb +14 -0
  34. data/lib/elastic_apm/metrics/{cpu_mem.rb → cpu_mem_set.rb} +62 -54
  35. data/lib/elastic_apm/metrics/metric.rb +117 -0
  36. data/lib/elastic_apm/metrics/set.rb +106 -0
  37. data/lib/elastic_apm/metrics/span_scoped_set.rb +39 -0
  38. data/lib/elastic_apm/metrics/transaction_set.rb +11 -0
  39. data/lib/elastic_apm/metrics/vm_set.rb +44 -0
  40. data/lib/elastic_apm/metricset.rb +31 -4
  41. data/lib/elastic_apm/normalizers.rb +6 -0
  42. data/lib/elastic_apm/normalizers/grape.rb +5 -0
  43. data/lib/elastic_apm/normalizers/grape/endpoint_run.rb +47 -0
  44. data/lib/elastic_apm/normalizers/rails/active_record.rb +16 -5
  45. data/lib/elastic_apm/opentracing.rb +4 -4
  46. data/lib/elastic_apm/rails.rb +12 -2
  47. data/lib/elastic_apm/railtie.rb +1 -5
  48. data/lib/elastic_apm/sinatra.rb +1 -1
  49. data/lib/elastic_apm/span.rb +15 -10
  50. data/lib/elastic_apm/spies.rb +0 -1
  51. data/lib/elastic_apm/sql_summarizer.rb +8 -6
  52. data/lib/elastic_apm/subscriber.rb +4 -1
  53. data/lib/elastic_apm/transaction.rb +6 -6
  54. data/lib/elastic_apm/transport/base.rb +7 -0
  55. data/lib/elastic_apm/transport/connection.rb +11 -69
  56. data/lib/elastic_apm/transport/connection/http.rb +43 -35
  57. data/lib/elastic_apm/transport/connection/proxy_pipe.rb +0 -3
  58. data/lib/elastic_apm/transport/headers.rb +62 -0
  59. data/lib/elastic_apm/transport/serializers.rb +0 -2
  60. data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +19 -6
  61. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
  62. data/lib/elastic_apm/transport/user_agent.rb +31 -0
  63. data/lib/elastic_apm/transport/worker.rb +1 -2
  64. data/lib/elastic_apm/version.rb +1 -1
  65. metadata +20 -6
  66. data/lib/elastic_apm/metrics/vm.rb +0 -60
  67. data/lib/elastic_apm/util/prefixed_logger.rb +0 -18
@@ -34,6 +34,13 @@ module ElasticAPM
34
34
  :cause
35
35
  )
36
36
 
37
+ def inspect
38
+ "<ElasticAPM::Error::Exception" \
39
+ " type:#{type}" \
40
+ " message:#{message}" \
41
+ ">"
42
+ end
43
+
37
44
  class << self
38
45
  private
39
46
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/subscriber'
4
+ require 'elastic_apm/normalizers/grape'
5
+
6
+ module ElasticAPM
7
+ # Module for starting the ElasticAPM agent and hooking into Grape.
8
+ module Grape
9
+ extend self
10
+
11
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
12
+ # Start the ElasticAPM agent and hook into Grape.
13
+ #
14
+ # @param app [Grape::API] A Grape app.
15
+ # @param config [Config, Hash] An instance of Config or a Hash config.
16
+ # @return [true, nil] true if the agent was started, nil otherwise.
17
+ def start(app, config = {})
18
+ config = Config.new(config) unless config.is_a?(Config)
19
+ configure_app(app, config)
20
+
21
+ ElasticAPM.start(config).tap do |agent|
22
+ attach_subscriber(agent)
23
+ end
24
+
25
+ ElasticAPM.running?
26
+ rescue StandardError => e
27
+ config.logger.error format('Failed to start: %s', e.message)
28
+ config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
29
+ end
30
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
31
+
32
+ private
33
+
34
+ def configure_app(app, config)
35
+ config.service_name ||= app.name
36
+ config.framework_name ||= 'Grape'
37
+ config.framework_version ||= ::Grape::VERSION
38
+ config.logger ||= app.logger
39
+ config.__root_path ||= Dir.pwd
40
+ end
41
+
42
+ def attach_subscriber(agent)
43
+ return unless agent
44
+
45
+ agent.instrumenter.subscriber = ElasticAPM::Subscriber.new(agent)
46
+ end
47
+ end
48
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'elastic_apm/trace_context'
4
+ require 'elastic_apm/child_durations'
4
5
  require 'elastic_apm/span'
5
6
  require 'elastic_apm/transaction'
6
7
  require 'elastic_apm/span_helpers'
@@ -39,10 +40,11 @@ module ElasticAPM
39
40
  end
40
41
  end
41
42
 
42
- def initialize(config, stacktrace_builder:, &enqueue)
43
+ def initialize(config, metrics:, stacktrace_builder:, &enqueue)
43
44
  @config = config
44
45
  @stacktrace_builder = stacktrace_builder
45
46
  @enqueue = enqueue
47
+ @metrics = metrics
46
48
 
47
49
  @current = Current.new
48
50
  end
@@ -90,7 +92,8 @@ module ElasticAPM
90
92
 
91
93
  if (transaction = current_transaction)
92
94
  raise ExistingTransactionError,
93
- "Transactions may not be nested.\nAlready inside #{transaction}"
95
+ "Transactions may not be nested.\n" \
96
+ "Already inside #{transaction.inspect}"
94
97
  end
95
98
 
96
99
  sampled = trace_context ? trace_context.recorded? : random_sample?(config)
@@ -120,6 +123,8 @@ module ElasticAPM
120
123
 
121
124
  enqueue.call transaction
122
125
 
126
+ update_transaction_metrics(transaction)
127
+
123
128
  transaction
124
129
  end
125
130
 
@@ -161,8 +166,9 @@ module ElasticAPM
161
166
  name: name,
162
167
  subtype: subtype,
163
168
  action: action,
164
- transaction_id: transaction.id,
165
- trace_context: trace_context || parent.trace_context.child,
169
+ transaction: transaction,
170
+ parent: parent,
171
+ trace_context: trace_context,
166
172
  type: type,
167
173
  context: context,
168
174
  stacktrace_builder: stacktrace_builder
@@ -187,6 +193,8 @@ module ElasticAPM
187
193
 
188
194
  enqueue.call span
189
195
 
196
+ update_span_metrics(span)
197
+
190
198
  span
191
199
  end
192
200
 
@@ -220,6 +228,71 @@ module ElasticAPM
220
228
  def random_sample?(config)
221
229
  rand <= config.transaction_sample_rate
222
230
  end
231
+
232
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
233
+ def update_transaction_metrics(transaction)
234
+ return unless transaction.config.collect_metrics?
235
+
236
+ tags = {
237
+ 'transaction.name': transaction.name,
238
+ 'transaction.type': transaction.type
239
+ }
240
+
241
+ @metrics.get(:transaction).timer(
242
+ :'transaction.duration.sum.us',
243
+ tags: tags, reset_on_collect: true
244
+ ).update(transaction.duration)
245
+
246
+ @metrics.get(:transaction).counter(
247
+ :'transaction.duration.count',
248
+ tags: tags, reset_on_collect: true
249
+ ).inc!
250
+
251
+ return unless transaction.sampled?
252
+ return unless transaction.config.breakdown_metrics?
253
+
254
+ @metrics.get(:breakdown).counter(
255
+ :'transaction.breakdown.count',
256
+ tags: tags, reset_on_collect: true
257
+ ).inc!
258
+
259
+ span_tags = tags.merge('span.type': 'app')
260
+
261
+ @metrics.get(:breakdown).timer(
262
+ :'span.self_time.sum.us',
263
+ tags: span_tags, reset_on_collect: true
264
+ ).update(transaction.self_time)
265
+
266
+ @metrics.get(:breakdown).counter(
267
+ :'span.self_time.count',
268
+ tags: span_tags, reset_on_collect: true
269
+ ).inc!
270
+ end
271
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
272
+
273
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
274
+ def update_span_metrics(span)
275
+ return unless span.transaction.config.breakdown_metrics?
276
+
277
+ tags = {
278
+ 'span.type': span.type,
279
+ 'transaction.name': span.transaction.name,
280
+ 'transaction.type': span.transaction.type
281
+ }
282
+
283
+ tags[:'span.subtype'] = span.subtype if span.subtype
284
+
285
+ @metrics.get(:breakdown).timer(
286
+ :'span.self_time.sum.us',
287
+ tags: tags, reset_on_collect: true
288
+ ).update(span.self_time)
289
+
290
+ @metrics.get(:breakdown).counter(
291
+ :'span.self_time.count',
292
+ tags: tags, reset_on_collect: true
293
+ ).inc!
294
+ end
295
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
223
296
  end
224
297
  # rubocop:enable Metrics/ClassLength
225
298
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
-
5
3
  module ElasticAPM
6
4
  # @api private
7
5
  module Logging
@@ -1,14 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/metricset'
4
-
5
3
  module ElasticAPM
6
4
  # @api private
7
5
  module Metrics
8
- MUTEX = Mutex.new
9
-
10
6
  def self.new(config, &block)
11
- Collector.new(config, &block)
7
+ Registry.new(config, &block)
12
8
  end
13
9
 
14
10
  def self.platform
@@ -16,24 +12,19 @@ module ElasticAPM
16
12
  end
17
13
 
18
14
  # @api private
19
- class Collector
15
+ class Registry
20
16
  include Logging
21
17
 
22
18
  TIMEOUT_INTERVAL = 5 # seconds
23
19
 
24
- def initialize(config, labels: nil, &block)
20
+ def initialize(config, &block)
25
21
  @config = config
26
- @labels = labels
27
- @samplers = [CpuMem, VM].map do |kls|
28
- debug "Adding metrics collector '#{kls}'"
29
- kls.new(config)
30
- end
31
22
  @callback = block
32
23
  end
33
24
 
34
- attr_reader :config, :samplers, :callback, :labels
25
+ attr_reader :config, :sets, :callback
35
26
 
36
- # rubocop:disable Metrics/MethodLength
27
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
37
28
  def start
38
29
  unless config.collect_metrics?
39
30
  debug 'Skipping metrics'
@@ -42,6 +33,16 @@ module ElasticAPM
42
33
 
43
34
  debug 'Starting metrics'
44
35
 
36
+ @sets = {
37
+ system: CpuMemSet,
38
+ vm: VMSet,
39
+ breakdown: BreakdownSet,
40
+ transaction: TransactionSet
41
+ }.each_with_object({}) do |(key, kls), sets|
42
+ debug "Adding metrics collector '#{kls}'"
43
+ sets[key] = kls.new(config)
44
+ end
45
+
45
46
  @timer_task = Concurrent::TimerTask.execute(
46
47
  run_now: true,
47
48
  execution_interval: config.metrics_interval,
@@ -60,7 +61,7 @@ module ElasticAPM
60
61
 
61
62
  @running = true
62
63
  end
63
- # rubocop:enable Metrics/MethodLength
64
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
64
65
 
65
66
  def stop
66
67
  return unless running?
@@ -75,24 +76,36 @@ module ElasticAPM
75
76
  !!@running
76
77
  end
77
78
 
78
- def collect_and_send
79
- metricset = Metricset.new(labels: labels, **collect)
80
- return if metricset.empty?
79
+ def get(key)
80
+ sets.fetch(key)
81
+ end
81
82
 
82
- callback.call(metricset)
83
+ def collect_and_send
84
+ metricsets = collect
85
+ metricsets.compact!
86
+ metricsets.each do |m|
87
+ callback.call(m)
88
+ end
83
89
  end
84
90
 
85
91
  def collect
86
- MUTEX.synchronize do
87
- samplers.each_with_object({}) do |sampler, samples|
88
- next unless (sample = sampler.collect)
89
- samples.merge!(sample)
90
- end
92
+ sets.each_value.each_with_object([]) do |set, arr|
93
+ samples = set.collect
94
+ next unless samples
95
+ arr.concat(samples)
91
96
  end
92
97
  end
93
98
  end
94
99
  end
95
100
  end
96
101
 
97
- require 'elastic_apm/metrics/cpu_mem'
98
- require 'elastic_apm/metrics/vm'
102
+ require 'elastic_apm/metricset'
103
+
104
+ require 'elastic_apm/metrics/metric'
105
+ require 'elastic_apm/metrics/set'
106
+
107
+ require 'elastic_apm/metrics/cpu_mem_set'
108
+ require 'elastic_apm/metrics/vm_set'
109
+ require 'elastic_apm/metrics/span_scoped_set'
110
+ require 'elastic_apm/metrics/transaction_set'
111
+ require 'elastic_apm/metrics/breakdown_set'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metrics
5
+ # @api private
6
+ class BreakdownSet < SpanScopedSet
7
+ def initialize(config)
8
+ super
9
+
10
+ disable! unless config.breakdown_metrics?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -3,100 +3,108 @@
3
3
  module ElasticAPM
4
4
  module Metrics
5
5
  # @api private
6
- class CpuMem
6
+ class CpuMemSet < Set
7
7
  include Logging
8
8
 
9
9
  # @api private
10
10
  class Sample
11
11
  # rubocop:disable Metrics/ParameterLists
12
12
  def initialize(
13
+ page_size:,
14
+ process_cpu_usage:,
15
+ process_memory_rss:,
16
+ process_memory_size:,
13
17
  system_cpu_total:,
14
18
  system_cpu_usage:,
15
- system_memory_total:,
16
19
  system_memory_free:,
17
- process_cpu_usage:,
18
- process_memory_size:,
19
- process_memory_rss:,
20
- page_size:
20
+ system_memory_total:
21
21
  )
22
+ @page_size = page_size
23
+ @process_cpu_usage = process_cpu_usage
24
+ @process_memory_rss = process_memory_rss
25
+ @process_memory_size = process_memory_size
22
26
  @system_cpu_total = system_cpu_total
23
27
  @system_cpu_usage = system_cpu_usage
24
- @system_memory_total = system_memory_total
25
28
  @system_memory_free = system_memory_free
26
- @process_cpu_usage = process_cpu_usage
27
- @process_memory_size = process_memory_size
28
- @process_memory_rss = process_memory_rss
29
- @page_size = page_size
29
+ @system_memory_total = system_memory_total
30
30
  end
31
31
  # rubocop:enable Metrics/ParameterLists
32
32
 
33
- attr_accessor :system_cpu_total, :system_cpu_usage,
34
- :system_memory_total, :system_memory_free, :process_cpu_usage,
35
- :process_memory_size, :process_memory_rss, :page_size
36
-
37
- def delta(previous)
38
- dup.tap do |sample|
39
- sample.system_cpu_total =
40
- system_cpu_total - previous.system_cpu_total
41
- sample.system_cpu_usage =
42
- system_cpu_usage - previous.system_cpu_usage
43
- sample.process_cpu_usage =
44
- process_cpu_usage - previous.process_cpu_usage
45
- end
46
- end
33
+ attr_accessor(
34
+ :page_size,
35
+ :process_cpu_usage,
36
+ :process_memory_rss,
37
+ :process_memory_size,
38
+ :system_cpu_total,
39
+ :system_cpu_usage,
40
+ :system_memory_free,
41
+ :system_memory_total
42
+ )
47
43
  end
48
44
 
49
45
  def initialize(config)
50
- @config = config
46
+ super
47
+
51
48
  @sampler = sampler_for_platform(Metrics.platform)
49
+ read! # set @previous on boot
52
50
  end
53
51
 
54
- attr_reader :config, :sampler
52
+ attr_reader :config
55
53
 
56
- def sample
57
- @sampler.sample
54
+ def collect
55
+ read!
56
+ super
57
+ end
58
+
59
+ private
60
+
61
+ def sampler_for_platform(platform)
62
+ case platform
63
+ when :linux then Linux.new
64
+ else
65
+ warn "Unsupported platform '#{platform}' - Disabling system metrics"
66
+ disable!
67
+ nil
68
+ end
58
69
  end
59
70
 
60
71
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
61
- def collect
62
- return unless sampler
72
+ def read!
73
+ return if disabled?
63
74
 
64
- current = sample
75
+ current = @sampler.sample
65
76
 
66
77
  unless @previous
67
78
  @previous = current
68
79
  return
69
80
  end
70
81
 
71
- delta = current.delta(@previous)
82
+ cpu_usage_pct, cpu_process_pct = calculate_deltas(current, @previous)
72
83
 
73
- cpu_usage_pct = delta.system_cpu_usage.to_f / delta.system_cpu_total
74
- cpu_process_pct = delta.process_cpu_usage.to_f / delta.system_cpu_total
84
+ gauge(:'system.cpu.total.norm.pct').value = cpu_usage_pct
85
+ gauge(:'system.memory.actual.free').value = current.system_memory_free
86
+ gauge(:'system.memory.total').value = current.system_memory_total
87
+ gauge(:'system.process.cpu.total.norm.pct').value = cpu_process_pct
88
+ gauge(:'system.process.memory.size').value = current.process_memory_size
89
+ gauge(:'system.process.memory.rss.bytes').value =
90
+ current.process_memory_rss * current.page_size
75
91
 
76
92
  @previous = current
77
-
78
- {
79
- 'system.cpu.total.norm.pct': cpu_usage_pct,
80
- 'system.memory.actual.free': current.system_memory_free,
81
- 'system.memory.total': current.system_memory_total,
82
- 'system.process.cpu.total.norm.pct': cpu_process_pct,
83
- 'system.process.memory.size': current.process_memory_size,
84
- 'system.process.memory.rss.bytes':
85
- current.process_memory_rss * current.page_size
86
- }
87
93
  end
88
94
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
89
95
 
90
- private
96
+ def calculate_deltas(current, previous)
97
+ system_cpu_total =
98
+ current.system_cpu_total - previous.system_cpu_total
99
+ system_cpu_usage =
100
+ current.system_cpu_usage - previous.system_cpu_usage
101
+ process_cpu_usage =
102
+ current.process_cpu_usage - previous.process_cpu_usage
91
103
 
92
- def sampler_for_platform(platform)
93
- case platform
94
- when :linux then Linux.new
95
- else
96
- warn "Unsupported platform '#{platform}' - Disabling metrics"
97
- @disabled = true
98
- nil
99
- end
104
+ cpu_usage_pct = system_cpu_usage.to_f / system_cpu_total
105
+ cpu_process_pct = process_cpu_usage.to_f / system_cpu_total
106
+
107
+ [cpu_usage_pct, cpu_process_pct]
100
108
  end
101
109
 
102
110
  # @api private