atatus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +57 -0
- data/LICENSE +65 -0
- data/LICENSE-THIRD-PARTY +205 -0
- data/README.md +13 -0
- data/Rakefile +19 -0
- data/atatus.gemspec +36 -0
- data/atatus.yml +2 -0
- data/bench/.gitignore +2 -0
- data/bench/app.rb +53 -0
- data/bench/benchmark.rb +36 -0
- data/bench/report.rb +55 -0
- data/bench/rubyprof.rb +39 -0
- data/bench/stackprof.rb +23 -0
- data/bin/build_docs +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/lib/atatus.rb +325 -0
- data/lib/atatus/agent.rb +260 -0
- data/lib/atatus/central_config.rb +141 -0
- data/lib/atatus/central_config/cache_control.rb +34 -0
- data/lib/atatus/collector/base.rb +329 -0
- data/lib/atatus/collector/builder.rb +317 -0
- data/lib/atatus/collector/transport.rb +72 -0
- data/lib/atatus/config.rb +248 -0
- data/lib/atatus/config/bytes.rb +25 -0
- data/lib/atatus/config/duration.rb +23 -0
- data/lib/atatus/config/options.rb +134 -0
- data/lib/atatus/config/regexp_list.rb +13 -0
- data/lib/atatus/context.rb +33 -0
- data/lib/atatus/context/request.rb +11 -0
- data/lib/atatus/context/request/socket.rb +19 -0
- data/lib/atatus/context/request/url.rb +42 -0
- data/lib/atatus/context/response.rb +22 -0
- data/lib/atatus/context/user.rb +42 -0
- data/lib/atatus/context_builder.rb +97 -0
- data/lib/atatus/deprecations.rb +22 -0
- data/lib/atatus/error.rb +22 -0
- data/lib/atatus/error/exception.rb +46 -0
- data/lib/atatus/error/log.rb +24 -0
- data/lib/atatus/error_builder.rb +76 -0
- data/lib/atatus/instrumenter.rb +224 -0
- data/lib/atatus/internal_error.rb +6 -0
- data/lib/atatus/logging.rb +55 -0
- data/lib/atatus/metadata.rb +19 -0
- data/lib/atatus/metadata/process_info.rb +18 -0
- data/lib/atatus/metadata/service_info.rb +61 -0
- data/lib/atatus/metadata/system_info.rb +35 -0
- data/lib/atatus/metadata/system_info/container_info.rb +121 -0
- data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
- data/lib/atatus/metadata/system_info/os_info.rb +31 -0
- data/lib/atatus/metrics.rb +98 -0
- data/lib/atatus/metrics/cpu_mem.rb +240 -0
- data/lib/atatus/metrics/vm.rb +60 -0
- data/lib/atatus/metricset.rb +19 -0
- data/lib/atatus/middleware.rb +76 -0
- data/lib/atatus/naively_hashable.rb +21 -0
- data/lib/atatus/normalizers.rb +68 -0
- data/lib/atatus/normalizers/action_controller.rb +27 -0
- data/lib/atatus/normalizers/action_mailer.rb +26 -0
- data/lib/atatus/normalizers/action_view.rb +77 -0
- data/lib/atatus/normalizers/active_record.rb +45 -0
- data/lib/atatus/opentracing.rb +346 -0
- data/lib/atatus/rails.rb +61 -0
- data/lib/atatus/railtie.rb +30 -0
- data/lib/atatus/span.rb +125 -0
- data/lib/atatus/span/context.rb +40 -0
- data/lib/atatus/span_helpers.rb +44 -0
- data/lib/atatus/spies.rb +86 -0
- data/lib/atatus/spies/action_dispatch.rb +28 -0
- data/lib/atatus/spies/delayed_job.rb +68 -0
- data/lib/atatus/spies/elasticsearch.rb +36 -0
- data/lib/atatus/spies/faraday.rb +70 -0
- data/lib/atatus/spies/http.rb +44 -0
- data/lib/atatus/spies/json.rb +22 -0
- data/lib/atatus/spies/mongo.rb +87 -0
- data/lib/atatus/spies/net_http.rb +70 -0
- data/lib/atatus/spies/rake.rb +45 -0
- data/lib/atatus/spies/redis.rb +27 -0
- data/lib/atatus/spies/sequel.rb +47 -0
- data/lib/atatus/spies/sidekiq.rb +89 -0
- data/lib/atatus/spies/sinatra.rb +41 -0
- data/lib/atatus/spies/tilt.rb +27 -0
- data/lib/atatus/sql_summarizer.rb +35 -0
- data/lib/atatus/stacktrace.rb +16 -0
- data/lib/atatus/stacktrace/frame.rb +52 -0
- data/lib/atatus/stacktrace_builder.rb +104 -0
- data/lib/atatus/subscriber.rb +77 -0
- data/lib/atatus/trace_context.rb +85 -0
- data/lib/atatus/transaction.rb +100 -0
- data/lib/atatus/transport/base.rb +174 -0
- data/lib/atatus/transport/connection.rb +156 -0
- data/lib/atatus/transport/connection/http.rb +116 -0
- data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
- data/lib/atatus/transport/filters.rb +43 -0
- data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
- data/lib/atatus/transport/serializers.rb +93 -0
- data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
- data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
- data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
- data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
- data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
- data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
- data/lib/atatus/transport/worker.rb +73 -0
- data/lib/atatus/util.rb +42 -0
- data/lib/atatus/util/inflector.rb +93 -0
- data/lib/atatus/util/lru_cache.rb +48 -0
- data/lib/atatus/util/prefixed_logger.rb +18 -0
- data/lib/atatus/util/throttle.rb +35 -0
- data/lib/atatus/version.rb +5 -0
- data/vendor/.gitkeep +0 -0
- 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,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
|