Использование Powershell для автоматизации VSPerfCmd.exe (профилирование производительности Visual Studio)

Поскольку этот вопрос является попыткой устранить большую проблему с моей стороны, я включил в свой пост существенную историю и историю расследований. Таким образом, я разбил пост на 3 раздела: "Предыстория", "Расследование пока" и "Проблема". Поскольку я провел дополнительное расследование самостоятельно, я также добавил раздел "Дополнительное расследование", чтобы расширить свои выводы. В конечном итоге это привело меня к решению проблемы самостоятельно, и я включил оригинальную статью в качестве руководства для любого разработчика, пытающегося сделать то же самое. В конечном итоге эта проблема закончилась возвращением контроля, который VSPerfCmd.exe имел в версиях Powershell после v1.

  1. Фон

Как часть моей сборки TFS (которая включает в себя автоматическое развертывание на моем веб-сервере разработки DEV), я хотел бы запустить и запустить тест производительности моего кода, чтобы я мог видеть, когда новые изменения негативно влияют на скорость моего API. С этой целью я установил на DEV тестовый прогон (SoapUI) и VS Team Tools и написал скрипт Powershell, который при локальном запуске на DEV создает нужные мне отчеты. Однако мне еще не удалось запустить этот скрипт и заставить его работать из любого другого места. Под этим я подразумеваю, что, только войдя на сервер, найдя файл.ps1 и запустив его в Powershell, он работает. Вот этот скрипт:

#script location+name \\DEV\C$\PerformanceTest\profile-tracing-SoapUI.ps1
$startPath = Get-Location
$siteUrl = "http://api.dev.com"
$sleepTime = 5
$logLocation = "C:\reports\api"
$websiteLocation = "W:\Sites\API\Code\API\_PublishedWebsites\API\bin"

try
{
    #Instrument the API dlls
    Write-Host "Instrumenting DLLs..."
    Get-ChildItem $websiteLocation "API*.dll" | ForEach-Object {
        Set-Location -Path $websiteLocation
        & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSInstr.exe" $_.Name
    }

    #set location back to start
    Set-Location -Path $startPath

    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCLREnv.cmd" /GlobalTraceOn
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCLREnv.cmd" /GlobalInteractionOn

    #launch the profiler on the site
    Write-Host "Starting the trace..."

    #start:Trace - in tracing mode so we get timing data
    #output - the output vsp file
    #cs - cross session mode because we are profiling an IIS session
    #user - give permissions to profile to everyone
    #globaloff - start with capturing off so we don't get start up data.
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /start:Trace /output:$logLocation"\API.vsp" /cs /User:Everyone /globaloff

    #restart IIS so the tracer can detect it
    Write-Host "Resetting IIS..."
    IISReset t80w103 /noforce
    IISReset t80w103 /status

    #output the status
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /status

    #set up request for triggering api start up
    Write-Host "Starting app via untraced request..."
    $request = [System.Net.WebRequest]::Create("$siteUrl/api/v1/foo/1")
    $request.ContentType = "application/json"
    $request.Method = "GET" 

    #run an initial request to trigger api start up
    $response = $request.GetResponse()
    Write-Host $response.StatusCode

    #enable capturing
    Write-Host "Enabling capturing..."
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /globalon

    #wait so everything can catch up
    Write-Host "Pausing for $sleepTime seconds to let processes catch up..."
    Start-Sleep -s $sleepTime

    #run the load tester
    Write-Host "Running SOAP UI load test"
    #e - the site to test against
    #s - the test suite name
    #c - the test cases
    #l - the loadtest to run
    #r - log reports
    #f - log location
    #<soap ui test suite location>
    $loadtestName = "LoadTest All API Methods"
    & "C:\Program Files (x86)\SmartBear\SoapUI-5.1.3\bin\loadtestrunner.bat" -e $siteUrl -s "Load Test" -c "Load Test Cases" -l $loadtestName -r -f $logLocation "Api Profiler Load Test.xml"


    #wait so everything can catch up
    Write-Host "Pausing for $sleepTime seconds to let processes catch up..."
    Start-Sleep -s $sleepTime

    #disable capturing
    Write-Host "Disabling capturing..."
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /globaloff

    #shut down the profiler
    Write-Host "Shutting down profiler. This may take some time..." 

    #shut down IIS so the /shutdown command works
    IISReset t80w103 /noforce
    IISReset t80w103 /status
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /shutdown
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCLREnv.cmd" /off /globaloff

    #generate report summary
    $date = Get-Date -format "yyyy-dd-M--HH-mm-ss"
    $summaryReportLocation = "$logLocation\API $date"
    & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\VSPerfReport.exe" "$logLocation\Api.vsp" /SummaryFile /Output:"$summaryReportLocation"

    #move items to shared location
    $reportFolder = "\\shareDrive\API\Performance Reports\$date"
    $profilerReport = get-childitem "$summaryReportLocation.vsps"
    New-Item $reportFolder -type directory
    Copy-Item $profilerReport.FullName  $reportFolder

    #log success
    $logDate = Get-Date -format "yyyy-dd-mm HH-mm-ss"
    $output = "[$logDate]" + [System.Environment]::NewLine + "Completed successfully."
    $output | out-file "profiler-log.log" -append

    Write-Host "Completed successfully."
}
catch{
    #log error
    Write-Host $_
    $logDate = Get-Date -format "yyyy-dd-mm HH-mm-ss"
    $output = "[$logDate]" + [System.Environment]::NewLine + $_ + [System.Environment]::NewLine
    $output | out-file "profiler-log.log" -append
}
finally{
    Write-Output "Done"
}

