如何测量和监控单个组件的 Autofac 解析时间
How to measure and monitor Autofac resolve time of individual components
我最近开始为一家拥有庞大代码库的组织工作,该组织使用 Autofac 作为他们选择的 DI 容器。
不幸的是,并非所有开发人员在编写 类 时都遵循最佳实践,这意味着他们有时会调用外部服务并在 内部 其构造函数中执行其他繁重的工作.这是 DI 代码的味道,如 injection constructors should be simple.
考虑到应用程序的大小,以及处理代码的开发人员数量,逐一审查所有现有的 类 是不可行的。相反,我想编写一个连接到 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);
如何使用 Autofac 获得类似的结果?
使用 Autofac 模块,您可以连接到 Preparing
和 Activating
事件:
Autofac 注册:
builder.RegisterModule<TimeMeasuringResolveModule>();
TimeMeasuringResolveModule:
public class TimeMeasuringResolveModule : Module
{
private 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);
}
}
}
解析信息:
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);
}
}
}
您通常应该在单元测试中使用输出,而不是记录到调试 Window。
警告:TimeMeasuringResolveModule
是 不是 thread-safe。考虑到此模块的性能开销,您应该仅将其用作单个集成测试的一部分。
另请注意,尽管此模块确实会生成对象图,但它不会输出具有代表性的对象图,而仅包含在该解析期间实际激活的对象。例如,已经初始化的单例不会出现在图中,因为它们实际上是 no-ops。对于真实对象图的可视化,应该使用不同的方法。
我最近开始为一家拥有庞大代码库的组织工作,该组织使用 Autofac 作为他们选择的 DI 容器。
不幸的是,并非所有开发人员在编写 类 时都遵循最佳实践,这意味着他们有时会调用外部服务并在 内部 其构造函数中执行其他繁重的工作.这是 DI 代码的味道,如 injection constructors should be simple.
考虑到应用程序的大小,以及处理代码的开发人员数量,逐一审查所有现有的 类 是不可行的。相反,我想编写一个连接到 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);
如何使用 Autofac 获得类似的结果?
使用 Autofac 模块,您可以连接到 Preparing
和 Activating
事件:
Autofac 注册:
builder.RegisterModule<TimeMeasuringResolveModule>();
TimeMeasuringResolveModule:
public class TimeMeasuringResolveModule : Module
{
private 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);
}
}
}
解析信息:
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);
}
}
}
您通常应该在单元测试中使用输出,而不是记录到调试 Window。
警告:TimeMeasuringResolveModule
是 不是 thread-safe。考虑到此模块的性能开销,您应该仅将其用作单个集成测试的一部分。
另请注意,尽管此模块确实会生成对象图,但它不会输出具有代表性的对象图,而仅包含在该解析期间实际激活的对象。例如,已经初始化的单例不会出现在图中,因为它们实际上是 no-ops。对于真实对象图的可视化,应该使用不同的方法。