rorvswild 0.6.1 → 1.0.0.pre.alpha

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.
@@ -23,7 +23,6 @@ development:
23
23
 
24
24
  production:
25
25
  api_key: #{api_key}
26
- # explain_sql_threshold: 500 # Execute EXPLAIN for queries above the specified time in ms.
27
26
  # ignored_exceptions:
28
27
  # - ActionController::RoutingError
29
28
  # - UncommentToIgnoreAnyExceptionNameListedHere
@@ -1,26 +1,13 @@
1
1
  module RorVsWild
2
2
  module Location
3
- def self.cleanup_method_name(method)
4
- method.sub!("block in ".freeze, "".freeze)
5
- method.sub!("in `".freeze, "".freeze)
6
- method.sub!("'".freeze, "".freeze)
7
- method.index("_app_views_".freeze) == 0 ? nil : method
3
+ def find_most_relevant_location(locations)
4
+ result = locations.find { |l| l.path.index(app_root) == 0 && !(l.path =~ gem_home_regex) } if app_root
5
+ result || locations.find { |l| !(l.path =~ gem_home_regex) } || locations.first
8
6
  end
9
7
 
10
- def self.split_file_location(location)
11
- file, line, method = location.split(":")
12
- method = cleanup_method_name(method) if method
13
- [file, line, method]
14
- end
15
-
16
- def extract_most_relevant_location(stack)
17
- location = stack.find { |str| str =~ app_root_regex && !(str =~ gem_home_regex) } if app_root_regex
18
- location ||= stack.find { |str| !(str =~ gem_home_regex) } if gem_home_regex
19
- RorVsWild::Location.split_file_location(relative_path(location || stack.first))
20
- end
21
-
22
- def app_root_regex
23
- @app_root_regex ||= RorVsWild.default_client.app_root ? /\A#{RorVsWild.default_client.app_root}/ : nil
8
+ def extract_most_relevant_file_and_line(stack)
9
+ location = find_most_relevant_location(stack)
10
+ [relative_path(location.path), location.lineno]
24
11
  end
25
12
 
26
13
  def gem_home_regex
@@ -28,6 +15,10 @@ module RorVsWild
28
15
  end
29
16
 
30
17
  def gem_home
18
+ @gem_home ||= guess_gem_home
19
+ end
20
+
21
+ def guess_gem_home
31
22
  if ENV["GEM_HOME"] && !ENV["GEM_HOME"].empty?
32
23
  ENV["GEM_HOME"]
33
24
  elsif ENV["GEM_PATH"] && !(first_gem_path = ENV["GEM_PATH"].split(":").first)
