From 7ff600992554f08c07181ff1c987212758c84a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=80=D0=B5=D0=BD=D0=B1=D0=B5=D1=80=D0=B3=20?= =?UTF-8?q?=D0=9C=D0=B0=D1=80=D0=BA=20=28=D0=9D=D0=BE=D1=83=D1=82=D0=B1?= =?UTF-8?q?=D1=83=D0=BA=20HP=29?= Date: Sun, 20 Jan 2019 17:35:09 +0500 Subject: [PATCH] refac and signals --- Makefile | 6 +- main.cpp | 255 +++++++++++++------------------------- main.py | 336 -------------------------------------------------- mysignals.cpp | 16 +++ mysignals.h | 11 ++ radosutil.cpp | 101 +++++++++++++++ radosutil.h | 41 ++++++ 7 files changed, 259 insertions(+), 507 deletions(-) delete mode 100755 main.py create mode 100644 mysignals.cpp create mode 100644 mysignals.h create mode 100644 radosutil.cpp create mode 100644 radosutil.h diff --git a/Makefile b/Makefile index 0738284..5f4ce8d 100644 --- a/Makefile +++ b/Makefile @@ -6,14 +6,14 @@ LDFLAGS += -pthread -lrados -ljsoncpp -lstdc++ #CC=clang-6.0 -main: main.o +main: main.o mysignals.o radosutil.o $(CC) $^ -o $@ $(LDFLAGS) .cpp.o: $(CC) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ -indent: main.cpp - clang-format-6.0 -i main.cpp +indent: *.cpp *.h + clang-format-6.0 -i $^ builddep: sudo apt install -y --no-install-recommends libjsoncpp-dev diff --git a/main.cpp b/main.cpp index 557c2df..10b07ef 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,6 +9,9 @@ #include #include +#include "mysignals.h" +#include "radosutil.h" + using namespace librados; using namespace std; using namespace chrono; @@ -116,20 +120,22 @@ static void print_breakdown(const vector &summary, size_t thread_count) { // Called in a thread. static void _do_bench(unsigned int secs, const string &obj_name, IoCtx &ioctx, vector *ops) { - auto b = steady_clock::now(); - const auto stop = b + seconds(secs); // cout<<"tt" <size() % 2 ? bar1 : bar2) < 0) throw "Write error"; const auto b2 = steady_clock::now(); @@ -153,9 +159,24 @@ static void do_bench(unsigned int secs, const vector &names, vector *> listofopts; for (const auto &name : names) { + + // TODO: memory leak on exception... auto results = new vector; listofopts.push_back(results); + + sigset_t new_set; + sigset_t old_set; + sigfillset(&new_set); + int err; + if ((err = pthread_sigmask(SIG_SETMASK, &new_set, &old_set))) + throw std::system_error(err, std::system_category(), + "Failed to set thread sigmask"); + threads.push_back(thread(_do_bench, secs, name, ref(ioctx), results)); + + if ((err = pthread_sigmask(SIG_SETMASK, &old_set, NULL))) + throw std::system_error(err, std::system_category(), + "Failed to restore thread sigmask"); } for (auto &th : threads) @@ -177,104 +198,6 @@ static void do_bench(unsigned int secs, const vector &names, print_breakdown(summary, names.size()); } -class RadosUtils { -public: - RadosUtils(Rados *rados_) - : rados(rados_), json_reader(Json::Features::strictMode()) {} - - unsigned int get_obj_acting_primary(const string &name, const string &pool) { - - Json::Value cmd(Json::objectValue); - cmd["prefix"] = "osd map"; - cmd["object"] = name; - cmd["pool"] = pool; - - auto &&location = do_mon_command(cmd); - - const auto &acting_primary = location["acting_primary"]; - if (!acting_primary.isNumeric()) - throw "Failed to get acting_primary"; - - return acting_primary.asUInt(); - } - - map get_osd_location(unsigned int osd) { - Json::Value cmd(Json::objectValue); - cmd["prefix"] = "osd find"; - cmd["id"] = osd; - - auto &&location = do_mon_command(cmd); - const auto &crush = location["crush_location"]; - - map result; - - for (auto &&it = crush.begin(); it != crush.end(); ++it) { - result[it.name()] = it->asString(); - } - - result["osd"] = "osd." + to_string(osd); - - return result; - } - - set get_osds(const string &pool) { - Json::Value cmd(Json::objectValue); - cmd["prefix"] = "pg ls-by-pool"; - cmd["poolstr"] = pool; - - const auto &&pgs = do_mon_command(cmd); - - set osds; - - // TODO: - // auto const & x: container - // https://stackoverflow.com/questions/27307373/c-how-to-create-iterator-over-one-field-of-a-struct-vector - for (const auto &pg : pgs) { - const auto &primary = pg["acting_primary"]; - if (!primary.isNumeric()) - throw "Failed to get acting_primary"; - osds.insert(primary.asUInt()); - } - - return osds; - } - - unsigned int get_pool_size(const string &pool) { - Json::Value cmd(Json::objectValue); - cmd["prefix"] = "osd pool get"; - cmd["pool"] = pool; - cmd["var"] = "size"; - - const auto &&v = do_mon_command(cmd); - - return v["size"].asUInt(); - } - -private: - Json::Value do_mon_command(Json::Value &cmd) { - int err; - bufferlist outbl; - string outs; - cmd["format"] = "json"; - bufferlist inbl; - if ((err = rados->mon_command(json_writer.write(cmd), inbl, &outbl, - &outs)) < 0) { - cerr << "mon_command error: " << outs << endl; - throw "mon_command error"; - } - - Json::Value root; - if (!json_reader.parse(outbl.to_str(), root)) - throw "JSON parse error"; - - return root; - } - - Rados *rados; - Json::Reader json_reader; - Json::FastWriter json_writer; -}; - static void _main(int argc, const char *argv[]) { struct { string pool; @@ -285,7 +208,6 @@ static void _main(int argc, const char *argv[]) { } settings; Rados rados; - int err; if ((err = rados.init("admin")) < 0) { cerr << "Failed to init: " << strerror(-err) << endl; @@ -329,90 +251,87 @@ static void _main(int argc, const char *argv[]) { // https://tracker.ceph.com/issues/24114 this_thread::sleep_for(milliseconds(100)); - auto rados_utils = RadosUtils(&rados); + try { + auto rados_utils = RadosUtils(&rados); - if (rados_utils.get_pool_size(settings.pool) != 1) - throw "It's required to have pool size 1"; + if (rados_utils.get_pool_size(settings.pool) != 1) + throw "It's required to have pool size 1"; - map> osd2location; + map> osd2location; - set bench_items; // node1, node2 ||| osd.1, osd.2, osd.3 + set bench_items; // node1, node2 ||| osd.1, osd.2, osd.3 - for (const auto &osd : rados_utils.get_osds(settings.pool)) { - const auto &location = rados_utils.get_osd_location(osd); + for (const auto &osd : rados_utils.get_osds(settings.pool)) { + const auto &location = rados_utils.get_osd_location(osd); - // TODO: do not fill this map if specific_bench_item specified - osd2location[osd] = location; + // TODO: do not fill this map if specific_bench_item specified + osd2location[osd] = location; - const auto &qwe = location.at(settings.mode); - if (settings.specific_bench_item.empty() || - qwe == settings.specific_bench_item) { - bench_items.insert(qwe); - } - } - - // benchitem -> [name1, name2] ||| i.e. "osd.2" => ["obj1", "obj2"] - map> name2location; - unsigned int cnt = 0; - - // for each bench_item find thread_count names. - // store every name in name2location = [bench_item, names, description] - const string prefix = "bench_"; - while (bench_items.size()) { - string name = prefix + to_string(++cnt); - - unsigned int osd = rados_utils.get_obj_acting_primary(name, settings.pool); - - const auto &location = osd2location.at(osd); - const auto &bench_item = location.at(settings.mode); - if (!bench_items.count(bench_item)) - continue; - - auto &names = name2location[bench_item]; - if (names.size() == settings.threads) { - bench_items.erase(bench_item); - continue; + const auto &qwe = location.at(settings.mode); + if (settings.specific_bench_item.empty() || + qwe == settings.specific_bench_item) { + bench_items.insert(qwe); + } } - names.push_back(name); + // benchitem -> [name1, name2] ||| i.e. "osd.2" => ["obj1", "obj2"] + map> name2location; + unsigned int cnt = 0; - cout << name << " - " << bench_item << endl; - } - - IoCtx ioctx; - // TODO: cleanup - /* - * NOTE: be sure to call watch_flush() prior to destroying any IoCtx - * that is used for watch events to ensure that racing callbacks - * have completed. - */ - - if (rados.ioctx_create(settings.pool.c_str(), ioctx) < 0) - throw "Failed to create ioctx"; - - for (const auto &p : name2location) { - const auto &bench_item = p.first; - const auto &obj_names = p.second; - cout << "Benching " << settings.mode << " " << bench_item << endl; - do_bench(settings.secs, obj_names, ioctx); + // for each bench_item find thread_count names. + // store every name in name2location = [bench_item, names, description] + const string prefix = "bench_"; + while (bench_items.size()) { + string name = prefix + to_string(++cnt); + + unsigned int osd = + rados_utils.get_obj_acting_primary(name, settings.pool); + + const auto &location = osd2location.at(osd); + const auto &bench_item = location.at(settings.mode); + if (!bench_items.count(bench_item)) + continue; + + auto &names = name2location[bench_item]; + if (names.size() == settings.threads) { + bench_items.erase(bench_item); + continue; + } + + names.push_back(name); + + cout << name << " - " << bench_item << endl; + } + + IoCtx ioctx; + + if (rados.ioctx_create(settings.pool.c_str(), ioctx) < 0) + throw "Failed to create ioctx"; + + for (const auto &p : name2location) { + const auto &bench_item = p.first; + const auto &obj_names = p.second; + cout << "Benching " << settings.mode << " " << bench_item << endl; + do_bench(settings.secs, obj_names, ioctx); + } + } catch (...) { + rados.watch_flush(); + throw; } + rados.watch_flush(); } int main(int argc, const char *argv[]) { - /* - * IoCtx p; - * rados.ioctx_create("my_pool", p); - * p->stat(&stats); - - - */ try { + setup_signal_handlers(); _main(argc, argv); + } catch (const AbortException &msg) { + cerr << "Test aborted" << endl; + return 1; } catch (const char *msg) { cerr << "Unhandled exception: " << msg << endl; - return 1; + return 2; } - cout << "Exiting successfully." << endl; return 0; } diff --git a/main.py b/main.py deleted file mode 100755 index b562987..0000000 --- a/main.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/python3 -# coding: utf-8 - -import json -import logging -import os -import signal -import sys -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter -from itertools import cycle -from threading import Thread - -import rados - -if sys.version_info >= (3, 0): - from time import monotonic, sleep -else: - from time import time as monotonic, sleep - -log = logging.getLogger(__name__) - -DO_ABORT = False - - -def _do_bench(secs, name, ioctx, data): - ops = [] - data = cycle(data) - b = monotonic() - stop = b + secs - try: - while not DO_ABORT and b <= stop: - ioctx.write(name, next(data)) - b2 = monotonic() - ops.append(b2 - b) - b = b2 - finally: - try: - log.debug('Removing object %s.', name) - ioctx.remove_object(name) - except Exception as e: - log.error('Failed to remove object %s: %r', name, e) - return ops - - -def signal_handler(*args): - global DO_ABORT - log.info('Aborted by signal.') - DO_ABORT = True - - -def do_bench(secs, object_names, ioctx, data): - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - try: - results = {} - thrds = { - # Appending is thread safe ? - Thread( - target=lambda name2: results.setdefault(name2, _do_bench(secs, name2, ioctx, data)), - args=(name,) - ) - for name in object_names - } - - for i in thrds: - i.start() - - # Thread.join() is not signal-interruptible (!) - while thrds: - for i in list(thrds): - i.join(1) - if not i.is_alive(): - thrds.remove(i) - finally: - signal.signal(signal.SIGINT, signal.SIG_DFL) - signal.signal(signal.SIGTERM, signal.SIG_DFL) - - if DO_ABORT: - raise RuntimeError('Aborted') - - return results - - -def get_pool_size(cluster, pool): - (ret, outbuf, outs) = cluster.mon_command( - json.dumps({ - "prefix": "osd pool get", - "pool": pool, - "format": "json", - "var": "size", - }), - '', - 0 - ) - if ret: - raise RuntimeError(outs) - result = json.loads(outbuf.decode('utf-8')) - return result['size'] - - -def get_osds(cluster, pool): - (ret, outbuf, outs) = cluster.mgr_command( - json.dumps({ - "prefix": "pg ls-by-pool", - "poolstr": pool, - "target": ["mgr", ""], - "format": "json", - }), - '', - 0 - ) - if ret: - raise RuntimeError(outs) - result = json.loads(outbuf.decode('utf-8')) - return {i['acting_primary'] for i in result} - - -def get_osd_location(cluster, osd): - (ret, outbuf, outs) = cluster.mon_command( - json.dumps({ - "prefix": "osd find", - "id": osd, - "format": "json", - }), - '', - 0 - ) - if ret: - raise RuntimeError(outs) - result = json.loads(outbuf.decode('utf-8')) - result = result['crush_location'] - result['osd'] = osd - log.debug('Location of OSD %r is %r.', osd, result) - return result - - -def get_obj_acting_primary(cluster, pool, name): - (ret, outbuf, outs) = cluster.mon_command( - json.dumps({ - "prefix": "osd map", - "object": name, - "pool": pool, - "format": "json", - }), - '', - 0 - ) - if ret: - raise RuntimeError(outs) - result = json.loads(outbuf.decode('utf-8')) - acting_primary = result['acting_primary'] - log.debug('Acting primary OSD %r (for object %r).', acting_primary, name) - return acting_primary - - -def get_description(cluster, location): - osd = location['osd'] - (ret, outbuf, outs) = cluster.mon_command( - json.dumps({ - "prefix": "osd metadata", - "id": osd, - "format": "json", - }), - '', - 0 - ) - if ret: - raise RuntimeError(outs) - result = json.loads(outbuf.decode('utf-8')) - - if result["osd_objectstore"] == 'filestore': - x = [ - 'jrn=%s' % ('hdd' if int(result["journal_rotational"]) else 'ssd'), - 'dat=%s' % ('hdd' if int(result["rotational"]) else 'ssd'), - ] - elif result["osd_objectstore"] == 'bluestore': - x = [ - 'db=%s(%s)' % (result['bluefs_db_type'], result["bluefs_db_model"].rstrip()), - 'dat=%s(%s)' % (result['bluestore_bdev_type'], result["bluestore_bdev_model"].rstrip()), - ] - else: - x = [] - - return ' '.join( - [ - 'r=%s,h=%s,osd.%s' % (location['root'], location['host'], osd), - result["osd_objectstore"], - ] + x + [result['cpu']] - ) - - -def main(): - parser = ArgumentParser( - description="Socketair Ceph tester. You should create pool of size 1 and provide a keyring file with user having rights to write to this pool.", - formatter_class=ArgumentDefaultsHelpFormatter, - epilog="For all questions contact Коренберг Марк and/or Telegram user @socketpair, as well as @socketpair on GitHub." - ) - parser.add_argument('--debug', action='store_true', help='Enable debug mode.') - parser.add_argument('--duration', type=int, default=10, help='Time limit for each test.', metavar='SECONDS') - parser.add_argument('--bigsize', type=int, default=4 * 1024 * 1024, help='Size of object for linear write.', - metavar='BYTES') - parser.add_argument('--smallsize', type=int, default=1, help='Size of object for linear IOPS write test.', - metavar='BYTES') - parser.add_argument('--keyring', type=str, default='./keyring.conf', help='Path to keyring file.', metavar='PATH') - parser.add_argument('--threads', type=int, default=1, - help='Parallel testing using multiple threads and different object in each.', metavar='COUNT') - parser.add_argument('pool', help='Ceph pool name.') - parser.add_argument('mode', default='host', - help='Test item selection. Possible values: any key from crush location, e.g. "host", "root". And also special value "osd" to test each OSD.') - - params = parser.parse_args() - - logging.basicConfig( - level=logging.DEBUG if params.debug else logging.INFO, - format='%(levelname)s:%(name)s:%(message)s' if params.debug else '%(message)s', - ) - conf = {'keyring': params.keyring} - pool = params.pool - mode = params.mode - secs = params.duration - bigsize = params.bigsize - smallsize = params.smallsize - threads_count = params.threads - - bigdata = [os.urandom(bigsize), os.urandom(bigsize)] - smalldata = [os.urandom(smallsize), os.urandom(smallsize)] - - if smalldata[0] == smalldata[1]: - raise ValueError('You are looser.') - - log.info('Attaching to CEPH cluster. pool=%s', pool) - with rados.Rados(conffile='/etc/ceph/ceph.conf', conf=conf) as cluster: - sleep(0.1) # https://tracker.ceph.com/issues/24114 - - log.debug('Checking that pool %r size is 1.', pool) - if get_pool_size(cluster, pool) != 1: - raise RuntimeError('Pool %r size must be 1.' % pool) - - log.debug('Getting list of OSDs for pool %r.', pool) - osds = sorted(get_osds(cluster, pool)) - log.debug('Total OSDs in this pool: %d.', len(osds)) - - log.info('Getting OSD locations.') - osd2location = {osd: get_osd_location(cluster, osd) for osd in osds} - - bench_items = set(v[mode] for v in osd2location.values()) - totlen = len(bench_items) - log.info('Figuring out object names for %d %ss.', totlen, mode) - name2location = {} # benchitem -> ([name1, name2], description) - cnt = 0 - foundcnt = 0 - - while bench_items: - cnt += 1 - name = 'bench_%d' % cnt - - osd = get_obj_acting_primary(cluster, pool, name) - location = osd2location[osd] - bench_item = location[mode] - - if bench_item not in bench_items: - continue - - foundcnt += 1 - xxx = name2location.get(bench_item) - if xxx is None: - xxx = [[name], get_description(cluster, location) if threads_count == 1 else '*multiple*'] - name2location[bench_item] = xxx - else: - xxx[0].append(name) - - if len(xxx[0]) == threads_count: - bench_items.remove(bench_item) - - log.info('Found %d/%d', foundcnt, totlen * threads_count) - - name2location = sorted( - ( - (bench_item, names, descr) - for bench_item, (names, descr) in name2location.items() - ), - key=lambda i: i[0] - ) - - log.debug('Opening IO context for pool %s. Each benchmark will last %d secs.', pool, secs) - with cluster.open_ioctx(pool) as ioctx: - log.info('Start write IOPS benchmarking of %d %ss with %d thread(s).', len(name2location), mode, - threads_count) - for (bench_item, names, description) in name2location: - log.debug('Benchmarking write IOPS on %r', bench_item) - # { 'name1': [1.2, 3.4, 5.6, ...], 'name2': [...], ...} - results = do_bench(secs, names, ioctx, smalldata) - - latencies = [] - for v in results.values(): - latencies.extend(v) - latencies.sort() - - elapsed = max(sum(v) for v in results.values()) - ops = sum(len(v) for v in results.values()) - - iops = ops / elapsed - log.info( - '%s %r: %2.2f IOPS (%2.2f ops), minlat=%.4f ms, maxlat=%.4f ms. %s.', - mode, - bench_item, - iops, - ops, - latencies[0] * 1000, - latencies[-1] * 1000, - description, - ) - - log.info('Start Linear write benchmarking of %d %ss. blocksize=%d MiB with %d thread(s).', - len(name2location), mode, - bigsize // (1024 * 1024), threads_count) - for (bench_item, names, description) in name2location: - log.debug('Benchmarking Linear write on %r', bench_item) - results = do_bench(secs, names, ioctx, bigdata) - - elapsed = max(sum(v) for v in results.values()) - ops = sum(len(v) for v in results.values()) - - bsec = ops * bigsize / elapsed - log.info( - '%s %r: %2.2f MB/sec (%2.2f Mbit/s). %s.', - mode, - bench_item, - bsec / 1000000, - bsec * 8 / 1000000, - description, - ) - - -if __name__ == '__main__': - main() diff --git a/mysignals.cpp b/mysignals.cpp new file mode 100644 index 0000000..246b56f --- /dev/null +++ b/mysignals.cpp @@ -0,0 +1,16 @@ +#include + +#include "mysignals.h" +static volatile std::sig_atomic_t gSignalStatus; + +static void signal_handler(int signal) { gSignalStatus = signal; } + +void setup_signal_handlers() { + std::signal(SIGINT, signal_handler); + std::signal(SIGTERM, signal_handler); +} + +void abort_if_signalled() { + if (gSignalStatus) + throw AbortException(); +} diff --git a/mysignals.h b/mysignals.h new file mode 100644 index 0000000..ccf9fe5 --- /dev/null +++ b/mysignals.h @@ -0,0 +1,11 @@ +#ifndef MYSIGNALS_H +#define MYSIGNALS_H + +#include + +void setup_signal_handlers(); + +class AbortException : public std::exception {}; + +void abort_if_signalled(); +#endif diff --git a/radosutil.cpp b/radosutil.cpp new file mode 100644 index 0000000..c54ab35 --- /dev/null +++ b/radosutil.cpp @@ -0,0 +1,101 @@ +#include + +#include +#include + +#include "radosutil.h" + +using namespace std; +using namespace librados; + +RadosUtils::RadosUtils(Rados *rados_) + : rados(rados_), /* */ + json_reader(new Json::Reader(Json::Features::strictMode())), /* */ + json_writer(new Json::FastWriter()) /* */ +{} + +unsigned int RadosUtils::get_obj_acting_primary(const string &name, + const string &pool) { + + Json::Value cmd(Json::objectValue); + cmd["prefix"] = "osd map"; + cmd["object"] = name; + cmd["pool"] = pool; + + auto &&location = do_mon_command(cmd); + + const auto &acting_primary = location["acting_primary"]; + if (!acting_primary.isNumeric()) + throw "Failed to get acting_primary"; + + return acting_primary.asUInt(); +} + +// TODO: std::map copying ? return unique_ptr ? +map RadosUtils::get_osd_location(unsigned int osd) { + Json::Value cmd(Json::objectValue); + cmd["prefix"] = "osd find"; + cmd["id"] = osd; + + auto &&location = do_mon_command(cmd); + const auto &crush = location["crush_location"]; + + map result; + + for (auto &&it = crush.begin(); it != crush.end(); ++it) { + result[it.name()] = it->asString(); + } + + result["osd"] = "osd." + to_string(osd); + + return result; +} + +// todo: std::set copying +set RadosUtils::get_osds(const string &pool) { + Json::Value cmd(Json::objectValue); + cmd["prefix"] = "pg ls-by-pool"; + cmd["poolstr"] = pool; + + const auto &&pgs = do_mon_command(cmd); + + set osds; + + for (const auto &pg : pgs) { + const auto &primary = pg["acting_primary"]; + if (!primary.isNumeric()) + throw "Failed to get acting_primary"; + osds.insert(primary.asUInt()); + } + + return osds; +} + +unsigned int RadosUtils::get_pool_size(const string &pool) { + Json::Value cmd(Json::objectValue); + cmd["prefix"] = "osd pool get"; + cmd["pool"] = pool; + cmd["var"] = "size"; + + const auto &&v = do_mon_command(cmd); + + return v["size"].asUInt(); +} + +Json::Value RadosUtils::do_mon_command(Json::Value &cmd) { + int err; + bufferlist outbl; + string outs; + cmd["format"] = "json"; + bufferlist inbl; + + if ((err = rados->mon_command(json_writer->write(cmd), inbl, &outbl, &outs)) < + 0) + throw MyRadosException(err, outs); + + Json::Value root; + if (!json_reader->parse(outbl.to_str(), root)) + throw "JSON parse error"; + + return root; +} diff --git a/radosutil.h b/radosutil.h new file mode 100644 index 0000000..fda4a2a --- /dev/null +++ b/radosutil.h @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +namespace Json { +class Reader; +class FastWriter; +class Value; +} // namespace Json + +namespace librados { +class Rados; +} + +class RadosUtils { +public: + RadosUtils(librados::Rados *rados_); + unsigned int get_obj_acting_primary(const std::string &name, + const std::string &pool); + std::map get_osd_location(unsigned int osd); + std::set get_osds(const std::string &pool); + unsigned int get_pool_size(const std::string &pool); + +private: + Json::Value do_mon_command(Json::Value &cmd); + librados::Rados *rados; + std::unique_ptr json_reader; + std::unique_ptr json_writer; +}; + +class MyRadosException : public std::exception { +public: + MyRadosException(int err, const std::string &msg) + : descr("Rados err " + std::to_string(err) + ": " + msg){}; + const char *what() const throw() { return descr.c_str(); } + +private: + std::string descr; +};