Skip to content

Commit ef78463

Browse files
committed
hmon: add heartbeat monitor
Add heartbeat monitor HMON.
1 parent 6b65643 commit ef78463

7 files changed

Lines changed: 1346 additions & 87 deletions

File tree

src/health_monitoring_lib/rust/common.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
// *******************************************************************************
1313

1414
use crate::deadline::DeadlineEvaluationError;
15+
use crate::heartbeat::HeartbeatEvaluationError;
1516
use crate::log::ScoreDebug;
1617
use crate::tag::MonitorTag;
1718
use core::hash::Hash;
1819
use core::time::Duration;
1920
use std::sync::Arc;
21+
use std::time::Instant;
2022

2123
/// Range of accepted time.
2224
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -48,7 +50,7 @@ pub(crate) trait Monitor {
4850
#[allow(dead_code)]
4951
pub(crate) enum MonitorEvaluationError {
5052
Deadline(DeadlineEvaluationError),
51-
Heartbeat,
53+
Heartbeat(HeartbeatEvaluationError),
5254
Logic,
5355
}
5456

@@ -58,12 +60,19 @@ impl From<DeadlineEvaluationError> for MonitorEvaluationError {
5860
}
5961
}
6062

63+
impl From<HeartbeatEvaluationError> for MonitorEvaluationError {
64+
fn from(value: HeartbeatEvaluationError) -> Self {
65+
MonitorEvaluationError::Heartbeat(value)
66+
}
67+
}
68+
6169
/// Trait for evaluating monitors and reporting errors to be used by HealthMonitor.
6270
pub(crate) trait MonitorEvaluator {
6371
/// Run monitor evaluation.
6472
///
73+
/// - `hmon_starting_point` - starting point of all monitors.
6574
/// - `on_error` - error handling, containing tag of failing object and error code.
66-
fn evaluate(&self, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError));
75+
fn evaluate(&self, hmon_starting_point: Instant, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError));
6776
}
6877

6978
/// Handle to a monitor evaluator, allowing for dynamic dispatch.
@@ -78,7 +87,68 @@ impl MonitorEvalHandle {
7887
}
7988

8089
impl MonitorEvaluator for MonitorEvalHandle {
81-
fn evaluate(&self, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError)) {
82-
self.inner.evaluate(on_error)
90+
fn evaluate(&self, hmon_starting_point: Instant, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError)) {
91+
self.inner.evaluate(hmon_starting_point, on_error)
92+
}
93+
}
94+
95+
/// Get offset between HMON and monitor starting time points as [`u32`].
96+
pub(crate) fn hmon_time_offset(hmon_starting_point: Instant, monitor_starting_point: Instant) -> u32 {
97+
let result = hmon_starting_point.checked_duration_since(monitor_starting_point);
98+
let duration_since = result.expect("HMON starting point is earlier than monitor starting point");
99+
duration_to_u32(duration_since)
100+
}
101+
102+
/// Get duration as [`u32`].
103+
pub(crate) fn duration_to_u32(duration: Duration) -> u32 {
104+
let millis = duration.as_millis();
105+
u32::try_from(millis).expect("Monitor running for too long")
106+
}
107+
108+
#[cfg(all(test, not(loom)))]
109+
mod tests {
110+
use crate::common::{duration_to_u32, hmon_time_offset};
111+
use core::time::Duration;
112+
use std::time::Instant;
113+
114+
#[test]
115+
fn hmon_time_offset_valid() {
116+
let monitor_starting_point = Instant::now();
117+
let hmon_starting_point = Instant::now();
118+
let offset = hmon_time_offset(hmon_starting_point, monitor_starting_point);
119+
// Allow small offset.
120+
assert!(offset < 10);
121+
}
122+
123+
#[test]
124+
#[should_panic(expected = "HMON starting point is earlier than monitor starting point")]
125+
fn hmon_time_offset_wrong_order() {
126+
let hmon_starting_point = Instant::now();
127+
let monitor_starting_point = Instant::now();
128+
let _offset = hmon_time_offset(hmon_starting_point, monitor_starting_point);
129+
}
130+
131+
#[test]
132+
#[should_panic(expected = "Monitor running for too long")]
133+
fn hmon_time_offset_diff_too_large() {
134+
const HUNDRED_DAYS_AS_SECS: u64 = 100 * 24 * 60 * 60;
135+
let monitor_starting_point = Instant::now();
136+
let hmon_starting_point = Instant::now()
137+
.checked_add(Duration::from_secs(HUNDRED_DAYS_AS_SECS))
138+
.unwrap();
139+
let _offset = hmon_time_offset(hmon_starting_point, monitor_starting_point);
140+
}
141+
142+
#[test]
143+
fn duration_to_u32_valid() {
144+
let result = duration_to_u32(Duration::from_millis(1234));
145+
assert_eq!(result, 1234);
146+
}
147+
148+
#[test]
149+
#[should_panic(expected = "Monitor running for too long")]
150+
fn duration_to_u32_too_large() {
151+
const HUNDRED_DAYS_AS_SECS: u64 = 100 * 24 * 60 * 60;
152+
let _result = duration_to_u32(Duration::from_secs(HUNDRED_DAYS_AS_SECS));
83153
}
84154
}

