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,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ module Metrics
5
+ # @api private
6
+ class CpuMem
7
+ include Logging
8
+
9
+ # @api private
10
+ class Sample
11
+ # rubocop:disable Metrics/ParameterLists
12
+ def initialize(
13
+ system_cpu_total:,
14
+ system_cpu_usage:,
15
+ system_memory_total:,
16
+ system_memory_free:,
17
+ process_cpu_usage:,
18
+ process_memory_size:,
19
+ process_memory_rss:,
20
+ page_size:
21
+ )
22
+ @system_cpu_total = system_cpu_total
23
+ @system_cpu_usage = system_cpu_usage
24
+ @system_memory_total = system_memory_total
25
+ @system_memory_free = system_memory_free
26
+ @process_cpu_usage = process_cpu_usage
27
+ @process_memory_size = process_memory_size
28
+ @process_memory_rss = process_memory_rss
29
+ @page_size = page_size
30
+ end
31
+ # rubocop:enable Metrics/ParameterLists
32
+
33
+ attr_accessor :system_cpu_total, :system_cpu_usage,
34
+ :system_memory_total, :system_memory_free, :process_cpu_usage,
35
+ :process_memory_size, :process_memory_rss, :page_size
36
+
37
+ def delta(previous)
38
+ dup.tap do |sample|
39
+ sample.system_cpu_total =
40
+ system_cpu_total - previous.system_cpu_total
41
+ sample.system_cpu_usage =
42
+ system_cpu_usage - previous.system_cpu_usage
43
+ sample.process_cpu_usage =
44
+ process_cpu_usage - previous.process_cpu_usage
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize(config)
50
+ @config = config
51
+ @sampler = sampler_for_platform(Metrics.platform)
52
+ end
53
+
54
+ attr_reader :config, :sampler
55
+
56
+ def sample
57
+ @sampler.sample
58
+ end
59
+
60
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
61
+ def collect
62
+ return unless sampler
63
+
64
+ current = sample
65
+
66
+ unless @previous
67
+ @previous = current
68
+ return
69
+ end
70
+
71
+ delta = current.delta(@previous)
72
+
73
+ cpu_usage_pct = delta.system_cpu_usage.to_f / delta.system_cpu_total
74
+ cpu_process_pct = delta.process_cpu_usage.to_f / delta.system_cpu_total
75
+
76
+ @previous = current
77
+
78
+ {
79
+ 'system.cpu.total.norm.pct': cpu_usage_pct,
80
+ 'system.memory.actual.free': current.system_memory_free,
81
+ 'system.memory.total': current.system_memory_total,
82
+ 'system.process.cpu.total.norm.pct': cpu_process_pct,
83
+ 'system.process.memory.size': current.process_memory_size,
84
+ 'system.process.memory.rss.bytes':
85
+ current.process_memory_rss * current.page_size
86
+ }
87
+ end
88
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
89
+
90
+ private
91
+
92
+ def sampler_for_platform(platform)
93
+ case platform
94
+ when :linux then Linux.new
95
+ else
96
+ warn "Unsupported platform '#{platform}' - Disabling metrics"
97
+ @disabled = true
98
+ nil
99
+ end
100
+ end
101
+
102
+ # @api private
103
+ class Linux
104
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
105
+ def sample
106
+ proc_stat = ProcStat.new.read!
107
+ proc_self_stat = ProcSelfStat.new.read!
108
+ meminfo = Meminfo.new.read!
109
+
110
+ Sample.new(
111
+ system_cpu_total: proc_stat.total,
112
+ system_cpu_usage: proc_stat.usage,
113
+ system_memory_total: meminfo.total,
114
+ system_memory_free: meminfo.available,
115
+ process_cpu_usage: proc_self_stat.total,
116
+ process_memory_size: proc_self_stat.vsize,
117
+ process_memory_rss: proc_self_stat.rss,
118
+ page_size: meminfo.page_size
119
+ )
120
+ end
121
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
122
+
123
+ # @api private
124
+ class ProcStat
125
+ attr_reader :total, :usage
126
+
127
+ CPU_FIELDS = %i[
128
+ user
129
+ nice
130
+ system
131
+ idle
132
+ iowait
133
+ irq
134
+ softirq
135
+ steal
136
+ guest
137
+ guest_nice
138
+ ].freeze
139
+
140
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
141
+ def read!
142
+ stat =
143
+ IO.readlines('/proc/stat')
144
+ .lazy
145
+ .find { |sp| sp.start_with?('cpu ') }
146
+ .split
147
+ .map(&:to_i)[1..-1]
148
+
149
+ values =
150
+ CPU_FIELDS.each_with_index.each_with_object({}) do |(key, i), v|
151
+ v[key] = stat[i] || 0
152
+ end
153
+
154
+ @total =
155
+ values[:user] +
156
+ values[:nice] +
157
+ values[:system] +
158
+ values[:idle] +
159
+ values[:iowait] +
160
+ values[:irq] +
161
+ values[:softirq] +
162
+ values[:steal]
163
+
164
+ @usage = @total - (values[:idle] + values[:iowait])
165
+
166
+ self
167
+ end
168
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
169
+ end
170
+
171
+ UTIME_POS = 13
172
+ STIME_POS = 14
173
+ VSIZE_POS = 22
174
+ RSS_POS = 23
175
+
176
+ # @api private
177
+ class ProcSelfStat
178
+ attr_reader :total, :vsize, :rss
179
+
180
+ def read!
181
+ stat =
182
+ IO.readlines('/proc/self/stat')
183
+ .lazy
184
+ .first
185
+ .split
186
+ .map(&:to_i)
187
+
188
+ @total = stat[UTIME_POS] + stat[STIME_POS]
189
+ @vsize = stat[VSIZE_POS]
190
+ @rss = stat[RSS_POS]
191
+
192
+ self
193
+ end
194
+ end
195
+
196
+ # @api private
197
+ class Meminfo
198
+ attr_reader :total, :available, :page_size
199
+
200
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
201
+ # rubocop:disable Metrics/PerceivedComplexity
202
+ # rubocop:disable Metrics/CyclomaticComplexity
203
+ def read!
204
+ # rubocop:disable Style/RescueModifier
205
+ @page_size = `getconf PAGESIZE`.chomp.to_i rescue 4096
206
+ # rubocop:enable Style/RescueModifier
207
+
208
+ info =
209
+ IO.readlines('/proc/meminfo')
210
+ .lazy
211
+ .each_with_object({}) do |line, hsh|
212
+ if line.start_with?('MemTotal:')
213
+ hsh[:total] = line.split[1].to_i * 1024
214
+ elsif line.start_with?('MemAvailable:')
215
+ hsh[:available] = line.split[1].to_i * 1024
216
+ elsif line.start_with?('MemFree:')
217
+ hsh[:free] = line.split[1].to_i * 1024
218
+ elsif line.start_with?('Buffers:')
219
+ hsh[:buffers] = line.split[1].to_i * 1024
220
+ elsif line.start_with?('Cached:')
221
+ hsh[:cached] = line.split[1].to_i * 1024
222
+ end
223
+
224
+ break hsh if hsh[:total] && hsh[:available]
225
+ end
226
+
227
+ @total = info[:total]
228
+ @available =
229
+ info[:available] || info[:free] + info[:buffers] + info[:cached]
230
+
231
+ self
232
+ end
233
+ # rubocop:enable Metrics/CyclomaticComplexity
234
+ # rubocop:enable Metrics/PerceivedComplexity
235
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ module Metrics
5
+ # @api private
6
+ class VM
7
+ include Logging
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ @total_time = 0
12
+ @disabled = false
13
+ end
14
+
15
+ attr_reader :config
16
+ attr_writer :disabled
17
+
18
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
19
+ # rubocop:disable Metrics/CyclomaticComplexity
20
+ def collect
21
+ return if disabled?
22
+
23
+ stat = GC.stat
24
+ thread_count = Thread.list.count
25
+
26
+ sample = {
27
+ 'ruby.gc.count': stat[:count],
28
+ 'ruby.threads': thread_count
29
+ }
30
+
31
+ (live_slots = stat[:heap_live_slots]) &&
32
+ sample[:'ruby.heap.slots.live'] = live_slots
33
+ (heap_slots = stat[:heap_free_slots]) &&
34
+ sample[:'ruby.heap.slots.free'] = heap_slots
35
+ (allocated = stat[:total_allocated_objects]) &&
36
+ sample[:'ruby.heap.allocations.total'] = allocated
37
+
38
+ return sample unless GC::Profiler.enabled?
39
+
40
+ @total_time += GC::Profiler.total_time
41
+ GC::Profiler.clear
42
+ sample[:'ruby.gc.time'] = @total_time
43
+
44
+ sample
45
+ rescue TypeError => e
46
+ error 'VM metrics encountered error: %s', e
47
+ debug('Backtrace:') { e.backtrace.join("\n") }
48
+
49
+ @disabled = true
50
+ nil
51
+ end
52
+ # rubocop:enable Metrics/CyclomaticComplexity
53
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
54
+
55
+ def disabled?
56
+ @disabled
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ class Metricset
6
+ def initialize(timestamp: Util.micros, labels: nil, **samples)
7
+ @timestamp = timestamp
8
+ @labels = labels
9
+ @samples = samples
10
+ end
11
+
12
+ attr_accessor :timestamp
13
+ attr_reader :samples, :labels
14
+
15
+ def empty?
16
+ samples.empty?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # frozen_string_literal: true
3
+
4
+ module Atatus
5
+ # @api private
6
+ class Middleware
7
+ include Logging
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
14
+ def call(env)
15
+ begin
16
+ if running? && !path_ignored?(env)
17
+ transaction = start_transaction(env)
18
+ end
19
+
20
+ resp = @app.call env
21
+ rescue InternalError
22
+ raise # Don't report Atatus errors
23
+ rescue ::Exception => e
24
+ context = Atatus.build_context(rack_env: env, for_type: :error)
25
+ Atatus.report(e, context: context, handled: false)
26
+ raise
27
+ ensure
28
+ if resp && transaction
29
+ status, headers, _body = resp
30
+ transaction.add_response(status, headers: headers.dup)
31
+ end
32
+
33
+ Atatus.end_transaction http_result(status)
34
+ end
35
+
36
+ resp
37
+ end
38
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
39
+
40
+ private
41
+
42
+ def http_result(status)
43
+ status && "HTTP #{status.to_s[0]}xx"
44
+ end
45
+
46
+ def path_ignored?(env)
47
+ config.ignore_url_patterns.any? do |r|
48
+ env['PATH_INFO'].match r
49
+ end
50
+ end
51
+
52
+ def start_transaction(env)
53
+ context = Atatus.build_context(rack_env: env, for_type: :transaction)
54
+
55
+ Atatus.start_transaction 'Rack', 'request',
56
+ context: context,
57
+ trace_context: trace_context(env)
58
+ end
59
+
60
+ def trace_context(env)
61
+ return unless (header = env['HTTP_ATATUS_APM_TRACEPARENT'])
62
+ TraceContext.parse(header)
63
+ rescue TraceContext::InvalidTraceparentHeader
64
+ warn "Couldn't parse invalid traceparent header: #{header.inspect}"
65
+ nil
66
+ end
67
+
68
+ def running?
69
+ Atatus.running?
70
+ end
71
+
72
+ def config
73
+ @config ||= Atatus.agent.config
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module NaivelyHashable
6
+ def naively_hashable?
7
+ true
8
+ end
9
+
10
+ def to_h
11
+ instance_variables.each_with_object({}) do |name, h|
12
+ key = name.to_s.delete('@').to_sym
13
+ value = instance_variable_get(name)
14
+ is_hashable =
15
+ value.respond_to?(:naively_hashable?) && value.naively_hashable?
16
+
17
+ h[key] = is_hashable ? value.to_h : value
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus # :nodoc:
4
+ # @api private
5
+ module Normalizers
6
+ # @api privagte
7
+ class Normalizer
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def self.register(name)
13
+ Normalizers.register(name, self)
14
+ end
15
+ end
16
+
17
+ def self.register(name, klass)
18
+ @registered ||= {}
19
+ @registered[name] = klass
20
+ end
21
+
22
+ def self.build(config)
23
+ normalizers = @registered.each_with_object({}) do |(name, klass), built|
24
+ built[name] = klass.new(config)
25
+ end
26
+
27
+ Collection.new(normalizers)
28
+ end
29
+
30
+ # @api private
31
+ class Collection
32
+ # @api private
33
+ class SkipNormalizer
34
+ def initialize; end
35
+
36
+ def normalize(*_args)
37
+ :skip
38
+ end
39
+ end
40
+
41
+ def initialize(normalizers)
42
+ @normalizers = normalizers
43
+ @default = SkipNormalizer.new
44
+ end
45
+
46
+ def for(name)
47
+ @normalizers.fetch(name) { @default }
48
+ end
49
+
50
+ def keys
51
+ @normalizers.keys
52
+ end
53
+
54
+ def normalize(transaction, name, payload)
55
+ self.for(name).normalize(transaction, name, payload)
56
+ end
57
+ end
58
+ end
59
+
60
+ %w[
61
+ action_controller
62
+ action_mailer
63
+ action_view
64
+ active_record
65
+ ].each do |lib|
66
+ require "atatus/normalizers/#{lib}"
67
+ end
68
+ end