rocksdb/build_tools/run_ci_db_test.ps1
Dmitri Smirnov 3277d172ba Improve concurrency when running tests
PowerShell seems to have a hard time when a flood of async tasks is
  scheduled at the same time.
  I speculated that WaitForMultipleObjects() in Windows can only take up
  to 64 process handles and if you want to handle more than you should write
  some additional code which can be sub-optimal. I.e to implement Wait-Job -Any.
  I decided to test that suggestion and introduced a $Concurrency parameter with a default value of 62.
  So in the new version the script fires up up to $Concurrency value
  and wait for anything to complete before starting any more processes.
  This improved matters greatly. Individual tests against ramdrive now
  run in 8 minutes and all of the 200+ db_tests run in 9 minutes with concurrency
  values of 8-16. About 48 is required to load a CPU on my box running against HD
  but that does not improve running times much.

  Other changes include respect -EnableJE for the individual test exes.
  Enforce exclusions for the individual tests.
2015-11-05 14:03:41 -08:00

273 lines
7.4 KiB
PowerShell

# This script enables you running RocksDB tests by running
# All the tests in paralell and utilizing all the cores
# For db_test the script first lists and parses the tests
# and then fires them up in parallel using async PS Job functionality
# Run the script from the enlistment
Param(
[switch]$EnableJE = $false, # Use je executable
[string]$WorkFolder = "", # Direct tests to use that folder
[int]$Limit = -1, # -1 means run all otherwise limit for testing purposes
[string]$Exclude = "", # Expect a comma separated list, no spaces
[string]$Run = "db_test", # Run db_test|tests
# Number of async tasks that would run concurrently. Recommend a number below 64.
# However, CPU utlization really depends on the storage media. Recommend ram based disk.
[int]$Concurrency = 62
)
# Folders and commands must be fullpath to run assuming
# the current folder is at the root of the git enlistment
Get-Date
# If running under Appveyor assume that root
[string]$Appveyor = $Env:APPVEYOR_BUILD_FOLDER
if($Appveyor -ne "") {
$RootFolder = $Appveyor
} else {
$RootFolder = $PSScriptRoot -replace '\\build_tools', ''
}
$LogFolder = -Join($RootFolder, "\db_logs\")
$BinariesFolder = -Join($RootFolder, "\build\Debug\")
if($WorkFolder -eq "") {
# If TEST_TMPDIR is set use it
[string]$var = $Env:TEST_TMPDIR
if($var -eq "") {
$WorkFolder = -Join($RootFolder, "\db_tests\")
$Env:TEST_TMPDIR = $WorkFolder
} else {
$WorkFolder = $var
}
} else {
# Override from a command line
$Env:TEST_TMPDIR = $WorkFolder
}
# Use JEMALLOC executables
if($EnableJE) {
$db_test = -Join ($BinariesFolder, "db_test_je.exe")
} else {
$db_test = -Join ($BinariesFolder, "db_test.exe")
}
Write-Output "Root: $RootFolder, WorkFolder: $WorkFolder"
Write-Output "Binaries: $BinariesFolder exe: $db_test"
#Exclusions that we do not want to run
$ExcludeTests = New-Object System.Collections.Generic.HashSet[string]
if($Exclude -ne "") {
Write-Host "Exclude: $Exclude"
$l = $Exclude -split ','
ForEach($t in $l) { $ExcludeTests.Add($t) | Out-Null }
}
# Create test directories in the current folder
md -Path $WorkFolder -ErrorAction Ignore | Out-Null
md -Path $LogFolder -ErrorAction Ignore | Out-Null
# Extract the names of its tests by running db_test with --gtest_list_tests.
# This filter removes the "#"-introduced comments, and expands to
# fully-qualified names by changing input like this:
#
# DBTest.
# Empty
# WriteEmptyBatch
# MultiThreaded/MultiThreadedDBTest.
# MultiThreaded/0 # GetParam() = 0
# MultiThreaded/1 # GetParam() = 1
#
# into this:
#
# DBTest.Empty
# DBTest.WriteEmptyBatch
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
# Output into the parameter in a form TestName -> Log File Name
function Normalize-DbTests($HashTable) {
$Tests = @()
# Run db_test to get a list of tests and store it into $a array
&$db_test --gtest_list_tests | tee -Variable Tests | Out-Null
# Current group
$Group=""
ForEach( $l in $Tests) {
# Trailing dot is a test group
if( $l -match "\.$") {
$Group = $l
} else {
# Otherwise it is a test name, remove leading space
$test = $l -replace '^\s+',''
# remove trailing comment if any and create a log name
$test = $test -replace '\s+\#.*',''
$test = "$Group$test"
if($ExcludeTests.Contains($test)) {
continue
}
$test_log = $test -replace '[\./]','_'
$test_log += ".log"
# Add to a hashtable
$HashTable.Add($test, $test_log);
}
}
}
# The function scans build\Debug folder to discover
# Test executables. It then populates a table with
# Test executable name -> Log file
function Discover-TestBinaries($HashTable) {
$Exclusions = @("db_test*", "db_sanity_test*")
if($EnableJE) {
$p = -join ($BinariesFolder, "*_test_je.exe")
} else {
$p = -join ($BinariesFolder, "*_test.exe")
}
dir -Path $p -Exclude $Exclusions | ForEach-Object {
$t = ($_.Name) -replace '.exe$', ''
if($ExcludeTests.Contains($t)) {
continue
}
$test_log = -join ($t, ".log")
$HashTable.Add($t, $test_log)
}
}
$TestToLog = [ordered]@{}
if($Run -ceq "db_test") {
Normalize-DbTests -HashTable $TestToLog
} elseif($Run -ceq "tests") {
Discover-TestBinaries -HashTable $TestToLog
} else {
Write-Warning "Invalid -Run option value"
exit 2
}
Write-Host "Attempting to start: " ($TestToLog.Count) " tests"
# Invoke a test with a filter and redirect all output
$InvokeTestCase = {
param($exe, $test, $log);
&$exe --gtest_filter=$test > $log 2>&1
}
# Invoke all tests and redirect output
$InvokeTestAsync = {
param($exe, $log)
&$exe > $log 2>&1
}
$jobs = @()
$JobToLog = @{}
# Test limiting factor here
$count = 0
[bool]$success = $true;
# Wait for all to finish and get the results
while(($JobToLog.Count -gt 0) -or
($TestToLog.Count -gt 0)) {
# Make sure we have maximum concurrent jobs running if anything
# and the $Limit either not set or allows to proceed
while(($JobToLog.Count -lt $Concurrency) -and
(($TestToLog.Count -gt 0) -and
(($Limit -lt 0) -or ($count -lt $Limit)))) {
# We only need the first key
foreach($key in $TestToLog.keys) {
$k = $key
break
}
Write-Host "Starting $k"
$log_path = -join ($LogFolder, ($TestToLog.$k))
if($Run -ceq "db_test") {
$job = Start-Job -Name $k -ScriptBlock $InvokeTestCase -ArgumentList @($db_test,$k,$log_path)
} else {
[string]$Exe = -Join ($BinariesFolder, $k)
$job = Start-Job -Name $k -ScriptBlock $InvokeTestAsync -ArgumentList @($exe,$log_path)
}
$JobToLog.Add($job, $log_path)
$TestToLog.Remove($k)
++$count
}
if($JobToLog.Count -lt 1) {
break
}
$jobs = @()
foreach($k in $JobToLog.Keys) { $jobs += $k }
$completed = Wait-Job -Job $jobs -Any
$log = $JobToLog[$completed]
$JobToLog.Remove($completed)
$message = -join @($completed.Name, " State: ", ($completed.State))
$log_content = @(Get-Content $log)
if($completed.State -ne "Completed") {
$success = $false
Write-Warning $message
$log_content | Write-Warning
} else {
# Scan the log. If we find PASSED and no occurence of FAILED
# then it is a success
[bool]$pass_found = $false
ForEach($l in $log_content) {
if(($l -match "^\[\s+FAILED") -or
($l -match "Assertion failed:")) {
$pass_found = $false
break
}
if(($l -match "^\[\s+PASSED") -or
($l -match " : PASSED$") -or
($l -match "^PASSED") -or
($l -match "Passed all tests!") ) {
$pass_found = $true
}
}
if(!$pass_found) {
$success = $false;
Write-Warning $message
$log_content | Write-Warning
} else {
Write-Host $message
}
}
# Remove cached job info from the system
# Should be no output
Receive-Job -Job $completed | Out-Null
}
Get-Date
if(!$success) {
# This does not succeed killing off jobs quick
# So we simply exit
# Remove-Job -Job $jobs -Force
# indicate failure using this exit code
exit 12345
}