elastic-apm 2.9.1 → 2.10.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_codecov.yml +5 -0
  3. data/.ci/.jenkins_exclude.yml +9 -19
  4. data/.ci/.jenkins_framework.yml +1 -4
  5. data/.ci/.jenkins_master_framework.yml +3 -0
  6. data/.ci/Jenkinsfile +43 -118
  7. data/.ci/downstreamTests.groovy +59 -30
  8. data/.ci/jobs/apm-agent-ruby-downstream.yml +31 -0
  9. data/.ci/jobs/apm-agent-ruby-mbp.yml +34 -0
  10. data/.ci/jobs/defaults.yml +1 -36
  11. data/.pre-commit-config.yaml +22 -0
  12. data/.rspec +0 -1
  13. data/.rubocop.yml +3 -3
  14. data/CHANGELOG.md +12 -0
  15. data/docs/api.asciidoc +2 -2
  16. data/docs/configuration.asciidoc +37 -1
  17. data/docs/metrics.asciidoc +77 -6
  18. data/lib/elastic_apm.rb +2 -2
  19. data/lib/elastic_apm/agent.rb +11 -2
  20. data/lib/elastic_apm/central_config.rb +141 -0
  21. data/lib/elastic_apm/central_config/cache_control.rb +34 -0
  22. data/lib/elastic_apm/config.rb +152 -338
  23. data/lib/elastic_apm/config/bytes.rb +25 -0
  24. data/lib/elastic_apm/config/duration.rb +6 -8
  25. data/lib/elastic_apm/config/options.rb +134 -0
  26. data/lib/elastic_apm/config/regexp_list.rb +13 -0
  27. data/lib/elastic_apm/metadata.rb +2 -1
  28. data/lib/elastic_apm/metrics.rb +2 -1
  29. data/lib/elastic_apm/metrics/vm.rb +57 -0
  30. data/lib/elastic_apm/normalizers/action_view.rb +1 -1
  31. data/lib/elastic_apm/railtie.rb +10 -5
  32. data/lib/elastic_apm/spies/mongo.rb +13 -2
  33. data/lib/elastic_apm/stacktrace_builder.rb +2 -2
  34. data/lib/elastic_apm/transport/connection.rb +2 -0
  35. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +6 -1
  36. data/lib/elastic_apm/version.rb +1 -1
  37. metadata +11 -3
  38. data/lib/elastic_apm/config/size.rb +0 -28
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class Bytes
7
+ MULTIPLIERS = {
8
+ 'kb' => 1024,
9
+ 'mb' => 1024 * 1_000,
10
+ 'gb' => 1024 * 100_000
11
+ }.freeze
12
+ REGEX = /^(\d+)(b|kb|mb|gb)?$/i.freeze
13
+
14
+ def initialize(default_unit: 'kb')
15
+ @default_unit = default_unit
16
+ end
17
+
18
+ def call(value)
19
+ _, amount, unit = REGEX.match(String(value)).to_a
20
+ unit ||= @default_unit
21
+ MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
22
+ end
23
+ end
24
+ end
25
+ end
@@ -7,18 +7,16 @@ module ElasticAPM
7
7
  MULTIPLIERS = { 'ms' => 0.001, 'm' => 60 }.freeze
8
8
  REGEX = /^(-)?(\d+)(m|ms|s)?$/i.freeze
9
9
 
10
- def initialize(seconds)
11
- @seconds = seconds
10
+ def initialize(default_unit: 's')
11
+ @default_unit = default_unit
12
12
  end
13
13
 
14
- attr_accessor :seconds
15
-
16
- def self.parse(str, default_unit:)
17
- _, negative, amount, unit = REGEX.match(str).to_a
18
- unit ||= default_unit
14
+ def call(str)
15
+ _, negative, amount, unit = REGEX.match(String(str)).to_a
16
+ unit ||= @default_unit
19
17
  seconds = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
20
18
  seconds = 0 - seconds if negative
21
- new(seconds)
19
+ seconds
22
20
  end
23
21
  end
