Skip to content
Open
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
269 changes: 269 additions & 0 deletions src/SOS/Strike/stressLogDump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "util.h"
#include <stdio.h>
#include <ctype.h>
#include <vector>

#ifndef STRESS_LOG
#define STRESS_LOG
Expand Down Expand Up @@ -361,6 +362,274 @@ StressMsg* GetStressMsgInLatestVersion(StressMsg* rawMsg, int version)
return rawMsg;
}

/*********************************************************************************/
// Try to dump the stress log using ISOSDacInterface17 (cDAC contract-based).
// Returns S_OK on success, S_FALSE if no messages, or E_NOINTERFACE if the
// interface is not available (caller should fall back to legacy path).
HRESULT DumpStressLogViaInterface17(const char* fileName, struct IDebugDataSpaces* memCallBack)
{
// Allow forcing the legacy path for A/B testing
if (GetEnvironmentVariableA("DOTNET_SOS_FORCE_LEGACY_STRESSLOG", NULL, 0) > 0)
{
return E_NOINTERFACE;
}

// Try to get ISOSDacInterface17
ISOSDacInterface17* pSos17 = NULL;
HRESULT hr = g_clrData->QueryInterface(__uuidof(ISOSDacInterface17), (void**)&pSos17);
if (FAILED(hr) || pSos17 == NULL)
{
return E_NOINTERFACE;
}

// Check if stress log is available
hr = pSos17->IsStressLogAvailable();
if (hr != S_OK)
{
pSos17->Release();
return FAILED(hr) ? hr : S_FALSE;
}

// Get stress log header data
DacpStressLogData logData;
hr = pSos17->GetStressLogData(&logData);
if (FAILED(hr))
{
pSos17->Release();
return hr;
}

BOOL bDoGcHist = (fileName == NULL);
FILE* file = NULL;

if (bDoGcHist)
{
GcHistClear();
}

// Get thread enumerator
ISOSStressLogThreadEnum* pThreadEnum = NULL;
hr = pSos17->GetStressLogThreadEnumerator(&pThreadEnum);
if (FAILED(hr) || pThreadEnum == NULL)
{
pSos17->Release();
return FAILED(hr) ? hr : E_FAIL;
}

// Collect all threads
struct ThreadInfo
{
DacpThreadStressLogData data;
uint64_t latestTimestamp;
};

std::vector<ThreadInfo> threads;
DacpThreadStressLogData threadData;
unsigned int fetched = 0;
for (;;)
{
fetched = 0;
hr = pThreadEnum->Next(1, &threadData, &fetched);
if (fetched == 0)
break;
threads.push_back({ threadData, 0 });
if (hr == S_OK) // S_OK means enumeration complete (no more items)
break;
}
pThreadEnum->Release();

if (threads.empty())
{
pSos17->Release();
ExtOut("----- No thread logs in the image -----\n");
return S_FALSE;
}

// Create message enumerators for each thread and find the latest timestamp
struct ThreadMsgState
{
ISOSStressLogMsgEnum* pEnum;
DacpStressMsgData currentMsg;
bool hasMsg;
uint64_t threadId;
};

std::vector<ThreadMsgState> msgStates;
uint64_t lastTimeStamp = 0;

for (auto& t : threads)
{
ISOSStressLogMsgEnum* pMsgEnum = NULL;
hr = pSos17->GetStressLogMessageEnumerator(t.data.ThreadLogAddress, &pMsgEnum);
if (FAILED(hr) || pMsgEnum == NULL)
continue;

ThreadMsgState state;
state.pEnum = pMsgEnum;
state.threadId = t.data.ThreadId;
state.hasMsg = false;

// Prime the enumerator with the first message
fetched = 0;
hr = pMsgEnum->Next(1, &state.currentMsg, &fetched);
if (SUCCEEDED(hr) && fetched > 0)
{
state.hasMsg = true;
if (state.currentMsg.Timestamp > lastTimeStamp)
lastTimeStamp = state.currentMsg.Timestamp;
}

msgStates.push_back(state);
}

// Open the output file before writing header (so vDoOut can write to it)
if (!bDoGcHist)
{
if ((file = fopen(fileName, "w")) == NULL)
{
hr = GetLastError();
for (auto& s : msgStates) s.pEnum->Release();
pSos17->Release();
return hr;
}
}

// Print header -- match legacy format exactly
FILETIME startTime;
memcpy(&startTime, &logData.StartTime, sizeof(FILETIME));
double totalSecs = 0;
if (lastTimeStamp > logData.StartTimestamp && logData.TickFrequency > 0)
{
totalSecs = ((double)(lastTimeStamp - logData.StartTimestamp)) / logData.TickFrequency;
}
FILETIME endTime;
INT64 endTimeVal = *((INT64*)&startTime) + ((INT64)(totalSecs * 1.0E7));
memcpy(&endTime, &endTimeVal, sizeof(FILETIME));

WCHAR timeBuff[64];
vDoOut(bDoGcHist, file, "STRESS LOG:\n"
" facilitiesToLog = 0x%x\n"
" levelToLog = %d\n"
" MaxLogSizePerThread = 0x%x (%d)\n"
" MaxTotalLogSize = 0x%x (%d)\n"
" CurrentTotalLogChunk = %d\n"
" ThreadsWithLogs = %d\n",
logData.LoggedFacilities, logData.Level,
logData.MaxSizePerThread, logData.MaxSizePerThread,
logData.MaxSizeTotal, logData.MaxSizeTotal,
logData.TotalChunks, (int)threads.size());

vDoOut(bDoGcHist, file, " Clock frequency = %5.3f GHz\n", logData.TickFrequency / 1.0E9);
vDoOut(bDoGcHist, file, " Start time %S\n", getTime(&startTime, timeBuff, 64));
vDoOut(bDoGcHist, file, " Last message time %S\n", getTime(&endTime, timeBuff, 64));
vDoOut(bDoGcHist, file, " Total elapsed time %5.3f sec\n", totalSecs);

if (!bDoGcHist)
{
fprintf(file, "\nTHREAD TIMESTAMP FACILITY MESSAGE\n");
fprintf(file, " ID (sec from start)\n");
fprintf(file, "--------------------------------------------------------------------------------------\n");
}

// Merge messages across all threads by timestamp (newest first)
char format[257];
format[256] = format[0] = 0;
unsigned msgCtr = 0;

for (;;)
{
// Find the thread with the newest message
int newestIdx = -1;
uint64_t newestTimestamp = 0;

for (int i = 0; i < (int)msgStates.size(); i++)
{
if (msgStates[i].hasMsg && msgStates[i].currentMsg.Timestamp > newestTimestamp)
{
newestTimestamp = msgStates[i].currentMsg.Timestamp;
newestIdx = i;
}
}

if (newestIdx < 0)
break;

if (IsInterrupt())
{
vDoOut(bDoGcHist, file, "----- Interrupted by user -----\n");
break;
}

ThreadMsgState& state = msgStates[newestIdx];
DacpStressMsgData& msg = state.currentMsg;

if (msg.FormatString != 0)
{
// Read the format string from target memory
hr = memCallBack->ReadVirtual(msg.FormatString, format, 256, 0);
if (hr != S_OK)
strcpy_s(format, ARRAY_SIZE(format), "Could not read address of format string");

double deltaTime = ((double)(msg.Timestamp - logData.StartTimestamp)) / logData.TickFrequency;

// Read arguments
void* args[StressMsg::maxArgCnt] = {};
if (msg.ArgumentCount > 0)
{
CLRDATA_ADDRESS argAddrs[StressMsg::maxArgCnt] = {};
unsigned int argsFetched = 0;
unsigned int argsToFetch = msg.ArgumentCount;
if (argsToFetch > StressMsg::maxArgCnt)
argsToFetch = StressMsg::maxArgCnt;
state.pEnum->GetArguments(0, argsToFetch, argAddrs, &argsFetched);
for (unsigned int i = 0; i < argsFetched; i++)
args[i] = (void*)(size_t)argAddrs[i];
}

// Handle TaskSwitch marker the same way as the legacy path
if (strcmp(format, ThreadStressLog::TaskSwitchMsg()) == 0)
{
if (bDoGcHist)
{
state.threadId = (unsigned)(size_t)args[0];
}
else
{
fprintf(file, "Task was switched from %x\n", (unsigned)(size_t)args[0]);
state.threadId = (unsigned)(size_t)args[0];
}
}
else if (!bDoGcHist)
{
formatOutput(memCallBack, file, format, (unsigned)state.threadId, deltaTime, msg.Facility, args);
}
msgCtr++;
}

// Advance to the next message for this thread
fetched = 0;
hr = state.pEnum->Next(1, &state.currentMsg, &fetched);
if (fetched == 0 || FAILED(hr))
{
state.hasMsg = false;
}
}

// Cleanup
for (auto& s : msgStates)
s.pEnum->Release();
pSos17->Release();

// Match legacy footer format
vDoOut(bDoGcHist, file, "---------------------------- %d total entries ------------------------------------\n", msgCtr);
if (!bDoGcHist)
{
fclose(file);
}

return msgCtr > 0 ? S_OK : S_FALSE;
}

