Sunday, December 12, 2010

LoadRunner script for SOAP over JMS to test TIBCO Middleware

Following is a script that I created to test SOAP message over JMS using SOA protocol. I would suggest using JAVA protocol to script SOAP over JMS rather than SOA protocol. It provides greater flexibility than SOA protocol. There is a good blog written by Stuart Moncrieff and I suggest reading it if you are planning on using JAVA protocol to test SOAP message over JMS. I don't want to reinvent the wheel by writing a new code. You will require some modification to the Java script. For example, if you are using username and password, you will need to pass these values into the createConnection function.

It will look like this(It is modification of Stuart's code):
queueConnection = queueConnectionFactory.createConnection(username,password);

Before you starting writing your script using either SOA or Java protocol, you will need few files and information and they are:

1: Tibjms.jar -compulsory
2: Java 1.6 -compulsory (older version may work, but I tested against 1.6)
3: JNDI initial context factory -compulsory
4: JNDI provider URL - compulsory
5: JMS connection factory - compulsory
6: JMS security credentials - compulsory, if implemented
7: JMS security principal - compulsory, if implemented
8: Request queue name - required, if you are sending message to the request queue
9: Receive queue name - required, if you are receiving message from the receive queue

You will need to get information for points 3-9 from your JMS administrator or whoever may have implemented JMS.

SOA Protocol script
The script was meant to test TIBCO with JMS Fire and forget pattern. No response was expected from TIBCO because the test was to find out whether the request queue is able to handle the expected requests per hour. Therefore, I did not require Receive queue name details.

Following details were set in the Run-time setting-> JMS advance option. For points 2-5, I have used dummy names.

1:JNDI initial context factory :com.tibco.tibjms.naming.TibjmsInitalContextFactory //testing TIBCO JMS. This is a dropdown option and might change depending on what you are testing.
2:JNDI provider URL :tibjmsnaming://tim.tom.au:12345
3:JMS connection factory :QueueConnectionFactory
4:JMS security credentials :jmsPerTester
5:JMS security principal :jmsPerTester

SOAP request
char *EmployeeDetails=
""
""
"Tom"
""
"110 Latrobe st"
"Melbourne"
"3000"
"Vic"
"1234567890";

Action()
{
long currentTime=1; 
char *ExpiryTime; 

//get current time in seconds
currentTime=time(¤tTime); 

currentTime = currentTime + 500000; 

ExpiryTime = (char*)malloc(20, sizeof(char)); 

//save current time value into ExpiryTime variable. This is how long the message should be valid for
sprintf(ExpiryTime,"%d",currentTime); 

//Save EmployeeDetails SOAP request into LR parameter
lr_save_string(EmployeeDetails,"EmployeeDetailSOAP_param");

//lr_output_message ("%s",lr_eval_string ("{EmployeeDetailSOAP_param}"));


//setup JMS properties
jms_set_general_property("msgJMSMessageType","JMS_MESSAGE_TYPE","BytesMessage"); //Change 'ByteMessage' to 'TextMessage', if you are sending as a text
jms_set_message_property("msgJMSExpiration","JMSExpiration", ExpiryTime); 
jms_set_message_property("msgJMSMessageID","JMSMessageID", "-JMSMessageID-");
jms_set_message_property("msgJMSPriority","JMSPriority", "4"); 
jms_set_message_property("msgJMSRedelivered","JMSRedelivered", "false");
jms_set_message_property ("msgJMSDeliveryMode","JMSDeliveryMode","2");
jms_set_message_property("msgJMSCorrelationID", "JMSCorrelationID", "VuserID-{VUserID}_{TimeDate}"); //VUserID & TimeDate are loadrunner parameters


//sending message
lr_start_transaction ("SoapRequest"); 
jms_send_message_queue("step 1: Sending SOAP message","{EmployeeDetailSOAP_param}", "EmployeeDetail.request.queue.com.au");  


//Receive message
/*jms_receive_message_queue("step 2: Received SOAP message", "EmployeeDetail.response.queue.com.au"); 
lr_message(lr_eval_string("{JMS_message}")); */

lr_end_transaction ("SoapRequest",LR_AUTO);

free(ExpiryTime);
return  0;
}

Note:In the above code I am using lr_start_transaction function to capture total number of requests posted to the queue. It does not measure the response time. This is because we are not expecting any response due to Fire and Forget pattern.

You could use file(/char array) for the SOAP request as well as parametrize the SOAP content. I will leave this as an exercise for anyone who wants to try it out.

Tuesday, December 7, 2010

Setting JMS DeliveryMode in LoadRunner

I have been scripting SOAP over JMS using LoadRunner SOA protocol and had forgotten that LoadRunner uses value associated with Delivery mode "PERSISTENT" or "NONPERSISTENT"/"NON_PERSISTENT".

Below is the screenshot of what loadRunner Script code & log looked when I used "NONPERSISTENT" text as my parameter in jms_set_message_property function. If you don't know the Deliverymode value, you may think value "2" is for "NONPERSISTENT" mode but that is incorrect as seen in HermesJMS(see following screenshot).






After I updated the function by passing the correct DeliveryMode value, everything looked great as seen from LoadRunner log and HermesJMS.





If you want JMS delivery message to be "PERSISTENT" then you have to use value "2" and for "NONPERSISTENT" message you need to use value "1" in your jms_set_message_property function.

Therefore your jms_set_message_property function will look like this:

jms_set_message_property("MsgDeliveryMode","JMSDeliveryMode","1"); //NONPERSISTENT msg

jms_set_message_property("MsgDeliveryMode","JMSDeliveryMode","2"); //PERSISTENT msg

Thursday, December 2, 2010

SAXParseException JMSQueue

If your queue is set up to receive JMSMessage type as ByteMessage and you are sending TextMessage, you may receive SAXParseException message. See the screenshot below.



I have been using HermesJMS application to view Tibco JMS queues during SOAP over JMS scripting. It allowed me to fix issues that I encountered during scripting.

You can download HermesJMS application and follow TibcoJMS blog by Serge on how to connect HermesJMS to TibcoJMS queue.

Tuesday, November 30, 2010

IE proxy setting in Rational Performance Tester(RPT)

If you haven't used RPT before, you may occasionally encounter problem recording your network traffic. This might be due to your proxy setting on IE. The issue will normally occur before you even start to record.

If you are not recording traffic through proxy server(connecting directly to internet), make sure you have following options unchecked in IE (LAN Setting).

1: Automatically detect settings
2: Use automatic configuration script
3: Use a proxy server for your LAN




If you want to record using a proxy setting. I suggest using proxy server and port option rather than using proxy.pac file. This is what the IE setting should look if you are trying to record via a proxy server.



Make sure you are not changing these setting while you are trying to record. The changes may not come into effect when you try to record again. Therefore make these changes before you start to record.

Thursday, November 25, 2010

Common error messages for SOAP over JMS using SOA protocol in LoadRunner 11.0

While testing SOAP over JMS using SOA protocol in LoadRunner, you may come across different error messages if you have not configured JMS parameters correctly in LoadRunner. Below are some common error that you may encounter and what could the cause of these error messages.

1: Wrong queue name

Error: Failed to send message ...SOAP details go here...to test.test.test.address.update due to the following exception : javax.naming.NameNotFoundException: Name not found: 'test.test.test.address.update'
javax.naming.NameNotFoundException: Name not found: 'test.test.test.address.update'
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:713)
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:489)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
at com.mercury.ws.jms.SessionManagerImpl.getQueue(SessionManagerImpl.java:94)
at com.mercury.ws.jms.JMSSupportImpl.sendMessageQueue(JMSSupportImpl.java:96)
at com.mercury.ws.jms.JMSBridge.send_message_queue(JMSBridge.java:43)



