Skip to main content

hara_native/instrumentation/
hub.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::error::Error;
3use std::fmt;
4
5use super::{
6    Capability, EventKind, EventMask, InstrumentHandle, InstrumentMode, InstrumentRegistration,
7    RuntimeBackend, TargetDescriptor, TargetHandle,
8};
9
10#[path = "hub/delivery.rs"]
11mod delivery;
12pub use delivery::{
13    DeliveredEvent, DispatchReport, EventAccess, EventBatch, EventProjection, PortableProjection,
14    ProducerEvent,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct InstrumentationAttachment {
19    pub instrument: InstrumentHandle,
20    pub target: TargetHandle,
21    pub granted_capabilities: BTreeSet<Capability>,
22    pub registration_order: u64,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ControlLease {
27    instrument: InstrumentHandle,
28    target: TargetHandle,
29}
30
31impl ControlLease {
32    pub fn instrument(&self) -> &InstrumentHandle {
33        &self.instrument
34    }
35
36    pub fn target(&self) -> &TargetHandle {
37        &self.target
38    }
39}
40
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
42pub struct SessionCleanup {
43    pub instruments: usize,
44    pub targets: usize,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum InstrumentationError {
49    InvalidRegistration(String),
50    InvalidTarget(String),
51    Execution(String),
52    DuplicateInstrument(String),
53    DuplicateTarget(String),
54    DuplicateAttachment {
55        instrument_id: String,
56        target_id: String,
57    },
58    UnknownInstrument(String),
59    UnknownTarget(String),
60    StaleInstrumentHandle {
61        instrument_id: String,
62        generation: u64,
63    },
64    StaleTargetHandle {
65        target_id: String,
66        generation: u64,
67    },
68    SessionMismatch {
69        instrument_session: String,
70        target_session: String,
71    },
72    FilterMismatch {
73        instrument_id: String,
74        target_id: String,
75    },
76    UnsupportedCapabilities {
77        target_id: String,
78        backend: RuntimeBackend,
79        missing: BTreeSet<Capability>,
80    },
81    UnsupportedEvents {
82        target_id: String,
83        backend: RuntimeBackend,
84        events: BTreeSet<EventKind>,
85    },
86    ControlModeRequired(String),
87    AttachmentRequired {
88        instrument_id: String,
89        target_id: String,
90    },
91    ControlLeaseHeld {
92        target_id: String,
93        holder: String,
94    },
95    InvalidControlLease {
96        target_id: String,
97        instrument_id: String,
98    },
99}
100
101impl fmt::Display for InstrumentationError {
102    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::InvalidRegistration(message) => {
105                write!(formatter, "instrumentation/invalid-registration: {message}")
106            }
107            Self::InvalidTarget(message) => {
108                write!(formatter, "instrumentation/invalid-target: {message}")
109            }
110            Self::Execution(message) => {
111                write!(formatter, "instrumentation/execution: {message}")
112            }
113            Self::DuplicateInstrument(id) => {
114                write!(formatter, "instrumentation/duplicate-instrument: {id}")
115            }
116            Self::DuplicateTarget(id) => {
117                write!(formatter, "instrumentation/duplicate-target: {id}")
118            }
119            Self::DuplicateAttachment {
120                instrument_id,
121                target_id,
122            } => write!(
123                formatter,
124                "instrumentation/duplicate-attachment: {instrument_id} -> {target_id}"
125            ),
126            Self::UnknownInstrument(id) => {
127                write!(formatter, "instrumentation/unknown-instrument: {id}")
128            }
129            Self::UnknownTarget(id) => {
130                write!(formatter, "instrumentation/unknown-target: {id}")
131            }
132            Self::StaleInstrumentHandle {
133                instrument_id,
134                generation,
135            } => write!(
136                formatter,
137                "instrumentation/stale-instrument: {instrument_id}@{generation}"
138            ),
139            Self::StaleTargetHandle {
140                target_id,
141                generation,
142            } => write!(
143                formatter,
144                "instrumentation/stale-target: {target_id}@{generation}"
145            ),
146            Self::SessionMismatch {
147                instrument_session,
148                target_session,
149            } => write!(
150                formatter,
151                "instrumentation/session-mismatch: instrument {instrument_session}, target {target_session}"
152            ),
153            Self::FilterMismatch {
154                instrument_id,
155                target_id,
156            } => write!(
157                formatter,
158                "instrumentation/filter-mismatch: {instrument_id} -> {target_id}"
159            ),
160            Self::UnsupportedCapabilities {
161                target_id,
162                backend,
163                missing,
164            } => write!(
165                formatter,
166                "instrumentation/unsupported-capabilities: target {target_id}, backend {}, missing {missing:?}",
167                backend.as_str()
168            ),
169            Self::UnsupportedEvents {
170                target_id,
171                backend,
172                events,
173            } => write!(
174                formatter,
175                "instrumentation/unsupported-events: target {target_id}, backend {}, events {events:?}",
176                backend.as_str()
177            ),
178            Self::ControlModeRequired(id) => {
179                write!(formatter, "instrumentation/control-mode-required: {id}")
180            }
181            Self::AttachmentRequired {
182                instrument_id,
183                target_id,
184            } => write!(
185                formatter,
186                "instrumentation/attachment-required: {instrument_id} -> {target_id}"
187            ),
188            Self::ControlLeaseHeld { target_id, holder } => write!(
189                formatter,
190                "instrumentation/control-lease-held: target {target_id}, holder {holder}"
191            ),
192            Self::InvalidControlLease {
193                target_id,
194                instrument_id,
195            } => write!(
196                formatter,
197                "instrumentation/invalid-control-lease: {instrument_id} -> {target_id}"
198            ),
199        }
200    }
201}
202
203impl Error for InstrumentationError {}
204
205#[derive(Debug, Clone)]
206struct InstrumentRecord {
207    handle: InstrumentHandle,
208    registration: InstrumentRegistration,
209    order: u64,
210}
211
212#[derive(Debug, Clone)]
213struct TargetRecord {
214    handle: TargetHandle,
215    descriptor: TargetDescriptor,
216}
217
218#[derive(Debug, Default)]
219pub struct InstrumentationHub {
220    registrations: BTreeMap<u64, InstrumentRecord>,
221    instrument_orders: BTreeMap<String, u64>,
222    instrument_generations: BTreeMap<String, u64>,
223    targets: BTreeMap<String, TargetRecord>,
224    target_generations: BTreeMap<String, u64>,
225    attachments: BTreeMap<(InstrumentHandle, TargetHandle), InstrumentationAttachment>,
226    enabled_events: EventMask,
227    control_leases: BTreeMap<TargetHandle, InstrumentHandle>,
228    next_registration_order: u64,
229    delivery: delivery::DeliveryState,
230}
231
232impl InstrumentationHub {
233    pub fn new() -> Self {
234        Self::default()
235    }
236
237    pub const fn enabled_events(&self) -> EventMask {
238        self.enabled_events
239    }
240
241    pub fn registration_count(&self) -> usize {
242        self.registrations.len()
243    }
244
245    pub fn target_count(&self) -> usize {
246        self.targets.len()
247    }
248
249    pub fn attachment_count(&self) -> usize {
250        self.attachments.len()
251    }
252
253    pub fn registrations(
254        &self,
255    ) -> impl Iterator<Item = (&InstrumentHandle, &InstrumentRegistration)> {
256        self.registrations
257            .values()
258            .map(|record| (&record.handle, &record.registration))
259    }
260
261    pub fn register(
262        &mut self,
263        registration: InstrumentRegistration,
264    ) -> Result<InstrumentHandle, InstrumentationError> {
265        registration
266            .validate()
267            .map_err(|message| InstrumentationError::InvalidRegistration(message.into()))?;
268        if self
269            .instrument_orders
270            .contains_key(&registration.instrument_id)
271        {
272            return Err(InstrumentationError::DuplicateInstrument(
273                registration.instrument_id,
274            ));
275        }
276        let generation = next_generation(
277            &mut self.instrument_generations,
278            &registration.instrument_id,
279        );
280        let handle = InstrumentHandle::new(registration.instrument_id.clone(), generation);
281        let order = self.next_registration_order;
282        self.next_registration_order = self.next_registration_order.saturating_add(1);
283        self.instrument_orders
284            .insert(registration.instrument_id.clone(), order);
285        self.registrations.insert(
286            order,
287            InstrumentRecord {
288                handle: handle.clone(),
289                registration,
290                order,
291            },
292        );
293        Ok(handle)
294    }
295
296    pub fn register_target(
297        &mut self,
298        descriptor: TargetDescriptor,
299    ) -> Result<TargetHandle, InstrumentationError> {
300        descriptor
301            .validate()
302            .map_err(|message| InstrumentationError::InvalidTarget(message.into()))?;
303        if self.targets.contains_key(&descriptor.target_id) {
304            return Err(InstrumentationError::DuplicateTarget(descriptor.target_id));
305        }
306        let generation = next_generation(&mut self.target_generations, &descriptor.target_id);
307        let handle = TargetHandle::new(descriptor.target_id.clone(), generation);
308        self.targets.insert(
309            descriptor.target_id.clone(),
310            TargetRecord {
311                handle: handle.clone(),
312                descriptor,
313            },
314        );
315        Ok(handle)
316    }
317
318    pub fn attach(
319        &mut self,
320        instrument: &InstrumentHandle,
321        target: &TargetHandle,
322    ) -> Result<InstrumentationAttachment, InstrumentationError> {
323        let instrument_record = self.resolve_instrument(instrument)?;
324        let target_record = self.resolve_target(target)?;
325        if instrument_record.registration.session_id != target_record.descriptor.session_id {
326            return Err(InstrumentationError::SessionMismatch {
327                instrument_session: instrument_record.registration.session_id.clone(),
328                target_session: target_record.descriptor.session_id.clone(),
329            });
330        }
331        if !instrument_record
332            .registration
333            .filter
334            .matches(&target_record.descriptor)
335        {
336            return Err(InstrumentationError::FilterMismatch {
337                instrument_id: instrument.instrument_id().into(),
338                target_id: target.target_id().into(),
339            });
340        }
341        let unsupported_events = instrument_record
342            .registration
343            .events
344            .iter()
345            .copied()
346            .filter(|event| !event.supports_target(target_record.descriptor.kind))
347            .collect::<BTreeSet<_>>();
348        if !unsupported_events.is_empty() {
349            return Err(InstrumentationError::UnsupportedEvents {
350                target_id: target.target_id().into(),
351                backend: target_record.descriptor.backend.clone(),
352                events: unsupported_events,
353            });
354        }
355        let missing = instrument_record
356            .registration
357            .capabilities
358            .difference(&target_record.descriptor.capabilities)
359            .copied()
360            .collect::<BTreeSet<_>>();
361        if !missing.is_empty() {
362            return Err(InstrumentationError::UnsupportedCapabilities {
363                target_id: target.target_id().into(),
364                backend: target_record.descriptor.backend.clone(),
365                missing,
366            });
367        }
368        let key = (instrument.clone(), target.clone());
369        if self.attachments.contains_key(&key) {
370            return Err(InstrumentationError::DuplicateAttachment {
371                instrument_id: instrument.instrument_id().into(),
372                target_id: target.target_id().into(),
373            });
374        }
375        let attachment = InstrumentationAttachment {
376            instrument: instrument.clone(),
377            target: target.clone(),
378            granted_capabilities: instrument_record.registration.capabilities.clone(),
379            registration_order: instrument_record.order,
380        };
381        self.attachments.insert(key, attachment.clone());
382        self.recompute_event_mask();
383        Ok(attachment)
384    }
385
386    pub fn attachments_for_target(
387        &self,
388        target: &TargetHandle,
389    ) -> Result<Vec<&InstrumentationAttachment>, InstrumentationError> {
390        self.resolve_target(target)?;
391        let mut attachments = self
392            .attachments
393            .values()
394            .filter(|attachment| &attachment.target == target)
395            .collect::<Vec<_>>();
396        attachments.sort_by_key(|attachment| attachment.registration_order);
397        Ok(attachments)
398    }
399
400    pub fn enabled_for_target(
401        &self,
402        target: &TargetHandle,
403        event: EventKind,
404    ) -> Result<bool, InstrumentationError> {
405        self.resolve_target(target)?;
406        if !self.enabled_events.contains(event) {
407            return Ok(false);
408        }
409        Ok(self.attachments.iter().any(|((instrument, attached), _)| {
410            attached == target
411                && self
412                    .resolve_instrument(instrument)
413                    .map_or(false, |record| record.registration.events.contains(&event))
414        }))
415    }
416
417    pub fn acquire_control(
418        &mut self,
419        instrument: &InstrumentHandle,
420        target: &TargetHandle,
421    ) -> Result<ControlLease, InstrumentationError> {
422        let record = self.resolve_instrument(instrument)?;
423        self.resolve_target(target)?;
424        if record.registration.mode != InstrumentMode::Control {
425            return Err(InstrumentationError::ControlModeRequired(
426                instrument.instrument_id().into(),
427            ));
428        }
429        if !self
430            .attachments
431            .contains_key(&(instrument.clone(), target.clone()))
432        {
433            return Err(InstrumentationError::AttachmentRequired {
434                instrument_id: instrument.instrument_id().into(),
435                target_id: target.target_id().into(),
436            });
437        }
438        if let Some(holder) = self.control_leases.get(target) {
439            if holder != instrument {
440                return Err(InstrumentationError::ControlLeaseHeld {
441                    target_id: target.target_id().into(),
442                    holder: holder.instrument_id().into(),
443                });
444            }
445        }
446        self.control_leases
447            .insert(target.clone(), instrument.clone());
448        Ok(ControlLease {
449            instrument: instrument.clone(),
450            target: target.clone(),
451        })
452    }
453
454    pub fn release_control(&mut self, lease: &ControlLease) -> Result<(), InstrumentationError> {
455        self.resolve_instrument(&lease.instrument)?;
456        self.resolve_target(&lease.target)?;
457        match self.control_leases.get(&lease.target) {
458            Some(holder) if holder == &lease.instrument => {
459                self.control_leases.remove(&lease.target);
460                self.delivery.remove_directive(&lease.target);
461                Ok(())
462            }
463            _ => Err(InstrumentationError::InvalidControlLease {
464                target_id: lease.target.target_id().into(),
465                instrument_id: lease.instrument.instrument_id().into(),
466            }),
467        }
468    }
469
470    pub fn detach(&mut self, instrument: &InstrumentHandle) -> Result<(), InstrumentationError> {
471        let order = self.resolve_instrument(instrument)?.order;
472        self.registrations.remove(&order);
473        self.instrument_orders.remove(instrument.instrument_id());
474        self.attachments
475            .retain(|(candidate, _), _| candidate != instrument);
476        let controlled_targets = self
477            .control_leases
478            .iter()
479            .filter(|(_, holder)| *holder == instrument)
480            .map(|(target, _)| (*target).clone())
481            .collect::<Vec<_>>();
482        self.control_leases.retain(|_, holder| holder != instrument);
483        for target in &controlled_targets {
484            self.delivery.remove_directive(target);
485        }
486        self.delivery.remove_instrument(instrument);
487        self.recompute_event_mask();
488        Ok(())
489    }
490
491    pub fn remove_target(&mut self, target: &TargetHandle) -> Result<(), InstrumentationError> {
492        self.resolve_target(target)?;
493        self.targets.remove(target.target_id());
494        self.attachments
495            .retain(|(_, candidate), _| candidate != target);
496        self.control_leases.remove(target);
497        self.delivery.remove_target(target);
498        self.recompute_event_mask();
499        Ok(())
500    }
501
502    pub fn detach_session(&mut self, session_id: &str) -> SessionCleanup {
503        let instruments = self
504            .registrations
505            .values()
506            .filter(|record| record.registration.session_id == session_id)
507            .map(|record| record.handle.clone())
508            .collect::<Vec<_>>();
509        let targets = self
510            .targets
511            .values()
512            .filter(|record| record.descriptor.session_id == session_id)
513            .map(|record| record.handle.clone())
514            .collect::<Vec<_>>();
515        for instrument in &instruments {
516            self.detach(instrument)
517                .expect("session cleanup collected a live instrument handle");
518        }
519        for target in &targets {
520            self.remove_target(target)
521                .expect("session cleanup collected a live target handle");
522        }
523        SessionCleanup {
524            instruments: instruments.len(),
525            targets: targets.len(),
526        }
527    }
528
529    pub fn clear(&mut self) {
530        self.registrations.clear();
531        self.instrument_orders.clear();
532        self.targets.clear();
533        self.attachments.clear();
534        self.control_leases.clear();
535        self.enabled_events = EventMask::empty();
536        self.delivery.clear();
537    }
538
539    fn resolve_instrument(
540        &self,
541        handle: &InstrumentHandle,
542    ) -> Result<&InstrumentRecord, InstrumentationError> {
543        let Some(order) = self.instrument_orders.get(handle.instrument_id()) else {
544            return if self
545                .instrument_generations
546                .contains_key(handle.instrument_id())
547            {
548                Err(InstrumentationError::StaleInstrumentHandle {
549                    instrument_id: handle.instrument_id().into(),
550                    generation: handle.generation(),
551                })
552            } else {
553                Err(InstrumentationError::UnknownInstrument(
554                    handle.instrument_id().into(),
555                ))
556            };
557        };
558        let record = self
559            .registrations
560            .get(order)
561            .expect("instrument index and registry must remain consistent");
562        if &record.handle == handle {
563            Ok(record)
564        } else {
565            Err(InstrumentationError::StaleInstrumentHandle {
566                instrument_id: handle.instrument_id().into(),
567                generation: handle.generation(),
568            })
569        }
570    }
571
572    fn resolve_target(&self, handle: &TargetHandle) -> Result<&TargetRecord, InstrumentationError> {
573        let Some(record) = self.targets.get(handle.target_id()) else {
574            return if self.target_generations.contains_key(handle.target_id()) {
575                Err(InstrumentationError::StaleTargetHandle {
576                    target_id: handle.target_id().into(),
577                    generation: handle.generation(),
578                })
579            } else {
580                Err(InstrumentationError::UnknownTarget(
581                    handle.target_id().into(),
582                ))
583            };
584        };
585        if &record.handle == handle {
586            Ok(record)
587        } else {
588            Err(InstrumentationError::StaleTargetHandle {
589                target_id: handle.target_id().into(),
590                generation: handle.generation(),
591            })
592        }
593    }
594
595    fn recompute_event_mask(&mut self) {
596        let mut mask = EventMask::empty();
597        for (instrument, _) in self.attachments.keys() {
598            if let Ok(record) = self.resolve_instrument(instrument) {
599                for event in &record.registration.events {
600                    mask.insert(*event);
601                }
602            }
603        }
604        self.enabled_events = mask;
605    }
606}
607
608fn next_generation(generations: &mut BTreeMap<String, u64>, id: &str) -> u64 {
609    let next = generations.entry(id.into()).or_insert(0);
610    let generation = *next;
611    *next = next.saturating_add(1);
612    generation
613}