Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Extensions.DependencyInjection.ServiceLookup;

namespace Microsoft.Extensions.DependencyInjection
Expand All @@ -14,9 +16,16 @@ internal sealed class DependencyInjectionEventSource : EventSource
{
public static readonly DependencyInjectionEventSource Log = new DependencyInjectionEventSource();

// Event source doesn't support large payloads so we chunk formatted call site tree
public static class Keywords
{
public const EventKeywords ServiceProviderInitialized = (EventKeywords)0x1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might need to specify a keyword for the other events in this provider now. If you turn on keyword 0x1, you'll just get the ServiceProviderInitialized events. If you specify keyword 0, I'm not sure you'll get the others - worth checking via ETW.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you turn on keyword 0x1, you'll just get the ServiceProviderInitialized events.

That was my intention. The existing events all check for EventKeywords.All:

public void CallSiteBuilt(Type serviceType, ServiceCallSite callSite)
{
if (IsEnabled(EventLevel.Verbose, EventKeywords.All))

So if you don't specify All, you won't get those events.

If you specify keyword 0, I'm not sure you'll get the others - worth checking via ETW.

When I do

dotnet trace collect --providers Microsoft-Extensions-DependencyInjection:0:Verbose --name OrchardCoreTest

I don't get any events logged.

When I do

 dotnet trace collect --providers Microsoft-Extensions-DependencyInjection::Verbose --name OrchardCoreTest

I get them all.

And when I do

dotnet trace collect --providers Microsoft-Extensions-DependencyInjection:1:Verbose --name OrchardCoreTest

I just get the 2 new ones.

}

// Event source doesn't support large payloads so we chunk large payloads like formatted call site tree and descriptors
private const int MaxChunkSize = 10 * 1024;

private readonly List<WeakReference<ServiceProvider>> _providers = new();

private DependencyInjectionEventSource() : base(EventSourceSettings.EtwSelfDescribingEventFormat)
{
}
Expand All @@ -32,27 +41,27 @@ private DependencyInjectionEventSource() : base(EventSourceSettings.EtwSelfDescr
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Parameters to this method are primitive and are trimmer safe.")]
[Event(1, Level = EventLevel.Verbose)]
private void CallSiteBuilt(string serviceType, string callSite, int chunkIndex, int chunkCount)
private void CallSiteBuilt(string serviceType, string callSite, int chunkIndex, int chunkCount, int serviceProviderHashCode)
{
WriteEvent(1, serviceType, callSite, chunkIndex, chunkCount);
WriteEvent(1, serviceType, callSite, chunkIndex, chunkCount, serviceProviderHashCode);
}

[Event(2, Level = EventLevel.Verbose)]
public void ServiceResolved(string serviceType)
public void ServiceResolved(string serviceType, int serviceProviderHashCode)
{
WriteEvent(2, serviceType);
WriteEvent(2, serviceType, serviceProviderHashCode);
}

[Event(3, Level = EventLevel.Verbose)]
public void ExpressionTreeGenerated(string serviceType, int nodeCount)
public void ExpressionTreeGenerated(string serviceType, int nodeCount, int serviceProviderHashCode)
{
WriteEvent(3, serviceType, nodeCount);
WriteEvent(3, serviceType, nodeCount, serviceProviderHashCode);
}

[Event(4, Level = EventLevel.Verbose)]
public void DynamicMethodBuilt(string serviceType, int methodSize)
public void DynamicMethodBuilt(string serviceType, int methodSize, int serviceProviderHashCode)
{
WriteEvent(4, serviceType, methodSize);
WriteEvent(4, serviceType, methodSize, serviceProviderHashCode);
}

[Event(5, Level = EventLevel.Verbose)]
Expand All @@ -62,52 +71,206 @@ public void ScopeDisposed(int serviceProviderHashCode, int scopedServicesResolve
}

[Event(6, Level = EventLevel.Error)]
public void ServiceRealizationFailed(string? exceptionMessage)
public void ServiceRealizationFailed(string? exceptionMessage, int serviceProviderHashCode)
{
WriteEvent(6, exceptionMessage, serviceProviderHashCode);
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Parameters to this method are primitive and are trimmer safe.")]
[Event(7, Level = EventLevel.Informational, Keywords = Keywords.ServiceProviderInitialized)]
private void ServiceProviderBuilt(int serviceProviderHashCode, int singletonServices, int scopedServices, int transientServices)
{
WriteEvent(6, exceptionMessage);
WriteEvent(7, serviceProviderHashCode, singletonServices, scopedServices, transientServices);
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Parameters to this method are primitive and are trimmer safe.")]
[Event(8, Level = EventLevel.Informational, Keywords = Keywords.ServiceProviderInitialized)]
private void ServiceProviderDescriptors(int serviceProviderHashCode, string descriptors, int chunkIndex, int chunkCount)
{
WriteEvent(8, serviceProviderHashCode, descriptors, chunkIndex, chunkCount);
}

[NonEvent]
public void ServiceResolved(Type serviceType)
public void ServiceResolved(ServiceProvider provider, Type serviceType)
{
if (IsEnabled(EventLevel.Verbose, EventKeywords.All))
{
ServiceResolved(serviceType.ToString());
ServiceResolved(serviceType.ToString(), provider.GetHashCode());
}
}

[NonEvent]
public void CallSiteBuilt(Type serviceType, ServiceCallSite callSite)
public void CallSiteBuilt(ServiceProvider provider, Type serviceType, ServiceCallSite callSite)
{
if (IsEnabled(EventLevel.Verbose, EventKeywords.All))
{
string format = CallSiteJsonFormatter.Instance.Format(callSite);
int chunkCount = format.Length / MaxChunkSize + (format.Length % MaxChunkSize > 0 ? 1 : 0);

int providerHashCode = provider.GetHashCode();
for (int i = 0; i < chunkCount; i++)
{
CallSiteBuilt(
serviceType.ToString(),
format.Substring(i * MaxChunkSize, Math.Min(MaxChunkSize, format.Length - i * MaxChunkSize)), i, chunkCount);
format.Substring(i * MaxChunkSize, Math.Min(MaxChunkSize, format.Length - i * MaxChunkSize)), i, chunkCount,
providerHashCode);
}
}
}

[NonEvent]
public void DynamicMethodBuilt(Type serviceType, int methodSize)
public void DynamicMethodBuilt(ServiceProvider provider, Type serviceType, int methodSize)
{
if (IsEnabled(EventLevel.Verbose, EventKeywords.All))
{
DynamicMethodBuilt(serviceType.ToString(), methodSize);
DynamicMethodBuilt(serviceType.ToString(), methodSize, provider.GetHashCode());
}
}

[NonEvent]
public void ServiceRealizationFailed(Exception exception)
public void ServiceRealizationFailed(Exception exception, int serviceProviderHashCode)
{
if (IsEnabled(EventLevel.Error, EventKeywords.All))
{
ServiceRealizationFailed(exception.ToString());
ServiceRealizationFailed(exception.ToString(), serviceProviderHashCode);
}
}

[NonEvent]
public void ServiceProviderBuilt(ServiceProvider provider)
{
lock (_providers)
{
_providers.Add(new WeakReference<ServiceProvider>(provider));
}

WriteServiceProviderBuilt(provider);
}

[NonEvent]
public void ServiceProviderDisposed(ServiceProvider provider)
{
lock (_providers)
{
for (int i = _providers.Count - 1; i >= 0; i--)
{
// remove the provider, along with any stale references
WeakReference<ServiceProvider> reference = _providers[i];
if (!reference.TryGetTarget(out ServiceProvider target) || target == provider)
{
_providers.RemoveAt(i);
}
}
}
}

[NonEvent]
private void WriteServiceProviderBuilt(ServiceProvider provider)
{
if (IsEnabled(EventLevel.Informational, Keywords.ServiceProviderInitialized))
{
int singletonServices = 0;
int scopedServices = 0;
int transientServices = 0;

StringBuilder descriptorBuilder = new StringBuilder("{ \"descriptors\":[ ");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally speaking, the pattern is to do less string formatting and to do more writing of events with payloads that can then be processed out-of-proc. This looks like it might be a place where building up the state in-proc is preferred, or that you have to string-ify things anyway, but just wanted to call that out. Definitely good that this is under its own keyword.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, here we want to dump a bunch of data - the list of registered DI services. We are doing something similar above already:

public void CallSiteBuilt(Type serviceType, ServiceCallSite callSite)
{
if (IsEnabled(EventLevel.Verbose, EventKeywords.All))
{
string format = CallSiteJsonFormatter.Instance.Format(callSite);
int chunkCount = format.Length / MaxChunkSize + (format.Length % MaxChunkSize > 0 ? 1 : 0);
for (int i = 0; i < chunkCount; i++)
{
CallSiteBuilt(
serviceType.ToString(),
format.Substring(i * MaxChunkSize, Math.Min(MaxChunkSize, format.Length - i * MaxChunkSize)), i, chunkCount);
}
}
}

bool firstDescriptor = true;
foreach (ServiceDescriptor descriptor in provider.CallSiteFactory.Descriptors)
{
if (firstDescriptor)
{
firstDescriptor = false;
}
else
{
descriptorBuilder.Append(", ");
}

AppendServiceDescriptor(descriptorBuilder, descriptor);

switch (descriptor.Lifetime)
{
case ServiceLifetime.Singleton:
singletonServices++;
break;
case ServiceLifetime.Scoped:
scopedServices++;
break;
case ServiceLifetime.Transient:
transientServices++;
break;
}
}
descriptorBuilder.Append(" ] }");

int providerHashCode = provider.GetHashCode();
ServiceProviderBuilt(providerHashCode, singletonServices, scopedServices, transientServices);

string descriptorString = descriptorBuilder.ToString();
int chunkCount = descriptorString.Length / MaxChunkSize + (descriptorString.Length % MaxChunkSize > 0 ? 1 : 0);

for (int i = 0; i < chunkCount; i++)
{
ServiceProviderDescriptors(
providerHashCode,
descriptorString.Substring(i * MaxChunkSize, Math.Min(MaxChunkSize, descriptorString.Length - i * MaxChunkSize)), i, chunkCount);
}
}
}

[NonEvent]
private static void AppendServiceDescriptor(StringBuilder builder, ServiceDescriptor descriptor)
{
builder.Append("{ \"serviceType\": \"");
builder.Append(descriptor.ServiceType);
builder.Append("\", \"lifetime\": \"");
builder.Append(descriptor.Lifetime);
builder.Append("\", ");

if (descriptor.ImplementationType is not null)
{
builder.Append("\"implementationType\": \"");
builder.Append(descriptor.ImplementationType);
}
else if (descriptor.ImplementationFactory is not null)
{
builder.Append("\"implementationFactory\": \"");
builder.Append(descriptor.ImplementationFactory.Method);
}
else if (descriptor.ImplementationInstance is not null)
{
builder.Append("\"implementationInstance\": \"");
builder.Append(descriptor.ImplementationInstance.GetType());
builder.Append(" (instance)");
}
else
{
builder.Append("\"unknown\": \"");
}

builder.Append("\" }");
}

protected override void OnEventCommand(EventCommandEventArgs command)
{
if (command.Command == EventCommand.Enable)
{
// When this EventSource becomes enabled, write out the existing ServiceProvider information
// because building the ServiceProvider happens early in the process. This way a listener
// can get this information, even if they attach while the process is running.

lock (_providers)
{
foreach (WeakReference<ServiceProvider> reference in _providers)
{
if (reference.TryGetTarget(out ServiceProvider provider))
{
WriteServiceProviderBuilt(provider);
}
}
}
}
}
}
Expand All @@ -117,13 +280,13 @@ internal static class DependencyInjectionEventSourceExtensions
// This is an extension method because this assembly is trimmed at a "type granular" level in Blazor,
// and the whole DependencyInjectionEventSource type can't be trimmed. So extracting this to a separate
// type allows for the System.Linq.Expressions usage to be trimmed by the ILLinker.
public static void ExpressionTreeGenerated(this DependencyInjectionEventSource source, Type serviceType, Expression expression)
public static void ExpressionTreeGenerated(this DependencyInjectionEventSource source, ServiceProvider provider, Type serviceType, Expression expression)
{
if (source.IsEnabled(EventLevel.Verbose, EventKeywords.All))
{
var visitor = new NodeCountingVisitor();
visitor.Visit(expression);
source.ExpressionTreeGenerated(serviceType.ToString(), visitor.NodeCount);
source.ExpressionTreeGenerated(serviceType.ToString(), visitor.NodeCount, provider.GetHashCode());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public CallSiteFactory(IEnumerable<ServiceDescriptor> descriptors)
Populate();
}

internal ServiceDescriptor[] Descriptors => _descriptors;

private void Populate()
{
foreach (ServiceDescriptor descriptor in _descriptors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override Func<ServiceProviderEngineScope, object> RealizeService(ServiceC
}
catch (Exception ex)
{
DependencyInjectionEventSource.Log.ServiceRealizationFailed(ex);
DependencyInjectionEventSource.Log.ServiceRealizationFailed(ex, _serviceProvider.GetHashCode());

Debug.Fail($"We should never get exceptions from the background compilation.{Environment.NewLine}{ex}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public Func<ServiceProviderEngineScope, object> Build(ServiceCallSite callSite)
public Func<ServiceProviderEngineScope, object> BuildNoCache(ServiceCallSite callSite)
{
Expression<Func<ServiceProviderEngineScope, object>> expression = BuildExpression(callSite);
DependencyInjectionEventSource.Log.ExpressionTreeGenerated(callSite.ServiceType, expression);
DependencyInjectionEventSource.Log.ExpressionTreeGenerated(_rootScope.RootProvider, callSite.ServiceType, expression);
return expression.Compile();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private GeneratedMethod BuildTypeNoCache(ServiceCallSite callSite)
type.CreateTypeInfo();
assembly.Save(assemblyName + ".dll");
#endif
DependencyInjectionEventSource.Log.DynamicMethodBuilt(callSite.ServiceType, ilGenerator.ILOffset);
DependencyInjectionEventSource.Log.DynamicMethodBuilt(_rootScope.RootProvider, callSite.ServiceType, ilGenerator.ILOffset);

return new GeneratedMethod()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, Serv
}
}

DependencyInjectionEventSource.Log.ServiceProviderBuilt(this);
}

/// <summary>
Expand All @@ -87,17 +88,23 @@ internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, Serv
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
DisposeCore();
Root.Dispose();
}

/// <inheritdoc/>
public ValueTask DisposeAsync()
{
_disposed = true;
DisposeCore();
return Root.DisposeAsync();
}

private void DisposeCore()
{
_disposed = true;
DependencyInjectionEventSource.Log.ServiceProviderDisposed(this);
}

private void OnCreate(ServiceCallSite callSite)
{
_callSiteValidator?.ValidateCallSite(callSite);
Expand All @@ -117,7 +124,7 @@ internal object GetService(Type serviceType, ServiceProviderEngineScope serviceP

Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
OnResolve(serviceType, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(serviceType);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
var result = realizedService.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
return result;
Expand Down Expand Up @@ -149,7 +156,7 @@ private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serv
ServiceCallSite callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
if (callSite != null)
{
DependencyInjectionEventSource.Log.CallSiteBuilt(serviceType, callSite);
DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceType, callSite);
OnCreate(callSite);

// Optimize singleton case
Expand Down
Loading