diff --git a/docs/design/datacontracts/StressLog.md b/docs/design/datacontracts/StressLog.md index 3b90a216cfdf50..c761b8b06938be 100644 --- a/docs/design/datacontracts/StressLog.md +++ b/docs/design/datacontracts/StressLog.md @@ -13,9 +13,11 @@ internal record struct StressLogData( int TotalChunks, ulong TickFrequency, ulong StartTimestamp, + ulong StartTime, TargetPointer Logs); internal record struct ThreadStressLogData( + TargetPointer Address, TargetPointer NextPointer, ulong ThreadId, bool WriteHasWrapped, @@ -52,6 +54,7 @@ Data descriptors used: | StressLog | TotalChunks | Total number of chunks across all thread-specific logs | | StressLog | TickFrequency | Number of ticks per second for stresslog timestamps | | StressLog | StartTimestamp | Timestamp when the stress log was started | +| StressLog | StartTime | Wall-clock time when the stress log was started (FILETIME, 100ns units since Jan 1 1601) | | StressLog | ModuleOffset | Offset of the module in the stress log | | StressLog | Modules | Offset of the stress log's module table (if StressLogHasModuleTable is `1`) | | StressLog | Logs | Pointer to the thread-specific logs | @@ -104,6 +107,7 @@ StressLogData GetStressLogData() stressLog.TotalChunks, stressLog.TickFrequency, stressLog.StartTimestamp, + stressLog.StartTime, stressLog.Logs); } @@ -118,6 +122,7 @@ StressLogData GetStressLogData(TargetPointer stressLogPointer) stressLog.TotalChunks, stressLog.TickFrequency, stressLog.StartTimestamp, + stressLog.StartTime, stressLog.Logs); } @@ -151,6 +156,7 @@ IEnumerable GetThreadStressLogs(TargetPointer logs) } yield return new ThreadStressLogData( + currentPointer, threadStressLog.Next, threadStressLog.ThreadId, threadStressLog.WriteHasWrapped, diff --git a/src/coreclr/inc/sospriv.idl b/src/coreclr/inc/sospriv.idl index 0820d20386b9e0..94b43d5ad98a1e 100644 --- a/src/coreclr/inc/sospriv.idl +++ b/src/coreclr/inc/sospriv.idl @@ -572,3 +572,86 @@ interface ISOSDacInterface16 : IUnknown { HRESULT GetGCDynamicAdaptationMode(int* pDynamicAdaptationMode); } + +cpp_quote("#ifndef _SOS_StressLogData") +cpp_quote("#define _SOS_StressLogData") + +typedef struct _SOSStressLogData +{ + unsigned int LoggedFacilities; + unsigned int Level; + unsigned int MaxSizePerThread; + unsigned int MaxSizeTotal; + int TotalChunks; + UINT64 TickFrequency; + UINT64 StartTimestamp; + UINT64 StartTime; +} SOSStressLogData; + +cpp_quote("#endif //_SOS_StressLogData") + +cpp_quote("#ifndef _SOS_ThreadStressLogData") +cpp_quote("#define _SOS_ThreadStressLogData") + +typedef struct _SOSThreadStressLogData +{ + CLRDATA_ADDRESS ThreadLogAddress; + UINT64 ThreadId; +} SOSThreadStressLogData; + +cpp_quote("#endif //_SOS_ThreadStressLogData") + +cpp_quote("#ifndef _SOS_StressMsgData") +cpp_quote("#define _SOS_StressMsgData") + +typedef struct _SOSStressMsgData +{ + unsigned int Facility; + CLRDATA_ADDRESS FormatString; + UINT64 Timestamp; + unsigned int ArgumentCount; +} SOSStressMsgData; + +cpp_quote("#endif //_SOS_StressMsgData") + +[ + object, + local, + uuid(94a2bd3d-ab3d-43bf-81d8-3ae96b8e33cd) +] +interface ISOSStressLogThreadEnum : ISOSEnum +{ + HRESULT Next([in] unsigned int count, + [out, size_is(count), length_is(*pFetched)] SOSThreadStressLogData values[], + [out] unsigned int *pFetched); +} + +[ + object, + local, + uuid(437cb033-afe7-4c0f-a4a7-82c891bc049e) +] +interface ISOSStressLogMsgEnum : ISOSEnum +{ + HRESULT Next([in] unsigned int count, + [out, size_is(count), length_is(*pFetched)] SOSStressMsgData values[], + [out] unsigned int *pFetched); + + HRESULT GetArguments([in] unsigned int messageIndex, + [in] unsigned int argCount, + [out, size_is(argCount), length_is(*pFetched)] CLRDATA_ADDRESS args[], + [out] unsigned int *pFetched); +} + +[ + object, + local, + uuid(2f4bb585-ed50-479e-bbe0-10a95a5da3bb) +] +interface ISOSDacInterface17 : IUnknown +{ + HRESULT GetStressLogData([out] SOSStressLogData *data); + HRESULT GetStressLogThreadEnumerator([out] ISOSStressLogThreadEnum **ppEnum); + HRESULT GetStressLogMessageEnumerator([in] CLRDATA_ADDRESS threadStressLogAddress, + [out] ISOSStressLogMsgEnum **ppEnum); +} diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc index 365abce8061d29..702cbd20889392 100644 --- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc +++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc @@ -94,6 +94,7 @@ CDAC_TYPE_FIELD(StressLog, T_INT32, TotalChunks, offsetof(StressLog, totalChunk) CDAC_TYPE_FIELD(StressLog, T_POINTER, Logs, offsetof(StressLog, logs)) CDAC_TYPE_FIELD(StressLog, T_UINT64, TickFrequency, offsetof(StressLog, tickFrequency)) CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTimestamp, offsetof(StressLog, startTimeStamp)) +CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTime, offsetof(StressLog, startTime)) CDAC_TYPE_END(StressLog) CDAC_TYPE_BEGIN(ThreadStressLog) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0cc320903e7560..3ea511df0e1f0a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -564,10 +564,11 @@ CDAC_TYPE_FIELD(StressLog, T_UINT32, LoggedFacilities, cdac_offsets:: CDAC_TYPE_FIELD(StressLog, T_UINT32, Level, cdac_offsets::levelToLog) CDAC_TYPE_FIELD(StressLog, T_UINT32, MaxSizePerThread, cdac_offsets::MaxSizePerThread) CDAC_TYPE_FIELD(StressLog, T_UINT32, MaxSizeTotal, cdac_offsets::MaxSizeTotal) -CDAC_TYPE_FIELD(StressLog, T_UINT32, TotalChunks, cdac_offsets::totalChunk) +CDAC_TYPE_FIELD(StressLog, T_INT32, TotalChunks, cdac_offsets::totalChunk) CDAC_TYPE_FIELD(StressLog, T_POINTER, Logs, cdac_offsets::logs) CDAC_TYPE_FIELD(StressLog, T_UINT64, TickFrequency, cdac_offsets::tickFrequency) CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTimestamp, cdac_offsets::startTimeStamp) +CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTime, cdac_offsets::startTime) CDAC_TYPE_FIELD(StressLog, T_NUINT, ModuleOffset, cdac_offsets::moduleOffset) CDAC_TYPE_FIELD(StressLog, T_ARRAY(TYPE(StressLogModuleDesc)), Modules, cdac_offsets::modules) CDAC_TYPE_END(StressLog) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 5c7120fc517158..76b7ca71eb0537 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -128,6 +128,10 @@ public abstract class ContractRegistry /// Gets an instance of the Debugger contract for the target. /// public virtual IDebugger Debugger => GetContract(); + /// + /// Gets an instance of the StressLog contract for the target. + /// + public virtual IStressLog StressLog => GetContract(); /// /// Gets an instance of the RuntimeMutableTypeSystem contract for the target. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStressLog.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStressLog.cs index 40535e3d1c834d..4bd41735561628 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStressLog.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStressLog.cs @@ -14,9 +14,11 @@ public record struct StressLogData( int TotalChunks, ulong TickFrequency, ulong StartTimestamp, + ulong StartTime, TargetPointer Logs); public record struct ThreadStressLogData( + TargetPointer Address, TargetPointer NextPointer, ulong ThreadId, bool WriteHasWrapped, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StressLog.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StressLog.cs index b3eb52892a0e17..c5d0d7b82ce7cd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StressLog.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StressLog.cs @@ -46,6 +46,7 @@ public StressLogData GetStressLogData(TargetPointer stressLogPointer) stressLog.TotalChunks, stressLog.TickFrequency, stressLog.StartTimestamp, + stressLog.StartTime, stressLog.Logs); } @@ -79,6 +80,7 @@ public IEnumerable GetThreadStressLogs(TargetPointer Logs) } yield return new ThreadStressLogData( + currentPointer, threadStressLog.Next, threadStressLog.ThreadId, threadStressLog.WriteHasWrapped, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StressLog.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StressLog.cs index abc16be4369e45..330ce4caae2b41 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StressLog.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StressLog.cs @@ -13,7 +13,8 @@ internal sealed partial class StressLog : IData [Field] public int TotalChunks { get; } [Field] public ulong TickFrequency { get; } [Field] public ulong StartTimestamp { get; } + [Field] public ulong StartTime { get; } [Field] public TargetNUInt ModuleOffset { get; } - [Field] public TargetPointer? Modules { get; } + [FieldAddress] public TargetPointer? Modules { get; } [Field] public TargetPointer Logs { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index 06c887b7ffd89b..cce31d11ab2d80 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -1191,3 +1191,78 @@ public unsafe partial interface ISOSDacInterface16 [PreserveSig] int GetGCDynamicAdaptationMode(int* pDynamicAdaptationMode); } + +// StressLog data structures for ISOSDacInterface17 + +public struct SOSStressLogData +{ + public uint LoggedFacilities; + public uint Level; + public uint MaxSizePerThread; + public uint MaxSizeTotal; + public int TotalChunks; + public ulong TickFrequency; + public ulong StartTimestamp; + public ulong StartTime; +} + +public struct SOSThreadStressLogData +{ + public ClrDataAddress ThreadLogAddress; + public ulong ThreadId; +} + +public struct SOSStressMsgData +{ + public uint Facility; + public ClrDataAddress FormatString; + public ulong Timestamp; + public uint ArgumentCount; +} + +[GeneratedComInterface] +[Guid("94a2bd3d-ab3d-43bf-81d8-3ae96b8e33cd")] +public unsafe partial interface ISOSStressLogThreadEnum : ISOSEnum +{ + [PreserveSig] + int Next(uint count, + [In, Out, MarshalUsing(CountElementName = nameof(count))] + SOSThreadStressLogData[] values, + uint* pFetched); +} + +[GeneratedComInterface] +[Guid("437cb033-afe7-4c0f-a4a7-82c891bc049e")] +public unsafe partial interface ISOSStressLogMsgEnum : ISOSEnum +{ + [PreserveSig] + int Next(uint count, + [In, Out, MarshalUsing(CountElementName = nameof(count))] + SOSStressMsgData[] values, + uint* pFetched); + + [PreserveSig] + int GetArguments(uint messageIndex, + uint argCount, + [In, Out, MarshalUsing(CountElementName = nameof(argCount))] + ClrDataAddress[] args, + uint* pFetched); +} + +[GeneratedComInterface] +[Guid("2f4bb585-ed50-479e-bbe0-10a95a5da3bb")] +public unsafe partial interface ISOSDacInterface17 +{ + [PreserveSig] + int GetStressLogData(SOSStressLogData* data); + + [PreserveSig] + int GetStressLogThreadEnumerator( + DacComNullableByRef ppEnum); + + [PreserveSig] + int GetStressLogMessageEnumerator( + ClrDataAddress threadStressLogAddress, + DacComNullableByRef ppEnum); + +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 2007bb81728a1e..168ea57a6e5e14 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -33,7 +33,7 @@ public sealed unsafe partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface2, ISOSDacInterface3, ISOSDacInterface4, ISOSDacInterface5, ISOSDacInterface6, ISOSDacInterface7, ISOSDacInterface8, ISOSDacInterface9, ISOSDacInterface10, ISOSDacInterface11, ISOSDacInterface12, ISOSDacInterface13, ISOSDacInterface14, ISOSDacInterface15, - ISOSDacInterface16 + ISOSDacInterface16, ISOSDacInterface17 { private readonly Target _target; @@ -7113,4 +7113,258 @@ int ISOSDacInterface16.GetGCDynamicAdaptationMode(int* pDynamicAdaptationMode) return hr; } #endregion ISOSDacInterface16 + + #region ISOSDacInterface17 + + [GeneratedComClass] + internal sealed unsafe partial class SOSStressLogThreadEnum : ISOSStressLogThreadEnum + { + private readonly SOSThreadStressLogData[] _threads; + private uint _index; + + public SOSStressLogThreadEnum(SOSThreadStressLogData[] threads) + { + _threads = threads; + } + + int ISOSStressLogThreadEnum.Next(uint count, SOSThreadStressLogData[] values, uint* pFetched) + { + int hr = HResults.S_OK; + try + { + if (pFetched is null || values is null) + throw new NullReferenceException(); + + *pFetched = 0; + count = Math.Min(count, (uint)values.Length); + uint written = 0; + while (written < count && _index < _threads.Length) + values[written++] = _threads[(int)_index++]; + + *pFetched = written; + hr = written < count ? HResults.S_FALSE : HResults.S_OK; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + int ISOSEnum.Skip(uint count) + { + _index = Math.Min(_index + count, (uint)_threads.Length); + return HResults.S_OK; + } + + int ISOSEnum.Reset() + { + _index = 0; + return HResults.S_OK; + } + + int ISOSEnum.GetCount(uint* pCount) + { + if (pCount is null) return HResults.E_POINTER; + *pCount = (uint)_threads.Length; + return HResults.S_OK; + } + } + + [GeneratedComClass] + internal sealed unsafe partial class SOSStressLogMsgEnum : ISOSStressLogMsgEnum + { + private readonly Target _target; + private readonly Contracts.StressMsgData[] _messages; + private uint _index; + private uint _lastBatchStart; + private uint _lastBatchCount; + + public SOSStressLogMsgEnum(Target target, IEnumerable messages) + { + _target = target; + _messages = messages.ToArray(); + } + + int ISOSStressLogMsgEnum.Next(uint count, SOSStressMsgData[] values, uint* pFetched) + { + int hr = HResults.S_OK; + try + { + if (pFetched is null || values is null) + throw new NullReferenceException(); + + *pFetched = 0; + count = Math.Min(count, (uint)values.Length); + _lastBatchStart = _index; + uint written = 0; + + while (written < count && _index < _messages.Length) + { + Contracts.StressMsgData msg = _messages[(int)_index++]; + values[written] = new SOSStressMsgData + { + Facility = msg.Facility, + FormatString = msg.FormatString.ToClrDataAddress(_target), + Timestamp = msg.Timestamp, + ArgumentCount = (uint)msg.Args.Count, + }; + written++; + } + + _lastBatchCount = written; + *pFetched = written; + hr = written < count ? HResults.S_FALSE : HResults.S_OK; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + int ISOSStressLogMsgEnum.GetArguments(uint messageIndex, uint argCount, ClrDataAddress[] args, uint* pFetched) + { + if (pFetched is null || args is null) + return HResults.E_POINTER; + + *pFetched = 0; + + if (messageIndex >= _lastBatchCount) + return HResults.E_INVALIDARG; + + Contracts.StressMsgData msg = _messages[(int)(_lastBatchStart + messageIndex)]; + uint toFetch = Math.Min(argCount, (uint)msg.Args.Count); + toFetch = Math.Min(toFetch, (uint)args.Length); + + for (uint i = 0; i < toFetch; i++) + args[i] = msg.Args[(int)i].ToClrDataAddress(_target); + + *pFetched = toFetch; + return toFetch < argCount ? HResults.S_FALSE : HResults.S_OK; + } + + int ISOSEnum.Skip(uint count) + { + _index = Math.Min(_index + count, (uint)_messages.Length); + return HResults.S_OK; + } + + int ISOSEnum.Reset() + { + _index = 0; + _lastBatchStart = 0; + _lastBatchCount = 0; + return HResults.S_OK; + } + + int ISOSEnum.GetCount(uint* pCount) + { + if (pCount is null) return HResults.E_POINTER; + *pCount = (uint)_messages.Length; + return HResults.S_OK; + } + } + + int ISOSDacInterface17.GetStressLogData(SOSStressLogData* data) + { + int hr = HResults.S_OK; + try + { + if (data is null) + return HResults.E_POINTER; + + *data = default; + + Contracts.IStressLog stressLogContract = _target.Contracts.StressLog; + if (!stressLogContract.HasStressLog()) + return HResults.S_FALSE; + + Contracts.StressLogData logData = stressLogContract.GetStressLogData(); + data->LoggedFacilities = logData.LoggedFacilities; + data->Level = logData.Level; + data->MaxSizePerThread = logData.MaxSizePerThread; + data->MaxSizeTotal = logData.MaxSizeTotal; + data->TotalChunks = logData.TotalChunks; + data->TickFrequency = logData.TickFrequency; + data->StartTimestamp = logData.StartTimestamp; + data->StartTime = logData.StartTime; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + int ISOSDacInterface17.GetStressLogThreadEnumerator(DacComNullableByRef ppEnum) + { + int hr = HResults.S_OK; + try + { + Contracts.IStressLog stressLogContract = _target.Contracts.StressLog; + if (!stressLogContract.HasStressLog()) + return HResults.S_FALSE; + + Contracts.StressLogData logData = stressLogContract.GetStressLogData(); + + var threads = stressLogContract.GetThreadStressLogs(logData.Logs) + .Select(t => new SOSThreadStressLogData + { + ThreadLogAddress = t.Address.ToClrDataAddress(_target), + ThreadId = t.ThreadId, + }) + .ToArray(); + ppEnum.Interface = new SOSStressLogThreadEnum(threads); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + int ISOSDacInterface17.GetStressLogMessageEnumerator( + ClrDataAddress threadStressLogAddress, + DacComNullableByRef ppEnum) + { + int hr = HResults.S_OK; + try + { + Contracts.IStressLog stressLogContract = _target.Contracts.StressLog; + if (!stressLogContract.HasStressLog()) + return HResults.S_FALSE; + + Contracts.StressLogData logData = stressLogContract.GetStressLogData(); + + // Find the matching thread + Contracts.ThreadStressLogData? matchedThread = null; + foreach (var thread in stressLogContract.GetThreadStressLogs(logData.Logs)) + { + if (thread.Address == threadStressLogAddress.ToTargetPointer(_target)) + { + matchedThread = thread; + break; + } + } + + if (matchedThread is null) + return HResults.E_INVALIDARG; + + IEnumerable messages = stressLogContract.GetStressMessages(matchedThread.Value); + ppEnum.Interface = new SOSStressLogMsgEnum(_target, messages); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + + #endregion ISOSDacInterface17 } diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/Program.cs new file mode 100644 index 00000000000000..ba1fef6add0858 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/Program.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +/// +/// Debuggee app for cDAC stress log dump tests. +/// Performs allocations and GC to generate stress log entries, then crashes +/// so a dump is produced for analysis. +/// +internal static class Program +{ + private static void Main() + { + // Perform some work to generate stress log entries. + // GC operations produce many log messages across facilities. + var list = new List(); + for (int i = 0; i < 1000; i++) + list.Add(new byte[1024]); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Environment.FailFast("cDAC dump test: StressLog debuggee intentional crash"); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/StressLog.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/StressLog.csproj new file mode 100644 index 00000000000000..e1cc07a08d570a --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/StressLog/StressLog.csproj @@ -0,0 +1,5 @@ + + + DOTNET_StressLog=1;DOTNET_LogFacility=0xffffffbf;DOTNET_LogLevel=6;DOTNET_StressLogSize=65536 + + diff --git a/src/native/managed/cdac/tests/DumpTests/StressLogDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StressLogDumpTests.cs new file mode 100644 index 00000000000000..afb3b3f3ec7bd6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/StressLogDumpTests.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Microsoft.Diagnostics.DataContractReader.TestInfrastructure; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for the StressLog contract. +/// Uses the StressLog debuggee dump, which enables stress logging via environment +/// variables, performs allocations and GC, then crashes. +/// +public class StressLogDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "StressLog"; + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void StressLogIsAvailable(TestConfiguration config) + { + InitializeDumpTest(config); + IStressLog stressLog = Target.Contracts.StressLog; + Assert.True(stressLog.HasStressLog()); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void StressLogDataIsValid(TestConfiguration config) + { + InitializeDumpTest(config); + IStressLog stressLog = Target.Contracts.StressLog; + StressLogData data = stressLog.GetStressLogData(); + + Assert.NotEqual(0UL, data.TickFrequency); + Assert.NotEqual(0UL, data.StartTimestamp); + Assert.NotEqual(0UL, data.StartTime); + Assert.NotEqual(TargetPointer.Null, data.Logs); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void CanEnumerateThreadsAndMessages(TestConfiguration config) + { + InitializeDumpTest(config); + IStressLog stressLog = Target.Contracts.StressLog; + StressLogData data = stressLog.GetStressLogData(); + + var threads = stressLog.GetThreadStressLogs(data.Logs).ToList(); + Assert.NotEmpty(threads); + + bool foundMessages = false; + foreach (ThreadStressLogData thread in threads) + { + var messages = stressLog.GetStressMessages(thread).Take(10).ToList(); + if (messages.Count > 0) + { + foundMessages = true; + Assert.NotEqual(0UL, messages[0].Timestamp); + Assert.NotEqual(TargetPointer.Null, messages[0].FormatString); + break; + } + } + Assert.True(foundMessages, "Expected at least one thread with stress log messages"); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void ISOSDacInterface17_GetStressLogData(TestConfiguration config) + { + InitializeDumpTest(config); + ISOSDacInterface17 sosDac = (ISOSDacInterface17)new SOSDacImpl(Target, legacyObj: null); + + SOSStressLogData data; + int hr = sosDac.GetStressLogData(&data); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(0UL, data.TickFrequency); + Assert.NotEqual(0UL, data.StartTimestamp); + Assert.NotEqual(0UL, data.StartTime); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void ISOSDacInterface17_GetStressLogThreadEnumerator(TestConfiguration config) + { + InitializeDumpTest(config); + ISOSDacInterface17 sosDac = (ISOSDacInterface17)new SOSDacImpl(Target, legacyObj: null); + + DacComNullableByRef ppEnum = new(isNullRef: false); + int hr = sosDac.GetStressLogThreadEnumerator(ppEnum); + Assert.Equal(System.HResults.S_OK, hr); + + ISOSStressLogThreadEnum? threadEnum = ppEnum.Interface; + Assert.NotNull(threadEnum); + + uint count; + hr = threadEnum.GetCount(&count); + Assert.Equal(System.HResults.S_OK, hr); + Assert.True(count > 0, "Expected at least one thread with stress log"); + + SOSThreadStressLogData[] threads = new SOSThreadStressLogData[count]; + uint fetched; + hr = threadEnum.Next(count, threads, &fetched); + Assert.True(hr == System.HResults.S_OK || hr == System.HResults.S_FALSE); + Assert.True(fetched > 0); + Assert.NotEqual((ClrDataAddress)0, threads[0].ThreadLogAddress); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void ISOSDacInterface17_GetStressLogMessageEnumerator(TestConfiguration config) + { + InitializeDumpTest(config); + ISOSDacInterface17 sosDac = (ISOSDacInterface17)new SOSDacImpl(Target, legacyObj: null); + + DacComNullableByRef ppThreadEnum = new(isNullRef: false); + int hr = sosDac.GetStressLogThreadEnumerator(ppThreadEnum); + Assert.Equal(System.HResults.S_OK, hr); + + ISOSStressLogThreadEnum? threadEnum = ppThreadEnum.Interface; + Assert.NotNull(threadEnum); + + uint threadCount; + hr = threadEnum.GetCount(&threadCount); + Assert.Equal(System.HResults.S_OK, hr); + + SOSThreadStressLogData[] threads = new SOSThreadStressLogData[threadCount]; + uint fetched; + hr = threadEnum.Next(threadCount, threads, &fetched); + Assert.True(hr == System.HResults.S_OK || hr == System.HResults.S_FALSE); + + bool foundMessages = false; + for (uint i = 0; i < fetched; i++) + { + DacComNullableByRef ppMsgEnum = new(isNullRef: false); + hr = sosDac.GetStressLogMessageEnumerator(threads[i].ThreadLogAddress, ppMsgEnum); + Assert.Equal(System.HResults.S_OK, hr); + + ISOSStressLogMsgEnum? msgEnum = ppMsgEnum.Interface; + Assert.NotNull(msgEnum); + + SOSStressMsgData[] messages = new SOSStressMsgData[10]; + uint msgFetched; + hr = msgEnum.Next(10, messages, &msgFetched); + Assert.True(hr == System.HResults.S_OK || hr == System.HResults.S_FALSE); + + if (msgFetched > 0) + { + foundMessages = true; + Assert.NotEqual(0UL, messages[0].Timestamp); + Assert.NotEqual((ClrDataAddress)0, messages[0].FormatString); + + if (messages[0].ArgumentCount > 0) + { + ClrDataAddress[] args = new ClrDataAddress[messages[0].ArgumentCount]; + uint argFetched; + hr = msgEnum.GetArguments(0, messages[0].ArgumentCount, args, &argFetched); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(messages[0].ArgumentCount, argFetched); + } + break; + } + } + Assert.True(foundMessages, "Expected at least one thread with stress log messages via ISOSDacInterface17"); + } +}