panoptimon 0.0.2
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 +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
|
+
|