| File: ProcessDumpUtility.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.Extensions.BlameDataCollector\Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj (Microsoft.TestPlatform.Extensions.BlameDataCollector) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; internal class ProcessDumpUtility : IProcessDumpUtility { private readonly IProcessHelper _processHelper; private readonly IFileHelper _fileHelper; private readonly IHangDumperFactory _hangDumperFactory; private readonly ICrashDumperFactory _crashDumperFactory; private ICrashDumper? _crashDumper; private string? _hangDumpDirectory; private bool _wasHangDumped; public ProcessDumpUtility() : this(new ProcessHelper(), new FileHelper(), new HangDumperFactory(), new CrashDumperFactory()) { } public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, IHangDumperFactory hangDumperFactory, ICrashDumperFactory crashDumperFactory) { _processHelper = processHelper; _fileHelper = fileHelper; _hangDumperFactory = hangDumperFactory; _crashDumperFactory = crashDumperFactory; } [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Part of the public API")] protected Action<object?, string?> OutputReceivedCallback => (process, data) => // Log all standard output message of procdump in diag files. // Otherwise they end up coming on console in pipleine. EqtTrace.Info($"ProcessDumpUtility.OutputReceivedCallback: Output received from procdump process: {data ?? "<null>"}"); /// <inheritdoc/> public IEnumerable<string> GetDumpFiles(bool warnOnNoDumpFiles, bool processCrashed) { if (!_wasHangDumped) { _crashDumper?.WaitForDumpToFinish(); } // If the process was hang dumped we killed it ourselves, so it crashed when executing tests, // but we already have the hang dump, and should not also collect the exit dump that we got // from killing the process by the hang dumper. IEnumerable<string> crashDumps = _crashDumper?.GetDumpFiles(processCrashed) ?? new List<string>(); IEnumerable<string> hangDumps = _fileHelper.DirectoryExists(_hangDumpDirectory) ? _fileHelper.GetFiles(_hangDumpDirectory, "*_hangdump*.dmp", SearchOption.TopDirectoryOnly) : []; var foundDumps = new List<string>(); foreach (var dumpPath in crashDumps.Concat(hangDumps)) { EqtTrace.Info($"ProcessDumpUtility.GetDumpFiles: Looking for dump file '{dumpPath}'."); var found = _fileHelper.Exists(dumpPath); if (found) { EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Found dump file '{dumpPath}'."); foundDumps.Add(dumpPath); } else { EqtTrace.Warning($"ProcessDumpUtility.GetDumpFile: Dump file '{dumpPath}' was not found."); } } if (warnOnNoDumpFiles && foundDumps.Count == 0) { EqtTrace.Error($"ProcessDumpUtility.GetDumpFile: Could not find any dump file in {_hangDumpDirectory}."); throw new FileNotFoundException(Resources.Resources.DumpFileNotGeneratedErrorMessage); } return foundDumps; } /// <inheritdoc/> public void StartHangBasedProcessDump(int processId, string tempDirectory, bool isFullDump, string targetFramework, Action<string>? logWarning = null) { HangDump(processId, tempDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework, logWarning); } /// <inheritdoc/> [MemberNotNull(nameof(_crashDumper))] public void StartTriggerBasedProcessDump(int processId, string testResultsDirectory, bool isFullDump, string targetFramework, bool collectAlways, Action<string> logWarning) { var dumpType = isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini; var processName = _processHelper.GetProcessName(processId); EqtTrace.Info($"ProcessDumpUtility.CrashDump: Creating a crash dumper for process {processName} ({processId}). If crash happens, dumper will try to create '{dumpType}' dump and store it temporarily in path '{testResultsDirectory}'. Later dumps will become attachments and will be moved to TestResults directory."); _crashDumper = _crashDumperFactory.Create(targetFramework); ConsoleOutput.Instance.Information(false, $"Blame: Attaching crash dump utility to process {processName} ({processId})."); _crashDumper.AttachToTargetProcess(processId, testResultsDirectory, dumpType, collectAlways, logWarning); } /// <inheritdoc/> public void DetachFromTargetProcess(int targetProcessId) { _crashDumper?.DetachFromTargetProcess(targetProcessId); } private void HangDump(int processId, string tempDirectory, DumpTypeOption dumpType, string targetFramework, Action<string>? logWarning = null) { _wasHangDumped = true; var processName = _processHelper.GetProcessName(processId); EqtTrace.Info($"ProcessDumpUtility.HangDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{tempDirectory}'."); _hangDumpDirectory = tempDirectory; // oh how ugly this is, but the whole infra above this starts with initializing the logger in Initialize // the logger needs to pass around 2 parameters, so I am just passing it around as callback instead _hangDumperFactory.LogWarning = logWarning; var dumper = _hangDumperFactory.Create(targetFramework); try { dumper.Dump(processId, tempDirectory, dumpType); } catch (Exception ex) { EqtTrace.Error($"Blame: Failed with error {ex}."); throw; } } }