what 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 76f5b8ecf552ae3e9be47ddb3dac0bc0563a1fec
4
+ data.tar.gz: d09ff6b7677d83e629bab958b5d5c4802505714b
5
+ SHA512:
6
+ metadata.gz: e2664f1e98896d28f1d5f55b2f28bf83ff781f3d1d2529ebbffe9c67048e2de981a6ef1bf8618d3431596a5614f6796f5019d95c84a21b1b521b2f7545bd0cfd
7
+ data.tar.gz: b471ae59e93c4d062f88aac1d5bfe8b59fc56e7e1c6c733bfd75c57e3003f7aa4aaa14cd4a201a8c2ff8fb2c821a207e9b84316f4f57edbffe719c4446bff169
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem "pry"
4
+
3
5
  # Specify your gem's dependencies in what.gemspec
4
6
  gemspec
data/Rakefile CHANGED
@@ -1,2 +1,32 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ # Example session:
5
+ # [1] pry(main)> mod = What::Modules::Disk.new({}, nil)
6
+ # => #<Celluloid::ActorProxy(What::Modules::Disk:0x3fe77443812c) ... >
7
+ # [2] pry(main)> mod.check
8
+ # => {"size"=>"391Gi",
9
+ # "used"=>"251Gi",
10
+ # "avail"=>"139Gi",
11
+ # "use%"=>65,
12
+ # "warning%"=>90,
13
+ # "alert%"=>99,
14
+ # "regexp"=>"/$"}
15
+ # [3] pry(main)> mod.status
16
+ # => {"type"=>"disk",
17
+ # "health"=>"ok",
18
+ # "details"=>
19
+ # {"size"=>"391Gi",
20
+ # "used"=>"251Gi",
21
+ # "avail"=>"139Gi",
22
+ # "use%"=>65,
23
+ # "warning%"=>90,
24
+ # "alert%"=>99,
25
+ # "regexp"=>"/$"}}
26
+ desc "Start a REPL with all Bunch modules loaded"
27
+ task :repl do
28
+ require "pry"
29
+ require "what"
30
+ What::Modules.load_all
31
+ Pry.start
32
+ end
@@ -15,10 +15,10 @@ class What::Modules::CustomModule < What::Modules::Base
15
15
  @hellos = 1
16
16
  end
17
17
 
18
- # The check! method is called every Config['interval'] seconds. It should
18
+ # The check method is called every Config['interval'] seconds. It should
19
19
  # collect whatever information is needed and put it into instance vars.
20
20
  # (optional)
21
- def check!
21
+ def check
22
22
  @hellos = Kernel.rand(4) + 1
23
23
  end
24
24
 
data/lib/what/config.rb CHANGED
@@ -1,6 +1,3 @@
1
-
2
- require 'yaml'
3
-
4
1
  module What
5
2
  class Config
