Menu

Home

Trevor Ash

SeaToad

This tool is used for creating load or testing concurrency inside .NET programs. Load is defined by a class which implements the IWorkTask interface. Any instance of such a class can then be passed, along with a LoadProfile, to the WorkManager. The WorkManager creates threads, and divvies up the specified work among them.

As a result, you can easily construct programs which consume resources, simulate stress, and identify concurrency issues using carefully constructed load characteristics.

Features

  • Compatible with unit testing frameworks such as NUnit and MSBuild.
  • Configurable thread ramp up time.
  • Quickly build scenarios because actual instances of your classes are used. Often it’s as simple as implementing the interfaces on an existing class.
  • High precision via use of Stopwatch instead of DateTime for measuring performance (DateTime is limited to ~10ms).
  • Because work items are instances of classes, each work task can be unique and contain unique properties.
  • Work instances are evenly distributed among all available threads (ensuring that each is executed as often as the others).
  • Supports as many concurrent threads as the system can handle.
  • Separate “Setup” and “Teardown” methods isolate performance metrics where you want it.

Creating Load

You must create a class and implement the IWorkTask interface. Here is a bare bones example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SeaToad.Interfaces;

namespace SeaToadExample
{
   public class MyLoad : IWorkTask
   {
      #region IWorkTask Members

      public void Setup()
      {
         throw new NotImplementedException();
      }

      public void Execute()
      {
         throw new NotImplementedException();
      }

      public void Teardown()
      {
         throw new NotImplementedException();
      }

      #endregion
   }
}

When the WorkManager runs an instance of your MyLoad task, it will first call Setup, then call Execute, and finally call Teardown. Put the work that you want to have measured inside of Execute and all other work inside Setup and/or Teardown.

For example, if you’re trying to measure the performance of a stored procedure, then you would want to ensure that you setup any Database Connections inside of Setup so that the time involved in doing that doesn’t impact the duration of the Execute method. Then, use your already configured Database Connection object inside of the Execute method.

Note that if WorkManager catches an exception during Setup, that Execute will not be called, but Teardown will. Similarly if Execute throws and exception, Teardown will still be called. This is important as anything you do inside Teardown cannot assume that your Setup and Execute methods completed without exception. Be sure to write your Teardown code carefully.

Regardless of where exceptions were thrown from, WorkManager will catch and report which method it occurred in along with the exception message in real time to the console.

Creating a Load Profile

A load profile tells the WorkManager how many concurrent threads and for how long, in addition to some other properties.

Here’s an example of a LoadProfile which will run 5 threads for 5 seconds with a 2 second ramp up time. The boolean value indicates whether the entire run should be aborted when a Work instance fails (a handy option for stopping the test when looking for concurrency issues).

int FiveConcurrentThreads = 5;
TimeSpan FiveSecondDuration = new TimeSpan(0, 0, 5);
TimeSpan TwoSecondRampUp = new TimeSpan(0, 0, 2);

//create a load profile
LoadProfile loadProfile = new LoadProfile(
   FiveConcurrentThreads,
   FiveSecondDuration,
   TwoSecondRampUp,
   true);

Creating a Work Manager

The WorkManager is what does all of the work. It needs only two things: A LoadProfile, and Work.

The following example shows creating, and adding one Work instance:

WorkManager workManager = new WorkManager(loadProfile);
ExampleLoad1 w1 = new ExampleLoad1(500);
workManager.AddWorkInstance(w1, null);

The second parameter to AddWorkInstance is an optional string that you can use to uniquely identify the thread in the test results. In the example above, when passing null for the value, the test results will show the thread as “ExampleWork1(##)” where ## is the index of the Work Instance as maintained by WorkManager. If you provide a non-null value such as “Trevor” then the thread name would be “ExampleWork1-Trevor”. If you choose to use a non null id for AddWorkInstance, try to ensure your Id’s are unique so that you can more easily understand the results.

You can add an infinite amount of Work Instances to the Work Manager. However, it is recommended that you have at least one Work Instance for every thread specified by Max Threads. Otherwise, WorkManager will not be able to create the desired amount of threads.

Finally, ensure each Work instance passed to AddWorkInstance is a unique instance of a class (do not pass the same instance multiple times to AddWorkInstance as WorkManager will almost surely encounter concurrency issues or other bizarre behavior).

Running the Work Manager

Once you’ve added the necessary Work instances it’s a matter of calling Run, as in this example:

WorkManagerResults results = workManager.Run();

WorkManagerResults contains some very basic result information such as pass and fail count. But you can also enumerate through the results of every single run. See the code in the SeaToadExample class to learn a couple ways to parse and report on the results.

Project Admins: