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