rails_customerbeats 0.0.4

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 (67) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/Gemfile +10 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +58 -0
  6. data/TODO.rdoc +1 -0
  7. data/app/controllers/rails_customerbeats_controller.rb +82 -0
  8. data/app/helpers/rails_customerbeats_helper.rb +164 -0
  9. data/app/views/layouts/rails_customerbeats.html.erb +21 -0
  10. data/app/views/rails_customerbeats/_request.html.erb +21 -0
  11. data/app/views/rails_customerbeats/_row.html.erb +28 -0
  12. data/app/views/rails_customerbeats/all.html.erb +26 -0
  13. data/app/views/rails_customerbeats/chart.html.erb +49 -0
  14. data/app/views/rails_customerbeats/index.html.erb +21 -0
  15. data/app/views/rails_customerbeats/show.html.erb +41 -0
  16. data/config/routes.rb +10 -0
  17. data/lib/generators/rails_customerbeats_generator.rb +33 -0
  18. data/lib/rails_customerbeats/async_consumer.rb +54 -0
  19. data/lib/rails_customerbeats/engine.rb +43 -0
  20. data/lib/rails_customerbeats/middleware.rb +27 -0
  21. data/lib/rails_customerbeats/orm/active_record.rb +79 -0
  22. data/lib/rails_customerbeats/orm/data_mapper.rb +88 -0
  23. data/lib/rails_customerbeats/payload_parser.rb +134 -0
  24. data/lib/rails_customerbeats/store.rb +137 -0
  25. data/lib/rails_customerbeats/version.rb +3 -0
  26. data/lib/rails_customerbeats.rb +121 -0
  27. data/public/images/rails_customerbeats/arrow_down.png +0 -0
  28. data/public/images/rails_customerbeats/arrow_up.png +0 -0
  29. data/public/images/rails_customerbeats/cancel.png +0 -0
  30. data/public/images/rails_customerbeats/chart_pie.png +0 -0
  31. data/public/images/rails_customerbeats/page_white_delete.png +0 -0
  32. data/public/images/rails_customerbeats/page_white_go.png +0 -0
  33. data/public/images/rails_customerbeats/tick.png +0 -0
  34. data/public/javascripts/rails_customerbeats/g.pie-min.js +6 -0
  35. data/public/javascripts/rails_customerbeats/g.raphael-min.js +5 -0
  36. data/public/javascripts/rails_customerbeats/raphael-min.js +5 -0
  37. data/public/stylesheets/rails_customerbeats.css +135 -0
  38. data/test/dummy/app/controllers/application_controller.rb +4 -0
  39. data/test/dummy/app/controllers/users_controller.rb +43 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/app/models/metric.rb +3 -0
  42. data/test/dummy/app/models/notification.rb +7 -0
  43. data/test/dummy/app/models/user.rb +2 -0
  44. data/test/dummy/config/application.rb +52 -0
  45. data/test/dummy/config/boot.rb +9 -0
  46. data/test/dummy/config/environment.rb +5 -0
  47. data/test/dummy/config/environments/development.rb +19 -0
  48. data/test/dummy/config/environments/production.rb +33 -0
  49. data/test/dummy/config/environments/test.rb +31 -0
  50. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  51. data/test/dummy/config/initializers/cookie_verification_secret.rb +7 -0
  52. data/test/dummy/config/initializers/session_store.rb +8 -0
  53. data/test/dummy/config/routes.rb +60 -0
  54. data/test/dummy/db/migrate/20100106152343_create_metrics.rb +17 -0
  55. data/test/dummy/db/migrate/20100108120821_create_users.rb +13 -0
  56. data/test/integration/instrumentation_test.rb +100 -0
  57. data/test/integration/navigation_test.rb +103 -0
  58. data/test/orm/active_record_test.rb +47 -0
  59. data/test/payload_parser_test.rb +36 -0
  60. data/test/rails_customerbeats_test.rb +43 -0
  61. data/test/store_test.rb +81 -0
  62. data/test/support/helpers.rb +16 -0
  63. data/test/support/instrumentation.rb +18 -0
  64. data/test/support/mock_store.rb +34 -0
  65. data/test/support/webrat/integrations/rails.rb +31 -0
  66. data/test/test_helper.rb +25 -0
  67. metadata +142 -0
