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.
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
+ }