Как я уже сказал, вышеописанное работает нормально, если я сам запускаю его на DEV, но я попытался запустить его, и он работает неправильно.

Enter-PSSession DEV
Invoke-Command -ComputerName DEV -FilePath "C:\PerformanceTest\profile-tracing-SoapUI.ps1"
Exit-PSSession
  1. До сих пор расследование

Вышеприведенное, кажется, вызывает скрипт, но я столкнулся с некоторыми странными проблемами. Во-первых, сценарий вызывается на компьютере, на котором я выполняю сценарий (мой для тестирования, TFS Build Server для автоматического развертывания), а не на DEV, как я ожидал. Вероятно, это мое неправильное понимание PowerShell и достаточно простое исправление - все это означает, что, если я не скопирую сценарий на сетевой компьютер, с которого я тестирую (мой собственный сервер или TFS Build Server), PowerShell просто исключит ошибку каталога, не найденную прежде чем даже нажать сценарий. Во-вторых, даже если у меня есть сценарий, установленный на компьютере, с которого я пытаюсь его запустить (который находит сценарий и запускает выполнение), он всегда попадает на вывод "Запуск трассировки...", за которым следуют некоторые Информация о vsperfcmd.

Starting the trace...
Microsoft (R) VSPerf Command Version 12.0.30723 x64
Copyright (C) Microsoft Corp. All rights reserved.


Global logging control
------------------------------------------------------------
Setting global profile state to OFF.

Он висит там, и я предполагаю, что вызов не полностью выполнен, поскольку я должен выйти из него. Я оставляю его включенным в течение значительного периода времени (более 20 минут, локально требуются секунды, чтобы пройти эту точку), но оно никогда не продолжалось до этой точки. Локальный запуск вывода скрипта в этом месте выглядит следующим образом:

Starting the trace...
Microsoft (R) VSPerf Command Version 12.0.30723 x64
Copyright (C) Microsoft Corp. All rights reserved.


Global logging control
------------------------------------------------------------
Setting global profile state to OFF.

Resetting IIS...
  1. Эта проблема

Это приводит меня к мысли, что проблема заключается в

& "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /start:Trace /output:$logLocation"\API.vsp" /cs /User:Everyone /globaloff

И действительно, когда я запустил сильно укороченный скрипт для проверки этой гипотезы, я смог проверить, что элемент управления никогда не возвращается оболочке после выполнения этой команды - ниже приведен скрипт, который я запустил для подтверждения этого И его вывода:

Enter-PSSession t80w103
Invoke-Command -ComputerName t80w103 -ScriptBlock{
    try{
        & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /start:Trace /output:$logLocation"\API.vsp" /cs /User:Everyone /globaloff
        Write-Host "No Error and Control Returned"
    }
    catch{
        Write-Host "Error!"
    }
    finally{
        Write-Output "Done.."
    }
}
Exit-PSSession

OUTPUT:
Microsoft (R) VSPerf Command Version 12.0.30723 x64
Copyright (C) Microsoft Corp. All rights reserved.


Global logging control
------------------------------------------------------------
Setting global profile state to OFF.

