Skip to content

Commit f07c0db

Browse files
committed
hmon: add heartbeat monitor
Add heartbeat monitor HMON.
1 parent c8b48ee commit f07c0db

14 files changed

Lines changed: 1624 additions & 142 deletions

File tree

Cargo.lock

Lines changed: 309 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"src/health_monitoring_lib",
77
"examples/rust_supervised_app",
88
]
9+
default-members = ["src/health_monitoring_lib"]
910

1011
[workspace.package]
1112
edition = "2021"
@@ -18,7 +19,6 @@ libc = "0.2.177"
1819
clap = { version = "4.5.49", features = ["derive"] }
1920
signal-hook = "0.3.18"
2021

21-
monitor_rs = { path = "src/launch_manager_daemon/health_monitor_lib/rust_bindings" } # Temporary API
2222
health_monitoring_lib = { path = "src/health_monitoring_lib" }
2323
score_log = { git = "https://github.com/eclipse-score/baselibs_rust.git", tag = "v0.0.4" }
2424
score_testing_macros = { git = "https://github.com/eclipse-score/baselibs_rust.git", tag = "v0.0.4" }
@@ -28,3 +28,6 @@ containers = { git = "https://github.com/eclipse-score/baselibs_rust.git", tag =
2828
[workspace.lints.clippy]
2929
std_instead_of_core = "warn"
3030
alloc_instead_of_core = "warn"
31+
32+
[workspace.lints.rust]
33+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }

src/health_monitoring_lib/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ CC_HDRS = [
4242
rust_library(
4343
name = "health_monitoring_lib",
4444
srcs = glob(["rust/**/*.rs"]),
45+
crate_features = ["score_supervisor_api_client"],
4546
proc_macro_deps = PROC_MACRO_DEPS,
4647
visibility = ["//visibility:public"],
4748
deps = COMMON_DEPS,
@@ -64,6 +65,7 @@ cc_library(
6465
rust_static_library(
6566
name = "health_monitoring_lib_ffi",
6667
srcs = glob(["rust/**/*.rs"]),
68+
crate_features = ["score_supervisor_api_client"],
6769
crate_name = "health_monitoring_lib",
6870
proc_macro_deps = [
6971
"@score_baselibs_rust//src/testing_macros:score_testing_macros",

src/health_monitoring_lib/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ workspace = true
1818
score_log.workspace = true
1919
score_testing_macros.workspace = true
2020
containers.workspace = true
21-
monitor_rs.workspace = true
21+
monitor_rs = { path = "../launch_manager_daemon/health_monitor_lib/rust_bindings", optional = true } # Temporary API
2222

2323
[dev-dependencies]
2424
stdout_logger.workspace = true
2525

26+
[target.'cfg(loom)'.dependencies]
27+
loom = { version = "0.7", features = ["checkpoint"] }
28+
2629
[features]
27-
default = []
30+
default = ["stub_supervisor_api_client"]
2831
stub_supervisor_api_client = []
32+
score_supervisor_api_client = ["monitor_rs"]

src/health_monitoring_lib/rust/common.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use core::fmt::Debug;
1515
use core::hash::Hash;
1616
use core::time::Duration;
1717
use std::sync::Arc;
18+
use std::time::Instant;
19+
1820
/// Unique identifier for deadlines.
1921
#[derive(Clone, Copy, Eq)]
2022
#[repr(C)]
@@ -90,6 +92,7 @@ impl From<&str> for IdentTag {
9092
}
9193
}
9294

95+
/// Range of accepted time.
9396
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9497
pub struct TimeRange {
9598
pub min: Duration,
@@ -103,16 +106,34 @@ impl TimeRange {
103106
}
104107
}
105108

109+
/// Get duration as [`u32`].
110+
pub(crate) fn duration_to_u32(duration: Duration) -> u32 {
111+
let millis = duration.as_millis();
112+
u32::try_from(millis).expect("Monitor running for too long")
113+
}
114+
115+
/// Heartbeat monitor error subgroup.
116+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, crate::log::ScoreDebug)]
117+
pub(crate) enum HeartbeatMonitorEvaluationError {
118+
/// Multiple heartbeats were observed in the same epoch.
119+
MultipleHeartbeats,
120+
}
121+
106122
/// Errors that can occur during monitor evaluation.
107123
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, crate::log::ScoreDebug)]
108124
pub(crate) enum MonitorEvaluationError {
109125
TooEarly,
110126
TooLate,
127+
HeartbeatSpecific(HeartbeatMonitorEvaluationError),
111128
}
112129

