easy-prof 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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