elastic-apm 2.9.1 → 2.10.0

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