src/health_monitoring_lib/rust/deadline/deadline_monitor.rs

Lines changed: 75 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
//
1111
// SPDX-License-Identifier: Apache-2.0
1212
// *******************************************************************************
13-
use crate::common::{Monitor, MonitorEvalHandle, MonitorEvaluationError, MonitorEvaluator};
13+
use crate::common::{duration_to_u32, Monitor, MonitorEvalHandle, MonitorEvaluationError, MonitorEvaluator, TimeRange};
1414
use crate::deadline::common::{DeadlineTemplate, StateIndex};
1515
use crate::deadline::deadline_state::{DeadlineState, DeadlineStateSnapshot};
1616
use crate::log::{error, warn, ScoreDebug};
1717
use crate::protected_memory::ProtectedMemoryAllocator;
1818
use crate::tag::{DeadlineTag, MonitorTag};
19-
use crate::TimeRange;
2019
use core::hash::Hash;
2120
use std::collections::HashMap;
2221
use std::sync::Arc;
@@ -153,7 +152,7 @@ impl Deadline {
153152
/// Caller must ensure that deadline is not used until it's stopped.
154153
/// After this call You shall assure there's only a single owner of the `Deadline` instance and it does not call start before stopping.
155154
pub(super) unsafe fn start_internal(&mut self) -> Result<(), DeadlineError> {
156-
let now = self.monitor.now();
155+
let now = duration_to_u32(self.monitor.monitor_starting_point.elapsed());
157156
let max_time = now + self.range.max.as_millis() as u32;
158157

159158
let mut is_broken = false;
@@ -178,7 +177,7 @@ impl Deadline {
178177
}
179178

180179
pub(super) fn stop_internal(&mut self) {
181-
let now = self.monitor.now();
180+
let now = duration_to_u32(self.monitor.monitor_starting_point.elapsed());
182181
let max = self.range.max.as_millis() as u32;
183182
let min = self.range.min.as_millis() as u32;
184183

@@ -260,7 +259,7 @@ struct DeadlineMonitorInner {
260259
}
261260

262261
impl MonitorEvaluator for DeadlineMonitorInner {
263-
fn evaluate(&self, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError)) {
262+
fn evaluate(&self, _hmon_starting_point: Instant, on_error: &mut dyn FnMut(&MonitorTag, MonitorEvaluationError)) {
264263
for (deadline_tag, deadline) in self.active_deadlines.iter() {
265264
let snapshot = deadline.snapshot();
266265
if snapshot.is_underrun() {
@@ -275,7 +274,7 @@ impl MonitorEvaluator for DeadlineMonitorInner {
275274
"Deadline snapshot cannot be both running and stopped"
276275
);
277276

278-
let now = self.now();
277+
let now = duration_to_u32(self.monitor_starting_point.elapsed());
279278
let expected = snapshot.timestamp_ms();
280279
if now > expected {
281280
// Deadline missed, report
@@ -336,13 +335,6 @@ impl DeadlineMonitorInner {
336335
Err(DeadlineMonitorError::DeadlineNotFound)
337336
}
338337
}
339-
340-
fn now(&self) -> u32 {
341-
let duration = self.monitor_starting_point.elapsed();
342-
// As u32 can hold up to ~49 days in milliseconds, this should be sufficient for our use case
343-
// We still have a room up to 60bits timestamp if needed in future
344-
u32::try_from(duration.as_millis()).expect("Monitor running for too long")
345-
}
346338
}
347339

348340
#[score_testing_macros::test_mod_with_log]
@@ -410,63 +402,73 @@ mod tests {
410402
#[test]
411403
fn start_stop_deadline_within_range_works() {
412404
let monitor = create_monitor_with_deadlines();
405+
let hmon_starting_point = Instant::now();
413406
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_long")).unwrap();
414407
let handle = deadline.start().unwrap();
415408

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

418411
drop(handle); // stop the deadline
419412

420-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
421-
panic!(
422-
"Deadline {:?} should not have failed or underrun({:?})",
423-
monitor_tag, deadline_failure
424-
);
425-
});
413+
monitor
414+
.inner
415+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
416+
panic!(
417+
"Deadline {:?} should not have failed or underrun({:?})",
418+
monitor_tag, deadline_failure
419+
);
420+
});
426421
}
427422

428423
#[test]
429424
fn start_stop_deadline_outside_ranges_is_error_when_dropped_before_evaluate() {
430425
let monitor = create_monitor_with_deadlines();
426+
let hmon_starting_point = Instant::now();
431427
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_long")).unwrap();
432428
let handle = deadline.start().unwrap();
433429

434430
drop(handle); // stop the deadline
435431

436-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
437-
assert_eq!(
438-
deadline_failure,
439-
DeadlineEvaluationError::TooEarly.into(),
440-
"Deadline {:?} should not have failed({:?})",
441-
monitor_tag,
442-
deadline_failure
443-
);
444-
});
432+
monitor
433+
.inner
434+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
435+
assert_eq!(
436+
deadline_failure,
437+
DeadlineEvaluationError::TooEarly.into(),
438+
"Deadline {:?} should not have failed({:?})",
439+
monitor_tag,
440+
deadline_failure
441+
);
442+
});
445443
}
446444
#[test]
447445
fn deadline_outside_time_range_is_error_when_dropped_after_evaluate() {
448446
let monitor = create_monitor_with_deadlines();
447+
let hmon_starting_point = Instant::now();
449448
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_long")).unwrap();
450449
let handle = deadline.start().unwrap();
451450

452451
// So deadline stop happens after evaluate, still it should be reported as failed
453452

454-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
455-
assert_eq!(
456-
deadline_failure,
457-
DeadlineEvaluationError::TooEarly.into(),
458-
"Deadline {:?} should not have failed({:?})",
459-
monitor_tag,
460-
deadline_failure
461-
);
462-
});
453+
monitor
454+
.inner
455+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
456+
assert_eq!(
457+
deadline_failure,
458+
DeadlineEvaluationError::TooEarly.into(),
459+
"Deadline {:?} should not have failed({:?})",
460+
monitor_tag,
461+
deadline_failure
462+
);
463+
});
463464

