Thursday, February 10, 2011

Dynamic values within files

I have a HTTP Post request sample to which we are passing an XML file through option "Send Files With Request". The XML file to be sent with request contain on Tag "Order id" which need to be modified [ dynamically during test plan executing] before sending the request. Original
Typically in JMeter we would use a ${variableName} notation. However JMeter only replaces these values when the the ${variableName} is within the script itself , not when it is referenced in some other file. As the user wants to use a separate (template) file which is added to the HTTP request, this technique wont work.
One option to consider is whether we could generate all the files we need before we run the test. this is possible for example when the dynamic data is a set of id's that is accessible say from a database. In this case the better (in the more efficient sense) is to have a initialisation step (perhaps as part of the build or as a separate step in Jmeter) to create all the files that the test would need. Once this is done it is easy to make the JMeter script pick one file (usually using the CSV DataSet Config).
However this isnt always possible. In the question above , the Order ID could be dynamically generated by the system under test in ways that might not be predictable. In this case we cannot generate the files in advance. We have to extend JMeter.
The cleaner more elegant way to do this would be to write a custom pre-processor of some sort because it sounds as if this functionality could be reused in some other context (or extend the HTTPSampler). But we will choose the quick and dirty way of writing a BeanShell preprocessor that does this for us.

Briefly the solution will
a. Have a template XML file. We will use @variableName@ within the file for what we want replaced. We will currently only support replacing a single variable (but it is easy to support multiple variables).
b. We will configure the HTTPSampler to use a generated file. At runtime a pre - processor will read the template file and generate this new file. This new file will be read and posted by JMeter

The Structure of the test is shown below
The HTTP Sampler adds a currently non existent file to be posted. This is the file the Pre Processor will create. The name of the file is varied based on the ThreadNumber (we could have added timestamps etc as well) so that each thread will get its own file name

We use user defined variables to define the location of the temp file, the regex that we want to run on the file and the replacement value.

The BeanShell pre-processor
import java.io.BufferedReader;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import java.io.File;
import java.io.FileInputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.nio.channels.Channels;

//read the file as string
FileInputStream stream = new FileInputStream(new File(vars.get("templatePath")));
String fileAsString = "";
try {
      FileChannel fc = stream.getChannel();
      MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());     
      fileAsString = Charset.defaultCharset().decode(bb).toString();
}
finally {
        stream.close();
}
//perform the replacement
fileAsString = fileAsString.replaceAll(vars.get("replaceRegex"), vars.get(vars.get("variableName")));

//assumes first file
HTTPFileArg arg = sampler.getHTTPFiles()[0];
//write it out to the file we want
FileChannel fout = new FileOutputStream(arg.getPath()).getChannel();
BufferedWriter bf = new BufferedWriter(Channels.newWriter(fout, Charset.defaultCharset().name()));
bf.write(fileAsString);
bf.close();
Disclaimer : File Reading writing snippets swiped from Google, you can probably make it more efficient.
  • Briefly the snippet reads the template file(location defined as a variable) as a String (using the default charset , if you deal with multiple languages you might want to force this to UTF-8).
  • It then runs the replacement using a regex (also a variable) and the subsitution value (also a variable which for this example we have just spoofed)
  • Finally it writes the file(assuming default charset , again you could force this to be UTF-8). To determine the location of the file , it reads the location from the HTTPSampler. This is currently set as the first file , but we could have used a name or any other technique.
And finally the sampler will read the file that we have created and post it as usual.

Sample JMX available here

44 comments:

navin said...

hii need some help in installing jmeterwith env variables and also help about bean shell post processor. . it is howing bean shell intrepretor error . . if any solution is know kindly let me know thanks

Deepak Shetty said...

mail the jmeter mailing list. You probably have a syntax eerror in your bean shell

navin said...

Interpretor not found error got resolved by changing the bean shell jar file.
I am using a JMS point to point from which response has to be parsed and written to a file.
I am able to create a file from bean shell post processor and write a string variable
I want to know how to get the response and store in string

