diff options
author | Luke Kanies <luke@madstop.com> | 2005-06-29 03:11:06 +0000 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2005-06-29 03:11:06 +0000 |
commit | 0c4254a9c1bad795a8bcc895cff74b6dd961ba44 (patch) | |
tree | 2d512a90e147d020486ff71591ad64202814ac06 | |
parent | 573d3018a67521f37dbbd46e86c7d1d8b7bc2703 (diff) | |
download | puppet-0c4254a9c1bad795a8bcc895cff74b6dd961ba44.tar.gz puppet-0c4254a9c1bad795a8bcc895cff74b6dd961ba44.tar.xz puppet-0c4254a9c1bad795a8bcc895cff74b6dd961ba44.zip |
metric testing and graphing now works in the library, although nothing has been done in the language
git-svn-id: https://reductivelabs.com/svn/puppet/library/trunk@314 980ebf18-57e1-0310-9a29-db15c13687c0
-rw-r--r-- | lib/puppet.rb | 12 | ||||
-rw-r--r-- | lib/puppet/client.rb | 7 | ||||
-rw-r--r-- | lib/puppet/event.rb | 2 | ||||
-rw-r--r-- | lib/puppet/log.rb | 10 | ||||
-rw-r--r-- | lib/puppet/metric.rb | 253 | ||||
-rw-r--r-- | lib/puppet/transaction.rb | 1 | ||||
-rw-r--r-- | lib/puppet/type.rb | 36 | ||||
-rw-r--r-- | test/other/tc_metrics.rb | 79 |
8 files changed, 394 insertions, 6 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index c144a6bb4..40c904e08 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -19,6 +19,8 @@ module Puppet @@config = Hash.new(false) @@config[:puppetroot] = "/var/puppet" + @@config[:rrddir] = "/var/puppet/rrd" + @@config[:rrdgraph] = false @@config[:logdir] = "/var/puppet/log" @@config[:logfile] = "/var/puppet/log/puppet.log" @@config[:statefile] = "/var/puppet/log/state" @@ -51,6 +53,16 @@ module Puppet # configuration parameter access and stuff def Puppet.[]=(param,value) @@config[param] = value + case param + when :debug: + if value + Puppet::Log.level(:debug) + else + Puppet::Log.level(:notice) + end + when :loglevel: + Puppet::Log.level(value) + end end end diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index f1140ea4d..06808a4b4 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -10,6 +10,7 @@ require 'puppet/type' require 'puppet/fact' require 'puppet/transaction' require 'puppet/transportable' +require 'puppet/metric' require 'http-access2' require 'soap/rpc/driver' require 'soap/rpc/httpserver' @@ -80,6 +81,12 @@ module Puppet #transaction = Puppet::Transaction.new(objects) transaction.toplevel = true transaction.evaluate + Puppet::Metric.gather + Puppet::Metric.tally + Metric.store + if @@config[:rrdgraph] == true + #Metric.store + end self.shutdown end diff --git a/lib/puppet/event.rb b/lib/puppet/event.rb index a8d818b39..86205591d 100644 --- a/lib/puppet/event.rb +++ b/lib/puppet/event.rb @@ -107,7 +107,7 @@ module Puppet @object = args[:object] @transaction = args[:transaction] - Puppet.warning "New Event: '%s' => '%s'" % + Puppet.info "%s: %s" % [@object,@event] # initially, just stuff all instances into a central bucket diff --git a/lib/puppet/log.rb b/lib/puppet/log.rb index 9cfb80193..5f77e623c 100644 --- a/lib/puppet/log.rb +++ b/lib/puppet/log.rb @@ -47,12 +47,14 @@ module Puppet def Log.create(level,*ary) msg = ary.join(" ") - if @@levels.index(@@loglevel) >= @@levels.index(level) - Puppet::Log.new( + if @@levels.index(level) >= @@loglevel + return Puppet::Log.new( :level => level, :source => "Puppet", :message => msg ) + else + return nil end end @@ -86,11 +88,11 @@ module Puppet level = level.intern end - unless @@loglevels.include?(level) + unless @@levels.include?(level) raise "Invalid loglevel %s" % level end - @@loglevel = @@loglevels.index(level) + @@loglevel = @@levels.index(level) end def Log.newmessage(msg) diff --git a/lib/puppet/metric.rb b/lib/puppet/metric.rb new file mode 100644 index 000000000..d9696b601 --- /dev/null +++ b/lib/puppet/metric.rb @@ -0,0 +1,253 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# included so we can test object types +require 'puppet' + +module Puppet + class Metric + def Metric.init + @@typemetrics = Hash.new { |typehash,type| + typehash[type] = Hash.new(0) + } + + @@eventmetrics = Hash.new(0) + + @@metrics = {} + end + + def Metric.clear + @@metrics = {} + @@eventmetrics = nil + @@typemetrics = nil + end + + def Metric.gather + metrics = Metric.init + + # first gather stats about all of the types + Puppet::Type.eachtype { |type| + type.each { |instance| + metrics[type][:total] += 1 + if instance.managed + metrics[type][:managed] += 1 + end + } + } + + # the rest of the metrics are injected directly by type.rb + end + + def Metric.add(type,instance,metric,count) + return unless defined? @@typemetrics + case metric + when :outofsync: + @@typemetrics[type][metric] += count + when :changes: + @@typemetrics[type][:changed] += 1 + @@typemetrics[type][:totalchanges] += count + else + raise "Unknown metric %s" % metric + end + end + + # we're currently throwing away the type and instance information + def Metric.addevents(type,instance,events) + return unless defined? @@eventmetrics + events.each { |event| + @@eventmetrics[event] += 1 + } + end + + def Metric.each + @@metrics.each { |name,metric| + yield metric + } + end + + def Metric.load(ary) + @@typemetrics = ary[0] + @@eventmetrics = ary[1] + end + + def Metric.graph(range = nil) + @@metrics.each { |name,metric| + metric.graph(range) + } + end + + def Metric.store(time = nil) + require 'RRD' + unless time + time = Time.now.to_i + end + @@metrics.each { |name,metric| + metric.store(time) + } + end + + def Metric.tally + type = Metric.new("typecount","Types") + type.newvalue("Number",@@typemetrics.length) + + metrics = { + :total => "Instances", + :managed => "Managed Instances", + :outofsync => "Out of Sync Instances", + :changed => "Changed Instances", + :totalchanges => "Total Number of Changes", + } + total = Hash.new(0) + @@typemetrics.each { |type,instancehash| + name = type.name.to_s + instmet = Metric.new("type-" + name,name.capitalize) + metrics.each { |symbol,label| + instmet.newvalue(symbol.to_s,instancehash[symbol],label) + total[symbol] += instancehash[symbol] + } + } + + totalmet = Metric.new("typetotals","Type Totals") + metrics.each { |symbol,label| + totalmet.newvalue(symbol.to_s,total[symbol],label) + } + + eventmet = Metric.new("events") + total = 0 + @@eventmetrics.each { |event,count| + event = event.to_s + # add the specific event as a value, with the label being a + # capitalized version with s/_/ /g + eventmet.newvalue( + event, + count, + event.capitalize.gsub(/_/,' ') + ) + + total += count + } + eventmet.newvalue("total",total,"Event Total") + end + + attr_accessor :type, :name, :value, :label + + + def create + dir = Puppet[:rrddir] + unless dir + raise "Cannot store metrics unless RRDDir is set" + end + + unless FileTest.exist?(dir) + tmp = dir.sub(/^\//,'') + path = [File::SEPARATOR] + tmp.split(File::SEPARATOR).each { |dir| + path.push dir + unless FileTest.exist?(File.join(path)) + Dir.mkdir(File.join(path)) + end + } + end + + unless FileTest.directory?(dir) + raise "%s must be a directory" % dir + end + + path = self.path + args = [ + path, + "--start", Time.now.to_i - 5, + "--step", "300", # XXX Defaulting to every five minutes, but prob bad + ] + + @values.each { |value| + args.push "DS:%s:GAUGE:600:U:U" % value[0] + } + args.push "RRA:AVERAGE:0.5:1:300" + + begin + RRD.create(*args) + rescue => detail + raise "Could not create RRD file %s: %s" % [path,detail] + end + end + + def initialize(name,label = nil) + @name = name + if label + @label = label + else + @label = name.capitalize + end + + @values = [] + if @@metrics.include?(self.name) + raise "Somehow created two metrics with name %s" % self.name + else + @@metrics[self.name] = self + end + end + + def newvalue(name,value,label = nil) + unless label + label = name.capitalize + end + @values.push [name,label,value] + end + + def path + return File.join(Puppet[:rrddir],@name + ".rrd") + end + + def graph(range = nil) + args = [self.path.sub(/rrd$/,"png")] + args.push("--title",self.label) + args.push("--imgformat","PNG") + args.push("--interlace") + colorstack = %w{#ff0000 #00ff00 #0000ff #099000 #000990 #f00990} + i = 0 + defs = [] + lines = [] + @values.zip(colorstack).each { |value,color| + next if value.nil? + # this actually uses the data label + defs.push("DEF:%s=%s:%s:AVERAGE" % [value[0],self.path,value[0]]) + lines.push("LINE3:%s%s:%s" % [value[0],color,value[1]]) + } + args << defs + args << lines + args.flatten! + if range + args.push("--start",range[0],"--end",range[1]) + end + + begin + RRD.graph(*args) + rescue => detail + Puppet.err "Failed to graph %s: %s" % [self.name,detail] + exit + end + end + + def store(time) + unless FileTest.exists?(File.join(Puppet[:rrddir],@name + ".rrd")) + self.create + end + + # XXX this is not terribly error-resistant + args = [time] + @values.each { |value| + args.push value[2] + } + arg = args.join(":") + Puppet.debug "Updating %s with %s" % [self.name,arg] + begin + RRD.update(self.path,args.join(":")) + rescue => detail + Puppet.err "Failed to update %s: %s" % [self.name,detail] + exit + end + end + end +end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 1a3a7361a..03f965a76 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -23,6 +23,7 @@ class Transaction # failed def Transaction.init @@failures = Hash.new(0) + Puppet::Metric.init @@changed = [] end #--------------------------------------------------------------- diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 81c20ceec..6d74e4ef3 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -6,6 +6,7 @@ require 'puppet' require 'puppet/element' require 'puppet/event' +require 'puppet/metric' require 'puppet/type/state' @@ -273,6 +274,15 @@ class Type < Puppet::Element #--------------------------------------------------------------- #--------------------------------------------------------------- + def Type.each + return unless defined? @objects + @objects.each { |instance| + yield instance + } + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- # all objects total def Type.push(object) @@allobjects.push object @@ -581,11 +591,13 @@ class Type < Puppet::Element #--------------------------------------------------------------- def sync - self.collect { |child| + events = self.collect { |child| child.sync }.reject { |event| ! (event.is_a?(Symbol) or event.is_a?(String)) }.flatten + + Puppet::Metric.addevents(self.class,self,events) end #--------------------------------------------------------------- @@ -602,6 +614,23 @@ class Type < Puppet::Element #--------------------------------------------------------------- #--------------------------------------------------------------- + def managed + if defined? @managed + return @managed + else + self.states.each { |state| + if state.should + @managed = true + else + @managed = false + end + } + end + return @managed + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- def states Puppet.debug "%s has %s states" % [self,@states.length] tmpstates = [] @@ -686,6 +715,8 @@ class Type < Puppet::Element # this only operates on states, not states + children self.retrieve unless self.insync? + # add one to the number of out-of-sync instances + Puppet::Metric.add(self.class,self,:outofsync,1) changes << self.states.find_all { |state| ! state.insync? }.collect { |state| @@ -697,6 +728,9 @@ class Type < Puppet::Element #self.collect { |child| # child.evaluate #} + + # now record how many changes we've resulted in + Puppet::Metric.add(self.class,self,:changes,changes.length) return changes end #--------------------------------------------------------------- diff --git a/test/other/tc_metrics.rb b/test/other/tc_metrics.rb new file mode 100644 index 000000000..4617d18c9 --- /dev/null +++ b/test/other/tc_metrics.rb @@ -0,0 +1,79 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../../../../language/trunk/" +end + +require 'puppet/metric' +require 'puppet' +require 'puppet/type' +require 'test/unit' + +# $Id$ + +class TestMetric < Test::Unit::TestCase + + def gendata + totalmax = 1000 + changemax = 10000 + eventmax = 10 + maxdiff = 10 + + types = [Puppet::Type::File, Puppet::Type::Package, Puppet::Type::Service] + data = [:total, :managed, :outofsync, :changed, :totalchanges] + events = [:file_changed, :package_installed, :service_restarted] + + # if this is the first set of data points... + typedata = {} + eventdata = {} + types.each { |type| + name = type.name + typedata[type] = {} + typedata[type][:total] = rand(totalmax) + typedata[type][:managed] = rand(typedata[type][:total]) + typedata[type][:outofsync] = rand(typedata[type][:managed]) + typedata[type][:changed] = rand(typedata[type][:outofsync]) + typedata[type][:totalchanges] = rand(changemax) + } + + events.each { |event| + eventdata[event] = rand(eventmax) + } + + return [typedata,eventdata] + end + + def setup + Puppet[:rrddir] = File.join(Dir.getwd,"rrdtests") + Puppet[:rrdgraph] = true + Puppet[:loglevel] = :debug + end + + def teardown + #system("rm -rf rrdtests") + end + + def test_fakedata + assert_nothing_raised { Puppet::Metric.init } + time = Time.now.to_i + start = time + 10.times { + assert_nothing_raised { Puppet::Metric.load(gendata) } + assert_nothing_raised { Puppet::Metric.tally } + assert_nothing_raised { Puppet::Metric.store(time) } + assert_nothing_raised { Puppet::Metric.clear } + time += 300 + } + assert_nothing_raised { Puppet::Metric.load(gendata) } + assert_nothing_raised { Puppet::Metric.tally } + assert_nothing_raised { Puppet::Metric.store(time) } + assert_nothing_raised { Puppet::Metric.graph([start,time]) } + + File.open(File.join(Puppet[:rrddir],"index.html"),"w") { |of| + of.puts "<html><body>" + Puppet::Metric.each { |metric| + of.puts "<img src=%s.png><br>" % metric.name + } + } + end +end |