diff --git a/DbgHelpUtils/CMakeLists.txt b/DbgHelpUtils/CMakeLists.txt index 191db55..d0397b7 100644 --- a/DbgHelpUtils/CMakeLists.txt +++ b/DbgHelpUtils/CMakeLists.txt @@ -333,6 +333,7 @@ set(Utility "hex_dump.h" "join.h" "memory_range.h" + "null_stream.h" "overload.h" "locale_number_formatting.cpp" "locale_number_formatting.h" diff --git a/DbgHelpUtils/DbgHelpUtils.vcxproj b/DbgHelpUtils/DbgHelpUtils.vcxproj index 6424244..472cb69 100644 --- a/DbgHelpUtils/DbgHelpUtils.vcxproj +++ b/DbgHelpUtils/DbgHelpUtils.vcxproj @@ -342,6 +342,7 @@ + diff --git a/DbgHelpUtils/DbgHelpUtils.vcxproj.filters b/DbgHelpUtils/DbgHelpUtils.vcxproj.filters index eb507e4..c686633 100644 --- a/DbgHelpUtils/DbgHelpUtils.vcxproj.filters +++ b/DbgHelpUtils/DbgHelpUtils.vcxproj.filters @@ -959,6 +959,9 @@ Heap + + Utility + diff --git a/DbgHelpUtils/filesystem_utils.h b/DbgHelpUtils/filesystem_utils.h index 544e1e5..7856612 100644 --- a/DbgHelpUtils/filesystem_utils.h +++ b/DbgHelpUtils/filesystem_utils.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include diff --git a/DbgHelpUtils/null_stream.h b/DbgHelpUtils/null_stream.h new file mode 100644 index 0000000..f3c9743 --- /dev/null +++ b/DbgHelpUtils/null_stream.h @@ -0,0 +1,27 @@ +#pragma once +#include + +class null_stream : public std::wostream +{ +private: + class null_buffer : public std::wstreambuf + { + public: + int_type overflow( int_type const ch ) override + { + return ch; + } + + // ReSharper disable once IdentifierTypo + std::streamsize xsputn( [[maybe_unused]] const char_type* s, std::streamsize const count ) override + { + return count; + } + } nb_; + +public: + null_stream() + : std::wostream{&nb_} + { + } +}; diff --git a/MiniDumper/dump_mini_dump_heap_statistics.cpp b/MiniDumper/dump_mini_dump_heap_statistics.cpp index d2bc934..560c559 100644 --- a/MiniDumper/dump_mini_dump_heap_statistics.cpp +++ b/MiniDumper/dump_mini_dump_heap_statistics.cpp @@ -181,15 +181,21 @@ void dump_mini_dump_heap_statistics(std::wostream& log, mini_dump const& mini_du auto const loading_heap_statistics = L"Loading heap statistics..."s; std::wstring const move_back(loading_heap_statistics.size(), L'\b'); std::wstring const clear(loading_heap_statistics.size(), L' '); - std::wcerr << loading_heap_statistics; + if(options.verbose_output()) + { + std::wcerr << loading_heap_statistics; + } auto const hex_length = heaps.peb().machine_hex_printable_length(); auto const is_x86_target = stream_stack_dump::is_x86_target_t{heaps.peb().is_x86_target()}; auto const statistics = heaps.statistics(); - std::wcerr << move_back; - std::wcerr << clear; - std::wcerr << move_back; + if(options.verbose_output()) + { + std::wcerr << move_back; + std::wcerr << clear; + std::wcerr << move_back; + } log << L"Heap Statistics:\n"; if(options.display_heap_statistic_view(heap_statistics_view::by_size_frequency_view)) diff --git a/MiniDumper/symbol_engine_ui.cpp b/MiniDumper/symbol_engine_ui.cpp index 05861f5..a78b427 100644 --- a/MiniDumper/symbol_engine_ui.cpp +++ b/MiniDumper/symbol_engine_ui.cpp @@ -24,24 +24,30 @@ void symbol_engine_ui::deferred_symbol_load_partial([[maybe_unused]] std::wstrin void symbol_engine_ui::start_download(std::wstring_view const& module_name) { module_ = std::format(L"downloading {}: ", module_name); - std::wcerr << module_; - last_percent_.clear(); + if(options_.verbose_output()) + { + std::wcerr << module_; + last_percent_.clear(); + } } void symbol_engine_ui::download_percent(unsigned const percent) { - if (!last_percent_.empty()) + if (options_.verbose_output() && !last_percent_.empty()) { std::wcerr << std::wstring(last_percent_.size(), L'\b'); } last_percent_ = std::format(L"{}%", percent); - std::wcerr << last_percent_; + if(options_.verbose_output()) + { + std::wcerr << last_percent_; + } } void symbol_engine_ui::download_complete() { - if (!last_percent_.empty() || !module_.empty()) + if (options_.verbose_output() && (!last_percent_.empty() || !module_.empty())) { std::wstring const clear(last_percent_.size() + module_.size(), L'\b'); std::wcerr << clear << std::wstring(last_percent_.size() + module_.size(), L' ') << clear; @@ -52,7 +58,12 @@ void symbol_engine_ui::download_complete() std::wostream& symbol_engine_ui::log_stream() const { - return std::wcerr; + if(options_.verbose_output()) + { + return std::wcerr; + } + + return null_stream_; } bool symbol_engine_ui::symbol_load_debug() const diff --git a/MiniDumper/symbol_engine_ui.h b/MiniDumper/symbol_engine_ui.h index 051ea8f..109a9ab 100644 --- a/MiniDumper/symbol_engine_ui.h +++ b/MiniDumper/symbol_engine_ui.h @@ -2,6 +2,7 @@ #include "dump_file_options.h" #include "DbgHelpUtils/i_symbol_load_callback.h" +#include "DbgHelpUtils/null_stream.h" class symbol_engine_ui : public dlg_help_utils::dbg_help::i_symbol_load_callback { @@ -21,4 +22,5 @@ class symbol_engine_ui : public dlg_help_utils::dbg_help::i_symbol_load_callback dump_file_options const& options_; std::wstring module_; std::wstring last_percent_; + mutable null_stream null_stream_; }; diff --git a/RunAllBuildHeapDumpTests.ps1 b/RunAllBuildHeapDumpTests.ps1 index 83fd2b6..ec507ea 100644 --- a/RunAllBuildHeapDumpTests.ps1 +++ b/RunAllBuildHeapDumpTests.ps1 @@ -5,6 +5,7 @@ Param [switch] $CheckOnly, [switch] $GenerateHeapLogs, [switch] $LastReport, + [int] $ConcurrentLimit = 0, [switch] $Verbose, [switch] $ExitOnFailure ) @@ -40,19 +41,23 @@ if(!$LastReport) # run release / x64 Write-Host "Running $Compiler Release x64 tests" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$false -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure + Write-Verbose "Command: \RunHeapDumpTests.ps1 -TestX86:`$false -TestDebug:`$false -CheckOnly:`$$CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:`$$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit" + .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$false -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit # run release / x86 Write-Host "Running $Compiler Release x86 tests" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$false -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure + Write-Verbose "Command: \RunHeapDumpTests.ps1 -TestX86:`$true -TestDebug:`$false -CheckOnly:`$$CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:`$$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit" + .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$false -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit # run debug / x64 Write-Host "Running $Compiler Debug x64 tests" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$true -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure + Write-Verbose "Command: \RunHeapDumpTests.ps1 -TestX86:`$false -TestDebug:`$true -CheckOnly:`$$CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:`$$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit" + .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$true -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit # run debug / x86 Write-Host "Running $Compiler Debug x86 tests" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$true -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure + Write-Verbose "Command: \RunHeapDumpTests.ps1 -TestX86:`$true -TestDebug:`$true -CheckOnly:`$$CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:`$$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit" + .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$true -CheckOnly:$CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -ExitOnFailure:$ExitOnFailure -ConcurrentLimit:$ConcurrentLimit } @@ -74,6 +79,7 @@ if($errorLines) { Write-Host "Complete with Errors:" Write-Host $errorLines + Write-Host "Completed with Errors" } else { diff --git a/RunAllFailedTests.ps1 b/RunAllFailedTests.ps1 index 8ebc74b..e9c0a68 100644 --- a/RunAllFailedTests.ps1 +++ b/RunAllFailedTests.ps1 @@ -5,13 +5,92 @@ Param [switch] $GenerateHeapLogs, [switch] $LastReport, [string[]] $Filter, + [int] $ConcurrentLimit = 0, [switch] $Verbose ) +if($Verbose) +{ + $VerbosePreference = "continue" +} + +if ($ConcurrentLimit -le 0) +{ + $ConcurrentLimit = $env:NUMBER_OF_PROCESSORS +} + +$engine = Get-Process -id $pid | Get-Item + +Function GenerateTempReportFile($tempReportFiles) +{ + $tempFile = New-TemporaryFile + $tempReportFiles.Add($tempFile) + return $tempFile +} + +Function RunTest($jobs, $tempReportFiles, $testArguments) +{ + if($ConcurrentLimit -eq 1) + { + $tempFile = GenerateTempReportFile $tempReportFiles + Write-Verbose "Command: .\RunHeapDumpTests.ps1 $testArguments -ResultFile:`"$($tempFile.FullName)`" -ConcurrentLimit:1" + Invoke-Expression -Command ".\RunHeapDumpTests.ps1 $testArguments -ResultFile:`"$($tempFile.FullName)`" -ConcurrentLimit:1" + return + } + + # waiting for a job to complete to start another job + if(($jobs | Where-Object -FilterScript { -not $_.HasExited }).Count -ge $ConcurrentLimit) + { + Write-Verbose "Waiting to start job" + while(($jobs | Where-Object -FilterScript { -not $_.HasExited }).Count -ge $ConcurrentLimit) + { + Start-Sleep -Milliseconds 500 + } + Write-Verbose "Wait complete" + } + + $tempFile = GenerateTempReportFile $tempReportFiles + Write-Verbose "Command: $($engine.FullName) -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\RunHeapDumpTests.ps1 $testArguments -ResultFile:`"$($tempFile.FullName)`"" + $arguments = "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\RunHeapDumpTests.ps1 $testArguments -ResultFile:`"$($tempFile.FullName)`"" + + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $engine.FullName + $pinfo.UseShellExecute = $false + $pinfo.Arguments = $arguments + $pinfo.WorkingDirectory = Get-Location + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + + $jobs.Add($p) + Write-Verbose "Started job [$($p.Id)]" +} + +Function RunTests($jobs, $tempReportFiles, $type, $name, $baseArguments) +{ + # run release / x64 + Write-Host "Running $Compiler Release x64 test on $type $name" + $rv = RunTest $jobs $tempReportFiles "-TestX86:`$false -TestDebug:`$false $baseArguments" + + # run release / x86 + Write-Host "Running $Compiler Release x86 tests on $type $name" + $rv = RunTest $jobs $tempReportFiles "-TestX86:`$true -TestDebug:`$false $baseArguments" + + # run debug / x64 + Write-Host "Running $Compiler Debug x64 tests on $type $name" + $rv = RunTest $jobs $tempReportFiles "-TestX86:`$false -TestDebug:`$true $baseArguments" + + # run debug / x86 + Write-Host "Running $Compiler Debug x86 tests on $type $name" + $rv = RunTest $jobs $tempReportFiles "-TestX86:`$true -TestDebug:`$true $baseArguments" +} + if(!$LastReport) { Remove-Item $ResultFile -ErrorAction:SilentlyContinue | Out-Null $dmps = Get-ChildItem failed\*.dmp -Recurse + $tempReportFiles = New-Object System.Collections.ArrayList + $jobs = New-Object System.Collections.ArrayList foreach ($dmp in $dmps) { $CheckDumpHasStackTrace = $dmp.BaseName -match '_ust' @@ -29,43 +108,45 @@ if(!$LastReport) elseif ($dmp.BaseName -match '_1$') { $name = $dmp.BaseName.SubString(0, $dmp.BaseName.Length - 2) - # run release / x64 - Write-Host "Running $Compiler Release x64 test on set $name" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$false -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $name -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -DumpFolder $dmp.Directory - - # run release / x86 - Write-Host "Running $Compiler Release x86 tests on set $name" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$false -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $name -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -DumpFolder $dmp.Directory + $baseArguments = "-CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -Compiler:$Compiler -CheckDumpFileBaseName `"$name`" -CheckDumpHasStackTrace:`$$CheckDumpHasStackTrace -SkipFakeOffsetCheck:`$$SkipFakeOffsetCheck -DumpFolder `"$($dmp.Directory)`" -ConcurrentLimit:$ConcurrentLimit" - # run debug / x64 - Write-Host "Running $Compiler Debug x64 tests on set $name" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$true -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $name -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -DumpFolder $dmp.Directory - - # run debug / x86 - Write-Host "Running $Compiler Debug x86 tests on set $name" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$true -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $name -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -DumpFolder $dmp.Directory + RunTests $jobs $tempReportFiles 'set' $name $baseArguments } else { - # run release / x64 - Write-Host "Running $Compiler Release x64 test on single $dmp" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$false -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $dmp.BaseName -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -SingleDumpOnly -DumpFolder $dmp.Directory + $baseArguments = "-CheckOnly -GenerateHeapLogs:`$$GenerateHeapLogs -Verbose:`$$Verbose -Compiler:$Compiler -CheckDumpFileBaseName `"$($dmp.BaseName)`" -CheckDumpHasStackTrace:`$$CheckDumpHasStackTrace -SkipFakeOffsetCheck:`$$SkipFakeOffsetCheck -SingleDumpOnly -DumpFolder `"$($dmp.Directory)`" -ConcurrentLimit:$ConcurrentLimit" + RunTests $jobs $tempReportFiles 'single' $dmp $baseArguments + } + } - # run release / x86 - Write-Host "Running $Compiler Release x86 tests on single $dmp" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$false -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $dmp.BaseName -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -SingleDumpOnly -DumpFolder $dmp.Directory + Write-Verbose "Waiting for all jobs to complete" + # wait for all jobs + foreach($p in $jobs) + { + $p.WaitForExit() + } - # run debug / x64 - Write-Host "Running $Compiler Debug x64 tests on single $dmp" - .\RunHeapDumpTests.ps1 -TestX86:$false -TestDebug:$true -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $dmp.BaseName -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -SingleDumpOnly -DumpFolder $dmp.Directory + Write-Verbose "Combining report output" - # run debug / x86 - Write-Host "Running $Compiler Debug x86 tests on single $dmp" - .\RunHeapDumpTests.ps1 -TestX86:$true -TestDebug:$true -CheckOnly -GenerateHeapLogs:$GenerateHeapLogs -Verbose:$Verbose -ResultFile:$ResultFile -Compiler:$Compiler -CheckDumpFileBaseName $dmp.BaseName -CheckDumpHasStackTrace:$CheckDumpHasStackTrace -SkipFakeOffsetCheck:$SkipFakeOffsetCheck -SingleDumpOnly -DumpFolder $dmp.Directory + # Combine result files + foreach ($reportFile in $tempReportFiles) + { + if(Test-Path -Path $reportFile -PathType Leaf) + { + Write-Verbose "Append $reportFile to $ResultFile" + Get-Content $reportFile | Add-Content $ResultFile + } else { + Write-Error "Report File $reportFile doesn't exist!" } } + + # wait for the above to complete it's file handling as the file may still be locked when we get to the remove temp files stage... + #Start-Sleep -Seconds 5 + + #$tempReportFiles | Remove-Item } +Write-Verbose "Reading report output" $errorLines = "" $log = Get-Content $ResultFile @@ -85,6 +166,7 @@ if($errorLines) { Write-Host "Complete with Errors:" Write-Host $errorLines + Write-Host "Completed with Errors" } else { diff --git a/RunHeapDumpTests.ps1 b/RunHeapDumpTests.ps1 index b4c2a33..e9d62ac 100644 --- a/RunHeapDumpTests.ps1 +++ b/RunHeapDumpTests.ps1 @@ -25,14 +25,57 @@ Param [switch] $CheckDumpHasStackTrace, [switch] $SkipFakeOffsetCheck, [switch] $SingleDumpOnly, + [int] $ConcurrentLimit = 0, [switch] $ExitOnFailure ) -Function RunCommand($command, $arguments) +$runningProcesses = New-Object System.Collections.ArrayList +$errorsDetected = $false + +if ($ConcurrentLimit -le 0) { - $text = "$command $arguments" - Write-Verbose "Run: $text" + $ConcurrentLimit = $env:NUMBER_OF_PROCESSORS +} + +Function DetectCompletedJobWithErrors() +{ + foreach($job in $runningProcesses | Where-Object -FilterScript { $_.Process.HasExited -and $_.Process.ExitCode -ne 0 }) + { + return $true + } + + return $false +} + +Function WaitForAllProcessesToComplete() +{ + $err = $null + foreach($p in $runningProcesses) + { + Write-Verbose "Waiting for $($p.Process.ProcessName) PID $($p.Process.Id)" + $p.Process.WaitForExit() + $exitCode = $p.Process.ExitCode + + if($p.ReportFile) + { + Write-Verbose "Add $($p.ReportFile) to $ResultFile" + Get-Content -Path $p.ReportFile | Add-Content -Path $ResultFile + Remove-Item $p.ReportFile + } + + if($exitCode -ne 0) + { + Write-Verbose "Add $($p.Process.ProcessName) Command Error to $ResultFile" + $text = "$($p.Process.StartInfo.FileName) $($p.Process.StartInfo.Arguments)" + $err = "ERROR: command failed: [$text] -- with $exitCode" + Add-Content -Path $ResultFile $err + Write-Error $err + } + } +} +Function RunCommandNow($command, $arguments) +{ $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $command $pinfo.UseShellExecute = $false @@ -42,22 +85,71 @@ Function RunCommand($command, $arguments) $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() - $exitCode = $p.ExitCode - - if($exitCode -ne 0) +} + +Function RunCommand($command, $arguments, $reportFile) +{ + if($errorsDetected) + { + return; + } + + if($ConcurrentLimit -eq 1) { - $err = "ERROR: command failed: [$text] -- with $exitCode" - Add-Content -Path $ResultFile $err + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $command + $pinfo.UseShellExecute = $false + $pinfo.Arguments = $arguments + $pinfo.WorkingDirectory = Get-Location + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + $p.WaitForExit() + $data = New-Object PSObject -Property @{ + Process = $p + ReportFile = $reportFile + } + $rv = $runningProcesses.Add($data) + return + } - if($ExitOnFailure) + # waiting for a job to complete to start another job + if(($runningProcesses | Where-Object -FilterScript { -not $_.Process.HasExited }).Count -ge $ConcurrentLimit) + { + Write-Verbose "Waiting to start process" + while(($runningProcesses | Where-Object -FilterScript { -not $_.Process.HasExited }).Count -ge $ConcurrentLimit) { - throw $err + Start-Sleep -Milliseconds 500 } - else + Write-Verbose "Wait complete" + } + + if($ExitOnFailure) + { + $errorsDetected = DetectCompletedJobWithErrors + + if($errorsDetected) { - Write-Error $err + return; } } + + $text = "$command $arguments" + Write-Verbose "Run: $text" + + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $command + $pinfo.UseShellExecute = $false + $pinfo.Arguments = $arguments + $pinfo.WorkingDirectory = Get-Location + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + $data = New-Object PSObject -Property @{ + Process = $p + ReportFile = $reportFile + } + $rv = $runningProcesses.Add($data) } Function RunAllAllocationApplicationArgs($options, $validateoptions, $ust_test) @@ -110,7 +202,7 @@ Function RunAllocationApplication($arg, $config, $arch_dir, $arch, $alloc, $opti remove-item $dmp_2 -ErrorAction:SilentlyContinue | Out-Null remove-item $log -ErrorAction:SilentlyContinue | Out-Null remove-item $json -ErrorAction:SilentlyContinue | Out-Null - RunCommand "$PSScriptRoot\$arch_dir\$CompilerDir\$config\$app_name" "--test `"$arg`" --type `"$alloc`" --dmp1 `"$dmp_1`" --dmp2 `"$dmp_2`" --log `"$log`" --json `"$json`"" + RunCommandNow "$PSScriptRoot\$arch_dir\$CompilerDir\$config\$app_name" "--test `"$arg`" --type `"$alloc`" --dmp1 `"$dmp_1`" --dmp2 `"$dmp_2`" --log `"$log`" --json `"$json`"" } RunAllocationApplicationChecks $validateoptions $base_name @@ -122,6 +214,17 @@ Function RunAllocationApplicationChecks($validateoptions, $base_name) $dmp_2 = "$DumpFolder\$($base_name)_2.dmp" $json = "$DumpFolder\$base_name.json" + if($ConcurrentLimit -eq 1) + { + $verbose_arg = "--verbose" + $no_output_arg = "" + } + else + { + $verbose_arg = "" + $no_output_arg = "--no-output" + } + if($GenerateHeapLogs) { $dmp_1_full_log = "$DumpFolder\$($base_name)_1_full.log" @@ -135,13 +238,15 @@ Function RunAllocationApplicationChecks($validateoptions, $base_name) remove-item $dmp_2_full_diff_log -ErrorAction:SilentlyContinue | Out-Null remove-item $dmp_2_debug_full_diff_log -ErrorAction:SilentlyContinue | Out-Null - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --dumpfile `"$dmp_1`" --out `"$dmp_1_full_log`"" - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --symbols --dumpfile `"$dmp_1`" --out `"$dmp_1_debug_full_log`"" - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --dumpfile `"$dmp_2`" --basediffdumpfile `"$dmp_1`" --out `"$dmp_2_full_diff_log`"" - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --symbols --dumpfile `"$dmp_2`" --basediffdumpfile `"$dmp_1`" --out `"$dmp_2_debug_full_diff_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --modules --dumpfile `"$dmp_1`" --out `"$dmp_1_full_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --symbols --modules --dumpfile `"$dmp_1`" --out `"$dmp_1_debug_full_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --modules --dumpfile `"$dmp_2`" --basediffdumpfile `"$dmp_1`" --out `"$dmp_2_full_diff_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --symbols --modules --dumpfile `"$dmp_2`" --basediffdumpfile `"$dmp_1`" --out `"$dmp_2_debug_full_diff_log`"" } - RunCommand "$ExeFolder\ValidateHeapEntries.exe" "--dmp1 `"$dmp_1`" --dmp2 `"$dmp_2`" --log `"$ResultFile`" --json `"$json`" $validateoptions" + $tempFile = New-TemporaryFile + Write-Verbose "Validating [$base_name]" + RunCommand "$ExeFolder\ValidateHeapEntries.exe" "$no_output_arg --dmp1 `"$dmp_1`" --dmp2 `"$dmp_2`" --log `"$tempFile`" --json `"$json`" $validateoptions" $tempFile } Function RunAllocationApplicationSingleDumpChecks($validateoptions, $base_name) @@ -149,6 +254,17 @@ Function RunAllocationApplicationSingleDumpChecks($validateoptions, $base_name) $dmp = "$DumpFolder\$($base_name).dmp" $json = "$DumpFolder\$base_name.json" + if($ConcurrentLimit -eq 1) + { + $verbose_arg = "--verbose" + $no_output_arg = "" + } + else + { + $verbose_arg = "" + $no_output_arg = "--no-output" + } + if($GenerateHeapLogs) { $dmp_full_log = "$DumpFolder\$($base_name)_full.log" @@ -158,11 +274,13 @@ Function RunAllocationApplicationSingleDumpChecks($validateoptions, $base_name) remove-item $dmp_full_log -ErrorAction:SilentlyContinue | Out-Null remove-item $dmp_debug_full_log -ErrorAction:SilentlyContinue | Out-Null - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --dumpfile `"$dmp`" --out `"$dmp_full_log`"" - RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries --verbose --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --symbols --dumpfile `"$dmp`" --out `"$dmp_debug_full_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --modules --dumpfile `"$dmp`" --out `"$dmp_full_log`"" + RunCommand "$ExeFolder\MiniDumper.exe" "--disable-symbol-load-cancel-keyboard-check --heap --crtheap --heapentries $verbose_arg --heapdebug --heapstat all --heapgraph --nofilterheapentries --nomarknoncrtsystem --modules --symbols --dumpfile `"$dmp`" --out `"$dmp_debug_full_log`"" } - RunCommand "$ExeFolder\ValidateHeapEntries.exe" "--dmp1 `"$dmp`" --log `"$ResultFile`" --json `"$json`" $validateoptions" + $tempFile = New-TemporaryFile + Write-Verbose "Validating [$base_name]" + RunCommand "$ExeFolder\ValidateHeapEntries.exe" "$no_output_arg --dmp1 `"$dmp`" --log `"$tempFile`" --json `"$json`" $validateoptions" $tempFile } Function RunStandardTests() @@ -334,3 +452,5 @@ else { RunStandardTests } + +WaitForAllProcessesToComplete diff --git a/ValidateHeapEntries/ValidateHeapEntries.cpp b/ValidateHeapEntries/ValidateHeapEntries.cpp index 4171685..715a35a 100644 --- a/ValidateHeapEntries/ValidateHeapEntries.cpp +++ b/ValidateHeapEntries/ValidateHeapEntries.cpp @@ -593,7 +593,7 @@ bool validate_allocation_graph_entries_are_not_marked_system(std::vector const& allocations) +[[nodiscard]] bool validate_dump_file(bool stacktrace, bool skip_fake_offset_check, bool no_output, std::wstring const& dump_filename, std::wstring const& base_dump_filename, std::wostream* o_log, std::vector const& allocations) { *o_log << std::format(L"Processing dmp [{}]\n", dump_filename); @@ -607,7 +607,7 @@ bool validate_allocation_graph_entries_are_not_marked_system(std::vector log; - std::wostream* o_log{&std::wcout}; + null_stream null_stream; + std::wostream* o_log{no_output ? &null_stream : &std::wcout}; if(!log_filename.empty()) { log = std::make_unique(log_filename, std::ios_base::out | std::ios_base::app); @@ -757,10 +763,10 @@ int main(int const argc, char* argv[]) return EXIT_FAILURE; } - auto successful = validate_dump_file(stacktrace, skip_fake_offset_check, dump_filename_1, {}, o_log, set.first_allocations); + auto successful = validate_dump_file(stacktrace, skip_fake_offset_check, no_output, dump_filename_1, {}, o_log, set.first_allocations); if(successful && !dump_filename_2.empty()) { - successful = validate_dump_file(stacktrace, skip_fake_offset_check, dump_filename_2, dump_filename_1, o_log, set.second_allocations); + successful = validate_dump_file(stacktrace, skip_fake_offset_check, no_output, dump_filename_2, dump_filename_1, o_log, set.second_allocations); } if(log) diff --git a/ValidateHeapEntries/symbol_engine_ui.cpp b/ValidateHeapEntries/symbol_engine_ui.cpp index 992943a..7082795 100644 --- a/ValidateHeapEntries/symbol_engine_ui.cpp +++ b/ValidateHeapEntries/symbol_engine_ui.cpp @@ -3,6 +3,11 @@ #include #include +symbol_engine_ui::symbol_engine_ui(bool const no_output) + : no_output_{no_output} +{ +} + bool symbol_engine_ui::deferred_symbol_load_cancel([[maybe_unused]] std::wstring_view const& module_name) { return false; @@ -17,7 +22,10 @@ void symbol_engine_ui::start_download(std::wstring_view const& module_name) std::wstringstream ss; ss << L"downloading " << module_name << L": "; module_ = std::move(ss).str(); - std::wcerr << module_; + if(!no_output_) + { + std::wcerr << module_; + } last_percent_.clear(); } @@ -30,13 +38,16 @@ void symbol_engine_ui::download_percent(unsigned const percent) std::wstringstream ss; ss << percent << L"%"; - std::wcerr << ss.str(); + if(!no_output_) + { + std::wcerr << ss.str(); + } last_percent_ = std::move(ss).str(); } void symbol_engine_ui::download_complete() { - if (!last_percent_.empty() || !module_.empty()) + if (!no_output_ && (!last_percent_.empty() || !module_.empty())) { std::wstring const clear(last_percent_.size() + module_.size(), L'\b'); std::wcerr << clear << std::wstring(last_percent_.size() + module_.size(), L' ') << clear; @@ -47,7 +58,12 @@ void symbol_engine_ui::download_complete() std::wostream& symbol_engine_ui::log_stream() const { - return std::wcerr; + if(!no_output_) + { + return std::wcerr; + } + + return null_stream_; } bool symbol_engine_ui::symbol_load_debug() const diff --git a/ValidateHeapEntries/symbol_engine_ui.h b/ValidateHeapEntries/symbol_engine_ui.h index 720ae0f..3128c56 100644 --- a/ValidateHeapEntries/symbol_engine_ui.h +++ b/ValidateHeapEntries/symbol_engine_ui.h @@ -1,10 +1,11 @@ #pragma once #include "DbgHelpUtils/i_symbol_load_callback.h" +#include "DbgHelpUtils/null_stream.h" class symbol_engine_ui : public dlg_help_utils::dbg_help::i_symbol_load_callback { public: - symbol_engine_ui() = default; + symbol_engine_ui(bool no_output); [[nodiscard]] bool deferred_symbol_load_cancel(std::wstring_view const& module_name) override; void deferred_symbol_load_partial(std::wstring_view const& module_name) override; @@ -16,6 +17,8 @@ class symbol_engine_ui : public dlg_help_utils::dbg_help::i_symbol_load_callback [[nodiscard]] bool symbol_load_debug_memory() const override; private: + bool no_output_; std::wstring module_; std::wstring last_percent_; + mutable null_stream null_stream_; };