navin said...

Also provide me some details on how to use
vars.get()
vars.put()
getResponseData()

I am totally new to jmeter. .
Thanks to u :)

Deepak Shetty said...

Look at
http://jakarta.apache.org/jmeter/api/index.html

Class is JMeterVariables (vars), HTTPSampleResult(SampleResult)

get and put are fairly straight forward
vars.put("key","value");
you can do use it as
vars.get("key") or ${key} anywhere later in JMeter (like HTTP Sampler).
The other method gets the response of the Sample the post processor is attached to

navin said...

Thanks for the information. I ll try and let u know the results . .:)

navin said...

hi it worked fine today , i used getResponse dsta to get the response :)
Now the next step i hv to parse it and store to a file
i guess jmeter has inbuilt sax parser jar. . But i dunno hw to parse using it.

Deepak Shetty said...

since you dont actually say what you want to do it is hard to advise. java has a built in XML parser so you could use that. However Jmeter can write data to file using listerners as well as extract data using XPATH post processor and Regex Post processor which should be sufficient most of the time instead of writing your own code

navin said...

hi i have a doubt using vars.put("key",value). it works fine if "value" is a string. But when i try to pass a integer value, i am not able to get it.

ex. int a = 5;
int b = "response";
vars.put("key1",a);
vars.put("key2",b);

i am able to get "b" value by using
vars.get(key2);

but not get a value by
vars.get(key1);

if i convert the int into string then i am able to retrive.

vars.put("key3",a.toString());
then i am able to retrive.

any idea of how to retrive integer value as it is . .

Deepak Shetty said...

Look at the javadoc
http://jakarta.apache.org/jmeter/api/org/apache/jmeter/threads/JMeterVariables.html

There is a method
put(String, String)
and there is putObject(String, object)
If you only intend using this in BSH then you could use putObject, but my recommendation would be to use

ex. int a = 5;
int b = "response";
vars.put("key1",String.valueOf(a));

To retrieve as int use something like
a =
Integer.parseInt(vars.get("key1"));

navin said...

Thanks a lot , ur sugesstions were very helpful.

navin said...

Hi . . hv u used switch controller. .
it it possible to give value using ${Var} in switch.. it works fine with integer

ex Switch
value = 2 it selects component with name 2.
but when i use bean shell preprocessor
int a = 5;
switch
value ${a} it deosnt work

Also in components reference it is given Non numeric values are also accepted but case sensitive.

it also doesn work . . If u hv any sugessions , it ll be helpful ty

Deepak Shetty said...

variable values work - However it says element (so any sampler) - it wont work for pre Processors.

Also use debug sampler to inspect whether your variable is set correctly

navin said...

Hi

I have 3 kind of users which will be determined from inputs from csv file.

For ex
input.csv has

Name sex age Address someIndicator
Naveen M 12 xxx " A "
Kumar M 20 asd " A"
Latha F 68 sda " B"
Saravanan M 20 asd " A"
Sam F 68 sda " C"


Say based on someIndicator

we have to pass certain parameters to a JMS request

For this i hv used bean shell preprocessor

String tempReq = null;

if(${someIndicator}==A){

tempReq = "Value<\param1>"+
"Value<\param2>"+
"Value<\param3>";
}
else if(${someIndicator}==B){
tempReq = "Value<\param4>"+
"Value<\param5>"+
"Value<\param6>";
}
else if(${someIndicator}==C){
tempReq = "Value<\param7>"+
"Value<\param8>"+
"Value<\param9>";
}

Instead of specifying the value directly
I want to keep as property with default valaue so that dynamically it can be changed.

For this i would prefer to have separate property file for each indicator

is it possible to select diff property file for each user based on input from csv.

Anonymous said...

Hello,

Very interesting work, Deepak! (as usual)

Does this method needs to save the files? (as you configured them into the sampler config: "data_${whatever}.xml")

I am confused as why do you need to configure a different file than the template because I would assume all the changes should be done in memory.

