panoptimon 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE +29 -0
- data/README.md +78 -0
- data/Rakefile +14 -0
- data/bin/panoptimon +118 -0
- data/collectors/cpu/cpu +39 -0
- data/collectors/cpu/cpu.json +1 -0
- data/collectors/disk/disk +37 -0
- data/collectors/disk/disk.json +1 -0
- data/collectors/disk/requires +1 -0
- data/collectors/disk_free/disk_free +15 -0
- data/collectors/disk_free/disk_free.json +1 -0
- data/collectors/dns/README.md +33 -0
- data/collectors/dns/dns +9 -0
- data/collectors/dns/dns.json +7 -0
- data/collectors/dns/lib/panoptimon-collector-dns/dns.rb +43 -0
- data/collectors/dns/lib/panoptimon-collector-dns.rb +2 -0
- data/collectors/dns/panoptimon-collector-dns.gemspec +4 -0
- data/collectors/files/README.md +32 -0
- data/collectors/files/files +129 -0
- data/collectors/files/files.json +14 -0
- data/collectors/files/spec/files_spec.rb +57 -0
- data/collectors/haproxy/README.md +40 -0
- data/collectors/haproxy/haproxy +16 -0
- data/collectors/haproxy/haproxy.json +3 -0
- data/collectors/haproxy/lib/panoptimon-collector-haproxy/haproxy.rb +149 -0
- data/collectors/haproxy/lib/panoptimon-collector-haproxy.rb +1 -0
- data/collectors/haproxy/notes.txt +13 -0
- data/collectors/haproxy/spec/haproxy_spec.rb +98 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-get.html +22 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_info.txt +21 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_stat.csv +25 -0
- data/collectors/haproxy/spec/haproxy_spec.rb-show_stat2.csv +11 -0
- data/collectors/http/README.md +49 -0
- data/collectors/http/http +27 -0
- data/collectors/http/http.json +3 -0
- data/collectors/http/lib/panoptimon-collector-http/http.rb +74 -0
- data/collectors/http/lib/panoptimon-collector-http/version.rb +7 -0
- data/collectors/http/lib/panoptimon-collector-http.rb +2 -0
- data/collectors/interfaces/interfaces +33 -0
- data/collectors/interfaces/interfaces.json +1 -0
- data/collectors/iostat/iostat +53 -0
- data/collectors/iostat/iostat.json +1 -0
- data/collectors/json/README.md +27 -0
- data/collectors/json/json +37 -0
- data/collectors/json/json.json +1 -0
- data/collectors/load/load +15 -0
- data/collectors/load/load.json +1 -0
- data/collectors/memcached/memcached +55 -0
- data/collectors/memcached/memcached.json +7 -0
- data/collectors/memcached/test-notes.txt +3 -0
- data/collectors/memory/memory +33 -0
- data/collectors/memory/memory.json +1 -0
- data/collectors/mysql_status/mysql_status +52 -0
- data/collectors/mysql_status/mysql_status.json +4 -0
- data/collectors/network/network +67 -0
- data/collectors/network/network.json +18 -0
- data/collectors/nginx/README.md +32 -0
- data/collectors/nginx/lib/panoptimon-collector-nginx/nginx.rb +45 -0
- data/collectors/nginx/lib/panoptimon-collector-nginx.rb +2 -0
- data/collectors/nginx/nginx +11 -0
- data/collectors/nginx/nginx.json +3 -0
- data/collectors/nginx/panoptimon-collector-nginx.gemspec +4 -0
- data/collectors/ping/README.md +54 -0
- data/collectors/ping/ping +57 -0
- data/collectors/ping/ping.json +7 -0
- data/collectors/process/README.md +36 -0
- data/collectors/process/process +61 -0
- data/collectors/process/process.json +7 -0
- data/collectors/service/README.md +51 -0
- data/collectors/service/samples/.gitignore +1 -0
- data/collectors/service/samples/data/disconnect +11 -0
- data/collectors/service/samples/data/flappy +7 -0
- data/collectors/service/samples/data/solid +18 -0
- data/collectors/service/samples/replay +27 -0
- data/collectors/service/service +86 -0
- data/collectors/service/service.json +7 -0
- data/collectors/smtp/lib/panoptimon-collector-smtp/smtp.rb +30 -0
- data/collectors/smtp/lib/panoptimon-collector-smtp.rb +1 -0
- data/collectors/smtp/smtp +27 -0
- data/collectors/smtp/smtp.json +10 -0
- data/collectors/socket/README.md +36 -0
- data/collectors/socket/lib/panoptimon-collector-socket/socket.rb +38 -0
- data/collectors/socket/lib/panoptimon-collector-socket/tcp.rb +34 -0
- data/collectors/socket/lib/panoptimon-collector-socket/unix.rb +28 -0
- data/collectors/socket/lib/panoptimon-collector-socket.rb +3 -0
- data/collectors/socket/socket +13 -0
- data/collectors/socket/socket.json +16 -0
- data/collectors/socket/tests/tcp_spec.rb +21 -0
- data/collectors/socket/tests/unix_spec.rb +35 -0
- data/collectors/ssh/README.md +27 -0
- data/collectors/ssh/ssh +41 -0
- data/collectors/ssh/ssh.json +3 -0
- data/lib/panoptimon/collector.rb +135 -0
- data/lib/panoptimon/eventmonkeypatch/popen3.rb +40 -0
- data/lib/panoptimon/http.rb +63 -0
- data/lib/panoptimon/logger.rb +19 -0
- data/lib/panoptimon/monitor.rb +154 -0
- data/lib/panoptimon/util/string-with-as_number.rb +5 -0
- data/lib/panoptimon/util.rb +23 -0
- data/lib/panoptimon/version.rb +5 -0
- data/lib/panoptimon.rb +144 -0
- data/misc/collector_setup.rb +23 -0
- data/misc/monitor_setup.rb +25 -0
- data/misc/plugins_setup.rb +25 -0
- data/misc/riemann-cli.rb +33 -0
- data/panoptimon.gemspec +33 -0
- data/plugins/daemon_health/README.md +31 -0
- data/plugins/daemon_health/daemon_health.json +4 -0
- data/plugins/daemon_health/daemon_health.rb +34 -0
- data/plugins/daemon_health/lib/panoptimon-plugin-daemon_health/rollup.rb +64 -0
- data/plugins/daemon_health/panoptimon-plugin-daemon_health.gemspec +10 -0
- data/plugins/daemon_health/spec/moving_avg_spec.rb +24 -0
- data/plugins/email/README.md +30 -0
- data/plugins/email/email.json +3 -0
- data/plugins/email/email.rb +52 -0
- data/plugins/log_to_file/log_to_file.json +1 -0
- data/plugins/log_to_file/log_to_file.rb +8 -0
- data/plugins/log_to_logger/log_to_logger.json +3 -0
- data/plugins/log_to_logger/log_to_logger.rb +7 -0
- data/plugins/metrics_http/README.md +23 -0
- data/plugins/metrics_http/metrics_http.json +1 -0
- data/plugins/metrics_http/metrics_http.rb +17 -0
- data/plugins/riemann_stream/requires +1 -0
- data/plugins/riemann_stream/riemann_stream.json +3 -0
- data/plugins/riemann_stream/riemann_stream.rb +23 -0
- data/plugins/status_http/requires +1 -0
- data/plugins/status_http/status_http.json +1 -0
- data/plugins/status_http/status_http.rb +60 -0
- data/sample_configs/1/collectors/alls_well.json +6 -0
- data/sample_configs/1/collectors/clock/clock +12 -0
- data/sample_configs/1/collectors/clock.json +1 -0
- data/sample_configs/1/collectors/df/df.json +6 -0
- data/sample_configs/1/collectors/df/wrap_df +21 -0
- data/sample_configs/1/collectors/load.json +1 -0
- data/sample_configs/1/panoptimon.json +4 -0
- data/sample_configs/1/plugins/isup/isup.rb +3 -0
- data/sample_configs/1/plugins/isup.json +1 -0
- data/sample_configs/1/plugins/log_to_file.json +1 -0
- data/sample_configs/err_handler/collectors/fail.json +4 -0
- data/sample_configs/err_handler/collectors/noisy.json +5 -0
- data/sample_configs/err_handler/collectors/noisy_failure.json +5 -0
- data/sample_configs/err_handler/collectors/notfound.json +4 -0
- data/sample_configs/err_handler/panoptimon.json +3 -0
- data/sample_configs/err_handler/plugins/.exists +0 -0
- data/sample_configs/passthru/collectors/beep.json +5 -0
- data/sample_configs/passthru/collectors/cat/collect_this.json +1 -0
- data/sample_configs/passthru/collectors/cat.json +5 -0
- data/sample_configs/passthru/panoptimon.json +3 -0
- data/sample_configs/passthru/plugins/okcat/okcat.rb +17 -0
- data/sample_configs/passthru/plugins/okcat.json +1 -0
- data/sample_configs/plugin_error/collectors/beep.json +5 -0
- data/sample_configs/plugin_error/panoptimon.json +1 -0
- data/sample_configs/plugin_error/plugins/error_always/error_always.rb +1 -0
- data/sample_configs/plugin_error/plugins/error_always.json +1 -0
- data/sample_configs/slow/collectors/slowbeep.json +6 -0
- data/sample_configs/slow/panoptimon.json +1 -0
- data/sample_configs/slow/plugins/.exists +0 -0
- data/sample_configs/timeout_newline/collectors/slow_lf.json +8 -0
- data/sample_configs/timeout_newline/panoptimon.json +1 -0
- data/sample_configs/timeout_newline/plugins/.exists +0 -0
- data/sample_configs/timeout_not/collectors/slowly.json +7 -0
- data/sample_configs/timeout_not/panoptimon.json +1 -0
- data/sample_configs/timeout_not/plugins/.exists +0 -0
- data/spec/collector/config_spec.rb +30 -0
- data/spec/collector/initialize_spec.rb +24 -0
- data/spec/collector/metric_spec.rb +22 -0
- data/spec/passthru_spec.rb +13 -0
- data/spec/util_spec.rb +37 -0
- data/tools/link_and_enable +37 -0
- data/tools/metricify +8 -0
- metadata +319 -0
@@ -0,0 +1,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,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,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,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,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,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' => [...] }
|