Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add first integration of pal_statistics #13

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7c374d7
add first integration of pal_statistics
saikishor Nov 17, 2024
c5e505b
namespace the introspection_data to the controller_manager node naming
saikishor Nov 18, 2024
18eb4d8
Add integration into the controller and hardware components base clas…
saikishor Nov 19, 2024
05c7596
remove execution time introspection
saikishor Nov 28, 2024
bf42165
handle cases when the value_ptr_ is invalid
saikishor Nov 28, 2024
41a000b
add CLEAR_ALL_REGISTRIES macro in destructor
saikishor Dec 4, 2024
9064687
disable introspection on cleanup
saikishor Dec 4, 2024
b771ac4
rename macro to REGISTER_ROS2_CONTROL_INTROSPECTION
saikishor Dec 5, 2024
d9548c5
update release_notes
saikishor Dec 6, 2024
c1208f0
add missing header in controller_interface_base.hpp
saikishor Dec 7, 2024
6c6e2d9
Update debugging docs
saikishor Dec 7, 2024
e35e619
add more documentation about the topics
saikishor Dec 9, 2024
3818a47
delete the move constructors
saikishor Dec 9, 2024
06ea546
Add macro with also enable argument
saikishor Dec 9, 2024
28bb1ae
place dependencies alphabetically
saikishor Dec 9, 2024
a5d8c60
Update doc/debugging.rst
saikishor Dec 9, 2024
b499248
move the documentation to introspection.rst
saikishor Dec 9, 2024
c471d0e
update docs on the third argument to enable introspection
saikishor Dec 9, 2024
e1750c9
use clang jobs from christoph
saikishor Dec 9, 2024
d74bfc0
Revert "Add macro with also enable argument"
saikishor Dec 9, 2024
414384f
Update doc/introspection.rst
saikishor Dec 9, 2024
90d12f2
Use the master CI for clang
saikishor Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "hardware_interface/loaned_command_interface.hpp"
#include "hardware_interface/loaned_state_interface.hpp"

#include "pal_statistics/pal_statistics_utils.hpp"
#include "rclcpp/version.h"
#include "rclcpp_lifecycle/lifecycle_node.hpp"

Expand Down Expand Up @@ -330,6 +331,15 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
CONTROLLER_INTERFACE_PUBLIC
void wait_for_trigger_update_to_finish();

CONTROLLER_INTERFACE_PUBLIC
std::string get_name() const;

/// Enable or disable introspection of the controller.
/**
* \param[in] enable Enable introspection if true, disable otherwise.
*/
void enable_introspection(bool enable);

protected:
std::vector<hardware_interface::LoanedCommandInterface> command_interfaces_;
std::vector<hardware_interface::LoanedStateInterface> state_interfaces_;
Expand All @@ -341,6 +351,9 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
bool is_async_ = false;
std::string urdf_ = "";
ControllerUpdateStats trigger_stats_;

protected:
pal_statistics::RegistrationsRAII stats_registrations_;
};

using ControllerInterfaceBaseSharedPtr = std::shared_ptr<ControllerInterfaceBase>;
Expand Down
26 changes: 25 additions & 1 deletion controller_interface/src/controller_interface_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <string>
#include <vector>

#include "hardware_interface/introspection.hpp"
#include "lifecycle_msgs/msg/state.hpp"

namespace controller_interface
Expand Down Expand Up @@ -61,6 +62,7 @@ return_type ControllerInterfaceBase::init(
node_->register_on_cleanup(
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(false);
if (is_async() && async_handler_ && async_handler_->is_running())
{
async_handler_->stop_thread();
Expand All @@ -71,6 +73,7 @@ return_type ControllerInterfaceBase::init(
node_->register_on_activate(
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(true);
if (is_async() && async_handler_ && async_handler_->is_running())
{
// This is needed if it is disabled due to a thrown exception in the async callback thread
Expand All @@ -80,7 +83,11 @@ return_type ControllerInterfaceBase::init(
});

node_->register_on_deactivate(
std::bind(&ControllerInterfaceBase::on_deactivate, this, std::placeholders::_1));
[this](const rclcpp_lifecycle::State & previous_state) -> CallbackReturn
{
enable_introspection(false);
return on_deactivate(previous_state);
});

node_->register_on_shutdown(
std::bind(&ControllerInterfaceBase::on_shutdown, this, std::placeholders::_1));
Expand Down Expand Up @@ -137,6 +144,8 @@ const rclcpp_lifecycle::State & ControllerInterfaceBase::configure()
thread_priority);
async_handler_->start_thread();
}
REGISTER_ROS2_CONTROL_INTROSPECTION("total_triggers", &trigger_stats_.total_triggers);
REGISTER_ROS2_CONTROL_INTROSPECTION("failed_triggers", &trigger_stats_.failed_triggers);
trigger_stats_.reset();

return get_node()->configure();
Expand Down Expand Up @@ -233,4 +242,19 @@ void ControllerInterfaceBase::wait_for_trigger_update_to_finish()
async_handler_->wait_for_trigger_cycle_to_finish();
}
}