6
3
  DEFAULTS = {
data/lib/what/helpers.rb CHANGED
@@ -32,9 +32,5 @@ module What
32
32
  word.downcase!
33
33
  word
34
34
  end
35
-
36
- def self.curl(uri)
37
- yield(Excon.get(uri).body)
38
- end
39
35
  end
40
36
  end
@@ -1,33 +1,73 @@
1
1
  module What
2
2
  class Modules::Base
3
+ include Celluloid
3
4
 
4
5
  attr_reader :interval
5
6
 
6
- def initialize(params={})
7
+ def initialize(params, output)
7
8
  defaults = (self.class)::DEFAULTS rescue {}
8
9
  @name = params['name']
9
10
  @config = defaults.merge(params['config'] || {})
10
11
  @max = params['max'] || 'alert'
11
- # Use global interval setting if not set on a
12
- # per module basis
13
12
  @interval = params['interval'] || Config['interval']
13
+ @output = output
14
+ @failures = 0
14
15
  initialize_module
15
16
  end
16
17
 
17
18
  def initialize_module
18
19
  end
19
20
 
21
+ def start_monitoring
22
+ @output[identifier] = nil
23
+
24
+ loop do
25
+ check
26
+ @output[identifier] = status
27
+ sleep interval
28
+ end
29
+ rescue Exception => e
30
+ # stop looping -- the Monitor will restart if necessary
31
+ @failures += 1
32
+ output = shared_status.merge(
33
+ "health" => "alert",
34
+ "error" => "#{e.class}: #{e.message}",
35
+ "failures" => @failures
36
+ )
37
+ puts "Error in module:\n#{YAML.dump(output)}"
38
+ @output[identifier] = output
39
+ end
40
+
20
41
  def name
21
42
  Helpers.underscore(self.class.name.split('::').last)
22
43
  end
23
44
 
24
- def check!
45
+ # A unique identifier that we can use to match results up with this
46
+ # specific instance. This is necessary because Celluloid weirds identity.
47
+ def identifier
48
+ object_id
49
+ end
50
+
51
+ # This method should be overridden. The implementation here is for
52
+ # backwards compatibility with older modules that implement check!
53
+ # instead of check.
54
+ def check
55
+ check!
56
+ end
57
+
58
+ # This method must be overridden.
59
+ def health
60
+ raise "Module #{self.class.name} doesn't override 'health'"
61
+ end
62
+
63
+ # This method may be overridden, to provide extra details based on the
64
+ # results of the check.
65
+ def details
66
+ {}
25
67
  end
26
68
 
27
69
  def status
28
- status = {}
29
- status['name'] = @name if @name
30
- status['type'] = name # FIXME
70
+ status = shared_status
31
71
  status['health'] = if @max == 'ok' || health == 'ok'
32
72
  'ok'
33
73
  elsif @max == 'warning' || health == 'warning'
@@ -39,12 +79,11 @@ module What
39
79
  status
40
80
  end
41
81
 
42
- def health
43
- raise "Module #{self.class.name} doesn't override 'health'"
44
- end
45
-
46
- def details
47
- {}
82
+ def shared_status
83
+ status = {}
84
+ status['name'] = @name if @name
85
+ status['type'] = name # FIXME
86
+ status
48
87
  end
49
88
  end
50
89
  end
@@ -10,7 +10,7 @@ module What
10
10
  @regexp = Regexp.new(@config['regexp'])
11
11
  end
12
12
 
13
- def check!
13
+ def check
14
14
  line = `df -h`.split("\n").grep(@regexp).first
15
15
  @info = if line
16
16
  fields = line.split(/\s+/)
@@ -8,7 +8,7 @@ module What
8
8
  @paths = {}
9
9
  end
10
10
 
11
- def check!
11
+ def check
12
12
  @config['paths'].each do |path|
13
13
  if Dir[path].count == 0
14
14
  @paths[path] = false
@@ -5,7 +5,7 @@ module What
5
5
  'alert' => '90%'
6
6
  }
7
7
 
8
- def check!
8
+ def check
9
9
  fields = self.memory_details
10
10
  @info = if fields.size == 2
