blob: 0c5dd9b1fc00275024e93651dbc44cd009818855 [file] [log] [blame]
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Microsoft Corporation
*
* -=- Robust Distributed System Nucleus (rDSN) -=-
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "utils/command_manager.h"
// IWYU pragma: no_include <ext/alloc_traits.h>
#include <stdlib.h>
#include <chrono>
#include <limits>
#include <sstream> // IWYU pragma: keep
#include <thread>
#include <utility>
namespace dsn {
std::unique_ptr<command_deregister>
command_manager::register_command(const std::vector<std::string> &commands,
const std::string &help_one_line,
const std::string &help_long,
command_handler handler)
{
auto *c = new command_instance();
c->commands = commands;
c->help_short = help_one_line;
c->help_long = help_long;
c->handler = std::move(handler);
utils::auto_write_lock l(_lock);
for (const auto &cmd : commands) {
CHECK(!cmd.empty(), "should not register empty command");
CHECK(_handlers.emplace(cmd, c).second, "command '{}' already registered", cmd);
}
return std::make_unique<command_deregister>(reinterpret_cast<uintptr_t>(c));
}
std::unique_ptr<command_deregister> command_manager::register_bool_command(
bool &value, const std::string &command, const std::string &help)
{
return register_command({command},
fmt::format("{} <true|false>", command),
help,
[&value, command](const std::vector<std::string> &args) {
return set_bool(value, command, args);
});
}
void command_manager::deregister_command(uintptr_t handle)
{
auto c = reinterpret_cast<command_instance *>(handle);
CHECK_NOTNULL(c, "cannot deregister a null handle");
utils::auto_write_lock l(_lock);
for (const std::string &cmd : c->commands) {
_handlers.erase(cmd);
}
}
bool command_manager::run_command(const std::string &cmd,
const std::vector<std::string> &args,
/*out*/ std::string &output)
{
command_instance *h = nullptr;
{
utils::auto_read_lock l(_lock);
auto it = _handlers.find(cmd);
if (it != _handlers.end())
h = it->second;
}
if (h == nullptr) {
output = std::string("unknown command '") + cmd + "'";
return false;
} else {
output = h->handler(args);
return true;
}
}
std::string command_manager::set_bool(bool &value,
const std::string &name,
const std::vector<std::string> &args)
{
// Query.
if (args.empty()) {
return value ? "true" : "false";
}
// Invalid arguments size.
if (args.size() > 1) {
return fmt::format("ERR: invalid arguments, only one boolean argument is acceptable");
}
// Invalid argument.
bool new_value;
if (!dsn::buf2bool(args[0], new_value, /* ignore_case */ true)) {
return fmt::format("ERR: invalid arguments, '{}' is not a boolean", args[0]);
}
// Set to a new value.
value = new_value;
LOG_INFO("set {} to {} by remote command", name, new_value);
return "OK";
}
command_manager::command_manager()
{
_cmds.emplace_back(register_command({"help", "h", "H", "Help"},
"help|Help|h|H [command] - display help information",
"",
[this](const std::vector<std::string> &args) {
std::stringstream ss;
if (args.size() == 0) {
utils::auto_read_lock l(_lock);
for (const auto &c : this->_handlers) {
ss << c.second->help_short << std::endl;
}
} else {
utils::auto_read_lock l(_lock);
auto it = _handlers.find(args[0]);
if (it == _handlers.end())
ss << "cannot find command '" << args[0] << "'";
else {
ss.width(6);
ss << std::left << it->first << ": "
<< it->second->help_short << std::endl
<< it->second->help_long << std::endl;
}
}
return ss.str();
}));
_cmds.emplace_back(register_command(
{"repeat", "r", "R", "Repeat"},
"repeat|Repeat|r|R interval_seconds max_count command - execute command periodically",
"repeat|Repeat|r|R interval_seconds max_count command - execute command every interval "
"seconds, to the max count as max_count (0 for infinite)",
[this](const std::vector<std::string> &args) {
std::stringstream ss;
if (args.size() < 3) {
return "insufficient arguments";
}
int interval_seconds = atoi(args[0].c_str());
if (interval_seconds <= 0) {
return "invalid interval argument";
}
int max_count = atoi(args[1].c_str());
if (max_count < 0) {
return "invalid max count";
}
if (max_count == 0) {
max_count = std::numeric_limits<int>::max();
}
std::string cmd = args[2];
std::vector<std::string> largs;
for (int i = 3; i < (int)args.size(); i++) {
largs.push_back(args[i]);
}
for (int i = 0; i < max_count; i++) {
std::string output;
auto r = this->run_command(cmd, largs, output);
if (!r) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(interval_seconds));
}
return "repeat command completed";
}));
}
command_manager::~command_manager()
{
_cmds.clear();
CHECK(_handlers.empty(),
"All commands must be deregistered before command_manager is destroyed, however '{}' is "
"still registered",
_handlers.begin()->first);
}
} // namespace dsn