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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/subscriber'
4
+
5
+ module Atatus
6
+ # Module for explicitly starting the Atatus agent and hooking into Rails.
7
+ # It is recommended to use the Railtie instead.
8
+ module Rails
9
+ extend self
10
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
11
+ # rubocop:disable Metrics/CyclomaticComplexity
12
+ # Start the Atatus agent and hook into Rails.
13
+ # Note that the agent won't be started if the Rails console is being used.
14
+ #
15
+ # @param config [Config, Hash] An instance of Config or a Hash config.
16
+ # @return [true, nil] true if the agent was started, nil otherwise.
17
+ def start(config)
18
+ config = Config.new(config) unless config.is_a?(Config)
19
+ if (reason = should_skip?(config))
20
+ unless config.disable_start_message?
21
+ config.logger.info "Skipping because: #{reason}. " \
22
+ "Start manually with `Atatus.start'"
23
+ end
24
+ return
25
+ end
26
+
27
+ Atatus.start(config).tap do |agent|
28
+ attach_subscriber(agent)
29
+ end
30
+
31
+ if Atatus.running? &&
32
+ !Atatus.agent.config.disabled_instrumentations.include?(
33
+ 'action_dispatch'
34
+ )
35
+ require 'atatus/spies/action_dispatch'
36
+ end
37
+ Atatus.running?
38
+ rescue StandardError => e
39
+ config.logger.error format('Failed to start: %s', e.message)
40
+ config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
41
+ end
42
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
43
+ # rubocop:enable Metrics/CyclomaticComplexity
44
+
45
+ private
46
+
47
+ def should_skip?(_config)
48
+ if ::Rails.const_defined? 'Rails::Console'
49
+ return 'Rails console'
50
+ end
51
+
52
+ nil
53
+ end
54
+
55
+ def attach_subscriber(agent)
56
+ return unless agent
57
+
58
+ agent.instrumenter.subscriber = Atatus::Subscriber.new(agent)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/rails'
4
+
5
+ module Atatus
6
+ # @api private
7
+ class Railtie < ::Rails::Railtie
8
+ config.atatus = ActiveSupport::OrderedOptions.new
9
+
10
+ Config.schema.each do |key, args|
11
+ next unless args.length > 1
12
+ config.atatus[key] = args.last[:default]
13
+ end
14
+
15
+ initializer 'atatus.initialize' do |app|
16
+ config = Config.new(app.config.atatus).tap do |c|
17
+ c.app = app
18
+
19
+ # Prepend Rails.root to log_path if present
20
+ if c.log_path && !c.log_path.start_with?('/')
21
+ c.log_path = ::Rails.root.join(c.log_path)
22
+ end
23
+ end
24
+
25
+ if Rails.start(config)
26
+ app.middleware.insert 0, Middleware
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'forwardable'
5
+
6
+ require 'atatus/span/context'
7
+
8
+ module Atatus
9
+ # @api private
10
+ class Span
11
+ extend Forwardable
12
+
13
+ def_delegators :@trace_context, :trace_id, :parent_id, :id
14
+
15
+ DEFAULT_TYPE = 'custom'
16
+
17
+ # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
18
+ def initialize(
19
+ name:,
20
+ transaction_id:,
21
+ trace_context:,
22
+ type: nil,
23
+ subtype: nil,
24
+ action: nil,
25
+ context: nil,
26
+ stacktrace_builder: nil
27
+ )
28
+ @name = name
29
+
30
+ if subtype.nil? && type&.include?('.')
31
+ @type, @subtype, @action = type.split('.')
32
+ else
33
+ @type = type || DEFAULT_TYPE
34
+ @subtype = subtype
35
+ @action = action
36
+ end
37
+
38
+ @transaction_id = transaction_id
39
+ @trace_context = trace_context
40
+
41
+ @context = context || Span::Context.new
42
+ @stacktrace_builder = stacktrace_builder
43
+ end
44
+ # rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
45
+
46
+ attr_accessor(
47
+ :action,
48
+ :name,
49
+ :original_backtrace,
50
+ :subtype,
51
+ :trace_context,
52
+ :type
53
+ )
54
+ attr_reader(
55
+ :context,
56
+ :duration,
57
+ :stacktrace,
58
+ :timestamp,
59
+ :transaction_id
60
+ )
61
+
62
+ # life cycle
63
+
64
+ def start(clock_start = Util.monotonic_micros)
65
+ @timestamp = Util.micros
66
+ @clock_start = clock_start
67
+ self
68
+ end
69
+
70
+ def stop(clock_end = Util.monotonic_micros)
71
+ @duration ||= (clock_end - @clock_start)
72
+ self
73
+ end
74
+
75
+ def done(clock_end: Util.monotonic_micros)
76
+ stop clock_end
77
+
78
+ build_stacktrace! if should_build_stacktrace?
79
+ self.original_backtrace = nil # release original
80
+
81
+ self
82
+ end
83
+
84
+ def stopped?
85
+ !!duration
86
+ end
87
+
88
+ def started?
89
+ !!timestamp
90
+ end
91
+
92
+ def running?
93
+ started? && !stopped?
94
+ end
95
+
96
+ # relations
97
+
98
+ def inspect
99
+ "<Atatus::Span id:#{id}" \
100
+ " name:#{name.inspect}" \
101
+ " type:#{type.inspect}" \
102
+ '>'
103
+ end
104
+
105
+ private
106
+
107
+ def build_stacktrace!
108
+ @stacktrace = @stacktrace_builder.build(original_backtrace, type: :span)
109
+ end
110
+
111
+ def should_build_stacktrace?
112
+ @stacktrace_builder && original_backtrace && long_enough_for_stacktrace?
113
+ end
114
+
115
+ def long_enough_for_stacktrace?
116
+ min_duration =
117
+ @stacktrace_builder.config.span_frames_min_duration_us
118
+
119
+ return true if min_duration < 0
120
+ return false if min_duration == 0
121
+
122
+ duration >= min_duration
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ class Span
5
+ # @api private
6
+ class Context
7
+ def initialize(db: nil, http: nil, labels: {})
8
+ @sync = true
9
+ @db = db && Db.new(db)
10
+ @http = http && Http.new(http)
11
+ @labels = labels
12
+ end
13
+
14
+ attr_accessor :sync, :db, :http, :labels
15
+
16
+ # @api private
17
+ class Db
18
+ def initialize(instance: nil, statement: nil, type: nil, user: nil)
19
+ @instance = instance
20
+ @statement = statement
21
+ @type = type
22
+ @user = user
23
+ end
24
+
25
+ attr_accessor :instance, :statement, :type, :user
26
+ end
27
+
28
+ # @api private
29
+ class Http
30
+ def initialize(url: nil, status_code: nil, method: nil)
31
+ @url = url
32
+ @status_code = status_code
33
+ @method = method
34
+ end
35
+
36
+ attr_accessor :url, :status_code, :method
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module SpanHelpers
6
+ # @api private
7
+ module ClassMethods
8
+ def span_class_method(method, name = nil, type = nil)
9
+ __span_method_on(singleton_class, method, name, type)
10
+ end
11
+
12
+ def span_method(method, name = nil, type = nil)
13
+ __span_method_on(self, method, name, type)
14
+ end
15
+
16
+ private
17
+
18
+ def __span_method_on(klass, method, name = nil, type = nil)
19
+ name ||= method.to_s
20
+ type ||= Span::DEFAULT_TYPE
21
+
22
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
23
+ alias :"__without_apm_#{method}" :"#{method}"
24
+
25
+ def #{method}(*args, &block)
26
+ unless Atatus.current_transaction
27
+ return __without_apm_#{method}(*args, &block)
28
+ end
29
+
30
+ Atatus.with_span "#{name}", "#{type}" do
31
+ __without_apm_#{method}(*args, &block)
32
+ end
33
+ end
34
+ RUBY
35
+ end
36
+ end
37
+
38
+ def self.included(kls)
39
+ kls.class_eval do
40
+ extend ClassMethods
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'atatus/util/inflector'
5
+
6
+ module Atatus
7
+ # @api private
8
+ module Spies
9
+ # @api private
10
+ class Registration
11
+ extend Forwardable
12
+
13
+ def initialize(const_name, require_paths, spy)
14
+ @const_name = const_name
15
+ @require_paths = Array(require_paths)
16
+ @spy = spy
17
+ end
18
+
19
+ attr_reader :const_name, :require_paths
20
+
21
+ def_delegator :@spy, :install
22
+ end
23
+
24
+ def self.require_hooks
25
+ @require_hooks ||= {}
26
+ end
27
+
28
+ def self.installed
29
+ @installed ||= {}
30
+ end
31
+
32
+ def self.register(*args)
33
+ registration = Registration.new(*args)
34
+
35
+ if safe_defined?(registration.const_name)
36
+ registration.install
37
+ installed[registration.const_name] = registration
38
+ else
39
+ register_require_hook registration
40
+ end
41
+ end
42
+
43
+ def self.register_require_hook(registration)
44
+ registration.require_paths.each do |path|
45
+ require_hooks[path] = registration
46
+ end
47
+ end
48
+
49
+ def self.hook_into(name)
50
+ return unless (registration = require_hooks[name])
51
+ return unless safe_defined?(registration.const_name)
52
+
53
+ installed[registration.const_name] = registration
54
+ registration.install
55
+
56
+ registration.require_paths.each do |path|
57
+ require_hooks.delete path
58
+ end
59
+ end
60
+
61
+ def self.safe_defined?(const_name)
62
+ Util::Inflector.safe_constantize(const_name)
63
+ end
64
+ end
65
+ end
66
+
67
+ # @api private
68
+ module Kernel
69
+ private
70
+
71
+ alias require_without_apm require
72
+
73
+ def require(path)
74
+ res = require_without_apm(path)
75
+
76
+ begin
77
+ Atatus::Spies.hook_into(path)
78
+ rescue ::Exception => e
79
+ puts "Failed hooking into '#{path}'. Please report this at " \
80
+ 'success@atatus.com'
81
+ puts e.backtrace.join("\n")
82
+ end
83
+
84
+ res
85
+ end
86
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class ActionDispatchSpy
8
+ def install
9
+ ::ActionDispatch::ShowExceptions.class_eval do
10
+ alias render_exception_without_apm render_exception
11
+
12
+ def render_exception(env, exception)
13
+ context = Atatus.build_context(rack_env: env, for_type: :error)
14
+ Atatus.report(exception, context: context, handled: false)
15
+
16
+ render_exception_without_apm env, exception
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ register(
23
+ 'ActionDispatch::ShowExceptions',
24
+ 'action_dispatch/show_exception',
25
+ ActionDispatchSpy.new
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class DelayedJobSpy
8
+ CLASS_SEPARATOR = '.'
9
+ METHOD_SEPARATOR = '#'
10
+ TYPE = 'Delayed::Job'
11
+
12
+ def install
13
+ ::Delayed::Backend::Base.class_eval do
14
+ alias invoke_job_without_apm invoke_job
15
+
16
+ def invoke_job(*args, &block)
17
+ ::Atatus::Spies::DelayedJobSpy
18
+ .invoke_job(self, *args, &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.invoke_job(job, *args, &block)
24
+ job_name = name_from_payload(job.payload_object)
25
+ transaction = Atatus.start_transaction(job_name, TYPE)
26
+ job.invoke_job_without_apm(*args, &block)
27
+ transaction.done 'success'
28
+ rescue ::Exception => e
29
+ Atatus.report(e, handled: false)
30
+ transaction.done 'error'
31
+ raise
32
+ ensure
33
+ Atatus.end_transaction
34
+ end
35
+
36
+ def self.name_from_payload(payload_object)
37
+ if payload_object.is_a?(::Delayed::PerformableMethod)
38
+ performable_method_name(payload_object)
39
+ else
40
+ payload_object.class.name
41
+ end
42
+ end
43
+
44
+ def self.performable_method_name(payload_object)
45
+ class_name = object_name(payload_object)
46
+ separator = name_separator(payload_object)
47
+ method_name = payload_object.method_name
48
+ "#{class_name}#{separator}#{method_name}"
49
+ end
50
+
51
+ def self.object_name(payload_object)
52
+ object = payload_object.object
53
+ klass = object.is_a?(Class) ? object : object.class
54
+ klass.name
55
+ end
56
+
57
+ def self.name_separator(payload_object)
58
+ payload_object.object.is_a?(Class) ? CLASS_SEPARATOR : METHOD_SEPARATOR
59
+ end
60
+ end
61
+
62
+ register(
63
+ 'Delayed::Backend::Base',
64
+ 'delayed/backend/base',
65
+ DelayedJobSpy.new
66
+ )
67
+ end
68
+ end