@@ -0,0 +1,46 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ class ActionController
4
+ def self.setup
5
+ return if @installed
6
+ return unless defined?(::ActionController::Base)
7
+ ActiveSupport::Notifications.subscribe("process_action.action_controller", new)
8
+ ::ActionController::Base.around_action(&method(:around_action))
9
+ ::ActionController::Base.rescue_from(StandardError) { |ex| RorVsWild::Plugin::ActionController.after_exception(ex, self) }
10
+ @installed = true
11
+ end
12
+
13
+ def self.after_exception(exception, controller)
14
+ if hash = RorVsWild.agent.push_exception(exception)
15
+ hash[:session] = controller.session.to_hash
16
+ hash[:environment_variables] = controller.request.filtered_env
17
+ end
18
+ raise exception
19
+ end
20
+
21
+ def self.around_action(controller, block)
22
+ begin
23
+ RorVsWild::Section.start do |section|
24
+ method_name = controller.method_for_action(controller.action_name)
25
+ section.file, section.line = controller.method(method_name).source_location
26
+ section.command = "#{controller.class}##{method_name}"
27
+ section.kind = "code".freeze
28
+ end
29
+ block.call
30
+ ensure
31
+ RorVsWild::Section.stop
32
+ end
33
+ end
34
+
35
+ # Payload: controller, action, params, format, method, path
36
+ def start(name, id, payload)
37
+ name = "#{payload[:controller]}##{payload[:action]}"
38
+ RorVsWild.agent.start_request(name: name, path: payload[:path])
39
+ end
40
+
41
+ def finish(name, id, payload)
42
+ RorVsWild.agent.stop_request
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ class ActionMailer
4
+ def self.setup
5
+ return if @installed
6
+ return unless defined?(::ActiveSupport::Notifications.subscribe)
7
+ ActiveSupport::Notifications.subscribe("deliver.action_mailer", new)
8
+ @installed = true
9
+ end
10
+
11
+ def start(name, id, payload)
12
+ RorVsWild::Section.start
13
+ end
14
+
15
+ def finish(name, id, payload)
16
+ RorVsWild::Section.stop do |section|
17
+ section.command = payload[:mailer]
18
+ section.kind = "mail".freeze
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ class ActionView
4
+ def self.setup
5
+ return if @installed
6
+ return unless defined?(::ActiveSupport::Notifications.subscribe)
7
+ ActiveSupport::Notifications.subscribe("render_partial.action_view", new)
8
+ ActiveSupport::Notifications.subscribe("render_template.action_view", new)
9
+ @installed = true
10
+ end
11
+
12
+ def start(name, id, payload)
13
+ RorVsWild::Section.start
14
+ end
15
+
16
+ def finish(name, id, payload)
17
+ RorVsWild::Section.stop do |section|
18
+ section.kind = "view".freeze
19
+ section.command = RorVsWild.agent.relative_path(payload[:identifier])
20
+ section.file = section.command
21
+ section.line = 1
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ module ActiveJob
4
+ def self.setup
5
+ return if @installed
6
+ return unless defined?(::ActiveJob::Base)
7
+ ::ActiveJob::Base.around_perform(&method(:around_perform))
8
+ @installed = true
9
+ end
10
+
11
+ def self.around_perform(job, block)
12
+ RorVsWild.measure_block(job.class.name, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ class ActiveRecord
4
+ def self.setup
5
+ return if @installed
6
+ setup_callback
7
+ @installed = true
8
+ end
9
+
10
+ def self.setup_callback
11
+ return unless defined?(::ActiveSupport::Notifications.subscribe)
12
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
13
+ end
14
+
15
+ IGNORED_QUERIES = %w[EXPLAIN SCHEMA].freeze
16
+
17
+ def start(name, id, payload)
18
+ return if IGNORED_QUERIES.include?(payload[:name])
19
+ RorVsWild::Section.start do |section|
20
+ section.command = payload[:sql]
21
+ section.kind = "sql".freeze
22
+ end
23
+ end
24
+
25
+ def finish(name, id, payload)
26
+ return if IGNORED_QUERIES.include?(payload[:name])
27
+ RorVsWild::Section.stop
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module RorVsWild
2
+ module Plugin
3
+ module DelayedJob
4
+ def self.setup
5
+ return if @installed
6
+ return unless defined?(Delayed::Worker)
7
+ Delayed::Worker.lifecycle.around(:invoke_job, &method(:around_perform))
8
+ @installed = true
9
+ end
10
+
11
+ def self.around_perform(job, &block)
12
+ RorVsWild.measure_block(job.name) { block.call(job) }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -15,6 +15,7 @@ module RorVsWild
15
15
  end
16
16
 
17
17
  def started(event)
18
+ RorVsWild::Section.start
18
19
  commands[event.request_id] = event.command
19
20
  end
20
21
 
@@ -27,10 +28,10 @@ module RorVsWild
27
28
  end
28
29
 
29
30
  def after_query(event)
30
- runtime = event.duration * 1000
31
- command = commands.delete(event.request_id).to_s
32
- file, line, method = RorVsWild.client.extract_most_relevant_location(caller)
33
- RorVsWild.client.send(:push_query, kind: "mongo", command: command, file: file, line: line, method: method, runtime: runtime)
31
+ RorVsWild::Section.stop do |section|
32
+ section.kind = "mongo".freeze
33
+ section.command = commands.delete(event.request_id).to_s
34
+ end
34
35
  end
35
36
  end
36
37
  end
@@ -1,6 +1,9 @@
1
1
  module RorVsWild
2
2
  module Plugin
3
3
  class NetHttp
4
+ HTTP = "http".freeze
5
+ HTTPS = "https".freeze
6
+
4
7
  def self.setup
5
8
  return if !defined?(Net::HTTP)
6
9
  return if Net::HTTP.method_defined?(:request_without_rorvswild)
@@ -9,12 +12,19 @@ module RorVsWild
9
12
  alias_method :request_without_rorvswild, :request
10
13
 
11
14
  def request(req, body = nil, &block)
12
- scheme = use_ssl? ? "https".freeze : "http".freeze
15
+ return request_without_rorvswild(req, body, &block) if request_called_twice?
16
+ scheme = use_ssl? ? HTTPS : HTTP
13
17
  url = "#{req.method} #{scheme}://#{address}#{req.path}"
14
- RorVsWild.client.measure_query("http".freeze, url) do
18
+ RorVsWild.agent.measure_section(url, HTTP) do
15
19
  request_without_rorvswild(req, body, &block)
16
20
  end
17
21
  end
22
+
23
+ def request_called_twice?
24
+ # Net::HTTP#request calls itself when connection is not started.
25
+ # This condition prevents from counting twice the request.
26
+ (current_section = RorVsWild::Section.current) && current_section.kind == HTTP
27
+ end
18
28
  end
19
29
  end
20
30
  end
@@ -9,7 +9,7 @@ module RorVsWild
9
9
 
10
10
  def process(commands, &block)
11
11
  string = RorVsWild::Plugin::Redis.commands_to_string(commands)
12
- RorVsWild.client.measure_query("redis".freeze, string) do
12
+ RorVsWild.agent.measure_block(string, "redis".freeze) do
13
13
  process_without_rorvswild(commands, &block)
14
14
  end
15
15
  end
@@ -0,0 +1,14 @@
1
+ require "rorvswild/plugin/net_http"
2
+
3
+ require "rorvswild/plugin/redis"
4
+ require "rorvswild/plugin/mongo"
5
+
6
+ require "rorvswild/plugin/resque"
7
+ require "rorvswild/plugin/sidekiq"
8
+ require "rorvswild/plugin/active_job"
9
+ require "rorvswild/plugin/delayed_job"
10
+
11
+ require "rorvswild/plugin/action_view"
12
+ require "rorvswild/plugin/active_record"
13
+ require "rorvswild/plugin/action_mailer"
14
+ require "rorvswild/plugin/action_controller"
@@ -13,7 +13,7 @@ module RorVsWild
13
13
  return if @started
14
14
  if (path = Rails.root.join("config/rorvswild.yml")).exist?
15
15
  if config = RorVsWild::RailsLoader.load_config_file(path)[Rails.env]
16
- RorVsWild::Client.new(config.symbolize_keys)
16
+ RorVsWild.start(config.symbolize_keys)
17
17
  @started = true
18
18
  end
19
19
  end
@@ -0,0 +1,54 @@
1
+ module RorVsWild
2
+ class Section
3
+ attr_reader :started_at
4
+ attr_accessor :kind, :file, :line, :calls, :command, :children_runtime, :total_runtime
5
+
6
+ def self.start(&block)
7
+ section = Section.new
8
+ block.call(section) if block_given?
9
+ stack.push(section)
10
+ section
11
+ end
12
+
13
+ def self.stop(&block)
14
+ section = stack.pop
15
+ block.call(section) if block_given?
16
+ section.total_runtime = (Time.now.utc - section.started_at) * 1000
17
+ current.children_runtime += section.total_runtime if current
18
+ RorVsWild.agent.add_section(section)
19
+ end
20
+
21
+ def self.stack
22
+ RorVsWild.agent.data[:section_stack] ||= []
23
+ end
24
+
25
+ def self.current
26
+ stack.last
27
+ end
28
+
29
+ def initialize
30
+ @calls = 1
31
+ @total_runtime = 0
32
+ @children_runtime = 0
33
+ @started_at = Time.now.utc
34
+ location = RorVsWild.agent.find_most_relevant_location(caller_locations)
35
+ @file = RorVsWild.agent.relative_path(location.path)
36
+ @line = location.lineno
37
+ end
38
+
39
+ def sibling?(section)
40
+ kind == section.kind && line == section.line && file == section.file
41
+ end
42
+
43
+ def merge(section)
44
+ self.calls += section.calls
45
+ self.total_runtime += section.total_runtime
46
+ self.children_runtime += section.children_runtime
47
+ self.command ||= section.command
48
+ end
49
+
50
+ def self_runtime
51
+ total_runtime - children_runtime
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "0.6.1".freeze
2
+ VERSION = "1.0.0-alpha".freeze
3
3
  end
@@ -5,3 +5,16 @@ require "rorvswild"
5
5
  require "minitest/autorun"
6
6
  require "mocha/mini_test"
7
7
  require "top_tests"
8
+
9
+ module RorVsWildAgentHelper
10
+ def agent
11
+ @agent ||= initialize_agent(app_root: File.dirname(__FILE__))
12
+ end
13
+
14
+ def initialize_agent(options = {})
15
+ agent ||= RorVsWild.start(options)
16
+ agent.stubs(:post_request)
17
+ agent.stubs(:post_job)
18
+ agent
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/helper")
2
+
3
+ class RorVsWild::MeasureNestedSectionsTest < Minitest::Test
4
+ include RorVsWildAgentHelper
5
+ include TopTests
6
+
7
+ def test_measure_section
8
+ result = agent.measure_block("root") do
9
+ agent.measure_block("parent") do
10
+ sleep 0.01
11
+ agent.measure_block("child") do
12
+ sleep 0.02
13
+ 42
14
+ end
15
+ end
16
+ end
17
+ assert_equal(42, result)
18
+ sections = agent.data[:sections]
19
+ parent, child = sections[1], sections[0]
20
+ assert_equal("child", child.command)
21
+ assert_equal("parent", parent.command)
22
+ assert(child.self_runtime > 20)
23
+ assert(parent.self_runtime > 10)
24
+ assert(child.self_runtime > parent.self_runtime)
25
+ assert_equal(child.total_runtime + parent.self_runtime, parent.total_runtime)
26
+ end
27
+
28
+ def test_measure_section_with_exception
29
+ assert_raises(ZeroDivisionError) do
30
+ agent.measure_block("root") do
31
+ agent.measure_block("parent") do
32
+ agent.measure_block("child") { 1 / 0 }
33
+ end
34
+ end
35
+ end
36
+ assert_equal(2, agent.data[:sections].size)
37
+ end
38
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ require "action_controller"
4
+
5
+ class RorVsWild::Plugin::ActionControllerTest < Minitest::Test
6
+ include RorVsWildAgentHelper
7
+
8
+ def test_callback
9
+ agent.expects(:post_request)
10
+ payload = {controller: "UsersController", action: "show"}
11
+ ActiveSupport::Notifications.instrument("process_action.action_controller", payload) do
12
+ sleep 0.01
13
+ end
14
+
15
+ data = agent.send(:data)
16
+ assert_equal(0, data[:sections].size)
17
+ assert_equal("UsersController#show", data[:name])
18
+ assert(data[:runtime] > 10)
19
+ end
20
+
21
+ def test_callback_when_exception_is_raised
22
+ agent.expects(:post_request)
23
+ controller = stub(session: {id: "session"}, request: stub(filtered_env: {header: "env"}))
24
+ payload = {controller: "UsersController", action: "show"}
25
+ assert_raises(ZeroDivisionError) do
26
+ ActiveSupport::Notifications.instrument("process_action.action_controller", payload) do
27
+ begin
28
+ 1 / 0
29
+ rescue => ex
30
+ RorVsWild::Plugin::ActionController.after_exception(ex, controller)
31
+ end
32
+ end
33
+ end
34
+
35
+ data = agent.send(:data)
36
+ assert_equal("UsersController#show", data[:name])
37
+ assert_equal("ZeroDivisionError", data[:error][:exception])
38
+ assert_equal({id: "session"}, data[:error][:session])
39
+ assert_equal({header: "env"}, data[:error][:environment_variables])
40
+ end
41
+
42
+ class SampleController
43
+ def index
44
+ end
45
+ end
46
+
47
+ def test_around_action
48
+ controller = SampleController.new
49
+ controller.stubs(action_name: "index", controller_name: "SampleController", method_for_action: "index")
50
+ agent.measure_block("test") do
51
+ RorVsWild::Plugin::ActionController.around_action(controller, controller.method(:index))
52
+ end
53
+ assert_equal(1, agent.data[:sections].size)
54
+ assert_equal(__FILE__, agent.data[:sections][0].file)
55
+ assert_equal("RorVsWild::Plugin::ActionControllerTest::SampleController#index", agent.data[:sections][0].command)
56
+ end
57
+ end
58
+