Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 401 lines (298 sloc) 14.254 kb
f359e9b @flyerhzm README for rails2 and rails3
authored
1 h1. Bullet
2
3 The Bullet plugin/gem is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you're using eager loading that isn't necessary and when you should use counter cache.
4
5 Best practice is to use Bullet in development mode or custom mode (staging, profile, etc.). The last thing you want is your clients getting alerts about how lazy you are.
6
7 The Bullet plugin/gem now supports rails 2.1, 2.2 and 2.3, tested in rails 2.1.2, 2.2.2 and 2.3.2.
8
9 ****************************************************************************
10
11 h2. Change
12
13 There is a large refactor from gem 1.4 to 1.5, so if you upgrade to 1.5 gem, please read the Configuration section again and rewrite your configuration.
14
15 ****************************************************************************
16
17 h2. Contributors
18
5e81f1b @flyerhzm update README for contributors
authored
19 "https://github.com/flyerhzm/bullet/contributors":https://github.com/flyerhzm/bullet/contributors
f359e9b @flyerhzm README for rails2 and rails3
authored
20
21 ****************************************************************************
22
23 h2. Install
24
25 You can install it as a gem:
26 <pre><code>
27 sudo gem install bullet
28 </code></pre>
29
30 ****************************************************************************
31
32 h2. Configuration
33
34 Bullet won't do ANYTHING unless you tell it to explicitly. Append to <code>config/environments/development.rb</code> initializer with the following code:
35 <pre><code>
36 config.after_initialize do
8335fac @flyerhzm update documents
authored
37 Bullet.enable = true
f359e9b @flyerhzm README for rails2 and rails3
authored
38 Bullet.alert = true
8335fac @flyerhzm update documents
authored
39 Bullet.bullet_logger = true
f359e9b @flyerhzm README for rails2 and rails3
authored
40 Bullet.console = true
41 Bullet.growl = true
42 Bullet.rails_logger = true
43 Bullet.disable_browser_cache = true
997a017 @flyerhzm introduce sriedel's awesome job
authored
44 Bullet.xmpp = { :account => 'bullets_account@jabber.org',
45 :password => 'bullets_password_for_jabber',
46 :receiver => 'your_account@jabber.org',
47 :show_online_status => true }
f359e9b @flyerhzm README for rails2 and rails3
authored
48 end
49 </code></pre>
50
8335fac @flyerhzm update documents
authored
51 The notifier of bullet is a wrap of "uniform_notifier":https://github.com/flyerhzm/uniform_notifier
997a017 @flyerhzm introduce sriedel's awesome job
authored
52
f359e9b @flyerhzm README for rails2 and rails3
authored
53 The code above will enable all five of the Bullet notification systems:
54 * <code>Bullet.enable</code>: enable Bullet plugin/gem, otherwise do nothing
55 * <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
56 * <code>Bullet.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
57 * <code>Bullet.rails_logger</code>: add warnings directly to the Rails log
58 * <code>Bullet.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
59 * <code>Bullet.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
60 * <code>Bullet.disable_browser_cache</code>: disable browser cache which usually causes unexpected problems
61
62 ****************************************************************************
63
64 h2. Log
65
66 The Bullet log <code>log/bullet.log</code> will look something like this:
67
68 * N+1 Query:
69 <pre><code>
70 2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
71 Add to your finder: :include => [:comments]
72 2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
73 /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
74 /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
75 /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
76 /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
77 </code></pre>
78
79 The first two lines are notifications that N+1 queries have been encountered. The remaining lines are stack traces so you can find exactly where the queries were invoked in your code, and fix them.
80
81 * Unused eager loading:
82 <pre><code>
83 2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
84 Remove from your finder: :include => [:comments]
85 </code></pre>
86
87 These two lines are notifications that unused eager loadings have been encountered.
88
89 * Need counter cache:
90 <pre><code>
91 2009-09-11 09:46:50[INFO] Need Counter Cache
92 Post => [:comments]
93 </code></pre>
94
95 ****************************************************************************
96
97 h2. Growl Support
98
99 To get Growl support up-and-running for Bullet, follow the steps below:
8335fac @flyerhzm update documents
authored
100 * Install the ruby-growl gem: <code>gem install ruby-growl</code>
f359e9b @flyerhzm README for rails2 and rails3
authored
101 * Open the Growl preference pane in Systems Preferences
102 * Click the "Network" tab
9bc850e @flyerhzm fix typo
authored
103 * Make sure both "Listen for incoming notifications" and "Allow remote application registration" are checked. *Note*: If you set a password, you will need to set <code>Bullet.growl = { :password => 'growl password' }</code> in the config file.
f359e9b @flyerhzm README for rails2 and rails3
authored
104 * Restart Growl ("General" tab -> Stop Growl -> Start Growl)
105 * Boot up your application. Bullet will automatically send a Growl notification when Growl is turned on. If you do not see it when your application loads, make sure it is enabled in your initializer and double-check the steps above.
106
107 ****************************************************************************
108
109 h2. Ruby 1.9 issue
110
111 ruby-growl gem has an issue about md5 in ruby 1.9, if you use growl and ruby 1.9, check this gist http://gist.github.com/300184
112
113 ****************************************************************************
114
8335fac @flyerhzm update documents
authored
115 h2. XMPP/Jabber Support
116
117 To get XMPP support up-and-running for Bullet, follow the steps below:
118 * Install the xmpp4r gem: <code>sudo gem install xmpp4r</code>
119 * Make both the bullet and the receipient account add each other as contacts.
120 This will require you to manually log into both accounts, add each other
121 as contact and confirm each others contact request.
122 * Boot up your application. Bullet will automatically send an XMPP notification when XMPP is turned on.
123
124 ****************************************************************************
125
f359e9b @flyerhzm README for rails2 and rails3
authored
126 h2. Important
127
128 If you encounter the following errors in development environment:
129
130 <pre><code>
131 You might have expected an instance of Array.
132 The error occurred while evaluating nil.include?
133 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:142:in `create_time_zone_conversion_attribute?'
134 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:75:in `define_attribute_methods'
135 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `each'
136 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `define_attribute_methods'
137 /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:242:in `method_missing'
138 </code></pre>
139
140 Or any strange behavior of bullet plugin/gem, *please disable your browser's cache*.
141
142 ****************************************************************************
143
144 h2. Advance
145
146 The bullet plugin/gem use rack middleware for http request. If you want to bullet for without http server, such as job server. You can do like this:
147
148 <pre><code>
149 Bullet.start_request if Bullet.enable?
150 # run job
151 if Bullet.enable?
152 Bullet.growl_notification
153 Bullet.log_notification('JobServer: ')
154 Bullet.end_request
155 end
156 </code></pre>
157
158 Or you want to use it in test mode
159
160 <pre><code>
161 before(:each)
162 Bullet.start_request if Bullet.enable?
163 end
164
165 after(:each)
166 if Bullet.enable?
7591f67 @flyerhzm update README for using bullet in test environment
authored
167 Bullet.perform_out_of_channel_notifications
f359e9b @flyerhzm README for rails2 and rails3
authored
168 Bullet.end_request
169 end
170 end
171 </code></pre>
172
173 Don't forget enabling bullet in test environment.
174
175 ****************************************************************************
176
177 h2. Links
178
179 * "http://weblog.rubyonrails.org/2009/10/22/community-highlights":http://weblog.rubyonrails.org/2009/10/22/community-highlights
180 * "http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009":http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009
181 * "http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1":http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1
182
183 ****************************************************************************
184
185 h2. Step by step example
186
187 Bullet is designed to function as you browse through your application in development. It will alert you whenever it encounters N+1 queries or unused eager loading.
188
189 1. setup test environment
190
191 <pre><code>
8ffd4b6 @flyerhzm update README
authored
192 $ rails test_bullet
193 $ cd test_bullet
8335fac @flyerhzm update documents
authored
194 $ script/generate scaffold post name:string
f359e9b @flyerhzm README for rails2 and rails3
authored
195 $ script/generate scaffold comment name:string post_id:integer
196 $ rake db:migrate
197 </code></pre>
198
199 2. change <code>app/model/post.rb</code> and <code>app/model/comment.rb</code>
200
201 <pre><code>
202 class Post < ActiveRecord::Base
203 has_many :comments
204 end
205
206 class Comment < ActiveRecord::Base
207 belongs_to :post
208 end
209 </code></pre>
210
211 3. go to script/console and execute
212
213 <pre><code>
214 post1 = Post.create(:name => 'first')
215 post2 = Post.create(:name => 'second')
216 post1.comments.create(:name => 'first')
217 post1.comments.create(:name => 'second')
218 post2.comments.create(:name => 'third')
219 post2.comments.create(:name => 'fourth')
220 </code></pre>
221
222 4. change the <code>app/views/posts/index.html.erb</code> to produce a N+1 query
223
224 <pre><code>
225 <% @posts.each do |post| %>
226 <tr>
227 <td><%=h post.name %></td>
228 <td><%= post.comments.collect(&:name) %></td>
229 <td><%= link_to 'Show', post %></td>
230 <td><%= link_to 'Edit', edit_post_path(post) %></td>
231 <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
232 </tr>
233 <% end %>
234 </code></pre>
235
236 5. add bullet plugin
237
238 <pre><code>
239 $ script/plugin install git://github.com/flyerhzm/bullet.git
240 </code></pre>
241
242 6. enable the bullet plugin in development, add a line to <code>config/environments/development.rb</code>
243
244 <pre><code>
245 config.after_initialize do
246 Bullet.enable = true
247 Bullet.alert = true
248 Bullet.bullet_logger = true
249 Bullet.console = true
250 # Bullet.growl = true
251 Bullet.rails_logger = true
252 Bullet.disable_browser_cache = true
253 end
254 </code></pre>
255
256 7. start server
257
258 <pre><code>
259 $ script/server
260 </code></pre>
261
262 8. input http://localhost:3000/posts in browser, then you will see a popup alert box says
263
264 <pre><code>
265 The request has unused preload associations as follows:
266 None
267 The request has N+1 queries as follows:
268 model: Post => associations: [comment]
269 </code></pre>
270
271 which means there is a N+1 query from post object to comments associations.
272
273 In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
274
275 <pre><code>
276 2009-08-20 09:12:19[INFO] N+1 Query: PATH_INFO: /posts; model: Post => assocations: [comments]
277 Add your finder: :include => [:comments]
278 2009-08-20 09:12:19[INFO] N+1 Query: method call stack:
8ffd4b6 @flyerhzm update README
authored
279 /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
280 /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:8:in `each'
281 /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
282 /Users/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'
f359e9b @flyerhzm README for rails2 and rails3
authored
283 </code></pre>
284
285 The generated SQLs are
286
287 <pre><code>
8335fac @flyerhzm update documents
authored
288 Post Load (1.0ms) SELECT * FROM "posts"
289 Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
290 Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
f359e9b @flyerhzm README for rails2 and rails3
authored
291 </code></pre>
292
293
294 9. fix the N+1 query, change <code>app/controllers/posts_controller.rb</code> file
295
296 <pre><code>
297 def index
298 @posts = Post.find(:all, :include => :comments)
299
300 respond_to do |format|
301 format.html # index.html.erb
302 format.xml { render :xml => @posts }
8335fac @flyerhzm update documents
authored
303 end
304 end
f359e9b @flyerhzm README for rails2 and rails3
authored
305 </code></pre>
306
307 10. refresh http://localhost:3000/posts page, no alert box and no log appended.
308
309 The generated SQLs are
310
311 <pre><code>
8335fac @flyerhzm update documents
authored
312 Post Load (0.5ms) SELECT * FROM "posts"
313 Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
f359e9b @flyerhzm README for rails2 and rails3
authored
314 </code></pre>
315
316 a N+1 query fixed. Cool!
317
318 11. now simulate unused eager loading. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
319
320 <pre><code>
321 def index
322 @posts = Post.find(:all, :include => :comments)
323
324 respond_to do |format|
325 format.html # index.html.erb
326 format.xml { render :xml => @posts }
8335fac @flyerhzm update documents
authored
327 end
328 end
f359e9b @flyerhzm README for rails2 and rails3
authored
329 </code></pre>
330
331 <pre><code>
332 <% @posts.each do |post| %>
333 <tr>
334 <td><%=h post.name %></td>
335 <td><%= link_to 'Show', post %></td>
336 <td><%= link_to 'Edit', edit_post_path(post) %></td>
337 <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
338 </tr>
339 <% end %>
340 </code></pre>
341
342 12. refresh http://localhost:3000/posts page, then you will see a popup alert box says
343
344 <pre><code>
345 The request has unused preload associations as follows:
346 model: Post => associations: [comment]
347 The request has N+1 queries as follows:
348 None
349 </code></pre>
350
351 In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
352
353 <pre><code>
354 2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts; model: Post => associations: [comments]·
355 Remove from your finder: :include => [:comments]
356 </code></pre>
357
358 13. simulate counter_cache. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
359
360 <pre><code>
361 def index
362 @posts = Post.find(:all)
363
364 respond_to do |format|
365 format.html # index.html.erb
366 format.xml { render :xml => @posts }
8335fac @flyerhzm update documents
authored
367 end
368 end
f359e9b @flyerhzm README for rails2 and rails3
authored
369 </code></pre>
370
371 <pre><code>
372 <% @posts.each do |post| %>
373 <tr>
374 <td><%=h post.name %></td>
375 <td><%=h post.comments.size %></td>
376 <td><%= link_to 'Show', post %></td>
377 <td><%= link_to 'Edit', edit_post_path(post) %></td>
378 <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
379 </tr>
380 <% end %>
381 </code></pre>
382
383 14. refresh http://localhost:3000/posts page, then you will see a popup alert box says
384
385 <pre><code>
386 Need counter cache
387 Post => [:comments]
388 </code></pre>
389
390 In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
391
392 <pre><code>
393 2009-09-11 10:07:10[INFO] Need Counter Cache
394 Post => [:comments]
395 </code></pre>
396
397 ****************************************************************************
398
399
c66c114 @flyerhzm update README
authored
400 Copyright (c) 2009 - 2012 Richard Huang (flyerhzm@gmail.com), released under the MIT license
Something went wrong with that request. Please try again.