std::string ControllerInterfaceBase::get_name() const { return get_node()->get_name(); }

void ControllerInterfaceBase::enable_introspection(bool enable)
{
if (enable)
{
stats_registrations_.enableAll();
}
else
{
stats_registrations_.disableAll();
}
}

} // namespace controller_interface
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class ControllerManager : public rclcpp::Node
const rclcpp::NodeOptions & options = get_cm_node_options());

CONTROLLER_MANAGER_PUBLIC
virtual ~ControllerManager() = default;
virtual ~ControllerManager();

CONTROLLER_MANAGER_PUBLIC
void robot_description_callback(const std_msgs::msg::String & msg);
Expand Down
9 changes: 9 additions & 0 deletions controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "controller_interface/controller_interface_base.hpp"
#include "controller_manager_msgs/msg/hardware_component_state.hpp"
#include "hardware_interface/introspection.hpp"
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "rcl/arguments.h"
Expand Down Expand Up @@ -283,6 +284,8 @@ ControllerManager::ControllerManager(
init_controller_manager();
}

ControllerManager::~ControllerManager() { CLEAR_ALL_REGISTRIES(); }

void ControllerManager::init_controller_manager()
{
// Get parameters needed for RT "update" loop to work
Expand Down Expand Up @@ -321,6 +324,10 @@ void ControllerManager::init_controller_manager()
diagnostics_updater_.add(
"Controller Manager Activity", this,
&ControllerManager::controller_manager_diagnostic_callback);
INITIALIZE_REGISTRY(
this, hardware_interface::DEFAULT_INTROSPECTION_TOPIC,
hardware_interface::DEFAULT_REGISTRY_KEY);
START_PUBLISH_THREAD(hardware_interface::DEFAULT_REGISTRY_KEY);
}

void ControllerManager::initialize_parameters()
Expand Down Expand Up @@ -2572,6 +2579,8 @@ controller_interface::return_type ControllerManager::update(
manage_switch();
}

PUBLISH_ASYNC_STATISTICS(hardware_interface::DEFAULT_REGISTRY_KEY);

return ret;
}

Expand Down
Binary file added doc/images/plotjuggler.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/plotjuggler_select_topics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/plotjuggler_visualizing_data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ Guidelines and Best Practices
:titlesonly:

Debugging the Controller Manager and Plugins <debugging.rst>
Introspecting Controllers and Hardware Components <introspection.rst>
81 changes: 81 additions & 0 deletions doc/introspection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@

Introspection of the ros2_control setup
***************************************

With the integration of the ``pal_statistics`` package, the ``controller_manager`` node publishes the registered variables within the same process to the ``~/introspection_data`` topics.
By default, all ``State`` and ``Command`` interfaces in the ``controller_manager`` are registered when they are added, and are unregistered when they are removed from the ``ResourceManager``.
The state of the all the registered entities are published at the end of every ``update`` cycle of the ``controller_manager``. For instance, In a complete synchronous ros2_control setup (with synchronous controllers and hardware components), this data in the ``Command`` interface is the command used by the hardware components to command the hardware.

All the registered variables are published over 3 topics: ``~/introspection_data/full``, ``~/introspection_data/names``, and ``~/introspection_data/values``.
- The ``~/introspection_data/full`` topic publishes the full introspection data along with names and values in a single message. This can be useful to track or view variables and information from command line.
- The ``~/introspection_data/names`` topic publishes the names of the registered variables. This topic contains the names of the variables registered. This is only published every time a a variables is registered and unregistered.
- The ``~/introspection_data/values`` topic publishes the values of the registered variables. This topic contains the values of the variables registered.

The topics ``~/introspection_data/full`` and ``~/introspection_data/values`` are always published on every update cycle asynchronously, provided that there is at least one subscriber to these topics.

The topic ``~/introspection_data/full`` can be used to integrate with your custom visualization tools or to track the variables from the command line. The topic ``~/introspection_data/names`` and ``~/introspection_data/values`` are to be used for visualization tools like `PlotJuggler <https://plotjuggler.io/>`_ to visualize the data.

