2016-03-26 2 views
3

В настоящее время я работаю над скриптом PowerShell, который будет использоваться в TeamCity как часть шага сборки. Сценарий должен:Производительность файлов чтения скриптов PowerShell слишком медленная

  • рекурсивно проверить все файлы с определенным расширением (.item) в папке,
  • читать третью строку каждого файла (который содержит GUID) и проверьте, есть ли дубликаты в этих строках,
  • войти путь к файлу, который содержит дубликат GUID и журнал самого GUID,
  • сделать TeamCity сборки потерпеть неудачу, если один или несколько дубликатов найдены

Я совершенно новый для ПОЛНОМОЧИЯ ад скрипты, но до сих пор я сделал что-то, что делает то, что я ожидаю, что это сделать:

Write-Host "Start checking for Unicorn serialization errors." 

$files = get-childitem "%system.teamcity.build.workingDir%\Sitecore\serialization" -recurse -include *.item | where {! $_.PSIsContainer} | % { $_.FullName } 
$arrayOfItemIds = @() 
$NrOfFiles = $files.Length 
[bool] $FoundDuplicates = 0 

Write-Host "There are $NrOfFiles Unicorn item files to check." 

foreach ($file in $files) 
{ 
    $thirdLineOfFile = (Get-Content $file)[2 .. 2] 

    if ($arrayOfItemIds -contains $thirdLineOfFile) 
    { 
     $FoundDuplicates = 1 
     $itemId = $thirdLineOfFile.Split(":")[1].Trim() 

     Write-Host "Duplicate item ID found!" 
     Write-Host "Item file path: $file" 
     Write-Host "Detected duplicate ID: $itemId" 
     Write-Host "-------------" 
     Write-Host "" 
    } 
    else 
    { 
     $arrayOfItemIds += $thirdLineOfFile 
    } 
} 

if ($foundDuplicates) 
{ 
    "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID's were detected in Sitecore serialised items. Check the build log to see which files and ID's are involved.']" 
    exit 1 
} 

Write-Host "End script checking for Unicorn serialization errors." 

Проблема в том, что это очень медленно! Папка, которая должна быть проверена этим скриптом, в настоящее время содержит более 14 000 файлов .item, и очень вероятно, что эта сумма будет только расти в будущем. Я понимаю, что открытие и чтение большого количества файлов - это обширная операция, но я не ожидал, что это займет примерно полчаса. Это слишком долго, потому что это означает, что время сборки для каждой (моментальной) сборки будет увеличено на полчаса, что неприемлемо. Я надеялся, что сценарий завершится через пару минут максимум.

Я не могу поверить, что для этого не существует более быстрого подхода .. так что любая помощь в этой области очень ценится!

Решение

Ну, я должен сказать, что все 3 ответов я получил до сих пор помогли мне в этом. Сначала я начал использовать классы .NET Framework, а затем использовал словарь, чтобы решить проблему растущего массива. Время, затрачиваемое на запуск моего собственного скрипта, составляло около 30 минут, а затем это заняло всего 2 минуты, используя классы .NET framework. После использования решения для словаря, он снизился всего на 6 или 7 секунд! Окончательный сценарий, который я использую:

Write-Host "Start checking for Unicorn serialization errors." 

[String[]] $allFilePaths = [System.IO.Directory]::GetFiles("%system.teamcity.build.workingDir%\Sitecore\serialization", "*.item", "AllDirectories") 
$IdsProcessed = New-Object 'system.collections.generic.dictionary[string,string]' 
[bool] $FoundDuplicates = 0 
$NrOfFiles = $allFilePaths.Length 

Write-Host "There are $NrOfFiles Unicorn item files to check." 
Write-Host "" 

foreach ($filePath in $allFilePaths) 
{ 
    [System.IO.StreamReader] $sr = [System.IO.File]::OpenText($filePath) 
    $unused1 = $sr.ReadLine() #read the first unused line 
    $unused2 = $sr.ReadLine() #read the second unused line 
    [string]$thirdLineOfFile = $sr.ReadLine() 
    $sr.Close() 

    if ($IdsProcessed.ContainsKey($thirdLineOfFile)) 
    { 
     $FoundDuplicates = 1 
     $itemId = $thirdLineOfFile.Split(":")[1].Trim() 
     $otherFileWithSameId = $IdsProcessed[$thirdLineOfFile] 

     Write-Host "---------------" 
     Write-Host "Duplicate item ID found!" 
     Write-Host "Detected duplicate ID: $itemId" 
     Write-Host "Item file path 1: $filePath" 
     Write-Host "Item file path 2: $otherFileWithSameId" 
     Write-Host "---------------" 
     Write-Host "" 
    } 
    else 
    { 
     $IdsProcessed.Add($thirdLineOfFile, $filePath) 
    } 
} 

if ($foundDuplicates) 
{ 
    "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID|'s were detected in Sitecore serialised items. Check the build log to see which files and ID|'s are involved.']" 
    exit 1 
} 

Write-Host "End script checking for Unicorn serialization errors. No duplicate ID's were found." 

Так что спасибо всем!

+0

Возможно, вы ограничены здесь IO-пропускной способностью. Таким образом, основное время (вероятно) используется при перетаскивании файлов с диска. Если это так (и подсчет конверта должен быть в состоянии подтвердить его), то самым простым способом ускорить его было бы перейти на более быстрое хранилище - например, SSD. –

