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
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
|