1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2
3use crate::instrumentation::{
4 Capability, EventDelivery, EventEnvelope, EventKind, EventLocation, EventPhase,
5 InstrumentDirective, InstrumentHandle, InstrumentRegistration, ProjectionLimits,
6 ProjectionRequest, TargetDescriptor, TargetHandle, INSTRUMENTATION_EVENT_SCHEMA,
7 INSTRUMENTATION_PROTOCOL,
8};
9
10use super::{ControlLease, InstrumentationError, InstrumentationHub};
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct PortableProjection {
14 pub kind: String,
15 pub fields: BTreeMap<String, String>,
16}
17
18impl PortableProjection {
19 pub fn new(kind: impl Into<String>) -> Self {
20 Self {
21 kind: kind.into(),
22 fields: BTreeMap::new(),
23 }
24 }
25
26 pub fn with_field(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
27 self.fields.insert(name.into(), value.into());
28 self
29 }
30}
31
32#[derive(Debug, Clone, Default, PartialEq, Eq)]
33pub struct EventProjection {
34 pub current_frame: Option<PortableProjection>,
35 pub frames: Option<PortableProjection>,
36 pub locals: Option<PortableProjection>,
37 pub stack: Option<PortableProjection>,
38 pub value_preview: Option<PortableProjection>,
39 pub machine_snapshot: Option<PortableProjection>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct ProducerEvent {
44 pub phase: EventPhase,
45 pub event: EventKind,
46 pub data: BTreeMap<String, String>,
47}
48
49impl ProducerEvent {
50 pub fn live(event: EventKind) -> Self {
51 Self {
52 phase: EventPhase::Live,
53 event,
54 data: BTreeMap::new(),
55 }
56 }
57
58 pub fn with_data(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
59 self.data.insert(name.into(), value.into());
60 self
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct DeliveredEvent {
66 pub envelope: EventEnvelope,
67 pub projection: EventProjection,
68 pub dropped_before: u64,
71}
72
73#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct EventBatch {
75 pub events: Vec<DeliveredEvent>,
76 pub dropped: u64,
77}
78
79#[derive(Debug, Clone, Default, PartialEq, Eq)]
80pub struct DispatchReport {
81 pub callbacks: Vec<DeliveredEvent>,
84 pub queued: usize,
85 pub dropped: usize,
86}
87
88pub trait EventAccess {
91 fn source_location(&mut self) -> Option<EventLocation> {
92 None
93 }
94
95 fn current_frame(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
96 None
97 }
98
99 fn frames(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
100 None
101 }
102
103 fn locals(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
104 None
105 }
106
107 fn stack(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
108 None
109 }
110
111 fn value_preview(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
112 None
113 }
114
115 fn machine_snapshot(&mut self, _limits: ProjectionLimits) -> Option<PortableProjection> {
116 None
117 }
118}
119
120#[derive(Debug, Default)]
121pub(super) struct DeliveryState {
122 queues: BTreeMap<InstrumentHandle, InstrumentQueue>,
123 sequences: BTreeMap<TargetHandle, u64>,
124 directives: BTreeMap<TargetHandle, InstrumentDirective>,
125}
126
127#[derive(Debug)]
128struct InstrumentQueue {
129 capacity: usize,
130 events: VecDeque<DeliveredEvent>,
131 dropped: u64,
132}
133
134impl InstrumentQueue {
135 fn new(capacity: usize) -> Self {
136 Self {
137 capacity,
138 events: VecDeque::with_capacity(capacity),
139 dropped: 0,
140 }
141 }
142
143 fn push(&mut self, mut event: DeliveredEvent) -> bool {
144 let dropped = if self.events.len() == self.capacity {
145 self.events.pop_front();
146 self.dropped = self.dropped.saturating_add(1);
147 true
148 } else {
149 false
150 };
151 event.dropped_before = self.dropped;
152 self.events.push_back(event);
153 dropped
154 }
155
156 fn drain(&mut self) -> EventBatch {
157 let events = self.events.drain(..).collect();
158 let dropped = std::mem::take(&mut self.dropped);
159 EventBatch { events, dropped }
160 }
161}
162
163impl DeliveryState {
164 pub(super) fn remove_instrument(&mut self, instrument: &InstrumentHandle) {
165 self.queues.remove(instrument);
166 }
167
168 pub(super) fn remove_directive(&mut self, target: &TargetHandle) {
169 self.directives.remove(target);
170 }
171
172 pub(super) fn remove_target(&mut self, target: &TargetHandle) {
173 self.sequences.remove(target);
174 self.directives.remove(target);
175 for queue in self.queues.values_mut() {
176 queue
177 .events
178 .retain(|event| event.envelope.target_id != target.target_id());
179 }
180 }
181
182 pub(super) fn clear(&mut self) {
183 self.queues.clear();
184 self.sequences.clear();
185 self.directives.clear();
186 }
187}
188
189impl InstrumentDirective {
190 pub const fn required_capability(self) -> Option<Capability> {
191 match self {
192 Self::Continue => None,
193 Self::Suspend => Some(Capability::ControlPause),
194 Self::StepNext => Some(Capability::ControlSingleStep),
195 Self::Terminate => Some(Capability::ControlTerminate),
196 }
197 }
198}
199
200impl ProjectionRequest {
201 pub fn is_empty(&self) -> bool {
202 !self.source_location
203 && self.current_frame.is_none()
204 && self.frames.is_none()
205 && self.locals.is_none()
206 && self.stack.is_none()
207 && self.value_preview.is_none()
208 && self.machine_snapshot.is_none()
209 }
210
211 pub fn needs_interpreter_environment(&self) -> bool {
212 self.current_frame.is_some() || self.frames.is_some()
213 }
214
215 pub fn merge_from(&mut self, other: &Self) {
216 self.source_location |= other.source_location;
217 self.current_frame = merge_limits(self.current_frame, other.current_frame);
218 self.frames = merge_limits(self.frames, other.frames);
219 self.locals = merge_limits(self.locals, other.locals);
220 self.stack = merge_limits(self.stack, other.stack);
221 self.value_preview = merge_limits(self.value_preview, other.value_preview);
222 self.machine_snapshot = merge_limits(self.machine_snapshot, other.machine_snapshot);
223 }
224}
225
226impl InstrumentationHub {
227 pub fn target_descriptor(
228 &self,
229 target: &TargetHandle,
230 ) -> Result<&TargetDescriptor, InstrumentationError> {
231 Ok(&self.resolve_target(target)?.descriptor)
232 }
233
234 pub fn instrument_registration(
235 &self,
236 instrument: &InstrumentHandle,
237 ) -> Result<&InstrumentRegistration, InstrumentationError> {
238 Ok(&self.resolve_instrument(instrument)?.registration)
239 }
240
241 pub fn requested_projection(
245 &self,
246 target: &TargetHandle,
247 event: EventKind,
248 ) -> Result<ProjectionRequest, InstrumentationError> {
249 self.resolve_target(target)?;
250 if !self.enabled_events.contains(event) {
251 return Ok(ProjectionRequest::default());
252 }
253 let mut projection = ProjectionRequest::default();
254 for attachment in self.attachments_for_target(target)? {
255 let record = self.resolve_instrument(&attachment.instrument)?;
256 if record.registration.events.contains(&event) {
257 projection.merge_from(&record.registration.projection);
258 }
259 }
260 Ok(projection)
261 }
262
263 pub fn emit<A: EventAccess>(
267 &mut self,
268 target: &TargetHandle,
269 event: ProducerEvent,
270 access: &mut A,
271 ) -> Result<DispatchReport, InstrumentationError> {
272 if !self.enabled_events.contains(event.event) {
273 self.resolve_target(target)?;
274 return Ok(DispatchReport::default());
275 }
276
277 let descriptor = self.resolve_target(target)?.descriptor.clone();
278 let subscriptions = self
279 .attachments_for_target(target)?
280 .into_iter()
281 .filter_map(|attachment| {
282 let record = self.resolve_instrument(&attachment.instrument).ok()?;
283 record.registration.events.contains(&event.event).then(|| {
284 (
285 attachment.instrument.clone(),
286 record.registration.projection.clone(),
287 record.registration.delivery.clone(),
288 )
289 })
290 })
291 .collect::<Vec<_>>();
292 if subscriptions.is_empty() {
293 return Ok(DispatchReport::default());
294 }
295
296 let sequence = self.delivery.sequences.entry(target.clone()).or_insert(0);
297 *sequence = sequence.saturating_add(1);
298 let sequence = *sequence;
299 let mut report = DispatchReport::default();
300
301 for (instrument, request, delivery) in subscriptions {
302 let (location, projection) = materialize(&request, access);
303 let delivered = DeliveredEvent {
304 envelope: EventEnvelope {
305 schema: INSTRUMENTATION_EVENT_SCHEMA.into(),
306 protocol: INSTRUMENTATION_PROTOCOL.into(),
307 instrument_id: instrument.instrument_id().into(),
308 runtime: descriptor.backend.clone(),
309 session_id: descriptor.session_id.clone(),
310 target_id: descriptor.target_id.clone(),
311 target_kind: descriptor.kind,
312 generation: target.generation(),
313 sequence,
314 phase: event.phase,
315 event: event.event,
316 location,
317 data: event.data.clone(),
318 },
319 projection,
320 dropped_before: 0,
321 };
322 match delivery {
323 EventDelivery::Callback => report.callbacks.push(delivered),
324 EventDelivery::Queue { capacity } => {
325 let queue = self
326 .delivery
327 .queues
328 .entry(instrument)
329 .or_insert_with(|| InstrumentQueue::new(capacity));
330 debug_assert_eq!(queue.capacity, capacity);
331 if queue.push(delivered) {
332 report.dropped = report.dropped.saturating_add(1);
333 }
334 report.queued = report.queued.saturating_add(1);
335 }
336 }
337 }
338 Ok(report)
339 }
340
341 pub fn drain_events(
342 &mut self,
343 instrument: &InstrumentHandle,
344 ) -> Result<EventBatch, InstrumentationError> {
345 let registration = self.resolve_instrument(instrument)?.registration.clone();
346 let EventDelivery::Queue { capacity } = registration.delivery else {
347 return Ok(EventBatch::default());
348 };
349 Ok(self
350 .delivery
351 .queues
352 .entry(instrument.clone())
353 .or_insert_with(|| InstrumentQueue::new(capacity))
354 .drain())
355 }
356
357 pub fn queued_event_count(
358 &self,
359 instrument: &InstrumentHandle,
360 ) -> Result<usize, InstrumentationError> {
361 self.resolve_instrument(instrument)?;
362 Ok(self
363 .delivery
364 .queues
365 .get(instrument)
366 .map_or(0, |queue| queue.events.len()))
367 }
368
369 pub fn authorize_control(
370 &self,
371 lease: &ControlLease,
372 capability: Capability,
373 ) -> Result<(), InstrumentationError> {
374 let instrument = self.resolve_instrument(&lease.instrument)?;
375 let target = self.resolve_target(&lease.target)?;
376 match self.control_leases.get(&lease.target) {
377 Some(holder) if holder == &lease.instrument => {}
378 _ => {
379 return Err(InstrumentationError::InvalidControlLease {
380 target_id: lease.target.target_id().into(),
381 instrument_id: lease.instrument.instrument_id().into(),
382 });
383 }
384 }
385 if !capability.is_control() || !instrument.registration.capabilities.contains(&capability) {
386 return Err(InstrumentationError::UnsupportedCapabilities {
387 target_id: target.descriptor.target_id.clone(),
388 backend: target.descriptor.backend.clone(),
389 missing: BTreeSet::from([capability]),
390 });
391 }
392 Ok(())
393 }
394
395 pub fn request_directive(
398 &mut self,
399 lease: &ControlLease,
400 directive: InstrumentDirective,
401 ) -> Result<(), InstrumentationError> {
402 if let Some(capability) = directive.required_capability() {
403 self.authorize_control(lease, capability)?;
404 self.delivery
405 .directives
406 .insert(lease.target.clone(), directive);
407 } else {
408 self.authorize_lease(lease)?;
409 self.delivery.directives.remove(&lease.target);
410 }
411 Ok(())
412 }
413
414 pub fn take_directive(
415 &mut self,
416 target: &TargetHandle,
417 ) -> Result<InstrumentDirective, InstrumentationError> {
418 self.resolve_target(target)?;
419 Ok(self
420 .delivery
421 .directives
422 .remove(target)
423 .unwrap_or(InstrumentDirective::Continue))
424 }
425
426 fn authorize_lease(&self, lease: &ControlLease) -> Result<(), InstrumentationError> {
427 self.resolve_instrument(&lease.instrument)?;
428 self.resolve_target(&lease.target)?;
429 match self.control_leases.get(&lease.target) {
430 Some(holder) if holder == &lease.instrument => Ok(()),
431 _ => Err(InstrumentationError::InvalidControlLease {
432 target_id: lease.target.target_id().into(),
433 instrument_id: lease.instrument.instrument_id().into(),
434 }),
435 }
436 }
437}
438
439fn merge_limits(
440 left: Option<ProjectionLimits>,
441 right: Option<ProjectionLimits>,
442) -> Option<ProjectionLimits> {
443 match (left, right) {
444 (None, value) | (value, None) => value,
445 (Some(left), Some(right)) => Some(ProjectionLimits {
446 max_items: left.max_items.max(right.max_items),
447 max_depth: left.max_depth.max(right.max_depth),
448 max_bytes: left.max_bytes.max(right.max_bytes),
449 }),
450 }
451}
452
453fn materialize<A: EventAccess>(
454 request: &ProjectionRequest,
455 access: &mut A,
456) -> (Option<EventLocation>, EventProjection) {
457 let location = request
458 .source_location
459 .then(|| access.source_location())
460 .flatten();
461 let projection = EventProjection {
462 current_frame: request
463 .current_frame
464 .and_then(|limits| access.current_frame(limits)),
465 frames: request.frames.and_then(|limits| access.frames(limits)),
466 locals: request.locals.and_then(|limits| access.locals(limits)),
467 stack: request.stack.and_then(|limits| access.stack(limits)),
468 value_preview: request
469 .value_preview
470 .and_then(|limits| access.value_preview(limits)),
471 machine_snapshot: request
472 .machine_snapshot
473 .and_then(|limits| access.machine_snapshot(limits)),
474 };
475 (location, projection)
476}