atatus 1.7.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +49 -13
  4. data/LICENSE +1 -1
  5. data/atatus.gemspec +3 -3
  6. data/lib/atatus/agent.rb +10 -7
  7. data/lib/atatus/central_config.rb +19 -8
  8. data/lib/atatus/collector/layer.rb +1 -1
  9. data/lib/atatus/{sql_summarizer.rb → config/log_level_map.rb} +22 -28
  10. data/lib/atatus/config/options.rb +2 -1
  11. data/lib/atatus/config/regexp_list.rb +1 -1
  12. data/lib/atatus/config/round_float.rb +31 -0
  13. data/lib/atatus/config/server_info.rb +50 -0
  14. data/lib/atatus/config/wildcard_pattern_list.rb +3 -1
  15. data/lib/atatus/config.rb +91 -70
  16. data/lib/atatus/context/request/socket.rb +1 -2
  17. data/lib/atatus/context/response.rb +1 -3
  18. data/lib/atatus/context.rb +3 -10
  19. data/lib/atatus/context_builder.rb +3 -3
  20. data/lib/atatus/error.rb +2 -1
  21. data/lib/atatus/error_builder.rb +1 -1
  22. data/lib/atatus/fields.rb +98 -0
  23. data/lib/atatus/graphql.rb +2 -0
  24. data/lib/atatus/grpc.rb +5 -7
  25. data/lib/atatus/instrumenter.rb +29 -25
  26. data/lib/atatus/metadata/cloud_info.rb +156 -0
  27. data/lib/atatus/metadata/service_info.rb +3 -3
  28. data/lib/atatus/metadata/system_info/container_info.rb +20 -8
  29. data/lib/atatus/metadata/system_info.rb +20 -5
  30. data/lib/atatus/metadata.rb +3 -1
  31. data/lib/atatus/metrics/cpu_mem_set.rb +10 -38
  32. data/lib/atatus/metrics/jvm_set.rb +88 -0
  33. data/lib/atatus/metrics/metric.rb +2 -0
  34. data/lib/atatus/metrics.rb +33 -16
  35. data/lib/atatus/middleware.rb +8 -3
  36. data/lib/atatus/naively_hashable.rb +1 -0
  37. data/lib/atatus/normalizers/rails/active_record.rb +25 -7
  38. data/lib/atatus/normalizers.rb +2 -2
  39. data/lib/atatus/opentracing.rb +5 -3
  40. data/lib/atatus/rails.rb +1 -1
  41. data/lib/atatus/span/context/db.rb +1 -1
  42. data/lib/atatus/span/context/destination.rb +58 -32
  43. data/lib/atatus/span/context/http.rb +2 -0
  44. data/lib/atatus/span/context/links.rb +32 -0
  45. data/lib/atatus/{sql.rb → span/context/message.rb} +16 -12
  46. data/lib/atatus/span/context/service.rb +55 -0
  47. data/lib/atatus/span/context.rb +28 -3
  48. data/lib/atatus/span.rb +35 -5
  49. data/lib/atatus/span_helpers.rb +12 -23
  50. data/lib/atatus/spies/action_dispatch.rb +10 -13
  51. data/lib/atatus/spies/azure_storage_table.rb +148 -0
  52. data/lib/atatus/spies/delayed_job.rb +19 -13
  53. data/lib/atatus/spies/dynamo_db.rb +56 -15
  54. data/lib/atatus/spies/elasticsearch.rb +54 -39
  55. data/lib/atatus/spies/faraday.rb +92 -58
  56. data/lib/atatus/spies/http.rb +33 -37
  57. data/lib/atatus/spies/json.rb +5 -9
  58. data/lib/atatus/spies/mongo.rb +26 -19
  59. data/lib/atatus/spies/net_http.rb +53 -51
  60. data/lib/atatus/spies/racecar.rb +77 -0
  61. data/lib/atatus/spies/rake.rb +27 -27
  62. data/lib/atatus/spies/redis.rb +11 -12
  63. data/lib/atatus/spies/resque.rb +18 -23
  64. data/lib/atatus/spies/s3.rb +132 -0
  65. data/lib/atatus/spies/sequel.rb +11 -2
  66. data/lib/atatus/spies/shoryuken.rb +4 -6
  67. data/lib/atatus/spies/sidekiq.rb +23 -31
  68. data/lib/atatus/spies/sinatra.rb +20 -28
  69. data/lib/atatus/spies/sneakers.rb +2 -0
  70. data/lib/atatus/spies/sns.rb +126 -0
  71. data/lib/atatus/spies/sqs.rb +231 -0
  72. data/lib/atatus/spies/sucker_punch.rb +20 -22
  73. data/lib/atatus/spies/tilt.rb +10 -13
  74. data/lib/atatus/spies.rb +20 -0
  75. data/lib/atatus/sql/signature.rb +4 -2
  76. data/lib/atatus/sql/tokenizer.rb +23 -7
  77. data/lib/atatus/stacktrace/frame.rb +1 -0
  78. data/lib/atatus/stacktrace_builder.rb +12 -16
  79. data/lib/atatus/subscriber.rb +1 -0
  80. data/lib/atatus/trace_context/traceparent.rb +5 -8
  81. data/lib/atatus/trace_context/tracestate.rb +16 -14
  82. data/lib/atatus/trace_context.rb +6 -16
  83. data/lib/atatus/transaction.rb +17 -4
  84. data/lib/atatus/transport/base.rb +1 -3
  85. data/lib/atatus/transport/connection/http.rb +11 -3
  86. data/lib/atatus/transport/connection/proxy_pipe.rb +1 -2
  87. data/lib/atatus/transport/connection.rb +3 -2
  88. data/lib/atatus/transport/filters/hash_sanitizer.rb +16 -34
  89. data/lib/atatus/transport/filters/secrets_filter.rb +35 -12
  90. data/lib/atatus/transport/serializers/context_serializer.rb +1 -2
  91. data/lib/atatus/transport/serializers/metadata_serializer.rb +54 -8
  92. data/lib/atatus/transport/serializers/metricset_serializer.rb +2 -2
  93. data/lib/atatus/transport/serializers/span_serializer.rb +55 -9
  94. data/lib/atatus/transport/serializers/transaction_serializer.rb +1 -0
  95. data/lib/atatus/transport/serializers.rb +9 -6
  96. data/lib/atatus/transport/user_agent.rb +16 -9
  97. data/lib/atatus/transport/worker.rb +2 -1
  98. data/lib/atatus/util/deep_dup.rb +65 -0
  99. data/lib/atatus/util/precision_validator.rb +46 -0
  100. data/lib/atatus/util.rb +2 -0
  101. data/lib/atatus/version.rb +1 -1
  102. data/lib/atatus.rb +32 -5
  103. metadata +40 -11
