Saturday, November 27, 2010

JMeter and AJAX - Part II

A four part post on JMeter and AJAX
Part I - A general discussion on why we might not need to worry about testing AJAX on JMETER
Part II - A diversion on creating a sampler that will allow us to synchronize between specific threads
Part III - A simple sample on executing some AJAX requests in parallel
Part III-1 - A diversion on how to use session ids between threads 
Part IV - A more complex sample

As mentioned in Part I , the most elegant way to simulate AJAX in JMeter would be to customize the HTTPSampler to allow child HTTPSampler requests to be executed in parallel.
Since customization of JMeter core isn't an option for me, we'll try and simulate this directly without customizing JMeter. Note that this is a toy, done for amusement rather than any real solution to an existing problem.

The first thing we should note that anything that we need done in parallel in JMeter needs its own thread. Suppose we have some requests that make two AJAX requests in parallel then the way we are going to work this is
Thread odd numbered(1,3,...) - Main thread that executes most of the requests
Thread even numbered(2,4,...) - Thread used only when needed for AJAX parallel requests so Thread(1,2) is a group as is Thread(3,4) and so on.
If we needed to make 3 AJAX requests in parallel then we would have grouped up Threads(1,2,3) and Threads (4,5,6) - but the main thread would have been Thread1, Thread4,...
You can immediately see resources are being wasted - Well we did say this was a toy.
Making the even numbered threads not execute some samplers is easy, we just need an IF controller to check. But how do you make Thread 1 and Thread 2 for e.g. execute a sampler in between at exactly the same time? In the Java world , this would usually mean reading up everything that Doug Lea and Brian Goetz have written - but in lazy developer mode this means downloading the JMeter source code and looking at SyncTimer (which blocks a fixed number of threads but not specific ones) and using the time honored technique of Copy Paste.
So before diving into the problem at hand lets see if we can simulate the following.
6 threads will arrive at varying times into one sampler. At which points they will wait till groups are ready i.e. Threads 1 and 2 will go together and Threads 3 and 4 will go together and so on.
Here's the Beanshell sampler which is actually a customized Sync Timer in disguise
import java.util.HashMap;
int totalThreads = 6; // MUST match total number defined in Thread Group
int groupsOf = 2; //MUST match number of requests to make in parallel

if (bsh.shared.myObj == void){
    // not yet defined, so create it:
    bsh.shared.myObj =new HashMap();
    bsh.shared.timerCounter =new HashMap();
    int numObjectsNeeded = totalThreads/groupsOf;
    for(int i = 0;i<numObjectsNeeded;i++) {
           bsh.shared.myObj.put(String.valueOf(i),new Object());
           bsh.shared.timerCounter.put(String.valueOf(i),new int[] { 0 });
    }
    print("populated");
} else {
   print("already init");
}

print(${__threadNum()});

int grpNo = (int)(${__threadNum()} -1)/groupsOf;
print(grpNo);
String groupNumber = String.valueOf(grpNo);
print(groupNumber);
Object syncObject = bsh.shared.myObj.get(groupNumber);
print(syncObject);
synchronized (syncObject) {
   int[] timerCount = (int[])bsh.shared.timerCounter.get(groupNumber);
   timerCount[0]++;
   final int count = timerCount[0];
   if (count >= groupsOf) {
                syncObject.notifyAll();
            } else {
                try {
                    syncObject.wait();
                } catch (InterruptedException e) {
                    log.warn(e.getLocalizedMessage());
                }
            }
            timerCount[0]=0; // Reset for next time   
}
SampleResult.setResponseData("sunk!");


The bsh.shared namespace is used to share objects between threads. So in our case we have 6 threads and we want 2 threads in parallel which means there are 3 groups of two threads each. To synchronize each group we need an object (hence 3 objects) and a counter each - to count how many threads are already available. Thats what the first part of the code sets up.(The first part is not thread safe but because we are going to test this by guaranteeing that threads arrive at different times , the snippet need not be thread safe).

The second part of the code then calculates which Group a thread belongs to, gets the object for the group, increases its count. If the number of threads is less than that we want in parallel it suspends the thread (by calling wait) otherwise it does a notifyAll which will wake up all suspended threads and allow these two threads to move in parallel

Heres what the test looks like


We have artificially added a timer to make sure requests to the Delay sampler arrive 1 thread at a time. We run the test and we can see that Threads(1,2) run the After Delay sampler at almost the same time , as do Threads(3,4) and as do Threads(5,6)
If we change the groupsOf in the delay sampler to be 3 then we see that three threads run in parallel.

And with that we are ready to use this delay sampler to make AJAX calls in parallel.
Onwards to Part III - An actual example of JMeter and AJAX

8 comments:

yahonto said...

Can I look at the Test Plan (*.jmx), please?

Deepak Shetty said...

Here is the link to the file

https://skydrive.live.com/?cid=1bd02fe33f80b8ac&sc=documents&uc=2&id=1BD02FE33F80B8AC%21865

naveen kumar said...

Hi Deepak,
Its a grt experience to read this, but i am facing a problem with my testing web application.
The application have dashboard having 4 kind of widgets. Out of 4 two are daypilot widget using calendars.The data are call on widget through web services, the third party service. When i start record the action on Jmeter, the calendar widget doesn't recorded, the step recorded as "Dashboard/Default.aspx". When i run the test, at this step Jmeter shows error as "Response code: 500". "Response message: Internal server error"

Can you please help me out why this behavior is showing by Jmeter.

Thanks,
Naveen

Deepak Shetty said...

>When i start record the action on Jmeter, the calendar widget doesn't recorded,

a. First verify on the browser - with no jmeter proxy -(using firebug or livehttpheader )whether any HTTP call is being made - sometimes the call is made onload of the page so that when you actually click something the data is already there and no AJAX call gets made.

b. If you do see that the browser makes a call then check on the browser where it is making the call too - then check if your browser is using a proxy or it is bypass proxy for this IP address or whether it has cached the page. Jmeter will only record actions that the browser requests it so if for e.g. the browser has cached some url it wont make a call and Jmeter wont record it. So clearing brwoser cache before you start recording is also a good idea

> at this step Jmeter shows error as "Response code: 500"
It means that your application uses something that changes dynamically so you have to tweak your script to deal with dynamic data - there are many many mails on the Jmeter forums that deal with this particular problem so look up some examples there

Anil said...

Hi Deepak,

Great info... thx for posting such a precious info on web... (for free :))
I've .net page with ajax in it. I need to test the page using jmeter. Can u please give me a sample test plan? I'm able to login and the login source code is shown in Response Data tab in View Results Tree listener, but after login, we get http://website/default.asp, this address will be throughout the site when logged in, once we logout it will change to http://website/logout.asp.

Regards,
Anil

Deepak Shetty said...

Without seeing the website, a sample isnt possible - ASP.NET just has a couple of dynamic parameters which vary with every request so those need to be extracted - other than that its the same as any other test

Unknown said...

Hi Deepak,

I am unable to open the SyncTimerGroup.jmx project in Jmeter. Is it version specific?

Thanks,
Tanisha

Deepak Shetty said...

@Tanisha
It shouldnt be but its possible you are using an older version of JMeter than what was used by me
It opens fine in 2.9
If you have an error in jmeter.log, what is it? and what version of Jmeter are you using?