]>
Commit | Line | Data |
---|---|---|
494da23a TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2019 Red Hat | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | ||
16 | #include <algorithm> | |
17 | #include <boost/algorithm/string/trim.hpp> | |
18 | #include <boost/process.hpp> | |
19 | #include <boost/tokenizer.hpp> | |
20 | #include <boost/uuid/uuid.hpp> // uuid class | |
21 | #include <boost/uuid/uuid_generators.hpp> // generators | |
22 | #include <boost/uuid/uuid_io.hpp> // streaming operators etc. | |
23 | #include <chrono> | |
24 | #include <iostream> | |
25 | #include <thread> | |
26 | #include <vector> | |
27 | ||
28 | #include "lazy_omap_stats_test.h" | |
f67539c2 | 29 | #include "include/compat.h" |
494da23a TL |
30 | |
31 | using namespace std; | |
32 | namespace bp = boost::process; | |
33 | ||
34 | void LazyOmapStatsTest::init(const int argc, const char** argv) | |
35 | { | |
36 | int ret = rados.init("admin"); | |
37 | if (ret < 0) { | |
38 | ret = -ret; | |
39 | cerr << "Failed to initialise rados! Error: " << ret << " " << strerror(ret) | |
40 | << endl; | |
41 | exit(ret); | |
42 | } | |
43 | ||
44 | ret = rados.conf_parse_argv(argc, argv); | |
45 | if (ret < 0) { | |
46 | ret = -ret; | |
47 | cerr << "Failed to parse command line config options! Error: " << ret << " " | |
48 | << strerror(ret) << endl; | |
49 | exit(ret); | |
50 | } | |
51 | ||
52 | rados.conf_parse_env(NULL); | |
53 | if (ret < 0) { | |
54 | ret = -ret; | |
55 | cerr << "Failed to parse environment! Error: " << ret << " " | |
56 | << strerror(ret) << endl; | |
57 | exit(ret); | |
58 | } | |
59 | ||
60 | rados.conf_read_file(NULL); | |
61 | if (ret < 0) { | |
62 | ret = -ret; | |
63 | cerr << "Failed to read config file! Error: " << ret << " " << strerror(ret) | |
64 | << endl; | |
65 | exit(ret); | |
66 | } | |
67 | ||
68 | ret = rados.connect(); | |
69 | if (ret < 0) { | |
70 | ret = -ret; | |
71 | cerr << "Failed to connect to running cluster! Error: " << ret << " " | |
72 | << strerror(ret) << endl; | |
73 | exit(ret); | |
74 | } | |
75 | ||
76 | string command = R"( | |
77 | { | |
78 | "prefix": "osd pool create", | |
79 | "pool": ")" + conf.pool_name + | |
80 | R"(", | |
81 | "pool_type": "replicated", | |
82 | "size": )" + to_string(conf.replica_count) + | |
83 | R"( | |
84 | })"; | |
85 | librados::bufferlist inbl; | |
86 | string output; | |
87 | ret = rados.mon_command(command, inbl, nullptr, &output); | |
88 | if (output.length()) cout << output << endl; | |
89 | if (ret < 0) { | |
90 | ret = -ret; | |
91 | cerr << "Failed to create pool! Error: " << ret << " " << strerror(ret) | |
92 | << endl; | |
93 | exit(ret); | |
94 | } | |
95 | ||
96 | ret = rados.ioctx_create(conf.pool_name.c_str(), io_ctx); | |
97 | if (ret < 0) { | |
98 | ret = -ret; | |
99 | cerr << "Failed to create ioctx! Error: " << ret << " " << strerror(ret) | |
100 | << endl; | |
101 | exit(ret); | |
102 | } | |
103 | } | |
104 | ||
105 | void LazyOmapStatsTest::shutdown() | |
106 | { | |
107 | rados.pool_delete(conf.pool_name.c_str()); | |
108 | rados.shutdown(); | |
109 | } | |
110 | ||
111 | void LazyOmapStatsTest::write_omap(const string& object_name) | |
112 | { | |
113 | librados::bufferlist bl; | |
114 | int ret = io_ctx.write_full(object_name, bl); | |
115 | if (ret < 0) { | |
116 | ret = -ret; | |
117 | cerr << "Failed to create object! Error: " << ret << " " << strerror(ret) | |
118 | << endl; | |
119 | exit(ret); | |
120 | } | |
121 | ret = io_ctx.omap_set(object_name, payload); | |
122 | if (ret < 0) { | |
123 | ret = -ret; | |
124 | cerr << "Failed to write omap payload! Error: " << ret << " " | |
125 | << strerror(ret) << endl; | |
126 | exit(ret); | |
127 | } | |
128 | cout << "Wrote " << conf.keys << " omap keys of " << conf.payload_size | |
129 | << " bytes to " | |
130 | << "the " << object_name << " object" << endl; | |
131 | } | |
132 | ||
133 | const string LazyOmapStatsTest::get_name() const | |
134 | { | |
135 | boost::uuids::uuid uuid = boost::uuids::random_generator()(); | |
136 | return boost::uuids::to_string(uuid); | |
137 | } | |
138 | ||
139 | void LazyOmapStatsTest::write_many(uint how_many) | |
140 | { | |
141 | for (uint i = 0; i < how_many; i++) { | |
142 | write_omap(get_name()); | |
143 | } | |
144 | } | |
145 | ||
146 | void LazyOmapStatsTest::create_payload() | |
147 | { | |
148 | librados::bufferlist Lorem; | |
149 | Lorem.append( | |
150 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " | |
151 | "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " | |
152 | "enim ad minim veniam, quis nostrud exercitation ullamco laboris " | |
153 | "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " | |
154 | "reprehenderit in voluptate velit esse cillum dolore eu fugiat " | |
155 | "nulla pariatur. Excepteur sint occaecat cupidatat non proident, " | |
156 | "sunt in culpa qui officia deserunt mollit anim id est laborum."); | |
157 | conf.payload_size = Lorem.length(); | |
158 | conf.total_bytes = conf.keys * conf.payload_size * conf.how_many; | |
159 | conf.total_keys = conf.keys * conf.how_many; | |
160 | uint i = 0; | |
161 | for (i = 1; i < conf.keys + 1; ++i) { | |
162 | payload[get_name()] = Lorem; | |
163 | } | |
164 | cout << "Created payload with " << conf.keys << " keys of " | |
165 | << conf.payload_size | |
166 | << " bytes each. Total size in bytes = " << conf.keys * conf.payload_size | |
167 | << endl; | |
168 | } | |
169 | ||
170 | void LazyOmapStatsTest::scrub() const | |
171 | { | |
172 | // Use CLI because we need to block | |
173 | ||
174 | cout << "Scrubbing" << endl; | |
175 | error_code ec; | |
176 | bp::ipstream is; | |
20effc67 TL |
177 | bp::child c("ceph osd deep-scrub all --block"); |
178 | c.wait(ec); | |
494da23a TL |
179 | if (ec) { |
180 | cout << "Deep scrub command failed! Error: " << ec.value() << " " | |
181 | << ec.message() << endl; | |
182 | exit(ec.value()); | |
183 | } | |
494da23a TL |
184 | } |
185 | ||
186 | const int LazyOmapStatsTest::find_matches(string& output, regex& reg) const | |
187 | { | |
188 | sregex_iterator cur(output.begin(), output.end(), reg); | |
189 | uint x = 0; | |
190 | for (auto end = std::sregex_iterator(); cur != end; ++cur) { | |
191 | cout << (*cur)[1].str() << endl; | |
192 | x++; | |
193 | } | |
194 | return x; | |
195 | } | |
196 | ||
197 | const string LazyOmapStatsTest::get_output(const string command, | |
198 | const bool silent) | |
199 | { | |
200 | librados::bufferlist inbl, outbl; | |
201 | string output; | |
202 | int ret = rados.mgr_command(command, inbl, &outbl, &output); | |
203 | if (output.length() && !silent) { | |
204 | cout << output << endl; | |
205 | } | |
206 | if (ret < 0) { | |
207 | ret = -ret; | |
208 | cerr << "Failed to get " << command << "! Error: " << ret << " " | |
209 | << strerror(ret) << endl; | |
210 | exit(ret); | |
211 | } | |
212 | return string(outbl.c_str(), outbl.length()); | |
213 | } | |
214 | ||
215 | void LazyOmapStatsTest::check_one() | |
216 | { | |
217 | string full_output = get_output(); | |
218 | cout << full_output << endl; | |
219 | regex reg( | |
220 | "\n" | |
221 | R"((PG_STAT[\s\S]*)" | |
222 | "\n)OSD_STAT"); // Strip OSD_STAT table so we don't find matches there | |
223 | smatch match; | |
224 | regex_search(full_output, match, reg); | |
225 | auto truncated_output = match[1].str(); | |
226 | cout << truncated_output << endl; | |
227 | reg = regex( | |
228 | "\n" | |
229 | R"(([0-9,s].*\s)" + | |
230 | to_string(conf.keys) + | |
231 | R"(\s.*))" | |
232 | "\n"); | |
233 | ||
234 | cout << "Checking number of keys " << conf.keys << endl; | |
235 | cout << "Found the following lines" << endl; | |
236 | cout << "*************************" << endl; | |
237 | uint result = find_matches(truncated_output, reg); | |
238 | cout << "**********************" << endl; | |
239 | cout << "Found " << result << " matching line(s)" << endl; | |
240 | uint total = result; | |
241 | ||
242 | reg = regex( | |
243 | "\n" | |
244 | R"(([0-9,s].*\s)" + | |
245 | to_string(conf.payload_size * conf.keys) + | |
246 | R"(\s.*))" | |
247 | "\n"); | |
248 | cout << "Checking number of bytes " | |
249 | << conf.payload_size * conf.keys << endl; | |
250 | cout << "Found the following lines" << endl; | |
251 | cout << "*************************" << endl; | |
252 | result = find_matches(truncated_output, reg); | |
253 | cout << "**********************" << endl; | |
254 | cout << "Found " << result << " matching line(s)" << endl; | |
255 | ||
256 | total += result; | |
257 | if (total != 6) { | |
258 | cout << "Error: Found " << total << " matches, expected 6! Exiting..." | |
259 | << endl; | |
260 | exit(22); // EINVAL | |
261 | } | |
262 | cout << "check_one successful. Found " << total << " matches as expected" | |
263 | << endl; | |
264 | } | |
265 | ||
266 | const int LazyOmapStatsTest::find_index(string& haystack, regex& needle, | |
267 | string label) const | |
268 | { | |
269 | smatch match; | |
270 | regex_search(haystack, match, needle); | |
271 | auto line = match[1].str(); | |
272 | boost::algorithm::trim(line); | |
273 | boost::char_separator<char> sep{" "}; | |
274 | boost::tokenizer<boost::char_separator<char>> tok(line, sep); | |
275 | vector<string> tokens(tok.begin(), tok.end()); | |
276 | auto it = find(tokens.begin(), tokens.end(), label); | |
277 | if (it != tokens.end()) { | |
278 | return distance(tokens.begin(), it); | |
279 | } | |
280 | ||
281 | cerr << "find_index failed to find index for " << label << endl; | |
282 | exit(2); // ENOENT | |
283 | return -1; // Unreachable | |
284 | } | |
285 | ||
286 | const uint LazyOmapStatsTest::tally_column(const uint omap_bytes_index, | |
287 | const string& table, | |
288 | bool header) const | |
289 | { | |
290 | istringstream buffer(table); | |
291 | string line; | |
292 | uint64_t total = 0; | |
293 | while (std::getline(buffer, line)) { | |
294 | if (header) { | |
295 | header = false; | |
296 | continue; | |
297 | } | |
298 | boost::char_separator<char> sep{" "}; | |
299 | boost::tokenizer<boost::char_separator<char>> tok(line, sep); | |
300 | vector<string> tokens(tok.begin(), tok.end()); | |
301 | total += stoi(tokens.at(omap_bytes_index)); | |
302 | } | |
303 | ||
304 | return total; | |
305 | } | |
306 | ||
307 | void LazyOmapStatsTest::check_column(const int index, const string& table, | |
308 | const string& type, bool header) const | |
309 | { | |
310 | uint expected; | |
311 | string errormsg; | |
312 | if (type.compare("bytes") == 0) { | |
313 | expected = conf.total_bytes; | |
314 | errormsg = "Error. Got unexpected byte count!"; | |
315 | } else { | |
316 | expected = conf.total_keys; | |
317 | errormsg = "Error. Got unexpected key count!"; | |
318 | } | |
319 | uint sum = tally_column(index, table, header); | |
320 | cout << "Got: " << sum << " Expected: " << expected << endl; | |
321 | if (sum != expected) { | |
322 | cout << errormsg << endl; | |
323 | exit(22); // EINVAL | |
324 | } | |
325 | } | |
326 | ||
327 | index_t LazyOmapStatsTest::get_indexes(regex& reg, string& output) const | |
328 | { | |
329 | index_t indexes; | |
330 | indexes.byte_index = find_index(output, reg, "OMAP_BYTES*"); | |
331 | indexes.key_index = find_index(output, reg, "OMAP_KEYS*"); | |
332 | ||
333 | return indexes; | |
334 | } | |
335 | ||
336 | const string LazyOmapStatsTest::get_pool_id(string& pool) | |
337 | { | |
338 | cout << R"(Querying pool id)" << endl; | |
339 | ||
340 | string command = R"({"prefix": "osd pool ls", "detail": "detail"})"; | |
341 | librados::bufferlist inbl, outbl; | |
342 | string output; | |
343 | int ret = rados.mon_command(command, inbl, &outbl, &output); | |
344 | if (output.length()) cout << output << endl; | |
345 | if (ret < 0) { | |
346 | ret = -ret; | |
347 | cerr << "Failed to get pool id! Error: " << ret << " " << strerror(ret) | |
348 | << endl; | |
349 | exit(ret); | |
350 | } | |
351 | string dump_output(outbl.c_str(), outbl.length()); | |
352 | cout << dump_output << endl; | |
353 | ||
354 | string poolregstring = R"(pool\s(\d+)\s')" + pool + "'"; | |
355 | regex reg(poolregstring); | |
356 | smatch match; | |
357 | regex_search(dump_output, match, reg); | |
358 | auto pool_id = match[1].str(); | |
359 | cout << "Found pool ID: " << pool_id << endl; | |
360 | ||
361 | return pool_id; | |
362 | } | |
363 | ||
364 | void LazyOmapStatsTest::check_pg_dump() | |
365 | { | |
366 | cout << R"(Checking "pg dump" output)" << endl; | |
367 | ||
368 | string dump_output = get_output(); | |
369 | cout << dump_output << endl; | |
370 | ||
371 | regex reg( | |
372 | "\n" | |
373 | R"((PG_STAT\s.*))" | |
374 | "\n"); | |
375 | index_t indexes = get_indexes(reg, dump_output); | |
376 | ||
377 | reg = | |
378 | "\n" | |
379 | R"((PG_STAT[\s\S]*))" | |
380 | "\n +\n[0-9]"; | |
381 | smatch match; | |
382 | regex_search(dump_output, match, reg); | |
383 | auto table = match[1].str(); | |
384 | ||
385 | cout << "Checking bytes" << endl; | |
386 | check_column(indexes.byte_index, table, string("bytes")); | |
387 | ||
388 | cout << "Checking keys" << endl; | |
389 | check_column(indexes.key_index, table, string("keys")); | |
390 | ||
391 | cout << endl; | |
392 | } | |
393 | ||
394 | void LazyOmapStatsTest::check_pg_dump_summary() | |
395 | { | |
396 | cout << R"(Checking "pg dump summary" output)" << endl; | |
397 | ||
398 | string command = R"({"prefix": "pg dump", "dumpcontents": ["summary"]})"; | |
399 | string dump_output = get_output(command); | |
400 | cout << dump_output << endl; | |
401 | ||
402 | regex reg( | |
403 | "\n" | |
404 | R"((PG_STAT\s.*))" | |
405 | "\n"); | |
406 | index_t indexes = get_indexes(reg, dump_output); | |
407 | ||
408 | reg = | |
409 | "\n" | |
410 | R"((sum\s.*))" | |
411 | "\n"; | |
412 | smatch match; | |
413 | regex_search(dump_output, match, reg); | |
414 | auto table = match[1].str(); | |
415 | ||
416 | cout << "Checking bytes" << endl; | |
417 | check_column(indexes.byte_index, table, string("bytes"), false); | |
418 | ||
419 | cout << "Checking keys" << endl; | |
420 | check_column(indexes.key_index, table, string("keys"), false); | |
421 | cout << endl; | |
422 | } | |
423 | ||
424 | void LazyOmapStatsTest::check_pg_dump_pgs() | |
425 | { | |
426 | cout << R"(Checking "pg dump pgs" output)" << endl; | |
427 | ||
428 | string command = R"({"prefix": "pg dump", "dumpcontents": ["pgs"]})"; | |
429 | string dump_output = get_output(command); | |
430 | cout << dump_output << endl; | |
431 | ||
432 | regex reg(R"(^(PG_STAT\s.*))" | |
433 | "\n"); | |
434 | index_t indexes = get_indexes(reg, dump_output); | |
435 | ||
436 | reg = R"(^(PG_STAT[\s\S]*))" | |
437 | "\n\n"; | |
438 | smatch match; | |
439 | regex_search(dump_output, match, reg); | |
440 | auto table = match[1].str(); | |
441 | ||
442 | cout << "Checking bytes" << endl; | |
443 | check_column(indexes.byte_index, table, string("bytes")); | |
444 | ||
445 | cout << "Checking keys" << endl; | |
446 | check_column(indexes.key_index, table, string("keys")); | |
447 | cout << endl; | |
448 | } | |
449 | ||
450 | void LazyOmapStatsTest::check_pg_dump_pools() | |
451 | { | |
452 | cout << R"(Checking "pg dump pools" output)" << endl; | |
453 | ||
454 | string command = R"({"prefix": "pg dump", "dumpcontents": ["pools"]})"; | |
455 | string dump_output = get_output(command); | |
456 | cout << dump_output << endl; | |
457 | ||
458 | regex reg(R"(^(POOLID\s.*))" | |
459 | "\n"); | |
460 | index_t indexes = get_indexes(reg, dump_output); | |
461 | ||
462 | auto pool_id = get_pool_id(conf.pool_name); | |
463 | ||
464 | reg = | |
465 | "\n" | |
466 | R"(()" + | |
467 | pool_id + | |
468 | R"(\s.*))" | |
469 | "\n"; | |
470 | smatch match; | |
471 | regex_search(dump_output, match, reg); | |
472 | auto line = match[1].str(); | |
473 | ||
474 | cout << "Checking bytes" << endl; | |
475 | check_column(indexes.byte_index, line, string("bytes"), false); | |
476 | ||
477 | cout << "Checking keys" << endl; | |
478 | check_column(indexes.key_index, line, string("keys"), false); | |
479 | cout << endl; | |
480 | } | |
481 | ||
482 | void LazyOmapStatsTest::check_pg_ls() | |
483 | { | |
484 | cout << R"(Checking "pg ls" output)" << endl; | |
485 | ||
486 | string command = R"({"prefix": "pg ls"})"; | |
487 | string dump_output = get_output(command); | |
488 | cout << dump_output << endl; | |
489 | ||
490 | regex reg(R"(^(PG\s.*))" | |
491 | "\n"); | |
492 | index_t indexes = get_indexes(reg, dump_output); | |
493 | ||
494 | reg = R"(^(PG[\s\S]*))" | |
495 | "\n\n"; | |
496 | smatch match; | |
497 | regex_search(dump_output, match, reg); | |
498 | auto table = match[1].str(); | |
499 | ||
500 | cout << "Checking bytes" << endl; | |
501 | check_column(indexes.byte_index, table, string("bytes")); | |
502 | ||
503 | cout << "Checking keys" << endl; | |
504 | check_column(indexes.key_index, table, string("keys")); | |
505 | cout << endl; | |
506 | } | |
507 | ||
508 | void LazyOmapStatsTest::wait_for_active_clean() | |
509 | { | |
510 | cout << "Waiting for active+clean" << endl; | |
511 | ||
512 | int index = -1; | |
513 | regex reg( | |
514 | "\n" | |
515 | R"((PG_STAT[\s\S]*))" | |
516 | "\n +\n[0-9]"); | |
517 | string command = R"({"prefix": "pg dump"})"; | |
518 | int num_not_clean; | |
519 | do { | |
520 | string dump_output = get_output(command, true); | |
521 | if (index == -1) { | |
522 | regex ireg( | |
523 | "\n" | |
524 | R"((PG_STAT\s.*))" | |
525 | "\n"); | |
526 | index = find_index(dump_output, ireg, "STATE"); | |
527 | } | |
528 | smatch match; | |
529 | regex_search(dump_output, match, reg); | |
530 | istringstream buffer(match[1].str()); | |
531 | string line; | |
532 | num_not_clean = 0; | |
533 | while (std::getline(buffer, line)) { | |
534 | if (line.compare(0, 1, "P") == 0) continue; | |
535 | boost::char_separator<char> sep{" "}; | |
536 | boost::tokenizer<boost::char_separator<char>> tok(line, sep); | |
537 | vector<string> tokens(tok.begin(), tok.end()); | |
538 | num_not_clean += tokens.at(index).compare("active+clean"); | |
539 | } | |
540 | cout << "." << flush; | |
541 | this_thread::sleep_for(chrono::milliseconds(250)); | |
542 | } while (num_not_clean); | |
543 | ||
544 | cout << endl; | |
545 | } | |
546 | ||
547 | const int LazyOmapStatsTest::run(const int argc, const char** argv) | |
548 | { | |
549 | init(argc, argv); | |
550 | create_payload(); | |
551 | wait_for_active_clean(); | |
552 | write_omap(get_name()); | |
553 | scrub(); | |
554 | check_one(); | |
555 | ||
556 | write_many(conf.how_many - 1); // Since we already wrote one | |
557 | scrub(); | |
558 | check_pg_dump(); | |
559 | check_pg_dump_summary(); | |
560 | check_pg_dump_pgs(); | |
561 | check_pg_dump_pools(); | |
562 | check_pg_ls(); | |
563 | cout << "All tests passed. Success!" << endl; | |
564 | ||
565 | shutdown(); | |
566 | ||
567 | return 0; | |
568 | } |