1use std::cmp::{
2 Ord,
3 Ordering,
4 max,
5 min,
6};
7
8use tracing::*;
9
10use super::*;
11use crate::prelude::*;
12
13impl PodLifecycleData {
38 fn new(start_ts: Option<i64>, end_ts: Option<i64>) -> PodLifecycleData {
39 match (start_ts, end_ts) {
40 (None, _) => PodLifecycleData::Empty,
41 (Some(ts), None) => PodLifecycleData::Running(ts),
42 (Some(start), Some(end)) => PodLifecycleData::Finished(start, end),
43 }
44 }
45
46 pub fn new_for(pod: &corev1::Pod) -> anyhow::Result<PodLifecycleData> {
47 let (mut earliest_start_ts, mut latest_end_ts) = (None, None);
48 let mut terminated_container_count = 0;
49
50 let pod_status = pod.status()?;
51 if let Some(cstats) = pod_status.init_container_statuses.as_ref() {
52 for (container, state) in cstats.iter().filter_map(|s| Some((&s.name, s.state.as_ref()?))) {
53 let (start_ts, end_ts) = get_start_end_ts(pod, container, state);
54 earliest_start_ts = min_some(start_ts, earliest_start_ts);
55 latest_end_ts = max(latest_end_ts, end_ts);
56 }
57 }
58
59 if let Some(cstats) = pod_status.container_statuses.as_ref() {
60 for (container, state) in cstats.iter().filter_map(|s| Some((&s.name, s.state.as_ref()?))) {
61 let (start_ts, end_ts) = get_start_end_ts(pod, container, state);
62 earliest_start_ts = min_some(start_ts, earliest_start_ts);
63
64 if end_ts.is_some() {
65 terminated_container_count += 1;
66 }
67 latest_end_ts = max(latest_end_ts, end_ts);
68 }
69 }
70
71 if terminated_container_count != pod.spec()?.containers.len() {
77 latest_end_ts = None;
78 }
79 Ok(PodLifecycleData::new(earliest_start_ts, latest_end_ts))
80 }
81
82 pub fn end_ts(&self) -> Option<i64> {
83 match self {
84 &PodLifecycleData::Finished(_, ts) => Some(ts),
85 _ => None,
86 }
87 }
88
89 pub fn start_ts(&self) -> Option<i64> {
90 match *self {
91 PodLifecycleData::Running(ts) => Some(ts),
92 PodLifecycleData::Finished(ts, _) => Some(ts),
93 _ => None,
94 }
95 }
96
97 pub fn overlaps(&self, start_ts: i64, end_ts: i64) -> bool {
98 match *self {
102 PodLifecycleData::Running(ts) => ts < end_ts,
103 PodLifecycleData::Finished(s, e) => (start_ts <= s && s < end_ts) || (start_ts <= e && e < end_ts),
104 _ => false,
105 }
106 }
107
108 pub fn bound_start_ts(mut self, min_start_ts: i64) -> Self {
109 match self {
110 PodLifecycleData::Empty => (),
111 PodLifecycleData::Running(ref mut ts) => *ts = max(*ts, min_start_ts),
112 PodLifecycleData::Finished(ref mut ts, _) => *ts = max(*ts, min_start_ts),
113 }
114 self
115 }
116
117 pub fn empty(&self) -> bool {
118 self == PodLifecycleData::Empty
119 }
120
121 pub fn running(&self) -> bool {
122 matches!(self, PodLifecycleData::Running(_))
123 }
124
125 pub fn finished(&self) -> bool {
126 matches!(self, PodLifecycleData::Finished(..))
127 }
128}
129
130impl PartialOrd for PodLifecycleData {
146 fn partial_cmp(&self, other: &PodLifecycleData) -> Option<Ordering> {
147 match self {
148 PodLifecycleData::Empty => {
149 if !other.empty() {
150 Some(Ordering::Less)
151 } else {
152 Some(Ordering::Equal)
153 }
154 },
155 PodLifecycleData::Running(ts) => match other {
156 PodLifecycleData::Empty => Some(Ordering::Greater),
157 PodLifecycleData::Running(other_ts) => {
158 if ts == other_ts {
159 Some(Ordering::Equal)
160 } else {
161 None
162 }
163 },
164 PodLifecycleData::Finished(..) => Some(Ordering::Less),
165 },
166 PodLifecycleData::Finished(sts, ets) => match other {
167 PodLifecycleData::Empty => Some(Ordering::Greater),
168 PodLifecycleData::Running(other_ts) => {
169 if sts == other_ts {
170 Some(Ordering::Greater)
171 } else {
172 None
173 }
174 },
175 PodLifecycleData::Finished(other_sts, other_ets) => {
176 if sts == other_sts && ets == other_ets {
177 Some(Ordering::Equal)
178 } else {
179 None
180 }
181 },
182 },
183 }
184 }
185}
186
187fn get_start_end_ts(pod: &corev1::Pod, container: &str, state: &corev1::ContainerState) -> (Option<i64>, Option<i64>) {
188 let start_ts = state.start_ts().unwrap_or_else(|err| {
189 warn!("could not find start_ts for container {container} in {}: {err:?}", pod.namespaced_name());
190 None
191 });
192 let end_ts = state.end_ts().unwrap_or_else(|err| {
193 warn!("could not find end_ts for container {container} in {}: {err:?}", pod.namespaced_name());
194 None
195 });
196
197 (start_ts, end_ts)
198}
199
200impl PartialEq<Option<&PodLifecycleData>> for PodLifecycleData {
201 fn eq(&self, other: &Option<&PodLifecycleData>) -> bool {
202 match self {
203 PodLifecycleData::Empty => other.is_none() || other.as_ref().is_some_and(|plt| plt.empty()),
204 _ => other.as_ref().is_some_and(|plt| plt == self),
205 }
206 }
207}
208
209impl PartialOrd<Option<&PodLifecycleData>> for PodLifecycleData {
210 fn partial_cmp(&self, other: &Option<&PodLifecycleData>) -> Option<Ordering> {
211 match self {
212 PodLifecycleData::Empty => other.as_ref().map_or(Some(Ordering::Equal), |o| self.partial_cmp(o)),
213 _ => other.as_ref().map_or(Some(Ordering::Greater), |o| self.partial_cmp(o)),
214 }
215 }
216}
217
218pub fn min_some<T: Ord>(o1: Option<T>, o2: Option<T>) -> Option<T> {
226 if o1.is_none() {
227 o2
228 } else if o2.is_none() {
229 o1
230 } else {
231 min(o1, o2)
232 }
233}
234
235#[cfg(test)]
236#[cfg_attr(coverage, coverage(off))]
237mod test {
238 use sk_testutils::*;
239
240 use super::*;
241
242 #[rstest]
243 #[case::both_none(None, None, None)]
244 #[case::left_some(Some(1), None, Some(1))]
245 #[case::right_some(None, Some(1), Some(1))]
246 #[case::both_some(Some(2), Some(1), Some(1))]
247 fn test_min_some(#[case] o1: Option<i32>, #[case] o2: Option<i32>, #[case] expected: Option<i32>) {
248 assert_eq!(min_some(o1, o2), expected);
249 }
250}