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,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