health_monitor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +119 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/generators/health_monitor/health_monitor_generator.rb +26 -0
- data/generators/health_monitor/templates/_health_monitor.html.erb +45 -0
- data/generators/health_monitor/templates/controller.rb +31 -0
- data/health_monitor.gemspec +66 -0
- data/lib/health_monitor.rb +2 -0
- data/lib/health_monitoring.rb +306 -0
- data/templates/_health_monitor.html.erb +29 -0
- data/test/helper.rb +31 -0
- data/test/test_health_monitor.rb +124 -0
- metadata +109 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Blythe Dunham
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
= Health Monitor
|
2
|
+
Monitor individual aspects of your rails application's health
|
3
|
+
|
4
|
+
Most rails applications have many additional moving parts of which the health cannot be assessed with
|
5
|
+
simply pinging the (hopefully page cached) homepage
|
6
|
+
|
7
|
+
For example,
|
8
|
+
* Is Email is sent successfully?
|
9
|
+
* Is the SMS gateway alive and you bought sufficient credits?
|
10
|
+
* All database connections are alive? Backgroundrb down again?
|
11
|
+
* The cloud computing setup jacked the imagemagick? Again?
|
12
|
+
* You are running out of disk space and there are no more file descriptors for your
|
13
|
+
* The git SHA and version is what ?
|
14
|
+
|
15
|
+
Health Monitor adds a single controller action to generate an html, js, or xml report with details of *all*
|
16
|
+
your custom defined health checks. An error response code (500 server error) indicates failure
|
17
|
+
when any monitored feature fails or exceeds the custom timeout definition. Health monitor is easier to setup than
|
18
|
+
custom server side cron tasks and adds the advantage of exercising more code since everything from your load balancer
|
19
|
+
to nginx to mongrels must be happily shoving rubies around to get that 200 oh boy success message.
|
20
|
+
So ping away, grab a beer and know that hey, you might be too drunk but at least you will know your application
|
21
|
+
is sick before your clients do.
|
22
|
+
|
23
|
+
=== Installation
|
24
|
+
Install the plugin or gem
|
25
|
+
script/plugin install git://github.com/blythedunham/health_monitor
|
26
|
+
|
27
|
+
sudo gem install blythedunham-health_monitor --sources=http://gemcutter
|
28
|
+
|
29
|
+
Run the generator to create HealthMonitorController
|
30
|
+
script/generate health_monitor
|
31
|
+
|
32
|
+
=== Examples
|
33
|
+
|
34
|
+
class HealthMonitorController < ApplicationController
|
35
|
+
acts_as_health_monitor
|
36
|
+
|
37
|
+
# montior the database connection
|
38
|
+
monitor_health :database, :description => 'Check database connection'
|
39
|
+
|
40
|
+
# Monitor email sending. Fail if it exceeds 4 minutes
|
41
|
+
monitor_health :email,
|
42
|
+
:timeout => 240000 # Fail this test if it exceeds 4 minutes
|
43
|
+
:method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
|
44
|
+
|
45
|
+
# Display the results of system df call with the results
|
46
|
+
monitor_health :check_disk, :description => 'Check Disk status' do |controller|
|
47
|
+
results = `df`
|
48
|
+
status = $? == 0 ? :success : :failure
|
49
|
+
{ :status => status, :message => "DF: #{results}" }
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def database; ActiveRecord::Base.connection; end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
=== +monitor_health+ Options
|
58
|
+
<tt>:description</tt> - description of the task
|
59
|
+
<tt>:message</tt> - Defaults to SUCCESS or FAILED!
|
60
|
+
Additional information that is either a string or a hash with keys <tt>:success</tt> and <tt>:failure</tt>
|
61
|
+
Message allows more custom result information, such as the number of servers queried or IP address
|
62
|
+
or git version.
|
63
|
+
|
64
|
+
<tt>:timeout</tt> - Fails the health check if the total time exceeds <tt>:timeout</tt> milliseconds
|
65
|
+
<tt>:method</tt> - The name of the method or a Proc to invoke. A block can be given as well.
|
66
|
+
Defaults to the method with the feature name
|
67
|
+
|
68
|
+
=== Monitored Methods
|
69
|
+
The proc or method defined should return its status as one of the following:
|
70
|
+
* true or false indicating success or failure
|
71
|
+
monitor_health :mymonitor, :method => { |controller| ...do something ... ; true }
|
72
|
+
|
73
|
+
monitor_health :myothermonitor
|
74
|
+
def myothermonitor; false; end
|
75
|
+
|
76
|
+
* a status symbol: of <tt>:success</tt>, <tt>:failure</tt>, <tt>:timeout</tt>, <tt>:skipped</tt>
|
77
|
+
monitor_health :mymonitor, :method => { |controller| ...do something ... ; :failure }
|
78
|
+
|
79
|
+
* a hash of attributes including:
|
80
|
+
** <tt>:status</tt> must be a value listed above: defaults to failure
|
81
|
+
** <tt>:message</tt> Custom message with result data
|
82
|
+
** <tt>:description</tt> The task description
|
83
|
+
|
84
|
+
monitor_health :mymonitor do |controller|
|
85
|
+
...do something ... ;
|
86
|
+
{ :status => :success, :message => 'My custom results for server abc', :description => 'task description' }
|
87
|
+
end
|
88
|
+
|
89
|
+
=== Skip tests or run a single test
|
90
|
+
To skip a test, add the skip parameter to your url with a list of comma delimited tests to skip
|
91
|
+
http://yayhost/health_monitor.html?skip=testblah
|
92
|
+
http://yayhost/health_monitor.js?skip=test1,test2
|
93
|
+
|
94
|
+
To specify tests to run, use the :only parameter
|
95
|
+
http://yayhost/health_monitor?only=test2,test4,test5
|
96
|
+
|
97
|
+
=== Routes
|
98
|
+
By default, the route resources are added (assume controller is named +HealthMonitorController+)
|
99
|
+
The action defined is named +monitor_health+
|
100
|
+
|
101
|
+
Base resource if show is not already defined by +health_monitor_url+
|
102
|
+
host/health_monitor.js?skip=mysql
|
103
|
+
|
104
|
+
Member accessor +monitor_health_health_monitor+
|
105
|
+
host/health_monitor/monitor_health.js?only=thatone,thisone
|
106
|
+
|
107
|
+
To disable and write your own routes, use +route+ option with +acts_as_health_monitor+
|
108
|
+
acts_as_health_monitor :route => false
|
109
|
+
|
110
|
+
=== Considerations
|
111
|
+
If you are reporting sensitive information, consider limiting this controller to specific IP addresses or
|
112
|
+
adding authorization logic to your controller with before_filters.
|
113
|
+
|
114
|
+
=== Inspirations and Fun
|
115
|
+
Health monitor was inspired by work at http://spongecell.com.
|
116
|
+
Health monitor is brought to you by the rule of threes and the letter B.
|
117
|
+
The official mascot of health monitor is GIRAFFE! and the official drink is sparklemotion (1 part sparks, 1 part champagne)
|
118
|
+
|
119
|
+
Copyright (c) 2009 Blythe Dunham. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "health_monitor"
|
8
|
+
gem.summary = %Q{Monitor all aspects of your applications health.}
|
9
|
+
gem.email = "blythe@snowgiraffe.com"
|
10
|
+
gem.homepage = "http://github.com/blythedunham/health_monitor"
|
11
|
+
gem.authors = ["Blythe Dunham"]
|
12
|
+
|
13
|
+
gem.add_dependency('activesupport', '>= 2.1')
|
14
|
+
gem.add_dependency('actionpack', '>=2.1')
|
15
|
+
|
16
|
+
#gem.add_development_dependency('timecop', '0.3.1')
|
17
|
+
gem.add_development_dependency('mocha', '>=0.9.8')
|
18
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
19
|
+
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
task :rcov do
|
43
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :test => :check_dependencies
|
48
|
+
|
49
|
+
task :default => :test
|
50
|
+
|
51
|
+
require 'rake/rdoctask'
|
52
|
+
Rake::RDocTask.new do |rdoc|
|
53
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
54
|
+
|
55
|
+
rdoc.rdoc_dir = 'rdoc'
|
56
|
+
rdoc.title = "health_monitor #{version}"
|
57
|
+
rdoc.rdoc_files.include('README*')
|
58
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
59
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class HealthMonitorGenerator < ControllerGenerator
|
3
|
+
def initialize(runtime_args, runtime_options = {})
|
4
|
+
runtime_args << 'health_monitor' if runtime_args.empty?
|
5
|
+
super runtime_args, runtime_options
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
# Override the source path to read the parents templates for everything
|
11
|
+
# except the controller
|
12
|
+
def source_path(relative_source)
|
13
|
+
original_source_root = self.source_root
|
14
|
+
if relative_source != 'controller.rb'
|
15
|
+
@source_root = File.join(self.class.lookup('Controller').path, 'templates')
|
16
|
+
end
|
17
|
+
super
|
18
|
+
ensure
|
19
|
+
@source_root = original_source_root
|
20
|
+
end
|
21
|
+
|
22
|
+
def banner
|
23
|
+
"Usage: #{$0} health_monitor [controller_name] [options] \n Default controller_name = HealthMonitorController]"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<h1>Health O Meter</h1>
|
2
|
+
<h2>Status: <%= healthy? ? "PASS" : "FAIL" %></h2>
|
3
|
+
|
4
|
+
<table border="1" width="90%">
|
5
|
+
<tr>
|
6
|
+
<th></th>
|
7
|
+
<th>Name</th>
|
8
|
+
<th>Result</th>
|
9
|
+
<th>Time (s)</th>
|
10
|
+
<th>Description</th>
|
11
|
+
</tr>
|
12
|
+
<% monitored_features.each do |name, options| %>
|
13
|
+
<% result = @results[ name ] %>
|
14
|
+
<tr>
|
15
|
+
<td style="background-color: <%= case result.try( '[]', :status )
|
16
|
+
when nil, :skipped then 'yellow'
|
17
|
+
when :success then 'green'
|
18
|
+
when :failure then 'red'
|
19
|
+
when :timeout then 'blue'
|
20
|
+
end
|
21
|
+
-%>">
|
22
|
+
<%= result ? result[:status].to_s.upcase : "SKIPPED" %>
|
23
|
+
</td>
|
24
|
+
<td><%= h name.to_s.titleize %></td>
|
25
|
+
<td><%= h result ? result[ :message ] : "SKIPPED" %></td>
|
26
|
+
<td><%= h "#{result[ :time ]} s" if result %></td>
|
27
|
+
<td><%= h result.try( '[]', :description ) || options[ :description ] %></td>
|
28
|
+
</tr>
|
29
|
+
<% end %>
|
30
|
+
</table>
|
31
|
+
<br>
|
32
|
+
<table>
|
33
|
+
<tr>
|
34
|
+
<td><b>Total Time: </b></td>
|
35
|
+
<td><%= h "#{@results.sum{|n, r| r[ :time ] || 0 }} seconds" %></td>
|
36
|
+
</tr>
|
37
|
+
<tr>
|
38
|
+
<td><b>PID: </b></td>
|
39
|
+
<td><%= $$ %></td>
|
40
|
+
</tr>
|
41
|
+
<tr>
|
42
|
+
<td><b>Host: </b></td>
|
43
|
+
<td><%= request.host %></td>
|
44
|
+
</tr>
|
45
|
+
</table>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class <%= class_name %>Controller < ApplicationController
|
2
|
+
acts_as_health_monitor
|
3
|
+
|
4
|
+
# Refer to the README at http://github.com/blythedunham/health_monitor
|
5
|
+
# for more examples
|
6
|
+
|
7
|
+
# # montior the database connection
|
8
|
+
# monitor_health :database, :description => 'Check database connection'
|
9
|
+
#
|
10
|
+
# # Monitor email sending. Fail if it exceeds 4 minutes
|
11
|
+
# monitor_health :email,
|
12
|
+
# :timeout => 240000 # Fail this test if it exceeds 4 minutes
|
13
|
+
# :method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
|
14
|
+
#
|
15
|
+
# # Display the results of system df call with the results
|
16
|
+
# monitor_health :check_disk, :description => 'Check Disk status' do |controller|
|
17
|
+
# results = `df`
|
18
|
+
# status = $? == 0 ? :success : :failure # base result on return code
|
19
|
+
# { :status => status, :message => "DF: #{results}" }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# protected
|
23
|
+
# def database; ActiveRecord::Base.connection; end
|
24
|
+
#
|
25
|
+
|
26
|
+
<% for action in actions -%>
|
27
|
+
def <%= action %>
|
28
|
+
end
|
29
|
+
|
30
|
+
<% end -%>
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{health_monitor}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Blythe Dunham"]
|
12
|
+
s.date = %q{2009-12-06}
|
13
|
+
s.email = %q{blythe@snowgiraffe.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"generators/health_monitor/health_monitor_generator.rb",
|
25
|
+
"generators/health_monitor/templates/_health_monitor.html.erb",
|
26
|
+
"generators/health_monitor/templates/controller.rb",
|
27
|
+
"health_monitor.gemspec",
|
28
|
+
"lib/health_monitor.rb",
|
29
|
+
"lib/health_monitoring.rb",
|
30
|
+
"templates/_health_monitor.html.erb",
|
31
|
+
"test/helper.rb",
|
32
|
+
"test/test_health_monitor.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/blythedunham/health_monitor}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.5}
|
38
|
+
s.summary = %q{Monitor all aspects of your applications health.}
|
39
|
+
s.test_files = [
|
40
|
+
"test/helper.rb",
|
41
|
+
"test/test_health_monitor.rb"
|
42
|
+
]
|
43
|
+
|
44
|
+
if s.respond_to? :specification_version then
|
45
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.1"])
|
50
|
+
s.add_runtime_dependency(%q<actionpack>, [">= 2.1"])
|
51
|
+
s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
|
52
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<activesupport>, [">= 2.1"])
|
55
|
+
s.add_dependency(%q<actionpack>, [">= 2.1"])
|
56
|
+
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
57
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<activesupport>, [">= 2.1"])
|
61
|
+
s.add_dependency(%q<actionpack>, [">= 2.1"])
|
62
|
+
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
63
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,306 @@
|
|
1
|
+
# = Health Monitor
|
2
|
+
# Monitor individual aspects of your rails application's health
|
3
|
+
#
|
4
|
+
# Most rails applications have many additional moving parts of which the health cannot be assessed with
|
5
|
+
# simply pinging the (hopefully page cached) homepage
|
6
|
+
#
|
7
|
+
# For example,
|
8
|
+
# * Is Email is sent successfully?
|
9
|
+
# * Is the SMS gateway alive and you bought sufficient credits?
|
10
|
+
# * All database connections are alive? Backgroundrb down again?
|
11
|
+
# * The cloud computing setup jacked the imagemagick? Again?
|
12
|
+
# * You are running out of disk space and there are no more file descriptors for your
|
13
|
+
# * The git SHA and version is what ?
|
14
|
+
#
|
15
|
+
# Health Monitor adds a single controller action to generate an html, js, or xml report with details of *all*
|
16
|
+
# your custom defined health checks. An error response code (500 server error) indicates failure
|
17
|
+
# when any monitored feature fails or exceeds the custom timeout definition. Health monitor is easier to setup than
|
18
|
+
# custom server side cron tasks and adds the advantage of exercising more code since everything from your load balancer
|
19
|
+
# to nginx to mongrels must be happily shoving rubies around to get that 200 oh boy success message.
|
20
|
+
# So ping away, grab a beer and know that hey, you might be too drunk but at least you will know your application
|
21
|
+
# is sick before your clients do.
|
22
|
+
#
|
23
|
+
# ====Examples
|
24
|
+
#
|
25
|
+
# class HealthMonitorController < ApplicationController
|
26
|
+
# acts_as_health_monitor
|
27
|
+
#
|
28
|
+
# # montior the database connection
|
29
|
+
# monitor_health :database, :description => 'Check database connection'
|
30
|
+
#
|
31
|
+
# # Monitor email sending. Fail if it exceeds 4 minutes
|
32
|
+
# monitor_health :email,
|
33
|
+
# :timeout => 240000 # Fail this test if it exceeds 4 minutes
|
34
|
+
# :method => lambda{ |controller| ActionMailer::Base.deliver_my_mail('blah') }
|
35
|
+
#
|
36
|
+
# # Display the results of system df call with the results
|
37
|
+
# monitor_health :check_disk, :description => 'Check Disk status' do |controller|
|
38
|
+
# results = `df`
|
39
|
+
# status = $? == 0 ? :success : :failure
|
40
|
+
# { :status => status, :message => "DF: #{results}" }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# protected
|
44
|
+
# def database; ActiveRecord::Base.connection; end
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# === +monitor_health+ Options
|
49
|
+
# <tt>:description</tt> - description of the task
|
50
|
+
# <tt>:message</tt> - Defaults to SUCCESS or FAILED!
|
51
|
+
# Additional information that is either a string or a hash with keys <tt>:success</tt> and <tt>:failure</tt>
|
52
|
+
# Message allows more custom result information, such as the number of servers queried or IP address
|
53
|
+
# or git version.
|
54
|
+
#
|
55
|
+
# <tt>:timeout</tt> - Fails the health check if the total time exceeds <tt>:timeout</tt> milliseconds
|
56
|
+
# <tt>:method</tt> - The name of the method or a Proc to invoke. A block can be given as well.
|
57
|
+
# Defaults to the method with the feature name
|
58
|
+
#
|
59
|
+
# === Monitored Methods
|
60
|
+
# The proc or method defined should return its status as one of the following:
|
61
|
+
# * true or false indicating success or failure
|
62
|
+
# monitor_health :mymonitor, :method => { |controller| ...do something ... ; true }
|
63
|
+
#
|
64
|
+
# monitor_health :myothermonitor
|
65
|
+
# def myothermonitor; false; end
|
66
|
+
#
|
67
|
+
# * a status symbol: of <tt>:success</tt>, <tt>:failure</tt>, <tt>:timeout</tt>, <tt>:skipped</tt>
|
68
|
+
# monitor_health :mymonitor, :method => { |controller| ...do something ... ; :failure }
|
69
|
+
#
|
70
|
+
# * a hash of attributes including:
|
71
|
+
# ** <tt>:status</tt> must be a value listed above: defaults to failure
|
72
|
+
# ** <tt>:message</tt> Custom message with result data
|
73
|
+
# ** <tt>:description</tt> The task description
|
74
|
+
#
|
75
|
+
# monitor_health :mymonitor do |controller|
|
76
|
+
# ...do something ... ;
|
77
|
+
# { :status => :success, :message => 'My custom results for server abc', :description => 'task description' }
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# === Routes
|
81
|
+
# By default, the route resources are added (assume controller is named +HealthMonitorController+)
|
82
|
+
# The action defined is named +monitor_health+
|
83
|
+
#
|
84
|
+
# Base resource if show is not already defined by +health_monitor_url+
|
85
|
+
# host/health_monitor.js?skip=mysql
|
86
|
+
#
|
87
|
+
# Member accessor +monitor_health_health_monitor+
|
88
|
+
# host/health_monitor/monitor_health.js?only=thatone,thisone
|
89
|
+
#
|
90
|
+
# To disable and write your own routes, use +route+ option with +acts_as_health_monitor+
|
91
|
+
# acts_as_health_monitor :route => false
|
92
|
+
#
|
93
|
+
module HealthMonitoring
|
94
|
+
def self.included( base )
|
95
|
+
base.class_eval do
|
96
|
+
extend HealthMonitoring::ClassMethods
|
97
|
+
cattr_accessor :monitored_features
|
98
|
+
self.monitored_features ||= ActiveSupport::OrderedHash.new
|
99
|
+
|
100
|
+
helper_method :monitored_features, :healthy?
|
101
|
+
hide_action :monitored_features, :monitored_features=
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# by default, this is a singleton route
|
106
|
+
# class HealthMonitor < ApplicationController
|
107
|
+
# acts_as_health_monitor
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# ===Options
|
111
|
+
# :route => false. Will disable autogenerating named paths and routes
|
112
|
+
#
|
113
|
+
# By default, the resouces are defined as (assume controller is named +HealthMonitorController+)
|
114
|
+
# Base resource if show is not already defined by +health_monitor_url+
|
115
|
+
# host/health_monitor.js?skip=mysql
|
116
|
+
#
|
117
|
+
# Member accessor +monitor_health_health_monitor+
|
118
|
+
# host/health_monitor/monitor_health.js?only=thatone,thisone
|
119
|
+
#
|
120
|
+
module ActsAsHealthMonitor
|
121
|
+
def acts_as_health_monitor( options = {} )
|
122
|
+
include HealthMonitoring unless method_defined?( :health_monitor )
|
123
|
+
|
124
|
+
setup_health_monitor_routes( options.delete( :route ) )
|
125
|
+
end
|
126
|
+
|
127
|
+
def setup_health_monitor_routes( route )
|
128
|
+
return if route == false
|
129
|
+
|
130
|
+
base_methods = if method_defined?( :show )
|
131
|
+
:none
|
132
|
+
else
|
133
|
+
alias_method :show, :monitor_health
|
134
|
+
:show
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
ActionController::Routing::Routes.draw do |map|
|
139
|
+
map.resource controller_name, :controller => controller_name, :only => base_methods, :member => { :monitor_health => :get }
|
140
|
+
#map.named_route controller_name, controller_name + '.:format', :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
|
141
|
+
#map.named_route "monitor_health_#{controller_name}", "#{controller_name}/monitor_health.:format", :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
|
142
|
+
|
143
|
+
#map.resource controller_name, :controller => controller_name, :only => :none, :member => { :monitor_health => :get } do |r|
|
144
|
+
# r.named_route '', '.:format', :conditions => {:method => :get}, :controller => controller_name, :action => 'monitor_health'
|
145
|
+
#end
|
146
|
+
#map.named_route route_name, route_path, :controller => controller_name, :action => 'health', :method => :get
|
147
|
+
#map.connect "#{controller_name}", :controller => controller_name, :action => 'health'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
module ClassMethods
|
153
|
+
def monitor_health( *features, &block )
|
154
|
+
options = features.extract_options!
|
155
|
+
options.symbolize_keys!
|
156
|
+
options[:method] = block if block_given?
|
157
|
+
features.each {|name| self.monitored_features[ name.to_sym ] = options }
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Show a status page showing the health of monitored features
|
162
|
+
# Returns a 404 if any features have a success of unsuccessful
|
163
|
+
#
|
164
|
+
# Skip features: z2live.com/health/status?skip=mongo,mysql
|
165
|
+
# Include features: z2live.com/health/status?feature=mongo
|
166
|
+
def monitor_health
|
167
|
+
find_features
|
168
|
+
@results = @features.inject({}) do |results, feature_name|
|
169
|
+
results[ feature_name ] = monitor_health_of( feature_name )
|
170
|
+
results
|
171
|
+
end
|
172
|
+
healthy? ? on_healthy : on_unhealthy
|
173
|
+
render_health
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
def monitor_health_of( feature_name )
|
179
|
+
feature_status = { :name => feature_name }
|
180
|
+
result = nil
|
181
|
+
feature_status[ :time ] = Benchmark.ms { result = invoke_health_check( feature_name ) }
|
182
|
+
report_health_status!( feature_status, result )
|
183
|
+
end
|
184
|
+
|
185
|
+
def invoke_health_check( feature_name )
|
186
|
+
case method = monitored_features[ feature_name ][ :method ]
|
187
|
+
when Proc, Method then method.call( self)
|
188
|
+
when NilClass then send( feature_name )
|
189
|
+
else send( method )
|
190
|
+
end
|
191
|
+
rescue => e
|
192
|
+
return { :status => :failure, :exception => e }
|
193
|
+
end
|
194
|
+
|
195
|
+
def healthy?
|
196
|
+
(@results||{}).all?{ |name, result| result[:status] == :success }
|
197
|
+
end
|
198
|
+
|
199
|
+
#response code
|
200
|
+
def healthy_response_code() 200; end
|
201
|
+
def unhealthy_response_code() 500; end
|
202
|
+
|
203
|
+
#callbacks
|
204
|
+
def on_healthy() end
|
205
|
+
def on_unhealthy() end
|
206
|
+
|
207
|
+
def health_check_template
|
208
|
+
File.join( File.dirname(__FILE__), "/../generators/health_monitor/templates/_health_monitor.html.erb" )
|
209
|
+
end
|
210
|
+
|
211
|
+
def health_response_code
|
212
|
+
healthy? ? healthy_response_code : unhealthy_response_code
|
213
|
+
end
|
214
|
+
|
215
|
+
# Render the html file
|
216
|
+
def render_health_html
|
217
|
+
render :file => health_check_template, :status => health_response_code
|
218
|
+
end
|
219
|
+
|
220
|
+
# Render the json file
|
221
|
+
def render_health_json
|
222
|
+
render :text => @results.values.to_json, :status => health_response_code
|
223
|
+
end
|
224
|
+
|
225
|
+
# Render the xml file
|
226
|
+
def render_health_xml
|
227
|
+
render :xml => @results.values, :status => health_response_code
|
228
|
+
end
|
229
|
+
|
230
|
+
# Render the result
|
231
|
+
def render_health
|
232
|
+
return if performed?
|
233
|
+
|
234
|
+
respond_to do |format|
|
235
|
+
format.html { render_health_html }
|
236
|
+
format.js { render_health_json }
|
237
|
+
format.xml { render_health_xml }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
# Skip features by appending skip=mongo,fun,etc
|
244
|
+
# Include features by appending feature=mongo,urban_airship,etc to filter
|
245
|
+
def find_features
|
246
|
+
@features = if params[ :only ]
|
247
|
+
params[ :only ].to_s.split( "," ).collect( &:to_sym ).uniq
|
248
|
+
|
249
|
+
elsif skip = params[ :skip ] || params[ :exclude ]
|
250
|
+
monitored_features.keys - skip.to_s.split( "," ).collect( &:to_sym )
|
251
|
+
|
252
|
+
else
|
253
|
+
monitored_features.keys
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def fail_health_timeout!( feature_status )
|
258
|
+
timeout = monitored_features[ feature_status[:name] ][:timeout]
|
259
|
+
if timeout && feature_status[:time].to_f >= timeout
|
260
|
+
feature_status.update(
|
261
|
+
:status => :timeout,
|
262
|
+
:message => "Timeout: Exceeded #{timeout} ms"
|
263
|
+
)
|
264
|
+
end
|
265
|
+
|
266
|
+
#Adjust time to seconds
|
267
|
+
feature_status[ :time ] = ("%.4f" % (feature_status[ :time ] / 1000)).to_f
|
268
|
+
|
269
|
+
feature_status
|
270
|
+
end
|
271
|
+
|
272
|
+
def set_default_health_status!( feature_status )
|
273
|
+
monitor_defaults = monitored_features[ feature_status[ :name ] ]
|
274
|
+
feature_status[ :description ] ||= monitor_defaults[ :description ]
|
275
|
+
health_monitor_message!( feature_status )
|
276
|
+
end
|
277
|
+
|
278
|
+
def health_monitor_message!( feature_status )
|
279
|
+
feature_status[ :message ] ||= monitored_features[ feature_status[ :name ] ][ :message ]
|
280
|
+
feature_status[ :message ] = feature_status[:message][ feature_status[:status] ] if feature_status[:message].is_a?( Hash )
|
281
|
+
feature_status[ :message ] ||= feature_status[:status].to_s.upcase
|
282
|
+
feature_status[ :message ] << " Error: #{feature_status[:exception]}" if feature_status[:exception]
|
283
|
+
feature_status
|
284
|
+
end
|
285
|
+
|
286
|
+
def report_health_status!( feature_status, result )
|
287
|
+
|
288
|
+
# update the hash with the result hash
|
289
|
+
case result
|
290
|
+
when Hash then feature_status.update( result )
|
291
|
+
else feature_status[ :status ] = result
|
292
|
+
end
|
293
|
+
|
294
|
+
# symbolize variables if strings
|
295
|
+
# convert false to :failure
|
296
|
+
# otherwise the existance of any object indicates success
|
297
|
+
feature_status[:status] = case feature_status[:status].to_s
|
298
|
+
when /(failure|success|timeout|skipped)/ then feature_status[:status].to_sym
|
299
|
+
when 'false' then :failure
|
300
|
+
else !!feature_status[:status] ? :success : :failure
|
301
|
+
end
|
302
|
+
|
303
|
+
fail_health_timeout!( feature_status )
|
304
|
+
set_default_health_status!( feature_status )
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
<h1>Health O Meter</h1>
|
3
|
+
<h2><%= healthy? ? "OK" : "FAIL" %></h2>
|
4
|
+
|
5
|
+
<table border="1" width="90%">
|
6
|
+
<tr>
|
7
|
+
<th></th>
|
8
|
+
<th>Name</th>
|
9
|
+
<th>Result</th>
|
10
|
+
<th>Time (s)</th>
|
11
|
+
<th>Description</th>
|
12
|
+
</tr>
|
13
|
+
<% monitored_features.each do |name, options| %>
|
14
|
+
<tr>
|
15
|
+
<td style="background-color:<%= status_color( name )%>">
|
16
|
+
<%= h status_text( name ) %>
|
17
|
+
</td>
|
18
|
+
<td><%= h name.to_s.titleize %></td>
|
19
|
+
<td><%= monitored_message( name ) %></td>
|
20
|
+
<td><%= monitored_timing( name ) %></td>
|
21
|
+
<td><%= monitored_description( name ) %></td>
|
22
|
+
</tr>
|
23
|
+
<% end %>
|
24
|
+
</table>
|
25
|
+
|
26
|
+
<br>
|
27
|
+
<b>Total: </b><%= total_time %> <br>
|
28
|
+
<b>PID: </b><%= $$ %><br>
|
29
|
+
<b>host: </b><%= request.host %>
|
data/test/helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Load the environment
|
2
|
+
require 'fileutils'
|
3
|
+
ENV['RAILS_ENV'] = 'test'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_support'
|
6
|
+
require 'action_controller'
|
7
|
+
# Load the testing framework
|
8
|
+
require 'test_help'
|
9
|
+
silence_warnings { RAILS_ENV = ENV['RAILS_ENV'] }
|
10
|
+
|
11
|
+
|
12
|
+
gem 'shoulda'
|
13
|
+
gem 'mocha'
|
14
|
+
require 'shoulda'
|
15
|
+
require 'shoulda/action_view'
|
16
|
+
require 'shoulda/action_controller'
|
17
|
+
require 'mocha'
|
18
|
+
|
19
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
21
|
+
|
22
|
+
require 'health_monitor'
|
23
|
+
|
24
|
+
|
25
|
+
test_dir = File.expand_path(File.dirname(__FILE__) + '/../tmp')
|
26
|
+
FileUtils.mkdir_p(test_dir) unless File.exist?(test_dir)
|
27
|
+
RAILS_LOGGER = Logger.new(test_dir + '/test.log')
|
28
|
+
ActionController::Base.logger = nil
|
29
|
+
|
30
|
+
class Test::Unit::TestCase
|
31
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
#require 'shoulda/controller/macros'
|
4
|
+
class TestHealthMonitorController < ActionController::Base
|
5
|
+
acts_as_health_monitor
|
6
|
+
|
7
|
+
monitor_health :proc_test,
|
8
|
+
:method => lambda{ |c| c.params[:fail] != 'true' },
|
9
|
+
:description => 'A proc test',
|
10
|
+
:message => {:success => "Successful test.", :failure => 'Failed test'}
|
11
|
+
|
12
|
+
monitor_health :block_test do |controller|
|
13
|
+
@block_controller = controller
|
14
|
+
{
|
15
|
+
:status => controller.send(:conditional_fail_test),
|
16
|
+
:message => "Params are: #{controller.params[:fail]}",
|
17
|
+
:description => 'A block test'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
monitor_health :conditional_fail_test
|
22
|
+
monitor_health :exception_test
|
23
|
+
|
24
|
+
protected
|
25
|
+
def conditional_fail_test( key = :fail)
|
26
|
+
params[key] != 'true'
|
27
|
+
end
|
28
|
+
|
29
|
+
def exception_test
|
30
|
+
raise StandardError.new("Exception test") if params[:exception] == 'true'
|
31
|
+
:success
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TestHealthMonitorControllerTest < ActionController::TestCase
|
36
|
+
|
37
|
+
def self.ping_monitor( testname, options = {} )
|
38
|
+
|
39
|
+
context testname do
|
40
|
+
expect_success = ![options[:fail], options[:exception]].include?( 'true' )
|
41
|
+
setup do
|
42
|
+
get :show, options
|
43
|
+
@success = expect_success#![options[:fail], options[:exception]].include?( 'true' )
|
44
|
+
@results = assigns(:results)
|
45
|
+
end
|
46
|
+
|
47
|
+
should_respond_with( expect_success ? 200 : 500 )
|
48
|
+
|
49
|
+
should 'have the description' do
|
50
|
+
assert_equal 'A proc test', @results[:proc_test][:description]
|
51
|
+
assert_equal 'A block test', @results[:block_test][:description]
|
52
|
+
assert_nil @results[:conditional_fail_test][:description]
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'set default message based on test status' do
|
56
|
+
result = @success ? 'SUCCESS' : 'FAILURE'
|
57
|
+
assert_equal result, @results[:conditional_fail_test][:message], "Expect message #{@success} for #{@results[:conditional_fail_test]} "
|
58
|
+
end
|
59
|
+
|
60
|
+
if expect_success
|
61
|
+
should 'set custom message on success to' do
|
62
|
+
assert_equal 'Successful test.', @results[:proc_test][:message]
|
63
|
+
assert_equal 'Params are: ', @results[:block_test][:message]
|
64
|
+
end
|
65
|
+
else
|
66
|
+
should 'set custom message on failure to' do
|
67
|
+
assert_equal 'Failed test', @results[:proc_test][:message]
|
68
|
+
assert_equal 'Params are: true', @results[:block_test][:message]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
should "have #{expect_success ? 'SUCCESS' : 'FAIL'} response" do
|
73
|
+
@results.each do |k,v|
|
74
|
+
expected_result = (k == :exception_test) || @success ? :success : :failure
|
75
|
+
assert_equal expected_result, v[:status], "#{k} results #{v.inspect}. Status should be #{expected_result}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
yield if block_given?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
should_route :get, '/test_health_monitor.js', :action => :show, :format => "js", :controller => 'test_health_monitor'
|
84
|
+
should_route :get, '/test_health_monitor/monitor_health', :controller => 'test_health_monitor', :action => :monitor_health
|
85
|
+
|
86
|
+
ping_monitor 'health monitor web page' do
|
87
|
+
should_respond_with_content_type :html
|
88
|
+
end
|
89
|
+
|
90
|
+
ping_monitor( 'monitor unhealthy app', :fail => 'true' ) do
|
91
|
+
should_respond_with_content_type :html
|
92
|
+
end
|
93
|
+
|
94
|
+
ping_monitor 'monitor an unhealthy app with js', {:fail => 'true', :format => 'js'} do
|
95
|
+
should_respond_with_content_type 'text/javascript'
|
96
|
+
end
|
97
|
+
|
98
|
+
ping_monitor 'monitor a healthy app with js', {:format => 'js'} do
|
99
|
+
should_respond_with_content_type 'text/javascript'
|
100
|
+
end
|
101
|
+
|
102
|
+
ping_monitor 'monitor an unhealthy app with js', {:fail => 'true', :format => 'xml'} do
|
103
|
+
should_respond_with_content_type :xml
|
104
|
+
end
|
105
|
+
|
106
|
+
ping_monitor 'monitor a healthy app with js', {:format => 'xml'} do
|
107
|
+
should_respond_with_content_type :xml
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'render a template if a test exception occurs' do
|
111
|
+
setup do
|
112
|
+
get :show, :exception => 'true'
|
113
|
+
@results = assigns(:results)
|
114
|
+
end
|
115
|
+
should_respond_with_content_type :html
|
116
|
+
should_respond_with 500
|
117
|
+
|
118
|
+
should 'fail exception test' do
|
119
|
+
assert_equal :failure, @results[:exception_test][:status]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: health_monitor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Blythe Dunham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-06 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "2.1"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: actionpack
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "2.1"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mocha
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.8
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: thoughtbot-shoulda
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description:
|
56
|
+
email: blythe@snowgiraffe.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.rdoc
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- LICENSE
|
67
|
+
- README.rdoc
|
68
|
+
- Rakefile
|
69
|
+
- VERSION
|
70
|
+
- generators/health_monitor/health_monitor_generator.rb
|
71
|
+
- generators/health_monitor/templates/_health_monitor.html.erb
|
72
|
+
- generators/health_monitor/templates/controller.rb
|
73
|
+
- health_monitor.gemspec
|
74
|
+
- lib/health_monitor.rb
|
75
|
+
- lib/health_monitoring.rb
|
76
|
+
- templates/_health_monitor.html.erb
|
77
|
+
- test/helper.rb
|
78
|
+
- test/test_health_monitor.rb
|
79
|
+
has_rdoc: true
|
80
|
+
homepage: http://github.com/blythedunham/health_monitor
|
81
|
+
licenses: []
|
82
|
+
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options:
|
85
|
+
- --charset=UTF-8
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
version:
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.5
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Monitor all aspects of your applications health.
|
107
|
+
test_files:
|
108
|
+
- test/helper.rb
|
109
|
+
- test/test_health_monitor.rb
|