2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#3328] Output usecs; add permon-get-all-durations

Statistics and raw data return durations in microseconds instead
of milliseconds.  Add initial support for perfmon-get-all-durations.

/doc/sphinx/arm/hooks-perfmon.rst
    Updated to microseconds
    Updated perfmon-control command

/src/hooks/dhcp/perfmon/monitored_duration.*
    DurationKey::toElement()
    MonitoredDuration::toElement() - new functions

/src/hooks/dhcp/perfmon/perfmon_callouts.cc
    int perfmon_control()
    int perfmon_get_all_durations() - new functions

    int load() - register commands

/src/hooks/dhcp/perfmon/perfmon_config.cc
    PerfMonConfig::parse() - replace use of copy ctor

    PerfMonConfig::enable_monitoring_
    PerfMonConfig::stats_mgr_reporting_ - made std::atomic

/src/hooks/dhcp/perfmon/perfmon_messages.mes
    PERFMON_CMDS_CONTROL_ERROR
    PERFMON_CMDS_CONTROL_OK
    PERFMON_CMDS_GET_ALL_DURATIONS_ERROR
    PERFMON_CMDS_GET_ALL_DURATIONS_OK - new messages

/src/hooks/dhcp/perfmon/perfmon_mgr.cc
    PerfMonMgr::perfmonControlHandler()
    PerfMonMgr::perfmonGetAllDurationsHandler()
    PerfMonMgr::formatDurationDataAsElements()
    PerfMonMgr::formatDurationDataAsResultSet() - new functions

/src/hooks/dhcp/perfmon/tests/Makefile.am
    Added perfmon_cmds_unittests.cc

/src/hooks/dhcp/perfmon/tests/perfmon_config_unittests.cc
    Replaced use of copy ctor

/src/hooks/dhcp/perfmon/tests/perfmon_mgr_unittests.cc
    Updated tests for microseconds
This commit is contained in:
Thomas Markwalder
2024-06-12 10:09:26 -04:00
committed by Francis Dupont
parent 58cc9eb76c
commit 4889c3f9a3
16 changed files with 1013 additions and 46 deletions

View File

@@ -145,7 +145,7 @@ duration updates for each of the above:
+--------------------------------------------------------------+--------------+
| Global Duration Keys | Update in |
| | milliseconds |
| | microseconds |
+==============================================================+==============+
| DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.0 | 247 |
+--------------------------------------------------------------+--------------+
@@ -170,29 +170,29 @@ statistic employs the following naming convention:
{subnet-id[x]}.perfmon.<query type>-<response type>.<start event>-<end event>.<value-name>
There is both a global and a subnet-specific value for each. Currently, the only
value reported for a given duration key is ``average-ms``; this statistic is the average time
value reported for a given duration key is ``averages-usecs``; this statistic is the average time
between the duration's event pair over the most recently completed interval. In other
words, if during a given interval there were seven occurrences (i.e. updates) totaling
350ms, the ``average-ms`` reported would be 50ms. Continuing with the example above, the
3500us, the ``average-usecs`` reported would be 500us. Continuing with the example above, the
statistics reported are named as follows for the subnet-level values:
::
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.average-ms
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.buffer_read-mt_queue.average-ms
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.mt_queued-process_started.average-ms
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.process_started-process_completed.average-ms
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.composite-total_response.average-ms
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.average-usecs
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.buffer_read-mt_queue.average-usecs
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.mt_queued-process_started.average-usecs
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.process_started-process_completed.average-usecs
subnet[100].perfmon.DHCPDISCOVER.DHCPOFFER.composite-total_response.average-usecs
and as shown for global values:
::
perfmon.DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.average-ms
perfmon.DHCPDISCOVER.DHCPOFFER.buffer_read-mt_queue.average-ms
perfmon.DHCPDISCOVER.DHCPOFFER.mt_queued-process_started.average-ms
perfmon.DHCPDISCOVER.DHCPOFFER.process_started-process_completed.average-ms
perfmon.DHCPDISCOVER.DHCPOFFER.composite-total_response.average-ms
perfmon.DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.average-usecs
perfmon.DHCPDISCOVER.DHCPOFFER.buffer_read-mt_queue.average-usecs
perfmon.DHCPDISCOVER.DHCPOFFER.mt_queued-process_started.average-usecs
perfmon.DHCPDISCOVER.DHCPOFFER.process_started-process_completed.average-usecs
perfmon.DHCPDISCOVER.DHCPOFFER.composite-total_response.average-usecs
The results are reported to StatsMgr, an internal Kea component that reports data as statistics
that can be retrieved using statistics commands. They can be fetched using the commands
@@ -225,8 +225,34 @@ The alarm-cleared INFO log looks like this:
API Commands
~~~~~~~~~~~~
Commands to enable or disable monitoring, clear or alter alarms, and fetch duration data
are anticipated but not yet supported.
.. _command-perfmon-control:
This command can be used to enable or disable active monitoring and statistics
reporting at runtime without altering or reloading configuration.
::
{
"command": "perfmon-control"
"arguments": {
"enable-monitoring": true,
"stats-mgr-reporting": false"
}
}
Regardless of the arguments (if any) are supplied, the current values of both
flags are always returned:
::
{
"result": 0,
"text": "perfmon-control success",
"arguments": {
"enable-monitoring": true,
"stats-mgr-reporting": false"
}
}
.. _perfmon-configuration:

View File