464465
drop(handle); // stop the deadline
465466
}
466467

467468
#[test]
468469
fn deadline_failed_on_first_run_and_then_restarted_is_evaluated_as_error() {
469470
let monitor = create_monitor_with_deadlines();
471+
let hmon_starting_point = Instant::now();
470472
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_long")).unwrap();
471473
let handle = deadline.start().unwrap();
472474

@@ -477,39 +479,45 @@ mod tests {
477479
let handle = deadline.start();
478480
assert_eq!(handle.err(), Some(DeadlineError::DeadlineAlreadyFailed));
479481

480-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
481-
assert_eq!(
482-
deadline_failure,
483-
DeadlineEvaluationError::TooEarly.into(),
484-
"Deadline {:?} should not have failed ({:?})",
485-
monitor_tag,
486-
deadline_failure
487-
);
488-
});
482+
monitor
483+
.inner
484+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
485+
assert_eq!(
486+
deadline_failure,
487+
DeadlineEvaluationError::TooEarly.into(),
488+
"Deadline {:?} should not have failed ({:?})",
489+
monitor_tag,
490+
deadline_failure
491+
);
492+
});
489493
}
490494

491495
#[test]
492496
fn start_stop_deadline_outside_ranges_is_evaluated_as_error() {
493497
let monitor = create_monitor_with_deadlines();
498+
let hmon_starting_point = Instant::now();
494499
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_fast")).unwrap();
495500
let handle = deadline.start().unwrap();
496501

497502
drop(handle); // stop the deadline
498503

499-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
500-
assert_eq!(
501-
deadline_failure,
502-
DeadlineEvaluationError::TooLate.into(),
503-
"Deadline {:?} should not have failed({:?})",
504-
monitor_tag,
505-
deadline_failure
506-
);
507-
});
504+
monitor
505+
.inner
506+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
507+
assert_eq!(
508+
deadline_failure,
509+
DeadlineEvaluationError::TooLate.into(),
510+
"Deadline {:?} should not have failed({:?})",
511+
monitor_tag,
512+
deadline_failure
513+
);
514+
});
508515
}
509516

510517
#[test]
511518
fn monitor_with_multiple_running_deadlines() {
512519
let monitor = create_monitor_with_multiple_running_deadlines();
520+
let hmon_starting_point = Instant::now();
513521

514522
let mut deadline = monitor.get_deadline(DeadlineTag::from("deadline_fast1")).unwrap();
515523
let _handle1 = deadline.start().unwrap();
@@ -524,16 +532,18 @@ mod tests {
524532

525533
let mut cnt = 0;
526534

527-
monitor.inner.evaluate(&mut |monitor_tag, deadline_failure| {
528-
cnt += 1;
529-
assert_eq!(
530-
deadline_failure,
531-
DeadlineEvaluationError::TooLate.into(),
532-
"Deadline {:?} should not have failed({:?})",
533-
monitor_tag,
534-
deadline_failure
535-
);
536-
});
535+
monitor
536+
.inner
537+
.evaluate(hmon_starting_point, &mut |monitor_tag, deadline_failure| {
538+
cnt += 1;
539+
assert_eq!(
540+
deadline_failure,
541+
DeadlineEvaluationError::TooLate.into(),
542+
"Deadline {:?} should not have failed({:?})",
543+
monitor_tag,
544+
deadline_failure
545+
);
546+
});
537547

538548
assert_eq!(cnt, 3, "All three deadlines should have been evaluated");
539549
}

0 commit comments

Comments
 (0)