what 0.2.7 → 0.3.0

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