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
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ == 0.1
2
+
3
+ *
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :gemcutter
2
+
3
+ gem "rails", "~> 3.0.0"
4
+ gem "mysql", "~> 2.8"
5
+ gem "webrat", "~> 0.7.0"
6
+ gem "jeweler"
7
+
8
+ if RUBY_VERSION < '1.9'
9
+ gem "ruby-debug", ">= 0.10.3"
10
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+
2
+ Copyright 2010 Engine Yard. http://www.engineyard.com
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,2 @@
1
+ == CJM
2
+
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ require "rake"
4
+ require "rake/testtask"
5
+ require "rdoc/task"
6
+ require "fileutils"
7
+ require File.expand_path("../lib/rails_customerbeat/version", __FILE__)
8
+
9
+ desc "Default: run unit tests"
10
+ task :default => :test
11
+
12
+ desc "Prepare environment for tests"
13
+ task :prepare do
14
+ FileUtils.cd File.expand_path("../test/dummy", __FILE__)
15
+ system("rake db:create:all")
16
+ system("rake db:migrate")
17
+ system("rake db:test:clone")
18
+ end
19
+
20
+ desc "Start the server for the dummy application used in tests"
21
+ task :server do
22
+ exec("test/dummy/script/rails server")
23
+ end
24
+
25
+ desc "Test RailsMetrics"
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << "test"
28
+ t.pattern = "test/**/*_test.rb"
29
+ t.verbose = true
30
+ end
31
+
32
+ desc "Generate documentation for RailsMetrics"
33
+ RDoc::Task.new(:rdoc) do |rdoc|
34
+ rdoc.rdoc_dir = "rdoc"
35
+ rdoc.title = "RailsMetrics"
36
+ rdoc.options << "--line-numbers" << "--inline-source"
37
+ rdoc.rdoc_files.include("README.rdoc")
38
+ rdoc.rdoc_files.include("lib/**/*.rb")
39
+ end
40
+
41
+ begin
42
+ require "jeweler"
43
+ Jeweler::Tasks.new do |s|
44
+ s.name = "rails_customerbeat"
45
+ s.version = RailsMetrics::VERSION
46
+ s.summary = "Metrics measurement for your app on top of ActiveSupport::Notifications"
47
+ s.email = "contact@engineyard.com"
48
+ s.homepage = "http://github.com/engineyard"
49
+ s.description = "Metrics measurement for your app on top of ActiveSupport::Notifications"
50
+ s.authors = ["José Valim"]
51
+ s.files = FileList["[A-Z]*", "{app,config,lib,public}/**/*"]
52
+ s.files.exclude("public/javascripts/rails_customerbeat.js")
53
+ end
54
+
55
+ Jeweler::GemcutterTasks.new
56
+ rescue LoadError
57
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
58
+ end
data/TODO.rdoc ADDED
@@ -0,0 +1 @@
1
+ *
@@ -0,0 +1,82 @@
1
+ class RailsCustomerbeatsController < ApplicationController
2
+ respond_to :html
3
+
4
+ # GET /rails_customerbeats
5
+ def index
6
+ @metrics = order_scopes(RailsCustomerbeats.store.requests)
7
+ @metrics_count = @metrics.count
8
+ @metrics = with_pagination(@metrics)
9
+ respond_with(@metrics)
10
+ end
11
+
12
+ # GET /rails_customerbeats/1/chart
13
+ def chart
14
+ @metrics = RailsCustomerbeats.store.earliest.by_request_id(params[:id]).all
15
+ @request = RailsCustomerbeats.store.mount_tree(@metrics.reverse)
16
+ respond_with(@metrics)
17
+ end
18
+
19
+ # GET /rails_customerbeats
20
+ def all
21
+ @metrics = all_scopes(RailsCustomerbeats.store)
22
+ @metrics_count = @metrics.count
23
+ @metrics = with_pagination(@metrics)
24
+ respond_with(@metrics)
25
+ end
26
+
27
+ # GET /rails_customerbeats/1
28
+ def show
29
+ @metric = find_store(params[:id])
30
+ respond_with(@metric)
31
+ end
32
+
33
+ # DELETE /rails_customerbeats/1
34
+ def destroy
35
+ @metric = find_store(params[:id])
36
+ @metric.destroy
37
+ flash[:notice] = "Metric ##{@metric.id} was deleted with success."
38
+ respond_with(@metric, :location => rails_customerbeats_path)
39
+ end
40
+
41
+ # DELETE /rails_customerbeats/destroy_all
42
+ def destroy_all
43
+ count = all_scopes(RailsCustomerbeats.store).send(RailsCustomerbeats::ORM.delete_all)
44
+ flash[:notice] = "All #{count} selected metrics were deleted."
45
+ redirect_to rails_customerbeats_path
46
+ end
47
+
48
+ protected
49
+
50
+ def with_pagination(scope)
51
+ @limit = (params[:limit].presence || 50).to_i
52
+ @offset = (params[:offset].presence || 0).to_i
53
+ if scope.respond_to?(:limit)
54
+ scope.limit(@limit).offset(@offset).all
55
+ else
56
+ scope.all(:limit => @limit, :offset => @offset)
57
+ end
58
+ end
59
+
60
+ def by_scopes(store)
61
+ @by_name = params[:by_name].presence
62
+ store = store.by_name(@by_name) if @by_name
63
+ store
64
+ end
65
+
66
+ def order_scopes(store)
67
+ @order_by = (valid_order_by? ? params[:order_by] : :latest).to_sym
68
+ store = store.send(@order_by)
69
+ end
70
+
71
+ def all_scopes(store)
72
+ order_scopes(by_scopes(store))
73
+ end
74
+
75
+ def valid_order_by?
76
+ RailsCustomerbeats::Store::VALID_ORDERS.include?(params[:order_by])
77
+ end
78
+
79
+ def find_store(id)
80
+ RailsCustomerbeats.store.send(RailsCustomerbeats::ORM.primary_key_finder, id)
81
+ end
82
+ end
@@ -0,0 +1,164 @@
1
+ module RailsCustomerbeatsHelper
2
+
3
+
4
+
5
+ module Pagination
6
+ # Returns information about pagination
7
+ def pagination_info
8
+ maximum = [@metrics_count, @offset + @limit].min
9
+ "#{@offset + 1} - #{maximum} of #{@metrics_count}"
10
+ end
11
+
12
+ # Shows per page links
13
+ def show_per_page(values)
14
+ values.map do |i|
15
+ link_to_unless(@limit == i, i.to_s, url_for(params.merge(:limit => i)))
16
+ end.join(" | ").html_safe
17
+ end
18
+
19
+ # Shows previous link for pagination
20
+ def previous_link
21
+ link = url_for(params.merge(:offset => [0, @offset - @limit].max))
22
+ link_to_if(@offset > 0, "Previous", link)
23
+ end
24
+
25
+ # Show next link for pagination
26
+ def next_link
27
+ link = url_for(params.merge(:offset => @offset + @limit))
28
+ link_to_if(@offset + @limit < @metrics_count, "Next", link)
29
+ end
30
+
31
+ # Add pagination to footlinks
32
+ def paginate!
33
+ content_for :rails_customerbeats_footlinks do
34
+ content_tag(:p, [previous_link, pagination_info, next_link].join(" | "),nil,false) <<
35
+ content_tag(:p, "Show per page: #{show_per_page([10, 25, 50, 100])}",nil,false)
36
+ end
37
+ end
38
+ end
39
+
40
+ module PayloadInspect
41
+ # Inspect payload to show more human readable information.
42
+ def payload_inspect(hash)
43
+ hash = hash.sort {|a,b| a[0].to_s <=> b[0].to_s }
44
+ content = []
45
+
46
+ hash.each do |key, value|
47
+ content << (content_tag(:b, key.to_s.humanize).safe_concat("<br />") << pretty_inspect(value))
48
+ end
49
+
50
+ content.map!{ |c| content_tag(:p, c) }
51
+ content.join("\n").html_safe
52
+ end
53
+
54
+ # Inspect a value using a more readable format.
55
+ def pretty_inspect(object)
56
+ case object
57
+ when String
58
+ object
59
+ when Array
60
+ "[#{object.map(&:inspect).join(", ")}]"
61
+ when Hash
62
+ hash = object.map { |k,v| " #{k.inspect} => #{pretty_inspect(v)}" }.join(",\n")
63
+ if object.size == 1
64
+ "{ #{hash[2..-1]} }"
65
+ else
66
+ "{\n#{hash}\n}"
67
+ end
68
+ else
69
+ object.inspect
70
+ end
71
+ end
72
+ end
73
+
74
+ module Scoping
75
+ # Returns information about scope
76
+ def scopes_info
77
+ filters = []
78
+ filters << "name" if @by_name
79
+ filters.map!{ |i| content_tag(:b, i) }
80
+
81
+ content = []
82
+ content << "filtered by #{filters.to_sentence}" unless filters.empty?
83
+ content << "ordered by <b>#{@order_by.to_s.humanize.downcase}</b>"
84
+ content.to_sentence.html_safe
85
+ end
86
+
87
+ # Link to set a by_scope using the given content. If no value is given,
88
+ # the content is used as link value as well.
89
+ def link_to_set_by_scope(metric, what)
90
+ value = metric.send(what)
91
+ return value if instance_variable_get(:"@by_#{what}")
92
+ link_to value, url_for_scope(:"by_#{what}" => value, :action => "all"), :title => value
93
+ end
94
+
95
+ # Link to clear a by_scope using a cancel image.
96
+ def link_to_clear_by_scope(what)
97
+ return unless instance_variable_get(:"@by_#{what}")
98
+ link_to_set_scope_with_image("rails_customerbeats/cancel.png",
99
+ "Remove #{what.to_s.humanize.inspect} filter", :"by_#{what}" => nil)
100
+ end
101
+
102
+ # Link to order by scopes by using two arrows, one up and other down
103
+ def link_to_order_by_scopes(up, down)
104
+ link_to_set_scope_with_image("images/r_metrics/arrow_up.png", "Order by #{up}", :order_by => up) <<
105
+ link_to_set_scope_with_image("images/r_metrics/arrow_down.png", "Order by #{down}", :order_by => down)
106
+ end
107
+
108
+ protected
109
+
110
+ def url_for_scope(hash) #:nodoc:
111
+ url_for(params.except(:limit, :offset, :id).merge!(hash))
112
+ end
113
+
114
+ def link_to_set_scope_with_image(src, title, scope) #:nodoc:
115
+ image = image_tag(src, :title => title, :alt => title)
116
+ link = url_for_scope(scope)
117
+ link_to image, link, :title => title
118
+ end
119
+ end
120
+
121
+ module Links
122
+ # Links to image inside rails_customerbeats if the given path it's not the current page using the given title.
123
+ def link_to_image_unless_current(icon, path, title)
124
+ return if current_page?(path)
125
+ image = image_tag("images/r_metrics/#{icon}.png", :title => title, :alt => title)
126
+ link_to image, path, :title => title
127
+ end
128
+
129
+ # Add action icons to the current page.
130
+ def add_action_links!(metric)
131
+ concat link_to_image_unless_current(:chart_pie, chart_rails_customerbeat_path(metric.request_id), "Chart")
132
+ concat link_to_image_unless_current(:page_white_go, rails_customerbeat_path(metric), "Show")
133
+ form_tag(rails_customerbeat_path(metric), :method => :delete) do
134
+ image_submit_tag "images/r_metrics/page_white_delete.png", :onclick => "return confirm('Are you sure?')", :alt => "Delete", :title => "Delete"
135
+ end
136
+ end
137
+
138
+ def nagivation_links
139
+ @navigation_links ||= begin
140
+ links = []
141
+ links << link_to("All metrics", all_rails_customerbeats_path)
142
+ links << link_to("Requests", rails_customerbeats_path)
143
+ links << link_to("Back", :back)
144
+ links.join(" | ").html_safe
145
+ end
146
+ end
147
+ end
148
+
149
+ include Pagination
150
+ include PayloadInspect
151
+ include Scoping
152
+ include Links
153
+
154
+ # Returns pagination and scopes information
155
+ def pagination_and_scopes_info(countable)
156
+ countable = countable.to_s.pluralize unless @metrics_count == 1
157
+ "Showing #{pagination_info} #{countable} ".html_safe << scopes_info
158
+ end
159
+
160
+ # Create a table row using rails_customerbeats_#{id} as row id and odd and even as classes.
161
+ def rails_customerbeats_table_row_for(metric, &block)
162
+ content_tag(:tr, :id => "rails_customerbeat_#{metric.id}", :class => cycle("odd", "even"), &block)
163
+ end
164
+ end
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RailsCustomerbeats: <%= controller.action_name %></title>
5
+ <%= stylesheet_link_tag 'stylesheets/r_metrics' %>
6
+ <%= javascript_include_tag 'javascripts/r_metrics/raphael-min', 'javascripts/r_metrics/g.raphael-min', 'javascripts/r_metrics/g.pie-min'%>
7
+ </head>
8
+ <body>
9
+ <%= content_tag(:div, content_tag(:p, notice), :class => :notice) if notice.present? %>
10
+ <div id="rails_customerbeats_header">
11
+ <h1><%= link_to "RailsCustomerbeats", rails_customerbeats_path %></h1>
12
+ <div><%= yield :rails_customerbeats_header %></div>
13
+ <p><%= nagivation_links %></p>
14
+ </div>
15
+ <%= yield %>
16
+ <div id="rails_customerbeats_footlinks">
17
+ <%= yield :rails_customerbeats_footlinks %>
18
+ <p><%= nagivation_links %></p>
19
+ </div>
20
+ </body>
21
+ </html>
@@ -0,0 +1,21 @@
1
+ <%= rails_customerbeats_table_row_for metric do %>
2
+ <td class="when">
3
+ <%= metric.started_at.strftime("%d %b %H:%M:%S") %>
4
+ </td>
5
+
6
+ <td class="method">
7
+ <%= metric.payload[:method] %>
8
+ </td>
9
+
10
+ <td class="path">
11
+ <%= metric.payload[:path] %>
12
+ </td>
13
+
14
+ <td class="duration">
15
+ <%= "%.1f" % metric.duration_in_ms %> ms
16
+ </td>
17
+
18
+ <td class="actions">
19
+ <% add_action_links!(metric) %>
20
+ </td>
21
+ <% end %>
@@ -0,0 +1,28 @@
1
+ <%= rails_customerbeats_table_row_for metric do %>
2
+ <% unless (skip_timestamps ||= false) %>
3
+ <td class="started_at">
4
+ <%= metric.started_at.strftime("%d %b %H:%M:%S") %>
5
+ </td>
6
+ <% end %>
7
+
8
+ <td class="Id">
9
+ <%= metric.request_id %>
10
+ </td>
11
+
12
+ <td class="name">
13
+ <%= link_to_set_by_scope metric, :name %>
14
+ </td>
15
+
16
+
17
+ <td class="duration">
18
+ <%= "%.1f" % metric.duration_in_ms %> <%= "(#{"%.1f" % metric.exclusive_duration_in_ms})" if metric.children.any? %> ms
19
+ </td>
20
+
21
+ <td class="payload">
22
+ <%= payload_inspect(metric.payload) %>
23
+ </td>
24
+
25
+ <td class="actions">
26
+ <% add_action_links!(metric) %>
27
+ </td>
28
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <% if @metrics.empty? %>
2
+ <h2>No metrics so far, navigate on your app and come back.</h2>
3
+ <% else %>
4
+ <% content_for(:rails_customerbeats_header) do %>
5
+ <%= pagination_and_scopes_info(:metrics) %>
6
+
7
+ <%= form_tag url_for(params.merge(:action => "destroy_all")), :method => :delete do %>
8
+ <%= submit_tag "Delete all", :onclick => "return confirm('Are you sure you want to delete those #{@metrics_count} metrics?')" %>
9
+ <% end %>
10
+ <% end %>
11
+
12
+ <table id="rails_customerbeats_table" class="all">
13
+ <tr>
14
+ <th>When<br /><%= link_to_order_by_scopes(:earliest, :latest) %></th>
15
+ <th>Id</th>
16
+ <th>Name<br /><%= link_to_clear_by_scope(:name) %></th>
17
+ <th>Duration<br /><%= link_to_order_by_scopes(:slowest, :fastest) %></th>
18
+ <th>Payload</th>
19
+ <th></th>
20
+ </tr>
21
+
22
+ <%= render :partial => "row", :collection => @metrics, :as => :metric %>
23
+ </table>
24
+
25
+ <% paginate! %>
26
+ <% end %>
@@ -0,0 +1,49 @@
1
+ <% content_for(:rails_customerbeats_header) do %>
2
+ Showing request #<%= @request.id %>
3
+ <div class="actions"><% add_action_links!(@request) %></div>
4
+ <% end %>
5
+
6
+ <div id="chart_container">
7
+ <div id="chart"></div>
8
+ </div>
9
+
10
+ <script type="text/javascript" charset="utf-8">
11
+ var r = Raphael("chart");
12
+ r.g.text(400, 30, "<%= @request.payload[:method] %> <%= @request.payload[:path] %> at <%= @request.started_at.strftime("%d %b %H:%M:%S") %>").attr({"font-size": 20});
13
+
14
+ var pie = r.g.piechart(250, 150, 100,
15
+ <%=raw @metrics.map { |m| m.exclusive_duration_in_ms }.inspect %>, {
16
+ legend: <%=raw @metrics.map { |m| "##.# ms - #{m.name}" }.inspect %>,
17
+ href: <%=raw @metrics.map { |m| "#rails_customerbeats_#{m.id}" }.inspect %>,
18
+ cut: 0
19
+ }
20
+ );
21
+
22
+ pie.hover(function () {
23
+ this.sector.stop();
24
+ this.sector.scale(1.1, 1.1, this.cx, this.cy);
25
+ if (this.label) {
26
+ this.label[0].stop();
27
+ this.label[0].scale(1.5);
28
+ this.label[1].attr({"font-weight": 800});
29
+ }
30
+ }, function () {
31
+ this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");
32
+ if (this.label) {
33
+ this.label[0].animate({scale: 1}, 500, "bounce");
34
+ this.label[1].attr({"font-weight": 400});
35
+ }
36
+ });
37
+ </script>
38
+
39
+ <table id="rails_customerbeats_table" class="chart">
40
+ <tr>
41
+ <th>Name</th>
42
+ <th>Duration (exclusive)</th>
43
+ <th>Payload</th>
44
+ <th></th>
45
+ </tr>
46
+
47
+ <%= render :partial => "row", :collection => @metrics, :as => :metric,
48
+ :locals => { :skip_timestamps => true } %>
49
+ </table>
@@ -0,0 +1,21 @@
1
+ <% if @metrics.empty? %>
2
+ <h2>No requests so far, navigate on your app and come back.</h2>
3
+ <% else %>
4
+ <% content_for(:rails_customerbeats_header) do %>
5
+ <%= pagination_and_scopes_info(:requests) %>
6
+ <% end %>
7
+
8
+ <table id="rails_customerbeats_table" class="requests">
9
+ <tr>
10
+ <th>When<br /><%= link_to_order_by_scopes(:earliest, :latest) %></th>
11
+ <th>Method</th>
12
+ <th>Path</th>
13
+ <th>Duration<br /><%= link_to_order_by_scopes(:slowest, :fastest) %></th>
14
+ <th></th>
15
+ </tr>
16
+
17
+ <%= render :partial => "request", :collection => @metrics, :as => :metric %>
18
+ </table>
19
+
20
+ <% paginate! %>
21
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <% content_for(:rails_customerbeats_header) do %>
2
+ Showing metric #<%= @metric.id %>
3
+ <div class="actions"><% add_action_links!(@metric) %></div>
4
+ <% end %>
5
+
6
+ <table id="rails_customerbeats_table" class="show">
7
+ <tr>
8
+ <th>Key</th>
9
+ <th>Value</th>
10
+ </tr>
11
+
12
+ <tr class="odd">
13
+ <td>Name</td>
14
+ <td><%= link_to_set_by_scope @metric, :name %></td>
15
+ </tr>
16
+
17
+ <tr class="even">
18
+ <td>Request</td>
19
+ <td><%= link_to @metric.request_id, chart_rails_customerbeat_path(@metric.request_id) %></td>
20
+ </tr>
21
+
22
+ <tr class="odd">
23
+ <td>Duration</td>
24
+ <td><%= @metric.duration_in_ms %> ms</td>
25
+ </tr>
26
+
27
+ <tr class="even">
28
+ <td>Payload</td>
29
+ <td class="payload"><%= payload_inspect(@metric.payload) %></td>
30
+ </tr>
31
+
32
+ <tr class="odd">
33
+ <td>Started at</td>
34
+ <td><%= @metric.started_at %></td>
35
+ </tr>
36
+
37
+ <tr class="even">
38
+ <td>Created at</td>
39
+ <td><%= @metric.created_at %></td>
40
+ </tr>
41
+ </table>
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ Rails.application.routes.draw do
2
+ resources :rails_customerbeats, :only => [:index, :show, :destroy] do
3
+ collection do
4
+ get :all
5
+ delete :destroy_all
6
+ end
7
+
8
+ get :chart, :on => :member
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ class RailsCustomerbeatsGenerator < Rails::Generators::NamedBase
2
+ class_option :migration, :type => :boolean, :default => true
3
+
4
+ class_option :update, :type => :boolean, :default => false,
5
+ :desc => "Just update public files, do not create a model"
6
+
7
+ def self.source_root
8
+ @_metrics_source_root ||= File.dirname(__FILE__)
9
+ end
10
+
11
+ def copy_public_files
12
+ directory "../../public", "public", :recursive => true
13
+ exit(0) if options.update?
14
+ end
15
+
16
+ def invoke_model
17
+ require "rails_customerbeats/orm/#{Rails::Generators.options[:rails][:orm]}"
18
+ invoke "model", [name].concat(RailsCustomerbeats::ORM.metric_model_properties),
19
+ :timestamps => false, :test_framework => false, :migration => options.migration?
20
+ end
21
+
22
+ def add_model_config
23
+ RailsCustomerbeats::ORM.add_metric_model_config(self, file_name, class_name)
24
+ end
25
+
26
+ def add_application_config
27
+ inject_into_class "config/application.rb", "Application", <<-CONTENT
28
+ # Set rails metrics store
29
+ config.rails_customerbeats.set_store = lambda { ::#{class_name} }
30
+
31
+ CONTENT
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ require 'thread'
2
+
3
+ module RailsCustomerbeats
4
+ # An instrumenter that does not send notifications. This is used in the
5
+ # AsyncQueue so saving events does not send any notifications, not even
6
+ # for logging.
7
+ class VoidInstrumenter < ::ActiveSupport::Notifications::Instrumenter
8
+ def instrument(name, payload={})
9
+ yield(payload) if block_given?
10
+ end
11
+ end
12
+
13
+ class AsyncConsumer
14
+ attr_reader :thread
15
+
16
+ def initialize(queue=Queue.new, &block)
17
+ @off = true
18
+ @block = block
19
+ @queue = queue
20
+ @mutex = Mutex.new
21
+
22
+ @thread = Thread.new do
23
+ set_void_instrumenter
24
+ consume
25
+ end
26
+ end
27
+
28
+ def push(*args)
29
+ @mutex.synchronize { @off = false }
30
+ @queue.push(*args)
31
+ end
32
+
33
+ def finished?
34
+ @off
35
+ end
36
+
37
+ protected
38
+
39
+ def set_void_instrumenter #:nodoc:
40
+ Thread.current[:"instrumentation_#{notifier.object_id}"] = VoidInstrumenter.new(notifier)
41
+ end
42
+
43
+ def notifier #:nodoc:
44
+ ActiveSupport::Notifications.notifier
45
+ end
46
+
47
+ def consume #:nodoc:
48
+ while args = @queue.shift
49
+ @block.call(args)
50
+ @mutex.synchronize { @off = @queue.empty? }
51
+ end
52
+ end
53
+ end
54
+ end