Deepak Shetty said...

@Anonymous

I am confused as why do you need to configure a different file than the template because I would assume all the changes should be done in memory.

As far as I know the HTTPSamplerBase (which is the base class for all HTTPSamplers) does not give you a way to specify the file contents in its public API. if I wanted to do this , I'd probably have to change some internal JMeter code, something I don't want to do.
Which means that I have to write the file somewhere.

Deepak Shetty said...

@navin
you should send your queries to the Jmeter mailing list, i do not check the comments on this blog frequently.
So suppose you have two CSV files
Naveen M 12 xxx " A "
Kumar M 20 asd " B"
and another
A,p1,p2,p3
B,p4,p5,p6

The easy way is to write a program that converts CSV file 1 into
Naveen, M ,12, xxx, " A ",p1,p2,p3
Kumar, M ,20, asd, " B",p4,p5,p6
And call this before your test (either through jmeter or external to it.).
Everything else needs a little bit of programming and will never be as efficient as pre processing the files into the format you want.

Rohit said...

Hi,

I was able to make this code work for jmeter 2.3. However it gives Method not allowed in jmeter 2.4.

Any pointers?

Thanks,

Rohit

Deepak Shetty said...

@Rohit
Whats the stack trace? What java version?
The only JMeter method being used is http://jmeter.apache.org/api/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.html#getHTTPFiles%28%29

which is present in 2.4 - so it looks like you are using a different version of java than the one I used(in which case either upgrade the java version or change the code that writes the file)

Anonymous said...
This comment has been removed by the author.
Anonymous said...
This comment has been removed by the author.
Anonymous said...

Hi Deepak,

I am using Jmeter for load test. I am using Bean Shell sampler and inside data i have hexadecimal data in String format which i am converting to integer byte array. My problem is when i am converting the data and doing vars.put("byteArray",data) or vars.putObject("byteArray",data). Its not working for me. I google a lot but didn't find any meaningful post.
I am sharing a piece of code

String hexdata=vars.get("data from csv data set");
int[] arrayOfValues = new int[hexdata.length() / 2];
int counter = 0;
for (int i = 0; i < hexdata.length(); i += 2)
{
String s = hexdata.substring(i, i + 2);
arrayOfValues[counter] = Integer.parseInt(s, 16);
counter++;
}
String auth = "&UserName=123456&UserPassword=132456" ;
byte[] authBytes = auth.getBytes(Charset.forName("US-ASCII"));
vars.put("mydata","arrayOfValues");
vars.put("myauth","authBytes");

In the HTTP request i am doing the HTTP POST

http://example.com/MyMethod/${mydata}${myauth}

Deepak Shetty said...

@Utsav - look in jmeter.log - you probably have an error

Anonymous said...

Hi Deepak,

Thanks for your reply but please tell me the way how can i send byte array in http post form bean shell sampler. Actually i have done almost with the code but the thing is i am not able to post the byte array from the Jmeter. Can we send Byte array in htt post if yes please tell me how to send and if it is not possible please tell me some other way to do that. Thank you very much for your comments.

Freddy said...

you forget to downlaod opera...

Anonymous said...

SystemAnnotationID = vars.get("SystemAnnotationID");
organizationId = vars.get("organizationId");
log.info(SystemAnnotationID); // if you want to log something to jmeter.log file

// Pass true if you want to append to existing file
// If you want to overwrite, then don't pass the second argument
f = new FileOutputStream("C:/Users/gsagar/Funtional and load/apache-jmeter-2.9/apache-jmeter-2.9/bin/SystemAnnotationFormatter/snresult.csv", true);
p = new PrintStream(f);
this.interpreter.setOut(p);
print(SystemAnnotationID+ "," + organizationId);
f.close();

Iam using the above script to write Json path variable into a file. But in if loop some iterations are failing and the above succuess step values are writing for all failed.

Deepak Shetty said...

