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,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true # persistent process
4
+
5
+ require 'json'
6
+ opt = ARGV[0] ? JSON::parse(ARGV[0]) : {'interval' => 1, 'count' => 2}
7
+
8
+ cmd = %w{iostat -xd}
9
+ if opt['interval']
10
+ cmd.push(opt['interval'].to_s)
11
+ cmd.push(opt['count'].to_s) if opt['count']
12
+ end
13
+ p = IO.popen(cmd, 'r')
14
+
15
+ [1,2].each { x = p.readline until x == "\n" } # header + first sample
16
+
17
+ puts '{}' # beep
18
+
19
+ def prep l
20
+ want = {
21
+ 'kb_read/s' => 'rkB/s',
22
+ 'kb_write/s' => 'wkB/s',
23
+ 'rrqm/s' => 'rrqm/s',
24
+ 'wrqm/s' => 'wrqm/s',
25
+ 'reads/s' => 'r/s',
26
+ 'writes/s' => 'w/s',
27
+ 'avgrq-sz' => 'avgrq-sz',
28
+ 'avgqu-sz' => 'avgqu-sz',
29
+ 'await' => 'await',
30
+ 'r_await' => 'r_await',
31
+ 'w_await' => 'w_await',
32
+ 'util' => '%util',
33
+ }
34
+ head = l.chomp.split(/\s+/)
35
+ head = Hash[*head.zip(0..(head.length-1)).flatten]
36
+ raise "oops" unless head.values_at(*want.values).length == want.values.length
37
+ o = {}; want.each {|k,v| o[k] = head[v]}
38
+ return o
39
+ end
40
+
41
+ omap = nil
42
+ group = {}
43
+
44
+ until p.eof?
45
+ l = p.readline("\n\n").split(/\n/)
46
+ omap ||= prep(l[0])
47
+ puts JSON::generate(Hash[*l.drop(1).map {|x|
48
+ r = x.split(/\s+/)
49
+ [r[0], # device name
50
+ Hash[*omap.keys.map {|k| [k, r[omap[k]].to_f]}.flatten]]
51
+ }.flatten])
52
+
53
+ end
@@ -0,0 +1 @@
1
+ {"interval":1}
@@ -0,0 +1,27 @@
1
+ # Config
2
+
3
+ Each item in the 'collect' hash becomes the name of a collector output.
4
+ The value can be simply a URL, or a hash ("url" entry required) with
5
+ additional options.
6
+
7
+ ```json
8
+ {
9
+ "collect" : {
10
+ "zebra" : "http://zebracage.localnet.zoo/status.json",
11
+ "monkey" : { "url" : "http://monkeycage.localnet.zoo/status.json" }
12
+ },
13
+ "default_timeout" : 3,
14
+
15
+ }
16
+ ```
17
+
18
+ # Output
19
+
20
+ Each collector name is reported as a toplevel name, along with the
21
+ collected hash -- as retrieved. If there is a timeout (on connect or
22
+ get), the value of the 'timeout' metric will be true.
23
+
24
+ ```json
25
+ { "_name" : "zebra", "temp" : 20, "rh" : 85, ... }
26
+ { "_name" : "monkey", "missing" : true, "last_seen" : 270 }
27
+ ```
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ ARGV[0] or raise "arguments required"
7
+ conf = JSON.parse(ARGV[0], {symbolize_names: true})
8
+
9
+ defaults = {
10
+ timeout: conf[:default_timeout] || 3,
11
+ }
12
+
13
+ raise "must have 'collect' value in config" unless conf[:collect]
14
+ setup = conf[:collect].map {|k,v|
15
+ o = defaults.merge(v.is_a?(Hash) ? v : {url: v})
16
+ o[:url] = URI( o[:url].match(%r{^\w+://}) ?
17
+ o[:url] : 'http://' . o[:url] )
18
+ o[:name] ||= k
19
+ o
20
+ }
21
+
22
+ go = ->(url, o){
23
+ response = begin; Timeout::timeout(o[:timeout]) {
24
+ r = ::Net::HTTP.start(url.host, url.port,
25
+ :use_ssl => url.scheme == 'https',
26
+ ).request(::Net::HTTP::Get.new(url.request_uri)) }
27
+ JSON.parse(r.body, {sybolize_names: true})
28
+ rescue Timeout::Error; {timeout: true}; end
29
+ }
30
+
31
+ # TODO consider persistence / staggering
32
+ setup.each {|o|
33
+ output = go[o[:url], o]
34
+ output[:_name] = o[:name]
35
+ puts JSON.generate(output)
36
+ }
37
+
@@ -0,0 +1 @@
1
+ {"collect" : { "stuff" : "http://localhost:8089" }}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ l = File.new('/proc/loadavg').read.split(/\s+/)
5
+ (j,n) = l[3].split('/')
6
+
7
+ # TODO support for systems with no /proc
8
+
9
+ puts JSON::generate({
10
+ avg_1: l[0].to_f,
11
+ avg_5: l[1].to_f,
12
+ avg_15: l[2].to_f,
13
+ jobs: j.to_i,
14
+ n: n.to_i})
15
+
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2012 Sourcefire, Inc.
3
+
4
+
5
+ require 'json'
6
+ opt = ARGV[0] ? JSON::parse(ARGV[0], {symbolize_names: true}) : {}
7
+
8
+ # TODO require 'file/which' ne see stackoverflow 2108727?
9
+ require 'pathname'
10
+ class Pathname; def self.which(cmd)
11
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
12
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
13
+ path = Pathname.new(path)
14
+ exts.each { |ext|
15
+ it = path + "#{cmd}#{ext}"
16
+ return it if it.executable?
17
+ }
18
+ end
19
+ raise "No such command '#{cmd}' in $PATH"
20
+ end; end
21
+
22
+ class String; def as_number # from perlfaq4
23
+ self =~ %r{\A[+-]?(?=\.?\d)\d*\.?\d*(?:[Ee][+-]?\d+)?\z} \
24
+ ? (self =~ %r{[\.Ee]} ? self.to_f : self.to_i)
25
+ : nil
26
+ end; end
27
+
28
+ class Array; def to_h ; Hash[*self.flatten]; end; end
29
+
30
+ hosts = opt[:hosts] || {default: 'localhost'}
31
+ hosts.values.each {|x| x += ':11211' unless x =~ /:\d+$/}
32
+ hostmap = hosts.map {|k,v| [v,k]}.to_h
33
+
34
+ runcmd = [opt[:memstat] || Pathname.which('memstat')].flatten.push(
35
+ '--servers=' + hostmap.keys.join(',')
36
+ )
37
+
38
+ lines = IO.popen(runcmd).readlines.map {|l| l.chomp.split(/:\s+/, 2)}
39
+ puts JSON.generate(lines.inject([]) {|l,p|
40
+ if p[0] == 'Server'
41
+ p[1] =~ %r{(.*)\s\((\d+)\)$} or raise "'#{p[1]}' - unexpected format"
42
+ host,port = $1,$2
43
+ name = hostmap["#{host}:#{port}"] or raise "unnamed host:port '#{host}:#{port}'"
44
+ l.push([name, {_info: {host: host, port: port.to_i}}])
45
+ else
46
+ key = p[0].sub(/^\s+/, '')
47
+ m = p[1].as_number
48
+ if m.nil?
49
+ l[-1][-1][:_info][key] = p[1]
50
+ else
51
+ l[-1][-1][key] = m
52
+ end
53
+ end
54
+ l
55
+ }.to_h)
@@ -0,0 +1,7 @@
1
+ {
2
+ "hosts": {
3
+ "jim" : "10.0.0.1:11211",
4
+ "bob" : "10.0.0.2:11211"
5
+ },
6
+ "memstat" : "/usr/bin/memstat"
7
+ }
@@ -0,0 +1,3 @@
1
+ # just lie about where memstat is to replay a static copy
2
+ ruby collectors/memcached/memcached '{"memstat" : ["cat", "/tmp/memstat-sample.txt"]}'
3
+
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ config = ARGV[0] ? JSON::parse(ARGV[0]) : {}
6
+
7
+ $stdout.sync = true
8
+
9
+ def GB b; (b.to_f / 1024**2).round(6); end # GB significant down to 4kB
10
+
11
+ while(true) do
12
+
13
+ info = File.readlines('/proc/meminfo').map {|l|
14
+ l = l.chomp.split(/:\s+/, 2)
15
+ raise "units assumption failed" if
16
+ l[1].sub!(/\s+(kB)$/, '') and not($+ == 'kB')
17
+ l
18
+ }
19
+ i = Hash[*info.flatten]
20
+
21
+ out = {
22
+ total: GB(i['MemTotal']),
23
+ free: GB(i['MemFree']),
24
+ buffers: GB(i['Buffers']),
25
+ cached: GB(i['Cached']),
26
+ swapped: GB(i['SwapTotal'].to_i - i['SwapFree'].to_i),
27
+ swap_free: GB(i['SwapFree']),
28
+ }
29
+ puts JSON::generate(out)
30
+
31
+ break unless config.include?('interval')
32
+ sleep config['interval']
33
+ end
@@ -0,0 +1 @@
1
+ {"interval":1}
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mysql';
4
+ require 'json';
5
+
6
+ class String; def as_number # from perlfaq4
7
+ self =~ %r{\A[+-]?(?=\.?\d)\d*\.?\d*(?:[Ee][+-]?\d+)?\z} \
8
+ ? (self =~ %r{[\.Ee]} ? self.to_f : self.to_i)
9
+ : nil
10
+ end; end
11
+
12
+ class Mysql_status
13
+
14
+ def self.get_status (db)
15
+ rs = db.query("SHOW STATUS")
16
+ h = {}
17
+ while( r = rs.fetch_row )
18
+ h[r[0].downcase] = r[1]
19
+ end
20
+ return h
21
+ end
22
+
23
+ def self.get_slave_status (db)
24
+ rs = db.query('SHOW SLAVE STATUS')
25
+ h = rs.fetch_hash or return {}
26
+ raise "too many results" if rs.num_rows > 1
27
+ return Hash[* h.keys.map {|k| [k.downcase, h[k]]}.flatten]
28
+ end
29
+
30
+ def self.run (config)
31
+ db = Mysql.new(*
32
+ %w{hostname username password database port socket}.
33
+ map {|k| config[k]})
34
+
35
+ m = get_status(db)
36
+ m.merge(get_slave_status(db))
37
+ info = {}
38
+ m.keys.each {|k|
39
+ n = m[k].as_number
40
+ if n
41
+ m[k] = n
42
+ else
43
+ info[k] = m.delete(k)
44
+ end
45
+ }
46
+ m[:_info] = info if info.length > 0
47
+ puts JSON::generate(m)
48
+ end
49
+
50
+ end
51
+
52
+ Mysql_status.run(JSON::parse(ARGV[0])) if __FILE__ == $0
@@ -0,0 +1,4 @@
1
+ {
2
+ "description" : "mysql status and slave status collector. Config must provide some valid combination of hostname,username,password,database,port,socket to connect.",
3
+ "socket" : "/tmp/mysql.socket"
4
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true # persistent process
4
+
5
+ require 'json'
6
+ opt = ARGV[0] ? JSON::parse(ARGV[0], {symbolize_names: true})
7
+ : {interval: 1, once: 1}
8
+ opt[:interval] ||= 5
9
+
10
+ cmd = %w{netstat -an --protocol=inet}
11
+
12
+ opt[:report] or raise "'report' section must be configured"
13
+
14
+ # TODO read /etc/services and support default numeric port match?
15
+
16
+ # allow addr to be regexp, glob, or literal
17
+ def _escape (addr)
18
+ addr =~ /\\./ ? addr
19
+ : addr =~ /\.\*/ ? addr.gsub(/\*/, '\d+').gsub(/\./, '\.')
20
+ : Regexp.escape(addr)
21
+ end
22
+
23
+ # build a report matchlist
24
+ report = opt[:report].keys.map { |k|
25
+ c = opt[:report][k]
26
+ {
27
+ name: k,
28
+ match: %r{^\w+\s+\d+\s+\d+\s+ # proto,recv,send
29
+ #{c[:localaddr] ? _escape(c[:localaddr]) : '\d+\.\d+\.\d+.\d+'}
30
+ :
31
+ #{c[:localport] ? c[:localport].to_s : '(?:\d+|\*)'}
32
+ \s+
33
+ #{c[:remoteaddr] ? _escape(c[:remoteaddr]) : '\d+\.\d+\.\d+.\d+'}
34
+ :
35
+ #{c[:remoteport] ? c[:remoteport].to_s : '(?:\d+|\*)'}
36
+ \s+
37
+ #{c[:state] ? c[:state].upcase : '\w+'}
38
+ \s*$
39
+ }x,
40
+ }
41
+ }
42
+
43
+
44
+ # TODO support netstat --continous if interval < 2 or something? Would
45
+ # require output to be delayed or something since there is no end marker
46
+ # to the netstat output.
47
+
48
+ while true
49
+
50
+ p = IO.popen(cmd)
51
+ lines = p.readlines.map {|l| l.chomp}
52
+ p.close # autoclose my foot
53
+ lines.shift; lines.shift
54
+
55
+ out = {}
56
+ lines.each { |l|
57
+ report.find_all {|r| l =~ r[:match]}.each {|match|
58
+ out[match[:name]] ||= 0
59
+ out[match[:name]] += 1
60
+ }
61
+ }
62
+
63
+ puts JSON::generate(out)
64
+
65
+ break if opt[:once]
66
+ sleep opt[:interval]
67
+ end
@@ -0,0 +1,18 @@
1
+ {
2
+ "report": {
3
+ "gemserver": {
4
+ "localaddr": "127.0.0.1",
5
+ "localport": 8808
6
+ },
7
+ "ssh" : {
8
+ "localport" : 22,
9
+ "state" : "ESTABLISHED"
10
+ },
11
+ "daemons" : { "state" : "LISTEN" },
12
+ "servers" : { "state" : "LISTEN", "localaddr" : "0.0.0.0" },
13
+ "self" : {"state" : "ESTABLISHED", "remoteaddr" : "127.0.0.1"},
14
+ "east" : {"remoteaddr" : "66\\.225\\.\\d+\\.\\d+"},
15
+ "west" : {"remoteaddr" : "173.194.*.*"}
16
+ }
17
+ ,"interval" : 1
18
+ }
@@ -0,0 +1,32 @@
1
+ #Description
2
+
3
+ The Panoptimon collector 'nginx' is intended to retrieve statistics from a
4
+ running nginx instance. The nginx module 'HTTPStubStatus' is required (see
5
+ below.)
6
+
7
+ # Config
8
+
9
+ ```json
10
+ {"url": "http://localhost/nginx-status"}
11
+ ```
12
+
13
+ # Output
14
+
15
+ ```
16
+ {"requests":"137","writing":"1","total":"1","reading":"0"}
17
+ ```
18
+
19
+
20
+ # Requisite Server Setup
21
+
22
+ The nginx server must provide the information via its HTTPStubStatus
23
+ module. Within a server definition simply add the following.
24
+
25
+ ```
26
+ location /nginx-status {
27
+ stub_status on;
28
+ access_log off;
29
+ allow 127.0.0.1;
30
+ deny all;
31
+ }
32
+ ```
@@ -0,0 +1,45 @@
1
+ module Panoptimon
2
+ module Collector
3
+ class Nginx
4
+
5
+ attr_reader :uri
6
+ def initialize(options={})
7
+ options = default_options.merge!(options)
8
+ @uri = URI(options[:url])
9
+ end
10
+
11
+ def default_options
12
+ {url: 'http://localhost/nginx-status'}
13
+ end
14
+
15
+ def connect
16
+ @connect ||= Net::HTTP.new(uri.host, uri.port)
17
+ end
18
+
19
+ def request
20
+ @request ||= Net::HTTP::Get.new(uri.request_uri)
21
+ end
22
+
23
+ def response
24
+ @response ||= connect.request(request)
25
+ end
26
+
27
+ def info(body=nil)
28
+ body ||= response.body
29
+ raise "not a status report page" if body.match('<') and
30
+ not body.match(/^Active/)
31
+ begin
32
+ {
33
+ requests: body.match('\s+(\d+)\s+(\d+)\s+(\d+)')[3].to_i,
34
+ total: body.match('connections:\s(\d+)')[1].to_i,
35
+ reading: body.match('Reading:\s(\d+)')[1].to_i,
36
+ writing: body.match('Writing:\s(\d+)')[1].to_i
37
+ }
38
+ rescue NoMethodError
39
+ raise "probably not status page"
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,2 @@
1
+ require 'net/http'
2
+ require 'panoptimon-collector-nginx/nginx'
@@ -0,0 +1,11 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
4
+ require 'rubygems'
5
+ require 'json'
6
+ require 'panoptimon-collector-nginx'
7
+
8
+ client = Panoptimon::Collector::Nginx.new(
9
+ ARGV[0] ? JSON.parse(ARGV[0], {symbolize_names: true}) : {})
10
+ puts client.info.to_json
11
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "url": "http://localhost/nginx-status"
3
+ }
@@ -0,0 +1,4 @@
1
+ Gem::Specification.new { |s|
2
+
3
+ s.add_dependency 'net-http'
4
+ }
@@ -0,0 +1,54 @@
1
+
2
+ # Permissions
3
+
4
+ Your ping binary must be `setcap cap_net_raw=ep` or setuid root. The
5
+ collector itself requires no special permissions.
6
+
7
+ # Config
8
+
9
+ ```json
10
+ {
11
+ "hosts" : [
12
+ "example.com",
13
+ # or with per-host settings
14
+ {"host" : "localhost", "timeout" : 1, "count" : 1}
15
+ ],
16
+
17
+ # defaults
18
+ "default_timeout" : 4,
19
+ "default_count" : 2,
20
+ "ping" : "/bin/ping", # defaults to search $PATH
21
+
22
+ # standard collector configuration
23
+ "interval" : ...
24
+ "timeout" : ...
25
+ "exec" : "ping/ping",
26
+ "args" : [],
27
+ }
28
+ ```
29
+
30
+ # Output
31
+
32
+ {
33
+ "ping|example.com|loss" : 0, # 0-1 float
34
+ "ping|example.com|rtt_avg" : 54.47,
35
+ "ping|example.com|rtt_max" : 55.914,
36
+ "ping|example.com|rtt_min" : 53.027,
37
+ "ping|example.com|rtt_mdev" : 1.462,
38
+ "ping|example.com|rx" : 8, # received
39
+ "ping|example.com|tx" : 8, # transmitted
40
+ "ping|example.com|_info" : { "resolved" : "192.0.43.10" }
41
+ }
42
+
43
+
44
+ # Errors
45
+
46
+ {
47
+ "ping|whatever.example.com|error" : 2, # return code
48
+ "ping|whatever.example.com|_info" : {
49
+ "error" : "ping: unknown host whatever.example.com"
50
+ },
51
+ "ping|whatever.example.com|loss" : 1,
52
+ "ping|whatever.example.com|rx" : 0,
53
+ "ping|whatever.example.com|tx" : 0
54
+ }
@@ -0,0 +1,57 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ conf = ARGV[0] ? JSON.parse(ARGV[0], {symbolize_names: true}) : {}
6
+
7
+ bin_ping = [conf[:ping] || ['ping']].flatten
8
+
9
+ defaults = {
10
+ timeout: conf[:default_timeout] || 4,
11
+ count: conf[:default_count] || 2,
12
+ }
13
+
14
+ setup = Hash[conf[:hosts].map {|h|
15
+ (host, opt) = h.is_a?(Hash) ? [h[:host], h.tap{|x| x.delete(:host)}]
16
+ : [h, {}]
17
+ opt = defaults.merge(opt)
18
+ [host, opt]
19
+ }]
20
+
21
+ pings = Hash[setup.map {|host, opt|
22
+ args = ['-c', opt[:count], '-W', opt[:timeout]]
23
+ p = IO.popen((bin_ping + args + [host]).map {|e| e.to_s} +
24
+ [:err => [:child, :out]])
25
+ text = p.readlines.join('')
26
+ p.close
27
+ if not($?.success?) and not(text.match(%r{% packet loss}))
28
+ raise "runnaway ping" unless $?.exited? # never
29
+ next [host, { error: $?.exitstatus, tx: 0, rx: 0, loss: 1.0,
30
+ _info: {error: text.chomp}}]
31
+ end
32
+
33
+ matched = text.match(%r{
34
+ ^PING\ [^(]+\((?<resolved>[^)]+)\).*
35
+ ^(?<tx>\d+)\ packets\ transmitted,\s+
36
+ (?<rx>\d+)\ received,\s+
37
+ (?<loss>\d+)%\ packet\ loss,
38
+ }xm) or raise "sad bunny said #{text} - cannot parse"
39
+ result = Hash[ %w{tx rx loss}.map {|n| [n, matched[n].to_i]} ].
40
+ merge({ _info: { resolved: matched[:resolved], } })
41
+ result['loss'] /= 100.0
42
+
43
+ if matched = text.match(%r{
44
+ ^rtt\ min/avg/max/mdev\ =\s+
45
+ (?<rtt_min>\d*\.\d+)/
46
+ (?<rtt_avg>\d*\.\d+)/
47
+ (?<rtt_max>\d*\.\d+)/
48
+ (?<rtt_mdev>\d*\.\d+)\ ms
49
+ }xm)
50
+ result.merge!(Hash[ %w{rtt_min rtt_avg rtt_max rtt_mdev}.map {|n|
51
+ [n, matched[n].to_f]} ])
52
+ end
53
+
54
+ [host, result]
55
+ }]
56
+
57
+ puts JSON.generate(pings)
@@ -0,0 +1,7 @@
1
+ {
2
+ "hosts": [
3
+ "example.com",
4
+ {"host" : "localhost", "timeout" : 1, "count" : 1}
5
+ ]
6
+ ,"default_count" : 8
7
+ }
@@ -0,0 +1,36 @@
1
+ Returns information about a process, or set of processes.
2
+
3
+ Can look simply for a process name, or match against an expression, search for all processed owned by a user... can also match processes that match certain characteristics as defined in the optional arguments section.
4
+
5
+ ps -wwo 'pid,time,etime,thcount,pcpu,ni,pri,vsz,rss,command' \
6
+ -p $(pgrep -d, sshd)
7
+
8
+ # Config
9
+
10
+ The "checks" hash contains a list of check names, plus info for invoking pgrep.
11
+
12
+ ```json
13
+ {
14
+ interval: 60,
15
+ checks: {
16
+ sshd: {
17
+ pattern: "^sshd", # also 'full: "^/usr/sbin/sshd"
18
+ user: "root,daemon"
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ # Return:
25
+
26
+ Information about each matched process, plus a count.
27
+
28
+ process|ssh|count => 1
29
+ process|ssh|0|time => 400
30
+ process|ssh|0|etime => 6000
31
+ process|ssh|0|pcpu => 0.2
32
+ process|ssh|0|thcount => 1
33
+ process|ssh|0|rss => ...
34
+ process|ssh|0|nice => ...
35
+ process|ssh|0|priority => ...
36
+ process|ssh|_info => { 'cmd' => [...] }