views:

150

answers:

2

Some code I just wrote follows.

It demonstrates applying a PostSharp aspect to a method for the purposes of recording the duration of the method invocation in an asynchronous manner - so that if the logging process is slow then this performance penalty is not seen by the caller of the method decorated with the aspect.

It seems to work, with MyFirstMethod completing, the logging method being set off in a separate thread, and MySecondMethod running on in parallel. The idea is that methods within a very highly-trafficked web application (i.e. a highly multi-threaded environment) be decorated with similar instrumentation.

What are the pitfalls of doing so? (e.g. I am concerned about reaching a limit on the number of threads permitted at any given time).

using System;
using System.Threading.Tasks;
using NUnit.Framework;
using PostSharp.Aspects;

namespace Test
{
    [TestFixture]
    public class TestClass
    {        
        [Test]
        public void MyTest()
        {            
            MyFirstMethod();
            MySecondMethod();
        }

        [PerformanceInstrument]
        private void MyFirstMethod()
        {
            //do nothing
        }

        private void MySecondMethod()
        {
            for (int x = 0; x < 9999999; x++);
        }
    }

    [Serializable]
    public class PerformanceInstrument : MethodInterceptionAspect
    {                    
        public override void OnInvoke(MethodInterceptionArgs args)
        {            
            var startDtg = DateTime.Now;
            args.Proceed();
            var duration = DateTime.Now - startDtg;
            Task.Factory.StartNew(() => MyLogger.MyLoggingMethod(duration)); //invoke the logging method asynchronously
        }        
    }

    public static class MyLogger
    {
        public static void MyLoggingMethod(TimeSpan duration)
        {
            for (int x = 0; x < 9999999; x++);
            Console.WriteLine(duration);
        }
    }
}
+2  A: 

The only possible downside that I see here is the overhead of managing the Tasks, which I am sure is probably insignificant, but I have not delved deaply into the TPL stuff to be certain.

An alternative approach that I have used in a large scale web application is to have the logging write the log message records to an in memory list, then I have a backgound thread that is responsible for writing the log messages out in the background. Currently the solution has the thread check the list every so often and flush the list to disk (in our case database) if the list length has exceeded a certain threashold or the list has not been flush for longer than a specific amount of time, which ever comes first.

This is something like the Producer/Consumer pattern, where you code produces log messages and the consumer is reponsible for flushing those messages to the persistence medium.

Chris Taylor
Just to clarify. In the worst case scenario, where there are a huge number of users of the application, and logging takes a long time, as i understand it, the number of Tasks blocked, waiting to complete their logging will grow quickly. You don't believe this will be an issue?
Ben Aston
My gut feeling is that using the Producer/Consumer type pattern I describe would have less impact on the system than having individual tasks queued for each log entry, but as I say I am not deeply familiar with TPL to know for sure, it might well handle this flawlessly. Using Produder/Consumer the only additional thread is the background thread responsible for persisting the log messages.
Chris Taylor
Chris - thanks for your insights. Please can you expand a little on your Producer/Consumer implementation. Your in-memory list will presumably have to be thread-safe (or surrounded by a lock). This would appear to preclude the advantage of having logging occur asynchronously, as progress through method calls will block on access to the thread-safe collection. I suspect I do not understand something about your implementation.
Ben Aston
@Ben, using a simple List from the .NET would require locking. For our implementation I implemented a lock free list. Before you go this route, if I was you I would confirm that the contention on a lock is going to introduce any measurable overhead. I doubt lock contention if any will be measurable. As for the consumer side, using a lock free list makes it easy not to have to lock the list to remove items. You can also lock and swap with an empty list unlock, then consumer drains the passive list which would be swapped in next flush cycle. Do you know your request/sec rate for the web site?
Chris Taylor
@Ben, btw. for .NET 4 you can look at ConcurrentQueue (http://msdn.microsoft.com/en-us/library/dd267265.aspx) which provides a lock free queue. Which could be ideal for this.
Chris Taylor
@Chris - I haven't used lock-free lists before. There is a good deal for me to learn. On a hunch, I'm inclined to agree regarding lock contention in my implementation being a non-issue. I want to explore all the possibilities though. The website gets 100000 unique visitors/day ballpark. I dont have the exact figures, but say thats constrained to the 12 "daytime" hours (a national site), that's 8k uniques an hour with even distribution, say there is a 3x spike, thats 24000 users / hour. Pulling a generous figure of 10 pages requests avg. from thin air, thats 240k req. / hour, 65 req / second.
Ben Aston
+1  A: 

There may be unintended consequences of your approach because the ASP.NET engine and the Task Parallel Library are both scheduling tasks on the .NET thread pool. Each web request is serviced by a thread from the thread pool. If you as scheduling Tasks to handle logging then you will be using tasks on the thread pool which can no longer be used to service web requests. This may reduce throughput.

The TPL team blogged about this here.

http://blogs.msdn.com/b/pfxteam/archive/2010/02/08/9960003.aspx

The producer/consumer pattern would mean that your MethodInterceptionAspect would simply add an entry into a global queue (as suggested by Ben) and then you would have a single (long running) Task which processed all the entries. So your intercaption method becomes:

ConcurrentQueue<TimeSpan> _queue;

public override void OnInvoke(MethodInterceptionArgs args)
{
    var startDtg = DateTime.Now;
    args.Proceed();
    var duration = DateTime.Now - startDtg;
    Task.Factory.StartNew(() => _queue.Add(duration)); 
}

Somewhere else you process the queue:

foreach (var d in _queue.GetConsumingEnumerable())
{
    Console.WriteLine(d);
}

The following post shows a similar implementation where multiple tasks created by a Parallel.For loop add images to a BlockingCollection and a single task processes the images.

http://stackoverflow.com/questions/3003656/parallel-task-library-waitany-design/3122818#3122818

How well this works depends a bit on the length of your request processing, the number of log entries your want to process per request and the overall server load etc. One thing you have to be aware of is that overall you need to be able to remove requests from the queue faster than they are being added.

Have you thought about an approach where you write your own performance counter and have the perf counter infrastructure handle the heavy lifting for you? This would save you needing to implement any of this recording infrastructure.

Ade Miller
@Ade - thanks for this great response. Will review this carefully after work. My latest implementation https://bitbucket.org/benaston/blog/src/tip/BJ.MA.Utilities.CodeMetrics/UnitTests/MethodCallTimerAspectTests.cs
Ben Aston