From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../VMM/testcase/tstPDMAsyncCompletionStress.cpp | 657 +++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 src/VBox/VMM/testcase/tstPDMAsyncCompletionStress.cpp (limited to 'src/VBox/VMM/testcase/tstPDMAsyncCompletionStress.cpp') diff --git a/src/VBox/VMM/testcase/tstPDMAsyncCompletionStress.cpp b/src/VBox/VMM/testcase/tstPDMAsyncCompletionStress.cpp new file mode 100644 index 00000000..8ccaaecf --- /dev/null +++ b/src/VBox/VMM/testcase/tstPDMAsyncCompletionStress.cpp @@ -0,0 +1,657 @@ +/* $Id: tstPDMAsyncCompletionStress.cpp $ */ +/** @file + * PDM Asynchronous Completion Stresstest. + * + * This testcase is for stress testing the async completion interface. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_PDM_ASYNC_COMPLETION + +#include "VMInternal.h" /* UVM */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TESTCASE "tstPDMAsyncCompletionStress" + +#if 0 +/** Number of simultaneous open endpoints for reading and writing. */ +#define NR_OPEN_ENDPOINTS 10 +/** Test pattern size. */ +#define TEST_PATTERN_SIZE (100*_1M) +/** Minimum file size. */ +#define FILE_SIZE_MIN (100 * _1M) +/** Maximum file size. */ +#define FILE_SIZE_MAX (10000UL * _1M) +/** Minimum segment size. */ +#define SEGMENT_SIZE_MIN (512) +/** Maximum segment size. */ +#define SEGMENT_SIZE_MAX (TEST_PATTERN_SIZE) +/** Maximum number of active tasks. */ +#define TASK_ACTIVE_MAX (1024) +/** Maximum size of a transfer. */ +#define TASK_TRANSFER_SIZE_MAX (10*_1M) +#else +/** Number of simultaneous open endpoints for reading and writing. */ +#define NR_OPEN_ENDPOINTS 5 +/** Test pattern size. */ +#define TEST_PATTERN_SIZE (10*_1M) +/** Minimum file size. */ +#define FILE_SIZE_MIN (100 * _1M) +/** Maximum file size. */ +#define FILE_SIZE_MAX (1000UL * _1M) +/** Minimum segment size. */ +#define SEGMENT_SIZE_MIN (512) +/** Maximum segment size. */ +#define SEGMENT_SIZE_MAX (TEST_PATTERN_SIZE) +/** Maximum number of active tasks. */ +#define TASK_ACTIVE_MAX (1) +/** Maximum size of a transfer. */ +#define TASK_TRANSFER_SIZE_MAX (_1M) +#endif + +/** + * Structure defining a file segment. + */ +typedef struct PDMACTESTFILESEG +{ + /** Start offset in the file. */ + RTFOFF off; + /** Size of the segment. */ + size_t cbSegment; + /** Pointer to the start of the data in the test pattern used for the segment. */ + uint8_t *pbData; +} PDMACTESTFILESEG, *PPDMACTESTFILESEG; + +/** + * Structure defining a I/O task. + */ +typedef struct PDMACTESTFILETASK +{ + /** Flag whether the task is currently active. */ + bool fActive; + /** Flag whether this is a write. */ + bool fWrite; + /** Start offset. */ + RTFOFF off; + /** Data segment */ + RTSGSEG DataSeg; + /** Task handle. */ + PPDMASYNCCOMPLETIONTASK hTask; +} PDMACTESTFILETASK, *PPDMACTESTFILETASK; + +/** + * Structure defining a test file. + */ +typedef struct PDMACTESTFILE +{ + /** The PDM async completion endpoint handle. */ + PPDMASYNCCOMPLETIONENDPOINT hEndpoint; + /** Template used for this file. */ + PPDMASYNCCOMPLETIONTEMPLATE pTemplate; + /** Maximum size of the file. */ + uint64_t cbFileMax; + /** Current size of the file. */ + uint64_t cbFileCurr; + /** Size of a file segment. */ + size_t cbFileSegment; + /** Maximum number of segments. */ + size_t cSegments; + /** Pointer to the array describing how the file is assembled + * of the test pattern. Used for comparing read data to ensure + * that no corruption occurred. + */ + PPDMACTESTFILESEG paSegs; + /** Maximum number of active tasks for this endpoint. */ + uint32_t cTasksActiveMax; + /** Number of current active tasks. */ + volatile uint32_t cTasksActiveCurr; + /** Pointer to the array of task. */ + PPDMACTESTFILETASK paTasks; + /** I/O thread handle. */ + PPDMTHREAD hThread; + /** Flag whether the thread should terminate. */ + bool fRunning; +} PDMACTESTFILE, *PPDMACTESTFILE; + +/** Buffer storing the random test pattern. */ +uint8_t *g_pbTestPattern = NULL; +/** Size of the test pattern. */ +size_t g_cbTestPattern; +/** Array holding test files. */ +PDMACTESTFILE g_aTestFiles[NR_OPEN_ENDPOINTS]; + +static DECLCALLBACK(void) tstPDMACStressTestFileTaskCompleted(PVM pVM, void *pvUser, void *pvUser2, int rcReq); + +static void tstPDMACStressTestFileVerify(PPDMACTESTFILE pTestFile, PPDMACTESTFILETASK pTestTask) +{ + size_t cbLeft = pTestTask->DataSeg.cbSeg; + RTFOFF off = pTestTask->off; + uint8_t *pbBuf = (uint8_t *)pTestTask->DataSeg.pvSeg; + + while (cbLeft) + { + size_t cbCompare; + size_t iSeg = off / pTestFile->cbFileSegment; + PPDMACTESTFILESEG pSeg = &pTestFile->paSegs[iSeg]; + uint8_t *pbTestPattern; + unsigned offSeg = off - pSeg->off; + + cbCompare = RT_MIN(cbLeft, pSeg->cbSegment - offSeg); + pbTestPattern = pSeg->pbData + offSeg; + + if (memcmp(pbBuf, pbTestPattern, cbCompare)) + { + unsigned idx = 0; + + while ( (idx < cbCompare) + && (pbBuf[idx] == pbTestPattern[idx])) + idx++; + + RTMsgError("Unexpected data for off=%RTfoff size=%u\n" + "Expected %c got %c\n", + pTestTask->off + idx, pTestTask->DataSeg.cbSeg, + pbTestPattern[idx], pbBuf[idx]); + RTAssertDebugBreak(); + } + + pbBuf += cbCompare; + off += cbCompare; + cbLeft -= cbCompare; + } +} + +static void tstPDMACStressTestFileFillBuffer(PPDMACTESTFILE pTestFile, PPDMACTESTFILETASK pTestTask) +{ + uint8_t *pbBuf = (uint8_t *)pTestTask->DataSeg.pvSeg; + size_t cbLeft = pTestTask->DataSeg.cbSeg; + RTFOFF off = pTestTask->off; + + Assert(pTestTask->fWrite && pTestTask->fActive); + + while (cbLeft) + { + size_t cbFill; + size_t iSeg = off / pTestFile->cbFileSegment; + PPDMACTESTFILESEG pSeg = &pTestFile->paSegs[iSeg]; + uint8_t *pbTestPattern; + unsigned offSeg = off - pSeg->off; + + cbFill = RT_MIN(cbLeft, pSeg->cbSegment - offSeg); + pbTestPattern = pSeg->pbData + offSeg; + + memcpy(pbBuf, pbTestPattern, cbFill); + + pbBuf += cbFill; + off += cbFill; + cbLeft -= cbFill; + } +} + +static int tstPDMACStressTestFileWrite(PPDMACTESTFILE pTestFile, PPDMACTESTFILETASK pTestTask) +{ + int rc = VINF_SUCCESS; + + Assert(!pTestTask->fActive); + + pTestTask->fActive = true; + pTestTask->fWrite = true; + pTestTask->DataSeg.cbSeg = RTRandU32Ex(512, TASK_TRANSFER_SIZE_MAX) & ~511; + + uint64_t offMax; + + /* Did we reached the maximum file size */ + if (pTestFile->cbFileCurr < pTestFile->cbFileMax) + { + offMax = (pTestFile->cbFileMax - pTestFile->cbFileCurr) < pTestTask->DataSeg.cbSeg + ? pTestFile->cbFileMax - pTestTask->DataSeg.cbSeg + : pTestFile->cbFileCurr; + } + else + offMax = pTestFile->cbFileMax - pTestTask->DataSeg.cbSeg; + + uint64_t offMin; + + /* + * If we reached the maximum file size write in the whole file + * otherwise we will enforce the range for random offsets to let it grow + * more quickly. + */ + if (pTestFile->cbFileCurr == pTestFile->cbFileMax) + offMin = 0; + else + offMin = RT_MIN(pTestFile->cbFileCurr, offMax); + + + pTestTask->off = RTRandU64Ex(offMin, offMax) & ~511; + + /* Set new file size of required */ + if ((uint64_t)pTestTask->off + pTestTask->DataSeg.cbSeg > pTestFile->cbFileCurr) + pTestFile->cbFileCurr = pTestTask->off + pTestTask->DataSeg.cbSeg; + + AssertMsg(pTestFile->cbFileCurr <= pTestFile->cbFileMax, + ("Current file size (%llu) exceeds final size (%llu)\n", + pTestFile->cbFileCurr, pTestFile->cbFileMax)); + + /* Allocate data buffer. */ + pTestTask->DataSeg.pvSeg = RTMemAlloc(pTestTask->DataSeg.cbSeg); + if (!pTestTask->DataSeg.pvSeg) + return VERR_NO_MEMORY; + + /* Fill data into buffer. */ + tstPDMACStressTestFileFillBuffer(pTestFile, pTestTask); + + /* Engage */ + rc = PDMR3AsyncCompletionEpWrite(pTestFile->hEndpoint, pTestTask->off, + &pTestTask->DataSeg, 1, + pTestTask->DataSeg.cbSeg, + pTestTask, + &pTestTask->hTask); + + return rc; +} + +static int tstPDMACStressTestFileRead(PPDMACTESTFILE pTestFile, PPDMACTESTFILETASK pTestTask) +{ + int rc = VINF_SUCCESS; + + Assert(!pTestTask->fActive); + + pTestTask->fActive = true; + pTestTask->fWrite = false; + pTestTask->DataSeg.cbSeg = RTRandU32Ex(1, RT_MIN(pTestFile->cbFileCurr, TASK_TRANSFER_SIZE_MAX)); + + AssertMsg(pTestFile->cbFileCurr >= pTestTask->DataSeg.cbSeg, ("Impossible\n")); + pTestTask->off = RTRandU64Ex(0, pTestFile->cbFileCurr - pTestTask->DataSeg.cbSeg); + + /* Allocate data buffer. */ + pTestTask->DataSeg.pvSeg = RTMemAlloc(pTestTask->DataSeg.cbSeg); + if (!pTestTask->DataSeg.pvSeg) + return VERR_NO_MEMORY; + + /* Engage */ + rc = PDMR3AsyncCompletionEpRead(pTestFile->hEndpoint, pTestTask->off, + &pTestTask->DataSeg, 1, + pTestTask->DataSeg.cbSeg, + pTestTask, + &pTestTask->hTask); + + return rc; +} + +/** + * Returns true with the given chance in percent. + * + * @returns true or false + * @param iPercentage The percentage of the chance to return true. + */ +static bool tstPDMACTestIsTrue(int iPercentage) +{ + int uRnd = RTRandU32Ex(0, 100); + + return (uRnd <= iPercentage); /* This should be enough for our purpose */ +} + +static DECLCALLBACK(int) tstPDMACTestFileThread(PVM pVM, PPDMTHREAD pThread) +{ + PPDMACTESTFILE pTestFile = (PPDMACTESTFILE)pThread->pvUser; + int iWriteChance = 100; /* Chance to get a write task in percent. */ + uint32_t cTasksStarted = 0; + int rc = VINF_SUCCESS; + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pTestFile->fRunning) + { + unsigned iTaskCurr = 0; + + + /* Fill all tasks */ + while ( (pTestFile->cTasksActiveCurr < pTestFile->cTasksActiveMax) + && (iTaskCurr < pTestFile->cTasksActiveMax)) + { + PPDMACTESTFILETASK pTask = &pTestFile->paTasks[iTaskCurr]; + + if (!pTask->fActive) + { + /* Read or write task? */ + bool fWrite = tstPDMACTestIsTrue(iWriteChance); + + ASMAtomicIncU32(&pTestFile->cTasksActiveCurr); + + if (fWrite) + rc = tstPDMACStressTestFileWrite(pTestFile, pTask); + else + rc = tstPDMACStressTestFileRead(pTestFile, pTask); + + if (rc != VINF_AIO_TASK_PENDING) + tstPDMACStressTestFileTaskCompleted(pVM, pTask, pTestFile, rc); + + cTasksStarted++; + } + + iTaskCurr++; + } + + /* + * Recalc write chance. The bigger the file the lower the chance to have a write. + * The minimum chance is 33 percent. + */ + iWriteChance = 100 - (int)((100.0 / (double)pTestFile->cbFileMax) * (double)pTestFile->cbFileCurr); + iWriteChance = RT_MAX(33, iWriteChance); + + /* Wait a random amount of time. (1ms - 100ms) */ + RTThreadSleep(RTRandU32Ex(1, 100)); + } + + /* Wait for the rest to complete. */ + while (pTestFile->cTasksActiveCurr) + RTThreadSleep(250); + + RTPrintf("Thread exiting: processed %u tasks\n", cTasksStarted); + return rc; +} + +static DECLCALLBACK(void) tstPDMACStressTestFileTaskCompleted(PVM pVM, void *pvUser, void *pvUser2, int rcReq) +{ + PPDMACTESTFILE pTestFile = (PPDMACTESTFILE)pvUser2; + PPDMACTESTFILETASK pTestTask = (PPDMACTESTFILETASK)pvUser; + NOREF(pVM); NOREF(rcReq); + + if (pTestTask->fWrite) + { + /** @todo Do something sensible here. */ + } + else + { + tstPDMACStressTestFileVerify(pTestFile, pTestTask); /* Will assert if it fails */ + } + + RTMemFree(pTestTask->DataSeg.pvSeg); + pTestTask->fActive = false; + AssertMsg(pTestFile->cTasksActiveCurr > 0, ("Trying to complete a non active task\n")); + ASMAtomicDecU32(&pTestFile->cTasksActiveCurr); +} + +/** + * Sets up a test file creating the I/O thread. + * + * @returns VBox status code. + * @param pVM Pointer to the shared VM instance structure. + * @param pTestFile Pointer to the uninitialized test file structure. + * @param iTestId Unique test id. + */ +static int tstPDMACStressTestFileOpen(PVM pVM, PPDMACTESTFILE pTestFile, unsigned iTestId) +{ + int rc = VERR_NO_MEMORY; + + /* Size is a multiple of 512 */ + pTestFile->cbFileMax = RTRandU64Ex(FILE_SIZE_MIN, FILE_SIZE_MAX) & ~(511UL); + pTestFile->cbFileCurr = 0; + pTestFile->cbFileSegment = RTRandU32Ex(SEGMENT_SIZE_MIN, RT_MIN(pTestFile->cbFileMax, SEGMENT_SIZE_MAX)) & ~((size_t)511); + + Assert(pTestFile->cbFileMax >= pTestFile->cbFileSegment); + + /* Set up the segments array. */ + pTestFile->cSegments = pTestFile->cbFileMax / pTestFile->cbFileSegment; + pTestFile->cSegments += ((pTestFile->cbFileMax % pTestFile->cbFileSegment) > 0) ? 1 : 0; + + pTestFile->paSegs = (PPDMACTESTFILESEG)RTMemAllocZ(pTestFile->cSegments * sizeof(PDMACTESTFILESEG)); + if (pTestFile->paSegs) + { + /* Init the segments */ + for (unsigned i = 0; i < pTestFile->cSegments; i++) + { + PPDMACTESTFILESEG pSeg = &pTestFile->paSegs[i]; + + pSeg->off = (RTFOFF)i * pTestFile->cbFileSegment; + pSeg->cbSegment = pTestFile->cbFileSegment; + + /* Let the buffer point to a random position in the test pattern. */ + uint32_t offTestPattern = RTRandU64Ex(0, g_cbTestPattern - pSeg->cbSegment); + + pSeg->pbData = g_pbTestPattern + offTestPattern; + } + + /* Init task array. */ + pTestFile->cTasksActiveMax = RTRandU32Ex(1, TASK_ACTIVE_MAX); + pTestFile->paTasks = (PPDMACTESTFILETASK)RTMemAllocZ(pTestFile->cTasksActiveMax * sizeof(PDMACTESTFILETASK)); + if (pTestFile->paTasks) + { + /* Create the template */ + char szDesc[256]; + + RTStrPrintf(szDesc, sizeof(szDesc), "Template-%d", iTestId); + rc = PDMR3AsyncCompletionTemplateCreateInternal(pVM, &pTestFile->pTemplate, tstPDMACStressTestFileTaskCompleted, + pTestFile, szDesc); + if (RT_SUCCESS(rc)) + { + /* Open the endpoint now. Because async completion endpoints cannot create files we have to do it before. */ + char szFile[RTPATH_MAX]; + + RTStrPrintf(szFile, sizeof(szFile), "tstPDMAsyncCompletionStress-%d.tmp", iTestId); + + RTFILE FileTmp; + rc = RTFileOpen(&FileTmp, szFile, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + RTFileClose(FileTmp); + + rc = PDMR3AsyncCompletionEpCreateForFile(&pTestFile->hEndpoint, szFile, 0, pTestFile->pTemplate); + if (RT_SUCCESS(rc)) + { + char szThreadDesc[256]; + + pTestFile->fRunning = true; + + /* Create the thread creating the I/O for the given file. */ + RTStrPrintf(szThreadDesc, sizeof(szThreadDesc), "PDMACThread-%d", iTestId); + rc = PDMR3ThreadCreate(pVM, &pTestFile->hThread, pTestFile, tstPDMACTestFileThread, + NULL, 0, RTTHREADTYPE_IO, szThreadDesc); + if (RT_SUCCESS(rc)) + { + rc = PDMR3ThreadResume(pTestFile->hThread); + AssertRC(rc); + + RTPrintf(TESTCASE ": Created test file %s cbFileMax=%llu cbFileSegment=%u cSegments=%u cTasksActiveMax=%u\n", + szFile, pTestFile->cbFileMax, pTestFile->cbFileSegment, pTestFile->cSegments, pTestFile->cTasksActiveMax); + return VINF_SUCCESS; + } + + PDMR3AsyncCompletionEpClose(pTestFile->hEndpoint); + } + + RTFileDelete(szFile); + } + + PDMR3AsyncCompletionTemplateDestroy(pTestFile->pTemplate); + } + + RTMemFree(pTestFile->paTasks); + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pTestFile->paSegs); + } + else + rc = VERR_NO_MEMORY; + + RTPrintf(TESTCASE ": Opening test file with id %d failed rc=%Rrc\n", iTestId, rc); + + return rc; +} + +/** + * Closes a test file. + * + * @param pTestFile Pointer to the test file. + */ +static void tstPDMACStressTestFileClose(PPDMACTESTFILE pTestFile) +{ + int rcThread; + int rc; + + RTPrintf("Terminating I/O thread, please wait...\n"); + + /* Let the thread know that it should terminate. */ + pTestFile->fRunning = false; + + /* Wait for the thread to terminate. */ + rc = PDMR3ThreadDestroy(pTestFile->hThread, &rcThread); + + RTPrintf("Thread terminated with status code rc=%Rrc\n", rcThread); + + /* Free resources */ + RTMemFree(pTestFile->paTasks); + RTMemFree(pTestFile->paSegs); + PDMR3AsyncCompletionEpClose(pTestFile->hEndpoint); + PDMR3AsyncCompletionTemplateDestroy(pTestFile->pTemplate); +} + +/** + * Inits the test pattern. + * + * @returns VBox status code. + */ +static int tstPDMACStressTestPatternInit(void) +{ + RTPrintf(TESTCASE ": Creating test pattern. Please wait...\n"); + g_cbTestPattern = TEST_PATTERN_SIZE; + g_pbTestPattern = (uint8_t *)RTMemAlloc(g_cbTestPattern); + if (!g_pbTestPattern) + return VERR_NO_MEMORY; + + RTRandBytes(g_pbTestPattern, g_cbTestPattern); + return VINF_SUCCESS; +} + +static void tstPDMACStressTestPatternDestroy(void) +{ + RTPrintf(TESTCASE ": Destroying test pattern\n"); + RTMemFree(g_pbTestPattern); +} + +/** + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) +{ + RT_NOREF1(envp); + int rcRet = 0; /* error count */ + + RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + + PVM pVM; + PUVM pUVM; + int rc = VMR3Create(1 /*cCpus*/, NULL, 0 /*fFlags*/, NULL, NULL, NULL, NULL, &pVM, &pUVM); + if (RT_SUCCESS(rc)) + { + /* + * Little hack to avoid the VM_ASSERT_EMT assertion. + */ + RTTlsSet(pVM->pUVM->vm.s.idxTLS, &pVM->pUVM->aCpus[0]); + pVM->pUVM->aCpus[0].pUVM = pVM->pUVM; + pVM->pUVM->aCpus[0].vm.s.NativeThreadEMT = RTThreadNativeSelf(); + + rc = tstPDMACStressTestPatternInit(); + if (RT_SUCCESS(rc)) + { + unsigned cFilesOpened = 0; + + /* Open the endpoints. */ + for (cFilesOpened = 0; cFilesOpened < NR_OPEN_ENDPOINTS; cFilesOpened++) + { + rc = tstPDMACStressTestFileOpen(pVM, &g_aTestFiles[cFilesOpened], cFilesOpened); + if (RT_FAILURE(rc)) + break; + } + + if (RT_SUCCESS(rc)) + { + /* Tests are running now. */ + RTPrintf(TESTCASE ": Successfully opened all files. Running tests forever now or until an error is hit :)\n"); + RTThreadSleep(RT_INDEFINITE_WAIT); + } + + /* Close opened endpoints. */ + for (unsigned i = 0; i < cFilesOpened; i++) + tstPDMACStressTestFileClose(&g_aTestFiles[i]); + + tstPDMACStressTestPatternDestroy(); + } + else + { + RTPrintf(TESTCASE ": failed to init test pattern!! rc=%Rrc\n", rc); + rcRet++; + } + + rc = VMR3Destroy(pUVM); + AssertMsg(rc == VINF_SUCCESS, ("%s: Destroying VM failed rc=%Rrc!!\n", __FUNCTION__, rc)); + } + else + { + RTPrintf(TESTCASE ": failed to create VM!! rc=%Rrc\n", rc); + rcRet++; + } + + return rcRet; +} + + +#if !defined(VBOX_WITH_HARDENING) || !defined(RT_OS_WINDOWS) +/** + * Main entry point. + */ +int main(int argc, char **argv, char **envp) +{ + return TrustedMain(argc, argv, envp); +} +#endif + -- cgit v1.2.3