stickshift 0.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,3 @@
1
+ == 0.1
2
+
3
+ - Initial release
@@ -0,0 +1,21 @@
1
+ # (c) Copyright 2008 Nick Sieger <nicksieger@gmail.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
@@ -0,0 +1,11 @@
1
+ examples/example.rake
2
+ examples/stickshift_rails.rb
3
+ History.txt
4
+ lib/stickshift/version.rb
5
+ lib/stickshift.rb
6
+ LICENSE.txt
7
+ Manifest.txt
8
+ Rakefile
9
+ README.markdown
10
+ stickshift.gemspec
11
+ test/test_stickshift.rb
@@ -0,0 +1,75 @@
1
+ Stickshift is a simple, manual-instrumenting call-tree profiler in as
2
+ few lines of code as possible.
3
+
4
+ ## Background
5
+
6
+ The idea is to be as minimally intrusive as possible, but also to be
7
+ stupid simple and manual. You tell stickshift to only instrument the
8
+ methods you want, and it measures time spent in that method, as well
9
+ as any time spent in any instrumented methods invoked by the method.
10
+ After leaving the top-most method, a call tree with timings is dumped
11
+ to $stdout.
12
+
13
+ Note: You aren't allowed to instrument methods on the String class, or any
14
+ object's #inspect method, because it can generate a recursive loop! For
15
+ #fine-grained profiling on any arbitrary class, use profiler or ruby-prof
16
+ instead. You're also not allowed to instrument an instrumented method.
17
+
18
+ ## Example
19
+
20
+ Consider the following Rakefile:
21
+
22
+ task :sleep => :snooze do
23
+ sleep 1
24
+ end
25
+ task :snooze do
26
+ sleep 2
27
+ end
28
+ task :default => :sleep
29
+
30
+ require 'stickshift'
31
+ ::Rake::Application.instrument :top_level
32
+ ::Rake::Application.instrument :[], :with_args => 0
33
+ ::Rake::Task.instrument :invoke, :execute, :inspect_self => true
34
+
35
+ Running 'rake' will produce the following output. Method self time is
36
+ shown along the left, while method total time is at the end of each
37
+ line.
38
+
39
+ 0ms > Rake::Application#top_level < 3001ms
40
+ 0ms > Rake::Application#[]("default") < 0ms
41
+ 0ms > Rake::Task#invoke{<Rake::Task default => [sleep]>} < 3001ms
42
+ 0ms > Rake::Application#[]("sleep") < 0ms
43
+ 0ms > Rake::Application#[]("snooze") < 0ms
44
+ 2000ms > Rake::Task#execute{<Rake::Task snooze => []>} < 2000ms
45
+ 1000ms > Rake::Task#execute{<Rake::Task sleep => [snooze]>} < 1000ms
46
+ 0ms > Rake::Task#execute{<Rake::Task default => [sleep]>} < 0ms
47
+
48
+ ## Instrumentation
49
+
50
+ Stickshift adds a method named #instrument to the Module class that
51
+ instruments methods in the receiver module or class.
52
+
53
+ It accepts one or more method names, and an optional trailing hash of
54
+ options:
55
+
56
+ * `:label => "label"` gives the instrumented method a custom label
57
+ instead of the default "Class#method" label.
58
+ * `:with_args => 0` causes the first argument to the method to be
59
+ included in the label. Ranges can also be used.
60
+ * `:inspect_self => true` causes the inspected object to be printed in
61
+ the label.
62
+ * `:top_level => true` causes Stickshift to only be activated after
63
+ passing through this method. If any other non-top-level instrumented
64
+ method is called outside of a top-level method, it will not be timed
65
+ or reported.
66
+
67
+ `#uninstrument` and `#uninstrument_all` methods are also available if
68
+ you wish to disable instrumentation.
69
+
70
+ ## Capture/Settings
71
+
72
+ * `Stickshift.enabled` (default `true`) controls whether Stickshift is
73
+ on or not.
74
+ * `Stickshift.output` (default `$stdout`) controls where output is
75
+ written. The object must respond to `#puts(*lines)`.
@@ -0,0 +1,38 @@
1
+ require 'rake/testtask'
2
+
3
+ begin
4
+ require 'hoe'
5
+ Hoe.plugin :rubyforge
6
+ hoe = Hoe.spec("stickshift") do |p|
7
+ require File.dirname(__FILE__) + '/lib/stickshift/version'
8
+ p.version = Stickshift::VERSION
9
+ p.rubyforge_name = "caldersphere"
10
+ p.url = "http://caldersphere.rubyforge.org/stickshift"
11
+ p.readme_file = "README.markdown"
12
+ p.author = "Nick Sieger"
13
+ p.email = "nick@nicksieger.com"
14
+ p.summary = "Stickshift is a pedal-to-the-metal manual profiler."
15
+ p.description = "Stickshift is a simple, manual-instrumenting call-tree profiler in as few lines of code as possible."
16
+ end
17
+
18
+ task :gemspec do
19
+ File.open("#{hoe.name}.gemspec", "w") {|f| f << hoe.spec.to_ruby }
20
+ end
21
+ task :package => :gemspec
22
+ rescue LoadError
23
+ puts "You need hoe installed to be able to package this gem"
24
+ end
25
+
26
+ # Hoe has its own test, but I want Rake::TestTask
27
+ Rake::Task['test'].send :instance_variable_set, "@actions", []
28
+ Rake::TestTask.new
29
+
30
+ task :default => :test
31
+
32
+ desc "Run the instrumented rake example"
33
+ task :example do
34
+ puts "# Here is the example Rakefile"
35
+ puts *(File.readlines("examples/example.rake"))
36
+ puts "# Running it"
37
+ ruby "-Ilib -S rake -f examples/example.rake"
38
+ end
@@ -0,0 +1,12 @@
1
+ task :sleep => :snooze do
2
+ sleep 1
3
+ end
4
+ task :snooze do
5
+ sleep 2
6
+ end
7
+ task :default => :sleep
8
+
9
+ require 'stickshift'
10
+ ::Rake::Application.instrument :top_level
11
+ ::Rake::Application.instrument :[], :with_args => 0
12
+ ::Rake::Task.instrument :invoke, :execute, :inspect_self => true
@@ -0,0 +1,34 @@
1
+ # Instrument some of the main methods hit during a Rails request dispatch.
2
+ # Put stickshift.rb in the 'lib' directory of your rails app and this file
3
+ # in the config/initializers directory.
4
+
5
+ require 'stickshift'
6
+ require 'dispatcher'
7
+ require 'action_controller'
8
+
9
+ class << Dispatcher; instrument :dispatch, :label => "Rails Dispatcher", :top_level => true; end
10
+ class << ActionController::Base; instrument :process; end
11
+ ActionController::Base.instrument :default_render, :respond_to
12
+ ActionController::Base.instance_methods.select {|m| m =~ /^(process|perform_action)/ }.each do |m|
13
+ ActionController::Base.instrument m
14
+ end
15
+ ActionController::Base.private_instance_methods.select {|m| m =~ /^(process|perform_action)/ }.each do |m|
16
+ ActionController::Base.instrument m
17
+ end
18
+ ObjectSpace.each_object(Class) do |klazz|
19
+ klazz.instrument :run, :call, :inspect_self => true if klazz < ActionController::Filters::ClassMethods::Filter
20
+ end
21
+ ActionController::Base.instance_methods.select {|m| m =~ /^render/ }.each do |m|
22
+ ActionController::Base.instrument m, :with_args => 0
23
+ end
24
+ ActionController::Routing::RouteSet.instrument :recognize
25
+ ActionView::Base.instrument :render
26
+
27
+ class << ActiveRecord::Base; instrument :find; end
28
+ ActiveRecord::Base.instrument :save
29
+ ActiveRecord::ConnectionAdapters.constants.each do |c|
30
+ adapter_class = ActiveRecord::ConnectionAdapters.const_get(c)
31
+ if adapter_class < ActiveRecord::ConnectionAdapters::AbstractAdapter
32
+ adapter_class.instrument :execute, :with_args => 0
33
+ end
34
+ end
@@ -0,0 +1,142 @@
1
+ module Stickshift
2
+ class << self; attr_accessor :enabled, :top_level_trigger, :output; end
3
+ @enabled = true
4
+ @output = $stdout
5
+
6
+ class Timer
7
+ def self.current; Thread.current['__stickshift']; end
8
+ def self.current=(timer); Thread.current['__stickshift'] = timer; end
9
+ attr_reader :depth
10
+
11
+ def initialize(obj, meth, options, *args)
12
+ @depth, @options, @args = 1, options, args
13
+ @label = if @options[:label]
14
+ @options[:label]
15
+ else
16
+ klass = Class === obj ? "#{obj.name}." : "#{obj.class.name}#"
17
+ "#{klass}#{meth}"
18
+ end
19
+ @label << "{#{obj.inspect}}" if @options[:inspect_self]
20
+ @label << "(#{@args[@options[:with_args]].inspect})" if @options[:with_args]
21
+ if @parent = Timer.current
22
+ @parent.add(self)
23
+ @depth = @parent.depth + 1
24
+ end
25
+ end
26
+
27
+ def enabled?
28
+ Stickshift.enabled && (Timer.current || Stickshift.top_level_trigger.nil? || @options[:top_level])
29
+ end
30
+
31
+ def invoke(&block)
32
+ return block.call unless enabled?
33
+ begin
34
+ Timer.current = self
35
+ result = nil
36
+ @elapsed = realtime do
37
+ result = block.call
38
+ end
39
+ result
40
+ ensure
41
+ Timer.current = @parent
42
+ report unless @parent
43
+ end
44
+ end
45
+
46
+ def realtime
47
+ start = Time.now
48
+ yield
49
+ Time.now - start
50
+ end
51
+
52
+ def add(child)
53
+ children << child
54
+ end
55
+
56
+ def children
57
+ @children ||= []
58
+ end
59
+
60
+ def elapsed
61
+ @elapsed ||= 0
62
+ end
63
+
64
+ def report
65
+ Stickshift.output.puts "#{self_format % self_time}ms >#{' ' * @depth}#{@label} < #{total_time}ms"
66
+ children.each {|c| c.report}
67
+ end
68
+
69
+ def ms(t)
70
+ (t * 1000)
71
+ end
72
+
73
+ def self_format
74
+ @self_format ||= if @parent
75
+ @parent.self_format
76
+ else
77
+ width = ("%0.2f" % total_time).length
78
+ "%#{width}.2f"
79
+ end
80
+ end
81
+
82
+ def self_time
83
+ @self_time ||= ms(elapsed - children.inject(0) {|sum,el| sum += el.elapsed})
84
+ end
85
+
86
+ def total_time
87
+ ms(elapsed)
88
+ end
89
+ end
90
+ end
91
+
92
+ class Module
93
+ def instrument(*meths)
94
+ options = Hash === meths.last ? meths.pop : {}
95
+ Stickshift.top_level_trigger = true if options[:top_level]
96
+ @__stickshift ||= {}
97
+ meths.each do |meth|
98
+ unless instrumented?(meth)
99
+ mangled = __stickshift_mangle(meth)
100
+ meth_opts = options.merge(:original => meth)
101
+ @__stickshift[mangled] = meth_opts
102
+ define_method("#{mangled}__instrumented") { meth_opts }
103
+ alias_method "#{mangled}__orig_instrument", meth
104
+ alias_method "#{mangled}__without_instrument", meth
105
+ module_eval(<<-METH, __FILE__, __LINE__)
106
+ def #{meth}(*args, &block)
107
+ Stickshift::Timer.new(self, "#{meth}", #{mangled}__instrumented, *args).invoke do
108
+ #{mangled}__without_instrument(*args, &block)
109
+ end
110
+ end
111
+ METH
112
+ end
113
+ end
114
+ end
115
+
116
+ RESTRICTED_CLASSES = [String]
117
+ RESTRICTED_METHODS = %w(inspect __send__ __id__)
118
+
119
+ def instrumented?(meth)
120
+ RESTRICTED_CLASSES.include?(self) ||
121
+ RESTRICTED_METHODS.include?(meth.to_s) ||
122
+ meth =~ /__instrumented$/ ||
123
+ instance_methods.include?("#{__stickshift_mangle(meth)}__instrumented")
124
+ end
125
+
126
+ def uninstrument(*meths)
127
+ meths.each do |meth|
128
+ if instrumented?(meth)
129
+ remove_method "#{__stickshift_mangle(meth)}__instrumented"
130
+ alias_method meth, "#{__stickshift_mangle(meth)}__orig_instrument"
131
+ end
132
+ end
133
+ end
134
+
135
+ def uninstrument_all
136
+ uninstrument(*(instance_methods.select {|m| m =~ /__instrumented$/}.map {|mi| @__stickshift[mi[0...-14]][:original]}))
137
+ end
138
+
139
+ def __stickshift_mangle(meth)
140
+ (s = meth.to_s) =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/ ? s : "_#{s.unpack("H*")[0]}"
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Stickshift
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{stickshift}
5
+ s.version = "0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Nick Sieger"]
9
+ s.date = %q{2011-04-25}
10
+ s.description = %q{Stickshift is a simple, manual-instrumenting call-tree profiler in as few lines of code as possible.}
11
+ s.email = %q{nick@nicksieger.com}
12
+ s.extra_rdoc_files = ["History.txt", "LICENSE.txt", "Manifest.txt"]
13
+ s.files = ["examples/example.rake", "examples/stickshift_rails.rb", "History.txt", "lib/stickshift/version.rb", "lib/stickshift.rb", "LICENSE.txt", "Manifest.txt", "Rakefile", "README.markdown", "stickshift.gemspec", "test/test_stickshift.rb", ".gemtest"]
14
+ s.homepage = %q{http://caldersphere.rubyforge.org/stickshift}
15
+ s.rdoc_options = ["--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{caldersphere}
18
+ s.rubygems_version = %q{1.5.1}
19
+ s.summary = %q{Stickshift is a pedal-to-the-metal manual profiler.}
20
+ s.test_files = ["test/test_stickshift.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_development_dependency(%q<rubyforge>, [">= 2.0.4"])
27
+ s.add_development_dependency(%q<hoe>, [">= 2.9.4"])
28
+ else
29
+ s.add_dependency(%q<rubyforge>, [">= 2.0.4"])
30
+ s.add_dependency(%q<hoe>, [">= 2.9.4"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<rubyforge>, [">= 2.0.4"])
34
+ s.add_dependency(%q<hoe>, [">= 2.9.4"])
35
+ end
36
+ end
@@ -0,0 +1,158 @@
1
+ require 'test/unit'
2
+ require 'stringio'
3
+ require 'stickshift'
4
+
5
+ class Foo
6
+ def slow_method
7
+ sleep 0.1
8
+ end
9
+ def hello
10
+ end
11
+ end
12
+
13
+ class Bar
14
+ def slow_method
15
+ call_foo
16
+ end
17
+
18
+ def call_foo
19
+ Foo.new.slow_method
20
+ end
21
+
22
+ def several_times
23
+ 10.times do
24
+ slow_method
25
+ end
26
+ end
27
+
28
+ def call(*args)
29
+ end
30
+ end
31
+
32
+ module TestHelper
33
+ def instrumented_name(meth)
34
+ Module.__stickshift_mangle(meth) + '__instrumented'
35
+ end
36
+ module_function :instrumented_name
37
+ end
38
+
39
+ class StickshiftTest < Test::Unit::TestCase
40
+ include TestHelper
41
+
42
+ def capture
43
+ Stickshift.output = @stdout = StringIO.new
44
+ @out = nil
45
+ end
46
+
47
+ def teardown
48
+ Foo.uninstrument_all
49
+ Bar.uninstrument_all
50
+ Array.uninstrument_all
51
+ String.uninstrument_all
52
+ puts @out if @out
53
+ Stickshift.top_level_trigger = nil
54
+ Stickshift.output = $stdout
55
+ end
56
+
57
+ def test_formatting
58
+ Bar.instrument :slow_method, :several_times
59
+ Foo.instrument :slow_method
60
+ Bar.new.several_times
61
+ end
62
+
63
+ def test_output_profile
64
+ Bar.instrument :slow_method
65
+ Foo.instrument :slow_method
66
+ capture
67
+ Bar.new.slow_method
68
+ out = @stdout.string
69
+ assert out =~ /Bar#slow_method/
70
+ assert out =~ /Foo#slow_method/
71
+ end
72
+
73
+ def test_custom_label
74
+ capture
75
+ Foo.instrument :hello, :label => "Hello World!"
76
+ Foo.new.hello
77
+ assert @stdout.string =~ /Hello World!/
78
+ end
79
+
80
+ def test_instrument_uninstrument
81
+ capture
82
+ Foo.new.hello
83
+ assert @stdout.string.empty?
84
+ Foo.instrument :hello
85
+ Foo.new.hello
86
+ assert @stdout.string =~ /Foo#hello/
87
+ @stdout.string = ''
88
+ Foo.uninstrument :hello
89
+ Foo.new.hello
90
+ assert @stdout.string.empty?
91
+ end
92
+
93
+ def test_instrument_with_first_arg
94
+ capture
95
+ Bar.instrument :call, :with_args => 0
96
+ Bar.new.call("abc")
97
+ Bar.new.call("def")
98
+ assert @stdout.string =~ /call.*"abc"/
99
+ assert @stdout.string =~ /call.*"def"/
100
+ end
101
+
102
+ def test_instrument_operator_method
103
+ capture
104
+ Array.instrument :<<
105
+ [] << 1
106
+ assert @stdout.string =~ /<</
107
+ end
108
+
109
+ def test_instrument_label_self_inspect
110
+ capture
111
+ Array.instrument :length, :inspect_self => true
112
+ %w(a b c).length
113
+ assert @stdout.string =~ /"a", "b", "c"/
114
+ end
115
+
116
+ def test_output_only_when_triggered_by_top_level_method
117
+ capture
118
+ Foo.instrument :slow_method
119
+ Bar.instrument :slow_method
120
+ Bar.new.slow_method
121
+
122
+ assert !@stdout.string.empty?
123
+ expected = @stdout.string
124
+ @stdout.reopen(StringIO.new)
125
+ assert @stdout.string.empty?
126
+
127
+ Bar.uninstrument :slow_method
128
+ Bar.instrument :slow_method, :top_level => true
129
+ assert Stickshift.top_level_trigger
130
+
131
+ Foo.new.slow_method
132
+ assert @stdout.string.empty?
133
+
134
+ Bar.new.slow_method
135
+ assert @stdout.string =~ /Bar#slow_method.*Foo#slow_method/m
136
+ end
137
+
138
+ def test_cannot_instrument_instrumented_methods
139
+ Foo.instrument :slow_method
140
+ assert Foo.instrumented?(:slow_method)
141
+ instrumented = instrumented_name(:slow_method)
142
+ assert Foo.instance_methods.include?(instrumented)
143
+ Foo.instrument instrumented
144
+ assert !Foo.instance_methods.include?(instrumented_name(instrumented))
145
+ end
146
+
147
+ def test_cannot_instrument_restricted_methods
148
+ [:inspect, :__send__, :__id__].each do |m|
149
+ Foo.instrument m
150
+ assert !Foo.instance_methods.include?(instrumented_name(m))
151
+ end
152
+ end
153
+
154
+ def test_cannot_instrument_string_or_benchmark_methods
155
+ String.instrument :to_s
156
+ assert !String.instance_methods.include?(instrumented_name(:to_s))
157
+ end
158
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stickshift
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.1"
6
+ platform: ruby
7
+ authors:
8
+ - Nick Sieger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-25 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rubyforge
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.0.4
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: hoe
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.9.4
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: Stickshift is a simple, manual-instrumenting call-tree profiler in as few lines of code as possible.
39
+ email: nick@nicksieger.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - History.txt
46
+ - LICENSE.txt
47
+ - Manifest.txt
48
+ files:
49
+ - examples/example.rake
50
+ - examples/stickshift_rails.rb
51
+ - History.txt
52
+ - lib/stickshift/version.rb
53
+ - lib/stickshift.rb
54
+ - LICENSE.txt
55
+ - Manifest.txt
56
+ - Rakefile
57
+ - README.markdown
58
+ - stickshift.gemspec
59
+ - test/test_stickshift.rb
60
+ - .gemtest
61
+ has_rdoc: true
62
+ homepage: http://caldersphere.rubyforge.org/stickshift
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.markdown
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project: caldersphere
86
+ rubygems_version: 1.5.1
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Stickshift is a pedal-to-the-metal manual profiler.
90
+ test_files:
91
+ - test/test_stickshift.rb