Thursday, 22 April 2010

Configuring Apache JMeter specifically for Oracle's ADF 11g

Sometime back I blogged about Stress & load testing web applications (even ADF & Apex) using Apache JMeter. That post dealt with the generic setup of recording a web session and then replaying under load via JMeter.

When considering using JMeter for JDeveloper 11g ADF applications there are additional JMeter configuration steps required beyond that detailed in the original post. As per the original post ADF passes around cookies to store the JSESSIONID, and you can use JMeter's HTTP Cookie Manager to correctly handle the cookie. However ADF also makes use of a number of parameters passed as HTTP POST parameters and URL parameters. On running an ADF application you'll often see URL parameters like _adf.ctrl-state, _afrLoop, and embedded within the HTTP bodies variables such as javax.faces.ViewState, used by ADF to coordinate state with the browser session. In order for JMeter to simulate multiple user sessions it needs to be correctly configured to handle these too, capturing and carrying the unique values for each session in the load test.

The following post lists the additional configurations required for JMeter beyond those described in the original post. As noted in the original post the easiest way to setup your JMeter Test Plan is to record an existing ADF session through JMeter's HTTP Proxy Server and I haven't bothered to reiterate those instructions here. In turn I've taken no effort to explain the JMeter features we're utilizing to do this; if you want to learn about the relevant JMeter features consult the JMeter User Manual.

Caveat for ADF 10g users: You'll note above I specifically stated JDeveloper 11g ADF, not 10g. I haven't tested any of the following settings for JDev 10g and I don't believe they'll work, as if memory serves me correctly the POST and URL parameters mechanisms changed between versions. However the following notes may give you enough information to dig around in a running 10g ADF application, looking at the HTTP Requests to work out what parameters to work with.

JMeter ADF Required Configurations

The following lists 5 additional objects and their settings. You need only create one of each for each Thread Group. The end result will look something like this:


a) HTTP Cookie Manager

Create via Test Plan -> Thread Group -> Right Click -> Add -> Config Element -> HTTP Cookie Manager

Name:HTTP Cookie Manager
Clear cookies each iteration:Selected

b) User Defined Variables

Create via Test Plan -> Thread Group -> Right Click -> Add -> Config Element -> User Defined Variables

Name:ADF Variables
Variable Name:afrLoop
Variable Name:afrWindowId
Variable Name:jsessionId
Variable Name:adf.ctrl-state
Variable Name:javax.faces.ViewState

No initial values.
c) Regular Expression Extractor #1

Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor

Name:adf.ctrl-state Extractor
Response Field to Check:Body
Reference Name:adf.ctrl-state
Regular Expression:_adf.ctrl-state=([-_0-9A-Za-z!]{10,13})
Template:$1$
Match No.:1
Default Value:n/a

Take notice that the variable name does not include an underscore in it's name unlike the actual parameter _adf.ctrl-state.

d) Regular Expression Extractor #2

Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor

Name:afrLoop Extractor
Response Field to Check:Body
Reference Name:afrLoop
Regular Expression:_afrLoop=([-_0-9A-Za-z]{13,16})
Template:$1$
Match No.:1
Default Value:n/a

Take notice that the variable name does not include an underscore in it's name unlike the actual parameter _afrLoop.

d) Regular Expression Extractor #3

Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor

Name:afrWindowId Extractor
Response Field to Check:Body
Reference Name:afrWindowId
Regular Expression:window.name='([-_0-9A-Za-z!]{10,13})'
Template:$1$
Match No.:1
Default Value:n/a

e) Regular Expression Extractor #4

Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor

Name:javax.faces.ViewState Extractor
Response Field to Check:Body
Reference Name:javax.faces.ViewState
Regular Expression:<input type="hidden" name="javax\.faces\.ViewState" value="!(.+?)">
Template:$1$
Match No.:1
Default Value:n/a

f) Regular Expression Extractor #5

Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor

Name:jsesssionId Extractor
Response Field to Check:Body
Reference Name:jsessionId
Regular Expression:;jsessionid=([-_0-9A-Za-z!]{63})
Template:$1$
Match No.:1
Default Value:n/a

Recorded HTTP Request Changes

After recording an ADF session in JMeter, and setting up the variables above, you'll note in the recorded HTTP Requests numerous references to the same named variables as HTTP parameters. Yet the recorded HTTP Requests include the parameters' values for the recorded session, which simply won't work when we replay the session as the values will have changed.

It is therefore necessary to identify all locations where the parameters exist in the recorded session HTTP Requests and substitute references to your variables.

a) HTTP Request – Path – any path that makes reference to jsessionId must be changed.

ie: /SageApp/faces/ViewEvents;jsessionId=${jsessionId}


b) HTTP Request – Path – ditto any path that makes reference to _adf.ctrl-state must be changed.

ie. /SageApp/faces/ViewEvents?_adf.ctrl-state=${adf.ctrl-state}


You can also change this in the name if you want but it wont make a different to the overall result, the name is for show only.

c) HTTP Request – Parameters – for any of the following parameters:

