atatus 1.0.0

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