easy-prof 1.0.0

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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ doc
2
+ rdoc
3
+ .yardoc
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Dmytro Shteflyuk
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.
data/README.rdoc ADDED
@@ -0,0 +1,248 @@
1
+ = easy-prof
2
+
3
+ Simple and easy to use Ruby code profiler, which could be used
4
+ as a Rails plugin.
5
+
6
+ == Installation
7
+
8
+ There are two options when approaching easy-prof installation:
9
+
10
+ * using the gem (recommended)
11
+ * install as a Rails plugin
12
+
13
+ To install as a gem, add this to your environment.rb:
14
+
15
+ config.gem 'easy-prof', :lib => 'easy_prof'
16
+
17
+ And then run the command:
18
+
19
+ sudo rake gems:install
20
+
21
+ To install meta-tags as a Rails plugin use this:
22
+
23
+ script/plugin install git://github.com/kpumuk/easy-prof.git
24
+
25
+ == Description
26
+
27
+ The main idea behind the easy-prof is creating check points and your
28
+ code and measuring time needed to execute code blocks. Here is the
29
+ example of easy-prof output:
30
+
31
+ [home#index] Benchmark results:
32
+ [home#index] debug: Logged in user home page
33
+ [home#index] progress: 0.7002 s [find top videos]
34
+ [home#index] progress: 0.0452 s [build categories list]
35
+ [home#index] progress: 0.0019 s [build tag cloud]
36
+ [home#index] progress: 0.0032 s [find featured videos]
37
+ [home#index] progress: 0.0324 s [find latest videos]
38
+ [home#index] debug: VIEW STARTED
39
+ [home#index] progress: 0.0649 s [top videos render]
40
+ [home#index] progress: 0.0014 s [categories render]
41
+ [home#index] progress: 2.5887 s [tag cloud render]
42
+ [home#index] progress: 0.0488 s [latest videos render]
43
+ [home#index] progress: 0.1053 s [featured video render]
44
+ [home#index] results: 3.592 s
45
+
46
+ From this output you can see what checkpoints takes longer to reach,
47
+ and what code fragments are pretty fast.
48
+
49
+ == Usage
50
+
51
+ The library extends <tt>Kernel</tt> with a method <tt>easy_profiler</tt>.
52
+ By default profiling is disabled globally, so you should pass :enabled
53
+ parameter to enable profiling of particular code. Also there is a time
54
+ :limit option which could be used to skip logging of blocks which are
55
+ fast enough.
56
+
57
+ For more details see the options description below.
58
+
59
+ easy_profiler('sleep', :enabled => true) do |p|
60
+ sleep 1
61
+ p.progress('sleep 1')
62
+ p.debug('checkpoint reached')
63
+ sleep 2
64
+ p.progress('sleep 2')
65
+ end
66
+
67
+ Method accepts two parameters: profiling session name and a hash of
68
+ options:
69
+
70
+ * <tt>:enabled</tt> -- value indicating whether profiling is enabled.
71
+ * <tt>:limit</tt> -- minimum time period which should be reached to
72
+ print profiling log.
73
+ * <tt>:count_ar_instances</tt> -- indicating whether profiler should
74
+ log an approximate number of instantiated ActiveRecord objects.
75
+ * <tt>:count_memory_usage</tt> -- indicating whether profiler should
76
+ log an approximate amount of memory used.
77
+ * <tt>:logger</tt> -- a +Logger+ instance.
78
+
79
+ == Configuration
80
+
81
+ There are some global configuration options exists:
82
+
83
+ EasyProfiler.configure do |config|
84
+ config.enable_profiling = false
85
+ config.print_limit = 0.01
86
+ config.count_ar_instances = false
87
+ config.count_memory_usage = false
88
+ config.logger = nil # or Rails.logger or whatever
89
+ config.colorize_logging = true
90
+ config.live_logging = false
91
+ end
92
+
93
+ * <tt>enable_profiling</tt> -- used to enable or disable profiling
94
+ globalle (<tt>false</tt> by default).
95
+ * <tt>print_limit</tt> -- used to set a minimum time period in seconds
96
+ which should be reached to dump profile to the log (<tt>0.01</tt>
97
+ by default).
98
+ * <tt>count_ar_instances</tt> -- indicating whether profiler should
99
+ log an approximate number of instantiated ActiveRecord objects.
100
+ * <tt>count_memory_usage</tt> -- indicating whether profiler should
101
+ log an approximate amount of memory used.
102
+ * <tt>logger</tt> -- a <tt>Logger</tt> instance to dump logs to.
103
+ * <tt>colorize_logging</tt> -- when <tt>true</tt>, output will be
104
+ colorized (useful when dumping profiling information into the
105
+ Rails log).
106
+ * <tt>live_logging</tt> -- when <tt>true</tt>, every profiling info
107
+ will be pushed to the log immediately (by default everything will
108
+ be dumped in the end of profiling session).
109
+
110
+ == Active Record instances number profiling
111
+
112
+ easy-prof can log a number of instantiated ActiveRecord instances.
113
+ To enable this kind of profiling, use a <tt>:count_ar_instances</tt>
114
+ option or global setting with the same name.
115
+
116
+ Please note, that easy-prof completely disables garbage collector
117
+ during this kind of profiling. It could hurt your overall application
118
+ performance, so do not use it on production boxes. Also I can't
119
+ guaranty 100% precision, but it is about this value in almost all
120
+ cases.
121
+
122
+ Further reading:
123
+ * That’s Not a Memory Leak, It’s Bloat http://www.engineyard.com/blog/2009/thats-not-a-memory-leak-its-bloat/
124
+
125
+ == Memory usage profiling
126
+
127
+ The plugin is able to log an amount of memory used by current Ruby
128
+ process. To enable this kind of profiling, use a <tt>:count_memory_usage</tt>
129
+ option or global setting with the same name.
130
+
131
+ Please note, that easy-prof completely disables garbage collector
132
+ during this kind of profiling. It could hurt your overall application
133
+ performance, so do not use it on production boxes. Also I can't
134
+ guaranty 100% precision, but it is about this value in almost all
135
+ cases.
136
+
137
+ == Dumping results to the Firebug console
138
+
139
+ If you are profiling a Ruby on Rails application, it could be useful
140
+ to get profiling results from production server sometimes. To achieve
141
+ this you can use a <tt>FirebugLogger</tt>, bundled with this plugin.
142
+ In any controller you have a helper called <tt>firebug_logger</tt>,
143
+ so you can pass it to EasyProfiler using <tt>:logger</tt> option:
144
+
145
+ easy_profiler('home#index', :logger => firebug_logger, :limit => 2) do |p|
146
+ end
147
+
148
+ The idea behind this logger is pretty simple (as everything in this
149
+ plugin): there is an <tt>after_filter</tt> named <tt>dump_firebug_profile</tt>,
150
+ which dumps profiling information after your action finished its work.
151
+ Please note: it will not output any line when profiling session is
152
+ disabled or time limit is not reached.
153
+
154
+ Do not forget to protect firebug output: it is a bad idea to allow
155
+ anyone to see your profiling session dump. You can allow admin
156
+ users only to use firebug, or restrict this feature by IP address.
157
+
158
+ BTW, you can use Firebug Console Lite (http://getfirebug.com/lite.html)
159
+ to get this feature working in any browser! By default it works
160
+ perfectly in Firefox with Firebug installed, and in Safari 4.
161
+
162
+ == Ruby on Rails application profiling
163
+
164
+ Here is the complete example of a Rails action profiling:
165
+
166
+ class HomeController < ApplicationController
167
+ def index
168
+ easy_profiler('home#index', :enabled => profile_request?, :limit => 2) do |p|
169
+ p.progress 'logged in user home page'
170
+
171
+ @top_videos = Video.top(:limit => 10)
172
+ p.progress 'find top videos'
173
+
174
+ @categories = Category.all(:order => 'name DESC')
175
+ p.progress 'build categories list'
176
+
177
+ @tag_cloud = Tag.tag_cloud(:limit => 200)
178
+ p.progress 'build tag cloud'
179
+
180
+ @featured_videos = Video.featured(limit => 5)
181
+ p.progress 'find featured videos'
182
+
183
+ @latest_videos = Video.latest(:limit => 5)
184
+ p.progress 'find latest videos'
185
+
186
+ @profiler = p
187
+ p.debug 'VIEW STARTED'
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ # Method returns +true+ if current request should ouput profiling information
194
+ def profile_request?
195
+ params['_with_profiling'] == 'yes'
196
+ end
197
+ end
198
+
199
+ And view:
200
+
201
+ <div id="top_videos">
202
+ <%= render :partial => 'top_videos' %>
203
+ <% @profiler.progress 'top videos render' %>
204
+ </div>
205
+
206
+ <div class="tabs">
207
+ <ul id="taxonomy">
208
+ <li><a href="#" id="categories" class="current">Categories</a></li>
209
+ <li><a href="#" id="tags">Tags</a></li>
210
+ </ul>
211
+ <div class="categories_panel">
212
+ <%= render :partial => 'categories' %>
213
+ <% @profiler.progress 'categories render' %>
214
+ </div>
215
+ <div class="categories_panel hidden">
216
+ <%= render :partial => 'tag_cloud' %>
217
+ <% @profiler.progress 'tag cloud render' %>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="box">
222
+ <div id="latest">
223
+ <%= render :partial => 'videos', :videos => @latest_videos %>
224
+ <% @profiler.progress 'latest videos render' %>
225
+ </div>
226
+ <div id="featured">
227
+ <%= render :partial => 'videos', :videos => @featured_videos %>
228
+ <% @profiler.progress 'featured video render' %>
229
+ </div>
230
+ </div>
231
+
232
+ As you can see from this example, profiler will be enabled only when
233
+ you pass a _with_profiling parameter with value yes:
234
+
235
+ http://example.com/home?_with_profiling=yes
236
+
237
+ == Who are the authors?
238
+
239
+ This plugin has been created in Scribd.com for our internal use
240
+ and then the sources were opened for other people to use. All the
241
+ code in this package has been developed by Dmytro Shteflyuk for
242
+ Scribd.com and is released under the MIT license. For more details,
243
+ see the MIT-LICENSE file.
244
+
245
+ == Credits
246
+
247
+ * Dmytro Shteflyuk (author) <kpumuk@kpumuk.info> http://kpumuk.info
248
+ * Alexey Kovyrin (contributor) <alexey@kovyrin.net> http://kovyrin.net
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = 'easy-prof'
7
+ gemspec.summary = 'Simple and easy to use Ruby code profiler'
8
+ gemspec.description = 'Simple Ruby code profiler to use both in Rails applications and generic Ruby scripts.'
9
+ gemspec.email = 'kpumuk@kpumuk.info'
10
+ gemspec.homepage = 'http://github.com/kpumuk/easy-prof'
11
+ gemspec.authors = ['Dmytro Shteflyuk']
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts 'Jeweler not available. Install it with: sudo gem install jeweler'
16
+ end
17
+
18
+ begin
19
+ require 'spec/rake/spectask'
20
+
21
+ desc 'Default: run unit tests.'
22
+ task :default => :spec
23
+
24
+ desc 'Test the easy-prof plugin.'
25
+ Spec::Rake::SpecTask.new(:spec) do |t|
26
+ t.libs << 'lib'
27
+ t.pattern = 'spec/**/*_spec.rb'
28
+ t.verbose = true
29
+ t.spec_opts = ['-cfs']
30
+ end
31
+ rescue LoadError
32
+ puts 'RSpec not available. Install it with: sudo gem install rspec'
33
+ end
34
+
35
+ begin
36
+ require 'yard'
37
+ YARD::Rake::YardocTask.new(:yard) do |t|
38
+ t.options = ['--title', 'EasyProf Documentation']
39
+ if ENV['PRIVATE']
40
+ t.options.concat ['--protected', '--private']
41
+ else
42
+ t.options.concat ['--protected', '--no-private']
43
+ end
44
+ end
45
+ rescue LoadError
46
+ puts 'Yard not available. Install it with: sudo gem install yard'
47
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :patch: 0
3
+ :build:
4
+ :major: 1
5
+ :minor: 0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'easy_prof'
data/lib/easy_prof.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'logger'
2
+
3
+ module EasyProfiler
4
+ autoload :Configuration, 'easy_profiler/configuration'
5
+ autoload :Profile, 'easy_profiler/profile'
6
+ autoload :ProfileInstanceBase, 'easy_profiler/profile_instance_base'
7
+ autoload :ProfileInstance, 'easy_profiler/profile_instance'
8
+ autoload :NoProfileInstance, 'easy_profiler/no_profile_instance'
9
+ autoload :FirebugLogger, 'easy_profiler/firebug_logger'
10
+ autoload :ActionControllerExtensions, 'easy_profiler/action_controller_extensions'
11
+
12
+ module ClassMethods
13
+ def configure(force = false)
14
+ yield configuration(force)
15
+ end
16
+
17
+ def configuration(force = false)
18
+ if !@configuration || force
19
+ @configuration = Configuration.new
20
+ end
21
+ @configuration
22
+ end
23
+ alias :config :configuration
24
+ end
25
+ extend ClassMethods
26
+ end
27
+
28
+ if Object.const_defined?(:ActionController)
29
+ ActionController::Base.send(:include, EasyProfiler::ActionControllerExtensions)
30
+ end
31
+
32
+ module Kernel
33
+ # Wraps code block into the profiling session.
34
+ #
35
+ # See the <tt>EasyProfiler::Profile.start</tt> method for
36
+ # parameters description.
37
+ #
38
+ # Example:
39
+ # easy_profiler('sleep', :enabled => true) do |p|
40
+ # sleep 1
41
+ # p.progress('sleep 1')
42
+ # p.debug('checkpoint reached')
43
+ # sleep 2
44
+ # p.progress('sleep 2')
45
+ # end
46
+ def easy_profiler(name, options = {})
47
+ profiler = EasyProfiler::Profile.start(name, options)
48
+ yield profiler
49
+ ensure
50
+ EasyProfiler::Profile.stop(name) if profiler
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ module EasyProfiler
2
+ module ActionControllerExtensions
3
+ def self.included(base) #:nodoc:
4
+ base.send :include, InstanceMethods
5
+
6
+ base.class_eval do
7
+ after_filter :dump_firebug_profile
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+ # Exposes firebug variable where logs can be submitted.
13
+ #
14
+ # class UserController < ApplicationController
15
+ # def index
16
+ # firebug.debug 'Why I can be easily debugging with this thing!'
17
+ # end
18
+ # end
19
+ def firebug_logger
20
+ @_firebug_logger ||= FirebugLogger.new
21
+ end
22
+
23
+ def dump_firebug_profile
24
+ return if firebug_logger.logs.empty?
25
+
26
+ logs = firebug_logger.logs.collect do |message|
27
+ # We have to add any escape characters
28
+ "console.info('#{self.class.helpers.escape_javascript(message)}');"
29
+ end.join("\n")
30
+
31
+ response.body << self.class.helpers.javascript_tag(logs)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,120 @@
1
+ module EasyProfiler
2
+ class Configuration
3
+ # Value indicating whether profiling is globally enabled.
4
+ attr_reader :enable_profiling
5
+
6
+ # Minimum time period which should be reached to dump
7
+ # profile to the log.
8
+ attr_reader :print_limit
9
+
10
+ # Value indicating whether profiler should log an
11
+ # approximate number of instantiated ActiveRecord objects.
12
+ attr_reader :count_ar_instances
13
+
14
+ # Value indicating whether profiler should log an
15
+ # approximate amount of memory used.
16
+ attr_reader :count_memory_usage
17
+
18
+ # Accepts a logger conforming to the interface of Log4r or the
19
+ # default Ruby 1.8+ Logger class, which is then passed
20
+ # on to any profiler instance made.
21
+ attr_writer :logger
22
+
23
+ attr_reader :colorize_logging
24
+
25
+ # Value indicating whether profiler should flush
26
+ # logs on every checkpoint.
27
+ attr_reader :live_logging
28
+
29
+ def initialize
30
+ @enable_profiling = false
31
+ @print_limit = 0.01
32
+ @count_ar_instances = false
33
+ @count_memory_usage = false
34
+ @logger = nil
35
+ @colorize_logging = true
36
+ @live_logging = false
37
+ end
38
+
39
+ # Sets a value indicating whether profiling is globally enabled.
40
+ def enable_profiling=(value)
41
+ @enable_profiling = !!value
42
+ end
43
+
44
+ # Sets a minimum time period which should be reached to dump
45
+ # profile to the log.
46
+ def print_limit=(value)
47
+ @print_limit = FalseClass === value ? false : value.to_f
48
+ end
49
+
50
+ # Sets a value indicating whether profiler should log an
51
+ # approximate number of instantiated ActiveRecord objects.
52
+ def count_ar_instances=(value)
53
+ @count_ar_instances = !!value
54
+ end
55
+
56
+ # Sets a value indicating whether profiler should log an
57
+ # approximate amount of memory used.
58
+ #
59
+ # @param Boolean value
60
+ # identifies whether memory profiling should be enabled.
61
+ #
62
+ def count_memory_usage=(value)
63
+ @count_memory_usage = !!value
64
+ end
65
+
66
+ def colorize_logging=(value)
67
+ @colorize_logging = !!value
68
+ end
69
+
70
+ # Sets a value indicating whether profiler should flush
71
+ # logs on every checkpoint.
72
+ #
73
+ def live_logging=(value)
74
+ @live_logging = !!value
75
+ end
76
+
77
+ # Gets a logger instance.
78
+ #
79
+ # When profiler is started inside Rails application,
80
+ # creates a "log/profile.log" files where all profile
81
+ # logs will be places. In regular scripts dumps
82
+ # information directly to STDOUT. You can use
83
+ # <tt>EasyProfiler.configuration.logger</tt> to set another
84
+ # logger.
85
+ #
86
+ def logger
87
+ unless @logger
88
+ @logger = if Object.const_defined?(:Rails)
89
+ Logger.new(File.join(Rails.root, 'log', 'profile.log'))
90
+ else
91
+ Logger.new($stdout)
92
+ end
93
+ end
94
+ @logger
95
+ end
96
+
97
+ def merge(options = {})
98
+ config = self.dup
99
+ config.enable_profiling = options[:enabled] if options.has_key?(:enabled)
100
+ config.enable_profiling = options[:enable_profiling] if options.has_key?(:enable_profiling)
101
+ config.print_limit = options[:limit] if options.has_key?(:limit)
102
+ config.print_limit = options[:print_limit] if options.has_key?(:print_limit)
103
+ config.count_ar_instances = options[:count_ar_instances] if options.has_key?(:count_ar_instances)
104
+ config.count_memory_usage = options[:count_memory_usage] if options.has_key?(:count_memory_usage)
105
+ config.logger = options[:logger] if options.has_key?(:logger)
106
+ config.colorize_logging = options[:colorize_logging] if options.has_key?(:colorize_logging)
107
+ config.live_logging = options[:live_logging] if options.has_key?(:live_logging)
108
+
109
+ config
110
+ end
111
+
112
+ def disable_gc?
113
+ count_ar_instances or count_memory_usage
114
+ end
115
+
116
+ def enabled?
117
+ enable_profiling
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,27 @@
1
+ module EasyProfiler
2
+ # A logger used to output logs to the Firebug console.
3
+ #
4
+ # Based on http://rails.aizatto.com/category/plugins/firebug-logger/
5
+ class FirebugLogger
6
+ attr_accessor :logs
7
+
8
+ def initialize #:nodoc:
9
+ @logs = []
10
+ end
11
+
12
+ # Clear the logs.
13
+ def clear
14
+ @logs = []
15
+ end
16
+
17
+ # Adds a line to the log.
18
+ #
19
+ # == arguments
20
+ # * +message+ -- log message to be logged
21
+ # * +block+ -- optional. Return value of the block will be the message that will be logged.
22
+ def info(message = nil)
23
+ message = yield if message.nil? && block_given?
24
+ @logs << message
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module EasyProfiler
2
+ # Class used when profiling is disabled. Does nothing.
3
+ class NoProfileInstance < ProfileInstanceBase
4
+ end
5
+ end
@@ -0,0 +1,59 @@
1
+ module EasyProfiler
2
+ # Contains global profiling parameters and methods to start and
3
+ # stop profiling.
4
+ class Profile
5
+ @@profile_results = {}
6
+
7
+ # Starts a profiling session.
8
+ #
9
+ # Parameters:
10
+ # * +name+ -- session name.
11
+ # * +options+ -- a +Hash+ of options.
12
+ #
13
+ # Possible options:
14
+ # * <tt>:enabled</tt> -- value indicating whether profiling is enabled.
15
+ # * <tt>:limit</tt> -- minimum time period which should be reached to print profiling log.
16
+ # * <tt>:count_ar_instances</tt> —- indicating whether profiler should log an approximate number of instantiated ActiveRecord objects.
17
+ # * <tt>:count_memory_usage</tt> —- indicating whether profiler should log an approximate amount of memory used.
18
+ # * <tt>:logger</tt> -- a +Logger+ instance.
19
+ # * <tt>:colorize_logging</tt> -- indicating whether profiling log lines should be colorized.
20
+ # * <tt>:live_logging</tt> -- indicating whether profiler should flush logs on every checkpoint.
21
+ #
22
+ # Returns:
23
+ # * an instance of profiler (descendant of the <tt>EasyProfiler::ProfileInstanceBase</tt> class).
24
+ def self.start(name, options = {})
25
+ if @@profile_results[name]
26
+ raise ArgumentError.new("EasyProfiler::Profile.start() collision! '#{name}' is already started.")
27
+ end
28
+
29
+ config = EasyProfiler.configuration.merge(options)
30
+
31
+ # Disable garbage collector to get more precise results
32
+ GC.disable if config.disable_gc?
33
+
34
+ klass = config.enabled? ? ProfileInstance : NoProfileInstance
35
+ instance = klass.new(name, config)
36
+
37
+ @@profile_results[name] = instance
38
+ end
39
+
40
+ # Finishes a profiling session and dumps results to the log.
41
+ #
42
+ # Parameters:
43
+ # * +name+ -- session name, used in +start+ method.
44
+ def self.stop(name)
45
+ unless instance = @@profile_results.delete(name)
46
+ raise ArgumentError.new("EasyProfiler::Profile.stop() error! '#{name}' is not started yet.")
47
+ end
48
+
49
+ instance.dump_results
50
+
51
+ # Enable garbage collector which has been disabled before
52
+ GC.enable if instance.config.disable_gc?
53
+ end
54
+
55
+ def self.reset!
56
+ @@profile_results = {}
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,111 @@
1
+ module EasyProfiler
2
+ # Class used when profiling is disabled.
3
+ class ProfileInstance < ProfileInstanceBase
4
+ # Sets a profiling checkpoint (block execution time will be printed).
5
+ #
6
+ # Parameters:
7
+ # * message -- a message to associate with a check point.
8
+ def progress(message)
9
+ progress = (now = Time.now.to_f) - @progress
10
+ @progress = now
11
+
12
+ ar_instances_count = if config.count_ar_instances
13
+ ar_instances_delta = (ar_instances = active_record_instances_count) - @current_ar_instances
14
+ @current_ar_instances = ar_instances
15
+ ", #{ar_instances_delta} AR objects"
16
+ end
17
+
18
+ memory_usage_value = if config.count_memory_usage
19
+ memory_usage_delta = (memory_usage = process_memory_usage) - @current_memory_usage
20
+ @current_memory_usage = memory_usage
21
+ ", #{format_memory_size(total_memory_usage)}"
22
+ end
23
+
24
+ buffer_checkpoint("progress: %0.4f s#{ar_instances_count}#{memory_usage_value} [#{message}]" % progress)
25
+ end
26
+
27
+ # Sets a profiling checkpoint without execution time printing.
28
+ #
29
+ # Parameters:
30
+ # * message -- a message to associate with a check point.
31
+ def debug(message)
32
+ @progress = Time.now.to_f
33
+ buffer_checkpoint("debug: #{message}")
34
+ end
35
+
36
+ # Dumps results to the log.
37
+ def dump_results
38
+ progress('END')
39
+ t = total
40
+
41
+ if config.live_logging || false === config.print_limit || t > config.print_limit.to_f
42
+ log_header(true)
43
+ @buffer.each { |message| log_line(message) }
44
+ log_footer(t)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Gets a total profiling time.
51
+ def total
52
+ Time.now.to_f - @start
53
+ end
54
+
55
+ # Gets a number of total AR objects instantiated number.
56
+ def total_ar_instances
57
+ active_record_instances_count - @start_ar_instances
58
+ end
59
+
60
+ # Gets a total amount of memory used.
61
+ def total_memory_usage
62
+ process_memory_usage - @start_memory_usage
63
+ end
64
+
65
+ # Buffers a profiling checkpoint.
66
+ def buffer_checkpoint(message)
67
+ log_header
68
+ if config.live_logging
69
+ log_line(message)
70
+ else
71
+ @buffer << message
72
+ end
73
+ end
74
+
75
+ # Write a header to the log.
76
+ def log_header(force = false)
77
+ if (config.live_logging && !@header_printed) || (!config.live_logging && force)
78
+ log_line("Benchmark results:")
79
+ @header_printed = true
80
+ end
81
+ end
82
+
83
+ # Write a footer with summary stats to the log.
84
+ def log_footer(total_time)
85
+ ar_instances_count = if config.count_ar_instances
86
+ ", #{total_ar_instances} AR objects"
87
+ end
88
+
89
+ memory_usage_value = if config.count_memory_usage
90
+ ", #{format_memory_size(total_memory_usage)}"
91
+ end
92
+
93
+ log_line("results: %0.4f s#{ar_instances_count}#{memory_usage_value}" % total_time)
94
+ end
95
+
96
+ # Write a log line.
97
+ def log_line(line)
98
+ if config.colorize_logging
99
+ @@row_even, message_color = if @@row_even
100
+ [false, '4;32;1']
101
+ else
102
+ [true, '4;33;1']
103
+ end
104
+
105
+ config.logger.info(" [\e[#{message_color}m%s\e[0m] %s" % [@name, line])
106
+ else
107
+ config.logger.info("[%s] %s" % [@name, line])
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,93 @@
1
+ module EasyProfiler
2
+ # Base class for profilers.
3
+ class ProfileInstanceBase
4
+ attr_reader :name, :config
5
+
6
+ @@row_even = true
7
+
8
+ # Initializes a new instance of +ProfileInstanceBase+ class.
9
+ #
10
+ # Parameters:
11
+ # * name -- session name.
12
+ # * options -- a +Hash+ of options (see <tt>EasyProfiler::Profile.start</tt> for details).
13
+ def initialize(name, config = nil)
14
+ @name = name
15
+ @config = case config
16
+ when Hash: EasyProfiler.configuration.merge(config)
17
+ when EasyProfiler::Configuration: config
18
+ else EasyProfiler.configuration
19
+ end
20
+
21
+ @start = @progress = Time.now.to_f
22
+
23
+ # Initial number of ActiveRecord::Base objects
24
+ if @config.count_ar_instances
25
+ @start_ar_instances = @current_ar_instances = active_record_instances_count
26
+ end
27
+
28
+ # Initial amount of memory used
29
+ if @config.count_memory_usage
30
+ @start_memory_usage = @current_memory_usage = process_memory_usage
31
+ end
32
+
33
+ # A buffer where all log messeges will be stored till the
34
+ # end of the profiling block. We need this because not every
35
+ # profiling log will be printed (see EasyProf::Configuration.print_limit).
36
+ @buffer = []
37
+ end
38
+
39
+ # Sets a profiling checkpoint (block execution time will be printed).
40
+ #
41
+ # Parameters:
42
+ # * message -- a message to associate with a check point.
43
+ #
44
+ def progress(message)
45
+ end
46
+
47
+ # Sets a profiling checkpoint without execution time printing.
48
+ #
49
+ # Parameters:
50
+ # * message -- a message to associate with a check point.
51
+ #
52
+ def debug(message)
53
+ end
54
+
55
+ # Dumps results to the log.
56
+ def dump_results
57
+ end
58
+
59
+ protected
60
+
61
+ # Returns a number of ActiveRecord instances in the Object Space.
62
+ #
63
+ def active_record_instances_count
64
+ count = 0
65
+ ObjectSpace.each_object(::ActiveRecord::Base) { count += 1 }
66
+ count
67
+ end
68
+
69
+ # Returns an amount of memory used by current Ruby process.
70
+ #
71
+ def process_memory_usage
72
+ `ps -o rss= -p #{$$}`.to_i
73
+ end
74
+
75
+ # Formats an amount of memory to print.
76
+ #
77
+ def format_memory_size(number)
78
+ if number > 10 ** 9
79
+ number = number.to_f / (10 ** 9)
80
+ suffix = 'G'
81
+ elsif number > 10 ** 6
82
+ number = number.to_f / (10 ** 6)
83
+ suffix = 'M'
84
+ elsif number > 10 ** 3
85
+ number = number.to_f / (10 ** 3)
86
+ suffix = 'K'
87
+ else
88
+ suffix = 'B'
89
+ end
90
+ "%.2f#{suffix}" % number
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'easy_profiler' do
4
+ it 'should pass block results outside' do
5
+ easy_profiler('test') { 1 }.should == 1
6
+ easy_profiler('test') { 'xxx' }.should == 'xxx'
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EasyProfiler::NoProfileInstance do
4
+ it 'should receive name and options in initialize' do
5
+ profiler = EasyProfiler::NoProfileInstance.new('myprofiler1')
6
+ profiler.name.should == 'myprofiler1'
7
+ profiler.config.should be(EasyProfiler.configuration)
8
+
9
+ profiler = EasyProfiler::NoProfileInstance.new('myprofiler2', { :print_limit => 100 })
10
+ profiler.name.should == 'myprofiler2'
11
+ profiler.config.print_limit.should == 100
12
+ end
13
+
14
+ it 'should respond to :progress' do
15
+ profiler = EasyProfiler::NoProfileInstance.new('myprofiler')
16
+ profiler.should respond_to(:progress)
17
+ lambda {
18
+ profiler.progress('message')
19
+ }.should_not raise_error
20
+ end
21
+
22
+ it 'should respond to :debug' do
23
+ profiler = EasyProfiler::NoProfileInstance.new('myprofiler')
24
+ profiler.should respond_to(:debug)
25
+ lambda {
26
+ profiler.debug('message')
27
+ }.should_not raise_error
28
+ end
29
+
30
+ it 'should respond to :dump_results' do
31
+ profiler = EasyProfiler::NoProfileInstance.new('myprofiler')
32
+ profiler.should respond_to(:dump_results)
33
+ lambda {
34
+ profiler.dump_results
35
+ }.should_not raise_error
36
+ end
37
+ end
@@ -0,0 +1,113 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EasyProfiler::ProfileInstance do
4
+ it 'should receive name and options in initialize' do
5
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler1')
6
+ profiler.name.should == 'myprofiler1'
7
+ profiler.config.should be(EasyProfiler.configuration)
8
+
9
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler2', { :print_limit => 100 })
10
+ profiler.name.should == 'myprofiler2'
11
+ profiler.config.print_limit.should == 100
12
+
13
+ config = EasyProfiler::Configuration.new
14
+ config.print_limit = 100
15
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler3', config)
16
+ profiler.name.should == 'myprofiler3'
17
+ profiler.config.should be(config)
18
+ end
19
+
20
+ it 'should respond to :progress' do
21
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler')
22
+ profiler.should respond_to(:progress)
23
+ lambda {
24
+ profiler.progress('message')
25
+ buffer = profiler.instance_variable_get(:@buffer)
26
+ buffer.should have(1).item
27
+ buffer.first.should match(/progress: \d+\.\d+ s \[message\]/)
28
+ }.should_not raise_error
29
+ end
30
+
31
+ it 'should respond to :debug' do
32
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler')
33
+ profiler.should respond_to(:debug)
34
+ lambda {
35
+ profiler.debug('message')
36
+ buffer = profiler.instance_variable_get(:@buffer)
37
+ buffer.should have(1).item
38
+ buffer.first.should match(/debug: message/)
39
+ }.should_not raise_error
40
+ end
41
+
42
+ it 'should respond to :dump_results' do
43
+ logger = mock('MockLogger')
44
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler', :logger => logger, :enabled => true, :limit => false, :colorize_logging => false)
45
+ profiler.should respond_to(:dump_results)
46
+
47
+ profiler.progress('progress message')
48
+ profiler.debug('debug message')
49
+
50
+ logger.should_receive(:info).ordered.with(/\[myprofiler\] Benchmark results:/)
51
+ logger.should_receive(:info).ordered.with(/\[myprofiler\] progress: \d+\.\d+ s \[progress message\]/)
52
+ logger.should_receive(:info).ordered.with(/\[myprofiler\] debug: debug message/)
53
+ logger.should_receive(:info).ordered.with(/\[myprofiler\] progress: \d+\.\d+ s \[END\]/)
54
+ logger.should_receive(:info).ordered.with(/\[myprofiler\] results: \d+\.\d+ s/)
55
+
56
+ profiler.dump_results
57
+ end
58
+
59
+ it 'should render nothing when time limit not reached' do
60
+ logger = mock('MockLogger')
61
+ profiler = EasyProfiler::ProfileInstance.new('myprofiler', :logger => logger, :enabled => true, :limit => 20)
62
+ logger.should_not_receive(:info)
63
+ profiler.dump_results
64
+ end
65
+
66
+ context 'when live logging is enabled' do
67
+ before :each do
68
+ @logger = mock('MockLogger').as_null_object
69
+ @profiler = EasyProfiler::ProfileInstance.new('myprofiler', :logger => @logger, :enabled => true, :live_logging => true, :colorize_logging => false)
70
+ end
71
+
72
+ it 'should print header when progress called first time' do
73
+ @logger.should_receive(:info).with(/\[myprofiler\] Benchmark results:/)
74
+ @profiler.progress('progress message')
75
+ end
76
+
77
+ it 'should print header when debug called first time' do
78
+ @logger.should_receive(:info).with(/\[myprofiler\] Benchmark results:/)
79
+ @profiler.debug('progress message')
80
+ end
81
+
82
+ it 'should not print header in dump_results' do
83
+ @profiler.debug('progress message')
84
+ @logger.should_not_receive(:info).with(/\[myprofiler\] Benchmark results:/)
85
+ @profiler.dump_results
86
+ end
87
+
88
+ it 'should print foter in dump_results' do
89
+ @profiler.debug('progress message')
90
+ @logger.should_receive(:info).ordered.with(/\[myprofiler\] results: \d+\.\d+ s/)
91
+ @profiler.dump_results
92
+ end
93
+
94
+ it 'should flush logs on every checkpoing if live logging is enabled' do
95
+ @logger.should_receive(:info).with(/\[myprofiler\] Benchmark results:/)
96
+ @logger.should_receive(:info).ordered.with(/\[myprofiler\] progress: \d+\.\d+ s \[progress message\]/)
97
+ @logger.should_receive(:info).ordered.with(/\[myprofiler\] debug: debug message/)
98
+ @profiler.progress('progress message')
99
+ @profiler.debug('debug message')
100
+ end
101
+
102
+ it 'should print footer in dump_results' do
103
+ @logger.should_receive(:info).with(/\[myprofiler\] results: \d+\.\d+ s/)
104
+ @profiler.dump_results
105
+ end
106
+
107
+ it 'should print header in dump_results if did not already' do
108
+ @logger.should_receive(:info).with(/\[myprofiler\] Benchmark results:/)
109
+ @logger.should_receive(:info).with(/\[myprofiler\] results: \d+\.\d+ s/)
110
+ @profiler.dump_results
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,113 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EasyProfiler::Profile do
4
+ after :each do
5
+ EasyProfiler.configure do |config|
6
+ config.enable_profiling = false
7
+ config.print_limit = 0.01
8
+ config.count_ar_instances = false
9
+ config.count_memory_usage = false
10
+ end
11
+ EasyProfiler::Profile.reset!
12
+ end
13
+
14
+ context '.start' do
15
+ it 'should pass name to profile instance' do
16
+ EasyProfiler::Profile.start('myprofiler').name.should == 'myprofiler'
17
+ end
18
+
19
+ it 'should pass options to profile instance' do
20
+ options = { :print_limit => 100 }
21
+ EasyProfiler::Profile.start('myprofiler', options).config.print_limit.should == 100
22
+ end
23
+
24
+ it 'should create a ProfileInstance object when enabled' do
25
+ options = { :enabled => true }
26
+ EasyProfiler::Profile.start('myprofiler', options).class.should == EasyProfiler::ProfileInstance
27
+ end
28
+
29
+ it 'should create a NoProfileInstance object when disabled' do
30
+ options = { :enabled => false }
31
+ EasyProfiler::Profile.start('myprofiler', options).class.should == EasyProfiler::NoProfileInstance
32
+ end
33
+
34
+ it 'should raise an error when two profilers with the same name started' do
35
+ EasyProfiler::Profile.start('myprofiler')
36
+ lambda {
37
+ EasyProfiler::Profile.start('myprofiler')
38
+ }.should raise_error(ArgumentError)
39
+ end
40
+
41
+ it 'should use global :enabled value' do
42
+ EasyProfiler::Profile.start('myprofiler1').config.enable_profiling.should be_false
43
+ EasyProfiler.config.enable_profiling = true
44
+ EasyProfiler::Profile.start('myprofiler2').config.enable_profiling.should be_true
45
+ end
46
+
47
+ it 'should use global :limit value' do
48
+ EasyProfiler::Profile.start('myprofiler1').config.print_limit.should == 0.01
49
+ EasyProfiler.config.print_limit = 10
50
+ EasyProfiler::Profile.start('myprofiler2').config.print_limit.should == 10.0
51
+ end
52
+
53
+ it 'should use global :count_ar_instances value' do
54
+ EasyProfiler::Profile.start('myprofiler1').config.count_ar_instances.should be_false
55
+ EasyProfiler.config.count_ar_instances = true
56
+ EasyProfiler::Profile.start('myprofiler2').config.count_ar_instances.should be_true
57
+ end
58
+
59
+ it 'should use global :count_memory_usage value' do
60
+ EasyProfiler::Profile.start('myprofiler1').config.count_memory_usage.should be_false
61
+ EasyProfiler.config.count_memory_usage = true
62
+ EasyProfiler::Profile.start('myprofiler2').config.count_memory_usage.should be_true
63
+ end
64
+
65
+ it 'should use global :logger value' do
66
+ logger = mock('MockLogger')
67
+ EasyProfiler.config.logger = logger
68
+ EasyProfiler::Profile.start('myprofiler2').config.logger.should be(logger)
69
+ end
70
+
71
+ it 'should use global :live_logging value' do
72
+ EasyProfiler::Profile.start('myprofiler1').config.live_logging.should be_false
73
+ EasyProfiler.config.live_logging = true
74
+ EasyProfiler::Profile.start('myprofiler2').config.live_logging.should be_true
75
+ end
76
+
77
+ it 'should disable garbage collector when needed' do
78
+ options = { :enabled => true, :count_ar_instances => true }
79
+ GC.should_receive(:disable)
80
+ EasyProfiler::Profile.start('myprofiler1', options)
81
+
82
+ options = { :enabled => true, :count_memory_usage => true }
83
+ GC.should_receive(:disable)
84
+ EasyProfiler::Profile.start('myprofiler2', options)
85
+ end
86
+ end
87
+
88
+ context '.stop' do
89
+ it 'should raise an error when profiler is not started' do
90
+ lambda {
91
+ EasyProfiler::Profile.stop('myprofiler')
92
+ }.should raise_error(ArgumentError)
93
+ end
94
+
95
+ it 'should call dump_results method on profiler' do
96
+ profiler = mock_profile_start('myprofiler', :enabled => true)
97
+ profiler.should_receive(:dump_results)
98
+ EasyProfiler::Profile.stop('myprofiler')
99
+ end
100
+
101
+ it 'should enable back garbage collector when needed' do
102
+ profiler = mock_profile_start('myprofiler1', :enabled => true, :count_ar_instances => true)
103
+ profiler.stub!(:dump_results)
104
+ GC.should_receive(:enable)
105
+ EasyProfiler::Profile.stop('myprofiler1')
106
+
107
+ profiler = mock_profile_start('myprofiler2', :enabled => true, :count_memory_usage => true)
108
+ profiler.stub!(:dump_results)
109
+ GC.should_receive(:enable)
110
+ EasyProfiler::Profile.stop('myprofiler2')
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+ require File.join(File.dirname(__FILE__), "../lib/easy_prof")
4
+
5
+ def mock_profile_start(name, options = {})
6
+ config = EasyProfiler.configuration.merge(options)
7
+ profiler = mock 'MockProfiler', :name => name, :config => config
8
+ EasyProfiler::Profile.send(:class_variable_get, :@@profile_results)[name] = profiler
9
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy-prof
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Dmytro Shteflyuk
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-23 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Simple Ruby code profiler to use both in Rails applications and generic Ruby scripts.
22
+ email: kpumuk@kpumuk.info
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - .gitignore
31
+ - MIT-LICENSE
32
+ - README.rdoc
33
+ - Rakefile
34
+ - VERSION.yml
35
+ - init.rb
36
+ - lib/easy_prof.rb
37
+ - lib/easy_profiler/action_controller_extensions.rb
38
+ - lib/easy_profiler/configuration.rb
39
+ - lib/easy_profiler/firebug_logger.rb
40
+ - lib/easy_profiler/no_profile_instance.rb
41
+ - lib/easy_profiler/profile.rb
42
+ - lib/easy_profiler/profile_instance.rb
43
+ - lib/easy_profiler/profile_instance_base.rb
44
+ - spec/easy_profiler_spec.rb
45
+ - spec/no_profile_instance_spec.rb
46
+ - spec/profile_instance_spec.rb
47
+ - spec/profile_spec.rb
48
+ - spec/spec_helper.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/kpumuk/easy-prof
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.6
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Simple and easy to use Ruby code profiler
79
+ test_files:
80
+ - spec/easy_profiler_spec.rb
81
+ - spec/no_profile_instance_spec.rb
82
+ - spec/profile_instance_spec.rb
83
+ - spec/profile_spec.rb
84
+ - spec/spec_helper.rb