Skip to content

Commit 39cc6a6

Browse files
authored
traffic_ctl: Add support to monitor metrics. (#9423)
The tool now accepts passing -i(time interval) and -c(count) to track one or more metrics.
1 parent cfacdec commit 39cc6a6

File tree

4 files changed

+122
-2
lines changed

4 files changed

+122
-2
lines changed

doc/appendices/command-line/traffic_ctl.en.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,36 @@ traffic_ctl metric
330330

331331
Error output available if ``--format pretty`` is specified.
332332

333+
.. program:: traffic_ctl metric
334+
.. option:: monitor [-i, -c] METRIC [METRIC...]
335+
336+
Display the current value of the specified metric(s) using an interval time
337+
and a count value. Use ``-i`` to set the interval time between requests, and
338+
``-c`` to set the number of requests the program will send in total per metric.
339+
Note that the metric will display `+` or `-` depending on the value of the last
340+
metric and the current being shown, if current is greater, then `+` will be
341+
added beside the metric value, `-` if the last value is less than current,
342+
and no symbol is the same.
343+
344+
Example:
345+
346+
.. code-block:: bash
347+
348+
$ traffic_ctl metric monitor proxy.process.eventloop.time.min.10s -i 2 -c 10
349+
proxy.process.eventloop.time.min.10s: 4025085
350+
proxy.process.eventloop.time.min.10s: 4025085
351+
proxy.process.eventloop.time.min.10s: 4025085
352+
proxy.process.eventloop.time.min.10s: 4025085
353+
proxy.process.eventloop.time.min.10s: 4011194 -
354+
proxy.process.eventloop.time.min.10s: 4011194
355+
proxy.process.eventloop.time.min.10s: 4011194
356+
proxy.process.eventloop.time.min.10s: 4011194
357+
proxy.process.eventloop.time.min.10s: 4011194
358+
proxy.process.eventloop.time.min.10s: 4018669 +
359+
--- metric monitor statistics ---
360+
┌ proxy.process.eventloop.time.min.10s
361+
└─ min/avg/max = 4011194/4017498/4025085
362+
333363
.. _traffic-control-command-server:
334364

335365
traffic_ctl server

src/traffic_ctl/CtrlCommands.cc

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*/
2121
#include <fstream>
2222
#include <unordered_map>
23+
#include <chrono>
24+
#include <thread>
2325

2426
#include "CtrlCommands.h"
2527
#include "jsonrpc/CtrlRPCRequests.h"
@@ -241,6 +243,9 @@ MetricCommand::MetricCommand(ts::Arguments *args) : RecordCommand(args)
241243
} else if (args->get(ZERO_STR)) {
242244
_printer = std::make_unique<GenericPrinter>(printOpts);
243245
_invoked_func = [&]() { metric_zero(); };
246+
} else if (args->get(MONITOR_STR)) {
247+
_printer = std::make_unique<MetricRecordPrinter>(printOpts);
248+
_invoked_func = [&]() { metric_monitor(); };
244249
}
245250
}
246251

@@ -277,6 +282,86 @@ MetricCommand::metric_zero()
277282

