/* * AsyncFileRead.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "fdbserver/workloads/workloads.actor.h" #include "flow/ActorCollection.h" #include "flow/SystemMonitor.h" #include "flow/IAsyncFile.h" #include "fdbserver/workloads/AsyncFile.actor.h" #include "flow/DeterministicRandom.h" #include "flow/actorcompiler.h" // This must be the last #include. static const double ROLL_TIME = 5.0; struct IOLog { struct ProcessLog { double startTime; double lastTime; double sumSq, sum, max, count; bool logLatency; // when false, log times and compute elapsed, else, interpret the log'd time as a latency ProcessLog() : logLatency(false){}; virtual void reset() { startTime = now(); lastTime = startTime; sumSq = 0.0; sum = 0.0; max = 0.0; count = 0.0; } void log(double time) { count++; auto l = logLatency ? time : (time - lastTime); // see logLatency comment above sum += l; sumSq += l * l; max = std::max(max, l); lastTime = time; } void dumpMetrics(std::string name) { double elapsed = now() - startTime; TraceEvent("ProcessLog") .detail("Name", name) .detail("Hz", count / elapsed) .detail("Latency", sumSq / elapsed / 2.0) .detail("AvgLatency", sum / count) .detail("MaxLatency", max) .detail("StartTime", startTime) .detail("Elapsed", elapsed); } }; double lastRoll; ProcessLog issue, completion, duration; ProcessLog issueR, completionR, durationR; ProcessLog issueW, completionW, durationW; std::vector> logs; IOLog() { logs.emplace_back("issue", &issue); logs.emplace_back("completion", &completion); logs.emplace_back("duration", &duration); logs.emplace_back("issueR", &issueR); logs.emplace_back("completionR", &completionR); logs.emplace_back("durationR", &durationR); logs.emplace_back("issueW", &issueW); logs.emplace_back("completionW", &completionW); logs.emplace_back("durationW", &durationW); duration.logLatency = true; durationR.logLatency = true; durationW.logLatency = true; lastRoll = now(); for (int i = 0; i < logs.size(); i++) logs[i].second->reset(); } ~IOLog() { roll(); } void roll() { for (int i = 0; i < logs.size(); i++) { logs[i].second->dumpMetrics(logs[i].first); logs[i].second->reset(); } } void checkRoll(double time) { if (time - lastRoll > ROLL_TIME) { roll(); lastRoll = time; } } void logIOIssue(bool isWrite, double issueTime) { issue.log(issueTime); if (isWrite) issueW.log(issueTime); else issueR.log(issueTime); checkRoll(issueTime); } void logIOCompletion(bool isWrite, double start, double end) { completion.log(end); if (isWrite) completionW.log(end); else completionR.log(end); auto elapsed = end - start; duration.log(elapsed); if (isWrite) durationW.log(elapsed); else durationR.log(elapsed); checkRoll(end); } }; struct AsyncFileReadWorkload : public AsyncFileWorkload { // Buffers used to store what is being read or written std::vector> readBuffers; // The futures for the asynchronous read operations std::vector> readFutures; // Number of reads to perform in parallel. Read tests are performed only if this is greater than zero int numParallelReads; // The number of bytes read in each call of read int readSize; // Whether or not I/O should be performed sequentially bool sequential; bool randomData; // if true, randomize the data in writes bool unbatched; // If true, issue reads continuously instead of waiting for all numParallelReads reads to complete double writeFraction; double fixedRate; double averageCpuUtilization; PerfIntCounter bytesRead; IOLog* ioLog; RandomByteGenerator rbg; AsyncFileReadWorkload(WorkloadContext const& wcx) : AsyncFileWorkload(wcx), bytesRead("Bytes Read"), ioLog(0) { // Only run on one client numParallelReads = getOption(options, LiteralStringRef("numParallelReads"), 0); readSize = getOption(options, LiteralStringRef("readSize"), _PAGE_SIZE); fileSize = getOption(options, LiteralStringRef("fileSize"), (int64_t)0); // 0 = use existing, else, change file size unbatched = getOption(options, LiteralStringRef("unbatched"), false); sequential = getOption(options, LiteralStringRef("sequential"), true); writeFraction = getOption(options, LiteralStringRef("writeFraction"), 0.0); randomData = getOption(options, LiteralStringRef("randomData"), true); fixedRate = getOption(options, LiteralStringRef("fixedRate"), 0.0); } ~AsyncFileReadWorkload() override {} std::string description() const override { return "AsyncFileRead"; } Future setup(Database const& cx) override { if (enabled) return _setup(this); return Void(); } ACTOR Future _setup(AsyncFileReadWorkload* self) { // Allow only 4K aligned reads if doing unbuffered IO if (self->unbufferedIO && self->readSize % AsyncFileWorkload::_PAGE_SIZE != 0) self->readSize = std::max(AsyncFileWorkload::_PAGE_SIZE, self->readSize - self->readSize % AsyncFileWorkload::_PAGE_SIZE); // Allocate the read buffers for (int i = 0; i < self->numParallelReads; i++) self->readBuffers.push_back(self->allocateBuffer(self->readSize)); wait(self->openFile( self, IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE, 0666, self->fileSize, self->fileSize != 0)); int64_t fileSize = wait(self->fileHandle->file->size()); self->fileSize = fileSize; return Void(); } Future start(Database const& cx) override { if (enabled) return _start(this); return Void(); } ACTOR Future _start(AsyncFileReadWorkload* self) { state StatisticsState statState; customSystemMonitor("AsyncFile Metrics", &statState); wait(timeout(self->runReadTest(self), self->testDuration, Void())); SystemStatistics stats = customSystemMonitor("AsyncFile Metrics", &statState); self->averageCpuUtilization = stats.processCPUSeconds / stats.elapsed; // Try to let the IO operations finish so we can clean up after them wait(timeout(waitForAll(self->readFutures), 10, Void())); return Void(); } ACTOR static Future readLoop(AsyncFileReadWorkload* self, int bufferIndex, double fixedRate) { state bool writeFlag = false; state double begin = 0.0; state double lastTime = now(); loop { if (fixedRate) wait(poisson(&lastTime, 1.0 / fixedRate)); // state Future d = delay( 1/25. * (.75 + 0.5*deterministicRandom()->random01()) ); int64_t offset; if (self->unbufferedIO) offset = (int64_t)(deterministicRandom()->random01() * (self->fileSize - 1) / AsyncFileWorkload::_PAGE_SIZE) * AsyncFileWorkload::_PAGE_SIZE; else offset = (int64_t)(deterministicRandom()->random01() * (self->fileSize - 1)); writeFlag = deterministicRandom()->random01() < self->writeFraction; if (writeFlag) self->rbg.writeRandomBytesToBuffer((char*)self->readBuffers[bufferIndex]->buffer, self->readSize); auto r = writeFlag ? tag(self->fileHandle->file->write(self->readBuffers[bufferIndex]->buffer, self->readSize, offset), self->readSize) : self->fileHandle->file->read(self->readBuffers[bufferIndex]->buffer, self->readSize, offset); begin = now(); if (self->ioLog) self->ioLog->logIOIssue(writeFlag, begin); wait(success(uncancellable(holdWhile(self->fileHandle, holdWhile(self->readBuffers[bufferIndex], r))))); if (self->ioLog) self->ioLog->logIOCompletion(writeFlag, begin, now()); self->bytesRead += self->readSize; // wait(d); } } ACTOR Future runReadTest(AsyncFileReadWorkload* self) { if (self->unbatched) { self->ioLog = new IOLog(); std::vector> readers; readers.reserve(self->numParallelReads); for (int i = 0; i < self->numParallelReads; i++) readers.push_back(readLoop(self, i, self->fixedRate / self->numParallelReads)); wait(waitForAll(readers)); delete self->ioLog; return Void(); } state int64_t offset = self->fileSize; loop { // Read consecutive chunks of the file using different actors for (int i = 0; i < self->numParallelReads; i++) { if (self->sequential) { offset += self->readSize; // If the file is exhausted, start over at the beginning if (offset >= self->fileSize) offset = 0; } else if (self->unbufferedIO) offset = (int64_t)(deterministicRandom()->random01() * (self->fileSize - 1) / AsyncFileWorkload::_PAGE_SIZE) * AsyncFileWorkload::_PAGE_SIZE; else offset = (int64_t)(deterministicRandom()->random01() * (self->fileSize - 1)); // Perform the read. Don't allow it to be cancelled (because the underlying IO may not be cancellable) // and don't allow objects that the read uses to be deleted self->readFutures.push_back(uncancellable(holdWhile( self->fileHandle, holdWhile(self->readBuffers[i], self->fileHandle->file->read(self->readBuffers[i]->buffer, self->readSize, offset))))); } wait(waitForAll(self->readFutures)); self->bytesRead += self->readSize * self->numParallelReads; self->readFutures.clear(); wait(delay(0)); } } void getMetrics(std::vector& m) override { if (enabled) { m.emplace_back("Bytes read/sec", bytesRead.getValue() / testDuration, Averaged::False); m.emplace_back("Average CPU Utilization (Percentage)", averageCpuUtilization * 100, Averaged::False); } } }; WorkloadFactory AsyncFileReadWorkloadFactory("AsyncFileRead");