blob: 81288f6be09a714daa8ac2866c7a97a74e0dd02f [file] [log] [blame]
Kelvin Zhang1bd2e5b2022-10-03 12:02:34 -07001//
2// Copyright (C) 2022 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include <gtest/gtest.h>
18#include <liburing_cpp/IoUring.h>
19
20#include <linux/fs.h>
21#include <stdio.h>
22
23#include <fcntl.h>
24#include <sys/mman.h>
25#include <sys/utsname.h>
26#include <unistd.h>
27
28#include <algorithm>
29#include <array>
30#include <cstring>
31#include <vector>
32
33using namespace io_uring_cpp;
34
35class IoUringTest : public ::testing::Test {
36 public:
37 IoUringTest() { fp = tmpfile(); }
38 ~IoUringTest() {
39 if (fp) {
40 fclose(fp);
41 }
42 }
43 void SetUp() override {
44 struct utsname buffer {};
45
46 ASSERT_EQ(uname(&buffer), 0)
47 << strerror(errno) << "Failed to get kernel version number";
48 int major = 0;
49 int minor = 0;
50 const auto matched = sscanf(buffer.release, "%d.%d", &major, &minor);
51 ASSERT_EQ(matched, 2) << "Unexpected kernel version format: "
52 << buffer.release;
53
54 if (major < 5 || (major == 5 && minor < 6)) {
55 GTEST_SKIP() << "Kernel version does not support io_uring "
56 << buffer.release;
57 return;
58 }
59
60 ring = IoUringInterface::CreateLinuxIoUring(4096, 0);
61 ASSERT_NE(ring, nullptr);
62 }
63 void Write(int fd, const void* data, const size_t len) {
64 const auto buf = static_cast<const char*>(data);
65 constexpr size_t IO_BATCH_SIZE = 4096;
66 size_t i = 0;
67 for (i = 0; i < len; i += IO_BATCH_SIZE) {
68 const auto sqe = ring->PrepWrite(fd, buf + i, IO_BATCH_SIZE, i);
69 ASSERT_TRUE(sqe.IsOk());
70 }
71 const auto bytes_remaining = len - i;
72 if (bytes_remaining) {
73 ASSERT_TRUE(ring->PrepWrite(fd, buf + i, bytes_remaining, i).IsOk());
74 }
75 const auto ret = ring->Submit();
76 ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
77 for (size_t i = (len + IO_BATCH_SIZE - 1) / IO_BATCH_SIZE; i > 0; i--) {
78 const auto cqe = ring->PopCQE();
79 ASSERT_TRUE(cqe.IsOk());
80 ASSERT_GT(cqe.GetResult().res, 0);
81 }
82 }
83 std::unique_ptr<IoUringInterface> ring;
84 FILE* fp = nullptr;
85};
86
87TEST_F(IoUringTest, SmallRead) {
88 int fd = open("/proc/self/maps", O_RDONLY);
89 std::array<char, 1024> buf{};
90 const auto sqe = ring->PrepRead(fd, buf.data(), buf.size(), 0);
91 ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
92 const auto ret = ring->Submit();
93 ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
94 const auto cqe = ring->PopCQE();
95 ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
96 ASSERT_GT(cqe.GetResult().res, 0);
97}
98
99TEST_F(IoUringTest, SmallWrite) {
100 auto fp = tmpfile();
101 int fd = fileno(fp);
102 std::string buffer(256, 'A');
103 const auto sqe = ring->PrepWrite(fd, buffer.data(), buffer.size(), 0);
104 ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
105 const auto ret = ring->Submit();
106 ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
107 const auto cqe = ring->PopCQE();
108 ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
109
110 const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
111
112 ASSERT_EQ(bytes_read, buffer.size());
113
114 ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
115 return a == 'A';
116 })) << buffer;
117 fclose(fp);
118}
119
120TEST_F(IoUringTest, ChunkedWrite) {
121 int fd = fileno(fp);
122 std::string buffer(16 * 1024 * 1024, 'A');
123 ASSERT_NO_FATAL_FAILURE(Write(fd, buffer.data(), buffer.size()));
124
125 const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
126
127 ASSERT_EQ(bytes_read, buffer.size());
128
129 ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
130 return a == 'A';
131 })) << buffer;
132}
133
134// Page size doesn't really matter. We can replace 4096 with any value.
135static constexpr size_t kBlockSize = 4096;
136constexpr std::array<unsigned char, 4096> GetArbitraryPageData() {
137 std::array<unsigned char, kBlockSize> arr{};
138 int i = 0;
139 for (auto& a : arr) {
140 a = i++;
141 }
142 return arr;
143}
144
145void WriteTestData(int fd, const size_t offset, const size_t size) {
146 ASSERT_EQ(size % kBlockSize, 0);
147 static const auto data = GetArbitraryPageData();
148 size_t bytes_written = 0;
149 size_t cur_offset = offset;
150 while (bytes_written < size) {
151 const auto ret = pwrite(fd, data.data(), kBlockSize, cur_offset);
152 ASSERT_GT(ret, 0) << "Failed to pwrite " << strerror(errno);
153 bytes_written += ret;
154 cur_offset += ret;
155 }
156}
157
158TEST_F(IoUringTest, ExtentRead) {
159 const int fd = fileno(fp);
160 ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 3, kBlockSize));
161 ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 5, kBlockSize));
162 ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 8, kBlockSize));
163 ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 13, kBlockSize));
164 fsync(fd);
165
166 std::vector<unsigned char> data;
167 data.resize(kBlockSize * 4);
168
169 ASSERT_TRUE(
170 ring->PrepRead(fd, data.data(), kBlockSize, 3 * kBlockSize).IsOk());
171 ASSERT_TRUE(
172 ring->PrepRead(fd, data.data() + kBlockSize, kBlockSize, 5 * kBlockSize)
173 .IsOk());
174 ASSERT_TRUE(
175 ring->PrepRead(
176 fd, data.data() + kBlockSize * 2, kBlockSize, 8 * kBlockSize)
177 .IsOk());
178 ASSERT_TRUE(
179 ring->PrepRead(
180 fd, data.data() + kBlockSize * 3, kBlockSize, 13 * kBlockSize)
181 .IsOk());
182 ring->SubmitAndWait(4);
183 const auto cqes = ring->PopCQE(4);
184 if (cqes.IsErr()) {
185 FAIL() << cqes.GetError().ErrMsg();
186 return;
187 }
188 for (const auto& cqe : cqes.GetResult()) {
189 ASSERT_GT(cqe.res, 0);
190 }
191 for (int i = 0; i < data.size(); ++i) {
192 ASSERT_EQ(data[i], i % 256);
193 }
194}