1use std::{cmp::Ordering, fmt};
4
5use stringtheory::MetaString;
6use tracing::warn;
7
8const ENTITY_PREFIX_CONTAINER_ID: &str = "container_id://";
9const ENTITY_PREFIX_CONTAINER_IMAGE_METADATA: &str = "container_image_metadata://";
10const ENTITY_PREFIX_ECS_TASK: &str = "ecs_task://";
11const ENTITY_PREFIX_KUBERNETES_DEPLOYMENT: &str = "deployment://";
12const ENTITY_PREFIX_KUBERNETES_METADATA: &str = "kubernetes_metadata://";
13const ENTITY_PREFIX_KUBERNETES_NODE: &str = "kubernetes_node://";
14const ENTITY_PREFIX_POD_UID: &str = "kubernetes_pod_uid://";
15const ENTITY_PREFIX_PROCESS: &str = "process://";
16const ENTITY_PREFIX_CONTAINER_INODE: &str = "container_inode://";
17const ENTITY_PREFIX_CONTAINER_PID: &str = "container_pid://";
18
19const LOCAL_DATA_PREFIX_INODE: &str = "in-";
20const LOCAL_DATA_PREFIX_CID: &str = "ci-";
21const LOCAL_DATA_PREFIX_LEGACY_CID: &str = "cid-";
22
23#[derive(Clone, Debug, Eq, Hash, PartialEq)]
25pub enum EntityId {
26 Global,
32
33 Container(MetaString),
37
38 ContainerImageMetadata(MetaString),
40
41 EcsTask(MetaString),
43
44 KubernetesDeployment(MetaString),
46
47 KubernetesMetadata(MetaString),
49
50 KubernetesNode(MetaString),
52
53 PodUid(MetaString),
57
58 Process(MetaString),
60
61 ContainerInode(u64),
65
66 ContainerPid(u32),
70}
71
72impl EntityId {
73 pub fn from_local_data<S>(raw_local_data: S) -> Option<Self>
86 where
87 S: AsRef<str> + Into<MetaString>,
88 {
89 let local_data_value = raw_local_data.as_ref();
90 if local_data_value.is_empty() {
91 return None;
92 }
93
94 if local_data_value.contains(',') {
95 let mut maybe_container_inode = None;
96 for local_data_subvalue in local_data_value.split(',') {
97 match parse_local_data_value(local_data_subvalue) {
98 Ok(Some(Self::Container(cid))) => return Some(Self::Container(cid)),
100 Ok(Some(Self::ContainerInode(inode))) => maybe_container_inode = Some(inode),
101 Err(()) => {
102 warn!(
103 local_data = local_data_value,
104 local_data_subvalue,
105 "Failed parsing Local Data subvalue. Metric may be missing origin detection-based tags."
106 );
107 }
108 _ => {}
109 }
110 }
111
112 if let Some(inode) = maybe_container_inode {
114 return Some(Self::ContainerInode(inode));
115 }
116 }
117
118 match parse_local_data_value(local_data_value) {
121 Ok(Some(eid)) => Some(eid),
123 Ok(None) => Some(Self::Container(raw_local_data.into())),
124 Err(()) => {
125 warn!(
126 local_data = local_data_value,
127 "Failed parsing Local Data value. Metric may be missing origin detection-based tags."
128 );
129 None
130 }
131 }
132 }
133
134 pub fn from_pod_uid<S>(pod_uid: S) -> Option<Self>
138 where
139 S: AsRef<str> + Into<MetaString>,
140 {
141 if pod_uid.as_ref() == "none" {
142 return None;
143 }
144 Some(Self::PodUid(pod_uid.into()))
145 }
146
147 pub fn try_into_container(self) -> Option<MetaString> {
151 match self {
152 Self::Container(container_id) => Some(container_id),
153 _ => None,
154 }
155 }
156
157 fn precedence_value(&self) -> usize {
158 match self {
159 Self::Global => 0,
160 Self::Container(_) => 1,
161 Self::ContainerImageMetadata(_) => 2,
162 Self::EcsTask(_) => 3,
163 Self::KubernetesDeployment(_) => 4,
164 Self::KubernetesMetadata(_) => 5,
165 Self::KubernetesNode(_) => 6,
166 Self::PodUid(_) => 7,
167 Self::Process(_) => 8,
168 Self::ContainerInode(_) => 9,
170 Self::ContainerPid(_) => 10,
171 }
172 }
173}
174
175impl fmt::Display for EntityId {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match self {
178 Self::Global => write!(f, "system://global"),
179 Self::Container(container_id) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_ID, container_id),
180 Self::ContainerImageMetadata(digest) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_IMAGE_METADATA, digest),
181 Self::EcsTask(task_arn) => write!(f, "{}{}", ENTITY_PREFIX_ECS_TASK, task_arn),
182 Self::KubernetesDeployment(deployment) => {
183 write!(f, "{}{}", ENTITY_PREFIX_KUBERNETES_DEPLOYMENT, deployment)
184 }
185 Self::KubernetesMetadata(metadata) => write!(f, "{}{}", ENTITY_PREFIX_KUBERNETES_METADATA, metadata),
186 Self::KubernetesNode(node) => write!(f, "{}{}", ENTITY_PREFIX_KUBERNETES_NODE, node),
187 Self::PodUid(pod_uid) => write!(f, "{}{}", ENTITY_PREFIX_POD_UID, pod_uid),
188 Self::Process(process_id) => write!(f, "{}{}", ENTITY_PREFIX_PROCESS, process_id),
189 Self::ContainerInode(inode) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_INODE, inode),
190 Self::ContainerPid(pid) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_PID, pid),
191 }
192 }
193}
194
195impl serde::Serialize for EntityId {
196 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197 where
198 S: serde::Serializer,
199 {
200 serializer.collect_str(self)
203 }
204}
205
206#[derive(Eq, PartialEq)]
222pub struct HighestPrecedenceEntityIdRef<'a>(&'a EntityId);
223
224impl<'a> From<&'a EntityId> for HighestPrecedenceEntityIdRef<'a> {
225 fn from(entity_id: &'a EntityId) -> Self {
226 Self(entity_id)
227 }
228}
229
230impl PartialOrd for HighestPrecedenceEntityIdRef<'_> {
231 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
232 Some(self.cmp(other))
233 }
234}
235
236impl Ord for HighestPrecedenceEntityIdRef<'_> {
237 fn cmp(&self, other: &Self) -> Ordering {
238 let self_precedence = self.0.precedence_value();
240 let other_precedence = other.0.precedence_value();
241 if self_precedence != other_precedence {
242 return self_precedence.cmp(&other_precedence);
243 }
244
245 match (self.0, other.0) {
247 (EntityId::Global, EntityId::Global) => Ordering::Equal,
249 (EntityId::Container(self_container_id), EntityId::Container(other_container_id)) => {
250 self_container_id.cmp(other_container_id)
251 }
252 (EntityId::ContainerImageMetadata(self_digest), EntityId::ContainerImageMetadata(other_digest)) => {
253 self_digest.cmp(other_digest)
254 }
255 (EntityId::EcsTask(self_task_arn), EntityId::EcsTask(other_task_arn)) => self_task_arn.cmp(other_task_arn),
256 (EntityId::KubernetesDeployment(self_deployment), EntityId::KubernetesDeployment(other_deployment)) => {
257 self_deployment.cmp(other_deployment)
258 }
259 (EntityId::KubernetesMetadata(self_metadata), EntityId::KubernetesMetadata(other_metadata)) => {
260 self_metadata.cmp(other_metadata)
261 }
262 (EntityId::KubernetesNode(self_node), EntityId::KubernetesNode(other_node)) => self_node.cmp(other_node),
263 (EntityId::PodUid(self_pod_uid), EntityId::PodUid(other_pod_uid)) => self_pod_uid.cmp(other_pod_uid),
264 (EntityId::Process(self_process_id), EntityId::Process(other_process_id)) => {
265 self_process_id.cmp(other_process_id)
266 }
267 (EntityId::ContainerInode(self_inode), EntityId::ContainerInode(other_inode)) => {
268 self_inode.cmp(other_inode)
269 }
270 (EntityId::ContainerPid(self_pid), EntityId::ContainerPid(other_pid)) => self_pid.cmp(other_pid),
271 _ => unreachable!("entities with different precedence should not be compared"),
272 }
273 }
274}
275
276fn parse_local_data_value(raw_local_data_value: &str) -> Result<Option<EntityId>, ()> {
277 if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_CID) {
278 let cid = raw_local_data_value.trim_start_matches(LOCAL_DATA_PREFIX_CID);
279 Ok(Some(EntityId::Container(cid.into())))
280 } else if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_INODE) {
281 let inode = raw_local_data_value
282 .trim_start_matches(LOCAL_DATA_PREFIX_INODE)
283 .parse()
284 .map_err(|_| ())?;
285 Ok(Some(EntityId::ContainerInode(inode)))
286 } else if raw_local_data_value.starts_with(LOCAL_DATA_PREFIX_LEGACY_CID) {
287 let cid = raw_local_data_value.trim_start_matches(LOCAL_DATA_PREFIX_LEGACY_CID);
288 Ok(Some(EntityId::Container(cid.into())))
289 } else {
290 Ok(None)
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn local_data() {
300 const PREFIX_CID: &str = "ci-singlecontainerid";
301 const PREFIX_LEGACY_CID: &str = "cid-singlecontainerid";
302 const CID: EntityId = EntityId::Container(MetaString::from_static("singlecontainerid"));
303 const PREFIX_INODE: &str = "in-12345";
304 const INODE: EntityId = EntityId::ContainerInode(12345);
305
306 let cases = [
307 ("".into(), None),
309 ("in-notanumber".into(), None),
311 ("random".into(), Some(EntityId::Container("random".into()))),
313 (PREFIX_CID.into(), Some(CID.clone())),
315 (PREFIX_INODE.into(), Some(INODE.clone())),
316 (PREFIX_LEGACY_CID.into(), Some(CID.clone())),
317 (format!("{},{}", PREFIX_CID, PREFIX_INODE), Some(CID.clone())),
322 (format!("{},{}", PREFIX_INODE, PREFIX_CID), Some(CID.clone())),
323 (format!("{},{}", PREFIX_LEGACY_CID, PREFIX_INODE), Some(CID.clone())),
324 (format!("{},{}", PREFIX_INODE, PREFIX_LEGACY_CID), Some(CID.clone())),
325 (format!("{},invalid", PREFIX_CID), Some(CID.clone())),
326 (format!("{},invalid", PREFIX_LEGACY_CID), Some(CID.clone())),
327 (format!("{},invalid", PREFIX_INODE), Some(INODE.clone())),
328 ];
329
330 for (input, expected) in cases {
331 let actual = EntityId::from_local_data(input);
332 assert_eq!(actual, expected);
333 }
334 }
335
336 #[test]
337 fn pod_uid_valid() {
338 let pod_uid = "abcdef1234567890";
339 let entity_id = EntityId::from_pod_uid(pod_uid).unwrap();
340 assert_eq!(entity_id, EntityId::PodUid(MetaString::from(pod_uid)));
341 }
342
343 #[test]
344 fn pod_uid_none() {
345 let pod_uid = "none";
346 let entity_id = EntityId::from_pod_uid(pod_uid);
347 assert!(entity_id.is_none());
348 }
349}