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,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class SidekiqSpy
8
+ ACTIVE_JOB_WRAPPER =
9
+ 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'
10
+
11
+ # @api private
12
+ class Middleware
13
+ # rubocop:disable Metrics/MethodLength
14
+ def call(_worker, job, queue)
15
+ name = SidekiqSpy.name_for(job)
16
+ transaction = Atatus.start_transaction(name, 'Sidekiq')
17
+ Atatus.set_label(:queue, queue)
18
+
19
+ yield
20
+
21
+ transaction.done :success if transaction
22
+ rescue ::Exception => e
23
+ Atatus.report(e, handled: false)
24
+ transaction.done :error if transaction
25
+ raise
26
+ ensure
27
+ Atatus.end_transaction
28
+ end
29
+ # rubocop:enable Metrics/MethodLength
30
+ end
31
+
32
+ def self.name_for(job)
33
+ klass = job['class']
34
+
35
+ case klass
36
+ when ACTIVE_JOB_WRAPPER
37
+ job['wrapped']
38
+ else
39
+ klass
40
+ end
41
+ end
42
+
43
+ def install_middleware
44
+ Sidekiq.configure_server do |config|
45
+ config.server_middleware do |chain|
46
+ chain.add Middleware
47
+ end
48
+ end
49
+ end
50
+
51
+ # rubocop:disable Metrics/MethodLength
52
+ def install_processor
53
+ require 'sidekiq/processor'
54
+
55
+ Sidekiq::Processor.class_eval do
56
+ alias start_without_apm start
57
+ alias terminate_without_apm terminate
58
+
59
+ def start
60
+ result = start_without_apm
61
+
62
+ # Already running from Railtie if Rails
63
+ if Atatus.running?
64
+ Atatus.agent.config.logger = Sidekiq.logger
65
+ else
66
+ Atatus.start
67
+ end
68
+
69
+ result
70
+ end
71
+
72
+ def terminate
73
+ terminate_without_apm
74
+
75
+ Atatus.stop
76
+ end
77
+ end
78
+ end
79
+ # rubocop:enable Metrics/MethodLength
80
+
81
+ def install
82
+ install_processor
83
+ install_middleware
84
+ end
85
+ end
86
+
87
+ register 'Sidekiq', 'sidekiq', SidekiqSpy.new
88
+ end
89
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class SinatraSpy
8
+ # rubocop:disable Metrics/MethodLength
9
+ def install
10
+ ::Sinatra::Base.class_eval do
11
+ alias dispatch_without_apm! dispatch!
12
+ alias compile_template_without_apm compile_template
13
+
14
+ def dispatch!(*args, &block)
15
+ dispatch_without_apm!(*args, &block).tap do
16
+ next unless (transaction = Atatus.current_transaction)
17
+ next unless (route = env['sinatra.route'])
18
+
19
+ transaction.name = route
20
+ end
21
+ end
22
+
23
+ def compile_template(engine, data, opts, *args, &block)
24
+ opts[:__atatus_template_name] =
25
+ case data
26
+ when Symbol then data.to_s
27
+ else format('Inline %s', engine)
28
+ end
29
+
30
+ compile_template_without_apm(engine, data, opts, *args, &block)
31
+ end
32
+ end
33
+ end
34
+ # rubocop:enable Metrics/MethodLength
35
+ end
36
+
37
+ register 'Sinatra::Base', 'sinatra/base', SinatraSpy.new
38
+
39
+ require 'atatus/spies/tilt'
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class TiltSpy
8
+ TYPE = 'template.tilt'
9
+
10
+ def install
11
+ ::Tilt::Template.class_eval do
12
+ alias render_without_apm render
13
+
14
+ def render(*args, &block)
15
+ name = options[:__atatus_template_name] || 'Unknown template'
16
+
17
+ Atatus.with_span name, TYPE do
18
+ render_without_apm(*args, &block)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ register 'Tilt::Template', 'tilt/template', TiltSpy.new
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/util/lru_cache'
4
+
5
+ module Atatus
6
+ # @api private
7
+ class SqlSummarizer
8
+ DEFAULT = 'SQL'
9
+ TABLE_REGEX = %{["'`]?([A-Za-z0-9_]+)["'`]?}
10
+
11
+ REGEXES = {
12
+ /^BEGIN/i => 'BEGIN',
13
+ /^COMMIT/i => 'COMMIT',
14
+ /^SELECT .* FROM #{TABLE_REGEX}/i => 'SELECT FROM ',
15
+ /^INSERT INTO #{TABLE_REGEX}/i => 'INSERT INTO ',
16
+ /^UPDATE #{TABLE_REGEX}/i => 'UPDATE ',
17
+ /^DELETE FROM #{TABLE_REGEX}/i => 'DELETE FROM '
18
+ }.freeze
19
+
20
+ FORMAT = '%s%s'
21
+
22
+ def self.cache
23
+ @cache ||= Util::LruCache.new
24
+ end
25
+
26
+ def summarize(sql)
27
+ self.class.cache[sql] ||=
28
+ REGEXES.find do |regex, sig|
29
+ if (match = sql[0...1000].match(regex))
30
+ break format(FORMAT, sig, match[1] && match[1].gsub(/["']/, ''))
31
+ end
32
+ end || DEFAULT
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atatus
4
+ # @api private
5
+ class Stacktrace
6
+ attr_accessor :frames
7
+
8
+ def length
9
+ frames.length
10
+ end
11
+
12
+ def to_a
13
+ frames.map(&:to_h)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/naively_hashable'
4
+
5
+ module Atatus
6
+ class Stacktrace
7
+ # @api private
8
+ class Frame
9
+ include NaivelyHashable
10
+
11
+ attr_accessor(
12
+ :abs_path,
13
+ :filename,
14
+ :function,
15
+ :vars,
16
+ :pre_context,
17
+ :context_line,
18
+ :post_context,
19
+ :library_frame,
20
+ :lineno,
21
+ :module,
22
+ :colno
23
+ )
24
+
25
+ # rubocop:disable Metrics/AbcSize
26
+ def build_context(context_line_count)
27
+ return unless abs_path && context_line_count > 0
28
+
29
+ padding = (context_line_count - 1) / 2
30
+ from = lineno - padding - 1
31
+ from = 0 if from < 0
32
+ to = lineno + padding - 1
33
+ file_lines = read_lines(abs_path, from..to)
34
+
35
+ return unless file_lines
36
+
37
+ self.context_line = file_lines[padding]
38
+ self.pre_context = file_lines.first(padding)
39
+ self.post_context = file_lines.last(padding)
40
+ end
41
+ # rubocop:enable Metrics/AbcSize
42
+
43
+ private
44
+
45
+ def read_lines(path, range)
46
+ File.readlines(path)[range]
47
+ rescue Errno::ENOENT
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/stacktrace/frame'
4
+ require 'atatus/util/lru_cache'
5
+
6
+ module Atatus
7
+ # @api private
8
+ class StacktraceBuilder
9
+ JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
10
+ RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/.freeze
11
+
12
+ RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}.freeze
13
+ JRUBY_ORG_REGEX = %r{org/jruby}.freeze
14
+
15
+ GEMS_PATH = defined?(Bundler) ? Bundler.bundle_path.to_s : Gem.dir
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ @cache = Util::LruCache.new(2048, &method(:build_frame))
20
+ end
21
+
22
+ attr_reader :config
23
+
24
+ def build(backtrace, type:)
25
+ Stacktrace.new.tap do |s|
26
+ s.frames = backtrace[0...config.stack_trace_limit].map do |line|
27
+ @cache[[line, type]]
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
35
+ def build_frame(cache, keys)
36
+ line, type = keys
37
+ abs_path, lineno, function, _module_name = parse_line(line)
38
+
39
+ frame = Stacktrace::Frame.new
40
+ frame.abs_path = abs_path
41
+ frame.filename = strip_load_path(abs_path)
42
+ frame.function = function
43
+ frame.lineno = lineno.to_i
44
+ frame.library_frame = library_frame?(config, abs_path)
45
+
46
+ line_count =
47
+ context_lines_for(config, type, library_frame: frame.library_frame)
48
+ frame.build_context line_count
49
+
50
+ cache[[line, type]] = frame
51
+ end
52
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
53
+
54
+ def parse_line(line)
55
+ ruby_match = line.match(RUBY_FORMAT)
56
+
57
+ if ruby_match
58
+ _, file, number, method = ruby_match.to_a
59
+ file.sub!(/\.class$/, '.rb')
60
+ module_name = nil
61
+ else
62
+ java_match = line.match(JAVA_FORMAT)
63
+ _, module_name, method, file, number = java_match.to_a
64
+ end
65
+
66
+ [file, number, method, module_name]
67
+ end
68
+
69
+ # rubocop:disable Metrics/CyclomaticComplexity
70
+ def library_frame?(config, abs_path)
71
+ return false unless abs_path
72
+
73
+ return true if abs_path.start_with?(GEMS_PATH)
74
+
75
+ if abs_path.start_with?(config.__root_path)
76
+ return true if abs_path.start_with?(config.__root_path + '/vendor')
77
+ return false
78
+ end
79
+
80
+ return true if abs_path.match(RUBY_VERS_REGEX)
81
+ return true if abs_path.match(JRUBY_ORG_REGEX)
82
+
83
+ false
84
+ end
85
+ # rubocop:enable Metrics/CyclomaticComplexity
86
+
87
+ def strip_load_path(path)
88
+ return nil if path.nil?
89
+
90
+ prefix =
91
+ $LOAD_PATH
92
+ .map(&:to_s)
93
+ .select { |s| path.start_with?(s) }
94
+ .max_by(&:length)
95
+
96
+ prefix ? path[prefix.chomp(File::SEPARATOR).length + 1..-1] : path
97
+ end
98
+
99
+ def context_lines_for(config, type, library_frame:)
100
+ key = "source_lines_#{type}_#{library_frame ? 'library' : 'app'}_frames"
101
+ config.send(key.to_sym)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/notifications'
4
+ require 'atatus/normalizers'
5
+
6
+ module Atatus
7
+ # @api private
8
+ class Subscriber
9
+ include Logging
10
+
11
+ def initialize(agent)
12
+ @agent = agent
13
+ @normalizers = Normalizers.build(agent.config)
14
+ end
15
+
16
+ def register!
17
+ unregister! if @subscription
18
+
19
+ @subscription =
20
+ ActiveSupport::Notifications.subscribe(notifications_regex, self)
21
+ end
22
+
23
+ def unregister!
24
+ ActiveSupport::Notifications.unsubscribe @subscription
25
+ @subscription = nil
26
+ end
27
+
28
+ # AS::Notifications API
29
+
30
+ Notification = Struct.new(:id, :span)
31
+
32
+ # rubocop:disable Metrics/MethodLength
33
+ def start(name, id, payload)
34
+ return unless (transaction = @agent.current_transaction)
35
+
36
+ normalized = @normalizers.normalize(transaction, name, payload)
37
+
38
+ span =
39
+ if normalized == :skip
40
+ nil
41
+ else
42
+ name, type, subtype, action, context = normalized
43
+
44
+ @agent.start_span(
45
+ name,
46
+ type,
47
+ subtype: subtype,
48
+ action: action,
49
+ context: context
50
+ )
51
+ end
52
+
53
+ transaction.notifications << Notification.new(id, span)
54
+ end
55
+ # rubocop:enable Metrics/MethodLength
56
+
57
+ def finish(_name, id, _payload)
58
+ # debug "AS::Notification#finish:#{name}:#{id}"
59
+ return unless (transaction = @agent.current_transaction)
60
+
61
+ while (notification = transaction.notifications.pop)
62
+ next unless notification.id == id
63
+
64
+ if (span = notification.span)
65
+ @agent.end_span if span == @agent.current_span
66
+ end
67
+ return
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def notifications_regex
74
+ @notifications_regex ||= /(#{@normalizers.keys.join('|')})/
75
+ end
76
+ end
77
+ end