11use k8s_openapi:: api:: core:: v1:: Node ;
22use moka:: future:: Cache ;
3+ use serde:: Serialize ;
34use std:: collections:: { BTreeMap , HashMap } ;
45use std:: sync:: Arc ;
56use std:: time:: { Duration , Instant } ;
67
78use crate :: ingest:: MetricsFetcherIngestion ;
89use 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 ) ]
3133pub 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
3669impl 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