munin_manager 1.2.1
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.
- 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
|