Stress-test your PHP App with ApacheBench

This article was sponsored by New Relic. Thank you for supporting the sponsors that make SitePoint possible!

There’s no telling when your app might attract a throng of visitors at once – maybe it’s a Hacker News post that’s submitted at a specific second on a specific time of day (as posts there tend to work), maybe it was a particularly well placed Reddit post, and maybe it’s actually good and people noticed it, spreading it virally. Regardless of the reason, massive influxes of visitors are a double-edged sword: they get you what you always wanted – a chance to prove your worth to a large chunk of the internet’s population – but also often bring with them what you always feared: absolute downtime.

There are several ways to try and prevent this – among the most prevalent is deploying your application on a service like Amazon, Google App Engine or Heroku which have the ability to not only scale out and in rapidly at your command, but also support automatic scaling, if you’re not afraid of DDoS attacks also scaling your bills while you’re asleep. These platforms usually offer plugins that can optimize your application while it’s up, so you can fine tune it as you go along, but why not try and predict issues while still developing locally and save yourself time, money and effort in the long run?

Apache Benchmark

ApacheBench (also known as “ab”, the command you run it with) is a tool designed to nuke an endpoint with requests and load-test web servers. It supports a wide array of parameters and options you can tweak to simulate different loads, like number of requests, number of concurrent requests, extra headers, falsified cookies, and more.

ab is often included with every Apache installation, but if not, you can easily install it by running sudo apt-get install apache2-utils. The current version of Laravel Homestead doesn’t have it, so if you’re following along please install it into that VM when you vagrant up and connect through SSH.

You can verify you have it installed by just running ab, which should produce a list of supported options:

For our demonstration, I created a subfolder Laravel under Code in the VM, added a subfolder called “public”, and put an index.php file inside with the following content:

<?php

echo "Hello Test";

In homestead.yaml, I called this site “homestead.app”, and in my hosts file on my host machine I added 127.0.0.1 homestead.app. This allows me to run sites hosted on the Homestead VM from the host machine’s browser via http://homestead.app:8000, all standard stuff we covered before. However, this also allows you to curl homestead.app from inside the VM – something we can take advantage of when bombing our app with ab.

Test run

For starters, let’s try and nuke our homestead.app with a sample ab grenade – just run ab on the URL, without any options. Note that you must follow the URL with a slash, otherwise you’ll get an invalid URL error from ab.

ab homestead.app/

We can see the benchmark finishes almost instantly – the number of requests per second and concurrency load is too unreal and simple to yield useful results. In fact, running the bench multiple times will produce results from 50 to 200 possible requests per second. To provide some more tangible results, we need to up our game.

Demo app

Go outside the Laravel folder and remove it entirely with rm -rf Laravel. Next, we’ll create a default sample Laravel application with all dependencies. If you don’t have Composer installed globally yet, do it, it’s easy and fast. Then, run the following command:

composer create-project laravel/laravel Laravel --prefer-dist

This will download the Laravel skeleton, all dependencies, and generate the autoload and lock files that are needed. You can verify it works by going to http://homestead.app:8000 on your host machine, or curling homestead.app inside the VM:

Let’s tweak the basic bench command and properly step on our app’s toes. Run ab -n 500 -c 100 homestead.app/ to make a more realistic test. The n param means “number of requests”, while c is concurrency, as in, how many requests at a time. Thus, this command will execute 5 batches of 100 simultaneous requests.

The default result format you get at the end of the output will tell you how many requests (in percentage) were executed within a given timeframe, 10 by 10 per cent from 50% to 100%. In other words, if the first row is 50% 3200, that means half the total requests were executed within 3.2 seconds. For 250 requests, this isn’t bad, especially on such a weak machine.

Let’s try and slow things down on purpose. Go into app/controllers/HomeController.php and change the showWelcome function to this:

    public function showWelcome()
    {

        if (isset($_GET['slower']) && $_GET['slower'] == 'true') {
            sleep(1);
        } else {
            usleep(1);
        }

        return View::make('hello');
    }

If you’d like to get some more consistent results, feel free to remove the call to Google Fonts from hello.php, as that removes the downloading lag when fetching the font and leaves everything in the hands of the VM, locally. Also, in the routes.php file, replace the current closure based route with this:

Route::get('/', 'HomeController@showWelcome');

Now try benching two different URLs: homestead.app and homestead.app?slower=true. As expected, you’ll notice the results differ wildly. This was a completely ridiculous example, but it’s enough to demonstrate the effect a long running script can have on your visitors when they flock in en-masse. If you have long running scripts like these, it’s much better to delegate them to the background via message queues or other means, and make sure your client-facing script is as fast as you can make it.

Conclusion

In this introduction to ApacheBench, we saw how much of an effect script efficiency had on huge traffic surges. We’ll be adding more ab tutorials further down the line, but in the meanwhile, all you should take away from this is – don’t underestimate small optimizations. There is the potential of premature optimization, but if you can detect and remove the boulders from the path sooner, the road will be smoother in the long run. Play around with ab, test it out, switch out parameters and options, add database connections to the sample Laravel app we played around with – break it, fix it, break it again. Let us know what you find – we’d love to publish your advanced use cases!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • gggeek

    A small tool which helps with running AB multiple times with varying concurrency is available at https://github.com/gggeek/ezab – it also has a full reimplementation of ab in php, in case you do not / can not install the original

    • http://www.bitfalls.com/ Bruno Skvorc

      Fantastic, thank you for the heads up!

  • http://www.bitfalls.com/ Bruno Skvorc

    Will do, thanks!

  • http://ortz.org/ Ortzinator

    Is it really worth benchmarking in a VM? Especially in Windows which is bottlenecked by slow synced folders.

    • http://blog-unisys12.rhcloud.com/ Phillip Jackson

      Absolutely @ortzinator:disqus! No matter what your local environment is. If you are make changes to your site/app, it only makes sense to test it in the same environment from one change to the other. Mainly because you’re testing for improvements in how your site/app reacts to those changes. Even taking into account the “slow synced folders” issues you mention, if you make changes to your site/app’s code and see a 10% improvement running in your local VM, you at the very least have a base line to judge those improvements by in your production environment.

      I guess the point I am trying to make here is that just because someone is developing on a Windows box, does not mean that it is a waste of time performing back end performance testing. I develop on a Windows laptop and when comparing code or apps, I can plainly see differences in performance. So…

  • gafitescudaniel

    I love tsung for stress testing http://tsung.erlang-projects.org/