]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3328] Output usecs; add permon-get-all-durations
authorThomas Markwalder <tmark@isc.org>
Wed, 12 Jun 2024 14:09:26 +0000 (10:09 -0400)
committerFrancis Dupont <fdupont@isc.org>
Sat, 15 Jun 2024 07:17:39 +0000 (09:17 +0200)
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

16 files changed:
doc/sphinx/arm/hooks-perfmon.rst
src/hooks/dhcp/perfmon/monitored_duration.cc
src/hooks/dhcp/perfmon/monitored_duration.h
src/hooks/dhcp/perfmon/perfmon_callouts.cc
src/hooks/dhcp/perfmon/perfmon_config.cc
src/hooks/dhcp/perfmon/perfmon_config.h
src/hooks/dhcp/perfmon/perfmon_messages.cc
src/hooks/dhcp/perfmon/perfmon_messages.h
src/hooks/dhcp/perfmon/perfmon_messages.mes
src/hooks/dhcp/perfmon/perfmon_mgr.cc
src/hooks/dhcp/perfmon/perfmon_mgr.h
src/hooks/dhcp/perfmon/tests/Makefile.am
src/hooks/dhcp/perfmon/tests/perfmon_cmds_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/perfmon/tests/perfmon_config_unittests.cc
src/hooks/dhcp/perfmon/tests/perfmon_mgr_unittests.cc
src/share/api/perfmon-control.json [new file with mode: 0644]

index 2da634380565ace5c7546a822398612dcca862f8..494c89f4161849e6112506d08b0238d67baf305f 100644 (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:
 
index af51f2eb185875baf77ccf297cd9843c06f44745..090da75876d2dddd49d209edbebbcaf45a10110e 100644 (file)
 #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
index 893544493e575c9c0e8260d5f600910d39f81362..d7e1a5b19a6aa4be3c12782d32653b22c014a9c6 100644 (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_;
index 09df3badbfbf0b001c827a91ddf901c08077dcf8..b995fe47c08991065203e172279652476fed37da 100644 (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());
index 99ab61c1d4a904866b64f45a0c15c68f469c1ff0..6a97ceaac59f9c9917e112cf7952406025862cfd 100644 (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
index cc03616a8bd9ed8e3b56fb35b7cf50236dfe4f43..9d62782bbf5657b9f4b30954729f21c4fe14603b 100644 (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.
index 7c390503cabd97d4e0fde50b60a80c023c83149a..5fee39676eefbb133e09b5b1cf2881c334c859f3 100644 (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]",
index 494bbc9dc303a8be7ace5fed94f510b3ce761e26..8ba726f80e1e9293309576a880a21dbc989e4d7f 100644 (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;
index fe5ad1b6a2c43c6d5d845653fd34ab7609a15bc8..f747033b1109ec431abe2c4c068f746ec3ab0d77 100644 (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.
index edff6a3f017540c02d14d39e7d208b8797e9455b..d7e8dda0543dac1303a818c8a895f774157a8023 100644 (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
index ef95d96b9d8a7418676e2f2df678fe623d6b3845..eb4e2768ce821a1091ab2fc7a7ae3b7e6b2db6ec 100644 (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.
index b3e474535da8ccf49793a993e72adab66a3c8a67..69bcf8b223eac2ef1ad019e08da24531c1c7d63c 100644 (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
 
diff --git a/src/hooks/dhcp/perfmon/tests/perfmon_cmds_unittests.cc b/src/hooks/dhcp/perfmon/tests/perfmon_cmds_unittests.cc
new file mode 100644 (file)
index 0000000..02bce85
--- /dev/null
@@ -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
index f99b24ba1569da4b6d6803a1ce237b786f8ca9c1..13a88e1bee32be4c73a5aba29a295558bc404cac 100644 (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
index 11af36dfc234187d968f3421cd13ff227572022f..cd1ad3477a33f9334dbcd1b160d13317d2143784 100644 (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());
diff --git a/src/share/api/perfmon-control.json b/src/share/api/perfmon-control.json
new file mode 100644 (file)
index 0000000..b5e3d81
--- /dev/null
@@ -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"
+    ]
+}