stickshift 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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