system-metrics 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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