Benchmarking Drupal code

Last modified: January 17, 2009 - 00:22

When attempting to create a large change in Drupal core, or even during the course of developing Drupal sites for clients, often 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 the ab tool in depth. Its a free, open source performance testing solution that comes with Apache and therefore is the tool that is most commonly used in the community.

In addition, you'll also need all the "normal" developer tools, such as:

  • CVS (handbook page)
  • Patch (handbook page)
  • curl or wget for retrieving remote files (optional, but useful)
  • 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 or in a dedicated testing environment so that you can eliminate the possibility of network traffic and other factors that might bias the results.

Begin by navigating to your local web directory:

cd /Applications/MAMP/htdocs

Perform a CVS checkout of Drupal:

cvs -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal checkout -d head drupal

This 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-6-3 -r DRUPAL-6-3 drupal

Install 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 to get its handy devel_generate.module. Note that since HEAD can be volatile, always check the issue queue for updated patches.

Enable a few different node type modules (blog, page, story, forum, etc.) and then browse your site to /admin and use the 'Generate content' links to generate the sample data outlined above.

Notes

  • You should disable MySQL query caching. Run this query:
    SET GLOBAL query_cache_size = 0;
  • You should also disable PHP debugger plugins.

Benchmarking code

Begin by performing a

cvs update -dP

in 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 or decreases.

ab2 -c1 -n500 http://localhost/head/index.php

The -c command 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 took on average (in milliseconds). Here, lower is better. The value is actually not very different from the requests per second value; it's just that 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. Here we're showing that you can retrieve the patch file with curl and apply it with patch

Now 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
<strong>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 now have two sets of values from our tests: The mean of the time taken per request and its standard deviation.

Without patch:

366 +- 29.7 ms

With patch:

378 +- 35.5ms

We 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 ms

With patch:

342 ms

The 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 might try to 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.

Drupal benchmark results

Just a point... You should

ednique - August 31, 2006 - 21:35

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

Tali - February 24, 2007 - 16:02

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

jsware - October 13, 2007 - 15:48

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

Timberwoofie - June 21, 2007 - 18:46

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

itkovian - October 22, 2007 - 20:58

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

Ian Ward - November 28, 2007 - 20:23

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

ab reports the http status

moshe weitzman - January 2, 2009 - 19:39

ab reports the http status code so you would see 403 in this case. but yeah, if you need to authenticate to benchmark an admin page or something, then -c is perfect.

Heres something i wrote on

dgtlmoon - February 19, 2008 - 00:42

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

mtop: top for mysql

blundeln - June 4, 2008 - 10:11

I use an application called mtop, which is very handy for quickly seeing which queries are taking the most time. This has been a big help for my Drupal development.

See here for details: http://mtop.sourceforge.net/

Yes, thats a good and useful

Oxego777 - May 6, 2009 - 09:39

Yes, thats a good and useful tool to check database queries.

 
 

Drupal is a registered trademark of Dries Buytaert.