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,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ module Normalizers
5
+ module ActionController
6
+ # @api private
7
+ class ProcessActionNormalizer < Normalizer
8
+ register 'process_action.action_controller'
9
+
10
+ TYPE = 'app'
11
+ SUBTYPE = 'controller'
12
+ ACTION = 'action'
13
+
14
+ def normalize(transaction, _name, payload)
15
+ transaction.name = endpoint(payload)
16
+ [transaction.name, TYPE, SUBTYPE, ACTION, nil]
17
+ end
18
+
19
+ private
20
+
21
+ def endpoint(payload)
22
+ "#{payload[:controller]}##{payload[:action]}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ module Normalizers
5
+ module ActionMailer
6
+ # @api private
7
+ class ProcessActionNormalizer < Normalizer
8
+ register 'process.action_mailer'
9
+
10
+ TYPE = 'app'
11
+ SUBTYPE = 'mailer'
12
+ ACTION = 'action'
13
+
14
+ def normalize(_transaction, _name, payload)
15
+ [endpoint(payload), TYPE, SUBTYPE, ACTION, nil]
16
+ end
17
+
18
+ private
19
+
20
+ def endpoint(payload)
21
+ "#{payload[:mailer]}##{payload[:action]}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ module Normalizers
5
+ module ActionView
6
+ # @api private
7
+ class RenderNormalizer < Normalizer
8
+ private
9
+
10
+ def normalize_render(payload, type, subtype, action)
11
+ [path_for(payload[:identifier]), type, subtype, action, nil]
12
+ end
13
+
14
+ def path_for(path)
15
+ return 'Unknown template' unless path
16
+ return path unless path.start_with?('/')
17
+
18
+ view_path(path) || gem_path(path) || 'Absolute path'
19
+ end
20
+
21
+ def view_path(path)
22
+ root = @config.__view_paths.find { |vp| path.start_with?(vp) }
23
+ return unless root
24
+
25
+ strip_root(root, path)
26
+ end
27
+
28
+ def gem_path(path)
29
+ root = Gem.path.find { |gp| path.start_with? gp }
30
+ return unless root
31
+
32
+ format '$GEM_PATH/%s', strip_root(root, path)
33
+ end
34
+
35
+ def strip_root(root, path)
36
+ start = root.length + 1
37
+ path[start, path.length]
38
+ end
39
+ end
40
+
41
+ # @api private
42
+ class RenderTemplateNormalizer < RenderNormalizer
43
+ register 'render_template.action_view'
44
+ TYPE = 'template'
45
+ SUBTYPE = 'view'
46
+
47
+ def normalize(_transaction, _name, payload)
48
+ normalize_render(payload, TYPE, SUBTYPE, nil)
49
+ end
50
+ end
51
+
52
+ # @api private
53
+ class RenderPartialNormalizer < RenderNormalizer
54
+ register 'render_partial.action_view'
55
+ TYPE = 'template'
56
+ SUBTYPE = 'view'
57
+ ACTION = 'partial'
58
+
59
+ def normalize(_transaction, _name, payload)
60
+ normalize_render(payload, TYPE, SUBTYPE, ACTION)
61
+ end
62
+ end
63
+
64
+ # @api private
65
+ class RenderCollectionNormalizer < RenderNormalizer
66
+ register 'render_collection.action_view'
67
+ TYPE = 'template'
68
+ SUBTYPE = 'view'
69
+ ACTION = 'collection'
70
+
71
+ def normalize(_transaction, _name, payload)
72
+ normalize_render(payload, TYPE, SUBTYPE, ACTION)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/sql_summarizer'
4
+
5
+ module Atatus
6
+ module Normalizers
7
+ module ActiveRecord
8
+ # @api private
9
+ class SqlNormalizer < Normalizer
10
+ register 'sql.active_record'
11
+
12
+ TYPE = 'db'
13
+ ACTION = 'sql'
14
+
15
+ def initialize(*args)
16
+ super
17
+
18
+ @subtype = lookup_adapter || 'unknown'
19
+ @summarizer = SqlSummarizer.new
20
+ end
21
+
22
+ def normalize(_transaction, _name, payload)
23
+ return :skip if %w[SCHEMA CACHE].include?(payload[:name])
24
+
25
+ name = summarize(payload[:sql]) || payload[:name]
26
+ context =
27
+ Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
28
+ [name, TYPE, @subtype, ACTION, context]
29
+ end
30
+
31
+ private
32
+
33
+ def summarize(sql)
34
+ @summarizer.summarize(sql)
35
+ end
36
+
37
+ def lookup_adapter
38
+ ::ActiveRecord::Base.connection.adapter_name.downcase
39
+ rescue StandardError
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,346 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus'
4
+ require 'opentracing'
5
+
6
+ module Atatus
7
+ module OpenTracing
8
+ # @api private
9
+ class Span
10
+ def initialize(atatus_span, span_context)
11
+ @atatus_span = atatus_span
12
+ @span_context = span_context
13
+ end
14
+
15
+ attr_reader :atatus_span
16
+
17
+ def operation_name=(name)
18
+ atatus_span.name = name
19
+ end
20
+
21
+ def context
22
+ @span_context
23
+ end
24
+
25
+ # rubocop:disable Metrics/MethodLength
26
+ def set_label(key, val)
27
+ if atatus_span.is_a?(Transaction)
28
+ case key.to_s
29
+ when 'type'
30
+ atatus_span.type = val
31
+ when 'result'
32
+ atatus_span.result = val
33
+ when /user\.(\w+)/
34
+ set_user_value($1, val)
35
+ else
36
+ atatus_span.context.labels[key] = val
37
+ end
38
+ else
39
+ atatus_span.context.labels[key] = val
40
+ end
41
+ end
42
+ # rubocop:enable Metrics/MethodLength
43
+
44
+ def set_baggage_item(_key, _value)
45
+ Atatus.agent.config.logger.warn(
46
+ 'Baggage is not supported by Atatus'
47
+ )
48
+ end
49
+
50
+ def get_baggage_item(_key)
51
+ Atatus.agent.config.logger.warn(
52
+ 'Baggage is not supported by Atatus'
53
+ )
54
+
55
+ nil
56
+ end
57
+
58
+ # rubocop:disable Lint/UnusedMethodArgument
59
+ def log_kv(timestamp: nil, **fields)
60
+ if (exception = fields[:'error.object'])
61
+ Atatus.report exception
62
+ elsif (message = fields[:message])
63
+ Atatus.report_message message
64
+ end
65
+ end
66
+ # rubocop:enable Lint/UnusedMethodArgument
67
+
68
+ # rubocop:disable Metrics/MethodLength
69
+ def finish(clock_end: Util.monotonic_micros, end_time: nil)
70
+ return unless (instrumenter = Atatus.agent&.instrumenter)
71
+
72
+ if end_time
73
+ warn '[Atatus] DEPRECATED: Setting a custom end time as a ' \
74
+ '`Time` is deprecated. Use `clock_end:` and monotonic time instead.'
75
+ clock_end = end_time
76
+ end
77
+
78
+ atatus_span.done clock_end: clock_end
79
+
80
+ case atatus_span
81
+ when Atatus::Transaction
82
+ instrumenter.current_transaction = nil
83
+ when Atatus::Span
84
+ instrumenter.current_spans.delete(atatus_span)
85
+ end
86
+
87
+ instrumenter.enqueue.call atatus_span
88
+ end
89
+ # rubocop:enable Metrics/MethodLength
90
+
91
+ private
92
+
93
+ def set_user_value(key, value)
94
+ return unless atatus_span.is_a?(Transaction)
95
+
96
+ setter = :"#{key}="
97
+ return unless atatus_span.context.user.respond_to?(setter)
98
+ atatus_span.context.user.send(setter, value)
99
+ end
100
+ end
101
+
102
+ # @api private
103
+ class SpanContext
104
+ def initialize(trace_context:, baggage: nil)
105
+ if baggage
106
+ Atatus.agent.config.logger.warn(
107
+ 'Baggage is not supported by Atatus'
108
+ )
109
+ end
110
+
111
+ @trace_context = trace_context
112
+ end
113
+
114
+ attr_accessor :trace_context
115
+
116
+ def self.from_trace_context(trace_context)
117
+ new(trace_context: trace_context)
118
+ end
119
+ end
120
+
121
+ # @api private
122
+ class Scope
123
+ def initialize(span, scope_stack, finish_on_close:)
124
+ @span = span
125
+ @scope_stack = scope_stack
126
+ @finish_on_close = finish_on_close
127
+ end
128
+
129
+ attr_reader :span
130
+
131
+ def atatus_span
132
+ span.atatus_span
133
+ end
134
+
135
+ def close
136
+ @span.finish if @finish_on_close
137
+ @scope_stack.pop
138
+ end
139
+ end
140
+
141
+ # @api private
142
+ class ScopeStack
143
+ KEY = :__atatus_ot_scope_stack
144
+
145
+ def push(scope)
146
+ scopes << scope
147
+ end
148
+
149
+ def pop
150
+ scopes.pop
151
+ end
152
+
153
+ def last
154
+ scopes.last
155
+ end
156
+
157
+ private
158
+
159
+ def scopes
160
+ Thread.current[KEY] ||= []
161
+ end
162
+ end
163
+
164
+ # @api private
165
+ class ScopeManager
166
+ def initialize
167
+ @scope_stack = ScopeStack.new
168
+ end
169
+
170
+ def activate(span, finish_on_close: true)
171
+ return active if active && active.span == span
172
+
173
+ scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close)
174
+ @scope_stack.push scope
175
+ scope
176
+ end
177
+
178
+ def active
179
+ @scope_stack.last
180
+ end
181
+ end
182
+
183
+ # rubocop:disable Metrics/ClassLength
184
+ # A custom tracer to use the OpenTracing API with Atatus
185
+ class Tracer
186
+ def initialize
187
+ @scope_manager = ScopeManager.new
188
+ end
189
+
190
+ attr_reader :scope_manager
191
+
192
+ def active_span
193
+ scope_manager.active&.span
194
+ end
195
+
196
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
197
+ def start_active_span(
198
+ operation_name,
199
+ child_of: nil,
200
+ references: nil,
201
+ start_time: Time.now,
202
+ labels: {},
203
+ ignore_active_scope: false,
204
+ finish_on_close: true,
205
+ **
206
+ )
207
+ span = start_span(
208
+ operation_name,
209
+ child_of: child_of,
210
+ references: references,
211
+ start_time: start_time,
212
+ labels: labels,
213
+ ignore_active_scope: ignore_active_scope
214
+ )
215
+ scope = scope_manager.activate(span, finish_on_close: finish_on_close)
216
+
217
+ if block_given?
218
+ begin
219
+ yield scope
220
+ ensure
221
+ scope.close
222
+ end
223
+ end
224
+
225
+ scope
226
+ end
227
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
228
+
229
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
230
+ # rubocop:disable Metrics/AbcSize
231
+ def start_span(
232
+ operation_name,
233
+ child_of: nil,
234
+ references: nil,
235
+ start_time: Time.now,
236
+ labels: {},
237
+ ignore_active_scope: false,
238
+ **
239
+ )
240
+ span_context = prepare_span_context(
241
+ child_of: child_of,
242
+ references: references,
243
+ ignore_active_scope: ignore_active_scope
244
+ )
245
+
246
+ if span_context
247
+ trace_context =
248
+ span_context &&
249
+ span_context.respond_to?(:trace_context) &&
250
+ span_context.trace_context
251
+ end
252
+
253
+ atatus_span =
254
+ if Atatus.current_transaction
255
+ Atatus.start_span(
256
+ operation_name,
257
+ trace_context: trace_context
258
+ )
259
+ else
260
+ Atatus.start_transaction(
261
+ operation_name,
262
+ trace_context: trace_context
263
+ )
264
+ end
265
+
266
+ # if no Atatus APM agent is running or transaction not sampled
267
+ unless atatus_span
268
+ return ::OpenTracing::Span::NOOP_INSTANCE
269
+ end
270
+
271
+ span_context ||=
272
+ SpanContext.from_trace_context(atatus_span.trace_context)
273
+
274
+ labels.each do |key, value|
275
+ atatus_span.context.labels[key] = value
276
+ end
277
+
278
+ atatus_span.start Util.micros(start_time)
279
+
280
+ Span.new(atatus_span, span_context)
281
+ end
282
+ # rubocop:enable Metrics/AbcSize
283
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
284
+
285
+ def inject(span_context, format, carrier)
286
+ case format
287
+ when ::OpenTracing::FORMAT_RACK
288
+ carrier['atatus-traceparent'] = span_context.to_header
289
+ else
290
+ warn 'Only injection via HTTP headers and Rack is available'
291
+ end
292
+ end
293
+
294
+ def extract(format, carrier)
295
+ case format
296
+ when ::OpenTracing::FORMAT_RACK
297
+ Atatus::TraceContext
298
+ .parse(carrier['HTTP_ATATUS_APM_TRACEPARENT'])
299
+ else
300
+ warn 'Only extraction from HTTP headers via Rack is available'
301
+ nil
302
+ end
303
+ rescue Atatus::TraceContext::InvalidTraceparentHeader
304
+ nil
305
+ end
306
+
307
+ private
308
+
309
+ def prepare_span_context(
310
+ child_of:,
311
+ references:,
312
+ ignore_active_scope:
313
+ )
314
+ context_from_child_of(child_of) ||
315
+ context_from_references(references) ||
316
+ context_from_active_scope(ignore_active_scope)
317
+ end
318
+
319
+ def context_from_child_of(child_of)
320
+ return unless child_of
321
+ child_of.respond_to?(:context) ? child_of.context : child_of
322
+ end
323
+
324
+ def context_from_references(references)
325
+ return if !references || references.none?
326
+
327
+ child_of = references.find do |reference|
328
+ reference.type == ::OpenTracing::Reference::CHILD_OF
329
+ end
330
+
331
+ (child_of || references.first).context
332
+ end
333
+
334
+ def context_from_active_scope(ignore_active_scope)
335
+ if ignore_active_scope
336
+ Atatus.agent&.config&.logger&.warn(
337
+ 'ignore_active_scope might lead to unexpeced results'
338
+ )
339
+ return
340
+ end
341
+ @scope_manager.active&.span&.context
342
+ end
343
+ end
344
+ # rubocop:enable Metrics/ClassLength
345
+ end
346
+ end