@@ -0,0 +1,156 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require "http"
21
+
22
+ module Atatus
23
+ class Metadata
24
+ # @api private
25
+ class CloudInfo
26
+ include Logging
27
+
28
+ AWS_URI = "http://169.254.169.254/latest/dynamic/instance-identity/document"
29
+ GCP_URI = "http://metadata.google.internal/computeMetadata/v1/?recursive=true"
30
+ AZURE_URI = "http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15"
31
+
32
+ def initialize(config)
33
+ @config = config
34
+ @client = HTTP.timeout(connect: 0.1, read: 0.1)
35
+ end
36
+
37
+ attr_reader :config
38
+
39
+ attr_accessor(
40
+ :account_id,
41
+ :account_name,
42
+ :instance_id,
43
+ :instance_name,
44
+ :machine_type,
45
+ :project_id,
46
+ :project_name,
47
+ :availability_zone,
48
+ :provider,
49
+ :region
50
+ )
51
+
52
+ # rubocop:disable Metrics/CyclomaticComplexity
53
+ def fetch!
54
+ case config.cloud_provider
55
+ when "aws"
56
+ fetch_aws
57
+ when "gcp"
58
+ fetch_gcp
59
+ when "azure"
60
+ fetch_azure || read_azure_app_services
61
+ when "auto"
62
+ fetch_aws || fetch_gcp || fetch_azure || read_azure_app_services
63
+ when "none"
64
+ nil
65
+ else
66
+ error("Unknown setting for cloud_provider '#{config.cloud_provider}'")
67
+ end
68
+
69
+ self
70
+ end
71
+ # rubocop:enable Metrics/CyclomaticComplexity
72
+
73
+ private
74
+
75
+ def fetch_aws
76
+ resp = @client.get(AWS_URI)
77
+
78
+ return unless resp.status == 200
79
+ return unless (metadata = JSON.parse(resp.body.to_s))
80
+
81
+ self.provider = "aws"
82
+ self.account_id = metadata["accountId"]
83
+ self.instance_id = metadata["instanceId"]
84
+ self.availability_zone = metadata["availabilityZone"]
85
+ self.machine_type = metadata["instanceType"]
86
+ self.region = metadata["region"]
87
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
88
+ nil
89
+ end
90
+
91
+ def fetch_gcp
92
+ resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
93
+
94
+ return unless resp.status == 200
95
+ return unless (metadata = JSON.parse(resp.body.to_s))
96
+
97
+ zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
98
+
99
+ self.provider = "gcp"
100
+ self.instance_id = metadata["instance"]["id"].to_s
101
+ self.instance_name = metadata["instance"]["name"]
102
+ self.project_id = metadata["project"]["projectId"]
103
+ self.availability_zone = zone
104
+ self.region = zone.split("-")[0..-2].join("-")
105
+ self.machine_type = metadata["instance"]["machineType"].split("/")[-1]
106
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
107
+ nil
108
+ end
109
+
110
+ def fetch_azure
111
+ resp = @client.headers("Metadata" => "true").get(AZURE_URI)
112
+
113
+ return unless resp.status == 200
114
+ return unless (metadata = JSON.parse(resp.body.to_s))
115
+
116
+ self.provider = 'azure'
117
+ self.account_id = metadata["subscriptionId"]
118
+ self.instance_id = metadata["vmId"]
119
+ self.instance_name = metadata["name"]
120
+ self.project_name = metadata["resourceGroupName"]
121
+ self.availability_zone = metadata["zone"]
122
+ self.machine_type = metadata["vmSize"]
123
+ self.region = metadata["location"]
124
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
125
+ nil
126
+ end
127
+
128
+ def read_azure_app_services
129
+ owner_name, instance_id, site_name, resource_group =
130
+ ENV.values_at(
131
+ 'WEBSITE_OWNER_NAME',
132
+ 'WEBSITE_INSTANCE_ID',
133
+ 'WEBSITE_SITE_NAME',
134
+ 'WEBSITE_RESOURCE_GROUP'
135
+ )
136
+
137
+ return unless owner_name && instance_id && site_name && resource_group
138
+
139
+ self.provider = 'azure'
140
+ self.instance_id = instance_id
141
+ self.instance_name = site_name
142
+ self.project_name = resource_group
143
+ self.account_id, self.region = parse_azure_app_services_owner_name(owner_name)
144
+ end
145
+
146
+ private
147
+
148
+ def parse_azure_app_services_owner_name(owner_name)
149
+ id, rest = owner_name.split('+')
150
+ *_, region = rest.split('-')
151
+ region.gsub!(/webspace.*$/, '')
152
+ [id, region]
153
+ end
154
+ end
155
+ end
156
+ end
@@ -49,11 +49,11 @@ module Atatus
49
49
  )
