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