This listener changes the current process name in function of what
instrumented code is running. If the name is too long, the process
name is automatically scrolled to show all the activity.

Signed-off-by: Brice Figureau <[email protected]>
---
 .../util/instrumentation/listeners/process_name.rb |  103 ++++++++++++
 .../instrumentation/listeners/process_name_spec.rb |  169 ++++++++++++++++++++
 2 files changed, 272 insertions(+), 0 deletions(-)
 create mode 100644 lib/puppet/util/instrumentation/listeners/process_name.rb
 create mode 100755 
spec/unit/util/instrumentation/listeners/process_name_spec.rb

diff --git a/lib/puppet/util/instrumentation/listeners/process_name.rb 
b/lib/puppet/util/instrumentation/listeners/process_name.rb
new file mode 100644
index 0000000..2b5d7d0
--- /dev/null
+++ b/lib/puppet/util/instrumentation/listeners/process_name.rb
@@ -0,0 +1,103 @@
+require 'monitor'
+
+# Unlike the other instrumentation plugins, this one doesn't gives back
+# data. Instead it changes the process name of the currently running process
+# with the last labels and data. 
+Puppet::Util::Instrumentation.new_listener(:process_name) do
+  # start scrolling when process name is longer than
+  SCROLL_LENGTH = 50
+
+  attr_accessor :active, :reason
+
+  def notify(label, event, data)
+    start(label) if event == :start
+    stop if event == :stop
+  end
+
+  def start(activity)
+    @scroller ||= Thread.new do
+      loop do
+        scroll
+        sleep 1
+      end
+    end
+
+    push_activity(Thread.current, activity)
+  end
+
+  def stop()
+    pop_activity(Thread.current)
+  end
+
+  def setproctitle
+    @oldname ||= $0
+    $0 = "#{base}: " + rotate(process_name,@x)
+  end
+
+  def push_activity(thread, activity)
+    mutex.synchronize do
+      @reason ||= {}
+      @reason[thread] ||= []
+      @reason[thread].push(activity)
+      setproctitle
+    end
+  end
+
+  def pop_activity(thread)
+    mutex.synchronize do
+      @reason[thread].pop
+      if @reason[thread].empty?
+        @reason.delete(thread)
+      end
+      setproctitle
+    end
+  end
+
+  def process_name
+    out = (@reason || {}).inject([]) do |out, reason|
+      out << "#{thread_id(reason[0])} #{reason[1].join(',')}"
+    end
+    out.join(' | ')
+  end
+
+  # certainly non-portable
+  def thread_id(thread)
+    thread.inspect.gsub(/^#<.*:0x([a-f0-9]+) .*>$/, '\1')
+  end
+
+  def 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 base
+    basename = case Puppet.run_mode.name
+    when :master
+      "master"
+    when :agent
+      "agent"
+    else
+      "puppet"
+    end
+  end
+
+  def mutex
+    #Thread.exclusive {
+      @mutex ||= Sync.new
+    #}
+    @mutex
+  end
+
+  def scroll
+    @x ||= 1
+    return if process_name.length < SCROLL_LENGTH
+    mutex.synchronize do
+      setproctitle
+      @x += 1
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/unit/util/instrumentation/listeners/process_name_spec.rb 
b/spec/unit/util/instrumentation/listeners/process_name_spec.rb
new file mode 100755
index 0000000..42b6b1c
--- /dev/null
+++ b/spec/unit/util/instrumentation/listeners/process_name_spec.rb
@@ -0,0 +1,169 @@
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? 
require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+require 'puppet/util/instrumentation'
+
+Puppet::Util::Instrumentation.init
+process_name = Puppet::Util::Instrumentation.listener(:process_name)
+
+describe process_name do
+  before(:each) do
+    @process_name = process_name.new
+  end
+
+  it "should have a notify method" do
+    @process_name.should respond_to(:notify)
+  end
+
+  it "should not have a data method" do
+    @process_name.should_not respond_to(:data)
+  end
+
+  describe "when managing thread activity" do
+    before(:each) do
+      @process_name.stubs(:setproctitle)
+      @process_name.stubs(:base).returns("base")
+    end
+
+    it "should be able to append activity" do
+      thread1 = stub 'thread1'
+      @process_name.push_activity(:thread1,"activity1")
+      @process_name.push_activity(:thread1,"activity2")
+
+      @process_name.reason[:thread1].should == ["activity1", "activity2"]
+    end
+
+    it "should be able to remove activity" do
+      @process_name.push_activity(:thread1,"activity1")
+      @process_name.push_activity(:thread1,"activity1")
+      @process_name.pop_activity(:thread1)
+
+      @process_name.reason[:thread1].should == ["activity1"]
+    end
+
+    it "should maintain activity thread by thread" do
+      @process_name.push_activity(:thread1,"activity1")
+      @process_name.push_activity(:thread2,"activity2")
+
+      @process_name.reason[:thread1].should == ["activity1"]
+      @process_name.reason[:thread2].should == ["activity2"]
+    end
+
+    it "should set process title" do
+      @process_name.expects(:setproctitle)
+
+      @process_name.push_activity("thread1","activity1")
+    end
+  end
+
+  describe "when computing the current process name" do
+    before(:each) do
+      @process_name.stubs(:setproctitle)
+      @process_name.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
+
+      @process_name.push_activity(thread1,"Compiling node1.domain.com")
+      @process_name.push_activity(thread2,"Compiling node4.domain.com")
+      @process_name.push_activity(thread1,"Parsing file site.pp")
+      @process_name.push_activity(thread2,"Parsing file node.pp")
+
+      @process_name.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)
+        @process_name.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>"
+      @process_name.thread_id(thread).should == "1234abdc"
+    end
+  end
+
+  describe "when scrolling the instrumentation string" do
+    it "should rotate the string of various step" do
+      @process_name.rotate("this is a rotation", 10).should == "rotation -- 
this is a "
+    end
+
+    it "should not rotate the string for the 0 offset" do
+      @process_name.rotate("this is a rotation", 0).should == "this is a 
rotation"
+    end
+  end
+
+  describe "when setting process name" do
+    before(:each) do
+      @process_name.stubs(:process_name).returns("12345 activity")
+      @process_name.stubs(:base).returns("base")
+      @oldname = $0
+    end
+
+    after(:each) do
+      $0 = @oldname
+    end
+
+    it "should do it if the feature is enabled" do
+      @process_name.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)
+      @process_name.active = true
+    end
+
+    it "should start the scroller thread" do
+      Thread.expects(:new)
+      @process_name.notify(:instrumentation, :start, {})
+      @process_name.notify(:instrumentation, :stop, {})
+    end
+
+    it "should push current thread activity and execute the block" do
+      @process_name.notify(:instrumentation, :start, {})
+      $0.should == "puppet: 1234abdc instrumentation"
+      @process_name.notify(:instrumentation, :stop, {})
+    end
+
+    it "should finally pop the activity" do
+      @process_name.notify(:instrumentation, :start, {})
+      @process_name.notify(:instrumentation, :stop, {})
+      $0.should == "puppet: "
+    end
+  end
+
+  describe "when scrolling" do
+    it "should do nothing for shorter process names" do
+      @process_name.expects(:setproctitle).never
+      @process_name.scroll
+    end
+
+    it "should call setproctitle" do
+      @process_name.stubs(:process_name).returns("x" * 60)
+      @process_name.expects(:setproctitle)
+      @process_name.scroll
+    end
+
+    it "should increment rotation offset" do
+      name = "x" * 60
+      @process_name.stubs(:process_name).returns(name)
+      @process_name.expects(:rotate).once.with(name,1).returns("")
+      @process_name.expects(:rotate).once.with(name,2).returns("")
+      @process_name.scroll
+      @process_name.scroll
+    end
+  end
+end
\ No newline at end of file
-- 
1.7.5.1

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to