如何测量和监视Autofac解决单个组件的时间

Ste*_*ven 5 .net c# dependency-injection autofac

我最近开始为一个拥有庞大代码库的组织工作,该组织使用Autofac作为他们选择的DI容器。

不幸的是,并不是所有的开发人员在编写类时都遵循最佳实践,这意味着他们有时会调用外部服务,并构造函数内部进行其他繁重的工作。这是DI代码的味道,因为注入构造函数应该很简单

考虑到应用程序的大小以及使用该代码的开发人员的数量,一一审查所有现有的类是不可行的。相反,我想编写一个集成测试,该测试可以连接到Autofac管道并报告解析,而这需要花费相当长的时间。

我将如何实现这样的目标?Autofac会暴露哪些拦截点以允许进行此测量?

使用Simple Injector,我可以通过注册解析拦截器来实现此目标,如以下示例所示:

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);
Run Code Online (Sandbox Code Playgroud)

如何用Autofac获得相似的结果?

Ste*_*ven 5

使用Autofac模块,您可以连接到PreparingActivating事件:

Autofac注册:

builder.RegisterModule<TimeMeasuringResolveModule>();
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通常应在单元测试中使用输出,而不是登录到“调试”窗口。注意,这TimeMeasuringResolveModule不是线程安全的。考虑到该模块的性能开销,您应该仅将其用作单个集成测试的一部分。

还要注意,尽管此模块确实会生成对象图,但它不会输出代表对象图,而仅包含在该解析过程中实际激活的对象。例如,已经初始化的单例将不会显示在图中,因为它们实际上是无操作的。为了可视化真实的对象图,应使用其他方法。