app_perf_rpm 0.0.2

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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/lib/app_perf_rpm/aggregator.rb +77 -0
  3. data/lib/app_perf_rpm/backtrace.rb +92 -0
  4. data/lib/app_perf_rpm/configuration.rb +56 -0
  5. data/lib/app_perf_rpm/dispatcher.rb +85 -0
  6. data/lib/app_perf_rpm/instrumentation.rb +21 -0
  7. data/lib/app_perf_rpm/instruments/action_controller.rb +51 -0
  8. data/lib/app_perf_rpm/instruments/action_view.rb +126 -0
  9. data/lib/app_perf_rpm/instruments/active_record/adapters/mysql2.rb +45 -0
  10. data/lib/app_perf_rpm/instruments/active_record/adapters/postgresql.rb +102 -0
  11. data/lib/app_perf_rpm/instruments/active_record/adapters/sqlite3.rb +103 -0
  12. data/lib/app_perf_rpm/instruments/active_record.rb +57 -0
  13. data/lib/app_perf_rpm/instruments/activerecord_import.rb +47 -0
  14. data/lib/app_perf_rpm/instruments/emque_consuming.rb +31 -0
  15. data/lib/app_perf_rpm/instruments/faraday.rb +37 -0
  16. data/lib/app_perf_rpm/instruments/net_http.rb +36 -0
  17. data/lib/app_perf_rpm/instruments/rack.rb +41 -0
  18. data/lib/app_perf_rpm/instruments/rack_middleware.rb +69 -0
  19. data/lib/app_perf_rpm/instruments/redis.rb +39 -0
  20. data/lib/app_perf_rpm/instruments/sequel.rb +92 -0
  21. data/lib/app_perf_rpm/instruments/sidekiq.rb +55 -0
  22. data/lib/app_perf_rpm/instruments/sinatra.rb +68 -0
  23. data/lib/app_perf_rpm/instruments/typhoeus.rb +55 -0
  24. data/lib/app_perf_rpm/introspector.rb +51 -0
  25. data/lib/app_perf_rpm/logger.rb +32 -0
  26. data/lib/app_perf_rpm/middleware.rb +30 -0
  27. data/lib/app_perf_rpm/rails.rb +14 -0
  28. data/lib/app_perf_rpm/railtie.rb +29 -0
  29. data/lib/app_perf_rpm/span.rb +103 -0
  30. data/lib/app_perf_rpm/tracer.rb +139 -0
  31. data/lib/app_perf_rpm/utils.rb +9 -0
  32. data/lib/app_perf_rpm/worker.rb +46 -0
  33. data/lib/app_perf_rpm.rb +124 -0
  34. data/lib/tasks/install.rake +6 -0
  35. metadata +146 -0
