app_perf_rpm 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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