240 lines
8.7 KiB
C++
240 lines
8.7 KiB
C++
// Copyright (c) Vitaliy Filippov, 2019+
|
|
// License: VNPL-1.1 (see README.md for details)
|
|
|
|
#include "cli.h"
|
|
#include "cluster_client.h"
|
|
#include "str_util.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_id_t, json11::Json::object> pool_stats;
|
|
|
|
bool is_done()
|
|
{
|
|
return state == 100;
|
|
}
|
|
|
|
void get_stats()
|
|
{
|
|
if (state == 1)
|
|
goto resume_1;
|
|
// Space statistics - pool/stats/<pool>
|
|
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<pool_id_t, uint64_t> 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/<N>
|
|
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/<N>::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<osd_num_t, uint64_t> 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" : "ec" },
|
|
{ "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<bool(cli_result_t &)> 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;
|
|
};
|
|
}
|