| // 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 |