После этой команды ничего не печатается. Некоторое время я думал, что это должно означать, что что-то не так с приложением или сервером DEV, которые мешали ему правильно обращаться к vsperfcmd, но я проверил в диспетчере задач DEV и смог обнаружить, что VSPerfMon.exe действительно появляется в списке процессов, когда Я запускаю команду и останавливаюсь, когда вырываюсь из нее ctrl +. Таким образом, кажется, что команда работает, по крайней мере частично, она просто не возвращает управление, чтобы можно было выполнить следующую часть скрипта.

Итак, мой вопрос, как я могу заставить это работать? Почему не возвращается управление после выполнения команды запуска монитора профиля? И это лучший способ для профилирования производительности после автоматического развертывания сборки из TFS? (см. ниже для обновлений)

  1. Дополнительное расследование

Я продолжал исследовать это самостоятельно после публикации и узнал некоторые интересные вещи. Во-первых, я должен отметить, что версия powershell на сервере DEV устарела (чего я не осознавал), и поэтому я обновил ее до версии 3. Однажды я попытался запустить скрипт локально на powershellV3 в ISE-сборке powershell I понял, что он зависает точно так же, как когда я пытался запустить скрипт удаленно. Только когда я запускаю сценарий с помощью команды правого клика "запустить с powershell", сценарий работает, что, как я понимаю, вызывает powershellV1. Кажется, есть четкое различие в том, как разные версии powershell обрабатывают мой скрипт.

Пересмотренные вопросы: почему скрипт работает в powershellV1, но не V3? Почему V3 зависает после \start:trace, а V1 нет? И как мне получить сетевое выполнение для запуска сценария с использованием V1, когда сеансы PowerShell не были представлены до V2?

1 ответ

Решение

Ответ на пересмотренный вопрос позволил мне решить эту проблему. Оказывается (хотя я не знаю, ПОЧЕМУ, так как я не вижу исходный код), что VSPerfCMD.exe не возвращает управление оболочке в версиях Powershell после V1, когда он запускается. Это означает, что вызов

VSPerfCmd.exe /Start

должно быть сделано в отдельной оболочке, иначе скрипт будет зависать. Управление возвращается только после того, как другая оболочка вызовет команду выключения. Чтобы решить эту проблему, я использую следующий код для запуска профилировщика, а затем подожду, пока мой скрипт не запустится, прежде чем продолжить:

Write-Host "Starting the trace..."

#start:Trace - in tracing mode so we get timing data
#output - the output vsp file
#cs - cross session mode because we are profiling an IIS session
#user - give permissions to profile to everyone
#globaloff - start with capturing off so we don't get start up data. > is this the problem?

start powershell -ArgumentList "-ExecutionPolicy Bypass -command & 'C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe' /start:Trace /output:$logLocation\CatalogAPI.vsp /cs /User:Everyone /GlobalOff"

#wait for VSPerfCmd to start up
Write-Host "Waiting 15s for VSPerfCmd to start up..."
Start-Sleep -s 15

$counter = 0
while($counter -lt 6)
{
    $e = & "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /status
    if ($LASTEXITCODE -eq 0){
        Write-Host $e
        break       
    }

    if ($LASTEXITCODE -eq 1 -and $counter -lt 5){
        Write-Host "Still waiting. Give it 10s more ($counter)..."
        Start-Sleep -s 10
        $counter++
        continue
    }

    throw "VSPerfCmd Error: $e"                 
}

Использование приведенного выше кода вместо исходного запуска трассировки в исходном сценарии должно позволить вам запустить автоматическое профилирование производительности с помощью сценария powershell, но я не могу гарантировать результаты, поскольку сценарий немного изменился с тех пор, как я его опубликовал (из-за различных исследований и улучшение кода). Чтобы заставить это работать, просто используйте выше вместо

#enable capturing
Write-Host "Enabling capturing..."
& "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Performance Tools\x64\VSPerfCmd.exe" /globalon

tl; dr VSPerfCmd.exe / Start не возвращает управление оболочке, которая вызывала команду, до тех пор, пока служба профилирования не будет остановлена ​​в версиях powershell после powershell v1. Если вы хотите автоматизировать профилирование производительности, вам нужно запустить профилировщик производительности в другой оболочке, а затем дождаться, пока состояние профилировщика покажет, что он активен, прежде чем продолжить.

Другие вопросы по тегам