Skip to content

Add managed entry points for raising Contention events #87087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 15, 2023
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 @@ -138,7 +138,7 @@
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Debugger.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\EditAndContinueHelper.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\NativeRuntimeEventSource.PortableThreadPool.NativeSinks.CoreCLR.cs" Condition="'$(FeaturePortableThreadPool)' == 'true'" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\NativeRuntimeEventSource.Threading.NativeSinks.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\ICustomDebuggerNotification.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\StackFrame.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\StackFrameHelper.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

Expand All @@ -14,31 +12,51 @@ internal sealed partial class NativeRuntimeEventSource : EventSource
{
[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadStart(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);
private static partial void LogContentionLockCreated(nint LockID, nint AssociatedObjectID, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadStop(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);
private static partial void LogContentionStart(
ContentionFlagsMap ContentionFlags,
ushort ClrInstanceID,
nint LockID,
nint AssociatedObjectID,
ulong LockOwnerThreadID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadWait(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);
private static partial void LogContentionStop(
ContentionFlagsMap ContentionFlags,
ushort ClrInstanceID,
double DurationNs);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolMinMaxThreads(ushort MinWorkerThreads, ushort MaxWorkerThreads, ushort MinIOCompletionThreads, ushort MaxIOCompletionThreads, ushort ClrInstanceID);
private static partial void LogThreadPoolWorkerThreadStart(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadAdjustmentSample(double Throughput, ushort ClrInstanceID);
private static partial void LogThreadPoolWorkerThreadStop(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadAdjustmentAdjustment(double AverageThroughput, uint NewWorkerThreadCount, ThreadAdjustmentReasonMap Reason, ushort ClrInstanceID);
private static partial void LogThreadPoolWorkerThreadWait(uint ActiveWorkerThreadCount, uint RetiredWorkerThreadCount, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkerThreadAdjustmentStats(
private static partial void LogThreadPoolMinMaxThreads(ushort MinWorkerThreads, ushort MaxWorkerThreads, ushort MinIOCompletionThreads, ushort MaxIOCompletionThreads, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
private static partial void LogThreadPoolWorkerThreadAdjustmentSample(double Throughput, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
private static partial void LogThreadPoolWorkerThreadAdjustmentAdjustment(double AverageThroughput, uint NewWorkerThreadCount, ThreadAdjustmentReasonMap Reason, ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
private static partial void LogThreadPoolWorkerThreadAdjustmentStats(
double Duration,
double Throughput,
double ThreadPoolWorkerThreadWait,
Expand All @@ -53,29 +71,29 @@ internal static partial void LogThreadPoolWorkerThreadAdjustmentStats(

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolIOEnqueue(
private static partial void LogThreadPoolIOEnqueue(
IntPtr NativeOverlapped,
IntPtr Overlapped,
[MarshalAs(UnmanagedType.Bool)] bool MultiDequeues,
ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolIODequeue(
private static partial void LogThreadPoolIODequeue(
IntPtr NativeOverlapped,
IntPtr Overlapped,
ushort ClrInstanceID);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolWorkingThreadCount(
private static partial void LogThreadPoolWorkingThreadCount(
uint Count,
ushort ClrInstanceID
);

[NonEvent]
[LibraryImport(RuntimeHelpers.QCall)]
internal static partial void LogThreadPoolIOPack(
private static partial void LogThreadPoolIOPack(
IntPtr NativeOverlapped,
IntPtr Overlapped,
ushort ClrInstanceID);
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/scripts/genRuntimeEventSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@ def getManifestsToGenerate(runtimeFlavor):

def generateEvent(eventNode, providerNode, outputFile, stringTable):

# ThreadPool events are defined manually in NativeRuntimeEventSource.PortableThreadPool.cs
# ThreadPool and Contention events are defined manually in NativeRuntimeEventSource.Threading.cs
symbol = eventNode.getAttribute("symbol")
if "ThreadPool" in symbol:
return
if "Contention" in symbol:
return

evtLevel = eventNode.getAttribute("level")[4:]
evtKeywords = ""
Expand Down
36 changes: 36 additions & 0 deletions src/coreclr/vm/nativeeventsource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,40 @@ extern "C" void QCALLTYPE LogThreadPoolIOPack(_In_z_ void* nativeOverlapped, _In

END_QCALL;
}

extern "C" void QCALLTYPE LogContentionLockCreated(void* LockID, void* AssociatedObjectID, uint16_t ClrInstanceID)
{
QCALL_CONTRACT;
BEGIN_QCALL;

FireEtwContentionLockCreated(LockID, AssociatedObjectID, ClrInstanceID);

END_QCALL;
}

extern "C" void QCALLTYPE LogContentionStart(
uint8_t ContentionFlags,
uint16_t ClrInstanceID,
void* LockID,
void* AssociatedObjectID,
uint64_t LockOwnerThreadID)
{
QCALL_CONTRACT;
BEGIN_QCALL;

FireEtwContentionStart_V2(ContentionFlags, ClrInstanceID, LockID, AssociatedObjectID, LockOwnerThreadID);

END_QCALL;
}

extern "C" void QCALLTYPE LogContentionStop(uint8_t ContentionFlags, uint16_t ClrInstanceID, double DurationNs)
{
QCALL_CONTRACT;
BEGIN_QCALL;

FireEtwContentionStop_V1(ContentionFlags, ClrInstanceID, DurationNs);

END_QCALL;
}

#endif // FEATURE_PERFTRACING
3 changes: 3 additions & 0 deletions src/coreclr/vm/nativeeventsource.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ extern "C" void QCALLTYPE LogThreadPoolIOEnqueue(_In_z_ void* nativeOverlapped,
extern "C" void QCALLTYPE LogThreadPoolIODequeue(_In_z_ void* nativeOverlapped, _In_z_ void* overlapped, _In_z_ short ClrInstanceID);
extern "C" void QCALLTYPE LogThreadPoolWorkingThreadCount(_In_z_ uint count, _In_z_ short ClrInstanceID);
extern "C" void QCALLTYPE LogThreadPoolIOPack(_In_z_ void* nativeOverlapped, _In_z_ void* overlapped, _In_z_ short ClrInstanceID);
extern "C" void QCALLTYPE LogContentionLockCreated(void* LockID, void* AssociatedObjectID, uint16_t ClrInstanceID);
extern "C" void QCALLTYPE LogContentionStart(uint8_t ContentionFlags, uint16_t ClrInstanceID, void* LockID, void* AssociatedObjectID, uint64_t LockOwnerThreadID);
extern "C" void QCALLTYPE LogContentionStop(uint8_t ContentionFlags, uint16_t ClrInstanceID, double DurationNs);
#endif // defined(FEATURE_PERFTRACING)

#endif //_NATIVEEVENTSOURCE_H_
3 changes: 3 additions & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ static const Entry s_QCall[] =
DllImportEntry(LogThreadPoolIODequeue)
DllImportEntry(LogThreadPoolIOPack)
DllImportEntry(LogThreadPoolWorkingThreadCount)
DllImportEntry(LogContentionLockCreated)
DllImportEntry(LogContentionStart)
DllImportEntry(LogContentionStop)
DllImportEntry(EventPipeInternal_Enable)
DllImportEntry(EventPipeInternal_Disable)
DllImportEntry(EventPipeInternal_GetSessionInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IncrementingEventCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IncrementingPollingCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\NativeRuntimeEventSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\NativeRuntimeEventSource.Threading.cs" Condition="'$(FeaturePerfTracing)' != 'true' or '$(FeatureNativeAot)' == 'true'" />
Copy link
Member

Choose a reason for hiding this comment

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

This file was not included previously if FeaturePortableThreadPool wasn't true and never on Mono/CoreCLR, now it looks like this will always be included if FeaturePerfTracing != 'true' and that is at least the case for TargetsBrowser (WASM).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously the file contained events that would only be raised by the portable thread pool. I reused the same file for contention events and they could be raised from anywhere. Some thread pool events too may be raised from different implementations in the future.

The condition that enables FeaturePortableThreadPool in Mono is currently similar to the one that enables FeaturePerfTracing. I believe the correct condition would be just '$(FeaturePerfTracing)' != 'true', as FeaturePerfTracing seems to be the one used to set up eventing (and using genRuntimeEventSources.py). In NativeAOT, currently FeaturePerfTracing is true but the eventing stuff appears to not be set up yet, so I modified the conditions so that the same file as before would for now be included in NativeAOT.

<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\NativeRuntimeEventSource.Threading.NativeSinks.cs" Condition="'$(FeaturePerfTracing)' == 'true' and '$(FeatureNativeAot)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\PollingCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSource.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\Winmeta.cs" />
Expand Down Expand Up @@ -2549,8 +2551,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\CompleteWaitThreadPoolWorkItem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.cs" Condition="'$(FeatureCoreCLR)' != 'true' and '$(FeatureMono)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs" Condition="'$(FeatureCoreCLR)' == 'true' or '$(FeatureMono)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Blocking.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.GateThread.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;

namespace System.Diagnostics.Tracing
{
// This is part of the NativeRuntimeEventsource, which is the managed version of the Microsoft-Windows-DotNETRuntime provider.
// It contains the handwritten implementation of the ThreadPool events.
// It contains the handwritten implementation of threading events.
// The events here do not call into the typical WriteEvent* APIs unlike most EventSources because that results in the
// events to be forwarded to EventListeners twice, once directly from the managed WriteEvent API, and another time
// from the mechanism in NativeRuntimeEventSource.ProcessEvents that forwards native runtime events to EventListeners.
// To prevent this, these events call directly into QCalls provided by the runtime (refer to NativeRuntimeEventSource.cs) which call
// FireEtw* methods auto-generated from ClrEtwAll.man. This ensures that corresponding event sinks are being used
// for the native platform.
// For implementation of these events not supporting native sinks, refer to NativeRuntimeEventSource.PortableThreadPool.cs.
// For implementation of these events not supporting native sinks, refer to NativeRuntimeEventSource.Threading.cs.
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "NativeRuntimeEventSource is a special case where event methods don't use WriteEvent/WriteEventCore but still need to be instance methods.")]
internal sealed partial class NativeRuntimeEventSource : EventSource
{
// This value does not seem to be used, leaving it as zero for now. It may be useful for a scenario that may involve
// multiple instances of the runtime within the same process, but then it seems unlikely that both instances' thread
// pools would be in moderate use.
private const ushort DefaultClrInstanceId = 0;

private static class Messages
private static partial class Messages
{
public const string ContentionLockCreated = "LockID={0};\nAssociatedObjectID={1};\nClrInstanceID={2}";
public const string ContentionStart = "ContentionFlags={0};\nClrInstanceID={1};\nLockID={2};\nAssociatedObjectID={3}\nLockOwnerThreadID={4}";
public const string ContentionStop = "ContentionFlags={0};\nClrInstanceID={1};\nDurationNs={2}";
public const string WorkerThread = "ActiveWorkerThreadCount={0};\nRetiredWorkerThreadCount={1};\nClrInstanceID={2}";
public const string MinMaxThreads = "MinWorkerThreads={0};\nMaxWorkerThreads={1};\nMinIOCompletionThreads={2};\nMaxIOCompletionThreads={3};\nClrInstanceID={4}";
public const string WorkerThreadAdjustmentSample = "Throughput={0};\nClrInstanceID={1}";
Expand All @@ -37,17 +35,19 @@ private static class Messages
}

// The task definitions for the ETW manifest
public static class Tasks // this name and visibility is important for EventSource
public static partial class Tasks // this name and visibility is important for EventSource
{
public const EventTask Contention = (EventTask)8;
public const EventTask ThreadPoolWorkerThread = (EventTask)16;
public const EventTask ThreadPoolWorkerThreadAdjustment = (EventTask)18;
public const EventTask ThreadPool = (EventTask)23;
public const EventTask ThreadPoolWorkingThreadCount = (EventTask)22;
public const EventTask ThreadPoolMinMaxThreads = (EventTask)38;
}

public static class Opcodes // this name and visibility is important for EventSource
public static partial class Opcodes // this name and visibility is important for EventSource
{
public const EventOpcode LockCreated = (EventOpcode)11;
public const EventOpcode IOEnqueue = (EventOpcode)13;
public const EventOpcode IODequeue = (EventOpcode)14;
public const EventOpcode IOPack = (EventOpcode)15;
Expand All @@ -57,6 +57,12 @@ private static class Messages
public const EventOpcode Stats = (EventOpcode)102;
}

public enum ContentionFlagsMap : byte
{
Managed,
Native,
}

public enum ThreadAdjustmentReasonMap : uint
{
Warmup,
Expand All @@ -66,9 +72,41 @@ public enum ThreadAdjustmentReasonMap : uint
ChangePoint,
Stabilizing,
Starvation,
ThreadTimedOut
ThreadTimedOut,
CooperativeBlocking,
}

[Event(90, Level = EventLevel.Informational, Message = Messages.ContentionLockCreated, Task = Tasks.Contention, Opcode = EventOpcode.Info, Version = 0, Keywords = Keywords.ContentionKeyword)]
private void ContentionLockCreated(nint LockID, nint AssociatedObjectID, ushort ClrInstanceID = DefaultClrInstanceId)
{
Debug.Assert(IsEnabled(EventLevel.Informational, Keywords.ContentionKeyword));
LogContentionLockCreated(LockID, AssociatedObjectID, ClrInstanceID);
}

[Event(81, Level = EventLevel.Informational, Message = Messages.ContentionStart, Task = Tasks.Contention, Opcode = EventOpcode.Start, Version = 2, Keywords = Keywords.ContentionKeyword)]
private void ContentionStart(
ContentionFlagsMap ContentionFlags,
ushort ClrInstanceID,
nint LockID,
nint AssociatedObjectID,
ulong LockOwnerThreadID)
{
Debug.Assert(IsEnabled(EventLevel.Informational, Keywords.ContentionKeyword));
LogContentionStart(ContentionFlags, ClrInstanceID, LockID, AssociatedObjectID, LockOwnerThreadID);
}

[Event(91, Level = EventLevel.Informational, Message = Messages.ContentionStop, Task = Tasks.Contention, Opcode = EventOpcode.Stop, Version = 1, Keywords = Keywords.ContentionKeyword)]
private void ContentionStop(ContentionFlagsMap ContentionFlags, ushort ClrInstanceID, double DurationNs)
{
Debug.Assert(IsEnabled(EventLevel.Informational, Keywords.ContentionKeyword));
LogContentionStop(ContentionFlags, ClrInstanceID, DurationNs);
}

[NonEvent]
[MethodImpl(MethodImplOptions.NoInlining)]
public void ContentionStop(double durationNs) =>
ContentionStop(ContentionFlagsMap.Managed, DefaultClrInstanceId, durationNs);

[Event(50, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)]
public unsafe void ThreadPoolWorkerThreadStart(
uint ActiveWorkerThreadCount,
Expand Down Expand Up @@ -184,7 +222,9 @@ public void ThreadPoolIOEnqueue(RegisteredWaitHandle registeredWaitHandle)
{
if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword))
{
#pragma warning disable CA1416 // 'RegisteredWaitHandle.Repeating' is unsupported on: 'browser'
ThreadPoolIOEnqueue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero, registeredWaitHandle.Repeating);
#pragma warning restore CA1416
}
}

Expand Down
Loading