panoptimon 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE +29 -0
- data/README.md +78 -0
- data/Rakefile +14 -0
- data/bin/panoptimon +118 -0
- data/collectors/cpu/cpu +39 -0
- data/collectors/cpu/cpu.json +1 -0
- data/collectors/disk/disk +37 -0
- data/collectors/disk/disk.json +1 -0
- data/collectors/disk/requires +1 -0
- data/collectors/disk_free/disk_free +15 -0
- data/collectors/disk_free/disk_free.json +1 -0
- data/collectors/dns/README.md +33 -0
- data/collectors/dns/dns +9 -0
- data/collectors/dns/dns.json +7 -0
- data/collectors/dns/lib/panoptimon-collector-dns/dns.rb +43 -0
- data/collectors/dns/lib/panoptimon-collector-dns.rb +2 -0
- data/collectors/dns/panoptimon-collector-dns.gemspec +4 -0
- data/collectors/files/README.md +32 -0
- data/collectors/files/files +129 -0
- data/collectors/files/files.json +14 -0
- data/collectors/files/spec/files_spec.rb +57 -0
- data/collectors/haproxy/README.md +40 -0
- data/collectors/haproxy/haproxy +16 -0
- data/collectors/haproxy/haproxy.json +3 -0
- data/collectors/haproxy/lib/panoptimon-collector-haproxy/haproxy.rb +149 -0
- data/collectors/haproxy/lib/panoptimon-collector-haproxy.rb +1 -0
- data/collectors/haproxy/notes.txt +13 -0
- data/collectors/haproxy/spec/haproxy_spec.rb +98 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-get.html +22 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_info.txt +21 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_stat.csv +25 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_stat2.csv +11 -0
- data/collectors/http/README.md +49 -0
- data/collectors/http/http +27 -0
- data/collectors/http/http.json +3 -0
- data/collectors/http/lib/panoptimon-collector-http/http.rb +74 -0
- data/collectors/http/lib/panoptimon-collector-http/version.rb +7 -0
- data/collectors/http/lib/panoptimon-collector-http.rb +2 -0
- data/collectors/interfaces/interfaces +33 -0
- data/collectors/interfaces/interfaces.json +1 -0
- data/collectors/iostat/iostat +53 -0
- data/collectors/iostat/iostat.json +1 -0
- data/collectors/json/README.md +27 -0
- data/collectors/json/json +37 -0
- data/collectors/json/json.json +1 -0
- data/collectors/load/load +15 -0
- data/collectors/load/load.json +1 -0
- data/collectors/memcached/memcached +55 -0
- data/collectors/memcached/memcached.json +7 -0
- data/collectors/memcached/test-notes.txt +3 -0
- data/collectors/memory/memory +33 -0
- data/collectors/memory/memory.json +1 -0
- data/collectors/mysql_status/mysql_status +52 -0
- data/collectors/mysql_status/mysql_status.json +4 -0
- data/collectors/network/network +67 -0
- data/collectors/network/network.json +18 -0
- data/collectors/nginx/README.md +32 -0
- data/collectors/nginx/lib/panoptimon-collector-nginx/nginx.rb +45 -0
- data/collectors/nginx/lib/panoptimon-collector-nginx.rb +2 -0
- data/collectors/nginx/nginx +11 -0
- data/collectors/nginx/nginx.json +3 -0
- data/collectors/nginx/panoptimon-collector-nginx.gemspec +4 -0
- data/collectors/ping/README.md +54 -0
- data/collectors/ping/ping +57 -0
- data/collectors/ping/ping.json +7 -0
- data/collectors/process/README.md +36 -0
- data/collectors/process/process +61 -0
- data/collectors/process/process.json +7 -0
- data/collectors/service/README.md +51 -0
- data/collectors/service/samples/.gitignore +1 -0
- data/collectors/service/samples/data/disconnect +11 -0
- data/collectors/service/samples/data/flappy +7 -0
- data/collectors/service/samples/data/solid +18 -0
- data/collectors/service/samples/replay +27 -0
- data/collectors/service/service +86 -0
- data/collectors/service/service.json +7 -0
- data/collectors/smtp/lib/panoptimon-collector-smtp/smtp.rb +30 -0
- data/collectors/smtp/lib/panoptimon-collector-smtp.rb +1 -0
- data/collectors/smtp/smtp +27 -0
- data/collectors/smtp/smtp.json +10 -0
- data/collectors/socket/README.md +36 -0
- data/collectors/socket/lib/panoptimon-collector-socket/socket.rb +38 -0
- data/collectors/socket/lib/panoptimon-collector-socket/tcp.rb +34 -0
- data/collectors/socket/lib/panoptimon-collector-socket/unix.rb +28 -0
- data/collectors/socket/lib/panoptimon-collector-socket.rb +3 -0
- data/collectors/socket/socket +13 -0
- data/collectors/socket/socket.json +16 -0
- data/collectors/socket/tests/tcp_spec.rb +21 -0
- data/collectors/socket/tests/unix_spec.rb +35 -0
- data/collectors/ssh/README.md +27 -0
- data/collectors/ssh/ssh +41 -0
- data/collectors/ssh/ssh.json +3 -0
- data/lib/panoptimon/collector.rb +135 -0
- data/lib/panoptimon/eventmonkeypatch/popen3.rb +40 -0
- data/lib/panoptimon/http.rb +63 -0
- data/lib/panoptimon/logger.rb +19 -0
- data/lib/panoptimon/monitor.rb +154 -0
- data/lib/panoptimon/util/string-with-as_number.rb +5 -0
- data/lib/panoptimon/util.rb +23 -0
- data/lib/panoptimon/version.rb +5 -0
- data/lib/panoptimon.rb +144 -0
- data/misc/collector_setup.rb +23 -0
- data/misc/monitor_setup.rb +25 -0
- data/misc/plugins_setup.rb +25 -0
- data/misc/riemann-cli.rb +33 -0
- data/panoptimon.gemspec +33 -0
- data/plugins/daemon_health/README.md +31 -0
- data/plugins/daemon_health/daemon_health.json +4 -0
- data/plugins/daemon_health/daemon_health.rb +34 -0
- data/plugins/daemon_health/lib/panoptimon-plugin-daemon_health/rollup.rb +64 -0
- data/plugins/daemon_health/panoptimon-plugin-daemon_health.gemspec +10 -0
- data/plugins/daemon_health/spec/moving_avg_spec.rb +24 -0
- data/plugins/email/README.md +30 -0
- data/plugins/email/email.json +3 -0
- data/plugins/email/email.rb +52 -0
- data/plugins/log_to_file/log_to_file.json +1 -0
- data/plugins/log_to_file/log_to_file.rb +8 -0
- data/plugins/log_to_logger/log_to_logger.json +3 -0
- data/plugins/log_to_logger/log_to_logger.rb +7 -0
- data/plugins/metrics_http/README.md +23 -0
- data/plugins/metrics_http/metrics_http.json +1 -0
- data/plugins/metrics_http/metrics_http.rb +17 -0
- data/plugins/riemann_stream/requires +1 -0
- data/plugins/riemann_stream/riemann_stream.json +3 -0
- data/plugins/riemann_stream/riemann_stream.rb +23 -0
- data/plugins/status_http/requires +1 -0
- data/plugins/status_http/status_http.json +1 -0
- data/plugins/status_http/status_http.rb +60 -0
- data/sample_configs/1/collectors/alls_well.json +6 -0
- data/sample_configs/1/collectors/clock/clock +12 -0
- data/sample_configs/1/collectors/clock.json +1 -0
- data/sample_configs/1/collectors/df/df.json +6 -0
- data/sample_configs/1/collectors/df/wrap_df +21 -0
- data/sample_configs/1/collectors/load.json +1 -0
- data/sample_configs/1/panoptimon.json +4 -0
- data/sample_configs/1/plugins/isup/isup.rb +3 -0
- data/sample_configs/1/plugins/isup.json +1 -0
- data/sample_configs/1/plugins/log_to_file.json +1 -0
- data/sample_configs/err_handler/collectors/fail.json +4 -0
- data/sample_configs/err_handler/collectors/noisy.json +5 -0
- data/sample_configs/err_handler/collectors/noisy_failure.json +5 -0
- data/sample_configs/err_handler/collectors/notfound.json +4 -0
- data/sample_configs/err_handler/panoptimon.json +3 -0
- data/sample_configs/err_handler/plugins/.exists +0 -0
- data/sample_configs/passthru/collectors/beep.json +5 -0
- data/sample_configs/passthru/collectors/cat/collect_this.json +1 -0
- data/sample_configs/passthru/collectors/cat.json +5 -0
- data/sample_configs/passthru/panoptimon.json +3 -0
- data/sample_configs/passthru/plugins/okcat/okcat.rb +17 -0
- data/sample_configs/passthru/plugins/okcat.json +1 -0
- data/sample_configs/plugin_error/collectors/beep.json +5 -0
- data/sample_configs/plugin_error/panoptimon.json +1 -0
- data/sample_configs/plugin_error/plugins/error_always/error_always.rb +1 -0
- data/sample_configs/plugin_error/plugins/error_always.json +1 -0
- data/sample_configs/slow/collectors/slowbeep.json +6 -0
- data/sample_configs/slow/panoptimon.json +1 -0
- data/sample_configs/slow/plugins/.exists +0 -0
- data/sample_configs/timeout_newline/collectors/slow_lf.json +8 -0
- data/sample_configs/timeout_newline/panoptimon.json +1 -0
- data/sample_configs/timeout_newline/plugins/.exists +0 -0
- data/sample_configs/timeout_not/collectors/slowly.json +7 -0
- data/sample_configs/timeout_not/panoptimon.json +1 -0
- data/sample_configs/timeout_not/plugins/.exists +0 -0
- data/spec/collector/config_spec.rb +30 -0
- data/spec/collector/initialize_spec.rb +24 -0
- data/spec/collector/metric_spec.rb +22 -0
- data/spec/passthru_spec.rb +13 -0
- data/spec/util_spec.rb +37 -0
- data/tools/link_and_enable +37 -0
- data/tools/metricify +8 -0
- metadata +319 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
class Array; def to_h; Hash[self]; end; end
|
5
|
+
|
6
|
+
opt = JSON::parse(ARGV[0], {symbolize_names: true})
|
7
|
+
|
8
|
+
pgrep = ['pgrep']
|
9
|
+
|
10
|
+
checks = opt[:checks]
|
11
|
+
|
12
|
+
# could match a process with more than one check, so keep track
|
13
|
+
names = Hash.new {|h,k| h[k] = []}
|
14
|
+
found = {}
|
15
|
+
checks.each {|n,o|
|
16
|
+
args = [
|
17
|
+
o[:user] ? ['-u', o[:user]] : [],
|
18
|
+
o[:full] ? ['-f', o[:full]] : o[:pattern] ? o[:pattern] : []
|
19
|
+
].flatten
|
20
|
+
raise "must have some process characteristic for #{n}" \
|
21
|
+
unless args.length > 0
|
22
|
+
cmd = pgrep + ['-d,'] + args
|
23
|
+
got = begin; IO.popen(cmd).readline.chomp.split(/,/); rescue; []; end
|
24
|
+
got.each {|pid| names[pid].push(n)}
|
25
|
+
found[n] = {count: got.length}
|
26
|
+
}
|
27
|
+
|
28
|
+
ps = ['ps']
|
29
|
+
fields = %w(pid time etime thcount pcpu ni pri vsz rss command)
|
30
|
+
fieldcount = fields.length
|
31
|
+
|
32
|
+
cnv = {}.tap {|it|
|
33
|
+
to_i = ->(x) {x.to_i}; to_f = ->(x) {x.to_f};
|
34
|
+
to_sec = ->(x) { d = 0; d = $1 if x.sub!(/^\d+-/, '');
|
35
|
+
(h,h,m,s) = x.match(/^(?:(\d+):)?(\d+):(\d+)/).to_a
|
36
|
+
h ||= 0
|
37
|
+
d.to_i * 24 * 60**2 + h.to_i * 60**2 + m.to_i * 60 + s.to_i}
|
38
|
+
it.merge!({
|
39
|
+
pcpu: to_f, time: to_sec, etime: to_sec,
|
40
|
+
thcount: to_i, pri: to_i, rss: to_i, ni: to_i, vsz: to_i
|
41
|
+
})
|
42
|
+
}
|
43
|
+
|
44
|
+
IO.popen(ps + ['-ww', '-o', fields.join(','), '-p', names.keys.join(',')]).
|
45
|
+
readlines.drop(1).each {|l|
|
46
|
+
f = l.chomp.sub(/^\s+/, '').split(/\s+/, fieldcount)
|
47
|
+
h = (0..fieldcount-1).map {|i| [fields[i].to_sym, f[i]]}.to_h
|
48
|
+
pid = h.delete(:pid)
|
49
|
+
inf = %w(command).map {|k| [k, h.delete(k.to_sym)]}.to_h
|
50
|
+
cnv.each {|k,v| h[k] = v[h[k]]}
|
51
|
+
names[pid].each {|n|
|
52
|
+
i = found[n][:i] ||= 0; found[n][:i] += 1
|
53
|
+
found[n][i] = h
|
54
|
+
_info = found[n][:_info] ||= Hash.new {|h,k| h[k] = []}
|
55
|
+
inf.each {|k,v| _info[k].push(v)}
|
56
|
+
}
|
57
|
+
} if names.keys.length > 0
|
58
|
+
|
59
|
+
found.each {|k,v| v.delete(:i)} # cleanup
|
60
|
+
|
61
|
+
puts JSON::generate(found)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
upstart: `initctl list`
|
2
|
+
|
3
|
+
```none
|
4
|
+
qemu-kvm start/running
|
5
|
+
rc stop/waiting
|
6
|
+
rsyslog start/running, process 1237
|
7
|
+
network-interface (lo) start/running
|
8
|
+
```
|
9
|
+
|
10
|
+
daemontools: `svstat /service/\*` (aside: `ps -eo %a | grep '[s]vscan ' | cut -d' ' -f2 | sort -u`)
|
11
|
+
|
12
|
+
```none
|
13
|
+
|
14
|
+
/service/chef-client: up (pid 14971) 1197 seconds
|
15
|
+
/service/hubot: up (pid 6583) 8895 seconds
|
16
|
+
/service/resmon: up (pid 633) 4131042 seconds
|
17
|
+
/service/syslog-ng: up (pid 632) 4131042 seconds
|
18
|
+
/service/mail-in: down 3 seconds, normally up
|
19
|
+
```
|
20
|
+
|
21
|
+
systemd: `systemctl list-units --full --type=service --all`, `systemctl show NAME NAME NAME ...`
|
22
|
+
|
23
|
+
## Config
|
24
|
+
|
25
|
+
```json
|
26
|
+
{
|
27
|
+
interval: 60
|
28
|
+
flaptime: 30,
|
29
|
+
since: 900,
|
30
|
+
services: {
|
31
|
+
init: { foo : { status_cmd : "..."} },
|
32
|
+
systemd: {
|
33
|
+
sshd : {...}
|
34
|
+
},
|
35
|
+
daemontools: {
|
36
|
+
"-monitor" : ["/service/*"], # probably all you need
|
37
|
+
"-options" : { "svstat" : ["..."] },
|
38
|
+
"chef-client" : { },
|
39
|
+
"syslog-ng" : {"path" : "/service/syslog-ng"}, # is the default
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
```
|
44
|
+
|
45
|
+
## Output
|
46
|
+
|
47
|
+
* up: seconds the service has been up (negative if it has been shutdown)
|
48
|
+
* flaps: number of flaps (runs under 'flaptime') within the 'since' horizon
|
49
|
+
|
50
|
+
services|daemontools|syslog-ng|up => $seconds
|
51
|
+
services|daemontools|syslog-ng|flaps => $n
|
@@ -0,0 +1 @@
|
|
1
|
+
replay.counter
|
@@ -0,0 +1,11 @@
|
|
1
|
+
up (pid 3655) 30392 seconds
|
2
|
+
up (pid 3655) 30452 seconds
|
3
|
+
up (pid 3655) 30512 seconds
|
4
|
+
up (pid 3655) 30572 seconds
|
5
|
+
down 3 seconds, normally up
|
6
|
+
down 63 seconds, normally up
|
7
|
+
down 123 seconds, normally up
|
8
|
+
down 183 seconds, normally up
|
9
|
+
down 243 seconds, normally up
|
10
|
+
down 303 seconds, normally up
|
11
|
+
down 363 seconds, normally up
|
@@ -0,0 +1,18 @@
|
|
1
|
+
up (pid 3656) 30032 seconds
|
2
|
+
up (pid 3656) 30092 seconds
|
3
|
+
up (pid 3656) 30152 seconds
|
4
|
+
up (pid 3656) 30212 seconds
|
5
|
+
up (pid 3656) 30272 seconds
|
6
|
+
up (pid 3656) 30332 seconds
|
7
|
+
up (pid 3656) 30392 seconds
|
8
|
+
up (pid 3656) 30452 seconds
|
9
|
+
up (pid 3656) 30512 seconds
|
10
|
+
up (pid 3656) 30572 seconds
|
11
|
+
up (pid 3656) 30632 seconds
|
12
|
+
up (pid 3656) 30692 seconds
|
13
|
+
up (pid 3656) 30752 seconds
|
14
|
+
up (pid 3656) 30812 seconds
|
15
|
+
up (pid 3656) 30872 seconds
|
16
|
+
up (pid 3656) 30932 seconds
|
17
|
+
up (pid 3656) 30992 seconds
|
18
|
+
up (pid 3656) 31052 seconds
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env perl
|
2
|
+
|
3
|
+
use v5.10;
|
4
|
+
use warnings;
|
5
|
+
use strict;
|
6
|
+
|
7
|
+
my $countfile = __FILE__ . '.counter';
|
8
|
+
|
9
|
+
my $count = 1 + do {
|
10
|
+
open(my $fh, '<', $countfile) or die "cannot open '$countfile' - $!";
|
11
|
+
chomp(my $c = <$fh>);
|
12
|
+
$c;
|
13
|
+
};
|
14
|
+
|
15
|
+
my %data = map({ my $file = $_;
|
16
|
+
open(my $fh, '<', $file) or die "cannot open '$file' - $!";
|
17
|
+
chomp(my @lines = <$fh>);
|
18
|
+
$count = 1 if @lines < $count and warn "resetting\n";
|
19
|
+
($file => \@lines);
|
20
|
+
} @ARGV);
|
21
|
+
|
22
|
+
say {
|
23
|
+
open(my $fh, '>', $countfile) or die "cannot open '$countfile' - $!";
|
24
|
+
$fh
|
25
|
+
} $count;
|
26
|
+
|
27
|
+
say "$_: $data{$_}[$count - 1]" for keys %data;
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright (C) 2012 Sourcefire, Inc.
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
$stdout.sync = true # persistent process
|
8
|
+
opt = JSON::parse(ARGV[0], {symbolize_names: true})
|
9
|
+
opt.merge!( interval: 60, flaptime: 30, since: 900 ) {|k,a,b| a}
|
10
|
+
|
11
|
+
class MatchData; def to_h
|
12
|
+
Hash[self.names.map {|n| [n.to_sym, self[n]]}]
|
13
|
+
end; end
|
14
|
+
class Array; def to_h; Hash[self]; end; end
|
15
|
+
|
16
|
+
handler = {
|
17
|
+
daemontools: ->(srv){
|
18
|
+
o = srv.delete(:'-options') || {}
|
19
|
+
mon = srv.delete(:'-monitor')
|
20
|
+
cmd = [o[:svstat] || 'svstat'].flatten
|
21
|
+
fn = mon.map {|n| Pathname.glob(n.to_s)}.flatten.
|
22
|
+
map {|p| [p.to_s, p.basename.to_s.to_sym]}.to_h.
|
23
|
+
merge(
|
24
|
+
srv.keys.map {|n| [n, srv[n][:path] || '/service/' + n]}.to_h)
|
25
|
+
->() {
|
26
|
+
p = IO.popen(cmd + fn.keys.map{|k| k.to_s})
|
27
|
+
stat = p.readlines
|
28
|
+
p.close
|
29
|
+
stat.map {|l| l.chomp!
|
30
|
+
info = l.match(%r{
|
31
|
+
\A(?<key>\S+):\s+
|
32
|
+
(?<state>up|down)
|
33
|
+
(?:\s+\(pid\s+(?<pid>\d+)\))?\s+
|
34
|
+
(?<duration>\d+)\s+seconds
|
35
|
+
(?:,\s+normally\s(?<normally>\S+))?
|
36
|
+
}x) or raise "cannot parse #{l}"
|
37
|
+
info = info.to_h
|
38
|
+
name = fn[info.delete(:key)]
|
39
|
+
[name, info]
|
40
|
+
}.to_h
|
41
|
+
}
|
42
|
+
},
|
43
|
+
}
|
44
|
+
|
45
|
+
services = Hash[opt[:services].map {|k,v|
|
46
|
+
how = handler[k] or raise "unknown service type #{k}"; [k, how[v]]}]
|
47
|
+
|
48
|
+
class AFlap
|
49
|
+
def initialize (opts = {})
|
50
|
+
@since = opts[:since] || 900
|
51
|
+
@flaptime = opts[:flaptime] || 5
|
52
|
+
@history = {}
|
53
|
+
end
|
54
|
+
def log (k, i)
|
55
|
+
l = @history[k] ||= []
|
56
|
+
t = Time.now
|
57
|
+
l.shift while(l.length > 0 and l[0][:time] < t - @since)
|
58
|
+
logged = {
|
59
|
+
time: t,
|
60
|
+
duration: i[:duration].to_i,
|
61
|
+
up: (i[:state] == 'up' ? true : false)
|
62
|
+
}
|
63
|
+
l.push(logged)
|
64
|
+
count = 0;
|
65
|
+
l.reverse.each {|i|
|
66
|
+
break if i[:duration] > @flaptime or not(i[:up])
|
67
|
+
count +=1}
|
68
|
+
{flaps: count, up: (logged[:up] ? 1 : -1) * logged[:duration]}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
hist = AFlap.new(opt)
|
73
|
+
while true
|
74
|
+
metrics = services.map {|k,v|
|
75
|
+
data = v[].map {|n,i|
|
76
|
+
[n, hist.log("#{k}|#{n}", i)]
|
77
|
+
}.to_h
|
78
|
+
[k, data]
|
79
|
+
}.to_h
|
80
|
+
puts JSON::generate(metrics)
|
81
|
+
sleep(opt[:interval])
|
82
|
+
if opt[:limit]
|
83
|
+
opt[:limit] -= 1
|
84
|
+
break if opt[:limit] == 0
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "timeout"
|
3
|
+
|
4
|
+
module Panoptimon
|
5
|
+
module Collector
|
6
|
+
class SMTP
|
7
|
+
|
8
|
+
attr_reader :host, :port, :timeout
|
9
|
+
def initialize(args={})
|
10
|
+
args.each { |k,v| instance_variable_set("@#{k}", v) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def collect
|
14
|
+
c = begin
|
15
|
+
Timeout::timeout(timeout) {
|
16
|
+
code = TCPSocket.new(host, port).gets.split.first
|
17
|
+
{status: code.to_i}
|
18
|
+
}
|
19
|
+
rescue Timeout::Error
|
20
|
+
{timeout: true}
|
21
|
+
rescue
|
22
|
+
{error: true, _info: {error: "#{$!.class}: #{$!}"}}
|
23
|
+
end
|
24
|
+
|
25
|
+
return c
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'panoptimon-collector-smtp/smtp'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'panoptimon-collector-smtp'
|
6
|
+
|
7
|
+
ARGV[0] or raise "arguments required"
|
8
|
+
conf = JSON.parse(ARGV[0], {symbolize_names: true})
|
9
|
+
|
10
|
+
defaults = {
|
11
|
+
port: conf[:default_port] || 25,
|
12
|
+
timeout: conf[:default_timeout] || 3,
|
13
|
+
}
|
14
|
+
|
15
|
+
raise "must have 'hosts' value in config" unless conf[:hosts]
|
16
|
+
setup = conf[:hosts].map {|h|
|
17
|
+
o = defaults.merge(h.is_a?(Hash) ? h : {host: h})
|
18
|
+
o[:host] or raise "must have 'host' attribute in entry #{o.inspect}"
|
19
|
+
o[:name] ||= o[:host]
|
20
|
+
o
|
21
|
+
}
|
22
|
+
|
23
|
+
output = Hash[setup.map {|o|
|
24
|
+
warn "o: #{o.inspect}"
|
25
|
+
[o[:name], Panoptimon::Collector::SMTP.new(o).collect]
|
26
|
+
}]
|
27
|
+
puts JSON.generate(output)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
### Description
|
2
|
+
|
3
|
+
Connect to arbitrary TCP & UNIX sockets and match output. Input is expected to be an Array of TCP & UNIX checks as the following describes.
|
4
|
+
|
5
|
+
|
6
|
+
``` json
|
7
|
+
{
|
8
|
+
"checks": {
|
9
|
+
"haproxy": {
|
10
|
+
"path": "/var/run/haproxy/stats",
|
11
|
+
"timeout": 10,
|
12
|
+
"query": "show info",
|
13
|
+
"match": ".*"
|
14
|
+
},
|
15
|
+
"cloudysunday": {
|
16
|
+
"path": "localhost",
|
17
|
+
"port": 22,
|
18
|
+
"timeout": 5,
|
19
|
+
"match": "SSH"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
```
|
24
|
+
|
25
|
+
### Check Attributes
|
26
|
+
|
27
|
+
**Path:** Describes the endpoint of a given socket. This will
|
28
|
+
automatically determine the type (TCP vs Unix) of check.
|
29
|
+
|
30
|
+
**Timeout:** Number of seconds to connect/query a given socket and return.
|
31
|
+
|
32
|
+
**Match:** Parse the output ensuring it includes the given match string.
|
33
|
+
|
34
|
+
**Query (Unix Only):** Input to a given a socket.
|
35
|
+
|
36
|
+
**Port (TCP Only):** Port to connect to (default: 80).
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'panoptimon-collector-socket/tcp'
|
2
|
+
require 'panoptimon-collector-socket/unix'
|
3
|
+
|
4
|
+
module Panoptimon
|
5
|
+
module Collector
|
6
|
+
class Socket
|
7
|
+
attr_accessor :path, :timeout, :match
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
@path = options[:path] || defaults[:path]
|
11
|
+
@match = options[:match] || defaults[:match]
|
12
|
+
@timeout = options[:timeout] || defaults[:timeout]
|
13
|
+
return options
|
14
|
+
end
|
15
|
+
|
16
|
+
# dispatching constructor
|
17
|
+
def self.construct(options={})
|
18
|
+
(options[:path] =~ %r{^/} ? Unix : TCP).new(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def defaults
|
22
|
+
{match: '.*'}
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
out = begin
|
27
|
+
a = Timeout::timeout(timeout.to_i) { get_banner }
|
28
|
+
{status: a.match(match) ? true : false}
|
29
|
+
rescue Timeout::Error
|
30
|
+
{timeout: true}
|
31
|
+
end
|
32
|
+
|
33
|
+
out
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'socket'
|
3
|
+
require 'timeout'
|
4
|
+
require 'panoptimon-collector-socket/socket'
|
5
|
+
|
6
|
+
module Panoptimon
|
7
|
+
module Collector
|
8
|
+
class Socket
|
9
|
+
class TCP < Panoptimon::Collector::Socket
|
10
|
+
attr_accessor :host, :port
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
opt = defaults.merge(options)
|
14
|
+
super(opt)
|
15
|
+
@port = opt[:port]
|
16
|
+
@host = @path.match('\w+:\/\/') ? URI(@path).host : @path
|
17
|
+
end
|
18
|
+
|
19
|
+
def defaults
|
20
|
+
super.merge({
|
21
|
+
path: 'http://localhost',
|
22
|
+
port: 80,
|
23
|
+
timeout: 10,
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_banner
|
28
|
+
TCPSocket.new(host, port).recv(100)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Panoptimon
|
2
|
+
module Collector
|
3
|
+
class Socket
|
4
|
+
class Unix < Panoptimon::Collector::Socket
|
5
|
+
attr_accessor :query
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
opt = super(defaults.merge(options))
|
9
|
+
@query = opt[:query]
|
10
|
+
opt
|
11
|
+
end
|
12
|
+
|
13
|
+
def defaults
|
14
|
+
super.merge({
|
15
|
+
path: '/var/run/haproxy/stats',
|
16
|
+
query: 'show info',
|
17
|
+
timeout: 5
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_banner
|
22
|
+
UNIXSocket.new(@path).puts(query).readlines.join('')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
require 'panoptimon-collector-socket'
|
7
|
+
|
8
|
+
raise 'Input must be provided!' unless ARGV[0]
|
9
|
+
|
10
|
+
config = JSON.parse(ARGV[0], symbolize_names: true)
|
11
|
+
puts Hash[config[:checks].map do |check, info|
|
12
|
+
[check, Panoptimon::Collector::Socket.construct(info).run]
|
13
|
+
end].to_json
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'panoptimon-collector-socket'
|
5
|
+
|
6
|
+
describe('basic test') {
|
7
|
+
it('works') {
|
8
|
+
socket = Panoptimon::Collector::Socket::TCP.new(
|
9
|
+
path: 'localhost',
|
10
|
+
port: 22,
|
11
|
+
match: 'SSH'
|
12
|
+
)
|
13
|
+
socket.host.should == 'localhost'
|
14
|
+
socket.port.should == 22
|
15
|
+
socket.match.should == 'SSH'
|
16
|
+
ans = socket.run
|
17
|
+
ans.class.should == Hash
|
18
|
+
ans[:status].should == true
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'panoptimon-collector-socket'
|
5
|
+
|
6
|
+
describe('basic test') {
|
7
|
+
it('works') {
|
8
|
+
socket = Panoptimon::Collector::Socket::Unix.new(
|
9
|
+
path: '/tmp/sockpuppet',
|
10
|
+
query: 'show info')
|
11
|
+
socket.path.class.should == String
|
12
|
+
socket.path.should =~ %r{^/\w+}
|
13
|
+
socket.query.should == 'show info'
|
14
|
+
|
15
|
+
yo = nil
|
16
|
+
socket.stub(:get_banner) { yo = "foo\nbar\nbaz" }
|
17
|
+
ans = socket.run
|
18
|
+
yo.should == "foo\nbar\nbaz"
|
19
|
+
ans[:status].should == true
|
20
|
+
}
|
21
|
+
it('does matching too') {
|
22
|
+
socket = Panoptimon::Collector::Socket::Unix.new(
|
23
|
+
path: '/tmp/sockpuppet',
|
24
|
+
query: 'show info',
|
25
|
+
match: 'frobnosticate',
|
26
|
+
)
|
27
|
+
socket.path.class.should == String
|
28
|
+
socket.path.should =~ %r{^/\w+}
|
29
|
+
socket.query.should == 'show info'
|
30
|
+
|
31
|
+
socket.stub(:get_banner) { "foo\nbar\nbaz" }
|
32
|
+
ans = socket.run
|
33
|
+
ans[:status].should == false
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
```json
|
4
|
+
{
|
5
|
+
"hosts" : [
|
6
|
+
"localhost",
|
7
|
+
{"name" : "example", "host": "ssh.example.com",
|
8
|
+
"port": 17, "timeout": 7},
|
9
|
+
],
|
10
|
+
"default_port" : 22,
|
11
|
+
"default_timeout" : 3,
|
12
|
+
|
13
|
+
"interval" : 60,
|
14
|
+
"timeout" : 25,
|
15
|
+
}
|
16
|
+
```
|
17
|
+
|
18
|
+
# Output
|
19
|
+
|
20
|
+
```json
|
21
|
+
{
|
22
|
+
"ssh|localhost|ok" : true, # whether connected or not
|
23
|
+
"ssh|localhost|_info" : {"banner" : "SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1"},
|
24
|
+
"ssh|example|ok" : true,
|
25
|
+
...
|
26
|
+
}
|
27
|
+
```
|
data/collectors/ssh/ssh
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
conf = ARGV[0] ? JSON.parse(ARGV[0], {symbolize_names: true}) : {}
|
6
|
+
|
7
|
+
defaults = {
|
8
|
+
port: conf[:default_port] || 22,
|
9
|
+
timeout: conf[:default_timeout] || 4,
|
10
|
+
}
|
11
|
+
|
12
|
+
conf[:hosts] or raise "hosts config required"
|
13
|
+
|
14
|
+
setup = conf[:hosts].map {|h|
|
15
|
+
opt = h.is_a?(Hash) ? h : {host: h}
|
16
|
+
raise "host is required" unless opt[:host]
|
17
|
+
opt = defaults.merge(opt)
|
18
|
+
opt[:name] ||= opt[:host]
|
19
|
+
opt
|
20
|
+
}
|
21
|
+
|
22
|
+
require 'socket'
|
23
|
+
require 'timeout'
|
24
|
+
get = ->(args) {
|
25
|
+
warn args[:host]
|
26
|
+
data = {_info: {}}
|
27
|
+
begin
|
28
|
+
data[:_info][:banner] = timeout(args[:timeout]) {
|
29
|
+
TCPSocket.open(args[:host], args[:port]).readline
|
30
|
+
}.chomp
|
31
|
+
data[:ok] = true
|
32
|
+
rescue TimeoutError
|
33
|
+
data[:ok] = false
|
34
|
+
end
|
35
|
+
data
|
36
|
+
}
|
37
|
+
|
38
|
+
output = Hash[setup.map {|opt| [opt[:name], get[opt]]}]
|
39
|
+
puts JSON.generate(output)
|
40
|
+
|
41
|
+
|