I dont understand your question since you havent given any context of where this snippet is being written and what your issue is.
The only thing I can see is you want something different to happen on failures and its possible to that by looking at the sampler response
Since this is unrelated to the blogpost you are better off sending an email to jmeter forums with better details of your problem

Anonymous said...

As A beginner I 'm having lot of doubts..Can u Please Show The xml file also

Anonymous said...

Hi,,can any one please help me out of this issue..


I want to replace the value of post method using Jmeter in order to test.

Anonymous said...

Hello,

My question is I want to parametrize data in binary files sending along with http request in jmeter while load tesing Flex based apps.

Will you please suggest how this can be done.

Deepak Shetty said...

>My question is I want to parametrize >data in binary files sending along >with http request in jmeter while >load tesing Flex based apps.

1) if you know what varies then its the same as the method described
2) if you dont then you need to have an API that understands the binary format and works with it e.g.
http://opensource.adobe.com/wiki/display/blazeds/Java+AMF+Client

3) Buy a commercial jmeter plug in
e.g.
http://www.ubik-ingenierie.com/blog/ubik-load-pack/

Unknown said...

Deepak ive recorded a set of requests from my app(its a game) and each request have a ".binary" passed with it. I can open the binary file and it shows many details including the user ID.

Our service is setup in a way that each user ID would be treated as a user. What can I do in this situation, will the same procedure work in this case too?

Deepak Shetty said...

@lohith
No this isn't exactly the same thing. This article deals with replacing values in files you want to post - You on the other hand have a binary type of data being returned as response that you probably want to extract data from and resubmit in some fashion)(depending on what you want to do).

a. if your binary file has some textual content that can be parsed then you normally use the regular jmeter tools (like regex post processor + variables) to maniuplate the data
OR
b. if not then you need (usually java baseD) parser for whatever your application returns and then use that to manipulate the contents

What is it that you want to do?

Unknown said...

@DEEPAK,

Im new to Jmeter and all this fuzz, i dont think that the binary file is a response i get from a request but this seems to be a binary file that get posted along with a request.
Told u right I recorded the app using jmeter proxy and this binary file im talking about is added as a post content.

I hope u can guide me now, need urgent help brother :(

Deepak Shetty said...

Again Im not sure I understand the issue you are trying to solve (and having no idea of your application its difficult to understand it too)

a. You recorded a script using proxy
b. You are trying to play it back I suppose
b.1 Is this not working?
OR
b.2 You are trying to parameterise it?
OR
b.3 something else?

Unknown said...

Thanks for the reply :)

Ill make it clear for you, Please find the reply for the questions uve asked

a. You recorded a script using proxy //yes ive recorded a script using proxy
b. You are trying to play it back I suppose // yupp u r right

b.1 Is this not working?
Reply: Yes the script is working fine, i can get it running without much fuss.


OR

b.2 You are trying to parameterise it?
Ans: In the recorded scripts, i have a set of requests that have a binary file attached in the "Send files with request". Our web service is designed so that when a request is send with different user ids these requests will be treated as different users. For some requests the id is passed in the aforementioned binary file. What i need to do is to create a scenario where i can dynamically pass the user ids to create a multi user scenario. My doubt is whether I should do something with the binary file or is it enough that i mention a parameter outside.

My doubt is whether the method u mentioned here works well in this situation.

Hope im clear now

Deepak Shetty said...

Our web service is designed so that >when a request is send with different user ids these requests will be treated as different users.

AND
My doubt is whether I should do something with the binary file or is it enough that i mention a parameter outside.

I do not understand the doubt - if you need to pass new ids as part of the webservice call - then that is how JMeter will have to pass it - you dont have an option here - you have to replicate exactly what the browser or your game is doing.

Since you are saying its a game (presumably flash) - how does this game get the user id ? is it passed to it ? in Which case you will just create a data file which has all the userids you want to use which will need to be sent in your calls - if these ids are part of some call then those calls will have to be parameterised.

