From ac97a0cba5efa4ab90e4cced576a7fdcb7e8992a Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 18 Jan 2024 23:03:31 +0100 Subject: [PATCH 01/43] formatting fixes from PR324 (#327) --- diagnostic_aggregator/README.md | 7 +++++-- diagnostic_aggregator/mainpage.dox | 9 +++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/diagnostic_aggregator/README.md b/diagnostic_aggregator/README.md index e7f63c2b7..08178a066 100644 --- a/diagnostic_aggregator/README.md +++ b/diagnostic_aggregator/README.md @@ -14,7 +14,7 @@ The robot has two of each, one on each side. The robot also 4 camera sensors, one left and one right and one in the front and one in the back. These are all the available diagnostic sources: -``` +``` /arms/left/motor /arms/right/motor /legs/left/motor @@ -119,6 +119,9 @@ Additional parameters depend on the type of the analyzer. Any diagnostic item that is not matched by any analyzer will be published by an "Other" analyzer. Items created by the "Other" analyzer will go stale after 5 seconds. +The `critical` parameter makes the aggregator react immediately to a degradation in diagnostic state. +This is useful if the toplevel state is parsed by a watchdog for example. + ## Launching You can launch the `aggregator_node` like this (see [example.launch.py.in](example/example.launch.py.in)): ``` python @@ -186,4 +189,4 @@ This means that things that are ignored by the `IgnoreAnalyzer` will still be pu - `analyzers` (map, default: {}) - The analyzers that will be used to aggregate the diagnostics # Tutorials -TODO: Port tutorials #contributions-welcome \ No newline at end of file +TODO: Port tutorials #contributions-welcome diff --git a/diagnostic_aggregator/mainpage.dox b/diagnostic_aggregator/mainpage.dox index f7827de57..b720b86e4 100644 --- a/diagnostic_aggregator/mainpage.dox +++ b/diagnostic_aggregator/mainpage.dox @@ -16,7 +16,7 @@ See Analyzer for more information on the base class. \subsubsection generic_analyzer GenericAnalyzer -\b generic_analyzer holds the GenericAnalyzer class, which is the most basic of the Analyzer's. It is used by the diagnostic_aggregator/Aggregator to store, process and republish diagnostics data. The GenericAnalyzer is loaded by the pluginlib as a Analyzer plugin. It is the most basic of all Analyzer's. +\b generic_analyzer holds the GenericAnalyzer class, which is the most basic of the Analyzer's. It is used by the diagnostic_aggregator/Aggregator to store, process and republish diagnostics data. The GenericAnalyzer is loaded by the pluginlib as a Analyzer plugin. It is the most basic of all Analyzer's. \subsubsection analyzer_group AnalyzerGroup @@ -37,10 +37,10 @@ aggregator_node subscribes to "/diagnostics" and publishes an aggregated set of \subsubsection topics ROS topics Subscribes to: -- \b "/diagnostics": [diagnostics_msgs/DiagnosticArray] +- \b "/diagnostics": [diagnostics_msgs/DiagnosticArray] Publishes to: -- \b "/diagnostics_agg": [diagnostics_msgs/DiagnosticArray] +- \b "/diagnostics_agg": [diagnostics_msgs/DiagnosticArray] \subsubsection parameters ROS parameters @@ -49,6 +49,7 @@ Reads the following parameters from the parameter server - \b "~pub_rate" : \b double [optional] Rate that output diagnostics published - \b "~base_path" : \b double [optional] Prepended to all analyzed output - \b "~analyzers" : \b {} Configuration for loading analyzers +- \b "~critical" : \b bool [optional] React immediately to a degradation in diagnostic state \subsection analyzer_loader analyzer_loader @@ -61,4 +62,4 @@ Reads the following parameters from the parameter server - \b "~analyzers" : \b {} Configuration for loading and testing analyzers -*/ \ No newline at end of file +*/ From 183b7de119dcc9632410404950cfc64beb7de297 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:39:29 +0100 Subject: [PATCH 02/43] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 521414bf7..727ac3af2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Overview -The diagnostics system collects information about hardware drivers and robot hardware to make them availaible to users and operators. +The diagnostics system collects information about hardware drivers and robot hardware to make them available to users and operators. The diagnostics system contains tools to collect and analyze this data. The diagnostics system is build around the `/diagnostics` topic. The topic is used for `diagnostic_msgs/DiagnosticArray` messages. From a09c0b1f6f03880b520fcc0f8a09acc363711fcd Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:38:09 +0100 Subject: [PATCH 03/43] including depdency (#322) --- diagnostic_updater/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/diagnostic_updater/package.xml b/diagnostic_updater/package.xml index 7cf5923a4..c84dca98a 100644 --- a/diagnostic_updater/package.xml +++ b/diagnostic_updater/package.xml @@ -32,6 +32,7 @@ ament_lint_common launch launch_testing + launch_testing_ros python3-pytest rclcpp_lifecycle From 1401f487c272d72a96a194a8371ce282a6fb599a Mon Sep 17 00:00:00 2001 From: Andrew Symington Date: Thu, 25 Jan 2024 06:39:14 -0800 Subject: [PATCH 04/43] Avoid rolling up an ERROR state when empty GenericAnalyzer blocks are marked discard_stale, or when all of their items are STALE. (#315) * If discard_stale = true, don't interpret stale as ERROR when rolling up * Check in test to exercise behavior --------- Signed-off-by: Andrew Symington Signed-off-by: Christian Henkel Co-authored-by: Christian Henkel --- diagnostic_aggregator/CMakeLists.txt | 5 + .../generic_analyzer_base.hpp | 16 +- .../diagnostic_aggregator/other_analyzer.hpp | 2 +- diagnostic_aggregator/package.xml | 1 + .../test/test_discard_behavior.py | 299 ++++++++++++++++++ 5 files changed, 317 insertions(+), 6 deletions(-) create mode 100644 diagnostic_aggregator/test/test_discard_behavior.py diff --git a/diagnostic_aggregator/CMakeLists.txt b/diagnostic_aggregator/CMakeLists.txt index fd6c2a070..12aac3b1f 100644 --- a/diagnostic_aggregator/CMakeLists.txt +++ b/diagnostic_aggregator/CMakeLists.txt @@ -128,6 +128,11 @@ if(BUILD_TESTING) test/test_critical_pub.py TIMEOUT 30 ) + + ament_add_pytest_test(test_discard_behavior + "${CMAKE_CURRENT_SOURCE_DIR}/test/test_discard_behavior.py" + TIMEOUT 60 + ) endif() install( diff --git a/diagnostic_aggregator/include/diagnostic_aggregator/generic_analyzer_base.hpp b/diagnostic_aggregator/include/diagnostic_aggregator/generic_analyzer_base.hpp index 11a5b4f43..d7cb8a485 100644 --- a/diagnostic_aggregator/include/diagnostic_aggregator/generic_analyzer_base.hpp +++ b/diagnostic_aggregator/include/diagnostic_aggregator/generic_analyzer_base.hpp @@ -178,7 +178,7 @@ class GenericAnalyzerBase : public Analyzer auto header_status = std::make_shared(); header_status->name = path_; - header_status->level = 0; + header_status->level = diagnostic_msgs::msg::DiagnosticStatus::OK; header_status->message = "OK"; std::vector> processed; @@ -224,22 +224,28 @@ class GenericAnalyzerBase : public Analyzer // Header is not stale unless all subs are if (all_stale) { - header_status->level = diagnostic_msgs::msg::DiagnosticStatus::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 = 2; + 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 = 0; + header_status->level = diagnostic_msgs::msg::DiagnosticStatus::OK; header_status->message = "OK"; } else if ( // NOLINT num_items_expected_ > 0 && static_cast(items_.size()) != num_items_expected_) { // NOLINT - int8_t lvl = 2; + int8_t lvl = diagnostic_msgs::msg::DiagnosticStatus::ERROR; header_status->level = std::max(lvl, static_cast(header_status->level)); std::stringstream expec, item; diff --git a/diagnostic_aggregator/include/diagnostic_aggregator/other_analyzer.hpp b/diagnostic_aggregator/include/diagnostic_aggregator/other_analyzer.hpp index 3cb9162e4..67d0f1b0a 100644 --- a/diagnostic_aggregator/include/diagnostic_aggregator/other_analyzer.hpp +++ b/diagnostic_aggregator/include/diagnostic_aggregator/other_analyzer.hpp @@ -145,7 +145,7 @@ class OtherAnalyzer : public GenericAnalyzerBase processed.begin(); for (; it != processed.end(); ++it) { if ((*it)->name == path_) { - (*it)->level = 2; + (*it)->level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; (*it)->message = "Unanalyzed items found in \"Other\""; break; } diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index 31095edd0..4e0b89a35 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -31,6 +31,7 @@ ament_cmake_pytest ament_lint_auto ament_lint_common + launch_pytest launch_testing_ament_cmake launch_testing_ros diff --git a/diagnostic_aggregator/test/test_discard_behavior.py b/diagnostic_aggregator/test/test_discard_behavior.py new file mode 100644 index 000000000..16dee0162 --- /dev/null +++ b/diagnostic_aggregator/test/test_discard_behavior.py @@ -0,0 +1,299 @@ +# Copyright 2023 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# DESCRIPTION +# This test ensures that a parent AnalyzerGroup does not roll up an ERROR state when a +# GenericAnalyzer child block is marked with discard_stale: true and either of these +# conditions are met: +# +# 1. There are no statuses that match any of the GenericAnalyzer child blocks. +# 2. Every matching status in the GenericAnalyzer child block has been marked stale +# +# In this example, if foo and bar have no matching statuses or all of their statuses +# are STALE, they will roll up as OK because the discard_stale: true flag implies that +# stale statuses are disposable. +# +# analyzer: +# ros__parameters: +# path: 'agg' +# pub_rate: 1.0 +# analyzers: +# part: +# type: 'diagnostic_aggregator/AnalyzerGroup' +# path: 'part' +# foo: +# type: 'diagnostic_aggregator/GenericAnalyzer' +# path: 'foo' +# find_and_remove_prefix: ['/foo:'] +# num_items: 1 +# bar: +# type: 'diagnostic_aggregator/GenericAnalyzer' +# path: 'bar' +# find_and_remove_prefix: ['/bar:'] +# discard_stale: true + +# Python includes. +from collections import namedtuple +import tempfile + +# ROS2 includes. +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus +import launch +import launch_pytest +import launch_ros +import pytest +import rclpy +from rclpy.executors import SingleThreadedExecutor +from rclpy.node import Node +from rclpy.task import Future + + +# All tests take a common structure. +TestMetadata = namedtuple( + 'TestMetadata', + ['foo_discard', 'foo_status', 'bar_discard', 'bar_status', 'agg_expected'], +) + +# A status value of 'None' means that the state is never sent (it's missing). +TEST_METADATA = [ + # CASE 1: both 'foo' and 'bar' are marked discard_stale := true + TestMetadata( + foo_discard=True, + foo_status=None, + bar_discard=True, + bar_status=None, + agg_expected=DiagnosticStatus.OK, + ), + TestMetadata( + foo_discard=True, + foo_status=DiagnosticStatus.STALE, + bar_discard=True, + bar_status=DiagnosticStatus.STALE, + agg_expected=DiagnosticStatus.OK, + ), + TestMetadata( + foo_discard=True, + foo_status=None, + bar_discard=True, + bar_status=DiagnosticStatus.STALE, + agg_expected=DiagnosticStatus.OK, + ), + # CASE 2: both 'foo' and 'bar' are marked discard_stale := false + TestMetadata( + foo_discard=False, + foo_status=None, + bar_discard=False, + bar_status=None, + agg_expected=DiagnosticStatus.STALE, + ), + TestMetadata( + foo_discard=False, + foo_status=DiagnosticStatus.STALE, + bar_discard=False, + bar_status=DiagnosticStatus.STALE, + agg_expected=DiagnosticStatus.STALE, + ), + TestMetadata( + foo_discard=False, + foo_status=None, + bar_discard=False, + bar_status=DiagnosticStatus.STALE, + agg_expected=DiagnosticStatus.STALE, + ), + # CASE 3: one of 'foo' or 'bar' are marked discard_stale := true + TestMetadata( + foo_discard=True, + foo_status=None, + bar_discard=False, + bar_status=None, + agg_expected=DiagnosticStatus.ERROR, # <-- This is the case we are testing for. + # if one of the children is *not* marked discard_stale := true and + # there are no statuses, then the parent should roll up to ERROR. + ), + TestMetadata( + foo_discard=True, + foo_status=DiagnosticStatus.OK, + bar_discard=False, + bar_status=None, + agg_expected=DiagnosticStatus.ERROR, + ), + TestMetadata( + foo_discard=True, + foo_status=None, + bar_discard=False, + bar_status=DiagnosticStatus.OK, + agg_expected=DiagnosticStatus.OK, # <-- This is the case we are testing for. + # but if a child is marked discard_stale := true and there are no statuses, + # the parent should roll up to OK. + ), + TestMetadata( + foo_discard=True, + foo_status=DiagnosticStatus.OK, + bar_discard=False, + bar_status=DiagnosticStatus.OK, + agg_expected=DiagnosticStatus.OK, + ), +] + + +class DiagnosticsTestNode(Node): + """Class that publishes raw diagnostics and listens for aggregated diagnostics.""" + + def __init__(self, foo_status, bar_status, agg_expected): + super().__init__(node_name='diagnostics_listener_node') + self.foo_status = foo_status + self.bar_status = bar_status + self.agg_expected = agg_expected + self.agg_received = None + self.counter = 0 + self.future = Future() + self.subscriber = self.create_subscription( + msg_type=DiagnosticArray, + topic='/diagnostics_agg', + callback=self.diagnostics_aggregated_callback, + qos_profile=10, + ) + self.publisher = self.create_publisher( + msg_type=DiagnosticArray, topic='/diagnostics', qos_profile=10 + ) + self.timer = self.create_timer(0.1, self.timer_callback) + + def timer_callback(self): + """Call from a timer to send off raw diagnostics.""" + msg = DiagnosticArray() + msg.header.stamp = self.get_clock().now().to_msg() + msg.header.frame_id = 'robot' + if self.foo_status is not None: + msg.status.append( + DiagnosticStatus( + name='/foo', level=self.foo_status, message='Foo', hardware_id='foo' + ) + ) + if self.bar_status is not None: + msg.status.append( + DiagnosticStatus( + name='/bar', level=self.bar_status, message='Bar', hardware_id='bar' + ) + ) + self.publisher.publish(msg) + + def diagnostics_aggregated_callback(self, msg): + """Call from a subscriber providing aggregated diagnostics.""" + for status in msg.status: + if status.name == '/robot/agg': + self.agg_received = status.level + self.counter += 1 + if self.agg_expected == status.level: + # Diagnostics may take a few iterations to 'settle' into the right + # state because of how the STALE logic is applied. So, we keep checking + # the aggregator result until it reaches the value we are expecting, + # and then trigger the future. + self.future.set_result(self.counter) + + +@pytest.fixture(scope='function') +def test_metadata(request): + """Enable parameter indirection, so we can pass a parameterization into fixtures.""" + return request.param + + +@pytest.fixture(scope='function') +def yaml_file(test_metadata): + """Generate a YAML file to test a specific configuration state.""" + with tempfile.NamedTemporaryFile(delete=False) as fp: + fp.write( + bytes( + f""" +diagnostic_aggregator: + ros__parameters: + path: 'robot' + pub_rate: 1.0 + analyzers: + part: + type: 'diagnostic_aggregator/AnalyzerGroup' + path: 'agg' + timeout: 2.0 + foo: + type: 'diagnostic_aggregator/GenericAnalyzer' + path: 'foo' + find_and_remove_prefix: ['/foo'] + discard_stale: {test_metadata.foo_discard} + bar: + type: 'diagnostic_aggregator/GenericAnalyzer' + path: 'bar' + find_and_remove_prefix: ['/bar'] + discard_stale: {test_metadata.bar_discard} +""", + 'utf-8', + ) + ) + return fp.name + + +@pytest.fixture(scope='function') +def diagnostic_aggregator_node(): + """Declare an aggregator that uses a global configuration set by the launch.""" + return launch_ros.actions.Node( + name='diagnostic_aggregator', + package='diagnostic_aggregator', + executable='aggregator_node', + ) + + +@launch_pytest.fixture(scope='function') +def launch_description(yaml_file, diagnostic_aggregator_node): + """Declare what should be launched in each test.""" + return launch.LaunchDescription( + [ + launch_ros.actions.SetParametersFromFile(yaml_file), + diagnostic_aggregator_node, + launch_pytest.actions.ReadyToTest(), + ] + ) + + +@pytest.mark.parametrize('test_metadata', TEST_METADATA, indirect=True) +@pytest.mark.launch(fixture=launch_description) +def test_discard_behavior(test_metadata, launch_context): + """Run a launch test for each test in our set of tests.""" + rclpy.init() + + node = DiagnosticsTestNode( + foo_status=test_metadata.foo_status, + bar_status=test_metadata.bar_status, + agg_expected=test_metadata.agg_expected, + ) + + executor = SingleThreadedExecutor() + executor.add_node(node) + + try: + executor.spin_until_future_complete(future=node.future, timeout_sec=10.0) + print( + f""" + The test produced the following result: + + foo_level: {test_metadata.foo_status} (discard: {test_metadata.foo_discard}) + + bar_level: {test_metadata.bar_status} (discard: {test_metadata.bar_discard}) + Expected level: {test_metadata.agg_expected} + Received level: {node.agg_received} + """ + ) + assert node.future.done(), 'Launch timed out without producing aggregation' + assert ( + node.agg_received == test_metadata.agg_expected + ), 'Unexpected parent status level' + print(f'It took {node.future.result()} aggregations to find the correct status') + + finally: + rclpy.try_shutdown() From b1c8dd16694869b9bc8d778ef0386b9f5dba7437 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Thu, 21 Mar 2024 23:20:28 +0100 Subject: [PATCH 05/43] documentation on new branch Signed-off-by: Christian Henkel --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 727ac3af2..70032ebf6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ The [`ros2` branch](https://github.com/ros/diagnostics/tree/ros2) targets - *Iron Irwini* and - *Rolling Ridley* +The [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) targets + +- *Jazzy Jalisco* + # License The source code is released under a [BSD 3-Clause license](LICENSE). From f89beba1b2c8537399e0e38986d6f2356d5a1ebd Mon Sep 17 00:00:00 2001 From: Richard <47382675+RichardvdK@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:37:13 +0100 Subject: [PATCH 06/43] Port cpu_monitor to ROS2 (#326) * add cpu monitor to the common diagnostics * add 3 unittests * add psutil to package xml * add debug print for cpu percentages and change checking percentage to -1 * update code with review comments. Added a SetUp() method in the test and move the sleep to there to make sure this is done every test in a systemactic way --------- Co-authored-by: Richardvdketterij --- diagnostic_common_diagnostics/CMakeLists.txt | 7 +- diagnostic_common_diagnostics/README.md | 32 ++++- .../cpu_monitor.py | 119 ++++++++++++++++++ diagnostic_common_diagnostics/mainpage.dox | 1 + diagnostic_common_diagnostics/package.xml | 6 +- .../test/systemtest/test_cpu_monitor.py | 116 +++++++++++++++++ 6 files changed, 271 insertions(+), 10 deletions(-) create mode 100755 diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py create mode 100644 diagnostic_common_diagnostics/test/systemtest/test_cpu_monitor.py diff --git a/diagnostic_common_diagnostics/CMakeLists.txt b/diagnostic_common_diagnostics/CMakeLists.txt index 65720a08e..5c0f11f6b 100644 --- a/diagnostic_common_diagnostics/CMakeLists.txt +++ b/diagnostic_common_diagnostics/CMakeLists.txt @@ -8,6 +8,7 @@ find_package(ament_cmake_python REQUIRED) ament_python_install_package(${PROJECT_NAME}) install(PROGRAMS + ${PROJECT_NAME}/cpu_monitor.py ${PROJECT_NAME}/ntp_monitor.py DESTINATION lib/${PROJECT_NAME} ) @@ -17,10 +18,14 @@ if(BUILD_TESTING) ament_lint_auto_find_test_dependencies() find_package(ament_cmake_pytest REQUIRED) + ament_add_pytest_test( + test_cpu_monitor + test/systemtest/test_cpu_monitor.py + TIMEOUT 10) ament_add_pytest_test( test_ntp_monitor test/systemtest/test_ntp_monitor.py TIMEOUT 10) endif() -ament_package() \ No newline at end of file +ament_package() diff --git a/diagnostic_common_diagnostics/README.md b/diagnostic_common_diagnostics/README.md index 452d163ed..42df1c8dc 100644 --- a/diagnostic_common_diagnostics/README.md +++ b/diagnostic_common_diagnostics/README.md @@ -7,8 +7,31 @@ Currently only the NTP monitor is ported to ROS2. # Nodes +## cpu_monitor.py +The `cpu_monitor` module allows users to monitor the CPU usage of their system in real-time. +It publishes the usage percentage in a diagnostic message. + +* Name of the node is "cpu_monitor_" + hostname. +* Uses the following args: + * warning_percentage: If the CPU usage is > warning_percentage, a WARN status will be publised. + * window: the maximum length of the used collections.deque for queuing CPU readings. + +### Published Topics +#### /diagnostics +diagnostic_msgs/DiagnosticArray +The diagnostics information. + +### Parameters +#### warning_percentage +(default: 90) +warning percentage threshold. + +#### window +(default: 1) +Length of CPU readings queue. + ## ntp_monitor.py -Runs 'ntpdate' to check if the system clock is synchronized with the NTP server. +Runs 'ntpdate' to check if the system clock is synchronized with the NTP server. * If the offset is smaller than `offset-tolerance`, an `OK` status will be published. * If the offset is larger than the configured `offset-tolerance`, a `WARN` status will be published, * if it is bigger than `error-offset-tolerance`, an `ERROR` status will be published. @@ -20,7 +43,7 @@ diagnostic_msgs/DiagnosticArray The diagnostics information. ### Parameters -#### ntp_hostname +#### ntp_hostname (default: "pool.ntp.org") Hostname of NTP server. @@ -46,9 +69,6 @@ Disable self test. ## hd_monitor.py **To be ported** -## cpu_monitor.py -**To be ported** - ## ram_monitor.py **To be ported** @@ -56,4 +76,4 @@ Disable self test. **To be ported** ## tf_monitor.py -**To be ported** \ No newline at end of file +**To be ported** diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py new file mode 100755 index 000000000..866629572 --- /dev/null +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2017, TNO IVS, Helmond, Netherlands +# 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 TNO IVS 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. + +# \author Rein Appeldoorn + +from collections import deque +import socket +import traceback + +from diagnostic_msgs.msg import DiagnosticStatus + +from diagnostic_updater import DiagnosticTask, Updater + +import psutil + +import rclpy +from rclpy.node import Node + + +class CpuTask(DiagnosticTask): + + def __init__(self, warning_percentage=90, window=1): + DiagnosticTask.__init__(self, 'CPU Information') + + self._warning_percentage = int(warning_percentage) + self._readings = deque(maxlen=window) + + def _get_average_reading(self): + def avg(lst): + return float(sum(lst)) / len(lst) if lst else float('nan') + + return [avg(cpu_percentages) + for cpu_percentages in zip(*self._readings)] + + def run(self, stat): + self._readings.append(psutil.cpu_percent(percpu=True)) + cpu_percentages = self._get_average_reading() + cpu_average = sum(cpu_percentages) / len(cpu_percentages) + + stat.add('CPU Load Average', f'{cpu_average:.2f}') + + warn = False + for idx, cpu_percentage in enumerate(cpu_percentages): + stat.add(f'CPU {idx} Load', f'{cpu_percentage:.2f}') + if cpu_percentage > self._warning_percentage: + warn = True + + if warn: + stat.summary(DiagnosticStatus.WARN, + f'At least one CPU exceeds {self._warning_percentage} percent') + else: + stat.summary(DiagnosticStatus.OK, + f'CPU Average {cpu_average:.2f} percent') + + return stat + + +def main(args=None): + rclpy.init(args=args) + + # Create the node + hostname = socket.gethostname() + node = Node(f'cpu_monitor_{hostname.replace("-", "_")}') + + # Declare and get parameters + node.declare_parameter('warning_percentage', 90) + node.declare_parameter('window', 1) + + warning_percentage = node.get_parameter( + 'warning_percentage').get_parameter_value().integer_value + window = node.get_parameter('window').get_parameter_value().integer_value + + # Create diagnostic updater with default updater rate of 1 hz + updater = Updater(node) + updater.setHardwareID(hostname) + updater.add(CpuTask(warning_percentage=warning_percentage, window=window)) + + rclpy.spin(node) + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass + except Exception: + traceback.print_exc() diff --git a/diagnostic_common_diagnostics/mainpage.dox b/diagnostic_common_diagnostics/mainpage.dox index 25ee9c863..7aa872cef 100644 --- a/diagnostic_common_diagnostics/mainpage.dox +++ b/diagnostic_common_diagnostics/mainpage.dox @@ -4,6 +4,7 @@ \b diagnostic_common_diagnostics contains a few common diagnostic nodes +- cpu_monitor publishes diagnostic messages with the CPU usage of the system. - ntp_monitor publishes diagnostic messages for how well the NTP time sync is working. - tf_monitor used to publish diagnostic messages reporting on the health of the TF tree. It is based on tfwtf. It is not ported to ROS2. diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index 9d50bbe33..f6bad9205 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -18,20 +18,20 @@ ament_cmake ament_cmake_python - rclpy diagnostic_updater python3-ntplib + python3-psutil ament_lint_auto ament_cmake_xmllint ament_cmake_lint_cmake - + ament_cmake_pytest diff --git a/diagnostic_common_diagnostics/test/systemtest/test_cpu_monitor.py b/diagnostic_common_diagnostics/test/systemtest/test_cpu_monitor.py new file mode 100644 index 000000000..28430c482 --- /dev/null +++ b/diagnostic_common_diagnostics/test/systemtest/test_cpu_monitor.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# Software License Agreement (BSD License) +# +# Copyright (c) 2023, Robert Bosch GmbH +# 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. + +import time +import unittest + +from diagnostic_common_diagnostics.cpu_monitor import CpuTask + +from diagnostic_msgs.msg import DiagnosticStatus + +from diagnostic_updater import DiagnosticArray, Updater +from diagnostic_updater import DiagnosticStatusWrapper + +import rclpy +from rclpy.node import Node + + +class TestCPUMonitor(unittest.TestCase): + + @classmethod + def setUpClass(cls): + rclpy.init(args=None) + + @classmethod + def tearDownClass(cls): + if rclpy.ok(): + rclpy.shutdown() + + def setUp(self): + # In this case is recommended for accuracy that psutil.cpu_percent() + # function be called with at least 0.1 seconds between calls. + time.sleep(0.1) + + def diagnostics_callback(self, msg): + self.message_recieved = True + self.assertEqual(len(msg.status), 1) + + def test_ok(self): + warning_percentage = 100 + task = CpuTask(warning_percentage) + stat = DiagnosticStatusWrapper() + task.run(stat) + self.assertEqual(task.name, 'CPU Information') + self.assertEqual(stat.level, DiagnosticStatus.OK) + self.assertIn(str('CPU Average'), stat.message) + + # Check for at least 1 CPU Load Average and 1 CPU Load + self.assertGreaterEqual(len(stat.values), 2) + + def test_warn(self): + warning_percentage = -1 + task = CpuTask(warning_percentage) + stat = DiagnosticStatusWrapper() + task.run(stat) + print(f'Raw readings: {task._readings}') + self.assertEqual(task.name, 'CPU Information') + self.assertEqual(stat.level, DiagnosticStatus.WARN) + self.assertIn(str('At least one CPU exceeds'), stat.message) + + # Check for at least 1 CPU Load Average and 1 CPU Load + self.assertGreaterEqual(len(stat.values), 2) + + def test_updater(self): + self.message_recieved = False + + node = Node('cpu_monitor_test') + updater = Updater(node) + updater.setHardwareID('test_id') + updater.add(CpuTask()) + + node.create_subscription( + DiagnosticArray, '/diagnostics', self.diagnostics_callback, 10) + + start_time = time.time() + timeout = 5.0 # Timeout in seconds + + while not self.message_recieved: + rclpy.spin_once(node) + time.sleep(0.1) + elapsed_time = time.time() - start_time + if elapsed_time >= timeout: + self.fail('No diagnostics received') + + +if __name__ == '__main__': + unittest.main() From 83c030e02514c185c91cea4b63da662566cb7039 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Fri, 22 Mar 2024 13:56:40 +0100 Subject: [PATCH 07/43] Rolling obviously also uses the jazzy branch Signed-off-by: Christian Henkel --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 70032ebf6..96c20ae59 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ Diagnostics messages that are not aggregated can be visualized by [`rqt_runtime_ The [`ros2` branch](https://github.com/ros/diagnostics/tree/ros2) targets - *Humble Hawksbill* -- *Iron Irwini* and -- *Rolling Ridley* +- *Iron Irwini* The [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) targets - *Jazzy Jalisco* +- *Rolling Ridley* # License From 07ed478a65a6b94bdf5419d41f6ee9c5a102a8d1 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Fri, 22 Mar 2024 14:16:26 +0100 Subject: [PATCH 08/43] changelogs Signed-off-by: Christian Henkel --- diagnostic_aggregator/CHANGELOG.rst | 10 ++++++++++ diagnostic_common_diagnostics/CHANGELOG.rst | 8 ++++++++ diagnostic_updater/CHANGELOG.rst | 9 +++++++++ diagnostics/CHANGELOG.rst | 3 +++ self_test/CHANGELOG.rst | 5 +++++ 5 files changed, 35 insertions(+) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index 9c48d2f13..8cf482f68 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,6 +2,16 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Avoid rolling up an ERROR state when empty GenericAnalyzer blocks are marked discard_stale, or when all of their items are STALE. (`#315 `_) +* formatting fixes from PR324 (`#327 `_) +* Debugging instability introduced by `#317 `_ (`#323 `_) +* feat: publish top level msg when error is received (`#317 `_) +* Empty default aggregator base_path (`#305 `_) +* using defined state for stale (`#298 `_) +* Contributors: Andrew Symington, Christian Henkel, outrider-jhulas + 3.1.2 (2023-03-24) ------------------ diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index 506989c68..958b01459 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Port cpu_monitor to ROS2 (`#326 `_) +* Debugging instability introduced by `#317 `_ (`#323 `_) +* not testing on foxy any more (`#310 `_) +* Iron support (`#304 `_) +* Contributors: Christian Henkel, Richard + 3.1.2 (2023-03-24) ------------------ * replacing ntpdate with ntplib (`#289 `_) diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index a4e692168..f85ecba49 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,6 +2,15 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* including depdency (`#322 `_) +* Debugging instability introduced by `#317 `_ (`#323 `_) +* feat: add param to use fqn in updater (`#320 `_) +* fix: method names & verbose logging (`#307 `_) +* Fix diagnostic_updater timestamps (`#299 `_) +* Contributors: Christian Henkel, Kevin Schwarzer, h-wata, outrider-jhulas + 3.1.2 (2023-03-24) ------------------ diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index 9e45ec832..992fbc2f3 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 3.1.2 (2023-03-24) ------------------ diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index fe0c3e22f..ed2779987 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Self test publishes the service under the node name, again (`#269 `_) +* Contributors: Christian Henkel + 3.1.2 (2023-03-24) ------------------ From c1b908cd83554a930d9076560041a8b1173252e9 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Fri, 22 Mar 2024 15:18:21 +0100 Subject: [PATCH 09/43] 3.2.0 --- diagnostic_aggregator/CHANGELOG.rst | 4 ++-- diagnostic_aggregator/package.xml | 2 +- diagnostic_common_diagnostics/CHANGELOG.rst | 4 ++-- diagnostic_common_diagnostics/package.xml | 2 +- diagnostic_updater/CHANGELOG.rst | 4 ++-- diagnostic_updater/package.xml | 2 +- diagnostics/CHANGELOG.rst | 4 ++-- diagnostics/package.xml | 2 +- self_test/CHANGELOG.rst | 4 ++-- self_test/package.xml | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index 8cf482f68..efda16d09 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.0 (2024-03-22) +------------------ * Avoid rolling up an ERROR state when empty GenericAnalyzer blocks are marked discard_stale, or when all of their items are STALE. (`#315 `_) * formatting fixes from PR324 (`#327 `_) * Debugging instability introduced by `#317 `_ (`#323 `_) diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index 4e0b89a35..677e04365 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -2,7 +2,7 @@ diagnostic_aggregator - 3.1.2 + 3.2.0 diagnostic_aggregator Austin Hendrix Brice Rebsamen diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index 958b01459..0c53e9ca1 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.0 (2024-03-22) +------------------ * Port cpu_monitor to ROS2 (`#326 `_) * Debugging instability introduced by `#317 `_ (`#323 `_) * not testing on foxy any more (`#310 `_) diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index f6bad9205..de8808d70 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostic_common_diagnostics - 3.1.2 + 3.2.0 diagnostic_common_diagnostics Austin Hendrix Brice Rebsamen diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index f85ecba49..e5d4da069 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.0 (2024-03-22) +------------------ * including depdency (`#322 `_) * Debugging instability introduced by `#317 `_ (`#323 `_) * feat: add param to use fqn in updater (`#320 `_) diff --git a/diagnostic_updater/package.xml b/diagnostic_updater/package.xml index c84dca98a..6935d9acb 100644 --- a/diagnostic_updater/package.xml +++ b/diagnostic_updater/package.xml @@ -2,7 +2,7 @@ diagnostic_updater - 3.1.2 + 3.2.0 diagnostic_updater contains tools for easily updating diagnostics. it is commonly used in device drivers to keep track of the status of output topics, device status, etc. Austin Hendrix Brice Rebsamen diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index 992fbc2f3..1f73b1330 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.0 (2024-03-22) +------------------ 3.1.2 (2023-03-24) ------------------ diff --git a/diagnostics/package.xml b/diagnostics/package.xml index c581103e0..9445c1f93 100644 --- a/diagnostics/package.xml +++ b/diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostics - 3.1.2 + 3.2.0 diagnostics Austin Hendrix Brice Rebsamen diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index ed2779987..1ebff1e4a 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.0 (2024-03-22) +------------------ * Self test publishes the service under the node name, again (`#269 `_) * Contributors: Christian Henkel diff --git a/self_test/package.xml b/self_test/package.xml index 600cc9cd4..6667d9b8f 100644 --- a/self_test/package.xml +++ b/self_test/package.xml @@ -2,7 +2,7 @@ self_test - 3.1.2 + 3.2.0 self_test Austin Hendrix Brice Rebsamen From 50770166eb7f5cead0687239d5c84f4712c2683f Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Fri, 22 Mar 2024 16:40:45 +0100 Subject: [PATCH 10/43] trying fix Signed-off-by: Christian Henkel --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2fe97d371..407b1ddbc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,7 +31,7 @@ jobs: os: ubuntu-22.04 runs-on: ${{ matrix.os }} steps: - - uses: ros-tooling/setup-ros@master + - uses: ct2034/setup-ros@patch-1 - run: | sudo pip install pydocstyle==6.1.1 # downgrade to fix https://github.com/ament/ament_lint/pull/428 sudo pip install pip --upgrade From 194753aa87ef2998bd1d65927e5a9ebf46534c21 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Fri, 22 Mar 2024 16:42:59 +0100 Subject: [PATCH 11/43] Revert "trying fix" This reverts commit 50770166eb7f5cead0687239d5c84f4712c2683f. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 407b1ddbc..2fe97d371 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,7 +31,7 @@ jobs: os: ubuntu-22.04 runs-on: ${{ matrix.os }} steps: - - uses: ct2034/setup-ros@patch-1 + - uses: ros-tooling/setup-ros@master - run: | sudo pip install pydocstyle==6.1.1 # downgrade to fix https://github.com/ament/ament_lint/pull/428 sudo pip install pip --upgrade From 8ee564091a06c980c50188ed6a8404e3a5484d71 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 28 Mar 2024 09:37:05 +0100 Subject: [PATCH 12/43] Building in docker (#335) * runs on container * all on ubuntu latest * pydocstyle fix is obsolete * no uncrustify for noble --------- Signed-off-by: Christian Henkel --- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 17 +++++++++-------- self_test/CMakeLists.txt | 4 ++++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 010a90157..3488e94ea 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,7 +22,7 @@ jobs: uncrustify, xmllint, ] - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest env: AMENT_CPPCHECK_ALLOW_SLOW_VERSIONS: 1 steps: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2fe97d371..4d1497641 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,18 +24,19 @@ jobs: distro: [humble, iron, rolling] include: - distro: humble - os: ubuntu-22.04 + os: 22.04 - distro: iron - os: ubuntu-22.04 + os: 22.04 - distro: rolling - os: ubuntu-22.04 - runs-on: ${{ matrix.os }} + os: 24.04 + runs-on: ubuntu-latest + container: ubuntu:${{ matrix.os }} steps: - uses: ros-tooling/setup-ros@master - - run: | - sudo pip install pydocstyle==6.1.1 # downgrade to fix https://github.com/ament/ament_lint/pull/428 - sudo pip install pip --upgrade - sudo pip install pyopenssl --upgrade # fix for AttributeError: module 'lib' has no attribute 'X509_V_FLAG_CB_ISSUER_CHECK' + # - run: | + # sudo apt install -y python3-pip + # pip3 install --break-system-packages 'flake8<5' # fix flake8.exceptions.FailedToLoadPlugin: Flake8 failed to load plugin "pycodestyle" due to cannot import name 'missing_whitespace_around_operator' from 'pycodestyle' (/usr/lib/python3/dist-packages/pycodestyle.py). + # if: ${{ matrix.os == '24.04' }} - uses: ros-tooling/action-ros-ci@master with: target-ros2-distro: ${{ matrix.distro }} diff --git a/self_test/CMakeLists.txt b/self_test/CMakeLists.txt index 0acbaa640..46401f9ed 100644 --- a/self_test/CMakeLists.txt +++ b/self_test/CMakeLists.txt @@ -67,6 +67,10 @@ if(BUILD_TESTING) set(ament_cmake_copyright_FOUND TRUE) ament_lint_auto_find_test_dependencies() + list(APPEND AMENT_LINT_AUTO_EXCLUDE + ament_cmake_uncrustify # Inconsistent between jammy and noble + ) + add_subdirectory(test) endif() From 431926f647cfc54ffbf729cd9c098b1764604d66 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:17:56 +0100 Subject: [PATCH 13/43] Fixing ntp launchtest (#330) - using a launchtest --------- Signed-off-by: Christian Henkel --- .github/workflows/test.yaml | 4 - diagnostic_common_diagnostics/CMakeLists.txt | 9 +- .../ntp_monitor.py | 20 ++- diagnostic_common_diagnostics/package.xml | 1 + .../test/systemtest/test_ntp_monitor.py | 130 ------------------ .../systemtest/test_ntp_monitor_launchtest.py | 108 +++++++++------ 6 files changed, 88 insertions(+), 184 deletions(-) delete mode 100644 diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4d1497641..1e07c0adc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,10 +33,6 @@ jobs: container: ubuntu:${{ matrix.os }} steps: - uses: ros-tooling/setup-ros@master - # - run: | - # sudo apt install -y python3-pip - # pip3 install --break-system-packages 'flake8<5' # fix flake8.exceptions.FailedToLoadPlugin: Flake8 failed to load plugin "pycodestyle" due to cannot import name 'missing_whitespace_around_operator' from 'pycodestyle' (/usr/lib/python3/dist-packages/pycodestyle.py). - # if: ${{ matrix.os == '24.04' }} - uses: ros-tooling/action-ros-ci@master with: target-ros2-distro: ${{ matrix.distro }} diff --git a/diagnostic_common_diagnostics/CMakeLists.txt b/diagnostic_common_diagnostics/CMakeLists.txt index 5c0f11f6b..e62c86ec0 100644 --- a/diagnostic_common_diagnostics/CMakeLists.txt +++ b/diagnostic_common_diagnostics/CMakeLists.txt @@ -15,6 +15,7 @@ install(PROGRAMS if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) + find_package(launch_testing_ament_cmake REQUIRED) ament_lint_auto_find_test_dependencies() find_package(ament_cmake_pytest REQUIRED) @@ -22,10 +23,10 @@ if(BUILD_TESTING) test_cpu_monitor test/systemtest/test_cpu_monitor.py TIMEOUT 10) - ament_add_pytest_test( - test_ntp_monitor - test/systemtest/test_ntp_monitor.py - TIMEOUT 10) + add_launch_test( + test/systemtest/test_ntp_monitor_launchtest.py + TARGET ntp_monitor_launchtest + TIMEOUT 20) endif() ament_package() diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py index 4d52e9e84..9462cebb3 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py @@ -47,13 +47,14 @@ class NTPMonitor(Node): """A diagnostic task that monitors the NTP offset of the system clock.""" - def __init__(self, ntp_hostname, offset=500, self_offset=500, + def __init__(self, ntp_hostname, ntp_port, offset=500, self_offset=500, diag_hostname=None, error_offset=5000000, do_self_test=True): """Initialize the NTPMonitor.""" super().__init__(__class__.__name__) self.ntp_hostname = ntp_hostname + self.ntp_port = ntp_port self.offset = offset self.self_offset = self_offset self.diag_hostname = diag_hostname @@ -67,7 +68,8 @@ def __init__(self, ntp_hostname, offset=500, self_offset=500, self.stat = DIAG.DiagnosticStatus() self.stat.level = DIAG.DiagnosticStatus.OK self.stat.name = 'NTP offset from ' + \ - self.diag_hostname + ' to ' + self.ntp_hostname + self.diag_hostname + ' to ' + self.ntp_hostname + \ + ':' + str(self.ntp_port) self.stat.message = 'OK' self.stat.hardware_id = self.hostname self.stat.values = [] @@ -127,7 +129,10 @@ def add_kv(stat_values, key, value): ntp_client = ntplib.NTPClient() response = None try: - response = ntp_client.request(self.ntp_hostname, version=3) + response = ntp_client.request( + self.ntp_hostname, + port=self.ntp_port, + version=3) except ntplib.NTPException as e: self.get_logger().error(f'NTP Error: {e}') st.level = DIAG.DiagnosticStatus.ERROR @@ -155,11 +160,17 @@ def add_kv(stat_values, key, value): def ntp_monitor_main(argv=sys.argv[1:]): + # filter out ROS args + argv = [a for a in argv if not a.startswith('__') and not a == '--ros-args' and not a == '-r'] + import argparse parser = argparse.ArgumentParser() parser.add_argument('--ntp_hostname', action='store', default='0.pool.ntp.org', type=str) + parser.add_argument('--ntp_port', + action='store', default=123, + type=int) parser.add_argument('--offset-tolerance', dest='offset_tol', action='store', default=500, help='Offset from NTP host [us]', metavar='OFFSET-TOL', @@ -188,7 +199,8 @@ def ntp_monitor_main(argv=sys.argv[1:]): assert offset < error_offset, \ 'Offset tolerance must be less than error offset tolerance' - ntp_monitor = NTPMonitor(args.ntp_hostname, offset, self_offset, + ntp_monitor = NTPMonitor(args.ntp_hostname, args.ntp_port, + offset, self_offset, args.diag_hostname, error_offset, args.do_self_test) diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index de8808d70..8496593a0 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -33,6 +33,7 @@ ament_cmake_lint_cmake ament_cmake_pytest + launch_testing_ament_cmake ament_cmake diff --git a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor.py b/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor.py deleted file mode 100644 index 767ed2ac6..000000000 --- a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Software License Agreement (BSD License) -# -# Copyright (c) 2023, Robert Bosch GmbH -# 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. - -import os -import subprocess -import unittest - -import ament_index_python - -from diagnostic_msgs.msg import DiagnosticArray - -import rclpy - -TIMEOUT_MAX_S = 5. - - -class TestNTPMonitor(unittest.TestCase): - - def __init__(self, methodName: str = 'runTest') -> None: - super().__init__(methodName) - rclpy.init() - self.n_msgs_received = 0 - - def setUp(self): - self.n_msgs_received = 0 - n = self._count_msgs(1.) - self.assertEqual(n, 0) - self.subprocess = subprocess.Popen( - [ - os.path.join( - ament_index_python.get_package_prefix( - 'diagnostic_common_diagnostics' - ), - 'lib', - 'diagnostic_common_diagnostics', - 'ntp_monitor.py' - ) - ] - ) - - def tearDown(self): - self.subprocess.kill() - - def _diagnostics_callback(self, msg): - rclpy.logging.get_logger('test_ntp_monitor').info( - f'Received diagnostics message: {msg}' - ) - search_strings = [ - 'NTP offset from', - 'NTP self-offset for' - ] - for search_string in search_strings: - if search_string in ''.join([ - s.name for s in msg.status - ]): - self.n_msgs_received += 1 - - def _count_msgs(self, timeout_s): - self.n_msgs_received = 0 - node = rclpy.create_node('test_ntp_monitor') - rclpy.logging.get_logger('test_ntp_monitor').info( - '_count_msgs' - ) - node.create_subscription( - DiagnosticArray, - 'diagnostics', - self._diagnostics_callback, - 1 - ) - TIME_D_S = .05 - waited_s = 0. - start = node.get_clock().now() - while waited_s < timeout_s and self.n_msgs_received == 0: - rclpy.spin_once(node, timeout_sec=TIME_D_S) - waited_s = (node.get_clock().now() - start).nanoseconds / 1e9 - rclpy.logging.get_logger('test_ntp_monitor').info( - f'received {self.n_msgs_received} messages after {waited_s}s' - ) - node.destroy_node() - return self.n_msgs_received - - def test_publishing(self): - self.assertEqual( - self.subprocess.poll(), - None, - 'NTP monitor subprocess died' - ) - - n = self._count_msgs(TIMEOUT_MAX_S) - - self.assertGreater( - n, - 0, - f'No messages received within {TIMEOUT_MAX_S}s' - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py b/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py index d70470ed2..495c9b684 100644 --- a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py +++ b/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py @@ -32,60 +32,84 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -import os +import unittest -import ament_index_python +from diagnostic_msgs.msg import DiagnosticArray import launch -import launch_pytest -from launch_pytest.tools import process as process_tools +import launch_ros import launch_testing +from launch_testing_ros import WaitForTopics + import pytest +import rclpy + -@pytest.fixture -def ntp_monitor_proc(): - # Launch a process to test - return launch.actions.ExecuteProcess( - cmd=[ - os.path.join( - ament_index_python.get_package_prefix( - 'diagnostic_common_diagnostics'), - 'lib', - 'diagnostic_common_diagnostics', - 'ntp_monitor.py' - ), - ], - ), - - -@launch_pytest.fixture -def launch_description(ntp_monitor_proc): +@pytest.mark.launch_test +def generate_test_description(): + """Launch the ntp_monitor node and return a launch description.""" return launch.LaunchDescription([ - ntp_monitor_proc, + launch_ros.actions.Node( + package='diagnostic_common_diagnostics', + executable='ntp_monitor.py', + name='ntp_monitor', + output='screen', + arguments=['--offset-tolerance', '10000', + '--error-offset-tolerance', '20000'] + # 10s, 20s, we are not testing if your clock is correct + ), launch_testing.actions.ReadyToTest() ]) -@pytest.mark.skip(reason='This test is not working yet') -@pytest.mark.launch(fixture=launch_description) -def test_read_stdout(ntp_monitor_proc, launch_context): - """Check if 'ntp_monitor' was found in the stdout.""" - def validate_output(output): - # this function can use assertions to validate the output or return a boolean. - # pytest generates easier to understand failures when assertions are used. - assert output.splitlines() == [ - 'ntp_monitor'], 'process never printed ntp_monitor' - process_tools.assert_output_sync( - launch_context, ntp_monitor_proc, validate_output, timeout=5) - - def validate_output(output): - return output == 'this will never happen' - assert not process_tools.wait_for_output_sync( - launch_context, ntp_monitor_proc, validate_output, timeout=0.1) - yield - # this is executed after launch service shutdown - assert ntp_monitor_proc.return_code == 0 +class TestNtpMonitor(unittest.TestCase): + """Test if the ntp_monitor node is publishing diagnostics.""" + + def __init__(self, methodName: str = 'runTest') -> None: + super().__init__(methodName) + self.received_messages = [] + + def _received_message(self, msg): + self.received_messages.append(msg) + + def _get_min_level(self): + levels = [ + int.from_bytes(status.level, 'little') + for diag in self.received_messages + for status in diag.status] + if len(levels) == 0: + return -1 + return min(levels) + + def test_topic_published(self): + """Test if the ntp_monitor node is publishing diagnostics.""" + with WaitForTopics( + [('/diagnostics', DiagnosticArray)], + timeout=5 + ): + print('Topic found') + + rclpy.init() + test_node = rclpy.create_node('test_node') + test_node.create_subscription( + DiagnosticArray, + '/diagnostics', + self._received_message, + 1 + ) + + while len(self.received_messages) < 10: + rclpy.spin_once(test_node, timeout_sec=1) + if (min_level := self._get_min_level()) == 0: + break + + test_node.destroy_node() + rclpy.shutdown() + print(f'Got {len(self.received_messages)} messages:') + for msg in self.received_messages: + print(msg) + self.assertEqual(min_level, 0) From 1d19a204aae9a13bbfd08961990efc73a240626b Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Mon, 13 May 2024 14:42:13 +0200 Subject: [PATCH 14/43] only one distro per branch Signed-off-by: Christian Henkel --- .github/workflows/test.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1e07c0adc..ab3f6a3d3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,12 +21,7 @@ jobs: diagnostic_updater, self_test, ] - distro: [humble, iron, rolling] include: - - distro: humble - os: 22.04 - - distro: iron - os: 22.04 - distro: rolling os: 24.04 runs-on: ubuntu-latest From e319448b9821b7fcdb5af0e07f762f61307500d7 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Mon, 13 May 2024 14:50:31 +0200 Subject: [PATCH 15/43] readme update Signed-off-by: Christian Henkel --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 96c20ae59..6766d3186 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Test diagnostics](https://img.shields.io/github/actions/workflow/status/ros/diagnostics/test.yaml?label=test&style=flat-square)](https://github.com/ros/diagnostics/actions/workflows/test.yaml) [![Lint diagnostics](https://img.shields.io/github/actions/workflow/status/ros/diagnostics/lint.yaml?label=lint&style=flat-square)](https://github.com/ros/diagnostics/actions/workflows/lint.yaml) [![ROS2 Humble](https://img.shields.io/ros/v/humble/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#humble) [![ROS2 Iron](https://img.shields.io/ros/v/iron/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#iron) [![ROS2 Rolling](https://img.shields.io/ros/v/rolling/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#rolling) +[![Test diagnostics](https://img.shields.io/github/actions/workflow/status/ros/diagnostics/test.yaml?label=test&style=flat-square)](https://github.com/ros/diagnostics/actions/workflows/test.yaml) [![Lint diagnostics](https://img.shields.io/github/actions/workflow/status/ros/diagnostics/lint.yaml?label=lint&style=flat-square)](https://github.com/ros/diagnostics/actions/workflows/lint.yaml) [![ROS2 Humble](https://img.shields.io/ros/v/humble/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#humble) [![ROS2 Iron](https://img.shields.io/ros/v/iron/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#iron) [![ROS2 Jazzy](https://img.shields.io/ros/v/jazzy/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#jazzy) [![ROS2 Rolling](https://img.shields.io/ros/v/rolling/diagnostics.svg?style=flat-square)](https://index.ros.org/r/diagnostics/#rolling) # Overview @@ -34,15 +34,10 @@ Diagnostics messages that are not aggregated can be visualized by [`rqt_runtime_ # Target Distribution -The [`ros2` branch](https://github.com/ros/diagnostics/tree/ros2) targets - -- *Humble Hawksbill* -- *Iron Irwini* - -The [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) targets - -- *Jazzy Jalisco* -- *Rolling Ridley* +- **Rolling Ridley** by the [`ros2` branch](https://github.com/ros/diagnostics/tree/ros2) +- **Humble Hawksbill** by the [`ros2-humble` branch](https://github.com/ros/diagnostics/tree/ros2-humble) +- **Iron Irwini** by the [`ros2-iron` branch](https://github.com/ros/diagnostics/tree/ros2-iron) +- **Jazzy Jalisco** by the [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) # License From c4f723161e95277a89d48ed4acc38b8a008368c3 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Tue, 14 May 2024 11:21:35 +0200 Subject: [PATCH 16/43] Using ubuntu ntp server in systemtest (#346) * testing with ubuntu ntp server * message makes a little more sense with abs * bigger offsets allowed * organizing imports with isort profile `google` --------- Signed-off-by: Christian Henkel --- .../diagnostic_common_diagnostics/ntp_monitor.py | 8 +++----- .../test/systemtest/test_ntp_monitor_launchtest.py | 13 ++++--------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py index 9462cebb3..8671ddb5b 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py @@ -37,9 +37,7 @@ import threading import diagnostic_updater as DIAG - import ntplib - import rclpy from rclpy.node import Node @@ -145,16 +143,16 @@ def add_kv(stat_values, key, value): if (abs(measured_offset) > self.offset): st.level = DIAG.DiagnosticStatus.WARN st.message = \ - f'NTP offset above threshold: {measured_offset}>'\ + f'NTP offset above threshold: abs({measured_offset})>'\ f'{self.offset} us' if (abs(measured_offset) > self.error_offset): st.level = DIAG.DiagnosticStatus.ERROR st.message = \ - f'NTP offset above error threshold: {measured_offset}>'\ + f'NTP offset above error threshold: abs({measured_offset})>'\ f'{self.error_offset} us' if (abs(measured_offset) < self.offset): st.level = DIAG.DiagnosticStatus.OK - st.message = f'NTP Offset OK: {measured_offset} us' + st.message = f'NTP Offset OK: abs({measured_offset}) us' return st diff --git a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py b/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py index 495c9b684..ede17b0f4 100644 --- a/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py +++ b/diagnostic_common_diagnostics/test/systemtest/test_ntp_monitor_launchtest.py @@ -35,17 +35,11 @@ import unittest from diagnostic_msgs.msg import DiagnosticArray - import launch - import launch_ros - import launch_testing - from launch_testing_ros import WaitForTopics - import pytest - import rclpy @@ -58,9 +52,10 @@ def generate_test_description(): executable='ntp_monitor.py', name='ntp_monitor', output='screen', - arguments=['--offset-tolerance', '10000', - '--error-offset-tolerance', '20000'] - # 10s, 20s, we are not testing if your clock is correct + arguments=['--offset-tolerance', '100000', + '--error-offset-tolerance', '200000', + '--ntp_hostname', 'ntp.ubuntu.com'] + # 100s, 200s, we are not testing if your clock is correct ), launch_testing.actions.ReadyToTest() ]) From 7efb71ad5118f6b7d57d32d8e6b93ce4aa53e41e Mon Sep 17 00:00:00 2001 From: Tony Najjar Date: Tue, 14 May 2024 16:03:23 +0200 Subject: [PATCH 17/43] NTP monitor improvements (#342) * NTP monitor improvements * commit to run tests again * bring back double callback * remove whiteline --------- Co-authored-by: Angsa Deployment Team --- .../diagnostic_common_diagnostics/ntp_monitor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py index 8671ddb5b..461d17bde 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py @@ -50,6 +50,8 @@ def __init__(self, ntp_hostname, ntp_port, offset=500, self_offset=500, do_self_test=True): """Initialize the NTPMonitor.""" super().__init__(__class__.__name__) + self.declare_parameter('frequency', 10.0) + frequency = self.get_parameter('frequency').get_parameter_value().double_value self.ntp_hostname = ntp_hostname self.ntp_port = ntp_port @@ -85,8 +87,8 @@ def __init__(self, ntp_hostname, ntp_port, offset=500, self_offset=500, # we need to periodically republish this self.current_msg = None - self.pubtimer = self.create_timer(0.1, self.pubCB) - self.checktimer = self.create_timer(0.1, self.checkCB) + self.pubtimer = self.create_timer(1/frequency, self.pubCB) + self.checktimer = self.create_timer(1/frequency, self.checkCB) def pubCB(self): with self.mutex: @@ -95,6 +97,7 @@ def pubCB(self): def checkCB(self): new_msg = DIAG.DiagnosticArray() + new_msg.header.stamp = self.get_clock().now().to_msg() st = self.ntp_diag(self.stat) if st is not None: @@ -159,7 +162,7 @@ def add_kv(stat_values, key, value): def ntp_monitor_main(argv=sys.argv[1:]): # filter out ROS args - argv = [a for a in argv if not a.startswith('__') and not a == '--ros-args' and not a == '-r'] + argv = argv[:argv.index('--ros-args')] if '--ros-args' in argv else argv import argparse parser = argparse.ArgumentParser() From 2730d508cbec21b3594258becde55ecfe1497736 Mon Sep 17 00:00:00 2001 From: Rein Appeldoorn Date: Wed, 26 Jun 2024 17:40:27 +0200 Subject: [PATCH 18/43] refactor(ram_monitor): ros2 port (#338) * refactor(ram_monitor): ros2 port * Update diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py * Apply suggestions from code review * Update diagnostic_common_diagnostics/README.md * fstrings * python3 --------- Co-authored-by: Christian Henkel <6976069+ct2034@users.noreply.github.com> --- diagnostic_common_diagnostics/CMakeLists.txt | 1 + diagnostic_common_diagnostics/README.md | 22 ++++- .../ram_monitor.py | 94 +++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100755 diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py diff --git a/diagnostic_common_diagnostics/CMakeLists.txt b/diagnostic_common_diagnostics/CMakeLists.txt index e62c86ec0..99569f20d 100644 --- a/diagnostic_common_diagnostics/CMakeLists.txt +++ b/diagnostic_common_diagnostics/CMakeLists.txt @@ -10,6 +10,7 @@ ament_python_install_package(${PROJECT_NAME}) install(PROGRAMS ${PROJECT_NAME}/cpu_monitor.py ${PROJECT_NAME}/ntp_monitor.py + ${PROJECT_NAME}/ram_monitor.py DESTINATION lib/${PROJECT_NAME} ) diff --git a/diagnostic_common_diagnostics/README.md b/diagnostic_common_diagnostics/README.md index 42df1c8dc..41c500d17 100644 --- a/diagnostic_common_diagnostics/README.md +++ b/diagnostic_common_diagnostics/README.md @@ -70,7 +70,27 @@ Disable self test. **To be ported** ## ram_monitor.py -**To be ported** +The `ram_monitor` module allows users to monitor the RAM usage of their system in real-time. +It publishes the usage percentage in a diagnostic message. + +* Name of the node is "ram_monitor_" + hostname. +* Uses the following args: + * warning_percentage: If the RAM usage is > warning_percentage, a WARN status will be published. + * window: the maximum length of the used collections.deque for queuing RAM readings. + +### Published Topics +#### /diagnostics +diagnostic_msgs/DiagnosticArray +The diagnostics information. + +### Parameters +#### warning_percentage +(default: 90) +warning percentage threshold. + +#### window +(default: 1) +Length of RAM readings queue. ## sensors_monitor.py **To be ported** diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py new file mode 100755 index 000000000..d6250b6b0 --- /dev/null +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2017, TNO IVS, Helmond, Netherlands +# 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 TNO IVS 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. + +# \author Rein Appeldoorn + +import collections +import socket + +from diagnostic_msgs.msg import DiagnosticStatus + +from diagnostic_updater import DiagnosticTask, Updater + +import psutil + +import rclpy + + +class RamTask(DiagnosticTask): + + def __init__(self, warning_percentage, window): + DiagnosticTask.__init__(self, 'RAM Information') + self._warning_percentage = int(warning_percentage) + self._readings = collections.deque(maxlen=window) + + def run(self, stat): + self._readings.append(psutil.virtual_memory().percent) + ram_average = sum(self._readings) / len(self._readings) + + stat.add('RAM Load Average', f'{ram_average:.2f}') + + if ram_average > self._warning_percentage: + stat.summary( + DiagnosticStatus.WARN, + f'RAM Average exceeds {self._warning_percentage:d} percent', + ) + else: + stat.summary(DiagnosticStatus.OK, f'RAM Average {ram_average:.2f} percent') + + return stat + + +def main(): + hostname = socket.gethostname() + rclpy.init() + node = rclpy.create_node(f'ram_monitor_{hostname.replace("-", "_")}') + + updater = Updater(node) + updater.setHardwareID(hostname) + updater.add( + RamTask( + node.declare_parameter('warning_percentage', 90).value, + node.declare_parameter('window', 1).value, + ) + ) + + rclpy.spin(node) + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass From 19f64806fd3bc23d880f00dca3456bd54ddc5413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Audren?= <101862279+haudren-woven@users.noreply.github.com> Date: Thu, 27 Jun 2024 00:48:45 +0900 Subject: [PATCH 19/43] Fix usage of rclcpp::ok with a non-default context (#352) * Fix usage of rclcpp::ok with a non-default context The current implementation calls `rclcpp::ok` without any arguments, which amounts to verifying that the global default context is valid. In the case where a node is added to a custom context, and the global context is not used, `rclcpp::ok` without any arguments will always return `false` since the global context has never been initialized. To fix it, pass to rclcpp the context that's available via the node's base interface. * Add a test for custom context --- .../diagnostic_updater/diagnostic_updater.hpp | 2 +- .../test/diagnostic_updater_test.cpp | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp b/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp index a301ada59..2ecb08ab1 100644 --- a/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp +++ b/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp @@ -511,7 +511,7 @@ class Updater : public DiagnosticTaskVector */ void update() { - if (rclcpp::ok()) { + if (rclcpp::ok(base_interface_->get_context())) { bool warn_nohwid = hwid_.empty(); std::vector status_vec; diff --git a/diagnostic_updater/test/diagnostic_updater_test.cpp b/diagnostic_updater/test/diagnostic_updater_test.cpp index 0362b2897..6fd7d1998 100644 --- a/diagnostic_updater/test/diagnostic_updater_test.cpp +++ b/diagnostic_updater/test/diagnostic_updater_test.cpp @@ -134,6 +134,42 @@ TEST(DiagnosticUpdater, testUpdaterAsNodeClassMember) { SUCCEED(); } +class SaveIfCalled : public diagnostic_updater::DiagnosticTask +{ +public: + SaveIfCalled() + : DiagnosticTask("SaveIfCalled") {} + + void run(diagnostic_updater::DiagnosticStatusWrapper & s) + { + s.summary(0, "Have been called!"); + has_been_called_ = true; + } + + bool has_been_called() const + { + return has_been_called_; + } + +private: + bool has_been_called_ = false; +}; + + +TEST(DiagnosticUpdater, testCustomContext) { + auto context = std::make_shared(); + context->init(0, nullptr, rclcpp::InitOptions()); + + auto node = + std::make_shared("CustomContextNode", rclcpp::NodeOptions().context(context)); + diagnostic_updater::Updater updater(node); + SaveIfCalled save_if_called; + updater.add(save_if_called); + updater.force_update(); + ASSERT_TRUE(save_if_called.has_been_called()); + context->shutdown("End test"); +} + TEST(DiagnosticUpdater, testDiagnosticStatusWrapperKeyValuePairs) { diagnostic_updater::DiagnosticStatusWrapper stat; From dbaec0420396bd4e33bb23d0453823d7df31546d Mon Sep 17 00:00:00 2001 From: Tim Clephas Date: Thu, 27 Jun 2024 07:58:51 +0200 Subject: [PATCH 20/43] Aggregator: publish diagnostics_toplevel_state immediately on every degradation (#324) * Aggregator: publish diagnostics_toplevel_state immediately on every degradation * Update docs * Re-use the publishData function such that also the actual diagnostics are available * Expand test with more degradation cases --- diagnostic_aggregator/src/aggregator.cpp | 28 +++++------- .../test/test_critical_pub.py | 45 ++++++++++++------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/diagnostic_aggregator/src/aggregator.cpp b/diagnostic_aggregator/src/aggregator.cpp index d9576c737..64b716caf 100644 --- a/diagnostic_aggregator/src/aggregator.cpp +++ b/diagnostic_aggregator/src/aggregator.cpp @@ -151,28 +151,11 @@ void Aggregator::diagCallback(const DiagnosticArray::SharedPtr diag_msg) checkTimestamp(diag_msg); bool analyzed = false; + bool immediate_report = false; { // lock the whole loop to ensure nothing in the analyzer group changes during it. std::lock_guard lock(mutex_); for (auto j = 0u; j < diag_msg->status.size(); ++j) { analyzed = false; - - const bool top_level_state_transition_to_error = - (last_top_level_state_ != DiagnosticStatus::ERROR) && - (diag_msg->status[j].level == DiagnosticStatus::ERROR); - - if (critical_ && top_level_state_transition_to_error) { - RCLCPP_DEBUG( - logger_, "Received error message: %s, publishing error immediately", - diag_msg->status[j].name.c_str()); - DiagnosticStatus diag_toplevel_state; - diag_toplevel_state.name = "toplevel_state_critical"; - diag_toplevel_state.level = diag_msg->status[j].level; - toplevel_state_pub_->publish(diag_toplevel_state); - - // store the last published state - last_top_level_state_ = diag_toplevel_state.level; - } - auto item = std::make_shared(&diag_msg->status[j]); if (analyzer_group_->match(item->getName())) { @@ -182,8 +165,17 @@ void Aggregator::diagCallback(const DiagnosticArray::SharedPtr diag_msg) if (!analyzed) { other_analyzer_->analyze(item); } + + // In case there is a degraded state, publish immediately + if (critical_ && item->getLevel() > last_top_level_state_) { + immediate_report = true; + } } } + + if (immediate_report) { + publishData(); + } } Aggregator::~Aggregator() diff --git a/diagnostic_aggregator/test/test_critical_pub.py b/diagnostic_aggregator/test/test_critical_pub.py index 279e82957..adad7ed7d 100644 --- a/diagnostic_aggregator/test/test_critical_pub.py +++ b/diagnostic_aggregator/test/test_critical_pub.py @@ -67,41 +67,56 @@ def publish_message(self, level): rclpy.spin_once(self.node) return self.node.get_clock().now() - def test_critical_publisher(self): + def critical_publisher_test( + self, initial_state=DiagnosticStatus.OK, new_state=DiagnosticStatus.ERROR + ): # Publish the ok message and wait till the toplevel state is received - state = DiagnosticStatus.OK - time_0 = self.publish_message(state) + time_0 = self.publish_message(initial_state) - assert (self.received_state[0] == state), \ + assert (self.received_state[0] == initial_state), \ ('Received state is not the same as the sent state:' - + f"'{self.received_state[0]}' != '{state}'") + + f"'{self.received_state[0]}' != '{initial_state}'") self.received_state.clear() # Publish the ok message and expect the toplevel state after 1 second period - time_1 = self.publish_message(state) + time_1 = self.publish_message(initial_state) assert (time_1 - time_0 > rclpy.duration.Duration(seconds=0.99)), \ 'OK message received too early' - assert (self.received_state[0] == state), \ + assert (self.received_state[0] == initial_state), \ ('Received state is not the same as the sent state:' - + f"'{self.received_state[0]}' != '{state}'") + + f"'{self.received_state[0]}' != '{initial_state}'") self.received_state.clear() # Publish the message and expect the critical error message immediately - state = DiagnosticStatus.ERROR - time_2 = self.publish_message(state) + time_2 = self.publish_message(new_state) assert (time_2 - time_1 < rclpy.duration.Duration(seconds=0.1)), \ 'Critical error message not received within 0.1 second' - assert (self.received_state[0] == state), \ + assert (self.received_state[0] == new_state), \ ('Received state is not the same as the sent state:' - + f"'{self.received_state[0]}' != '{state}'") + + f"'{self.received_state[0]}' != '{new_state}'") self.received_state.clear() # Next error message should be sent at standard 1 second rate - time_3 = self.publish_message(state) + time_3 = self.publish_message(new_state) assert (time_3 - time_1 > rclpy.duration.Duration(seconds=0.99)), \ 'Periodic error message received too early' - assert (self.received_state[0] == state), \ + assert (self.received_state[0] == new_state), \ ('Received state is not the same as the sent state:' - + f"'{self.received_state[0]}' != '{state}'") + + f"'{self.received_state[0]}' != '{new_state}'") + + def test_critical_publisher_ok_error(self): + self.critical_publisher_test( + initial_state=DiagnosticStatus.OK, new_state=DiagnosticStatus.ERROR + ) + + def test_critical_publisher_ok_warn(self): + self.critical_publisher_test( + initial_state=DiagnosticStatus.OK, new_state=DiagnosticStatus.WARN + ) + + def test_critical_publisher_warn_error(self): + self.critical_publisher_test( + initial_state=DiagnosticStatus.WARN, new_state=DiagnosticStatus.ERROR + ) From 5e1415c042e770bab4a7925a55a1325e3b5fb15c Mon Sep 17 00:00:00 2001 From: MartinCornelis2 <51268547+MartinCornelis2@users.noreply.github.com> Date: Thu, 27 Jun 2024 08:42:34 +0200 Subject: [PATCH 21/43] Add add_analyzer functionality (#329) * Add add_analyzer functionality * Add copyright notice and license, remove unused includes, re-order includes correctly * Increase clarity of prefix_ by renaming it to analyzers_ns_ * Add add_analyzer functionality * Fix bug where base_path is not reset correctly * Make the parameter forwarding condition more generic, fix the default service namespace from diagnostics_agg to analyzers * Add an add_analyzer example to the diagnostic_aggregator * Update the relevant READMEs * Fix linter errors * Add test for add_analyzer at runtime, remove unnecessary ros info logger, remove unnecessary hardcoded namespace from yaml files * Remove the now redundant analyzers_ns_ * Change the copyright of add_analyzer, forgot to update it to Nobleo after copying the notice --- diagnostic_aggregator/CMakeLists.txt | 35 +++++- diagnostic_aggregator/README.md | 27 +++++ diagnostic_aggregator/example/README.md | 4 +- .../example/example.launch.py.in | 8 ++ .../example/example_add_analyzers.yaml | 6 + diagnostic_aggregator/example/example_pub.py | 4 + .../diagnostic_aggregator/aggregator.hpp | 12 ++ diagnostic_aggregator/package.xml | 3 +- diagnostic_aggregator/src/add_analyzer.cpp | 110 ++++++++++++++++++ diagnostic_aggregator/src/aggregator.cpp | 62 ++++++---- .../test/add_analyzers.launch.py.in | 74 ++++++++++++ diagnostic_aggregator/test/all_analyzers.yaml | 2 +- .../test/analyzer_group.yaml | 2 +- diagnostic_aggregator/test/default.yaml | 9 ++ .../test/empty_root_path.yaml | 2 +- .../expected_output/add_all_analyzers.txt | 6 + .../test/expected_stale_analyzers.yaml | 2 +- .../test/multiple_match_analyzers.yaml | 2 +- .../test/primitive_analyzers.yaml | 2 +- 19 files changed, 343 insertions(+), 29 deletions(-) create mode 100644 diagnostic_aggregator/example/example_add_analyzers.yaml create mode 100644 diagnostic_aggregator/src/add_analyzer.cpp create mode 100644 diagnostic_aggregator/test/add_analyzers.launch.py.in create mode 100644 diagnostic_aggregator/test/default.yaml create mode 100644 diagnostic_aggregator/test/expected_output/add_all_analyzers.txt diff --git a/diagnostic_aggregator/CMakeLists.txt b/diagnostic_aggregator/CMakeLists.txt index 12aac3b1f..a72335e6f 100644 --- a/diagnostic_aggregator/CMakeLists.txt +++ b/diagnostic_aggregator/CMakeLists.txt @@ -14,6 +14,7 @@ find_package(ament_cmake REQUIRED) find_package(diagnostic_msgs REQUIRED) find_package(pluginlib REQUIRED) find_package(rclcpp REQUIRED) +find_package(rcl_interfaces REQUIRED) find_package(std_msgs REQUIRED) add_library(${PROJECT_NAME} SHARED @@ -67,6 +68,10 @@ add_executable(aggregator_node src/aggregator_node.cpp) target_link_libraries(aggregator_node ${PROJECT_NAME}) +# Add analyzer +add_executable(add_analyzer src/add_analyzer.cpp) +ament_target_dependencies(add_analyzer rclcpp rcl_interfaces) + # Testing macro if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) @@ -77,6 +82,7 @@ if(BUILD_TESTING) find_package(launch_testing_ament_cmake REQUIRED) file(TO_CMAKE_PATH "${CMAKE_INSTALL_PREFIX}/lib/${PROJECT_NAME}/aggregator_node" AGGREGATOR_NODE) + file(TO_CMAKE_PATH "${CMAKE_INSTALL_PREFIX}/lib/${PROJECT_NAME}/add_analyzer" ADD_ANALYZER) file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/test/test_listener.py" TEST_LISTENER) set(create_analyzers_tests "primitive_analyzers" @@ -124,6 +130,27 @@ if(BUILD_TESTING) ) endforeach() + set(add_analyzers_tests + "all_analyzers") + + foreach(test_name ${add_analyzers_tests}) + file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/test/default.yaml" PARAMETER_FILE) + file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/test/${test_name}.yaml" ADD_PARAMETER_FILE) + file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/test/expected_output/add_${test_name}" EXPECTED_OUTPUT) + + configure_file( + "test/add_analyzers.launch.py.in" + "test_add_${test_name}.launch.py" + @ONLY + ) + add_launch_test( + "${CMAKE_CURRENT_BINARY_DIR}/test_add_${test_name}.launch.py" + TARGET "test_add_${test_name}" + TIMEOUT 30 + ENV + ) + endforeach() + add_launch_test( test/test_critical_pub.py TIMEOUT 30 @@ -140,6 +167,11 @@ install( DESTINATION lib/${PROJECT_NAME} ) +install( + TARGETS add_analyzer + DESTINATION lib/${PROJECT_NAME} +) + install( TARGETS ${PROJECT_NAME} ${ANALYZERS} EXPORT ${PROJECT_NAME}Targets @@ -157,6 +189,7 @@ ament_python_install_package(${PROJECT_NAME}) # Install Example set(ANALYZER_PARAMS_FILEPATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/example_analyzers.yaml") +set(ADD_ANALYZER_PARAMS_FILEPATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/example_add_analyzers.yaml") configure_file(example/example.launch.py.in example.launch.py @ONLY) install( # launch descriptor FILES ${CMAKE_CURRENT_BINARY_DIR}/example.launch.py @@ -167,7 +200,7 @@ install( # example publisher DESTINATION lib/${PROJECT_NAME} ) install( # example aggregator configration - FILES example/example_analyzers.yaml + FILES example/example_analyzers.yaml example/example_add_analyzers.yaml DESTINATION share/${PROJECT_NAME} ) diff --git a/diagnostic_aggregator/README.md b/diagnostic_aggregator/README.md index 08178a066..fa0eea7a9 100644 --- a/diagnostic_aggregator/README.md +++ b/diagnostic_aggregator/README.md @@ -135,6 +135,33 @@ You can launch the `aggregator_node` like this (see [example.launch.py.in](examp ]) ``` +You can add analyzers at runtime using the `add_analyzer` node like this (see [example.launch.py.in](example/example.launch.py.in)): +``` + add_analyzer = launch_ros.actions.Node( + package='diagnostic_aggregator', + executable='add_analyzer', + output='screen', + parameters=[add_analyzer_params_filepath]) + return launch.LaunchDescription([ + add_analyzer, + ]) +``` +This node updates the parameters of the `aggregator_node` by calling the service `/analyzers/set_parameters_atomically`. +The `aggregator_node` will detect when a `parameter-event` has introduced new parameters to it. +When this happens the `aggregator_node` will reload all analyzers based on its new set of parameters. +Adding analyzers this way can be done at runtime and can be made conditional. + +In the example, `add_analyzer` will add an analyzer for diagnostics that are marked optional: +``` yaml +/**: + ros__parameters: + optional: + type: diagnostic_aggregator/GenericAnalyzer + path: Optional + startswith: [ '/optional' ] +``` +This will move the `/optional/runtime/analyzer` diagnostic from the "Other" to "Aggregation" where it will not go stale after 5 seconds and will be taken into account for the toplevel state. + # Basic analyzers The `diagnostic_aggregator` package provides a few basic analyzers that you can use to aggregate your diagnostics. diff --git a/diagnostic_aggregator/example/README.md b/diagnostic_aggregator/example/README.md index 10e9b2574..27c593be9 100644 --- a/diagnostic_aggregator/example/README.md +++ b/diagnostic_aggregator/example/README.md @@ -1,5 +1,7 @@ # Aggregator Example -This is a simple example to show the diagnostic_aggregator in action. It involves one python script producing dummy diagnostic data ([example_pub.py](./example_pub.py)), and one diagnostic aggregator configuration ([example.yaml](./example.yaml)) that provides analyzers aggregating it. +This is a simple example to show the diagnostic_aggregator and add_analyzer in action. It involves one python script producing dummy diagnostic data ([example_pub.py](./example_pub.py)), one diagnostic aggregator configuration ([example_analyzers.yaml](./example_analyzers.yaml)) and one add_analyzer configuration ([example_add_analyzers.yaml](./example_add_analyzers.yaml)). + +The aggregator will launch and load all the analyzers listed in ([example_analyzers.yaml](./example_analyzers.yaml)). Then the aggregator will be notified that there are additional analyzers that we also want to load in ([example_add_analyzers.yaml](./example_add_analyzers.yaml)). After this reload all analyzers will be active. Run the example with `ros2 launch diagnostic_aggregator example.launch.py` diff --git a/diagnostic_aggregator/example/example.launch.py.in b/diagnostic_aggregator/example/example.launch.py.in index 48cd62f66..81a749220 100644 --- a/diagnostic_aggregator/example/example.launch.py.in +++ b/diagnostic_aggregator/example/example.launch.py.in @@ -4,6 +4,7 @@ import launch import launch_ros.actions analyzer_params_filepath = "@ANALYZER_PARAMS_FILEPATH@" +add_analyzer_params_filepath = "@ADD_ANALYZER_PARAMS_FILEPATH@" def generate_launch_description(): @@ -12,11 +13,18 @@ def generate_launch_description(): executable='aggregator_node', output='screen', parameters=[analyzer_params_filepath]) + add_analyzer = launch_ros.actions.Node( + package='diagnostic_aggregator', + executable='add_analyzer', + output='screen', + parameters=[add_analyzer_params_filepath] + ) diag_publisher = launch_ros.actions.Node( package='diagnostic_aggregator', executable='example_pub.py') return launch.LaunchDescription([ aggregator, + add_analyzer, diag_publisher, launch.actions.RegisterEventHandler( event_handler=launch.event_handlers.OnProcessExit( diff --git a/diagnostic_aggregator/example/example_add_analyzers.yaml b/diagnostic_aggregator/example/example_add_analyzers.yaml new file mode 100644 index 000000000..1c6c264c7 --- /dev/null +++ b/diagnostic_aggregator/example/example_add_analyzers.yaml @@ -0,0 +1,6 @@ +/**: + ros__parameters: + optional: + type: diagnostic_aggregator/GenericAnalyzer + path: Optional + contains: [ '/optional' ] diff --git a/diagnostic_aggregator/example/example_pub.py b/diagnostic_aggregator/example/example_pub.py index 887dc18df..0c1b10436 100755 --- a/diagnostic_aggregator/example/example_pub.py +++ b/diagnostic_aggregator/example/example_pub.py @@ -81,6 +81,10 @@ def __init__(self): name='/sensors/front/cam', message='OK'), DiagnosticStatus(level=DiagnosticStatus.OK, name='/sensors/rear/cam', message='OK'), + + # Optional + DiagnosticStatus(level=DiagnosticStatus.OK, + name='/optional/runtime/analyzer', message='OK'), ] def timer_callback(self): diff --git a/diagnostic_aggregator/include/diagnostic_aggregator/aggregator.hpp b/diagnostic_aggregator/include/diagnostic_aggregator/aggregator.hpp index b901acdc0..5f48dd6b1 100644 --- a/diagnostic_aggregator/include/diagnostic_aggregator/aggregator.hpp +++ b/diagnostic_aggregator/include/diagnostic_aggregator/aggregator.hpp @@ -133,6 +133,8 @@ class Aggregator rclcpp::Service::SharedPtr add_srv_; /// DiagnosticArray, /diagnostics rclcpp::Subscription::SharedPtr diag_sub_; + /// ParameterEvent, /parameter_events + rclcpp::Subscription::SharedPtr param_sub_; /// DiagnosticArray, /diagnostics_agg rclcpp::Publisher::SharedPtr agg_pub_; /// DiagnosticStatus, /diagnostics_toplevel_state @@ -165,6 +167,16 @@ class Aggregator /// Records all ROS warnings. No warnings are repeated. std::set ros_warnings_; + /* + *!\brief Checks for new parameters to trigger reinitialization of the AnalyzerGroup and OtherAnalyzer + */ + void parameterCallback(const rcl_interfaces::msg::ParameterEvent::SharedPtr param_msg); + + /* + *!\brief (re)initializes the AnalyzerGroup and OtherAnalyzer + */ + void initAnalyzers(); + /* *!\brief Checks timestamp of message, and warns if timestamp is 0 (not set) */ diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index 677e04365..cfe473cd7 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -12,7 +12,7 @@ BSD-3-Clause http://www.ros.org/wiki/diagnostic_aggregator - + Kevin Watts Brice Rebsamen Arne Nordmann @@ -22,6 +22,7 @@ diagnostic_msgs pluginlib + rcl_interfaces rclcpp std_msgs diff --git a/diagnostic_aggregator/src/add_analyzer.cpp b/diagnostic_aggregator/src/add_analyzer.cpp new file mode 100644 index 000000000..42ff2b0aa --- /dev/null +++ b/diagnostic_aggregator/src/add_analyzer.cpp @@ -0,0 +1,110 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2024, Nobleo Technology + * 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 copyright holder 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. + *********************************************************************/ + +/**< \author Martin Cornelis */ + +#include + +#include "rclcpp/rclcpp.hpp" +#include "rcl_interfaces/srv/set_parameters_atomically.hpp" +#include "rcl_interfaces/msg/parameter.hpp" + +using namespace std::chrono_literals; + +class AddAnalyzer : public rclcpp::Node +{ +public: + AddAnalyzer() + : Node("add_analyzer_node", "", rclcpp::NodeOptions().allow_undeclared_parameters( + true).automatically_declare_parameters_from_overrides(true)) + { + client_ = this->create_client( + "/analyzers/set_parameters_atomically"); + } + + void send_request() + { + while (!client_->wait_for_service(1s)) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), "Interrupted while waiting for the service. Exiting."); + return; + } + RCLCPP_INFO_ONCE(this->get_logger(), "service not available, waiting ..."); + } + auto request = std::make_shared(); + std::map parameters; + + if (!this->get_parameters("", parameters)) { + RCLCPP_ERROR(this->get_logger(), "Failed to retrieve parameters"); + } + for (const auto & [param_name, param] : parameters) { + // Find the suffix + size_t suffix_start = param_name.find_last_of('.'); + // Remove suffix if it exists + if (suffix_start != std::string::npos) { + std::string stripped_param_name = param_name.substr(0, suffix_start); + // Check in map if the stripped param name with the added suffix "path" exists + // This indicates the parameter is part of an analyzer description + if (parameters.count(stripped_param_name + ".path") > 0) { + auto parameter_msg = param.to_parameter_msg(); + request->parameters.push_back(parameter_msg); + } + } + } + + auto result = client_->async_send_request(request); + // Wait for the result. + if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) == + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_INFO(this->get_logger(), "Parameters succesfully set"); + } else { + RCLCPP_ERROR(this->get_logger(), "Failed to set parameters"); + } + } + +private: + rclcpp::Client::SharedPtr client_; +}; + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + + auto add_analyzer = std::make_shared(); + add_analyzer->send_request(); + rclcpp::shutdown(); + + return 0; +} diff --git a/diagnostic_aggregator/src/aggregator.cpp b/diagnostic_aggregator/src/aggregator.cpp index 64b716caf..4619ec297 100644 --- a/diagnostic_aggregator/src/aggregator.cpp +++ b/diagnostic_aggregator/src/aggregator.cpp @@ -59,7 +59,8 @@ using diagnostic_msgs::msg::DiagnosticStatus; Aggregator::Aggregator() : n_(std::make_shared( "analyzers", "", - rclcpp::NodeOptions().automatically_declare_parameters_from_overrides(true))), + rclcpp::NodeOptions().allow_undeclared_parameters(true). + automatically_declare_parameters_from_overrides(true))), logger_(rclcpp::get_logger("Aggregator")), pub_rate_(1.0), history_depth_(1000), @@ -69,6 +70,36 @@ Aggregator::Aggregator() last_top_level_state_(DiagnosticStatus::STALE) { RCLCPP_DEBUG(logger_, "constructor"); + initAnalyzers(); + + diag_sub_ = n_->create_subscription( + "/diagnostics", rclcpp::SystemDefaultsQoS().keep_last(history_depth_), + std::bind(&Aggregator::diagCallback, this, _1)); + agg_pub_ = n_->create_publisher("/diagnostics_agg", 1); + toplevel_state_pub_ = + n_->create_publisher("/diagnostics_toplevel_state", 1); + + int publish_rate_ms = 1000 / pub_rate_; + publish_timer_ = n_->create_wall_timer( + std::chrono::milliseconds(publish_rate_ms), + std::bind(&Aggregator::publishData, this)); + + param_sub_ = n_->create_subscription( + "/parameter_events", 1, std::bind(&Aggregator::parameterCallback, this, _1)); +} + +void Aggregator::parameterCallback(const rcl_interfaces::msg::ParameterEvent::SharedPtr msg) +{ + if (msg->node == "/" + std::string(n_->get_name())) { + if (msg->new_parameters.size() != 0) { + base_path_ = ""; + initAnalyzers(); + } + } +} + +void Aggregator::initAnalyzers() +{ bool other_as_errors = false; std::map parameters; @@ -101,26 +132,17 @@ Aggregator::Aggregator() RCLCPP_DEBUG( logger_, "Aggregator critical publisher configured to: %s", (critical_ ? "true" : "false")); - analyzer_group_ = std::make_unique(); - if (!analyzer_group_->init(base_path_, "", n_)) { - RCLCPP_ERROR(logger_, "Analyzer group for diagnostic aggregator failed to initialize!"); - } - - // Last analyzer handles remaining data - other_analyzer_ = std::make_unique(other_as_errors); - other_analyzer_->init(base_path_); // This always returns true - - diag_sub_ = n_->create_subscription( - "/diagnostics", rclcpp::SystemDefaultsQoS().keep_last(history_depth_), - std::bind(&Aggregator::diagCallback, this, _1)); - agg_pub_ = n_->create_publisher("/diagnostics_agg", 1); - toplevel_state_pub_ = - n_->create_publisher("/diagnostics_toplevel_state", 1); + { // lock the mutex while analyzer_group_ and other_analyzer_ are being updated + std::lock_guard lock(mutex_); + analyzer_group_ = std::make_unique(); + if (!analyzer_group_->init(base_path_, "", n_)) { + RCLCPP_ERROR(logger_, "Analyzer group for diagnostic aggregator failed to initialize!"); + } - int publish_rate_ms = 1000 / pub_rate_; - publish_timer_ = n_->create_wall_timer( - std::chrono::milliseconds(publish_rate_ms), - std::bind(&Aggregator::publishData, this)); + // Last analyzer handles remaining data + other_analyzer_ = std::make_unique(other_as_errors); + other_analyzer_->init(base_path_); // This always returns true + } } void Aggregator::checkTimestamp(const DiagnosticArray::SharedPtr diag_msg) diff --git a/diagnostic_aggregator/test/add_analyzers.launch.py.in b/diagnostic_aggregator/test/add_analyzers.launch.py.in new file mode 100644 index 000000000..8e4eae8da --- /dev/null +++ b/diagnostic_aggregator/test/add_analyzers.launch.py.in @@ -0,0 +1,74 @@ +import os + +import unittest + +from launch import LaunchDescription +from launch.actions import ExecuteProcess +from launch.events import matches_action +from launch.events.process import ShutdownProcess + +import launch_testing +import launch_testing.actions +import launch_testing.asserts +import launch_testing.util +import launch_testing_ros + + +def generate_test_description(): + os.environ['OSPL_VERBOSITY'] = '8' + os.environ['RCUTILS_CONSOLE_OUTPUT_FORMAT'] = '{message}' + + aggregator_node = ExecuteProcess( + cmd=[ + "@AGGREGATOR_NODE@", + "--ros-args", + "--params-file", + "@PARAMETER_FILE@" + ], + name='aggregator_node', + emulate_tty=True, + output='screen') + + add_analyzer = ExecuteProcess( + cmd=[ + "@ADD_ANALYZER@", + "--ros-args", + "--params-file", + "@ADD_PARAMETER_FILE@" + ], + name='add_analyzer', + emulate_tty=True, + output='screen') + + launch_description = LaunchDescription() + launch_description.add_action(aggregator_node) + launch_description.add_action(add_analyzer) + launch_description.add_action(launch_testing.util.KeepAliveProc()) + launch_description.add_action(launch_testing.actions.ReadyToTest()) + return launch_description, locals() + +class TestAggregator(unittest.TestCase): + + def test_processes_output(self, proc_output, aggregator_node): + """Check aggregator logging output for expected strings.""" + + from launch_testing.tools.output import get_default_filtered_prefixes + output_filter = launch_testing_ros.tools.basic_output_filter( + filtered_prefixes=get_default_filtered_prefixes() + ['service not available, waiting...'], + filtered_rmw_implementation='@rmw_implementation@' + ) + proc_output.assertWaitFor( + expected_output=launch_testing.tools.expected_output_from_file(path="@EXPECTED_OUTPUT@"), + process=aggregator_node, + output_filter=output_filter, + timeout=15 + ) + + import time + time.sleep(1) + +@launch_testing.post_shutdown_test() +class TestAggregatorShutdown(unittest.TestCase): + + def test_last_process_exit_code(self, proc_info, aggregator_node): + launch_testing.asserts.assertExitCodes(proc_info, process=aggregator_node) diff --git a/diagnostic_aggregator/test/all_analyzers.yaml b/diagnostic_aggregator/test/all_analyzers.yaml index 84b330e34..4cb012a83 100644 --- a/diagnostic_aggregator/test/all_analyzers.yaml +++ b/diagnostic_aggregator/test/all_analyzers.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: ros__parameters: path: BASIC prefix1: diff --git a/diagnostic_aggregator/test/analyzer_group.yaml b/diagnostic_aggregator/test/analyzer_group.yaml index 72bb5639d..14c938fff 100644 --- a/diagnostic_aggregator/test/analyzer_group.yaml +++ b/diagnostic_aggregator/test/analyzer_group.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: ros__parameters: path: TEST primary: diff --git a/diagnostic_aggregator/test/default.yaml b/diagnostic_aggregator/test/default.yaml new file mode 100644 index 000000000..2da82b92f --- /dev/null +++ b/diagnostic_aggregator/test/default.yaml @@ -0,0 +1,9 @@ +/**: + ros__parameters: + path: BASIC + prefix0: + type: diagnostic_aggregator/GenericAnalyzer + path: Zeroth + contains: [ + 'contain0a', + 'contain0b' ] \ No newline at end of file diff --git a/diagnostic_aggregator/test/empty_root_path.yaml b/diagnostic_aggregator/test/empty_root_path.yaml index 391de4e99..b4b25509f 100644 --- a/diagnostic_aggregator/test/empty_root_path.yaml +++ b/diagnostic_aggregator/test/empty_root_path.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: ros__parameters: primary: type: 'diagnostic_aggregator/AnalyzerGroup' diff --git a/diagnostic_aggregator/test/expected_output/add_all_analyzers.txt b/diagnostic_aggregator/test/expected_output/add_all_analyzers.txt new file mode 100644 index 000000000..9c5c2fc23 --- /dev/null +++ b/diagnostic_aggregator/test/expected_output/add_all_analyzers.txt @@ -0,0 +1,6 @@ +/BASIC/Zeroth +prefix0 +/BASIC/First +prefix1 +/BASIC/Third +prefix3 \ No newline at end of file diff --git a/diagnostic_aggregator/test/expected_stale_analyzers.yaml b/diagnostic_aggregator/test/expected_stale_analyzers.yaml index 9110f84d6..9546204d5 100644 --- a/diagnostic_aggregator/test/expected_stale_analyzers.yaml +++ b/diagnostic_aggregator/test/expected_stale_analyzers.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: my_path: type: diagnostic_aggregator/GenericAnalyzer path: My Path diff --git a/diagnostic_aggregator/test/multiple_match_analyzers.yaml b/diagnostic_aggregator/test/multiple_match_analyzers.yaml index 46153c6a7..3f0ec91f4 100644 --- a/diagnostic_aggregator/test/multiple_match_analyzers.yaml +++ b/diagnostic_aggregator/test/multiple_match_analyzers.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: my_path: type: diagnostic_aggregator/GenericAnalyzer path: Header1 diff --git a/diagnostic_aggregator/test/primitive_analyzers.yaml b/diagnostic_aggregator/test/primitive_analyzers.yaml index 9601cce77..fc98e2ee8 100644 --- a/diagnostic_aggregator/test/primitive_analyzers.yaml +++ b/diagnostic_aggregator/test/primitive_analyzers.yaml @@ -1,4 +1,4 @@ -analyzers: +/**: ros__parameters: log_level: debug primary: From e67a69c9b54198521d5dc0bf92e43dc256f24867 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Tue, 14 May 2024 16:01:16 +0200 Subject: [PATCH 22/43] refactor(sensors_monitor): ros2 port #339 thanks to https://github.com/reinzor Signed-off-by: Christian Henkel --- diagnostic_common_diagnostics/CMakeLists.txt | 1 + diagnostic_common_diagnostics/README.md | 15 +- .../sensors_monitor.py | 255 ++++++++++++++++++ diagnostic_common_diagnostics/package.xml | 1 + 4 files changed, 271 insertions(+), 1 deletion(-) create mode 100755 diagnostic_common_diagnostics/diagnostic_common_diagnostics/sensors_monitor.py diff --git a/diagnostic_common_diagnostics/CMakeLists.txt b/diagnostic_common_diagnostics/CMakeLists.txt index 99569f20d..9ab1c21f9 100644 --- a/diagnostic_common_diagnostics/CMakeLists.txt +++ b/diagnostic_common_diagnostics/CMakeLists.txt @@ -11,6 +11,7 @@ install(PROGRAMS ${PROJECT_NAME}/cpu_monitor.py ${PROJECT_NAME}/ntp_monitor.py ${PROJECT_NAME}/ram_monitor.py + ${PROJECT_NAME}/sensors_monitor.py DESTINATION lib/${PROJECT_NAME} ) diff --git a/diagnostic_common_diagnostics/README.md b/diagnostic_common_diagnostics/README.md index 41c500d17..3b410ee34 100644 --- a/diagnostic_common_diagnostics/README.md +++ b/diagnostic_common_diagnostics/README.md @@ -93,7 +93,20 @@ warning percentage threshold. Length of RAM readings queue. ## sensors_monitor.py -**To be ported** +The `sensors_monitor` module allows users to monitor the temperature, volt and fan speeds of their system in real-time. +It uses the [`LM Sensors` package](https://packages.debian.org/sid/utils/lm-sensors) to get the data. + +* Name of the node is "sensors_monitor_" + hostname. + +### Published Topics +#### /diagnostics +diagnostic_msgs/DiagnosticArray +The diagnostics information. + +### Parameters +#### ignore_fans +(default: false) +Whether to ignore the fan speed. ## tf_monitor.py **To be ported** diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/sensors_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/sensors_monitor.py new file mode 100755 index 000000000..c6733e119 --- /dev/null +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/sensors_monitor.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 + +# Software License Agreement (BSD License) +# +# Copyright (c) 2012, 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. + +from __future__ import division, with_statement + +from io import StringIO +import math +import re +import socket +import subprocess + +from diagnostic_msgs.msg import DiagnosticStatus + +import diagnostic_updater as DIAG + +import rclpy +from rclpy.node import Node + + +class Sensor(object): + + def __init__(self): + self.critical = None + self.min = None + self.max = None + self.input = None + self.name = None + self.type = None + self.high = None + self.alarm = None + + def __repr__(self): + return 'Sensor object (name: {}, type: {})'.format(self.name, self.type) + + def getCrit(self): + return self.critical + + def getMin(self): + return self.min + + def getMax(self): + return self.max + + def getInput(self): + return self.input + + def getName(self): + return self.name + + def getType(self): + return self.type + + def getHigh(self): + return self.high + + def getAlarm(self): + return self.alarm + + def __str__(self): + lines = [] + lines.append(str(self.name)) + lines.append('\t' + 'Type: ' + str(self.type)) + if self.input: + lines.append('\t' + 'Input: ' + str(self.input)) + if self.min: + lines.append('\t' + 'Min: ' + str(self.min)) + if self.max: + lines.append('\t' + 'Max: ' + str(self.max)) + if self.high: + lines.append('\t' + 'High: ' + str(self.high)) + if self.critical: + lines.append('\t' + 'Crit: ' + str(self.critical)) + lines.append('\t' + 'Alarm: ' + str(self.alarm)) + return '\n'.join(lines) + + +def parse_sensor_line(line): + sensor = Sensor() + line = line.lstrip() + [name, reading] = line.split(':') + + try: + [sensor.name, sensor.type] = name.rsplit(' ', 1) + except ValueError: + return None + + if sensor.name == 'Core': + sensor.name = name + sensor.type = 'Temperature' + elif sensor.name.find('Physical id') != -1: + sensor.name = name + sensor.type = 'Temperature' + + try: + [reading, params] = reading.lstrip().split('(') + except ValueError: + return None + + sensor.alarm = False + if line.find('ALARM') != -1: + sensor.alarm = True + + if reading.find('°C') == -1: + sensor.input = float(reading.split()[0]) + else: + sensor.input = float(reading.split('°C')[0]) + + params = params.split(',') + for param in params: + m = re.search('[0-9]+.[0-9]*', param) + if param.find('min') != -1: + sensor.min = float(m.group(0)) + elif param.find('max') != -1: + sensor.max = float(m.group(0)) + elif param.find('high') != -1: + sensor.high = float(m.group(0)) + elif param.find('crit') != -1: + sensor.critical = float(m.group(0)) + + return sensor + + +def _rads_to_rpm(rads): + return rads / (2 * math.pi) * 60 + + +def _rpm_to_rads(rpm): + return rpm * (2 * math.pi) / 60 + + +def parse_sensors_output(node: Node, output): + out = StringIO(output if isinstance(output, str) else output.decode('utf-8')) + + sensorList = [] + for line in out.readlines(): + # Check for a colon + if ':' in line and 'Adapter' not in line: + s = None + try: + s = parse_sensor_line(line) + except Exception as exc: + node.get_logger().warn( + 'Unable to parse line "%s", due to %s', line, exc + ) + if s is not None: + sensorList.append(s) + return sensorList + + +def get_sensors(): + p = subprocess.Popen( + 'sensors', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True + ) + (o, e) = p.communicate() + if not p.returncode == 0: + return '' + if not o: + return '' + return o + + +class SensorsMonitor(object): + + def __init__(self, node: Node, hostname): + self.node = node + self.hostname = hostname + self.ignore_fans = node.declare_parameter('ignore_fans', False).value + node.get_logger().info('Ignore fanspeed warnings: %s' % self.ignore_fans) + + self.updater = DIAG.Updater(node) + self.updater.setHardwareID('none') + self.updater.add('%s Sensor Status' % self.hostname, self.monitor) + + def monitor(self, stat): + try: + stat.summary(DiagnosticStatus.OK, 'OK') + for sensor in parse_sensors_output(self.node, get_sensors()): + if sensor.getType() == 'Temperature': + if sensor.getInput() > sensor.getCrit(): + stat.mergeSummary( + DiagnosticStatus.ERROR, 'Critical Temperature' + ) + elif sensor.getInput() > sensor.getHigh(): + stat.mergeSummary(DiagnosticStatus.WARN, 'High Temperature') + stat.add( + ' '.join([sensor.getName(), sensor.getType()]), + str(sensor.getInput()), + ) + elif sensor.getType() == 'Voltage': + if sensor.getInput() < sensor.getMin(): + stat.mergeSummary(DiagnosticStatus.ERROR, 'Low Voltage') + elif sensor.getInput() > sensor.getMax(): + stat.mergeSummary(DiagnosticStatus.ERROR, 'High Voltage') + stat.add( + ' '.join([sensor.getName(), sensor.getType()]), + str(sensor.getInput()), + ) + elif sensor.getType() == 'Speed': + if not self.ignore_fans: + if sensor.getInput() < sensor.getMin(): + stat.mergeSummary(DiagnosticStatus.ERROR, 'No Fan Speed') + stat.add( + ' '.join([sensor.getName(), sensor.getType()]), + str(sensor.getInput()), + ) + except Exception: + import traceback + + self.node.get_logger().error('Unable to process lm-sensors data') + self.node.get_logger().error(traceback.format_exc()) + return stat + + +if __name__ == '__main__': + rclpy.init() + hostname = socket.gethostname() + hostname_clean = hostname.translate(hostname.maketrans('-', '_')) + node = rclpy.create_node('sensors_monitor_%s' % hostname_clean) + + monitor = SensorsMonitor(node, hostname) + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index 8496593a0..cc9d820ef 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -19,6 +19,7 @@ ament_cmake_python diagnostic_updater + lm-sensors python3-ntplib python3-psutil From 16b5e8a7e450003a4a64de34730c85bb56cb56ba Mon Sep 17 00:00:00 2001 From: Rein Appeldoorn Date: Thu, 27 Jun 2024 10:12:49 +0200 Subject: [PATCH 23/43] change(diagnosed-publisher): allow specifying node clock (#340) A new clock was created for every diagnosed publisher. When running with 'use_sim_time', the /clock time was not respected. --- diagnostic_updater/include/diagnostic_updater/publisher.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/diagnostic_updater/include/diagnostic_updater/publisher.hpp b/diagnostic_updater/include/diagnostic_updater/publisher.hpp index d23c7fd1f..f2bdcb47a 100644 --- a/diagnostic_updater/include/diagnostic_updater/publisher.hpp +++ b/diagnostic_updater/include/diagnostic_updater/publisher.hpp @@ -226,8 +226,9 @@ class DiagnosedPublisher : public TopicDiagnostic const typename PublisherT::SharedPtr & pub, diagnostic_updater::Updater & diag, const diagnostic_updater::FrequencyStatusParam & freq, - const diagnostic_updater::TimeStampStatusParam & stamp) - : TopicDiagnostic(pub->get_topic_name(), diag, freq, stamp), + const diagnostic_updater::TimeStampStatusParam & stamp, + const rclcpp::Clock::SharedPtr & clock = std::make_shared()) + : TopicDiagnostic(pub->get_topic_name(), diag, freq, stamp, clock), publisher_(pub) { static_assert(has_header::value, "Message type has to have a header."); From 9ad7117e9a91bc940de10b72105b854bf868096e Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:15:52 +0200 Subject: [PATCH 24/43] Adopting CI changes similar to jazzy #358 (#368) * Adoptin Ci changes similar to jazzy Signed-off-by: Christian Henkel * also ignoring pep257 here ... Signed-off-by: Christian Henkel --------- Signed-off-by: Christian Henkel --- .github/workflows/lint.yaml | 12 +++++++++--- .github/workflows/test.yaml | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3488e94ea..4f926eef6 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -18,17 +18,23 @@ jobs: cppcheck, cpplint, flake8, - pep257, + # pep257, TODO: enable when we fixed + # Error: diagnostic_common_diagnostics/diagnostic_common_diagnostics/ntp_monitor.py:113 in public method `ntp_diag`: D417: Missing argument descriptions in the docstring (argument(s) st are missing descriptions in 'ntp_diag' docstring) + # using ros-rolling-ament-pep257 amd64 0.18.0-1noble.20240426.150718 uncrustify, xmllint, ] - runs-on: ubuntu-latest + include: + - distro: rolling + os: ubuntu-24.04 + runs-on: ${{ matrix.os }} env: AMENT_CPPCHECK_ALLOW_SLOW_VERSIONS: 1 steps: - uses: actions/checkout@v1 - uses: ros-tooling/setup-ros@master - - run: sudo pip install pydocstyle==6.1.1 # downgrade to fix https://github.com/ament/ament_lint/pull/428 + with: + required-ros-distributions: ${{ matrix.distro }} - uses: ros-tooling/action-ros-lint@master with: linter: ${{ matrix.linter }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ab3f6a3d3..224a0cf3a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,8 +14,7 @@ jobs: strategy: fail-fast: false matrix: - package: - [ + package: [ diagnostic_aggregator, diagnostic_common_diagnostics, diagnostic_updater, @@ -28,6 +27,8 @@ jobs: container: ubuntu:${{ matrix.os }} steps: - uses: ros-tooling/setup-ros@master + with: + required-ros-distributions: ${{ matrix.distro }} - uses: ros-tooling/action-ros-ci@master with: target-ros2-distro: ${{ matrix.distro }} From 8b0fb51346a9dadc5b6bde1e1aca9a7c033a0893 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Thu, 27 Jun 2024 10:59:04 +0200 Subject: [PATCH 25/43] changelogs upated Signed-off-by: Christian Henkel --- diagnostic_aggregator/CHANGELOG.rst | 6 ++++++ diagnostic_common_diagnostics/CHANGELOG.rst | 9 +++++++++ diagnostic_updater/CHANGELOG.rst | 6 ++++++ diagnostics/CHANGELOG.rst | 3 +++ self_test/CHANGELOG.rst | 5 +++++ 5 files changed, 29 insertions(+) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index efda16d09..c4955de7d 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Add add_analyzer functionality (`#329 `_) +* Aggregator: publish diagnostics_toplevel_state immediately on every degradation (`#324 `_) +* Contributors: MartinCornelis2, Tim Clephas + 3.2.0 (2024-03-22) ------------------ * Avoid rolling up an ERROR state when empty GenericAnalyzer blocks are marked discard_stale, or when all of their items are STALE. (`#315 `_) diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index 0c53e9ca1..d52853601 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,6 +2,15 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* refactor(sensors_monitor): ros2 port `#339 `_ +* refactor(ram_monitor): ros2 port (`#338 `_) +* NTP monitor improvements (`#342 `_) +* Using ubuntu ntp server in systemtest (`#346 `_) +* Fixing ntp launchtest (`#330 `_) +* Contributors: Christian Henkel, Rein Appeldoorn, Tony Najjar + 3.2.0 (2024-03-22) ------------------ * Port cpu_monitor to ROS2 (`#326 `_) diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index e5d4da069..30c357bd5 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* change(diagnosed-publisher): allow specifying node clock (`#340 `_) +* Fix usage of rclcpp::ok with a non-default context (`#352 `_) +* Contributors: Hervé Audren, Rein Appeldoorn + 3.2.0 (2024-03-22) ------------------ * including depdency (`#322 `_) diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index 1f73b1330..e65ea0b99 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 3.2.0 (2024-03-22) ------------------ diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index 1ebff1e4a..b8ae3174b 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Building in docker (`#335 `_) +* Contributors: Christian Henkel + 3.2.0 (2024-03-22) ------------------ * Self test publishes the service under the node name, again (`#269 `_) From ac7cac9913f3dc752555d428494675285f727022 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Thu, 27 Jun 2024 11:00:12 +0200 Subject: [PATCH 26/43] 3.2.1 --- diagnostic_aggregator/CHANGELOG.rst | 4 ++-- diagnostic_aggregator/package.xml | 2 +- diagnostic_common_diagnostics/CHANGELOG.rst | 4 ++-- diagnostic_common_diagnostics/package.xml | 2 +- diagnostic_updater/CHANGELOG.rst | 4 ++-- diagnostic_updater/package.xml | 2 +- diagnostics/CHANGELOG.rst | 4 ++-- diagnostics/package.xml | 2 +- self_test/CHANGELOG.rst | 4 ++-- self_test/package.xml | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index c4955de7d..8b6b2b001 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.1 (2024-06-27) +------------------ * Add add_analyzer functionality (`#329 `_) * Aggregator: publish diagnostics_toplevel_state immediately on every degradation (`#324 `_) * Contributors: MartinCornelis2, Tim Clephas diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index cfe473cd7..75dabc892 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -2,7 +2,7 @@ diagnostic_aggregator - 3.2.0 + 3.2.1 diagnostic_aggregator Austin Hendrix Brice Rebsamen diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index d52853601..6e78fefc9 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.1 (2024-06-27) +------------------ * refactor(sensors_monitor): ros2 port `#339 `_ * refactor(ram_monitor): ros2 port (`#338 `_) * NTP monitor improvements (`#342 `_) diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index cc9d820ef..00d146740 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostic_common_diagnostics - 3.2.0 + 3.2.1 diagnostic_common_diagnostics Austin Hendrix Brice Rebsamen diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index 30c357bd5..922f41300 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.1 (2024-06-27) +------------------ * change(diagnosed-publisher): allow specifying node clock (`#340 `_) * Fix usage of rclcpp::ok with a non-default context (`#352 `_) * Contributors: Hervé Audren, Rein Appeldoorn diff --git a/diagnostic_updater/package.xml b/diagnostic_updater/package.xml index 6935d9acb..346b567e9 100644 --- a/diagnostic_updater/package.xml +++ b/diagnostic_updater/package.xml @@ -2,7 +2,7 @@ diagnostic_updater - 3.2.0 + 3.2.1 diagnostic_updater contains tools for easily updating diagnostics. it is commonly used in device drivers to keep track of the status of output topics, device status, etc. Austin Hendrix Brice Rebsamen diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index e65ea0b99..abdc0ea52 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.1 (2024-06-27) +------------------ 3.2.0 (2024-03-22) ------------------ diff --git a/diagnostics/package.xml b/diagnostics/package.xml index 9445c1f93..47a8ec1d3 100644 --- a/diagnostics/package.xml +++ b/diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostics - 3.2.0 + 3.2.1 diagnostics Austin Hendrix Brice Rebsamen diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index b8ae3174b..7ee57f1c8 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +3.2.1 (2024-06-27) +------------------ * Building in docker (`#335 `_) * Contributors: Christian Henkel diff --git a/self_test/package.xml b/self_test/package.xml index 6667d9b8f..91ed7370a 100644 --- a/self_test/package.xml +++ b/self_test/package.xml @@ -2,7 +2,7 @@ self_test - 3.2.0 + 3.2.1 self_test Austin Hendrix Brice Rebsamen From 97c65e3c1c853a7eb300e2de634e83b354821b86 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Thu, 27 Jun 2024 14:08:11 +0200 Subject: [PATCH 27/43] 4.3.0 --- diagnostic_aggregator/package.xml | 2 +- diagnostic_common_diagnostics/package.xml | 2 +- diagnostic_updater/package.xml | 2 +- diagnostics/package.xml | 2 +- self_test/package.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index 75dabc892..8b11223d9 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -2,7 +2,7 @@ diagnostic_aggregator - 3.2.1 + 4.3.0 diagnostic_aggregator Austin Hendrix Brice Rebsamen diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index 00d146740..ec4f0be3d 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostic_common_diagnostics - 3.2.1 + 4.3.0 diagnostic_common_diagnostics Austin Hendrix Brice Rebsamen diff --git a/diagnostic_updater/package.xml b/diagnostic_updater/package.xml index 346b567e9..00adf92a6 100644 --- a/diagnostic_updater/package.xml +++ b/diagnostic_updater/package.xml @@ -2,7 +2,7 @@ diagnostic_updater - 3.2.1 + 4.3.0 diagnostic_updater contains tools for easily updating diagnostics. it is commonly used in device drivers to keep track of the status of output topics, device status, etc. Austin Hendrix Brice Rebsamen diff --git a/diagnostics/package.xml b/diagnostics/package.xml index 47a8ec1d3..de05e4ce3 100644 --- a/diagnostics/package.xml +++ b/diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostics - 3.2.1 + 4.3.0 diagnostics Austin Hendrix Brice Rebsamen diff --git a/self_test/package.xml b/self_test/package.xml index 91ed7370a..c1dcec942 100644 --- a/self_test/package.xml +++ b/self_test/package.xml @@ -2,7 +2,7 @@ self_test - 3.2.1 + 4.3.0 self_test Austin Hendrix Brice Rebsamen From 4f5ae697c6d1dbd00983eead89f735099199cd0a Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:30:51 +0200 Subject: [PATCH 28/43] versioning info (#373) Signed-off-by: Christian Henkel --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 6766d3186..a17017fce 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,17 @@ Diagnostics messages that are not aggregated can be visualized by [`rqt_runtime_ - **Iron Irwini** by the [`ros2-iron` branch](https://github.com/ros/diagnostics/tree/ros2-iron) - **Jazzy Jalisco** by the [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) +# Versioning and Releases + +- (__X__.0.0) We use the major version number to indicate a breaking change. +- (0.__Y__.0) The minor version number is used to differentiate between different ROS distributions: + - x.__0__.z: Humble Hawksbill + - x.__1__.z: Iron Irwini + - x.__2__.z: Jazzy Jalisco + - x.__3__.z: Rolling Ridley + - Future releases (Kilted Kaiju 05/25) will get x.__3__.z and _Rolling_ will be incremented accordingly. +- (0.0.__Z__) The patch version number is used for changes in the current ROS distribution that do not affect the API. + # License The source code is released under a [BSD 3-Clause license](LICENSE). From 0af0de12889f748cf9b29591a1a365ad5ac9eb82 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:49:38 +0200 Subject: [PATCH 29/43] writing down the backport tool and its usage (#377) Signed-off-by: Christian Henkel --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index a17017fce..46509be6c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,22 @@ Diagnostics messages that are not aggregated can be visualized by [`rqt_runtime_ - **Iron Irwini** by the [`ros2-iron` branch](https://github.com/ros/diagnostics/tree/ros2-iron) - **Jazzy Jalisco** by the [`ros2-jazzy` branch](https://github.com/ros/diagnostics/tree/ros2-jazzy) +## Workflow + +New features are to be developed in custom branches and then merged into the `ros2` branch. + +From there, the changes are backported to the other branches. + +## Backport Tooling + +This tool has proven to be useful: [backport](https://www.npmjs.com/package/backport) + +Use this command to port a given PR of `PR_NUMBER` to the other branches: + +```bash +backport --pr PR_NUMBER -b ros2-humble ros2-iron ros2-jazzy +``` + # Versioning and Releases - (__X__.0.0) We use the major version number to indicate a breaking change. From 05a96450bad1f074156615aaafa00a78b894d312 Mon Sep 17 00:00:00 2001 From: Antoine Lima <7421319+limaanto@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:30:50 +0200 Subject: [PATCH 30/43] Port hd_monitor to ROS2 (#334) * Port hd_monitor.py * Adapt documentation and CMakeList to new hd_monitor.py * Fix execution flag of hd_monitor * Add launch test for hd_monitor * Implement low and crit parameters in hd_monitor * Improve hd_monitor code quality --- diagnostic_common_diagnostics/CMakeLists.txt | 5 + diagnostic_common_diagnostics/README.md | 15 +- .../hd_monitor.py | 153 ++++++++++++++++++ diagnostic_common_diagnostics/mainpage.dox | 1 + .../systemtest/test_hd_monitor_launchtest.py | 110 +++++++++++++ 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100755 diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py create mode 100644 diagnostic_common_diagnostics/test/systemtest/test_hd_monitor_launchtest.py diff --git a/diagnostic_common_diagnostics/CMakeLists.txt b/diagnostic_common_diagnostics/CMakeLists.txt index 9ab1c21f9..261253671 100644 --- a/diagnostic_common_diagnostics/CMakeLists.txt +++ b/diagnostic_common_diagnostics/CMakeLists.txt @@ -12,6 +12,7 @@ install(PROGRAMS ${PROJECT_NAME}/ntp_monitor.py ${PROJECT_NAME}/ram_monitor.py ${PROJECT_NAME}/sensors_monitor.py + ${PROJECT_NAME}/hd_monitor.py DESTINATION lib/${PROJECT_NAME} ) @@ -29,6 +30,10 @@ if(BUILD_TESTING) test/systemtest/test_ntp_monitor_launchtest.py TARGET ntp_monitor_launchtest TIMEOUT 20) + add_launch_test( + test/systemtest/test_hd_monitor_launchtest.py + TARGET hd_monitor_launchtest + TIMEOUT 20) endif() ament_package() diff --git a/diagnostic_common_diagnostics/README.md b/diagnostic_common_diagnostics/README.md index 3b410ee34..fdece7f82 100644 --- a/diagnostic_common_diagnostics/README.md +++ b/diagnostic_common_diagnostics/README.md @@ -67,7 +67,20 @@ Computer name in diagnostics output (ex: 'c1') Disable self test. ## hd_monitor.py -**To be ported** +Runs 'shutil.disk_usage' to check if there is enough space left on a given device. +* Above 5% of free space left, an `OK` status will be published. +* Between 5% and 1%, a `WARN` status will be published, +* Below 1%, an `ERROR` status will be published. + +### Published Topics +#### /diagnostics +diagnostic_msgs/DiagnosticArray +The diagnostics information. + +### Parameters +#### path +(default: home directory "~") +Path in which to check remaining space. ## ram_monitor.py The `ram_monitor` module allows users to monitor the RAM usage of their system in real-time. diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py new file mode 100755 index 000000000..24c04d332 --- /dev/null +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py @@ -0,0 +1,153 @@ +#! /usr/bin/env python3 +"""Hard Drive (or any other memory) monitor. Contains a the monitor node and its main function.""" +# -*- coding: utf-8 -*- +# +# 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. + +# \author Kevin Watts +# \author Antoine Lima + +from pathlib import Path +from shutil import disk_usage +from socket import gethostname +from typing import List + +from diagnostic_msgs.msg import DiagnosticStatus, KeyValue +from diagnostic_updater import Updater +from rcl_interfaces.msg import SetParametersResult +import rclpy +from rclpy.node import Node + + +FREE_PERCENT_LOW = 0.05 +FREE_PERCENT_CRIT = 0.01 +DICT_STATUS = { + DiagnosticStatus.OK: 'OK', + DiagnosticStatus.WARN: 'Warning', + DiagnosticStatus.ERROR: 'Error', +} +DICT_USAGE = { + DiagnosticStatus.OK: 'OK', + DiagnosticStatus.WARN: 'Low Disk Space', + DiagnosticStatus.ERROR: 'Very Low Disk Space', +} + + +class HDMonitor(Node): + """Diagnostic node checking the remaining space on the specified hard drive. + + Three ROS parameters: + - path: Path on the filesystem to check (string, default: home directory) + - free_percent_low: Percentage at which to consider the space left as low + - free_percent_crit: Percentage at which to consider the space left as critical + """ + + def __init__(self): + hostname = gethostname().replace('.', '_').replace('-', '_') + super().__init__(f'hd_monitor_{hostname}') + + self._path = '~' + self._free_percent_low = 0.05 + self._free_percent_crit = 0.01 + + self.add_on_set_parameters_callback(self.callback_config) + self.declare_parameter('path', self._path) + self.declare_parameter('free_percent_low', self._free_percent_low) + self.declare_parameter('free_percent_crit', self._free_percent_crit) + + self._updater = Updater(self) + self._updater.setHardwareID(hostname) + self._updater.add(f'{hostname} HD Usage', self.check_disk_usage) + + def callback_config(self, params: List[rclpy.Parameter]): + """Retrieve ROS parameters. + + see the class documentation for declared parameters. + """ + for param in params: + match param.name: + case 'path': + self._path = str( + Path(param.value).expanduser().resolve(strict=True) + ) + case 'free_percent_low': + self._free_percent_low = param.value + case 'free_percent_crit': + self._free_percent_crit = param.value + + return SetParametersResult(successful=True) + + def check_disk_usage(self, diag: DiagnosticStatus) -> DiagnosticStatus: + """Compute the disk usage and derive a status from it. + + Task periodically ran by the diagnostic updater. + """ + diag.level = DiagnosticStatus.OK + + total, _, free = disk_usage(self._path) + percent = free / total + + if percent > self._free_percent_low: + diag.level = DiagnosticStatus.OK + elif percent > self._free_percent_crit: + diag.level = DiagnosticStatus.WARN + else: + diag.level = DiagnosticStatus.ERROR + + total_go = total // (1024 * 1024) + diag.values.extend( + [ + KeyValue(key='Name', value=self._path), + KeyValue(key='Status', value=DICT_STATUS[diag.level]), + KeyValue(key='Total (Go)', value=str(total_go)), + KeyValue(key='Available (%)', value=str(round(percent, 2))), + ] + ) + + diag.message = DICT_USAGE[diag.level] + return diag + + +def main(args=None): + """Run the HDMonitor class.""" + rclpy.init(args=args) + + node = HDMonitor() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/diagnostic_common_diagnostics/mainpage.dox b/diagnostic_common_diagnostics/mainpage.dox index 7aa872cef..f9cd677de 100644 --- a/diagnostic_common_diagnostics/mainpage.dox +++ b/diagnostic_common_diagnostics/mainpage.dox @@ -5,6 +5,7 @@ \b diagnostic_common_diagnostics contains a few common diagnostic nodes - cpu_monitor publishes diagnostic messages with the CPU usage of the system. +- hd_monitor publishes diagnostic messages related to the available space on a given storage device. - ntp_monitor publishes diagnostic messages for how well the NTP time sync is working. - tf_monitor used to publish diagnostic messages reporting on the health of the TF tree. It is based on tfwtf. It is not ported to ROS2. diff --git a/diagnostic_common_diagnostics/test/systemtest/test_hd_monitor_launchtest.py b/diagnostic_common_diagnostics/test/systemtest/test_hd_monitor_launchtest.py new file mode 100644 index 000000000..9086c5d75 --- /dev/null +++ b/diagnostic_common_diagnostics/test/systemtest/test_hd_monitor_launchtest.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Software License Agreement (BSD License) +# +# Copyright (c) 2023, Robert Bosch GmbH +# 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. + +import unittest + +from diagnostic_msgs.msg import DiagnosticArray + +import launch + +import launch_ros + +import launch_testing + +from launch_testing_ros import WaitForTopics + +import pytest + +import rclpy + + +@pytest.mark.launch_test +def generate_test_description(): + """Launch the hd_monitor node and return a launch description.""" + return launch.LaunchDescription( + [ + launch_ros.actions.Node( + package='diagnostic_common_diagnostics', + executable='hd_monitor.py', + name='hd_monitor', + output='screen', + parameters=[{'free_percent_low': 0.20, 'free_percent_crit': 0.05}], + ), + launch_testing.actions.ReadyToTest(), + ] + ) + + +class TestHDMonitor(unittest.TestCase): + """Test if the hd_monitor node is publishing diagnostics.""" + + def __init__(self, methodName: str = 'runTest') -> None: + super().__init__(methodName) + self.received_messages = [] + + def _received_message(self, msg): + self.received_messages.append(msg) + + def _get_min_level(self): + levels = [ + int.from_bytes(status.level, 'little') + for diag in self.received_messages + for status in diag.status + ] + if len(levels) == 0: + return -1 + return min(levels) + + def test_topic_published(self): + """Test if the hd_monitor node is publishing diagnostics.""" + with WaitForTopics([('/diagnostics', DiagnosticArray)], timeout=5): + print('Topic found') + + rclpy.init() + test_node = rclpy.create_node('test_node') + test_node.create_subscription( + DiagnosticArray, '/diagnostics', self._received_message, 1 + ) + + while len(self.received_messages) < 10: + rclpy.spin_once(test_node, timeout_sec=1) + if (min_level := self._get_min_level()) == 0: + break + + test_node.destroy_node() + rclpy.shutdown() + print(f'Got {len(self.received_messages)} messages:') + for msg in self.received_messages: + print(msg) + self.assertEqual(min_level, 0) From b5040595e7d2b708c99437f7d1c819c4e4aa1596 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:51:46 +0200 Subject: [PATCH 31/43] fixing pep257 problems introduced by #334 (#384) Signed-off-by: Christian Henkel --- .../diagnostic_common_diagnostics/hd_monitor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py index 24c04d332..ac4eae748 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/hd_monitor.py @@ -64,7 +64,8 @@ class HDMonitor(Node): - """Diagnostic node checking the remaining space on the specified hard drive. + """ + Diagnostic node checking the remaining space on the specified hard drive. Three ROS parameters: - path: Path on the filesystem to check (string, default: home directory) @@ -90,7 +91,8 @@ def __init__(self): self._updater.add(f'{hostname} HD Usage', self.check_disk_usage) def callback_config(self, params: List[rclpy.Parameter]): - """Retrieve ROS parameters. + """ + Retrieve ROS parameters. see the class documentation for declared parameters. """ @@ -108,7 +110,8 @@ def callback_config(self, params: List[rclpy.Parameter]): return SetParametersResult(successful=True) def check_disk_usage(self, diag: DiagnosticStatus) -> DiagnosticStatus: - """Compute the disk usage and derive a status from it. + """ + Compute the disk usage and derive a status from it. Task periodically ran by the diagnostic updater. """ From 0447648c6481119986f9eb1dbf951c7201b01e90 Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:12:22 +0200 Subject: [PATCH 32/43] Minimize header includes by moving impl to .cpp files (#331) and Fix usage of rclcpp::ok with a non-default context (#352) (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Minimize header includes by moving impl to .cpp files (#331) * Minimize header includes by moving impl to .cpp files * Make sure to build a shared library Signed-off-by: Christian Henkel * Fix usage of rclcpp::ok with a non-default context (#352) * Fix usage of rclcpp::ok with a non-default context The current implementation calls `rclcpp::ok` without any arguments, which amounts to verifying that the global default context is valid. In the case where a node is added to a custom context, and the global context is not used, `rclcpp::ok` without any arguments will always return `false` since the global context has never been initialized. To fix it, pass to rclcpp the context that's available via the node's base interface. * Add a test for custom context Signed-off-by: Christian Henkel --------- Signed-off-by: Christian Henkel Co-authored-by: Ramon Wijnands Co-authored-by: Hervé Audren <101862279+haudren-woven@users.noreply.github.com> --- diagnostic_updater/CMakeLists.txt | 19 +- .../diagnostic_status_wrapper.hpp | 3 +- .../diagnostic_updater/diagnostic_updater.hpp | 168 ++------------- diagnostic_updater/src/diagnostic_updater.cpp | 198 ++++++++++++++++++ diagnostic_updater/src/example.cpp | 3 +- 5 files changed, 224 insertions(+), 167 deletions(-) create mode 100644 diagnostic_updater/src/diagnostic_updater.cpp diff --git a/diagnostic_updater/CMakeLists.txt b/diagnostic_updater/CMakeLists.txt index cd723d2e6..dd4261e72 100644 --- a/diagnostic_updater/CMakeLists.txt +++ b/diagnostic_updater/CMakeLists.txt @@ -11,22 +11,25 @@ if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif() find_package(ament_cmake REQUIRED) +find_package(ament_cmake_ros REQUIRED) find_package(ament_cmake_python REQUIRED) find_package(diagnostic_msgs REQUIRED) find_package(rclcpp REQUIRED) find_package(rclpy REQUIRED) find_package(std_msgs REQUIRED) -add_library(${PROJECT_NAME} INTERFACE) +add_library(${PROJECT_NAME} + src/diagnostic_updater.cpp +) target_include_directories( ${PROJECT_NAME} - INTERFACE + PUBLIC $ $ ) ament_target_dependencies( ${PROJECT_NAME} - INTERFACE + PUBLIC diagnostic_msgs rclcpp ) @@ -63,12 +66,10 @@ if(BUILD_TESTING) $ $ ) + target_link_libraries(diagnostic_updater_test ${PROJECT_NAME}) ament_target_dependencies( diagnostic_updater_test - "diagnostic_msgs" - "rclcpp" "rclcpp_lifecycle" - "std_msgs" ) ament_add_gtest(diagnostic_status_wrapper_test test/diagnostic_status_wrapper_test.cpp) @@ -90,11 +91,7 @@ if(BUILD_TESTING) $ $ ) - ament_target_dependencies( - status_msg_test - "diagnostic_msgs" - "rclcpp" - ) + target_link_libraries(status_msg_test ${PROJECT_NAME}) find_package(ament_cmake_pytest REQUIRED) ament_add_pytest_test(diagnostic_updater_test.py "test/diagnostic_updater_test.py") diff --git a/diagnostic_updater/include/diagnostic_updater/diagnostic_status_wrapper.hpp b/diagnostic_updater/include/diagnostic_updater/diagnostic_status_wrapper.hpp index 904ca6e5c..8d4795669 100644 --- a/diagnostic_updater/include/diagnostic_updater/diagnostic_status_wrapper.hpp +++ b/diagnostic_updater/include/diagnostic_updater/diagnostic_status_wrapper.hpp @@ -47,7 +47,8 @@ #include "diagnostic_msgs/msg/diagnostic_status.hpp" -#include "rclcpp/rclcpp.hpp" +#include "rclcpp/logger.hpp" +#include "rclcpp/logging.hpp" namespace diagnostic_updater { diff --git a/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp b/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp index 2ecb08ab1..635ead06a 100644 --- a/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp +++ b/diagnostic_updater/include/diagnostic_updater/diagnostic_updater.hpp @@ -50,8 +50,12 @@ #include "rcl/time.h" -#include "rclcpp/create_timer.hpp" -#include "rclcpp/rclcpp.hpp" +#include "rclcpp/node_interfaces/node_base_interface.hpp" +#include "rclcpp/node_interfaces/node_clock_interface.hpp" +#include "rclcpp/node_interfaces/node_logging_interface.hpp" +#include "rclcpp/node_interfaces/node_parameters_interface.hpp" +#include "rclcpp/node_interfaces/node_timers_interface.hpp" +#include "rclcpp/node_interfaces/node_topics_interface.hpp" namespace diagnostic_updater { @@ -381,43 +385,7 @@ class Updater : public DiagnosticTaskVector std::shared_ptr parameters_interface, std::shared_ptr timers_interface, std::shared_ptr topics_interface, - double period = 1.0) - : verbose_(false), - base_interface_(base_interface), - timers_interface_(timers_interface), - clock_(clock_interface->get_clock()), - period_(rclcpp::Duration::from_seconds(period)), - publisher_( - rclcpp::create_publisher( - topics_interface, "/diagnostics", 1)), - logger_(logging_interface->get_logger()), - node_name_(base_interface->get_name()), - warn_nohwid_done_(false) - { - constexpr const char * period_param_name = "diagnostic_updater.period"; - rclcpp::ParameterValue period_param; - if (parameters_interface->has_parameter(period_param_name)) { - period_param = parameters_interface->get_parameter(period_param_name).get_parameter_value(); - } else { - period_param = parameters_interface->declare_parameter( - period_param_name, rclcpp::ParameterValue(period)); - } - period = period_param.get(); - period_ = rclcpp::Duration::from_seconds(period); - - reset_timer(); - - constexpr const char * use_fqn_param_name = "diagnostic_updater.use_fqn"; - rclcpp::ParameterValue use_fqn_param; - if (parameters_interface->has_parameter(use_fqn_param_name)) { - use_fqn_param = parameters_interface->get_parameter(use_fqn_param_name).get_parameter_value(); - } else { - use_fqn_param = parameters_interface->declare_parameter( - use_fqn_param_name, rclcpp::ParameterValue(false)); - } - node_name_ = use_fqn_param.get() ? - base_interface->get_fully_qualified_name() : base_interface->get_name(); - } + double period = 1.0); /** * \brief Returns the interval between updates. @@ -459,105 +427,20 @@ class Updater : public DiagnosticTaskVector * * \param msg Status message to output. */ - void broadcast(unsigned char lvl, const std::string msg) - { - std::vector status_vec; - - const std::vector & tasks = getTasks(); - for (std::vector::const_iterator iter = - tasks.begin(); - iter != tasks.end(); iter++) - { - diagnostic_updater::DiagnosticStatusWrapper status; - - status.name = iter->getName(); - status.summary(lvl, msg); - - status_vec.push_back(status); - } + void broadcast(unsigned char lvl, const std::string msg); - publish(status_vec); - } - - void setHardwareIDf(const char * format, ...) - { - va_list va; - const int kBufferSize = 1000; - char buff[kBufferSize]; // @todo This could be done more elegantly. - va_start(va, format); - if (vsnprintf(buff, kBufferSize, format, va) >= kBufferSize) { - RCLCPP_DEBUG(logger_, "Really long string in diagnostic_updater::setHardwareIDf."); - } - hwid_ = std::string(buff); - va_end(va); - } + void setHardwareIDf(const char * format, ...); void setHardwareID(const std::string & hwid) {hwid_ = hwid;} private: - void reset_timer() - { - update_timer_ = rclcpp::create_timer( - base_interface_, - timers_interface_, - clock_, - period_, - std::bind(&Updater::update, this)); - } + void reset_timer(); /** * \brief Causes the diagnostics to update if the inter-update interval * has been exceeded. */ - void update() - { - if (rclcpp::ok(base_interface_->get_context())) { - bool warn_nohwid = hwid_.empty(); - - std::vector status_vec; - - std::unique_lock lock( - lock_); // Make sure no adds happen while we are processing here. - const std::vector & tasks = getTasks(); - for (std::vector::const_iterator iter = - tasks.begin(); - iter != tasks.end(); iter++) - { - diagnostic_updater::DiagnosticStatusWrapper status; - - status.name = iter->getName(); - status.level = 2; - status.message = "No message was set"; - status.hardware_id = hwid_; - - iter->run(status); - - status_vec.push_back(status); - - if (status.level) { - warn_nohwid = false; - } - - if (verbose_ && status.level) { - RCLCPP_WARN( - logger_, "Non-zero diagnostic status. Name: '%s', status %i: '%s'", - status.name.c_str(), status.level, status.message.c_str()); - } - } - - if (warn_nohwid && !warn_nohwid_done_) { - std::string error_msg = "diagnostic_updater: No HW_ID was set."; - error_msg += " This is probably a bug. Please report it."; - error_msg += " For devices that do not have a HW_ID, set this value to 'none'."; - error_msg += " This warning only occurs once all diagnostics are OK."; - error_msg += " It is okay to wait until the device is open before calling setHardwareID."; - RCLCPP_WARN(logger_, "%s", error_msg.c_str()); - warn_nohwid_done_ = true; - } - - publish(status_vec); - } - } + void update(); /** * Recheck the diagnostic_period on the parameter server. (Cached) @@ -574,41 +457,18 @@ class Updater : public DiagnosticTaskVector /** * Publishes a single diagnostic status. */ - void publish(diagnostic_msgs::msg::DiagnosticStatus & stat) - { - std::vector status_vec; - status_vec.push_back(stat); - publish(status_vec); - } + void publish(diagnostic_msgs::msg::DiagnosticStatus & stat); /** * Publishes a vector of diagnostic statuses. */ - void publish(std::vector & status_vec) - { - for (std::vector::iterator iter = - status_vec.begin(); - iter != status_vec.end(); iter++) - { - iter->name = node_name_ + std::string(": ") + iter->name; - } - diagnostic_msgs::msg::DiagnosticArray msg; - msg.status = status_vec; - msg.header.stamp = clock_->now(); - publisher_->publish(msg); - } + void publish(std::vector & status_vec); /** * Causes a placeholder DiagnosticStatus to be published as soon as a * diagnostic task is added to the Updater. */ - virtual void addedTaskCallback(DiagnosticTaskInternal & task) - { - DiagnosticStatusWrapper stat; - stat.name = task.getName(); - stat.summary(0, "Node starting up"); - publish(stat); - } + virtual void addedTaskCallback(DiagnosticTaskInternal & task); rclcpp::node_interfaces::NodeBaseInterface::SharedPtr base_interface_; rclcpp::node_interfaces::NodeTimersInterface::SharedPtr timers_interface_; diff --git a/diagnostic_updater/src/diagnostic_updater.cpp b/diagnostic_updater/src/diagnostic_updater.cpp new file mode 100644 index 000000000..bfd5ce6d9 --- /dev/null +++ b/diagnostic_updater/src/diagnostic_updater.cpp @@ -0,0 +1,198 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2008, 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. + *********************************************************************/ + +#include + +#include "rclcpp/rclcpp.hpp" + +namespace diagnostic_updater +{ +Updater::Updater( + std::shared_ptr base_interface, + std::shared_ptr clock_interface, + std::shared_ptr logging_interface, + std::shared_ptr parameters_interface, + std::shared_ptr timers_interface, + std::shared_ptr topics_interface, double period) +: verbose_(false), + base_interface_(base_interface), + timers_interface_(timers_interface), + clock_(clock_interface->get_clock()), + period_(rclcpp::Duration::from_seconds(period)), + publisher_(rclcpp::create_publisher( + topics_interface, "/diagnostics", 1)), + logger_(logging_interface->get_logger()), + node_name_(base_interface->get_name()), + warn_nohwid_done_(false) +{ + constexpr const char * period_param_name = "diagnostic_updater.period"; + rclcpp::ParameterValue period_param; + if (parameters_interface->has_parameter(period_param_name)) { + period_param = parameters_interface->get_parameter(period_param_name).get_parameter_value(); + } else { + period_param = + parameters_interface->declare_parameter(period_param_name, rclcpp::ParameterValue(period)); + } + period = period_param.get(); + period_ = rclcpp::Duration::from_seconds(period); + + reset_timer(); + + constexpr const char * use_fqn_param_name = "diagnostic_updater.use_fqn"; + rclcpp::ParameterValue use_fqn_param; + if (parameters_interface->has_parameter(use_fqn_param_name)) { + use_fqn_param = parameters_interface->get_parameter(use_fqn_param_name).get_parameter_value(); + } else { + use_fqn_param = + parameters_interface->declare_parameter(use_fqn_param_name, rclcpp::ParameterValue(false)); + } + node_name_ = use_fqn_param.get() ? base_interface->get_fully_qualified_name() : + base_interface->get_name(); +} + +void Updater::broadcast(unsigned char lvl, const std::string msg) +{ + std::vector status_vec; + + const std::vector & tasks = getTasks(); + for (std::vector::const_iterator iter = tasks.begin(); + iter != tasks.end(); iter++) + { + diagnostic_updater::DiagnosticStatusWrapper status; + + status.name = iter->getName(); + status.summary(lvl, msg); + + status_vec.push_back(status); + } + + publish(status_vec); +} + +void Updater::setHardwareIDf(const char * format, ...) +{ + va_list va; + const int kBufferSize = 1000; + char buff[kBufferSize]; // @todo This could be done more elegantly. + va_start(va, format); + if (vsnprintf(buff, kBufferSize, format, va) >= kBufferSize) { + RCLCPP_DEBUG(logger_, "Really long string in diagnostic_updater::setHardwareIDf."); + } + hwid_ = std::string(buff); + va_end(va); +} + +void Updater::reset_timer() +{ + update_timer_ = rclcpp::create_timer( + base_interface_, timers_interface_, clock_, period_, std::bind(&Updater::update, this)); +} + +void Updater::update() +{ + if (rclcpp::ok(base_interface_->get_context())) { + bool warn_nohwid = hwid_.empty(); + + std::vector status_vec; + + std::unique_lock lock( + lock_); // Make sure no adds happen while we are processing here. + const std::vector & tasks = getTasks(); + for (std::vector::const_iterator iter = tasks.begin(); + iter != tasks.end(); iter++) + { + diagnostic_updater::DiagnosticStatusWrapper status; + + status.name = iter->getName(); + status.level = 2; + status.message = "No message was set"; + status.hardware_id = hwid_; + + iter->run(status); + + status_vec.push_back(status); + + if (status.level) { + warn_nohwid = false; + } + + if (verbose_ && status.level) { + RCLCPP_WARN( + logger_, "Non-zero diagnostic status. Name: '%s', status %i: '%s'", status.name.c_str(), + status.level, status.message.c_str()); + } + } + + if (warn_nohwid && !warn_nohwid_done_) { + std::string error_msg = "diagnostic_updater: No HW_ID was set."; + error_msg += " This is probably a bug. Please report it."; + error_msg += " For devices that do not have a HW_ID, set this value to 'none'."; + error_msg += " This warning only occurs once all diagnostics are OK."; + error_msg += " It is okay to wait until the device is open before calling setHardwareID."; + RCLCPP_WARN(logger_, "%s", error_msg.c_str()); + warn_nohwid_done_ = true; + } + + publish(status_vec); + } +} + +void Updater::publish(diagnostic_msgs::msg::DiagnosticStatus & stat) +{ + std::vector status_vec; + status_vec.push_back(stat); + publish(status_vec); +} + +void Updater::publish(std::vector & status_vec) +{ + for (std::vector::iterator iter = status_vec.begin(); + iter != status_vec.end(); iter++) + { + iter->name = node_name_ + std::string(": ") + iter->name; + } + diagnostic_msgs::msg::DiagnosticArray msg; + msg.status = status_vec; + msg.header.stamp = clock_->now(); + publisher_->publish(msg); +} + +void Updater::addedTaskCallback(DiagnosticTaskInternal & task) +{ + DiagnosticStatusWrapper stat; + stat.name = task.getName(); + stat.summary(0, "Node starting up"); + publish(stat); +} +} // namespace diagnostic_updater diff --git a/diagnostic_updater/src/example.cpp b/diagnostic_updater/src/example.cpp index fb03c98dc..328c2f717 100644 --- a/diagnostic_updater/src/example.cpp +++ b/diagnostic_updater/src/example.cpp @@ -34,9 +34,10 @@ #include #include - #include +#include "rclcpp/rclcpp.hpp" + using namespace std::chrono_literals; double time_to_launch; From 275dd5e2292ace3598032ef7db37c1386f8399fb Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:16:00 +0200 Subject: [PATCH 33/43] Fix correctly exporting the library (#388) (#393) Without this, downstream packages have missing symbols Relates to: https://github.com/ros/diagnostics/issues/387 (cherry picked from commit d8fef6900e40c553510c1f8b9ec3003d315992b0) Co-authored-by: Ramon Wijnands --- diagnostic_updater/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/diagnostic_updater/CMakeLists.txt b/diagnostic_updater/CMakeLists.txt index dd4261e72..ebca013ee 100644 --- a/diagnostic_updater/CMakeLists.txt +++ b/diagnostic_updater/CMakeLists.txt @@ -118,6 +118,7 @@ install( ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET) ament_export_include_directories(include) +ament_export_libraries(${PROJECT_NAME}) ament_export_dependencies(ament_cmake) ament_export_dependencies(ament_cmake_python) ament_export_dependencies(diagnostic_msgs) From a223af1beaa8e76253eafa178062be4010d6041e Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Tue, 30 Jul 2024 15:20:50 +0200 Subject: [PATCH 34/43] changelogs --- diagnostic_aggregator/CHANGELOG.rst | 3 +++ diagnostic_common_diagnostics/CHANGELOG.rst | 6 ++++++ diagnostic_updater/CHANGELOG.rst | 6 ++++++ diagnostics/CHANGELOG.rst | 3 +++ self_test/CHANGELOG.rst | 3 +++ 5 files changed, 21 insertions(+) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index 8b6b2b001..022ab7181 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 3.2.1 (2024-06-27) ------------------ * Add add_analyzer functionality (`#329 `_) diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index 6e78fefc9..f3f3d675d 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* fixing pep257 problems introduced by `#334 `_ (`#384 `_) +* Port hd_monitor to ROS2 (`#334 `_) +* Contributors: Antoine Lima, Christian Henkel + 3.2.1 (2024-06-27) ------------------ * refactor(sensors_monitor): ros2 port `#339 `_ diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index 922f41300..c97ad2a09 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Fix correctly exporting the library (`#388 `_) (`#393 `_) +* Minimize header includes by moving impl to .cpp files (`#331 `_) and Fix usage of rclcpp::ok with a non-default context (`#352 `_) (`#390 `_) +* Contributors: Christian Henkel, Ramon Wijnands, Hervé Audren + 3.2.1 (2024-06-27) ------------------ * change(diagnosed-publisher): allow specifying node clock (`#340 `_) diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index abdc0ea52..44bc6ef61 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 3.2.1 (2024-06-27) ------------------ diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index 7ee57f1c8..a0e2fd91f 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 3.2.1 (2024-06-27) ------------------ * Building in docker (`#335 `_) From cd24251b844b6243fdd1b1d9fb198a8492d6f0ca Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Tue, 30 Jul 2024 15:21:27 +0200 Subject: [PATCH 35/43] 4.3.1 --- diagnostic_aggregator/CHANGELOG.rst | 4 ++-- diagnostic_aggregator/package.xml | 2 +- diagnostic_common_diagnostics/CHANGELOG.rst | 4 ++-- diagnostic_common_diagnostics/package.xml | 2 +- diagnostic_updater/CHANGELOG.rst | 4 ++-- diagnostic_updater/package.xml | 2 +- diagnostics/CHANGELOG.rst | 4 ++-- diagnostics/package.xml | 2 +- self_test/CHANGELOG.rst | 4 ++-- self_test/package.xml | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/diagnostic_aggregator/CHANGELOG.rst b/diagnostic_aggregator/CHANGELOG.rst index 022ab7181..023c8bd87 100644 --- a/diagnostic_aggregator/CHANGELOG.rst +++ b/diagnostic_aggregator/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_aggregator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.3.1 (2024-07-30) +------------------ 3.2.1 (2024-06-27) ------------------ diff --git a/diagnostic_aggregator/package.xml b/diagnostic_aggregator/package.xml index 8b11223d9..b63f01646 100644 --- a/diagnostic_aggregator/package.xml +++ b/diagnostic_aggregator/package.xml @@ -2,7 +2,7 @@ diagnostic_aggregator - 4.3.0 + 4.3.1 diagnostic_aggregator Austin Hendrix Brice Rebsamen diff --git a/diagnostic_common_diagnostics/CHANGELOG.rst b/diagnostic_common_diagnostics/CHANGELOG.rst index f3f3d675d..727be8108 100644 --- a/diagnostic_common_diagnostics/CHANGELOG.rst +++ b/diagnostic_common_diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_common_diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.3.1 (2024-07-30) +------------------ * fixing pep257 problems introduced by `#334 `_ (`#384 `_) * Port hd_monitor to ROS2 (`#334 `_) * Contributors: Antoine Lima, Christian Henkel diff --git a/diagnostic_common_diagnostics/package.xml b/diagnostic_common_diagnostics/package.xml index ec4f0be3d..a698c4cb1 100644 --- a/diagnostic_common_diagnostics/package.xml +++ b/diagnostic_common_diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostic_common_diagnostics - 4.3.0 + 4.3.1 diagnostic_common_diagnostics Austin Hendrix Brice Rebsamen diff --git a/diagnostic_updater/CHANGELOG.rst b/diagnostic_updater/CHANGELOG.rst index c97ad2a09..c51706fc9 100644 --- a/diagnostic_updater/CHANGELOG.rst +++ b/diagnostic_updater/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostic_updater ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.3.1 (2024-07-30) +------------------ * Fix correctly exporting the library (`#388 `_) (`#393 `_) * Minimize header includes by moving impl to .cpp files (`#331 `_) and Fix usage of rclcpp::ok with a non-default context (`#352 `_) (`#390 `_) * Contributors: Christian Henkel, Ramon Wijnands, Hervé Audren diff --git a/diagnostic_updater/package.xml b/diagnostic_updater/package.xml index 00adf92a6..2683e65bc 100644 --- a/diagnostic_updater/package.xml +++ b/diagnostic_updater/package.xml @@ -2,7 +2,7 @@ diagnostic_updater - 4.3.0 + 4.3.1 diagnostic_updater contains tools for easily updating diagnostics. it is commonly used in device drivers to keep track of the status of output topics, device status, etc. Austin Hendrix Brice Rebsamen diff --git a/diagnostics/CHANGELOG.rst b/diagnostics/CHANGELOG.rst index 44bc6ef61..4dc637ad4 100644 --- a/diagnostics/CHANGELOG.rst +++ b/diagnostics/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.3.1 (2024-07-30) +------------------ 3.2.1 (2024-06-27) ------------------ diff --git a/diagnostics/package.xml b/diagnostics/package.xml index de05e4ce3..158fe74a7 100644 --- a/diagnostics/package.xml +++ b/diagnostics/package.xml @@ -2,7 +2,7 @@ diagnostics - 4.3.0 + 4.3.1 diagnostics Austin Hendrix Brice Rebsamen diff --git a/self_test/CHANGELOG.rst b/self_test/CHANGELOG.rst index a0e2fd91f..aeac3fd06 100644 --- a/self_test/CHANGELOG.rst +++ b/self_test/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package self_test ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.3.1 (2024-07-30) +------------------ 3.2.1 (2024-06-27) ------------------ diff --git a/self_test/package.xml b/self_test/package.xml index c1dcec942..10a894bd0 100644 --- a/self_test/package.xml +++ b/self_test/package.xml @@ -2,7 +2,7 @@ self_test - 4.3.0 + 4.3.1 self_test Austin Hendrix Brice Rebsamen From bb392ee850b7d549e621c265b214c47ad9f2dd8f Mon Sep 17 00:00:00 2001 From: Christian Henkel <6976069+ct2034@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:28:02 +0200 Subject: [PATCH 36/43] adding buildfarm statuses (#394) Signed-off-by: Christian Henkel --- README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 46509be6c..3b7041626 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Use this command to port a given PR of `PR_NUMBER` to the other branches: backport --pr PR_NUMBER -b ros2-humble ros2-iron ros2-jazzy ``` -# Versioning and Releases +## Versioning and Releases - (__X__.0.0) We use the major version number to indicate a breaking change. - (0.__Y__.0) The minor version number is used to differentiate between different ROS distributions: @@ -66,6 +66,51 @@ backport --pr PR_NUMBER -b ros2-humble ros2-iron ros2-jazzy - Future releases (Kilted Kaiju 05/25) will get x.__3__.z and _Rolling_ will be incremented accordingly. - (0.0.__Z__) The patch version number is used for changes in the current ROS distribution that do not affect the API. +## Buildfarm Statuses + +### diagnostic_aggregator + +| | H | I | J | R | +| ------- | - | - | - | - | +| src, ubuntu | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_uJ__diagnostic_aggregator__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_uJ__diagnostic_aggregator__ubuntu_jammy__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_uJ__diagnostic_aggregator__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_uJ__diagnostic_aggregator__ubuntu_jammy__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_uN__diagnostic_aggregator__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_uN__diagnostic_aggregator__ubuntu_noble__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_uN__diagnostic_aggregator__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_uN__diagnostic_aggregator__ubuntu_noble__source) | +| src, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_el8__diagnostic_aggregator__rhel_8__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_el8__diagnostic_aggregator__rhel_8__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_el9__diagnostic_aggregator__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_el9__diagnostic_aggregator__rhel_9__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_el9__diagnostic_aggregator__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_el9__diagnostic_aggregator__rhel_9__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_el9__diagnostic_aggregator__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_el9__diagnostic_aggregator__rhel_9__source) | +| bin, ubuntu, amd64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_uJ64__diagnostic_aggregator__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_uJ64__diagnostic_aggregator__ubuntu_jammy_amd64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_uJ64__diagnostic_aggregator__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_uJ64__diagnostic_aggregator__ubuntu_jammy_amd64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_uN64__diagnostic_aggregator__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_uN64__diagnostic_aggregator__ubuntu_noble_amd64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_uN64__diagnostic_aggregator__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_uN64__diagnostic_aggregator__ubuntu_noble_amd64__binary) | +| bin, ubuntu, arm64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_ujv8_uJv8__diagnostic_aggregator__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_ujv8_uJv8__diagnostic_aggregator__ubuntu_jammy_arm64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_ujv8_uJv8__diagnostic_aggregator__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_ujv8_uJv8__diagnostic_aggregator__ubuntu_jammy_arm64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_unv8_uNv8__diagnostic_aggregator__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_unv8_uNv8__diagnostic_aggregator__ubuntu_noble_arm64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_unv8_uNv8__diagnostic_aggregator__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_unv8_uNv8__diagnostic_aggregator__ubuntu_noble_arm64__binary) | +| bin, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_rhel_el864__diagnostic_aggregator__rhel_8_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_rhel_el864__diagnostic_aggregator__rhel_8_x86_64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_rhel_el964__diagnostic_aggregator__rhel_9_x86_64__binary) | + + +### diagnostic_common_diagnostics + +| | H | I | J | R | +| ------- | - | - | - | - | +| src, ubuntu | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_uJ__diagnostic_common_diagnostics__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_uJ__diagnostic_common_diagnostics__ubuntu_jammy__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_uJ__diagnostic_common_diagnostics__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_uJ__diagnostic_common_diagnostics__ubuntu_jammy__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_uN__diagnostic_common_diagnostics__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_uN__diagnostic_common_diagnostics__ubuntu_noble__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_uN__diagnostic_common_diagnostics__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_uN__diagnostic_common_diagnostics__ubuntu_noble__source) | +| src, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_el8__diagnostic_common_diagnostics__rhel_8__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_el8__diagnostic_common_diagnostics__rhel_8__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_el9__diagnostic_common_diagnostics__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_el9__diagnostic_common_diagnostics__rhel_9__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_el9__diagnostic_common_diagnostics__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_el9__diagnostic_common_diagnostics__rhel_9__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_el9__diagnostic_common_diagnostics__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_el9__diagnostic_common_diagnostics__rhel_9__source) | +| bin, ubuntu, amd64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_uJ64__diagnostic_common_diagnostics__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_uJ64__diagnostic_common_diagnostics__ubuntu_jammy_amd64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_uJ64__diagnostic_common_diagnostics__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_uJ64__diagnostic_common_diagnostics__ubuntu_jammy_amd64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_uN64__diagnostic_common_diagnostics__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_uN64__diagnostic_common_diagnostics__ubuntu_noble_amd64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_uN64__diagnostic_common_diagnostics__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_uN64__diagnostic_common_diagnostics__ubuntu_noble_amd64__binary) | +| bin, ubuntu, arm64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_ujv8_uJv8__diagnostic_common_diagnostics__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_ujv8_uJv8__diagnostic_common_diagnostics__ubuntu_jammy_arm64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_ujv8_uJv8__diagnostic_common_diagnostics__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_ujv8_uJv8__diagnostic_common_diagnostics__ubuntu_jammy_arm64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_unv8_uNv8__diagnostic_common_diagnostics__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_unv8_uNv8__diagnostic_common_diagnostics__ubuntu_noble_arm64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_unv8_uNv8__diagnostic_common_diagnostics__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_unv8_uNv8__diagnostic_common_diagnostics__ubuntu_noble_arm64__binary) | +| bin, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_rhel_el864__diagnostic_common_diagnostics__rhel_8_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_rhel_el864__diagnostic_common_diagnostics__rhel_8_x86_64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_rhel_el964__diagnostic_common_diagnostics__rhel_9_x86_64__binary) | + + +### diagnostic_updater + +| | H | I | J | R | +| ------- | - | - | - | - | +| src, ubuntu | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_uJ__diagnostic_updater__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_uJ__diagnostic_updater__ubuntu_jammy__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_uJ__diagnostic_updater__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_uJ__diagnostic_updater__ubuntu_jammy__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_uN__diagnostic_updater__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_uN__diagnostic_updater__ubuntu_noble__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_uN__diagnostic_updater__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_uN__diagnostic_updater__ubuntu_noble__source) | +| src, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_el8__diagnostic_updater__rhel_8__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_el8__diagnostic_updater__rhel_8__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_el9__diagnostic_updater__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_el9__diagnostic_updater__rhel_9__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_el9__diagnostic_updater__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_el9__diagnostic_updater__rhel_9__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_el9__diagnostic_updater__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_el9__diagnostic_updater__rhel_9__source) | +| bin, ubuntu, amd64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_uJ64__diagnostic_updater__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_uJ64__diagnostic_updater__ubuntu_jammy_amd64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_uJ64__diagnostic_updater__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_uJ64__diagnostic_updater__ubuntu_jammy_amd64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_uN64__diagnostic_updater__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_uN64__diagnostic_updater__ubuntu_noble_amd64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_uN64__diagnostic_updater__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_uN64__diagnostic_updater__ubuntu_noble_amd64__binary) | +| bin, ubuntu, arm64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_ujv8_uJv8__diagnostic_updater__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_ujv8_uJv8__diagnostic_updater__ubuntu_jammy_arm64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_ujv8_uJv8__diagnostic_updater__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_ujv8_uJv8__diagnostic_updater__ubuntu_jammy_arm64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_unv8_uNv8__diagnostic_updater__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_unv8_uNv8__diagnostic_updater__ubuntu_noble_arm64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_unv8_uNv8__diagnostic_updater__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_unv8_uNv8__diagnostic_updater__ubuntu_noble_arm64__binary) | +| bin, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_rhel_el864__diagnostic_updater__rhel_8_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_rhel_el864__diagnostic_updater__rhel_8_x86_64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_rhel_el964__diagnostic_updater__rhel_9_x86_64__binary) | + +### self_test + +| | H | I | J | R | +| ------- | - | - | - | - | +| src, ubuntu | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_uJ__self_test__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_uJ__self_test__ubuntu_jammy__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_uJ__self_test__ubuntu_jammy__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_uJ__self_test__ubuntu_jammy__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_uN__self_test__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_uN__self_test__ubuntu_noble__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_uN__self_test__ubuntu_noble__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_uN__self_test__ubuntu_noble__source) | +| src, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hsrc_el8__self_test__rhel_8__source&style=ball-32x32)](https://build.ros2.org/job/Hsrc_el8__self_test__rhel_8__source) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Isrc_el9__self_test__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Isrc_el9__self_test__rhel_9__source) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jsrc_el9__self_test__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Jsrc_el9__self_test__rhel_9__source) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rsrc_el9__self_test__rhel_9__source&style=ball-32x32)](https://build.ros2.org/job/Rsrc_el9__self_test__rhel_9__source) | +| bin, ubuntu, amd64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_uJ64__self_test__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_uJ64__self_test__ubuntu_jammy_amd64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_uJ64__self_test__ubuntu_jammy_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_uJ64__self_test__ubuntu_jammy_amd64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_uN64__self_test__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_uN64__self_test__ubuntu_noble_amd64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_uN64__self_test__ubuntu_noble_amd64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_uN64__self_test__ubuntu_noble_amd64__binary) | +| bin, ubuntu, arm64 | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_ujv8_uJv8__self_test__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_ujv8_uJv8__self_test__ubuntu_jammy_arm64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_ujv8_uJv8__self_test__ubuntu_jammy_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_ujv8_uJv8__self_test__ubuntu_jammy_arm64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_unv8_uNv8__self_test__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_unv8_uNv8__self_test__ubuntu_noble_arm64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_unv8_uNv8__self_test__ubuntu_noble_arm64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_unv8_uNv8__self_test__ubuntu_noble_arm64__binary) | +| bin, rhel | [![Humble](https://build.ros2.org/buildStatus/icon?job=Hbin_rhel_el864__self_test__rhel_8_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Hbin_rhel_el864__self_test__rhel_8_x86_64__binary) | [![Iron](https://build.ros2.org/buildStatus/icon?job=Ibin_rhel_el964__self_test__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Ibin_rhel_el964__self_test__rhel_9_x86_64__binary) | [![Jazzy](https://build.ros2.org/buildStatus/icon?job=Jbin_rhel_el964__self_test__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Jbin_rhel_el964__self_test__rhel_9_x86_64__binary) | [![Rolling](https://build.ros2.org/buildStatus/icon?job=Rbin_rhel_el964__self_test__rhel_9_x86_64__binary&style=ball-32x32)](https://build.ros2.org/job/Rbin_rhel_el964__self_test__rhel_9_x86_64__binary) | + + # License The source code is released under a [BSD 3-Clause license](LICENSE). From 59b45f603aaf4bbbaa0cb383f6e04cbfc2444223 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Tue, 15 Oct 2024 14:52:39 +0300 Subject: [PATCH 37/43] Add setup.py for diagnostic_common_diagnostics package --- diagnostic_common_diagnostics/setup.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 diagnostic_common_diagnostics/setup.py diff --git a/diagnostic_common_diagnostics/setup.py b/diagnostic_common_diagnostics/setup.py new file mode 100644 index 000000000..e019c6f5e --- /dev/null +++ b/diagnostic_common_diagnostics/setup.py @@ -0,0 +1,37 @@ +from setuptools import setup + +package_name = 'diagnostic_common_diagnostics' + +setup( + name=package_name, + version='4.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + [f'resource/{package_name}']), + ('share/' + package_name, ['package.xml']), + ('lib/' + package_name, [ + 'cpu_monitor.py', + 'ntp_monitor.py', + 'ram_monitor.py', + 'sensors_monitor.py', + 'hd_monitor.py', + ]), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='root', + maintainer_email='vladyslav.hrynchak@logivations.com', + description='Package for diagnostics', + license="TODO: License declaration", + tests_require=["pytest"], + entry_points={ + 'console_scripts': [ + 'cpu_monitor = diagnostic_common_diagnostics.cpu_monitor:main', + 'ntp_monitor = diagnostic_common_diagnostics.ntp_monitor:main', + 'ram_monitor = diagnostic_common_diagnostics.ram_monitor:main', + 'sensors_monitor = diagnostic_common_diagnostics.sensors_monitor:main', + 'hd_monitor = diagnostic_common_diagnostics.hd_monitor:main', + ], + }, +) From f392c12ca5d93d3d562943a7e612d85fa25d6f9f Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Wed, 16 Oct 2024 10:25:37 +0300 Subject: [PATCH 38/43] resolve conflicts: added lock_guard to aggregator.cpp --- diagnostic_aggregator/src/aggregator.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/diagnostic_aggregator/src/aggregator.cpp b/diagnostic_aggregator/src/aggregator.cpp index e4d75ad53..487e0834c 100644 --- a/diagnostic_aggregator/src/aggregator.cpp +++ b/diagnostic_aggregator/src/aggregator.cpp @@ -129,15 +129,18 @@ void Aggregator::initAnalyzers() RCLCPP_DEBUG( logger_, "Aggregator critical publisher configured to: %s", (critical_ ? "true" : "false")); - n_ = std::shared_ptr(this, [](rclcpp::Node *) {}); - analyzer_group_ = std::make_unique(); - if (!analyzer_group_->init(base_path_, "", n_)) { - RCLCPP_ERROR(logger_, "Analyzer group for diagnostic aggregator failed to initialize!"); - } + { // lock the mutex while analyzer_group_ and other_analyzer_ are being updated + std::lock_guard lock(mutex_); + n_ = std::shared_ptr(this, [](rclcpp::Node *) {}); + analyzer_group_ = std::make_unique(); + if (!analyzer_group_->init(base_path_, "", n_)) { + RCLCPP_ERROR(logger_, "Analyzer group for diagnostic aggregator failed to initialize!"); + } - // Last analyzer handles remaining data - other_analyzer_ = std::make_unique(other_as_errors); - other_analyzer_->init(base_path_); // This always returns true + // Last analyzer handles remaining data + other_analyzer_ = std::make_unique(other_as_errors); + other_analyzer_->init(base_path_); // This always returns true + } diag_sub_ = create_subscription( "/diagnostics", rclcpp::SystemDefaultsQoS().keep_last(history_depth_), From 3be05537d153509334b98ecd2bf1a682d387f017 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Wed, 16 Oct 2024 15:31:28 +0300 Subject: [PATCH 39/43] added function to get node and removed hostname from node name --- .../diagnostic_common_diagnostics/cpu_monitor.py | 15 ++++++++++----- .../diagnostic_common_diagnostics/ram_monitor.py | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py index 866629572..5b1d165d5 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py @@ -86,13 +86,11 @@ def run(self, stat): return stat - -def main(args=None): - rclpy.init(args=args) - +def get_cpu_diagnostics_node() -> Node: + """get cpu diagnostics node""" # Create the node hostname = socket.gethostname() - node = Node(f'cpu_monitor_{hostname.replace("-", "_")}') + node = Node('cpu_monitor') # Declare and get parameters node.declare_parameter('warning_percentage', 90) @@ -107,6 +105,13 @@ def main(args=None): updater.setHardwareID(hostname) updater.add(CpuTask(warning_percentage=warning_percentage, window=window)) + return node + + +def main(args=None): + rclpy.init(args=args) + + node = get_cpu_diagnostics_node() rclpy.spin(node) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py index d6250b6b0..fff4f77e4 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py @@ -44,6 +44,7 @@ import psutil import rclpy +from rclpy.node import Node class RamTask(DiagnosticTask): @@ -69,11 +70,11 @@ def run(self, stat): return stat - -def main(): +def get_ram_diagnostics_node() -> Node: + """get ram diagnostics node""" hostname = socket.gethostname() - rclpy.init() - node = rclpy.create_node(f'ram_monitor_{hostname.replace("-", "_")}') + + node = rclpy.create_node('ram_monitor') updater = Updater(node) updater.setHardwareID(hostname) @@ -84,6 +85,13 @@ def main(): ) ) + return node + + +def main(): + rclpy.init() + + node = get_ram_diagnostics_node() rclpy.spin(node) From 78ac516be55f88fe910cfa22d49615f9f4c98bf4 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Mon, 21 Oct 2024 10:55:35 +0300 Subject: [PATCH 40/43] fix typo in description --- .../diagnostic_common_diagnostics/cpu_monitor.py | 2 +- .../diagnostic_common_diagnostics/ram_monitor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py index 5b1d165d5..7cacfdaea 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py @@ -87,7 +87,7 @@ def run(self, stat): return stat def get_cpu_diagnostics_node() -> Node: - """get cpu diagnostics node""" + """get cpu diagnostics node.""" # Create the node hostname = socket.gethostname() node = Node('cpu_monitor') diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py index fff4f77e4..eb0787687 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py @@ -71,7 +71,7 @@ def run(self, stat): return stat def get_ram_diagnostics_node() -> Node: - """get ram diagnostics node""" + """get ram diagnostics node.""" hostname = socket.gethostname() node = rclpy.create_node('ram_monitor') From abbf2cca4a1095e2cdbb0e8ad122b333b97521e2 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Mon, 21 Oct 2024 11:03:07 +0300 Subject: [PATCH 41/43] fix errors --- .../diagnostic_common_diagnostics/cpu_monitor.py | 3 ++- .../diagnostic_common_diagnostics/ram_monitor.py | 3 ++- diagnostic_common_diagnostics/setup.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py index 7cacfdaea..61675da3e 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py @@ -86,8 +86,9 @@ def run(self, stat): return stat + def get_cpu_diagnostics_node() -> Node: - """get cpu diagnostics node.""" + """Get cpu diagnostics node.""" # Create the node hostname = socket.gethostname() node = Node('cpu_monitor') diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py index eb0787687..fe1c2bf26 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py @@ -70,8 +70,9 @@ def run(self, stat): return stat + def get_ram_diagnostics_node() -> Node: - """get ram diagnostics node.""" + """Get ram diagnostics node.""" hostname = socket.gethostname() node = rclpy.create_node('ram_monitor') diff --git a/diagnostic_common_diagnostics/setup.py b/diagnostic_common_diagnostics/setup.py index e019c6f5e..7ceb28c91 100644 --- a/diagnostic_common_diagnostics/setup.py +++ b/diagnostic_common_diagnostics/setup.py @@ -24,7 +24,7 @@ maintainer_email='vladyslav.hrynchak@logivations.com', description='Package for diagnostics', license="TODO: License declaration", - tests_require=["pytest"], + tests_require=['pytest'], entry_points={ 'console_scripts': [ 'cpu_monitor = diagnostic_common_diagnostics.cpu_monitor:main', From 78af864dce74acd8232d6d99f5f408f4c85df0cc Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Mon, 21 Oct 2024 11:08:01 +0300 Subject: [PATCH 42/43] fix errors --- diagnostic_common_diagnostics/setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/diagnostic_common_diagnostics/setup.py b/diagnostic_common_diagnostics/setup.py index 7ceb28c91..048e933eb 100644 --- a/diagnostic_common_diagnostics/setup.py +++ b/diagnostic_common_diagnostics/setup.py @@ -23,15 +23,15 @@ maintainer='root', maintainer_email='vladyslav.hrynchak@logivations.com', description='Package for diagnostics', - license="TODO: License declaration", + license='TODO: License declaration', tests_require=['pytest'], entry_points={ 'console_scripts': [ - 'cpu_monitor = diagnostic_common_diagnostics.cpu_monitor:main', - 'ntp_monitor = diagnostic_common_diagnostics.ntp_monitor:main', - 'ram_monitor = diagnostic_common_diagnostics.ram_monitor:main', - 'sensors_monitor = diagnostic_common_diagnostics.sensors_monitor:main', - 'hd_monitor = diagnostic_common_diagnostics.hd_monitor:main', + 'cpu_monitor = diagnostic_common_diagnostics.cpu_monitor:main', + 'ntp_monitor = diagnostic_common_diagnostics.ntp_monitor:main', + 'ram_monitor = diagnostic_common_diagnostics.ram_monitor:main', + 'sensors_monitor = diagnostic_common_diagnostics.sensors_monitor:main', + 'hd_monitor = diagnostic_common_diagnostics.hd_monitor:main', ], }, ) From bbb18d08393644dbf7bfd0fff061f3724445acd4 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrynchak Date: Mon, 21 Oct 2024 11:37:20 +0300 Subject: [PATCH 43/43] fix errors with whitespace --- .../diagnostic_common_diagnostics/cpu_monitor.py | 1 - .../diagnostic_common_diagnostics/ram_monitor.py | 1 - 2 files changed, 2 deletions(-) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py index 61675da3e..976a57437 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/cpu_monitor.py @@ -111,7 +111,6 @@ def get_cpu_diagnostics_node() -> Node: def main(args=None): rclpy.init(args=args) - node = get_cpu_diagnostics_node() rclpy.spin(node) diff --git a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py index fe1c2bf26..ea91c3f4f 100755 --- a/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py +++ b/diagnostic_common_diagnostics/diagnostic_common_diagnostics/ram_monitor.py @@ -91,7 +91,6 @@ def get_ram_diagnostics_node() -> Node: def main(): rclpy.init() - node = get_ram_diagnostics_node() rclpy.spin(node)