munin_manager 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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