@@ -0,0 +1,17 @@
1
+ class CreateMetrics < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :metrics do |t|
4
+ t.string :name
5
+ t.integer :request_id
6
+ t.integer :parent_id
7
+ t.integer :duration
8
+ t.text :payload
9
+ t.datetime :started_at
10
+ t.datetime :created_at
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table :metrics
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :users
12
+ end
13
+ end
@@ -0,0 +1,100 @@
1
+ require 'test_helper'
2
+
3
+ class InstrumentationTest < ActionController::IntegrationTest
4
+ setup do
5
+ Metric.delete_all
6
+ end
7
+
8
+ test "rails metrics request is added to notifications" do
9
+ get "/users"
10
+ wait
11
+
12
+ request = Metric.first
13
+
14
+ assert_equal "rack.request", request.name
15
+ assert (request.duration >= 0)
16
+ assert_kind_of Time, request.started_at
17
+ assert_equal Hash[:path => "/users", :method => "GET",
18
+ :instrumenter_id => ActiveSupport::Notifications.instrumenter.id], request.payload
19
+ end
20
+
21
+ test "processed actions are added to RailsMetrics" do
22
+ get "/users"
23
+ wait
24
+
25
+ assert_equal 4, Metric.count
26
+ request, action, sql, template = Metric.all
27
+
28
+ assert_equal "action_controller.process_action", action.name
29
+ assert_equal "active_record.sql", sql.name
30
+ assert_equal "action_view.render_template", template.name
31
+
32
+ assert (action.duration >= 0)
33
+ assert (sql.duration >= 0)
34
+ assert (template.duration >= 0)
35
+
36
+ assert_kind_of Time, action.started_at
37
+ assert_kind_of Time, sql.started_at
38
+ assert_kind_of Time, template.started_at
39
+
40
+ assert_equal Hash[:status=>200, :end_point=>"UsersController#index",
41
+ :formats=>[:html]], action.payload
42
+
43
+ assert_equal Hash[:sql => "SELECT `users`.* FROM `users`",
44
+ :name => "User Load"], sql.payload
45
+
46
+ assert_equal Hash[:identifier => "RAILS_ROOT/app/views/users/index.html.erb",
47
+ :layout => "RAILS_ROOT/app/views/layouts/users.html.erb"], template.payload
48
+ end
49
+
50
+ test "instrumentations are saved nested in the database" do
51
+ get "/users"
52
+ wait
53
+
54
+ assert_equal 4, Metric.count
55
+ request, action, sql, template = Metric.all
56
+
57
+ assert_nil request.parent_id
58
+ assert_equal action.parent_id, request.id
59
+ assert_equal sql.parent_id, action.id
60
+ assert_equal template.parent_id, action.id
61
+
62
+ assert_equal request.id, request.request_id
63
+ assert_equal request.id, action.request_id
64
+ assert_equal request.id, sql.request_id
65
+ assert_equal request.id, template.request_id
66
+ end
67
+
68
+ test "does not create metrics when accessing /rails_metrics" do
69
+ assert_no_difference "Metric.count" do
70
+ get "/rails_metrics"
71
+ wait
72
+ end
73
+ end
74
+
75
+ test "fragment cache are added to RailsMetrics" do
76
+ get "/users/new"
77
+ wait
78
+
79
+ assert_equal 6, Metric.count
80
+ request, action, template, partial, exist, write = Metric.all
81
+
82
+ assert_equal "action_view.render_partial", partial.name
83
+ assert_equal "action_controller.exist_fragment?", exist.name
84
+ assert_equal "action_controller.write_fragment", write.name
85
+
86
+ assert (partial.duration >= 0)
87
+ assert (exist.duration >= 0)
88
+ assert (write.duration >= 0)
89
+
90
+ assert_kind_of Time, partial.started_at
91
+ assert_kind_of Time, exist.started_at
92
+ assert_kind_of Time, write.started_at
93
+
94
+ assert_equal Hash[:identifier => "RAILS_ROOT/app/views/users/_form.html.erb"],
95
+ partial.payload
96
+
97
+ assert_equal Hash[:key => "views/foo.bar"], exist.payload
98
+ assert_equal Hash[:key => "views/foo.bar"], write.payload
99
+ end
100
+ end
@@ -0,0 +1,103 @@
1
+ require 'test_helper'
2
+
3
+ class NagivationTest < ActionController::IntegrationTest
4
+ setup do
5
+ Metric.delete_all
6
+ get "/users"
7
+ wait
8
+ end
9
+
10
+ test "can navigate all notifications" do
11
+ get "/rails_metrics"
12
+ click_link "All metrics"
13
+
14
+ assert_contain "action_view.render_template"
15
+ assert_contain "action_controller.process_action"
16
+ assert_contain ActiveSupport::Notifications.instrumenter.id
17
+
18
+ id = Metric.last.id
19
+
20
+ within "#rails_customerbeat_#{id}" do
21
+ click_link "Show"
22
+ end
23
+
24
+ assert_contain "action_view.render_template"
25
+ click_link "action_view.render_template"
26
+
27
+ within "#rails_customerbeat_#{id}" do
28
+ click_button "Delete"
29
+ end
30
+
31
+ assert_contain "Metric ##{id} was deleted with success"
32
+
33
+ get "/rails_metrics/all"
34
+ assert_not_contain "action_view.render_template"
35
+ end
36
+
37
+ test "can nagivate all metrics with by scopes" do
38
+ get "/rails_metrics/all"
39
+
40
+ click_link "active_record.sql"
41
+ assert_contain "Showing 1 - 1 of 1 metrics filtered by name"
42
+
43
+ click_link "Remove \"Name\" filter"
44
+ assert_contain "Showing 1 - 4 of 4 metrics"
45
+ end
46
+
47
+ test "can nagivate all metrics with order by scopes" do
48
+ get "/rails_metrics/all"
49
+ click_link "Order by latest"
50
+ assert_contain "ordered by latest"
51
+
52
+ click_link "Show"
53
+ assert_contain "action_view.render_template"
54
+
55
+ click_link "Back"
56
+ click_link "Order by earliest"
57
+ assert_contain "ordered by earliest"
58
+
59
+ click_link "Show"
60
+ assert_contain "rack.request"
61
+
62
+ click_link "Back"
63
+ click_link "Order by fastest"
64
+ assert_contain "ordered by fastest"
65
+
66
+ click_link "Show"
67
+ assert_contain Metric.fastest.first.name
68
+ end
69
+
70
+ test "can destroy all notifications in a given scope" do
71
+ get "/rails_metrics/all"
72
+ click_link "active_record.sql"
73
+ assert_contain "Showing 1 - 1 of 1 metrics filtered by name"
74
+
75
+ click_button "Delete all"
76
+ assert_contain "All 1 selected metrics were deleted."
77
+
78
+ click_link "All metrics"
79
+ assert_contain "Showing 1 - 3 of 3 metrics"
80
+ end
81
+
82
+ test "can navigate all metrics with pagination" do
83
+ get "/rails_metrics/all"
84
+ assert_contain "Showing 1 - 4 of 4 metrics"
85
+
86
+ get "/rails_metrics/all?limit=2"
87
+ assert_contain "Showing 1 - 2 of 4 metrics"
88
+
89
+ click_link "Next"
90
+ assert_contain "Showing 3 - 4 of 4 metrics"
91
+
92
+ assert_raise Webrat::NotFoundError do
93
+ click_link "Next"
94
+ end
95
+
96
+ click_link "Previous"
97
+ assert_contain "Showing 1 - 2 of 4 metrics"
98
+
99
+ assert_raise Webrat::NotFoundError do
100
+ click_link "Previous"
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveRecordTest < ActiveSupport::TestCase
4
+ setup do
5
+ Metric.delete_all
6
+ end
7
+
8
+ test "does not store queries other than SELECT, INSERT, UPDATE and DELETE" do
9
+ instrument "test.event" do
10
+ User.connection.send(:select, "SHOW tables;")
11
+ end
12
+ wait
13
+ assert_equal 2, Metric.all.count # one for test.event and other for rack.request
14
+ end
15
+
16
+ test "serializes payload" do
17
+ metric = Metric.new
18
+ metric.configure(["metric", Time.now, Time.now, "id", {:foo => :bar}])
19
+ metric.save!
20
+ assert_equal Hash[:foo => :bar], metric.payload
21
+ end
22
+
23
+ test "is invalid when name is blank" do
24
+ metric = Metric.new
25
+ assert metric.invalid?
26
+ assert "can't be blank", metric.errors["name"].join
27
+ end
28
+
29
+ test "is invalid when started_at is blank" do
30
+ metric = Metric.new
31
+ assert metric.invalid?
32
+ assert "can't be blank", metric.errors["started_at"].join
33
+ end
34
+
35
+ test "is invalid when duration is blank" do
36
+ metric = Metric.new
37
+ assert metric.invalid?
38
+ assert "can't be blank", metric.errors["duration"].join
39
+ end
40
+
41
+ test "responds to all required named scopes" do
42
+ [ :by_name, :by_request_id, :latest,
43
+ :earliest, :slowest, :fastest, :requests ].each do |method|
44
+ assert_respond_to Metric, method
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ class PayloadParserTest < ActiveSupport::TestCase
4
+ setup do
5
+ @_previous_parsers = RailsMetrics::PayloadParser.parsers.dup
6
+ end
7
+
8
+ teardown do
9
+ RailsMetrics::PayloadParser.parsers.replace @_previous_parsers
10
+ end
11
+
12
+ delegate :add, :ignore, :filter, :to => RailsMetrics::PayloadParser
13
+
14
+ test "a non registered parser returns payload as is" do
15
+ assert_equal Hash[:some => :info], filter("rails_metrics.something", :some => :info)
16
+ end
17
+
18
+ test "a parser with hash converts into method calls" do
19
+ add "rails_metrics.something", :except => [:foo, :bar]
20
+ assert_equal Hash[:some => :info], filter("rails_metrics.something", :some => :info,
21
+ :foo => :baz, :bar => :baz)
22
+ end
23
+
24
+ test "a parser with a block calls the block with payload" do
25
+ add "rails_metrics.something" do |payload|
26
+ assert_equal Hash[:some => :info], payload
27
+ { :foo => :bar }
28
+ end
29
+ assert_equal Hash[:foo => :bar], filter("rails_metrics.something", :some => :info)
30
+ end
31
+
32
+ test "a parser can be ignored" do
33
+ ignore "rails_metrics.something"
34
+ assert_nil filter("rails_metrics.something", :some => :info)
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ class RailsMetricsTest < ActiveSupport::TestCase
4
+ setup do
5
+ RailsMetrics.set_store { MockStore }
6
+ MockStore.instances.clear
7
+ end
8
+
9
+ test "send instrumentation event to the specified store" do
10
+ instrument "rails_metrics.something"
11
+ wait
12
+
13
+ assert_equal 2, MockStore.instances.size
14
+ assert_equal "rails_metrics.something", MockStore.instances.first.name
15
+ end
16
+
17
+ test "does not send an event to the store if it matches an ignored pattern" do
18
+ RailsMetrics.ignore_patterns << /rails_metrics/
19
+
20
+ begin
21
+ instrument "rails_metrics.something"
22
+ wait
23
+ assert MockStore.instances.none? { |m| m.name == "rails_metrics.something" }
24
+ ensure
25
+ RailsMetrics.ignore_patterns.pop
26
+ end
27
+ end
28
+
29
+ test "does not send an event to the store if it was generated inside it" do
30
+ instrument "rails_metrics.kicker"
31
+ wait
32
+
33
+ assert_equal 2, MockStore.instances.size
34
+ assert MockStore.instances.first.kicked?
35
+ assert_equal "rails_metrics.kicker", MockStore.instances.first.name
36
+ end
37
+
38
+ test "does not send an event if not listening" do
39
+ ActiveSupport::Notifications.instrument "rails_metrics.kicker"
40
+ wait
41
+ assert_equal 0, MockStore.instances.size
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+
3
+ class StoreTest < ActiveSupport::TestCase
4
+ setup do
5
+ RailsMetrics.set_store { MockStore }
6
+ MockStore.instances.clear
7
+ end
8
+
9
+ def sample_args
10
+ time = Time.now
11
+ ["rails_metrics.example", time, time + 1, 1, { :some => :info }]
12
+ end
13
+
14
+ def store!(args=sample_args)
15
+ metric = Metric.new
16
+ metric.configure(args)
17
+ metric
18
+ end
19
+
20
+ test "sets the name" do
21
+ assert_equal "rails_metrics.example", store!.name
22
+ end
23
+
24
+ test "sets the duration" do
25
+ assert_equal 1000000, store!.duration
26
+ assert_equal 1000000, store!.duration_in_us
27
+ assert_equal 1000, store!.duration_in_ms
28
+ end
29
+
30
+ test "sets started at" do
31
+ assert_kind_of Time, store!.started_at
32
+ end
33
+
34
+ test "sets the payload" do
35
+ assert_equal Hash[:some => :info], store!.payload
36
+ end
37
+
38
+ test "nested instrumentations are saved nested" do
39
+ instrument "rails_metrics.parent" do
40
+ instrument "rails_metrics.child" do
41
+ end
42
+ end
43
+
44
+ instrument "rails_metrics.another"
45
+
46
+ assert_equal 5, MockStore.instances.size
47
+ child, parent, request, another, another_request = MockStore.instances
48
+
49
+ assert_equal request.id, request.request_id
50
+ assert_equal request.id, parent.request_id
51
+ assert_equal request.id, parent.parent_id
52
+ assert_equal request.id, child.request_id
53
+ assert_equal parent.id, child.parent_id
54
+
55
+ assert parent.parent_of?(child)
56
+ assert child.child_of?(parent)
57
+
58
+ assert !parent.parent_of?(another)
59
+ assert !another.parent_of?(parent)
60
+ assert !parent.child_of?(another)
61
+ assert !another.child_of?(parent)
62
+ end
63
+
64
+ test "tree can be created from nested instrumentations" do
65
+ instrument "rails_metrics.parent" do
66
+ instrument "rails_metrics.child" do
67
+ end
68
+ end
69
+
70
+ child, parent, request = MockStore.instances
71
+ root = RailsMetrics.store.mount_tree(MockStore.instances)
72
+
73
+ assert_equal "rails_metrics.child", child.name
74
+ assert_equal "rails_metrics.parent", parent.name
75
+ assert_equal "rack.request", request.name
76
+
77
+ assert root.rack_request?
78
+ assert [parent], root.children
79
+ assert [child], parent.children
80
+ end
81
+ end
@@ -0,0 +1,16 @@
1
+ class ActiveSupport::TestCase
2
+ # Execute the block setting the given values and restoring old values after
3
+ # the block is executed.
4
+ def swap(object, new_values)
5
+ old_values = {}
6
+ new_values.each do |key, value|
7
+ old_values[key] = object.send key
8
+ object.send :"#{key}=", value
9
+ end
10
+ yield
11
+ ensure
12
+ old_values.each do |key, value|
13
+ object.send :"#{key}=", value
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ class ActiveSupport::TestCase
2
+ def wait
3
+ RailsMetrics.wait
4
+ end
5
+
6
+ # Fake a request instrumentation.
7
+ def instrument(*args, &block)
8
+ if RailsMetrics.listening?
9
+ ActiveSupport::Notifications.instrument(*args, &block)
10
+ else
11
+ RailsMetrics.listen_request do
12
+ ActiveSupport::Notifications.instrument "rack.request" do
13
+ ActiveSupport::Notifications.instrument(*args, &block)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ class ActiveSupport::TestCase
2
+ class MockStore
3
+ include RailsMetrics::Store
4
+
5
+ attr_accessor :id, :name, :parent_id, :request_id, :started_at, :duration, :payload
6
+
7
+ def self.instances
8
+ @instances ||= []
9
+ end
10
+
11
+ def initialize
12
+ self.class.instances << self
13
+ end
14
+
15
+ def kicked?
16
+ @kicked || false
17
+ end
18
+
19
+ def new_record?
20
+ true
21
+ end
22
+
23
+ protected
24
+
25
+ def save_metric!
26
+ self.id ||= (rand * 1000).to_i
27
+
28
+ if self.name == "rails_metrics.kicker"
29
+ @kicked = true
30
+ ActiveSupport::Notifications.instrument("rails_metrics.inside_store")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'webrat/core/elements/field'
2
+
3
+ module Webrat
4
+ Field.class_eval do
5
+ def parse_rails_request_params(params)
6
+ Rack::Utils.parse_nested_query(params)
7
+ end
8
+ end
9
+ end
10
+
11
+ module ActionController #:nodoc:
12
+ IntegrationTest.class_eval do
13
+ include Webrat::Methods
14
+ include Webrat::Matchers
15
+
16
+ # The Rails version of within supports passing in a model and Webrat
17
+ # will apply a scope based on Rails' dom_id for that model.
18
+ #
19
+ # Example:
20
+ # within User.last do
21
+ # click_link "Delete"
22
+ # end
23
+ def within(selector_or_object, &block)
24
+ if selector_or_object.is_a?(String)
25
+ super
26
+ else
27
+ super('#' + RecordIdentifier.dom_id(selector_or_object), &block)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # Configure Rails Envinronment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+
7
+ ActionMailer::Base.delivery_method = :test
8
+ ActionMailer::Base.perform_deliveries = true
9
+ ActionMailer::Base.default_url_options[:host] = "test.com"
10
+
11
+ Webrat.configure do |config|
12
+ config.mode = :rails
13
+ config.open_error_files = false
14
+ end
15
+
16
+ # Add support to load paths so we can overwrite broken webrat setup
17
+ $:.unshift File.expand_path('../support', __FILE__)
18
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
19
+
20
+ class ActiveSupport::TestCase
21
+ setup do
22
+ wait
23
+ RailsMetrics.set_store { Metric }
24
+ end
25
+ end