@@ -11,8 +11,11 @@
#include <dhcp/dhcp6.h>
#include <exceptions/exceptions.h>
#include <monitored_duration.h>
#include <util/boost_time_utils.h>
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::util;
using namespace boost::posix_time;
namespace isc {
@@ -194,6 +197,17 @@ DurationKey::getStatName(const std::string& value_name) const {
return (oss.str());
}
ElementPtr
DurationKey::toElement() const {
ElementPtr element = Element::createMap();
element->set("subnet-id", Element::create(static_cast<long long>(subnet_id_)));
element->set("query-type", Element::create(getMessageTypeLabel(family_, query_type_)));
element->set("response-type", Element::create(getMessageTypeLabel(family_, response_type_)));
element->set("start-event", Element::create(start_event_label_));
element->set("stop-event", Element::create(stop_event_label_));
return (element);
}
bool
DurationKey::operator==(const DurationKey& other) const {
return (
@@ -309,5 +323,26 @@ MonitoredDuration::clear() {
previous_interval_.reset();
}
ElementPtr
MonitoredDuration::toElement() const {
ElementPtr element = Element::createMap();
element->set("duration-key", DurationKey::toElement());
if (previous_interval_) {
element->set("start-time", Element::create(ptimeToText(previous_interval_->getStartTime())));
element->set("occurrences", Element::create(static_cast<long long>(previous_interval_->getOccurrences())));
element->set("min-duration-usecs", Element::create(previous_interval_->getMinDuration().total_microseconds()));
element->set("max-duration-usecs", Element::create(previous_interval_->getMaxDuration().total_microseconds()));
element->set("total-duration-usecs", Element::create(previous_interval_->getTotalDuration().total_microseconds()));
} else {
element->set("start-time", Element::create("<none>"));
element->set("occurrences", Element::create(0));
element->set("min-duration-usecs", Element::create(0));
element->set("max-duration-usecs", Element::create(0));
element->set("total-duration-usecs", Element::create(0));
}
return (element);
}
} // end of namespace perfmon
} // end of namespace isc

View File

@@ -7,6 +7,7 @@
#ifndef _MONITORED_DURATION_H
#define _MONITORED_DURATION_H
#include <cc/data.h>
#include <dhcp/pkt.h>
#include <dhcpsrv/subnet_id.h>
@@ -226,7 +227,7 @@ public:
/// @brief Get the StatsMgr formatted compatible name.
///
/// @param value_name name of the specific value (e.g. "average-ms", "min-duration-ms").
/// @param value_name name of the specific value (e.g. "average-usecs", "min-duration-usecs").
/// The format of the string:
///
/// @code
@@ -235,15 +236,32 @@ public:
///
/// Examples:
///
/// perfmon.discover-offer.socket_received-buffer_read.average-ms
/// perfmon.discover-offer.socket_received-buffer_read.average-usecs
///
/// subnet[9].perfmon.discover-offer.socket_received-buffer_read.average-ms
/// subnet[9].perfmon.discover-offer.socket_received-buffer_read.average-usecs
///
/// @endcode
///
/// @return the statistic name.
std::string getStatName(const std::string& value_name) const;
/// @brief Renders the the duration key as an Element.
///
/// The element will appear as follows:
///
/// @code
/// {
/// "query-type": "discover",
/// "response-type": "offer",
/// "start-event": "socket_received",
/// "stop-event": "buffer_read",
/// "subnet-id": 10
/// }
/// @endcode
///
/// @return Element::map containing the duration key values.
virtual data::ElementPtr toElement() const;
/// @brief Validates that a query and response message type pair is sane.
///
/// @param family Protocol family of the key (AF_INET or AF_INET6)
@@ -379,6 +397,49 @@ public:
/// @brief Deletes the current and previous intervals.
void clear();
/// @brief Renders the the duration as an Element.
///
/// The element includes the duration key and the previous interval
/// content(if one) as follows:
/// @code
/// {
/// "duration-key": {
/// "query-type": "discover",
/// "response-type": "offer",
/// "start-event": "socket_received",
/// "stop-event": "buffer_read",
/// "subnet-id": 10
/// },
/// "start-time": "2024-01-18 10:11:19.498739",
/// "occurrences": 105,
/// "min-duration-usecs": 5300,
/// "max-duration-usecs": 9000,
/// "total-duration-usecs": 786500
/// }
/// @endcode
///
/// If there is no previous interval, it will appears as follows:
///
/// @code
/// {
/// "duration-key": {
/// "query-type": "discover",
/// "response-type": "offer",
/// "start-event": "socket_received",
/// "stop-event": "buffer_read",
/// "subnet-id": 10
/// },
/// "start-time": "<none>",
/// "occurrences": 0,
/// "min-duration-usecs": 0,
/// "max-duration-usecs": 0,
/// "total-duration-usecs": 0
/// }
/// @endcode
///
/// @return Element::map containing the duration key values.
virtual data::ElementPtr toElement() const;
private:
/// @brief Length of the time of a single data interval.
Duration interval_duration_;

View File

@@ -115,6 +115,26 @@ int pkt6_send(CalloutHandle& handle) {
return (0);
}
/// @brief This is a command callout for 'perfmon-control' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 otherwise.
int perfmon_control(CalloutHandle& handle) {
return (mgr->perfmonControlHandler(handle));
}
/// @brief This is a command callout for 'perfmon-get-all-durations' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 otherwise.
int perfmon_get_all_durations(CalloutHandle& handle) {
return (mgr->perfmonGetAllDurationsHandler(handle));
}
/// @brief This function is called when the library is loaded.
///
/// @param handle library handle
@@ -141,8 +161,9 @@ int load(LibraryHandle& handle) {
ConstElementPtr json = handle.getParameters();
mgr->configure(json);
/// @todo register commands
/// handle.registerCommandCallout("command-here", handler_here);
/// Register commands.
handle.registerCommandCallout("perfmon-control", perfmon_control);
handle.registerCommandCallout("perfmon-get-all-durations", perfmon_get_all_durations);
} catch (const std::exception& ex) {
LOG_ERROR(perfmon_logger, PERFMON_INIT_FAILED)
.arg(ex.what());

View File

@@ -324,8 +324,13 @@ PerfMonConfig::parse(data::ConstElementPtr config) {
local.parseAlarms(elem);
}
// All values good, shallow copy from local instance.
*this = local;
// All values good, copy them from local instance.
family_= local.getFamily();
enable_monitoring_ = local.getEnableMonitoring();
interval_width_secs_ = local.getIntervalWidthSecs();
stats_mgr_reporting_ = local.getStatsMgrReporting();
alarm_report_secs_ = local.getAlarmReportSecs();
alarm_store_= local.getAlarmStore();
}
void

View File

@@ -12,6 +12,8 @@
#include <alarm_store.h>
#include <monitored_duration.h>
#include <atomic>
namespace isc {
namespace perfmon {
@@ -238,7 +240,7 @@ protected:
/// true. If false the library loads and configures but does nothing.
/// Gives users a way to keep the library loaded without it being active.
/// Should be accessible via explicit API command.
bool enable_monitoring_;
std::atomic<bool> enable_monitoring_;
/// @brief Number of seconds a duration accumulates samples until reporting.
/// Defaults to 60.
@@ -246,7 +248,7 @@ protected:
/// @brief If true durations report to StatsMgr at the end of each interval.
/// Defaults to true.
bool stats_mgr_reporting_;
std::atomic<bool> stats_mgr_reporting_;
/// @brief Number of seconds between reports of a raised alarm.
/// Defaults to 300. A value of zero disables alarms.

View File

@@ -6,6 +6,10 @@
extern const isc::log::MessageID PERFMON_ALARM_CLEARED = "PERFMON_ALARM_CLEARED";
extern const isc::log::MessageID PERFMON_ALARM_TRIGGERED = "PERFMON_ALARM_TRIGGERED";
extern const isc::log::MessageID PERFMON_CMDS_CONTROL_ERROR = "PERFMON_CMDS_CONTROL_ERROR";
extern const isc::log::MessageID PERFMON_CMDS_CONTROL_OK = "PERFMON_CMDS_CONTROL_OK";
extern const isc::log::MessageID PERFMON_CMDS_GET_ALL_DURATIONS_ERROR = "PERFMON_CMDS_GET_ALL_DURATIONS_ERROR";
extern const isc::log::MessageID PERFMON_CMDS_GET_ALL_DURATIONS_OK = "PERFMON_CMDS_GET_ALL_DURATIONS_OK";
extern const isc::log::MessageID PERFMON_DEINIT_FAILED = "PERFMON_DEINIT_FAILED";
extern const isc::log::MessageID PERFMON_DEINIT_OK = "PERFMON_DEINIT_OK";
extern const isc::log::MessageID PERFMON_DHCP4_PKT_EVENTS = "PERFMON_DHCP4_PKT_EVENTS";
@@ -22,6 +26,10 @@ namespace {
const char* values[] = {
"PERFMON_ALARM_CLEARED", "Alarm for %1 has been cleared, reported average duration %2 is now below low-water-ms: %3",
"PERFMON_ALARM_TRIGGERED", "Alarm for %1 has been triggered since %2, reported average duration %3 exceeds high-water-ms: %4",
"PERFMON_CMDS_CONTROL_ERROR", "perfmon-control command processing failed: %1",
"PERFMON_CMDS_CONTROL_OK", "perfmon-control command success: active monitoring: %1, stats-mgr-reporting: %2",
"PERFMON_CMDS_GET_ALL_DURATIONS_ERROR", "perfmon-get-all-durations command processing failed: %1",
"PERFMON_CMDS_GET_ALL_DURATIONS_OK", "perfmon-get-all-durations returning %1 durations",
"PERFMON_DEINIT_FAILED", "unloading PerfMon hooks library failed: %1",
"PERFMON_DEINIT_OK", "unloading PerfMon hooks library successful",
"PERFMON_DHCP4_PKT_EVENTS", "query: %1 events=[%2]",

View File

@@ -7,6 +7,10 @@
extern const isc::log::MessageID PERFMON_ALARM_CLEARED;
extern const isc::log::MessageID PERFMON_ALARM_TRIGGERED;
extern const isc::log::MessageID PERFMON_CMDS_CONTROL_ERROR;
extern const isc::log::MessageID PERFMON_CMDS_CONTROL_OK;
extern const isc::log::MessageID PERFMON_CMDS_GET_ALL_DURATIONS_ERROR;
extern const isc::log::MessageID PERFMON_CMDS_GET_ALL_DURATIONS_OK;
extern const isc::log::MessageID PERFMON_DEINIT_FAILED;
extern const isc::log::MessageID PERFMON_DEINIT_OK;
extern const isc::log::MessageID PERFMON_DHCP4_PKT_EVENTS;

View File

@@ -70,3 +70,23 @@ the log message.
% PERFMON_INIT_OK loading PerfMon hooks library successful
This info message indicates that the PerfMon hooks library has been
loaded successfully. Enjoy!
% PERFMON_CMDS_CONTROL_ERROR perfmon-control command processing failed: %1
This error message is issued when the PerfMon hook library encounters an
error processing a perfmon-control command. The argument explains the
command error.
% PERFMON_CMDS_CONTROL_OK perfmon-control command success: active monitoring: %1, stats-mgr-reporting: %2
This info log is issued when perfmon-control command has successfully
enabled/disabled active monitoring and/or statistics mgr reporting.
Arguments reflect the current state of both.
% PERFMON_CMDS_GET_ALL_DURATIONS_ERROR perfmon-get-all-durations command processing failed: %1
This error message is issued when the PerfMon hook library encounters an
error processing a perfmon-get-all-durations command. The argument explains the
command error.
% PERFMON_CMDS_GET_ALL_DURATIONS_OK perfmon-get-all-durations returning %1 durations
This info log is issued when perfmon-get-all-durations command has
completed successfully. The argument contains the number of
durations returned.

View File

@@ -11,6 +11,8 @@
#include <perfmon_log.h>
#include <perfmon_mgr.h>
#include <cc/simple_parser.h>
#include <config/cmd_response_creator.h>
#include <stats/stats_mgr.h>
#include <dhcp/dhcp6.h>
#include <util/boost_time_utils.h>
@@ -18,6 +20,7 @@
namespace isc {
namespace perfmon {
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::log;
@@ -26,7 +29,7 @@ using namespace isc::util;
using namespace boost::posix_time;
PerfMonMgr::PerfMonMgr(uint16_t family_)
: PerfMonConfig(family_) {
: PerfMonConfig(family_), mutex_(new std::mutex) {
init();
}
@@ -173,8 +176,8 @@ PerfMonMgr::reportToStatsMgr(MonitoredDurationPtr duration) {
auto average = previous_interval->getAverageDuration();
if (getStatsMgrReporting()) {
StatsMgr::instance().setValue(duration->getStatName("average-ms"),
static_cast<int64_t>(average.total_milliseconds()));
StatsMgr::instance().setValue(duration->getStatName("average-usecs"),
static_cast<int64_t>(average.total_microseconds()));
}
/// @todo - decide if we want to report min and max values too.
@@ -219,5 +222,160 @@ PerfMonMgr::setNextReportExpiration() {
isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
}
int
PerfMonMgr::perfmonControlHandler(hooks::CalloutHandle& handle) {
static SimpleKeywords keywords = {
{ "enable-monitoring", Element::boolean },
{ "stats-mgr-reporting", Element::boolean }
};
std::string txt = "(missing parameters)";
ElementPtr result = Element::createMap();
ConstElementPtr response;
// Extract the command and then the parameters
try {
extractCommand(handle);
if (cmd_args_) {
txt = cmd_args_->str();
}
if (cmd_args_) {
SimpleParser::checkKeywords(keywords, cmd_args_);
ConstElementPtr elem = cmd_args_->get("enable-monitoring");
if (elem) {
enable_monitoring_ = elem->boolValue();
}
elem = cmd_args_->get("stats-mgr-reporting");
if (elem) {
stats_mgr_reporting_ = elem->boolValue();
}
}
LOG_INFO(perfmon_logger, PERFMON_CMDS_CONTROL_OK)
.arg(enable_monitoring_ ? "enabled" : "disabled")
.arg(stats_mgr_reporting_ ? "enabled" : "disabled");
result->set("enable-monitoring", Element::create(enable_monitoring_));
result->set("stats-mgr-reporting", Element::create(stats_mgr_reporting_));
response = createAnswer(CONTROL_RESULT_SUCCESS, "perfmon-control success", result);
} catch (const std::exception& ex) {
LOG_ERROR(perfmon_logger, PERFMON_CMDS_CONTROL_ERROR)
.arg(ex.what());
setErrorResponse(handle, ex.what());
return (1);
}
setResponse(handle, response);
return (0);
}
int
PerfMonMgr::perfmonGetAllDurationsHandler(hooks::CalloutHandle& handle) {
static SimpleKeywords keywords = {
{ "result-set-format", Element::boolean }
};
ElementPtr result = Element::createMap();
ConstElementPtr response;
try {
// Extract the command and then the parameters
bool result_set_format = false;
extractCommand(handle);
if (cmd_args_) {
SimpleParser::checkKeywords(keywords, cmd_args_);
ConstElementPtr elem = cmd_args_->get("result-set-format");
if (elem) {
result_set_format = elem->boolValue();
}
}
// Fetch the durations from the store.
auto durations = duration_store_->getAll();
auto rows = durations->size();
ElementPtr formatted_durations;
// Format them either as a list of elements or as a result set
if (!result_set_format) {
formatted_durations = formatDurationDataAsElements(durations);
} else {
formatted_durations = formatDurationDataAsResultSet(durations);
}
// Construct the result
result->set("interval-width-secs", Element::create(getIntervalWidthSecs()));
result->set("timestamp", Element::create(isc::util::ptimeToText(PktEvent::now())));
result->set((result_set_format ? "durations-result-set" : "durations"), formatted_durations);
std::ostringstream oss;
oss << "perfmon-get-all-durations: " << rows << " found";
response = createAnswer(CONTROL_RESULT_SUCCESS, oss.str(), result);
LOG_INFO(perfmon_logger, PERFMON_CMDS_GET_ALL_DURATIONS_OK)
.arg(rows);
} catch (const std::exception& ex) {
LOG_ERROR(perfmon_logger, PERFMON_CMDS_GET_ALL_DURATIONS_ERROR)
.arg(ex.what());
setErrorResponse(handle, ex.what());
return (1);
}
setResponse(handle, response);
return (0);
}
ElementPtr
PerfMonMgr::formatDurationDataAsElements(MonitoredDurationCollectionPtr durations) const {
// Create the list.
ElementPtr duration_list = Element::createList();
// Add in the duration elements.
for (auto const& d : *durations) {
ElementPtr element = d->toElement();
duration_list->add(element);
}
return (duration_list);
}
ElementPtr
PerfMonMgr::formatDurationDataAsResultSet(MonitoredDurationCollectionPtr /*durations */) const{
isc_throw (NotImplemented, "Not Implemented - " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
#if 0
// Create the result-set map and add it to the wrapper.
ElementPtr result_set = Element::createMap();
result_wrapper->set("result-set", result_set);
// Create the list of column names and add it to the result set.
ElementPtr columns = Element::createList();
for (auto const& label : column_labels) {
columns->add(Element::create(label));
}
result_set->set("columns", columns);
// Create the empty value_rows list, add it and then return it.
ElementPtr value_rows = Element::createList();
result_set->set("rows", value_rows);
if (durations.empty()) {
return;
}
return (value_rows);
for (auto const& d : *durations) {
const auto& reported_interval = d->getPreviousInterval();
if (reported_interval) {
std::string label = d->getLabel();
}
#endif
}
} // end of namespace perfmon
} // end of namespace isc

View File

@@ -12,6 +12,8 @@
#define PERFMON_MGR_H
#include <perfmon_config.h>
#include <config/command_mgr.h>
#include <config/cmds_impl.h>
#include <monitored_duration_store.h>
#include <asiolink/io_service.h>
#include <asiolink/interval_timer.h>
@@ -24,7 +26,7 @@ namespace perfmon {
/// the PerfMon hook library. It owns the MonitoredDurationStore and AlarmStore
/// instances and supplies callout and command API handlers. It derives from
/// PerfMonConfig.
class PerfMonMgr : public PerfMonConfig {
class PerfMonMgr : public PerfMonConfig, private config::CmdsImpl {
public:
/// @brief Constructor.
///
@@ -46,7 +48,15 @@ public:
/// @brief Processes the event stack of a query packet.
///
/// @todo DETAILS TO FOLLOW
/// -# Emit a dump of packet stack to perfmon debug log at detail level.
/// -# Iterates over the query's event stack creating a DurationKey for each
/// adjacent event pair, computing the elapsed time between them and then calls
/// addDurationSample().
/// -# Generates composite duration updates. Durations that monitor total response
/// time for a given query/response pair will be computed using the first and last
/// events in the stack, with a begin event label of "composite" and an end label
/// of "total_response" for the DurationKey. These duration updates will be passed
/// into addDurationSample(). Other composite durations may be added in the future.
///
/// @param query query packet whose stack is to be processed.
/// @param response response packet generated for the query.
@@ -105,6 +115,151 @@ public:
/// cancel report timer
void setNextReportExpiration();
/// @brief perfmon-control command handler
///
/// This command sets enable-monitoring and/or stats-mgr-reporting (affects
/// in memory value(s) only).
///
/// @code
/// {
/// "command": "perfmon-control",
/// "arguments": {
/// "enable-monitoring": true,
/// "stats-mgr-reporting": true
/// }
/// }
/// @endcode
///
/// It extracts the command name and arguments from the given CalloutHandle,
/// attempts to process them, and then set's the handle's "response"
/// arguments accordingly. Regardless of which parameters were specified
/// in the command arguments (if any), it returns the values for both
/// parameters:
///
/// @code
/// "arguments": {
/// "enable-monitoring": false,
/// "stats-mgr-reporting": false
/// },
/// "result": 0,
/// "text": "perfmon-control success"
/// }
/// @endcode
///
/// @param handle Callout context - which is expected to contain the
/// command JSON text in the "command" argument
/// @return result of the operation
int perfmonControlHandler(hooks::CalloutHandle& handle);
/// @brief perfmon-get-all-durations handler
///
/// This command fetches all of the monitored durations and their preivous
/// intervals (if one).
///
/// @code
/// {
/// "command": "perfmon-get-all-duations",
/// "arguments": {
/// "result-set-format": true
/// }
/// }
/// @endcode
///
/// It extracts the command name and arguments from the given CalloutHandle,
/// attempts to process them, and then set's the handle's "response"
/// arguments accordingly. If result-set-format is false (the default) the
/// durations are returned as a list of Elements:
///
/// @code
/// {
/// "result": 0,
/// "text": "perfmon-get-all-durations: n rows found",
/// "arguments": {
/// "result-set-format": false,
/// "interval-width-secs": 5,
/// "timestamp": "2024-01-18 10:11:20.594800"
/// "durations": [{
/// "duration-key": {
/// "query-type": "discover",
/// "response-type": "offer",
/// "start-event": "socket_received",
/// "stop-event": "buffer_read",
/// "subnet-id": 10
/// },
/// "start-time": "2024-01-18 10:11:19.498739",
/// "occurrences": 105,
/// "min-duration-usecs": 5300,
/// "max-duration-usecs": 9000,
/// "total-duration-usecs": 786500
/// },
/// ..
/// ]
/// },
/// }
/// @endcode
///
/// If result-set-format is true, the durations are returned in a more compact format,
/// patterned after an SQL result set:
///
/// @code
/// {
/// "result": 0,
/// "text": "perfmon-get-all-durations: n rows found",
/// "arguments": {
/// "result-set-format": true,
/// "interval-width-secs": 5,
/// "timestamp": "2024-01-18 10:11:20.594800"
/// "durations-result-set": {
/// "columns": [
/// "subnet-id", "query-type", "response-type", "start-event", "end-event",
/// "interval start", "occurences", "min-duration-usecs", "max-duration-usecs",
/// "total-duration-usecs"
/// ],
/// "rows": [
/// [
/// 10, "discover", "offer", "socket_received", "buffer_read",
/// "2024-01-18 10:11:19.498739", 105, 5300, 9000, 786500
/// ],
/// ..
/// ]
/// }
/// }
/// }
/// @endcode
///
/// @param handle Callout context - which is expected to contain the
/// command JSON text in the "command" argument
/// @return result of the operation
int perfmonGetAllDurationsHandler(hooks::CalloutHandle& handle);
/// @brief Renders a list of MonitoredDurations as a map of individual Elements
///
/// @param durations collection of durations to convert
data::ElementPtr formatDurationDataAsElements(MonitoredDurationCollectionPtr durations) const;
/// @brief Renders a list of MonitoredDurations as a result set
///
/// The result set Element will be as shown below:
///
/// @code
/// "durations-result-set": {
/// "columns": [
/// "subnet-id", "query-type", "response-type", "start-event", "end-event",
/// "interval start", "occurences", "min-duration-usecs", "max-duration-usecs",
/// "total-duration-usecs"
/// ],
/// "rows": [
/// [
/// 10, "discover", "offer", "socket_received", "buffer_read",
/// "2024-01-18 10:11:19.498739", 105, 5300, 9000, 786500
/// ],
/// ..
/// ]
/// @endcode
///
/// @param durations collection of durations to convert
data::ElementPtr formatDurationDataAsResultSet(MonitoredDurationCollectionPtr durations) const;
/// @brief Get the interval duration.
///
/// @return interval-width-secs as a Duration.

View File

@@ -33,6 +33,7 @@ perfmon_unittests_SOURCES += monitored_duration_store_unittests.cc
perfmon_unittests_SOURCES += alarm_store_unittests.cc
perfmon_unittests_SOURCES += perfmon_config_unittests.cc
perfmon_unittests_SOURCES += perfmon_mgr_unittests.cc
perfmon_unittests_SOURCES += perfmon_cmds_unittests.cc
perfmon_unittests_SOURCES += duration_key_parser_unittests.cc
perfmon_unittests_SOURCES += alarm_parser_unittests.cc

View File

@@ -0,0 +1,441 @@
// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/// @file This file contains tests which exercise the PerfmonMgr class.
#include <config.h>
#include <perfmon_mgr.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/subnet.h>
#include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h>
#include <testutils/log_utils.h>
#include <testutils/gtest_utils.h>
#include <testutils/multi_threading_utils.h>
#include <gtest/gtest.h>
#include <list>
#include <sstream>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace isc::perfmon;
using namespace isc::stats;
using namespace isc::test;
using namespace isc::dhcp::test;
using namespace boost::posix_time;
namespace {
/// @brief Test fixture for testing PerfMonMgr
class PerfMonCmdTest : public LogContentTest {
public:
/// @brief Constructor.
explicit PerfMonCmdTest(uint16_t family) : family_(family) {
StatsMgr::instance();
StatsMgr::instance().removeAll();
StatsMgr::instance().setMaxSampleCountAll(1);
if (family_ == AF_INET) {
subnet22_.reset(new Subnet4(IOAddress("192.0.22.0"), 8, 100, 200, 300, 22));
subnet33_.reset(new Subnet4(IOAddress("192.0.33.0"), 8, 100, 200, 300, 33));
} else {
subnet22_.reset(new Subnet6(IOAddress("3001:22::"), 64, 100, 200, 300, 300, 22));
subnet33_.reset(new Subnet6(IOAddress("3002:33::"), 64, 100, 200, 300, 300, 33));
}
}
void SetUp() {
std::string valid_config =
R"({
"enable-monitoring": false,
"interval-width-secs": 5000,
"stats-mgr-reporting": false,
"alarm-report-secs": 600000,
"alarms": [{
"duration-key": {
"query-type": "",
"response-type": "",
"start-event": "process-started",
"stop-event": "process-completed",
"subnet-id": 70
},
"enable-alarm": true,
"high-water-ms": 500,
"low-water-ms": 25
}]
})";
ASSERT_NO_THROW_LOG(createMgr(valid_config));
}
/// @brief Destructor.
virtual ~PerfMonCmdTest() = default;
/// @brief Re-creates and then configures the PerfMonMgr instance with a
/// given configuration.
///
/// @param config JSON configuration text
void createMgr(const std::string& config) {
mgr_.reset(new PerfMonMgr(family_));
ConstElementPtr json_elements;
json_elements = Element::fromJSON(config);
mgr_->configure(json_elements);
}
/// @brief Make a valid family-specific query.
///
/// @return if family is AF_INET return a pointer to a DHCPDISCOVER
/// otherwise a pointer to a DHCPV6_SOLICIT.
PktPtr makeFamilyQuery() {
if (family_ == AF_INET) {
return (PktPtr(new Pkt4(DHCPDISCOVER, 7788)));
}
return (PktPtr(new Pkt6(DHCPV6_SOLICIT, 7788)));
}
/// @brief Make a valid family-specific response.
///
/// @return if family is AF_INET return a pointer to a DHCPOFFER
/// otherwise a pointer to a DHCPV6_ADVERTISE.
PktPtr makeFamilyResponse() {
if (family_ == AF_INET) {
return (PktPtr(new Pkt4(DHCPOFFER, 7788)));
}
return (PktPtr(new Pkt6(DHCPV6_ADVERTISE, 7788)));
}
/// @brief Tests specified command and verifies response.
///
/// This method processes supplied command by invoking the
/// corresponding PerfMonMgr command handler and checks
/// if the expected response was returned.
///
/// @param cmd_txt JSON text command to be sent (must be valid JSON)
/// @param exp_result 0 - success, 1 - error, 2 - ...
/// @param exp_txt expected text response (optional)
/// @return full response returned by the command execution.
ConstElementPtr testCommand(string cmd_txt, int exp_result, string exp_txt,
ConstElementPtr exp_args = ConstElementPtr()) {
ConstElementPtr cmd;
EXPECT_NO_THROW(cmd = Element::fromJSON(cmd_txt));
if (!cmd) {
ADD_FAILURE() << cmd_txt << " is not a valid JSON, test broken";
return (ConstElementPtr());
}
return (testCommand(cmd, exp_result, exp_txt, exp_args));
}
/// @brief Tests specified command and verifies response.
///
/// This method processes supplied command by invoking the
/// corresponding PerfMonMgr command handler.
///
/// @param cmd JSON command to be sent
/// @param exp_result 0 - success, 1 - error, 2 - ...
/// @param exp_txt expected text response (optional)
/// @return full response returned by the command execution.
ConstElementPtr testCommand(ConstElementPtr cmd,
int exp_result,
string exp_txt,
ConstElementPtr exp_args = ConstElementPtr()) {
string cmd_txt("...");
if (cmd) {
cmd_txt = prettyPrint(cmd);
}
SCOPED_TRACE(cmd_txt);
// Command must be a map.
if (!cmd || (cmd->getType() != Element::map)) {
ADD_FAILURE() << cmd_txt << " is not a map, test broken";
return (ConstElementPtr());
}
// We need to extract command name to select appropriate handler.
ConstElementPtr command_element = cmd->get("command");
if (!command_element || (command_element->getType() != Element::string)) {
ADD_FAILURE() << cmd_txt << " does not contain command parameter";
return (ConstElementPtr());
}
// Command name found.
std::string command_name = command_element->stringValue();
// Need to encapsulate the command in CalloutHandle.
CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
callout_handle->setArgument("command", cmd);
// Run the command handler appropriate for the given command name.
if (command_name == "perfmon-control") {
static_cast<void>(mgr_->perfmonControlHandler(*callout_handle));
} else {
ADD_FAILURE() << "unrecognized command '" << command_name << "'";
}
// Get the response.
ConstElementPtr rsp;
callout_handle->getArgument("response", rsp);
// Response must be present.
if (!rsp) {
ADD_FAILURE() << "no response returned for command '"
<< command_name << "'";
return (ConstElementPtr());
}
// Verify the response against expected values.
checkAnswer(rsp, exp_result, exp_txt, exp_args);
return (rsp);
}
/// @brief Compares the status in the given parse result to a given value.
///
/// @param answer Element set containing an integer response and string
/// comment.
/// @param exp_status is an integer against which to compare the status.
/// @param exp_txt is expected text (not checked if "")
///
void checkAnswer(isc::data::ConstElementPtr answer,
int exp_status,
string exp_txt = "",
ConstElementPtr exp_args = ConstElementPtr()) {
int rcode = 0;
isc::data::ConstElementPtr comment;
comment = isc::config::parseAnswer(rcode, answer);
if (rcode != exp_status) {
ADD_FAILURE() << "Expected status code " << exp_status
<< " but received " << rcode << ", comment: "
<< (comment ? comment->str() : "(none)");
}
// Ok, parseAnswer interface is weird. If there are no arguments,
// it returns content of text. But if there is an argument,
// it returns the argument and it's not possible to retrieve
// "text" (i.e. comment).
if (comment->getType() != Element::string) {
comment = answer->get("text");
}
if (!exp_txt.empty()) {
EXPECT_EQ(exp_txt, comment->stringValue());
}
if (exp_args) {
ConstElementPtr args = answer->get("arguments");
ASSERT_TRUE(args);
EXPECT_EQ(*exp_args, *args);
}
}
// Verify that invalid perfmon-control commands are caught.
void testInvalidPerfMonControl() {
struct Scenario {
int line_; // Scenario line number
std::string cmd_; // JSON command text
int exp_result_; // Expected result code
std::string exp_text_; // Expected result text
};
std::list<Scenario> scenarios = {
{
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
"enable-monitoring": "bogus"
}
})",
CONTROL_RESULT_ERROR,
"'enable-monitoring' parameter is not a boolean"
},
{
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
"bogus": 23
}
})",
CONTROL_RESULT_ERROR,
"spurious 'bogus' parameter"
}
};
for (const auto& scenario : scenarios) {
stringstream oss;
oss << "scenario at line: " << scenario.line_;
SCOPED_TRACE(oss.str());
ConstElementPtr answer = testCommand(scenario.cmd_,
scenario.exp_result_,
scenario.exp_text_);
}
}
// Verify that valid perfmon-control are processed correctly.
void testValidPerfMonControl() {
struct Scenario {
int line_; // Scenario line number
std::string cmd_; // JSON command text
int exp_result_; // Expected result code
std::string exp_text_; // Expected result text
bool exp_monitor_enabled_; // Expected state of monitoring
bool exp_stats_mgr_reporting_; // Expected state of monitoring
};
// Verify that monitoring is enabled.
ASSERT_EQ(mgr_->getEnableMonitoring(), false);
// Define valid scenarios.
std::list<Scenario> scenarios = {
{
// No arguments element should be ok.
__LINE__,
R"({
"command": "perfmon-control"
})",
CONTROL_RESULT_SUCCESS,
"perfmon-control success",
false,
false
},
{
// Empty arguments element should be ok.
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
}
})",
CONTROL_RESULT_SUCCESS,
"perfmon-control success",
false,
false
},
{
// Only enable-monitoring should be ok.
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
"enable-monitoring": true
}
})",
CONTROL_RESULT_SUCCESS,
"perfmon-control success",
true,
false
},
{
// Only stats-mgr-reporting should be ok.
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
"stats-mgr-reporting": true
}
})",
CONTROL_RESULT_SUCCESS,
"perfmon-control success",
true,
true
},
{
// Both enable-monitoring and stats-mgr-reporting should be ok.
__LINE__,
R"({
"command": "perfmon-control",
"arguments": {
"enable-monitoring": false,
"stats-mgr-reporting": false
}
})",
CONTROL_RESULT_SUCCESS,
"perfmon-control success",
false,
false
}
};
for (const auto& scenario : scenarios) {
stringstream oss;
oss << "scenario at line: " << scenario.line_;
SCOPED_TRACE(oss.str());
ElementPtr exp_args(Element::createMap());
exp_args->set("enable-monitoring",
Element::create(scenario.exp_monitor_enabled_));
exp_args->set("stats-mgr-reporting",
Element::create(scenario.exp_stats_mgr_reporting_));
ConstElementPtr answer = testCommand(scenario.cmd_,
scenario.exp_result_,
scenario.exp_text_,
exp_args);
EXPECT_EQ(mgr_->getEnableMonitoring(), scenario.exp_monitor_enabled_);
EXPECT_EQ(mgr_->getStatsMgrReporting(), scenario.exp_stats_mgr_reporting_);
}
}
/// @brief Protocol family AF_INET or AF_INET6
uint16_t family_;
/// @brief PerfMonMgr instance used in test functions.
PerfMonMgrPtr mgr_;
/// @brief Family specific subnets.
SubnetPtr subnet22_;
SubnetPtr subnet33_;
};
/// @brief Test fixture for testing PerfMonConfig for DHCPV4.
class PerfMonCmdTest4: public PerfMonCmdTest {
public:
/// @brief Constructor.
explicit PerfMonCmdTest4() : PerfMonCmdTest(AF_INET) {
}
/// @brief Destructor.
virtual ~PerfMonCmdTest4() = default;
};
/// @brief Test fixture for testing PerfMonConfig for DHCPV6.
class PerfMonCmdTest6: public PerfMonCmdTest {
public:
/// @brief Constructor.
explicit PerfMonCmdTest6() : PerfMonCmdTest(AF_INET6) {
}
/// @brief Destructor.
virtual ~PerfMonCmdTest6() = default;
};
TEST_F(PerfMonCmdTest4, invalidPerfMonControl) {
testInvalidPerfMonControl();
}
TEST_F(PerfMonCmdTest6, invalidPerfMonControl) {
testInvalidPerfMonControl();
}
TEST_F(PerfMonCmdTest4, validPerfMonControl) {
testValidPerfMonControl();
}
TEST_F(PerfMonCmdTest6, validPerfMonControl) {
testValidPerfMonControl();
}
} // end of anonymous namespace