113130
/// Trait for evaluating monitors and reporting errors to be used by HealthMonitor.
114131
pub(crate) trait MonitorEvaluator {
115-
fn evaluate(&self, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError));
132+
/// Run monitor evaluation.
133+
///
134+
/// - `hmon_starting_point` - starting point of all monitors.
135+
/// - `on_error` - error handling, containing tag of failing object and error code.
136+
fn evaluate(&self, hmon_starting_point: Instant, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError));
116137
}
117138

118139
/// Handle to a monitor evaluator, allowing for dynamic dispatch.
@@ -127,8 +148,8 @@ impl MonitorEvalHandle {
127148
}
128149

129150
impl MonitorEvaluator for MonitorEvalHandle {
130-
fn evaluate(&self, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError)) {
131-
self.inner.evaluate(on_error)
151+
fn evaluate(&self, hmon_starting_point: Instant, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError)) {
152+
self.inner.evaluate(hmon_starting_point, on_error)
132153
}
133154
}
134155

src/health_monitoring_lib/rust/deadline/deadline_monitor.rs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
// *******************************************************************************
1313
use super::common::DeadlineTemplate;
14-
use crate::common::{IdentTag, MonitorEvalHandle, MonitorEvaluationError, MonitorEvaluator, TimeRange};
14+
use crate::common::{
15+
duration_to_u32, IdentTag, MonitorEvalHandle, MonitorEvaluationError, MonitorEvaluator, TimeRange,
16+
};
1517
use crate::{
1618
deadline::{
1719
common::StateIndex,
@@ -96,7 +98,7 @@ impl DeadlineMonitor {
9698
inner: Arc::new(DeadlineMonitorInner {
9799
deadlines,
98100
active_deadlines: active_deadlines.into(),
99-
start_time: Instant::now(),
101+
monitor_starting_point: Instant::now(),
100102
}),
101103
}
102104
}
@@ -176,7 +178,7 @@ impl Deadline {
176178
/// Caller must ensure that deadline is not used until it's stopped.
177179
/// After this call You shall assure there's only a single owner of the `Deadline` instance and it does not call start before stopping.
178180
pub(super) unsafe fn start_internal(&mut self) -> Result<(), DeadlineError> {
179-
let now = self.monitor.now();
181+
let now = duration_to_u32(self.monitor.monitor_starting_point.elapsed());
180182
let max_time = now + self.range.max.as_millis() as u32;
181183

182184
let mut is_broken = false;
@@ -201,7 +203,7 @@ impl Deadline {
201203
}
202204

203205
pub(super) fn stop_internal(&mut self) {
204-
let now = self.monitor.now();
206+
let now = duration_to_u32(self.monitor.monitor_starting_point.elapsed());
205207
let max = self.range.max.as_millis() as u32;
206208
let min = self.range.min.as_millis() as u32;
207209

@@ -243,6 +245,7 @@ impl Deadline {
243245
(Some(MonitorEvaluationError::TooLate), val) => {
244246
error!("Deadline {:?} stopped too late by {} ms", self.tag, val);
245247
},
248+
(Some(MonitorEvaluationError::HeartbeatSpecific(_)), _) => unreachable!(),
246249
(None, _) => {},
247250
}
248251
}
@@ -267,8 +270,9 @@ impl Drop for Deadline {
267270
}
268271

269272
struct DeadlineMonitorInner {
270-
// Start time of the monitor creation to calculate relative timestamps
271-
start_time: Instant,
273+
/// Monitor starting point.
274+
/// Offset is calculated during evaluation in relation to provided health monitor starting point.
275+
monitor_starting_point: Instant,
272276

273277
// Templates for deadlines registered in the monitor to create `Deadline` instances.
274278
deadlines: HashMap<IdentTag, DeadlineTemplate>,
@@ -280,8 +284,8 @@ struct DeadlineMonitorInner {
280284
}
281285

282286
impl MonitorEvaluator for DeadlineMonitorInner {
283-
fn evaluate(&self, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError)) {
284-
self.evaluate(on_error);
287+
fn evaluate(&self, hmon_starting_point: Instant, on_error: &mut dyn FnMut(&IdentTag, MonitorEvaluationError)) {
288+
self.evaluate(hmon_starting_point, on_error);
285289
}
286290
}
287291

@@ -294,14 +298,7 @@ impl DeadlineMonitorInner {
294298
}
295299
}
296300

297-
fn now(&self) -> u32 {
298-
let duration = self.start_time.elapsed();
299-
// As u32 can hold up to ~49 days in milliseconds, this should be sufficient for our use case
300-
// We still have a room up to 60bits timestamp if needed in future
301-
u32::try_from(duration.as_millis()).expect("Monitor running for too long")
302-
}
303-
304-
fn evaluate(&self, mut on_failed: impl FnMut(&IdentTag, MonitorEvaluationError)) {
301+
fn evaluate(&self, hmon_starting_point: Instant, mut on_failed: impl FnMut(&IdentTag, MonitorEvaluationError)) {
305302
for (tag, deadline) in self.active_deadlines.iter() {
306303
let snapshot = deadline.snapshot();
307304
if snapshot.is_underrun() {
@@ -316,7 +313,9 @@ impl DeadlineMonitorInner {
316313
"Deadline snapshot cannot be both running and stopped"
317314
);
318315

319-
let now = self.now();
316+
// Get current timestamp, with offset to HMON time.
317+
let offset = hmon_starting_point.duration_since(self.monitor_starting_point);
318+
let now = duration_to_u32(self.monitor_starting_point.elapsed() - offset);
320319
let expected = snapshot.timestamp_ms();
321320
if now > expected {
322321
// Deadline missed, report
@@ -395,12 +394,13 @@ mod tests {
395394
let monitor = create_monitor_with_deadlines();
396395
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_long")).unwrap();
397396
let handle = deadline.start().unwrap();
397+
let hmon_starting_point = Instant::now();
398398

399399
std::thread::sleep(core::time::Duration::from_millis(1001)); // Sleep to simulate work within the deadline range
400400

401401
drop(handle); // stop the deadline
402402

403-
monitor.inner.evaluate(|tag, deadline_failure| {
403+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
404404
panic!(
405405
"Deadline {:?} should not have failed or underrun({:?})",
406406
tag, deadline_failure
@@ -413,10 +413,11 @@ mod tests {
413413
let monitor = create_monitor_with_deadlines();
414414
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_long")).unwrap();
415415
let handle = deadline.start().unwrap();
416+
let hmon_starting_point = Instant::now();
416417

417418
drop(handle); // stop the deadline
418419

419-
monitor.inner.evaluate(|tag, deadline_failure| {
420+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
420421
assert_eq!(
421422
deadline_failure,
422423
MonitorEvaluationError::TooEarly,
@@ -431,10 +432,11 @@ mod tests {
431432
let monitor = create_monitor_with_deadlines();
432433
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_long")).unwrap();
433434
let handle = deadline.start().unwrap();
435+
let hmon_starting_point = Instant::now();
434436

435437
// So deadline stop happens after evaluate, still it should be reported as failed
436438

437-
monitor.inner.evaluate(|tag, deadline_failure| {
439+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
438440
assert_eq!(
439441
deadline_failure,
440442
MonitorEvaluationError::TooEarly,
@@ -452,6 +454,7 @@ mod tests {
452454
let monitor = create_monitor_with_deadlines();
453455
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_long")).unwrap();
454456
let handle = deadline.start().unwrap();
457+
let hmon_starting_point = Instant::now();
455458

456459
// So deadline failed, then we start it again so it shall be already expired and also evaluation shall work
457460
drop(handle); // stop the deadline
@@ -460,7 +463,7 @@ mod tests {
460463
let handle = deadline.start();
461464
assert_eq!(handle.err(), Some(DeadlineError::DeadlineAlreadyFailed));
462465

463-
monitor.inner.evaluate(|tag, deadline_failure| {
466+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
464467
assert_eq!(
465468
deadline_failure,
466469
MonitorEvaluationError::TooEarly,
@@ -476,10 +479,11 @@ mod tests {
476479
let monitor = create_monitor_with_deadlines();
477480
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_fast")).unwrap();
478481
let handle = deadline.start().unwrap();
482+
let hmon_starting_point = Instant::now();
479483

480484
drop(handle); // stop the deadline
481485

482-
monitor.inner.evaluate(|tag, deadline_failure| {
486+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
483487
assert_eq!(
484488
deadline_failure,
485489
MonitorEvaluationError::TooLate,
@@ -493,6 +497,7 @@ mod tests {
493497
#[test]
494498
fn monitor_with_multiple_running_deadlines() {
495499
let monitor = create_monitor_with_multiple_running_deadlines();
500+
let hmon_starting_point = Instant::now();
496501

497502
let mut deadline = monitor.get_deadline(&IdentTag::from("deadline_fast1")).unwrap();
498503
let _handle1 = deadline.start().unwrap();
@@ -507,7 +512,7 @@ mod tests {
507512

508513
let mut cnt = 0;
509514

510-
monitor.inner.evaluate(|tag, deadline_failure| {
515+
monitor.inner.evaluate(hmon_starting_point, |tag, deadline_failure| {
511516
cnt += 1;
512517
assert_eq!(
513518
deadline_failure,

0 commit comments

Comments
 (0)