Skip to content

Commit cec4ce7

Browse files
committed
snapshot: Consume reflector health work
1 parent bd40849 commit cec4ce7

4 files changed

Lines changed: 135 additions & 15 deletions

File tree

metrics-cache/src/ingest/reflectors.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use tracing::{debug, error, trace};
1818
macro_rules! define_reflectors {
1919
(
2020
$(
21-
$field:ident : $resource:ty
21+
$field:ident : $resource:ty => $kind:literal
2222
),* $(,)?
2323
) => {
2424
/// The [`Store`]s where all of the watched Kubernetes kinds that rustik
@@ -109,7 +109,7 @@ macro_rules! define_reflectors {
109109
}
110110
}
111111

112-
#[derive(Clone, Debug)]
112+
#[derive(Clone, Debug, Default)]
113113
pub(crate) struct FrozenReflectorHealths {
114114
$(
115115
pub(crate) $field: ReflectorHealth,
@@ -127,7 +127,7 @@ macro_rules! define_reflectors {
127127
fn into_iter(self) -> Self::IntoIter {
128128
[
129129
$(
130-
(stringify!($field), self.$field),
130+
($kind, self.$field),
131131
)*
132132
]
133133
.into_iter()
@@ -137,16 +137,17 @@ macro_rules! define_reflectors {
137137
}
138138
}
139139

140+
// field name: type from k8s_openapi => "KindName"
140141
define_reflectors! {
141-
pods: Pod,
142-
nodes: Node,
143-
deployments: Deployment,
144-
daemonsets: DaemonSet,
145-
namespaces: Namespace,
146-
replicasets: ReplicaSet,
147-
persistent_volumes: PersistentVolume,
148-
persistent_volume_claims: PersistentVolumeClaim,
149-
statefulsets: StatefulSet,
142+
pods: Pod => "Pod",
143+
nodes: Node => "Node",
144+
deployments: Deployment => "Deployment",
145+
daemonsets: DaemonSet => "DaemonSet",
146+
namespaces: Namespace => "Namespace",
147+
replicasets: ReplicaSet => "ReplicaSet",
148+
persistent_volumes: PersistentVolume => "PersistentVolume",
149+
persistent_volume_claims: PersistentVolumeClaim => "PersistentVolumeClaim",
150+
statefulsets: StatefulSet => "StatefulSet",
150151
}
151152

152153
/// The inner-state of a reflector. This gets updated by the reflector's

metrics-cache/src/section/self_health.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mod tests {
5757
]);
5858
let self_health = SelfHealth {
5959
kubelet_stats_summary_age,
60+
..Default::default()
6061
};
6162
let section = KubeRustikHealthV1::from_self_health(&self_health);
6263
insta::assert_json_snapshot!(section);

metrics-cache/src/snapshot/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,17 @@ impl Snapshot {
4848
kubelet_stats_summary_cache: Cache<String, Arc<MetricsFetcherIngestion<StatsSummary>>>,
4949
) -> Self {
5050
let instant = Instant::now();
51+
let reflector_healths = stores.freeze_healths();
5152
let stores = stores.freeze();
5253
let owner_graph = OwnerGraph::from_frozen_stores(&stores);
5354
let metrics = MetricTables::from_cache(&kubelet_stats_summary_cache);
5455
let indexes = Indexes::from_frozen_stores(&stores);
55-
let self_health = SelfHealth::new(instant, &stores.nodes, &kubelet_stats_summary_cache);
56+
let self_health = SelfHealth::new(
57+
instant,
58+
&stores.nodes,
59+
reflector_healths,
60+
&kubelet_stats_summary_cache,
61+
);
5662
Snapshot {
5763
instant,
5864
stores,

metrics-cache/src/snapshot/self_health.rs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use k8s_openapi::api::core::v1::Node;
22
use moka::future::Cache;
3+
use serde::Serialize;
34
use std::collections::{BTreeMap, HashMap};
45
use std::sync::Arc;
56
use std::time::{Duration, Instant};
67

78
use crate::ingest::MetricsFetcherIngestion;
89
use crate::ingest::kubelet_stats::StatsSummary;
10+
use crate::ingest::reflectors::{self, FrozenReflectorHealths};
911

1012
/// A point-in-time view of the health of cmk-rustik and its components:
1113
/// metrics-cache and metrics-fetcher.
@@ -27,21 +29,55 @@ use crate::ingest::kubelet_stats::StatsSummary;
2729
/// as the source of truth. Then in the monitoring/Checkmk side, we can alert on
2830
/// "Kubernetes said the node should be there, it's missing from the cache data,
2931
/// that means it stopped reporting at some point, CRIT!"
30-
#[derive(Debug)]
32+
#[derive(Debug, Default)]
3133
pub struct SelfHealth {
3234
/// Maps node names to the age of the last kubelet stats push from the node.
3335
pub kubelet_stats_summary_age: BTreeMap<String, Option<Duration>>,
36+
/// Maps kind names to reflector states.
37+
pub reflector_healths: BTreeMap<String, ReflectorHealth>,
38+
}
39+
40+
#[derive(Debug, Default, Serialize)]
41+
pub struct ReflectorHealth {
42+
has_been_initialized: bool,
43+
relist_started_age: Option<Duration>,
44+
relist_completed_age: Option<Duration>,
45+
relist_duration: Option<Duration>,
46+
last_error_age: Option<Duration>,
47+
errors_total: u64,
48+
}
49+
50+
impl ReflectorHealth {
51+
fn from_reflector_health(health: reflectors::ReflectorHealth, now: Instant) -> Self {
52+
Self {
53+
has_been_initialized: health.has_been_initialized,
54+
relist_started_age: health
55+
.relist_started_at
56+
.map(|i| now.saturating_duration_since(i)),
57+
relist_completed_age: health
58+
.relist_completed_at
59+
.map(|i| now.saturating_duration_since(i)),
60+
relist_duration: health.relist_duration,
61+
last_error_age: health
62+
.last_error_at
63+
.map(|i| now.saturating_duration_since(i)),
64+
errors_total: health.errors_total,
65+
}
66+
}
3467
}
3568

3669
impl SelfHealth {
37-
pub fn new(
70+
pub(crate) fn new(
3871
now: Instant,
3972
nodes: &[Arc<Node>],
73+
reflector_healths: FrozenReflectorHealths,
4074
kubelet_stats_summary_cache: &Cache<String, Arc<MetricsFetcherIngestion<StatsSummary>>>,
4175
) -> SelfHealth {
4276
let kubelet_stats_summary_age = Self::cache_age(now, nodes, kubelet_stats_summary_cache);
77+
let reflector_healths = Self::reflector_healths_from_frozen(now, reflector_healths);
4378
SelfHealth {
4479
kubelet_stats_summary_age,
80+
reflector_healths,
4581
}
4682
}
4783

@@ -75,6 +111,26 @@ impl SelfHealth {
75111
}
76112
map
77113
}
114+
115+
/// Given an `Instant` representing _the moment the `Snapshot` is being
116+
/// taken_, and a [`crate::ingest::reflectors::FrozenReflectorHealths`],
117+
/// generate a `BTreeMap` using the `IntoIterator` instance of the
118+
/// `FrozenReflectorHealths`, converting each reflector health into a
119+
/// [`crate::snapshot::self_health::ReflectorHealth`].
120+
fn reflector_healths_from_frozen(
121+
now: Instant,
122+
healths: FrozenReflectorHealths,
123+
) -> BTreeMap<String, ReflectorHealth> {
124+
healths
125+
.into_iter()
126+
.map(|(kind, health)| {
127+
(
128+
kind.to_string(),
129+
ReflectorHealth::from_reflector_health(health, now),
130+
)
131+
})
132+
.collect()
133+
}
78134
}
79135

80136
#[cfg(test)]
@@ -131,4 +187,60 @@ mod tests {
131187
// A node in the cache but not in the nodes store is not added
132188
assert!(!ages.contains_key("decommissioned01"));
133189
}
190+
191+
#[test]
192+
fn from_reflector_health() {
193+
let now = Instant::now();
194+
let health = reflectors::ReflectorHealth {
195+
has_been_initialized: true,
196+
relist_started_at: Some(now - Duration::from_secs(10)),
197+
relist_completed_at: Some(now - Duration::from_secs(20)),
198+
relist_duration: Some(Duration::from_secs(3)),
199+
last_error_at: Some(now - Duration::from_secs(30)),
200+
errors_total: 7,
201+
};
202+
let converted = ReflectorHealth::from_reflector_health(health, now);
203+
assert!(converted.has_been_initialized);
204+
assert_eq!(converted.relist_started_age, Some(Duration::from_secs(10)));
205+
assert_eq!(
206+
converted.relist_completed_age,
207+
Some(Duration::from_secs(20))
208+
);
209+
assert_eq!(converted.relist_duration, Some(Duration::from_secs(3)));
210+
assert_eq!(converted.last_error_age, Some(Duration::from_secs(30)));
211+
assert_eq!(converted.errors_total, 7);
212+
}
213+
214+
#[test]
215+
fn from_reflector_health_defaults() {
216+
let now = Instant::now();
217+
let health = reflectors::ReflectorHealth::default();
218+
let converted = ReflectorHealth::from_reflector_health(health, now);
219+
assert!(!converted.has_been_initialized);
220+
assert!(converted.relist_started_age.is_none());
221+
assert!(converted.relist_completed_age.is_none());
222+
assert!(converted.relist_duration.is_none());
223+
assert!(converted.last_error_age.is_none());
224+
assert_eq!(converted.errors_total, 0);
225+
}
226+
227+
#[test]
228+
fn from_reflector_health_future_time_does_not_panic() {
229+
let now = Instant::now();
230+
let health = reflectors::ReflectorHealth {
231+
last_error_at: Some(now + Duration::from_secs(1)),
232+
..Default::default()
233+
};
234+
let converted = ReflectorHealth::from_reflector_health(health, now);
235+
assert_eq!(converted.last_error_age, Some(Duration::ZERO));
236+
assert_eq!(converted.errors_total, 0);
237+
}
238+
239+
#[test]
240+
fn reflector_healths_from_frozen_sanity() {
241+
let now = Instant::now();
242+
let frozen_healths = FrozenReflectorHealths::default();
243+
let map = SelfHealth::reflector_healths_from_frozen(now, frozen_healths);
244+
assert!(map.contains_key("ReplicaSet"));
245+
}
134246
}

0 commit comments

Comments
 (0)