24
22
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ module Options
7
+ # @api private
8
+ class Option
9
+ def initialize(
10
+ key,
11
+ value: nil,
12
+ type: :string,
13
+ default: nil,
14
+ converter: nil
15
+ )
16
+ @key = key
17
+ @type = type
18
+ @default = default
19
+ @converter = converter
20
+
21
+ set(value || default)
22
+ end
23
+
24
+ attr_reader :key, :value, :default, :type
25
+
26
+ def set(value)
27
+ @value = normalize(value)
28
+ end
29
+
30
+ def env_key
31
+ "ELASTIC_APM_#{key.upcase}"
32
+ end
33
+
34
+ private
35
+
36
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
37
+ def normalize(val)
38
+ return unless val
39
+
40
+ if @converter
41
+ return @converter.call(val)
42
+ end
43
+
44
+ case type
45
+ when :string then val.to_s
46
+ when :int then val.to_i
47
+ when :float then val.to_f
48
+ when :bool then normalize_bool(val)
49
+ when :list then normalize_list(val)
50
+ when :dict then normalize_dict(val)
51
+ else
52
+ # raise "Unknown options type '#{type.inspect}'"
53
+ val
54
+ end
55
+ end
56
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
57
+
58
+ def normalize_bool(val)
59
+ return val unless val.is_a?(String)
60
+ !%w[0 false].include?(val.strip.downcase)
61
+ end
62
+
63
+ def normalize_list(val)
64
+ return Array(val) unless val.is_a?(String)
65
+ val.split(/[ ,]/)
66
+ end
67
+
68
+ def normalize_dict(val)
69
+ return val unless val.is_a?(String)
70
+ Hash[val.split(/[&,]/).map { |kv| kv.split('=') }]
71
+ end
72
+ end
73
+
74
+ # @api private
75
+ module ClassMethods
76
+ def schema
77
+ @schema ||= {}
78
+ end
79
+
80
+ def option(*args)
81
+ key = args.shift
82
+ schema[key] = *args
83
+ end
84
+ end
85
+
86
+ # @api private
87
+ module InstanceMethods
88
+ def load_schema
89
+ Hash[self.class.schema.map do |key, args|
90
+ [key, Option.new(key, *args)]
91
+ end]
92
+ end
93
+
94
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
95
+ def method_missing(name, *value)
96
+ name_str = name.to_s
97
+
98
+ if name_str.end_with?('=')
99
+ key = name_str[0...-1].to_sym
100
+ set(key, value.first)
101
+
102
+ elsif name_str.end_with?('?')
103
+ key = name_str[0...-1].to_sym
104
+ options.key?(key) ? options[key].value : super
105
+
106
+ elsif options.key?(name)
107
+ options.fetch(name).value
108
+
109
+ else
110
+ super
111
+ end
112
+ end
113
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
114
+
115
+ def [](key)
116
+ options[key]
117
+ end
118
+ alias :get :[]
119
+
120
+ def set(key, value)
121
+ options.fetch(key.to_sym).set(value)
122
+ rescue KeyError
123
+ warn format("Unknown option '%s'", key)
124
+ nil
125
+ end
126
+ end
127
+
128
+ def self.extended(kls)
129
+ kls.instance_eval { extend ClassMethods }
130
+ kls.class_eval { include InstanceMethods }
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class RegexpList
7
+ def call(value)
8
+ value = value.is_a?(String) ? value.split(',') : Array(value)
9
+ value.map(&Regexp.method(:new))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -7,9 +7,10 @@ module ElasticAPM
7
7
  @service = ServiceInfo.new(config)
8
8
  @process = ProcessInfo.new(config)
9
9
  @system = SystemInfo.new(config)
10
+ @labels = config.global_labels
10
11
  end
11
12
 
12
- attr_reader :service, :process, :system
13
+ attr_reader :service, :process, :system, :labels
13
14
  end
14
15
  end
15
16
 
@@ -24,7 +24,7 @@ module ElasticAPM
24
24
  def initialize(config, tags: nil, &block)
25
25
  @config = config
26
26
  @tags = tags
27
- @samplers = [CpuMem].map do |kls|
27
+ @samplers = [CpuMem, VM].map do |kls|
28
28
  debug "Adding metrics collector '#{kls}'"
29
29
  kls.new(config)
30
30
  end
@@ -95,3 +95,4 @@ module ElasticAPM
95
95
  end
96
96
 
97
97
  require 'elastic_apm/metrics/cpu_mem'