2: Incorrect port number for your JNDI provider URL

Error: Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : javax.naming.InvalidNameException: Supplied URL (xxxxx:342434) contains an invalid port number: Invalid port number
javax.naming.InvalidNameException: Supplied URL (xxxxx:342434) contains an invalid port number: Invalid port number
at com.tibco.tibjms.naming.TibjmsNamingEnvUtil._parseURL(TibjmsNamingEnvUtil.java:184)
at com.tibco.tibjms.naming.TibjmsNamingEnvUtil.parseURL(TibjmsNamingEnvUtil.java:263



3: You may get following error message incase you have entered incorrect credentials (username/&password)


Error: Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : javax.naming.AuthenticationException: Not permitted: invalid name or password [Root exception is javax.jms.JMSSecurityException: invalid name or password]
javax.naming.AuthenticationException: Not permitted: invalid name or password [Root exception is javax.jms.JMSSecurityException: invalid name or password]
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:668)
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:489)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
at com.mercury.ws.jms.ConnectionManagerImpl.initialize(ConnectionManagerImpl.java:99)
...5 more



4: You may get following error message incase you have setup an Incorrect JNDI URL

Error: Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : javax.naming.ServiceUnavailableException: Failed to query JNDI: Failed to connect to the server at tcp://xxxx.xxx.xx:12345 [Root exception is javax.jms.JMSException: Failed to connect to the server at tcp://xxxx.xxx.xx:12345]
javax.naming.ServiceUnavailableException: Failed to query JNDI: Failed to connect to the server at tcp://xxxx.xxx.xx:12345 [Root exception is javax.jms.JMSException: Failed to connect to the server at tcp://xxxx.xxx.xx:12345]
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:669)
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:489)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
... 5 more



