elastic-apm 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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