// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) #include "cli.h" #include "cluster_client.h" #include "base64.h" // List pools with space statistics struct pool_lister_t { cli_tool_t *parent; int state = 0; json11::Json space_info; cli_result_t result; std::map pool_stats; bool is_done() { return state == 100; } void get_stats() { if (state == 1) goto resume_1; // Space statistics - pool/stats/ parent->etcd_txn(json11::Json::object { { "success", json11::Json::array { json11::Json::object { { "request_range", json11::Json::object { { "key", base64_encode( parent->cli->st_cli.etcd_prefix+"/pool/stats/" ) }, { "range_end", base64_encode( parent->cli->st_cli.etcd_prefix+"/pool/stats0" ) }, } }, }, json11::Json::object { { "request_range", json11::Json::object { { "key", base64_encode( parent->cli->st_cli.etcd_prefix+"/osd/stats/" ) }, { "range_end", base64_encode( parent->cli->st_cli.etcd_prefix+"/osd/stats0" ) }, } }, }, } }, }); state = 1; resume_1: if (parent->waiting > 0) return; if (parent->etcd_err.err) { result = parent->etcd_err; state = 100; return; } space_info = parent->etcd_result; std::map osd_free; for (auto & kv_item: space_info["responses"][0]["response_range"]["kvs"].array_items()) { auto kv = parent->cli->st_cli.parse_etcd_kv(kv_item); // pool ID pool_id_t pool_id; char null_byte = 0; sscanf(kv.key.substr(parent->cli->st_cli.etcd_prefix.length()).c_str(), "/pool/stats/%u%c", &pool_id, &null_byte); if (!pool_id || pool_id >= POOL_ID_MAX || null_byte != 0) { fprintf(stderr, "Invalid key in etcd: %s\n", kv.key.c_str()); continue; } // pool/stats/ pool_stats[pool_id] = kv.value.object_items(); } for (auto & kv_item: space_info["responses"][1]["response_range"]["kvs"].array_items()) { auto kv = parent->cli->st_cli.parse_etcd_kv(kv_item); // osd ID osd_num_t osd_num; char null_byte = 0; sscanf(kv.key.substr(parent->cli->st_cli.etcd_prefix.length()).c_str(), "/osd/stats/%lu%c", &osd_num, &null_byte); if (!osd_num || osd_num >= POOL_ID_MAX || null_byte != 0) { fprintf(stderr, "Invalid key in etcd: %s\n", kv.key.c_str()); continue; } // osd/stats/::free osd_free[osd_num] = kv.value["free"].uint64_value(); } // Calculate max_avail for each pool for (auto & pp: parent->cli->st_cli.pool_config) { auto & pool_cfg = pp.second; uint64_t pool_avail = UINT64_MAX; std::map pg_per_osd; for (auto & pgp: pool_cfg.pg_config) { for (auto pg_osd: pgp.second.target_set) { if (pg_osd != 0) { pg_per_osd[pg_osd]++; } } } for (auto pg_per_pair: pg_per_osd) { uint64_t pg_free = osd_free[pg_per_pair.first] * pool_cfg.pg_count / pg_per_pair.second; if (pool_avail > pg_free) { pool_avail = pg_free; } } if (pool_avail == UINT64_MAX) { pool_avail = 0; } if (pool_cfg.scheme != POOL_SCHEME_REPLICATED) { uint64_t pg_real_size = pool_stats[pool_cfg.id]["pg_real_size"].uint64_value(); pool_avail = pg_real_size > 0 ? pool_avail * (pool_cfg.pg_size - pool_cfg.parity_chunks) / pg_real_size : 0; } pool_stats[pool_cfg.id] = json11::Json::object { { "name", pool_cfg.name }, { "pg_count", pool_cfg.pg_count }, { "scheme", pool_cfg.scheme == POOL_SCHEME_REPLICATED ? "replicated" : "jerasure" }, { "scheme_name", pool_cfg.scheme == POOL_SCHEME_REPLICATED ? std::to_string(pool_cfg.pg_size)+"/"+std::to_string(pool_cfg.pg_minsize) : "EC "+std::to_string(pool_cfg.pg_size-pool_cfg.parity_chunks)+"+"+std::to_string(pool_cfg.parity_chunks) }, { "used_raw", (uint64_t)(pool_stats[pool_cfg.id]["used_raw_tb"].number_value() * ((uint64_t)1<<40)) }, { "total_raw", (uint64_t)(pool_stats[pool_cfg.id]["total_raw_tb"].number_value() * ((uint64_t)1<<40)) }, { "max_available", pool_avail }, { "raw_to_usable", pool_stats[pool_cfg.id]["raw_to_usable"].number_value() }, { "space_efficiency", pool_stats[pool_cfg.id]["space_efficiency"].number_value() }, { "pg_real_size", pool_stats[pool_cfg.id]["pg_real_size"].uint64_value() }, { "failure_domain", pool_cfg.failure_domain }, }; } } json11::Json::array to_list() { json11::Json::array list; for (auto & kv: pool_stats) { list.push_back(kv.second); } return list; } void loop() { get_stats(); if (parent->waiting > 0) return; if (state == 100) return; if (parent->json_output) { // JSON output result.data = to_list(); state = 100; return; } // Table output: name, scheme_name, pg_count, total, used, max_avail, used%, efficiency json11::Json::array cols; cols.push_back(json11::Json::object{ { "key", "name" }, { "title", "NAME" }, }); cols.push_back(json11::Json::object{ { "key", "scheme_name" }, { "title", "SCHEME" }, }); cols.push_back(json11::Json::object{ { "key", "pg_count" }, { "title", "PGS" }, }); cols.push_back(json11::Json::object{ { "key", "total_fmt" }, { "title", "TOTAL" }, }); cols.push_back(json11::Json::object{ { "key", "used_fmt" }, { "title", "USED" }, }); cols.push_back(json11::Json::object{ { "key", "max_avail_fmt" }, { "title", "AVAILABLE" }, }); cols.push_back(json11::Json::object{ { "key", "used_pct" }, { "title", "USED%" }, }); cols.push_back(json11::Json::object{ { "key", "eff_fmt" }, { "title", "EFFICIENCY" }, }); json11::Json::array list; for (auto & kv: pool_stats) { double raw_to = kv.second["raw_to_usable"].number_value(); if (raw_to < 0.000001 && raw_to > -0.000001) raw_to = 1; kv.second["total_fmt"] = format_size(kv.second["total_raw"].uint64_value() / raw_to); kv.second["used_fmt"] = format_size(kv.second["used_raw"].uint64_value() / raw_to); kv.second["max_avail_fmt"] = format_size(kv.second["max_available"].uint64_value()); kv.second["used_pct"] = format_q(kv.second["total_raw"].uint64_value() ? (100 - 100*kv.second["max_available"].uint64_value() * kv.second["raw_to_usable"].number_value() / kv.second["total_raw"].uint64_value()) : 100)+"%"; kv.second["eff_fmt"] = format_q(kv.second["space_efficiency"].number_value()*100)+"%"; } result.data = to_list(); result.text = print_table(result.data, cols, parent->color); state = 100; } }; std::function cli_tool_t::start_df(json11::Json cfg) { auto lister = new pool_lister_t(); lister->parent = this; return [lister](cli_result_t & result) { lister->loop(); if (lister->is_done()) { result = lister->result; delete lister; return true; } return false; }; }