munin_manager 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +30 -0
- data/README.markdown +0 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/bin/munin_manager +95 -0
- data/bin/runner +18 -0
- data/lib/munin_manager.rb +9 -0
- data/lib/munin_manager/acts_as_munin_plugin.rb +82 -0
- data/lib/munin_manager/log_reader.rb +85 -0
- data/lib/munin_manager/plugins.rb +29 -0
- data/lib/munin_manager/plugins/fbproxy_latency.rb +78 -0
- data/lib/munin_manager/plugins/haproxy_app_response_time.rb +104 -0
- data/lib/munin_manager/plugins/haproxy_response_time.rb +87 -0
- data/lib/munin_manager/plugins/haproxy_rps.rb +95 -0
- data/lib/munin_manager/plugins/network_latency.rb +53 -0
- data/lib/munin_manager/plugins/notification_classification.rb +27 -0
- data/lib/munin_manager/plugins/packet_loss.rb +53 -0
- data/lib/munin_manager/plugins/rails_rendering.rb +82 -0
- data/lib/munin_manager/plugins/rails_response_time.rb +103 -0
- data/lib/munin_manager/plugins/scribe_net.rb +81 -0
- data/lib/munin_manager/plugins/starling_age.rb +77 -0
- data/lib/munin_manager/plugins/starling_net.rb +81 -0
- data/lib/munin_manager/plugins/starling_ops.rb +85 -0
- data/lib/munin_manager/plugins/starling_queue.rb +84 -0
- data/lib/munin_manager/starling/starling_stats.rb +66 -0
- data/test/haproxy_response_time_test.rb +37 -0
- data/test/log_reader_test.rb +31 -0
- data/test/rails_response_time_test.rb +51 -0
- data/test/test_helper.rb +32 -0
- metadata +86 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::HaproxyAppResponseTime < LogReader
|
3
|
+
include ActsAsMuninPlugin
|
4
|
+
|
5
|
+
def data
|
6
|
+
@data ||= Hash.new {|h, k| h[k] = Hash.new{|d,v| d[v] = Array.new}}
|
7
|
+
end
|
8
|
+
|
9
|
+
def scan(log_file)
|
10
|
+
loop do
|
11
|
+
line = log_file.readline
|
12
|
+
chunks = line.split(/\s+/)
|
13
|
+
server, port = chunks[8].split(":") rescue []
|
14
|
+
server_name = server.split("/")[1] rescue nil
|
15
|
+
next if server_name.nil?
|
16
|
+
timers = chunks[9].split("/") rescue []
|
17
|
+
data[server_name][:client_connect] << timers[0].to_f
|
18
|
+
data[server_name][:waiting_in_queue] << timers[1].to_f
|
19
|
+
data[server_name][:server_connect] << timers[2].to_f
|
20
|
+
data[server_name][:server_response] << timers[3].to_f
|
21
|
+
data[server_name][:rails_action] << line.match(/\{([0-9.]+)\}/).captures[0].to_f rescue 0
|
22
|
+
data[server_name][:total] << timers[4].to_f
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def process!
|
27
|
+
data.each do |server, values|
|
28
|
+
values.each do |k, v|
|
29
|
+
values[k] = values[k].inject(0) {|sum, i| sum + i} / values[k].length rescue 0
|
30
|
+
values[k] = formatted(values[k] / 1000)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def config
|
36
|
+
log_file = ENV['log_file'] || "/var/log/haproxy.log"
|
37
|
+
f = File.open(log_file)
|
38
|
+
count = 0
|
39
|
+
server_hash = {}
|
40
|
+
while(!f.eof? && count < 100)
|
41
|
+
count += 1
|
42
|
+
line = f.readline
|
43
|
+
chunks = line.split(/\s+/)
|
44
|
+
server, port = chunks[8].split(":") rescue []
|
45
|
+
server_name = server.split("/")[1] rescue nil
|
46
|
+
server_hash[server_name] = '' unless server_name.nil?
|
47
|
+
end
|
48
|
+
default = [:client_connect,:waiting_in_queue, :server_connect, :server_response, :rails_action, :total]
|
49
|
+
|
50
|
+
config_text = <<-LABEL
|
51
|
+
graph_title HAProxy Response Breakdown
|
52
|
+
graph_vlabel time (secs)
|
53
|
+
graph_category Haproxy
|
54
|
+
LABEL
|
55
|
+
server_hash.keys.sort.each do |server|
|
56
|
+
config_text << default.map{|k| "#{server}_#{k}.label #{server}_#{k}"}.join("\n")
|
57
|
+
config_text << "\n"
|
58
|
+
end
|
59
|
+
config_text
|
60
|
+
end
|
61
|
+
|
62
|
+
def values
|
63
|
+
data.inject([]){|datas, (server, values)| (datas + values.map{|k,v| "#{server}_#{k}.value #{v}"})}.join("\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.run
|
67
|
+
log_file = ENV['log_file'] || "/var/log/haproxy.log"
|
68
|
+
allowed_commands = ['config']
|
69
|
+
|
70
|
+
haproxy = new(log_file)
|
71
|
+
|
72
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd then
|
73
|
+
puts haproxy.send(cmd.to_sym)
|
74
|
+
else
|
75
|
+
haproxy.collect!
|
76
|
+
puts haproxy.values
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.help_text
|
81
|
+
%Q{
|
82
|
+
#{plugin_name.capitalize} Munin Plugin
|
83
|
+
===========================
|
84
|
+
|
85
|
+
Please remember to add something like the lines below to /etc/munin/plugin-conf.d/munin-node
|
86
|
+
if the haproxy log file is not at /var/log/haproxy.log
|
87
|
+
|
88
|
+
[#{plugin_name}]
|
89
|
+
env.log_file /var/log/custom/haproxy.log
|
90
|
+
|
91
|
+
Also, make sure that the '/var/lib/munin/plugin-state' is writable by munin.
|
92
|
+
|
93
|
+
$ sudo chmod 777 /var/lib/munin/plugin-state
|
94
|
+
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def formatted(num)
|
101
|
+
"%.10f" % num
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::HaproxyResponseTime < LogReader
|
3
|
+
include ActsAsMuninPlugin
|
4
|
+
|
5
|
+
def data
|
6
|
+
@data ||= Hash.new {|h, k| h[k] = Array.new}
|
7
|
+
end
|
8
|
+
|
9
|
+
def scan(log_file)
|
10
|
+
loop do
|
11
|
+
line = log_file.readline
|
12
|
+
chunks = line.split(/\s+/)
|
13
|
+
|
14
|
+
timers = chunks[9].split("/") rescue []
|
15
|
+
data[:client_connect] << timers[0].to_f
|
16
|
+
data[:waiting_in_queue] << timers[1].to_f
|
17
|
+
data[:server_connect] << timers[2].to_f
|
18
|
+
data[:server_response] << timers[3].to_f
|
19
|
+
data[:rails_action] << line.match(/\{([0-9.]+)\}/).captures[0].to_f * 1000 rescue 0
|
20
|
+
data[:total] << timers[4].to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def process!
|
25
|
+
data.each do |k, v|
|
26
|
+
data[k] = data[k].inject(0) {|sum, i| sum + i} / data[k].length rescue 0
|
27
|
+
data[k] = formatted(data[k] / 1000)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
<<-LABEL
|
33
|
+
graph_title HAProxy Response Breakdown
|
34
|
+
graph_vlabel time (secs)
|
35
|
+
graph_category Haproxy
|
36
|
+
client_connect.label Client Connect
|
37
|
+
waiting_in_queue.label Waiting In Queue
|
38
|
+
server_connect.label Server Connect
|
39
|
+
server_response.label Server Response
|
40
|
+
rails_action.label Rails Controller Action
|
41
|
+
total.label Total Response Time
|
42
|
+
LABEL
|
43
|
+
end
|
44
|
+
|
45
|
+
def values
|
46
|
+
data.map {|k, v| "#{k}.value #{v}"}.join("\n")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.run
|
50
|
+
log_file = ENV['log_file'] || "/var/log/haproxy.log"
|
51
|
+
allowed_commands = ['config']
|
52
|
+
|
53
|
+
haproxy = new(log_file)
|
54
|
+
|
55
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd then
|
56
|
+
puts haproxy.send(cmd.to_sym)
|
57
|
+
else
|
58
|
+
haproxy.collect!
|
59
|
+
puts haproxy.values
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.help_text
|
64
|
+
%Q{
|
65
|
+
#{plugin_name.capitalize} Munin Plugin
|
66
|
+
===========================
|
67
|
+
|
68
|
+
Please remember to add something like the lines below to /etc/munin/plugin-conf.d/munin-node
|
69
|
+
if the haproxy log file is not at /var/log/haproxy.log
|
70
|
+
|
71
|
+
[#{plugin_name}]
|
72
|
+
env.log_file /var/log/custom/haproxy.log
|
73
|
+
|
74
|
+
Also, make sure that the '/var/lib/munin/plugin-state' is writable by munin.
|
75
|
+
|
76
|
+
$ sudo chmod 777 /var/lib/munin/plugin-state
|
77
|
+
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def formatted(num)
|
84
|
+
"%.10f" % num
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# Munin plugin for starling.
|
3
|
+
require 'open-uri'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module MuninManager
|
7
|
+
class Plugins::HaproxyRps
|
8
|
+
include ActsAsMuninPlugin
|
9
|
+
|
10
|
+
def initialize(url, user, pass)
|
11
|
+
@url = url
|
12
|
+
@user = user
|
13
|
+
@pass = pass
|
14
|
+
@data = Hash.new{|h,k|h[k] = 0}
|
15
|
+
@category = 'Haproxy'
|
16
|
+
parse_csv
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_csv
|
20
|
+
base64 = Base64.encode64("#{@user}:#{@pass}")
|
21
|
+
file = open(@url, {"Authorization" => "Basic #{base64}"})
|
22
|
+
file.readline #skip first line
|
23
|
+
file.each_line do |line|
|
24
|
+
data_ary = line.split(",")
|
25
|
+
host,port = data_ary[1].split(":")
|
26
|
+
@data[host] += data_ary[7].to_i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ops_stats
|
31
|
+
defaults = {
|
32
|
+
'min' => 0,
|
33
|
+
'type' => 'DERIVE',
|
34
|
+
'draw' => 'LINE2',
|
35
|
+
}
|
36
|
+
|
37
|
+
stats = {
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
@data.each_key do |k|
|
42
|
+
stats[k] = defaults.merge({:label => k})
|
43
|
+
end
|
44
|
+
return stats
|
45
|
+
end
|
46
|
+
|
47
|
+
def config
|
48
|
+
graph_config = <<-END.gsub(/ +/, '')
|
49
|
+
graph_title Haproxy RPS / App Server
|
50
|
+
graph_args --base 1000
|
51
|
+
graph_vlabel ops/${graph_period}
|
52
|
+
graph_category #{@category}
|
53
|
+
graph_order #{@data.keys.sort.join(" ")}
|
54
|
+
END
|
55
|
+
|
56
|
+
stat_config = []
|
57
|
+
ops_stats.each do |stat,config|
|
58
|
+
config.each do |var,value|
|
59
|
+
stat_config << "#{stat}.#{var} #{value}\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return graph_config + stat_config.sort.join
|
63
|
+
end
|
64
|
+
|
65
|
+
def values
|
66
|
+
ret = ''
|
67
|
+
@data.each do |key, stat|
|
68
|
+
ret << "#{key}.value #{stat}\n"
|
69
|
+
end
|
70
|
+
return ret
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.run
|
74
|
+
url = ENV['URL'] || 'http://lb01.ffs.seriousops.com/haproxy?stats;csv'
|
75
|
+
user = ENV['USERNAME'] || 'admin'
|
76
|
+
pass = ENV['PASSWORD'] || 'pass'
|
77
|
+
haproxy = new(url, user, pass)
|
78
|
+
|
79
|
+
allowed_commands = ['config']
|
80
|
+
|
81
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd then
|
82
|
+
puts haproxy.send(cmd.to_sym)
|
83
|
+
else
|
84
|
+
puts haproxy.values
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def format_for_munin(str)
|
91
|
+
str.to_s.gsub(/[^A-Za-z0-9_]/, "_")
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::NetworkLatency < LogReader
|
3
|
+
include ActsAsMuninPlugin
|
4
|
+
|
5
|
+
def self.run
|
6
|
+
hostnames = (ENV['hostnames'] || 'localhost').split(",")
|
7
|
+
count = (ENV["count"] || 1).to_i
|
8
|
+
if ARGV[0] == "config"
|
9
|
+
puts config(hostnames)
|
10
|
+
else
|
11
|
+
values = Hash.new {|h,k| h[k] = []}
|
12
|
+
threads = []
|
13
|
+
count.times do |i|
|
14
|
+
threads << Thread.new do
|
15
|
+
output = %x{/usr/sbin/fping -c 10 -p 100 -q #{hostnames.join(" ")} 2>&1}
|
16
|
+
Thread.current[:output] = output
|
17
|
+
end
|
18
|
+
end
|
19
|
+
threads.each do |t|
|
20
|
+
t.join
|
21
|
+
t[:output].to_s.split("\n").each do |line|
|
22
|
+
if line =~ /^([^\s]+)\s*:\s*xmt\/rcv\/%loss = [0-9]+\/[0-9]+\/[0-9]+%, min\/avg\/max = [0-9.]+\/([0-9.]+)\/[0-9.]+/
|
23
|
+
values[$1] << $2.to_f
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
values.each do |host, results|
|
29
|
+
avg = results.size > 0 ? results.inject(0){|a,b| a + b}.to_f / results.size : -1
|
30
|
+
puts "#{sanitize(host)}.value #{avg}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.sanitize(hostname)
|
36
|
+
hostname.to_s.gsub(/[^\w]/, '_')
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.config(hostnames)
|
40
|
+
configs = {
|
41
|
+
"graph_title" => "Network Latency",
|
42
|
+
"graph_vlabel" => "time (ms)",
|
43
|
+
"graph_category" => "Network",
|
44
|
+
"graph_order" => ""
|
45
|
+
}
|
46
|
+
hostnames.each do |hostname|
|
47
|
+
configs["#{sanitize(hostname)}.label"] = hostname
|
48
|
+
configs["graph_order"] += "#{sanitize(hostname)} "
|
49
|
+
end
|
50
|
+
configs.map {|key_value_pair| key_value_pair.join(" ")}.join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module MuninManager
|
5
|
+
class Plugins::NotificationClassification
|
6
|
+
include ActsAsMuninPlugin
|
7
|
+
|
8
|
+
def config
|
9
|
+
"graph_title #{ENV['app']} Notification Classification Creation
|
10
|
+
graph_vlabel new classifications / hour
|
11
|
+
notification_rate.label new classifications / hour
|
12
|
+
notification_rate.type derive
|
13
|
+
notification_rate.warning 10
|
14
|
+
notification_rate.critical 100"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.run
|
18
|
+
allowed_commands = ['config']
|
19
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd
|
20
|
+
puts new.config
|
21
|
+
else
|
22
|
+
cmd = "mysql -u #{ENV['mysql_user']} --password=#{ENV['mysql_password']} -h #{ENV['host']} -e 'use #{ENV['database']}; select count(*) from notification_classifications where created_at >= \"#{1.hour.ago.to_s(:db)}\";' --skip-column-names --silent"
|
23
|
+
puts "notification_rate.value %s" % `#{cmd}`
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::PacketLoss < LogReader
|
3
|
+
include ActsAsMuninPlugin
|
4
|
+
|
5
|
+
def self.run
|
6
|
+
hostnames = (ENV['hostnames'] || 'localhost').split(",")
|
7
|
+
count = (ENV["count"] || 1).to_i
|
8
|
+
if ARGV[0] == "config"
|
9
|
+
puts config(hostnames)
|
10
|
+
else
|
11
|
+
values = Hash.new {|h,k| h[k] = []}
|
12
|
+
threads = []
|
13
|
+
count.times do |i|
|
14
|
+
threads << Thread.new do
|
15
|
+
output = %x{/usr/sbin/fping -c 10 -p 100 -q #{hostnames.join(" ")} 2>&1}
|
16
|
+
Thread.current[:output] = output
|
17
|
+
end
|
18
|
+
end
|
19
|
+
threads.each do |t|
|
20
|
+
t.join
|
21
|
+
t[:output].to_s.split("\n").each do |line|
|
22
|
+
if line =~ /^([^\s]+)\s*:\s*xmt\/rcv\/%loss = [0-9]+\/[0-9]+\/([0-9]+)%/
|
23
|
+
values[$1] << $2.to_f
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
values.each do |host, results|
|
29
|
+
avg = results.size > 0 ? results.inject(0){|a,b| a + b}.to_f / results.size : -1
|
30
|
+
puts "#{sanitize(host)}.value #{avg}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.sanitize(hostname)
|
36
|
+
hostname.to_s.gsub(/[^\w]/, '_')
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.config(hostnames)
|
40
|
+
configs = {
|
41
|
+
"graph_title" => "Packet Loss",
|
42
|
+
"graph_vlabel" => "percentage",
|
43
|
+
"graph_category" => "Network",
|
44
|
+
"graph_order" => ""
|
45
|
+
}
|
46
|
+
hostnames.each do |hostname|
|
47
|
+
configs["#{sanitize(hostname)}.label"] = hostname
|
48
|
+
configs["graph_order"] += "#{sanitize(hostname)} "
|
49
|
+
end
|
50
|
+
configs.map {|key_value_pair| key_value_pair.join(" ")}.join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::RailsRendering < LogReader
|
3
|
+
include ActsAsMuninPlugin
|
4
|
+
|
5
|
+
def data
|
6
|
+
@data ||= Hash.new {|h, k| h[k] = Array.new}
|
7
|
+
end
|
8
|
+
|
9
|
+
def scan(log_file)
|
10
|
+
loop do
|
11
|
+
line = log_file.readline
|
12
|
+
next unless line.match(/^Completed in/)
|
13
|
+
|
14
|
+
chunks = line.split(/\s/)
|
15
|
+
data[:total] << chunks[2].to_f
|
16
|
+
data[:rendering] << chunks[7].to_f
|
17
|
+
data[:memcache] << chunks[11].to_f
|
18
|
+
data[:db] << chunks[14].to_f
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def process!
|
23
|
+
data.each_pair do |component, response_times|
|
24
|
+
data[component] = response_times.inject(0) {|sum, i| sum + i} / data[component].length rescue 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def config
|
29
|
+
<<-LABEL
|
30
|
+
graph_title Rails Response Breakdown
|
31
|
+
graph_vlabel response time
|
32
|
+
graph_category Rails
|
33
|
+
total.label total
|
34
|
+
rendering.label rendering
|
35
|
+
db.label db
|
36
|
+
memcache.label memcache
|
37
|
+
LABEL
|
38
|
+
end
|
39
|
+
|
40
|
+
def values
|
41
|
+
data.map {|k, v| "#{format_for_munin(k)}.value #{"%.10f" % v}"}.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.run
|
45
|
+
log_file = ENV['log_file'] || "/var/log/rails.log"
|
46
|
+
allowed_commands = ['config']
|
47
|
+
|
48
|
+
rails = new(log_file)
|
49
|
+
|
50
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd then
|
51
|
+
puts rails.send(cmd.to_sym)
|
52
|
+
else
|
53
|
+
rails.collect!
|
54
|
+
puts rails.values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.help_text(options = {})
|
59
|
+
%Q{
|
60
|
+
#{plugin_name.capitalize} Munin Plugin
|
61
|
+
===========================
|
62
|
+
|
63
|
+
Please remember to add something like the lines below to /etc/munin/plugin-conf.d/munin-node
|
64
|
+
if the rails log file is not at /var/log/rails.log
|
65
|
+
|
66
|
+
[#{options[:symlink] || plugin_name}]
|
67
|
+
env.log_file /var/log/custom/rails.log
|
68
|
+
|
69
|
+
Also, make sure that the '/var/lib/munin/plugin-state' is writable by munin.
|
70
|
+
|
71
|
+
$ sudo chmod 777 /var/lib/munin/plugin-state
|
72
|
+
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def format_for_munin(str)
|
79
|
+
str.to_s.gsub(/[^A-Za-z0-9_]/, "_")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|