@@ -0,0 +1,55 @@
1
+ module AppPerfRpm
2
+ module Instruments
3
+ module TyphoeusRequest
4
+ def run_with_trace
5
+ if ::AppPerfRpm.tracing?
6
+ span = ::AppPerfRpm::Tracer.start_span("typhoeus")
7
+ response = run_without_trace
8
+ span.finish
9
+
10
+ uri = URI(response.effective_url)
11
+
12
+ span.options = {
13
+ "http_status" => response.code,
14
+ "remote_url" => uri.to_s,
15
+ "http_method" => options[:method]
16
+ }
17
+ span.submit(opts)
18
+
19
+ response
20
+ else
21
+ run_without_trace
22
+ end
23
+ end
24
+ end
25
+
26
+ module TyphoeusHydra
27
+ def run_with_trace
28
+ ::AppPerfRpm::Tracer.trace("typhoeus") do |span|
29
+ span.options = {
30
+ "method" => :hydra,
31
+ "queued_requests" => queued_requests.count,
32
+ "max_concurrency" => max_concurrency
33
+ }
34
+ run_without_trace
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ if ::AppPerfRpm.configuration.instrumentation[:typhoeus][:enabled] && defined?(::Typhoeus)
42
+ ::AppPerfRpm.logger.info "Initializing typhoeus tracer."
43
+
44
+ ::Typhoeus::Request::Operations.send(:include, AppPerfRpm::Instruments::TyphoeusRequest)
45
+ ::Typhoeus::Request::Operations.class_eval do
46
+ alias_method :run_without_trace, :run
47
+ alias_method :run, :run_with_trace
48
+ end
49
+
50
+ ::Typhoeus::Hydra.send(:include, AppPerfRpm::Instruments::TyphoeusHydra)
51
+ ::Typhoeus::Hydra.class_eval do
52
+ alias_method :run_without_trace, :run
53
+ alias_method :run, :run_with_trace
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ module AppPerfRpm
2
+ class Introspector
3
+
4
+ VALID_RUNNERS = [
5
+ :Passenger,
6
+ :Puma,
7
+ :Rainbows,
8
+ :Resque,
9
+ :Sidekiq,
10
+ :Sinatra,
11
+ :Unicorn,
12
+ :Webrick
13
+ ]
14
+ class << self
15
+
16
+ def agentable?
17
+ if raking? || rspecing?
18
+ AppPerfRpm.logger.info("Detected rake, not initializing agent")
19
+ return false
20
+ end
21
+ AppPerfRpm.logger.info("Detecting runner...")
22
+ VALID_RUNNERS.each do |runner|
23
+ if const_defined?(runner.to_s)
24
+ AppPerfRpm.logger.info("#{runner} detected. You're valid")
25
+ return true
26
+ end
27
+ end
28
+ AppPerfRpm.logger.info("No valid runner detected!")
29
+ false
30
+ end
31
+
32
+ def rspecing?
33
+ (File.basename($0) =~ /\Arspec/) == 0
34
+ end
35
+
36
+ def raking?
37
+ (File.basename($0) =~ /\Arake/) == 0
38
+ end
39
+
40
+ def const_defined?(string_const)
41
+ begin
42
+ Object.const_get(string_const)
43
+ true
44
+ rescue NameError
45
+ false
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ require 'logger'
2
+
3
+ module AppPerfRpm
4
+ class << self
5
+ attr_accessor :logger
6
+ end
7
+
8
+ class Logger
9
+ def info(msg)
10
+ AppPerfRpm.info(msg)
11
+ end
12
+
13
+ def debug(msg)
14
+ AppPerfRpm.info(msg)
15
+ end
16
+
17
+ def warn(msg)
18
+ AppPerfRpm.info(msg)
19
+ end
20
+
21
+ def error(msg)
22
+ AppPerfRpm.info(msg)
23
+ end
24
+
25
+ def fatal(msg)
26
+ AppPerfRpm.info(msg)
27
+ end
28
+ end
29
+ end
30
+
31
+ AppPerfRpm.logger = Logger.new(STDERR)
32
+ AppPerfRpm.logger.level = Logger::INFO
@@ -0,0 +1,30 @@
1
+ module AppPerfRpm
2
+ class Middleware
3
+ attr_reader :app
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ begin
11
+ @status, @headers, @response = @app.call(env)
12
+ rescue Exception => e
13
+ handle_exception(env, e)
14
+ end
15
+ [@status, @headers, @response]
16
+ end
17
+
18
+ def handle_exception(env, exception)
19
+ ::AppPerfRpm::Tracer.log_event("error",
20
+ "path" => env["PATH_INFO"],
21
+ "method" => env["REQUEST_METHOD"],
22
+ "message" => exception.message,
23
+ "error_class" => exception.class.to_s,
24
+ "backtrace" => ::AppPerfRpm::Backtrace.clean(exception.backtrace),
25
+ "source" => ::AppPerfRpm::Backtrace.source_extract(exception.backtrace)
26
+ )
27
+ raise exception
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ if defined?(::Rails)
2
+ if ::Rails::VERSION::MAJOR > 2
3
+ require 'app_perf_rpm/railtie'
4
+ else
5
+ Rails.configuration.after_initialize do
6
+ unless AppPerfRpm.disable_agent?
7
+ AppPerfRpm.load
8
+ Rails.configuration.middleware.use AppPerfRpm::Middleware
9
+ AppPerfRpm.logger.info "Initializing rack middleware tracer."
10
+ Rails.configuration.middleware.insert 0, AppPerfRpm::Instruments::Rack
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ module AppPerfRpm
2
+ class Railtie < ::Rails::Railtie
3
+ require 'app_perf_rpm/instruments/rack'
4
+
5
+ # TODO: Why this isn't working with the initializer?
6
+ initializer "app_perf.initialize" do |app|
7
+ unless AppPerfRpm.disable_agent?
8
+ app.middleware.use AppPerfRpm::Middleware
9
+
10
+ if ::AppPerfRpm.configuration.instrumentation[:rack][:enabled]
11
+ AppPerfRpm.logger.info "Initializing rack tracer."
12
+ app.middleware.insert 0, AppPerfRpm::Instruments::Rack
13
+
14
+ if AppPerfRpm.configuration.instrumentation[:rack][:trace_middleware]
15
+ AppPerfRpm.logger.info "Initializing rack middleware tracer."
16
+ require 'app_perf_rpm/instruments/rack_middleware'
17
+ app.middleware.insert 1, AppPerfRpm::Instruments::RackMiddleware
18
+ end
19
+ end
20
+ end
21
+
22
+ config.after_initialize do
23
+ AppPerfRpm.configuration.app_root = Rails.root
24
+ AppPerfRpm.configuration.reload
25
+ AppPerfRpm.load
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ module AppPerfRpm
2
+ class Span
3
+ attr_accessor :layer,
4
+ :controller,
5
+ :action,
6
+ :url,
7
+ :domain,
8
+ :type,
9
+ :backtrace,
10
+ :source,
11
+ :trace_id,
12
+ :started_at,
13
+ :ended_at,
14
+ :children,
15
+ :options
16
+
17
+ def self.arrange(spans)
18
+ spans.sort! { |a, b| (a.ended_at <=> b.ended_at) }
19
+
20
+ null_span = Span.new
21
+ controller = (spans.find {|s| s.controller } || null_span).controller
22
+ action = (spans.find {|s| s.action } || null_span).action
23
+ domain = (spans.find {|s| s.domain } || null_span).domain
24
+ url = (spans.find {|s| s.url } || null_span).url
25
+
26
+ while span = spans.shift
27
+ span.controller ||= controller
28
+ span.action ||= action
29
+ span.domain ||= domain
30
+ span.url ||= url
31
+
32
+ if parent = spans.find { |n| n.parent_of?(span) }
33
+ parent.children << span
34
+ elsif spans.empty?
35
+ root = span
36
+ end
37
+ end
38
+
39
+ root
40
+ end
41
+
42
+ def initialize
43
+ self.children = []
44
+ self.type = "web"
45
+ self.options = {}
46
+ end
47
+
48
+ def duration
49
+ @duration ||= (ended_at - started_at) * 1000.0
50
+ end
51
+
52
+ def exclusive_duration
53
+ @exclusive_duration ||= duration - children.inject(0.0) { |sum, child| sum + child.duration }
54
+ end
55
+
56
+ def parent_of?(span)
57
+ start = (started_at - span.started_at) * 1000.0
58
+ start <= 0 && (start + duration >= span.duration)
59
+ end
60
+
61
+ def child_of?(span)
62
+ span.parent_of?(self)
63
+ end
64
+
65
+ def to_spans
66
+ span = self.dup
67
+ span.exclusive_duration
68
+ span.children = []
69
+
70
+ if children.size > 0
71
+ return [span] + children.map(&:to_spans)
72
+ else
73
+ return [span]
74
+ end
75
+ end
76
+
77
+ def base_options
78
+ opts = {}
79
+ opts["domain"] = domain
80
+ opts["controller"] = controller
81
+ opts["action"] = action
82
+ opts["url"] = url
83
+ opts["type"] = type
84
+ #opts["backtrace"] = ::AppPerfRpm::Backtrace.backtrace
85
+ opts["source"] = ::AppPerfRpm::Backtrace.source_extract
86
+ opts.delete_if { |k, v| v.nil? }
87
+ end
88
+
89
+ def to_s
90
+ "#{layer}:#{trace_id}:#{started_at}:#{exclusive_duration}"
91
+ end
92
+
93
+ def to_a
94
+ [
95
+ layer,
96
+ trace_id,
97
+ started_at,
98
+ duration,
99
+ base_options.merge(options)
100
+ ]
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,139 @@
1
+ module AppPerfRpm
2
+ class Tracer
3
+ class << self
4
+ def trace_id
5
+ Thread.current[:trace_id]
6
+ end
7
+
8
+ def trace_id=(t)
9
+ Thread.current[:trace_id] = t
10
+ end
11
+
12
+ def tracing?
13
+ Thread.current[:trace_id]
14
+ end
15
+
16
+ def in_trace?
17
+ !Thread.current[:trace_id].nil?
18
+ end
19
+
20
+ def start_span(layer, opts = {})
21
+ Instance.new(layer, opts)
22
+ end
23
+
24
+ def random_percentage
25
+ rand * 100
26
+ end
27
+
28
+ def should_trace?
29
+ random_percentage < ::AppPerfRpm.configuration.sample_rate.to_i
30
+ end
31
+
32
+ def start_trace(layer, opts = {})
33
+ start = Time.now.to_f
34
+
35
+ trace_id = opts.delete("trace_id")
36
+ if trace_id || should_trace?
37
+ self.trace_id = trace_id || generate_trace_id
38
+ result = trace(layer, opts) do |span|
39
+ yield(span)
40
+ end
41
+ self.trace_id = nil
42
+ else
43
+ result = yield
44
+ end
45
+
46
+ return result, (Time.now.to_f - start) * 1000
47
+ end
48
+
49
+ def trace(layer, opts = {})
50
+ result = nil
51
+
52
+ if tracing?
53
+ span = Span.new
54
+ span.layer = layer
55
+ span.trace_id = self.trace_id
56
+ span.started_at = Time.now.to_f
57
+ result = yield(span)
58
+ span.ended_at = Time.now.to_f
59
+ span.options.merge!(opts)
60
+ ::AppPerfRpm.store(span)
61
+ else
62
+ result = yield
63
+ end
64
+
65
+ result
66
+ end
67
+
68
+ def profile(layer, opts = {})
69
+ if defined?(TracePoint)
70
+ @times = {}
71
+ traces = []
72
+ tracer = TracePoint.new(:call, :return) do |tp|
73
+ backtrace = caller(0)
74
+ key = "#{tp.defined_class}_#{tp.method_id}_#{backtrace.size}"
75
+ if tp.event == :call
76
+ @times[key] = Time.now.to_f
77
+ else
78
+ if @times[key]
79
+ @times[key] = Time.now.to_f - @times[key].to_f
80
+ traces << {
81
+ "duration "=> @times[key].to_f,
82
+ "class" => tp.defined_class,
83
+ "method" => tp.method_id,
84
+ "backtrace" => backtrace,
85
+ "line" => ::AppPerfRpm::Backtrace.send(:clean_line, tp.path),
86
+ "line_number" => tp.lineno
87
+ }
88
+ end
89
+ end
90
+ end
91
+
92
+ result = tracer.enable { yield }
93
+ @times = {}
94
+
95
+ return traces, result
96
+ else
97
+ return [], yield
98
+ end
99
+ end
100
+
101
+ def log_event(event, opts = {})
102
+ ::AppPerfRpm.store([event, generate_trace_id, Time.now.to_f, opts])
103
+ end
104
+
105
+ def generate_trace_id
106
+ Digest::SHA1.hexdigest([Time.now, rand].join)
107
+ end
108
+
109
+ class Instance
110
+ attr_accessor :layer, :opts, :start, :duration
111
+
112
+ def initialize(layer, opts = {})
113
+ @span = Span.new
114
+ @span.layer = layer
115
+ @span.options = opts
116
+ @span.started_at = Time.now.to_f
117
+ end
118
+
119
+ def finish(opts = {})
120
+ @span.options.merge!(opts)
121
+ @span.ended_at = Time.now.to_f
122
+ end
123
+
124
+ def submit(opts = {})
125
+ if ::AppPerfRpm::Tracer.tracing?
126
+ @span.options.merge!(opts)
127
+ ::AppPerfRpm.store(@span)
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def trace_id
134
+ ::AppPerfRpm::Tracer.trace_id
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,9 @@
1
+ module AppPerfRpm
2
+ module Utils
3
+ REGEXP ||= Regexp.new('(\'[\s\S][^\']*\'|\d*\.\d+|\d+|NULL)')
4
+
5
+ def sanitize_sql(sql, adapter)
6
+ sql.gsub(REGEXP, '?')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module AppPerfRpm
2
+ class Worker
3
+ def initialize
4
+ AppPerfRpm.logger.info "Starting worker."
5
+ @dispatcher = Dispatcher.new
6
+ end
7
+
8
+ def save(event)
9
+ start
10
+ return if event.nil?
11
+ @dispatcher.add_event(event)
12
+ end
13
+
14
+ def start
15
+ return if worker_running?
16
+ ::AppPerfRpm.mutex.synchronize do
17
+ return if worker_running?
18
+ start_dispatcher
19
+ ::AppPerfRpm.logger.info "Worker is running."
20
+ end
21
+ end
22
+
23
+ def worker_running?
24
+ @worker_thread && @worker_thread.alive?
25
+ end
26
+
27
+ def start_dispatcher
28
+ @worker_thread = Thread.new do
29
+ ::AppPerfRpm.configuration.reload
30
+ @dispatcher.reset
31
+
32
+ loop do
33
+ start = Time.now
34
+ if @dispatcher.ready?
35
+ @dispatcher.dispatch
36
+ @dispatcher.reset
37
+ end
38
+ sleep_for = (start + 15 - Time.now)
39
+ sleep_for = 1 if sleep_for < 1
40
+ sleep sleep_for
41
+ end
42
+ end
43
+ @worker_thread.abort_on_exception = true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,124 @@
1
+ require 'oj'
2
+
3
+ module AppPerfRpm
4
+ require 'app_perf_rpm/logger'
5
+ require 'app_perf_rpm/configuration'
6
+ require 'app_perf_rpm/span'
7
+ require 'app_perf_rpm/aggregator'
8
+ require 'app_perf_rpm/dispatcher'
9
+ require 'app_perf_rpm/worker'
10
+ require 'app_perf_rpm/backtrace'
11
+ require 'app_perf_rpm/tracer'
12
+ require 'app_perf_rpm/utils'
13
+ require 'app_perf_rpm/middleware'
14
+ require 'app_perf_rpm/instrumentation'
15
+ require 'app_perf_rpm/rails'
16
+ require 'app_perf_rpm/introspector'
17
+
18
+ class << self
19
+ attr_writer :configuration
20
+
21
+ def configuration
22
+ @configuration ||= Configuration.new
23
+ end
24
+
25
+ def configure
26
+ yield(configuration)
27
+ end
28
+
29
+ def load
30
+ #Oj.mimic_JSON
31
+ unless disable_agent?
32
+ AppPerfRpm::Instrumentation.load
33
+ @worker = ::AppPerfRpm::Worker.new
34
+
35
+ if @worker.start
36
+ @worker_running = true
37
+ AppPerfRpm.tracing_on
38
+ end
39
+ end
40
+ end
41
+
42
+ def worker
43
+ @worker
44
+ end
45
+
46
+ def mutex
47
+ @mutex ||= Mutex.new
48
+ end
49
+
50
+ def store(event)
51
+ if @worker_running && tracing?
52
+ @worker.save(event)
53
+ end
54
+ event
55
+ end
56
+
57
+ def tracing_on
58
+ if @without_tracing_enabled
59
+ AppPerfRpm.logger.debug "Not turning tracing on due to without tracing mode."
60
+ return
61
+ end
62
+ mutex.synchronize do
63
+ AppPerfRpm.logger.debug "Enabling tracing."
64
+ @tracing = true
65
+ end
66
+ end
67
+
68
+ def tracing_off
69
+ mutex.synchronize do
70
+ AppPerfRpm.logger.debug "Disabling tracing."
71
+ @tracing = false
72
+ end
73
+ end
74
+
75
+ def tracing?
76
+ @tracing
77
+ end
78
+
79
+ def without_tracing
80
+ @previously_tracing = AppPerfRpm.tracing?
81
+ @without_tracing_enabled = true
82
+ AppPerfRpm.tracing_off
83
+ yield if block_given?
84
+ @without_tracing_enabled = false
85
+ ensure
86
+ AppPerfRpm.tracing_on if @previously_tracing
87
+ end
88
+
89
+ def host
90
+ @host ||= Socket.gethostname
91
+ end
92
+
93
+ def round_time(t, sec = 1)
94
+ t = Time.parse(t.to_s)
95
+
96
+ down = t - (t.to_i % sec)
97
+ up = down + sec
98
+
99
+ difference_down = t - down
100
+ difference_up = up - t
101
+
102
+ if (difference_down < difference_up)
103
+ return down
104
+ else
105
+ return up
106
+ end
107
+ end
108
+
109
+ def floor_time(t, sec = 1)
110
+ Time.at((t.to_f / sec).floor * sec)
111
+ end
112
+
113
+ def disable_agent?
114
+ if configuration.agent_disabled
115
+ true
116
+ elsif Introspector.agentable?
117
+ false
118
+ else
119
+ true
120
+ end
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,6 @@
1
+ namespace :app_perf do
2
+ desc "Install app_perf_ruby_agent.yml file"
3
+ task :install do
4
+ load File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "install.rb"))
5
+ end
6
+ end