profiler 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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 58258bcebbf6b14691ad6d8341420b5dd937b72a
4
+ data.tar.gz: 92c30edb79f42cee34c2a37722a3755fab371c4b
5
+ SHA512:
6
+ metadata.gz: dafdc17bd578221193b2ea2847e4db54827580d9e08c34377a2f7802aae1b1fa0d0c1fac80297f55df2b0bfeee0efdd7862c6e9ca133fdc82874077b3b961337
7
+ data.tar.gz: 7c31053d492a2cc7aae9d197bf52b51d1bfee8efbab38b989ef2e037260d54ef180b415036968eeaead037179a1b368c771149d1ae3e399bba8bf1c9b80e8e0e
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ Gemfile.lock
3
+ *.gem
4
+ log/*.log
5
+ .bundle/
6
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Tom Benner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ 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 BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,48 @@
1
+ Profiler
2
+ ========
3
+ Easy, targeted Ruby profiling
4
+
5
+ Overview
6
+ --------
7
+
8
+ Profiler lets you easily measure the time spent in specified code paths.
9
+
10
+ If a certain code path is taking some time, call `Profiler.start` before it, `Profiler.stop` after it, and `Profiler.check` at significant checkpoints throughout it. Profiler will print information about all of the edges (between the nodes that are specified by `Profiler.check`) that run:
11
+
12
+ ```ruby
13
+ class MyClass
14
+ def initialize
15
+ Profiler.start
16
+ do_something
17
+ Profiler.stop
18
+ end
19
+
20
+ def do_something
21
+ # ...
22
+ Profiler.check
23
+ # ...
24
+ Profiler.check
25
+ # ...
26
+ 100.times do
27
+ Profiler.check
28
+ # ...
29
+ end
30
+ end
31
+ end
32
+ ```
33
+
34
+ ```
35
+ Time Calls Path
36
+ 5.0e-05 1 -> my_class.rb:3:in `initialize'
37
+ 0.00015 1 my_class.rb:3:in `initialize' -> my_class.rb:62:in `do_something'
38
+ 0.0001 1 my_class.rb:62:in `do_something' -> my_class.rb:75:in `do_something'
39
+ 0.01509 1 my_class.rb:75:in `do_something' -> my_class.rb:84:in `block in do_something'
40
+ 0.0126 100 my_class.rb:84:in `block in do_something' -> my_class.rb:84:in `block in do_something'
41
+ 0.00027 1 my_class.rb:84:in `block in do_something' -> my_class.rb:111:in `do_something'
42
+ Total time: 0.028271198272705078
43
+ ```
44
+
45
+ License
46
+ -------
47
+
48
+ Profiler is released under the MIT License. Please see the MIT-LICENSE file for details.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,31 @@
1
+ directory = File.dirname(File.absolute_path(__FILE__))
2
+ Dir.glob("#{directory}/profiler/*.rb") { |file| require file }
3
+ Dir.glob("#{directory}/profiler/printers/*.rb") { |file| require file }
4
+
5
+ module Profiler
6
+ class << self
7
+ attr_accessor :profile, :is_paused
8
+
9
+ def start
10
+ self.profile = Profile.new
11
+ self.is_paused = false
12
+ end
13
+
14
+ def pause
15
+ self.is_paused = true
16
+ end
17
+
18
+ def resume
19
+ self.is_paused = false
20
+ end
21
+
22
+ def check(caller_offset=0)
23
+ return if is_paused
24
+ profile.check(caller_offset)
25
+ end
26
+
27
+ def stop
28
+ profile.stop
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module Profiler
2
+ class AbstractPrinter
3
+ attr_accessor :result
4
+
5
+ def initialize(result)
6
+ self.result = result
7
+ end
8
+
9
+ def print(options={})
10
+ defaults = {
11
+ output: STDOUT
12
+ }
13
+ options = defaults.merge(options)
14
+ print_result(options)
15
+ end
16
+
17
+ private
18
+
19
+ def print_result
20
+ raise NotImplementedError
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ module Profiler
2
+ class FlatPrinter < AbstractPrinter
3
+ private
4
+
5
+ def print_result(options)
6
+ string = result_to_string
7
+ options[:output].puts(string)
8
+ end
9
+
10
+ def result_to_string
11
+ lines = []
12
+ lines << get_header
13
+ lines += get_body
14
+ lines << get_footer
15
+ lines.join("\n")
16
+ end
17
+
18
+ def get_header
19
+ columns_to_line([
20
+ 'Time',
21
+ 'Calls',
22
+ 'Path'
23
+ ])
24
+ end
25
+
26
+ def get_body
27
+ column_lengths = get_column_lengths
28
+ result.paths_durations.map do |path, duration|
29
+ count = result.paths_counts[path]
30
+ columns = [
31
+ duration.round(5),
32
+ count,
33
+ path_to_displayed_path(path)
34
+ ]
35
+ columns_to_line(columns)
36
+ end
37
+ end
38
+
39
+ def get_footer
40
+ ["Total time: #{result.paths_durations.values.reduce(&:+)}"]
41
+ end
42
+
43
+ def columns_to_line(columns)
44
+ columns = columns.map.with_index do |column, index|
45
+ column.to_s.ljust(column_lengths[index])
46
+ end
47
+ columns.join(' ')
48
+ end
49
+
50
+ def column_lengths
51
+ @column_lengths ||= get_column_lengths
52
+ end
53
+
54
+ def get_column_lengths
55
+ [
56
+ 9,
57
+ 5,
58
+ get_longest_path_length + 1
59
+ ]
60
+ end
61
+
62
+ def get_longest_path_length
63
+ result.paths_durations.keys.map { |path| path_to_displayed_path(path).length }.sort.last
64
+ end
65
+
66
+ # For example, convert 'results_html_haml___3582891738431186295_70358606312040' to 'results_html_haml'
67
+ def path_to_displayed_path(path)
68
+ path.gsub(/___[\d_]+/, '')
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ module Profiler
2
+ class Profile
3
+ attr_accessor :previous_line, :previous_time, :paths_counts, :paths_durations
4
+
5
+ def initialize
6
+ self.paths_counts = Hash.new(0)
7
+ self.paths_durations = Hash.new(0)
8
+ self.previous_line = nil
9
+ self.previous_time = Time.now.to_f
10
+ end
11
+
12
+ def check(caller_offset=0)
13
+ now = Time.now.to_f
14
+ line = caller[caller_offset + 1].split("/")[-1]
15
+ path = "#{self.previous_line} -> #{line}"
16
+ self.paths_counts[path] += 1
17
+ self.paths_durations[path] += now - self.previous_time
18
+ self.previous_line = line.freeze
19
+ self.previous_time = now
20
+ end
21
+
22
+ def stop
23
+ result = Result.new(self)
24
+ FlatPrinter.new(result).print
25
+ result
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module Profiler
2
+ class Result
3
+ attr_accessor :paths_counts, :paths_durations
4
+
5
+ def initialize(profile)
6
+ self.paths_counts = profile.paths_counts.dup
7
+ self.paths_durations = profile.paths_durations.dup
8
+ end
9
+
10
+ def paths
11
+ self.paths_counts.keys
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Profiler
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../lib/profiler/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.authors = ['Tom Benner']
5
+ s.email = ['tombenner@gmail.com']
6
+ s.description = s.summary = %q{Easy, targeted Ruby profiling}
7
+ s.homepage = 'https://github.com/tombenner/profiler'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.name = 'profiler'
11
+ s.require_paths = ['lib']
12
+ s.version = Profiler::VERSION
13
+ s.license = 'MIT'
14
+
15
+ s.add_development_dependency 'rspec'
16
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Profiler do
4
+ describe "profiling" do
5
+ before :each do
6
+ expect(STDOUT).to receive(:puts)
7
+ end
8
+
9
+ it "records one path" do
10
+ Profiler.start
11
+ Profiler.check
12
+ result = Profiler.stop
13
+ expect(result.paths.length).to eq(1)
14
+ end
15
+
16
+ it "records two paths" do
17
+ Profiler.start
18
+ Profiler.check
19
+ Profiler.check
20
+ result = Profiler.stop
21
+ expect(result.paths.length).to eq(2)
22
+ end
23
+
24
+ it "records one call" do
25
+ Profiler.start
26
+ Profiler.check
27
+ result = Profiler.stop
28
+ expect(result.paths_counts.values).to eq([1])
29
+ end
30
+
31
+ it "records multiple calls" do
32
+ Profiler.start
33
+ 5.times do
34
+ Profiler.check
35
+ end
36
+ result = Profiler.stop
37
+ expect(result.paths_counts.values).to eq([1, 4])
38
+ end
39
+
40
+ it "records paths" do
41
+ Profiler.start
42
+ Profiler.check
43
+ result = Profiler.stop
44
+ expect(result.paths.length).to eq(1)
45
+ expect(result.paths.first).to start_with(' -> profiler_spec.rb:')
46
+ end
47
+
48
+ it "records durations" do
49
+ Profiler.start
50
+ Profiler.check
51
+ result = Profiler.stop
52
+ expect(result.paths_durations.length).to eq(1)
53
+ expect(result.paths_durations.values.first).to be_between(0, 0.1)
54
+ end
55
+
56
+ it "pauses and resumes" do
57
+ Profiler.start
58
+ Profiler.check
59
+ Profiler.pause
60
+ Profiler.check
61
+ Profiler.resume
62
+ Profiler.check
63
+ result = Profiler.stop
64
+ expect(result.paths.length).to eq(2)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,17 @@
1
+ require 'profiler'
2
+
3
+ RSpec.configure do |config|
4
+ # ## Mock Framework
5
+ #
6
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
7
+ #
8
+ # config.mock_with :mocha
9
+ # config.mock_with :flexmock
10
+ # config.mock_with :rr
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = "random"
17
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: profiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom Benner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Easy, targeted Ruby profiling
28
+ email:
29
+ - tombenner@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - MIT-LICENSE
37
+ - README.md
38
+ - Rakefile
39
+ - lib/profiler.rb
40
+ - lib/profiler/printers/abstract_printer.rb
41
+ - lib/profiler/printers/flat_printer.rb
42
+ - lib/profiler/profile.rb
43
+ - lib/profiler/result.rb
44
+ - lib/profiler/version.rb
45
+ - profiler.gemspec
46
+ - spec/profiler_spec.rb
47
+ - spec/spec_helper.rb
48
+ homepage: https://github.com/tombenner/profiler
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.4.5
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Easy, targeted Ruby profiling
72
+ test_files: []
73
+ has_rdoc: