system-metrics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +106 -0
  3. data/app/controllers/system_metrics/metrics_controller.rb +49 -0
  4. data/app/helpers/system_metrics/metrics_helper.rb +36 -0
  5. data/app/models/system_metrics/metric.rb +36 -0
  6. data/app/views/layouts/system_metrics/metrics.html.erb +42 -0
  7. data/app/views/system_metrics/metrics/_menu.html.erb +8 -0
  8. data/app/views/system_metrics/metrics/_metric_row.html.erb +19 -0
  9. data/app/views/system_metrics/metrics/_metric_table.html.erb +24 -0
  10. data/app/views/system_metrics/metrics/_portlet.html.erb +18 -0
  11. data/app/views/system_metrics/metrics/_title_bar.html.erb +17 -0
  12. data/app/views/system_metrics/metrics/admin.html.erb +27 -0
  13. data/app/views/system_metrics/metrics/category.html.erb +4 -0
  14. data/app/views/system_metrics/metrics/index.html.erb +6 -0
  15. data/app/views/system_metrics/metrics/show.html.erb +26 -0
  16. data/config/routes.rb +7 -0
  17. data/init.rb +1 -0
  18. data/lib/generators/system_metrics.rb +9 -0
  19. data/lib/generators/system_metrics/install/install_generator.rb +21 -0
  20. data/lib/generators/system_metrics/migration/migration_generator.rb +26 -0
  21. data/lib/generators/system_metrics/migration/templates/migration.rb +22 -0
  22. data/lib/system-metrics.rb +1 -0
  23. data/lib/system_metrics.rb +33 -0
  24. data/lib/system_metrics/collector.rb +32 -0
  25. data/lib/system_metrics/config.rb +38 -0
  26. data/lib/system_metrics/engine.rb +43 -0
  27. data/lib/system_metrics/instrument.rb +10 -0
  28. data/lib/system_metrics/instrument/action_controller.rb +20 -0
  29. data/lib/system_metrics/instrument/action_mailer.rb +15 -0
  30. data/lib/system_metrics/instrument/action_view.rb +23 -0
  31. data/lib/system_metrics/instrument/active_record.rb +20 -0
  32. data/lib/system_metrics/instrument/base.rb +77 -0
  33. data/lib/system_metrics/instrument/rack.rb +11 -0
  34. data/lib/system_metrics/middleware.rb +32 -0
  35. data/lib/system_metrics/nested_event.rb +57 -0
  36. data/lib/system_metrics/store.rb +31 -0
  37. data/lib/system_metrics/version.rb +3 -0
  38. data/public/images/rings_13.png +0 -0
  39. data/public/stylesheets/app.css +13 -0
  40. data/public/stylesheets/base.css +46 -0
  41. data/public/stylesheets/footer.css +6 -0
  42. data/public/stylesheets/graphs.css +9 -0
  43. data/public/stylesheets/header.css +52 -0
  44. data/public/stylesheets/ie.css +36 -0
  45. data/public/stylesheets/menu_bar.css +7 -0
  46. data/public/stylesheets/metric.css +19 -0
  47. data/public/stylesheets/payload.css +4 -0
  48. data/public/stylesheets/portlet.css +11 -0
  49. data/public/stylesheets/print.css +29 -0
  50. data/public/stylesheets/reset.css +65 -0
  51. data/public/stylesheets/title_bar.css +29 -0
  52. data/public/stylesheets/typography.css +123 -0
  53. data/spec/spec_helper.rb +10 -0
  54. data/spec/support/db_setup.rb +41 -0
  55. data/spec/support/mock_app.rb +23 -0
  56. data/spec/support/notifications_support.rb +12 -0
  57. data/spec/support/test_logger.rb +11 -0
  58. data/spec/support/test_store.rb +11 -0
  59. data/spec/support/transactional_specs.rb +17 -0
  60. data/spec/system_metrics/collector_spec.rb +60 -0
  61. data/spec/system_metrics/config_spec.rb +24 -0
  62. data/spec/system_metrics/engine_spec.rb +50 -0
  63. data/spec/system_metrics/middleware_spec.rb +45 -0
  64. data/spec/system_metrics_spec.rb +29 -0
  65. data/system-metrics.gemspec +24 -0
  66. metadata +163 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Near Infinity Corporation
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,106 @@
1
+ = System Metrics
2
+
3
+ System Metrics is a Rails 3 Engine that provides a clean web interface to the performance metrics instrumented with `ActiveSupport::Notifications`. It'll collect and display the notifications baked into Rails and any additional custom ones you add using `ActiveSupport::Notification#instrument`.
4
+
5
+ = Installation
6
+
7
+ To install the System Metrics gem in your Rails 3 app, add the following line to your Gemfile
8
+
9
+ gem 'system-metrics'
10
+
11
+ You'll then need to move the System Metrics migration into your project and run it.
12
+
13
+ rails generate system_metrics:migration
14
+ rake db:migrate
15
+
16
+ Lastly, the public assets from the System Metrics gem need to be moved into your app
17
+
18
+ rails generate system_metrics:install
19
+
20
+ = Setup
21
+
22
+ Out of the box, System Metrics collects the more interesting performance notifications built into Rails with no configuration. However, there are a few options available to fine tune it and add your own instrumentations.
23
+
24
+ == Path Exclusion Patterns
25
+
26
+ You can append patterns to the `path_exclude_patterns` setting if there are URLs you don't care to collect metrics about.
27
+
28
+ # config/application.rb
29
+ config.system_metrics.path_exclude_patterns << /^\/admin/
30
+
31
+ Adding the `/^\/admin/` exclusion pattern will ensure that no metrics are collected for any path beginning with `/admin`.
32
+
33
+ == Notification Exclusion Patterns
34
+
35
+ You can append patterns to the `notification_exclude_patterns` setting if you notice metrics in the System Metrics interface that you don't care about. The patterns are matched against the `ActiveSupport::Notifications::Event#name`.
36
+
37
+ # config/application.rb
38
+ config.system_metrics.notification_exclude_patterns << /annoying$/
39
+
40
+ Adding the `/annoying$/` exclusion pattern will prevent notifications whose name ends with `annoying` from being collected by System Metrics.
41
+
42
+ == Instruments
43
+
44
+ Instruments are responsible for the decision to collect a notification as a metric and processing it before storage. By default, system metrics add instruments for ActionController, ActionMailer, ActionView, ActiveRecord, and a high level Rack request. However, you can easily add additional instruments to the configuration.
45
+
46
+ # config/application.rb
47
+ config.system_metrics.instruments << MyCustomInstrument.new
48
+
49
+ = Custom Instruments
50
+
51
+ By default, System Metrics will collect all notifications it has not been specifically configured not to collect. Therefore, simply adding ActiveSupport::Notifications#instrument calls to your code is normally good enough. System Metrics will just start collecting these new notifications. However, if you'd like to modify the notification event payloads or be more selective about which types of notifications get collected, you'll want to write a custom instrument.
52
+
53
+ Instrument implementations require three methods, #handles?(event), #ignore?(event), and #process(event). If your needs are fairly simple, you may be able to extend SystemMetrics::Instrument::Base. Check its RDoc for details.
54
+
55
+ Below is a custom instrument for timing Sunspot searches:
56
+
57
+ class SunspotInstrument
58
+ def handles?(event)
59
+ event.name =~ /sunspot$/
60
+ end
61
+
62
+ def ignore?(event)
63
+ User.current.admin?
64
+ end
65
+
66
+ def process(event)
67
+ event.payload[:user] = User.current.name
68
+ end
69
+ end
70
+
71
+ This example instrument illustrates three concepts
72
+
73
+ (1) It will handle any event whose name ends with sunspot.
74
+ (2) It will inform SystemMetrics that the event should not be collected if the current user is an administrator.
75
+ (3) It will add the current user's name to the event payload
76
+
77
+ = Authorization
78
+
79
+ There's no authorization built into SystemMetrics, although it should be rather straightforward to add your own. Consider the following example implementation:
80
+
81
+ # config/initializers/system_metrics_authorization.rb
82
+ module SystemMetricsAuthorization
83
+ def self.included(base)
84
+ base.send(:before_filter, :authorize)
85
+ end
86
+
87
+ def authorize
88
+ # Do you authorization thing
89
+ end
90
+ end
91
+
92
+ SystemMetrics::MetricsController.send(:include, SystemMetricsAuthorization)
93
+
94
+ = Caveats
95
+
96
+ I would not currently recommend using SystemMetrics in a production environment. There are far too many synchronous database inserts of the collected metrics to confidently say that it wouldn't impact your application's performance.
97
+
98
+ = Credits
99
+
100
+ The idea behind System Metrics and the inspiration for a good portion of its code comes from José Valim's Rails Metrics project. The inspiration for the user interface comes from Greg Bell's ActiveAdmin project.
101
+
102
+ = License
103
+
104
+ System Metrics is released under the MIT license.
105
+
106
+ Copyright (c) 2011 Near Infinity. http://www.nearinfinity.com
@@ -0,0 +1,49 @@
1
+ module SystemMetrics
2
+ class MetricsController < ActionController::Base
3
+ def index
4
+ @category_metrics = {}
5
+ categories = SystemMetrics::Metric.select('DISTINCT(category)').order('category ASC').map(&:category)
6
+ categories.each do |category|
7
+ @category_metrics[category] = SystemMetrics::Metric.where(:category => category, :started_at => date_range).order('duration DESC').limit(limit(10))
8
+ end
9
+ end
10
+
11
+ def show
12
+ @metric = SystemMetrics::Metric.find(params[:id])
13
+ end
14
+
15
+ def destroy
16
+ category = params[:id]
17
+ if category == 'all'
18
+ SystemMetrics::Metric.delete_all
19
+ else
20
+ SystemMetrics::Metric.where(:category => category).delete_all
21
+ end
22
+
23
+ redirect_to system_metrics_admin_path
24
+ end
25
+
26
+ def category
27
+ @metrics = SystemMetrics::Metric.where(:category => params[:category], :started_at => date_range).order('duration DESC').limit(limit)
28
+ end
29
+
30
+ def admin
31
+ @categories = SystemMetrics::Metric.select('category, count(category) as count').order('category ASC').group('category')
32
+ end
33
+
34
+ private
35
+ def date_range
36
+ from = params[:from] || '30.minutes'
37
+ from_num, from_unit = from.split('.')
38
+ @from_date = from_num.to_i.send(from_unit.to_sym).ago
39
+ to = params[:to] || '0.minutes'
40
+ to_num, to_unit = to.split('.')
41
+ @to_date = to_num.to_i.send(to_unit.to_sym).ago
42
+ @from_date..@to_date
43
+ end
44
+
45
+ def limit(default=100)
46
+ @limit = params[:limit] || default
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module SystemMetrics
2
+ module MetricsHelper
3
+ IDENTIFIER_ATTRS = [:end_point, :layout, :identifier, :path, :name]
4
+
5
+ def set_title(title)
6
+ content_for(:title, title.to_s)
7
+ end
8
+
9
+ def title
10
+ content_for?(:title) ? content_for(:title) : 'System Metrics'
11
+ end
12
+
13
+ def set_page_title(page_title)
14
+ content_for(:page_title, page_title.to_s)
15
+ end
16
+
17
+ def page_title
18
+ content_for?(:page_title) ? content_for(:page_title) : ''
19
+ end
20
+
21
+ def identifier(metric)
22
+ attr = IDENTIFIER_ATTRS.find { |attr| metric.payload.include?(attr) && metric.payload[attr] != nil }
23
+ attr.present? ? metric.payload[attr] : metric.name
24
+ end
25
+
26
+ def slow_threshold(metric)
27
+ case metric.name
28
+ when 'request.rack' then 500.0
29
+ when 'process_action.action_controller' then 450.0
30
+ when 'sql.active_record' then 150.0
31
+ when /render_[^\.]+\.action_view/ then 250.0
32
+ else 200.0
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module SystemMetrics
2
+ class Metric < ActiveRecord::Base
3
+
4
+ set_table_name 'system_metrics'
5
+ has_many :children, :class_name => self.name, :foreign_key => :parent_id
6
+ belongs_to :parent, :class_name => self.name
7
+ serialize :payload
8
+
9
+ def ancestors
10
+ ancestors = []
11
+ metric = self
12
+ while parent = metric.parent
13
+ ancestors << parent
14
+ metric = parent
15
+ end
16
+ ancestors
17
+ end
18
+
19
+ # Returns if the current node is the parent of the given node.
20
+ # If this is a new record, we can use started_at values to detect parenting.
21
+ # However, if it was already saved, we lose microseconds information from
22
+ # timestamps and we must rely solely in id and parent_id information.
23
+ def parent_of?(metric)
24
+ if new_record?
25
+ start = (started_at - metric.started_at) * 1000.0
26
+ start <= 0 && (start + duration >= metric.duration)
27
+ else
28
+ self.id == metric.parent_id
29
+ end
30
+ end
31
+
32
+ def child_of?(metric)
33
+ metric.parent_of?(self)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
5
+ <title><%= title %></title>
6
+ <%= stylesheet_link_tag 'system_metrics/reset',
7
+ 'system_metrics/typography',
8
+ :media => 'screen, projection' %>
9
+ <%= stylesheet_link_tag 'system_metrics/print', :media => 'print' %>
10
+ <!--[if lt IE 8]>
11
+ <%= stylesheet_link_tag 'system_metrics/ie', :media => 'screen, projection' %>
12
+ <![endif]-->
13
+ <%= stylesheet_link_tag 'system_metrics/base',
14
+ 'system_metrics/app',
15
+ 'system_metrics/header',
16
+ 'system_metrics/footer',
17
+ 'system_metrics/title_bar',
18
+ 'system_metrics/portlet',
19
+ 'system_metrics/payload',
20
+ 'system_metrics/metric',
21
+ 'system_metrics/menu_bar',
22
+ 'system_metrics/graphs',
23
+ :media => 'screen, projection', :cache => 'system_metrics' %>
24
+ <%= csrf_meta_tag %>
25
+ </head>
26
+ <body>
27
+ <div class="container">
28
+ <div id="header">
29
+ <%= image_tag 'system_metrics/rings_13.png' %>
30
+ <h1>System Metrics</h1>
31
+ <%= render "menu" %>
32
+ </div>
33
+ <%= render "title_bar" %>
34
+ <div id="content">
35
+ <%= yield %>
36
+ </div>
37
+ <div id="footer">
38
+ <span>Developed by <a href="http://www.nearinfinity.com">Near Infinity</a></span>
39
+ </div>
40
+ </div>
41
+ </body>
42
+ </html>
@@ -0,0 +1,8 @@
1
+ <ul id="tabs">
2
+ <li><%= link_to 'Dashboard', metrics_path %></li>
3
+ <% for metric in SystemMetrics::Metric.select('DISTINCT(category)').order('category ASC') -%>
4
+ <li><%= link_to metric.category.titleize, system_metrics_category_path(metric.category) %></li>
5
+ <% end -%>
6
+ <li>|</li>
7
+ <li><%= link_to 'Administration', system_metrics_admin_path %></li>
8
+ </ul>
@@ -0,0 +1,19 @@
1
+ <tr>
2
+ <td><%= link_to metric.id, metric_path(metric) %></td>
3
+ <% if columns.include?(:name) %><td><%= ('&rarr; ' * indent).html_safe %><%= metric.name %></td><% end %>
4
+ <% if columns.include?(:identifier) %><td><%= identifier(metric) %></td><% end %>
5
+ <% if columns.include?(:action) %><td><%= metric.action %></td><% end %>
6
+ <% if columns.include?(:when) %><td><%= distance_of_time_in_words_to_now metric.started_at %> ago</td><% end %>
7
+ <% if columns.include?(:duration) %><td><span class="hbar<%= metric.duration > slow_threshold(metric) ? ' slow' : '' %>" style="width: <%= metric.duration / longest_duration * 100 %>%"></span><%= metric.duration %></td><% end %>
8
+ </tr>
9
+ <% if include_children -%>
10
+ <% for child_metric in metric.children -%>
11
+ <%= render :partial => 'metric_row',:locals => {
12
+ :indent => indent + 1,
13
+ :columns => columns,
14
+ :metric => child_metric,
15
+ :longest_duration => longest_duration,
16
+ :include_children => include_children
17
+ } %>
18
+ <% end -%>
19
+ <% end -%>
@@ -0,0 +1,24 @@
1
+ <% longest_duration = metrics.first.try(:duration) -%>
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <th>ID</th>
6
+ <% if columns.include?(:name) %><th>Name</th><% end %>
7
+ <% if columns.include?(:identifier) %><th>Identifier</th><% end %>
8
+ <% if columns.include?(:action) %><th>Action</th><% end %>
9
+ <% if columns.include?(:when) %><th>When</th><% end %>
10
+ <% if columns.include?(:duration) %><th>Duration (ms)</th><% end %>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% for metric in metrics -%>
15
+ <%= render :partial => 'metric_row', :locals => {
16
+ :indent => 0,
17
+ :columns => columns,
18
+ :metric => metric,
19
+ :longest_duration => longest_duration,
20
+ :include_children => include_children
21
+ } %>
22
+ <% end -%>
23
+ </tbody>
24
+ </table>
@@ -0,0 +1,18 @@
1
+ <div class="portlet">
2
+ <h2><%= category.titleize %></h2>
3
+ <table>
4
+ <thead>
5
+ <tr><th>ID</th><th>Action</th><th>Identifier</th><th>Duration</th></tr>
6
+ </thead>
7
+ <tbody>
8
+ <% for metric in metrics -%>
9
+ <tr>
10
+ <td><%= link_to metric.id, metric_path(metric) %></td>
11
+ <td><%= metric.action %></td>
12
+ <td><%= identifier(metric) %></td>
13
+ <td><%= metric.duration %></td>
14
+ </tr>
15
+ <% end -%>
16
+ </tbody>
17
+ </table>
18
+ </div>
@@ -0,0 +1,17 @@
1
+ <div id="title-bar" class="span-24 last">
2
+ <h2 id="page-title"><%= page_title %></h2>
3
+
4
+ <% if @from_date.present? && @to_date.present? -%>
5
+ <h4>Viewing <%= distance_of_time_in_words @from_date, @to_date %>
6
+ of metrics from <%= @from_date.strftime('%b %e, %Y at %H:%M %Z') %></h4>
7
+ <% end -%>
8
+
9
+ <% if @metric.present? -%>
10
+ <h4>
11
+ <% for parent in @metric.ancestors.reverse -%>
12
+ <%= link_to parent.name, metric_path(parent) %> &gt;
13
+ <% end -%>
14
+ <%= @metric.name %>
15
+ </h4>
16
+ <% end -%>
17
+ </div>
@@ -0,0 +1,27 @@
1
+ <% set_title "System Metrics Administration" -%>
2
+ <% set_page_title "Administration" -%>
3
+ <div id="menu-bar">
4
+ <%= form_tag "/system/metrics/all", :method => :delete do -%>
5
+ <%= submit_tag 'Delete All' %>
6
+ <% end -%>
7
+ <h2>Metrics</h2>
8
+ </div>
9
+
10
+ <table>
11
+ <thead>
12
+ <tr><th>Category</th><th>Count</th><th>Action</th></tr>
13
+ <thead>
14
+ <tbody>
15
+ <% for category in @categories -%>
16
+ <tr>
17
+ <td><%= category.category.titleize %></td>
18
+ <td><%= category.count %></td>
19
+ <td>
20
+ <%= form_tag "/system/metrics/#{category.category}", :method => :delete do %>
21
+ <%= submit_tag 'Delete Category' %>
22
+ <% end -%>
23
+ </td>
24
+ <tr>
25
+ <% end -%>
26
+ </tbody>
27
+ </table>
@@ -0,0 +1,4 @@
1
+ <% set_title "#{params[:category].titleize} Category System Metrics" -%>
2
+ <% set_page_title params[:category].titleize -%>
3
+ <%= render :partial => "metric_table",
4
+ :locals => { :metrics => @metrics, :include_children => false, :columns => [:identifier, :action, :when, :duration] } %>
@@ -0,0 +1,6 @@
1
+ <% set_title 'System Metrics Dashboard' -%>
2
+ <% set_page_title 'Dashboard' -%>
3
+ <% @category_metrics.each do |category, metrics| -%>
4
+ <%= render :partial => "portlet", :locals => {:category => category, :metrics => metrics} %>
5
+ <% end -%>
6
+ <div class="clear"></div>
@@ -0,0 +1,26 @@
1
+ <% set_title "System Metric details for #{@metric.name}:#{@metric.id}" -%>
2
+ <% set_page_title "#{@metric.name}:#{@metric.id}" -%>
3
+
4
+ <div id="metric-details">
5
+ <h2><%= @metric.duration %> ms</h2>
6
+ <dl>
7
+ <dt>When</dt>
8
+ <dd><%= distance_of_time_in_words_to_now @metric.started_at, true %> ago</dd>
9
+ <% @metric.payload.each do |key, value| -%>
10
+ <dt><%= key.to_s.titleize %></dt>
11
+ <dd><%= value.kind_of?(String) ? value : value.inspect %></dd>
12
+ <% end -%>
13
+ </dl>
14
+ </div>
15
+
16
+ <div id="metric-children">
17
+ <h2>Child Metrics</h2>
18
+ <%= render :partial => "metric_table",
19
+ :locals => {
20
+ :metrics => @metric.children.order('duration DESC'),
21
+ :include_children => true,
22
+ :columns => [:name, :identifier, :duration]
23
+ } %>
24
+ </div>
25
+
26
+ <div class="clear"></div>