blob: 8d1fec1cd7f99a4e86034aaef6fdd00663d1ac32 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/blobfs/test/integration/load_generator.h"
#include <errno.h>
#include <fcntl.h>
#include <fbl/auto_lock.h>
#include <gtest/gtest.h>
namespace blobfs {
// Make sure we do not exceed maximum fd count.
static_assert(FDIO_MAX_FD >= 256);
constexpr uint32_t kMaxBlobs = FDIO_MAX_FD - 32;
enum class BlobList::QueueId : uint32_t { kCreated = 0, kTruncated, kWritten };
void BlobList::GenerateLoad(uint32_t num_operations, unsigned int* rand_state) {
auto one_operation = [&]() {
switch (rand_r(rand_state) % 6) {
case 0:
CreateBlob(rand_state);
break;
case 1:
TruncateBlob();
break;
case 2:
WriteData();
break;
case 3:
ReadData();
break;
case 4:
ReopenBlob();
break;
case 5:
UnlinkBlob(rand_state);
break;
}
};
for (uint32_t i = 0; i < num_operations; ++i) {
ASSERT_NO_FATAL_FAILURE(one_operation());
}
}
void BlobList::VerifyFiles() {
fbl::AutoLock al(&list_lock_);
for (auto it = lists_[static_cast<uint32_t>(QueueId::kWritten)].begin();
it != lists_[static_cast<uint32_t>(QueueId::kWritten)].end(); ++it) {
it->fd.reset(open(it->info->path, O_RDONLY));
ASSERT_NO_FATAL_FAILURE(
VerifyContents(it->fd.get(), it->info->data.get(), it->info->size_data));
}
}
void BlobList::CloseFiles() {
CloseFilesFromQueue(QueueId::kCreated);
CloseFilesFromQueue(QueueId::kTruncated);
CloseFilesFromQueue(QueueId::kWritten);
}
BlobFile BlobList::GetFileFrom(QueueId queue) {
BlobFile file;
fbl::AutoLock al(&list_lock_);
if (lists_[static_cast<uint32_t>(queue)].empty()) {
return file;
}
file = std::move(lists_[static_cast<uint32_t>(queue)].front());
lists_[static_cast<uint32_t>(queue)].pop_front();
return file;
}
void BlobList::PushFileInto(QueueId queue, BlobFile file) {
fbl::AutoLock al(&list_lock_);
lists_[static_cast<uint32_t>(queue)].push_back(std::move(file));
}
BlobList::QueueId BlobList::GetRandomQueue(unsigned int* rand_state) const {
uint32_t value = rand_r(rand_state) % static_cast<uint32_t>(QueueId::kWritten);
return static_cast<QueueId>(value);
}
void BlobList::CloseFilesFromQueue(QueueId queue) {
fbl::AutoLock al(&list_lock_);
for (auto it = lists_[static_cast<uint32_t>(queue)].begin();
it != lists_[static_cast<uint32_t>(queue)].end(); ++it) {
it->fd.reset();
}
}
void BlobList::CreateBlob(unsigned int* rand_state, size_t num_writes) {
std::unique_ptr<BlobInfo> info =
GenerateRandomBlob(mount_path_, 1 + (rand_r(rand_state) % (1 << 16)));
BlobFile file(std::move(info), num_writes);
file.fd.reset(open(file.info->path, O_CREAT | O_RDWR));
ASSERT_TRUE(file.fd);
{
fbl::AutoLock al(&list_lock_);
if (blob_count_ < kMaxBlobs) {
lists_[static_cast<uint32_t>(QueueId::kCreated)].push_back(std::move(file));
blob_count_++;
}
}
if (file.info) {
// Failed to insert.
ASSERT_EQ(0, unlink(file.info->path));
}
}
void BlobList::TruncateBlob() {
BlobFile file = GetFileFrom(QueueId::kCreated);
if (!file.info) {
return;
}
// If we are going to run out of space on the underlying blobfs partition, the
// ZX_ERR_NO_SPACE is going to come up here. if we run out of space, put the
// kEmpty blob back onto the blob list.
if (ftruncate(file.fd.get(), file.info->size_data) != 0) {
ASSERT_EQ(errno, ENOSPC) << "ftruncate returned an unrecoverable error";
}
PushFileInto(QueueId::kTruncated, std::move(file));
}
void BlobList::WriteData() {
BlobFile file = GetFileFrom(QueueId::kTruncated);
if (!file.info) {
return;
}
size_t to_write = file.bytes_remaining / file.writes_remaining;
size_t bytes_offset = file.info->size_data - file.bytes_remaining;
ASSERT_EQ(write(file.fd.get(), file.info->data.get() + bytes_offset, to_write),
static_cast<ssize_t>(to_write));
file.writes_remaining--;
file.bytes_remaining -= to_write;
if (file.bytes_remaining == 0) {
PushFileInto(QueueId::kWritten, std::move(file));
} else {
PushFileInto(QueueId::kTruncated, std::move(file));
}
}
void BlobList::ReadData() {
BlobFile file = GetFileFrom(QueueId::kWritten);
if (!file.info) {
return;
}
ASSERT_NO_FATAL_FAILURE(
VerifyContents(file.fd.get(), file.info->data.get(), file.info->size_data));
PushFileInto(QueueId::kWritten, std::move(file));
}
void BlobList::UnlinkBlob(unsigned int* rand_state) {
QueueId queue = GetRandomQueue(rand_state);
BlobFile file = GetFileFrom(queue);
if (!file.info) {
return;
}
ASSERT_EQ(0, unlink(file.info->path));
file.fd.reset();
{
fbl::AutoLock al(&list_lock_);
blob_count_--;
}
}
void BlobList::ReopenBlob() {
BlobFile file = GetFileFrom(QueueId::kWritten);
if (!file.info) {
return;
}
file.fd.reset(open(file.info->path, O_RDONLY));
ASSERT_TRUE(file.fd);
PushFileInto(QueueId::kWritten, std::move(file));
}
} // namespace blobfs