11
11
  {
@@ -5,7 +5,7 @@ module What
5
5
  @processes = []
6
6
  end
7
7
 
8
- def check!
8
+ def check
9
9
  @processes = `ps aux`.split("\n").grep(@regexp).map do |ln|
10
10
  ln =~ /^\w+\s+(\d+).*(\d+:\d\d(?:\.\d\d)?) (.*)$/
11
11
  {'pid' => $1, 'cpu_time' => $2, 'proctitle' => $3.strip}
@@ -9,7 +9,7 @@ module What
9
9
  @unicorns = []
10
10
  end
11
11
 
12
- def check!
12
+ def check
13
13
  @unicorns = `ps aux`.split("\n").grep(/unicorn_rails worker/).map do |ln|
14
14
  ln =~ /^\w+\s+(\d+).*(\d+:\d\d(?:\.\d\d)?) unicorn/
15
15
  {'pid' => $1, 'cpu_time' => $2}
@@ -8,9 +8,10 @@ module What
8
8
  @whats = {}
9
9
  end
10
10
 
11
- def check!
11
+ def check
12
12
  @config.map do |name, uri|
13
- Helpers.curl(uri) { |body| @whats[name] = JSON.parse(body) rescue nil }
13
+ body = open(uri).read
14
+ @whats[name] = JSON.parse(body) rescue nil
14
15
  end
15
16
  end
16
17
 
data/lib/what/modules.rb CHANGED
@@ -1,58 +1,29 @@
1
- require 'what/config'
1
+ require "what/config"
2
2
 
3
3
  module What
4
4
  module Modules
5
-
6
- def self.default_modules_full_path
7
- File.join(File.dirname(__FILE__), 'modules')
8
- end
9
-
10
- def self.config_module_full_paths
11
- (Config['module_paths'] || []).map do |p|
12
- if p.match(%r(^/))
13
- p
14
- else
15
- File.join(Config['base'], p)
16
- end
17
- end
18
- end
19
-
20
- # load all modules defined in what/modules, in addition to any paths
21
- # specified in the config file.
5
+ # Load all modules from any paths specified in the config file.
22
6
  def self.load_all
23
- require 'what/modules/base'
24
-
25
- require_dir(self.default_modules_full_path)
26
-
27
- self.config_module_full_paths.each do |path|
7
+ config_module_full_paths.each do |path|
28
8
  require_dir(path)
29
9
  end
30
10
  end
31
11
 
32
- def self.load_module(module_name)
33
- require 'what/modules/base'
34
-
35
- loaded = false
36
-
37
- short_module_name = module_name.sub(/\.rb$/, '')
12
+ def self.config_module_full_paths
13
+ paths = Config["module_paths"] || []
38
14
 
39
- ([self.default_modules_full_path] + self.config_module_full_paths).each do |path|
40
- candidate = File.join(path, short_module_name)
41
- if File.exists?("#{candidate}.rb")
42
- loaded = require candidate
43
- end
15
+ paths.map do |path|
16
+ path.include?("/") ? path : File.join(Config["base"], path)
44
17
  end
45
-
46
- loaded
47
18
  end
48
19
 
49
- private
50
-
51
20
  def self.require_dir(path)
52
21
  Dir[File.join(path, '*.rb')].each do |fn|
53
22
  require fn
54
23
  end
55
24
  end
56
-
57
25
  end
58
26
  end
27
+
28
+ require "what/modules/base"
29
+ What::Modules.require_dir File.expand_path("../modules", __FILE__)
data/lib/what/monitor.rb CHANGED
@@ -1,57 +1,46 @@
1
-
2
- require 'what/modules'
3
- require 'what/helpers'
4
-
5
1
  module What
6
2
  class Monitor
7
-
8
- # don't worry, these method names are ironic
9
-
10
- def initialize(modules)
11
- @modules = modules.map do |mod|
12
- name = Helpers.camelize(mod.delete('type'))
13
- Modules.const_get(name).new(mod)
14
- end
15
- end
16
-
3
+ # don't worry, this method name is ironic
17
4
  def go!
18
- Thread.abort_on_exception = true
19
- @threads = @modules.collect do |mod|
20
- Thread.new do
21
- loop do
22
- mod.check!
23
- Thread.current[:status] = mod.status
24
- sleep mod.interval
25
- end
26
- end
5
+ # For now, use a hash as the centralized collection point for data. We
6
+ # can't use a method on the module because Celluloid will block that call
7
+ # until after whatever method is currently running, which could be
8
+ # unacceptably slow. We could use a fancy thread-safe data structure
9
+ # instead of a hash, but the GIL means hash assignment and reading are
10
+ # safe as long as we're on MRI.
11
+ @results = {}
12
+ @modules = Config['modules'].map do |config_hash|
13
+ name = Helpers.camelize(config_hash.delete('type'))
14
+ klass = Modules.const_get(name)
15
+ instance = klass.new(config_hash, @results)
16
+ {
17
+ class: klass,
18
+ config_hash: config_hash,
19
+ module: instance,
20
+ id: instance.identifier
21
+ }
27
22
  end
28
- end
29
23
 
30
- def do_it(interval)
31
- loop do
32
- statuses = []
33
- healths = []
34
- @threads.each do |th|
35
- if th[:status]
36
- healths << th[:status]['health']
37
- statuses << th[:status]
38
- end
39
- end
40
- yield Helpers.overall_health(healths), statuses
41
- sleep interval
24
+ @modules.each do |mod|
25
+ mod[:module].async.start_monitoring
42
26
  end
43
27
  end
44
28
 
45
- def self.go!
46
- monitor = new Config['modules']
47
- monitor.go!
48
- Thread.new do
49
- monitor.do_it(Config['interval']) do |health, statuses|
50
- Status['health'] = health
51
- Status['details'] = statuses
29
+ def status
30
+ statuses = []
31
+ healths = []
32
+
33
+ @modules.each do |mod|
34
+ status = @results[mod[:id]] || {"health" => nil}
35
+ healths << status['health']
36
+ statuses << status
37
+
38
+ if status["error"] && status["failures"] < 6
39
+ mod[:module].async.start_monitoring
52
40
  end
53
41
  end
54
- end
55
42
 
43
+ {"health" => Helpers.overall_health(healths), "details" => statuses}
44
+ end
56
45
  end
57
46
  end
data/lib/what/server.rb CHANGED
@@ -3,17 +3,19 @@ module What
3
3
  def initialize
4
4
  Modules.load_all
5
5
  Formatters.load_all
6
- Monitor.go!
6
+ @monitor = Monitor.new
7
+ @monitor.go!
7
8
  end
8
9
 
9
10
  def call(env)
10
11
  if Config['secret_token'] && Config['secret_token'] != env['QUERY_STRING']
11
12
  [403, {'Content-Type' => 'text/plain'}, ["403 Forbidden\n"]]
12
13
  else
14
+ status = @monitor.status
13
15
  [
14
- Status['health'] != 'alert' ? 200 : 503,
16
+ status['health'] != 'alert' ? 200 : 503,
15
17
  {'Content-Type' => Formatter.mime},
16
- [Formatter.format(Status.all)]
18
+ [Formatter.format(status)]
17
19
  ]
18
20
  end
19
21
  end
data/lib/what/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module What
2
- VERSION = "0.2.7"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/what.rb CHANGED
@@ -5,7 +5,8 @@ require 'json'
5
5
  require 'yaml'
6
6
  require 'webrick'
7
7
  require 'rack'
8
- require 'excon'
8
+ require 'open-uri'
9
+ require 'celluloid'
9
10
 
10
11
  require 'what/config'
11
12
  require 'what/formatter'
@@ -14,5 +15,4 @@ require 'what/helpers'
14
15
  require 'what/modules'
15
16
  require 'what/monitor'
16
17
  require 'what/server'
17
- require 'what/status'
18
18
  require 'what/version'
data/what.gemspec CHANGED
@@ -24,6 +24,6 @@ EOS
24
24
  s.require_paths = ["lib"]
25
25
 
26
26
  s.add_dependency("rack", ">= 1.1.2")
27
- s.add_dependency("excon", "~> 0.13.4")
28
27
  s.add_dependency("json")
28
+ s.add_dependency("celluloid")
29
29
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: what
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.2.7
4
+ version: 0.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Ryan Fitzgerald
@@ -11,73 +10,61 @@ authors:
11
10
  autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2012-12-11 00:00:00.000000000 Z
13
+ date: 2013-07-15 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
- version_requirements: !ruby/object:Gem::Requirement
16
+ name: rack
17
+ requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - ! '>='
19
+ - - '>='
20
20
  - !ruby/object:Gem::Version
21
21
  version: 1.1.2
22
- none: false
23
- name: rack
24
22
  type: :runtime
25
23
  prerelease: false
26
- requirement: !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
27
25
  requirements:
28
- - - ! '>='
26
+ - - '>='
29
27
  - !ruby/object:Gem::Version
30
28
  version: 1.1.2
31
- none: false
32
29
  - !ruby/object:Gem::Dependency
33
- version_requirements: !ruby/object:Gem::Requirement
30
+ name: json
31
+ requirement: !ruby/object:Gem::Requirement
34
32
  requirements:
35
- - - ~>
33
+ - - '>='
36
34
  - !ruby/object:Gem::Version
37
- version: 0.13.4
38
- none: false
39
- name: excon
35
+ version: '0'
40
36
  type: :runtime
41
37
  prerelease: false
42
- requirement: !ruby/object:Gem::Requirement
38
+ version_requirements: !ruby/object:Gem::Requirement
43
39
  requirements:
44
- - - ~>
40
+ - - '>='
45
41
  - !ruby/object:Gem::Version
46
- version: 0.13.4
47
- none: false
42
+ version: '0'
48
43
  - !ruby/object:Gem::Dependency
49
- version_requirements: !ruby/object:Gem::Requirement
44
+ name: celluloid
45
+ requirement: !ruby/object:Gem::Requirement
50
46
  requirements:
51
- - - ! '>='
47
+ - - '>='
52
48
  - !ruby/object:Gem::Version
53
49
  version: '0'
54
- none: false
55
- name: json
56
50
  type: :runtime
57
51
  prerelease: false
58
- requirement: !ruby/object:Gem::Requirement
52
+ version_requirements: !ruby/object:Gem::Requirement
59
53
  requirements:
60
- - - ! '>='
54
+ - - '>='
61
55
  - !ruby/object:Gem::Version
62
56
  version: '0'
63
- none: false
64
- description: ! 'What uses WEBrick to serve a JSON object representing the state of
65
- services
66
-
57
+ description: |
58
+ What uses WEBrick to serve a JSON object representing the state of services
67
59
  running on a machine. It currently includes modules for monitoring Unicorn
68
-
69
60
  workers, checking for the existence of files and processes, and combining
70
-
71
61
  the output of other What servers.
72
-
73
- '
74
62
  email:
75
63
  - rwfitzge@gmail.com
76
64
  - rpjlower@gmail.com
77
65
  - colin.t.curtin@gmail.com
78
66
  executables:
79
67
  - what
80
- - whatch
81
68
  - whatsup
82
69
  extensions: []
83
70
  extra_rdoc_files: []
@@ -88,12 +75,10 @@ files:
88
75
  - README.md
89
76
  - Rakefile
90
77
  - bin/what
91
- - bin/whatch
92
78
  - bin/whatsup
93
79
  - example/modules/custom_module.rb
94
80
  - example/what.yml
95
81
  - lib/what.rb
96
- - lib/what/ch.rb
97
82
  - lib/what/config.rb
98
83
  - lib/what/formatter.rb
99
84
  - lib/what/formatters.rb
@@ -111,32 +96,30 @@ files:
111
96
  - lib/what/modules/what.rb
112
97
  - lib/what/monitor.rb
113
98
  - lib/what/server.rb
114
- - lib/what/status.rb
115
99
  - lib/what/sup.rb
116
100
  - lib/what/version.rb
117
101
  - what.gemspec
118
102
  homepage: http://academia.edu/
119
103
  licenses: []
104
+ metadata: {}
120
105
  post_install_message:
121
106
  rdoc_options: []
122
107
  require_paths:
123
108
  - lib
124
109
  required_ruby_version: !ruby/object:Gem::Requirement
125
110
  requirements:
126
- - - ! '>='
111
+ - - '>='
127
112
  - !ruby/object:Gem::Version
128
113
  version: '0'
129
- none: false
130
114
  required_rubygems_version: !ruby/object:Gem::Requirement
131
115
  requirements:
132
- - - ! '>='
116
+ - - '>='
133
117
  - !ruby/object:Gem::Version
134
118
  version: '0'
135
- none: false
136
119
  requirements: []
137
120
  rubyforge_project: what
138
- rubygems_version: 1.8.24
121
+ rubygems_version: 2.0.2
139
122
  signing_key:
140
- specification_version: 3
123
+ specification_version: 4
141
124
  summary: Simple server monitoring tool
142
125
  test_files: []
data/bin/whatch DELETED
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- require 'optparse'
5
- require 'what/version'
6
- require 'what/ch'
7
-
8
- options = Struct.new(:interval, :all).new
9
-
10
- opts = OptionParser.new do |opts|
11
- opts.banner = "Usage: what [options] module_name1 [ module_name2 ... ]"
12
- opts.separator ""
13
-
14
- opts.on_tail('-v', '--version', 'Show the version number.') do
15
- puts What::VERSION
16
- exit
17
- end
18
-
19
- opts.on_tail('-h', '--help', 'Show this message.') do
20
- puts opts
21
- exit
22
- end
23
-
24
- opts.on('-c', '--config FILE', 'Specify the location of the YAML config file.') do |fn|
25
- What::Config.load(fn)
26
- end
27
-
28
- opts.on('-n', '--interval SECS', 'Run module check every SECS seconds.') do |secs|
29
- options.interval = secs
30
- end
31
-
32
- opts.on_tail('-a', '--all', 'Load all modules specified in config option (which must be present too).') do
33
- options.all = true
34
- end
35
-
36
- end
37
- opts.parse!(ARGV)
38
-
39
- What::Ch.run(ARGV, options)
data/lib/what/ch.rb DELETED
@@ -1,65 +0,0 @@
1
- ## Impletments the whatsch CLI for running modules from the command line
2
-
3
- require 'what/config'
4
- require 'what/modules'
5
- require 'what/monitor'
6
-
7
- module What
8
- class Ch
9
-
10
- def self.run(module_names, opts)
11
- new(module_names, opts).run
12
- end
13
-
14
- def initialize(module_names, opts)
15
- if opts.all && !Config.loaded?
16
- self.error "Use all: No config given to read for module names"
17
- end
18
-
19
- if 0 == module_names.size && !opts.all
20
- self.error "No module names given"
21
- end
22
-
23
- @modules = if What::Config.loaded?
24
- if opts.all
25
- What::Config['modules']
26
- else
27
- What::Config['modules'].select{|m| module_names.include?(m['name'])}
28
- end
29
- else
30
- What::Config.set_defaults
31
- module_names.uniq.map{|mn| {'type' => mn, 'name' => mn} }
32
- end
33
-
34
- if 0 == @modules.size
35
- self.error "No modules to use"
36
- end
37
-
38
- @interval = (opts.interval || 10).to_i
39
- end
40
-
41
- def run
42
- @modules.each do |m|
43
- unless Modules.load_module(m['type'])
44
- self.error("Unknown module #{m['type']}")
45
- end
46
- end
47
- @monitor = Monitor.new(@modules)
48
- @monitor.go!
49
- @monitor.do_it(@interval) do |health, statuses|
50
- self.report health, statuses
51
- end
52
- end
53
-
54
- def report(health, statuses)
55
- $stdout.puts health
56
- $stdout.puts statuses.inspect
57
- end
58
-
59
- def error(message)
60
- $stderr.puts message
61
- exit(1)
62
- end
63
-
64
- end
65
- end
data/lib/what/status.rb DELETED
@@ -1,17 +0,0 @@
1
- module What
2
- class Status
3
- @status = {}
4
-
5
- def self.[](attr)
6
- @status[attr]
7
- end
8
-
9
- def self.[]=(attr, val)
10
- @status[attr] = val
11
- end
12
-
13
- def self.all
14
- @status
15
- end
16
- end
17
- end