// Copyright (C) 2015-2023 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/. #ifndef STATSMGR_H #define STATSMGR_H #include #include #include #include #include #include #include #include #include #include namespace isc { namespace stats { /// @brief Statistics Manager class /// /// StatsMgr is a singleton class that represents a subsystem that manages /// collection, storage and reporting of various types of statistics. /// It is also the intended API for both core code and hooks. /// /// As of May 2015, Tomek ran performance benchmarks (see unit-tests in /// stats_mgr_unittest.cc with performance in their names) and it seems /// the code is able to register ~2.5-3 million observations per second, even /// with 1000 different statistics recorded. That seems sufficient for now, /// so there is no immediate need to develop any multi-threading solutions /// for now. However, should this decision be revised in the future, the /// best place for it would to be modify @ref addObservation method here. /// It's the common code point that all new observations must pass through. /// One possible way to enable multi-threading would be to run a separate /// thread handling collection. The main thread would call @ref addValue and /// @ref setValue methods that would end up calling @ref addObservation. /// That method would pass the data to separate thread to be collected and /// would immediately return. Further processing would be mostly as it /// is today, except happening in a separate thread. One unsolved issue in /// this approach is how to extract data, but that will remain unsolvable /// until we get the control socket implementation. /// /// Statistics Manager does not use logging by design. The reasons are: /// - performance impact (logging every observation would degrade performance /// significantly. While it's possible to log on sufficiently high debug /// level, such a log would be not that useful) /// - dependency (statistics are intended to be a lightweight library, adding /// dependency on libkea-log, which has its own dependencies, including /// external log4cplus, is against 'lightweight' design) /// - if logging of specific statistics is warranted, it is recommended to /// add log entries in the code that calls StatsMgr. /// - enabling logging in StatsMgr does not offer fine tuning. It would be /// either all or nothing. Adding logging entries only when necessary /// in the code that uses StatsMgr gives better granularity. /// /// If this decision is revisited in the future, the most universal places /// for adding logging have been marked in @ref addValueInternal and /// @ref setValueInternal. class StatsMgr : public boost::noncopyable { public: /// @brief Statistics Manager accessor method. static StatsMgr& instance(); /// @defgroup producer_methods Methods are used by data producers. /// /// @brief The following methods are used by data producers: /// /// @{ /// @brief Records absolute integer observation. /// /// @param name name of the observation /// @param value integer value observed /// @throw InvalidStatType if statistic is not integer void setValue(const std::string& name, const int64_t value); /// @brief Records an absolute big integer observation. /// /// @param name name of the observation /// @param value integer value observed /// @throw InvalidStatType if statistic is not integer void setValue(const std::string& name, const isc::util::int128_t& value); /// @brief Records absolute floating point observation. /// /// @param name name of the observation /// @param value floating point value observed /// @throw InvalidStatType if statistic is not fp void setValue(const std::string& name, const double value); /// @brief Records absolute duration observation. /// /// @param name name of the observation /// @param value duration value observed /// @throw InvalidStatType if statistic is not time duration void setValue(const std::string& name, const StatsDuration& value); /// @brief Records absolute string observation. /// /// @param name name of the observation /// @param value string value observed /// @throw InvalidStatType if statistic is not a string void setValue(const std::string& name, const std::string& value); /// @brief Records incremental integer observation. /// /// @param name name of the observation /// @param value integer value observed /// @throw InvalidStatType if statistic is not integer void addValue(const std::string& name, const int64_t value); /// @brief Records an incremental big integer observation. /// /// @param name name of the observation /// @param value integer value observed /// @throw InvalidStatType if statistic is not integer void addValue(const std::string& name, const isc::util::int128_t& value); /// @brief Records incremental floating point observation. /// /// @param name name of the observation /// @param value floating point value observed /// @throw InvalidStatType if statistic is not fp void addValue(const std::string& name, const double value); /// @brief Records incremental duration observation. /// /// @param name name of the observation /// @param value duration value observed /// @throw InvalidStatType if statistic is not time duration void addValue(const std::string& name, const StatsDuration& value); /// @brief Records incremental string observation. /// /// @param name name of the observation /// @param value string value observed /// @throw InvalidStatType if statistic is not a string void addValue(const std::string& name, const std::string& value); /// @brief Determines maximum age of samples. /// /// Specifies that statistic name should be stored not as a single value, /// but rather as a set of values. duration determines the timespan. /// Samples older than duration will be discarded. This is time-constrained /// approach. For sample count constrained approach, see @ref /// setMaxSampleCount() below. /// Example: /// To set a statistic to keep observations for the last 5 minutes, call: /// setMaxSampleAge("incoming-packets", StatsDuration::minutes(5)); /// to revert statistic to a single value, call: /// setMaxSampleAge("incoming-packets", StatsDuration:zero()); /// /// @param name name of the observation /// @param duration determines maximum age of samples /// @return true if successful, false if there's no such statistic bool setMaxSampleAge(const std::string& name, const StatsDuration& duration); /// @brief Determines how many samples of a given statistic should be kept. /// /// Specifies that statistic name should be stored not as single value, but /// rather as a set of values. In this form, at most max_samples will be kept. /// When adding max_samples + 1 sample, the oldest sample will be discarded. /// Example: /// To set a statistic to keep the last 100 observations, call: /// setMaxSampleCount("incoming-packets", 100); /// /// @param name name of the observation /// @param max_samples how many samples of a given statistic should be kept /// @return true if successful, false if there's no such statistic bool setMaxSampleCount(const std::string& name, uint32_t max_samples); /// @brief Set duration limit for all collected statistics. /// /// @param duration determines maximum age of samples void setMaxSampleAgeAll(const StatsDuration& duration); /// @brief Set count limit for all collected statistics. /// /// @param max_samples how many samples of a given statistic should be kept void setMaxSampleCountAll(uint32_t max_samples); /// @brief Set default duration limit. /// /// @param duration default maximum age of samples to keep void setMaxSampleAgeDefault(const StatsDuration& duration); /// @brief Set default count limit. /// /// @param max_samples default maximum number of samples to keep /// (0 means to disable count limit and enable age limit) void setMaxSampleCountDefault(uint32_t max_samples); /// @brief Get default duration limit. /// /// @return default maximum age of samples to keep. const StatsDuration& getMaxSampleAgeDefault() const; /// @brief Get default count limit. /// /// @return default maximum number of samples to keep. /// (0 means that count limit was disabled) uint32_t getMaxSampleCountDefault() const; /// @} /// @defgroup consumer_methods Methods are used by data consumers. /// /// @brief The following methods are used by data consumers: /// /// @{ /// @brief Resets specified statistic. /// /// This is a convenience function and is equivalent to setValue(name, /// neutral_value), where neutral_value is 0, 0.0 or "". /// /// @param name name of the statistic to be reset. /// @return true if successful, false if there's no such statistic bool reset(const std::string& name); /// @brief Removes specified statistic. /// /// @param name name of the statistic to be removed. /// @return true if successful, false if there's no such statistic bool del(const std::string& name); /// @brief Resets all collected statistics back to zero. void resetAll(); /// @brief Removes all collected statistics. /// @note This command was deprecated. void removeAll(); /// @brief Returns size of specified statistic. /// /// @param name name of the statistic which size should be return. /// @return size of specified statistic, 0 means lack of given statistic. size_t getSize(const std::string& name) const; /// @brief Returns number of available statistics. /// /// @return number of recorded statistics. size_t count() const; /// @brief Returns a single statistic as a JSON structure. /// /// @return JSON structures representing a single statistic isc::data::ConstElementPtr get(const std::string& name) const; /// @brief Returns all statistics as a JSON structure. /// /// @return JSON structures representing all statistics isc::data::ConstElementPtr getAll() const; /// @} /// @brief Returns an observation. /// /// Used in testing only. Production code should use @ref get() method /// when the value is dereferenced. /// Calls @ref getObservationInternal() method in a thread safe context. /// /// @param name name of the statistic /// @return Pointer to the Observation object ObservationPtr getObservation(const std::string& name) const; /// @brief Returns an observation in a thread safe context. /// /// Used in testing only. Production code should use @ref get() method /// when the value is dereferenced. Should be called in a thread safe context. /// /// @param name name of the statistic /// @return Pointer to the Observation object ObservationPtr getObservationInternal(const std::string& name) const; /// @brief Generates statistic name in a given context /// /// Example: /// @code /// generateName("subnet", 123, "received-packets"); /// @endcode /// will return subnet[123].received-packets. Any printable type /// can be used as index. /// /// @tparam Type any type that can be used to index contexts /// @param context name of the context (e.g. 'subnet') /// @param index value used for indexing contexts (e.g. subnet_id) /// @param stat_name name of the statistic /// @return returns full statistic name in form context[index].stat_name template static std::string generateName(const std::string& context, Type index, const std::string& stat_name) { std::stringstream name; name << context << "[" << index << "]." << stat_name; return (name.str()); } /// @defgroup command_methods Methods are used to handle commands. /// /// @brief The following methods are used to handle commands: /// /// @{ /// @brief Handles statistic-get command /// /// This method handles statistic-get command, which returns value /// of a given statistic). It expects one parameter stored in params map: /// name: name of the statistic /// /// Example params structure: /// { /// "name": "packets-received" /// } /// /// @param name name of the command (ignored, should be "statistic-get") /// @param params structure containing a map that contains "name" /// @return answer containing details of specified statistic static isc::data::ConstElementPtr statisticGetHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-reset command /// /// This method handles statistic-reset command, which resets value /// of a given statistic. It expects one parameter stored in params map: /// name: name of the statistic /// /// Example params structure: /// { /// "name": "packets-received" /// } /// /// @param name name of the command (ignored, should be "statistic-reset") /// @param params structure containing a map that contains "name" /// @return answer containing confirmation static isc::data::ConstElementPtr statisticResetHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-remove command /// /// This method handles statistic-reset command, which removes a given /// statistic completely. It expects one parameter stored in params map: /// name: name of the statistic /// /// Example params structure: /// { /// "name": "packets-received" /// } /// /// @param name name of the command (ignored, should be "statistic-remove") /// @param params structure containing a map that contains "name" element /// @return answer containing confirmation static isc::data::ConstElementPtr statisticRemoveHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-sample-age-set command /// /// This method handles statistic-sample-age-set command, /// which sets max_sample_age_ limit of a given statistic /// and leaves max_sample_count_ disabled. /// It expects two parameters stored in params map: /// name: name of the statistic /// duration: time limit expressed as a number of seconds /// /// Example params structure: /// { /// "name": "packets-received", /// "duration": 1245 /// } /// /// @param name name of the command (ignored, should be "statistic-sample-age-set") /// @param params structure containing a map that contains "name" and "duration" /// @return answer containing information about successfully setup limit of statistic static isc::data::ConstElementPtr statisticSetMaxSampleAgeHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-sample-count-set command /// /// This method handles statistic-sample-count-set command, /// which sets max_sample_count_ limit of a given statistic /// and leaves max_sample_age_ disabled. /// It expects two parameters stored in params map: /// name: name of the statistic /// max-samples: count limit /// /// Example params structure: /// { /// "name": "packets-received", /// "max-samples": 15 /// } /// /// @param name name of the command (ignored, should be "statistic-sample-count-set") /// @param params structure containing a map that contains "name" and "max-samples" /// @return answer containing information about successfully setup limit of statistic static isc::data::ConstElementPtr statisticSetMaxSampleCountHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-get-all command /// /// This method handles statistic-get-all command, which returns values /// of all statistics. Params parameter is ignored. /// /// @param name name of the command (ignored, should be "statistic-get-all") /// @param params ignored /// @return answer containing values of all statistic static isc::data::ConstElementPtr statisticGetAllHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-reset-all command /// /// This method handles statistic-reset-all command, which sets values of /// all statistics back to zero. Params parameter is ignored. /// /// @param name name of the command (ignored, should be "statistic-reset-all") /// @param params ignored /// @return answer confirming success of this operation static isc::data::ConstElementPtr statisticResetAllHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-remove-all command /// /// @note The statistic-remove-all command was deprecated. /// /// This method handles statistic-remove-all command, which removes all /// statistics. Params parameter is ignored. /// /// @param name name of the command (ignored, should be "statistic-remove-all") /// @param params ignored /// @return answer confirming success of this operation static isc::data::ConstElementPtr statisticRemoveAllHandler(const std::string& name, const isc::data::ConstElementPtr& params); /// @brief Handles statistic-sample-age-set-all command /// /// This method handles statistic-sample-age-set-all command, /// which sets max_sample_age_ limit to all statistics and the default. /// It expects one parameter stored in params map: /// duration: limit expressed as a number of seconds /// /// Example params structure: /// { /// "duration": 1245 /// } /// /// @param params structure containing a map that contains "duration" /// @return answer confirming success of this operation isc::data::ConstElementPtr statisticSetMaxSampleAgeAllHandler(const isc::data::ConstElementPtr& params); /// @brief Handles statistic-sample-count-set-all command /// /// This method handles statistic-sample-count-set-all command, /// which sets max_sample_count_ limit of all statistics and the default. /// It expects one parameter stored in params map: /// max-samples: count limit /// The value 0 is out of range. /// /// Example params structure: /// { /// "max-samples": 15 /// } /// /// @param params structure containing a map that contains "max-samples" /// @return answer confirming success of this operation isc::data::ConstElementPtr statisticSetMaxSampleCountAllHandler(const isc::data::ConstElementPtr& params); /// @} private: /// @private /// @brief Private constructor. /// /// StatsMgr is a singleton. It should be accessed using @ref instance /// method. StatsMgr(); /// @public /// @brief Sets a given statistic to specified value (internal version). /// /// This template method sets statistic identified by name to a value /// specified by value. This internal method is used by public @ref setValue /// methods. /// /// @tparam DataType one of int64_t, double, StatsDuration or string /// @param name name of the statistic /// @param value specified statistic will be set to this value /// @throw InvalidStatType is statistic exists and has a different type. template void setValueInternal(const std::string& name, DataType value) { // If we want to log each observation, here would be the best place for it. ObservationPtr stat = getObservationInternal(name); if (stat) { stat->setValue(value); } else { stat.reset(new Observation(name, value)); addObservationInternal(stat); } } /// @public /// @brief Adds specified value to a given statistic (internal version). /// /// This template method adds specified value to a given statistic (identified /// by name to a value). This internal method is used by public @ref setValue /// methods. /// /// @tparam DataType one of int64_t, double, StatsDuration or string /// @param name name of the statistic /// @param value specified statistic will be set to this value /// @throw InvalidStatType is statistic exists and has a different type. template void addValueInternal(const std::string& name, DataType value) { // If we want to log each observation, here would be the best place for it. ObservationPtr existing = getObservationInternal(name); if (!existing) { // We tried to add to a non-existing statistic. We can recover from // that. Simply add the new incremental value as a new statistic and // we're done. setValueInternal(name, value); return; } else { // Let's hope it is of correct type. If not, the underlying // addValue() method will throw. existing->addValue(value); } } /// @public /// @brief Adds a new observation. /// /// That's an utility method used by public @ref setValue() and /// @ref addValue() methods. /// Calls @ref addObservationInternal() method in a thread safe context. /// /// @param stat observation void addObservation(const ObservationPtr& stat); /// @public /// @brief Adds a new observation in a thread safe context. /// /// That's an utility method used by public @ref setValue() and /// @ref addValue() methods. /// Should be called in a thread safe context. /// /// @param stat observation void addObservationInternal(const ObservationPtr& stat); /// @private /// @brief Tries to delete an observation. /// /// Calls @ref deleteObservationInternal() method in a thread safe context. /// /// @param name of the statistic to be deleted /// @return true if deleted, false if not found bool deleteObservation(const std::string& name); /// @private /// @brief Tries to delete an observation in a thread safe context. /// /// Should be called in a thread safe context. /// /// @param name of the statistic to be deleted /// @return true if deleted, false if not found bool deleteObservationInternal(const std::string& name); /// @private /// @brief Determines maximum age of samples. /// /// Should be called in a thread safe context. /// /// @param name name of the observation /// @param duration determines maximum age of samples /// @return true if successful, false if there's no such statistic bool setMaxSampleAgeInternal(const std::string& name, const StatsDuration& duration); /// @private /// @brief Determines how many samples of a given statistic should be kept. /// /// Should be called in a thread safe context. /// /// @param name name of the observation /// @param max_samples how many samples of a given statistic should be kept /// @return true if successful, false if there's no such statistic bool setMaxSampleCountInternal(const std::string& name, uint32_t max_samples); /// @private /// @brief Set duration limit for all collected statistics. /// /// Should be called in a thread safe context. /// /// @param duration determines maximum age of samples void setMaxSampleAgeAllInternal(const StatsDuration& duration); /// @private /// @brief Set count limit for all collected statistics. /// /// Should be called in a thread safe context. /// /// @param max_samples how many samples of a given statistic should be kept void setMaxSampleCountAllInternal(uint32_t max_samples); /// @private /// @brief Set default duration limit. /// /// Should be called in a thread safe context. /// /// @param duration default maximum age of samples to keep. void setMaxSampleAgeDefaultInternal(const StatsDuration& duration); /// @brief Set default count limit. /// /// Should be called in a thread safe context. /// /// @param max_samples default maximum number of samples to keep. /// (0 means to disable count limit and enable age limit) void setMaxSampleCountDefaultInternal(uint32_t max_samples); /// @private /// @brief Get default duration limit. /// /// Should be called in a thread safe context. /// /// @return default maximum age of samples to keep. const StatsDuration& getMaxSampleAgeDefaultInternal() const; /// @brief Get default count limit. /// /// Should be called in a thread safe context. /// /// @return default maximum number of samples to keep. /// (0 means that count limit was disabled) uint32_t getMaxSampleCountDefaultInternal() const; /// @private /// @brief Resets specified statistic. /// /// Should be called in a thread safe context. /// /// @param name name of the statistic to be reset. /// @return true if successful, false if there's no such statistic bool resetInternal(const std::string& name); /// @private /// @brief Removes specified statistic. /// /// Should be called in a thread safe context. /// /// @param name name of the statistic to be removed. /// @return true if successful, false if there's no such statistic bool delInternal(const std::string& name); /// @private /// @brief Resets all collected statistics back to zero. /// /// Should be called in a thread safe context. void resetAllInternal(); /// @private /// @brief Removes all collected statistics. /// /// Should be called in a thread safe context. void removeAllInternal(); /// @private /// @brief Returns size of specified statistic. /// /// Should be called in a thread safe context. /// /// @param name name of the statistic which size should be return. /// @return size of specified statistic, 0 means lack of given statistic. size_t getSizeInternal(const std::string& name) const; /// @private /// @brief Returns number of available statistics. /// /// Should be called in a thread safe context. /// /// @return number of recorded statistics. size_t countInternal() const; /// @private /// @brief Returns a single statistic as a JSON structure. /// /// Should be called in a thread safe context. /// /// @return JSON structures representing a single statistic isc::data::ConstElementPtr getInternal(const std::string& name) const; /// @private /// @brief Returns all statistics as a JSON structure. /// /// Should be called in a thread safe context. /// /// @return JSON structures representing all statistics isc::data::ConstElementPtr getAllInternal() const; /// @private /// @brief Utility method that attempts to extract statistic name /// /// This method attempts to extract statistic name from the params /// structure. It is expected to be a map that contains 'name' element, /// that is of type string. If present as expected, statistic name /// set and true is returned. If missing or is of incorrect type, the reason /// is specified in reason parameter and false is returned. /// /// @param params parameters structure received in command /// @param name [out] name of the statistic (if no error detected) /// @param reason [out] failure reason (if error is detected) /// @return true (if everything is ok), false otherwise static bool getStatName(const isc::data::ConstElementPtr& params, std::string& name, std::string& reason); /// @private /// @brief Utility method that attempts to extract duration limit for /// a given statistic /// /// This method attempts to extract duration limit for a given statistic /// from the params structure. /// It is expected to be a map that contains four 'duration' elements: 'hours', /// 'minutes', 'seconds' and 'milliseconds' /// all are of type int. If present as expected, statistic duration /// limit is set and true is returned. /// If any of these four parameters is missing or is of incorrect type, /// the reason is specified in reason parameter and false is returned. /// /// @param params parameters structure received in command /// @param duration [out] duration limit for the statistic (if no error detected) /// @param reason [out] failure reason (if error is detected) /// @return true (if everything is ok), false otherwise static bool getStatDuration(const isc::data::ConstElementPtr& params, StatsDuration& duration, std::string& reason); /// @private /// @brief Utility method that attempts to extract count limit for /// a given statistic /// /// This method attempts to extract count limit for a given statistic /// from the params structure. /// It is expected to be a map that contains 'max-samples' element, /// that is of type int. If present as expected, statistic count /// limit (max_samples) is set and true is returned. /// If missing or is of incorrect type, the reason is specified in reason /// parameter and false is returned. /// /// @param params parameters structure received in command /// @param max_samples [out] count limit for the statistic (if no error detected) /// @param reason [out] failure reason (if error is detected) /// @return true (if everything is ok), false otherwise static bool getStatMaxSamples(const isc::data::ConstElementPtr& params, uint32_t& max_samples, std::string& reason); /// @brief This is a global context. All statistics will initially be stored here. StatContextPtr global_; /// @brief The mutex used to protect internal state. const boost::scoped_ptr mutex_; }; } // namespace stats } // namespace isc #endif // STATS_MGR