.. note::
If you have a high frequency of data, it is recommended to use the ``~/introspection_data/names`` and ``~/introspection_data/values`` topic. So, that the data transferred and stored is minimized.

How to introspect internal variables of controllers and hardware components
============================================================================

Any member variable of a controller or hardware component can be registered for the introspection. It is very important that the lifetime of this variable exists as long as the controller or hardware component is available.

.. note::
If a variable's lifetime is not properly managed, it may be attempted to read, which in the worst case scenario will cause a segmentation fault.

How to register a variable for introspection
---------------------------------------------

1. Include the necessary headers in the controller or hardware component header file.

.. code-block:: cpp

#include <hardware_interface/introspection.hpp>

2. Register the variable in the configure method of the controller or hardware component.

.. code-block:: cpp

void MyController::on_configure()
{
...
// Register the variable for introspection (disabled by default)
// The variable is introspected only when the controller is active and
// then deactivated when the controller is deactivated.
REGISTER_ROS2_CONTROL_INTROSPECTION("my_variable_name", &my_variable_);
...
}

3. By default, the introspection of all the registered variables of the controllers and the hardware components is only activated, when they are active and it is deactivated when the controller or hardware component is deactivated.

.. note::
If you want to keep the introspection active even when the controller or hardware component is not active, you can do that by calling ``this->enable_introspection(true)`` in the ``on_configure`` and ``on_deactivate`` method of the controller or hardware component after registering the variables.

Types of entities that can be introspected
-------------------------------------------

- Any variable that can be cast to a double is suitable for registration.
- A function that returns a value that can be cast to a double is also suitable for registration.
- Variables of complex structures can be registered by having defined introspection for its every internal variable.
- Introspection of custom types can be done by defining a `custom introspection function <https://github.com/pal-robotics/pal_statistics/blob/humble-devel/pal_statistics/include/pal_statistics/registration_utils.hpp>`_.

.. note::
Registering the variables for introspection is not real-time safe. It is recommended to register the variables in the ``on_configure`` method only.

Data Visualization
*******************

Data can be visualized with any tools that display ROS topics, but we recommend `PlotJuggler <https://plotjuggler.io/>`_ for viewing high resolution live data, or data in bags.

1. Open ``PlotJuggler`` running ``ros2 run plotjuggler plotjuggler``.
.. image:: images/plotjuggler.png
2. Visualize the data:
- Importing from the ros2bag
- Subscribing to the ROS2 topics live with the ``ROS2 Topic Subscriber`` option under ``Streaming`` header.
3. Choose the topics ``~/introspection_data/names`` and ``~/introspection_data/values`` from the popup window.
.. image:: images/plotjuggler_select_topics.png
4. Now, select the variables that are of your interest and drag them to the plot.
.. image:: images/plotjuggler_visualizing_data.png
3 changes: 3 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ For details see the controller_manager section.
* The ``assign_interfaces`` and ``release_interfaces`` methods are now virtual, so that the user can override them to store the interfaces into custom variable types, so that the user can have the flexibility to take the ownership of the loaned interfaces to the controller (`#1743 <https://github.com/ros-controls/ros2_control/pull/1743>`_)
* The new ``PoseSensor`` semantic component provides a standard interface for hardware providing cartesian poses (`#1775 <https://github.com/ros-controls/ros2_control/pull/1775>`_)
* The controllers now support the fallback controllers (a list of controllers that will be activated, when the spawned controllers fails by throwing an exception or returning ``return_type::ERROR`` during the ``update`` cycle) (`#1789 <https://github.com/ros-controls/ros2_control/pull/1789>`_)
* The controllers can be easily introspect the internal member variables using the macro ``REGISTER_ROS2_CONTROL_INTROSPECTION`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_)

controller_manager
******************
Expand Down Expand Up @@ -85,6 +86,7 @@ controller_manager
* The ``ros2_control_node`` node has a new ``cpu_affinity`` parameter to bind the process to a specific CPU core. By default, this is not enabled. (`#1852 <https://github.com/ros-controls/ros2_control/pull/1852>`_).
* The ``--service-call-timeout`` was added as parameter to the helper scripts ``spawner.py``. Useful when the CPU load is high at startup and the service call does not return immediately (`#1808 <https://github.com/ros-controls/ros2_control/pull/1808>`_).
* The ``cpu_affinity`` parameter can now accept of types ``int`` or ``int_array`` to bind the process to a specific CPU core or multiple CPU cores. (`#1915 <https://github.com/ros-controls/ros2_control/pull/1915>`_).
* The ``pal_statistics`` is now integrated into the controller_manager, so that the controllers, hardware components and the controller_manager can be easily introspected and monitored using the topics ``~/introspection_data/names`` and ``~/introspection_data/values`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_).

hardware_interface
******************
Expand Down Expand Up @@ -158,6 +160,7 @@ hardware_interface
* With (`#1421 <https://github.com/ros-controls/ros2_control/pull/1421>`_) a key-value storage is added to InterfaceInfo. This allows to define extra params with per Command-/StateInterface in the ``.ros2_control.xacro`` file.
* With (`#1763 <https://github.com/ros-controls/ros2_control/pull/1763>`_) parsing for SDF published to ``robot_description`` topic is now also supported.
* With (`#1567 <https://github.com/ros-controls/ros2_control/pull/1567>`_) all the Hardware components now have a fully functional asynchronous functionality, by simply adding ``is_async`` tag to the ros2_control tag in the URDF. This will allow the hardware components to run in a separate thread, and the controller manager will be able to run the controllers in parallel with the hardware components.
* The hardware components can be easily introspect the internal member variables using the macro ``REGISTER_ROS2_CONTROL_INTROSPECTION`` (`#1918 <https://github.com/ros-controls/ros2_control/pull/1918>`_)

