panoptimon 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +29 -0
  5. data/README.md +78 -0
  6. data/Rakefile +14 -0
  7. data/bin/panoptimon +118 -0
  8. data/collectors/cpu/cpu +39 -0
  9. data/collectors/cpu/cpu.json +1 -0
  10. data/collectors/disk/disk +37 -0
  11. data/collectors/disk/disk.json +1 -0
  12. data/collectors/disk/requires +1 -0
  13. data/collectors/disk_free/disk_free +15 -0
  14. data/collectors/disk_free/disk_free.json +1 -0
  15. data/collectors/dns/README.md +33 -0
  16. data/collectors/dns/dns +9 -0
  17. data/collectors/dns/dns.json +7 -0
  18. data/collectors/dns/lib/panoptimon-collector-dns/dns.rb +43 -0
  19. data/collectors/dns/lib/panoptimon-collector-dns.rb +2 -0
  20. data/collectors/dns/panoptimon-collector-dns.gemspec +4 -0
  21. data/collectors/files/README.md +32 -0
  22. data/collectors/files/files +129 -0
  23. data/collectors/files/files.json +14 -0
  24. data/collectors/files/spec/files_spec.rb +57 -0
  25. data/collectors/haproxy/README.md +40 -0
  26. data/collectors/haproxy/haproxy +16 -0
  27. data/collectors/haproxy/haproxy.json +3 -0
  28. data/collectors/haproxy/lib/panoptimon-collector-haproxy/haproxy.rb +149 -0
  29. data/collectors/haproxy/lib/panoptimon-collector-haproxy.rb +1 -0
  30. data/collectors/haproxy/notes.txt +13 -0
  31. data/collectors/haproxy/spec/haproxy_spec.rb +98 -0
  32. data/collectors/haproxy/spec/haproxy_spec.rb-get.html +22 -0
  33. data/collectors/haproxy/spec/haproxy_spec.rb-show_info.txt +21 -0
  34. data/collectors/haproxy/spec/haproxy_spec.rb-show_stat.csv +25 -0
  35. data/collectors/haproxy/spec/haproxy_spec.rb-show_stat2.csv +11 -0
  36. data/collectors/http/README.md +49 -0
  37. data/collectors/http/http +27 -0
  38. data/collectors/http/http.json +3 -0
  39. data/collectors/http/lib/panoptimon-collector-http/http.rb +74 -0
  40. data/collectors/http/lib/panoptimon-collector-http/version.rb +7 -0
  41. data/collectors/http/lib/panoptimon-collector-http.rb +2 -0
  42. data/collectors/interfaces/interfaces +33 -0
  43. data/collectors/interfaces/interfaces.json +1 -0
  44. data/collectors/iostat/iostat +53 -0
  45. data/collectors/iostat/iostat.json +1 -0
  46. data/collectors/json/README.md +27 -0
  47. data/collectors/json/json +37 -0
  48. data/collectors/json/json.json +1 -0
  49. data/collectors/load/load +15 -0
  50. data/collectors/load/load.json +1 -0
  51. data/collectors/memcached/memcached +55 -0
  52. data/collectors/memcached/memcached.json +7 -0
  53. data/collectors/memcached/test-notes.txt +3 -0
  54. data/collectors/memory/memory +33 -0
  55. data/collectors/memory/memory.json +1 -0
  56. data/collectors/mysql_status/mysql_status +52 -0
  57. data/collectors/mysql_status/mysql_status.json +4 -0
  58. data/collectors/network/network +67 -0
  59. data/collectors/network/network.json +18 -0
  60. data/collectors/nginx/README.md +32 -0
  61. data/collectors/nginx/lib/panoptimon-collector-nginx/nginx.rb +45 -0
  62. data/collectors/nginx/lib/panoptimon-collector-nginx.rb +2 -0
  63. data/collectors/nginx/nginx +11 -0
  64. data/collectors/nginx/nginx.json +3 -0
  65. data/collectors/nginx/panoptimon-collector-nginx.gemspec +4 -0
  66. data/collectors/ping/README.md +54 -0
  67. data/collectors/ping/ping +57 -0
  68. data/collectors/ping/ping.json +7 -0
  69. data/collectors/process/README.md +36 -0
  70. data/collectors/process/process +61 -0
  71. data/collectors/process/process.json +7 -0
  72. data/collectors/service/README.md +51 -0
  73. data/collectors/service/samples/.gitignore +1 -0
  74. data/collectors/service/samples/data/disconnect +11 -0
  75. data/collectors/service/samples/data/flappy +7 -0
  76. data/collectors/service/samples/data/solid +18 -0
  77. data/collectors/service/samples/replay +27 -0
  78. data/collectors/service/service +86 -0
  79. data/collectors/service/service.json +7 -0
  80. data/collectors/smtp/lib/panoptimon-collector-smtp/smtp.rb +30 -0
  81. data/collectors/smtp/lib/panoptimon-collector-smtp.rb +1 -0
  82. data/collectors/smtp/smtp +27 -0
  83. data/collectors/smtp/smtp.json +10 -0
  84. data/collectors/socket/README.md +36 -0
  85. data/collectors/socket/lib/panoptimon-collector-socket/socket.rb +38 -0
  86. data/collectors/socket/lib/panoptimon-collector-socket/tcp.rb +34 -0
  87. data/collectors/socket/lib/panoptimon-collector-socket/unix.rb +28 -0
  88. data/collectors/socket/lib/panoptimon-collector-socket.rb +3 -0
  89. data/collectors/socket/socket +13 -0
  90. data/collectors/socket/socket.json +16 -0
  91. data/collectors/socket/tests/tcp_spec.rb +21 -0
  92. data/collectors/socket/tests/unix_spec.rb +35 -0
  93. data/collectors/ssh/README.md +27 -0
  94. data/collectors/ssh/ssh +41 -0
  95. data/collectors/ssh/ssh.json +3 -0
  96. data/lib/panoptimon/collector.rb +135 -0
  97. data/lib/panoptimon/eventmonkeypatch/popen3.rb +40 -0
  98. data/lib/panoptimon/http.rb +63 -0
  99. data/lib/panoptimon/logger.rb +19 -0
  100. data/lib/panoptimon/monitor.rb +154 -0
  101. data/lib/panoptimon/util/string-with-as_number.rb +5 -0
  102. data/lib/panoptimon/util.rb +23 -0
  103. data/lib/panoptimon/version.rb +5 -0
  104. data/lib/panoptimon.rb +144 -0
  105. data/misc/collector_setup.rb +23 -0
  106. data/misc/monitor_setup.rb +25 -0
  107. data/misc/plugins_setup.rb +25 -0
  108. data/misc/riemann-cli.rb +33 -0
  109. data/panoptimon.gemspec +33 -0
  110. data/plugins/daemon_health/README.md +31 -0
  111. data/plugins/daemon_health/daemon_health.json +4 -0
  112. data/plugins/daemon_health/daemon_health.rb +34 -0
  113. data/plugins/daemon_health/lib/panoptimon-plugin-daemon_health/rollup.rb +64 -0
  114. data/plugins/daemon_health/panoptimon-plugin-daemon_health.gemspec +10 -0
  115. data/plugins/daemon_health/spec/moving_avg_spec.rb +24 -0
  116. data/plugins/email/README.md +30 -0
  117. data/plugins/email/email.json +3 -0
  118. data/plugins/email/email.rb +52 -0
  119. data/plugins/log_to_file/log_to_file.json +1 -0
  120. data/plugins/log_to_file/log_to_file.rb +8 -0
  121. data/plugins/log_to_logger/log_to_logger.json +3 -0
  122. data/plugins/log_to_logger/log_to_logger.rb +7 -0
  123. data/plugins/metrics_http/README.md +23 -0
  124. data/plugins/metrics_http/metrics_http.json +1 -0
  125. data/plugins/metrics_http/metrics_http.rb +17 -0
  126. data/plugins/riemann_stream/requires +1 -0
  127. data/plugins/riemann_stream/riemann_stream.json +3 -0
  128. data/plugins/riemann_stream/riemann_stream.rb +23 -0
  129. data/plugins/status_http/requires +1 -0
  130. data/plugins/status_http/status_http.json +1 -0
  131. data/plugins/status_http/status_http.rb +60 -0
  132. data/sample_configs/1/collectors/alls_well.json +6 -0
  133. data/sample_configs/1/collectors/clock/clock +12 -0
  134. data/sample_configs/1/collectors/clock.json +1 -0
  135. data/sample_configs/1/collectors/df/df.json +6 -0
  136. data/sample_configs/1/collectors/df/wrap_df +21 -0
  137. data/sample_configs/1/collectors/load.json +1 -0
  138. data/sample_configs/1/panoptimon.json +4 -0
  139. data/sample_configs/1/plugins/isup/isup.rb +3 -0
  140. data/sample_configs/1/plugins/isup.json +1 -0
  141. data/sample_configs/1/plugins/log_to_file.json +1 -0
  142. data/sample_configs/err_handler/collectors/fail.json +4 -0
  143. data/sample_configs/err_handler/collectors/noisy.json +5 -0
  144. data/sample_configs/err_handler/collectors/noisy_failure.json +5 -0
  145. data/sample_configs/err_handler/collectors/notfound.json +4 -0
  146. data/sample_configs/err_handler/panoptimon.json +3 -0
  147. data/sample_configs/err_handler/plugins/.exists +0 -0
  148. data/sample_configs/passthru/collectors/beep.json +5 -0
  149. data/sample_configs/passthru/collectors/cat/collect_this.json +1 -0
  150. data/sample_configs/passthru/collectors/cat.json +5 -0
  151. data/sample_configs/passthru/panoptimon.json +3 -0
  152. data/sample_configs/passthru/plugins/okcat/okcat.rb +17 -0
  153. data/sample_configs/passthru/plugins/okcat.json +1 -0
  154. data/sample_configs/plugin_error/collectors/beep.json +5 -0
  155. data/sample_configs/plugin_error/panoptimon.json +1 -0
  156. data/sample_configs/plugin_error/plugins/error_always/error_always.rb +1 -0
  157. data/sample_configs/plugin_error/plugins/error_always.json +1 -0
  158. data/sample_configs/slow/collectors/slowbeep.json +6 -0
  159. data/sample_configs/slow/panoptimon.json +1 -0
  160. data/sample_configs/slow/plugins/.exists +0 -0
  161. data/sample_configs/timeout_newline/collectors/slow_lf.json +8 -0
  162. data/sample_configs/timeout_newline/panoptimon.json +1 -0
  163. data/sample_configs/timeout_newline/plugins/.exists +0 -0
  164. data/sample_configs/timeout_not/collectors/slowly.json +7 -0
  165. data/sample_configs/timeout_not/panoptimon.json +1 -0
  166. data/sample_configs/timeout_not/plugins/.exists +0 -0
  167. data/spec/collector/config_spec.rb +30 -0
  168. data/spec/collector/initialize_spec.rb +24 -0
  169. data/spec/collector/metric_spec.rb +22 -0
  170. data/spec/passthru_spec.rb +13 -0
  171. data/spec/util_spec.rb +37 -0
  172. data/tools/link_and_enable +37 -0
  173. data/tools/metricify +8 -0
  174. 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,7 @@
