views:

354

answers:

8

I've got code like this:

Logger logger = new Logger();
System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
logger.LogInformation("Calling SomeObject.SomeMethod at " + DateTime.Now.ToString());
stopWatch.Start();
// This is the method I'm interested in.
SomeResponse response = someObject.SomeMethod(someParam);
stopWatch.Stop();
logger.LogInformation("SomeObject.SomeMethod returned at " + DateTime.Now.ToString());
logger.LogInformation("SomeObject.SomeMethod took " + stopWatch.ElapsedMilliseconds + " milliseconds.");

I need to wrap similar code around a lot more objects and their methods to do some performance profiling. I'm not allowed to use 3rd party plugins or software, etc.

I'd really rather not write this same code around all of these method calls this all of this logging code. How would you refactor this to eliminate some of my coding effort?

If I'm not being very clear, please ask questions in the comments and I will try to clarify.

Thanks for any help!!

+1  A: 

Definitely a candidate for AOP. We use PostSharp for this sort of thing. http://www.postsharp.org/

Robert
Thanks Robert, but I'm not allowed to use 3rd party code right now.
Terry Donaghe
I'd say it's worth asking your employer about. It's free and doesn't require anything extra on the users' end. It just adds extra code in during the compilation step., and it makes this sort of thing as simple as adding an attribute to a method.If nothing else, you could just use it on your own local copy and make sure all traces are removed before checking in the code. Pain in the ass, but it'll solve your problem.
Chris Doggett
Well, I'm certainly going to start playing with postsharp at home! :)
Terry Donaghe
+6  A: 

For simplicity sake, you could use generics, like so (off the top of my head):

public T MyLogMethod<T,S>(Func<S, T> someFunction, S someParameter) {}

Func(S,T) where S is the parameter type of the method, and T is the return type.

Jonas
+18  A: 

You can refactor the code to accept a method pointer instance (aka System.Action).

public void CallWithLogTiming (Action theAction)
{
  Logger logger = new Logger();
  System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
  logger.LogInformation("Calling SomeObject.SomeMethod at " + DateTime.Now.ToString());
  stopWatch.Start();

// This is the method I'm interested in.
  theAction();

  stopWatch.Stop();
  logger.LogInformation("SomeObject.SomeMethod returned at " + DateTime.Now.ToString());
  logger.LogInformation("SomeObject.SomeMethod took " + stopWatch.ElapsedMilliseconds + " milliseconds.");
}

Then you can call it by creating a lambda expression. Since myResponse is a captured variable, it will be populated when this Action is run and myResponse will be available for use later in this scope.

SomeResponse myResponse = null;
CallWithLogTiming( () => myResponse = someObject.SomeMethod(someParam) );
David B
That's pretty sexy. Will probably confuse the hell out of my poor ex-Java colleagues. lol
Terry Donaghe
That is pretty cool...confuses the hell out of me...what exactly are you doing there? :)
Jonas
What does the () mean in your call sample? Does it just mean you're calling the lambda block without passing anything into it?
Terry Donaghe
The parens are not required in lambda expressions with 1 parameter, they are required in lambda expressions with 0, 2 or more parameters.
David B
I'm curious to try this, but can't get it working...David can you edit with actual code? I keep getting errors about Action requiring a generic parameter...
Jonas
Nevermind...must have been Snippet Compiler getting confused...very slick, David.
Jonas
The only thing you might add to this would be wrap the action in a try / catch and log any exceptions? Certainly a valid logging operation.
Andrew Robinson
Thank you SO much David. I knew that there was a way to do this that involved Delegates and lambda expressions, but didn't know how off the top of my head. You saved me at least an hour or two of research! StackOverflow FTW!
Terry Donaghe
I believe this is called the "execute around" idiom, isn't it?
Michael Myers
+4  A: 

I think I implement a timer class which can be used like this:

void test()
{
  foo();
  //timer for the following statements
  using (new MyTimer("Some method"))
  {
    bar();
  }
  baz();
}

The MyTimer class is implemented as follows:

  • Contains a stop watch instance, and a message string which identifies the timer
  • The constructor starts the stop watch and remember the message string
  • The Dispose method stops the stop watch, and logs the message string and the ellapsed time
ChrisW
I like this. Simple and understandable. +1
David B
+1  A: 

If you write a class like this (I'm using Java; there could be some stuff that doesn't translate exactly):

public class myProfiler {
    final String method;
    final Logger logger = new Logger();
    final System.Diagnostics.Stopwatch stopWatch = new Stopwatch();
    public myProfiler(method) {this.method = method};
    public void Start() {
     logger.LogInformation("Calling " + method + " at " + DateTime.Now.ToString());
     stopWatch.Start();  
    }
    public void Stop() {
     stopWatch.Stop();
     logger.LogInformation(method + " returned at " + DateTime.Now.ToString());
     logger.LogInformation(method + " took " + stopWatch.ElapsedMilliseconds + " milliseconds.");
    }
}

Then you've reduced the code you need in each method to just

myProfiler profiler = new myProfiler("SomeObject.SomeMethod");
profiler.Start();
...
profiler.Stop();
Carl Manaster
+1  A: 

There is always the PostSharp library that lets you do aspect-oriented code. It lets you do the logging and stopwatch as an attribute, which is cool. It will inject pre and post code in your method as a post-compile step.

Further, you might consider some timer/logger static methods like this to wrap the code you want to time/log:

Timer.LogExecutionTime("SomeObject.SomeMethod", () =>
{
    Logger.LogBeforeAndAfter("SomeObject.SomeMethod", () =>
    {
        SomeResponse response = someObject.SomeMethod(someParam);
    }
});
Brian Genisio
+2  A: 

You could make the syntax a little cleaner with some simple extension methods on Logger, this requires no additional assemblies then what you already have and you can plug it in right away. And it is reusable, if you are going to do this a bunch of times throughout your code.

public static class LoggerExtentions
{
    public static void StartTimerLogInformation(this Logger logger, Stopwatch stopWatch, string method)
    {
        stopWatch.Reset();
        stopWatch.Start();
        logger.LogInformation(string.Format("Calling {0} at {1}", method, DateTime.Now.ToString()));
    }        

    public static void StopTimerLogInformation(this Logger logger, Stopwatch stopWatch, string method)
    {
        stopWatch.Stop();
        logger.LogInformation(string.Format("{0} returned at {1}", method, DateTime.Now.ToString()));
        logger.LogInformation(string.Format("{0} took {1} milliseconds", method, stopWatch.ElapsedMilliseconds));
        stopWatch.Reset();
    }
}

then you could just use this code in replacement of the code that you have in your original post

Logger logger = new Logger();
Stopwatch stopWatch = new Stopwatch();
logger.StartTimerLogInformation(stopWatch, "SomeObject.SomeMethod");
SomeResponse response = someObject.SomeMethod(someParam);
logger.StopTimerLogInformation(stopWatch, "SomeObject.SomeMethod");
Jon Erickson
A: 

How about you inherit the timer class on all of your objects?

Mike Curry