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