1
+ {
2
+ "checks" : {
3
+ "sshd" : { "full" : "^/usr/sbin/sshd" },
4
+ "root shells" : { "pattern" : "^(ba)?sh", "user" : "root" },
5
+ "daemons" : { "user" : "daemon,nobody,www-data" }
6
+ }
7
+ }
@@ -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,7 @@
1
+ up (pid 3657) 132 seconds
2
+ up (pid 3657) 192 seconds
3
+ up (pid 3657) 252 seconds
4
+ up (pid 3658) 25 seconds
5
+ up (pid 3659) 27 seconds
6
+ up (pid 3660) 22 seconds
7
+ up (pid 3661) 12 seconds
@@ -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,7 @@
1
+ {
2
+ "interval" : 60,
3
+ "flaptime" : 30, "since" : 900,
4
+ "services" : {
5
+ "daemontools" : { "-monitor" : ["/service/*"] }
6
+ }
7
+ }
@@ -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,10 @@
1
+ {
2
+ "hosts": [
3
+ "smtp.gmail.com",
4
+ {"host": "localhost", "port": "235"}
5
+ ],
6
+ "default_port" : 587,
7
+ "default_timeout" : 7,
8
+
9
+ "interval" : 240
10
+ }
@@ -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,3 @@
1
+ require 'panoptimon-collector-socket/socket'
2
+ require 'panoptimon-collector-socket/tcp'
3
+ require 'panoptimon-collector-socket/unix'
@@ -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,16 @@
1
+ {
2
+ "checks": {
3
+ "haproxy": {
4
+ "path": "/var/run/haproxy/stats",
5
+ "timeout": 10,
6
+ "query": "show info",
7
+ "match": ".*"
8
+ },
9
+ "ssh": {
10
+ "path": "localhost",
11
+ "port": 22,
12
+ "timeout": 5,
13
+ "match": "SSH"
14
+ }
15
+ }
16
+ }
@@ -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
+ ```
@@ -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
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "hosts": ["localhost"]
3
+ }