]> git.proxmox.com Git - ceph.git/blame - ceph/src/test/lazy-omap-stats/lazy_omap_stats_test.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / test / lazy-omap-stats / lazy_omap_stats_test.cc
CommitLineData
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
31using namespace std;
32namespace bp = boost::process;
33
34void 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
105void LazyOmapStatsTest::shutdown()
106{
107 rados.pool_delete(conf.pool_name.c_str());
108 rados.shutdown();
109}
110
111void 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
133const string LazyOmapStatsTest::get_name() const
134{
135 boost::uuids::uuid uuid = boost::uuids::random_generator()();
136 return boost::uuids::to_string(uuid);
137}
138
139void LazyOmapStatsTest::write_many(uint how_many)
140{
141 for (uint i = 0; i < how_many; i++) {
142 write_omap(get_name());
143 }
144}
145
146void 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
170void 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
186const 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
197const 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
215void 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
266const 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
286const 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
307void 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
327index_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
336const 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
364void 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
394void 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
424void 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
450void 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
482void 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
508void 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
547const 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}