50
50
  @language = Language.new(name: 'ruby', version: RUBY_VERSION)
51
51
  @runtime = lookup_runtime
52
- @version = @config.service_version || Util.git_sha
52
+ @version = @config.service_version
53
53
  end
54
54
 
55
- attr_reader :name, :node_name, :environment, :agent, :framework, :language,
56
- :runtime, :version
55
+ attr_reader :name, :node_name, :environment, :agent, :framework,
56
+ :language, :runtime, :version
57
57
 
58
58
  private
59
59
 
@@ -33,14 +33,15 @@ module Atatus
33
33
 
34
34
  attr_reader :cgroup_path
35
35
 
36
- def read!
36
+ def read!(hostname)
37
37
  read_from_cgroup!
38
+ self.kubernetes_pod_name = hostname if kubernetes_pod_uid
38
39
  read_from_env!
39
40
  self
40
41
  end
41
42
 
42
- def self.read!
43
- new.read!
43
+ def self.read!(hostname)
44
+ new.read!(hostname)
44
45
  end
45
46
 
46
47
  def container
@@ -81,15 +82,23 @@ module Atatus
81
82
  ENV.fetch('KUBERNETES_POD_UID', kubernetes_pod_uid)
82
83
  end
83
84
 
85
+ # rubocop:disable Style/RegexpLiteral
84
86
  CONTAINER_ID_REGEXES = [
85
87
  %r{^[[:xdigit:]]{64}$},
86
- %r{^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$}
87
- ]
88
+ %r{
89
+ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]
90
+ {4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$
91
+ }x
92
+ ].freeze
88
93
  KUBEPODS_REGEXES = [
89
94
  %r{(?:^/kubepods[^\s]*/pod([^/]+)$)},
90
- %r{(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice$)}
91
- ]
95
+ %r{
96
+ (?:^/kubepods\.slice/(kubepods-[^/]+\.slice/)?
97
+ kubepods[^/]*-pod([^/]+)\.slice$)
98
+ }x
99
+ ].freeze
92
100
  SYSTEMD_SCOPE_SUFFIX = '.scope'
