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/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ Rails.application.routes.draw do
2
+ scope '/system', :module => 'system_metrics' do
3
+ get 'metrics/admin' => 'metrics#admin', :as => 'system_metrics_admin'
4
+ get 'metrics/category/:category' => 'metrics#category', :as => 'system_metrics_category'
5
+ resources :metrics, :only => [:index, :show, :destroy]
6
+ end
7
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'system_metrics'
@@ -0,0 +1,9 @@
1
+ module Auditor
2
+ module Generators
3
+ class Base < Rails::Generators::NamedBase
4
+ def self.source_root
5
+ File.expand_path(File.join(File.dirname(__FILE__), 'system_metrics', generator_name, 'templates'))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'rails/generators'
2
+
3
+ module SystemMetrics
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+
7
+ desc "Install System Metrics public assets"
8
+
9
+ source_root File.expand_path("../../../../../public", __FILE__)
10
+
11
+ def copy_css_files
12
+ directory "stylesheets", "public/stylesheets/system_metrics", :recursive => true
13
+ end
14
+
15
+ def copy_image_files
16
+ directory "images", "public/images/system_metrics", :recursive => true
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module SystemMetrics
5
+ module Generators
6
+ class MigrationGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ desc "Create migration for System Metrics metrics table"
10
+
11
+ source_root File.expand_path("../templates", __FILE__)
12
+
13
+ def self.next_migration_number(dirname)
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_migration_file
22
+ migration_template 'migration.rb', 'db/migrate/create_metrics_table.rb'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ class CreateMetricsTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :system_metrics, :force => true do |t|
4
+ t.column :name, :string, :null => false
5
+ t.column :started_at, :datetime, :null => false
6
+ t.column :transaction_id, :string
7
+ t.column :payload, :text
8
+ t.column :duration, :float, :null => false
9
+ t.column :exclusive_duration, :float, :null => false
10
+ t.column :request_id, :integer
11
+ t.column :parent_id, :integer
12
+ t.column :action, :string, :null => false
13
+ t.column :category, :string, :null => false
14
+ end
15
+
16
+ # TODO: Add indexes
17
+ end
18
+
19
+ def self.down
20
+ drop_table :system_metrics
21
+ end
22
+ end
@@ -0,0 +1 @@
1
+ require 'system_metrics'
@@ -0,0 +1,33 @@
1
+ module SystemMetrics
2
+ autoload :Collector, 'system_metrics/collector'
3
+ autoload :Config, 'system_metrics/config'
4
+ autoload :Middleware, 'system_metrics/middleware'
5
+ autoload :NestedEvent, 'system_metrics/nested_event'
6
+ autoload :Store, 'system_metrics/store'
7
+ autoload :Version, 'system_metrics/version'
8
+
9
+ def self.collection_on
10
+ Thread.current[:system_metrics_collecting] = true
11
+ end
12
+
13
+ def self.collection_off
14
+ Thread.current[:system_metrics_collecting] = false
15
+ end
16
+
17
+ def collecting?
18
+ Thread.current[:system_metrics_collecting] || false
19
+ end
20
+
21
+ def without_collection
22
+ previously_collecting = collecting?
23
+ SystemMetrics.collection_off
24
+ yield if block_given?
25
+ ensure
26
+ SystemMetrics.collection_on if previously_collecting
27
+ end
28
+
29
+ module_function :collecting?, :without_collection
30
+ end
31
+
32
+ require 'system_metrics/instrument'
33
+ require 'system_metrics/engine'
@@ -0,0 +1,32 @@
1
+ module SystemMetrics
2
+ class Collector
3
+ attr_reader :store
4
+
5
+ def initialize(store)
6
+ @store = store
7
+ end
8
+
9
+ def collect_event(event)
10
+ events.push event if SystemMetrics.collecting?
11
+ end
12
+
13
+ def collect
14
+ events.clear
15
+ SystemMetrics.collection_on
16
+ result = yield
17
+ SystemMetrics.collection_off
18
+ store.save events.dup
19
+ result
20
+ ensure
21
+ SystemMetrics.collection_off
22
+ events.clear
23
+ end
24
+
25
+ private
26
+
27
+ def events
28
+ Thread.current[:system_metrics_events] ||= []
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ module SystemMetrics
2
+ class Config
3
+ attr_accessor :store, :instruments, :notification_exclude_patterns, :path_exclude_patterns
4
+
5
+ def initialize
6
+ self.store = SystemMetrics::Store.new
7
+ self.notification_exclude_patterns = []
8
+ self.path_exclude_patterns = [/system\/metrics/, /system_metrics/]
9
+ self.instruments = [
10
+ SystemMetrics::Instrument::ActionController.new,
11
+ SystemMetrics::Instrument::ActionView.new,
12
+ SystemMetrics::Instrument::ActiveRecord.new,
13
+ SystemMetrics::Instrument::Rack.new
14
+ ]
15
+ end
16
+
17
+ def valid?
18
+ !invalid?
19
+ end
20
+
21
+ def invalid?
22
+ store.nil? ||
23
+ instruments.nil? ||
24
+ notification_exclude_patterns.nil? ||
25
+ path_exclude_patterns.nil?
26
+ end
27
+
28
+ def errors
29
+ return nil if valid?
30
+ errors = []
31
+ errors << 'store cannot be nil' if store.nil?
32
+ errors << 'instruments cannot be nil' if instruments.nil?
33
+ errors << 'notification_exclude_patterns cannot be nil' if notification_exclude_patterns.nil?
34
+ errors << 'path_exclude_patterns cannot be nil' if path_exclude_patterns.nil?
35
+ errors.join("\n")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ module SystemMetrics
2
+ class Engine < ::Rails::Engine
3
+
4
+ attr_accessor :collector, :smc
5
+
6
+ config.system_metrics = SystemMetrics::Config.new
7
+
8
+ initializer "system_metrics.initialize" do |app|
9
+ self.smc = app.config.system_metrics
10
+ raise ArgumentError.new(smc.errors) if smc.invalid?
11
+ self.collector = SystemMetrics::Collector.new(smc.store)
12
+ end
13
+
14
+ initializer "system_metrics.start_subscriber" do |app|
15
+ ActiveSupport::Notifications.subscribe /^[^!]/ do |*args|
16
+ unless smc.notification_exclude_patterns.any? { |pattern| pattern =~ name }
17
+ process_event SystemMetrics::NestedEvent.new(*args)
18
+ end
19
+ end
20
+ end
21
+
22
+ initializer "system_metrics.add_middleware" do |app|
23
+ app.config.middleware.use SystemMetrics::Middleware, collector, smc.path_exclude_patterns
24
+ end
25
+
26
+ private
27
+
28
+ def process_event(event)
29
+ instrument = smc.instruments.find { |instrument| instrument.handles?(event) }
30
+
31
+ if instrument.present?
32
+ unless instrument.ignore?(event)
33
+ instrument.prepare(event)
34
+ collector.collect_event(event)
35
+ end
36
+ else
37
+ collector.collect_event(event)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,10 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ autoload :Base, 'system_metrics/instrument/base'
4
+ autoload :ActionController, 'system_metrics/instrument/action_controller'
5
+ autoload :ActionMailer, 'system_metrics/instrument/action_mailer'
6
+ autoload :ActionView, 'system_metrics/instrument/action_view'
7
+ autoload :ActiveRecord, 'system_metrics/instrument/active_record'
8
+ autoload :Rack, 'system_metrics/instrument/rack'
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ class ActionController < SystemMetrics::Instrument::Base
4
+
5
+ def initialize
6
+ super /\.action_controller$/
7
+ end
8
+
9
+ def ignore?(event)
10
+ event.name != 'process_action.action_controller'
11
+ end
12
+
13
+ def prepare(event)
14
+ event.payload[:end_point] = "#{event.payload.delete(:controller)}##{event.payload.delete(:action)}"
15
+ event.payload.slice!(:path, :method, :params, :db_runtime, :view_runtime, :end_point)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ class ActionMailer < SystemMetrics::Instrument::Base
4
+
5
+ def initialize
6
+ super /\.action_mailer$/
7
+ end
8
+
9
+ def prepare(event)
10
+ event.payload.except!(:mail)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ class ActionView < SystemMetrics::Instrument::Base
4
+
5
+ def initialize
6
+ super /\.action_view$/
7
+ end
8
+
9
+ def prepare(event)
10
+ event.payload.each do |key, value|
11
+ case value
12
+ when NilClass
13
+ when String
14
+ event.payload[key] = prune_path(value)
15
+ else
16
+ event.payload[key] = value
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ class ActiveRecord < SystemMetrics::Instrument::Base
4
+
5
+ def initialize
6
+ super /\.active_record$/
7
+ end
8
+
9
+ def ignore?(event)
10
+ event.payload[:sql] !~ /^(SELECT|INSERT|UPDATE|DELETE)/
11
+ end
12
+
13
+ def prepare(event)
14
+ event.payload[:sql] = event.payload[:sql].squeeze(" ")
15
+ event.payload.delete(:connection_id)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,77 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+
4
+ # Base class for System Metric instruments. The default implementations
5
+ # for the methods in this class are all based on a regular expression
6
+ # that is matched against a pattern. Custom intruments that simply need
7
+ # to match against a notfication name can easily extend this class like:
8
+ #
9
+ # class SearchInstrument < SystemMetrics::Instrument::Base
10
+ # def initialize
11
+ # super /search$/
12
+ # end
13
+ # end
14
+ class Base
15
+
16
+ attr_reader :pattern
17
+
18
+ # Create an instrument that will match notification names on the given
19
+ # pattern.
20
+ def initialize(pattern)
21
+ @pattern = pattern
22
+ end
23
+
24
+ # Holds the mapped paths used in prunning.
25
+ def mapped_paths
26
+ @mapped_paths ||= default_mapped_paths
27
+ end
28
+
29
+ # Prune paths based on the mapped paths set.
30
+ def prune_path(raw_path)
31
+ mapped_paths.each do |path, replacement|
32
+ next unless path.present?
33
+ raw_path = raw_path.gsub(path, replacement)
34
+ end
35
+ raw_path
36
+ end
37
+
38
+ # Declares whether this instrument handles the given event type.
39
+ #
40
+ # Please Note: Even if the instrument would ultimately like to
41
+ # ignore the event, it should still return true if it generally
42
+ # handles events like the one passed.
43
+ def handles?(event)
44
+ event.name =~ pattern
45
+ end
46
+
47
+ # Indicates whether the given event should be completely ingored
48
+ # and not collected. This is called only if #handles?(event)
49
+ # returns `true`
50
+ def ignore?(event)
51
+ false
52
+ end
53
+
54
+ # Provides an opportunity to modify the event before it's collected
55
+ # and stored. This is where you would normally modify the payload
56
+ # to add, remove, or format its elements.
57
+ def prepare(event)
58
+ # Modify the payload if you care to
59
+ end
60
+
61
+ private
62
+
63
+ def default_mapped_paths
64
+ # Make Rails.root appear as RAILS_ROOT in pruned paths.
65
+ paths = { Rails.root.to_s => 'RAILS_ROOT' }
66
+
67
+ # Make Gem paths appear as GEMS_ROOT in pruned paths.
68
+ Gem.path.each do |path|
69
+ paths[File.join(path, "gems")] = "GEMS_ROOT"
70
+ end if defined?(Gem)
71
+
72
+ paths
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ module SystemMetrics
2
+ module Instrument
3
+ class Rack < SystemMetrics::Instrument::Base
4
+
5
+ def initialize
6
+ super /^request\.rack$/
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ module SystemMetrics
2
+ class Middleware
3
+ def initialize(app, collector, path_exclude_patterns)
4
+ @app = app
5
+ @collector = collector
6
+ @path_exclude_patterns = path_exclude_patterns
7
+ end
8
+
9
+ def call(env)
10
+ if exclude_path? env["PATH_INFO"]
11
+ @app.call(env)
12
+ else
13
+ @collector.collect do
14
+ response = notifications.instrument "request.rack",
15
+ :path => env["PATH_INFO"], :method => env["REQUEST_METHOD"] do
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def exclude_path?(path)
25
+ @path_exclude_patterns.any? { |pattern| pattern =~ path }
26
+ end
27
+
28
+ def notifications
29
+ ActiveSupport::Notifications
30
+ end
31
+ end
32
+ end