watchmonkey_cli 1.4 → 1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +62 -7
- data/VERSION +1 -1
- data/bin/watchmonkey +1 -0
- data/lib/watchmonkey_cli/application/configuration.rb +34 -3
- data/lib/watchmonkey_cli/application/core.rb +155 -0
- data/lib/watchmonkey_cli/application/dispatch.rb +1 -0
- data/lib/watchmonkey_cli/application/output_helper.rb +30 -0
- data/lib/watchmonkey_cli/application.rb +7 -160
- data/lib/watchmonkey_cli/checker.rb +12 -8
- data/lib/watchmonkey_cli/checkers/ftp_availability.rb +2 -2
- data/lib/watchmonkey_cli/checkers/www_availability.rb +3 -3
- data/lib/watchmonkey_cli/{helpers.rb → helper.rb} +1 -1
- data/lib/watchmonkey_cli/hooks/platypus.rb +84 -3
- data/lib/watchmonkey_cli/hooks/requeue.rb +1 -1
- data/lib/watchmonkey_cli/version.rb +1 -1
- data/lib/watchmonkey_cli.rb +3 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 856c3354f0039a351a73dfd0175af79e387078fb
|
4
|
+
data.tar.gz: 07e20ff5db1bc068d50784b826a05d412b5a7836
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e19791d169aa0d08700b7f85a2b38932271f640b676737f50ecd0beab86c87486068967f5a3ed896925d4fc43548a09932bed2cec201d8fc9ba58d62ed4d3538
|
7
|
+
data.tar.gz: 1fa78746dafc66551783806f3ea963badf8aae01a61fdc23fdf349655449721c6203837607013506279d513c8f6cc0f7b44c00de008b22f91b58a7614d658077
|
data/README.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# Watchmonkey CLI
|
2
2
|
|
3
|
-
|
3
|
+
Watchmonkey is a very simple tool to monitor resources with Ruby without the need of installing agents on the systems you want to monitor. To accomplish this the application polls information via SSH or other endpoints (e.g. websites, FTP access). It's suitable for small to medium amounts of services.
|
4
|
+
|
5
|
+
Before looking any further you might want to know:
|
6
|
+
|
7
|
+
* There is no escalation or notification system but you may add it yourself
|
8
|
+
* I created this for being used with [Platypus](http://sveinbjorn.org/platypus) hence the [Platypus Hook](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/platypus.rb)
|
9
|
+
* This is how the text output looks like: [Screenshot](http://imgur.com/8yLYnKb)
|
10
|
+
* This is how the Platypus support looks like: [ProgressBar](http://imgur.com/Vd8ZD7A) [HTML/WebView](http://imgur.com/5FwmWFZ)
|
4
11
|
|
5
12
|
---
|
6
13
|
|
@@ -9,24 +16,73 @@ If you need help or have problems [open an issue](https://github.com/2called-cha
|
|
9
16
|
|
10
17
|
|
11
18
|
## Features
|
12
|
-
*
|
13
|
-
|
19
|
+
* Monitor external resources (Web, FTP, Server health via SSH)
|
20
|
+
* Run once or loop forever with ReQueue (define intervals globally, per checker or per single test)
|
21
|
+
* Includes a selection of buildin checkers (basic *nix health, WWW availability & SSL expiration, FTP)
|
14
22
|
|
15
23
|
|
16
24
|
## Requirements
|
17
25
|
* Ruby >= 2.0
|
18
|
-
* Unixoid OS (such as Ubuntu/Debian, OS X, maybe others) or Windows
|
26
|
+
* Unixoid OS (such as Ubuntu/Debian, OS X, maybe others) or Windows (not recommended)
|
19
27
|
* something you want to monitor
|
20
28
|
|
21
29
|
|
22
|
-
|
23
30
|
## Installation
|
24
31
|
* `gem install watchmonkey_cli`
|
25
32
|
* `watchmonkey --generate-config [name=default]`
|
33
|
+
* Edit the created file to fit your needs
|
34
|
+
* Run `watchmonkey`
|
35
|
+
* Check out the additional features below (e.g. ReQueue)
|
36
|
+
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
To get a list of available options invoke Watchmonkey with the `--help` or `-h` option:
|
40
|
+
|
41
|
+
Usage: watchmonkey [options]
|
42
|
+
# Application options
|
43
|
+
--generate-config [myconfig] Generates a example config in ~/.watchmonkey
|
44
|
+
-l, --log [file] Log to file, defaults to ~/.watchmonkey/logs/watchmonkey.log
|
45
|
+
-t, --threads [NUM] Amount of threads to be used for checking (default: 10)
|
46
|
+
-s, --silent Only print errors and infos
|
47
|
+
-q, --quiet Only print errors
|
48
|
+
|
49
|
+
# General options
|
50
|
+
-d, --debug [lvl=1] Enable debug output
|
51
|
+
-m, --monochrome Don't colorize output
|
52
|
+
-h, --help Shows this help
|
53
|
+
-v, --version Shows version and other info
|
54
|
+
-z Do not check for updates on GitHub (with -v/--version)
|
55
|
+
--dump-core for developers
|
56
|
+
|
57
|
+
|
58
|
+
## Documentation
|
59
|
+
Writing a documentation takes time and I'm not sure if anyone is even interested in this tool. If there is interest I will write a documentation but for now this Readme must suffice. If you need help just [open an issue](https://github.com/2called-chaos/watchmonkey_cli/issues/new) :)
|
60
|
+
|
61
|
+
|
62
|
+
## Create configs
|
63
|
+
Use the `--generate-config` option or refer to the [configuration template](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/application/configuration.tpl) for examples and documentation.
|
26
64
|
|
27
65
|
|
28
66
|
## Deactivate configs
|
29
|
-
If you want to deactivate configs
|
67
|
+
If you want to deactivate single configs just rename the file to start with two underscores (e.g.: `__example.rb`).
|
68
|
+
|
69
|
+
|
70
|
+
## Application configuration
|
71
|
+
If you want to add custom checkers, hooks or change default settings you can create `~/.watchmonkey/config.rb`. The file will be eval'd in the application object's context. Take a look at the [example configuration file](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/application.rb) and [application.rb](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/doc/config_example.rb).
|
72
|
+
|
73
|
+
|
74
|
+
## Custom checkers
|
75
|
+
If you want to monitor something that is not covered by the buildin handlers you can create your own, it's not that hard and should be a breeze if you are used to Ruby. All descendants of the `WatchmonkeyCli::Checker` class will be initialized and are usable in the application. Documentation is thin but you can take a look at the [example checker](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/doc/checker_example.rb), the [buildin checkers](https://github.com/2called-chaos/watchmonkey_cli/tree/master/lib/watchmonkey_cli/checkers) or just [open an issue](https://github.com/2called-chaos/watchmonkey_cli/issues/new) and I might just implement it real quick.
|
76
|
+
|
77
|
+
|
78
|
+
## Additional Features
|
79
|
+
|
80
|
+
### ReQueue
|
81
|
+
By default Watchmonkey will run all tests once and then exit. This addon will enable Watchmonkey to run in a loop and run tests on a periodic interval.
|
82
|
+
Since this seems like a core feature it might get included directly into Watchmonkey but for now take a look at the documentation in the [ReQueue source code](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/requeue.rb) for integration instructions.
|
83
|
+
|
84
|
+
### Platypus support
|
85
|
+
[Platypus](http://sveinbjorn.org/platypus) is a MacOS software to create dead simple GUI wrappers for scripts. There is buildin support for the interface types ProgressBar and WebView. For information look at the documentation in the [Platypus hook source code](https://github.com/2called-chaos/watchmonkey_cli/blob/master/lib/watchmonkey_cli/hooks/platypus.rb).
|
30
86
|
|
31
87
|
|
32
88
|
## Contributing
|
@@ -39,7 +95,6 @@ If you want to deactivate configs entirely just rename the file to start with tw
|
|
39
95
|
5. Create new Pull Request
|
40
96
|
|
41
97
|
|
42
|
-
|
43
98
|
## Legal
|
44
99
|
* © 2016, Sven Pachnit (www.bmonkeys.net)
|
45
100
|
* watchmonkey_cli is licensed under the MIT license.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.5
|
data/bin/watchmonkey
CHANGED
@@ -2,22 +2,53 @@ module WatchmonkeyCli
|
|
2
2
|
class Application
|
3
3
|
class Configuration
|
4
4
|
module AppHelper
|
5
|
+
def wm_cfg_path
|
6
|
+
ENV["WM_CFGDIR"].presence || File.expand_path("~/.watchmonkey")
|
7
|
+
end
|
8
|
+
|
5
9
|
def config_directory
|
6
10
|
"#{wm_cfg_path}/configs"
|
7
11
|
end
|
8
12
|
|
13
|
+
def checker_directory
|
14
|
+
"#{wm_cfg_path}/checkers"
|
15
|
+
end
|
16
|
+
|
17
|
+
def wm_cfg_configfile
|
18
|
+
"#{wm_cfg_path}/config.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def config_filename name = "default"
|
22
|
+
"#{config_directory}/#{name}.rb"
|
23
|
+
end
|
24
|
+
|
9
25
|
def config_files
|
10
26
|
Dir["#{config_directory}/**/*.rb"].reject do |file|
|
11
27
|
file.gsub(config_directory, "").split("/").any?{|fp| fp.start_with?("__") }
|
12
28
|
end
|
13
29
|
end
|
14
30
|
|
15
|
-
def
|
16
|
-
"#{
|
31
|
+
def checker_files
|
32
|
+
Dir["#{checker_directory}/**/*.rb"].reject do |file|
|
33
|
+
file.gsub(config_directory, "").split("/").any?{|fp| fp.start_with?("__") }
|
34
|
+
end
|
17
35
|
end
|
18
36
|
|
19
37
|
def load_configs!
|
20
|
-
|
38
|
+
configs = config_files
|
39
|
+
debug "Loading #{configs.length} config files from `#{config_directory}'"
|
40
|
+
configs.each {|f| Configuration.new(self, f) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_checkers!
|
44
|
+
checkers = checker_files
|
45
|
+
debug "Loading #{checkers.length} checker files from `#{checker_directory}'"
|
46
|
+
checkers.each {|f| require f }
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_appconfig
|
50
|
+
return unless File.exist?(wm_cfg_configfile)
|
51
|
+
eval File.read(wm_cfg_configfile, encoding: "utf-8"), binding, wm_cfg_configfile
|
21
52
|
end
|
22
53
|
|
23
54
|
def generate_config name = "default"
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
module Core
|
4
|
+
# ===================
|
5
|
+
# = Signal trapping =
|
6
|
+
# ===================
|
7
|
+
def trap_signals
|
8
|
+
debug "Trapping INT signal..."
|
9
|
+
Signal.trap("INT") do
|
10
|
+
$wm_runtime_exiting = true
|
11
|
+
Kernel.puts "Interrupting..."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def release_signals
|
16
|
+
debug "Releasing INT signal..."
|
17
|
+
Signal.trap("INT", "DEFAULT")
|
18
|
+
end
|
19
|
+
|
20
|
+
def haltpoint
|
21
|
+
raise Interrupt if $wm_runtime_exiting
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# ==========
|
26
|
+
# = Events =
|
27
|
+
# ==========
|
28
|
+
def hook *which, &hook_block
|
29
|
+
which.each do |w|
|
30
|
+
@hooks[w.to_sym] ||= []
|
31
|
+
@hooks[w.to_sym] << hook_block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def fire which, *args
|
36
|
+
return if @disable_event_firing
|
37
|
+
sync { debug "[Event] Firing #{which} (#{@hooks[which].try(:length) || 0} handlers) #{args.map(&:class)}", 99 }
|
38
|
+
@hooks[which] && @hooks[which].each{|h| h.call(*args) }
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# ==========
|
43
|
+
# = Logger =
|
44
|
+
# ==========
|
45
|
+
def logger_filename
|
46
|
+
"#{wm_cfg_path}/logs/watchmonkey.log"
|
47
|
+
end
|
48
|
+
|
49
|
+
def logger
|
50
|
+
sync do
|
51
|
+
@logger ||= begin
|
52
|
+
FileUtils.mkdir_p(File.dirname(@opts[:logfile]))
|
53
|
+
Logger.new(@opts[:logfile], 10, 1024000)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# =======================
|
60
|
+
# = Connection handling =
|
61
|
+
# =======================
|
62
|
+
def fetch_connection type, id, opts = {}, &initializer
|
63
|
+
if !@connections[type] || !@connections[type][id]
|
64
|
+
@connections[type] ||= {}
|
65
|
+
case type
|
66
|
+
when :loopback
|
67
|
+
@connections[type][id] = LoopbackConnection.new(id, opts, &initializer)
|
68
|
+
when :ssh
|
69
|
+
@connections[type][id] = SshConnection.new(id, opts, &initializer)
|
70
|
+
else
|
71
|
+
raise NotImplementedError, "unknown connection type `#{type}'!"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@connections[type][id]
|
75
|
+
end
|
76
|
+
|
77
|
+
def close_connections!
|
78
|
+
@connections.each do |type, clist|
|
79
|
+
clist.each{|id, con| con.close! }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# =========================
|
85
|
+
# = Queue tasks & methods =
|
86
|
+
# =========================
|
87
|
+
def enqueue checker, *a, &block
|
88
|
+
sync do
|
89
|
+
cb = block || checker.method(:check!)
|
90
|
+
evreg = @disable_event_registration
|
91
|
+
fire(:enqueue, checker, a, cb) unless evreg
|
92
|
+
@queue << [checker, a, ->(*a) {
|
93
|
+
begin
|
94
|
+
result = Checker::Result.new(checker, *a)
|
95
|
+
checker.debug(result.str_running)
|
96
|
+
checker.safe(result.str_safe) { cb.call(result, *a) }
|
97
|
+
fire(:result_dump, result, a, checker)
|
98
|
+
result.dump!
|
99
|
+
ensure
|
100
|
+
fire(:dequeue, checker, a) unless evreg
|
101
|
+
end
|
102
|
+
}]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def enqueue_sub checker, which, *args
|
107
|
+
sync do
|
108
|
+
if sec = @checkers[which.to_s]
|
109
|
+
begin
|
110
|
+
# ef_was = @disable_event_firing
|
111
|
+
er_was = @disable_event_registration
|
112
|
+
# @disable_event_firing = true
|
113
|
+
@disable_event_registration = true
|
114
|
+
sec.enqueue(*args)
|
115
|
+
ensure
|
116
|
+
# @disable_event_firing = ef_was
|
117
|
+
@disable_event_registration = er_was
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def spawn_threads_and_run!
|
124
|
+
if @opts[:threads] > 1
|
125
|
+
debug "Spawning #{@opts[:threads]} consumer threads..."
|
126
|
+
@opts[:threads].times do
|
127
|
+
@threads << Thread.new do
|
128
|
+
Thread.current.abort_on_exception = true
|
129
|
+
_queueoff
|
130
|
+
end
|
131
|
+
end
|
132
|
+
else
|
133
|
+
debug "Running threadless..."
|
134
|
+
_queueoff
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def _queueoff
|
139
|
+
while !@queue.empty? || @opts[:loop_forever]
|
140
|
+
break if $wm_runtime_exiting
|
141
|
+
item = queue.pop(true) rescue false
|
142
|
+
if item
|
143
|
+
Thread.current[:working] = true
|
144
|
+
fire(:wm_work_start, Thread.current)
|
145
|
+
sync { @processed += 1 }
|
146
|
+
item[2].call(*item[1])
|
147
|
+
Thread.current[:working] = false
|
148
|
+
fire(:wm_work_end, Thread.current)
|
149
|
+
end
|
150
|
+
sleep @opts[:loop_wait_empty] if @opts[:loop_forever] && @opts[:loop_wait_empty] && @queue.empty?
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module WatchmonkeyCli
|
2
|
+
class Application
|
3
|
+
module OutputHelper
|
4
|
+
def puts *a
|
5
|
+
sync { @opts[:stdout].send(:puts, *a) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def print *a
|
9
|
+
sync { @opts[:stdout].send(:print, *a) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def warn *a
|
13
|
+
sync { @opts[:stdout].send(:warn, *a) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug msg, lvl = 1
|
17
|
+
puts c("[DEBUG] #{msg}", :black) if @opts[:debug] && @opts[:debug] >= lvl
|
18
|
+
end
|
19
|
+
|
20
|
+
def abort msg, exit_code = 1
|
21
|
+
puts c("[ABORT] #{msg}", :red)
|
22
|
+
exit(exit_code)
|
23
|
+
end
|
24
|
+
|
25
|
+
def error msg
|
26
|
+
warn c(msg, :red)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module WatchmonkeyCli
|
2
2
|
class Application
|
3
3
|
attr_reader :opts, :checkers, :connections, :threads, :queue, :hooks, :processed
|
4
|
-
include
|
4
|
+
include Helper
|
5
|
+
include OutputHelper
|
5
6
|
include Colorize
|
7
|
+
include Core
|
6
8
|
include Dispatch
|
7
9
|
include Configuration::AppHelper
|
8
10
|
include Checker::AppHelper
|
@@ -12,7 +14,7 @@ module WatchmonkeyCli
|
|
12
14
|
# =========
|
13
15
|
def self.dispatch *a
|
14
16
|
new(*a) do |app|
|
15
|
-
app.
|
17
|
+
app.load_appconfig
|
16
18
|
app.parse_params
|
17
19
|
begin
|
18
20
|
app.dispatch
|
@@ -27,6 +29,7 @@ module WatchmonkeyCli
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def initialize env, argv
|
32
|
+
@boot = Time.current
|
30
33
|
@env, @argv = env, argv
|
31
34
|
@connections = {}
|
32
35
|
@hooks = {}
|
@@ -46,6 +49,7 @@ module WatchmonkeyCli
|
|
46
49
|
loop_wait_empty: 1, # (internal) time to wait in thread if queue is empty
|
47
50
|
silent: false, # -s flag
|
48
51
|
quiet: false, # -q flag
|
52
|
+
stdout: STDOUT, # (internal) STDOUT redirect
|
49
53
|
}
|
50
54
|
init_params
|
51
55
|
yield(self)
|
@@ -63,7 +67,7 @@ module WatchmonkeyCli
|
|
63
67
|
opts.on("-q", "--quiet", "Only print errors") { @opts[:quiet] = true }
|
64
68
|
|
65
69
|
opts.separator("\n" << c("# General options", :blue))
|
66
|
-
opts.on("-d", "--debug", "Enable debug output") { @opts[:debug] =
|
70
|
+
opts.on("-d", "--debug [lvl=1]", Integer, "Enable debug output") {|l| @opts[:debug] = l || 1 }
|
67
71
|
opts.on("-m", "--monochrome", "Don't colorize output") { @opts[:colorize] = false }
|
68
72
|
opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
|
69
73
|
opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
|
@@ -84,167 +88,10 @@ module WatchmonkeyCli
|
|
84
88
|
@running
|
85
89
|
end
|
86
90
|
|
87
|
-
def load_config
|
88
|
-
return unless File.exist?(wm_cfg_configfile)
|
89
|
-
eval File.read(wm_cfg_configfile, encoding: "utf-8"), binding, wm_cfg_configfile
|
90
|
-
end
|
91
|
-
|
92
|
-
def debug msg
|
93
|
-
puts c("[DEBUG] #{msg}", :black) if @opts[:debug]
|
94
|
-
end
|
95
|
-
|
96
|
-
def abort msg, exit_code = 1
|
97
|
-
puts c("[ABORT] #{msg}", :red)
|
98
|
-
exit(exit_code)
|
99
|
-
end
|
100
|
-
|
101
|
-
def error msg
|
102
|
-
warn c(msg, :red)
|
103
|
-
end
|
104
|
-
|
105
|
-
def hook *which, &hook_block
|
106
|
-
which.each do |w|
|
107
|
-
@hooks[w.to_sym] ||= []
|
108
|
-
@hooks[w.to_sym] << hook_block
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def fire which, *args
|
113
|
-
return if @disable_event_firing
|
114
|
-
sync { debug "[Event] Firing #{which} (#{@hooks[which].try(:length) || 0} handlers) #{args.map(&:class)}" }
|
115
|
-
@hooks[which] && @hooks[which].each{|h| h.call(*args) }
|
116
|
-
end
|
117
|
-
|
118
|
-
def fetch_connection type, id, opts = {}, &initializer
|
119
|
-
if !@connections[type] || !@connections[type][id]
|
120
|
-
@connections[type] ||= {}
|
121
|
-
case type
|
122
|
-
when :loopback
|
123
|
-
@connections[type][id] = LoopbackConnection.new(id, opts, &initializer)
|
124
|
-
when :ssh
|
125
|
-
@connections[type][id] = SshConnection.new(id, opts, &initializer)
|
126
|
-
else
|
127
|
-
raise NotImplementedError, "unknown connection type `#{type}'!"
|
128
|
-
end
|
129
|
-
end
|
130
|
-
@connections[type][id]
|
131
|
-
end
|
132
|
-
|
133
|
-
def close_connections!
|
134
|
-
@connections.each do |type, clist|
|
135
|
-
clist.each{|id, con| con.close! }
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
91
|
def sync &block
|
140
92
|
@monitor.synchronize(&block)
|
141
93
|
end
|
142
94
|
|
143
|
-
def spawn_threads_and_run!
|
144
|
-
if @opts[:threads] > 1
|
145
|
-
debug "Spawning #{@opts[:threads]} consumer threads..."
|
146
|
-
@opts[:threads].times do
|
147
|
-
@threads << Thread.new do
|
148
|
-
Thread.current.abort_on_exception = true
|
149
|
-
_queueoff
|
150
|
-
end
|
151
|
-
end
|
152
|
-
else
|
153
|
-
debug "Running threadless..."
|
154
|
-
_queueoff
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def enqueue checker, *a, &block
|
159
|
-
sync do
|
160
|
-
cb = block || checker.method(:check!)
|
161
|
-
evreg = @disable_event_registration
|
162
|
-
fire(:enqueue, checker, a, cb) unless evreg
|
163
|
-
@queue << [checker, a, ->(*a) {
|
164
|
-
begin
|
165
|
-
result = Checker::Result.new(checker, *a)
|
166
|
-
checker.debug(result.str_running)
|
167
|
-
checker.safe(result.str_safe) { cb.call(result, *a) }
|
168
|
-
fire(:result_dump, result, a, checker)
|
169
|
-
result.dump!
|
170
|
-
ensure
|
171
|
-
fire(:dequeue, checker, a) unless evreg
|
172
|
-
end
|
173
|
-
}]
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def enqueue_sub checker, which, *args
|
178
|
-
sync do
|
179
|
-
if sec = @checkers[which.to_s]
|
180
|
-
begin
|
181
|
-
# ef_was = @disable_event_firing
|
182
|
-
er_was = @disable_event_registration
|
183
|
-
# @disable_event_firing = true
|
184
|
-
@disable_event_registration = true
|
185
|
-
sec.enqueue(*args)
|
186
|
-
ensure
|
187
|
-
# @disable_event_firing = ef_was
|
188
|
-
@disable_event_registration = er_was
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def _queueoff
|
195
|
-
while !@queue.empty? || @opts[:loop_forever]
|
196
|
-
break if $wm_runtime_exiting
|
197
|
-
item = queue.pop(true) rescue false
|
198
|
-
if item
|
199
|
-
Thread.current[:working] = true
|
200
|
-
fire(:wm_work_start, Thread.current)
|
201
|
-
sync { @processed += 1 }
|
202
|
-
item[2].call(*item[1])
|
203
|
-
Thread.current[:working] = false
|
204
|
-
fire(:wm_work_end, Thread.current)
|
205
|
-
end
|
206
|
-
sleep @opts[:loop_wait_empty] if @opts[:loop_forever] && @opts[:loop_wait_empty] && @queue.empty?
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def wm_cfg_path
|
211
|
-
ENV["WM_CFGDIR"].presence || File.expand_path("~/.watchmonkey")
|
212
|
-
end
|
213
|
-
|
214
|
-
def wm_cfg_configfile
|
215
|
-
"#{wm_cfg_path}/config.rb"
|
216
|
-
end
|
217
|
-
|
218
|
-
def logger_filename
|
219
|
-
"#{wm_cfg_path}/logs/watchmonkey.log"
|
220
|
-
end
|
221
|
-
|
222
|
-
def logger
|
223
|
-
sync do
|
224
|
-
@logger ||= begin
|
225
|
-
FileUtils.mkdir_p(File.dirname(@opts[:logfile]))
|
226
|
-
Logger.new(@opts[:logfile], 10, 1024000)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def trap_signals
|
232
|
-
debug "Trapping INT signal..."
|
233
|
-
Signal.trap("INT") do
|
234
|
-
$wm_runtime_exiting = true
|
235
|
-
puts "Interrupting..."
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def release_signals
|
240
|
-
debug "Releasing INT signal..."
|
241
|
-
Signal.trap("INT", "DEFAULT")
|
242
|
-
end
|
243
|
-
|
244
|
-
def haltpoint
|
245
|
-
raise Interrupt if $wm_runtime_exiting
|
246
|
-
end
|
247
|
-
|
248
95
|
def dump_and_exit!
|
249
96
|
puts " Queue: #{@queue.length}"
|
250
97
|
puts " AppOpts: #{@opts}"
|
@@ -34,6 +34,7 @@ module WatchmonkeyCli
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def stop_checkers!
|
37
|
+
return unless @checkers
|
37
38
|
@checkers.each do |key, instance|
|
38
39
|
debug "[SETUP] Stopping checker `#{key}' (#{instance.class.name})"
|
39
40
|
instance.stop
|
@@ -116,9 +117,7 @@ module WatchmonkeyCli
|
|
116
117
|
app.fire(:on_info, msg, robj)
|
117
118
|
return if app.opts[:quiet]
|
118
119
|
_tolog(msg, :info)
|
119
|
-
app.
|
120
|
-
puts app.c(msg, :blue)
|
121
|
-
end
|
120
|
+
app.puts app.c(msg, :blue)
|
122
121
|
end
|
123
122
|
|
124
123
|
def debug msg, robj = nil
|
@@ -126,9 +125,7 @@ module WatchmonkeyCli
|
|
126
125
|
app.fire(:on_message, msg, robj)
|
127
126
|
return if app.opts[:quiet] || app.opts[:silent]
|
128
127
|
_tolog(msg, :debug)
|
129
|
-
app.
|
130
|
-
puts app.c(msg, :black)
|
131
|
-
end
|
128
|
+
app.puts app.c(msg, :black)
|
132
129
|
end
|
133
130
|
|
134
131
|
def error msg, robj = nil
|
@@ -144,8 +141,7 @@ module WatchmonkeyCli
|
|
144
141
|
end
|
145
142
|
|
146
143
|
def spawn_sub which, *args
|
147
|
-
|
148
|
-
if sec = app.checkers["ssl_expiration"]
|
144
|
+
if sec = app.checkers[which.to_s]
|
149
145
|
sec.enqueue(*args)
|
150
146
|
end
|
151
147
|
end
|
@@ -221,7 +217,15 @@ module WatchmonkeyCli
|
|
221
217
|
# and therefore shared resources might still be in use
|
222
218
|
end
|
223
219
|
|
220
|
+
def enqueue *args
|
221
|
+
# Called by configuration defining a check with all the arguments.
|
222
|
+
# e.g. www_availability :my_host, foo: "bar" => args = [:my_host, {foo: "bar"}]
|
223
|
+
# Should invoke `app.enqueue` which will by default call `#check!` method with given arguments.
|
224
|
+
raise NotImplementedError, "a checker (#{self.class.name}) must implement `#enqueue' method!"
|
225
|
+
end
|
226
|
+
|
224
227
|
def check! *a
|
228
|
+
# required, see #enqueue
|
225
229
|
raise NotImplementedError, "a checker (#{self.class.name}) must implement `#check!' method!"
|
226
230
|
end
|
227
231
|
end
|
@@ -13,9 +13,9 @@ module WatchmonkeyCli
|
|
13
13
|
ftp.login(opts[:user], opts[:password])
|
14
14
|
end
|
15
15
|
rescue Net::FTPPermError
|
16
|
-
result.error "Invalid credentials!"
|
16
|
+
result.error! "Invalid credentials!"
|
17
17
|
rescue SocketError => e
|
18
|
-
result.error "#{e.class}: #{e.message}"
|
18
|
+
result.error! "#{e.class}: #{e.message}"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -7,7 +7,7 @@ module WatchmonkeyCli
|
|
7
7
|
app.enqueue(self, page, opts.except(:ssl_expiration))
|
8
8
|
|
9
9
|
# if available enable ssl_expiration support
|
10
|
-
if page.start_with?("https://") && opts[:ssl_expiration] != false
|
10
|
+
if page.start_with?("https://") && opts[:ssl_expiration] != false && !app.running?
|
11
11
|
spawn_sub("ssl_expiration", page, opts[:ssl_expiration].is_a?(Hash) ? opts[:ssl_expiration] : {})
|
12
12
|
end
|
13
13
|
end
|
@@ -33,9 +33,9 @@ module WatchmonkeyCli
|
|
33
33
|
# body
|
34
34
|
if rx = opts[:body]
|
35
35
|
if rx.is_a?(String)
|
36
|
-
result.error! "body does not include
|
36
|
+
result.error! "body does not include `#{rx}'!" if !result.result.body.include?(rx)
|
37
37
|
elsif rx.is_a?(Regexp)
|
38
|
-
result.error! "body does not match
|
38
|
+
result.error! "body does not match `#{rx}'!" if !result.result.body.match(rx)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -1,13 +1,31 @@
|
|
1
1
|
module WatchmonkeyCli
|
2
|
+
JS_ESCAPE_MAP = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
|
2
3
|
class Platypus
|
4
|
+
class SilentOutput
|
5
|
+
def print *a
|
6
|
+
end
|
7
|
+
def puts *a
|
8
|
+
end
|
9
|
+
def warn *a
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
3
13
|
def self.hook!(app, opts = {})
|
4
|
-
opts = opts.reverse_merge(notifications: true)
|
14
|
+
opts = opts.reverse_merge(notifications: 1, progress: true, html: false, draw_delay: 1)
|
15
|
+
opts[:progress] = false if opts[:html]
|
5
16
|
app.instance_eval do
|
17
|
+
@opts[:stdout] = SilentOutput.new
|
18
|
+
@platypus_status_cache = {
|
19
|
+
errors: [],
|
20
|
+
}
|
21
|
+
|
6
22
|
# send errors via notification center
|
7
23
|
hook :result_dump do |robj, args, checker|
|
8
24
|
if robj.error?
|
9
25
|
robj.messages.each do |m|
|
10
26
|
msg = "#{robj.args[0].try(:name) || robj.args[0].presence || "?"}: #{m}"
|
27
|
+
@platypus_status_cache[:errors].unshift([Time.current, msg])
|
28
|
+
@platypus_status_cache[:errors].pop if @platypus_status_cache[:errors].length > 20
|
11
29
|
|
12
30
|
case opts[:notifications]
|
13
31
|
when 1
|
@@ -16,7 +34,7 @@ module WatchmonkeyCli
|
|
16
34
|
`osascript -e 'display notification "#{fmsg}" with title "WatchMonkey"'`
|
17
35
|
when 2
|
18
36
|
# makes a sound
|
19
|
-
|
37
|
+
puts "NOTIFICATION:#{msg}"
|
20
38
|
end
|
21
39
|
end
|
22
40
|
end
|
@@ -24,13 +42,76 @@ module WatchmonkeyCli
|
|
24
42
|
|
25
43
|
hook :wm_work_start, :wm_work_end do
|
26
44
|
# mastermind calculation I swear :D (<-- no idea what I did here)
|
27
|
-
#
|
45
|
+
# puts "PROGRESS:#{((@threads.length-@threads.select{|t| t[:working] }.length.to_d) / @threads.length * 100).to_i}"
|
28
46
|
sync do
|
29
47
|
active = @threads.select{|t| t[:working] }.length
|
30
48
|
total = @threads.select{|t| t[:working] }.length + @queue.length
|
31
49
|
perc = total.zero? ? 100 : (active.to_d / total * 100).to_i
|
32
50
|
puts "PROGRESS:#{perc}"
|
33
51
|
end
|
52
|
+
end if opts[:progress]
|
53
|
+
|
54
|
+
# HTML output (fancy as fuck!)
|
55
|
+
if opts[:html]
|
56
|
+
def escape_javascript str
|
57
|
+
str.gsub(/(\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
|
58
|
+
end
|
59
|
+
|
60
|
+
def platypus_init_html
|
61
|
+
output = %{
|
62
|
+
<html>
|
63
|
+
<head>
|
64
|
+
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
|
65
|
+
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
66
|
+
<title>Moep</title>
|
67
|
+
</head>
|
68
|
+
<body style="padding: 8px">
|
69
|
+
<dl class="dl-horizontal">
|
70
|
+
<dt>Items in Queue</dt><dd class="qlength">#{@queue.length}</dd>
|
71
|
+
<dt>Items in ReQ</dt><dd class="rqlength">#{@requeue.length}</dd>
|
72
|
+
<dt>Workers</dt><dd class="workers">#{@threads.select{|t| t[:working] }.length}/#{@threads.length} working (#{@threads.select(&:alive?).length} alive)</dd>
|
73
|
+
<dt>Threads</dt><dd class="tlength">#{Thread.list.length}</dd>
|
74
|
+
<dt>Processed entries</dt><dd class="processed">#{@processed}</dd>
|
75
|
+
<dt>Watching since</dt><dd>#{@boot}</dd>
|
76
|
+
<dt>Last draw</dt><dd class="lastdraw">#{Time.current}</dd>
|
77
|
+
</dl>
|
78
|
+
<h3>Latest errors</h3>
|
79
|
+
<pre class="lasterrors" style="display: block; white-space: pre; word-break: normal; word-wrap: normal;"></pre>
|
80
|
+
</body>
|
81
|
+
</html>
|
82
|
+
}
|
83
|
+
sync { Kernel.puts output }
|
84
|
+
end
|
85
|
+
|
86
|
+
def platypus_update_html
|
87
|
+
dead = @threads.reject(&:alive?).length
|
88
|
+
ti = " (#{dead} DEAD)" if dead > 0
|
89
|
+
output = %{
|
90
|
+
<script>
|
91
|
+
$("script").remove();
|
92
|
+
$("dd.qlength").html("#{@queue.length}");
|
93
|
+
$("dd.rqlength").html("#{@requeue.length}");
|
94
|
+
$("dd.workers").html("#{@threads.select{|t| t[:working] }.length}/#{@threads.length} working#{ti}");
|
95
|
+
$("dd.tlength").html("#{Thread.list.length}");
|
96
|
+
$("dd.processed").html("#{@processed}");
|
97
|
+
$("dd.lastdraw").html("#{Time.current}");
|
98
|
+
$("pre.lasterrors").html("#{escape_javascript @platypus_status_cache[:errors].map{|t,e| "#{t}: #{e}" }.join("\n")}");
|
99
|
+
</script>
|
100
|
+
}
|
101
|
+
sync { Kernel.puts output }
|
102
|
+
end
|
103
|
+
|
104
|
+
platypus_init_html
|
105
|
+
@platypus_status_thread = Thread.new do
|
106
|
+
Thread.current.abort_on_exception = true
|
107
|
+
loop do
|
108
|
+
platypus_update_html
|
109
|
+
sleep opts[:draw_delay]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
hook :wm_shutdown do
|
113
|
+
@platypus_status_thread.try(:kill).try(:join)
|
114
|
+
end
|
34
115
|
end
|
35
116
|
end
|
36
117
|
end
|
data/lib/watchmonkey_cli.rb
CHANGED
@@ -21,8 +21,10 @@ require 'net/ssh'
|
|
21
21
|
require "watchmonkey_cli/version"
|
22
22
|
require "watchmonkey_cli/loopback_connection"
|
23
23
|
require "watchmonkey_cli/ssh_connection"
|
24
|
-
require "watchmonkey_cli/
|
24
|
+
require "watchmonkey_cli/helper"
|
25
25
|
require "watchmonkey_cli/checker"
|
26
|
+
require "watchmonkey_cli/application/core"
|
27
|
+
require "watchmonkey_cli/application/output_helper"
|
26
28
|
require "watchmonkey_cli/application/colorize"
|
27
29
|
require "watchmonkey_cli/application/configuration"
|
28
30
|
require "watchmonkey_cli/application/dispatch"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: watchmonkey_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sven Pachnit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -117,7 +117,9 @@ files:
|
|
117
117
|
- lib/watchmonkey_cli/application/colorize.rb
|
118
118
|
- lib/watchmonkey_cli/application/configuration.rb
|
119
119
|
- lib/watchmonkey_cli/application/configuration.tpl
|
120
|
+
- lib/watchmonkey_cli/application/core.rb
|
120
121
|
- lib/watchmonkey_cli/application/dispatch.rb
|
122
|
+
- lib/watchmonkey_cli/application/output_helper.rb
|
121
123
|
- lib/watchmonkey_cli/checker.rb
|
122
124
|
- lib/watchmonkey_cli/checkers/ftp_availability.rb
|
123
125
|
- lib/watchmonkey_cli/checkers/mysql_replication.rb
|
@@ -129,7 +131,7 @@ files:
|
|
129
131
|
- lib/watchmonkey_cli/checkers/unix_mdadm.rb
|
130
132
|
- lib/watchmonkey_cli/checkers/unix_memory.rb
|
131
133
|
- lib/watchmonkey_cli/checkers/www_availability.rb
|
132
|
-
- lib/watchmonkey_cli/
|
134
|
+
- lib/watchmonkey_cli/helper.rb
|
133
135
|
- lib/watchmonkey_cli/hooks/platypus.rb
|
134
136
|
- lib/watchmonkey_cli/hooks/requeue.rb
|
135
137
|
- lib/watchmonkey_cli/loopback_connection.rb
|