101
+ # rubocop:enable Style/RegexpLiteral
93
102
 
94
103
  # rubocop:disable Metrics/PerceivedComplexity
95
104
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -125,7 +134,10 @@ module Atatus
125
134
  end
126
135
 
127
136
  if (kubepods_match = match_kubepods(directory))
128
- pod_id = kubepods_match[1] || kubepods_match[2]
137
+ unless (pod_id = kubepods_match[1])
138
+ pod_id = kubepods_match[2]
139
+ pod_id&.tr!('_', '-')
140
+ end
129
141
 
130
142
  self.container_id = container_id
131
143
  self.kubernetes_pod_uid = pod_id
@@ -24,11 +24,13 @@ module Atatus
24
24
  def initialize(config)
25
25
  @config = config
26
26
 
27
- @hostname = @config.hostname || self.class.system_hostname
27
+ @configured_hostname = @config.hostname
28
+ @detected_hostname = detect_hostname
29
+ @hostname = @configured_hostname || @detected_hostname
28
30
  @architecture = gem_platform.cpu
29
31
  @platform = gem_platform.os
30
32
 
31
- container_info = ContainerInfo.read!
33
+ container_info = ContainerInfo.read!(@detected_hostname)
32
34
  @container = container_info.container
33
35
  @kubernetes = container_info.kubernetes
34
36
 
@@ -36,14 +38,27 @@ module Atatus
36
38
  @osinfo = OSInfo.read!
37
39
  end
38
40
 
39
- attr_reader :hostname, :architecture, :platform, :container, :kubernetes, :hwinfo, :osinfo
41
+ attr_reader(
42
+ :detected_hostname,
43
+ :configured_hostname,
44
+ :architecture,
45
+ :platform,
46
+ :container,
47
+ :kubernetes,
48
+ :hwinfo,
49
+ :osinfo,
50
+ :hostname
51
+ )
40
52
 
41
53
  def gem_platform
42
54
  @gem_platform ||= Gem::Platform.local
43
55
  end
44
56
 
45
- def self.system_hostname
46
- @system_hostname ||= `hostname`.chomp
57
+ private
58
+
59
+ def detect_hostname
60
+ Socket.gethostname.chomp
61
+ rescue
47
62
  end
48
63
  end
49
64
  end
@@ -25,12 +25,14 @@ module Atatus
25
25
  @process = ProcessInfo.new(config)
26
26
  @system = SystemInfo.new(config)
27
27
  @labels = config.global_labels
28
+ @cloud = CloudInfo.new(config).fetch!
28
29
  end
29
30
 
30
- attr_reader :service, :process, :system, :labels
31
+ attr_reader :service, :process, :system, :cloud, :labels
31
32
  end
32
33
  end
33
34
 
34
35
  require 'atatus/metadata/service_info'
35
36
  require 'atatus/metadata/system_info'
36
37
  require 'atatus/metadata/process_info'
38
+ require 'atatus/metadata/cloud_info'
@@ -60,11 +60,10 @@ module Atatus
60
60
  end
61
61
 
62
62
  def initialize(config)
63
- @vmset_disabled = false
64
63
  super
65
64
 
66
- @sampler = sampler_for_platform(Metrics.platform)
67
- read! # set @previous on boot
65
+ @sampler = sampler_for_os(Metrics.os)
66
+ read! # set initial values to calculate deltas from
68
67
  end
