Получение большей "детализации" от MVC Mini Profiler

Если это окажется полезным для всех, я с радостью превращу его в вики-сообщество сообщества.

У меня есть несколько медленных страниц в приложении MVC3, и, поскольку в моем коде было мало времени выполнения, я хотел посмотреть, могу ли я узнать больше о том, что заняло так много времени. Не то, чтобы я преуспел, но я получил немного больше мудрости на этом пути.

Здесь ничего нет, что не очевидно никому с опытом MVC. В принципе, я создал свой собственный ActionFilterAttribute, который выглядит так:

public class ProfilerAttribute : ActionFilterAttribute
{
    IDisposable actionStep = null;
    IDisposable resultStep = null;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        actionStep = MiniProfiler.Current.Step("OnActionExecuting " + ResultDescriptor(filterContext));
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (actionStep != null)
        {
            actionStep.Dispose();
            actionStep = null;
        }
        base.OnActionExecuted(filterContext);
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        resultStep = MiniProfiler.Current.Step("OnResultExecuting " + ResultDescriptor(filterContext));
        base.OnResultExecuting(filterContext);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (resultStep != null)
        {
            resultStep.Dispose();
            resultStep = null;
        }
        base.OnResultExecuted(filterContext);
    }

    private string ResultDescriptor(ActionExecutingContext filterContext)
    {
        return filterContext.ActionDescriptor.ControllerDescriptor.ControllerName + "." + filterContext.ActionDescriptor.ActionName;
    }

    private string ResultDescriptor(ResultExecutingContext filterContext)
    {
        var values = filterContext.RouteData.Values;

        return String.Format("{0}.{1}", values["controller"], values["action"]);
    }

Кажется, что это работает хорошо, и в моем случае я узнал, что большую часть времени фактически тратится на часть выполнения ResultExecuting, а не внутри моих действий.

Однако у меня есть некоторые вопросы об этом подходе.

1) Является ли это безопасным для пользователя способом? Я предполагаю, что нет, так как actionfilter создается только один раз, в методе RegisterGlobalFilters() в Global.asax.cs. Если сразу появятся два запроса, actionStep и resultStep будут бесполезны. Это правда? Если да, может ли кто-то, кто знает больше, чем я, способствовал бы умному способу справиться с этим? Работает для меня во время локального профилирования машины, но, вероятно, не так много развертывается на сервере с несколькими людьми, делающими запросы одновременно.

2) Есть ли способ получить более полное представление о процессе выполнения результата? Или я должен просто согласиться с тем, что визуализация представления и т.д. Требует времени? В моем собственном приложении я гарантирую, что весь доступ к базе данных закончен до того, как мой метод действия закончен (с использованием NHibernate Profiler в моем случае), и я хотел бы, чтобы мои представления были тонкими и простыми; Однако любое понимание того, что замедляет рендеринг, все равно может быть полезным. Думаю, использование Mini Profiler в моих объектах модели появилось бы здесь, если бы был выполнен какой-то медленный код с моей стороны.

3) Методы ResultDescriptor, вероятно, являются злыми и ядовитыми. Они работали на меня в моих тестах, но, вероятно, их нужно было заменить чем-то более надежным. Я просто пошел с первыми версиями, которые дали мне что-то наполовину полезное.

Любые другие комментарии к этому также будут очень желанными, даже если они "Это плохая идея, иди умереть в одиночку".

Ответ 1

Это выглядит как классная идея. Я считаю, что это НЕ - безопасный способ делать вещи.

Вы можете связать его с HttpContext.Items следующим образом

HttpContext.Items.Add("actionstep", actionStep);
HttpContext.Items.Add("resultstep", resultStep);

И затем извлеките его аналогичным образом

actionStep = HttpContext.Items["actionstep"];
resultStep = HttpContext.Items["resultstep"];

Очевидно, что вы ставите свои собственные проверки на нули и т.д.

HttpContext для каждого пользователя/запроса отличается.

Что нужно помнить о HttpContext.Current.Session.SessionID, которое я иногда забываю, что это SessionId текущего HTTP-запроса (т.е. он меняется каждый раз, когда вы нажимаете F5 или иным образом создаете новый запрос). Другая важная вещь, которую следует помнить, заключается в том, что, хотя в любой момент времени все значения HttpContext.Current.Session.SessionID обязательно уникальны (т.е. по одному для каждого пользователя или запроса), их можно повторно использовать, поэтому не думайте о них как о GUID, которые используется только один раз.

Ответ 2

В сборке MiniProfiler уже есть атрибут фильтра действий, который выполняет профилирование для действий. Он находится в пространстве имен StackExchange.Profiling.MVCHelpers и называется ProfilingActionFilter. Вы можете расширить его, чтобы также просмотреть свои представления.

Он использует тот же подход, что и описанный @Dommer, но вместо того, чтобы хранить IDisposable напрямую, он хранит Stack в HttpContext.Current.Items. Вы можете сделать то же самое для представлений.

Вот код для профилирования действия:

/// <summary>
/// This filter can be applied globally to hook up automatic action profiling
/// 
/// </summary>
public class ProfilingActionFilter : ActionFilterAttribute
{
    private const string stackKey = "ProfilingActionFilterStack";

    /// <summary>
    /// Happens before the action starts running
    /// 
    /// </summary>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (MiniProfiler.Current != null)
        {
            Stack<IDisposable> stack = HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] as Stack<IDisposable>;
            if (stack == null)
            {
                stack = new Stack<IDisposable>();
                HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] = (object) stack;
            }
            MiniProfiler current = MiniProfiler.Current;
            if (current != null)
            {
                RouteValueDictionary dataTokens = filterContext.RouteData.DataTokens;
                string str1 = !dataTokens.ContainsKey("area") || string.IsNullOrEmpty(dataTokens["area"].ToString()) ? "" : (string) dataTokens["area"] + (object) ".";
                string str2 = Enumerable.Last<string>((IEnumerable<string>) filterContext.Controller.ToString().Split(new char[1] { '.' })) + ".";
                string actionName = filterContext.ActionDescriptor.ActionName;
                stack.Push(MiniProfilerExtensions.Step(current, "Controller: " + str1 + str2 + actionName, ProfileLevel.Info));
            }
        }
        base.OnActionExecuting(filterContext);
    }

    /// <summary>
    /// Happens after the action executes
    /// 
    /// </summary>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        Stack<IDisposable> stack = HttpContext.Current.Items[(object) "ProfilingActionFilterStack"] as Stack<IDisposable>;
        if (stack == null || stack.Count <= 0) return;
        stack.Pop().Dispose();
    }
}

Надеюсь на эту помощь.

Ответ 3

Вы можете просто обернуть метод ExecuteCore на контроллере.:)