278283
[[maybe_unused]] auto const &response = invoke_rpc(request);
279284
}
285+
286+
void
287+
MetricCommand::metric_monitor()
288+
{
289+
ts::ArgumentData const &arg = get_parsed_arguments()->get(MONITOR_STR);
290+
std::string err_text;
291+
292+
//
293+
// Note: if any of the string->number fails, the exception will be caught by the invoke function from the ArgParser.
294+
//
295+
const int32_t count = std::stoi(get_parsed_arguments()->get("count").value());
296+
const int32_t interval = std::stoi(get_parsed_arguments()->get("interval").value());
297+
if (count <= 0 || interval <= 0) {
298+
throw std::runtime_error(
299+
ts::bwprint(err_text, "monitor: invalid input, count({}), interval({}) should be > than '0'", count, interval));
300+
}
301+
302+
// keep track of each metric
303+
struct ctx {
304+
float min{std::numeric_limits<float>::max()};
305+
float max{std::numeric_limits<float>::lowest()};
306+
float sum{0.0f};
307+
float last{0.0f};
308+
};
309+
310+
// Keep track of the requested metric(s), we support more than one at the same time.
311+
std::unordered_map<std::string, ctx> summary;
312+
313+
for (int idx = 0;; idx++) {
314+
// Request will hold all metrics in a single message.
315+
shared::rpc::JSONRPCResponse const &resp = record_fetch(arg, shared::rpc::NOT_REGEX, RecordQueryType::METRIC);
316+
317+
if (resp.is_error()) { // something went wrong in the server, report it.
318+
_printer->write_output(resp);
319+
return;
320+
}
321+
322+
auto const &response = resp.result.as<shared::rpc::RecordLookUpResponse>();
323+
if (response.errorList.size() && response.recordList.size() == 0) {
324+
// nothing to be done or report, use '-f rpc' for details.
325+
break;
326+
}
327+
328+
for (auto &&rec : response.recordList) { // requested metric(s)
329+
auto &s = summary[rec.name]; // We will update it.
330+
// Note: To avoid
331+
const float val = std::stof(rec.currentValue);
332+
333+
s.sum += val;
334+
s.max = std::max<float>(s.max, val);
335+
s.min = std::min<float>(s.min, val);
336+
std::string symbol;
337+
if (idx > 0) {
338+
if (val > s.last) {
339+
symbol = "+";
340+
} else if (val < s.last) {
341+
symbol = "-";
342+
}
343+
}
344+
s.last = val;
345+
_printer->write_output(ts::bwprint(err_text, "{}: {} {}", rec.name, rec.currentValue, symbol));
346+
}
347+
if (idx == count - 1) {
348+
break;
349+
}
350+
std::this_thread::sleep_for(std::chrono::seconds(interval));
351+
}
352+
if (summary.size() == 0) {
353+
// nothing to report.
354+
return;
355+
}
356+
357+
_printer->write_output("--- metric monitor statistics ---");
358+
359+
for (auto &&item : summary) {
360+
ctx const &s = item.second;
361+
const int avg = s.sum / count;
362+
_printer->write_output(ts::bwprint(err_text, "┌ {}\n└─ min/avg/max = {:.5}/{}/{:.5}", item.first, s.min, avg, s.max));
363+
}
364+
}
280365
//------------------------------------------------------------------------------------------------------------------------------------
281366
// TODO, let call the super const
282367
HostCommand::HostCommand(ts::Arguments *args) : CtrlCommand(args)

src/traffic_ctl/CtrlCommands.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,14 @@ class MetricCommand : public RecordCommand
144144
{
145145
static inline const std::string CLEAR_STR{"clear"};
146146
static inline const std::string ZERO_STR{"zero"};
147+
static inline const std::string MONITOR_STR{"monitor"};
147148

148149
void metric_get();
149150
void metric_match();
150151
void metric_describe();
151152
void metric_clear();
152153
void metric_zero();
154+
void metric_monitor();
153155

154156
public:
155157
MetricCommand(ts::Arguments *args);

src/traffic_ctl/traffic_ctl.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,11 @@ main(int argc, const char **argv)
123123
[&]() { command->execute(); }); // not implemented
124124
metric_command.add_command("match", "Get metrics matching a regular expression", "", MORE_THAN_ZERO_ARG_N,
125125
[&]() { command->execute(); });
126-
metric_command.add_command("monitor", "Display the value of a metric over time", "", MORE_THAN_ZERO_ARG_N,
127-
[&]() { CtrlUnimplementedCommand("monitor"); }); // not implemented
126+
metric_command
127+
.add_command("monitor", "Display the value of a metric(s) over time", "", MORE_THAN_ZERO_ARG_N, [&]() { command->execute(); })
128+
.add_example_usage("traffic_ctl metric monitor METRIC -i 3 -c 10")
129+
.add_option("--count", "-c", "Stop after requesting count metrics.", "", 1, "10")
130+
.add_option("--interval", "-i", "Wait interval seconds between sending each metric request. Minimum value is 1s.", "", 1, "5");
128131
metric_command.add_command("zero", "Clear one or more metric values", "", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); });
129132

130133
// plugin command

0 commit comments

Comments
 (0)