69
68
 
70
69
  attr_reader :config
@@ -76,19 +75,11 @@ module Atatus
76
75
 
77
76
  private
78
77
 
79
- def vmset_disable!
80
- @vmset_disabled = true
81
- end
82
-
83
- def vmset_disabled?
84
- @vmset_disabled
85
- end
86
-
87
- def sampler_for_platform(platform)
88
- case platform
89
- when :linux then Linux.new
78
+ def sampler_for_os(os)
79
+ case os
80
+ when /^linux/ then Linux.new
90
81
  else
91
- warn "Unsupported platform '#{platform}' - Disabling system metrics"
82
+ warn "Disabling system metrics, unsupported host OS '#{os}'"
92
83
  disable!
93
84
  nil
94
85
  end
@@ -115,29 +106,6 @@ module Atatus
115
106
  current.process_memory_rss * current.page_size
116
107
 
117
108
  @previous = current
118
-
119
- return if vmset_disabled?
120
-
121
- stat = GC.stat
122
-
123
- gauge(:'ruby.gc.count').value = stat[:count]
124
- gauge(:'ruby.threads').value = Thread.list.count
125
- gauge(:'ruby.heap.slots.live').value = stat[:heap_live_slots]
126
-
127
- gauge(:'ruby.heap.slots.free').value = stat[:heap_free_slots]
128
- gauge(:'ruby.heap.allocations.total').value =
129
- stat[:total_allocated_objects]
130
-
131
- return unless GC::Profiler.enabled?
132
- @total_time ||= 0
133
- @total_time += GC::Profiler.total_time
134
- GC::Profiler.clear
135
- gauge(:'ruby.gc.time').value = @total_time
136
- rescue TypeError => e
137
- error 'VM metrics encountered error: %s', e
138
- debug('Backtrace:') { e.backtrace.join("\n") }
139
-
140
- vmset_disable!
141
109
  end
142
110
 
143
111
  def calculate_deltas(current, previous)
@@ -148,6 +116,9 @@ module Atatus
148
116
  process_cpu_usage =
149
117
  current.process_cpu_usage - previous.process_cpu_usage
150
118
 
119
+ # No change / avoid dividing by 0
120
+ return [0, 0] if system_cpu_total == 0
121
+
151
122
  cpu_usage_pct = system_cpu_usage.to_f / system_cpu_total
152
123
  cpu_process_pct = process_cpu_usage.to_f / system_cpu_total
153
124
 
@@ -246,6 +217,7 @@ module Atatus
246
217
  # @api private
247
218
  class Meminfo
248
219
  attr_reader :total, :available, :page_size
220
+
249
221
  # rubocop:disable Metrics/PerceivedComplexity
250
222
  # rubocop:disable Metrics/CyclomaticComplexity
251
223
  def read!
@@ -0,0 +1,88 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # frozen_string_literal: true
19
+
20
+ require 'java'
21
+
22
+ module Atatus
23
+ module Metrics
24
+ # @api private
25
+ class JVMSet < Set
26
+ include Logging
27
+
28
+ MAX_TRIES = 3
29
+
30
+ def initialize(*args)
31
+ super
32
+
33
+ @error_count = 0
34
+ end
35
+
36
+ def collect
37
+ read!
38
+ super
39
+ end
40
+
41
+ def read!
42
+ return if disabled?
43
+
44
+ heap = platform_bean.get_heap_memory_usage
45
+ non_heap = platform_bean.get_non_heap_memory_usage
46
+
47
+ gauge(:"jvm.memory.heap.used").value = heap.get_used
48
+ gauge(:"jvm.memory.heap.committed").value = heap.get_committed
49
+ gauge(:"jvm.memory.heap.max").value = heap.get_max
50
+
51
+ gauge(:"jvm.memory.non_heap.used").value = non_heap.get_used
52
+ gauge(:"jvm.memory.non_heap.committed").value = non_heap.get_committed
53
+ gauge(:"jvm.memory.non_heap.max").value = non_heap.get_max
54
+
55
+ pool_beans.each do |bean|
56
+ next unless bean.type.name == "HEAP"
57
+
58
+ tags = { name: bean.get_name }
59
+
60
+ gauge(:"jvm.memory.heap.pool.used", tags: tags).value = bean.get_usage.get_used
61
+ gauge(:"jvm.memory.heap.pool.committed", tags: tags).value = bean.get_usage.get_committed
62
+ gauge(:"jvm.memory.heap.pool.max", tags: tags).value = bean.get_usage.get_max
63
+ end
64
+ rescue Exception => e
65
+ error("JVM metrics encountered error: %s", e)
66
+ debug("Backtrace:") { e.backtrace.join("\n") }
67
+
68
+ @error_count += 1
69
+ if @error_count >= MAX_TRIES
70
+ disable!
71
+ error("Disabling JVM metrics after #{MAX_TRIES} errors", e)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def platform_bean
78
+ @platform_bean ||= java.lang.management.ManagementFactory.getPlatformMXBean(
79
+ java.lang.management.MemoryMXBean.java_class
80
+ )
81
+ end
82
+
83
+ def pool_beans
84
+ @pool_beans ||= java.lang.management.ManagementFactory.getMemoryPoolMXBeans()
85
+ end
86
+ end
87
+ end
88
+ end
@@ -57,6 +57,8 @@ module Atatus
57
57
  @mutex.synchronize do
