[eggPlant Performance] Custom VU methods

Tips & Tricks: Custom VU Methods

One of the benefits of eggPlant Performance’s object-oriented design and use of common programming languages is the resulting ability to override methods. Overriding essentially allows you to modify what these methods are doing in some way, be it to “do something else” or even “do nothing at all”. This is possible with several methods accessible across Virtual User types in both C# and Java, but to make it easier to locate them, we have exposed a few of the more useful ones in the custom VU template source code on recent versions of the product (v6.0 onwards).

Let’s take a look at some of these methods now, and how these might be used in a real-world performance testing scenario. The file of interest is the custom VU source, whose methods should consist of the below code, assuming you are using at least v6.0 and the C# VU:


using System;
using System.Collections.Generic;
using System.Text;

using Facilita.Native;
using Facilita.Web;
using Facilita.Fc.Runtime;

using AVirtualUser = Facilita.Web.WebBrowserVirtualUser;

namespace com.testplant.testing
{
	public class CSCustomVU:AVirtualUser
	{
		public CSCustomVU() // Constructor

		protected override void Pre()
		{
			base.Pre();
		}

		protected override void Post()
		{
			base.Post();
		}

		public override void PrepareRequest(Request request) { }

		public override void ProcessResponse(Response response) { }

		protected override bool OnError(string id, string info)
		{
			return true;
		}

		protected override bool OnWarn(string id, string info)
		{
			return true;
		}

		protected override bool OnException(Exception e)
		{
			return true;
		}
	}
}


Each of these methods gives you the opportunity to modify certain aspects of what the VUs are doing, or even just to output useful bits of information like progress (although that might be better for Progress Points!). Here are some example use cases:

Pre() and Post()

Much like the Pre() method in regular scripts, Pre() at the custom VU level allows you to define code that should execute at the start of the test. This Pre() method actually executes before that of the scripts, and as such is usually the most appropriate place to put initialization code, whatever that may be. A common example is instantiating a Random object for generating random numbers:


Random myRandom; // declare the variable outside of the method. You can add the public access modifier to make it visible to your scripts using VU.myRandom

protected override void Pre()
{
	int seed = UniqueID + (int)DateTime.Now.Ticks & 0x0000FFFF; // create seed using UniqueID and current time
	myRandom = new Random(seed);

	//do not remove following line
	base.Pre();


	// Put any code that needs to execute at the start of the test here
	WriteMessage("Some random numbers: " + myRandom.Next(500).ToString());
}


The Pre() method often actually executes at exactly the same time for each VU, meaning that there is a possibility that they end up using the same seed if using the default time-based constructor for Random(). To handle this, the above example prepends the VU’s UniqueID property value to the seed, allowing these to be unique and preventing the VUs from using the same random sequence.

Post() conversely allows you to define code that you would like to execute at the end of your test. This could be clean-up routines or even custom reporting. In both cases, make sure not to remove the base calls to the underlying methods.

PrepareRequest(Request request) and ProcessResponse(Response response)

Both of these methods give you the opportunity to pre-process all HTTP requests and responses sent & received by each VU. This could potentially be useful for modifying or outputting header values, or even to validate the presence of particular content (although this might be better achieved using a Verify Contains Generation Rule).

For example, a response containing a HTTP 200 code, which browsers are meant to interpret as “OK”, can often still contain an application-specific error message that may need to be handled, or at the very least logged. If that error can happen during any transaction, it would take a long time to put in handling in each individual script.


public override void ProcessResponse(Response response)
{
	// Put any code that needs to execute after each HTTP request here

	// check for the error message:
	if (response.Content.Contains("Unexpected error: failed to communicate with database"))
	{
		// if found, raise an Error explicitly and move on to the next iteration:
		Error("Database error encountered. Page source: " + response.Content);
		NextIteration();
	}
}

public override void PrepareRequest(Request request)
{
	// Put any code that needs to execute before each HTTP request here

	// add the dynaTrace header to all requests (http://www.dynatrace.com):
	request.SetHeader("x-dynaTrace", String.Format("VU={1};NA={2};SN={3}", Index, CurrentTransaction, CurrentScript.ScriptName));
}


OnError, OnWarn, OnException

These methods give you more control over what should happen when particular errors, warnings or exceptions occur. An example use-case is when your application returns non-standard HTTP response codes which eggPlant Performance subsequently treats in a potentially undesirable way. Other times, there may be temporary faults generated by particular requests that may already be known issues.

In these cases, it is often useful to simply ignore these messages, such that when the test completes, only errors that you are actually interested in will be listed in the summary.

In this example, assume you have the following request which, upon execution, causes a 404:


StartTransaction("Query AnalyzerREST");

IpEndPoint localhost = new IpEndPoint("localhost", 5000);
Protocol http = GetProtocol("http", "http");

Url myUrl = new Url(http, localhost, "/analyzer/api/1.0/");
Request myRequest = WebBrowser.CreateRequest(HttpMethod.GET, myUrl, 1);

myRequest.Send();

myRequest.VerifyResult(HttpStatus.OK, ActionType.ACT_WARNING);

EndTransaction("Query AnalyzerREST");


Normally this request would raise 2 warnings: one for the fact that a 404 was received (these are always logged as warnings by default), and another warning when the automatically-generated VerifyResult() code fails. To suppress both of these messages, you could put the following code in the OnWarn override:


protected override bool OnWarn(string id, string info)
{
	// This method is called whenever a warning occurs
	// Returning false from this method will suppress the warning in question

	// 'info' contains the actual error message that is normally reported in the VU's event log
	WriteMessage("info: " + info);

	// In this case we are checking it for either the "NOT_FOUND" string *or* the string that would normally be output when the VerifyResult code fails:
	if (info.Contains("NOT_FOUND") || info.Contains("Expected HTTP result 200 but got 404"))
	{
		WriteMessage("Received a 404, ignoring...");
		return false;
	}

	// info also contains the URL that caused the warning, making it possible to ignore only particular URLs

	return true;
}


Below is some example output of the above code:


ElapsedTime     Description         Info
00:00:00:025    Start
00:00:00:031    Start activity
00:00:00:034    Current Script      testplant.testing.myFirstScript	1
00:00:00:039    Start transaction   Query AnalyzerREST
00:00:00:056    Message	          info: NOT_FOUND http://localhost:5000/analyzer/api/1.0/
00:00:00:056    Message	          Received a 404 warning, ignoring...
00:00:00:066    Message	          info: Expected HTTP result 200 but got 404 for URL http://localhost:5000/analyzer/api/1.0/
00:00:00:067    Message	          Received a 404 warning, ignoring...
00:00:00:067    End transaction     Query AnalyzerREST
00:00:00:068    End activity
00:00:00:068    End                 Completed