+--------------------------------------------------------------+--------------+
| Global Duration Keys | Update in |
- | | milliseconds |
+ | | microseconds |
+==============================================================+==============+
| DHCPDISCOVER.DHCPOFFER.socket_received-buffer_read.0 | 247 |
+--------------------------------------------------------------+--------------+
{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
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:
#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 {
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 (
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
#ifndef _MONITORED_DURATION_H
#define _MONITORED_DURATION_H
+#include <cc/data.h>
#include <dhcp/pkt.h>
#include <dhcpsrv/subnet_id.h>
/// @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
///
/// 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)
/// @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_;
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
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());
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
#include <alarm_store.h>
#include <monitored_duration.h>
+#include <atomic>
+
namespace isc {
namespace perfmon {
/// 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.
/// @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.
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";
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]",
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;
% 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.
#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>
namespace isc {
namespace perfmon {
+using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::log;
using namespace boost::posix_time;
PerfMonMgr::PerfMonMgr(uint16_t family_)
- : PerfMonConfig(family_) {
+ : PerfMonConfig(family_), mutex_(new std::mutex) {
init();
}
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.
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
#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>
/// 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.
///
/// @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.
/// 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.
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
--- /dev/null
+// 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
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
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);
}
// 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);
// 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);
// 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());
--- /dev/null
+{
+ "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"
+ ]
+}