Sunday, June 13, 2010

Silverlight RIA Services Load and Performance Testing

Introduction

I was able to do some initial unit testing of my RIA Service methods using Silverlight unit test projects. Now I needed to do some performance/load testing of the asynchronous RIA Services methods. After doing some research I quickly found out that I could not use the Enqueue* methods that come with the Silverlight Unit Testing frame; my solution using multithreading.

The goal of the performance/load testing is to ensure that the new RIA Services do not become a bottleneck. The system we are writing needs to be able to support lots of transactions and some transactions take a long time to calculate in the database end. Even though the user experience is acceptable because RIA easily supports asynchronous transactions, we have to be concerned about the amount of resources being consumed on the IIS machine which is hosting the RIA Services.

Some colleagues had suggested to me to write unit tests outside of the Silverlight and call the RIA Service methods using WCF directly. However I believe that is not a good solution because:

  • I cannot use the generated code that call the WCF RIA methods and I would have to subsequently write a lot of new code to call those services. Plus if there are inefficiencies in the generated code, they would not be discovered.
  • I cannot use my ViewModels which we had spent a lot time developing so they would be testable.

The Solution

I was able to come up with a simple solution that allows me to use Silverlight Unit Test project to call my RIA Services. The solution is to:

  • Initiate threads from the test method to call the RIA Service methods.
  • Write logs out to a log file that captures the duration of each RIA Service call.

Another nice thing about this solution is I have the ability to install the Unit Test Silverlight page onto the eventual server and run the unit tests from a client machine.

I basically had to relearn System.Threading and I was able to get things to work. Below I will show you the code on how to do some basic load testing of your RIA Services. I still like run the load/performance tests off a development environment. This is because production machines are more powerful and will hide performance errors. It is better to discover them on a machine that less resources.

The Code

I highly recommend reading the last section this blog so you understand the design, issues and challenges so you understand why I did what I did. I know many just look for code to copy – I know I do <g>

This is the test class. In it you can see that all I do is create a log file that will be used by all the threads to write to. I then initiate the search threads by calling a class called SearchTestScenario.

[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;
bool blnSearch1Complete = false;
bool blnSearch2Complete = false;
bool blnSearch3Complete = false;

[TestMethod]
public void TestMethod()
{
string fileName = CreateLogFile();

SearchTestScenario searchTest1 = new SearchTestScenario(fileName);
Thread thread1 = new Thread(new ThreadStart(searchTest1.Search1));
thread1.Start();

SearchTestScenario searchTest2 = new SearchTestScenario(fileName);
Thread thread2 = new Thread(new ThreadStart(searchTest2.Search2));
thread2.Start();

SearchTestScenario searchTest3 = new SearchTestScenario(fileName);
Thread thread3 = new Thread(new ThreadStart(searchTest3.Search3));
thread3.Start();
}

private string CreateLogFile()
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
string strLogFileName = System.DateTime.Now.ToString().Replace("/", "_").Replace(":", "_").Replace(" ", "_") + "_Search.txt";
IsolatedStorageFileStream rootFile = store.CreateFile(strLogFileName);
rootFile.Close();

return strLogFileName;
}
}

}

Here is SearchTestScenario. In this class I write methods that will execute searches and do some logs. I have not added anything into the call backs but that would be a good place to do some Asserts.

public class SearchTestScenario
{

private MyBusinessContext context;
private System.DateTime duration;
private string fileName;

public SearchTestScenario(string fileName)
{
context = new MyBusinessContext();
this.fileName = fileName;
}

public void Search1()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search1Complete, null);

Log("Search1 Started - " + duration.ToString());
}

public void Search2()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search2Complete, null);

Log("Search2 started - " + duration.ToString());
}

public void Search3()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search3Complete, null);

Log("Search3 Search - " + duration.ToString());
}

private void Search1Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search1Complete Duration - " + span.Seconds.ToString());
}

private void Search2Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search2Complete Duration - " + span.Seconds.ToString());
}

private void Search3Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search3Complete Duration - " + span.Seconds.ToString());
}

static readonly object lockObject = new object();
private void Log(string message)
{
lock (lockObject)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream rootFile = store.OpenFile(fileName, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(rootFile))
{
sw.WriteLine(message);
sw.Close();
}
rootFile.Close();
}
}
}
}

}

Design, Issues and Challenges

I have to admit I ran into a lot of problems trying to get this simple solution working. I had to relearn threading but there were tons of issues I had to address. Here is a short list of them.

  • I elected to not use a single instance of RIA Service context and pass that from the main thread into the running threads. This is because you get locking issues when access that object instance across threads. Even though I could lock it, I figured there was no value to it either.
  • For someone who is experienced with threading you will probably ask why did I not join all the running search threads back together when the searches were completed. Let me tell you I tried and burned lots of hours. I tried a solution where I used WaitHandle.WaitAll with ManualResetEvent for each running search thread. The problem was when a RIA Service method was called the entire unit test would freeze. I found this which was very similar to what I was experiencing. The net effect is the unit test will complete and if any assertions or errors occur, they will be treated as exceptions and not be picked up by the unit testing framework.
  • I do not create a single search method on SearchTestScenario because I may want to test for different thresholds for each search query.
  • I have to write the results to isolated storage because that was the easiest thing to get access to very quickly.

Relearn Threading Resources

1 comment:

thavo said...

Thanks for that {I referenced you post on my Blog ;-) }.

As far as I am concerned, here are my findings on free tools based on

Fiddler2 beta + stresstimulus

that gave me a results in 5 min.
http://memoprojects.blogspot.com/2011/10/alleluia-load-test-tool-for-silverlight.html

Vincent THAVONEKHAM