Update: There is now a nuget package that will take care of wiring this up automatically see: http://nuget.org/List/Packages/MiniProfiler.MVC3


MiniProfiler seems to have upset quite a few people. One major complain we got when Scott Hanselman blogged about it, goes:

Why do you expect me to sprinkle all these ugly using (MiniProfiler.Current.Step("stuff")) throughout my code. This is ugly and wrong, profilers should never require you to alter your code base. FAIL."

Our usual retort is that you are able to properly control the granularity of your profiling this way. Sure you can introduce aspects using PostSharp or your favorite IOC container, however this method may also force you to refactor your code to accommodate profiling.

That said, I agree that sometimes it may seem tedious and unnecessary to chuck using statements where the framework can do that for us.

Here is a quick document on how you can get far more granular timings out-of-the-box without altering any of your controllers or actions.


Profiling Controllers

Automatically wrapping your controllers with MVC3 is actually quite trivial, it can be done with a GlobalActionFilter

For example:

public class ProfilingActionFilter : ActionFilterAttribute
{

    const string stackKey = "ProfilingActionFilterStack";

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var mp = MiniProfiler.Current;
        if (mp != null)
        {
            var stack = HttpContext.Current.Items[stackKey] as Stack<IDisposable>;
            if (stack == null)
            {
                stack = new Stack<IDisposable>();
                HttpContext.Current.Items[stackKey] = stack;
            }

            var prof = MiniProfiler.Current.Step("Controller: " + filterContext.Controller.ToString() + "." + filterContext.ActionDescriptor.ActionName);
            stack.Push(prof);

        }
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        var stack = HttpContext.Current.Items[stackKey] as Stack<IDisposable>;
        if (stack != null && stack.Count > 0)
        {
            stack.Pop().Dispose();
        }
    }
}

You wire this up in Global.asax.cs

protected void Application_Start()
{
   // other stuff ...

   GlobalFilters.Filters.Add(new ProfilingActionFilter());
   RegisterGlobalFilters(GlobalFilters.Filters);

   /// more stuff 
}

Profiling Views

Views on the other hand a bit more tricky, what you can do is register a special "profiling" view engine that takes care of instrumentation:

public class ProfilingViewEngine : IViewEngine
{
   class WrappedView : IView
    {
        IView wrapped;
        string name;
        bool isPartial;

        public WrappedView(IView wrapped, string name, bool isPartial)
        {
            this.wrapped = wrapped;
            this.name = name;
            this.isPartial = isPartial;
        }

        public void Render(ViewContext viewContext, System.IO.TextWriter writer)
        {
            using (MiniProfiler.Current.Step("Render "  + (isPartial?"partial":"")  + ": " + name))
            {
                wrapped.Render(viewContext, writer);
            }
        }
    }

    IViewEngine wrapped;

    public ProfilingViewEngine(IViewEngine wrapped)
    {
        this.wrapped = wrapped;
    }

    public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        var found = wrapped.FindPartialView(controllerContext, partialViewName, useCache);
        if (found != null && found.View != null)
        {
            found = new ViewEngineResult(new WrappedView(found.View, partialViewName, isPartial: true), this);
        }
        return found;
    }

    public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        var found = wrapped.FindView(controllerContext, viewName, masterName, useCache);
        if (found != null && found.View != null)
        {
            found = new ViewEngineResult(new WrappedView(found.View, viewName, isPartial: false), this);
        }
        return found;
    }

    public void ReleaseView(ControllerContext controllerContext, IView view)
    {
        wrapped.ReleaseView(controllerContext, view);
    }
}

You wire this up in Global.asax.cs:

protected void Application_Start()
{
   // stuff 

   var copy = ViewEngines.Engines.ToList();
   ViewEngines.Engines.Clear();
   foreach (var item in copy)
   {
       ViewEngines.Engines.Add(new ProfilingViewEngine(item));
   }

   // more stuff
}

The trick here is that we return a special view engine that simply wraps the parent and intercepts calls to Render.

The results speak for themselves:

Before

before

After

after
after.png638x542 44.7 KB


There you go, with minimal changes to your app you are now able to get a fair amount of instrumentation. My controller in the above sample had a System.Threading.Thread.Sleep(50); call which is really easy to spot now. My slow view had a System.Threading.Thread.Sleep(20); which is also easy to spot.

The caveat of this approach is that we now have another layer of indirection which has a minimal effect on performance. We will look at porting in these helpers to MiniProfiler in the near future.

