system-metrics 0.1.0 → 0.2.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 (39) hide show
  1. data/README.rdoc +16 -1
  2. data/app/views/layouts/system_metrics/metrics.html.erb +4 -15
  3. data/app/views/system_metrics/metrics/_menu.html.erb +1 -1
  4. data/app/views/system_metrics/metrics/admin.html.erb +1 -1
  5. data/lib/system_metrics/engine.rb +3 -3
  6. data/lib/system_metrics/instrument/base.rb +1 -1
  7. data/lib/system_metrics/nested_event.rb +8 -1
  8. data/lib/system_metrics/store.rb +10 -15
  9. data/lib/system_metrics/version.rb +1 -1
  10. data/public/images/gradient.png +0 -0
  11. data/public/stylesheets/app.css +197 -3
  12. data/spec/spec_helper.rb +2 -0
  13. data/spec/support/db_setup.rb +3 -0
  14. data/spec/support/mock_app.rb +1 -3
  15. data/spec/support/notifications_support.rb +10 -0
  16. data/spec/system_metrics/collector_spec.rb +1 -2
  17. data/spec/system_metrics/config_spec.rb +16 -8
  18. data/spec/system_metrics/engine_spec.rb +53 -28
  19. data/spec/system_metrics/instrument/action_controller_spec.rb +51 -0
  20. data/spec/system_metrics/instrument/action_mailer_spec.rb +40 -0
  21. data/spec/system_metrics/instrument/action_view_spec.rb +40 -0
  22. data/spec/system_metrics/instrument/active_record_spec.rb +50 -0
  23. data/spec/system_metrics/instrument/base_spec.rb +38 -0
  24. data/spec/system_metrics/instrument/rack_spec.rb +28 -0
  25. data/spec/system_metrics/middleware_spec.rb +1 -3
  26. data/spec/system_metrics/nested_event_spec.rb +87 -0
  27. data/spec/system_metrics/store_spec.rb +49 -0
  28. data/spec/system_metrics_spec.rb +0 -1
  29. data/system-metrics.gemspec +1 -0
  30. metadata +30 -11
  31. data/public/stylesheets/base.css +0 -46
  32. data/public/stylesheets/footer.css +0 -6
  33. data/public/stylesheets/graphs.css +0 -9
  34. data/public/stylesheets/header.css +0 -52
  35. data/public/stylesheets/menu_bar.css +0 -7
  36. data/public/stylesheets/metric.css +0 -19
  37. data/public/stylesheets/payload.css +0 -4
  38. data/public/stylesheets/portlet.css +0 -11
  39. data/public/stylesheets/title_bar.css +0 -29
@@ -1,8 +1,7 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
- require 'system_metrics/collector'
3
2
 
4
3
  describe SystemMetrics::Collector do
5
- before(:each) do
4
+ before(:each) do
6
5
  @store = TestStore.new
7
6
  @collector = SystemMetrics::Collector.new(@store)
8
7
  end
@@ -1,24 +1,32 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
- require 'system_metrics/config'
3
2
 
4
3
  describe SystemMetrics::Config do
5
- it 'should be valid with instruments, path_exclude_patterns, and a store' do
6
- config = SystemMetrics::Config.new({:instruments => [Object.new], :store => Object.new})
4
+ it 'should be valid by default' do
5
+ config = SystemMetrics::Config.new
7
6
  config.should be_valid
8
7
  end
9
8
 
10
- it 'should be invalid without instruments' do
11
- config = SystemMetrics::Config.new({:store => Object.new})
9
+ it 'should be invalid with nil instruments' do
10
+ config = SystemMetrics::Config.new
11
+ config.instruments = nil
12
12
  config.should_not be_valid
13
13
  end
14
14
 
15
15
  it 'should be invalid without a store' do
16
- config = SystemMetrics::Config.new({:instruments => [Object.new]})
16
+ config = SystemMetrics::Config.new
17
+ config.store = nil
17
18
  config.should_not be_valid
18
19
  end
19
20
 
20
- it 'should be invalid if passed an unrecognized option' do
21
- config = SystemMetrics::Config.new({:instruments => [Object.new], :path_exclude_patterns => [], :store => Object.new, :bogus => true})
21
+ it 'should be invalid with a nil path_exclude_patterns' do
22
+ config = SystemMetrics::Config.new
23
+ config.path_exclude_patterns = nil
24
+ config.should_not be_valid
25
+ end
26
+
27
+ it 'should be invalid with a nil notification_exclude_patterns' do
28
+ config = SystemMetrics::Config.new
29
+ config.notification_exclude_patterns = nil
22
30
  config.should_not be_valid
