whysoslow 0.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.
@@ -0,0 +1,6 @@
1
+ pkg/*
2
+ .bundle
3
+ *.gem
4
+ *.log
5
+ .rvmrc
6
+ .rbenv-version
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify dependencies in whysoslow.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~>0.9.2'
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ whysoslow (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ ansi (1.4.2)
10
+ assert (0.7.3)
11
+ assert-view (~> 0.5)
12
+ assert-view (0.5.0)
13
+ ansi (~> 1.3)
14
+ undies (~> 2.0)
15
+ rake (0.9.2.2)
16
+ undies (2.2.0)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ assert
23
+ bundler
24
+ rake (~> 0.9.2)
25
+ whysoslow!
@@ -0,0 +1,59 @@
1
+ = Whysoslow
2
+
3
+ == Description
4
+
5
+ Whysoslow is a little runner/printer I wrote to benchmark my Ruby code. It runs a block of code, collects run time measurements, and can snapshot memory usage information at various points during execution.
6
+
7
+ It has no tests, shockingly sparse docs, and is more of an experiment at this point. It comes as is for now so use if you see fit.
8
+
9
+ I will happily accept any pull requests for documentation, tests, extensions, and improvements.
10
+
11
+ == Installation
12
+
13
+ gem install whysoslow
14
+
15
+ == Usage
16
+
17
+ require 'whysoslow'
18
+
19
+ printer = Whysoslow::DefaultPrinter.new({
20
+ :title => "Bench Report Title",
21
+ :verbose => true
22
+ })
23
+
24
+ runner = Whysoslow::Runner.new(@printer)
25
+
26
+ runner.run do
27
+ # ... some long running script or loop
28
+ runner.snapshot("mem usage during run")
29
+ # ... some more long running script or loop
30
+ end
31
+
32
+ == Output: DefaultPrinter
33
+
34
+ The DefaultPrinter outputs to an io stream ($stdout by default). It uses Ansi to output columned memory usage snapshot data and measurement data.
35
+
36
+ == License
37
+
38
+ Copyright (c) 2012 Kelly Redding
39
+
40
+ Permission is hereby granted, free of charge, to any person
41
+ obtaining a copy of this software and associated documentation
42
+ files (the "Software"), to deal in the Software without
43
+ restriction, including without limitation the rights to use,
44
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
45
+ copies of the Software, and to permit persons to whom the
46
+ Software is furnished to do so, subject to the following
47
+ conditions:
48
+
49
+ The above copyright notice and this permission notice shall be
50
+ included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
53
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
54
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
55
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
56
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
57
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
58
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
59
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ require 'assert/rake_tasks'
2
+ Assert::RakeTasks.for(:test)
3
+
4
+ require 'bundler'
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ task :default => :build
@@ -0,0 +1,4 @@
1
+ module Whysoslow; end
2
+
3
+ require 'whysoslow/runner'
4
+ require 'whysoslow/default_printer'
@@ -0,0 +1,91 @@
1
+ require 'ansi'
2
+
3
+ module Whysoslow
4
+ class DefaultPrinter
5
+
6
+ def initialize(*args)
7
+ @opts, @ios = [
8
+ args.last.kind_of?(::Hash) ? args.pop : {},
9
+ args.pop || $stdout
10
+ ]
11
+ @ios.sync = true
12
+ @title = @opts[:title]
13
+ @verbose = @opts[:verbose]
14
+ end
15
+
16
+ def print(thing)
17
+ case thing
18
+ when :title
19
+ if @title
20
+ @ios.puts @title.to_s
21
+ @ios.puts '-'*(@title.to_s.length)
22
+ end
23
+ when :run_start
24
+ @ios.print "whysoslow? " if @verbose
25
+ when :snapshot
26
+ @ios.print '.' if @verbose
27
+ when :run_end
28
+ @ios.print "\n\n" if @verbose
29
+ when Whysoslow::Results
30
+ @ios << snapshots_table(thing)
31
+ @ios.puts
32
+ @ios << measurements_table(thing)
33
+ else
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def snapshots_table(results)
40
+ units = results.snapshot_units
41
+ tdata = [
42
+ results.snapshot_labels.collect { |l| "mem @ #{l}" },
43
+ results.snapshot_usages.collect { |u| "#{memory_s(u)} #{units}" },
44
+ results.snapshot_diffs.collect do |diff_perc|
45
+ diff = diff_perc.first
46
+ perc = diff_perc.last
47
+ if diff == '??'
48
+ "??"
49
+ elsif diff > 0
50
+ ANSI.red do
51
+ "+#{memory_s(diff.abs)} #{units}, #{perc_s(perc)}%"
52
+ end
53
+ else
54
+ ANSI.green do
55
+ "-#{memory_s(diff.abs)} #{units}, #{perc_s(perc)}%"
56
+ end
57
+ end
58
+ end
59
+ ].flatten
60
+ ANSI::Columns.new(tdata, {
61
+ :columns => 3,
62
+ :align => :left,
63
+ :padding => 1
64
+ }).to_s
65
+ end
66
+
67
+ def measurements_table(results)
68
+ units = results.measurement_units
69
+ tdata = [
70
+ ['', 'time'],
71
+ results.measurements.collect do |l_t|
72
+ [l_t.first, "#{l_t.last} #{units}"]
73
+ end
74
+ ].flatten
75
+ ANSI::Columns.new(tdata, :columns => 5, :align=>:left).to_s
76
+ end
77
+
78
+ def snapshots_label_col
79
+ results.snapshots.collect{|r| "mem @ #{r.label}" }
80
+ end
81
+
82
+ def memory_s(amount)
83
+ amount.round.to_s.rjust(4)
84
+ end
85
+
86
+ def perc_s(perc)
87
+ (perc*100).round.abs.to_s.rjust(3)
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,34 @@
1
+ require 'benchmark'
2
+
3
+ module Whysoslow
4
+ class Measurement
5
+
6
+ MEASUREMENTS = [:user, :system, :total, :real]
7
+ attr_reader :units, :multiplier
8
+ attr_reader *MEASUREMENTS
9
+
10
+ def initialize(units='ms')
11
+ @user, @system, @total, @real = 0
12
+ @units, @multiplier = if units == 's'
13
+ ['s', 1]
14
+ else # MB
15
+ ['ms', 1000]
16
+ end
17
+ end
18
+
19
+ def benchmark(&block)
20
+ Benchmark.measure(&block).tap do |values|
21
+ measurments = values.to_s.strip.gsub(/[^\s|0-9|\.]/, '').split(/\s+/)
22
+ self.user, self.system, self.total, self.real = measurments
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def user=(value_in_secs); @user = value_in_secs.to_f * @multiplier; end
29
+ def system=(value_in_secs); @system = value_in_secs.to_f * @multiplier; end
30
+ def total=(value_in_secs); @total = value_in_secs.to_f * @multiplier; end
31
+ def real=(value_in_secs); @real = value_in_secs.to_f * @multiplier; end
32
+
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ module Whysoslow
2
+
3
+ class MemoryProfile
4
+
5
+ class Snapshot; end
6
+
7
+ attr_reader :snapshots, :divider
8
+ attr_accessor :units
9
+
10
+ def initialize(units='MB')
11
+ @snapshots = []
12
+ self.units = units
13
+ end
14
+
15
+ def units=(value)
16
+ @units, @divider = if value == 'KB'
17
+ ['KB', 1]
18
+ else # MB
19
+ ['MB', 1000]
20
+ end
21
+ end
22
+
23
+ def snapshot(label)
24
+ Snapshot.new(label, @divider).tap { |snap| @snapshots.push(snap) }
25
+ end
26
+
27
+ end
28
+
29
+ class MemoryProfile::Snapshot
30
+
31
+ attr_reader :label, :memory
32
+
33
+ def initialize(label, divider)
34
+ @label = label
35
+ @memory = capture_memory_usage(divider)
36
+ end
37
+
38
+ protected
39
+
40
+ def capture_memory_usage(divider)
41
+ ((`ps -o rss= -p #{$$}`.to_i) / divider.to_f)
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,67 @@
1
+ require 'whysoslow/measurement'
2
+ require 'whysoslow/memory_profile'
3
+
4
+ module Whysoslow
5
+ class Results
6
+
7
+ attr_reader :desc
8
+
9
+ def initialize(desc, time_unit, memory_unit)
10
+ @desc = desc
11
+ @measurement = Measurement.new(time_unit)
12
+ @memory_profile = MemoryProfile.new(memory_unit)
13
+ end
14
+
15
+ def benchmark(*args, &block)
16
+ @measurement.benchmark(*args, &block)
17
+ end
18
+
19
+ def snapshot(*args)
20
+ @memory_profile.snapshot(*args)
21
+ end
22
+
23
+ def snapshots
24
+ @snapshots ||= @memory_profile.snapshots
25
+ end
26
+
27
+ def snapshot_units
28
+ @memory_profile.units
29
+ end
30
+
31
+ def snapshot_labels
32
+ snapshots.collect{|s| s.label }
33
+ end
34
+
35
+ def snapshot_usages
36
+ snapshots.collect{|s| s.memory }
37
+ end
38
+
39
+ def snapshot_diffs
40
+ prev = nil
41
+ snapshots.inject([]) do |diffs, snapshot|
42
+ if prev.nil? || prev.memory == 0
43
+ diffs << ['??', '??']
44
+ else
45
+ diff = snapshot.memory - prev.memory
46
+ diffs << [
47
+ diff,
48
+ diff.to_f / prev.memory.to_f
49
+ ]
50
+ end
51
+ prev = snapshot
52
+ diffs
53
+ end
54
+ end
55
+
56
+ def measurements
57
+ @measurements ||= Measurement::MEASUREMENTS.collect do |m|
58
+ [m, @measurement.send(m)]
59
+ end
60
+ end
61
+
62
+ def measurement_units
63
+ @measurement.units
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,37 @@
1
+ require 'whysoslow/results'
2
+
3
+ module Whysoslow
4
+
5
+ class Runner
6
+
7
+ attr_accessor :desc, :verbose, :time_unit, :memory_unit
8
+
9
+ def initialize(printer, opts={})
10
+ @printer = printer
11
+ @time_unit = opts[:time_unit]
12
+ @memory_unit = opts[:memory_unit]
13
+ @results = nil
14
+ end
15
+
16
+ def run(&block)
17
+ @printer.print :title
18
+ @printer.print(Results.new(@desc, @time_unit, @memory_unit).tap do |results|
19
+ @results = results
20
+ @printer.print :run_start
21
+ self.snapshot('start')
22
+ @results.benchmark(&block)
23
+ self.snapshot('finish')
24
+ @printer.print :run_end
25
+ @results = nil
26
+ end)
27
+ end
28
+
29
+ def snapshot(*args)
30
+ raise RuntimeError, "no active results being gathered - be sure and call snapshot during a run session" if @results.nil?
31
+ @results.snapshot(*args)
32
+ @printer.print :snapshot
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,3 @@
1
+ module Whysoslow
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ # this file is automatically required in when you require 'assert' in your tests
2
+ # put test helpers here
3
+
4
+ # add root dir to the load path
5
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
@@ -0,0 +1,9 @@
1
+ require 'assert/setup'
2
+
3
+ # this file is required in when the 'irb' rake test is run.
4
+ # b/c 'assert/setup' is required above, the test helper will be
5
+ # required in as well.
6
+
7
+ # put any IRB setup code here
8
+
9
+ require 'whysoslow'
@@ -0,0 +1,4 @@
1
+ require "assert"
2
+
3
+ class WhysoslowTest < Assert::Context
4
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "whysoslow/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "whysoslow"
7
+ s.version = Whysoslow::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Kelly Redding"]
10
+ s.email = ["kelly@kellyredding.com"]
11
+ s.homepage = "http://github.com/kellyredding/whysoslow"
12
+ s.summary = %q{A little runner/printer to benchmark Ruby code blocks}
13
+ s.description = %q{A little runner/printer to benchmark Ruby code blocks}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency("bundler")
21
+ s.add_development_dependency("assert")
22
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: whysoslow
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Kelly Redding
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-20 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :development
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ version_requirements: *id001
33
+ name: bundler
34
+ - !ruby/object:Gem::Dependency
35
+ type: :development
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ version_requirements: *id002
47
+ name: assert
48
+ description: A little runner/printer to benchmark Ruby code blocks
49
+ email:
50
+ - kelly@kellyredding.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - .gitignore
59
+ - Gemfile
60
+ - Gemfile.lock
61
+ - README.rdoc
62
+ - Rakefile
63
+ - lib/whysoslow.rb
64
+ - lib/whysoslow/default_printer.rb
65
+ - lib/whysoslow/measurement.rb
66
+ - lib/whysoslow/memory_profile.rb
67
+ - lib/whysoslow/results.rb
68
+ - lib/whysoslow/runner.rb
69
+ - lib/whysoslow/version.rb
70
+ - test/helper.rb
71
+ - test/irb.rb
72
+ - test/whysoslow_test.rb
73
+ - whysoslow.gemspec
74
+ homepage: http://github.com/kellyredding/whysoslow
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options: []
79
+
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.11
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: A little runner/printer to benchmark Ruby code blocks
107
+ test_files:
108
+ - test/helper.rb
109
+ - test/irb.rb
110
+ - test/whysoslow_test.rb