Program Listing for File generic_analyzer_base.hpp

Return to documentation for file (/tmp/ws/src/diagnostics/diagnostic_aggregator/include/diagnostic_aggregator/generic_analyzer_base.hpp)

/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2009, Willow Garage, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the Willow Garage nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

#ifndef DIAGNOSTIC_AGGREGATOR__GENERIC_ANALYZER_BASE_HPP_
#define DIAGNOSTIC_AGGREGATOR__GENERIC_ANALYZER_BASE_HPP_

#include <algorithm>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "diagnostic_aggregator/analyzer.hpp"
#include "diagnostic_aggregator/status_item.hpp"
#include "diagnostic_aggregator/visibility_control.hpp"

#include "diagnostic_msgs/msg/diagnostic_status.h"
#include "diagnostic_msgs/msg/key_value.h"

#include "pluginlib/class_list_macros.hpp"

#include "rclcpp/rclcpp.hpp"

namespace diagnostic_aggregator
{
class GenericAnalyzerBase : public Analyzer
{
public:
  GenericAnalyzerBase()
  : nice_name_(""),
    path_(""),
    timeout_(-1.0),
    num_items_expected_(-1),
    discard_stale_(false),
    has_initialized_(false),
    has_warned_(false)
  {
  }

  virtual ~GenericAnalyzerBase()
  {
    items_.clear();
  }

  /*
   *\brief Cannot be initialized from (string, NodeHandle) like defined Analyzers
   */
  DIAGNOSTIC_AGGREGATOR_PUBLIC
  bool init(
    const std::string & base_path, const std::string & breadcrumb,
    const rclcpp::Node::SharedPtr node) = 0;

  /*
   *\brief Must be initialized with path, and a "nice name"
   *
   * Must be initialized in order to prepend the path to all outgoing status messages.
   */
  bool init(
    const std::string & path, const std::string & breadcrumb, double timeout = -1.0,
    int num_items_expected = -1, bool discard_stale = false)
  {
    num_items_expected_ = num_items_expected;
    timeout_ = timeout;
    path_ = path + "/" + nice_name_;
    discard_stale_ = discard_stale;
    breadcrumb_ = breadcrumb;

    if (discard_stale_ && timeout <= 0) {
      RCLCPP_WARN(
        rclcpp::get_logger("generic_analyzer_base"),
        "Cannot discard stale items if no timeout specified. No items will be discarded");
      discard_stale_ = false;
    }

    has_initialized_ = true;

    RCLCPP_INFO(
      rclcpp::get_logger("GenericAnalyzerBase"),
      "Initialized analyzer '%s' with path '%s' and breadcrumb '%s'.", nice_name_.c_str(),
      path_.c_str(), breadcrumb_.c_str());
    return true;
  }

  virtual bool analyze(const std::shared_ptr<StatusItem> item)
  {
    RCLCPP_DEBUG(
      rclcpp::get_logger("GenericAnalyzerBase"), "Analyzer '%s' analyze, item %s: %s",
      nice_name_.c_str(), item->getName().c_str(), item->getMessage().c_str());

    if (!has_initialized_ && !has_warned_) {
      has_warned_ = true;
      RCLCPP_WARN(
        rclcpp::get_logger(
          "generic_analyzer_base"),
        R"(GenericAnalyzerBase is asked to analyze diagnostics without being initialized.
        init() must be called in order to correctly use this class.)");
    }

    if (!has_initialized_) {
      return false;
    }

    items_[item->getName()] = item;

    return has_initialized_;
  }

