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