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,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' => [...] }