From eab194265cc83f47630f3182dc368a88f4e6d1b9 Mon Sep 17 00:00:00 2001 From: Eduard Moskalchuk Date: Tue, 21 Jul 2026 12:24:49 +0200 Subject: [PATCH] Redesign Health Monitor C++ API with abstract interfaces Replace concrete class hierarchy with abstract interface design to decouple user code from the Rust FFI implementation: - Extract pure virtual interfaces: HealthMonitor, HealthMonitorBuilder, DeadlineMonitor, Deadline, HeartbeatMonitor, LogicMonitor - Move FFI-backed implementations into details/ namespace (*Impl classes) - Introduce HealthMonitorBuilder::Create() factory method returning std::unique_ptr for polymorphic construction - Add GMock-based mock classes (health_monitor_mocks.h) enabling unit testing without the Rust FFI library - Flatten per-monitor subdirectories (deadline/, heartbeat/, logic/) into top-level headers with details/ impl split - Remove obsolete tag.h, thread.h/cpp in favor of common.h Tag class and ThreadParameters struct in builder header - Update example app and integration tests to new API --- examples/cpp_supervised_app/main.cpp | 51 ++- score/health_monitor/BUILD | 14 +- score/health_monitor/src/cpp/BUILD | 62 +-- score/health_monitor/src/cpp/common.h | 92 ++-- score/health_monitor/src/cpp/deadline/BUILD | 28 -- .../src/cpp/deadline/deadline_monitor.cpp | 149 ------- .../src/cpp/deadline/deadline_monitor.h | 144 ------- .../health_monitor/src/cpp/deadline_monitor.h | 88 ++++ score/health_monitor/src/cpp/details/BUILD | 40 +- .../common_internal.cpp} | 24 +- .../src/cpp/details/common_internal.h | 73 ++++ .../src/cpp/details/deadline_monitor_impl.cpp | 80 ++++ .../src/cpp/details/deadline_monitor_impl.h | 64 +++ .../src/cpp/details/ffi_declarations.h | 116 +++++ .../details/health_monitor_builder_impl.cpp | 396 ++++++++++++++++++ .../cpp/details/health_monitor_builder_impl.h | 75 ++++ .../src/cpp/details/health_monitor_impl.cpp | 83 ++++ .../src/cpp/details/health_monitor_impl.h | 46 ++ .../cpp/details/heartbeat_monitor_impl.cpp | 40 ++ .../src/cpp/details/heartbeat_monitor_impl.h | 45 ++ .../src/cpp/details/logic_monitor_impl.cpp | 61 +++ .../src/cpp/details/logic_monitor_impl.h | 46 ++ .../health_monitor/src/cpp/details/thread.cpp | 123 ------ score/health_monitor/src/cpp/details/thread.h | 91 ---- .../health_monitor/src/cpp/health_monitor.cpp | 254 ----------- score/health_monitor/src/cpp/health_monitor.h | 83 +--- .../src/cpp/health_monitor_builder.h | 150 +++++++ .../src/cpp/health_monitor_loader.h | 41 ++ .../src/cpp/health_monitor_mocks.cpp | 47 +++ .../src/cpp/health_monitor_mocks.h | 109 +++++ score/health_monitor/src/cpp/heartbeat/BUILD | 27 -- .../src/cpp/heartbeat/heartbeat_monitor.cpp | 57 --- .../src/cpp/heartbeat/heartbeat_monitor.h | 84 ---- .../src/cpp/heartbeat_monitor.h | 36 ++ score/health_monitor/src/cpp/logic/BUILD | 29 -- .../src/cpp/logic/logic_monitor.cpp | 94 ----- .../src/cpp/logic/logic_monitor.h | 91 ---- score/health_monitor/src/cpp/logic_monitor.h | 44 ++ score/health_monitor/src/cpp/tag.h | 75 ---- .../src/cpp/tests/deadline_monitor_test.cpp | 113 +++-- .../src/cpp/tests/health_monitor_test.cpp | 205 ++++----- .../src/cpp/tests/heartbeat_monitor_test.cpp | 26 +- .../src/cpp/tests/integrated_test.cpp | 85 ++-- .../src/cpp/tests/logic_monitor_test.cpp | 55 ++- .../src/cpp/tests/mock_test.cpp | 235 +++++++++++ .../src/cpp/tests/time_range_test.cpp | 4 +- .../component_complex_monitoring.cpp | 19 +- 47 files changed, 2255 insertions(+), 1739 deletions(-) delete mode 100644 score/health_monitor/src/cpp/deadline/BUILD delete mode 100644 score/health_monitor/src/cpp/deadline/deadline_monitor.cpp delete mode 100644 score/health_monitor/src/cpp/deadline/deadline_monitor.h create mode 100644 score/health_monitor/src/cpp/deadline_monitor.h rename score/health_monitor/src/cpp/{common.cpp => details/common_internal.cpp} (70%) create mode 100644 score/health_monitor/src/cpp/details/common_internal.h create mode 100644 score/health_monitor/src/cpp/details/deadline_monitor_impl.cpp create mode 100644 score/health_monitor/src/cpp/details/deadline_monitor_impl.h create mode 100644 score/health_monitor/src/cpp/details/ffi_declarations.h create mode 100644 score/health_monitor/src/cpp/details/health_monitor_builder_impl.cpp create mode 100644 score/health_monitor/src/cpp/details/health_monitor_builder_impl.h create mode 100644 score/health_monitor/src/cpp/details/health_monitor_impl.cpp create mode 100644 score/health_monitor/src/cpp/details/health_monitor_impl.h create mode 100644 score/health_monitor/src/cpp/details/heartbeat_monitor_impl.cpp create mode 100644 score/health_monitor/src/cpp/details/heartbeat_monitor_impl.h create mode 100644 score/health_monitor/src/cpp/details/logic_monitor_impl.cpp create mode 100644 score/health_monitor/src/cpp/details/logic_monitor_impl.h delete mode 100644 score/health_monitor/src/cpp/details/thread.cpp delete mode 100644 score/health_monitor/src/cpp/details/thread.h delete mode 100644 score/health_monitor/src/cpp/health_monitor.cpp create mode 100644 score/health_monitor/src/cpp/health_monitor_builder.h create mode 100644 score/health_monitor/src/cpp/health_monitor_loader.h create mode 100644 score/health_monitor/src/cpp/health_monitor_mocks.cpp create mode 100644 score/health_monitor/src/cpp/health_monitor_mocks.h delete mode 100644 score/health_monitor/src/cpp/heartbeat/BUILD delete mode 100644 score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.cpp delete mode 100644 score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.h create mode 100644 score/health_monitor/src/cpp/heartbeat_monitor.h delete mode 100644 score/health_monitor/src/cpp/logic/BUILD delete mode 100644 score/health_monitor/src/cpp/logic/logic_monitor.cpp delete mode 100644 score/health_monitor/src/cpp/logic/logic_monitor.h create mode 100644 score/health_monitor/src/cpp/logic_monitor.h delete mode 100644 score/health_monitor/src/cpp/tag.h create mode 100644 score/health_monitor/src/cpp/tests/mock_test.cpp diff --git a/examples/cpp_supervised_app/main.cpp b/examples/cpp_supervised_app/main.cpp index f6705e65c..e26f9964e 100644 --- a/examples/cpp_supervised_app/main.cpp +++ b/examples/cpp_supervised_app/main.cpp @@ -23,10 +23,10 @@ #include #endif +#include +#include #include #include -#include -#include #include /// @brief CLI configuration options for the demo_application process @@ -114,22 +114,19 @@ int main(int argc, char** argv) using namespace score::mw::health; auto builder_mon = - deadline::DeadlineMonitorBuilder() - .add_deadline(DeadlineTag("deadline_1"), - TimeRange(std::chrono::milliseconds(50), std::chrono::milliseconds(150))) - .add_deadline(DeadlineTag("deadline_2"), - TimeRange(std::chrono::milliseconds(2), - std::chrono::milliseconds(20))); // Not used, only shows - // that multiple deadlines can be added - - MonitorTag ident("monitor"); + DeadlineMonitorConfiguration() + .AddDeadline(Tag("deadline_1"), TimeRange(std::chrono::milliseconds(50), std::chrono::milliseconds(150))) + .AddDeadline(Tag("deadline_2"), + TimeRange(std::chrono::milliseconds(2), + std::chrono::milliseconds(20))); // Not used, only shows + // that multiple deadlines can be added { - auto hm_res = HealthMonitorBuilder() - .add_deadline_monitor(ident, std::move(builder_mon)) - .with_internal_processing_cycle(std::chrono::milliseconds(50)) - .with_supervisor_api_cycle(std::chrono::milliseconds(50)) - .build(); + auto hm_res = HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("monitor"), std::move(builder_mon)) + .WithInternalProcessingCycle(std::chrono::milliseconds(50)) + .WithSupervisorApiCycle(std::chrono::milliseconds(50)) + .Build(); if (!hm_res.has_value()) { std::cerr << "Failed to build health monitor" << std::endl; @@ -137,20 +134,27 @@ int main(int argc, char** argv) } auto hm = std::move(*hm_res); - auto deadline_monitor_res = hm.get_deadline_monitor(ident); + auto deadline_monitor_res = hm->GetDeadlineMonitor(Tag("monitor")); if (!deadline_monitor_res.has_value()) { std::cerr << "Failed to get deadline monitor" << std::endl; return EXIT_FAILURE; } - hm.start(); + hm->Start(); score::mw::lifecycle::report_running(); auto deadline_mon = std::move(*deadline_monitor_res); - auto deadline_res = deadline_mon.get_deadline(DeadlineTag("deadline_1")); + auto deadline_result = deadline_mon->GetDeadline(Tag("deadline_1")); + if (!deadline_result.has_value()) + { + std::cerr << "Failed to get deadline" << std::endl; + return EXIT_FAILURE; + } + auto deadline = std::move(*deadline_result); + while (!exitRequested) { if (stopReportingCheckpoints.load()) @@ -158,12 +162,13 @@ int main(int argc, char** argv) break; } - auto deadline_guard = deadline_res.value().start(); + { + DeadlineGuard deadline_guard(*deadline); - std::this_thread::sleep_for(std::chrono::milliseconds(config->delayInMs)); + std::this_thread::sleep_for(std::chrono::milliseconds(config->delayInMs)); - // deadline_guard.stop(); // Optional, will be stopped automatically when going out of scope - this way we - // dont check Result from start() call + // Deadline is automatically stopped when deadline_guard goes out of scope. + } } } diff --git a/score/health_monitor/BUILD b/score/health_monitor/BUILD index 6d250130a..13f04fcf3 100644 --- a/score/health_monitor/BUILD +++ b/score/health_monitor/BUILD @@ -11,14 +11,24 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") - alias( name = "health_monitoring_cc", actual = "//score/health_monitor/src/cpp:health_monitoring_cc", visibility = ["//visibility:public"], ) +alias( + name = "health_monitoring_cc_interface", + actual = "//score/health_monitor/src/cpp:health_monitoring_cc_interface", + visibility = ["//visibility:public"], +) + +alias( + name = "health_monitoring_cc_mock", + actual = "//score/health_monitor/src/cpp:health_monitoring_cc_mock", + visibility = ["//visibility:public"], +) + alias( name = "health_monitoring_rust", actual = "//score/health_monitor/src/rust:health_monitoring_lib", diff --git a/score/health_monitor/src/cpp/BUILD b/score/health_monitor/src/cpp/BUILD index 8fbace30b..8e0200bc2 100644 --- a/score/health_monitor/src/cpp/BUILD +++ b/score/health_monitor/src/cpp/BUILD @@ -15,37 +15,34 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load("@score_baselibs//:bazel/unit_tests.bzl", "cc_gtest_unit_test") cc_library( - name = "tag", - hdrs = ["tag.h"], - include_prefix = "score/mw/health", - strip_include_prefix = "/score/health_monitor/src/cpp", - visibility = ["//visibility:public"], -) - -cc_library( - name = "common", - srcs = ["common.cpp"], - hdrs = ["common.h"], + name = "health_monitoring_cc_interface", + hdrs = [ + "common.h", + "deadline_monitor.h", + "health_monitor.h", + "health_monitor_builder.h", + "heartbeat_monitor.h", + "logic_monitor.h", + ], include_prefix = "score/mw/health", strip_include_prefix = "/score/health_monitor/src/cpp", - visibility = ["//visibility:public"], - deps = ["@score_baselibs//score/language/futurecpp"], + visibility = ["//score/health_monitor:__subpackages__"], + deps = [ + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], ) cc_library( - name = "health_monitor", - srcs = ["health_monitor.cpp"], - hdrs = ["health_monitor.h"], + name = "health_monitoring_cc_mock", + srcs = ["health_monitor_mocks.cpp"], + hdrs = ["health_monitor_mocks.h"], include_prefix = "score/mw/health", strip_include_prefix = "/score/health_monitor/src/cpp", - visibility = ["//score:__subpackages__"], + visibility = ["//score/health_monitor:__pkg__"], deps = [ - ":common", - ":tag", - "//score/health_monitor/src/cpp/deadline:deadline_monitor", - "//score/health_monitor/src/cpp/details:thread", - "//score/health_monitor/src/cpp/heartbeat:heartbeat_monitor", - "//score/health_monitor/src/cpp/logic:logic_monitor", + ":health_monitoring_cc_interface", + "@googletest//:gtest", ], ) @@ -53,7 +50,7 @@ cc_library( name = "health_monitoring_cc", visibility = ["//score/health_monitor:__pkg__"], deps = [ - "//score/health_monitor/src/cpp:health_monitor", + "//score/health_monitor/src/cpp/details:health_monitor_impl", "//score/health_monitor/src/rust:health_monitoring_lib_ffi", ], ) @@ -62,7 +59,7 @@ cc_library( name = "health_monitoring_cc_stub_supervisor", visibility = ["//score/health_monitor/src/cpp:__pkg__"], deps = [ - "//score/health_monitor/src/cpp:health_monitor", + "//score/health_monitor/src/cpp/details:health_monitor_impl", "//score/health_monitor/src/rust:health_monitoring_lib_stub_supervisor", ], ) @@ -81,7 +78,7 @@ cc_gtest_unit_test( "@platforms//os:linux": [], }), deps = [ - "//score/health_monitor/src/cpp:health_monitoring_cc_stub_supervisor", + ":health_monitoring_cc_stub_supervisor", "//score/health_monitor/src/cpp/details:log_init", ], ) @@ -96,7 +93,18 @@ cc_gtest_unit_test( "@platforms//os:linux": [], }), deps = [ - "//score/health_monitor/src/cpp:health_monitoring_cc_stub_supervisor", + ":health_monitoring_cc_stub_supervisor", "//score/health_monitor/src/cpp/details:log_init", ], ) + +cc_gtest_unit_test( + name = "cpp_mock_tests", + srcs = [ + "tests/mock_test.cpp", + ], + deps = [ + ":health_monitoring_cc_interface", + ":health_monitoring_cc_mock", + ], +) diff --git a/score/health_monitor/src/cpp/common.h b/score/health_monitor/src/cpp/common.h index b6e349951..428c87d07 100644 --- a/score/health_monitor/src/cpp/common.h +++ b/score/health_monitor/src/cpp/common.h @@ -15,76 +15,48 @@ #include #include -#include +#include +#include +#include +#include namespace score::mw::health { -/// FFI internal helpers -namespace internal -{ - -/// Internal success representation. -constexpr int kSuccess = 0; - -/// Internal return code. -using FFICode = uint8_t; - -/// Opaque handle type for Rust managed object -using FFIHandle = void*; - -/// Droppable wrapper that denotes that the object can be dropped by Rust side -template -class RustDroppable +/// Compile-time string tag. Only constructible from string literals (static storage duration). +/// Pointers are always valid — no lifetime management needed. +class Tag { public: - virtual ~RustDroppable() = default; - - protected: - /// Marks object as no longer managed by C++ side, releasing handle to be passed to Rust side for dropping - std::optional drop_by_rust() + template + constexpr explicit Tag(const char (&literal)[N]) : data_(literal), length_(N - 1) { - return static_cast(this)->_drop_by_rust_impl(); } -}; - -/// Wrapper for FFIHandle that ensures proper dropping via provided drop function -class DroppableFFIHandle -{ - public: - using DropFn = internal::FFICode (*)(FFIHandle); - - DroppableFFIHandle(FFIHandle handle, DropFn drop_fn); - - DroppableFFIHandle(const DroppableFFIHandle&) = delete; - DroppableFFIHandle& operator=(const DroppableFFIHandle&) = delete; - - DroppableFFIHandle(DroppableFFIHandle&& other) noexcept; - DroppableFFIHandle& operator=(DroppableFFIHandle&& other) noexcept; - - /// Get the underlying FFI handle if it was not dropped before - std::optional as_rust_handle() const; - /// Marks object as no longer managed by C++ side, releasing handle to be passed to Rust side for dropping - std::optional drop_by_rust(); + constexpr bool operator==(const Tag& other) const noexcept + { + return std::string_view{data_, length_} == std::string_view{other.data_, other.length_}; + } - virtual ~DroppableFFIHandle(); + constexpr bool operator!=(const Tag& other) const noexcept + { + return !(*this == other); + } private: - FFIHandle handle_; - DropFn drop_fn_; + const char* data_; + size_t length_; }; -} // namespace internal +static_assert(std::is_standard_layout_v, "Tag must be standard-layout for FFI compatibility"); -enum class Error : internal::FFICode +enum class Error : uint8_t { - NullParameter = internal::kSuccess + 1, - NotFound, - AlreadyExists, - InvalidArgument, - WrongState, - Failed + kNotFound = 2, + kAlreadyExists, + kInvalidArgument, + kWrongState, + kFailed }; /// @@ -98,19 +70,19 @@ class TimeRange SCORE_LANGUAGE_FUTURECPP_PRECONDITION(min_ms_ <= max_ms_); } - uint32_t min_ms() const + std::chrono::milliseconds Min() const { - return min_ms_.count(); + return min_ms_; } - uint32_t max_ms() const + std::chrono::milliseconds Max() const { - return max_ms_.count(); + return max_ms_; } private: - const std::chrono::milliseconds min_ms_; - const std::chrono::milliseconds max_ms_; + std::chrono::milliseconds min_ms_; + std::chrono::milliseconds max_ms_; }; } // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/deadline/BUILD b/score/health_monitor/src/cpp/deadline/BUILD deleted file mode 100644 index 98cf96049..000000000 --- a/score/health_monitor/src/cpp/deadline/BUILD +++ /dev/null @@ -1,28 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "deadline_monitor", - srcs = ["deadline_monitor.cpp"], - hdrs = ["deadline_monitor.h"], - include_prefix = "score/mw/health", - strip_include_prefix = "/score/health_monitor/src/cpp/deadline", - visibility = ["//score:__subpackages__"], - deps = [ - "//score/health_monitor/src/cpp:common", - "//score/health_monitor/src/cpp:tag", - "@score_baselibs//score/result", - ], -) diff --git a/score/health_monitor/src/cpp/deadline/deadline_monitor.cpp b/score/health_monitor/src/cpp/deadline/deadline_monitor.cpp deleted file mode 100644 index 4ee9f6273..000000000 --- a/score/health_monitor/src/cpp/deadline/deadline_monitor.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#include "score/mw/health/deadline_monitor.h" - -namespace -{ -extern "C" { -using namespace score::mw::health; -using namespace score::mw::health::internal; -using namespace score::mw::health::deadline; - -// Functions below must match functions defined in `crate::deadline::ffi`. - -FFICode deadline_monitor_builder_create(FFIHandle* deadline_monitor_builder_handle_out); -FFICode deadline_monitor_builder_destroy(FFIHandle deadline_monitor_builder_handle); -FFICode deadline_monitor_builder_add_deadline(FFIHandle deadline_monitor_builder_handle, - const DeadlineTag* deadline_tag, - uint32_t min_ms, - uint32_t max_ms); -FFICode deadline_monitor_get_deadline(FFIHandle deadline_monitor_handle, - const DeadlineTag* deadline_tag, - FFIHandle* deadline_handle_out); -FFICode deadline_monitor_destroy(FFIHandle deadline_monitor_handle); -FFICode deadline_destroy(FFIHandle deadline_handle); -FFICode deadline_start(FFIHandle deadline_handle); -FFICode deadline_stop(FFIHandle deadline_handle); -} - -FFIHandle deadline_monitor_builder_create_wrapper() -{ - FFIHandle handle{nullptr}; - auto result{deadline_monitor_builder_create(&handle)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return handle; -} - -} // namespace - -// C++ wrapper for Rust library - the API implementation obeys the Rust API semantics and it's invariants - -namespace score::mw::health::deadline -{ -DeadlineMonitorBuilder::DeadlineMonitorBuilder() - : monitor_builder_handler_{deadline_monitor_builder_create_wrapper(), &deadline_monitor_builder_destroy} -{ -} - -DeadlineMonitorBuilder DeadlineMonitorBuilder::add_deadline(const DeadlineTag& deadline_tag, const TimeRange& range) && -{ - auto handle = monitor_builder_handler_.as_rust_handle(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - - auto result{deadline_monitor_builder_add_deadline(handle.value(), &deadline_tag, range.min_ms(), range.max_ms())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -DeadlineMonitor::DeadlineMonitor(FFIHandle handle) : monitor_handle_(handle, &deadline_monitor_destroy) {} - -score::cpp::expected DeadlineMonitor::get_deadline(const DeadlineTag& deadline_tag) -{ - auto handle = monitor_handle_.as_rust_handle(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - - FFIHandle ret = nullptr; - auto result = deadline_monitor_get_deadline(handle.value(), &deadline_tag, &ret); - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(Deadline{ret}); -} - -Deadline::Deadline(FFIHandle handle) : deadline_handle_(handle, &deadline_destroy), has_handle_(false) {} - -Deadline::~Deadline() -{ - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(!has_handle_); -} - -score::cpp::expected Deadline::start() -{ - // Cannot start a deadline that is already started - if (has_handle_) - { - return score::cpp::unexpected(::score::mw::health::Error::WrongState); - } - - auto handle = deadline_handle_.as_rust_handle(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - - auto result = deadline_start(handle.value()); - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - has_handle_ = true; - return score::cpp::expected(DeadlineHandle{*this}); -} - -DeadlineHandle::DeadlineHandle(Deadline& deadline) : was_stopped_(false), deadline_(deadline) {} - -void DeadlineHandle::stop() -{ - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(deadline_.has_value()); - - if (!was_stopped_) - { - was_stopped_ = true; - auto handle = deadline_.value().get().deadline_handle_.as_rust_handle(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); - - auto result{deadline_stop(handle.value())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - } -} - -DeadlineHandle::DeadlineHandle(DeadlineHandle&& other) - : was_stopped_(other.was_stopped_), deadline_(std::move(other.deadline_)) -{ - other.was_stopped_ = true; - other.deadline_ = std::optional>{}; // None -} - -DeadlineHandle::~DeadlineHandle() -{ - if (!deadline_.has_value()) - { - return; - } - - stop(); - deadline_.value().get().has_handle_ = false; -} - -} // namespace score::mw::health::deadline diff --git a/score/health_monitor/src/cpp/deadline/deadline_monitor.h b/score/health_monitor/src/cpp/deadline/deadline_monitor.h deleted file mode 100644 index 9ea0cd7ac..000000000 --- a/score/health_monitor/src/cpp/deadline/deadline_monitor.h +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#ifndef SCORE_HM_DEADLINE_DEADLINE_MONITOR_H -#define SCORE_HM_DEADLINE_DEADLINE_MONITOR_H - -#include -#include "score/mw/health/common.h" -#include "score/mw/health/tag.h" -#include -#include - -namespace score::mw::health -{ -// Forward declaration -class HealthMonitor; -class HealthMonitorBuilder; -} // namespace score::mw::health - -namespace score::mw::health::deadline -{ - -// Forward declaration -class DeadlineMonitor; -class DeadlineHandle; -class Deadline; - -/// DeadlineMonitorBuilder for constructing DeadlineMonitor instance -class DeadlineMonitorBuilder final : public internal::RustDroppable -{ - public: - /// Create a new `DeadlineMonitorBuilder`. - DeadlineMonitorBuilder(); - - DeadlineMonitorBuilder(const DeadlineMonitorBuilder&) = delete; - DeadlineMonitorBuilder& operator=(const DeadlineMonitorBuilder&) = delete; - - DeadlineMonitorBuilder(DeadlineMonitorBuilder&&) = default; - DeadlineMonitorBuilder& operator=(DeadlineMonitorBuilder&&) = delete; - - /// Adds a deadline with the given tag and duration range to the monitor. - DeadlineMonitorBuilder add_deadline(const DeadlineTag& deadline_tag, const TimeRange& range) &&; - - protected: - std::optional _drop_by_rust_impl() - { - return monitor_builder_handler_.drop_by_rust(); - } - - private: - internal::DroppableFFIHandle monitor_builder_handler_; - - // Allow to hide drop_by_rust implementation - friend class internal::RustDroppable; - - // Allow HealthMonitorBuilder to access drop_by_rust implementation - friend class ::score::mw::health::HealthMonitorBuilder; -}; - -class DeadlineMonitor final -{ - public: - // Delete copy, allow move - DeadlineMonitor(const DeadlineMonitor&) = delete; - DeadlineMonitor& operator=(const DeadlineMonitor&) = delete; - - DeadlineMonitor(DeadlineMonitor&& other) noexcept = default; - DeadlineMonitor& operator=(DeadlineMonitor&& other) noexcept = default; - - ::score::cpp::expected get_deadline(const DeadlineTag& deadline_tag); - - private: - explicit DeadlineMonitor(internal::FFIHandle handle); - - // Allow only HealthMonitor to create DeadlineMonitor instances. - friend class score::mw::health::HealthMonitor; - internal::DroppableFFIHandle monitor_handle_; -}; - -/// Deadline instance representing a specific deadline to be monitored. -class Deadline final -{ - public: - ~Deadline(); - - Deadline(const Deadline&) = delete; - Deadline& operator=(const Deadline&) = delete; - - Deadline(Deadline&& other) noexcept = default; - Deadline& operator=(Deadline&& other) noexcept = delete; - - /// Starts the deadline monitoring. Returns a DeadlineHandle to manage the deadline. - // After this call the Deadline instance cannot be used until connected DeadlineHandle is destroyed - ::score::cpp::expected start(); - - private: - explicit Deadline(internal::FFIHandle handle); - - // Allow only DeadlineMonitor to create Deadline instances. - friend class DeadlineMonitor; - - // Allow DeadlineHandle to access internal members as its wrapper type only - friend class DeadlineHandle; - internal::DroppableFFIHandle deadline_handle_; - bool has_handle_; -}; - -/// Deadline guard to manage the lifetime of a started deadline. -class DeadlineHandle final -{ - public: - /// Stops the deadline monitoring. - void stop(); - - /// Destructor that ensures the deadline is stopped if not already done. - ~DeadlineHandle(); - - DeadlineHandle(const DeadlineHandle&) = delete; - DeadlineHandle& operator=(const DeadlineHandle&) = delete; - - DeadlineHandle(DeadlineHandle&& other); - DeadlineHandle& operator=(DeadlineHandle&& other) = delete; - - private: - DeadlineHandle(Deadline& deadline); - - // Allow only Deadline to create DeadlineHandle instances. - friend class Deadline; - bool was_stopped_; - std::optional> deadline_; -}; - -} // namespace score::mw::health::deadline - -#endif // SCORE_HM_DEADLINE_DEADLINE_MONITOR_H diff --git a/score/health_monitor/src/cpp/deadline_monitor.h b/score/health_monitor/src/cpp/deadline_monitor.h new file mode 100644 index 000000000..f888d26ca --- /dev/null +++ b/score/health_monitor/src/cpp/deadline_monitor.h @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DEADLINE_MONITOR_H +#define SCORE_HM_DEADLINE_MONITOR_H + +#include "score/mw/health/common.h" + +#include +#include +#include + +namespace score::mw::health +{ + +/// A pre-resolved deadline handle. Obtained once via DeadlineMonitor::GetDeadline(), +/// then used repeatedly to start/stop measurements without per-call tag lookup. +class Deadline +{ + public: + Deadline() = default; + virtual ~Deadline() = default; + + Deadline(const Deadline&) = delete; + Deadline& operator=(const Deadline&) = delete; + Deadline(Deadline&&) = delete; + Deadline& operator=(Deadline&&) = delete; + + /// Starts a deadline measurement. + virtual ::score::cpp::expected_blank Start() = 0; + + /// Stops a deadline measurement. + virtual ::score::cpp::expected_blank Stop() = 0; +}; + +/// Provides access to configured deadlines by tag. +class DeadlineMonitor +{ + public: + DeadlineMonitor() = default; + virtual ~DeadlineMonitor() = default; + + DeadlineMonitor(const DeadlineMonitor&) = delete; + DeadlineMonitor& operator=(const DeadlineMonitor&) = delete; + DeadlineMonitor(DeadlineMonitor&&) = delete; + DeadlineMonitor& operator=(DeadlineMonitor&&) = delete; + + /// Resolves a deadline by tag. The returned Deadline can be used to start measurements repeatedly. + virtual ::score::cpp::expected, score::mw::health::Error> GetDeadline(Tag tag) = 0; +}; + +/// RAII guard for a deadline measurement. Starts the deadline on construction and stops it on destruction. +class DeadlineGuard final +{ + public: + explicit DeadlineGuard(Deadline& deadline) : deadline_(deadline) + { + const auto result = deadline_.Start(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(result.has_value()); + } + + ~DeadlineGuard() + { + const auto result = deadline_.Stop(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(result.has_value()); + } + + DeadlineGuard(const DeadlineGuard&) = delete; + DeadlineGuard& operator=(const DeadlineGuard&) = delete; + DeadlineGuard(DeadlineGuard&&) = delete; + DeadlineGuard& operator=(DeadlineGuard&&) = delete; + + private: + Deadline& deadline_; +}; + +} // namespace score::mw::health + +#endif // SCORE_HM_DEADLINE_MONITOR_H diff --git a/score/health_monitor/src/cpp/details/BUILD b/score/health_monitor/src/cpp/details/BUILD index 6dbd9e45f..1a9b1db3a 100644 --- a/score/health_monitor/src/cpp/details/BUILD +++ b/score/health_monitor/src/cpp/details/BUILD @@ -14,24 +14,40 @@ load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( - name = "log_init", - srcs = ["log_init.cpp"], - visibility = ["//score/health_monitor/src/cpp:__pkg__"], + name = "health_monitor_impl", + srcs = [ + "common_internal.cpp", + "deadline_monitor_impl.cpp", + "health_monitor_builder_impl.cpp", + "health_monitor_impl.cpp", + "heartbeat_monitor_impl.cpp", + "logic_monitor_impl.cpp", + ], + hdrs = [ + "common_internal.h", + "deadline_monitor_impl.h", + "ffi_declarations.h", + "health_monitor_builder_impl.h", + "health_monitor_impl.h", + "heartbeat_monitor_impl.h", + "logic_monitor_impl.h", + ], + include_prefix = "score/mw/health", + strip_include_prefix = "/score/health_monitor/src/cpp", + visibility = ["//score:__subpackages__"], deps = [ - "@googletest//:gtest", - "@score_baselibs//src/log/stdout_logger_cpp_init", + "//score/health_monitor/src/cpp:health_monitoring_cc_interface", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", ], ) cc_library( - name = "thread", - srcs = ["thread.cpp"], - hdrs = ["thread.h"], - include_prefix = "score/mw/health", - strip_include_prefix = "/score/health_monitor/src/cpp/details", + name = "log_init", + srcs = ["log_init.cpp"], visibility = ["//score/health_monitor/src/cpp:__pkg__"], deps = [ - "//score/health_monitor/src/cpp:common", - "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest", + "@score_baselibs//src/log/stdout_logger_cpp_init", ], ) diff --git a/score/health_monitor/src/cpp/common.cpp b/score/health_monitor/src/cpp/details/common_internal.cpp similarity index 70% rename from score/health_monitor/src/cpp/common.cpp rename to score/health_monitor/src/cpp/details/common_internal.cpp index d53d81c24..1010d409d 100644 --- a/score/health_monitor/src/cpp/common.cpp +++ b/score/health_monitor/src/cpp/details/common_internal.cpp @@ -10,43 +10,42 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +#include "score/mw/health/details/common_internal.h" + #include -#include "score/mw/health/common.h" namespace score::mw::health::internal { -DroppableFFIHandle::DroppableFFIHandle(FFIHandle handle, DropFn drop_fn) : handle_(handle), drop_fn_(drop_fn) {} +DroppableFfiHandle::DroppableFfiHandle(FfiHandle handle, DropFn drop_fn) : handle_(handle), drop_fn_(drop_fn) {} -DroppableFFIHandle::DroppableFFIHandle(DroppableFFIHandle&& other) noexcept +DroppableFfiHandle::DroppableFfiHandle(DroppableFfiHandle&& other) noexcept : handle_(other.handle_), drop_fn_(other.drop_fn_) { other.handle_ = nullptr; other.drop_fn_ = nullptr; } -DroppableFFIHandle& DroppableFFIHandle::operator=(DroppableFFIHandle&& other) noexcept +DroppableFfiHandle& DroppableFfiHandle::operator=(DroppableFfiHandle&& other) noexcept { if (this != &other) { - // Clean up existing resources if (drop_fn_) { - drop_fn_(handle_); + auto result{drop_fn_(handle_)}; + SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); } - // Move resources from other handle_ = other.handle_; drop_fn_ = other.drop_fn_; - // Nullify other's resources other.handle_ = nullptr; other.drop_fn_ = nullptr; } return *this; } -std::optional DroppableFFIHandle::as_rust_handle() const +std::optional DroppableFfiHandle::AsRustHandle() const { if (handle_ == nullptr) { @@ -56,23 +55,22 @@ std::optional DroppableFFIHandle::as_rust_handle() const return handle_; } -std::optional DroppableFFIHandle::drop_by_rust() +std::optional DroppableFfiHandle::DropByRust() { if (handle_ == nullptr) { return std::nullopt; } - FFIHandle temp = handle_; + FfiHandle temp = handle_; handle_ = nullptr; drop_fn_ = nullptr; return temp; } -DroppableFFIHandle::~DroppableFFIHandle() +DroppableFfiHandle::~DroppableFfiHandle() { - // Clean up resources associated with the FFI handle if (drop_fn_) { auto result{drop_fn_(handle_)}; diff --git a/score/health_monitor/src/cpp/details/common_internal.h b/score/health_monitor/src/cpp/details/common_internal.h new file mode 100644 index 000000000..76d296a4b --- /dev/null +++ b/score/health_monitor/src/cpp/details/common_internal.h @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_COMMON_INTERNAL_H +#define SCORE_HM_DETAILS_COMMON_INTERNAL_H + +#include "score/mw/health/common.h" + +#include +#include + +namespace score::mw::health::internal +{ + +/// Internal success representation. +constexpr int kSuccess = 0; + +/// Internal return code. +using FfiCode = uint8_t; + +/// Opaque handle type for Rust managed object. +using FfiHandle = void*; + +/// Maps an FFI return code to the public Error enum. +/// FFI code 1 (null parameter) is an internal concern and maps to Error::kFailed. +inline Error MapFfiError(FfiCode code) +{ + if (code >= static_cast(Error::kNotFound) && code <= static_cast(Error::kFailed)) + { + return static_cast(code); + } + return Error::kFailed; +} + +/// Wrapper for FFIHandle that ensures proper dropping via provided drop function. +class DroppableFfiHandle +{ + public: + using DropFn = internal::FfiCode (*)(FfiHandle); + + DroppableFfiHandle(FfiHandle handle, DropFn drop_fn); + + DroppableFfiHandle(const DroppableFfiHandle&) = delete; + DroppableFfiHandle& operator=(const DroppableFfiHandle&) = delete; + + DroppableFfiHandle(DroppableFfiHandle&& other) noexcept; + DroppableFfiHandle& operator=(DroppableFfiHandle&& other) noexcept; + + /// Get the underlying FFI handle if it was not dropped before. + std::optional AsRustHandle() const; + + /// Marks object as no longer managed by C++ side, releasing handle to be passed to Rust side for dropping. + std::optional DropByRust(); + + ~DroppableFfiHandle(); + + private: + FfiHandle handle_; + DropFn drop_fn_; +}; + +} // namespace score::mw::health::internal + +#endif // SCORE_HM_DETAILS_COMMON_INTERNAL_H diff --git a/score/health_monitor/src/cpp/details/deadline_monitor_impl.cpp b/score/health_monitor/src/cpp/details/deadline_monitor_impl.cpp new file mode 100644 index 000000000..bb6c662f0 --- /dev/null +++ b/score/health_monitor/src/cpp/details/deadline_monitor_impl.cpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/details/deadline_monitor_impl.h" + +#include "score/mw/health/details/ffi_declarations.h" + +#include + +namespace score::mw::health::details +{ + +DeadlineImpl::DeadlineImpl(score::mw::health::internal::FfiHandle handle) + : deadline_handle_(handle, &internal::deadline_destroy) +{ +} + +::score::cpp::expected_blank DeadlineImpl::Start() +{ + auto handle = deadline_handle_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + auto result = internal::deadline_start(handle.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return {}; +} + +::score::cpp::expected_blank DeadlineImpl::Stop() +{ + auto handle = deadline_handle_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + auto result = internal::deadline_stop(handle.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return {}; +} + +DeadlineMonitorImpl::DeadlineMonitorImpl(score::mw::health::internal::FfiHandle handle) + : monitor_handle_(handle, &internal::deadline_monitor_destroy) +{ +} + +::score::cpp::expected, score::mw::health::Error> DeadlineMonitorImpl::GetDeadline(Tag tag) +{ + auto monitor_handle = monitor_handle_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + + internal::FfiHandle deadline_ffi_handle = nullptr; + auto result = internal::deadline_monitor_get_deadline(monitor_handle.value(), &tag, &deadline_ffi_handle); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return std::make_unique(deadline_ffi_handle); +} + +std::unique_ptr MakeDeadlineMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle) +{ + return std::make_unique(monitor_handle); +} + +} // namespace score::mw::health::details diff --git a/score/health_monitor/src/cpp/details/deadline_monitor_impl.h b/score/health_monitor/src/cpp/details/deadline_monitor_impl.h new file mode 100644 index 000000000..572707b85 --- /dev/null +++ b/score/health_monitor/src/cpp/details/deadline_monitor_impl.h @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_DEADLINE_MONITOR_IMPL_H +#define SCORE_HM_DETAILS_DEADLINE_MONITOR_IMPL_H + +#include "score/mw/health/deadline_monitor.h" + +#include "score/mw/health/details/common_internal.h" + +#include + +namespace score::mw::health::details +{ + +class DeadlineImpl final : public Deadline +{ + public: + explicit DeadlineImpl(score::mw::health::internal::FfiHandle handle); + ~DeadlineImpl() override = default; + + DeadlineImpl(const DeadlineImpl&) = delete; + DeadlineImpl& operator=(const DeadlineImpl&) = delete; + DeadlineImpl(DeadlineImpl&&) = delete; + DeadlineImpl& operator=(DeadlineImpl&&) = delete; + + ::score::cpp::expected_blank Start() override; + ::score::cpp::expected_blank Stop() override; + + private: + score::mw::health::internal::DroppableFfiHandle deadline_handle_; +}; + +class DeadlineMonitorImpl final : public DeadlineMonitor +{ + public: + explicit DeadlineMonitorImpl(score::mw::health::internal::FfiHandle handle); + ~DeadlineMonitorImpl() override = default; + + DeadlineMonitorImpl(const DeadlineMonitorImpl&) = delete; + DeadlineMonitorImpl& operator=(const DeadlineMonitorImpl&) = delete; + DeadlineMonitorImpl(DeadlineMonitorImpl&&) = delete; + DeadlineMonitorImpl& operator=(DeadlineMonitorImpl&&) = delete; + + ::score::cpp::expected, score::mw::health::Error> GetDeadline(Tag tag) override; + + private: + score::mw::health::internal::DroppableFfiHandle monitor_handle_; +}; + +std::unique_ptr MakeDeadlineMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle); + +} // namespace score::mw::health::details + +#endif // SCORE_HM_DETAILS_DEADLINE_MONITOR_IMPL_H diff --git a/score/health_monitor/src/cpp/details/ffi_declarations.h b/score/health_monitor/src/cpp/details/ffi_declarations.h new file mode 100644 index 000000000..9bd05fd61 --- /dev/null +++ b/score/health_monitor/src/cpp/details/ffi_declarations.h @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_FFI_DECLARATIONS_H +#define SCORE_HM_DETAILS_FFI_DECLARATIONS_H + +#include "score/mw/health/common.h" +#include "score/mw/health/details/common_internal.h" +#include "score/mw/health/health_monitor_builder.h" + +#include +#include + +namespace score::mw::health::internal +{ + +extern "C" { + +// --- Deadline --- +FfiCode deadline_destroy(FfiHandle deadline_handle); +FfiCode deadline_start(FfiHandle deadline_handle); +FfiCode deadline_stop(FfiHandle deadline_handle); + +// --- Deadline Monitor --- +FfiCode deadline_monitor_destroy(FfiHandle deadline_monitor_handle); +FfiCode deadline_monitor_get_deadline(FfiHandle deadline_monitor_handle, + const Tag* deadline_tag, + FfiHandle* deadline_handle_out); + +// --- Deadline Monitor Builder --- +FfiCode deadline_monitor_builder_create(FfiHandle* deadline_monitor_builder_handle_out); +FfiCode deadline_monitor_builder_destroy(FfiHandle deadline_monitor_builder_handle); +FfiCode deadline_monitor_builder_add_deadline(FfiHandle deadline_monitor_builder_handle, + const Tag* deadline_tag, + uint32_t min_ms, + uint32_t max_ms); + +// --- Heartbeat Monitor --- +FfiCode heartbeat_monitor_destroy(FfiHandle heartbeat_monitor_handle); +FfiCode heartbeat_monitor_heartbeat(FfiHandle heartbeat_monitor_handle); + +// --- Heartbeat Monitor Builder --- +FfiCode heartbeat_monitor_builder_create(uint32_t range_min_ms, + uint32_t range_max_ms, + FfiHandle* heartbeat_monitor_builder_handle_out); +FfiCode heartbeat_monitor_builder_destroy(FfiHandle heartbeat_monitor_builder_handle); + +// --- Logic Monitor --- +FfiCode logic_monitor_destroy(FfiHandle logic_monitor_handle); +FfiCode logic_monitor_transition(FfiHandle logic_monitor_handle, const Tag* target_state); +FfiCode logic_monitor_state(FfiHandle logic_monitor_handle, Tag* state_out); + +// --- Logic Monitor Builder --- +FfiCode logic_monitor_builder_create(const Tag* initial_state, FfiHandle* logic_monitor_builder_handle_out); +FfiCode logic_monitor_builder_destroy(FfiHandle logic_monitor_builder_handle); +FfiCode logic_monitor_builder_add_state(FfiHandle logic_monitor_builder_handle, + const Tag* state, + const Tag* allowed_states, + size_t num_allowed_states); + +// --- Health Monitor --- +FfiCode health_monitor_destroy(FfiHandle health_monitor_handle); +FfiCode health_monitor_start(FfiHandle health_monitor_handle); +FfiCode health_monitor_get_deadline_monitor(FfiHandle health_monitor_handle, + const Tag* monitor_tag, + FfiHandle* deadline_monitor_handle_out); +FfiCode health_monitor_get_heartbeat_monitor(FfiHandle health_monitor_handle, + const Tag* monitor_tag, + FfiHandle* heartbeat_monitor_handle_out); +FfiCode health_monitor_get_logic_monitor(FfiHandle health_monitor_handle, + const Tag* monitor_tag, + FfiHandle* logic_monitor_handle_out); + +// --- Health Monitor Builder --- +FfiCode health_monitor_builder_create(FfiHandle* health_monitor_builder_handle_out); +FfiCode health_monitor_builder_destroy(FfiHandle health_monitor_builder_handle); +FfiCode health_monitor_builder_build(FfiHandle health_monitor_builder_handle, + const uint64_t* supervisor_cycle_ms, + const uint64_t* internal_cycle_ms, + FfiHandle thread_parameters_handle, + FfiHandle* health_monitor_handle_out); +FfiCode health_monitor_builder_add_deadline_monitor(FfiHandle health_monitor_builder_handle, + const Tag* monitor_tag, + FfiHandle deadline_monitor_builder_handle); +FfiCode health_monitor_builder_add_heartbeat_monitor(FfiHandle health_monitor_builder_handle, + const Tag* monitor_tag, + FfiHandle heartbeat_monitor_builder_handle); +FfiCode health_monitor_builder_add_logic_monitor(FfiHandle health_monitor_builder_handle, + const Tag* monitor_tag, + FfiHandle logic_monitor_builder_handle); + +// --- Thread Parameters --- +FfiCode thread_parameters_create(FfiHandle* thread_parameters_handle_out); +FfiCode thread_parameters_destroy(FfiHandle thread_parameters_handle); +FfiCode thread_parameters_scheduler_parameters(FfiHandle thread_parameters_handle, + SchedulerPolicy policy, + int32_t priority); +FfiCode thread_parameters_affinity(FfiHandle thread_parameters_handle, const size_t* affinity, size_t num_affinity); +FfiCode thread_parameters_stack_size(FfiHandle thread_parameters_handle, size_t stack_size); +FfiCode scheduler_policy_priority_min(SchedulerPolicy scheduler_policy, int32_t* priority_out); +FfiCode scheduler_policy_priority_max(SchedulerPolicy scheduler_policy, int32_t* priority_out); + +} // extern "C" + +} // namespace score::mw::health::internal + +#endif // SCORE_HM_DETAILS_FFI_DECLARATIONS_H diff --git a/score/health_monitor/src/cpp/details/health_monitor_builder_impl.cpp b/score/health_monitor/src/cpp/details/health_monitor_builder_impl.cpp new file mode 100644 index 000000000..89d3ada99 --- /dev/null +++ b/score/health_monitor/src/cpp/details/health_monitor_builder_impl.cpp @@ -0,0 +1,396 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/details/health_monitor_builder_impl.h" + +#include "score/mw/health/details/common_internal.h" +#include "score/mw/health/details/ffi_declarations.h" +#include "score/mw/health/details/health_monitor_impl.h" + +#include +#include + +namespace score::mw::health::details +{ +namespace +{ + +internal::FfiHandle HealthMonitorBuilderCreateWrapper() +{ + internal::FfiHandle handle{nullptr}; + auto result{internal::health_monitor_builder_create(&handle)}; + SCORE_LANGUAGE_FUTURECPP_ASSERT(result == internal::kSuccess); + return handle; +} + +score::cpp::expected BuildThreadParametersHandle(const ThreadParameters& thread_parameters) +{ + internal::FfiHandle thread_params_handle{nullptr}; + auto result{internal::thread_parameters_create(&thread_params_handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + internal::DroppableFfiHandle handle{thread_params_handle, &internal::thread_parameters_destroy}; + + if (thread_parameters.scheduler_parameters.has_value()) + { + const auto scheduler = thread_parameters.scheduler_parameters.value(); + int32_t min_priority{0}; + int32_t max_priority{0}; + result = internal::scheduler_policy_priority_min(scheduler.policy, &min_priority); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + result = internal::scheduler_policy_priority_max(scheduler.policy, &max_priority); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + if (scheduler.priority < min_priority || scheduler.priority > max_priority) + { + return score::cpp::unexpected(Error::kInvalidArgument); + } + + result = internal::thread_parameters_scheduler_parameters( + handle.AsRustHandle().value(), scheduler.policy, scheduler.priority); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + } + + if (!thread_parameters.affinity.empty()) + { + result = internal::thread_parameters_affinity( + handle.AsRustHandle().value(), thread_parameters.affinity.data(), thread_parameters.affinity.size()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + } + + if (thread_parameters.stack_size.has_value()) + { + result = + internal::thread_parameters_stack_size(handle.AsRustHandle().value(), thread_parameters.stack_size.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + } + + auto rust_handle = handle.DropByRust(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(rust_handle.has_value()); + return rust_handle.value(); +} + +score::cpp::expected BuildDeadlineMonitorBuilderHandle( + const DeadlineMonitorConfiguration& config) +{ + internal::FfiHandle handle{nullptr}; + auto result{internal::deadline_monitor_builder_create(&handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + internal::DroppableFfiHandle builder_handle{handle, &internal::deadline_monitor_builder_destroy}; + for (const auto& deadline : config.Deadlines()) + { + result = internal::deadline_monitor_builder_add_deadline(builder_handle.AsRustHandle().value(), + &deadline.deadline_tag, + static_cast(deadline.range.Min().count()), + static_cast(deadline.range.Max().count())); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + } + + auto rust_handle = builder_handle.DropByRust(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(rust_handle.has_value()); + return rust_handle.value(); +} + +score::cpp::expected BuildHeartbeatMonitorBuilderHandle(const TimeRange& range) +{ + internal::FfiHandle handle{nullptr}; + auto result{internal::heartbeat_monitor_builder_create( + static_cast(range.Min().count()), static_cast(range.Max().count()), &handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + internal::DroppableFfiHandle builder_handle{handle, &internal::heartbeat_monitor_builder_destroy}; + auto rust_handle = builder_handle.DropByRust(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(rust_handle.has_value()); + return rust_handle.value(); +} + +score::cpp::expected BuildLogicMonitorBuilderHandle(const LogicMonitorConfiguration& config) +{ + internal::FfiHandle handle{nullptr}; + auto initial_state = config.InitialState(); + auto result{internal::logic_monitor_builder_create(&initial_state, &handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + internal::DroppableFfiHandle builder_handle{handle, &internal::logic_monitor_builder_destroy}; + for (const auto& state : config.States()) + { + result = internal::logic_monitor_builder_add_state(builder_handle.AsRustHandle().value(), + &state.state, + state.allowed_states.data(), + state.allowed_states.size()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + } + + auto rust_handle = builder_handle.DropByRust(); + SCORE_LANGUAGE_FUTURECPP_ASSERT(rust_handle.has_value()); + return rust_handle.value(); +} + +class ScopeGuard +{ + public: + explicit ScopeGuard(std::function fn) : fn_(std::move(fn)) {} + ~ScopeGuard() + { + fn_(); + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + + private: + std::function fn_; +}; + +} // namespace + +HealthMonitorBuilder& HealthMonitorBuilderImpl::AddDeadlineMonitor(Tag tag, DeadlineMonitorConfiguration&& config) +{ + deadline_monitors_.push_back(DeadlineMonitorSpec{tag, std::move(config)}); + return *this; +} + +HealthMonitorBuilder& HealthMonitorBuilderImpl::AddHeartbeatMonitor(Tag tag, const TimeRange& range) +{ + heartbeat_monitors_.push_back(HeartbeatMonitorSpec{tag, range}); + return *this; +} + +HealthMonitorBuilder& HealthMonitorBuilderImpl::AddLogicMonitor(Tag tag, LogicMonitorConfiguration&& config) +{ + logic_monitors_.push_back(LogicMonitorSpec{tag, std::move(config)}); + return *this; +} + +HealthMonitorBuilder& HealthMonitorBuilderImpl::WithInternalProcessingCycle(std::chrono::milliseconds cycle_duration) +{ + auto count{cycle_duration.count()}; + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(count > 0, "cycle duration must be positive"); + internal_processing_cycle_ms_ = count; + return *this; +} + +HealthMonitorBuilder& HealthMonitorBuilderImpl::WithSupervisorApiCycle(std::chrono::milliseconds cycle_duration) +{ + auto count{cycle_duration.count()}; + SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(count > 0, "cycle duration must be positive"); + supervisor_api_cycle_ms_ = count; + return *this; +} + +HealthMonitorBuilder& HealthMonitorBuilderImpl::WithThreadParameters( + score::mw::health::ThreadParameters&& thread_parameters) +{ + thread_parameters_ = std::move(thread_parameters); + return *this; +} + +score::cpp::expected, Error> HealthMonitorBuilderImpl::Build() +{ + ScopeGuard clear_guard([this]() { + ClearState(); + }); + + auto hm_builder_handle = HealthMonitorBuilderCreateWrapper(); + internal::DroppableFfiHandle builder_handle_guard{hm_builder_handle, &internal::health_monitor_builder_destroy}; + + // Register all monitors with the Rust builder. + auto deadline_result = RegisterDeadlineMonitors(hm_builder_handle); + if (!deadline_result.has_value()) + { + return score::cpp::unexpected(deadline_result.error()); + } + + auto heartbeat_result = RegisterHeartbeatMonitors(hm_builder_handle); + if (!heartbeat_result.has_value()) + { + return score::cpp::unexpected(heartbeat_result.error()); + } + + auto logic_result = RegisterLogicMonitors(hm_builder_handle); + if (!logic_result.has_value()) + { + return score::cpp::unexpected(logic_result.error()); + } + + // Build thread parameters. + const uint64_t* supervisor_api_cycle_ms{nullptr}; + const uint64_t* internal_processing_cycle_ms{nullptr}; + if (supervisor_api_cycle_ms_.has_value()) + { + supervisor_api_cycle_ms = &supervisor_api_cycle_ms_.value(); + } + if (internal_processing_cycle_ms_.has_value()) + { + internal_processing_cycle_ms = &internal_processing_cycle_ms_.value(); + } + + auto thread_params_result = BuildThreadParameters(); + if (!thread_params_result.has_value()) + { + return score::cpp::unexpected(thread_params_result.error()); + } + internal::FfiHandle thread_parameters_handle = thread_params_result.value(); + internal::DroppableFfiHandle thread_params_guard{ + thread_parameters_handle, thread_parameters_handle != nullptr ? &internal::thread_parameters_destroy : nullptr}; + + // Build the Rust HealthMonitor. + auto builder_rust_handle = builder_handle_guard.DropByRust(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(builder_rust_handle.has_value()); + + internal::FfiHandle health_monitor_handle{nullptr}; + auto build_result{internal::health_monitor_builder_build(builder_rust_handle.value(), + supervisor_api_cycle_ms, + internal_processing_cycle_ms, + thread_parameters_handle, + &health_monitor_handle)}; + if (build_result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(build_result)); + } + + // Ownership of thread_parameters_handle transferred to Rust on success. + std::ignore = thread_params_guard.DropByRust(); + + return std::make_unique(health_monitor_handle); +} + +score::cpp::expected_blank HealthMonitorBuilderImpl::RegisterDeadlineMonitors(internal::FfiHandle builder_handle) +{ + for (const auto& monitor : deadline_monitors_) + { + auto handle_result = BuildDeadlineMonitorBuilderHandle(monitor.config); + if (!handle_result.has_value()) + { + return score::cpp::unexpected(handle_result.error()); + } + internal::DroppableFfiHandle guard{handle_result.value(), &internal::deadline_monitor_builder_destroy}; + auto result = internal::health_monitor_builder_add_deadline_monitor( + builder_handle, &monitor.monitor_tag, handle_result.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + std::ignore = guard.DropByRust(); + } + return {}; +} + +score::cpp::expected_blank HealthMonitorBuilderImpl::RegisterHeartbeatMonitors( + internal::FfiHandle builder_handle) +{ + for (const auto& monitor : heartbeat_monitors_) + { + auto handle_result = BuildHeartbeatMonitorBuilderHandle(monitor.range); + if (!handle_result.has_value()) + { + return score::cpp::unexpected(handle_result.error()); + } + internal::DroppableFfiHandle guard{handle_result.value(), &internal::heartbeat_monitor_builder_destroy}; + auto result = internal::health_monitor_builder_add_heartbeat_monitor( + builder_handle, &monitor.monitor_tag, handle_result.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + std::ignore = guard.DropByRust(); + } + return {}; +} + +score::cpp::expected_blank HealthMonitorBuilderImpl::RegisterLogicMonitors(internal::FfiHandle builder_handle) +{ + for (const auto& monitor : logic_monitors_) + { + auto handle_result = BuildLogicMonitorBuilderHandle(monitor.config); + if (!handle_result.has_value()) + { + return score::cpp::unexpected(handle_result.error()); + } + internal::DroppableFfiHandle guard{handle_result.value(), &internal::logic_monitor_builder_destroy}; + auto result = internal::health_monitor_builder_add_logic_monitor( + builder_handle, &monitor.monitor_tag, handle_result.value()); + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + std::ignore = guard.DropByRust(); + } + return {}; +} + +score::cpp::expected HealthMonitorBuilderImpl::BuildThreadParameters() +{ + if (!thread_parameters_.has_value()) + { + return internal::FfiHandle{nullptr}; + } + return BuildThreadParametersHandle(thread_parameters_.value()); +} + +void HealthMonitorBuilderImpl::ClearState() +{ + deadline_monitors_.clear(); + heartbeat_monitors_.clear(); + logic_monitors_.clear(); + supervisor_api_cycle_ms_.reset(); + internal_processing_cycle_ms_.reset(); + thread_parameters_.reset(); +} + +} // namespace score::mw::health::details + +namespace score::mw::health +{ + +std::unique_ptr HealthMonitorBuilder::Create() +{ + return std::make_unique(); +} + +} // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/details/health_monitor_builder_impl.h b/score/health_monitor/src/cpp/details/health_monitor_builder_impl.h new file mode 100644 index 000000000..13cf37189 --- /dev/null +++ b/score/health_monitor/src/cpp/details/health_monitor_builder_impl.h @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_HEALTH_MONITOR_BUILDER_IMPL_H +#define SCORE_HM_DETAILS_HEALTH_MONITOR_BUILDER_IMPL_H + +#include "score/mw/health/details/common_internal.h" +#include "score/mw/health/health_monitor_builder.h" + +namespace score::mw::health::details +{ + +class HealthMonitorBuilderImpl final : public HealthMonitorBuilder +{ + public: + HealthMonitorBuilder& AddDeadlineMonitor(Tag tag, DeadlineMonitorConfiguration&& config) override; + + HealthMonitorBuilder& AddHeartbeatMonitor(Tag tag, const TimeRange& range) override; + + HealthMonitorBuilder& AddLogicMonitor(Tag tag, LogicMonitorConfiguration&& config) override; + + HealthMonitorBuilder& WithInternalProcessingCycle(std::chrono::milliseconds cycle_duration) override; + HealthMonitorBuilder& WithSupervisorApiCycle(std::chrono::milliseconds cycle_duration) override; + + HealthMonitorBuilder& WithThreadParameters(score::mw::health::ThreadParameters&& thread_parameters) override; + + score::cpp::expected, Error> Build() override; + + private: + struct DeadlineMonitorSpec + { + Tag monitor_tag; + DeadlineMonitorConfiguration config; + }; + + struct HeartbeatMonitorSpec + { + Tag monitor_tag; + TimeRange range; + }; + + struct LogicMonitorSpec + { + Tag monitor_tag; + LogicMonitorConfiguration config; + }; + + score::cpp::expected_blank RegisterDeadlineMonitors(internal::FfiHandle builder_handle); + score::cpp::expected_blank RegisterHeartbeatMonitors(internal::FfiHandle builder_handle); + score::cpp::expected_blank RegisterLogicMonitors(internal::FfiHandle builder_handle); + score::cpp::expected BuildThreadParameters(); + + void ClearState(); + + std::optional supervisor_api_cycle_ms_; + std::optional internal_processing_cycle_ms_; + std::optional thread_parameters_; + + std::vector deadline_monitors_; + std::vector heartbeat_monitors_; + std::vector logic_monitors_; +}; + +} // namespace score::mw::health::details + +#endif // SCORE_HM_DETAILS_HEALTH_MONITOR_BUILDER_IMPL_H diff --git a/score/health_monitor/src/cpp/details/health_monitor_impl.cpp b/score/health_monitor/src/cpp/details/health_monitor_impl.cpp new file mode 100644 index 000000000..fde81a0dc --- /dev/null +++ b/score/health_monitor/src/cpp/details/health_monitor_impl.cpp @@ -0,0 +1,83 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/details/health_monitor_impl.h" + +#include "score/mw/health/details/deadline_monitor_impl.h" +#include "score/mw/health/details/ffi_declarations.h" +#include "score/mw/health/details/heartbeat_monitor_impl.h" +#include "score/mw/health/details/logic_monitor_impl.h" + +#include + +namespace score::mw::health::details +{ + +HealthMonitorImpl::HealthMonitorImpl(score::mw::health::internal::FfiHandle handle) + : health_monitor_(handle, &internal::health_monitor_destroy) +{ +} + +score::cpp::expected, Error> HealthMonitorImpl::GetDeadlineMonitor(Tag tag) +{ + auto handle = health_monitor_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + internal::FfiHandle deadline_monitor_handle{nullptr}; + auto result{internal::health_monitor_get_deadline_monitor(handle.value(), &tag, &deadline_monitor_handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return MakeDeadlineMonitorFromFfi(deadline_monitor_handle); +} + +score::cpp::expected, Error> HealthMonitorImpl::GetHeartbeatMonitor(Tag tag) +{ + auto handle = health_monitor_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + internal::FfiHandle heartbeat_monitor_handle{nullptr}; + auto result{internal::health_monitor_get_heartbeat_monitor(handle.value(), &tag, &heartbeat_monitor_handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return MakeHeartbeatMonitorFromFfi(heartbeat_monitor_handle); +} + +score::cpp::expected, Error> HealthMonitorImpl::GetLogicMonitor(Tag tag) +{ + auto handle = health_monitor_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + + internal::FfiHandle logic_monitor_handle{nullptr}; + auto result{internal::health_monitor_get_logic_monitor(handle.value(), &tag, &logic_monitor_handle)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return MakeLogicMonitorFromFfi(logic_monitor_handle); +} + +void HealthMonitorImpl::Start() +{ + auto handle = health_monitor_.AsRustHandle(); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(handle.has_value()); + auto result{internal::health_monitor_start(handle.value())}; + SCORE_LANGUAGE_FUTURECPP_ASSERT(result == internal::kSuccess); +} + +} // namespace score::mw::health::details diff --git a/score/health_monitor/src/cpp/details/health_monitor_impl.h b/score/health_monitor/src/cpp/details/health_monitor_impl.h new file mode 100644 index 000000000..0429b22cd --- /dev/null +++ b/score/health_monitor/src/cpp/details/health_monitor_impl.h @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_HEALTH_MONITOR_IMPL_H +#define SCORE_HM_DETAILS_HEALTH_MONITOR_IMPL_H + +#include "score/mw/health/health_monitor.h" + +#include "score/mw/health/details/common_internal.h" + +namespace score::mw::health::details +{ + +class HealthMonitorImpl final : public HealthMonitor +{ + public: + explicit HealthMonitorImpl(score::mw::health::internal::FfiHandle handle); + ~HealthMonitorImpl() override = default; + + HealthMonitorImpl(const HealthMonitorImpl&) = delete; + HealthMonitorImpl& operator=(const HealthMonitorImpl&) = delete; + HealthMonitorImpl(HealthMonitorImpl&&) = delete; + HealthMonitorImpl& operator=(HealthMonitorImpl&&) = delete; + + score::cpp::expected, Error> GetDeadlineMonitor(Tag tag) override; + score::cpp::expected, Error> GetHeartbeatMonitor(Tag tag) override; + score::cpp::expected, Error> GetLogicMonitor(Tag tag) override; + + void Start() override; + + private: + score::mw::health::internal::DroppableFfiHandle health_monitor_; +}; + +} // namespace score::mw::health::details + +#endif // SCORE_HM_DETAILS_HEALTH_MONITOR_IMPL_H diff --git a/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.cpp b/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.cpp new file mode 100644 index 000000000..8108e20e3 --- /dev/null +++ b/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.cpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/details/heartbeat_monitor_impl.h" + +#include "score/mw/health/details/ffi_declarations.h" + +#include + +namespace score::mw::health::details +{ + +HeartbeatMonitorImpl::HeartbeatMonitorImpl(score::mw::health::internal::FfiHandle monitor_handle) + : monitor_handle_(monitor_handle, &internal::heartbeat_monitor_destroy) +{ +} + +void HeartbeatMonitorImpl::Heartbeat() +{ + auto monitor_handle{monitor_handle_.AsRustHandle()}; + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + SCORE_LANGUAGE_FUTURECPP_ASSERT(internal::heartbeat_monitor_heartbeat(monitor_handle.value()) == + internal::kSuccess); +} + +std::unique_ptr MakeHeartbeatMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle) +{ + return std::make_unique(monitor_handle); +} + +} // namespace score::mw::health::details diff --git a/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.h b/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.h new file mode 100644 index 000000000..3039757c4 --- /dev/null +++ b/score/health_monitor/src/cpp/details/heartbeat_monitor_impl.h @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_HEARTBEAT_MONITOR_IMPL_H +#define SCORE_HM_DETAILS_HEARTBEAT_MONITOR_IMPL_H + +#include "score/mw/health/details/common_internal.h" +#include "score/mw/health/heartbeat_monitor.h" + +#include + +namespace score::mw::health::details +{ + +class HeartbeatMonitorImpl final : public HeartbeatMonitor +{ + public: + explicit HeartbeatMonitorImpl(score::mw::health::internal::FfiHandle monitor_handle); + ~HeartbeatMonitorImpl() override = default; + + HeartbeatMonitorImpl(const HeartbeatMonitorImpl&) = delete; + HeartbeatMonitorImpl& operator=(const HeartbeatMonitorImpl&) = delete; + HeartbeatMonitorImpl(HeartbeatMonitorImpl&&) = delete; + HeartbeatMonitorImpl& operator=(HeartbeatMonitorImpl&&) = delete; + + void Heartbeat() override; + + private: + score::mw::health::internal::DroppableFfiHandle monitor_handle_; +}; + +std::unique_ptr MakeHeartbeatMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle); + +} // namespace score::mw::health::details + +#endif // SCORE_HM_DETAILS_HEARTBEAT_MONITOR_IMPL_H diff --git a/score/health_monitor/src/cpp/details/logic_monitor_impl.cpp b/score/health_monitor/src/cpp/details/logic_monitor_impl.cpp new file mode 100644 index 000000000..085cd9b7b --- /dev/null +++ b/score/health_monitor/src/cpp/details/logic_monitor_impl.cpp @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/details/logic_monitor_impl.h" + +#include "score/mw/health/details/ffi_declarations.h" + +#include + +namespace score::mw::health::details +{ + +LogicMonitorImpl::LogicMonitorImpl(score::mw::health::internal::FfiHandle monitor_handle) + : monitor_handle_(monitor_handle, &internal::logic_monitor_destroy) +{ +} + +score::cpp::expected LogicMonitorImpl::Transition(Tag state) +{ + auto monitor_handle{monitor_handle_.AsRustHandle()}; + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + + auto result{internal::logic_monitor_transition(monitor_handle.value(), &state)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return state; +} + +score::cpp::expected LogicMonitorImpl::State() +{ + auto monitor_handle{monitor_handle_.AsRustHandle()}; + SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); + + Tag state_tag{""}; + auto result{internal::logic_monitor_state(monitor_handle.value(), &state_tag)}; + if (result != internal::kSuccess) + { + return score::cpp::unexpected(internal::MapFfiError(result)); + } + + return state_tag; +} + +std::unique_ptr MakeLogicMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle) +{ + return std::make_unique(monitor_handle); +} + +} // namespace score::mw::health::details diff --git a/score/health_monitor/src/cpp/details/logic_monitor_impl.h b/score/health_monitor/src/cpp/details/logic_monitor_impl.h new file mode 100644 index 000000000..4dc80d677 --- /dev/null +++ b/score/health_monitor/src/cpp/details/logic_monitor_impl.h @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_DETAILS_LOGIC_MONITOR_IMPL_H +#define SCORE_HM_DETAILS_LOGIC_MONITOR_IMPL_H + +#include "score/mw/health/details/common_internal.h" +#include "score/mw/health/logic_monitor.h" + +#include + +namespace score::mw::health::details +{ + +class LogicMonitorImpl final : public LogicMonitor +{ + public: + explicit LogicMonitorImpl(score::mw::health::internal::FfiHandle monitor_handle); + ~LogicMonitorImpl() override = default; + + LogicMonitorImpl(const LogicMonitorImpl&) = delete; + LogicMonitorImpl& operator=(const LogicMonitorImpl&) = delete; + LogicMonitorImpl(LogicMonitorImpl&&) = delete; + LogicMonitorImpl& operator=(LogicMonitorImpl&&) = delete; + + score::cpp::expected Transition(Tag state) override; + score::cpp::expected State() override; + + private: + score::mw::health::internal::DroppableFfiHandle monitor_handle_; +}; + +std::unique_ptr MakeLogicMonitorFromFfi(score::mw::health::internal::FfiHandle monitor_handle); + +} // namespace score::mw::health::details + +#endif // SCORE_HM_DETAILS_LOGIC_MONITOR_IMPL_H diff --git a/score/health_monitor/src/cpp/details/thread.cpp b/score/health_monitor/src/cpp/details/thread.cpp deleted file mode 100644 index def62a413..000000000 --- a/score/health_monitor/src/cpp/details/thread.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -#include "score/mw/health/thread.h" -#include - -namespace -{ -extern "C" { - -using namespace score::mw::health; -using namespace score::mw::health::internal; - -// Functions below must match functions defined in `crate::thread_ffi`. - -FFICode scheduler_policy_priority_min(SchedulerPolicy scheduler_policy, int32_t* priority_out); -FFICode scheduler_policy_priority_max(SchedulerPolicy scheduler_policy, int32_t* priority_out); -FFICode thread_parameters_create(FFIHandle* thread_parameters_handle_out); -FFICode thread_parameters_destroy(FFIHandle thread_parameters_handle); -FFICode thread_parameters_scheduler_parameters(FFIHandle thread_parameters_handle, - SchedulerPolicy policy, - int32_t priority); -FFICode thread_parameters_affinity(FFIHandle thread_parameters_handle, const size_t* affinity, size_t num_affinity); -FFICode thread_parameters_stack_size(FFIHandle thread_parameters_handle, size_t stack_size); -} - -FFIHandle thread_parameters_create_wrapper() -{ - FFIHandle handle{nullptr}; - auto result{thread_parameters_create(&handle)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return handle; -} -} // namespace - -namespace score::mw::health -{ - -int32_t scheduler_policy_priority_min(SchedulerPolicy scheduler_policy) -{ - int32_t priority{0}; - auto result{::scheduler_policy_priority_min(scheduler_policy, &priority)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return priority; -} - -int32_t scheduler_policy_priority_max(SchedulerPolicy scheduler_policy) -{ - int32_t priority{0}; - auto result{::scheduler_policy_priority_max(scheduler_policy, &priority)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return priority; -} - -SchedulerParameters::SchedulerParameters(SchedulerPolicy policy, int32_t priority) - : policy_{policy}, priority_{priority} -{ - auto min{scheduler_policy_priority_min(policy)}; - auto max{scheduler_policy_priority_max(policy)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(priority >= min && priority <= max); -} - -SchedulerPolicy SchedulerParameters::policy() const -{ - return policy_; -} - -int32_t SchedulerParameters::priority() const -{ - return priority_; -} - -ThreadParameters::ThreadParameters() - : thread_parameters_handle_{thread_parameters_create_wrapper(), &thread_parameters_destroy} -{ -} - -ThreadParameters ThreadParameters::scheduler_parameters(SchedulerParameters scheduler_parameters) && -{ - auto rust_handle{thread_parameters_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(rust_handle.has_value()); - - auto policy{scheduler_parameters.policy()}; - auto priority{scheduler_parameters.priority()}; - auto result{thread_parameters_scheduler_parameters(rust_handle.value(), policy, priority)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -ThreadParameters ThreadParameters::affinity(const std::vector& affinity) && -{ - auto rust_handle{thread_parameters_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(rust_handle.has_value()); - - auto result{thread_parameters_affinity(rust_handle.value(), affinity.data(), affinity.size())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -ThreadParameters ThreadParameters::stack_size(size_t stack_size) && -{ - auto rust_handle{thread_parameters_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(rust_handle.has_value()); - - auto result{thread_parameters_stack_size(rust_handle.value(), stack_size)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -} // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/details/thread.h b/score/health_monitor/src/cpp/details/thread.h deleted file mode 100644 index 61f489b22..000000000 --- a/score/health_monitor/src/cpp/details/thread.h +++ /dev/null @@ -1,91 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#ifndef SCORE_HM_THREAD_H -#define SCORE_HM_THREAD_H - -#include "score/mw/health/common.h" -#include -#include - -namespace score::mw::health -{ - -class HealthMonitorBuilder; - -/// Scheduler policy. -enum class SchedulerPolicy : int32_t -{ - Other, - Fifo, - RoundRobin, -}; - -/// Get min thread priority for given policy. -int32_t scheduler_policy_priority_min(SchedulerPolicy scheduler_policy); - -/// Get max thread priority for given policy. -int32_t scheduler_policy_priority_max(SchedulerPolicy scheduler_policy); - -class SchedulerParameters final -{ - public: - /// Create a new `SchedulerParameters`. - /// Priority must be in allowed range for the scheduler policy. - SchedulerParameters(SchedulerPolicy policy, int32_t priority); - - /// Scheduler policy. - SchedulerPolicy policy() const; - - /// Thread priority. - int32_t priority() const; - - private: - SchedulerPolicy policy_; - int32_t priority_; -}; - -/// Thread parameters. -class ThreadParameters final : public internal::RustDroppable -{ - public: - /// Create a new `ThreadParameters` containing default values. - ThreadParameters(); - - /// Scheduler parameters, including scheduler policy and thread priority. - ThreadParameters scheduler_parameters(SchedulerParameters scheduler_parameters) &&; - - /// Set thread affinity - array of CPU core IDs that the thread can run on. - ThreadParameters affinity(const std::vector& affinity) &&; - - /// Set stack size. - ThreadParameters stack_size(size_t stack_size) &&; - - protected: - std::optional _drop_by_rust_impl() - { - return thread_parameters_handle_.drop_by_rust(); - } - - private: - internal::DroppableFFIHandle thread_parameters_handle_; - - // Allow to hide `drop_by_rust` implementation. - friend class internal::RustDroppable; - - // Allow `HealthMonitorBuilder` to access `drop_by_rust` implementation. - friend class score::mw::health::HealthMonitorBuilder; -}; - -} // namespace score::mw::health - -#endif // SCORE_HM_THREAD_H diff --git a/score/health_monitor/src/cpp/health_monitor.cpp b/score/health_monitor/src/cpp/health_monitor.cpp deleted file mode 100644 index 54b18216b..000000000 --- a/score/health_monitor/src/cpp/health_monitor.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#include "score/mw/health/health_monitor.h" - -namespace -{ -extern "C" { -using namespace score::mw::health; -using namespace score::mw::health::internal; -using namespace score::mw::health::deadline; -using namespace score::mw::health::heartbeat; -using namespace score::mw::health::logic; - -// Functions below must match functions defined in `crate::ffi`. - -FFICode health_monitor_builder_create(FFIHandle* health_monitor_builder_handle_out); -FFICode health_monitor_builder_destroy(FFIHandle health_monitor_builder_handle); -FFICode health_monitor_builder_build(FFIHandle health_monitor_builder_handle, - const uint64_t* supervisor_cycle_ms, - const uint64_t* internal_cycle_ms, - FFIHandle thread_parameters_handle, - FFIHandle* health_monitor_handle_out); -FFICode health_monitor_builder_add_deadline_monitor(FFIHandle health_monitor_builder_handle, - const MonitorTag* monitor_tag, - FFIHandle deadline_monitor_builder_handle); -FFICode health_monitor_builder_add_heartbeat_monitor(FFIHandle health_monitor_builder_handle, - const MonitorTag* monitor_tag, - FFIHandle heartbeat_monitor_builder_handle); -FFICode health_monitor_builder_add_logic_monitor(FFIHandle health_monitor_builder_handle, - const MonitorTag* monitor_tag, - FFIHandle logic_monitor_builder_handle); -FFICode health_monitor_get_deadline_monitor(FFIHandle health_monitor_handle, - const MonitorTag* monitor_tag, - FFIHandle* deadline_monitor_handle_out); -FFICode health_monitor_get_heartbeat_monitor(FFIHandle health_monitor_handle, - const MonitorTag* monitor_tag, - FFIHandle* heartbeat_monitor_handle_out); -FFICode health_monitor_get_logic_monitor(FFIHandle health_monitor_handle, - const MonitorTag* monitor_tag, - FFIHandle* logic_monitor_handle_out); -FFICode health_monitor_start(FFIHandle health_monitor_handle); -FFICode health_monitor_destroy(FFIHandle health_monitor_handle); -} - -FFIHandle health_monitor_builder_create_wrapper() -{ - FFIHandle handle{nullptr}; - auto result{health_monitor_builder_create(&handle)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return handle; -} - -} // namespace - -// C++ wrapper for Rust library - the API implementation obeys the Rust API semantics and it's invariants - -namespace score::mw::health -{ - -HealthMonitorBuilder::HealthMonitorBuilder() - : health_monitor_builder_handle_{health_monitor_builder_create_wrapper(), &health_monitor_builder_destroy} -{ -} - -HealthMonitorBuilder HealthMonitorBuilder::add_deadline_monitor(const MonitorTag& monitor_tag, - DeadlineMonitorBuilder&& monitor) && -{ - auto monitor_handle = monitor.drop_by_rust(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle_.as_rust_handle().has_value()); - - auto result{health_monitor_builder_add_deadline_monitor( - health_monitor_builder_handle_.as_rust_handle().value(), &monitor_tag, monitor_handle.value())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -HealthMonitorBuilder HealthMonitorBuilder::add_heartbeat_monitor(const MonitorTag& monitor_tag, - HeartbeatMonitorBuilder&& monitor) && -{ - auto monitor_handle = monitor.drop_by_rust(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle_.as_rust_handle().has_value()); - - auto result{health_monitor_builder_add_heartbeat_monitor( - health_monitor_builder_handle_.as_rust_handle().value(), &monitor_tag, monitor_handle.value())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -HealthMonitorBuilder HealthMonitorBuilder::add_logic_monitor(const MonitorTag& monitor_tag, - LogicMonitorBuilder&& monitor) && -{ - auto monitor_handle = monitor.drop_by_rust(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle_.as_rust_handle().has_value()); - - auto result{health_monitor_builder_add_logic_monitor( - health_monitor_builder_handle_.as_rust_handle().value(), &monitor_tag, monitor_handle.value())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -HealthMonitorBuilder HealthMonitorBuilder::with_internal_processing_cycle(std::chrono::milliseconds cycle_duration) && -{ - auto count{cycle_duration.count()}; - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(count >= 0, "cycle duration must be positive"); - internal_processing_cycle_ms_ = count; - return std::move(*this); -} - -HealthMonitorBuilder HealthMonitorBuilder::with_supervisor_api_cycle(std::chrono::milliseconds cycle_duration) && -{ - auto count{cycle_duration.count()}; - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(count >= 0, "cycle duration must be positive"); - supervisor_api_cycle_ms_ = count; - return std::move(*this); -} - -HealthMonitorBuilder HealthMonitorBuilder::thread_parameters(score::mw::health::ThreadParameters&& thread_parameters) && -{ - thread_parameters_ = std::move(thread_parameters); - return std::move(*this); -} - -score::cpp::expected HealthMonitorBuilder::build() && -{ - auto health_monitor_builder_handle = health_monitor_builder_handle_.drop_by_rust(); - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(health_monitor_builder_handle.has_value()); - - // Handle optional parameters. - const uint64_t* supervisor_api_cycle_ms{nullptr}; - const uint64_t* internal_processing_cycle_ms{nullptr}; - if (supervisor_api_cycle_ms_.has_value()) - { - supervisor_api_cycle_ms = &supervisor_api_cycle_ms_.value(); - } - if (internal_processing_cycle_ms_.has_value()) - { - internal_processing_cycle_ms = &internal_processing_cycle_ms_.value(); - } - - // Handle thread parameters. - FFIHandle thread_parameters_handle{nullptr}; - if (thread_parameters_.has_value()) - { - auto rust_handle{thread_parameters_.value().drop_by_rust()}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(rust_handle.has_value()); - thread_parameters_handle = rust_handle.value(); - } - - FFIHandle health_monitor_handle{nullptr}; - auto result{health_monitor_builder_build(health_monitor_builder_handle.value(), - supervisor_api_cycle_ms, - internal_processing_cycle_ms, - thread_parameters_handle, - &health_monitor_handle)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(HealthMonitor{health_monitor_handle}); -} - -HealthMonitor::HealthMonitor(FFIHandle handle) : health_monitor_(handle) -{ - // Initialize health monitor -} - -HealthMonitor::HealthMonitor(HealthMonitor&& other) -{ - health_monitor_ = std::move(other.health_monitor_); - other.health_monitor_ = nullptr; -} - -score::cpp::expected HealthMonitor::get_deadline_monitor(const MonitorTag& monitor_tag) -{ - FFIHandle handle{nullptr}; - auto result{health_monitor_get_deadline_monitor(health_monitor_, &monitor_tag, &handle)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(DeadlineMonitor{handle}); -} - -score::cpp::expected HealthMonitor::get_heartbeat_monitor(const MonitorTag& monitor_tag) -{ - FFIHandle handle{nullptr}; - auto result{health_monitor_get_heartbeat_monitor(health_monitor_, &monitor_tag, &handle)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(HeartbeatMonitor{handle}); -} - -score::cpp::expected HealthMonitor::get_logic_monitor(const MonitorTag& monitor_tag) -{ - FFIHandle handle{nullptr}; - auto result{health_monitor_get_logic_monitor(health_monitor_, &monitor_tag, &handle)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(LogicMonitor{handle}); -} - -void HealthMonitor::start() -{ - auto result{health_monitor_start(health_monitor_)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); -} - -HealthMonitor::~HealthMonitor() -{ - if (health_monitor_ != nullptr) - { - health_monitor_destroy(health_monitor_); - } -} - -HealthMonitor& HealthMonitor::operator=(HealthMonitor&& other) -{ - if (this != &other) - { - if (health_monitor_ != nullptr) - { - health_monitor_destroy(health_monitor_); - } - health_monitor_ = std::move(other.health_monitor_); - other.health_monitor_ = nullptr; - } - return *this; -} - -} // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/health_monitor.h b/score/health_monitor/src/cpp/health_monitor.h index 5f6c61f71..d1d81b7c3 100644 --- a/score/health_monitor/src/cpp/health_monitor.h +++ b/score/health_monitor/src/cpp/health_monitor.h @@ -17,87 +17,34 @@ #include "score/mw/health/deadline_monitor.h" #include "score/mw/health/heartbeat_monitor.h" #include "score/mw/health/logic_monitor.h" -#include "score/mw/health/tag.h" -#include "score/mw/health/thread.h" + +#include namespace score::mw::health { -class HealthMonitor; - -/// -/// Builder for HealthMonitor instances. -/// -class HealthMonitorBuilder final +class HealthMonitor { public: - /// Create a new `HealthMonitorBuilder`. - HealthMonitorBuilder(); - - ~HealthMonitorBuilder() = default; - HealthMonitorBuilder(const HealthMonitorBuilder&) = delete; - HealthMonitorBuilder& operator=(const HealthMonitorBuilder&) = delete; - - HealthMonitorBuilder(HealthMonitorBuilder&&) = default; - HealthMonitorBuilder& operator=(HealthMonitorBuilder&&) = delete; - - /// Adds a deadline monitor to the builder to construct DeadlineMonitor instances during HealthMonitor build. - HealthMonitorBuilder add_deadline_monitor(const MonitorTag& monitor_tag, - deadline::DeadlineMonitorBuilder&& monitor) &&; - - /// Adds a heartbeat monitor for a specific identifier tag. - HealthMonitorBuilder add_heartbeat_monitor(const MonitorTag& monitor_tag, - heartbeat::HeartbeatMonitorBuilder&& monitor) &&; - - /// Adds a logic monitor for a specific identifier tag. - HealthMonitorBuilder add_logic_monitor(const MonitorTag& monitor_tag, logic::LogicMonitorBuilder&& monitor) &&; - - /// Sets the cycle duration for supervisor API notifications. - /// This duration determines how often the health monitor notifies the supervisor that the system is alive. - HealthMonitorBuilder with_supervisor_api_cycle(std::chrono::milliseconds cycle_duration) &&; - - /// Sets the internal processing cycle duration. - /// This duration determines how often the health monitor checks deadlines. - HealthMonitorBuilder with_internal_processing_cycle(std::chrono::milliseconds cycle_duration) &&; - - /// Sets the monitoring thread parameters. - HealthMonitorBuilder thread_parameters(score::mw::health::ThreadParameters&& thread_parameters) &&; + HealthMonitor() = default; + virtual ~HealthMonitor() = default; - /// Build a new `HealthMonitor` instance based on provided parameters. - score::cpp::expected build() &&; - - private: - internal::DroppableFFIHandle health_monitor_builder_handle_; - - std::optional supervisor_api_cycle_ms_; - std::optional internal_processing_cycle_ms_; - std::optional thread_parameters_; -}; - -class HealthMonitor final -{ - public: HealthMonitor(const HealthMonitor&) = delete; HealthMonitor& operator=(const HealthMonitor&) = delete; + HealthMonitor(HealthMonitor&&) = delete; + HealthMonitor& operator=(HealthMonitor&&) = delete; - HealthMonitor(HealthMonitor&& other); - HealthMonitor& operator=(HealthMonitor&&); - - ~HealthMonitor(); - - score::cpp::expected get_deadline_monitor(const MonitorTag& monitor_tag); - score::cpp::expected get_heartbeat_monitor(const MonitorTag& monitor_tag); - score::cpp::expected get_logic_monitor(const MonitorTag& monitor_tag); - - void start(); + /// Retrieves the deadline monitor registered with the given tag. + virtual score::cpp::expected, Error> GetDeadlineMonitor(Tag tag) = 0; - private: - // Allow only the builder to create HealthMonitor instances. - friend class HealthMonitorBuilder; + /// Retrieves the heartbeat monitor registered with the given tag. + virtual score::cpp::expected, Error> GetHeartbeatMonitor(Tag tag) = 0; - HealthMonitor(internal::FFIHandle handle); + /// Retrieves the logic monitor registered with the given tag. + virtual score::cpp::expected, Error> GetLogicMonitor(Tag tag) = 0; - internal::FFIHandle health_monitor_; + /// Starts the health monitor's background monitoring thread. + virtual void Start() = 0; }; } // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/health_monitor_builder.h b/score/health_monitor/src/cpp/health_monitor_builder.h new file mode 100644 index 000000000..80ef141ed --- /dev/null +++ b/score/health_monitor/src/cpp/health_monitor_builder.h @@ -0,0 +1,150 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_HEALTH_MONITOR_BUILDER_H +#define SCORE_HM_HEALTH_MONITOR_BUILDER_H + +#include "score/mw/health/common.h" +#include "score/mw/health/health_monitor.h" + +#include +#include +#include + +namespace score::mw::health +{ + +enum class SchedulerPolicy : int32_t +{ + kOther, + kFifo, + kRoundRobin, +}; + +struct SchedulerParameters +{ + SchedulerPolicy policy; + int32_t priority; +}; + +struct ThreadParameters +{ + std::optional scheduler_parameters; + std::vector affinity; + std::optional stack_size; +}; + +struct DeadlineConfiguration +{ + Tag deadline_tag; + TimeRange range; +}; + +class DeadlineMonitorConfiguration +{ + public: + /// Adds a deadline with the given tag and duration range to the monitor. + DeadlineMonitorConfiguration& AddDeadline(Tag tag, const TimeRange& range) + { + deadlines_.push_back(DeadlineConfiguration{tag, range}); + return *this; + } + + /// Returns all deferred deadline entries. + const std::vector& Deadlines() const + { + return deadlines_; + } + + private: + std::vector deadlines_{}; +}; + +struct StateConfiguration +{ + Tag state; + std::vector allowed_states; +}; + +class LogicMonitorConfiguration +{ + public: + explicit LogicMonitorConfiguration(Tag initial_state) : initial_state_(initial_state) + { + } + + /// Add state along with allowed transitions. + /// If state already exists - it is overwritten. + LogicMonitorConfiguration& AddState(Tag state, std::vector allowed_states) + { + states_.push_back(StateConfiguration{state, std::move(allowed_states)}); + return *this; + } + + Tag InitialState() const + { + return initial_state_; + } + + const std::vector& States() const + { + return states_; + } + + private: + Tag initial_state_; + std::vector states_{}; +}; + +/// +/// Builder for HealthMonitor instances. +/// +class HealthMonitorBuilder +{ + public: + HealthMonitorBuilder() = default; + virtual ~HealthMonitorBuilder() = default; + + HealthMonitorBuilder(const HealthMonitorBuilder&) = delete; + HealthMonitorBuilder& operator=(const HealthMonitorBuilder&) = delete; + HealthMonitorBuilder(HealthMonitorBuilder&&) = delete; + HealthMonitorBuilder& operator=(HealthMonitorBuilder&&) = delete; + + static std::unique_ptr Create(); + + /// Adds a deadline monitor to the builder to construct DeadlineMonitor instances during HealthMonitor build. + virtual HealthMonitorBuilder& AddDeadlineMonitor(Tag tag, DeadlineMonitorConfiguration&& config) = 0; + + /// Adds a heartbeat monitor for a specific identifier tag. + virtual HealthMonitorBuilder& AddHeartbeatMonitor(Tag tag, const TimeRange& range) = 0; + + /// Adds a logic monitor for a specific identifier tag. + virtual HealthMonitorBuilder& AddLogicMonitor(Tag tag, LogicMonitorConfiguration&& config) = 0; + + /// Sets the cycle duration for supervisor API notifications. + /// This duration determines how often the health monitor notifies the supervisor that the system is alive. + virtual HealthMonitorBuilder& WithSupervisorApiCycle(std::chrono::milliseconds cycle_duration) = 0; + + /// Sets the internal processing cycle duration. + /// This duration determines how often the health monitor checks deadlines. + virtual HealthMonitorBuilder& WithInternalProcessingCycle(std::chrono::milliseconds cycle_duration) = 0; + + /// Sets the monitoring thread parameters. + virtual HealthMonitorBuilder& WithThreadParameters(score::mw::health::ThreadParameters&& thread_parameters) = 0; + + /// Build a new `HealthMonitor` instance based on provided parameters. + virtual score::cpp::expected, Error> Build() = 0; +}; + +} // namespace score::mw::health + +#endif // SCORE_HM_HEALTH_MONITOR_BUILDER_H diff --git a/score/health_monitor/src/cpp/health_monitor_loader.h b/score/health_monitor/src/cpp/health_monitor_loader.h new file mode 100644 index 000000000..830ad3311 --- /dev/null +++ b/score/health_monitor/src/cpp/health_monitor_loader.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_HEALTH_MONITOR_LOADER_H +#define SCORE_HM_HEALTH_MONITOR_LOADER_H + +#include "score/mw/health/common.h" +#include "score/mw/health/health_monitor.h" + +namespace score::mw::health +{ + +class HealthMonitorLoader +{ + public: + HealthMonitorLoader() = default; + virtual ~HealthMonitorLoader() = default; + + HealthMonitorLoader(const HealthMonitorLoader&) = delete; + HealthMonitorLoader& operator=(const HealthMonitorLoader&) = delete; + HealthMonitorLoader(HealthMonitorLoader&&) = delete; + HealthMonitorLoader& operator=(HealthMonitorLoader&&) = delete; + + static std::unique_ptr Create(); + + /// Build a HealthMonitor based on configuration. + virtual score::cpp::expected, Error> Load(std::string_view config_path) = 0; +}; + +} // namespace score::mw::health + +#endif // SCORE_HM_HEALTH_MONITOR_LOADER_H diff --git a/score/health_monitor/src/cpp/health_monitor_mocks.cpp b/score/health_monitor/src/cpp/health_monitor_mocks.cpp new file mode 100644 index 000000000..8af152752 --- /dev/null +++ b/score/health_monitor/src/cpp/health_monitor_mocks.cpp @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/health/health_monitor_mocks.h" + +#include + +namespace score::mw::health +{ + +namespace +{ +thread_local mocks::ScopedBuilderFactory::Factory active_factory{}; +} // namespace + +std::unique_ptr HealthMonitorBuilder::Create() +{ + SCORE_LANGUAGE_FUTURECPP_PRECONDITION_MESSAGE(active_factory, + "No builder factory set. Use mocks::ScopedBuilderFactory in tests."); + return active_factory(); +} + +namespace mocks +{ + +ScopedBuilderFactory::ScopedBuilderFactory(Factory factory) +{ + SCORE_LANGUAGE_FUTURECPP_PRECONDITION_MESSAGE(!active_factory, "Another ScopedBuilderFactory is already active."); + active_factory = std::move(factory); +} + +ScopedBuilderFactory::~ScopedBuilderFactory() +{ + active_factory = nullptr; +} + +} // namespace mocks +} // namespace score::mw::health diff --git a/score/health_monitor/src/cpp/health_monitor_mocks.h b/score/health_monitor/src/cpp/health_monitor_mocks.h new file mode 100644 index 000000000..3685072cf --- /dev/null +++ b/score/health_monitor/src/cpp/health_monitor_mocks.h @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_HEALTH_MONITOR_MOCKS_H +#define SCORE_HM_HEALTH_MONITOR_MOCKS_H + +#include "score/mw/health/deadline_monitor.h" +#include "score/mw/health/health_monitor.h" +#include "score/mw/health/health_monitor_builder.h" +#include "score/mw/health/heartbeat_monitor.h" +#include "score/mw/health/logic_monitor.h" + +#include +#include + +namespace score::mw::health::mocks +{ + +class DeadlineMock : public Deadline +{ + public: + MOCK_METHOD((::score::cpp::expected_blank), Start, (), (override)); + MOCK_METHOD((::score::cpp::expected_blank), Stop, (), (override)); +}; + +class DeadlineMonitorMock : public DeadlineMonitor +{ + public: + MOCK_METHOD((::score::cpp::expected, Error>), GetDeadline, (Tag tag), (override)); +}; + +class HeartbeatMonitorMock : public HeartbeatMonitor +{ + public: + MOCK_METHOD(void, Heartbeat, (), (override)); +}; + +class LogicMonitorMock : public LogicMonitor +{ + public: + MOCK_METHOD((score::cpp::expected), Transition, (Tag state), (override)); + MOCK_METHOD((score::cpp::expected), State, (), (override)); +}; + +class HealthMonitorMock : public HealthMonitor +{ + public: + MOCK_METHOD((score::cpp::expected, Error>), + GetDeadlineMonitor, + (Tag tag), + (override)); + MOCK_METHOD((score::cpp::expected, Error>), + GetHeartbeatMonitor, + (Tag tag), + (override)); + MOCK_METHOD((score::cpp::expected, Error>), GetLogicMonitor, (Tag tag), (override)); + MOCK_METHOD(void, Start, (), (override)); +}; + +class HealthMonitorBuilderMock : public HealthMonitorBuilder +{ + public: + MOCK_METHOD(HealthMonitorBuilder&, + AddDeadlineMonitor, + (Tag tag, DeadlineMonitorConfiguration&& config), + (override)); + MOCK_METHOD(HealthMonitorBuilder&, AddHeartbeatMonitor, (Tag tag, const TimeRange& range), (override)); + MOCK_METHOD(HealthMonitorBuilder&, AddLogicMonitor, (Tag tag, LogicMonitorConfiguration&& config), (override)); + MOCK_METHOD(HealthMonitorBuilder&, WithSupervisorApiCycle, (std::chrono::milliseconds cycle_duration), (override)); + MOCK_METHOD(HealthMonitorBuilder&, + WithInternalProcessingCycle, + (std::chrono::milliseconds cycle_duration), + (override)); + MOCK_METHOD(HealthMonitorBuilder&, + WithThreadParameters, + (score::mw::health::ThreadParameters && thread_parameters), + (override)); + MOCK_METHOD((score::cpp::expected, Error>), Build, (), (override)); +}; + +/// RAII guard that overrides HealthMonitorBuilder::Create() for the guard's lifetime. +/// The factory can capture pre-configured mocks for injection. +/// Only one ScopedBuilderFactory may be active at a time. +class ScopedBuilderFactory +{ + public: + using Factory = std::function()>; + + explicit ScopedBuilderFactory(Factory factory); + ~ScopedBuilderFactory(); + + ScopedBuilderFactory(const ScopedBuilderFactory&) = delete; + ScopedBuilderFactory& operator=(const ScopedBuilderFactory&) = delete; + ScopedBuilderFactory(ScopedBuilderFactory&&) = delete; + ScopedBuilderFactory& operator=(ScopedBuilderFactory&&) = delete; +}; + +} // namespace score::mw::health::mocks + +#endif // SCORE_HM_HEALTH_MONITOR_MOCKS_H diff --git a/score/health_monitor/src/cpp/heartbeat/BUILD b/score/health_monitor/src/cpp/heartbeat/BUILD deleted file mode 100644 index f603e9deb..000000000 --- a/score/health_monitor/src/cpp/heartbeat/BUILD +++ /dev/null @@ -1,27 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "heartbeat_monitor", - srcs = ["heartbeat_monitor.cpp"], - hdrs = ["heartbeat_monitor.h"], - include_prefix = "score/mw/health", - strip_include_prefix = "/score/health_monitor/src/cpp/heartbeat", - visibility = ["//score:__subpackages__"], - deps = [ - "//score/health_monitor/src/cpp:common", - "@score_baselibs//score/result", - ], -) diff --git a/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.cpp b/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.cpp deleted file mode 100644 index bb4b6ba03..000000000 --- a/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#include "score/mw/health/heartbeat_monitor.h" - -namespace { -extern "C" { -using namespace score::mw::health; -using namespace score::mw::health::internal; -using namespace score::mw::health::heartbeat; - -FFICode heartbeat_monitor_builder_create(uint32_t range_min_ms, uint32_t range_max_ms, FFIHandle* heartbeat_monitor_builder_handle_out); -FFICode heartbeat_monitor_builder_destroy(FFIHandle heartbeat_monitor_builder_handle); -FFICode heartbeat_monitor_destroy(FFIHandle heartbeat_monitor_builder_handle); -FFICode heartbeat_monitor_heartbeat(FFIHandle heartbeat_monitor_builder_handle); -} - -FFIHandle heartbeat_monitor_builder_create_wrapper(uint32_t range_min_ms, uint32_t range_max_ms) -{ - FFIHandle handle{nullptr}; - auto result{heartbeat_monitor_builder_create(range_min_ms, range_max_ms, &handle)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return handle; -} - -} - -namespace score::mw::health::heartbeat -{ -HeartbeatMonitorBuilder::HeartbeatMonitorBuilder(const TimeRange& range) - : monitor_builder_handle_{heartbeat_monitor_builder_create_wrapper(range.min_ms(), range.max_ms()), - &heartbeat_monitor_builder_destroy} -{ -} - -HeartbeatMonitor::HeartbeatMonitor(FFIHandle monitor_handle) - : monitor_handle_{monitor_handle, &heartbeat_monitor_destroy} -{ -} - -void HeartbeatMonitor::heartbeat() -{ - auto monitor_handle{monitor_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - SCORE_LANGUAGE_FUTURECPP_ASSERT(heartbeat_monitor_heartbeat(monitor_handle.value()) == kSuccess); -} - -} // namespace score::mw::health::heartbeat diff --git a/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.h b/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.h deleted file mode 100644 index e6c41fe3e..000000000 --- a/score/health_monitor/src/cpp/heartbeat/heartbeat_monitor.h +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#ifndef SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H -#define SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H - -#include -#include "score/mw/health/common.h" - -namespace score::mw::health -{ -// Forward declaration -class HealthMonitor; -class HealthMonitorBuilder; -} // namespace score::mw::health - -namespace score::mw::health::heartbeat -{ -// Forward declaration -class HeartbeatMonitor; - -/// Builder for `HeartbeatMonitor`. -class HeartbeatMonitorBuilder final : public internal::RustDroppable -{ - public: - /// Create a new `HeartbeatMonitorBuilder`. - /// - /// - `range` - time range between heartbeats. - HeartbeatMonitorBuilder(const TimeRange& range); - - HeartbeatMonitorBuilder(const HeartbeatMonitorBuilder&) = delete; - HeartbeatMonitorBuilder& operator=(const HeartbeatMonitorBuilder&) = delete; - - HeartbeatMonitorBuilder(HeartbeatMonitorBuilder&&) = default; - HeartbeatMonitorBuilder& operator=(HeartbeatMonitorBuilder&&) = delete; - - protected: - std::optional _drop_by_rust_impl() - { - return monitor_builder_handle_.drop_by_rust(); - } - - private: - internal::DroppableFFIHandle monitor_builder_handle_; - - // Allow to hide drop_by_rust implementation - friend class internal::RustDroppable; - - // Allow HealthMonitorBuilder to access drop_by_rust implementation - friend class ::score::mw::health::HealthMonitorBuilder; -}; - -class HeartbeatMonitor final -{ - public: - // Delete copy, allow move - HeartbeatMonitor(const HeartbeatMonitor&) = delete; - HeartbeatMonitor& operator=(const HeartbeatMonitor&) = delete; - - HeartbeatMonitor(HeartbeatMonitor&& other) noexcept = default; - HeartbeatMonitor& operator=(HeartbeatMonitor&& other) noexcept = default; - - void heartbeat(); - - private: - explicit HeartbeatMonitor(internal::FFIHandle monitor_handle); - - // Only `HealthMonitor` is allowed to create `HeartbeatMonitor` instances. - friend class score::mw::health::HealthMonitor; - internal::DroppableFFIHandle monitor_handle_; -}; - -} // namespace score::mw::health::heartbeat - -#endif // SCORE_HM_HEARTBEAT_HEARTBEAT_MONITOR_H diff --git a/score/health_monitor/src/cpp/heartbeat_monitor.h b/score/health_monitor/src/cpp/heartbeat_monitor.h new file mode 100644 index 000000000..3057c544b --- /dev/null +++ b/score/health_monitor/src/cpp/heartbeat_monitor.h @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_HEARTBEAT_MONITOR_H +#define SCORE_HM_HEARTBEAT_MONITOR_H + +namespace score::mw::health +{ + +class HeartbeatMonitor +{ + public: + HeartbeatMonitor() = default; + virtual ~HeartbeatMonitor() = default; + + HeartbeatMonitor(const HeartbeatMonitor&) = delete; + HeartbeatMonitor& operator=(const HeartbeatMonitor&) = delete; + HeartbeatMonitor(HeartbeatMonitor&&) = delete; + HeartbeatMonitor& operator=(HeartbeatMonitor&&) = delete; + + /// Signals a heartbeat. Call periodically from the monitored thread. + virtual void Heartbeat() = 0; +}; + +} // namespace score::mw::health + +#endif // SCORE_HM_HEARTBEAT_MONITOR_H diff --git a/score/health_monitor/src/cpp/logic/BUILD b/score/health_monitor/src/cpp/logic/BUILD deleted file mode 100644 index 8162812bf..000000000 --- a/score/health_monitor/src/cpp/logic/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "logic_monitor", - srcs = ["logic_monitor.cpp"], - hdrs = ["logic_monitor.h"], - include_prefix = "score/mw/health", - strip_include_prefix = "/score/health_monitor/src/cpp/logic", - visibility = ["//score:__subpackages__"], - deps = [ - "//score/health_monitor/src/cpp:common", - "//score/health_monitor/src/cpp:tag", - "@score_baselibs//score/language/futurecpp", - "@score_baselibs//score/result", - ], -) diff --git a/score/health_monitor/src/cpp/logic/logic_monitor.cpp b/score/health_monitor/src/cpp/logic/logic_monitor.cpp deleted file mode 100644 index df10f1c55..000000000 --- a/score/health_monitor/src/cpp/logic/logic_monitor.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#include "score/mw/health/logic_monitor.h" -#include - -namespace -{ -extern "C" { -using namespace score::mw::health; -using namespace score::mw::health::internal; -using namespace score::mw::health::logic; - -FFICode logic_monitor_builder_create(const StateTag* initial_state, FFIHandle* logic_monitor_builder_handle_out); -FFICode logic_monitor_builder_destroy(FFIHandle logic_monitor_builder_handle); -FFICode logic_monitor_builder_add_state(FFIHandle logic_monitor_builder_handle, - const StateTag* state, - const StateTag* allowed_states, - size_t num_allowed_states); -FFICode logic_monitor_destroy(FFIHandle logic_monitor_handle); -FFICode logic_monitor_transition(FFIHandle logic_monitor_handle, const StateTag* target_state); -FFICode logic_monitor_state(FFIHandle logic_monitor_handle, StateTag* state_out); -} - -FFIHandle logic_monitor_builder_create_wrapper(const StateTag& initial_state) -{ - FFIHandle handle{nullptr}; - auto result{logic_monitor_builder_create(&initial_state, &handle)}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - return handle; -} -} // namespace - -namespace score::mw::health::logic -{ -LogicMonitorBuilder::LogicMonitorBuilder(const StateTag& initial_state) - : monitor_builder_handle_{logic_monitor_builder_create_wrapper(initial_state), &logic_monitor_builder_destroy} -{ -} - -LogicMonitorBuilder LogicMonitorBuilder::add_state(const StateTag& state, - const std::vector& allowed_states) && -{ - auto monitor_builder_handle{monitor_builder_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_builder_handle.has_value()); - - auto result{logic_monitor_builder_add_state( - monitor_builder_handle.value(), &state, allowed_states.data(), allowed_states.size())}; - SCORE_LANGUAGE_FUTURECPP_ASSERT(result == kSuccess); - - return std::move(*this); -} - -LogicMonitor::LogicMonitor(FFIHandle monitor_handle) : monitor_handle_{monitor_handle, &logic_monitor_destroy} {} - -score::cpp::expected LogicMonitor::transition(const StateTag& state) -{ - auto monitor_handle{monitor_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - - auto result{logic_monitor_transition(monitor_handle.value(), &state)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(state); -} - -score::cpp::expected LogicMonitor::state() -{ - auto monitor_handle{monitor_handle_.as_rust_handle()}; - SCORE_LANGUAGE_FUTURECPP_PRECONDITION(monitor_handle.has_value()); - - StateTag state_tag{""}; - auto result{logic_monitor_state(monitor_handle.value(), &state_tag)}; - if (result != kSuccess) - { - return score::cpp::unexpected(static_cast(result)); - } - - return score::cpp::expected(state_tag); -} - -} // namespace score::mw::health::logic diff --git a/score/health_monitor/src/cpp/logic/logic_monitor.h b/score/health_monitor/src/cpp/logic/logic_monitor.h deleted file mode 100644 index 3cd626049..000000000 --- a/score/health_monitor/src/cpp/logic/logic_monitor.h +++ /dev/null @@ -1,91 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#ifndef SCORE_HM_LOGIC_LOGIC_MONITOR_H -#define SCORE_HM_LOGIC_LOGIC_MONITOR_H - -#include "score/mw/health/common.h" -#include "score/mw/health/tag.h" -#include -#include - -namespace score::mw::health -{ -// Forward declaration -class HealthMonitor; -class HealthMonitorBuilder; -} // namespace score::mw::health - -namespace score::mw::health::logic -{ - -class LogicMonitorBuilder final : public internal::RustDroppable -{ - public: - /// Create a new `LogicMonitorBuilder`. - /// - /// - `initial_state` - starting point. - LogicMonitorBuilder(const StateTag& initial_state); - - LogicMonitorBuilder(const LogicMonitorBuilder&) = delete; - LogicMonitorBuilder& operator=(const LogicMonitorBuilder&) = delete; - - LogicMonitorBuilder(LogicMonitorBuilder&&) = default; - LogicMonitorBuilder& operator=(LogicMonitorBuilder&&) = delete; - - /// Add state along with allowed transitions. - /// If state already exists - it is overwritten. - LogicMonitorBuilder add_state(const StateTag& state, const std::vector& allowed_states) &&; - - protected: - std::optional _drop_by_rust_impl() - { - return monitor_builder_handle_.drop_by_rust(); - } - - private: - internal::DroppableFFIHandle monitor_builder_handle_; - - // Allow to hide drop_by_rust implementation - friend class internal::RustDroppable; - - // Allow HealthMonitorBuilder to access drop_by_rust implementation - friend class ::score::mw::health::HealthMonitorBuilder; -}; - -class LogicMonitor final -{ - public: - LogicMonitor(const LogicMonitor&) = delete; - LogicMonitor& operator=(const LogicMonitor&) = delete; - - LogicMonitor(LogicMonitor&& other) noexcept = default; - LogicMonitor& operator=(LogicMonitor&& other) noexcept = default; - - /// Perform transition to a new state. - /// On success, current state is returned. - score::cpp::expected transition(const StateTag& state); - - /// Current monitor state. - score::cpp::expected state(); - - private: - explicit LogicMonitor(internal::FFIHandle monitor_handle); - - // Only `HealthMonitor` is allowed to create `LogicMonitor` instances. - friend class score::mw::health::HealthMonitor; - internal::DroppableFFIHandle monitor_handle_; -}; - -} // namespace score::mw::health::logic - -#endif // SCORE_HM_LOGIC_LOGIC_MONITOR_H diff --git a/score/health_monitor/src/cpp/logic_monitor.h b/score/health_monitor/src/cpp/logic_monitor.h new file mode 100644 index 000000000..ecf0066ea --- /dev/null +++ b/score/health_monitor/src/cpp/logic_monitor.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_HM_LOGIC_MONITOR_H +#define SCORE_HM_LOGIC_MONITOR_H + +#include "score/mw/health/common.h" + +#include + +namespace score::mw::health +{ + +class LogicMonitor +{ + public: + LogicMonitor() = default; + virtual ~LogicMonitor() = default; + + LogicMonitor(const LogicMonitor&) = delete; + LogicMonitor& operator=(const LogicMonitor&) = delete; + LogicMonitor(LogicMonitor&&) = delete; + LogicMonitor& operator=(LogicMonitor&&) = delete; + + /// Perform transition to a new state. + /// On success, current state is returned. + virtual score::cpp::expected Transition(Tag state) = 0; + + /// Returns the current monitor state. + virtual score::cpp::expected State() = 0; +}; + +} // namespace score::mw::health + +#endif // SCORE_HM_LOGIC_MONITOR_H diff --git a/score/health_monitor/src/cpp/tag.h b/score/health_monitor/src/cpp/tag.h deleted file mode 100644 index 204cb083a..000000000 --- a/score/health_monitor/src/cpp/tag.h +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -#ifndef SCORE_HM_TAG_H -#define SCORE_HM_TAG_H - -#include -#include - -namespace score::mw::health -{ - -/// Common string-based tag. -template -class Tag -{ - public: - /// Create a new tag from a C-style string. - template - explicit Tag(const char (&tag)[N]) : data_(tag), length_(N - 1) - { - } - - bool operator==(const T& other) const noexcept - { - std::string_view this_sv{data_, length_}; - std::string_view other_sv{other.data_, other.length_}; - return this_sv.compare(other_sv) == 0; - } - - bool operator!=(const T& other) const noexcept - { - return !(*this == other); - } - - private: - /// SAFETY: This has to be FFI compatible with the Rust side representation. - const char* data_; - size_t length_; -}; - -/// Monitor tag. -class MonitorTag : public Tag -{ - public: - using Tag::Tag; -}; - -/// Deadline tag. -class DeadlineTag : public Tag -{ - public: - using Tag::Tag; -}; - -/// State tag. -class StateTag : public Tag -{ - public: - using Tag::Tag; -}; - -} // namespace score::mw::health - -#endif // SCORE_HM_TAG_H diff --git a/score/health_monitor/src/cpp/tests/deadline_monitor_test.cpp b/score/health_monitor/src/cpp/tests/deadline_monitor_test.cpp index e8c9e1371..aacd40812 100644 --- a/score/health_monitor/src/cpp/tests/deadline_monitor_test.cpp +++ b/score/health_monitor/src/cpp/tests/deadline_monitor_test.cpp @@ -12,13 +12,13 @@ ********************************************************************************/ #include "score/mw/health/deadline_monitor.h" -#include "score/mw/health/health_monitor.h" +#include "score/mw/health/health_monitor_builder.h" + #include using namespace score::mw::health; -using namespace score::mw::health::deadline; -class DeadlineMonitorBuilderFixture : public ::testing::Test +class DeadlineMonitorConfigurationFixture : public ::testing::Test { protected: void SetUp() override @@ -28,103 +28,94 @@ class DeadlineMonitorBuilderFixture : public ::testing::Test } }; -TEST_F(DeadlineMonitorBuilderFixture, New_Succeeds) +TEST_F(DeadlineMonitorConfigurationFixture, New_Succeeds) { RecordProperty("Description", "Object successfully constructed."); - DeadlineMonitorBuilder deadline_monitor_builder; + DeadlineMonitorConfiguration cfg; } -TEST_F(DeadlineMonitorBuilderFixture, AddDeadline_Succeeds) +TEST_F(DeadlineMonitorConfigurationFixture, AddDeadline_Succeeds) { RecordProperty("Description", "Deadline successfully added."); using namespace std::chrono_literals; - DeadlineTag deadline_tag{"deadline"}; TimeRange range{50ms, 150ms}; - auto deadline_monitor_builder{DeadlineMonitorBuilder{}.add_deadline(deadline_tag, range)}; + DeadlineMonitorConfiguration cfg; + cfg.AddDeadline(Tag("deadline"), range); } class DeadlineMonitorFixture : public ::testing::Test { protected: - std::optional deadline_monitor_; + std::unique_ptr deadline_monitor_; void SetUp() override { RecordProperty("TestType", "interface-test"); RecordProperty("DerivationTechnique", "explorative-testing"); - // Monitor must be obtained from HMON. - // Initialize deadline monitor builder. using namespace std::chrono_literals; - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineTag deadline_tag{"deadline"}; TimeRange range{50ms, 150ms}; - auto deadline_monitor_builder{DeadlineMonitorBuilder{}.add_deadline(deadline_tag, range)}; + DeadlineMonitorConfiguration cfg; + cfg.AddDeadline(Tag("deadline"), range); - // Build HMON, including deadline monitor. - auto hmon_build_result{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .build()}; + auto hmon_build_result{ + HealthMonitorBuilder::Create()->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(cfg)).Build()}; ASSERT_TRUE(hmon_build_result.has_value()); auto hmon{std::move(hmon_build_result.value())}; - // Get deadline monitor. - auto get_deadline_monitor_result{hmon.get_deadline_monitor(deadline_monitor_tag)}; + auto get_deadline_monitor_result{hmon->GetDeadlineMonitor(Tag("deadline_monitor"))}; ASSERT_TRUE(get_deadline_monitor_result.has_value()); deadline_monitor_ = std::move(get_deadline_monitor_result.value()); } }; -TEST_F(DeadlineMonitorFixture, GetDeadline_Succeeds) +TEST_F(DeadlineMonitorFixture, Start_Succeeds) { - RecordProperty("Description", "Deadline successfully obtained using known tag."); - // Get deadline. - auto get_deadline_result{deadline_monitor_->get_deadline(DeadlineTag{"deadline"})}; - ASSERT_TRUE(get_deadline_result.has_value()); + RecordProperty("Description", "Deadline successfully started and stopped."); + auto deadline_result{deadline_monitor_->GetDeadline(Tag("deadline"))}; + ASSERT_TRUE(deadline_result.has_value()); + auto deadline = std::move(deadline_result.value()); + + auto start_result{deadline->Start()}; + ASSERT_TRUE(start_result.has_value()); + auto stop_result{deadline->Stop()}; + ASSERT_TRUE(stop_result.has_value()); } -TEST_F(DeadlineMonitorFixture, GetDeadline_Unknown) +TEST_F(DeadlineMonitorFixture, DeadlineGuard_StartsAndStops) { - RecordProperty("Description", "Deadline failed to be obtained due to unknown tag."); - // Get deadline. - auto get_deadline_result{deadline_monitor_->get_deadline(DeadlineTag{"unknown"})}; - ASSERT_FALSE(get_deadline_result.has_value()); - ASSERT_EQ(get_deadline_result.error(), Error::NotFound); + RecordProperty("Description", "DeadlineGuard starts the deadline on construction and stops it on destruction."); + auto deadline_result{deadline_monitor_->GetDeadline(Tag("deadline"))}; + ASSERT_TRUE(deadline_result.has_value()); + auto deadline = std::move(deadline_result.value()); + + { + DeadlineGuard guard(*deadline); + } } -class DeadlineFixture : public DeadlineMonitorFixture +TEST_F(DeadlineMonitorFixture, Start_AlreadyRunning) { -}; + RecordProperty("Description", "Deadline failed to start twice."); + auto deadline_result{deadline_monitor_->GetDeadline(Tag("deadline"))}; + ASSERT_TRUE(deadline_result.has_value()); + auto deadline = std::move(deadline_result.value()); -TEST_F(DeadlineFixture, Start_Succeeds) -{ - RecordProperty("Description", "Deadline successfully started and stopped."); - // Get deadline. - DeadlineTag deadline_tag{"deadline"}; - auto get_deadline_result{deadline_monitor_->get_deadline(deadline_tag)}; - ASSERT_TRUE(get_deadline_result.has_value()); - auto deadline{std::move(get_deadline_result.value())}; - - // Try to start and stop deadline. - auto deadline_start_result{deadline.start()}; - ASSERT_TRUE(deadline_start_result.has_value()); - auto deadline_handle{std::move(deadline_start_result.value())}; - - deadline_handle.stop(); + auto first_start{deadline->Start()}; + ASSERT_TRUE(first_start.has_value()); + + auto second_start{deadline->Start()}; + ASSERT_FALSE(second_start.has_value()); + ASSERT_EQ(second_start.error(), Error::kFailed); + + auto stop_result{deadline->Stop()}; + ASSERT_TRUE(stop_result.has_value()); } -TEST_F(DeadlineFixture, Start_AlreadyRunning) +TEST_F(DeadlineMonitorFixture, Start_Unknown) { - RecordProperty("Description", "Deadline failed to start twice."); - // Get deadline. - DeadlineTag deadline_tag{"deadline"}; - auto get_deadline_result{deadline_monitor_->get_deadline(deadline_tag)}; - ASSERT_TRUE(get_deadline_result.has_value()); - auto deadline{std::move(get_deadline_result.value())}; - - // Try to start the deadline twice. - deadline.start(); - auto deadline_start_result{deadline.start()}; - ASSERT_FALSE(deadline_start_result.has_value()); - ASSERT_EQ(deadline_start_result.error(), Error::Failed); + RecordProperty("Description", "Deadline failed to start due to unknown tag."); + auto deadline_result{deadline_monitor_->GetDeadline(Tag("unknown"))}; + ASSERT_FALSE(deadline_result.has_value()); + ASSERT_EQ(deadline_result.error(), Error::kNotFound); } diff --git a/score/health_monitor/src/cpp/tests/health_monitor_test.cpp b/score/health_monitor/src/cpp/tests/health_monitor_test.cpp index abdd8d4e1..aa7667366 100644 --- a/score/health_monitor/src/cpp/tests/health_monitor_test.cpp +++ b/score/health_monitor/src/cpp/tests/health_monitor_test.cpp @@ -11,29 +11,21 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#include "score/mw/health/health_monitor.h" -#include "score/mw/health/deadline_monitor.h" -#include "score/mw/health/heartbeat_monitor.h" -#include "score/mw/health/logic_monitor.h" +#include "score/mw/health/health_monitor_builder.h" + #include using namespace score::mw::health; -using namespace score::mw::health::deadline; -using namespace score::mw::health::heartbeat; -using namespace score::mw::health::logic; -HeartbeatMonitorBuilder def_heartbeat_monitor_builder() +TimeRange def_heartbeat_range() { using namespace std::chrono_literals; - TimeRange range{100ms, 200ms}; - return HeartbeatMonitorBuilder{range}; + return TimeRange{100ms, 200ms}; } -LogicMonitorBuilder def_logic_monitor_builder() +LogicMonitorConfiguration def_logic_monitor_builder() { - StateTag state1{"state1"}; - StateTag state2{"state2"}; - return LogicMonitorBuilder{state1}.add_state(state1, {state2}).add_state(state2, {state1}); + return LogicMonitorConfiguration{Tag("state1")}.AddState(Tag("state1"), {Tag("state2")}).AddState(Tag("state2"), {Tag("state1")}); } class HealthMonitorBuilderFixture : public ::testing::Test @@ -46,28 +38,25 @@ class HealthMonitorBuilderFixture : public ::testing::Test } }; -TEST_F(HealthMonitorBuilderFixture, New_Succeeds) +TEST_F(HealthMonitorBuilderFixture, Create_Succeeds) { - RecordProperty("Description", "Object successfully constructed."); - // Check able to construct and destruct only. - HealthMonitorBuilder health_monitor_builder; + RecordProperty("Description", "Object successfully constructed via factory."); + auto builder = HealthMonitorBuilder::Create(); + ASSERT_NE(builder, nullptr); } TEST_F(HealthMonitorBuilderFixture, Build_Succeeds) { RecordProperty("Description", "Successfully build monitor containing all monitor types."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - auto heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - MonitorTag logic_monitor_tag{"logic_monitor"}; + DeadlineMonitorConfiguration deadline_monitor_builder; + auto heartbeat_range{def_heartbeat_range()}; auto logic_monitor_builder{def_logic_monitor_builder()}; - auto result{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)) - .build()}; + auto result{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range) + .AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build()}; ASSERT_TRUE(result.has_value()); } @@ -77,183 +66,169 @@ TEST_F(HealthMonitorBuilderFixture, Build_InvalidCycles) "Description", "Failed to build monitor with mismatched supervisor API cycle and internal processing cycle values."); using namespace std::chrono_literals; - auto result{HealthMonitorBuilder{}.with_supervisor_api_cycle(123ms).with_internal_processing_cycle(100ms).build()}; + auto result{ + HealthMonitorBuilder::Create()->WithSupervisorApiCycle(123ms).WithInternalProcessingCycle(100ms).Build()}; ASSERT_FALSE(result.has_value()); - ASSERT_EQ(result.error(), Error::InvalidArgument); + ASSERT_EQ(result.error(), Error::kInvalidArgument); } TEST_F(HealthMonitorBuilderFixture, Build_NoMonitors) { RecordProperty("Description", "Failed to build monitor with no monitors."); - auto result{HealthMonitorBuilder{}.build()}; + auto result{HealthMonitorBuilder::Create()->Build()}; ASSERT_FALSE(result.has_value()); - ASSERT_EQ(result.error(), Error::WrongState); + ASSERT_EQ(result.error(), Error::kWrongState); } TEST(HealthMonitor, GetDeadlineMonitor_Available) { RecordProperty("Description", "Successfully obtained deadline monitor."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - auto health_monitor{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .build() + DeadlineMonitorConfiguration deadline_monitor_builder; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .Build() .value()}; - auto result{health_monitor.get_deadline_monitor(deadline_monitor_tag)}; + auto result{health_monitor->GetDeadlineMonitor(Tag("deadline_monitor"))}; ASSERT_TRUE(result.has_value()); } TEST(HealthMonitor, GetDeadlineMonitor_Taken) { RecordProperty("Description", "Failed to reobtain already taken deadline monitor."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - auto health_monitor{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .build() + DeadlineMonitorConfiguration deadline_monitor_builder; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .Build() .value()}; - health_monitor.get_deadline_monitor(deadline_monitor_tag); - auto result{health_monitor.get_deadline_monitor(deadline_monitor_tag)}; + health_monitor->GetDeadlineMonitor(Tag("deadline_monitor")); + auto result{health_monitor->GetDeadlineMonitor(Tag("deadline_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, GetDeadlineMonitor_Unknown) { RecordProperty("Description", "Failed to obtain deadline monitor using unknown tag."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - auto health_monitor{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .build() + DeadlineMonitorConfiguration deadline_monitor_builder; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .Build() .value()}; - auto result{health_monitor.get_deadline_monitor(MonitorTag{"undefined_monitor"})}; + auto result{health_monitor->GetDeadlineMonitor(Tag("undefined_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, GetHeartbeatMonitor_Available) { RecordProperty("Description", "Successfully obtained heartbeat monitor."); - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - auto heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - auto health_monitor{HealthMonitorBuilder{} - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .build() - .value()}; + auto heartbeat_range{def_heartbeat_range()}; + auto health_monitor{ + HealthMonitorBuilder::Create()->AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range).Build().value()}; - auto result{health_monitor.get_heartbeat_monitor(heartbeat_monitor_tag)}; + auto result{health_monitor->GetHeartbeatMonitor(Tag("heartbeat_monitor"))}; ASSERT_TRUE(result.has_value()); } TEST(HealthMonitor, GetHeartbeatMonitor_Taken) { RecordProperty("Description", "Failed to reobtain already taken heartbeat monitor."); - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - HeartbeatMonitorBuilder heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - auto health_monitor{HealthMonitorBuilder{} - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .build() - .value()}; + auto heartbeat_range{def_heartbeat_range()}; + auto health_monitor{ + HealthMonitorBuilder::Create()->AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range).Build().value()}; - health_monitor.get_heartbeat_monitor(heartbeat_monitor_tag); - auto result{health_monitor.get_heartbeat_monitor(heartbeat_monitor_tag)}; + health_monitor->GetHeartbeatMonitor(Tag("heartbeat_monitor")); + auto result{health_monitor->GetHeartbeatMonitor(Tag("heartbeat_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, GetHeartbeatMonitor_Unknown) { - RecordProperty("Description", "Failed to obtain deadline monitor using unknown tag."); - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - HeartbeatMonitorBuilder heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - auto health_monitor{HealthMonitorBuilder{} - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .build() - .value()}; + RecordProperty("Description", "Failed to obtain heartbeat monitor using unknown tag."); + auto heartbeat_range{def_heartbeat_range()}; + auto health_monitor{ + HealthMonitorBuilder::Create()->AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range).Build().value()}; - auto result{health_monitor.get_heartbeat_monitor(MonitorTag{"undefined_monitor"})}; + auto result{health_monitor->GetHeartbeatMonitor(Tag("undefined_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, GetLogicMonitor_Available) { RecordProperty("Description", "Successfully obtained logic monitor."); - MonitorTag logic_monitor_tag{"logic_monitor"}; auto logic_monitor_builder{def_logic_monitor_builder()}; - auto health_monitor{ - HealthMonitorBuilder{}.add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)).build().value()}; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build() + .value()}; - auto result{health_monitor.get_logic_monitor(logic_monitor_tag)}; + auto result{health_monitor->GetLogicMonitor(Tag("logic_monitor"))}; ASSERT_TRUE(result.has_value()); } TEST(HealthMonitor, GetLogicMonitor_Taken) { RecordProperty("Description", "Failed to reobtain already taken logic monitor."); - MonitorTag logic_monitor_tag{"logic_monitor"}; - LogicMonitorBuilder logic_monitor_builder{def_logic_monitor_builder()}; - auto health_monitor{ - HealthMonitorBuilder{}.add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)).build().value()}; + LogicMonitorConfiguration logic_monitor_builder{def_logic_monitor_builder()}; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build() + .value()}; - health_monitor.get_logic_monitor(logic_monitor_tag); - auto result{health_monitor.get_logic_monitor(logic_monitor_tag)}; + health_monitor->GetLogicMonitor(Tag("logic_monitor")); + auto result{health_monitor->GetLogicMonitor(Tag("logic_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, GetLogicMonitor_Unknown) { - RecordProperty("Description", "Failed to obtain deadline monitor using unknown tag."); - MonitorTag logic_monitor_tag{"logic_monitor"}; - LogicMonitorBuilder logic_monitor_builder{def_logic_monitor_builder()}; - auto health_monitor{ - HealthMonitorBuilder{}.add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)).build().value()}; + RecordProperty("Description", "Failed to obtain logic monitor using unknown tag."); + LogicMonitorConfiguration logic_monitor_builder{def_logic_monitor_builder()}; + auto health_monitor{HealthMonitorBuilder::Create() + ->AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build() + .value()}; - auto result{health_monitor.get_logic_monitor(MonitorTag{"undefined_monitor"})}; + auto result{health_monitor->GetLogicMonitor(Tag("undefined_monitor"))}; ASSERT_FALSE(result.has_value()); } TEST(HealthMonitor, Start_Succeeds) { RecordProperty("Description", "Successfully started monitor containing all monitor types."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - auto heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - MonitorTag logic_monitor_tag{"logic_monitor"}; + DeadlineMonitorConfiguration deadline_monitor_builder; + auto heartbeat_range{def_heartbeat_range()}; auto logic_monitor_builder{def_logic_monitor_builder()}; - auto health_monitor{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)) - .build() + auto health_monitor{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range) + .AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build() .value()}; - health_monitor.get_deadline_monitor(deadline_monitor_tag); - health_monitor.get_heartbeat_monitor(heartbeat_monitor_tag); - health_monitor.get_logic_monitor(logic_monitor_tag); + health_monitor->GetDeadlineMonitor(Tag("deadline_monitor")); + health_monitor->GetHeartbeatMonitor(Tag("heartbeat_monitor")); + health_monitor->GetLogicMonitor(Tag("logic_monitor")); - health_monitor.start(); + health_monitor->Start(); } TEST(HealthMonitor, Start_MonitorsNotTaken) { RecordProperty("Description", "Failed to start of a health monitor with no monitors obtained."); - MonitorTag deadline_monitor_tag{"deadline_monitor"}; - DeadlineMonitorBuilder deadline_monitor_builder; - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; - auto heartbeat_monitor_builder{def_heartbeat_monitor_builder()}; - MonitorTag logic_monitor_tag{"logic_monitor"}; + DeadlineMonitorConfiguration deadline_monitor_builder; + auto heartbeat_range{def_heartbeat_range()}; auto logic_monitor_builder{def_logic_monitor_builder()}; - auto health_monitor{HealthMonitorBuilder{} - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)) - .build() + auto health_monitor{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range) + .AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .Build() .value()}; // `SIGABRT` is expected. - ASSERT_DEATH({ health_monitor.start(); }, ""); + ASSERT_DEATH({ health_monitor->Start(); }, ""); } diff --git a/score/health_monitor/src/cpp/tests/heartbeat_monitor_test.cpp b/score/health_monitor/src/cpp/tests/heartbeat_monitor_test.cpp index e113e7a8e..ad32d83af 100644 --- a/score/health_monitor/src/cpp/tests/heartbeat_monitor_test.cpp +++ b/score/health_monitor/src/cpp/tests/heartbeat_monitor_test.cpp @@ -12,22 +12,11 @@ ********************************************************************************/ #include "score/mw/health/heartbeat_monitor.h" -#include "score/mw/health/health_monitor.h" +#include "score/mw/health/health_monitor_builder.h" + #include using namespace score::mw::health; -using namespace score::mw::health::heartbeat; - -TEST(HeartbeatMonitorBuilder, New_Succeeds) -{ - RecordProperty("TestType", "interface-test"); - RecordProperty("DerivationTechnique", "explorative-testing"); - RecordProperty("Description", "Object successfully constructed."); - - using namespace std::chrono_literals; - TimeRange range{100ms, 200ms}; - HeartbeatMonitorBuilder heartbeat_monitor_builder{range}; -} TEST(HeartbeatMonitor, Heartbeat_Succeeds) { @@ -36,24 +25,19 @@ TEST(HeartbeatMonitor, Heartbeat_Succeeds) RecordProperty("Description", "Heartbeat successfully used."); // Monitor must be obtained from HMON. - // Initialize heartbeat monitor builder. using namespace std::chrono_literals; - MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; TimeRange range{100ms, 200ms}; - HeartbeatMonitorBuilder heartbeat_monitor_builder{range}; // Build HMON, including heartbeat monitor. - auto hmon_build_result{HealthMonitorBuilder{} - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .build()}; + auto hmon_build_result{HealthMonitorBuilder::Create()->AddHeartbeatMonitor(Tag("heartbeat_monitor"), range).Build()}; ASSERT_TRUE(hmon_build_result.has_value()); auto hmon{std::move(hmon_build_result.value())}; // Get heartbeat monitor. - auto get_heartbeat_monitor_result{hmon.get_heartbeat_monitor(heartbeat_monitor_tag)}; + auto get_heartbeat_monitor_result{hmon->GetHeartbeatMonitor(Tag("heartbeat_monitor"))}; ASSERT_TRUE(get_heartbeat_monitor_result.has_value()); auto heartbeat_monitor{std::move(get_heartbeat_monitor_result.value())}; // Check heartbeat is not failing. - heartbeat_monitor.heartbeat(); + heartbeat_monitor->Heartbeat(); } diff --git a/score/health_monitor/src/cpp/tests/integrated_test.cpp b/score/health_monitor/src/cpp/tests/integrated_test.cpp index bf37f631e..53f1f1fda 100644 --- a/score/health_monitor/src/cpp/tests/integrated_test.cpp +++ b/score/health_monitor/src/cpp/tests/integrated_test.cpp @@ -11,8 +11,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/mw/health/common.h" -#include "score/mw/health/health_monitor.h" -#include "score/mw/health/tag.h" +#include "score/mw/health/health_monitor_builder.h" + #include using namespace score::mw::health; @@ -39,98 +39,85 @@ TEST_F(HealthMonitorTest, Integrated) // - Affinity set to first core. // - Stack size set to common default stack size. // - Scheduler not set - avoid additional caps required. - auto thread_parameters{ThreadParameters{}.affinity({0}).stack_size(8 * 1024 * 1024)}; + ThreadParameters thread_parameters{}; + thread_parameters.affinity = {0}; + thread_parameters.stack_size = (8 * 1024 * 1024); // Setup deadline monitor construction. - const MonitorTag deadline_monitor_tag{"deadline_monitor"}; - auto deadline_monitor_builder = - deadline::DeadlineMonitorBuilder() - .add_deadline(DeadlineTag("deadline_1"), - TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))) - .add_deadline(DeadlineTag("deadline_2"), - TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))); + DeadlineMonitorConfiguration deadline_monitor_builder{}; + deadline_monitor_builder + .AddDeadline(Tag("deadline_1"), TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))) + .AddDeadline(Tag("deadline_2"), TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))); // Setup heartbeat monitor construction. - const MonitorTag heartbeat_monitor_tag{"heartbeat_monitor"}; const TimeRange heartbeat_range{std::chrono::milliseconds{100}, std::chrono::milliseconds{200}}; - auto heartbeat_monitor_builder = heartbeat::HeartbeatMonitorBuilder(heartbeat_range); // Setup logic monitor construction. - const MonitorTag logic_monitor_tag{"logic_monitor"}; - StateTag from_state{"from_state"}; - StateTag to_state{"to_state"}; - auto logic_monitor_builder = - logic::LogicMonitorBuilder{from_state}.add_state(from_state, std::vector{to_state}).add_state(to_state, {}); - - auto hmon_result{HealthMonitorBuilder() - .add_deadline_monitor(deadline_monitor_tag, std::move(deadline_monitor_builder)) - .add_heartbeat_monitor(heartbeat_monitor_tag, std::move(heartbeat_monitor_builder)) - .add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)) - .with_internal_processing_cycle(std::chrono::milliseconds(50)) - .with_supervisor_api_cycle(std::chrono::milliseconds(50)) - .thread_parameters(std::move(thread_parameters)) - .build()}; + LogicMonitorConfiguration logic_monitor_builder{Tag("from_state")}; + logic_monitor_builder.AddState(Tag("from_state"), {Tag("to_state")}).AddState(Tag("to_state"), {}); + + auto hmon_result{HealthMonitorBuilder::Create() + ->AddDeadlineMonitor(Tag("deadline_monitor"), std::move(deadline_monitor_builder)) + .AddHeartbeatMonitor(Tag("heartbeat_monitor"), heartbeat_range) + .AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)) + .WithInternalProcessingCycle(std::chrono::milliseconds(50)) + .WithSupervisorApiCycle(std::chrono::milliseconds(50)) + .WithThreadParameters(std::move(thread_parameters)) + .Build()}; EXPECT_TRUE(hmon_result.has_value()); auto hm{std::move(hmon_result.value())}; // Obtain deadline monitor from HMON. - auto deadline_monitor_res = hm.get_deadline_monitor(deadline_monitor_tag); + auto deadline_monitor_res = hm->GetDeadlineMonitor(Tag("deadline_monitor")); EXPECT_TRUE(deadline_monitor_res.has_value()); { // Try again to get the same monitor. - auto deadline_monitor_res = hm.get_deadline_monitor(deadline_monitor_tag); + auto deadline_monitor_res = hm->GetDeadlineMonitor(Tag("deadline_monitor")); EXPECT_FALSE(deadline_monitor_res.has_value()); } auto deadline_mon = std::move(*deadline_monitor_res); // Obtain heartbeat monitor from HMON. - auto heartbeat_monitor_res{hm.get_heartbeat_monitor(heartbeat_monitor_tag)}; + auto heartbeat_monitor_res{hm->GetHeartbeatMonitor(Tag("heartbeat_monitor"))}; EXPECT_TRUE(heartbeat_monitor_res.has_value()); { // Try again to get the same monitor. - auto heartbeat_monitor_res{hm.get_heartbeat_monitor(heartbeat_monitor_tag)}; + auto heartbeat_monitor_res{hm->GetHeartbeatMonitor(Tag("heartbeat_monitor"))}; EXPECT_FALSE(heartbeat_monitor_res.has_value()); } auto heartbeat_monitor{std::move(*heartbeat_monitor_res)}; // Obtain logic monitor from HMON. - auto logic_monitor_res{hm.get_logic_monitor(logic_monitor_tag)}; + auto logic_monitor_res{hm->GetLogicMonitor(Tag("logic_monitor"))}; EXPECT_TRUE(logic_monitor_res.has_value()); { // Try again to get the same monitor. - auto logic_monitor_res{hm.get_logic_monitor(logic_monitor_tag)}; + auto logic_monitor_res{hm->GetLogicMonitor(Tag("logic_monitor"))}; EXPECT_FALSE(logic_monitor_res.has_value()); } auto logic_monitor{std::move(*logic_monitor_res)}; // Start HMON. - hm.start(); + hm->Start(); - heartbeat_monitor.heartbeat(); + heartbeat_monitor->Heartbeat(); - EXPECT_TRUE(logic_monitor.transition(to_state).has_value()); - auto current_state_res{logic_monitor.state()}; + EXPECT_TRUE(logic_monitor->Transition(Tag("to_state")).has_value()); + auto current_state_res{logic_monitor->State()}; EXPECT_TRUE(current_state_res.has_value()); - EXPECT_EQ(current_state_res.value(), to_state); - - auto deadline_res = deadline_mon.get_deadline(DeadlineTag("deadline_1")); - ASSERT_TRUE(deadline_res.has_value()); - auto& deadline{deadline_res.value()}; + EXPECT_EQ(current_state_res.value(), Tag("to_state")); { - auto first_start_res{deadline.start()}; - EXPECT_TRUE(first_start_res.has_value()); - auto deadline_guard{std::move(first_start_res.value())}; - - auto second_start_res{deadline_res.value().start()}; - EXPECT_FALSE(second_start_res.has_value()); - EXPECT_EQ(second_start_res.error(), ::score::mw::health::Error::WrongState); - deadline_guard.stop(); + auto deadline_result{deadline_mon->GetDeadline(Tag("deadline_1"))}; + EXPECT_TRUE(deadline_result.has_value()); + auto deadline = std::move(deadline_result.value()); + + DeadlineGuard guard(*deadline); } } diff --git a/score/health_monitor/src/cpp/tests/logic_monitor_test.cpp b/score/health_monitor/src/cpp/tests/logic_monitor_test.cpp index d2b27ada7..75e0516cc 100644 --- a/score/health_monitor/src/cpp/tests/logic_monitor_test.cpp +++ b/score/health_monitor/src/cpp/tests/logic_monitor_test.cpp @@ -12,13 +12,13 @@ ********************************************************************************/ #include "score/mw/health/logic_monitor.h" -#include "score/mw/health/health_monitor.h" +#include "score/mw/health/health_monitor_builder.h" + #include using namespace score::mw::health; -using namespace score::mw::health::logic; -class LogicMonitorBuilderFixture : public ::testing::Test +class LogicMonitorConfigurationFixture : public ::testing::Test { protected: void SetUp() override @@ -28,27 +28,22 @@ class LogicMonitorBuilderFixture : public ::testing::Test } }; -TEST_F(LogicMonitorBuilderFixture, New_Succeeds) +TEST_F(LogicMonitorConfigurationFixture, New_Succeeds) { RecordProperty("Description", "Object successfully constructed."); - StateTag state1{"state1"}; - LogicMonitorBuilder logic_monitor_builder{state1}; + LogicMonitorConfiguration logic_monitor_builder{Tag("state1")}; } -TEST_F(LogicMonitorBuilderFixture, AddState_Succeeds) +TEST_F(LogicMonitorConfigurationFixture, AddState_Succeeds) { RecordProperty("Description", "State successfully added."); - StateTag state1{"state1"}; - StateTag state2{"state2"}; - auto logic_monitor_builder{LogicMonitorBuilder{state1}.add_state(state1, {state2})}; + auto logic_monitor_builder{LogicMonitorConfiguration{Tag("state1")}.AddState(Tag("state1"), {Tag("state2")})}; } class LogicMonitorFixture : public ::testing::Test { protected: - std::optional logic_monitor_; - StateTag state1_{"state1"}; - StateTag state2_{"state2"}; + std::unique_ptr logic_monitor_; void SetUp() override { @@ -57,17 +52,17 @@ class LogicMonitorFixture : public ::testing::Test // Monitor must be obtained from HMON. // Initialize logic monitor builder. - MonitorTag logic_monitor_tag{"logic_monitor"}; - auto logic_monitor_builder{LogicMonitorBuilder{state1_}.add_state(state1_, {state2_}).add_state(state2_, {})}; + auto logic_monitor_builder{ + LogicMonitorConfiguration{Tag("state1")}.AddState(Tag("state1"), {Tag("state2")}).AddState(Tag("state2"), {})}; // Build HMON, including logic monitor. auto hmon_build_result{ - HealthMonitorBuilder{}.add_logic_monitor(logic_monitor_tag, std::move(logic_monitor_builder)).build()}; + HealthMonitorBuilder::Create()->AddLogicMonitor(Tag("logic_monitor"), std::move(logic_monitor_builder)).Build()}; ASSERT_TRUE(hmon_build_result.has_value()); auto hmon{std::move(hmon_build_result.value())}; // Get logic monitor. - auto get_logic_monitor_result{hmon.get_logic_monitor(logic_monitor_tag)}; + auto get_logic_monitor_result{hmon->GetLogicMonitor(Tag("logic_monitor"))}; ASSERT_TRUE(get_logic_monitor_result.has_value()); logic_monitor_ = std::move(get_logic_monitor_result.value()); } @@ -77,52 +72,52 @@ TEST_F(LogicMonitorFixture, Transition_Succeeds) { RecordProperty("Description", "Monitor successfully transitioned to an allowed state."); // State transition. - auto transition_result{logic_monitor_->transition(state2_)}; + auto transition_result{logic_monitor_->Transition(Tag("state2"))}; ASSERT_TRUE(transition_result.has_value()); - ASSERT_EQ(transition_result.value(), state2_); + ASSERT_EQ(transition_result.value(), Tag("state2")); } TEST_F(LogicMonitorFixture, Transition_Unknown) { RecordProperty("Description", "Monitor failed to transition into unknown state."); // State transition. - auto transition_result{logic_monitor_->transition(StateTag{"unknown"})}; + auto transition_result{logic_monitor_->Transition(Tag("unknown"))}; ASSERT_FALSE(transition_result.has_value()); - ASSERT_EQ(transition_result.error(), Error::Failed); + ASSERT_EQ(transition_result.error(), Error::kFailed); } TEST_F(LogicMonitorFixture, Transition_Invalid) { RecordProperty("Description", "Monitor failed to transition from invalid state."); // State transition into invalid state. - logic_monitor_->transition(StateTag{"unknown"}); + logic_monitor_->Transition(Tag("unknown")); // State transition. - auto transition_result{logic_monitor_->transition(state2_)}; + auto transition_result{logic_monitor_->Transition(Tag("state2"))}; ASSERT_FALSE(transition_result.has_value()); - ASSERT_EQ(transition_result.error(), Error::Failed); + ASSERT_EQ(transition_result.error(), Error::kFailed); } TEST_F(LogicMonitorFixture, State_Succeeds) { RecordProperty("Description", "Successfully obtained current state."); // State transition. - logic_monitor_->transition(state2_); + logic_monitor_->Transition(Tag("state2")); // Get state. - auto state_result{logic_monitor_->state()}; + auto state_result{logic_monitor_->State()}; ASSERT_TRUE(state_result.has_value()); - ASSERT_EQ(state_result.value(), state2_); + ASSERT_EQ(state_result.value(), Tag("state2")); } TEST_F(LogicMonitorFixture, State_Invalid) { RecordProperty("Description", "Failed to obtain current state while being in an invalid state."); // State transition. - logic_monitor_->transition(StateTag{"unknown"}); + logic_monitor_->Transition(Tag("unknown")); // Get state. - auto state_result{logic_monitor_->state()}; + auto state_result{logic_monitor_->State()}; ASSERT_FALSE(state_result.has_value()); - ASSERT_EQ(state_result.error(), Error::Failed); + ASSERT_EQ(state_result.error(), Error::kFailed); } diff --git a/score/health_monitor/src/cpp/tests/mock_test.cpp b/score/health_monitor/src/cpp/tests/mock_test.cpp new file mode 100644 index 000000000..da6c039fa --- /dev/null +++ b/score/health_monitor/src/cpp/tests/mock_test.cpp @@ -0,0 +1,235 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "score/mw/health/health_monitor_mocks.h" + +#include "score/mw/health/deadline_monitor.h" + +#include +#include +#include + +using namespace score::mw::health; +using namespace score::mw::health::mocks; +using ::testing::_; +using ::testing::Return; + +/// Example application component that depends on the HealthMonitor interface. +/// This demonstrates that user code can be tested with mocks alone, without linking +/// the Rust FFI library. +class ExampleComponent +{ + public: + explicit ExampleComponent(HealthMonitor& health_monitor, Tag heartbeat_tag, Tag deadline_tag) + : health_monitor_(health_monitor), heartbeat_tag_(heartbeat_tag), deadline_tag_(deadline_tag) + { + } + + bool Initialize() + { + auto hb_result = health_monitor_.GetHeartbeatMonitor(heartbeat_tag_); + if (!hb_result.has_value()) + { + return false; + } + heartbeat_monitor_ = std::move(hb_result.value()); + + auto dl_result = health_monitor_.GetDeadlineMonitor(deadline_tag_); + if (!dl_result.has_value()) + { + return false; + } + deadline_monitor_ = std::move(dl_result.value()); + + return true; + } + + void SendHeartbeat() + { + if (heartbeat_monitor_) + { + heartbeat_monitor_->Heartbeat(); + } + } + + bool RunTimedOperation() + { + if (!deadline_monitor_) + { + return false; + } + + auto deadline_result = deadline_monitor_->GetDeadline(Tag("operation")); + if (!deadline_result.has_value()) + { + return false; + } + + { + DeadlineGuard guard(*deadline_result.value()); + } + + return true; + } + + private: + HealthMonitor& health_monitor_; + Tag heartbeat_tag_; + Tag deadline_tag_; + std::unique_ptr heartbeat_monitor_; + std::unique_ptr deadline_monitor_; +}; + +class MockTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "unit-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + } +}; + +TEST_F(MockTest, ComponentInitialization_WithMocks) +{ + RecordProperty("Description", + "Verify that user code can be initialized and tested using mock objects without the FFI library."); + + HealthMonitorMock health_monitor_mock; + + auto heartbeat_mock = std::make_unique(); + auto deadline_mock = std::make_unique(); + + EXPECT_CALL(health_monitor_mock, GetHeartbeatMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(heartbeat_mock)))); + EXPECT_CALL(health_monitor_mock, GetDeadlineMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(deadline_mock)))); + + ExampleComponent component(health_monitor_mock, Tag("heartbeat"), Tag("deadline")); + ASSERT_TRUE(component.Initialize()); +} + +TEST_F(MockTest, ComponentHeartbeat_WithMocks) +{ + RecordProperty("Description", "Verify heartbeat is forwarded through mock."); + + HealthMonitorMock health_monitor_mock; + + auto heartbeat_mock = std::make_unique(); + auto* heartbeat_mock_ptr = heartbeat_mock.get(); + auto deadline_mock = std::make_unique(); + + EXPECT_CALL(health_monitor_mock, GetHeartbeatMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(heartbeat_mock)))); + EXPECT_CALL(health_monitor_mock, GetDeadlineMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(deadline_mock)))); + + ExampleComponent component(health_monitor_mock, Tag("heartbeat"), Tag("deadline")); + ASSERT_TRUE(component.Initialize()); + + EXPECT_CALL(*heartbeat_mock_ptr, Heartbeat()).Times(1); + component.SendHeartbeat(); +} + +TEST_F(MockTest, ComponentTimedOperation_WithMocks) +{ + RecordProperty("Description", "Verify deadline start/stop is forwarded through mocks."); + + HealthMonitorMock health_monitor_mock; + + auto heartbeat_mock = std::make_unique(); + auto deadline_mock = std::make_unique(); + auto* deadline_mock_ptr = deadline_mock.get(); + + EXPECT_CALL(health_monitor_mock, GetHeartbeatMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(heartbeat_mock)))); + EXPECT_CALL(health_monitor_mock, GetDeadlineMonitor(_)) + .WillOnce(Return(::testing::ByMove(std::move(deadline_mock)))); + + ExampleComponent component(health_monitor_mock, Tag("heartbeat"), Tag("deadline")); + ASSERT_TRUE(component.Initialize()); + + auto deadline_mock_obj = std::make_unique(); + EXPECT_CALL(*deadline_mock_obj, Start()).WillOnce(Return(::score::cpp::expected_blank())); + EXPECT_CALL(*deadline_mock_obj, Stop()).WillOnce(Return(::score::cpp::expected_blank())); + + EXPECT_CALL(*deadline_mock_ptr, GetDeadline(_)) + .WillOnce(Return( + ::testing::ByMove(::score::cpp::expected, Error>(std::move(deadline_mock_obj))))); + + ASSERT_TRUE(component.RunTimedOperation()); +} + +TEST_F(MockTest, ComponentInitialization_FailsOnMissingMonitor) +{ + RecordProperty("Description", "Verify component handles initialization failure gracefully."); + + HealthMonitorMock health_monitor_mock; + + EXPECT_CALL(health_monitor_mock, GetHeartbeatMonitor(_)) + .WillOnce(Return(::testing::ByMove(score::cpp::unexpected(Error::kNotFound)))); + + ExampleComponent component(health_monitor_mock, Tag("heartbeat"), Tag("deadline")); + ASSERT_FALSE(component.Initialize()); +} + +TEST_F(MockTest, BuilderMock_ViaCreate) +{ + RecordProperty("Description", + "Verify code that calls HealthMonitorBuilder::Create() can be tested via ScopedBuilderFactory."); + + using namespace std::chrono_literals; + + auto mock_builder = std::make_unique(); + auto* builder_mock = mock_builder.get(); + + EXPECT_CALL(*builder_mock, AddHeartbeatMonitor(_, _)).WillOnce(::testing::ReturnRef(*builder_mock)); + EXPECT_CALL(*builder_mock, WithSupervisorApiCycle(_)).WillOnce(::testing::ReturnRef(*builder_mock)); + EXPECT_CALL(*builder_mock, WithInternalProcessingCycle(_)).WillOnce(::testing::ReturnRef(*builder_mock)); + EXPECT_CALL(*builder_mock, Build()).WillOnce([]() { + return std::make_unique(); + }); + + ScopedBuilderFactory scoped_factory([&mock_builder]() { + return std::move(mock_builder); + }); + + TimeRange range{100ms, 200ms}; + + auto builder = HealthMonitorBuilder::Create(); + + auto result = builder->AddHeartbeatMonitor(Tag("hb"), range) + .WithSupervisorApiCycle(100ms) + .WithInternalProcessingCycle(100ms) + .Build(); + ASSERT_TRUE(result.has_value()); + ASSERT_NE(result.value(), nullptr); +} + +TEST_F(MockTest, LogicMonitorMock_Transition) +{ + RecordProperty("Description", "Verify LogicMonitorMock can simulate state transitions."); + + LogicMonitorMock logic_mock; + + EXPECT_CALL(logic_mock, Transition(_)).WillOnce(Return(score::cpp::expected(Tag{"stopped"}))); + EXPECT_CALL(logic_mock, State()).WillOnce(Return(score::cpp::expected(Tag{"stopped"}))); + + auto transition_result = logic_mock.Transition(Tag("stopped")); + ASSERT_TRUE(transition_result.has_value()); + ASSERT_EQ(transition_result.value(), Tag("stopped")); + + auto state_result = logic_mock.State(); + ASSERT_TRUE(state_result.has_value()); + ASSERT_EQ(state_result.value(), Tag("stopped")); +} diff --git a/score/health_monitor/src/cpp/tests/time_range_test.cpp b/score/health_monitor/src/cpp/tests/time_range_test.cpp index 47c0c7bab..cca69d857 100644 --- a/score/health_monitor/src/cpp/tests/time_range_test.cpp +++ b/score/health_monitor/src/cpp/tests/time_range_test.cpp @@ -46,6 +46,6 @@ TEST_F(TimeRangeFixture, MinMax) RecordProperty("Description", "`min` and `max` member functions are returning correct values."); using namespace std::chrono_literals; TimeRange range{123ms, 456ms}; - ASSERT_EQ(range.min_ms(), 123); - ASSERT_EQ(range.max_ms(), 456); + ASSERT_EQ(range.Min(), 123ms); + ASSERT_EQ(range.Max(), 456ms); } diff --git a/tests/integration/complex_monitoring/component_complex_monitoring.cpp b/tests/integration/complex_monitoring/component_complex_monitoring.cpp index dd134d8ed..160387175 100644 --- a/tests/integration/complex_monitoring/component_complex_monitoring.cpp +++ b/tests/integration/complex_monitoring/component_complex_monitoring.cpp @@ -17,7 +17,7 @@ #include "score/mw/log/rust/stdout_logger_init.h" #include "tests/utils/test_helper/test_helper.hpp" #include -#include +#include TEST(ComplexMonitoring, ComponentComplexMonitoring) { @@ -28,21 +28,20 @@ TEST(ComplexMonitoring, ComponentComplexMonitoring) using namespace score::mw::health; using namespace std::chrono_literals; - auto hm_result = HealthMonitorBuilder() - .add_heartbeat_monitor(MonitorTag("complex_monitoring_monitor"), - heartbeat::HeartbeatMonitorBuilder(TimeRange(50ms, 150ms))) - .with_internal_processing_cycle(50ms) - .with_supervisor_api_cycle(50ms) - .build(); + auto hm_result = HealthMonitorBuilder::Create() + ->AddHeartbeatMonitor(Tag("complex_monitoring_monitor"), TimeRange(50ms, 150ms)) + .WithInternalProcessingCycle(50ms) + .WithSupervisorApiCycle(50ms) + .Build(); ASSERT_TRUE(hm_result.has_value()) << "Failed to build HealthMonitor"; auto hm = std::move(*hm_result); - auto heartbeat_monitor_result = hm.get_heartbeat_monitor(MonitorTag("complex_monitoring_monitor")); + auto heartbeat_monitor_result = hm->GetHeartbeatMonitor(Tag("complex_monitoring_monitor")); ASSERT_TRUE(heartbeat_monitor_result.has_value()) << "Failed to get heartbeat monitor"; auto heartbeat_monitor = std::move(*heartbeat_monitor_result); - hm.start(); + hm->Start(); TEST_STEP("Report running") { @@ -56,7 +55,7 @@ TEST(ComplexMonitoring, ComponentComplexMonitoring) while (std::chrono::steady_clock::now() < time_to_report_checkpoints_until) { std::this_thread::sleep_for(75ms); - heartbeat_monitor.heartbeat(); + heartbeat_monitor->Heartbeat(); } EXPECT_FALSE(TestRunner::exitRequested) << "Process should not be terminated yet"; }