1use std::{cmp::Ordering, fmt};
4
5use stringtheory::MetaString;
6use tracing::warn;
7
8const ENTITY_PREFIX_POD_UID: &str = "kubernetes_pod_uid://";
9const ENTITY_PREFIX_CONTAINER_ID: &str = "container_id://";
10const ENTITY_PREFIX_CONTAINER_INODE: &str = "container_inode://";
11const ENTITY_PREFIX_CONTAINER_PID: &str = "container_pid://";
12
13const LOCAL_DATA_PREFIX_INODE: &str = "in-";
14const LOCAL_DATA_PREFIX_CID: &str = "ci-";
15const LOCAL_DATA_PREFIX_LEGACY_CID: &str = "cid-";
16
17#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19pub enum EntityId {
20 Global,
26
27 PodUid(MetaString),
31
32 Container(MetaString),
36
37 ContainerInode(u64),
41
42 ContainerPid(u32),
46}
47
48impl EntityId {
49 pub fn from_local_data<S>(raw_local_data: S) -> Option<Self>
62 where
63 S: AsRef<str> + Into<MetaString>,
64 {
65 let local_data_value = raw_local_data.as_ref();
66 if local_data_value.is_empty() {
67 return None;
68 }
69
70 if local_data_value.contains(',') {
71 let mut maybe_container_inode = None;
72 for local_data_subvalue in local_data_value.split(',') {
73 match parse_local_data_value(local_data_subvalue) {
74 Ok(Some(Self::Container(cid))) => return Some(Self::Container(cid)),
76 Ok(Some(Self::ContainerInode(inode))) => maybe_container_inode = Some(inode),
77 Err(()) => {
78 warn!(
79 local_data = local_data_value,
80 local_data_subvalue,
81 "Failed parsing Local Data subvalue. Metric may be missing origin detection-based tags."
82 );
83 }
84 _ => {}
85 }
86 }
87
88 if let Some(inode) = maybe_container_inode {
90 return Some(Self::ContainerInode(inode));
91 }
92 }
93
94 match parse_local_data_value(local_data_value) {
97 Ok(Some(eid)) => Some(eid),
99 Ok(None) => Some(Self::Container(raw_local_data.into())),
100 Err(()) => {
101 warn!(
102 local_data = local_data_value,
103 "Failed parsing Local Data value. Metric may be missing origin detection-based tags."
104 );
105 None
106 }
107 }
108 }
109
110 pub fn from_pod_uid<S>(pod_uid: S) -> Option<Self>
114 where
115 S: AsRef<str> + Into<MetaString>,
116 {
117 if pod_uid.as_ref() == "none" {
118 return None;
119 }
120 Some(Self::PodUid(pod_uid.into()))
121 }
122
123 pub fn try_into_container(self) -> Option<MetaString> {
127 match self {
128 Self::Container(container_id) => Some(container_id),
129 _ => None,
130 }
131 }
132
133 fn precedence_value(&self) -> usize {
134 match self {
135 Self::Global => 0,
136 Self::PodUid(_) => 1,
137 Self::Container(_) => 2,
138 Self::ContainerInode(_) => 3,
139 Self::ContainerPid(_) => 4,
140 }
141 }
142}
143
144impl fmt::Display for EntityId {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 Self::Global => write!(f, "system://global"),
148 Self::PodUid(pod_uid) => write!(f, "{}{}", ENTITY_PREFIX_POD_UID, pod_uid),
149 Self::Container(container_id) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_ID, container_id),
150 Self::ContainerInode(inode) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_INODE, inode),
151 Self::ContainerPid(pid) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_PID, pid),
152 }
153 }
154}
155
156impl serde::Serialize for EntityId {
157 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158 where
159 S: serde::Serializer,
160 {
161 serializer.collect_str(self)
164 }
165}
166
167#[derive(Eq, PartialEq)]
181pub struct HighestPrecedenceEntityIdRef<'a>(&'a EntityId);
182
183impl<'a> From<&'a EntityId> for HighestPrecedenceEntityIdRef<'a> {
184 fn from(entity_id: &'a EntityId) -> Self {
185 Self(entity_id)
186 }
187}
188
189impl PartialOrd for HighestPrecedenceEntityIdRef<'_> {
190 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
191 Some(self.cmp(other))
192 }
193}
194
195impl Ord for HighestPrecedenceEntityIdRef<'_> {
196 fn cmp(&self, other: &Self) -> Ordering {
197 let self_precedence = self.0.precedence_value();
199 let other_precedence = other.0.precedence_value();
200 if self_precedence != other_precedence {
201 return self_precedence.cmp(&other_precedence);
202 }
203
204 match (self.0, other.0) {
206 (EntityId::Global, EntityId::Global) => Ordering::Equal,
208 (EntityId::PodUid(self_pod_uid), EntityId::PodUid(other_pod_uid)) => self_pod_uid.cmp(other_pod_uid),
209 (EntityId::Container(self_container_id), EntityId::Container(other_container_id)) => {
210 self_container_id.cmp(other_container_id)
211 }
212 (EntityId::ContainerInode(self_inode), EntityId::ContainerInode(other_inode)) => {
213 self_inode.cmp(other_inode)
214 }
215 (EntityId::ContainerPid(self_pid), EntityId::ContainerPid(other_pid)) => self_pid.cmp(other_pid),
216 _ => unreachable!("entities with different precedence should not be compared"),
217 }
218 }
219}
220
221fn parse_local_data_value(raw_local_data_value: &str) -> Result<Option<EntityId>, ()> {
222 if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_CID) {
223 let cid = raw_local_data_value.trim_start_matches(LOCAL_DATA_PREFIX_CID);
224 Ok(Some(EntityId::Container(cid.into())))
225 } else if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_INODE) {
226 let inode = raw_local_data_value
227 .trim_start_matches(LOCAL_DATA_PREFIX_INODE)
228 .parse()
229 .map_err(|_| ())?;
230 Ok(Some(EntityId::ContainerInode(inode)))
231 } else if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_LEGACY_CID) {
232 let cid = raw_local_data_value.trim_start_matches(LOCAL_DATA_PREFIX_LEGACY_CID);
233 Ok(Some(EntityId::Container(cid.into())))
234 } else {
235 Ok(None)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn local_data() {
245 const PREFIX_CID: &str = "ci-singlecontainerid";
246 const PREFIX_LEGACY_CID: &str = "cid-singlecontainerid";
247 const CID: EntityId = EntityId::Container(MetaString::from_static("singlecontainerid"));
248 const PREFIX_INODE: &str = "in-12345";
249 const INODE: EntityId = EntityId::ContainerInode(12345);
250
251 let cases = [
252 ("".into(), None),
254 ("in-notanumber".into(), None),
256 ("random".into(), Some(EntityId::Container("random".into()))),
258 (PREFIX_CID.into(), Some(CID.clone())),
260 (PREFIX_INODE.into(), Some(INODE.clone())),
261 (PREFIX_LEGACY_CID.into(), Some(CID.clone())),
262 (format!("{},{}", PREFIX_CID, PREFIX_INODE), Some(CID.clone())),
267 (format!("{},{}", PREFIX_INODE, PREFIX_CID), Some(CID.clone())),
268 (format!("{},{}", PREFIX_LEGACY_CID, PREFIX_INODE), Some(CID.clone())),
269 (format!("{},{}", PREFIX_INODE, PREFIX_LEGACY_CID), Some(CID.clone())),
270 (format!("{},invalid", PREFIX_CID), Some(CID.clone())),
271 (format!("{},invalid", PREFIX_LEGACY_CID), Some(CID.clone())),
272 (format!("{},invalid", PREFIX_INODE), Some(INODE.clone())),
273 ];
274
275 for (input, expected) in cases {
276 let actual = EntityId::from_local_data(input);
277 assert_eq!(actual, expected);
278 }
279 }
280
281 #[test]
282 fn pod_uid_valid() {
283 let pod_uid = "abcdef1234567890";
284 let entity_id = EntityId::from_pod_uid(pod_uid).unwrap();
285 assert_eq!(entity_id, EntityId::PodUid(MetaString::from(pod_uid)));
286 }
287
288 #[test]
289 fn pod_uid_none() {
290 let pod_uid = "none";
291 let entity_id = EntityId::from_pod_uid(pod_uid);
292 assert!(entity_id.is_none());
293 }
294}