atatus 1.7.0 → 2.0.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 (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)