joint_limits
************
Expand Down
1 change: 1 addition & 0 deletions hardware_interface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
tinyxml2_vendor
joint_limits
urdf
pal_statistics
)

find_package(ament_cmake REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "hardware_interface/types/trigger_type.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "pal_statistics/pal_statistics_utils.hpp"
#include "rclcpp/duration.hpp"
#include "rclcpp/logger.hpp"
#include "rclcpp/node_interfaces/node_clock_interface.hpp"
Expand Down Expand Up @@ -93,7 +94,7 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
*/
ActuatorInterface(const ActuatorInterface & other) = delete;

ActuatorInterface(ActuatorInterface && other) = default;
ActuatorInterface(ActuatorInterface && other) = delete;

virtual ~ActuatorInterface() = default;

Expand Down Expand Up @@ -522,6 +523,22 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
*/
const HardwareInfo & get_hardware_info() const { return info_; }

/// Enable or disable introspection of the hardware.
/**
* \param[in] enable Enable introspection if true, disable otherwise.
*/
void enable_introspection(bool enable)
{
if (enable)
{
stats_registrations_.enableAll();
}
else
{
stats_registrations_.disableAll();
}
}

protected:
HardwareInfo info_;
// interface names to InterfaceDescription
Expand All @@ -548,6 +565,9 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
std::unordered_map<std::string, StateInterface::SharedPtr> actuator_states_;
std::unordered_map<std::string, CommandInterface::SharedPtr> actuator_commands_;
std::atomic<TriggerType> next_trigger_ = TriggerType::READ;

protected:
pal_statistics::RegistrationsRAII stats_registrations_;
};

} // namespace hardware_interface
Expand Down
41 changes: 41 additions & 0 deletions hardware_interface/include/hardware_interface/handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <variant>

#include "hardware_interface/hardware_info.hpp"
#include "hardware_interface/introspection.hpp"
#include "hardware_interface/macros.hpp"

namespace hardware_interface
Expand Down Expand Up @@ -207,6 +208,24 @@ class StateInterface : public Handle
{
}

void registerIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
std::function<double()> f = [this]()
{ return value_ptr_ ? *value_ptr_ : std::numeric_limits<double>::quiet_NaN(); };
REGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "state_interface." + get_name(), f);
}
}

void unregisterIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
UNREGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "state_interface." + get_name());
}
}

StateInterface(const StateInterface & other) = default;

StateInterface(StateInterface && other) = default;
Expand Down Expand Up @@ -234,6 +253,28 @@ class CommandInterface : public Handle

CommandInterface(CommandInterface && other) = default;

void registerIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
RCLCPP_INFO_STREAM(
rclcpp::get_logger("command_interface"), "Registering handle: " << get_name());
std::function<double()> f = [this]()
{ return value_ptr_ ? *value_ptr_ : std::numeric_limits<double>::quiet_NaN(); };
REGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "command_interface." + get_name(), f);
}
}

void unregisterIntrospection() const
{
if (std::holds_alternative<double>(value_))
{
RCLCPP_INFO_STREAM(
rclcpp::get_logger("command_interface"), "Unregistering handle: " << get_name());
UNREGISTER_ENTITY(DEFAULT_REGISTRY_KEY, "command_interface." + get_name());
}
}

using Handle::Handle;

using SharedPtr = std::shared_ptr<CommandInterface>;
Expand Down
Loading