Coverage Report

Created: 2024-11-22 20:17

/build/source/nativelink-util/src/health_utils.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2024 The Native Link Authors. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use std::borrow::Cow;
16
use std::collections::HashMap;
17
use std::fmt::Debug;
18
use std::pin::Pin;
19
use std::sync::Arc;
20
21
use async_trait::async_trait;
22
use futures::{Stream, StreamExt};
23
use parking_lot::Mutex;
24
use serde::Serialize;
25
26
/// Struct name health indicator component.
27
type StructName = str;
28
/// Readable message status of the health indicator.
29
type Message = str;
30
31
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
32
pub enum HealthStatus {
33
    Ok {
34
        struct_name: &'static StructName,
35
        message: Cow<'static, Message>,
36
    },
37
    Initializing {
38
        struct_name: &'static StructName,
39
        message: Cow<'static, Message>,
40
    },
41
    /// This status is used to indicate a non-fatal issue with the component.
42
    Warning {
43
        struct_name: &'static StructName,
44
        message: Cow<'static, Message>,
45
    },
46
    Failed {
47
        struct_name: &'static StructName,
48
        message: Cow<'static, Message>,
49
    },
50
}
51
52
impl HealthStatus {
53
0
    pub fn new_ok(
54
0
        component: &(impl HealthStatusIndicator + ?Sized),
55
0
        message: Cow<'static, str>,
56
0
    ) -> Self {
57
0
        Self::Ok {
58
0
            struct_name: component.struct_name(),
59
0
            message,
60
0
        }
61
0
    }
62
63
0
    pub fn new_initializing(
64
0
        component: &(impl HealthStatusIndicator + ?Sized),
65
0
        message: Cow<'static, str>,
66
0
    ) -> HealthStatus {
67
0
        Self::Initializing {
68
0
            struct_name: component.struct_name(),
69
0
            message,
70
0
        }
71
0
    }
72
73
0
    pub fn new_warning(
74
0
        component: &(impl HealthStatusIndicator + ?Sized),
75
0
        message: Cow<'static, str>,
76
0
    ) -> HealthStatus {
77
0
        Self::Warning {
78
0
            struct_name: component.struct_name(),
79
0
            message,
80
0
        }
81
0
    }
82
83
0
    pub fn new_failed(
84
0
        component: &(impl HealthStatusIndicator + ?Sized),
85
0
        message: Cow<'static, str>,
86
0
    ) -> HealthStatus {
87
0
        Self::Failed {
88
0
            struct_name: component.struct_name(),
89
0
            message,
90
0
        }
91
0
    }
92
}
93
94
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
95
pub struct HealthStatusDescription {
96
    pub namespace: Cow<'static, str>,
97
    pub status: HealthStatus,
98
}
99
100
/// Health status indicator trait. This trait is used to define
101
/// a health status indicator by implementing the `check_health` function.
102
/// A default implementation is provided for the `check_health` function
103
/// that returns healthy component.
104
#[async_trait]
105
pub trait HealthStatusIndicator: Sync + Send + Unpin {
106
    fn get_name(&self) -> &'static str;
107
108
    /// Returns the name of the struct implementing the trait.
109
0
    fn struct_name(&self) -> &'static str {
110
0
        std::any::type_name::<Self>()
111
0
    }
112
113
    /// Check the health status of the component. This function should be
114
    /// implemented by the component to check the health status of the component.
115
    async fn check_health(&self, _namespace: Cow<'static, str>) -> HealthStatus;
116
}
117
118
type HealthRegistryBuilderState =
119
    Arc<Mutex<HashMap<Cow<'static, str>, Arc<dyn HealthStatusIndicator>>>>;
120
pub struct HealthRegistryBuilder {
121
    namespace: Cow<'static, str>,
122
    state: HealthRegistryBuilderState,
123
}
124
125
/// Health registry builder that is used to build a health registry.
126
/// The builder provides creation, registering of health status indicators,
127
/// sub-building scoped health registries and building the health registry.
128
/// `build()` should be called once for finalizing the production of a health registry.
129
impl HealthRegistryBuilder {
130
5
    pub fn new(namespace: &str) -> Self {
131
5
        Self {
132
5
            namespace: format!("/{namespace}").into(),
133
5
            state: Arc::new(Mutex::new(HashMap::new())),
134
5
        }
135
5
    }
136
137
    /// Register a health status indicator at current namespace.
138
9
    pub fn register_indicator(&mut self, indicator: Arc<dyn HealthStatusIndicator>) {
139
9
        let name = format!("{}/{}", self.namespace, indicator.get_name());
140
9
        self.state.lock().insert(name.into(), indicator);
141
9
    }
142
143
    /// Create a sub builder for a namespace.
144
    #[must_use]
145
4
    pub fn sub_builder(&mut self, namespace: &str) -> HealthRegistryBuilder {
146
4
        HealthRegistryBuilder {
147
4
            namespace: format!("{}/{}", self.namespace, namespace).into(),
148
4
            state: self.state.clone(),
149
4
        }
150
4
    }
151
152
    /// Finalize the production of the health registry.
153
5
    pub fn build(&mut self) -> HealthRegistry {
154
5
        HealthRegistry {
155
5
            indicators: self.state.lock().clone().into_iter().collect(),
156
5
        }
157
5
    }
158
}
159
160
#[derive(Default, Clone)]
161
pub struct HealthRegistry {
162
    indicators: Vec<(Cow<'static, str>, Arc<dyn HealthStatusIndicator>)>,
163
}
164
165
pub trait HealthStatusReporter {
166
    fn health_status_report(
167
        &self,
168
    ) -> Pin<Box<dyn Stream<Item = HealthStatusDescription> + Send + '_>>;
169
}
170
171
/// Health status reporter implementation for the health registry that provides a stream
172
/// of health status descriptions.
173
impl HealthStatusReporter for HealthRegistry {
174
5
    fn health_status_report(
175
5
        &self,
176
5
    ) -> Pin<Box<dyn Stream<Item = HealthStatusDescription> + Send + '_>> {
177
5
        Box::pin(futures::stream::iter(self.indicators.iter()).then(
178
9
            |(namespace, indicator)| async move {
179
9
                HealthStatusDescription {
180
9
                    namespace: namespace.clone(),
181
9
                    status: indicator.check_health(namespace.clone()).
await0
,
182
                }
183
18
            },
184
5
        ))
185
5
    }
186
}
187
188
/// Default health status indicator implementation for a component.
189
/// Generally used for components that don't need custom implementations
190
/// of the `check_health` function.
191
#[macro_export]
192
macro_rules! default_health_status_indicator {
193
    ($type:ty) => {
194
        #[async_trait::async_trait]
195
        impl HealthStatusIndicator for $type {
196
0
            fn get_name(&self) -> &'static str {
197
0
                stringify!($type)
198
0
            }
199
200
0
            async fn check_health(
201
0
                &self,
202
0
                namespace: std::borrow::Cow<'static, str>,
203
0
            ) -> nativelink_util::health_utils::HealthStatus {
204
0
                StoreDriver::check_health(Pin::new(self), namespace).await
205
            }
206
        }
207
    };
208
}
209
210
// Re-scoped for the health_utils module.
211
pub use crate::default_health_status_indicator;