23
31
  end
24
32
  end
@@ -1,50 +1,75 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
- require 'system_metrics/engine'
3
2
 
4
3
  describe SystemMetrics::Engine do
5
4
  before(:each) do
6
5
  @app = MockApp.new
7
6
  @store = TestStore.new
8
- @app.config.system_metrics[:instruments] = [Comb::Instrument::Base.new(/xyz123/)]
9
- @app.config.system_metrics[:store] = @store
7
+ @app.config.system_metrics = SystemMetrics::Config.new
8
+ @app.config.system_metrics.instruments << SystemMetrics::Instrument::Base.new(/xyz123/)
9
+ @app.config.system_metrics.store = @store
10
+ @engine = SystemMetrics::Engine.new
11
+ run_initializers(@engine)
10
12
  end
11
13
 
12
- it 'should establish ActiveSupport::Notification subscribers for each instrument' do
13
- @app.config.system_metrics[:instruments] = [Comb::Instrument::Base.new(/^sql/), Comb::Instrument::Base.new(/^render/)]
14
- engine = SystemMetrics::Engine.new
15
- run_initializers(engine)
16
-
17
- collector = SystemMetrics::Collector.new(@store)
18
- collector.collect do
19
- ActiveSupport::Notifications.instrument 'sql.active_record'
20
- ActiveSupport::Notifications.instrument 'render.action_view'
21
- ActiveSupport::Notifications.instrument 'process.action_controller'
22
- end
23
-
24
- @store.should have(2).events
25
- @store.events.map(&:name) =~ ['sql.active_record', 'render.action_view']
14
+ it 'should initialize the SystemMetrics configuration' do
15
+ @engine.smc.should_not be_nil
16
+ @engine.smc.should be_valid
17
+ @engine.collector.should_not be_nil
26
18
  end
27
19
 
28
20
  it 'should setup the SystemMetrics::Middleware' do
29
- engine = SystemMetrics::Engine.new
30
- run_initializers(engine)
31
-
32
21
  middleware = @app.config.middleware.first
33
22
  middleware[0].should == SystemMetrics::Middleware
34
23
  middleware[1].should_not be_nil
35
24
  middleware[2].should_not be_nil
36
25
  end
37
26
 
38
- def run_initializer(engine, name)
39
- initializer = engine.initializers.find do |initializer|
40
- initializer.name == name
27
+ describe '#process_event' do
28
+ it 'should collect events that do not have an instrument that handles them' do
29
+ event = ActiveSupport::Notifications::Event.new('unknown', Time.now - 5, Time.now, 'tid', {})
30
+ @engine.collector.collect { @engine.send(:process_event, event) }
31
+ @store.events.should include(event)
41
32
  end
42
- initializer.run @app
43
- end
44
33
 
45
- def run_initializers(engine)
46
- ['system_metrics.initialize', 'system_metrics.add_subscribers', 'system_metrics.add_middleware'].each do |name|
47
- run_initializer(engine, name)
34
+ it 'should collect events that have an instrument that handles and does not ignore them' do
35
+ event = ActiveSupport::Notifications::Event.new('xyz123', Time.now - 5, Time.now, 'tid', {})
36
+ @engine.collector.collect { @engine.send(:process_event, event) }
37
+ @store.events.should include(event)
38
+ end
39
+
40
+ it 'should not collect events whose instrument indicates that it should be ignored' do
41
+ class IgnoringInstrument
42
+ def handles?(event)
43
+ true
44
+ end
45
+
46
+ def ignore?(event)
47
+ true
48
+ end
49
+ end
50
+
51
+ @app.config.system_metrics.instruments << IgnoringInstrument.new
52
+ engine = SystemMetrics::Engine.new
53
+ run_initializers(engine)
54
+ event = ActiveSupport::Notifications::Event.new('abc123', Time.now - 5, Time.now, 'tid', {})
55
+ engine.collector.collect { @engine.send(:process_event, event) }
56
+ @store.events.should_not include(event)
48
57
  end
49
58
  end