5: You may get following errors incase you haven’t set up ConnectionFactory or it is incorrect.


No ConnectionFactory
Error:Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : java.lang.ClassCastException: com.tibco.tibjms.naming.TibjmsContext
java.lang.ClassCastException: com.tibco.tibjms.naming.TibjmsContext
at com.mercury.ws.jms.ConnectionManagerImpl.initialize(ConnectionManagerImpl.java:109)
at com.mercury.ws.jms.JMSSupportImpl.initialize(JMSSupportImpl.java:28)
at com.mercury.ws.jms.JMSBridge.init_jms(JMSBridge.java:154)


Incorrect ConnectionFactory Name
Error:Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : javax.naming.NameNotFoundException: Name not found: 'xxxxQueueConnectionFactory'
javax.naming.NameNotFoundException: Name not found: 'xxxxQueueConnectionFactory'
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:713)
at com.tibco.tibjms.naming.TibjmsContext.lookup(TibjmsContext.java:489)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
at com.mercury.ws.jms.ConnectionManagerImpl.initialize(ConnectionManagerImpl.java:99)
at com.mercury.ws.jms.JMSSupportImpl.initialize(JMSSupportImpl.java:28)
at com.mercury.ws.jms.JMSBridge.init_jms(JMSBridge.java:154)



6: You may get following error message if you have Incorrect Initial Context Factory name (For example, it is expected that you use com.tibco.tibjms.naming.tibjmsinitialcontextfactory but by mistake you selected weblogic.jndi.WLinitialContextFactory from InitialContectFactory dropdown list in LR)

Error: Failed to set property name JMS_MESSAGE_TYPE value BytesMessage due to the following exception : javax.naming.NamingException: Cannot parse url: tibjmsnaming://xxxx.xxx.xx:12345 [Root exception is java.net.MalformedURLException: Not an LDAP URL: tibjmsnaming://xxxx.xxx.xx:12345]
javax.naming.NamingException: Cannot parse url: tibjmsnaming://xxxx.xxx.xx:12345 [Root exception is java.net.MalformedURLException: Not an LDAP URL: tibjmsnaming://xxxx.xxx.xx:12345]
at com.sun.jndi.ldap.LdapURL.(LdapURL.java:77)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:146)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:247)
at javax.naming.InitialContext.init(InitialContext.java:223)
... 11 more



Let me know if you encounter other error messages using LoadRunner or any other tool for SOAP over JMS.

Thursday, November 18, 2010

Pacing code using Openscript application

OATS(Oracle application testing suite) comes with an OpenScript application, which is used for creating the load scripts. The application is based on Eclipse IDE and uses Java for scripting. During my analysis of the tool, I could not find an option to set pacing and therefore wrote a Java code to do so. 

In load testing tool such as LoadRunner, you can set the passing through Run-Time Setting option.

//Import these file for date, random number etc
import java.util.Random;
import java.util.Date;
import java.util.*;
import java.text.*;
import java.io.*;

import oracle.oats.scripting.modules.basic.api.internal.*;
import oracle.oats.scripting.modules.basic.api.*;
import oracle.oats.scripting.modules.http.api.*;
import oracle.oats.scripting.modules.http.api.HTTPService.*;
import oracle.oats.scripting.modules.utilities.api.*;
import oracle.oats.scripting.modules.utilities.api.sql.*;
import oracle.oats.scripting.modules.utilities.api.xml.*;
import oracle.oats.scripting.modules.utilities.api.file.*;


