Using timer based Unit of Work and Command classes to measure repositories performance
In my last post Find out how long your method runs I introduced how to measure the speed of code using actions. In this posting I will make a step further and give you some ideas about how to create easily measurable code units and how to build measurable scenarios of them. As a foundation I use simplified versions of patterns like Command and Unit of Work.
Here is what I am doing and what I want to achieve:
- I have database with many rows,
- I have core library that defines interfaces for repositories,
- I have two implementations of repositories – NHibernate and Entity Framework,
- I have simple project for playing with database and repositories,
- I want to be able to write some simple functional units that perform tasks using repositories,
- I want to measure how my repositories and tasks perform and how fast their code runs.
I have to warn you about something too…
NB! All code here is experimental and under heavy construction. Take this code as a snapshot and don’t consider it as complete framework for some more serious diagnostics and performance measuring. During time I will add more commands to my current solution and it is 100% sure that all things you find here are subjects to change.
NB! Patterns of Enterprise Architecture by Martin Fowler describes these pattern in the context of business applications and his patterns are also ready for locks and transactions. I have here no support for locks and transactions as this code is still experimental and I have not started with locks and transactions yet. I have here way simpler and very basic implementation of patterns mentioned.
Here is simple class diagram generated by Visual Studio illustrating some classes I have.
Now you have some ideas about what I have and it is time to get our hands dirty.
Commands
Let’s start with short discussion about Command pattern. Command pattern allows us to handle code units offering different functionality as commands using same interface. This interface in it’s minimal form defines just one method so commands can be executed.
Here is the interface I created for commands.
public interface ICommand
{
void Execute();
bool RequiresWarmupCall { get; }
}
RequiresWarmupCall is nasty hack that should be somewhere else but right now it can be where it is. But be warned that I plan to move it to some other place very soon.
To get some information about how one or another command worked I defined interface IDatabaseCommand that has additional members for returned row count and row count for not paged results. Here is how IDatabaseCommand is defined.
public interface IDatabaseCommand : ICommand
{
long RowsReturned { get; }
long RowsInUnpagedResult { get; }
}
And here is example of one of my commands. This command calls ListOrders() method of given repository and cycled through all results returned so I can be sure that results are also retrieved when some of repositories supports lazy loading (and they all do).
public class ListOrdersCommand : IDatabaseCommand
{
private readonly IOrderRepository _repository;
private readonly int _page;
private readonly int _pageSize;
public ListOrdersCommand(IOrderRepository repository,
int page, int pageSize)
{
_repository = repository;
_page = page;
_pageSize = pageSize;
}
public void Execute()
{
var results = _repository.ListOrders(_page, _pageSize);
foreach (var order in results.Results)
foreach (var line in order.OrderLines)
;
RowsReturned = results.Results.Count;
RowsInUnpagedResult = results.RowCount;
}
public bool RequiresWarmupCall
{
get { return true; }
}
public long RowsReturned
{
get;
private set;
}
public long RowsInUnpagedResult
{
get;
private set;
}
}
In Execute() method I can, of course, do more than I did here. This is just one of simpler commands.
Unit of Work
To be able to run sequence of commands I defined my own Unit of Work. Okay, it is simple and non-transactional unit of work but later I will add transactions support there too. Here is my Unit of Work how it is defined.
public class UnitOfWork
{
private readonly List<ICommand> _commands = new List<ICommand>();
public virtual void Commit()
{
foreach (var command in Commands)
command.Execute();
}
public IList<ICommand> Commands
{
get { return _commands; }
}
public void Merge(UnitOfWork uow)
{
foreach (var command in uow.Commands)
Commands.Add(command);
}
}
It is simple and straightforward, open wide to world and I am sure we can find more issues if we want. But for now it is enough and I am the only customer of this code, so I will modify it later when there is need for it.
To measure the work units I extended UnitOfWork and created class TimerBasedUnitOfWork.
public class TimerBasedUnitOfWork : UnitOfWork
{
public override void Commit()
{
var stopper = new Stopwatch();
Time = 0;
TimeTable = new Dictionary<ICommand, long>();
foreach(var command in Commands)
{
if (command.RequiresWarmupCall)
command.Execute();
stopper.Reset();
stopper.Start();
command.Execute();
stopper.Stop();
TimeTable.Add(command, stopper.ElapsedMilliseconds);
Time += stopper.ElapsedMilliseconds;
}
}
public long Time { get; private set; }
public Dictionary<ICommand, long> TimeTable { get; private set; }
}
This class has all the same functionality as its parent but it is able to measure the time that commands take to run. It also supports warm-up calls so before measuring commands that read data from database I can make the call during what mapper and database can do all their preparing activities.
Comparing mappers performance
Now let’s see how I compare mappers performance in my test application.
static void Main()
{
var uow = OrdersForProduct(ProductId);
uow.Commit();
foreach(var entry in uow.TimeTable)
{
var command = entry.Key as IDatabaseCommand;
if (command == null)
throw new ArgumentNullException();
Console.Write(command.GetType());
Console.Write(": ");
Console.Write(entry.Value);
Console.Write(" ms, rows: ");
Console.Write(command.RowsReturned);
Console.Write(" / ");
Console.WriteLine(command.RowsInUnpagedResult);
}
Console.WriteLine("\r\nPress any key to exit ...");
Console.ReadLine();
}
private static TimerBasedUnitOfWork OrdersForProduct(Guid productId)
{
var nhOrderRepository = new NhRepository.OrderRepository(Session);
var efOrderRepository = new EfRepository.OrderRepository(Context);
var nhCommand = new OrdersForProductCommand(nhOrderRepository, productId);
var efCommand = new OrdersForProductCommand(efOrderRepository, productId);
var uow = new TimerBasedUnitOfWork();
uow.Commands.Add(nhCommand);
uow.Commands.Add(efCommand);
return uow;
}
Okay, this code should be illustrative enough to get idea what I have built.
Hot to use it?
So what I can do using my Unit of Work and ICommand? I can do a lot of things:
- I am able to measure how long my unit are running,
- I can define simple commands that carry one specific functionality,
- I can use different repositories with my commands,
- I can combine my commands to units of work and handle sets of commands as one unit to execute and measure,
- I can merge separate units of work to create more complex API usage scenarios.
By example, I can create the following commands:
- ListOrdersCommand – lists orders for first page of result set,
- ListOrdersForCustomerCommand – lists orders for given customer and select second page,
- SearchProductByNamePart – returns all products that contain given string in their names,
- AddOrderForCustomerCommand – adds new order for customer.
From these commands I can build up scenario like inserting new order:
- user opens orders form and first 10 orders are listed,
- user filters orders by customer and first 10 orders for customer are listed,
- user opens new order form and selects product for order line,
- user saves new order,
- user is returned back to orders list where active filter is restored.
I have no need for user interface, I can easily define functional units to perform different tasks and I can measure how good or bad my repositories perform. This far the most time consuming part has been messing with NHibernate mappings.
A bit off topic, but are NHibernate and Entity Framework tests going against the same database? Do not forget about the database buffer cache. To make things fair, you may wish to create a new command that clears the caches before executing each test.
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
Thanks for feedback, Phil! Currently I let some command to “warm-up” database and then I run my tests. When considering live environment then there is no cache and buffers cleaning before all requests.
But for exact measurements of mappers these commands are needed.