_adf.ctrl-state
_afrLoop
_afrWindowId (* see exception at the end of this step)

...for their values substitute the relevant variable. The following example shows the values for the afrLoop and adf.ctrl-state replaced:


However there is one exception with _afrWindowId. In one of the initial requests its value will be the literal string "null" (see the picture in step (a) for an example). Do not replace this, leave it as null.

d) HTTP Request – Parameters – for the "javax.faces.ViewState" parameter, ditto replace its values. Note it is necessary to put a not (negate) symbol in front of it:

ie. !${javax.faces.ViewState}

In addition by default the "Encode?" parameter will be on for each javax.faces.ViewState. Instead turn it off for each:


e) HTTP Request – Parameters – for the "unique" parameter, occurring in the Path and request parameters replace it with the following notation:

${__javaScript(new Date().getTime(),DUMMY)}

Note there is no JMeter user variable for this, we just write it straight into the unique value.

f) HTTP Header Manager Referer – if the HTTP session has been recorded via JMeter's HTTP Proxy, each HTTP Request will include a sub-element HTTP Header Manager that includes an element called Referer. You'll note it will have hardcoded many of the parameters discussed above. However I don't think ADF requires the Referer element and you can just ignore it or delete it from every HTTP Header Manager.

Assertions

Once you've completed the above setup and want to run your load test via JMeter, it is important to see that the test is working correctly. One way to do this is to create a View Results Tree (Test Plan -> Thread Group -> Right Click -> Add -> Listener -> View Results Tree) that allows you to inspect the response of each HTTP Request. If you then see HTTP Request responses in red this indicates a HTTP error code response (!= 200). However it's also necessary to inspect each response even if they are green to see if there's a valid response returned carrying an error message. There is definitely a small knack to differentiating between the different type of failures in an ADF application, which you'll become familiar with on some experience.

In order to save you having to look for the same problems for each test-run, you can create a Response Assertion (Test Plan -> Thread Group -> Add -> Assertions -> Response Assertion) to look for specific errors in the responses. From my own ADF experience you want to setup a Response Assertion with the following values:

Which Samples to Test:Main Sample Only
Response Field to Test:Text Response
Pattern Matching Rules:Contains
Not:Selected
Patterns to Test #1:The following exception occurred
Patterns to Test #2:Server Exception during PPR
Patterns to Test #3:ADFC-12000
Patterns to Test #4:java.net.SocketException
Patterns to Test #5:java.io.IOException

This isn't a definitive list, and in turn there may be specific errors that you may want to pick up for your application.

Sample JMeter file

Readers can download a sample JMeter JMX file for ADF here with the configurations already setup.

Finishing Comment

I haven't had the chance to test this in every ADF scenario or application. It's possible in your application, dependent on the components you're using or some other trick of ADF, that I've missed one of the state values it wants the browser to carry. With the post above hopefully you'll get an idea of the majority and be able to identify more. I'd appreciate it if anybody finds more that we need to cater for please post a comment with your findings to assist other readers.

18 comments:

nonymouse said...

Great post, very useful. I had to change the regex for _afrLoop to be 17 chars as it was losing the last digit.

I'm using the Fusiom Order Demo, I have the home page working but currently get "Because of inactivity, your session has timed out and is no longer active. Click OK to reload the page" when I try to interact with the home page. Any ideas?

Amanda

Chris Muir said...

Hi Amanda

Thanks for the feedback. I'll double check the field sizes on Tuesday and make sure I haven't made a transcribe error.

Regards the error you're seeing, this can happen for 2 reasons:

1) The most common cause for me is when I miss substitute one of the variables, or I've incorrectly typed the variable expression. For example on Friday I made the mistake of specifying the java.faces.ViewState variable as !{javax.faces.ViewState}, forgotting the $ symbol.  This resulted in the error you're seeing.

Identifying where the mistake took place requires you to:

A) run the thread group for exactly one session, this allows you to focus on one session's set of requests over the much more confusing concurrent user output

B) in the results tree, work backwards up the requests until you find the first error. At some point in that specific request or earlier (ie you've now eliminated all the previous results) the variable substitution has failed. You can all ignore all requests for images, Javascript files and .css as these don't push state variables about.

C) again in the tree carefully look at each remaining request and see if in either the URL or POST param set the variables have been correctly substituted. The easiest mistake to identify is when you can see the actual variable name sitting in the request, implying there's a typo in the original request where you specified the variable syntax, and as a result it'll just substitute the variable name. The more difficult one is as you described with the afrLoop where you can see that a value has been sustituted but part of the value was lost.

I was trying to think of a scientific way of describing how to ensure these problems didn't occur in the original post, but couldn't come up with anything worthy IMHO. I figured in the end the post would spark enough inspiration for most to understand the technique and run with it. Hopefully this detailed comment will also help.

2) as described in the original post the second failure point maybe a state variable I've missed. For example we use no DVT controls, and given they are often rendered in Flash, I bet I missed something there. This solution was tested with one of our own apps, not Oracle's. As such if you identify something I haven't covered it'd be appreciated if you could post a follow up comment.  As a point I'll try and get around to testing the SRDemo too, but my time is fairly limited so no gaurantees.

I hope all the above makes sense, I've been pecking this in with my iPhone, not the best wordprocrssing environment.

Cheers,

CM.

akhanof said...

Thanks Mr. Muir,
A very useful post regarding testing, I really appreciate it.

Regards,

GEBS said...

Hi,

I've tried to configure jmeter according to your post, but the Response Data in a View Results Tree is displayed incomplete (only the menu and the rest of the body is missing).

Do you have any suggestions?

Thanks in advance,
Dorin

Chris Muir said...

Hi Dorin

Do you mean the raw HTML file returned is incomplete, or when rendering the HTML it doesn't display correctly in JMeter?

I'm not sure what's going wrong if you're experiencing the former issue, but if you mean the later, JMeter isn't a fully fledged browser. As such JMeter doesn't really bother to render HTML correctly at all (as far as I know it ignores CSS altogether), and in turn doesn't process JavaScript either. Ultimately JMeter is best as a load/stress testing tool.

An alternative to JMeter is "Badboy" (http://www.badboy.com.au) which is built upon the JMeter engine, and takes specific care to render the results correctly. If memory serves me correctly you can import JMeter projects into Badboy, or vice versa (sorry, it's been awhile since I've used it).

Hope this helps. Drop me an email directly if you want more assistance (see http://www.sagecomputing.com.au/contact_sage_computing_services.html)

Btw, excellent work on the GEBS blog

CM.

GEBS said...

Hi Chris,

Thanks for your usefull and quickly reply. After some work around I've managed to fix my problem.

In my case the HTML pages were displayed corectely and the jspx not. For the jspx pages:
- I've added and configured an HTTP Header Manager
- I've changed a little the definition of the jsessionid Extractor like ;jsessionid=([-_0-9A-Za-z!]{62,63})
- I've included the j_security_check request in the Thread Group to pass the security and to perform a fully testing cycle

Best regards,
Dorin

Chris Muir said...

Hi Dorin

Thanks for the update, that'll help others readers too.

Question for you, what was the specific need for the HTTP Header Manager?

CM.

GEBS said...

Hi Chris,

Initially I've added an HTTP Request manually by specifying the path and the send parameters. In this case, HTML pages were displayed corectelly in the response data section and the jspx were incomplete.

Then I've recorded in a thread group a scenario from the browser and I saw that each request has as a child a HTTP Header Manager, so I copied that to my manually created item. After this operations the jspx pages were displayed corectelly.

I'm going to create a demo Test Plan and I'll post it for other interested about this topic.

Anyway your post was great and helped me a lot configuring jmeter in adf.
Dorin.

William said...

Great tutorial - it helped quite a bit; however, I have something to add when it comes to modals/subflows. I won't to too far into the details - if the reader has made it this far, they should know be able to pick up the context.

Our application had a few nested taskflows that popped up as modals. In order to get the JMeter script up and running, we had to do two things for each modal:

1) Read the "parent" javax.faces.ViewState (most recent one before one is assigned to the modal) and store it for re-use on the request following closing of the modal.
2) Each taskflow in ADF gets it's own adf.ctrl-state. Make sure that you pay attention to when one is assigned for the child taskflow and use/manage it for the entire child taskflow. Then go back to the parent one when you're done.

Chris Muir said...

Hi William

Thanks for the follow up, I appreciate that you wanted to share your findings with readers.

Regards,

CM.

crossin said...

Hi,

Great article. Could you advise hot to set Extractor for HTTP Parameter rtrnId (_rtrnId)? The test results captured showed that is was a 13-digit long numeric string so I set it similar to adf.ctrl-state but it didn't seem to work. Any insight?

Thanks,
Jonathan

Chris Muir said...

Hi Jonathan

Can you let me know which specific version of JDev your using please, as I'm not familiar with that parameter?

Cheers,

CM.

Suresh Karunarathne said...

I followed this nice article thanx Chris Muir ..
How i test following scenario
In my adf application i have a page with a input text and a search button.After i pressed search button show queried data in same page.
How i test this scenario using JMeter for various value passing by the input text.

Thanks for reading
Jdev 11.1.1.3
JMeter 2.4

Chris Muir said...

Refer to the JMeter manuals for creating and populating variables.

CM.

Juliano said...

Dear Chris Muir,

I read this post and was very usefull. I followed your hints and can move on. Thank you.

Now, I have to create tests for a Oracle BPM aplication. I can login, and acess the pages, but, when a execute the tests, some actions don't work. For exemple, I click in a link to create a new item and the new itens is not create.

Did you already use jMeter to execute tests with Oracle BPM?

Chris Muir said...

Sorry Juliano, I haven't.

CM.

Chris Muir said...

Since 11.1.1.6.0, it appears on some requests it's now important to include a HTTP Header Manager "User-Agent" value such as "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5" on each request. Without this subsequent responses (sometimes not the next response but 1 or 2 down the request/response chain) will fail with the usual "user inactivity" message.

CM.

Mostafa Taha said...

thank you very much :) this was very helpful