public class script extends IteratingVUserScript {
@ScriptService oracle.oats.scripting.modules.utilities.api.UtilitiesService utilities;
@ScriptService oracle.oats.scripting.modules.http.api.HTTPService http;
/************** LOCAL VARIABLES FOR THE SCRIPT************************/
static int MIN=90; //min percentage think time 90%
static int MAX=110; //max percentage think time 110%
static float Pacing=(float) 100.0; //expected iteration completion time


/************this function generates a random time between 90% and 110% of the recorded time**************/
long calRandtime(int Rectime, int minPerc,int maxPerc)
{
Random aRandom= new Random();
int range = (int)(Rectime*(maxPerc-minPerc)/100) + 1;
// compute a fraction of the range, 0 <= frac < range
int fraction = (int)(range * aRandom.nextDouble());
long randomNumber = (long)((fraction + (Rectime*maxPerc/100))*1000);
System.out.println("The calculated think time is " + randomNumber/1000);
return (randomNumber);
}

public void initialize() throws Exception {

}

public void run() throws Exception
{
beginStep("ALL");
{
long now = System.currentTimeMillis(); //Get current time in milliseconds
System.out.println(now); //print out time in milliseconds

Thread.sleep(calRandtime(20,MIN,MAX)); //sleep the thread for random calculated time
beginStep("Step1");
{
System.out.println("Transaction 1 completed");
}
endStep();

Thread.sleep(calRandtime(10,MIN,MAX));
beginStep("Step2");
{
System.out.println("Transaction 2 completed");
}
endStep();

Thread.sleep(calRandtime(5,MIN,MAX));
beginStep("Step3");
{
System.out.println("Transaction 3 completed");
}
endStep();

long diff = System.currentTimeMillis()- now; // calculate how long it took to execute the code
float seconds= diff/1000.0f; //convert milliseconds into seconds
System.out.println("It took " + seconds+" seconds to execute the code"); //print out how long it took ti execute the code

if(seconds<=Pacing) //check if execution time is less than expected pacing time
{
System.out.println("Going to sleep for");
System.out.println(Pacing-seconds); //calculated second for which the code needs to sleep
Thread.sleep((long)(Pacing-seconds)*1000);
System.out.println("Finished sleeping");
}
}
endStep();
}

public void finish() throws Exception {
}
}
The code is self explanatory. Following is an execution of the above code as capture in console window of this tool.

Monday, November 15, 2010

Pacing issue with LoadRunner 9.5

One of my collegue discovered an issue with pacing in LoadRunner 9.5. It seems that if there is a pacing applied to the script then the VU waits for sometime after completing vuser_init function before executing Action function.

To confirm the issue he created following script with a randomized pacing between 90-120sec.
vuser_init()
{
 lr_output_message (lr_eval_string("init: {Timestamp}"));

 return 0;
}
 
Action()
{
 
 lr_output_message (lr_eval_string("Iteration: {Iteration} {Timestamp}"));

 return 0;
}
 
vuser_end()
{
 
 lr_output_message (lr_eval_string("end: {Timestamp}"));

 return 0;
}

Following is the output from Replay Log.



Based on the timestamp between vuser_init and iteration 1 we know that 60 seconds of pacing was applied even though the replay log does not show 'Waiting ## seconds for iteration pacing.' at the end of 'vuser_init'.

What this means is that, if you have a long pacing the vu will not generate load for a while eventhough LR scenario say's that the VU is in run state.

One way to overcome this issue is to write your own pacing code and setting pacing in run-time setting as "As soon as the previous iteration ends". See the Replay log after adding pacing code and changing the runtime setting. In this case, the iteration is expected to finish in 105seconds.



I haven't tested this issue in LR11. Let me know if this issue still exists in LR11.

Sunday, November 14, 2010

vmstat tool for OpenSolaris

I have been interested in learning different tools that are available with Solaris OS. One of these tool is vmstat.

vmstat provides you a one line overall system behavior. I suggest starting out with vmstat tool to understand system behavior before using more detailed tools such as DTRACE.

vmstat provides you a detail on runnable threads, memory, disk, faults, cpu utilization as shown below.



Following definitions have been taken from "Solaris Performance and Tools" book by Richard McDougall.

Counters
kthr:r -> Total number of runnable threads on the dispatcher queue. It provides information on CPU saturation.

faults:in -> number of interrupts per second
faults:sy -> number of system calls per second
faults:cs -> number of context switches per second, both voluntary and involuntary

cpu:us ->percent user time
cpu:sy -> percent system time
cpu:id ->percent idle time


Description
vmstat 3 10 -> means display vmstat information 10 time with a 3 second interval.

For me the most important counters are kthr:r, kthr:b and CPU id.

Bit of explanation on the vmstat picture.
1: First line is a summary since boot.

