diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst index 9b10d2c4e4b..556fb6727e2 100644 --- a/doc/appendices/command-line/traffic_ctl.en.rst +++ b/doc/appendices/command-line/traffic_ctl.en.rst @@ -325,6 +325,9 @@ traffic_ctl metric Display the current value of the specified metric(s) using an interval time and a count value. Use ``-i`` to set the interval time between requests, and ``-c`` to set the number of requests the program will send in total per metric. + The program will terminate execution after requesting metrics. + If ``count=0`` is passed or ``count`` is not specified then the program should be terminated + by SIGINT. Note that the metric will display `+` or `-` depending on the value of the last metric and the current being shown, if current is greater, then `+` will be added beside the metric value, `-` if the last value is less than current, @@ -345,7 +348,7 @@ traffic_ctl metric proxy.process.eventloop.time.min.10s: 4011194 proxy.process.eventloop.time.min.10s: 4011194 proxy.process.eventloop.time.min.10s: 4018669 + - --- metric monitor statistics --- + --- metric monitor statistics(10) --- ┌ proxy.process.eventloop.time.min.10s └─ min/avg/max = 4011194/4017498/4025085 diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc index f786e29ede9..39384c90dd8 100644 --- a/src/traffic_ctl/CtrlCommands.cc +++ b/src/traffic_ctl/CtrlCommands.cc @@ -18,27 +18,32 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include "CtrlCommands.h" + #include #include #include #include +#include +#include + +#include +#include +#include -#include "CtrlCommands.h" #include "jsonrpc/CtrlRPCRequests.h" #include "jsonrpc/ctrl_yaml_codecs.h" -#include "swoc/TextView.h" -#include "swoc/BufferWriter.h" -#include "swoc/bwf_base.h" namespace { /// We use yamlcpp as codec implementation. using Codec = yamlcpp_json_emitter; -} // namespace + const std::unordered_map _Fmt_str_to_enum = { {"json", BasePrinter::Options::OutputFormat::JSON}, {"rpc", BasePrinter::Options::OutputFormat::RPC } }; +} // namespace BasePrinter::Options::OutputFormat parse_format(ts::Arguments *args) @@ -57,13 +62,13 @@ parse_format(ts::Arguments *args) } return val; } - BasePrinter::Options parse_print_opts(ts::Arguments *args) { return {parse_format(args)}; } +std::atomic_int CtrlCommand::Signal_Flagged{0}; //------------------------------------------------------------------------------------------------------------------------------------ CtrlCommand::CtrlCommand(ts::Arguments *args) : _arguments(args) {} @@ -289,11 +294,12 @@ MetricCommand::metric_monitor() // // Note: if any of the string->number fails, the exception will be caught by the invoke function from the ArgParser. // - const int32_t count = std::stoi(get_parsed_arguments()->get("count").value()); + const int32_t count = std::stoi(get_parsed_arguments()->get("count").value()); + int32_t query_count{0}; const int32_t interval = std::stoi(get_parsed_arguments()->get("interval").value()); - if (count <= 0 || interval <= 0) { - throw std::runtime_error( - ts::bwprint(err_text, "monitor: invalid input, count({}), interval({}) should be > than '0'", count, interval)); + // default count is 0. + if (count < 0 || interval <= 0) { + throw std::runtime_error(swoc::bwprint(err_text, "monitor: invalid input, count: {}(>=0), interval: {}(>=1)", count, interval)); } // keep track of each metric @@ -305,9 +311,27 @@ MetricCommand::metric_monitor() }; // Keep track of the requested metric(s), we support more than one at the same time. + + // To be used to print all the stats. This is a lambda function as this could + // be called when SIGINT is invoked, so we dump what we have before exit. + auto dump = [&](std::unordered_map const &_summary) { + if (_summary.size() == 0) { + // nothing to report. + return; + } + + _printer->write_output(swoc::bwprint(err_text, "--- metric monitor statistics({}) ---", query_count)); + + for (auto const &item : _summary) { + ctx const &s = item.second; + const int avg = s.sum / query_count; + _printer->write_output(swoc::bwprint(err_text, "┌ {}\n└─ min/avg/max = {:.5}/{}/{:.5}", item.first, s.min, avg, s.max)); + } + }; + std::unordered_map summary; - for (int idx = 0;; idx++) { + while (!Signal_Flagged.load()) { // Request will hold all metrics in a single message. shared::rpc::JSONRPCResponse const &resp = record_fetch(arg, shared::rpc::NOT_REGEX, RecordQueryType::METRIC); @@ -323,15 +347,14 @@ MetricCommand::metric_monitor() } for (auto &&rec : response.recordList) { // requested metric(s) - auto &s = summary[rec.name]; // We will update it. - // Note: To avoid + auto &s = summary[rec.name]; // We will update it. const float val = std::stof(rec.currentValue); s.sum += val; s.max = std::max(s.max, val); s.min = std::min(s.min, val); std::string symbol; - if (idx > 0) { + if (query_count > 0) { if (val > s.last) { symbol = "+"; } else if (val < s.last) { @@ -339,25 +362,18 @@ MetricCommand::metric_monitor() } } s.last = val; - _printer->write_output(ts::bwprint(err_text, "{}: {} {}", rec.name, rec.currentValue, symbol)); + _printer->write_output(swoc::bwprint(err_text, "{}: {} {}", rec.name, rec.currentValue, symbol)); } - if (idx == count - 1) { + + if ((query_count++ == count - 1) && count > 0 /* could be a forever loop*/) { break; } - std::this_thread::sleep_for(std::chrono::seconds(interval)); - } - if (summary.size() == 0) { - // nothing to report. - return; - } - _printer->write_output("--- metric monitor statistics ---"); - - for (auto &&item : summary) { - ctx const &s = item.second; - const int avg = s.sum / count; - _printer->write_output(ts::bwprint(err_text, "┌ {}\n└─ min/avg/max = {:.5}/{}/{:.5}", item.first, s.min, avg, s.max)); + sleep(interval); } + + // all done, print summary. + dump(summary); } //------------------------------------------------------------------------------------------------------------------------------------ // TODO, let call the super const diff --git a/src/traffic_ctl/CtrlCommands.h b/src/traffic_ctl/CtrlCommands.h index fb12e468bfc..cd15f3febd3 100644 --- a/src/traffic_ctl/CtrlCommands.h +++ b/src/traffic_ctl/CtrlCommands.h @@ -72,6 +72,10 @@ class CtrlCommand /// If @c _invoked_func is not properly set, the function will throw @c logic_error virtual void execute(); + /// @brief This variable is used to mark if a Signal was flagged by the application. Default value is 0 and the signal number + /// should be set when the signal is handled. + static std::atomic_int Signal_Flagged; + protected: /// @brief The whole design is that the command will execute the @c _invoked_func once invoked. This function ptr should be /// set by the appropriated derived class base on the passed parameters. The derived class have the option to override diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc index 3c7f82476df..0637ab39b0b 100644 --- a/src/traffic_ctl/traffic_ctl.cc +++ b/src/traffic_ctl/traffic_ctl.cc @@ -22,10 +22,13 @@ */ #include +#include #include "tscore/I_Layout.h" #include "tscore/runroot.h" #include "tscore/ArgParser.h" +#include "tscore/ink_assert.h" +#include "tscore/signals.h" #include "CtrlCommands.h" #include "FileConfigCommand.h" @@ -36,6 +39,28 @@ constexpr int CTRL_EX_UNIMPLEMENTED = 3; int status_code{CTRL_EX_OK}; +namespace +{ +void +handle_signal(int signal_num, siginfo_t *, void *) +{ + CtrlCommand::Signal_Flagged = signal_num; +} + +void +signal_register_handler(int signal_num, signal_handler_t handle_signal) +{ + struct sigaction act; + + act.sa_handler = nullptr; + act.sa_sigaction = handle_signal; + act.sa_flags = SA_NODEFER | SA_RESETHAND; + sigemptyset(&(act.sa_mask)); + + ink_release_assert(sigaction(signal_num, &act, nullptr) == 0); +} +} // namespace + int main(int argc, const char **argv) { @@ -124,9 +149,14 @@ main(int argc, const char **argv) metric_command.add_command("match", "Get metrics matching a regular expression", "", MORE_THAN_ZERO_ARG_N, [&]() { command->execute(); }); metric_command - .add_command("monitor", "Display the value of a metric(s) over time", "", MORE_THAN_ZERO_ARG_N, [&]() { command->execute(); }) + .add_command( + "monitor", + "Display the value of a metric(s) over time. Program stops after or with a SIGINT. A brief summary is displayed.", "", + MORE_THAN_ZERO_ARG_N, [&]() { command->execute(); }) .add_example_usage("traffic_ctl metric monitor METRIC -i 3 -c 10") - .add_option("--count", "-c", "Stop after requesting count metrics.", "", 1, "10") + .add_option("--count", "-c", + "Terminate execution after requesting metrics. If 0 is passed, program should be terminated by a SIGINT", + "", 1, "0") .add_option("--interval", "-i", "Wait interval seconds between sending each metric request. Minimum value is 1s.", "", 1, "5"); metric_command.add_command("zero", "Clear one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); }); @@ -183,6 +213,9 @@ main(int argc, const char **argv) .add_example_usage("traffic_ctl rpc invoke foo_bar -p \"numbers: [1, 2, 3]\""); try { + // for now we only care about SIGINT(SIGQUIT, ... ?) + signal_register_handler(SIGINT, handle_signal); + auto args = parser.parse(argv); argparser_runroot_handler(args.get("run-root").value(), argv[0]); Layout::create();