system-metrics 0.1.0

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 (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