58
58
  collected = @value
59
59
 
60
+ return nil if collected.is_a?(Float) && !collected.finite?
61
+
60
62
  @value = initial_value if reset_on_collect?
61
63
 
62
64
  return nil if reset_on_collect? && collected == 0
@@ -28,44 +28,38 @@ module Atatus
28
28
  @platform ||= Gem::Platform.local.os.to_sym
29
29
  end
30
30
 
31
+ def self.os
32
+ @os ||= RbConfig::CONFIG.fetch('host_os', 'unknown').to_sym
33
+ end
34
+
31
35
  # @api private
32
36
  class Registry
33
37
  include Logging
34
38
 
35
- TIMEOUT_INTERVAL = 5 # seconds
36
-
37
39
  def initialize(config, &block)
38
40
  @config = config
39
41
  @callback = block
42
+ @sets = nil
40
43
  end
41
44
 
42
45
  attr_reader :config, :sets, :callback
46
+
43
47
  def start
44
48
  unless config.collect_metrics?
45
49
  debug 'Skipping metrics'
46
50
  return
47
51
  end
48
52
 
49
- debug 'Starting metrics'
53
+ define_sets
50
54
 
51
- # Only set the @sets once, in case we stop
52
- # and start again.
53
- @sets ||= {
54
- system: CpuMemSet,
55
- # vm: VMSet,
56
- breakdown: BreakdownSet,
57
- transaction: TransactionSet
58
- }.each_with_object({}) do |(key, kls), sets|
59
- debug "Adding metrics collector '#{kls}'"
60
- sets[key] = kls.new(config)
61
- end
55
+ debug 'Starting metrics'
62
56
 
63
57
  @timer_task = Concurrent::TimerTask.execute(
64
58
  run_now: true,
65
- execution_interval: config.metrics_interval,
66
- timeout_interval: TIMEOUT_INTERVAL
59
+ execution_interval: config.metrics_interval
67
60
  ) do
68
61
  begin
62
+ debug 'Collecting metrics'
69
63
  collect_and_send
70
64
  true
71
65
  rescue StandardError => e
@@ -123,6 +117,28 @@ module Atatus
123
117
  arr.concat(samples)
124
118
  end
125
119
  end
120
+
121
+ def define_sets
122
+ # Only set the @sets once, in case we stop
123
+ # and start again.
124
+ return unless @sets.nil?
125
+
126
+ sets = {
127
+ system: CpuMemSet,
128
+ vm: VMSet,
129
+ breakdown: BreakdownSet,
130
+ transaction: TransactionSet
131
+ }
132
+ if defined?(JVMSet)
133
+ debug "Enabling JVM metrics collection"
134
+ sets[:jvm] = JVMSet
135
+ end
136
+
137
+ @sets = sets.each_with_object({}) do |(key, kls), _sets_|
138
+ debug "Adding metrics collector '#{kls}'"
139
+ _sets_[key] = kls.new(config)
140
+ end
141
+ end
126
142
  end
127
143
  end
128
144
  end
@@ -134,6 +150,7 @@ require 'atatus/metrics/set'
134
150
 
135
151
  require 'atatus/metrics/cpu_mem_set'
