Как измерить и контролировать Autofac время разрешения отдельных компонентов - PullRequest
5 голосов
/ 04 июня 2019

Я недавно начал работать в организации, имеющей огромную базу кода, которая использует Autofac в качестве своего предпочтительного DI-контейнера.

К сожалению, не все разработчики следуют передовым методам при написании своих классов, что означаетиногда они вызывают внешние службы и выполняют другие тяжелые работы внутри своих конструкторов.Это запах кода DI, так как конструкторы инъекций должны быть простыми .

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

Как бы я достиг такой цели? Какие точки перехвата предоставляет Autofac, что позволяет проводить это измерение?

С помощью простого инжектора я могу добиться этого, зарегистрировав перехватчик разрешения, как показано в этом примере:

container.Options.RegisterResolveInterceptor((context, producer) =>
    {
        var watch = Stopwatch.StartNew();
        try
        {
            return producer.Invoke();
        }
        finally
        {
            if (watch.TotalElapsedMiliseconds > THRESHOLD)
            {
                Console.WriteLine(
                    $"Resolving {context.Registration.ImplementationType} " +
                    $"took {watch.TotalElapsedMiliseconds} ms.");
            }
        }
    },
    c => true);

Как добиться аналогичного результата с Autofac?

1 Ответ

5 голосов
/ 04 июня 2019

Используя модуль Autofac, вы можете подключиться к событиям Preparing и Activating:

Autofac регистрация:

builder.RegisterModule<TimeMeasuringResolveModule>();

TimeMeasuringResolveModule:

public class TimeMeasuringResolveModule : Module
{
    private readonly ResolveInfo _current;

    protected override void AttachToComponentRegistration(
        IComponentRegistry componentRegistry, IComponentRegistration registration)
    {
        registration.Preparing += Registration_Preparing;
        registration.Activating += Registration_Activating;

        base.AttachToComponentRegistration(componentRegistry, registration);
    }

    private void Registration_Preparing(object sender, PreparingEventArgs e)
    {
        // Called before resolving type
        _current = new ResolveInfo(e.Component.Activator.LimitType, _current);
    }

    private void Registration_Activating(object sender, ActivatingEventArgs<object> e)
    {
        // Called when type is constructed
        var current = _current;
        current.MarkComponentAsResolved();
        _current = current.Parent;

        if (current.Parent == null)
        {
            ResolveInfoVisualizer.VisualizeGraph(current);
        }
    }
}

ResolveInfo:

public sealed class ResolveInfo
{
    private Stopwatch _watch = Stopwatch.StartNew();

    public ResolveInfo(Type componentType, ResolveInfo parent)
    {
        ComponentType = componentType;
        Parent = parent;
        Dependencies = new List<ResolveInfo>(4);

        if (parent != null) parent.Dependencies.Add(this);
    }

    public Type ComponentType { get; }
    public List<ResolveInfo> Dependencies { get; }

    // Time it took to create the type including its dependencies
    public TimeSpan ResolveTime { get; private set; }

    // Time it took to create the type excluding its dependencies
    public TimeSpan CreationTime { get; private set; }
    public ResolveInfo Parent { get; }

    public void MarkComponentAsResolved()
    {
        ResolveTime = _watch.Elapsed;
        CreationTime = ResolveTime;

        foreach (var dependency in this.Dependencies)
        {
            CreationTime -= dependency.ResolveTime;
        }
    }
}

ResolveInfoVisualizer:

public static class ResolveInfoVisualizer
{
    public static void VisualizeGraph(ResolveInfo node, int depth = 0)
    {
        Debug.WriteLine(
            $"{new string(' ', depth * 3)}" +
            $"{node.ComponentType.FullName} " +
            $"({node.ResolveTime.TotalMilliseconds.ToString("F1")} ms. / " +
            $"/ {node.CreationTime.TotalMilliseconds.ToString("F1")} ms.));

        foreach (var dependency in node.Dependencies)
        {
            VisualizeGraph(dependency, depth + 1);
        }
    }
}

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

Также обратите внимание, что хотя этот модуль генерирует графы объектов, он не выводит граф репрезентативных объектов, а состоит только из объектов, которые фактически активируются во время этого разрешения. Например, уже инициализированные синглтоны не будут отображаться на графике, так как они фактически не используются. Для визуализации достоверных графов объектов следует использовать другой метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...