Skip to content

Commit f1a8960

Browse files
authored
Merge pull request ceph#60714 from aclamk/wip-aclamk-bluefs-unittest-async-compaction
os/bluestore: New unit test for BlueFS async compaction
2 parents b8af5ab + a090f14 commit f1a8960

File tree

4 files changed

+265
-0
lines changed

4 files changed

+265
-0
lines changed

src/os/bluestore/BlueFS.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,7 +2950,9 @@ void BlueFS::_compact_log_async_LD_LNF_D() //also locks FW for new_writer
29502950
// we need to flush all bdev because we will be streaming all dirty files to log
29512951
// TODO - think - if _flush_and_sync_log_jump will not add dirty files nor release pending allocations
29522952
// then flush_bdev() will not be necessary
2953+
tracepoint_async_compact(1);
29532954
_flush_bdev();
2955+
tracepoint_async_compact(2);
29542956
_flush_and_sync_log_jump_D(old_log_jump_to);
29552957

29562958
//
@@ -3073,14 +3075,18 @@ void BlueFS::_compact_log_async_LD_LNF_D() //also locks FW for new_writer
30733075

30743076
// 3.2. flush and wait
30753077
_flush_special(new_log_writer);
3078+
tracepoint_async_compact(3);
30763079
_flush_bdev(new_log_writer, false); // do not check log.lock is locked
30773080

30783081
// Part 4.
30793082
// Write out new superblock to reflect all the changes.
30803083
//
30813084

3085+
tracepoint_async_compact(4);
30823086
_write_super(BDEV_DB);
3087+
tracepoint_async_compact(5);
30833088
_flush_bdev();
3089+
tracepoint_async_compact(6);
30843090

30853091
// Part 5.
30863092
// Apply new log fnode

src/os/bluestore/BlueFS.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,35 @@ struct bluefs_shared_alloc_context_t {
216216
}
217217
};
218218

