Skip to content

Commit df31080

Browse files
committed
Capture more diagnostics when memory dump test fails, sync Steeltoe logs with CLR dump logs
1 parent 0f06725 commit df31080

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

src/Management/src/Endpoint/Actuators/ThreadDump/EventPipeThreadDumper.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ public async Task<IList<ThreadInfo>> DumpThreadsAsync(CancellationToken cancella
8888

8989
internal async Task<TResult> CaptureLogOutputAsync<TResult>(Func<TextWriter, Task<TResult>> action, CancellationToken cancellationToken)
9090
{
91-
bool isLogEnabled = _logger.IsEnabled(LogLevel.Trace);
91+
bool isTraceLogEnabled = _logger.IsEnabled(LogLevel.Trace);
9292
using var logStream = new MemoryStream();
9393
Exception? error = null;
9494
TResult? result = default;
9595

96-
await using (TextWriter logWriter = isLogEnabled ? new StreamWriter(logStream, leaveOpen: true) : TextWriter.Null)
96+
await using (TextWriter logWriter = isTraceLogEnabled ? new StreamWriter(logStream, leaveOpen: true) : TextWriter.Null)
9797
{
9898
try
9999
{
@@ -108,7 +108,7 @@ internal async Task<TResult> CaptureLogOutputAsync<TResult>(Func<TextWriter, Tas
108108

109109
string? logOutput = null;
110110

111-
if (isLogEnabled)
111+
if (isTraceLogEnabled)
112112
{
113113
logStream.Seek(0, SeekOrigin.Begin);
114114
using var logReader = new StreamReader(logStream);
@@ -119,14 +119,14 @@ internal async Task<TResult> CaptureLogOutputAsync<TResult>(Func<TextWriter, Tas
119119
{
120120
cancellationToken.ThrowIfCancellationRequested();
121121

122-
string message = isLogEnabled
122+
string message = isTraceLogEnabled
123123
? $"Failed to create a thread dump. Captured log:{System.Environment.NewLine}{logOutput}"
124124
: "Failed to create a thread dump.";
125125

126126
throw new InvalidOperationException(message, error);
127127
}
128128

129-
if (isLogEnabled)
129+
if (isTraceLogEnabled)
130130
{
131131
_logger.LogTrace("Captured log from thread dump:{LineBreak}{DumpLog}", System.Environment.NewLine, logOutput);
132132
}
@@ -153,9 +153,9 @@ private async Task<List<ThreadInfo>> GetThreadsFromEventPipeSessionAsync(EventPi
153153
var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader);
154154
computer.GenerateThreadTimeStacks(stackSource);
155155

156-
List<ThreadInfo> results = ReadStackSource(stackSource, symbolReader).ToList();
156+
List<ThreadInfo> results = ReadStackSource(stackSource, symbolReader, logWriter).ToList();
157157

158-
_logger.LogTrace("Finished thread walk.");
158+
_logger.LogTrace("Finished thread walk, found {Count} results.", results.Count);
159159
return results;
160160
}
161161
finally
@@ -231,12 +231,14 @@ private static SymbolReader CreateSymbolReader(TextWriter logWriter)
231231
};
232232
}
233233

234-
private IEnumerable<ThreadInfo> ReadStackSource(MutableTraceEventStackSource stackSource, SymbolReader symbolReader)
234+
private IEnumerable<ThreadInfo> ReadStackSource(MutableTraceEventStackSource stackSource, SymbolReader symbolReader, TextWriter logWriter)
235235
{
236236
var samplesForThread = new Dictionary<int, List<StackSourceSample>>();
237237

238238
stackSource.ForEach(sample =>
239239
{
240+
logWriter.WriteLine($"[Steeltoe] Tracking sample: {sample}");
241+
240242
StackSourceCallStackIndex stackIndex = sample.StackIndex;
241243

242244
while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith(ThreadIdTemplate, StringComparison.Ordinal))
@@ -257,10 +259,17 @@ private IEnumerable<ThreadInfo> ReadStackSource(MutableTraceEventStackSource sta
257259
}
258260
});
259261

262+
// Workaround for Sonar bug, which incorrectly flags the code as unreachable.
263+
#pragma warning disable S2589 // Boolean expressions should not be gratuitous
264+
logWriter.WriteLine(samplesForThread.Count == 0
265+
? "[Steeltoe] WARN: No managed samples found in memory dump."
266+
: $"[Steeltoe] Start analyzing all {samplesForThread.Count} threads.");
267+
#pragma warning restore S2589 // Boolean expressions should not be gratuitous
268+
260269
// For every thread recorded in our trace, use the first stack.
261270
foreach ((int threadId, List<StackSourceSample> samples) in samplesForThread)
262271
{
263-
_logger.LogDebug("Found {Stacks} stacks for thread {Thread}.", samples.Count, threadId);
272+
logWriter.WriteLine($"[Steeltoe] Found {samples.Count} samples for thread {threadId}, analyzing the first one.");
264273

265274
var threadInfo = new ThreadInfo
266275
{
@@ -269,7 +278,13 @@ private IEnumerable<ThreadInfo> ReadStackSource(MutableTraceEventStackSource sta
269278
ThreadName = $"Thread-{threadId:D5}"
270279
};
271280

272-
List<StackTraceElement> stackTrace = GetStackTrace(threadId, samples[0], stackSource, symbolReader).ToList();
281+
List<StackTraceElement> stackTrace = GetStackTrace(threadId, samples[0], stackSource, symbolReader, logWriter).ToList();
282+
283+
if (logWriter != TextWriter.Null)
284+
{
285+
int managedCount = stackTrace.Count(frame => !frame.IsNativeMethod);
286+
logWriter.WriteLine($"[Steeltoe] Found {managedCount} of {stackTrace.Count} frames in managed code for thread {threadId}.");
287+
}
273288

274289
foreach (StackTraceElement stackFrame in stackTrace)
275290
{
@@ -289,10 +304,10 @@ private static int ExtractThreadId(string frameName)
289304
return int.Parse(frameName.AsSpan(ThreadIdTemplate.Length, firstIndex - ThreadIdTemplate.Length), CultureInfo.InvariantCulture);
290305
}
291306

292-
private IEnumerable<StackTraceElement> GetStackTrace(int threadId, StackSourceSample stackSourceSample, TraceEventStackSource stackSource,
293-
SymbolReader symbolReader)
307+
private static IEnumerable<StackTraceElement> GetStackTrace(int threadId, StackSourceSample stackSourceSample, TraceEventStackSource stackSource,
308+
SymbolReader symbolReader, TextWriter logWriter)
294309
{
295-
_logger.LogDebug("Processing thread with ID: {Thread}.", threadId);
310+
logWriter.WriteLine($"[Steeltoe] Walking stack frames of thread {threadId}.");
296311

297312
StackSourceCallStackIndex stackIndex = stackSourceSample.StackIndex;
298313
StackSourceFrameIndex frameIndex = stackSource.GetFrameIndex(stackIndex);

src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ public async Task Can_resolve_source_location_from_pdb()
3535
StackTraceElement? backgroundThreadFrame = threads.SelectMany(thread => thread.StackTrace)
3636
.FirstOrDefault(frame => frame.MethodName == "BackgroundThreadCallback(class System.Object)");
3737

38-
backgroundThreadFrame.Should().NotBeNull();
38+
if (backgroundThreadFrame == null)
39+
{
40+
string logs = loggerProvider.GetAsText();
41+
throw new InvalidOperationException($"Failed to find expected stack frame. Captured log:{System.Environment.NewLine}{logs}");
42+
}
43+
3944
backgroundThreadFrame.IsNativeMethod.Should().BeFalse();
4045
backgroundThreadFrame.ModuleName.Should().Be(GetType().Assembly.GetName().Name);
4146
backgroundThreadFrame.ClassName.Should().Be(typeof(NestedType).FullName);

0 commit comments

Comments
 (0)