atatus 1.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 (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +57 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-THIRD-PARTY +205 -0
  7. data/README.md +13 -0
  8. data/Rakefile +19 -0
  9. data/atatus.gemspec +36 -0
  10. data/atatus.yml +2 -0
  11. data/bench/.gitignore +2 -0
  12. data/bench/app.rb +53 -0
  13. data/bench/benchmark.rb +36 -0
  14. data/bench/report.rb +55 -0
  15. data/bench/rubyprof.rb +39 -0
  16. data/bench/stackprof.rb +23 -0
  17. data/bin/build_docs +5 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/bin/with_framework +7 -0
  21. data/lib/atatus.rb +325 -0
  22. data/lib/atatus/agent.rb +260 -0
  23. data/lib/atatus/central_config.rb +141 -0
  24. data/lib/atatus/central_config/cache_control.rb +34 -0
  25. data/lib/atatus/collector/base.rb +329 -0
  26. data/lib/atatus/collector/builder.rb +317 -0
  27. data/lib/atatus/collector/transport.rb +72 -0
  28. data/lib/atatus/config.rb +248 -0
  29. data/lib/atatus/config/bytes.rb +25 -0
  30. data/lib/atatus/config/duration.rb +23 -0
  31. data/lib/atatus/config/options.rb +134 -0
  32. data/lib/atatus/config/regexp_list.rb +13 -0
  33. data/lib/atatus/context.rb +33 -0
  34. data/lib/atatus/context/request.rb +11 -0
  35. data/lib/atatus/context/request/socket.rb +19 -0
  36. data/lib/atatus/context/request/url.rb +42 -0
  37. data/lib/atatus/context/response.rb +22 -0
  38. data/lib/atatus/context/user.rb +42 -0
  39. data/lib/atatus/context_builder.rb +97 -0
  40. data/lib/atatus/deprecations.rb +22 -0
  41. data/lib/atatus/error.rb +22 -0
  42. data/lib/atatus/error/exception.rb +46 -0
  43. data/lib/atatus/error/log.rb +24 -0
  44. data/lib/atatus/error_builder.rb +76 -0
  45. data/lib/atatus/instrumenter.rb +224 -0
  46. data/lib/atatus/internal_error.rb +6 -0
  47. data/lib/atatus/logging.rb +55 -0
  48. data/lib/atatus/metadata.rb +19 -0
  49. data/lib/atatus/metadata/process_info.rb +18 -0
  50. data/lib/atatus/metadata/service_info.rb +61 -0
  51. data/lib/atatus/metadata/system_info.rb +35 -0
  52. data/lib/atatus/metadata/system_info/container_info.rb +121 -0
  53. data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
  54. data/lib/atatus/metadata/system_info/os_info.rb +31 -0
  55. data/lib/atatus/metrics.rb +98 -0
  56. data/lib/atatus/metrics/cpu_mem.rb +240 -0
  57. data/lib/atatus/metrics/vm.rb +60 -0
  58. data/lib/atatus/metricset.rb +19 -0
  59. data/lib/atatus/middleware.rb +76 -0
  60. data/lib/atatus/naively_hashable.rb +21 -0
  61. data/lib/atatus/normalizers.rb +68 -0
  62. data/lib/atatus/normalizers/action_controller.rb +27 -0
  63. data/lib/atatus/normalizers/action_mailer.rb +26 -0
  64. data/lib/atatus/normalizers/action_view.rb +77 -0
  65. data/lib/atatus/normalizers/active_record.rb +45 -0
  66. data/lib/atatus/opentracing.rb +346 -0
  67. data/lib/atatus/rails.rb +61 -0
  68. data/lib/atatus/railtie.rb +30 -0
  69. data/lib/atatus/span.rb +125 -0
  70. data/lib/atatus/span/context.rb +40 -0
  71. data/lib/atatus/span_helpers.rb +44 -0
  72. data/lib/atatus/spies.rb +86 -0
  73. data/lib/atatus/spies/action_dispatch.rb +28 -0
  74. data/lib/atatus/spies/delayed_job.rb +68 -0
  75. data/lib/atatus/spies/elasticsearch.rb +36 -0
  76. data/lib/atatus/spies/faraday.rb +70 -0
  77. data/lib/atatus/spies/http.rb +44 -0
  78. data/lib/atatus/spies/json.rb +22 -0
  79. data/lib/atatus/spies/mongo.rb +87 -0
  80. data/lib/atatus/spies/net_http.rb +70 -0
  81. data/lib/atatus/spies/rake.rb +45 -0
  82. data/lib/atatus/spies/redis.rb +27 -0
  83. data/lib/atatus/spies/sequel.rb +47 -0
  84. data/lib/atatus/spies/sidekiq.rb +89 -0
  85. data/lib/atatus/spies/sinatra.rb +41 -0
  86. data/lib/atatus/spies/tilt.rb +27 -0
  87. data/lib/atatus/sql_summarizer.rb +35 -0
  88. data/lib/atatus/stacktrace.rb +16 -0
  89. data/lib/atatus/stacktrace/frame.rb +52 -0
  90. data/lib/atatus/stacktrace_builder.rb +104 -0
  91. data/lib/atatus/subscriber.rb +77 -0
  92. data/lib/atatus/trace_context.rb +85 -0
  93. data/lib/atatus/transaction.rb +100 -0
  94. data/lib/atatus/transport/base.rb +174 -0
  95. data/lib/atatus/transport/connection.rb +156 -0
  96. data/lib/atatus/transport/connection/http.rb +116 -0
  97. data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
  98. data/lib/atatus/transport/filters.rb +43 -0
  99. data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
  100. data/lib/atatus/transport/serializers.rb +93 -0
  101. data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
  102. data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
  103. data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
  104. data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
  105. data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
  106. data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
  107. data/lib/atatus/transport/worker.rb +73 -0
  108. data/lib/atatus/util.rb +42 -0
  109. data/lib/atatus/util/inflector.rb +93 -0
  110. data/lib/atatus/util/lru_cache.rb +48 -0
  111. data/lib/atatus/util/prefixed_logger.rb +18 -0
  112. data/lib/atatus/util/throttle.rb +35 -0
  113. data/lib/atatus/version.rb +5 -0
  114. data/vendor/.gitkeep +0 -0
  115. metadata +190 -0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ class Metadata
6
+ def initialize(config)
7
+ @service = ServiceInfo.new(config)
8
+ @process = ProcessInfo.new(config)
9
+ @system = SystemInfo.new(config)
10
+ @labels = config.global_labels
11
+ end
12
+
13
+ attr_reader :service, :process, :system, :labels
14
+ end
15
+ end
16
+
17
+ require 'atatus/metadata/service_info'
18
+ require 'atatus/metadata/system_info'
19
+ require 'atatus/metadata/process_info'
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Metadata
5
+ # @api private
6
+ class ProcessInfo
7
+ def initialize(config)
8
+ @config = config
9
+
10
+ @argv = ARGV
11
+ @pid = $PID || Process.pid
12
+ @title = $PROGRAM_NAME
13
+ end
14
+
15
+ attr_reader :argv, :pid, :title
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Metadata
5
+ # @api private
6
+ class ServiceInfo
7
+ # @api private
8
+ class Versioned
9
+ def initialize(name: nil, version: nil)
10
+ @name = name
11
+ @version = version
12
+ end
13
+
14
+ attr_reader :name, :version
15
+ end
16
+ class Agent < Versioned; end
17
+ class Framework < Versioned; end
18
+ class Language < Versioned; end
19
+ class Runtime < Versioned; end
20
+
21
+ # rubocop:disable Metrics/MethodLength
22
+ def initialize(config)
23
+ @config = config
24
+
25
+ @name = @config.service_name
26
+ @environment = @config.environment
27
+ @agent = Agent.new(name: 'ruby', version: VERSION)
28
+ @framework = Framework.new(
29
+ name: @config.framework_name,
30
+ version: @config.framework_version
31
+ )
32
+ @language = Language.new(name: 'ruby', version: RUBY_VERSION)
33
+ @runtime = lookup_runtime
34
+ @version = @config.service_version || Util.git_sha
35
+ end
36
+ # rubocop:enable Metrics/MethodLength
37
+
38
+ attr_reader :name, :environment, :agent, :framework, :language, :runtime,
39
+ :version
40
+
41
+ private
42
+
43
+ # rubocop:disable Metrics/MethodLength
44
+ def lookup_runtime
45
+ case RUBY_ENGINE
46
+ when 'ruby'
47
+ Runtime.new(
48
+ name: RUBY_ENGINE,
49
+ version: RUBY_VERSION || RUBY_ENGINE_VERSION
50
+ )
51
+ when 'jruby'
52
+ Runtime.new(
53
+ name: RUBY_ENGINE,
54
+ version: JRUBY_VERSION || RUBY_ENGINE_VERSION
55
+ )
56
+ end
57
+ end
58
+ # rubocop:enable Metrics/MethodLength
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Metadata
5
+ # @api private
6
+ class SystemInfo
7
+ def initialize(config)
8
+ @config = config
9
+
10
+ @hostname = @config.hostname || `hostname`.chomp
11
+ @architecture = gem_platform.cpu
12
+ @platform = gem_platform.os
13
+
14
+ container_info = ContainerInfo.read!
15
+ @container = container_info.container
16
+ @kubernetes = container_info.kubernetes
17
+
18
+ @hwinfo = HWInfo.read!
19
+ @osinfo = OSInfo.read!
20
+ end
21
+
22
+ attr_reader :hostname, :architecture, :platform, :container, :kubernetes, :hwinfo, :osinfo
23
+
24
+ private
25
+
26
+ def gem_platform
27
+ @gem_platform ||= Gem::Platform.local
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ require 'atatus/metadata/system_info/container_info'
34
+ require 'atatus/metadata/system_info/hw_info'
35
+ require 'atatus/metadata/system_info/os_info'
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Metadata
5
+ class SystemInfo
6
+ # @api private
7
+ class ContainerInfo
8
+ CGROUP_PATH = '/proc/self/cgroup'
9
+
10
+ attr_accessor :container_id, :kubernetes_namespace,
11
+ :kubernetes_node_name, :kubernetes_pod_name, :kubernetes_pod_uid
12
+
13
+ def initialize(cgroup_path: CGROUP_PATH)
14
+ @cgroup_path = cgroup_path
15
+ end
16
+
17
+ attr_reader :cgroup_path
18
+
19
+ def read!
20
+ read_from_cgroup!
21
+ read_from_env!
22
+ self
23
+ end
24
+
25
+ def self.read!
26
+ new.read!
27
+ end
28
+
29
+ def container
30
+ @container ||=
31
+ begin
32
+ return unless container_id
33
+ { id: container_id }
34
+ end
35
+ end
36
+
37
+ # rubocop:disable Metrics/MethodLength
38
+ def kubernetes
39
+ @kubernetes =
40
+ begin
41
+ kubernetes = {
42
+ namespace: kubernetes_namespace,
43
+ node: { name: kubernetes_node_name },
44
+ pod: {
45
+ name: kubernetes_pod_name,
46
+ uid: kubernetes_pod_uid
47
+ }
48
+ }
49
+ return nil if kubernetes.values.all?(&:nil?)
50
+
51
+ kubernetes
52
+ end
53
+ end
54
+ # rubocop:enable Metrics/MethodLength
55
+
56
+ private
57
+
58
+ def read_from_env!
59
+ self.kubernetes_namespace =
60
+ ENV.fetch('KUBERNETES_NAMESPACE', kubernetes_namespace)
61
+ self.kubernetes_node_name =
62
+ ENV.fetch('KUBERNETES_NODE_NAME', kubernetes_node_name)
63
+ self.kubernetes_pod_name =
64
+ ENV.fetch('KUBERNETES_POD_NAME', kubernetes_pod_name)
65
+ self.kubernetes_pod_uid =
66
+ ENV.fetch('KUBERNETES_POD_UID', kubernetes_pod_uid)
67
+ end
68
+
69
+ CONTAINER_ID_REGEX = /^[0-9A-Fa-f]{64}$/.freeze
70
+ KUBEPODS_REGEX = %r{(?:^/kubepods/[^/]+/pod([^/]+)$)|(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice$)}.freeze # rubocop:disable Metrics/LineLength
71
+ SYSTEMD_SCOPE_SUFFIX = '.scope'
72
+
73
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
74
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
75
+ def read_from_cgroup!
76
+ return unless File.exist?(cgroup_path)
77
+ IO.readlines(cgroup_path).each do |line|
78
+ parts = line.strip.split(':')
79
+ next if parts.length != 3
80
+
81
+ cgroup_path = parts[2]
82
+
83
+ # Depending on the filesystem driver used for cgroup
84
+ # management, the paths in /proc/pid/cgroup will have
85
+ # one of the following formats in a Docker container:
86
+ #
87
+ # systemd: /system.slice/docker-<container-ID>.scope
88
+ # cgroupfs: /docker/<container-ID>
89
+ #
90
+ # In a Kubernetes pod, the cgroup path will look like:
91
+ #
92
+ # systemd:
93
+ # /kubepods.slice/kubepods-<QoS-class>.slice/kubepods-\
94
+ # <QoS-class>-pod<pod-UID>.slice/<container-iD>.scope
95
+ # cgroupfs:
96
+ # /kubepods/<QoS-class>/pod<pod-UID>/<container-iD>
97
+ directory, container_id = File.split(cgroup_path)
98
+
99
+ if container_id.end_with?(SYSTEMD_SCOPE_SUFFIX)
100
+ container_id = container_id[0...-SYSTEMD_SCOPE_SUFFIX.length]
101
+ if container_id.include?('-')
102
+ container_id = container_id.split('-', 2)[1]
103
+ end
104
+ end
105
+
106
+ if (kubepods_match = KUBEPODS_REGEX.match(directory))
107
+ pod_id = kubepods_match[1] || kubepods_match[2]
108
+
109
+ self.container_id = container_id
110
+ self.kubernetes_pod_uid = pod_id
111
+ elsif CONTAINER_ID_REGEX.match(container_id)
112
+ self.container_id = container_id
113
+ end
114
+ end
115
+ end
116
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
117
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+ require 'rbconfig'
3
+ require 'socket'
4
+
5
+ module Atatus
6
+ class Metadata
7
+ class SystemInfo
8
+ # @api private
9
+ class HWInfo
10
+ attr_accessor :cpuinfo_cores, :cpuinfo_model, :cpuinfo_mhz,
11
+ :meminfo_total, :host_bootid
12
+
13
+ def initialize
14
+ @os = RbConfig::CONFIG['target_os']
15
+ end
16
+
17
+ def read!
18
+ if @os =~ /(linux)/i
19
+ read_from_cpuinfo!
20
+ read_from_meminfo!
21
+ read_from_bootid!
22
+ elsif @os =~ /(darwin)/i
23
+ read_cpuinfo_from_sysctl!
24
+ read_meminfo_from_sysctl!
25
+ end
26
+ self
27
+ end
28
+
29
+ def self.read!
30
+ new.read!
31
+ end
32
+
33
+ # rubocop:disable Metrics/MethodLength
34
+ def cpuinfo
35
+ @cpuinfo ||=
36
+ begin
37
+ cpuinfo = {
38
+ cores: @cpuinfo_cores,
39
+ model: @cpuinfo_model
40
+ }
41
+ cpuinfo[:mhz] = @cpuinfo_mhz unless @cpuinfo_mhz.nil?
42
+
43
+ cpuinfo
44
+ end
45
+ end
46
+ # rubocop:enable Metrics/MethodLength
47
+
48
+ def meminfo
49
+ @meminfo ||= @meminfo_total
50
+ end
51
+
52
+ # rubocop:disable Metrics/MethodLength
53
+ def hostid
54
+ @hostid ||=
55
+ begin
56
+ return Socket.gethostname if @host_bootid.nil?
57
+ @host_bootid
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/MethodLength
61
+
62
+ private
63
+
64
+ def get_sysctl_value(key)
65
+ `sysctl -n #{key} 2>/dev/null`
66
+ end
67
+
68
+ LINUX_CPUINFO_PATH = '/proc/cpuinfo'.freeze
69
+ PROCESSOR_COUNT_REGEX = /^processor\s*:/.freeze
70
+ MODEL_NAME_REGEX = /model name.+:(.+)/.freeze
71
+ CPU_MHZ_REGEX = /cpu MHz\s+:\s+([0-9.]+)/.freeze
72
+
73
+ LINUX_MEMINFO_PATH = '/proc/meminfo'.freeze
74
+ TOTAL_MEMORY_REGEX = /^MemTotal:\s+(\d+)\skB$/.freeze
75
+
76
+ LINUX_BOOTID_PATH = '/proc/sys/kernel/random/boot_id'.freeze
77
+
78
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
79
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
80
+ def read_from_cpuinfo!
81
+ return unless File.exist?(LINUX_CPUINFO_PATH)
82
+ cpuinfo = File.read(LINUX_CPUINFO_PATH)
83
+ self.cpuinfo_cores = cpuinfo.scan(PROCESSOR_COUNT_REGEX).size
84
+ self.cpuinfo_model = cpuinfo.scan(MODEL_NAME_REGEX).flatten.first.strip
85
+ self.cpuinfo_mhz = cpuinfo.scan(CPU_MHZ_REGEX).flatten.first
86
+ end
87
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
88
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
89
+
90
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
91
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
92
+ def read_from_meminfo!
93
+ return unless File.exist?(LINUX_MEMINFO_PATH)
94
+ meminfo = File.read(LINUX_MEMINFO_PATH)
95
+ self.meminfo_total = meminfo.scan(TOTAL_MEMORY_REGEX).flatten.first.to_i
96
+ end
97
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
98
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
99
+
100
+ def read_from_bootid!
101
+ return unless File.exist?(LINUX_BOOTID_PATH)
102
+ self.host_bootid = File.read(LINUX_BOOTID_PATH)
103
+ self.host_bootid.strip!
104
+ end
105
+
106
+ def read_cpuinfo_from_sysctl!
107
+ self.cpuinfo_cores = get_sysctl_value('hw.logicalcpu_max').to_i
108
+ self.cpuinfo_model = get_sysctl_value('machdep.cpu.brand_string').strip
109
+ end
110
+
111
+ def read_meminfo_from_sysctl!
112
+ self.meminfo_total = get_sysctl_value('hw.memsize').to_i
113
+ self.meminfo_total
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Metadata
5
+ class SystemInfo
6
+ # @api private
7
+ class OSInfo
8
+ attr_accessor :os, :kernel
9
+
10
+ def initialize
11
+ end
12
+
13
+ def read!
14
+ read_from_uname!
15
+ self
16
+ end
17
+
18
+ def self.read!
19
+ new.read!
20
+ end
21
+
22
+ private
23
+
24
+ def read_from_uname!
25
+ self.os = `uname -s`.strip.downcase!
26
+ self.kernel = `uname -r`.strip
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/metricset'
4
+
5
+ module Atatus
6
+ # @api private
7
+ module Metrics
8
+ MUTEX = Mutex.new
9
+
10
+ def self.new(config, &block)
11
+ Collector.new(config, &block)
12
+ end
13
+
14
+ def self.platform
15
+ @platform ||= Gem::Platform.local.os.to_sym
16
+ end
17
+
18
+ # @api private
19
+ class Collector
20
+ include Logging
21
+
22
+ TIMEOUT_INTERVAL = 5 # seconds
23
+
24
+ def initialize(config, labels: nil, &block)
25
+ @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
+ @callback = block
32
+ end
33
+
34
+ attr_reader :config, :samplers, :callback, :labels
35
+
36
+ # rubocop:disable Metrics/MethodLength
37
+ def start
38
+ unless config.collect_metrics?
39
+ debug 'Skipping metrics'
40
+ return
41
+ end
42
+
43
+ debug 'Starting metrics'
44
+
45
+ @timer_task = Concurrent::TimerTask.execute(
46
+ run_now: true,
47
+ execution_interval: config.metrics_interval,
48
+ timeout_interval: TIMEOUT_INTERVAL
49
+ ) do
50
+ begin
51
+ debug 'Collecting metrics'
52
+ collect_and_send
53
+ true
54
+ rescue StandardError => e
55
+ error 'Error while collecting metrics: %e', e.inspect
56
+ debug { e.backtrace.join("\n") }
57
+ false
58
+ end
59
+ end
60
+
61
+ @running = true
62
+ end
63
+ # rubocop:enable Metrics/MethodLength
64
+
65
+ def stop
66
+ return unless running?
67
+
68
+ debug 'Stopping metrics'
69
+
70
+ @timer_task.shutdown
71
+ @running = false
72
+ end
73
+
74
+ def running?
75
+ !!@running
76
+ end
77
+
78
+ def collect_and_send
79
+ metricset = Metricset.new(labels: labels, **collect)
80
+ return if metricset.empty?
81
+
82
+ callback.call(metricset)
83
+ end
84
+
85
+ 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
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ require 'atatus/metrics/cpu_mem'
98
+ require 'atatus/metrics/vm'