summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/configurer.rb25
-rw-r--r--lib/puppet/network/http/handler.rb6
-rw-r--r--lib/puppet/parser/compiler.rb6
-rw-r--r--lib/puppet/transaction.rb9
-rw-r--r--lib/puppet/util/instrumentation.rb12
-rw-r--r--lib/puppet/util/instrumentation/process_name.rb129
-rw-r--r--spec/unit/util/instrumentation/process_name_spec.rb207
7 files changed, 384 insertions, 10 deletions
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 72e387c64..a39f9cda8 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -3,6 +3,7 @@ require 'sync'
require 'timeout'
require 'puppet/network/http_pool'
require 'puppet/util'
+require 'puppet/util/instrumentation'
class Puppet::Configurer
class CommandHookError < RuntimeError; end
@@ -12,6 +13,7 @@ class Puppet::Configurer
include Puppet::Configurer::FactHandler
include Puppet::Configurer::PluginHandler
+ include Puppet::Util::Instrumentation
# For benchmarking
include Puppet::Util
@@ -76,11 +78,17 @@ class Puppet::Configurer
def prepare(options)
dostorage
- download_plugins unless options[:skip_plugin_download]
+ instrument("downloading plugins") do
+ download_plugins unless options[:skip_plugin_download]
+ end
- download_fact_plugins unless options[:skip_plugin_download]
+ instrument("downloading facts plugins") do
+ download_fact_plugins unless options[:skip_plugin_download]
+ end
- execute_prerun_command
+ instrument("executing prerun command") do
+ execute_prerun_command
+ end
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
@@ -146,8 +154,10 @@ class Puppet::Configurer
transaction = nil
begin
- benchmark(:notice, "Finished catalog run") do
- transaction = catalog.apply(options)
+ instrument("applying catalog") do
+ benchmark(:notice, "Finished catalog run") do
+ transaction = catalog.apply(options)
+ end
end
report
rescue => detail
@@ -166,7 +176,10 @@ class Puppet::Configurer
execute_postrun_command
Puppet::Util::Log.close(report)
- send_report(report, transaction)
+
+ instrument("sending report") do
+ send_report(report, transaction)
+ end
end
def send_report(report, trans)
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index 2b9e81b61..aa33f82f7 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -5,10 +5,12 @@ require 'puppet/network/http/api/v1'
require 'puppet/network/rest_authorization'
require 'puppet/network/rights'
require 'resolv'
+require 'puppet/util/instrumentation'
module Puppet::Network::HTTP::Handler
include Puppet::Network::HTTP::API::V1
include Puppet::Network::RestAuthorization
+ include Puppet::Util::Instrumentation
attr_reader :server, :handler
@@ -65,7 +67,9 @@ module Puppet::Network::HTTP::Handler
check_authorization(indirection, method, key, params)
- send("do_#{method}", indirection, key, params, request, response)
+ instrument("processing #{indirection} #{key}") do
+ send("do_#{method}", indirection, key, params, request, response)
+ end
rescue SystemExit,NoMemoryError
raise
rescue Exception => e
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index fdabd05c9..4301e8c0f 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -4,6 +4,7 @@
require 'puppet/node'
require 'puppet/resource/catalog'
require 'puppet/util/errors'
+require 'puppet/util/instrumentation'
require 'puppet/resource/type_collection_helper'
@@ -13,9 +14,12 @@ class Puppet::Parser::Compiler
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Resource::TypeCollectionHelper
+ extend Puppet::Util::Instrumentation
def self.compile(node)
- new(node).compile.to_resource
+ instrument("compiling #{node.name}") do
+ new(node).compile.to_resource
+ end
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::Error, "#{detail} on node #{node.name}"
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index eba601cfe..df4c8b27d 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -4,8 +4,11 @@
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/application'
+require 'puppet/util/instrumentation'
class Puppet::Transaction
+ include Puppet::Util::Instrumentation
+
require 'puppet/transaction/event'
require 'puppet/transaction/event_manager'
require 'puppet/transaction/resource_harness'
@@ -138,8 +141,10 @@ class Puppet::Transaction
next
end
ret = nil
- seconds = thinmark do
- ret = eval_resource(resource)
+ instrument("evaluating #{resource}") do
+ seconds = thinmark do
+ ret = eval_resource(resource)
+ end
end
resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
diff --git a/lib/puppet/util/instrumentation.rb b/lib/puppet/util/instrumentation.rb
new file mode 100644
index 000000000..5981bea59
--- /dev/null
+++ b/lib/puppet/util/instrumentation.rb
@@ -0,0 +1,12 @@
+require 'puppet/util/instrumentation/process_name'
+
+module Puppet::Util::Instrumentation
+
+ def instrument(title)
+ Puppet::Util::Instrumentation::ProcessName.instrument(title) do
+ yield
+ end
+ end
+ module_function :instrument
+
+end \ No newline at end of file
diff --git a/lib/puppet/util/instrumentation/process_name.rb b/lib/puppet/util/instrumentation/process_name.rb
new file mode 100644
index 000000000..370d29e2e
--- /dev/null
+++ b/lib/puppet/util/instrumentation/process_name.rb
@@ -0,0 +1,129 @@
+require 'puppet'
+require 'puppet/util/instrumentation'
+
+module Puppet::Util::Instrumentation
+ class ProcessName
+
+ # start scrolling when process name is longer than
+ SCROLL_LENGTH = 50
+
+ @active = false
+ class << self
+ attr_accessor :active, :reason
+ end
+
+ trap(:QUIT) do
+ active? ? disable : enable
+ end
+
+ def self.active?
+ !! @active
+ end
+
+ def self.enable
+ mutex.synchronize do
+ Puppet.info("Process Name instrumentation is enabled")
+ @active = true
+ @x = 0
+ setproctitle
+ end
+ end
+
+ def self.disable
+ mutex.synchronize do
+ Puppet.info("Process Name instrumentation is disabled")
+ @active = false
+ $0 = @oldname
+ end
+ end
+
+ def self.instrument(activity)
+ # inconditionnally start the scroller thread here
+ # because it doesn't seem possible to start a new thrad
+ # from the USR2 signal handler
+ @scroller ||= Thread.new do
+ loop do
+ scroll if active?
+ sleep 1
+ end
+ end
+
+ push_activity(Thread.current, activity)
+ yield
+ ensure
+ pop_activity(Thread.current)
+ end
+
+ def self.setproctitle
+ @oldname ||= $0
+ $0 = "#{base}: " + rotate(process_name,@x) if active?
+ end
+
+ def self.push_activity(thread, activity)
+ mutex.synchronize do
+ @reason ||= {}
+ @reason[thread] ||= []
+ @reason[thread].push(activity)
+ setproctitle
+ end
+ end
+
+ def self.pop_activity(thread)
+ mutex.synchronize do
+ @reason[thread].pop
+ if @reason[thread].empty?
+ @reason.delete(thread)
+ end
+ setproctitle
+ end
+ end
+
+ def self.process_name
+ out = (@reason || {}).inject([]) do |out, reason|
+ out << "#{thread_id(reason[0])} #{reason[1].join(',')}"
+ end
+ out.join(' | ')
+ end
+
+ # certainly non-portable
+ def self.thread_id(thread)
+ thread.inspect.gsub(/^#<.*:0x([a-f0-9]+) .*>$/, '\1')
+ end
+
+ def self.rotate(string, steps)
+ steps ||= 0
+ if string.length > 0 && steps > 0
+ steps = steps % string.length
+ return string[steps..string.length].concat " -- #{string[0..(steps-1)]}"
+ end
+ string
+ end
+
+ def self.base
+ basename = case Puppet.run_mode.name
+ when :master
+ "master"
+ when :agent
+ "agent"
+ else
+ "puppet"
+ end
+ end
+
+ def self.mutex
+ #Thread.exclusive {
+ @mutex ||= Sync.new
+ #}
+ @mutex
+ end
+
+ def self.scroll
+ return if process_name.length < SCROLL_LENGTH
+ mutex.synchronize do
+ setproctitle
+ @x += 1
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/spec/unit/util/instrumentation/process_name_spec.rb b/spec/unit/util/instrumentation/process_name_spec.rb
new file mode 100644
index 000000000..9cbedf2d2
--- /dev/null
+++ b/spec/unit/util/instrumentation/process_name_spec.rb
@@ -0,0 +1,207 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Util::Instrumentation::ProcessName do
+
+ ProcessName = Puppet::Util::Instrumentation::ProcessName
+
+ after(:each) do
+ ProcessName.reason = {}
+ end
+
+ it "should be disabled by default" do
+ ProcessName.should_not be_active
+ end
+
+ describe "when managing thread activity" do
+ before(:each) do
+ ProcessName.stubs(:setproctitle)
+ ProcessName.stubs(:base).returns("base")
+ end
+
+ it "should be able to append activity" do
+ thread1 = stub 'thread1'
+ ProcessName.push_activity(:thread1,"activity1")
+ ProcessName.push_activity(:thread1,"activity2")
+
+ ProcessName.reason[:thread1].should == ["activity1", "activity2"]
+ end
+
+ it "should be able to remove activity" do
+ ProcessName.push_activity(:thread1,"activity1")
+ ProcessName.push_activity(:thread1,"activity1")
+ ProcessName.pop_activity(:thread1)
+
+ ProcessName.reason[:thread1].should == ["activity1"]
+ end
+
+ it "should maintain activity thread by thread" do
+ ProcessName.push_activity(:thread1,"activity1")
+ ProcessName.push_activity(:thread2,"activity2")
+
+ ProcessName.reason[:thread1].should == ["activity1"]
+ ProcessName.reason[:thread2].should == ["activity2"]
+ end
+
+ it "should set process title" do
+ ProcessName.expects(:setproctitle)
+
+ ProcessName.push_activity("thread1","activity1")
+ end
+ end
+
+ describe "when computing the current process name" do
+ before(:each) do
+ ProcessName.stubs(:setproctitle)
+ ProcessName.stubs(:base).returns("base")
+ end
+
+ it "should include every running thread activity" do
+ thread1 = stub 'thread1', :inspect => "\#<Thread:0xdeadbeef run>", :hash => 1
+ thread2 = stub 'thread2', :inspect => "\#<Thread:0x12344321 run>", :hash => 0
+
+ ProcessName.push_activity(thread1,"Compiling node1.domain.com")
+ ProcessName.push_activity(thread2,"Compiling node4.domain.com")
+ ProcessName.push_activity(thread1,"Parsing file site.pp")
+ ProcessName.push_activity(thread2,"Parsing file node.pp")
+
+ ProcessName.process_name.should == "12344321 Compiling node4.domain.com,Parsing file node.pp | deadbeef Compiling node1.domain.com,Parsing file site.pp"
+ end
+ end
+
+ describe "when finding base process name" do
+ {:master => "master", :agent => "agent", :user => "puppet"}.each do |program,base|
+ it "should return #{base} for #{program}" do
+ Puppet.run_mode.stubs(:name).returns(program)
+ ProcessName.base.should == base
+ end
+ end
+ end
+
+ describe "when finding a thread id" do
+ it "should return the id from the thread inspect string" do
+ thread = stub 'thread', :inspect => "\#<Thread:0x1234abdc run>"
+ ProcessName.thread_id(thread).should == "1234abdc"
+ end
+ end
+
+ describe "when scrolling the instrumentation string" do
+ it "should rotate the string of various step" do
+ ProcessName.rotate("this is a rotation", 10).should == "rotation -- this is a "
+ end
+
+ it "should not rotate the string for the 0 offset" do
+ ProcessName.rotate("this is a rotation", 0).should == "this is a rotation"
+ end
+ end
+
+ describe "when setting process name" do
+ before(:each) do
+ ProcessName.stubs(:process_name).returns("12345 activity")
+ ProcessName.stubs(:base).returns("base")
+ @oldname = $0
+ end
+
+ after(:each) do
+ $0 = @oldname
+ end
+
+ it "should not do it if the feature is disabled" do
+ ProcessName.setproctitle
+
+ $0.should_not == "base: 12345 activity"
+ end
+
+ it "should do it if the feature is enabled" do
+ ProcessName.active = true
+ ProcessName.setproctitle
+
+ $0.should == "base: 12345 activity"
+ end
+ end
+
+ describe "when setting a probe" do
+ before(:each) do
+ thread = stub 'thread', :inspect => "\#<Thread:0x1234abdc run>"
+ Thread.stubs(:current).returns(thread)
+ Thread.stubs(:new)
+ ProcessName.active = true
+ end
+
+ it "should start the scroller thread" do
+ Thread.expects(:new)
+ ProcessName.instrument("doing something") do
+ end
+ end
+
+ it "should push current thread activity and execute the block" do
+ ProcessName.instrument("doing something") do
+ $0.should == "puppet: 1234abdc doing something"
+ end
+ end
+
+ it "should finally pop the activity" do
+ ProcessName.instrument("doing something") do
+ end
+ $0.should == "puppet: "
+ end
+ end
+
+ describe "when enabling" do
+ before do
+ Thread.stubs(:new)
+ ProcessName.stubs(:setproctitle)
+ end
+
+ it "should be active" do
+ ProcessName.enable
+ ProcessName.should be_active
+ end
+
+ it "should set the new process name" do
+ ProcessName.expects(:setproctitle)
+ ProcessName.enable
+ end
+ end
+
+ describe "when disabling" do
+ it "should set active to false" do
+ ProcessName.active = true
+ ProcessName.disable
+ ProcessName.should_not be_active
+ end
+
+ it "should restore the old process name" do
+ oldname = $0
+ ProcessName.active = true
+ ProcessName.setproctitle
+ ProcessName.disable
+ $0.should == oldname
+ end
+ end
+
+ describe "when scrolling" do
+ it "should do nothing for shorter process names" do
+ ProcessName.expects(:setproctitle).never
+ ProcessName.scroll
+ end
+
+ it "should call setproctitle" do
+ ProcessName.stubs(:process_name).returns("x" * 60)
+ ProcessName.expects(:setproctitle)
+ ProcessName.scroll
+ end
+
+ it "should increment rotation offset" do
+ name = "x" * 60
+ ProcessName.active = true
+ ProcessName.stubs(:process_name).returns(name)
+ ProcessName.expects(:rotate).once.with(name,1).returns("")
+ ProcessName.expects(:rotate).once.with(name,2).returns("")
+ ProcessName.scroll
+ ProcessName.scroll
+ end
+ end
+
+end \ No newline at end of file