Also please differentiate between a response which has a .binary extension and "sending files with request"
In HTTP communication you have requests and response where typically the request is a GET or POST with parameters. But you can also sometimes upload files along with the request - this blog post deals with uploading files - I doubt that, that's what your game is doing. Most likely its making a request with parameters (where the parameter itself may or may not be binary)

Deepak Shetty said...

Also without either seeing your game or your jmeter test script or your HTTP request response interactions I doubt I can provide any advice

Unknown said...

thanks for the reply brother;

I do not understand the doubt - if you need to pass new ids as part of the webservice call - then that is how JMeter will have to pass it - you dont have an option here - you have to replicate exactly what the browser or your game is doing.

Reply: I need to pass the parameters with the request as in normal gameplay, I understand that brother. Told you right my requests were recorded using jmeter proxy so I believe that the requests are in the right way it has to be.

Since you are saying its a game (presumably flash) - yes its a flash game

How does this game get the user id ? is it passed to it ? yes it is, the game gets the fb id of the user and then pass it on as an argument to the backend

in Which case you will just create a data file which has all the userids you want to use which will need to be sent in your calls - if these ids are part of some call then those calls will have to be parameterised.

As far as I know, creating a data set with all the user Ids is easy in Jmeter, but my backend team have confirmed to me that if I dont pass this ".binary" object along with the request it will not serve the purpose.

the request are used to setdata and getdata, for which the main parameter to be passed is the userid


Also please differentiate between a response which has a .binary extension and "sending files with request"
In HTTP communication you have requests and response where typically the request is a GET or POST with parameters. But you can also sometimes upload files along with the request - this blog post deals with uploading files - I doubt that, that's what your game is doing. Most likely its making a request with parameters (where the parameter itself may or may not be binary)

the game use POST with parameters to communicate with the servers. No file upload is happening in the game.

The recorded scripts are requests made from the game, and these requests have a file with ".binary" extension and "sending files with request" active.

My doubt was how to dynamically set the values to the userid field in the binary file so in each thread a request is passed along with a new ID.


The game is set local and cant be accessed by URL, sorry abt that

Unknown said...

Also please differentiate between a response which has a .binary extension and "sending files with request"
In HTTP communication you have requests and response where typically the request is a GET or POST with parameters. But you can also sometimes upload files along with the request - this blog post deals with uploading files - I doubt that, that's what your game is doing. Most likely its making a request with parameters (where the parameter itself may or may not be binary)

this .binary file im talking about is uploaded along with the HTTP request.

Deepak Shetty said...

@lohith
the game use POST with parameters to communicate with the servers. No file upload is happening in the game.

AND

The recorded scripts are requests made from the game, and these requests have a file with ".binary" extension and "sending files with request" active.

Are contradictory. If the latter statement is true - then yes you have to parameterise this file using something like what the article says or something like
http://jmeter-plugins.org/wiki/RawRequest/ where you can specify the contents within the sampler itself and parameterise the values there.

Anonymous said...

knock knock, anyone home? --Hi Deepak,
--need to use csv config containing multiple sqls, read each line, sub variables inside file to property values, capture result from output and verify results in generated email
--Mike N. ex stanislite.

Deepak Shetty said...

Michael neef ?
You usually cant use both the CSV data set config AND variable substitution. You could try this.
a. Use the CSV dataset config element to read the SQL into a variable
b. use __eval or __evalVar to replace the values you want -- http://jmeter.apache.org/usermanual/functions.html#__eval
c. For capturing result save the resultset into String (using JDBC request) -- http://jmeter.apache.org/usermanual/component_reference.html#JDBC_Request . Save that into Jmeters result file using sample_variables - http://jmeter.apache.org/usermanual/listeners.html#sample_variables
d. Finally to email it I would probably do it externally using ANT or something (else you have to use the SMTP sampler)

Anonymous said...

Yea -c'est moi. Thanks for tips. Sorry for the delayed response - lost this link - yes mail reader sampler pretty pathetic... can't get it to work reading line by line each "label: field with spaces" doesn't seem to want to match the result.