Happy profiling, Sam

Comments

Dhananjay_Goyani over 2 years ago
Dhananjay_Goyani

Awesome!

Scott_Hanselman over 2 years ago
Scott_Hanselman

Lovely. Throw these in version 1.7 so they are ubiquitous, and lets you and I talk about the magic of AppStart and make a NuGet package that’s MiniProfiler.MVC3 that hooks all this up automatically.

Sam Saffron over 2 years ago
Sam Saffron

awesome

Marius_Schulz over 2 years ago
Marius_Schulz

Really nice! Please build the NuGet package Scott mentioned — looking forward to it …

Finlay over 2 years ago
Finlay

Small thing, in WrappedView.Render it writes out “parital” instead of partial.

using (MiniProfiler.Current.Step(“ Render ” + (isPartial? “ parital ”: “”)
(fixed quotes so it is rendered properly)

Other than that it looks excellent!

Sam Saffron over 2 years ago
Sam Saffron

smile thanks, fixed

Usman_Masood over 2 years ago
Usman_Masood

Nicely done… great job!!

Augi over 2 years ago
Augi

I’m afraid that the code doesn’t work if there is more requests in parallel.

The problem is that you store request-state in private field of action-filter (prof in ProfilingActionFilter class).
And there is just one instance of the ProfilingActionFilter class.

So you should store the reference to MiniProfiler i.e. into HttpContext.Current.Items.

After this change, I will say: good work, guy! :–)

Sam Saffron over 2 years ago
Sam Saffron

Great catch ... fixed!

Zote over 2 years ago
Zote

Great project, thank you!

Is that possible to redirect results to some place like App_Data? That way when a customer says that site is slow, I can enable profiler for him (based on authentication) and see results, without any modifications to him.

Thank you.

Sam Saffron over 2 years ago
Sam Saffron

we have a demo sql data store, grap the code and have a look at the demo app

Alexandre_Jobin over 2 years ago
Alexandre_Jobin

Really good! thank you for the code!

Bryan_Migliorisi over 2 years ago
Bryan_Migliorisi

FYI, IF you wanted to use PostSharp to enable instrumentation of more than just your MVC actions, I wrote a short blog post with the code for a PostSharp aspect Attribute to do just this..

Use PostSharp to easily allow MiniProfiler to profile your ASP.NET application

Sam Saffron over 2 years ago
Sam Saffron

Nice one!

Hendry_Ten over 2 years ago
Hendry_Ten

Great one.
It’ll be wonderful if the FindPartialView and FindView methods of the ProfilingViewEngine are virtual.

Vijayant over 2 years ago
Vijayant

Thank u..

Anton_Vishnyak over 2 years ago
Anton_Vishnyak

I added a bit more automatic instrumentation. If your site does any amount of real work in custom ActionFilterAttributes, check out my code to instrument them automatically.

Expanding the Mvc-Mini-Profiler to measure and report performance of ActionFilters

Cezar_Gradinariu almost 2 years ago
Cezar_Gradinariu

Hi,

I took today the latest version 2.0.2 and while the UI profiling works like charm, thank you, but for some reason I could not find a way to set the DB profiling at all.

The thing is I am using Devart’s Oracle connector 6.8 and EF code first (EF is 4.3) with MVC 4.

When setting the MiniProfilerPackage.PreStart() I am clueless on what to do to give it the right DbConnection that i can get from Devart. If i do:

var conn = new OracleConnection(“connection string here”);
var profiled = new ProfiledDbConnection(conn, MiniProfiler.Current);

MiniProfilerEF.Initialize();

, I keep getting this error:

Unable to cast object of type ‘StackExchange.Profiling.Data.EFProfiledDbConnection’ to type ‘Devart.Data.Oracle.OracleConnection’.

Is there any possibility to make this combination work?

Thank you for this great tool.

Sam Saffron almost 2 years ago
Sam Saffron

suggest you post on http://community.miniprofiler.com

Darren_H 7 months ago
Darren_H

Hi Sam. I am a noob when it comes to MVC profiling. I have followed all the steps but I am unable to setup the Mini Profiler in any MVC app. Is it possible to post a small demo app with the mini profiler setup?

Thanks.

Sam Saffron 6 months ago
Sam Saffron

There are plenty of resources on this online, see the Stack Overflow mini profiler tag and countless other blog posts.


comments powered by Discourse