updawg 0.3.5

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ Gemfile.lock
23
+
24
+ ## rubymine
25
+ .idea
@@ -0,0 +1,8 @@
1
+ 0.3.3:
2
+ * ADD text reporting
3
+ * ADD check timeouts
4
+ * ADD std deviation checks
5
+ 0.3.2:
6
+ * FIX handling of StandardError in checks
7
+ 0.3.1:
8
+ * ADD nagios reporting
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ gem "builder", '~>2.1'
4
+ gem 'aggregate'
5
+
6
+ group :development do
7
+ gem "rspec", "~> 1.3"
8
+ gem "hpricot", '~>0.8'
9
+ end
10
+
11
+ group :test do
12
+ gem 'SystemTimer', :platforms => :ruby
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Viximo, Inc.
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.
@@ -0,0 +1,99 @@
1
+ = updawg
2
+
3
+ A simple way to see what's up, dawg (and what's not.)
4
+
5
+ Define a list of checks and create a controller that renders the results. Ping the controller with
6
+ the monitoring tool of your choice, perhaps setting a 503 response when one of the checks fails.
7
+
8
+ Some options can be set:
9
+ UpDawg.configure do |config|
10
+ config.success_text = 'OK'
11
+ end
12
+
13
+ Checks are defined with a simple DSL:
14
+
15
+ monitor = Updawg.monitor do
16
+ check 'MySQL' do
17
+ # Will raise an exception if the connection is unavailable resulting in a failed check
18
+ ActiveRecord::Base.connection.execute('SELECT 1 FROM DUAL')
19
+ end
20
+
21
+ check 'Some threshold' do
22
+ result ||= error('Threshold exceeded y') if threshold_value > y
23
+ result ||= warning('Threshold exceeded x') if threshold_value > x
24
+ result
25
+ end
26
+
27
+ # alternatively, use warning!/error!/pass! to break out of the check immediately
28
+ check 'Something else' do
29
+ warning!('Something kind of bad happened') if threshold_value > x
30
+ error! ('Something really bad happened') if threshold_value > y
31
+
32
+ pass!('Everything is just fine')
33
+ end
34
+
35
+ # Specify a threshold check for captured purchases in the last 24 hours
36
+ threshold 'Captured Purchases (24 hours)', :warn_under => 10, :error_under => 1 do
37
+ Purchase.captured_at_after(24.hours.ago)
38
+ end
39
+
40
+ # Specify a "sweet spot threshold check" to alert when a business metric goes out of normal range
41
+ threshold 'Transactions (24 hours)',
42
+ :warn_under => 100, :error_under => 1,
43
+ :warn_over => 1000, :error_over => 2000 do
44
+ Transaction.created_at_after(24.hours.ago)
45
+ end
46
+
47
+ # Specify a std deviation check to alert when a value is outside of 2 std deviations from the mean
48
+ # The block should yield an array of values, the last of which is considered the sample.
49
+ # Supports options:
50
+ # scale_factor - multiple of the std_dev to user (default: 2)
51
+ # smoothing - whether to ignore outliers in the dataset (default: true)
52
+ deviation 'Hourly Transaction Value' do
53
+ Transaction.group('hour').order('hour ASC') # will report if the last hour of data is our of range
54
+ end
55
+ end
56
+
57
+ results = monitor.perform
58
+
59
+ results.success? => true if no checks error
60
+ results.warning? => true if no checks error and at least one has a warning
61
+ results.error? => true if at least one check fails
62
+
63
+ Render with:
64
+ results.to_html
65
+
66
+ <table class="updawg monitor">
67
+ <tr class="check error">
68
+ <td class="name">MySQL</td>
69
+ <td class="message">ActiveRecord::StatementInvalid: Mysql::Error: You have an error in your SQL syntax</td>
70
+ <td class="status">ERROR</td>
71
+ </tr>
72
+ <tr class="check pass">
73
+ <td class="name">Some threshold</td>
74
+ <td class="message"></td>
75
+ <td class="status">PASS</td>
76
+ </tr>
77
+ </table>
78
+
79
+ === Nagios Integration
80
+ Use Updawg to easily script nagios checks in Ruby. Create your checks as usual and call #nagios_report!
81
+ on the result. Updawg will print a status line formatted for nagios and exit with an appropriate status code.
82
+
83
+ == Note on Patches/Pull Requests
84
+
85
+ * Fork the project.
86
+ * Create a feature branch and make your feature addition or bug fix there.
87
+ * Add tests for it. This is important so I don't break it in a
88
+ future version unintentionally.
89
+ * Commit, do not mess with rakefile, version, or history.
90
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
91
+ * Send me a pull request.
92
+
93
+ == Authors
94
+
95
+ Matt Griffin http://github.com/betamatt (matt@griffinonline.org)
96
+
97
+ == Copyright
98
+
99
+ Copyright (c) 2010 Viximo, Inc. See LICENSE for details.
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'bundler'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "updawg"
9
+ gem.summary = %Q{A simple way to see what's up, dawg (and what's not.)}
10
+ gem.email = "matt@griffinonline.org"
11
+ gem.homepage = "http://github.com/Viximo/updawg"
12
+ gem.authors = ["Matt Griffin"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "updawg #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.5
@@ -0,0 +1,36 @@
1
+ require 'updawg/monitor'
2
+ require 'updawg/checks'
3
+ require 'updawg/results'
4
+
5
+ module Updawg
6
+
7
+ class Configuration
8
+ attr_accessor :error_text, :warning_text, :success_text, :timeout
9
+
10
+ def initialize
11
+ self.error_text = 'ERROR'
12
+ self.warning_text = 'WARNING'
13
+ self.success_text = 'PASS'
14
+ self.timeout = 30
15
+ end
16
+ end
17
+
18
+ class << self
19
+ attr_accessor :configuration
20
+ end
21
+ self.configuration = Configuration.new
22
+
23
+ class << self
24
+ def configure
25
+ self.configuration ||= Configuration.new
26
+ yield(configuration) if block_given?
27
+ configuration
28
+ end
29
+
30
+ def monitor(&block)
31
+ monitor = Updawg::Monitor.new
32
+ monitor.instance_eval(&block)
33
+ monitor
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ require 'updawg/checks/check'
2
+ require 'updawg/checks/deviation_check'
3
+ require 'updawg/checks/threshold_check'
@@ -0,0 +1,67 @@
1
+ module Updawg
2
+ begin
3
+ require 'system_timer'
4
+ Timeout = SystemTimer
5
+ rescue LoadError
6
+ module Timeout
7
+ class << self
8
+ public :timeout
9
+ end
10
+ end
11
+ end
12
+
13
+ class Check
14
+ attr_reader :name
15
+
16
+ def initialize(name, options = {}, &block)
17
+ @name = name
18
+ @block = block
19
+ @timeout = options.delete(:timeout) || Updawg.configuration.timeout
20
+ end
21
+
22
+ def perform
23
+ result = execute_check
24
+ return result if result.kind_of?(Result)
25
+ pass
26
+ rescue RuntimeError => e
27
+ error e.message
28
+ end
29
+
30
+ def pass(message = nil)
31
+ Updawg::Result.new(name, :pass, message)
32
+ end
33
+
34
+ def warning(message = nil)
35
+ Updawg::Result.new(name, :warning, message)
36
+ end
37
+
38
+ def error(message = nil)
39
+ Updawg::Result.new(name, :error, message)
40
+ end
41
+
42
+ def pass!(message = nil)
43
+ raise ResultException.new(Updawg::Result.new(name, :pass, message))
44
+ end
45
+
46
+ def warning!(message = nil)
47
+ raise ResultException.new(Updawg::Result.new(name, :warning, message))
48
+ end
49
+
50
+ def error!(message = nil)
51
+ raise ResultException.new(Updawg::Result.new(name, :error, message))
52
+ end
53
+
54
+
55
+ private
56
+
57
+ def execute_check
58
+ begin
59
+ Updawg::Timeout.timeout(@timeout) { instance_eval(&@block) } if @block
60
+ rescue ::Timeout::Error
61
+ raise 'timeout'
62
+ rescue ResultException => exception
63
+ exception.result
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ require 'aggregate'
2
+
3
+ module Updawg
4
+ class DeviationCheck < Check
5
+ def initialize(name, options = {}, &block)
6
+ super(name, options, &block)
7
+
8
+ unknown_keys = options.keys - [:deviates_high, :deviates_low, :scale_factor, :smoothing].flatten
9
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
10
+
11
+ @options = options
12
+ @scale_factor = options.fetch(:scale_factor, 2)
13
+ @smoothing = options.fetch(:smoothing, true)
14
+ end
15
+
16
+ def scale_factor
17
+ @scale_factor
18
+ end
19
+
20
+ def smoothing
21
+ @smoothing
22
+ end
23
+
24
+ private
25
+
26
+ def execute_check
27
+ values = @block.call
28
+ sample = values.pop
29
+
30
+ return pass('Not enough data.') if sample.nil? || values.size < 2
31
+
32
+ aggregate = Aggregate.new
33
+ values.each {|v| aggregate << v}
34
+ std_dev = aggregate.std_dev
35
+
36
+ mean = aggregate.mean
37
+ min = mean - std_dev
38
+ max = mean + std_dev
39
+
40
+ if smoothing
41
+ without_outliers = values.select{|v| v >= min && v <= max}
42
+ smoothed_aggregate = Aggregate.new
43
+ without_outliers.each {|v| smoothed_aggregate << v}
44
+ mean = smoothed_aggregate.mean
45
+ std_dev = smoothed_aggregate.std_dev
46
+ end
47
+
48
+ min = mean - scale_factor * std_dev
49
+ max = mean + scale_factor * std_dev
50
+
51
+ return low(sample, min) if sample < min
52
+ return high(sample, max) if sample > max
53
+ return pass("#{sample} within expected range (#{min..max})")
54
+ end
55
+
56
+ def low(value, min)
57
+ send(@options.fetch(:deviates_low, :error), "#{value} is below expected minimum (#{min})")
58
+ end
59
+
60
+ def high(value, max)
61
+ send(@options.fetch(:deviates_high, :error), "#{value} is above expected maximum (#{max})")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ module Updawg
2
+ class ThresholdCheck < Check
3
+ def initialize(name, options = {}, &block)
4
+ super(name, options, &block)
5
+
6
+ unknown_keys = options.keys - [:error_under, :error_over, :warning_under, :warning_over].flatten
7
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
8
+
9
+ @options = options
10
+ end
11
+
12
+ private
13
+
14
+ def execute_check
15
+ value = @block.call
16
+
17
+ return under(:error, value, @options[:error_under]) if @options[:error_under] &&
18
+ value < @options[:error_under]
19
+ return over(:error, value, @options[:error_over]) if @options[:error_over] &&
20
+ value > @options[:error_over]
21
+ return under(:warning, value, @options[:warning_under]) if @options[:warning_under] &&
22
+ value < @options[:warning_under]
23
+ return over(:warning, value, @options[:warning_over]) if @options[:warning_over] &&
24
+ value > @options[:warning_over]
25
+ pass("#{value} OK")
26
+ end
27
+
28
+ def under(type, value, limit)
29
+ send(type, "Threshold lower bound exceeded: Got #{value}, expected > #{limit}")
30
+ end
31
+
32
+ def over(type, value, limit)
33
+ send(type, "Threshold upper bound exceeded: Got #{value}, expected < #{limit}")
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ module Updawg
2
+ class Monitor
3
+ attr_accessor :checks
4
+
5
+ def initialize
6
+ self.checks = []
7
+ end
8
+
9
+ def check(name, options = {}, &block)
10
+ check = Updawg::Check.new(name, options, &block).tap do |check|
11
+ self.checks << check
12
+ end
13
+ end
14
+
15
+ def threshold(name, options = {}, &block)
16
+ check = Updawg::ThresholdCheck.new(name, options, &block).tap do |check|
17
+ self.checks << check
18
+ end
19
+ end
20
+
21
+ def deviation(name, options = {}, &block)
22
+ Updawg::DeviationCheck.new(name, options, &block).tap do |check|
23
+ self.checks << check
24
+ end
25
+ end
26
+
27
+ def perform
28
+ results = ResultSet.new
29
+ checks.each{|c| results << c.perform}
30
+ results
31
+ end
32
+ end
33
+ end