59
+
60
+ private
61
+
62
+ def run_initializer(engine, name)
63
+ initializer = engine.initializers.find do |initializer|
64
+ initializer.name == name
65
+ end
66
+ initializer.run @app
67
+ end
68
+
69
+ def run_initializers(engine)
70
+ ['system_metrics.initialize', 'system_metrics.start_subscriber', 'system_metrics.add_middleware'].each do |name|
71
+ run_initializer(engine, name)
72
+ end
73
+ end
74
+
50
75
  end
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::ActionController do
4
+ include NotificationsSupport
5
+
6
+ before(:each) do
7
+ @instrument = SystemMetrics::Instrument::ActionController.new
8
+ end
9
+
10
+ describe '#handle?' do
11
+ it 'should handle any event whose name ends with action_controller' do
12
+ @instrument.handles?(event(:name => 'process_action.action_controller')).should be_true
13
+ @instrument.handles?(event(:name => 'do_something.action_controller')).should be_true
14
+ end
15
+
16
+ it 'should not handle an event whose name does not end with action_controller' do
17
+ @instrument.handles?(event(:name => 'do_something.else')).should be_false
18
+ @instrument.handles?(event(:name => 'action_controller.process_action')).should be_false
19
+ end
20
+ end
21
+
22
+ describe '#ignore?' do
23
+ it 'should ignore all events unless the name is process_action.action_controller' do
24
+ @instrument.ignore?(event(:name => 'process_action.action_controller')).should be_false
25
+ @instrument.ignore?(event(:name => 'start_processing.action_controller')).should be_true
26
+ end
27
+ end
28
+
29
+ describe '#prepare' do
30
+ it 'should add an endpoint entry to the payload' do
31
+ e = event(:payload => { :controller => 'User', :action => 'create' })
32
+ @instrument.prepare(e)
33
+ e.payload.should include(:end_point)
34
+ e.payload[:end_point].should == 'User#create'
35
+ end
36
+
37
+ it 'should remove all payload keys except :path, :method, :params, :db_runtime, :view_runtime, and :end_point' do
38
+ e = event(:payload => {
39
+ :controller => 'User',
40
+ :action => 'create',
41
+ :path => '/',
42
+ :method => 'GET',
43
+ :params => {},
44
+ :db_runtime => 10,
45
+ :view_runtime => 10
46
+ })
47
+ @instrument.prepare(e)
48
+ e.payload.keys.should =~ [:path, :method, :params, :db_runtime, :view_runtime, :end_point]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::ActionMailer do
4
+ include NotificationsSupport
5
+
6
+ before(:each) do
7
+ @instrument = SystemMetrics::Instrument::ActionMailer.new
8
+ end
9
+
10
+ describe '#handle?' do
11
+ it 'should handle any event whose name ends with action_mailer' do
12
+ @instrument.handles?(event(:name => 'recieve.action_mailer')).should be_true
13
+ @instrument.handles?(event(:name => 'send.action_mailer')).should be_true
14
+ end
15
+
16
+ it 'should not handle an event whose name does not end with action_mailer' do
17
+ @instrument.handles?(event(:name => 'do_something.else')).should be_false
18
+ @instrument.handles?(event(:name => 'action_mailer.process_action')).should be_false
19
+ end
20
+ end
21
+
22
+ describe '#ignore?' do
23
+ it 'should always return false' do
24
+ @instrument.ignore?(event(:name => 'process_action.action_mailer')).should be_false
25
+ @instrument.ignore?(event(:name => 'start_processing.action_controller')).should be_false
26
+ end
27
+ end
28
+
29
+ describe '#prepare' do
30
+ it 'should should keep all payload attributes except :mail' do
31
+ e = event(:payload => {
32
+ :controller => 'User',
33
+ :action => 'create',
34
+ :mail => 'big long message'
35
+ })
36
+ @instrument.prepare(e)
37
+ e.payload.keys.should =~ [:controller, :action]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::ActionView do
4
+ include NotificationsSupport
5
+
6
+ before(:each) do
7
+ @instrument = SystemMetrics::Instrument::ActionView.new
8
+ end
9
+
10
+ describe '#handle?' do
11
+ it 'should handle any event whose name ends with action_view' do
12
+ @instrument.handles?(event(:name => 'recieve.action_view')).should be_true
13
+ @instrument.handles?(event(:name => 'send.action_view')).should be_true
14
+ end
15
+
16
+ it 'should not handle an event whose name does not end with action_view' do
17
+ @instrument.handles?(event(:name => 'do_something.else')).should be_false
18
+ @instrument.handles?(event(:name => 'action_view.process_action')).should be_false
19
+ end
20
+ end
21
+
22
+ describe '#ignore?' do
23
+ it 'should always return false' do
24
+ @instrument.ignore?(event(:name => 'process_action.action_view')).should be_false
25
+ @instrument.ignore?(event(:name => 'start_processing.action_controller')).should be_false
26
+ end
27
+ end
28
+
29
+ describe '#prepare' do
30
+ it 'should try to replace any paths in payload values' do
31
+ @instrument.mapped_paths['/abc123'] = 'A'
32
+ e = event(:payload => {
33
+ :path => '/abc123/more/path',
34
+ :action => 'create'
35
+ })
36
+ @instrument.prepare(e)
37
+ e.payload.should == { :path => 'A/more/path', :action => 'create' }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::ActiveRecord do
4
+ include NotificationsSupport
5
+
6
+ before(:each) do
7
+ @instrument = SystemMetrics::Instrument::ActiveRecord.new
8
+ end
9
+
10
+ describe '#handle?' do
11
+ it 'should handle any event whose name ends with active_record' do
12
+ @instrument.handles?(event(:name => 'sql.active_record')).should be_true
13
+ @instrument.handles?(event(:name => 'arel.active_record')).should be_true
14
+ end
15
+
16
+ it 'should not handle an event whose name does not end with active_record' do
17
+ @instrument.handles?(event(:name => 'do_something.else')).should be_false
18
+ @instrument.handles?(event(:name => 'active_record.sql')).should be_false
19
+ end
20
+ end
21
+
22
+ describe '#ignore?' do
23
+ it 'should ignore all events whose payload[:sql] does not begin with SELECT, INSERT, UPDATE, or DELETE' do
24
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'SELECT * from users' })
25
+ @instrument.ignore?(e).should be_false
26
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'INSERT into users' })
27
+ @instrument.ignore?(e).should be_false
28
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'UPDATE users' })
29
+ @instrument.ignore?(e).should be_false
30
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'DELETE from users' })
31
+ @instrument.ignore?(e).should be_false
32
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'DESCRIBE users' })
33
+ @instrument.ignore?(e).should be_true
34
+ end
35
+ end
36
+
37
+ describe '#prepare' do
38
+ it 'should replace all runs of multiple spaces and newlines in the payload[:sql] with a single space' do
39
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'SELECT * from users' })
40
+ @instrument.prepare(e)
41
+ e.payload[:sql].should == 'SELECT * from users'
42
+ end
43
+
44
+ it 'should remove the :connection_id from the payload' do
45
+ e = event(:name => 'sql.active_record', :payload => { :sql => 'SELECT * from users', :connection_id => 27 })
46
+ @instrument.prepare(e)
47
+ e.payload.should_not include(:connection_id)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::Base do
4
+ include NotificationsSupport
5
+
6
+ describe '#handle?' do
7
+ it 'should handle any event whose name matches the pattern passed to the constructor' do
8
+ instrument = SystemMetrics::Instrument::Base.new(/action_mailer$/)
9
+ instrument.handles?(event(:name => 'recieve.action_mailer')).should be_true
10
+ instrument = SystemMetrics::Instrument::Base.new(/action_controller$/)
11
+ instrument.handles?(event(:name => 'process_action.action_controller')).should be_true
12
+ end
13
+
14
+ it 'should not handle an event whose name does not match the pattern passed to the constructor' do
15
+ instrument = SystemMetrics::Instrument::Base.new(/action_mailer$/)
16
+ instrument.handles?(event(:name => 'action_mailer.receive')).should be_false
17
+ instrument = SystemMetrics::Instrument::Base.new(/action_controller$/)
18
+ instrument.handles?(event(:name => 'action_controller.go')).should be_false
19
+ end
20
+ end
21
+
22
+ describe '#ignore?' do
23
+ it 'should always return false' do
24
+ instrument = SystemMetrics::Instrument::Base.new(/action_mailer$/)
25
+ instrument.ignore?(event(:name => 'process_action.action_mailer')).should be_false
26
+ instrument.ignore?(event(:name => 'start_processing.action_controller')).should be_false
27
+ end
28
+ end
29
+
30
+ describe '#prune_path' do
31
+ it 'should allow path elements to be replaced' do
32
+ instrument = SystemMetrics::Instrument::Base.new(/action_mailer$/)
33
+ instrument.mapped_paths['/abc123'] = 'A'
34
+ instrument.prune_path('/abc123/more/path').should == 'A/more/path'
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe SystemMetrics::Instrument::Rack do
4
+ include NotificationsSupport
5
+
6
+ before(:each) do
7
+ @instrument = SystemMetrics::Instrument::Rack.new
8
+ end
9
+
10
+ describe '#handle?' do
11
+ it 'should handle any event whose name is request.rack' do
12
+ @instrument.handles?(event(:name => 'request.rack')).should be_true
13
+ end
14
+
15
+ it 'should not handle an event whose name is not request.rack' do
16
+ @instrument.handles?(event(:name => 'response.rack')).should be_false
17
+ @instrument.handles?(event(:name => 'view.rack')).should be_false
18
+ end
19
+ end
20
+
21
+ describe '#ignore?' do
22
+ it 'should always return false' do
23
+ @instrument.ignore?(event(:name => 'request.rack')).should be_false
24
+ @instrument.ignore?(event(:name => 'start_processing.action_controller')).should be_false
25
+ end
26
+ end
27
+
28
+ end
@@ -1,6 +1,4 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
- require 'system_metrics/middleware'
3
- require 'system_metrics/collector'
4
2
 