/*********************************************************************************/
HRESULT StressLog::Dump(ULONG64 outProcLog, const char* fileName, struct IDebugDataSpaces* memCallBack)
{
Expand Down
20 changes: 17 additions & 3 deletions src/SOS/Strike/strike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@
#include "hillclimbing.h"
#include "sos_md.h"

// Forward declaration for the cDAC-based stress log dump path (stressLogDump.cpp).
HRESULT DumpStressLogViaInterface17(const char* fileName, struct IDebugDataSpaces* memCallBack);

#ifndef FEATURE_PAL

#include "ExpressionNode.h"
Expand Down Expand Up @@ -7653,9 +7656,20 @@ DECLARE_API(DumpLog)

ExtOut("Attempting to dump Stress log to file '%s'\n", fileName);



Status = StressLog::Dump(StressLogAddress, fileName, g_ExtData);
// Try the cDAC-based path (ISOSDacInterface17) first.
// This supports both CoreCLR and NativeAOT stress logs.
{
HRESULT hrInterface17 = DumpStressLogViaInterface17(fileName, g_ExtData);
if (hrInterface17 != E_NOINTERFACE)
{
Status = hrInterface17;
}
else
{
// ISOSDacInterface17 not available -- fall back to legacy raw-read path
Status = StressLog::Dump(StressLogAddress, fileName, g_ExtData);
}
}

if (Status == S_OK)
ExtOut("SUCCESS: Stress log dumped\n");
Expand Down
12 changes: 12 additions & 0 deletions src/shared/pal/prebuilt/idl/sospriv_i.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface15,0x7ed81261,0x52a9,0x4a23,0xa3,0x58,

MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface16,0x4ba12ff8,0xdaac,0x4e43,0xac,0x56,0x98,0xcf,0x8d,0x5c,0x59,0x5d);


MIDL_DEFINE_GUID(IID, IID_ISOSStressLogThreadEnum,0x94a2bd3d,0xab3d,0x43bf,0x81,0xd8,0x3a,0xe9,0x6b,0x8e,0x33,0xcd);


MIDL_DEFINE_GUID(IID, IID_ISOSStressLogMsgEnum,0x437cb033,0xafe7,0x4c0f,0xa4,0xa7,0x82,0xc8,0x91,0xbc,0x04,0x9e);


MIDL_DEFINE_GUID(IID, IID_ISOSStressLogMemoryEnum,0x8e20713e,0x960c,0x4be4,0xbd,0x55,0xed,0xb9,0xa6,0x18,0xbc,0x8d);


MIDL_DEFINE_GUID(IID, IID_ISOSDacInterface17,0x2f4bb585,0xed50,0x479e,0xbb,0xe0,0x10,0xa9,0x5a,0x5d,0xa3,0xbb);

#undef MIDL_DEFINE_GUID

#ifdef __cplusplus
Expand Down
Loading
Loading