1: There are no threads waiting in the queue to get CPU time slice. kthr:r = 0. If this value is more than 2 and sustained on a single CPU server then it indicates that CPU saturation.

2: There are no threads waiting on a resource. kthr:b = 0. If this value is more than 0, it might suggest that we may have a bottleneck somewhere. You need to look at these counter over a long duration before making any conclusion.

3: On average, CPU is spending 1% of its time servicing user-mode threads, 2% servicing kernel threads and 97% ideal.

You can save vmstat to a text file from command line for further analysis as follows. To make my log file unique I normally use datetime and pid values:

vmstat [interval] [total count] >>~/[location where to save]/vmstat_`date +%y%m%d_%H%M%S`_$$.txt

i.e.
vmstat 5 5 >>~/Desktop/PerfTools/vmstat/vmstat_`date +%y%m%d_%H%M%S`_$$.txt
This will save a file as vmstat_101111_135557_824.txt in location /Desktop/PerfTools/vmstat/.
This file will consist of 5 vmstat records with 5 second interval between each record.

vmstat 5 >>~/Desktop/PerfTools/vmstat/vmstat_`date +%y%m%d_%H%M%S`.txt
This file will continue saving the data indefinitely with 5 second interval.

Saturday, November 13, 2010

Report generation code in Load Runner 9.5

Recently was working on a LoadRunner script that needed to generate and download a report and the requirement was that it should not take more than 5 minutes to generate the report.

Manually testing the report generation, I noticed that on completion a "Report Generated" text wa displayed. Therefore, I wrote following LR Code to wait for report to generate and raise an error message if it takes more than 5 minutes to generate the report.

ReportGeneration()
{
int ReportGenerationTimeout = 300; // 5 minutes report gen timeout
long StartTime;
...
time(&StartTime); //save the time into StartTime
do
{ //execute this code while Report Generated Text is not found
lr_think_time (5);

web_reg_find("Text=Report Generated",
"Search=Body",
"SaveCount=ReportGeneratedCount",
LAST);

web_submit_data("GenerateReport",
"Action={URL}/xxx/xxx.xx",
"Method=POST",
"RecContentType=text/html",
"Referer={URL}/xxx/xxxcc.xx",
"Snapshot=t5.inf",
"Mode=HTML",
ITEMDATA,
"Name=ContinueButton", "Value=Continue", ENDITEM,
LAST);

if ( (time(NULL) - StartTime) > ReportGenerationTimeout) //check if the report generation time is more than 5 minutes
{
lr_error_message("Report took more than 5 minutes to generate. The user id is ", lr_eval_string("{UserName}"));
Logout(); //execute logout function
lr_exit(LR_EXIT_ITERATION_AND_CONTINUE,LR_AUTO); //exit the current iteration
and start next one
}

} while (atoi(lr_eval_string("{ReportGeneratedCount}")) == 0);
...
return 0;
}

Description:
- The ReportGeneration function defines two variables, ReportGenerationTimeout and StartTime.
-ReportGenerationTimeout is the report generation maximum time
-StartTime saves the time before the code that generates the report is executed.

- {URL} and {UserName} are LR parameters.

-The while loop checks for the text ="Report Generated" from the server response. It continues executing the while loop until it either gets the text or 5 minutes timeout limit is reached.

-If it finds the text then it exits the loop and continues executing rest of the code.
-If the report is not generated within 5 minutes (IF condition) then log an error message with username for whom it did not generate the report, execute Logout function and exit the current iteration.

-time(NULL) function return the current time. For more on time function refer to
http://www.cplusplus.com/reference/clibrary/ctime/time/

Note:
-if you use lr_abort() function rather then lr_exit(...), you will end the execution. This will execute Vuser_end function. Depending on how you have set up your scenario you may want to stop the execution of script(lr_abort - this will stop virtual users and your load level will drop, that is what you want) or continue to next iteration(lr_exit) when the report timeout is met.

-You could also use lr_log_message() instead of lr_error_message(). This will avoid overloading the network. However it will depend how often you are sending the message. For more information refer to LR help on these functions.

Friday, November 12, 2010

Command + fn + F10 shortcut key for "Run Step by Step" LoadRunner function on Mac

When running LoadRunner on a Windows OS, the shortcut key to execute "Run Step by Step" function is F10 (Assuming you are running LR on a physical windows machine and not on a windows VM).

However, if you are running Window LR VM on a Mac Laptop, the shortcut key is "Command + fn + F10".