Poor Man's Guide to Load Testing AEM / Adobe CQ

Have you ever wondered how your AEM publish environment would perform without dispatcher? If so, there's no need to purchase expensive tools--unless you're an enterprise and can afford it. For everyone else, I hope you enjoy the poor man's guide to load testing CQ. 

The Tools

Since we're performing our load test with a budget of $0.00 dollars, begin by downloading Apache JMeter. It is an open source, Java-based load testing tool that's perfect given our budget. We will also need a UNIX/Linux OS to run wget, grep, awk and tail.

Enabling JMX

While running our load test, it would be helpful to inspect the internals of our JVM. So, let's start by enabling JMX on our CQ publish instance(s).

Edit crx-quickstart/start and add the following JVM parameters to your CQ_JVM_OPTS environment variable. You can use any port that is not in use; I selected port 4513.

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=4513 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false

Restart CQ.

Next, start Oracle's Java VisualVM on your local system. If you have Java installed, you already have VisualVM. On a Mac, simply execute jvisualvm from a terminal.

If you are testing a remote CQ publish instance, select Remote and provide the hostname and JMX port we specified above. If you are running CQ locally, you should see the process listed under Local.



VisualVM after connecting to CQ.

Creating a Request Log

There are several methods for creating a JMeter test suite. However, my preferred approach is to use real customer traffic and replay the request log files. So, how do we do this before our site goes live? Simple, let's crawl our site and use those requests to seed our request log.

We can achieve this by mirroring our CQ publish instance with wget. In the example below, we will mirror the Geometrixx Outdoors site. Let this command run until it is finished or until you feel you have enough requests to simulate a good load test.


$ wget -mk -e robots=off -w 1 http://localhost:4503/content/geometrixx-outdoors/en.html

With the above command, we instruct wget to mirror the site, ignore robots.txt and fetch 1 resource every second.

Next, we need to reformat CQ's request log to match the format expected by JMeter. (I am not sure why Adobe is using a non-standard format. I assume it is needed to support rlog.jar). For example, their log file looks as follows (crx-quickstart/logs/request.log).


$ tail request.log
17/Nov/2013:19:24:16 -0800 [8512] -> GET /content/geometrixx-outdoors-mobile/en/equipment/biking/tacna.html HTTP/1.1
17/Nov/2013:19:24:16 -0800 [8512] <- 200 text/html 82ms
17/Nov/2013:19:24:17 -0800 [8513] -> GET /content/geometrixx-outdoors-mobile/en/equipment/sunglasses/timor.html HTTP/1.1
17/Nov/2013:19:24:17 -0800 [8513] <- 200 text/html 87ms
17/Nov/2013:19:24:18 -0800 [8514] -> GET /content/geometrixx-outdoors-mobile/en/equipment/skiing/tobermory-snow.html HTTP/1.1
17/Nov/2013:19:24:18 -0800 [8514] <- 200 text/html 97ms
17/Nov/2013:19:24:19 -0800 [8515] -> GET /content/geometrixx-outdoors-mobile/en/equipment/hiking/tuareg-summer.html HTTP/1.1
17/Nov/2013:19:24:19 -0800 [8515] <- 200 text/html 88ms
17/Nov/2013:19:24:20 -0800 [8516] -> GET /content/geometrixx-outdoors-mobile/en/equipment/skiing/whistler-snow.html HTTP/1.1
17/Nov/2013:19:24:20 -0800 [8516] <- 200 text/html 88ms

We can easily reformat the requests with a few shell commands. In the command below, we are filtering out lines that do not begin with this hour's timestamp (i.e. 17/Nov/2013:19). You will need to change this accordingly. Next, we are only keeping the requests and not the responses (i.e. MIME type + response time). Lastly, we reformat each line to use the standard used by Apache and create a new file named load-test-request.log.

$ grep "^17/Nov/2013:19" request.log | grep GET | awk '{print "::1 - - ["$1" "$2"] \"GET "$6" "$7"\" 200 100"}' > load-test-request.log

If we tail load-test-request.log, you can see that we now have a nicely formatted log suitable for use by JMeter.



$ tail load-test-request.log
::1 - - [17/Nov/2013:19:24:10 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/running/nairobi-runners.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:11 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/sunglasses/raja-ampat.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:12 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/skiing/saskatoon-parka.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:13 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/biking/sparrow.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:14 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/sunglasses/sumatra.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:16 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/biking/tacna.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:17 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/sunglasses/timor.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:18 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/skiing/tobermory-snow.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:19 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/hiking/tuareg-summer.html HTTP/1.1" 200 100
::1 - - [17/Nov/2013:19:24:20 -0800] "GET /content/geometrixx-outdoors-mobile/en/equipment/skiing/whistler-snow.html HTTP/1.1" 200 100

Configuring & Running JMeter

Finally, the fun part! Begin by, unpacking the JMeter binary distribution and starting JMeter.


$ tar -xzf apache-jmeter-2.10.tgz
$ cd apache-jmeter-2.10/bin

1. Change the Test Plan to CQ Publish Load Test.

2. Right-click CQ Publish Test and select Add > Threads (Users) > Thread Group.

3. Rename Thread Group to CQ Users.

4. Under Thread Properties, select the number of users that is appropriate for your expected load. I used 10 threads, with a ramp-up period of 300 seconds and Forever as my loop count. Basically, this will add one user every 30 seconds until we have a total of 10 concurrent users.

5. Next, right-click the CQ User node and select Add > Sampler > Access Log Sampler. Set Server and Port to the hostname and port of your CQ publish instance. Then, set the Log File to the location where you saved load-test-request.log.

6. Right-click the CQ Users node and select Add > Listener > Aggregate Report. This will allow us to capture metrics for each request that is submitted.

7. Right-click the CQ Users node and select Add > Listener > View Results Tree. This is useful for seeing the requests in real-time along with the request and response.

8. Save your test plan.

9. Lastly, click the run button. Monitor your error.log file, VisualVM console and your JMeter reports (Aggregate Report and Result Tree).


JMeter Aggregate Report View during run.