health_monitor 0.0.1
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/.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
|