+0

@Mike Wise: Возможно, это было, хотя полчаса даже для копирования 14000 файлов были бы чрезмерными на любой современной машине. Я думаю, что в этом случае файл IO не является проблемой вообще. Поскольку его код является «частью шага сборки», все файлы будут обрабатываться или создаваться непосредственно перед его запуском кода и, следовательно, все еще будут в кеше диска. –

ответ

4

Непонятно, что делает PowerShell, когда вы используете команды высокого уровня, такие как Get-ChildItem и Get-Content. Поэтому я бы более подробно рассказал об этом и напрямую использовал классы .NET Framework.

Получите пути к файлам в папке с помощью

[String[]] $files = [System.IO.Directory]::GetFiles($folderPath, "*.yourext") 

Затем, вместо того чтобы использовать Get-Content, откройте каждый файл и прочитать первые три строки. Как так:

[System.IO.StreamReader] $sr = [System.IO.File]::OpenText(path) 
[String]$line = $sr.ReadLine() 
while ($line -ne $null) 
{ 
    # do your thing, break when you know enough 
    # ... 
    [String]$line = $sr.ReadLine() 
} 
$sr.Close() 

я, возможно, сделал ошибку или два, я слишком ленив, встать и проверить это на ПК.

И вы можете подумать о перепроектировании вашей системы сборки, чтобы использовать меньше файлов. 14000 файлов и рост кажется ненужным. Если вы можете консолидировать некоторые данные в меньшем количестве файлов, это также может помочь в производительности.

Для проверки дубликатов указателей используйте словарь < Guid, String> с строкой, являющейся вашим именем файла. Затем вы можете сообщить, где находятся дубликаты, если найдете их.

+0

Мы используем этот скрипт в проекте Sitecore. Когда вы разрабатываете Sitecore, вы в основном создаете код C#, но некоторые части «разработки» должны выполняться в CMS (т. Е. В базе данных). Из-за этого наша база данных автоматически сериализуется инструментом Unicorn. Он хранит каждый «элемент» в CMS для сериализованного файла на диске. Таким образом, вся база данных также хранится в Git и легко распространяется среди других разработчиков. Он работает очень хорошо, но недостатком этого является огромное количество файлов ... но другого способа сделать это не так, поэтому перепроектирование не является вариантом. – Niles11

5

Попробуйте заменить Get-Content на [System.IO.File]::ReadLines. Если это все еще слишком медленно, используйте System.IO.StreamReader - это заставит вас написать немного больше кода, но позволит вам просто прочитать первые 3 строки.

1

Я думаю, что ваша проблема может быть вызвана вашим массивом и, вероятно, не является проблемой чтения файлов.

  1. Размер массива в PowerShell неизменен, поэтому каждый раз, когда вы добавляете элемент в массив, он создает новый массив и копирует все элементы.

  2. Ваш массив, как правило, НЕ содержит значение, которое ищет, и ему придется сравнивать $thirdLineOfFile с каждым элементом в растущем массиве.

Я использую .Net Словари для решения этой проблемы. (Или ArrayLists, когда я не делаю много поисков) MSDN Dictionary Reference

Примечание: PowerShell предоставляет команду под названием «Measure-Command», который вы можете использовать, чтобы определить, какая часть вашего скрипта на самом деле работает медленно. Я бы проверил время и время чтения файла, чтобы увеличить значения массива и поиска. В зависимости от размера файлов у вас могут быть проблемы с производительностью.

Вот ваш код, адаптированный для использования .Net Dictionary. Я переименовал вашу переменную, поскольку она больше не является массивом.

Write-Host "Start checking for Unicorn serialization errors." 

$files = get-childitem "%system.teamcity.build.workingDir%\Sitecore\serialization" -recurse -include *.item | where {! $_.PSIsContainer} | % { $_.FullName } 
#$arrayOfItemIds = @() 
$IdsProcessed = New-Object 'system.collections.generic.dictionary[string,string]' # A .Net Dictionary will be faster for inserts and lookups. 
$NrOfFiles = $files.Length 
[bool] $FoundDuplicates = 0 

Write-Host "There are $NrOfFiles Unicorn item files to check." 

foreach ($file in $files) 
{ 
    $thirdLineOfFile = (Get-Content -path $file -TotalCount 3)[2] # TotalCount param will let us pull in just the beginning of the file. 

    #if ($arrayOfItemIds -contains $thirdLineOfFile) 
    if($IdsProcessed.ContainsKey($thirdLineOfFile)) 
    { 
     $FoundDuplicates = 1 
     $itemId = $thirdLineOfFile.Split(":")[1].Trim() 

     Write-Host "Duplicate item ID found!" 
     Write-Host "Item file path: $file" 
     Write-Host "Detected duplicate ID: $itemId" 
     Write-Host "-------------" 
     Write-Host "" 
    } 
    else 
    { 
     #$arrayOfItemIds += $thirdLineOfFile 
     $IdsProcessed.Add($thirdLineOfFile,$null) 
    } 
} 

if ($foundDuplicates) 
{ 
    "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID's were detected in Sitecore serialised items. Check the build log to see which files and ID's are involved.']" 
    exit 1 
} 

Write-Host "End script checking for Unicorn serialization errors." 
Смежные вопросы