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
data/Manifest
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
bin/munin_manager
|
2
|
+
bin/runner
|
3
|
+
ext/string.rb
|
4
|
+
lib/munin_manager/acts_as_munin_plugin.rb
|
5
|
+
lib/munin_manager/log_reader.rb
|
6
|
+
lib/munin_manager/plugins/fbproxy_latency.rb
|
7
|
+
lib/munin_manager/plugins/haproxy_app_response_time.rb
|
8
|
+
lib/munin_manager/plugins/haproxy_response_time.rb
|
9
|
+
lib/munin_manager/plugins/haproxy_rps.rb
|
10
|
+
lib/munin_manager/plugins/network_latency.rb
|
11
|
+
lib/munin_manager/plugins/notification_classification.rb
|
12
|
+
lib/munin_manager/plugins/packet_loss.rb
|
13
|
+
lib/munin_manager/plugins/rails_rendering.rb
|
14
|
+
lib/munin_manager/plugins/rails_response_time.rb
|
15
|
+
lib/munin_manager/plugins/scribe_net.rb
|
16
|
+
lib/munin_manager/plugins/starling_age.rb
|
17
|
+
lib/munin_manager/plugins/starling_net.rb
|
18
|
+
lib/munin_manager/plugins/starling_ops.rb
|
19
|
+
lib/munin_manager/plugins/starling_queue.rb
|
20
|
+
lib/munin_manager/plugins.rb
|
21
|
+
lib/munin_manager/starling/starling_stats.rb
|
22
|
+
lib/munin_manager.rb
|
23
|
+
Manifest
|
24
|
+
munin_manager.gemspec
|
25
|
+
Rakefile
|
26
|
+
README.markdown
|
27
|
+
test/haproxy_response_time_test.rb
|
28
|
+
test/log_reader_test.rb
|
29
|
+
test/rails_response_time_test.rb
|
30
|
+
test/test_helper.rb
|
data/README.markdown
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |s|
|
4
|
+
s.name = "munin_manager"
|
5
|
+
s.executables = "munin_manager"
|
6
|
+
s.summary = "Tool to maintain and install munin plugins written in Ruby"
|
7
|
+
s.email = "entombedvirus@gmail.com"
|
8
|
+
s.homepage = "http://github.com/entombedvirus/jeweler"
|
9
|
+
s.authors = ["Rohith Ravi"]
|
10
|
+
s.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
|
11
|
+
end
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
14
|
+
end
|
15
|
+
#spec = Gem::Specification.new do |s|
|
16
|
+
# s.name = "MuninManager"
|
17
|
+
# s.version = "0.0.1"
|
18
|
+
# s.platform = Gem::Platform::RUBY
|
19
|
+
# s.files = FileList["{bin,lib,ext}/**/*"].to_a
|
20
|
+
# s.require_path = "lib"
|
21
|
+
# s.autorequire = "munin_manager"
|
22
|
+
# s.test_files = FileList["{test}/**/*test.rb"].to_a
|
23
|
+
# s.has_rdoc = false
|
24
|
+
# s.extra_rdoc_files = ["README.markdown"]
|
25
|
+
#end
|
26
|
+
#
|
27
|
+
#Rake::GemPackageTask.new(spec) do |pkg|
|
28
|
+
# pkg.need_tar = true
|
29
|
+
#end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.2.1
|
data/bin/munin_manager
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ruby-debug'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'ostruct'
|
8
|
+
require "#{File.dirname(__FILE__)}/../lib/munin_manager"
|
9
|
+
|
10
|
+
options = OpenStruct.new
|
11
|
+
options.plugin_dir = "/etc/munin/plugins"
|
12
|
+
|
13
|
+
parser = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: munin_manager <command> [options]"
|
15
|
+
opts.separator ""
|
16
|
+
|
17
|
+
opts.separator "Commands:"
|
18
|
+
opts.separator "PLUGIN_NAME is of the form <plugin>[:<symlink>]"
|
19
|
+
opts.separator ""
|
20
|
+
|
21
|
+
opts.on("-l", "--list", "List available plugins to install") do
|
22
|
+
buffer = []
|
23
|
+
buffer << "Available Plugins"
|
24
|
+
buffer << "================="
|
25
|
+
buffer << ""
|
26
|
+
|
27
|
+
MuninManager::Plugins.each do |plugin|
|
28
|
+
buffer << plugin.plugin_name
|
29
|
+
end
|
30
|
+
|
31
|
+
buffer << ""
|
32
|
+
puts buffer.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-s", "--show PLUGIN_NAME", "Shows details about a plugin") do |name|
|
36
|
+
begin
|
37
|
+
puts MuninManager::Plugins[name].help_text
|
38
|
+
rescue NoMethodError
|
39
|
+
STDERR.puts "'%s' plugin was not found" % name
|
40
|
+
rescue
|
41
|
+
STDERR.puts "No additional information is available about this plugin."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-i", "--install PLUGIN_NAME[,PLUGIN_NAME]", Array,
|
46
|
+
"Installs a plugin by creating a symlink in '%s'" % options.plugin_dir) do |names|
|
47
|
+
options.action = :install
|
48
|
+
options.plugin_names = names
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-u", "--uninstall PLUGIN_NAME[,PLUGIN_NAME]", Array,
|
52
|
+
"Removes plugins from '%s'" % options.plugin_dir,
|
53
|
+
"if it is a symlink") do |names|
|
54
|
+
options.action = :uninstall
|
55
|
+
options.plugin_names = names
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.separator ""
|
59
|
+
opts.separator "Options:"
|
60
|
+
|
61
|
+
opts.on("--plugin-dir DIR", "Directory where symlinks will be created when a plugin is installed",
|
62
|
+
"(default is '%s'" % options.plugin_dir) do |dir|
|
63
|
+
if File.directory?(dir) && File.writable?(dir)
|
64
|
+
options.plugin_dir = dir
|
65
|
+
else
|
66
|
+
STDERR.puts "'%s' does not exist or is not writable" % options.plugin_dir
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("--force", "Forces the installation or uninstallation of plugins") do
|
71
|
+
options.force = true
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
parser.parse!(ARGV)
|
78
|
+
options.freeze!
|
79
|
+
|
80
|
+
case options.action
|
81
|
+
when :install, :uninstall
|
82
|
+
options.plugin_names.each do |plugin_name|
|
83
|
+
plugin_klass = MuninManager::Plugins.search(plugin_name)
|
84
|
+
|
85
|
+
unless plugin_klass
|
86
|
+
STDERR.puts "ERROR: Matching plugin not found for '%s'" % plugin_name
|
87
|
+
exit(1)
|
88
|
+
end
|
89
|
+
|
90
|
+
new_options = options.dup
|
91
|
+
new_options.install_name = plugin_name
|
92
|
+
|
93
|
+
plugin_klass.send(options.action, new_options)
|
94
|
+
end
|
95
|
+
end
|
data/bin/runner
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This file will serve as the target for the symlinks in /etc/munin/plugins.
|
4
|
+
# It's the responsibility of this script to run the plugin indicated by the
|
5
|
+
# symlink name.
|
6
|
+
|
7
|
+
this_file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
8
|
+
require "#{File.dirname(this_file)}/../lib/munin_manager"
|
9
|
+
|
10
|
+
plugin_name = File.basename($0)
|
11
|
+
|
12
|
+
if plugin = MuninManager::Plugins[plugin_name.split('.')[0]]
|
13
|
+
plugin.run
|
14
|
+
exit(0)
|
15
|
+
else
|
16
|
+
STDERR.puts "'%s' plugin was not found" % plugin_name
|
17
|
+
exit(1)
|
18
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
munin_manager = File.join(File.dirname(__FILE__), "munin_manager")
|
2
|
+
|
3
|
+
%w(../../ext/string log_reader plugins acts_as_munin_plugin).each do |f|
|
4
|
+
require File.join(munin_manager, f)
|
5
|
+
end
|
6
|
+
|
7
|
+
Dir[File.join(munin_manager, "plugins", "*")].each do |file|
|
8
|
+
require file
|
9
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module MuninManager
|
4
|
+
module ActsAsMuninPlugin
|
5
|
+
def self.included(klass)
|
6
|
+
klass.send(:include, InstanceMethods)
|
7
|
+
klass.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def run
|
12
|
+
raise "This is the entry point of the plugin when invoked by munin. Needs implementation by included class"
|
13
|
+
end
|
14
|
+
|
15
|
+
def plugin_name
|
16
|
+
# Name of the plugin. Must not contain spaces or special chars
|
17
|
+
|
18
|
+
# Default is underscorized version of the class name
|
19
|
+
self.name.split('::').last.
|
20
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
21
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
22
|
+
tr("-", "_").
|
23
|
+
downcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def help_text(options = {})
|
27
|
+
# Any general info concerning the plugin. Should be overriden by included class
|
28
|
+
end
|
29
|
+
|
30
|
+
def install(options)
|
31
|
+
install_as = options.install_name.split(":").last
|
32
|
+
symlink = File.join(options.plugin_dir, install_as)
|
33
|
+
runner = File.join(File.dirname(__FILE__), "..", "..", "bin", "runner")
|
34
|
+
runner = File.expand_path(runner)
|
35
|
+
|
36
|
+
if File.exists?(symlink)
|
37
|
+
if options.force
|
38
|
+
File.unlink(symlink)
|
39
|
+
else
|
40
|
+
STDERR.puts "'%s' already exists. Please specify --force option to overwrite" % symlink
|
41
|
+
return
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
STDOUT.puts "Installing '%s' at '%s'" % [plugin_name, symlink]
|
46
|
+
FileUtils.ln_sf(runner, symlink)
|
47
|
+
|
48
|
+
STDOUT.puts help_text(:symlink => install_as)
|
49
|
+
|
50
|
+
rescue Errno::EACCES
|
51
|
+
STDERR.puts "ERROR: Permission denied while attempting to install to '%s'" % symlink
|
52
|
+
end
|
53
|
+
|
54
|
+
# Default uninstaller. Override in included classes if the default is not sufficient
|
55
|
+
def uninstall(options)
|
56
|
+
install_as = options.install_name.split(":").last
|
57
|
+
symlink = File.join(options.plugin_dir, install_as)
|
58
|
+
|
59
|
+
unless File.exists?(symlink)
|
60
|
+
STDERR.puts "'%s' does not seem to exist. Aborting..." % symlink
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
unless File.symlink?(symlink) || options.force
|
65
|
+
STDERR.puts "'%s' does not appear to be a symlink. Please specify --force option to remove" % symlink
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
STDOUT.puts "Removing '%s'..." % symlink
|
70
|
+
File.unlink(symlink)
|
71
|
+
rescue Errno::EACCES
|
72
|
+
STDERR.puts "ERROR: Permission denied while attempting to uninstall '%s'" % symlink
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module InstanceMethods
|
77
|
+
def config
|
78
|
+
raise "This is invoked by munin to get graph details. Needs implementation by included class"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MuninManager
|
2
|
+
# A LogReader that continues from where it left off.
|
3
|
+
# Ex:
|
4
|
+
# class RailsLogReader < LogReader
|
5
|
+
# def scan(log_file)
|
6
|
+
# @req_counter = 0
|
7
|
+
# loop do
|
8
|
+
# line = log_file.readline
|
9
|
+
# @req_counter += 1 if line =~ /Completed in/
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# def process!
|
14
|
+
# # Do nothing
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def print_data
|
18
|
+
# "num_requests.value #{@req_counter}"
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Usage:
|
23
|
+
#
|
24
|
+
# reader = RailsLogReader.new("log/development.log")
|
25
|
+
# reader.collect!
|
26
|
+
# reader.print_data
|
27
|
+
#
|
28
|
+
class LogReader
|
29
|
+
attr_accessor :file_name, :me, :state_dir, :state_file
|
30
|
+
|
31
|
+
def initialize(file_name)
|
32
|
+
@file_name = file_name
|
33
|
+
@me = File.basename($0)
|
34
|
+
@state_dir = ENV['MUNIN_PLUGSTATE'] || '/var/lib/munin/plugin-state'
|
35
|
+
end
|
36
|
+
|
37
|
+
def state_file
|
38
|
+
@state_file ||= File.join(@state_dir, @me)
|
39
|
+
end
|
40
|
+
|
41
|
+
def collect!(options = {})
|
42
|
+
options = {
|
43
|
+
:save_state => true
|
44
|
+
}.merge(options)
|
45
|
+
|
46
|
+
File.open(@file_name, "r") do |f|
|
47
|
+
load_saved_state(f)
|
48
|
+
|
49
|
+
begin
|
50
|
+
scan(f)
|
51
|
+
rescue EOFError
|
52
|
+
end
|
53
|
+
|
54
|
+
process!
|
55
|
+
save_state(f) if options[:save_state]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_saved_state(log_file)
|
60
|
+
return unless File.exists?(state_file) && !(state = File.read(state_file)).nil?
|
61
|
+
pos, last_file_size = Marshal.load(state)
|
62
|
+
|
63
|
+
# Check for log rotation
|
64
|
+
return if File.size(@file_name) < last_file_size
|
65
|
+
|
66
|
+
log_file.pos = pos
|
67
|
+
end
|
68
|
+
|
69
|
+
def scan(log_file)
|
70
|
+
# Only subclasses know how to process each type of logfile
|
71
|
+
raise "Needs to be implemented by subclasses"
|
72
|
+
end
|
73
|
+
|
74
|
+
def process!
|
75
|
+
raise "Needs to be implemented by subclasses"
|
76
|
+
end
|
77
|
+
|
78
|
+
def save_state(log_file)
|
79
|
+
File.open(state_file, "w") do |f|
|
80
|
+
f.write(Marshal.dump([log_file.pos, File.size(log_file)]))
|
81
|
+
f.flush
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MuninManager
|
2
|
+
module Plugins
|
3
|
+
extend self
|
4
|
+
extend Enumerable
|
5
|
+
|
6
|
+
def each(&block)
|
7
|
+
registered_plugins.each(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def registered_plugins
|
11
|
+
@registered_plugins ||= constants.
|
12
|
+
map {|c| const_get(c) }.
|
13
|
+
select {|const| const.is_a?(Class) && const < MuninManager::ActsAsMuninPlugin}
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](*names)
|
17
|
+
if names.length == 1
|
18
|
+
return detect {|plugin_klass| plugin_klass.plugin_name == names.first}
|
19
|
+
end
|
20
|
+
|
21
|
+
names.map {|name| self[name]}
|
22
|
+
end
|
23
|
+
|
24
|
+
def search(name)
|
25
|
+
str = name.to_s.split(':', 2).first
|
26
|
+
detect {|plugin_klass| plugin_klass.plugin_name.starts_with?(str)}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MuninManager
|
2
|
+
class Plugins::FbProxyLatency < 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(/^Benchmark /)
|
13
|
+
chunks = line.split(/-/).map{ |x| x.strip }
|
14
|
+
data_type = chunks[1] =~ /Queue/ ? 'queue' : 'fb_api'
|
15
|
+
next if chunks[2].nil?
|
16
|
+
data[data_type] << chunks[2].match('\((.*)\)')[1].to_f
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def process!
|
21
|
+
data.each_pair do |component, response_times|
|
22
|
+
data[component] = response_times.inject(0.0) {|sum, i| sum + i} / data[component].length rescue 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def config
|
27
|
+
<<-LABEL
|
28
|
+
graph_title Facebook Proxy Latency
|
29
|
+
graph_vlabel latency
|
30
|
+
graph_category Facebook Proxy
|
31
|
+
queue.label queue_latency
|
32
|
+
fb_api.label fb_api_latency
|
33
|
+
LABEL
|
34
|
+
end
|
35
|
+
|
36
|
+
def values
|
37
|
+
data.map {|k, v| "#{format_for_munin(k)}.value #{"%.10f" % v}"}.join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.run
|
41
|
+
log_file = ENV['log_file'] || "/var/log/rails.log"
|
42
|
+
allowed_commands = ['config']
|
43
|
+
|
44
|
+
rails = new(log_file)
|
45
|
+
|
46
|
+
if cmd = ARGV[0] and allowed_commands.include? cmd then
|
47
|
+
puts rails.send(cmd.to_sym)
|
48
|
+
else
|
49
|
+
rails.collect!
|
50
|
+
puts rails.values
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.help_text(options = {})
|
55
|
+
%Q{
|
56
|
+
#{plugin_name.capitalize} Munin Plugin
|
57
|
+
===========================
|
58
|
+
|
59
|
+
Please remember to add something like the lines below to /etc/munin/plugin-conf.d/munin-node
|
60
|
+
if the rails log file is not at /var/log/rails.log
|
61
|
+
|
62
|
+
[#{options[:symlink] || plugin_name}]
|
63
|
+
env.log_file /var/log/custom/rails.log
|
64
|
+
|
65
|
+
Also, make sure that the '/var/lib/munin/plugin-state' is writable by munin.
|
66
|
+
|
67
|
+
$ sudo chmod 777 /var/lib/munin/plugin-state
|
68
|
+
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def format_for_munin(str)
|
75
|
+
str.to_s.gsub(/[^A-Za-z0-9_]/, "_")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|