5
3
  describe SystemMetrics::Middleware do
6
4
  before(:each) do
@@ -8,7 +6,7 @@ describe SystemMetrics::Middleware do
8
6
  @collector = SystemMetrics::Collector.new(@store)
9
7
  end
10
8
 
11
- it 'should invoke the collector for a non-excluded paths' do
9
+ it 'should invoke the collector for non-excluded paths' do
12
10
  event = Object.new
13
11
  blk = Proc.new { @collector.collect_event(event) }
14
12
  middleware = SystemMetrics::Middleware.new(blk, @collector, [])
@@ -0,0 +1,87 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe SystemMetrics::NestedEvent do
4
+ it "should provide a reader for started_at that pulls directly from the event's time property" do
5
+ time = Time.now
6
+ e = event(:start => time)
7
+ e.started_at.should == time
8
+ end
9
+
10
+ it "should provide a reader for ended_at that pulls directly from the event's end property" do
11
+ time = Time.now
12
+ e = event(:end => time)
13
+ e.ended_at.should == time
14
+ end
15
+
16
+ it 'should contain child events' do
17
+ e = event
18
+ e.children << event()
19
+ e.should have(1).child
20
+ end
21
+
22
+ describe '.arrange' do
23
+ it 'should arrange an array of events in a nested structure' do
24
+ parent = event(:start => Time.now - 10.seconds, :end => Time.now)
25
+ child = event(:start => Time.now - 9.seconds, :end => Time.now - 1.seconds)
26
+ grandchild = event(:start => Time.now - 8.seconds, :end => Time.now - 2.seconds)
27
+
28
+ root = SystemMetrics::NestedEvent.arrange([grandchild, child, parent])
29
+ root.should have(1).child
30
+ root.children[0].should == child
31
+ root.children[0].should have(1).child
32
+ root.children[0].children[0].should == grandchild
33
+ end
34
+ end
35
+
36
+ describe '#parent_of?' do
37
+ it 'should be a parent of another event if its start and end times contain the target event' do
38
+ parent = event(:start => Time.now - 10.seconds, :end => Time.now)
39
+ child = event(:start => Time.now - 6.seconds, :end => Time.now - 3.seconds)
40
+ parent.parent_of?(child).should be_true
41
+ end
42
+ end
43
+
44
+ describe '#child_of?' do
45
+ it 'should be a child of another event if its start and end times are within the parent event' do
46
+ parent = event(:start => Time.now - 10.seconds, :end => Time.now)
47
+ child = event(:start => Time.now - 6.seconds, :end => Time.now - 3.seconds)
48
+ child.child_of?(parent).should be_true
49
+ end
50
+ end
51
+
52
+ describe '#exclusive_duration' do
53
+ it 'should calculate the time spent within an event minus all the time spent in child events' do
54
+ now = Time.now
55
+ parent = event(:start => now - 10.seconds, :end => now)
56
+ child = event(:start => now - 9.seconds, :end => now - 1.seconds)
57
+ grandchild = event(:start => now - 8.seconds, :end => now - 2.seconds)
58
+ root = SystemMetrics::NestedEvent.arrange([grandchild, child, parent])
59
+ root.exclusive_duration.should be_within(100).of(2000)
60
+ end
61
+ end
62
+
63
+ describe '#to_hash' do
64
+ it 'should return a hash with keys for :name, :action, :category, :started_at, :transaction_id, :payload, :duration, and :exclusive_duration' do
65
+ e = event
66
+ hash = event.to_hash
67
+ hash[:name].should == e.name
68
+ hash[:started_at].should be_within(1).of(e.started_at)
69
+ hash[:transaction_id].should == e.transaction_id
70
+ hash[:payload].should == e.payload
71
+ hash[:duration].should be_within(1).of(e.duration)
72
+ hash[:exclusive_duration].should be_within(1).of(e.exclusive_duration)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def event(options={})
79
+ SystemMetrics::NestedEvent.new(
80
+ options[:name] || 'sql.active_record',
81
+ options[:start] || (Time.now - 5.seconds),
82
+ options[:end] || Time.now,
83
+ options[:transaction_id] || 'tid',
84
+ options[:payload] || {}
85
+ )
86
+ end
87
+ end