219+
/*
220+
Class debug_point is a helper intended for inserting debug tracepoints
221+
in a least intrusive way.
222+
The intention is to minimize both visual and code footprints.
223+
224+
In code, it should look like:
225+
debug_async_compact(1);
226+
227+
Which should translate to:
228+
if (debug_async_compact) debug_async_compact(1);
229+
230+
And in release builds be eliminated completely.
231+
*/
232+
template <class T>
233+
class debug_point_t {
234+
public:
235+
debug_point_t() : m_func(nullptr) {};
236+
debug_point_t(T&& func)
237+
: m_func(func) {}
238+
template<typename... Arg>
239+
void operator()(Arg... arg) { if (m_func) m_func(std::forward<Arg...>(arg...)); }
240+
void operator=(T&& func) { m_func = std::move(func);}
241+
void operator=(T& func) { m_func = func;}
242+
private:
243+
T m_func;
244+
};
245+
246+
247+
219248
class BlueFS {
220249
public:
221250
CephContext* cct;
@@ -776,6 +805,7 @@ class BlueFS {
776805
}
777806
uint64_t debug_get_dirty_seq(FileWriter *h);
778807
bool debug_get_is_dev_dirty(FileWriter *h, uint8_t dev);
808+
debug_point_t<std::function<void(uint32_t)>> tracepoint_async_compact;
779809
void trim_free_space(const std::string& type, std::ostream& outss);
780810

781811
private:

src/test/objectstore/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ if(WITH_BLUESTORE)
110110
add_ceph_unittest(unittest_bluefs)
111111
target_link_libraries(unittest_bluefs os global)
112112

113+
# unittest_bluefs_ex
114+
add_executable(unittest_bluefs_ex
115+
test_bluefs_ex.cc
116+
)
117+
add_ceph_unittest(unittest_bluefs_ex)
118+
target_link_libraries(unittest_bluefs_ex os global)
119+
113120
# unittest_bluestore_types
114121
add_executable(unittest_bluestore_types
115122
test_bluestore_types.cc
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2+
// vim: ts=8 sw=2 smarttab
3+
4+
#include <cstdlib>
5+
#include <initializer_list>
6+
#include <stdio.h>
7+
#include <string.h>
8+
#include <iostream>
9+
#include <time.h>
10+
#include <fcntl.h>
11+
#include <unistd.h>
12+
#include <random>
13+
#include <thread>
14+
#include <stack>
15+
#include <gtest/gtest.h>
16+
#include "global/global_init.h"
17+
#include "common/ceph_argparse.h"
18+
#include "include/stringify.h"
19+
#include "include/scope_guard.h"
20+
#include "common/errno.h"
21+
22+
#include "os/bluestore/Allocator.h"
23+
#include "os/bluestore/bluestore_common.h"
24+
#include "os/bluestore/BlueFS.h"
25+
26+
using namespace std;
27+
28+
int argc;
29+
char **argv;
30+
31+
std::unique_ptr<char[]> gen_buffer(uint64_t size)
32+
{
33+
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size);
34+
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> e;
35+
std::generate(buffer.get(), buffer.get()+size, std::ref(e));
36+
return buffer;
37+
}
38+
39+
class TempBdev {
40+
public:
41+
TempBdev() {}
42+
~TempBdev() {}
43+
void choose_name(pid_t pid = getpid()) {
44+
static int n = 0;
45+
path = "ceph_test_bluefs.tmp.block." + stringify(pid)
46+
+ "." + stringify(++n);
47+
}
48+
void create_bdev(uint64_t size) {
49+
ceph_assert(!path.empty());
50+
int fd = ::open(path.c_str(), O_CREAT|O_RDWR|O_TRUNC, 0644);
51+
ceph_assert(fd >= 0);
52+
int r = ::ftruncate(fd, size);
53+
ceph_assert(r >= 0);
54+
::close(fd);
55+
}
56+
void rm_bdev() {
57+
ceph_assert(!path.empty());
58+
::unlink(path.c_str());
59+
}
60+
std::string path;
61+
};
62+
63+
class ConfSaver {
64+
std::stack<std::pair<std::string, std::string>> saved_settings;
65+
ConfigProxy& conf;
66+
public:
67+
ConfSaver(ConfigProxy& conf) : conf(conf) {
68+
conf._clear_safe_to_start_threads();
69+
};
70+
~ConfSaver() {
71+
conf._clear_safe_to_start_threads();
72+
while(saved_settings.size() > 0) {
73+
auto& e = saved_settings.top();
74+
conf.set_val_or_die(e.first, e.second);
75+
saved_settings.pop();
76+
}
77+
conf.set_safe_to_start_threads();
78+
conf.apply_changes(nullptr);
79+
}
80+
void SetVal(const char* key, const char* val) {
81+
std::string skey(key);
82+
std::string prev_val;
83+
conf.get_val(skey, &prev_val);
84+
conf.set_val_or_die(skey, val);
85+
saved_settings.emplace(skey, prev_val);
86+
}
87+
void ApplyChanges() {
88+
conf.set_safe_to_start_threads();
89+
conf.apply_changes(nullptr);
90+
}
91+
};
92+
93+
94+
class BlueFS_ex : virtual public ::testing::Test {
95+
96+
public:
97+
explicit BlueFS_ex()
98+
{
99+
}
100+
boost::intrusive_ptr<CephContext> init_ceph()
101+
{
102+
boost::intrusive_ptr<CephContext> cct;
103+
auto args = argv_to_vec(argc, argv);
104+
map<string, string> defaults = {
105+
{"debug_bluefs", "0/20"},
106+
{"debug_bdev", "0/20"},
107+
{"log_to_stderr", "false"}};
108+
cct = global_init(
109+
&defaults, args, CEPH_ENTITY_TYPE_CLIENT,
110+
CODE_ENVIRONMENT_UTILITY, CINIT_FLAG_NO_DEFAULT_CONFIG_FILE);
111+
common_init_finish(g_ceph_context);
112+
g_ceph_context->_conf.set_val(
113+
"enable_experimental_unrecoverable_data_corrupting_features", "*");
114+
g_ceph_context->_conf.apply_changes(nullptr);
115+
return cct;
116+
}
117+
118+
void SetUp() override
119+
{
120+
}
121+
void TearDown() override
122+
{
123+
}
124+
125+
void grow_log_interrupt_on_compact(pid_t parent_pid, uint32_t stop_point)
126+
{
127+
auto cct = init_ceph();
128+
ConfSaver conf(g_ceph_context->_conf);
129+
conf.SetVal("bluefs_alloc_size", "4096");
130+
conf.SetVal("bluefs_shared_alloc_size", "4096");
131+
conf.SetVal("bluefs_compact_log_sync", "false");
132+
conf.SetVal("bluefs_log_compact_min_size", "1048576");
133+
conf.ApplyChanges();
134+
135+
auto stop_at_fixed_point = [&](uint32_t i) -> void {
136+
if (i == stop_point) exit(107);
137+
};
138+
BlueFS fs(g_ceph_context);
139+
fs.tracepoint_async_compact = stop_at_fixed_point;
140+
ASSERT_EQ(0, fs.add_block_device(BlueFS::BDEV_DB, bdev.path, false));
141+
uuid_d fsid;
142+
ASSERT_EQ(0, fs.mkfs(fsid, {BlueFS::BDEV_DB, false, false}));
143+
ASSERT_EQ(0, fs.mount());
144+
ASSERT_EQ(0, fs.maybe_verify_layout({BlueFS::BDEV_DB, false, false}));
145+
ASSERT_EQ(0, fs.mkdir("dir"));
146+
147+
auto fill = [&](uint32_t filenum) {
148+
char data[2000] = {'x'};
149+
BlueFS::FileWriter *h;
150+
ASSERT_EQ(0, fs.open_for_write("dir", "file"+to_string(filenum), &h, false));
151+
for (size_t i = 0; i < 10000; i++) {
152+
h->append(data, 2000);
153+
fs.fsync(h);
154+
}
155+
fs.close_writer(h);
156+
};
157+
std::thread thr[10];
158+
for (int i=0; i< 10;i++) {
159+
thr[i] = std::thread(fill, i);
160+
}
161+
for (int i=0; i< 10;i++) {
162+
thr[i].join();
163+
}
164+
EXPECT_TRUE(false && "reaching this point means test was not executed");
165+
exit(111);
166+
}
167+
168+
TempBdev bdev;
169+
};
170+
171+
TEST_F(BlueFS_ex, test_interrupted_compaction)
172+
{
173+
for (uint32_t stop_point = 1; stop_point <= 6; stop_point++)
174+
{
175+
pid_t fork_for_test = fork();
176+
if (fork_for_test != 0) {
177+
int stat;
178+
waitpid(fork_for_test, &stat, 0);
179+
ASSERT_TRUE(WIFEXITED(stat));
180+
ASSERT_TRUE(WEXITSTATUS(stat) == 0);
181+
continue;
182+
}
183+
pid_t parent_pid = getpid();
184+
uint64_t size = 1048576LL * (2 * 1024 + 128);
185+
bdev.choose_name();
186+
bdev.create_bdev(size);
187+
pid_t fork_pid = fork();
188+
if (fork_pid == 0) {
189+
std::cout << "growing BlueFS log for async compact, stop at #" << (int)stop_point << std::endl;
190+
grow_log_interrupt_on_compact(parent_pid, stop_point);
191+
} else {
192+
int stat;
193+
std::cout << "waiting for compaction to terminate" << std::endl;
194+
waitpid(fork_pid, &stat, 0);
195+
std::cout << "done code=" << WEXITSTATUS(stat) << std::endl;
196+
if(!WIFEXITED(stat) || WEXITSTATUS(stat) != 107) exit(107);
197+
auto cct = init_ceph();
198+
ConfSaver conf(g_ceph_context->_conf);
199+
conf.SetVal("bluefs_alloc_size", "4096");
200+
conf.SetVal("bluefs_shared_alloc_size", "4096");
201+
conf.SetVal("bluefs_compact_log_sync", "false");
202+
conf.SetVal("bluefs_log_compact_min_size", "1048576");
203+
conf.ApplyChanges();
204+
205+
BlueFS fs(g_ceph_context);
206+
ASSERT_EQ(0, fs.add_block_device(BlueFS::BDEV_DB, bdev.path, false));
207+
// fs.log_dump();
208+
ASSERT_EQ(0, fs.mount());
209+
fs.umount();
210+
}
211+
bdev.rm_bdev();
212+
exit(0); //this terminates one loop of 'fork_for_test'
213+
}
214+
215+
}
216+
217+
int main(int _argc, char **_argv) {
218+
argc = _argc;
219+
argv = _argv;
220+
::testing::InitGoogleTest(&argc, argv);
221+
return RUN_ALL_TESTS();
222+
}

0 commit comments

Comments
 (0)