StartDumpCollectionForHangingBuilds.ps1 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. param(
  2. [Parameter(Mandatory = $true)]
  3. [ValidateNotNullOrEmpty()]
  4. [string]
  5. $ProcDumpPath,
  6. [Parameter(Mandatory = $true)]
  7. [ValidateNotNullOrEmpty()]
  8. [string]
  9. $ProcDumpOutputPath,
  10. [Parameter(Mandatory = $true)]
  11. [datetime]
  12. $WakeTime,
  13. [Parameter(Mandatory = $true)]
  14. [ValidateNotNullOrEmpty()]
  15. [string []]
  16. $CandidateProcessNames
  17. )
  18. Write-Output "Setting up a scheduled job to capture process dumps.";
  19. if ((-not (Test-Path $ProcDumpPath))) {
  20. Write-Warning "Can't find ProcDump at '$ProcDumpPath'.";
  21. }
  22. else {
  23. Write-Output "Using ProcDump from '$ProcDumpPath'.";
  24. }
  25. try {
  26. $previousJobs = Get-Job -Name CaptureDumps* -ErrorAction SilentlyContinue;
  27. $previousScheduledJobs = Get-ScheduledJob CaptureDumps* -ErrorAction SilentlyContinue;
  28. if ($previousJobs.Count -ne 0) {
  29. Write-Output "Found existing dump jobs.";
  30. }
  31. if ($previousScheduledJobs.Count -ne 0) {
  32. Write-Output "Found existing dump jobs.";
  33. }
  34. $previousJobs | Stop-Job -PassThru | Remove-Job;
  35. $previousScheduledJobs | Unregister-ScheduledJob;
  36. }
  37. catch {
  38. Write-Output "There was an error cleaning up previous jobs.";
  39. Write-Output $_.Exception.Message;
  40. }
  41. $repoRoot = Resolve-Path "$PSScriptRoot\..\..";
  42. $ProcDumpOutputPath = Join-Path $repoRoot $ProcDumpOutputPath;
  43. Write-Output "Dumps will be placed at '$ProcDumpOutputPath'.";
  44. Write-Output "Watching processes $($CandidateProcessNames -join ', ')";
  45. # This script registers as a scheduled job. This scheduled job executes after $WakeTime.
  46. # When the scheduled job executes, it runs procdump on all alive processes whose name matches $CandidateProcessNames.
  47. # The dumps are placed in $ProcDumpOutputPath
  48. # If the build completes successfully in less than $WakeTime, a final step unregisters the job.
  49. # Create a unique identifier for the job name
  50. $JobName = "CaptureDumps" + (New-Guid).ToString("N");
  51. # Ensure that the dumps output path exists.
  52. if ((-not (Test-Path $ProcDumpOutputPath))) {
  53. New-Item -ItemType Directory $ProcDumpOutputPath | Out-Null;
  54. }
  55. # We write a sentinel file that we use at the end of the build to
  56. # find the job we started and to determine the results from the sheduled
  57. # job (Whether it ran or not and to display the outputs form the job)
  58. $sentinelFile = Join-Path $ProcDumpOutputPath "dump-sentinel.txt";
  59. Out-File -FilePath $sentinelFile -InputObject $JobName | Out-Null;
  60. [scriptblock] $ScriptCode = {
  61. param(
  62. $ProcDumpPath,
  63. $ProcDumpOutputPath,
  64. $CandidateProcessNames)
  65. Write-Output "Waking up to capture process dumps. Determining hanging processes.";
  66. [System.Diagnostics.Process []]$AliveProcesses = @();
  67. foreach ($candidate in $CandidateProcessNames) {
  68. try {
  69. $candidateProcesses = Get-Process $candidate 2>$null
  70. $candidateProcesses | ForEach-Object { Write-Output "Found candidate process $candidate with PID '$($_.Id)'." };
  71. $AliveProcesses += $candidateProcesses;
  72. }
  73. catch {
  74. Write-Output "No process found for $candidate";
  75. }
  76. }
  77. Write-Output "Starting process dump capture.";
  78. $dumpFullPath = [System.IO.Path]::Combine($ProcDumpOutputPath, "hung_PROCESSNAME_PID_YYMMDD_HHMMSS.dmp");
  79. Write-Output "Capturing output for $($AliveProcesses.Length) processes.";
  80. foreach ($process in $AliveProcesses) {
  81. $procDumpArgs = @("-accepteula", "-ma", $process.Id, $dumpFullPath);
  82. try {
  83. Write-Output "Capturing dump for dump for '$($process.Name)' with PID '$($process.Id)'.";
  84. Start-Process -FilePath $ProcDumpPath -ArgumentList $procDumpArgs -NoNewWindow -Wait;
  85. }
  86. catch {
  87. Write-Output "There was an error capturing a process dump for '$($process.Name)' with PID '$($process.Id)'."
  88. Write-Warning $_.Exception.Message;
  89. }
  90. }
  91. Write-Output "Done capturing process dumps.";
  92. }
  93. $ScriptTrigger = New-JobTrigger -Once -At $WakeTime;
  94. try {
  95. Register-ScheduledJob -Name $JobName -ScriptBlock $ScriptCode -Trigger $ScriptTrigger -ArgumentList $ProcDumpPath, $ProcDumpOutputPath, $CandidateProcessNames;
  96. }
  97. catch {
  98. Write-Warning "Failed to register scheduled job '$JobName'. Dumps will not be captured for build hangs.";
  99. Write-Warning $_.Exception.Message;
  100. }