136
152
  require 'atatus/metrics/vm_set'
153
+ require 'atatus/metrics/jvm_set' if defined?(JRUBY_VERSION)
137
154
  require 'atatus/metrics/span_scoped_set'
138
155
  require 'atatus/metrics/transaction_set'
139
156
  require 'atatus/metrics/breakdown_set'
@@ -41,9 +41,14 @@ module Atatus
41
41
  Atatus.report(e, context: context, handled: false)
42
42
  raise
43
43
  ensure
44
- if resp && transaction
45
- status, headers, _body = resp
46
- transaction.add_response(status, headers: headers.dup)
44
+ if transaction
45
+ if resp
46
+ status, headers, _body = resp
47
+ transaction.add_response(status, headers: headers.dup)
48
+ transaction&.outcome = Transaction::Outcome.from_http_status(status)
49
+ else
50
+ transaction&.outcome = Transaction::Outcome::FAILURE
51
+ end
47
52
  end
48
53
 
49
54
  Atatus.end_transaction http_result(status)
@@ -18,6 +18,7 @@
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module Atatus
21
+ # TODO: Remove this?
21
22
  # @api private
22
23
  module NaivelyHashable
23
24
  def naively_hashable?
@@ -17,7 +17,7 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'atatus/sql'
20
+ require 'atatus/sql/signature'
21
21
 
22
22
  module Atatus
23
23
  module Normalizers
@@ -34,7 +34,7 @@ module Atatus
34
34
  def initialize(*args)
35
35
  super
36
36
 
37
- @summarizer = Sql.summarizer
37
+ @summarizer = Sql::Signature::Summarizer.new
38
38
 
39
39
  @adapters = {}
40
40
  end
@@ -48,7 +48,7 @@ module Atatus
48
48
  context =
49
49
  Span::Context.new(
50
50
  db: { statement: payload[:sql], type: 'sql' },
51
- destination: { name: subtype, resource: subtype, type: TYPE }
51
+ destination: { service: { name: subtype, resource: subtype, type: TYPE } }
52
52
  )
53
53
 
54
54
  [name, TYPE, subtype, ACTION, context]
@@ -57,10 +57,21 @@ module Atatus
57
57
  private
58
58
 
59
59
  def subtype_for(payload)
60
- cached_adapter_name(
61
- payload[:connection]&.adapter_name ||
62
- ::ActiveRecord::Base.connection_config[:adapter]
63
- )
60
+ if payload[:connection]
61
+ return cached_adapter_name(payload[:connection].adapter_name)
62
+ end
63
+
64
+ if can_attempt_connection_id_lookup?(payload)
65
+ begin
66
+ loaded_object = ObjectSpace._id2ref(payload[:connection_id])
67
+ if loaded_object.respond_to?(:adapter_name)
68
+ return cached_adapter_name(loaded_object.adapter_name)
69
+ end
70
+ rescue RangeError # if connection object has somehow been garbage collected
71
+ end
72
+ end
73
+
74
+ cached_adapter_name(::ActiveRecord::Base.connection_config[:adapter])
64
75
  end
65
76
 
66
77
  def summarize(sql)
@@ -69,11 +80,18 @@ module Atatus
69
80
 
70
81
  def cached_adapter_name(adapter_name)
71
82
  return UNKNOWN if adapter_name.nil? || adapter_name.empty?
83
+
72
84
  @adapters[adapter_name] ||
73
85
  (@adapters[adapter_name] = adapter_name.downcase)
74
86
  rescue StandardError
75
87
  nil
76
88
  end
89
+
90
+ def can_attempt_connection_id_lookup?(payload)
91
+ RUBY_ENGINE == "ruby" &&
92
+ payload[:connection_id] &&
93
+ ObjectSpace.respond_to?(:_id2ref)
94
+ end
77
95
  end
78
96
  end
79
97
  end
@@ -39,8 +39,8 @@ module Atatus # :nodoc:
39
39
  end
40
40
 
41
41
  def self.build(config)
42
- normalizers = @registered.each_with_object({}) do |(name, klass), built|
43
- built[name] = klass.new(config)
42
+ normalizers = @registered.transform_values do |klass|
43
+ klass.new(config)
44
44
  end
45
45
 
46
46
  Collection.new(normalizers)