  virtual std::vector<std::shared_ptr<diagnostic_msgs::msg::DiagnosticStatus>> report()
  {
    RCLCPP_DEBUG(
      rclcpp::get_logger("GenericAnalyzerBase"), "Analyzer '%s' report()", nice_name_.c_str());

    if (!has_initialized_ && !has_warned_) {
      has_warned_ = true;
      RCLCPP_ERROR(
        rclcpp::get_logger(
          "generic_analyzer_base"),
        R"("GenericAnalyzerBase is asked to report diagnostics without being initialized.
        init() must be called in order to correctly use this class.)");
    }
    if (!has_initialized_) {
      std::vector<std::shared_ptr<diagnostic_msgs::msg::DiagnosticStatus>> vec;
      return vec;
    }

    auto header_status = std::make_shared<diagnostic_msgs::msg::DiagnosticStatus>();
    header_status->name = path_;
    header_status->level = diagnostic_msgs::msg::DiagnosticStatus::OK;
    header_status->message = "OK";

    std::vector<std::shared_ptr<diagnostic_msgs::msg::DiagnosticStatus>> processed;
    processed.push_back(header_status);

    bool all_stale = true;

    auto it = items_.begin();
    while (it != items_.end()) {
      auto name = it->first;
      auto item = it->second;

      bool stale = false;
      if (timeout_ > 0) {
        stale = (clock_->now() - item->getLastUpdateTime()).seconds() > timeout_;
      }

      // Erase item if its stale and we're discarding items
      if (discard_stale_ && stale) {
        items_.erase(it++);
        continue;
      }

      int8_t level = item->getLevel();
      header_status->level = std::max(static_cast<int8_t>(header_status->level), level);

      diagnostic_msgs::msg::KeyValue kv;
      kv.key = name;
      kv.value = item->getMessage();

      header_status->values.push_back(kv);

      all_stale = all_stale && ((level == diagnostic_msgs::msg::DiagnosticStatus::STALE) || stale);

      processed.push_back(item->toStatusMsg(path_, stale));

      if (stale) {
        header_status->level = diagnostic_msgs::msg::DiagnosticStatus::STALE;
      }

      ++it;
    }

    // Header is not stale unless all subs are
    if (all_stale) {
      // If we elect to discard stale items, then it signals that the absence of an item
      // is not considered problematic, so we should allow empty queues to roll up as OK.
      if (discard_stale_) {
        header_status->level = diagnostic_msgs::msg::DiagnosticStatus::OK;
      } else {
        header_status->level = diagnostic_msgs::msg::DiagnosticStatus::STALE;
      }
    } else if (header_status->level == diagnostic_msgs::msg::DiagnosticStatus::STALE) {
      header_status->level = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
    }

    header_status->message = valToMsg(header_status->level);

    // If we expect a given number of items, check that we have this number
    if (num_items_expected_ == 0 && items_.empty()) {
      header_status->level = diagnostic_msgs::msg::DiagnosticStatus::OK;
      header_status->message = "OK";
    } else if (  // NOLINT
      num_items_expected_ > 0 &&
      static_cast<int8_t>(items_.size()) != num_items_expected_)
    {  // NOLINT
      int8_t lvl = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
      header_status->level = std::max(lvl, static_cast<int8_t>(header_status->level));

      std::stringstream expec, item;
      expec << num_items_expected_;
      item << items_.size();

      if (!items_.empty()) {
        header_status->message = "Expected " + expec.str() + ", found " + item.str();
      } else {
        header_status->message = "No items found, expected " + expec.str();
      }
    }

    return processed;
  }

  DIAGNOSTIC_AGGREGATOR_PUBLIC
  virtual bool match(const std::string & name) = 0;

  virtual std::string getPath() const {return path_;}

  virtual std::string getName() const {return nice_name_;}

protected:
  std::string nice_name_;
  std::string path_;
  std::string breadcrumb_;

  double timeout_;
  int num_items_expected_;

  void addItem(std::string name, std::shared_ptr<StatusItem> item)
  {
    items_[name] = item;
  }

private:
  std::map<std::string, std::shared_ptr<StatusItem>> items_;

  bool discard_stale_, has_initialized_, has_warned_;
};

}  // namespace diagnostic_aggregator

#endif  // DIAGNOSTIC_AGGREGATOR__GENERIC_ANALYZER_BASE_HPP_