98
+ require 'elastic_apm/metrics/vm'
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metrics
5
+ # @api private
6
+ class VM
7
+ include Logging
8
+
9
+ def initialize(config)
10
+ @total_time = 0
11
+ @disabled = false
12
+ end
13
+
14
+ attr_reader :config
15
+ attr_writer :disabled
16
+
17
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
18
+ def collect
19
+ return if disabled?
20
+
21
+ stat = GC.stat
22
+ thread_count = Thread.list.count
23
+
24
+ sample = {
25
+ 'ruby.gc.count': stat[:count],
26
+ 'ruby.threads': thread_count
27
+ }
28
+
29
+ (live_slots = stat[:heap_live_slots]) &&
30
+ sample[:'ruby.heap.slots.live'] = live_slots
31
+ (heap_slots = stat[:heap_free_slots]) &&
32
+ sample[:'ruby.heap.slots.free'] = heap_slots
33
+ (allocated = stat[:total_allocated_objects]) &&
34
+ sample[:'ruby.heap.allocations.total'] = allocated
35
+
36
+ return sample unless GC::Profiler.enabled?
37
+
38
+ @total_time += GC::Profiler.total_time
39
+ GC::Profiler.clear
40
+ sample[:'ruby.gc.time'] = @total_time
41
+
42
+ sample
43
+ rescue TypeError => e
44
+ error 'VM metrics encountered error: %s', e
45
+ debug('Backtrace:') { e.backtrace.join("\n") }
46
+
47
+ @disabled = true
48
+ nil
49
+ end
50
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
51
+
52
+ def disabled?
53
+ @disabled
54
+ end
55
+ end
56
+ end
57
+ end
@@ -19,7 +19,7 @@ module ElasticAPM
19
19
  end
20
20
 
21
21
  def view_path(path)
22
- root = @config.view_paths.find { |vp| path.start_with?(vp) }
22
+ root = @config.__view_paths.find { |vp| path.start_with?(vp) }
23
23
  return unless root
24
24
 
25
25
  strip_root(root, path)
@@ -7,10 +7,15 @@ module ElasticAPM
7
7
  class Railtie < Rails::Railtie
8
8
  config.elastic_apm = ActiveSupport::OrderedOptions.new
9
9
 
10
- Config::DEFAULTS.each { |option, value| config.elastic_apm[option] = value }
10
+ Config.schema.each do |key, args|
11
+ next unless args.length > 1
12
+ config.elastic_apm[key] = args.last[:default]
13
+ end
11
14
 
12
15
  initializer 'elastic_apm.initialize' do |app|
13
- config = Config.new(app.config.elastic_apm.merge(app: app)).tap do |c|
16
+ config = Config.new(app.config.elastic_apm).tap do |c|
17
+ c.app = app
18
+
14
19
  # Prepend Rails.root to log_path if present
15
20
  if c.log_path && !c.log_path.start_with?('/')
16
21
  c.log_path = Rails.root.join(c.log_path)
@@ -35,7 +40,7 @@ module ElasticAPM
35
40
  def start(config)
36
41
  if (reason = should_skip?(config))
37
42
  unless config.disable_start_message?
38
- config.alert_logger.info "Skipping because: #{reason}. " \
43
+ config.logger.info "Skipping because: #{reason}. " \
39
44
  "Start manually with `ElasticAPM.start'"
40
45
  end
41
46
  return
@@ -45,8 +50,8 @@ module ElasticAPM
45
50
  attach_subscriber(agent)
46
51
  end
47
52
  rescue StandardError => e
48
- config.alert_logger.error format('Failed to start: %s', e.message)
49
- config.alert_logger.debug "Backtrace:\n" + e.backtrace.join("\n")
53
+ config.logger.error format('Failed to start: %s', e.message)
54
+ config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
50
55
  end
51
56
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
52
57
 
@@ -34,18 +34,29 @@ module ElasticAPM
34
34
 
35
35
  private
36
36
 
37
+ # rubocop:disable Metrics/MethodLength
37
38
  def push_event(event)
38
39
  return unless ElasticAPM.current_transaction
40
+ # Some MongoDB commands are not on collections but rather are db
41
+ # admin commands. For these commands, the value at the `command_name`
42
+ # key is the integer 1.
43
+ unless event.command[event.command_name] == 1
44
+ collection = event.command[event.command_name]
45
+ end
46
+ name = [event.database_name,
47
+ collection,
48
+ event.command_name].compact.join('.')
39
49
 
40
50
  span =
41
51
  ElasticAPM.start_span(
42
- event.command_name.to_s,
52
+ name,
43
53
  TYPE,
44
54
  context: build_context(event)
45
55
  )
46
56
 
47
57
  @events[event.operation_id] = span
48
58
  end
