Few weeks ago, I was working for a client and I wanted to analyze Websphere Application server verboseGC logs. I could have used tools such as IBM PMAT but what I really wanted was to merge verboseGC graph with response time graph in Load Runner and this required a lot of manual work. Therefore, I modified existing Silkperformer vbscript to Websphere verboseGC vbscript.
For the blogging purpose, this script saves only handful of verboseGC attributes into CSV file. You can then import the csv file as an external monitor in Load Runner Analysis tool.
NOTE: You can modify this script to suit your requirements. Also make sure everything is contained within verbosegc tag in your verboseGC log file. See the example below.
Please read the following blog on how to generate the CSV file.
Websphere verboseGC Vbscript
Option Explicit
Dim xmlDoc
Dim af, gc, timestamp, gcIntervalms, DateTimeArray, afDate, afTime, afIntervalms,minimum,requestedBytes,Totalms,gcTotalms,gcTotal,Time
Dim reportFile, outputFile
Dim myFSO, fileHandle
'Murray Wardle code
Set xmlDoc = CreateObject("Microsoft.XMLDOM")
Const ForReading = 1, ForWriting = 2, ForAppending = 8
If Wscript.Arguments.Count = 0 Then
msgbox "Please specify the overview Report file to process"
Else
' Get report file name & set output filename
reportFile = Wscript.Arguments(0)
outputFile = Left(reportFile, Len(reportFile)-3) + "csv"
xmlDoc.async = false
xmlDoc.SetProperty "SelectionLanguage", "XPath"
xmlDoc.SetProperty "ServerHTTPRequest", True
xmlDoc.validateOnParse = False
xmlDoc.resolveExternals = False
'load overview report
xmlDoc.load(reportFile)
xmlDoc.setProperty "SelectionLanguage", "XPath"
'open csv file to dump results into
Set myFSO = CreateObject("Scripting.FileSystemObject")
Set fileHandle = myFSO.OpenTextFile(outputFile, ForWriting, True)
'Modified code for verboseGC
fileHandle.WriteLine("date,time,afIntervalms,requestedBytes,gcIntervalms,gcTotalms,Totalms")
For Each af In xmlDoc.SelectNodes("//af")
timestamp = af.getAttribute("timestamp")'get af timestamp attribute value
DateTimeArray=Split(timestamp," ",-1,1) 'split the date time into array
afDate=FormatDateTime(DateTimeArray(1)+"/"+DateTimeArray(0)+"/"++DateTimeArray(3),vbShortDate) 'format into date
afTime=FormatDateTime(DateTimeArray(2),vbLongTime) 'format into time
afIntervalms=af.getAttribute("intervalms") 'get af intervalms attribute value
For Each minimum In af.SelectNodes("./minimum") 'get minimum requested bytes value
requestedBytes = minimum.getAttribute("requested_bytes")
Next
For Each gc In af.SelectNodes("./gc") 'get gc intervalms value
gcIntervalms = gc.getAttribute("intervalms")
Next
For Each gcTotal In af.SelectNodes("./gc/time") 'get total gc time value
gcTotalms = gcTotal.getAttribute("totalms")
Next
For Each Time In af.SelectNodes("./time")
Totalms = Time.getAttribute("totalms") 'get total time value
Next
fileHandle.WriteLine(afDate+","+afTime+","+afIntervalms+","+ requestedBytes+","+ gcIntervalms+","+gcTotalms+","+Totalms)
Next
fileHandle.Close
end if
Scenario:
You are using same parameter(s) in more than one script and you don't want to manually create them again for each script. So how do you go about copying the parameters from an existing script.
For example:
The following parameters have already been manually created in Script 1 and same parameters are also required for Script 2.
DateTime
IterationNo
RandNo
VuserID
Rather than manually creating them, you can copy these parameters from Script 1 and this is how you do it.
Steps:
1: Navigate to Script 1 folder and open file that ends with "prm" extension. This file defines all the parameters and their attributes.
2: Copy the parameters & their attributes and paste into the parameter file of script 2.
3: Save the parameter file. Now open script 2 and navigate to parameter list. You will now noticed that the parameters have been successfully copied.
There have been multiple times where I needed to generate UUID (Verion 4) in Loadrunner but there is no inbuild function to do so. Therefore, I had to either create or modify functions to satisfy my need.
NOTE: Loadrunner does have a function(lr_generate_uuid) for UUID but it does not generate your standard UUID.
Below are three different UUID functions that I normally use depending on the requirement.
For example, If only requirement is that UUID be 32 hexadecimal digits then I will use simple LR UUID to generate it. If there are proper checks in place to see the UUID generated is valid then I will use a proper UUID function's. Therefore you can select which one you want to use depending on your requirement.
Simple UUID - this is a simple 32 hexadecimal digits. You can generate hexaadecimal in Loadrunner using %x or %X Random number.
Following screenshot shows how to generate a random Hexadecimal.
Lr UUID - this function generates 32 hexadecimal digits UUID using Version 4(random) variant. This is the most common format I have seen applications use.
Win UUID - this function uses windows inbuild CoCreateGuid function (ole32.dll). The original code is written by Scott Moore and I have modified it a little bit.
NOTE: I have left the deallocation of pointer for you to do in the code.
#define Hexlength 50 //Max length
char *lr_guid_gen(); //explicitely declare the function
char *lr_uuid_gen(); //explicitely declare the function
Action()
{
char *Win_UUID=0; //declare window function UUID variable
char *slr_UUID=0; //declare loadrunner function UUID variable
char *lr_uuid; //declare UUID variable
Win_UUID=(char *)malloc(sizeof(char)); //allocate dynamic memory
slr_UUID=(char *)malloc(sizeof(char)); //allocate dynamic memory
Win_UUID=lr_guid_gen(); //execute Window UUID function
slr_UUID=lr_uuid_gen(); //execute LR UUID function
lr_uuid=lr_generate_uuid(); //assign result generated by loadrunner internal
function to lr_uuid
/*output a simple UUID*/
lr_output_message("smp_UUID: %s",lr_eval_string("{sHex}{sHex}-{sHex}-{sHex}-{sHex}-{sHex}{sHex}{sHex}"));
//you could use something like this as well -> lr_output_message("smp_UUID: %s",lr_eval_string("{sHex}{sHex}-{sHex}-{FourHex}-{sHex}-{sHex}{sHex}{sHex}"));
/*output loadrunner UUID*/
lr_output_message("slr_UUID: %s",lr_eval_string(slr_UUID));
/*output window generated UUID*/
lr_output_message("Win_UUID: %s",lr_eval_string(Win_UUID));
/*output base64 UUID*/
lr_output_message("%s",lr_uuid);
/*frees uuid created by lr_generarte_uuid*/
lr_generate_uuid_free(lr_uuid);
return 0;
}
/*This function uses windows ole32 CoCreateGuid function to generate UUID*/
char *lr_guid_gen()
{
typedef struct _GUID
{
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[4];
} GUID;
char guid[Hexlength];
GUID m_guid;
lr_load_dll ("ole32.dll");
CoCreateGuid(&m_guid);
sprintf (guid, "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
m_guid.Data1, m_guid.Data2, m_guid.Data3,
m_guid.Data4[0], m_guid.Data4[1], m_guid.Data4[2], m_guid.Data4[3],
m_guid.Data4[4], m_guid.Data4[5], m_guid.Data4[6], m_guid.Data4[7]);
return guid;
}
/*This function uses loadrunner random numbers to generate version 4 UUID*/
char *lr_uuid_gen()
{
char uuid[Hexlength];
switch (atoi(lr_eval_string("{RandNo}")))
{
case 1:
sprintf(uuid,lr_eval_string("{Hex}{Hex}-{Hex}-{FourHex}-{aHex}-{Hex}{Hex}{Hex}"));
break;
case 2:
sprintf(uuid,lr_eval_string("{Hex}{Hex}-{Hex}-{FourHex}-{bHex}-{Hex}{Hex}{Hex}"));
break;
case 3:
sprintf(uuid,lr_eval_string("{Hex}{Hex}-{Hex}-{FourHex}-{EightHex}-{Hex}{Hex}{Hex}"));
break;
case 4:
sprintf(uuid,lr_eval_string("{Hex}{Hex}-{Hex}-{FourHex}-{NineHex}-{Hex}{Hex}{Hex}"));
break;
default:
break;
}
return uuid;
}
This simple LR script inserts and retrieves a record from mongoDB. You can modify it to accommodate your own need.
Requirement: Download Java driver for mongoDB and add it to the classpath in the java script.
import java.lang.String;
import java.net.UnknownHostException;
import java.util.Set;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import lrapi.lr;
public class Actions
{
static String HostName ="localhost";
static Integer Port = 27017;
static String Username ="Harinder";
static String Password ="Password";
Mongo mDB;
DB db;
public int init() throws Throwable{
try {
mDB= new Mongo( HostName , Port ); //connect to MongoDB using HostName and Port
db = mDB.getDB("test"); // get test database from MongoDB
boolean auth = db.authenticate(Username, Password.toCharArray()); //authenticate user access to test database
if (auth = true)
lr.output_message("Successfully Authenticated");
else
lr.output_message("Incorrect Username/Password");
}catch (UnknownHostException e) {
e.printStackTrace();
}
return 0;
}//end of init function
public int action() throws Throwable {
try {
InsertInToMongoDB("Harinder1", "Seera1", "ThisIsMongoDB1@gmail.com");
}catch (MongoException e){
e.printStackTrace();
}
try {
SearchMongoDB("Harinder");
}catch (MongoException e){
e.printStackTrace();
}
return 0;
}//end of action function
public int end() throws Throwable {
mDB.close();
return 0;
}//end of end function
public void InsertInToMongoDB(String FirstName, String LastName, String Email)
{
// Get collection from mongoDB.
// If collection doesn't exist, mongoDB will create it automatically
DBCollection collection = db.getCollection("MyCollection");
BasicDBObject document = new BasicDBObject(); // create a document to store key and value
document.put("FirstName",FirstName);
document.put("LastName", LastName);
document.put("Email", Email);
collection.insert(document); //save the document into collection
}
public void SearchMongoDB(String FirstName)
{
DBCollection collection = db.getCollection("MyCollection");
BasicDBObject srchQuery = new BasicDBObject(); // search query
srchQuery.put("FirstName", FirstName);
DBCursor cur = collection.find(srchQuery); // query it
// loop over the cursor and display the retrieved result
while (cur.hasNext()) {
System.out.println(cur.next()); //I am using it only for the blog purpose to show the output.
}
}
}
You can monitor MySQL database using Sitescope. However, if you don't have access to the Sitescope application then you can write a simple LoadRunner script to monitor specific MySQL counters and plot the values using lr_user_data_point LR function. The following script is an enhancement to MySQL script detailed in my earlier post.
NOTE:
All session related information in MySQL is stored in a table called "SESSION_STATUS". Therefore, we will be querying this table for MySQL counter values.
For demonstration purpose, the script below will query the values for LAST_QUERY_COST, OPENED_TABLES, QCACHE_HITS, SELECT_FULL_JOIN and SELECT_SCAN counters.
Global declaration
#include "C:\\Program Files\\MySQL\\oldfiles\\include\\mysql.h" //mySQL.h path is included
/*MySQL structure defined*/
MYSQL *mySQL;
MYSQL_ROW row;
MYSQL_RES *result;
int MyRC;
char *MySQLserver = "localhost"; // Location where server is running
char *MySQLuser = "root"; // User name used to connect to the database
char *MySQLpassword = ""; // Not a good idea to leave password empty
char *MySQLdatabase = "information_schema"; //Database name
int MySQLport = 3306; // Database port
Vvuser_int
//libmysql.dll file loaded using lr_load_dll function
MyRC= lr_load_dll("C:\\Program Files\\MySQL\\MySQL Connector C 6.0.2\\lib\\opt\\libmysql.dll");
//Initialise mySQL connection handler
mySQL= mysql_init(NULL);
// Connect to the database
mysql_real_connect(mySQL,MySQLserver, MySQLuser, MySQLpassword, MySQLdatabase, MySQLport,NULL,0);
return 0;
Query Function
double atof(const char *string); // Explicit declaration
Query()
{
int i=0;
float VarValue[5]; //Float variable array
/*Save SQL statement into variable into sqlQuery.
This query returns values for the variable name ={LAST_QUERY_COST,OPENED_TABLES,QCACHE_HITS,SELECT_FULL_JOIN,SELECT_SCAN}*/
lr_param_sprintf("sqlQuery","select variable_value from session_status where variable_name IN"
"('LAST_QUERY_COST','OPENED_TABLES','QCACHE_HITS','SELECT_FULL_JOIN','SELECT_SCAN')");
mysql_query(mySQL, lr_eval_string ("{sqlQuery}")); //Execute SQL statement
result = mysql_store_result(mySQL); //Result of the sql statement is placed into MYSQL_RES result structure
row=mysql_fetch_row(result);
while(row!=NULL) //Iterate through to last row
{
VarValue[i]=atof(row[0]); //Save float row value to VarValue
row=mysql_fetch_row(result); //Retrive next row of the fetched result
i++;
}
/*Use lr_user_data_point function to generate the graph*/
lr_user_data_point("LAST_QUERY_COST", VarValue[0]);
lr_user_data_point("OPEN_TABLES", VarValue[1]);
lr_user_data_point("QCACHE_HITS", VarValue[2]);
lr_user_data_point("SELECT_FULL_JOIN", VarValue[3]);
lr_user_data_point("SELECT_SCAN", VarValue[4]);
return 0;
}
vuser_end
/*Free the memory allocated to result structure and close the database connection*/
mysql_free_result(result);
mysql_close(mySQL);
return 0;
NOTE: Make sure you are explicitly declaring the atof function before using it or else you will get totally different values. See the screenshot of the values received for the above counters when atof was not declared explicitly before using it.
Question:
How to submit a SOAP over JMS request to Websphere MQ using LoadRunner SOA protocol?
Solution: Prerequisite
JMS Queue details
Websphere MQ Client installed (if not installed, instructions are below)
Websphere MQ jar files installed (if not installed, instructions are below)
JDK installed (if not installed, instructions are below)
JMS Queue Details
Depending on which queue(s) you want to send and receive a message from as well as the MQ architecture design, you will require following MQ information from your Websphere MQ Admin.
HostName
Channel
Port
Queue Manager
Input Queue
Output Queue
Queue Connection Factory
Username and password
For example, in my case, to send a message to an Input Queue, following information was required:
HostName – xxx.xxx.xxx
Channel - ICF.DEF.SVRCONN
Port - 1414(might be different)
Queue Manager - Not Required
Input Queue - Input_queue_name
Output Queue - Not Required
Queue Connection Factory - qcf
Username and password - Not required
Webshere MQ Jar files
Install JAVA JDK on your local machine
Copy MQ jar files into the JDK...->Java ->jre-> ext folder. You will need to get these jar files from your Webpshere MQ admin
Also, since we are using fscontext as initial context factory, you will need to download and save fscontext and providerutil.jar files in the above mentioned folder
How to create JNDI binding
Install Websphere MQ Client application on your local machine.
Navigate to the location where JMSAdmin.config file is located and open it. We are going to select a context factory to use and path where to create the binding file. In my case this file is located at C:\Program Files (x86)\IBM\WebSphere MQ\Java\bin Update and save the file with following details:
Create a new qm.scp file (make sure this file is in the same folder as JMSAdmin.bat file) and put there the MQ details. The .scp file will look like this:
Create the JNDI folder on your C drive. This is where your binding file will automatically be saved.
Navigate to “...\WebSphere MQ\Java\bin” and edit JMSAdmin.bat by replacing “java” text with the full java path incase it is not already defined in your System environment settings.
Navigate to Websphere MQ Java bin folder via the command prompt and execute the JMSAdmin Tool (JMSAdmin. Bat file) with qm.scp as the parameter. This will generate a .bindings file in the JNDI folder automatically.
You will see something like this when JMSAdmin bat application is executed.
Cool, JNDI binding is done. Creating a Webservices Loadrunner Script
Open up a new web services script.
Press F4 to Navigate to Run-time setting and update the following fields in JMS->Advanced option:
If you are testing SOAP over JMS using Web Services protocol in Loadrunner, you can monitor JVM(in process) which is invoked by mmdrv.exe, either using JConsole or JVisualVM or any other tool that allows you to monitor JVM.
Before you use JConsole or JVisualVM, you will need to set jmxremote parameter in Loadrunner. To do so, add “-Dcom.sun.management.jmxremote” as a Value in the “Additional VM Parameters” textbox. See the figure below.
Now, run your script and open up JVisualVM to monitor the JVM running within the mmdrv process.
NOTE: If you are using JVisualVM tool for monitoring then you do not need to set remote jmx parameter. It is only required for Jconsole.
Problem:
How to capture soapUI request(s) in LoadRunner.
Assumption:
1: A SOAP request has already been created in soapUI. I am using Metric Weight Unit Convertor WSDL from webserviceX for this problem.
Solution:
1: Select an Web HTTP/HTML protocol in LoadRunner.
2: In Start Recording window, input the following options: Application type: Win32 Applications Program to record: "...\HP\LoadRunner\bin\micexec.exe" -->micexec.exe path Program arguments: "...\soapUI-4.0.0.exe" -->soapUI path
3: Click OK button. This launches the soapUI application.
4: Run the SOAP request in soapUI. You will notice that LoadRunner is now capturing the soapUI traffic.
5: Stop the LoadRunner recording, once soapUI has finished submitting the request. You will notice that the same request created in soapUI has been captured in LoadRunner.
6: Replay the script in the LoadRunner and you will notice that the correct response is returned.
NOTE:
You might get "The JVM could not be started. The maximum heap size (-Xmx) might be too large or an antivirus or firewall tool could block the execution" error message.
You can fix this issue by updating the -Xmx value in the soapUI-4.0.0.vmoptions file. Reduce the size JVM Xmx value. In my case, I reduced it from 1200 to 512. This fixed the issue and I was able to record the script. You might also get OutOfMemory issue when launching soapUI using LoadRunner. This is because the maximum heap size set in soapUI is less than the memory required by soapUI to successfully launch. Increase the heap size to what the soapUI application requires or remove the unnecessary projects.
Problem:
How do you capture a variable in LR that has a dynamic right boundary?
Example:
Depending on the data I used in a project (Siebel Project), the right boundary of a row ID that I wanted to capture from a response would change. For example,
1: Data set 1 would return following response - ...*Y1*Y10*1-1YK-49251*11*11*N1*...
2: Data set 2 would return following response - ...*Y1*Y10*1-2XY-45233*11*11*N1*...
3: Data set 3 would return following response - ...*Y1*Y10*1-1KM-72142*11*11*N1*...
In the above response, I needed to capture the row ID "1-1YK-4925","1-2XY-4523" & "1-1KM-7214" respectively. As you can see, the right boundary is different for different data set and therefore just passing left and right boundary values in web_reg_save_param, would fail or/and capture wrong row ID value.
web_reg_save_param("rowID","LB=*Y1*Y10*","RB=1*11*11*N1*",LAST); --Will fail for data set 2 & 3. Same thing will happen if you are using RB from data set 2 and 3.
web_reg_save_param("rowID","LB=*Y1*Y10*","RB=*11*11*N1*",LAST); --will fail for all data sets because an extra character is saved in Temp parameter.
Possible solution:
As you might have noticed, the row ID that we want to capture consists of 10 characters and therefore we could use one of the following approaches to solve this problem:
1: SaveLen attribute- since we know that the number of characters for row ID, we can use SaveLen Attribute in web_reg_save_param function to capture the correct value.
2: Regular expression- Second approach is to use regular expression to capture the row ID. Their is a really good blog written by Dmitry Motevich on how to use regular expression in LoadRunner.
To demonstate the above two approaches in Siebel, I have used three parameters to save the row ID value from the server response (see the image below):
1: SubActivityIDWithoutRegAndNoSaveLen - this parameter is captured using web_reg_save_param without SaveLen attribute. In this case a wrong value is captured.
2: SubActivityIDWithoutRegAndSaveLen - this parameter is captured by passing SaveLen value in web_reg_save_param function. In this case a right value is captured.
3: SubActivityIDWithReg - this parameter is captured using web_reg_save_param and then passed into the regular expression function to get correct rowID. In this case the pattern that matches the above value is "\\d-[0-9A-Z]{3}-[0-9A-Z]{4}" and right value is returned.
From the RunTime Data window you can see that the final value of the parameter captured using the regular expression and SaveLen attribute are the same.
Therefore, depending on your situation, you might be able to use any one of these approaches to capture a value that has dynamic boundaries.
Recently, a friend wrote a blog where he solved a tricky situation by using parameter file rather than using database for parameter. The scenario was as follows:
"This particular scenario required that a user logged on to the Application can only perform particular searches based on criteria defined and assigned to that particular user. That is,VUSER1 can only search on terms1, terms2 and terms3 whilst VUSER2 can only search on terms4, terms5 and so on."
I have not come across a situation where I had to use database for LR parameter. Therefore, as a challenge(as well as to learn something new) I wrote a simple LR script to solve this problem using database.
NOTE:
1: You will need to add error handling code to the script.
2: Haven't had a chance to check out how much memory this code consumes since I am using mysql_store_result function to store the result into memory.
3: All the applications were running on localhost. You will need to change the database connection details, if you need to connect to remote MySQL server.
4: Also you will need to add all the library files and DLL(see below) on a system that will be executing this script. That is either the vugen or LR agent machines.
Steps:
1: You will first need to download and install MySQL database.
2: Download C driver for MySQL (Connector/C)
3: Create a database, table and add records into the table as shown below in screenshot.
4: In LoadRunner you will need to include mysql.h header file which comes with C driver. You might also need to update path of the following header files in mysql.h.
-mysql_com.h
-mysql_time.h
-mysql_version.h
-typelib.h
-my_list.h
-my_alloc.h
5: You will also need to add a function to load the DLL(libmysql.dll) which allows LR to connect to the MySQL database. Add this file in the vuser_init function.
6: You will need to add all the database connection details. You will need to replace the below values with your database details.
char *MySQLserver = "localhost";
char *MySQLuser = "root";
char *MySQLpassword = "";
char *MySQLdatabase = "loadrunner";
int MySQLport = 3306;
7: Your global.h and vuser_init will look something like this.
vuser_init()
{
//libmysql.dll file loaded using lr_load_dll function
MyRC= lr_load_dll("C:\\Program Files\\MySQL\\MySQL Connector C 6.0.2\\lib\\opt\\libmysql.dll");
//initialise mySQL connection handler
mySQL= mysql_init(NULL);
// Connect to the database
mysql_real_connect(mySQL,MySQLserver, MySQLuser, MySQLpassword, MySQLdatabase, MySQLport,NULL,0);
//save SQL statement into variable into sqlQuery
//This stamentement returns result that matches UserName = {Vuser} parameter
lr_param_sprintf("sqlQuery","SELECT SearchTerm FROM lrdata WHERE UserName='%s'",lr_eval_string("{Vuser}"));
//Execute SQL statement
mysql_query(mySQL, lr_eval_string ("{sqlQuery}"));
//result of the sql statement is placed into MYSQL_RES result structure
result = mysql_store_result(mySQL);
//num_fields = mysql_field_count(mySQL);
return 0;
}
8: In Action function, add following code. NOTE: Make sure you have created an LR parameter called "Vuser" that has a text format "Vuser%s" so that a correct vuser name is passed into the sql statement.
//1: initialize connection handler
//2: connect to the database server
//3: Execute SQL statement
//4: Close the connection to the database server
Action()
{
row=mysql_fetch_row(result); //retrive next row of the fetched result
//Incase # of transactions to be executed is more than returned sql result
//move the result pointer to first row
// This is similar to "Continue in cyclic manner" for When out of values option in LR
if(row==NULL)
{
mysql_data_seek(result,0);
row=mysql_fetch_row(result);
lr_output_message("The searched term for [%s] is: [%s]",lr_eval_string ("{Vuser}"), row[0]);
}
else // print the fetched row
{
lr_output_message("The searched term for [%s] is: [%s]", lr_eval_string ("{Vuser}"),row[0]);
}
return 0;
}
9: In your vuser_end function add following code.
vuser_end()
{
//free the memory allocated to result structure and close the database connection
mysql_free_result(result);
mysql_close(mySQL);
return 0;
}
10: Running this code for five iterations, we get following result.
If you find any bugs in the code or you have an updated(/improved) version of this code, please leave a comment.
LoadRunner 11 comes with a new protocol called TruClient. It records a business process using the Firefox browser. By default, the browser does not come with plugins such as Firebug. However, you can still add a Firefox plugin and use it while using TruClient during scripting process. For demonstration purpose, I will show you how to add "Firebug" plugin to the TruClient browser and the steps are as follows:
1: Select the TruClient protocol and then click Tools -> Ajax TruClient Browser Configuration option. An "Ajax TruClient Browser Configuration" popup window is displayed.
2: Click on "Extensions" tab. All existing plugin list will be displayed. See the screenshot below.
3: Click on "Get Add-Ons" button. All the recommended plugin list is displayed.
4: Type in "Firebug" in the search textbox and click "Search" icon. A result list is displayed. Incase you do not see the "Firebug" plugin, click on "See all results..." link. This will bring up the list of all the plugins in a browser.
5: Click on "Add to Firefox" button next to the Firebug plugin and follow all the necessary steps. Once the Firebug is installed. Restart the Firefox browser.
6: If the browser is open, close the browser and click "Develop Script" button in VUGen. This will open up a Firefox browser. At the bottom right corner of the browser, you will now see a firebug icon. Firebug plugin is now ready to be used while you are recording a script using TruClient protocol.
Issue: When working with Siebel protocol in LoadRunner 11, you might get following error message 'Failed to load the "LrwiSiebelCorrelationWrapper.dll" auto-correlation callback library. The specified module could not be found.'
Solution:
Make sure ssdtcorr.dll file exists in the LoadRunner bin folder.
Below is a list of all available LoadRunner 11 patches. You will be required to login to the website to download them. Also, if you don't have Contract identifier number(SAID) then you might not be able to download the patches.
From HP website
After a clean installation of LoadRunner 11 there are 3 important patches available so far. The patches are mandatory and should be installed by all LoadRunner customers. All patches are accumulative, that is, the newest one contains all of the previous ones. However due to issues with the uninstallation any of the first 2 (Critical or Patch 1) should be present before installing Patch 2.
There are situations during load testing when you want to find out which data(i.e. username) was consumed by a VUser(For example, Few VUser were having errors). If you don't have proper logging turned on(or a code) during load testing, it can be a pain. One way of getting data information is to use lr_vuser_status_message function.
From LR Help "The lr_vuser_status_message function sends a string to the Status area of the Controller. It also sends this string to the Vuser log. When run from VuGen, the message is sent to output.txt"
For example, In the error log(Controller) you can find out which VUser had an error and using lr_vuser_status_message function you can then find out what data that VUser was using. This way it helps save time in debugging any data related issues.
Following example shows how to use lr_vuser_status_message function.
In the code above, UserName parameter string is passed into the lr_vuser_status_message function.
Now during load testing if you open VUser window in controller, following is what you will see.
From the image above you can notice the UserName(Status column) that each VUser is using in current iteration.
Now if you have an error in the error log, find out which VUser had the error and open up VUser window to find out what data that Vuser was using. It won't tell you what caused the error but will give you enough information to start investigating.
If you haven't scripted a siebel application before for load testing using Load Runner then you are in for some good time. I am sure you will come across different issues which you might not have encountered before. I wanted to compile a list of issues that I/colleague have encountered and how they were solved. Therefore this blog contains some of those issues and possible solution.
Following issues were encountered by my colleague:
Issue: Sometimes the application does not function correctly when recording with Loadrunner, but works fine without recording. Solution: Close all browser instances, search and delete a file called Siebel High Interactivity Framework, Log into the Siebel application and agree to 'repair' the browser configuration.
Issue: Some "web_url" requests fail during replay and the application responds with "We detected an Error which may have occurred for one or more of the following reasons: We are unable to process your request. This is most likely because you used the browser BACK or REFRESH button to get to this point.(SBL-UIF-00335)".
The following request is a sample which generated this error:
Solution: At first glance the requested URL seems to be parametrized correctly. However, the automatically correlated SWEC={Siebel_SWECount} is actually incorrect. In most cases the Siebel_SWECount parameter is incremented correctly by Loadrunner. However, when using SWECmd=GetCachedFrame, that value for the SWEC parameter must be correlated manually from the previous request.
The response from the previous request will contain something like:
Normally this kind of correlation is natural and intuitive, but with the Siebel protocol SWEC is a counter which is not usually correlated and therefore easily overlooked.
If you come across different issue(s) related to Siebel application scripting, I would like to hear about them as well as how you were able to solve it. If I come across more issues, I will update this blog.
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
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.
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:
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.