HowTo: Benchmark Drupal code
Note: this page is mostly a result of me Googling around and jotting down some notes as I find them; I need someone who actually knows about this stuff to look it over and ensure I actually know wtf I'm talking about. ;)
Introduction
When attempting to create a large change in Drupal core, or even during the course of developing Drupal sites for clients, often times you are asked to provide benchmark results to ensure that your change doesn't negatively impact performance (or to see how much it improves performance). This document will talk about the various strategies involved in benchmarking Drupal code.
Tools of the Trade
This document will discuss in-depth the ab tool, as it is most commonly used in the community due to its being a free, open source performance testing solution that comes with Apache.
In addition, you'll also need all the "normal" developer tools, such as:
- CVS (handbook page)
- patch (handbook page)
- (optional, but useful) curl or wget for retrieving remote files
- A web server environment with PHP and MySQL/PostgreSQL installed. If the thought of installing these yourself gives you the heebie-jeebies, or you have an existing install you don't want to mess with, there are "all in one" packages that are as simple as unzipping/installing and you're up and running:
Setting up a performance testing environment
Benchmarking is best done on your local computer so you can eliminate the possibility of network traffic and other things impairing the results.
Begin by changing into your local web directory:
cd /Applications/MAMP/htdocsFirst, perform a CVS checkout of Drupal:
cvs -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal checkout -d head drupalThis will retrieve a copy of the current HEAD version of Drupal into a folder called "head." You may also want a copy of the current "stable" version of Drupal for benchmarking purposes:
cvs -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal checkout -d drupal-4-7 -r DRUPAL-4-7 drupalInstall Drupal in the normal way and create the first user.
Following Dries's recommendations, we're going to setup an environment with:
- 2,000 users
- 5,000 nodes
- 5,000 path aliases
- 10,000 comments
- 250 terms
- 15 vocabularies
Download a copy of Devel module for its handy devel_generate.module. Note that since HEAD can be volatile, always check the issue queue for updated patches and such.
Enable a few different node type modules (blog, page, story, forum, etc.) and then browse to /admin and see the 'generate content' links and friends. Use those forms to generate your items above.
Note: you should disable MySQL query caching. Rub this query:
SET GLOBAL query_cache_size = 0;
Note: you should disable PHP debugger plugins.
Benchmarking code
Begin by performing a
cvs update -dPin order to ensure your source tree is up-to-date.
Then, execute the following from the command-line before applying your new patch. This will establish a baseline from which to judge performance improvements/decreases.
ab2 -c1 -n500 http://localhost/head/index.phpIn this command, -c specifies the number of concurrent requests and -n specifies the total number of page requests. In 99 out of 100 cases it is not necessary to specify a higher value than 1 for the concurrency. Increasing c will only put more stress on your server. This is useful if you want a general stress test, but it will falsify your results if you only want to test the impact of a particular patch on a particular Drupal page.
This will produce output similar to:
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient).....done
Server Software: Apache/2.0.55
Server Hostname: localhost
Server Port: 80
Document Path: /head/index.php
Document Length: 16668 bytes
Concurrency Level: 1
Time taken for tests: 36.695602 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 1718500 bytes
HTML transferred: 1666800 bytes
Requests per second: 2.73 [#/sec] (mean)
Time per request: 366.956 [ms] (mean)
Time per request: 366.956 [ms] (mean, across all concurrent requests)
Transfer rate: 45.73 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 316 366 29.7 362 608
Waiting: 298 346 29.3 342 588
Total: 316 366 29.7 362 608
Percentage of the requests served within a certain time (ms)
50% 362
66% 367
75% 370
80% 373
90% 384
95% 390
98% 462
99% 608
100% 608 (longest request)There are two important parts of this result:
1) Requests per second.
This is a good value to judge the overall performance impact. Higher is better.
2) The Connection Times table
The first row of the table specifies the headers for the columns. We are mostly interested in the second (mean) and third ([+/-sd]) column of the last row (Total). The mean or average tells us how long a page request did take on average (in milliseconds). Here, lower is better. The value is actually not very different from the Requests per second value, just the computation is done "the other way around". What makes this value interesting is the fact that ab provides the standard deviation of the value in the third column. The standard deviation is a means to judge how accurate a particular test result is. If the standard deviation is large, then this is an indicator that something wasn't working as it should, for example the webserver was under high load or there was a network lag. If this is the case, you will usually see that in the table at the bottom (Percentage of the requests served within a certain time) the last value is much larger than the first one. In this case you should investigate your experimental setup and try to reduce the standard deviation.
Now, apply your patch:
curl -LO http://drupal.org/files/issues/patch_file.patch
patch -p0 < patch_file.patch...and re-execute the command above, and compare the results:
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient).....done
Server Software: Apache/2.0.55
Server Hostname: localhost
Server Port: 80
Document Path: /head/index.php
Document Length: 17100 bytes
Concurrency Level: 1
Time taken for tests: 37.903596 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 1761700 bytes
HTML transferred: 1710000 bytes
Requests per second: 2.64 [#/sec] (mean)
Time per request: 379.036 [ms] (mean)
Time per request: 379.036 [ms] (mean, across all concurrent requests)
Transfer rate: 45.38 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 318 378 35.5 376 690
Waiting: 301 358 35.4 355 672
Total: 318 378 35.5 376 690
Percentage of the requests served within a certain time (ms)
50% 376
66% 381
75% 385
80% 388
90% 396
95% 401
98% 436
99% 690
100% 690 (longest request)interpreting results
Ideally, we have now two sets of values from our tests: The mean of the time taken per request and its standard deviation.
Without patch
366 +- 29.7 msWith patch
378 +- 35.5msWe can see that the second value is higher by 12ms and we might draw the conclusion that the patch makes Drupal slower. However, there is also the standard deviation (which is rather large at almost 10% of the result). If you add the standard deviation of the first result and subtract the standard deviation from the second you will get:
Without patch
396 msWith patch
342 msThe order is now reversed.
This means the two results are within their respective standard deviations. This means that the test wasn't able to deliver a decisive result. In such a case you should for example increase the number of requests that ab does.
Ideally, your results should be several standard deviations apart to allow for a clear statement on the performance impact of a patch.

Just a point... You should
Just a point...
You should run ab on another server to get real results...
Preferably a server from another network...
Next it would be better to use the -c (clients)...
specifying -n 100 will do 100 requests after each other. Like one person hitting a 100 times the reload button.
specifying -n 100 -c 10 will do 10 times 10 request at the same time. Like 10 persons hitting 10 times on reload.
I usually do thses tests:
-n 100 -c 10
-n 1000 -c 10
-n 1000 -c 50
This will give you a better overview of the ratings...
The most important output is
failed requests: hopefully not too many. zero is perfect
requests per second: the higher the better (look at the difference for this value if you call ab on the same server compared to a remote server)
Time per request: The lower the better (put in mind that after 3 seconds your visitors go away...)
I found a great doc about performance tuning that uses ab as example for validating...
It's mainly about Perl but the "Performance tuning by tweaking Apache Configuration" is helpfull for php people too...
here: http://perl.apache.org/docs/1.0/guide/performance.html#Performance_Tunin...
Run from localhost
ednique,
Actually the author is correct, you want to run ab from localhost in this case. You aren't trying to benchmark the server per se, you are (in this case) trying to benchmark the code. Seeing as how network latency is far greater and less predictable for each run than CPU cycles are, you would have the greatest margin or error and the least accurate results from running from a remote server.
Benchmarking code - run from localhost
Benchmarking apache server - run from remote host
Benchmarking the server
IMHO, even when benchmarking the server, one should do it on local network instead.
When benchmarking the webapp or server directly on localhost, the "benchmarkee" shares the processor(s) and HDD with "benchmarker".
When benchmarking the webapp or apache server remotely (not on local network), the results may be biased by connection limitation. E.g. if the average node has say 20k in all its files, then 50 requests/sec would require 1MBps stream, which would be pretty small on local network but quite intense to do it on the internet.
Updated Instructions
Vastly easier than mucking about with CS, just download and install the Devel module in your test server. The Devel module makes it fast and easy to add lots of content to your Drupal server. Once installed ...
Administer > Site Building > Blocks
Devel: right sidebar
Devel Node Access: content
Administer > Site Configuration > Devel
check Collect query info
check Display query log (shows queries for the page at the bottom of the page)
sort query log by duration
check Store executed queries (needed for the Database queries log to work)
check Display page timer
check Display memory usage
click Save Configuration
Administer > Content Management > Generate Categories
15 vocabularies
250 terms
12 max word length
click Do it!
Administer > Content Management > Generate Content
5000 nodes
10000 comments
8 max title words
click Do it!
Now you can run the benchmarks.
Sensitivity to DB population
I am wondering how sensitive these timing results are to the populating of the databse the Devel module does. Has anybody any results on that?
Second, I think it would be better if ab gave a confidence interval for the mean response time, instead of just the mean and standard deviation. Obviously you could compute this yourself, but still ...
When you use ab I believe
When you use ab I believe you need to specify the -C flag and a session/cookie name/value pair, otherwise you could end up benchmarking the 403 access denied page, if the page you'd like to benchmark is behind Drupal access control, as opposed to available for the anonymous visitor, or if any of the content on the page is not available to the anonymous user.
Development Seed Blog
Heres something i wrote on
Heres something i wrote on using the shell to quickly analyze your mysql logs, good for seeing which modules are hitting the server the most