system-metrics 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +106 -0
- data/app/controllers/system_metrics/metrics_controller.rb +49 -0
- data/app/helpers/system_metrics/metrics_helper.rb +36 -0
- data/app/models/system_metrics/metric.rb +36 -0
- data/app/views/layouts/system_metrics/metrics.html.erb +42 -0
- data/app/views/system_metrics/metrics/_menu.html.erb +8 -0
- data/app/views/system_metrics/metrics/_metric_row.html.erb +19 -0
- data/app/views/system_metrics/metrics/_metric_table.html.erb +24 -0
- data/app/views/system_metrics/metrics/_portlet.html.erb +18 -0
- data/app/views/system_metrics/metrics/_title_bar.html.erb +17 -0
- data/app/views/system_metrics/metrics/admin.html.erb +27 -0
- data/app/views/system_metrics/metrics/category.html.erb +4 -0
- data/app/views/system_metrics/metrics/index.html.erb +6 -0
- data/app/views/system_metrics/metrics/show.html.erb +26 -0
- data/config/routes.rb +7 -0
- data/init.rb +1 -0
- data/lib/generators/system_metrics.rb +9 -0
- data/lib/generators/system_metrics/install/install_generator.rb +21 -0
- data/lib/generators/system_metrics/migration/migration_generator.rb +26 -0
- data/lib/generators/system_metrics/migration/templates/migration.rb +22 -0
- data/lib/system-metrics.rb +1 -0
- data/lib/system_metrics.rb +33 -0
- data/lib/system_metrics/collector.rb +32 -0
- data/lib/system_metrics/config.rb +38 -0
- data/lib/system_metrics/engine.rb +43 -0
- data/lib/system_metrics/instrument.rb +10 -0
- data/lib/system_metrics/instrument/action_controller.rb +20 -0
- data/lib/system_metrics/instrument/action_mailer.rb +15 -0
- data/lib/system_metrics/instrument/action_view.rb +23 -0
- data/lib/system_metrics/instrument/active_record.rb +20 -0
- data/lib/system_metrics/instrument/base.rb +77 -0
- data/lib/system_metrics/instrument/rack.rb +11 -0
- data/lib/system_metrics/middleware.rb +32 -0
- data/lib/system_metrics/nested_event.rb +57 -0
- data/lib/system_metrics/store.rb +31 -0
- data/lib/system_metrics/version.rb +3 -0
- data/public/images/rings_13.png +0 -0
- data/public/stylesheets/app.css +13 -0
- data/public/stylesheets/base.css +46 -0
- data/public/stylesheets/footer.css +6 -0
- data/public/stylesheets/graphs.css +9 -0
- data/public/stylesheets/header.css +52 -0
- data/public/stylesheets/ie.css +36 -0
- data/public/stylesheets/menu_bar.css +7 -0
- data/public/stylesheets/metric.css +19 -0
- data/public/stylesheets/payload.css +4 -0
- data/public/stylesheets/portlet.css +11 -0
- data/public/stylesheets/print.css +29 -0
- data/public/stylesheets/reset.css +65 -0
- data/public/stylesheets/title_bar.css +29 -0
- data/public/stylesheets/typography.css +123 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/db_setup.rb +41 -0
- data/spec/support/mock_app.rb +23 -0
- data/spec/support/notifications_support.rb +12 -0
- data/spec/support/test_logger.rb +11 -0
- data/spec/support/test_store.rb +11 -0
- data/spec/support/transactional_specs.rb +17 -0
- data/spec/system_metrics/collector_spec.rb +60 -0
- data/spec/system_metrics/config_spec.rb +24 -0
- data/spec/system_metrics/engine_spec.rb +50 -0
- data/spec/system_metrics/middleware_spec.rb +45 -0
- data/spec/system_metrics_spec.rb +29 -0
- data/system-metrics.gemspec +24 -0
- 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,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,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,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
|