diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 8679af770a3..455295114a6 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -163,6 +163,13 @@ Commands -l, also show snapshots, and use longer-format output including size, parent (if clone), format, etc. +:command:`du` [--image *image-name*] [*pool-name*] + Will calculate the provisioned and actual disk usage of all images and + associated snapshots within the specified pool. It can also be used against + individual images. + + If the RBD fast-diff feature isn't enabled on images, this operation will + require querying the OSDs for every potential object within the image. :command:`info` [*image-name*] Will dump information (such as size and order) about a specific rbd image. If image is a clone, information about its parent is also displayed. diff --git a/src/rbd.cc b/src/rbd.cc index e50eeb30c7c..4355c4f0847 100644 --- a/src/rbd.cc +++ b/src/rbd.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,8 @@ void usage() "where 'pool' is a rados pool name (default is 'rbd') and 'cmd' is one of:\n" " (ls | list) [-l | --long ] [pool-name] list rbd images\n" " (-l includes snapshots/clones)\n" +" (du | disk-usage) [--image ] [pool-name]\n" +" show pool image disk usage stats\n" " info show information about image size,\n" " striping, etc.\n" " create [--order ] [--image-features ] [--image-shared]\n" @@ -2551,6 +2554,198 @@ static int parse_map_options(char *options) return 0; } +static int disk_usage_callback(uint64_t offset, size_t len, int exists, + void *arg) { + uint64_t *used_size = reinterpret_cast(arg); + if (exists) { + (*used_size) += len; + } + return 0; +} + +static int compute_image_disk_usage(const std::string& name, + const std::string& snap_name, + const std::string& from_snap_name, + librbd::Image &image, uint64_t size, + TextTable& tbl, Formatter *f, + uint64_t *used_size) { + const char* from = NULL; + if (!from_snap_name.empty()) { + from = from_snap_name.c_str(); + } + + uint64_t flags; + int r = image.get_flags(&flags); + if (r < 0) { + cerr << "rbd: failed to retrieve image flags: " << cpp_strerror(r) + << std::endl; + return r; + } + if ((flags & RBD_FLAG_FAST_DIFF_INVALID) != 0) { + cerr << "warning: fast-diff map is invalid for " << name + << (snap_name.empty() ? "" : "@" + snap_name) << ". " + << "operation may be slow." << std::endl; + } + + *used_size = 0; + r = image.diff_iterate2(from, 0, size, false, true, + &disk_usage_callback, used_size); + if (r < 0) { + cerr << "rbd: failed to iterate diffs: " << cpp_strerror(r) << std::endl; + return r; + } + + if (f) { + f->open_object_section("image"); + f->dump_string("name", name); + if (!snap_name.empty()) { + f->dump_string("snapshot", snap_name); + } + f->dump_unsigned("provisioned_size", size); + f->dump_unsigned("used_size" , *used_size); + f->close_section(); + } else { + std::string full_name = name; + if (!snap_name.empty()) { + full_name += "@" + snap_name; + } + tbl << full_name + << stringify(si_t(size)) + << stringify(si_t(*used_size)) + << TextTable::endrow; + } + return 0; +} + +static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx, + const char *imgname, const char *snapname, + Formatter *f) { + std::vector names; + int r = rbd.list(io_ctx, names); + if (r == -ENOENT) { + r = 0; + } else if (r < 0) { + return r; + } + + TextTable tbl; + if (f) { + f->open_object_section("stats"); + f->open_array_section("images"); + } else { + tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("PROVISIONED", TextTable::RIGHT, TextTable::RIGHT); + tbl.define_column("USED", TextTable::RIGHT, TextTable::RIGHT); + } + + uint64_t used_size = 0; + uint64_t total_prov = 0; + uint64_t total_used = 0; + std::sort(names.begin(), names.end()); + for (std::vector::const_iterator name = names.begin(); + name != names.end(); ++name) { + if (imgname != NULL && *name != imgname) { + continue; + } + + librbd::Image image; + r = rbd.open_read_only(io_ctx, image, name->c_str(), NULL); + if (r < 0) { + if (r != -ENOENT) { + cerr << "rbd: error opening " << *name << ": " << cpp_strerror(r) + << std::endl; + } + continue; + } + + uint64_t features; + int r = image.features(&features); + if (r < 0) { + cerr << "rbd: failed to retrieve image features: " << cpp_strerror(r) + << std::endl; + return r; + } + if ((features & RBD_FEATURE_FAST_DIFF) == 0) { + cerr << "warning: fast-diff map is not enabled for " << *name << ". " + << "operation may be slow." << std::endl; + } + + librbd::image_info_t info; + if (image.stat(info, sizeof(info)) < 0) { + return -EINVAL; + } + + std::vector snap_list; + r = image.snap_list(snap_list); + if (r < 0) { + cerr << "rbd: error opening " << *name << " snapshots: " + << cpp_strerror(r) << std::endl; + continue; + } + + std::string last_snap_name; + std::sort(snap_list.begin(), snap_list.end(), + boost::bind(&librbd::snap_info_t::id, _1) < + boost::bind(&librbd::snap_info_t::id, _2)); + for (std::vector::const_iterator snap = + snap_list.begin(); snap != snap_list.end(); ++snap) { + librbd::Image snap_image; + r = rbd.open_read_only(io_ctx, snap_image, name->c_str(), + snap->name.c_str()); + if (r < 0) { + cerr << "rbd: error opening snapshot " << *name << "@" + << snap->name << ": " << cpp_strerror(r) << std::endl; + return r; + } + + if (imgname == NULL || (snapname != NULL && snap->name == snapname)) { + r = compute_image_disk_usage(*name, snap->name, last_snap_name, + snap_image, snap->size, tbl, f, + &used_size); + if (r < 0) { + return r; + } + + if (snapname != NULL) { + total_prov += snap->size; + } + total_used += used_size; + } + last_snap_name = snap->name; + } + + if (snapname == NULL) { + r = compute_image_disk_usage(*name, "", last_snap_name, image, info.size, + tbl, f, &used_size); + if (r < 0) { + return r; + } + total_prov += info.size; + total_used += used_size; + } + } + + if (f) { + f->close_section(); + if (imgname == NULL) { + f->dump_unsigned("total_provisioned_size", total_prov); + f->dump_unsigned("total_used_size", total_used); + } + f->close_section(); + f->flush(cout); + } else { + if (imgname == NULL) { + tbl << "" + << stringify(si_t(total_prov)) + << stringify(si_t(total_used)) + << TextTable::endrow; + } + cout << tbl; + } + + return 0; +} + enum CommandType{ COMMAND_TYPE_NONE, COMMAND_TYPE_SNAP, @@ -2600,7 +2795,8 @@ enum { OPT_METADATA_SET, OPT_METADATA_GET, OPT_METADATA_REMOVE, - OPT_OBJECT_MAP_REBUILD + OPT_OBJECT_MAP_REBUILD, + OPT_DISK_USAGE }; static int get_cmd(const char *cmd, CommandType command_type) @@ -2611,6 +2807,9 @@ static int get_cmd(const char *cmd, CommandType command_type) if (strcmp(cmd, "ls") == 0 || strcmp(cmd, "list") == 0) return OPT_LIST; + if (strcmp(cmd, "du") == 0 || + strcmp(cmd, "disk-usage") == 0) + return OPT_DISK_USAGE; if (strcmp(cmd, "info") == 0) return OPT_INFO; if (strcmp(cmd, "create") == 0) @@ -2965,6 +3164,7 @@ if (!set_conf_param(v, p1, p2, p3)) { \ const char *v = *i; switch (opt_cmd) { case OPT_LIST: + case OPT_DISK_USAGE: SET_CONF_PARAM(v, &poolname, NULL, NULL); break; case OPT_INFO: @@ -3069,7 +3269,8 @@ if (!set_conf_param(v, p1, p2, p3)) { \ opt_cmd != OPT_INFO && opt_cmd != OPT_LIST && opt_cmd != OPT_SNAP_LIST && opt_cmd != OPT_LOCK_LIST && opt_cmd != OPT_CHILDREN && opt_cmd != OPT_DIFF && - opt_cmd != OPT_METADATA_LIST && opt_cmd != OPT_STATUS) { + opt_cmd != OPT_METADATA_LIST && opt_cmd != OPT_STATUS && + opt_cmd != OPT_DISK_USAGE) { cerr << "rbd: command doesn't use output formatting" << std::endl; return EXIT_FAILURE; @@ -3118,7 +3319,8 @@ if (!set_conf_param(v, p1, p2, p3)) { \ opt_cmd != OPT_IMPORT_DIFF && opt_cmd != OPT_UNMAP && /* needs imgname but handled below */ opt_cmd != OPT_SHOWMAPPED && - opt_cmd != OPT_MERGE_DIFF && !imgname) { + opt_cmd != OPT_MERGE_DIFF && + opt_cmd != OPT_DISK_USAGE && !imgname) { cerr << "rbd: image name was not specified" << std::endl; return EXIT_FAILURE; } @@ -3177,7 +3379,8 @@ if (!set_conf_param(v, p1, p2, p3)) { \ opt_cmd != OPT_DIFF && opt_cmd != OPT_COPY && opt_cmd != OPT_MAP && opt_cmd != OPT_UNMAP && opt_cmd != OPT_CLONE && opt_cmd != OPT_SNAP_PROTECT && opt_cmd != OPT_SNAP_UNPROTECT && - opt_cmd != OPT_CHILDREN && opt_cmd != OPT_OBJECT_MAP_REBUILD) { + opt_cmd != OPT_CHILDREN && opt_cmd != OPT_OBJECT_MAP_REBUILD && + opt_cmd != OPT_DISK_USAGE) { cerr << "rbd: snapname specified for a command that doesn't use it" << std::endl; return EXIT_FAILURE; @@ -3291,13 +3494,13 @@ if (!set_conf_param(v, p1, p2, p3)) { \ opt_cmd == OPT_METADATA_SET || opt_cmd == OPT_METADATA_LIST || opt_cmd == OPT_METADATA_REMOVE || opt_cmd == OPT_METADATA_GET || opt_cmd == OPT_FEATURE_DISABLE || opt_cmd == OPT_FEATURE_ENABLE || - opt_cmd == OPT_OBJECT_MAP_REBUILD)) { + opt_cmd == OPT_OBJECT_MAP_REBUILD || opt_cmd == OPT_DISK_USAGE)) { if (opt_cmd == OPT_INFO || opt_cmd == OPT_SNAP_LIST || opt_cmd == OPT_EXPORT || opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY || opt_cmd == OPT_CHILDREN || opt_cmd == OPT_LOCK_LIST || - opt_cmd == OPT_METADATA_LIST || - opt_cmd == OPT_STATUS || opt_cmd == OPT_WATCH) { + opt_cmd == OPT_METADATA_LIST || opt_cmd == OPT_STATUS || + opt_cmd == OPT_WATCH || opt_cmd == OPT_DISK_USAGE) { r = rbd.open_read_only(io_ctx, image, imgname, NULL); } else { r = rbd.open(io_ctx, image, imgname); @@ -3316,7 +3519,8 @@ if (!set_conf_param(v, p1, p2, p3)) { \ opt_cmd == OPT_DIFF || opt_cmd == OPT_COPY || opt_cmd == OPT_CHILDREN || - opt_cmd == OPT_OBJECT_MAP_REBUILD)) { + opt_cmd == OPT_OBJECT_MAP_REBUILD || + opt_cmd == OPT_DISK_USAGE)) { r = image.snap_set(snapname); if (r < 0) { cerr << "rbd: error setting snapshot context: " << cpp_strerror(-r) @@ -3751,6 +3955,14 @@ if (!set_conf_param(v, p1, p2, p3)) { \ return -r; } break; + + case OPT_DISK_USAGE: + r = do_disk_usage(rbd, io_ctx, imgname, snapname, formatter.get()); + if (r < 0) { + cerr << "du failed: " << cpp_strerror(-r) << std::endl; + return -r; + } + break; } return 0; } diff --git a/src/test/cli-integration/rbd/formatted-output.t b/src/test/cli-integration/rbd/formatted-output.t index 61ba9d18eb6..846760b0a36 100644 --- a/src/test/cli-integration/rbd/formatted-output.t +++ b/src/test/cli-integration/rbd/formatted-output.t @@ -31,6 +31,7 @@ clone $ rbd clone bar@snap rbd_other/child $ rbd snap create rbd_other/child@snap $ rbd flatten rbd_other/child 2> /dev/null + $ rbd bench-write rbd_other/child --io-pattern seq --io-total 1 > /dev/null 2>&1 lock ==== @@ -676,6 +677,50 @@ whenever it is run. grep -v to ignore it, but still work on other distros. 536870912 + $ rbd disk-usage rbd_other + warning: fast-diff map is not enabled for child. operation may be slow. + NAME PROVISIONED USED + child@snap 512M 0 + child 512M 4096k + 512M 4096k + $ rbd disk-usage rbd_other --format json | python -mjson.tool | sed 's/,$/, /' + warning: fast-diff map is not enabled for child. operation may be slow. + { + "images": [ + { + "name": "child", + "provisioned_size": 536870912, + "snapshot": "snap", + "used_size": 0 + }, + { + "name": "child", + "provisioned_size": 536870912, + "used_size": 4194304 + } + ], + "total_provisioned_size": 536870912, + "total_used_size": 4194304 + } + $ rbd disk-usage rbd_other --format xml | xml_pp 2>&1 | grep -v '^new version at /usr/bin/xml_pp' + warning: fast-diff map is not enabled for child. operation may be slow. + + + + child + snap + 536870912 + 0 + + + child + 536870912 + 4194304 + + + 536870912 + 4194304 + # cleanup $ rbd snap remove rbd_other/child@snap diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index a9b721cfb33..80806ed7f00 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -3,6 +3,8 @@ where 'pool' is a rados pool name (default is 'rbd') and 'cmd' is one of: (ls | list) [-l | --long ] [pool-name] list rbd images (-l includes snapshots/clones) + (du | disk-usage) [--image ] [pool-name] + show pool image disk usage stats info show information about image size, striping, etc. create [--order ] [--image-features ] [--image-shared]