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