59
+ # rubocop:enable Metrics/MethodLength
49
60
 
50
61
  def pop_event(event)
51
62
  return unless (curr = ElasticAPM.current_span)
@@ -58,7 +69,7 @@ module ElasticAPM
58
69
  Span::Context.new(
59
70
  db: {
60
71
  instance: event.database_name,
61
- statement: nil,
72
+ statement: event.command.to_s,
62
73
  type: 'mongodb',
63
74
  user: nil
64
75
  }
@@ -67,8 +67,8 @@ module ElasticAPM
67
67
  def library_frame?(config, abs_path)
68
68
  return false unless abs_path
69
69
 
70
- if abs_path.start_with?(config.root_path)
71
- return true if abs_path.start_with?(config.root_path + '/vendor')
70
+ if abs_path.start_with?(config.__root_path)
71
+ return true if abs_path.start_with?(config.__root_path + '/vendor')
72
72
  return false
73
73
  end
74
74
 
@@ -7,6 +7,7 @@ require 'elastic_apm/transport/connection/http'
7
7
 
8
8
  module ElasticAPM
9
9
  module Transport
10
+ # rubocop:disable Metrics/ClassLength
10
11
  # @api private
11
12
  class Connection
12
13
  include Logging
@@ -150,5 +151,6 @@ module ElasticAPM
150
151
  end
151
152
  end
152
153
  end
154
+ # rubocop:enable Metrics/ClassLength
153
155
  end
154
156
  end
@@ -10,7 +10,8 @@ module ElasticAPM
10
10
  metadata: {
11
11
  service: build_service(metadata.service),
12
12
  process: build_process(metadata.process),
13
- system: build_system(metadata.system)
13
+ system: build_system(metadata.system),
14
+ labels: build_labels(metadata.labels)
14
15
  }
15
16
  }
16
17
  end
@@ -59,6 +60,10 @@ module ElasticAPM
59
60
  kubernetes: keyword_object(system.kubernetes)
60
61
  }
61
62
  end
63
+
64
+ def build_labels(labels)
65
+ keyword_object(labels)
66
+ end
62
67
  end
63
68
  end
64
69
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- VERSION = '2.9.1'
4
+ VERSION = '2.10.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.1
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-28 00:00:00.000000000 Z
11
+ date: 2019-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -45,8 +45,10 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".ci/.jenkins_codecov.yml"
48
49
  - ".ci/.jenkins_exclude.yml"
49
50
  - ".ci/.jenkins_framework.yml"
51
+ - ".ci/.jenkins_master_framework.yml"
50
52
  - ".ci/.jenkins_ruby.yml"
51
53
  - ".ci/Jenkinsfile"
52
54
  - ".ci/bin/check_paths_for_matches.py"
@@ -57,6 +59,7 @@ files:
57
59
  - ".ci/jobs/defaults.yml"
58
60
  - ".gitignore"
59
61
  - ".hound.yml"
62
+ - ".pre-commit-config.yaml"
60
63
  - ".rspec"
61
64
  - ".rubocop.yml"
62
65
  - CHANGELOG.md
@@ -95,9 +98,13 @@ files:
95
98
  - lib/elastic-apm.rb
96
99
  - lib/elastic_apm.rb
97
100
  - lib/elastic_apm/agent.rb
101
+ - lib/elastic_apm/central_config.rb
102
+ - lib/elastic_apm/central_config/cache_control.rb
98
103
  - lib/elastic_apm/config.rb
104
+ - lib/elastic_apm/config/bytes.rb
99
105
  - lib/elastic_apm/config/duration.rb
100
- - lib/elastic_apm/config/size.rb
106
+ - lib/elastic_apm/config/options.rb
107
+ - lib/elastic_apm/config/regexp_list.rb
101
108
  - lib/elastic_apm/context.rb
102
109
  - lib/elastic_apm/context/request.rb
103
110
  - lib/elastic_apm/context/request/socket.rb
@@ -120,6 +127,7 @@ files:
120
127
  - lib/elastic_apm/metadata/system_info/container_info.rb
121
128
  - lib/elastic_apm/metrics.rb
122
129
  - lib/elastic_apm/metrics/cpu_mem.rb
130
+ - lib/elastic_apm/metrics/vm.rb
123
131
  - lib/elastic_apm/metricset.rb
124
132
  - lib/elastic_apm/middleware.rb
125
133
  - lib/elastic_apm/naively_hashable.rb