View File

@@ -62,14 +62,6 @@ public:
EXPECT_NO_THROW_LOG(config->setAlarmReportSecs(120));
EXPECT_EQ(config->getAlarmReportSecs(), 120);
// Verify shallow copy construction.
PerfMonConfigPtr config2(new PerfMonConfig(*config));
EXPECT_TRUE(config2->getEnableMonitoring());
EXPECT_EQ(config2->getIntervalWidthSecs(), 4);
EXPECT_FALSE(config2->getStatsMgrReporting());
EXPECT_EQ(config2->getAlarmReportSecs(), 120);
EXPECT_EQ(config2->getAlarmStore(), config->getAlarmStore());
}
/// @brief Exercises PerfMonConfig parameter parsing with valid configuration

View File

@@ -332,16 +332,16 @@ public:
ASSERT_NO_THROW_LOG(average = mgr_->reportToStatsMgr(mond));
EXPECT_EQ(milliseconds(175), average);
auto obs = StatsMgr::instance().getObservation(mond->getStatName("average-ms"));
auto obs = StatsMgr::instance().getObservation(mond->getStatName("average-usecs"));
ASSERT_TRUE(obs);
EXPECT_EQ(175, obs->getInteger().first);
EXPECT_EQ(175000, obs->getInteger().first);
StatsMgr::instance().removeAll();
mgr_->setStatsMgrReporting(false);
ASSERT_NO_THROW_LOG(average = mgr_->reportToStatsMgr(mond));
EXPECT_EQ(milliseconds(175), average);
obs = StatsMgr::instance().getObservation(mond->getStatName("average-ms"));
obs = StatsMgr::instance().getObservation(mond->getStatName("average-usecs"));
ASSERT_FALSE(obs);
}
@@ -411,9 +411,9 @@ public:
// Should have one stat reported with a average value of 80.
EXPECT_EQ(1, StatsMgr::instance().count());
auto obs = StatsMgr::instance().getObservation(key->getStatName("average-ms"));
auto obs = StatsMgr::instance().getObservation(key->getStatName("average-usecs"));
ASSERT_TRUE(obs);
EXPECT_EQ(80, obs->getInteger().first);
EXPECT_EQ(80000, obs->getInteger().first);
// The alarm should have triggered and reported.
beforeAndAfterAlarm(__LINE__, before_alarm, Alarm::TRIGGERED, true);
@@ -450,9 +450,9 @@ public:
// Should have one stat reported with a value of 100.
EXPECT_EQ(1, StatsMgr::instance().count());
obs = StatsMgr::instance().getObservation(key->getStatName("average-ms"));
obs = StatsMgr::instance().getObservation(key->getStatName("average-usecs"));
ASSERT_TRUE(obs);
EXPECT_EQ(100, obs->getInteger().first);
EXPECT_EQ(100000, obs->getInteger().first);
// Sleep 100ms second to make sure the current interval duration elapses.
usleep(100 * 1000);
@@ -470,9 +470,9 @@ public:
// Should have one stat reported with a value of 10.
EXPECT_EQ(1, StatsMgr::instance().count());
obs = StatsMgr::instance().getObservation(key->getStatName("average-ms"));
obs = StatsMgr::instance().getObservation(key->getStatName("average-usecs"));
ASSERT_TRUE(obs);
EXPECT_EQ(10, obs->getInteger().first);
EXPECT_EQ(10000, obs->getInteger().first);
// Lastly, verify the log entries.
EXPECT_TRUE(checkFile());

View File

@@ -0,0 +1,38 @@
{
"access": "write",
"avail": "2.7.0",
"brief": [
"This command enables/disables active monitoring and statistics reporting."
],
"cmd-syntax": [
"{",
" \"command\": \"perfmon-control\",",
" \"arguments\": {",
" \"enable-monitoring\": true,",
" \"stats-mgr-reporting\": false",
" }",
"}",
""
],
"description": "See <xref linkend=\"command-perfmon-control\"/>",
"hook": "perfmon",
"name": "perfmon-control",
"resp-comment": [
"Result 0 is returned if command succeeds along with the resultant values of both flags.",
"Result is 1 when parameters are malformed or missing."
],
"resp-syntax": [
" {",
" \"arguments\": {",
" \"enable-monitoring\": true,",
" \"stats-mgr-reporting\": false",
" },",
" \"result\": 0,",
" \"text\": \"perfmon-control success.\"",
" }"
],
"support": [
"kea-dhcp4",
"kea-dhcp6"
]
}