1use arrow_array::builder::{
19 FixedSizeBinaryBuilder, StringBuilder, TimestampNanosecondBuilder, UInt8Builder, UInt16Builder,
20 UInt32Builder, UInt64Builder,
21};
22use arrow_array::{ArrayRef, RecordBatch};
23use std::sync::Arc;
24
25use mira_proto::collector::trace::v1::ExportTraceServiceRequest;
26use mira_proto::trace::v1::Span;
27
28use crate::attrs::{AttrsBuilder, DictColumn, ResourceScope, resource_kv, scope_kv};
29use crate::error::Result;
30use crate::logs::{append_fixed, nanos};
31use crate::schema::{SPAN_EVENTS, SPAN_LINKS, SPANS};
32use crate::signal::{Sealed, Sidecars, SignalBuilder};
33
34pub struct TracesBuilder {
35 id: UInt32Builder,
36 trace_id: FixedSizeBinaryBuilder,
37 span_id: FixedSizeBinaryBuilder,
38 parent_span_id: FixedSizeBinaryBuilder,
39 trace_state: StringBuilder,
40 flags: UInt32Builder,
41 name: DictColumn,
42 kind: UInt8Builder,
43 start: TimestampNanosecondBuilder,
44 duration: UInt64Builder,
45 status_code: UInt8Builder,
46 status_message: StringBuilder,
47 dropped_attrs: UInt32Builder,
48 dropped_events: UInt32Builder,
49 dropped_links: UInt32Builder,
50 resource_id: UInt16Builder,
51 scope_id: UInt16Builder,
52 span_attrs: AttrsBuilder,
53
54 ev_id: UInt32Builder,
55 ev_parent: UInt32Builder,
56 ev_time: TimestampNanosecondBuilder,
57 ev_name: DictColumn,
58 ev_dropped: UInt32Builder,
59 event_attrs: AttrsBuilder,
60 next_event_id: u32,
61
62 ln_id: UInt32Builder,
63 ln_parent: UInt32Builder,
64 ln_trace_id: FixedSizeBinaryBuilder,
65 ln_span_id: FixedSizeBinaryBuilder,
66 ln_trace_state: StringBuilder,
67 ln_flags: UInt32Builder,
68 ln_dropped: UInt32Builder,
69 link_attrs: AttrsBuilder,
70 next_link_id: u32,
71
72 rs: ResourceScope,
73 next_id: u32,
74 min_ts: i64,
75 max_ts: i64,
76}
77
78impl Default for TracesBuilder {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84impl TracesBuilder {
85 pub fn new() -> Self {
86 Self {
87 id: UInt32Builder::new(),
88 trace_id: FixedSizeBinaryBuilder::new(16),
89 span_id: FixedSizeBinaryBuilder::new(8),
90 parent_span_id: FixedSizeBinaryBuilder::new(8),
91 trace_state: StringBuilder::new(),
92 flags: UInt32Builder::new(),
93 name: DictColumn::new("spans.name"),
94 kind: UInt8Builder::new(),
95 start: TimestampNanosecondBuilder::new(),
96 duration: UInt64Builder::new(),
97 status_code: UInt8Builder::new(),
98 status_message: StringBuilder::new(),
99 dropped_attrs: UInt32Builder::new(),
100 dropped_events: UInt32Builder::new(),
101 dropped_links: UInt32Builder::new(),
102 resource_id: UInt16Builder::new(),
103 scope_id: UInt16Builder::new(),
104 span_attrs: AttrsBuilder::new("span_attrs.key"),
105
106 ev_id: UInt32Builder::new(),
107 ev_parent: UInt32Builder::new(),
108 ev_time: TimestampNanosecondBuilder::new(),
109 ev_name: DictColumn::new("span_events.name"),
110 ev_dropped: UInt32Builder::new(),
111 event_attrs: AttrsBuilder::new("span_event_attrs.key"),
112 next_event_id: 0,
113
114 ln_id: UInt32Builder::new(),
115 ln_parent: UInt32Builder::new(),
116 ln_trace_id: FixedSizeBinaryBuilder::new(16),
117 ln_span_id: FixedSizeBinaryBuilder::new(8),
118 ln_trace_state: StringBuilder::new(),
119 ln_flags: UInt32Builder::new(),
120 ln_dropped: UInt32Builder::new(),
121 link_attrs: AttrsBuilder::new("span_link_attrs.key"),
122 next_link_id: 0,
123
124 rs: ResourceScope::new(),
125 next_id: 0,
126 min_ts: i64::MAX,
127 max_ts: i64::MIN,
128 }
129 }
130
131 pub fn num_rows(&self) -> usize {
132 self.next_id as usize
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.next_id == 0
137 }
138
139 pub fn approx_bytes(&self) -> usize {
143 self.next_id as usize * 96
144 + self.next_event_id as usize * 24
145 + self.next_link_id as usize * 48
146 + (self.span_attrs.len()
147 + self.event_attrs.len()
148 + self.link_attrs.len()
149 + self.rs.len())
150 * 48
151 + self.trace_state.values_slice().len()
152 + self.status_message.values_slice().len()
153 + self.ln_trace_state.values_slice().len()
154 + self.span_attrs.heap_bytes()
155 + self.event_attrs.heap_bytes()
156 + self.link_attrs.heap_bytes()
157 + self.rs.heap_bytes()
158 }
159
160 pub fn has_headroom_for(&self, req: &ExportTraceServiceRequest) -> bool {
161 let (mut resources, mut scopes) = (0usize, 0usize);
162 let (mut res_kv, mut sc_kv) = (0usize, 0usize);
163 let (mut spans, mut events) = (0usize, 0usize);
166 let (mut span_kv, mut ev_kv, mut ln_kv) = (0usize, 0usize, 0usize);
167 for rs in &req.resource_spans {
168 resources += 1;
169 res_kv += resource_kv(rs.resource.as_ref());
170 for ss in &rs.scope_spans {
171 scopes += 1;
172 sc_kv += scope_kv(ss.scope.as_ref());
173 spans += ss.spans.len();
174 for s in &ss.spans {
175 span_kv += s.attributes.len();
176 events += s.events.len();
177 ev_kv += s.events.iter().map(|e| e.attributes.len()).sum::<usize>();
178 ln_kv += s.links.iter().map(|l| l.attributes.len()).sum::<usize>();
179 }
180 }
181 }
182 self.rs.has_headroom(resources, scopes, res_kv, sc_kv)
183 && self.span_attrs.has_headroom(span_kv)
184 && self.event_attrs.has_headroom(ev_kv)
185 && self.link_attrs.has_headroom(ln_kv)
186 && self.name.has_headroom(spans)
187 && self.ev_name.has_headroom(events)
188 }
189
190 pub fn append_request(&mut self, req: &ExportTraceServiceRequest) -> Result<usize> {
191 let mut added = 0;
192 for rs in &req.resource_spans {
193 let rid = self.rs.resource(rs.resource.as_ref())?;
194 for ss in &rs.scope_spans {
195 let sid = self.rs.scope(ss.scope.as_ref())?;
196 for span in &ss.spans {
197 self.append_span(span, rid, sid)?;
198 added += 1;
199 }
200 }
201 }
202 Ok(added)
203 }
204
205 fn append_span(&mut self, s: &Span, rid: u16, sid: u16) -> Result<()> {
206 self.name.append(&s.name)?;
209
210 let id = self.next_id;
211 self.next_id += 1;
212
213 let start = nanos(s.start_time_unix_nano);
214 let end = nanos(s.end_time_unix_nano);
215 let duration = s.end_time_unix_nano.saturating_sub(s.start_time_unix_nano);
220 if start != 0 {
229 self.min_ts = self.min_ts.min(start);
230 self.max_ts = self.max_ts.max(start.max(end));
231 }
232
233 self.id.append_value(id);
234 append_fixed(&mut self.trace_id, &s.trace_id, 16)?;
235 append_fixed(&mut self.span_id, &s.span_id, 8)?;
236 append_fixed(&mut self.parent_span_id, &s.parent_span_id, 8)?;
237 opt_str(&mut self.trace_state, &s.trace_state);
238 self.flags.append_value(s.flags);
239 self.kind
242 .append_value(u8::try_from(s.kind).unwrap_or(0).min(5));
243 self.start.append_value(start);
244 self.duration.append_value(duration);
245 let (code, msg) = s.status.as_ref().map_or((0, ""), |st| {
246 (
247 u8::try_from(st.code).unwrap_or(0).min(2),
248 st.message.as_str(),
249 )
250 });
251 self.status_code.append_value(code);
252 opt_str(&mut self.status_message, msg);
253 self.dropped_attrs.append_value(s.dropped_attributes_count);
254 self.dropped_events.append_value(s.dropped_events_count);
255 self.dropped_links.append_value(s.dropped_links_count);
256 self.resource_id.append_value(rid);
257 self.scope_id.append_value(sid);
258
259 for e in &s.events {
260 self.ev_name.append(&e.name)?;
261 let eid = self.next_event_id;
262 self.next_event_id += 1;
263 self.ev_id.append_value(eid);
264 self.ev_parent.append_value(id);
265 self.ev_time.append_value(nanos(e.time_unix_nano));
266 self.ev_dropped.append_value(e.dropped_attributes_count);
267 self.event_attrs.append_all(eid, &e.attributes)?;
268 }
269
270 for l in &s.links {
271 let lid = self.next_link_id;
272 self.next_link_id += 1;
273 self.ln_id.append_value(lid);
274 self.ln_parent.append_value(id);
275 append_fixed(&mut self.ln_trace_id, &l.trace_id, 16)?;
276 append_fixed(&mut self.ln_span_id, &l.span_id, 8)?;
277 opt_str(&mut self.ln_trace_state, &l.trace_state);
278 self.ln_flags.append_value(l.flags);
279 self.ln_dropped.append_value(l.dropped_attributes_count);
280 self.link_attrs.append_all(lid, &l.attributes)?;
281 }
282
283 self.span_attrs.append_all(id, &s.attributes)?;
284 Ok(())
285 }
286
287 pub fn finish(&mut self) -> Result<Sealed> {
290 let out = self.seal(Sidecars::Build);
291 *self = Self::new();
292 out
293 }
294
295 fn seal(&self, sidecars: Sidecars) -> Result<Sealed> {
296 let trace_ids = self.trace_id.finish_cloned();
297 let trace_idx = match sidecars {
301 Sidecars::Build => crate::bloom::build(&trace_ids),
302 Sidecars::Skip => None,
303 };
304 let spans: Vec<ArrayRef> = vec![
305 Arc::new(self.id.finish_cloned()),
306 Arc::new(trace_ids),
307 Arc::new(self.span_id.finish_cloned()),
308 Arc::new(self.parent_span_id.finish_cloned()),
309 Arc::new(self.trace_state.finish_cloned()),
310 Arc::new(self.flags.finish_cloned()),
311 self.name.finish(),
312 Arc::new(self.kind.finish_cloned()),
313 Arc::new(self.start.finish_cloned()),
314 Arc::new(self.duration.finish_cloned()),
315 Arc::new(self.status_code.finish_cloned()),
316 Arc::new(self.status_message.finish_cloned()),
317 Arc::new(self.dropped_attrs.finish_cloned()),
318 Arc::new(self.dropped_events.finish_cloned()),
319 Arc::new(self.dropped_links.finish_cloned()),
320 Arc::new(self.resource_id.finish_cloned()),
321 Arc::new(self.scope_id.finish_cloned()),
322 ];
323 let events: Vec<ArrayRef> = vec![
324 Arc::new(self.ev_id.finish_cloned()),
325 Arc::new(self.ev_parent.finish_cloned()),
326 Arc::new(self.ev_time.finish_cloned()),
327 self.ev_name.finish(),
328 Arc::new(self.ev_dropped.finish_cloned()),
329 ];
330 let links: Vec<ArrayRef> = vec![
331 Arc::new(self.ln_id.finish_cloned()),
332 Arc::new(self.ln_parent.finish_cloned()),
333 Arc::new(self.ln_trace_id.finish_cloned()),
334 Arc::new(self.ln_span_id.finish_cloned()),
335 Arc::new(self.ln_trace_state.finish_cloned()),
336 Arc::new(self.ln_flags.finish_cloned()),
337 Arc::new(self.ln_dropped.finish_cloned()),
338 ];
339
340 let mut tables = vec![
344 ("spans", RecordBatch::try_new(SPANS.clone(), spans)?),
345 ("span_attrs", self.span_attrs.finish()?),
346 (
347 "span_events",
348 RecordBatch::try_new(SPAN_EVENTS.clone(), events)?,
349 ),
350 ("span_event_attrs", self.event_attrs.finish()?),
351 (
352 "span_links",
353 RecordBatch::try_new(SPAN_LINKS.clone(), links)?,
354 ),
355 ("span_link_attrs", self.link_attrs.finish()?),
356 ];
357 tables.extend(self.rs.finish()?);
358 Ok(Sealed::with(
359 sidecars,
360 self.next_id as usize,
361 tables,
362 self.min_ts,
363 self.max_ts,
364 )
365 .with_sidecar(crate::bloom::TRACE_IDX, trace_idx))
366 }
367}
368
369impl SignalBuilder for TracesBuilder {
370 type Request = ExportTraceServiceRequest;
371 const SIGNAL: &'static str = "traces";
372
373 fn has_headroom_for(&self, req: &Self::Request) -> bool {
374 TracesBuilder::has_headroom_for(self, req)
375 }
376 fn append_request(&mut self, req: &Self::Request) -> Result<usize> {
377 TracesBuilder::append_request(self, req)
378 }
379 fn approx_bytes(&self) -> usize {
380 TracesBuilder::approx_bytes(self)
381 }
382 fn is_empty(&self) -> bool {
383 TracesBuilder::is_empty(self)
384 }
385 fn finish(&mut self) -> Result<Sealed> {
386 TracesBuilder::finish(self)
387 }
388 fn snapshot(&self) -> Result<Sealed> {
389 self.seal(Sidecars::Skip)
390 }
391}
392
393fn opt_str(b: &mut StringBuilder, v: &str) {
397 if v.is_empty() {
398 b.append_null();
399 } else {
400 b.append_value(v);
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use arrow_array::TimestampNanosecondArray;
408 use mira_proto::trace::v1::span::Event;
409 use mira_proto::trace::v1::{ResourceSpans, ScopeSpans};
410
411 #[test]
416 fn span_times_past_i64_do_not_wrap_the_block_range() {
417 let mut b = TracesBuilder::new();
418 b.append_request(&ExportTraceServiceRequest {
419 resource_spans: vec![ResourceSpans {
420 scope_spans: vec![ScopeSpans {
421 spans: vec![
422 Span {
423 name: "overflowed".into(),
424 start_time_unix_nano: u64::MAX,
425 end_time_unix_nano: u64::MAX,
426 ..Default::default()
427 },
428 Span {
429 name: "half-overflowed".into(),
430 start_time_unix_nano: 6_000,
431 end_time_unix_nano: u64::MAX,
432 events: vec![Event {
433 time_unix_nano: u64::MAX,
434 name: "exception".into(),
435 ..Default::default()
436 }],
437 ..Default::default()
438 },
439 ],
440 ..Default::default()
441 }],
442 ..Default::default()
443 }],
444 })
445 .unwrap();
446
447 let sealed = b.finish().unwrap();
448 assert_eq!(
449 sealed.num_rows, 2,
450 "malformed spans are stored, not dropped"
451 );
452 assert_eq!(sealed.min_ts, 6_000, "an unrepresentable start is no start");
453 assert_eq!(
454 sealed.max_ts, 6_000,
455 "an unrepresentable end contributes nothing rather than i64::MAX"
456 );
457
458 let ts = |t: &str, c: &str| {
459 sealed
460 .table(t)
461 .unwrap()
462 .column_by_name(c)
463 .unwrap()
464 .as_any()
465 .downcast_ref::<TimestampNanosecondArray>()
466 .unwrap()
467 .clone()
468 };
469 assert_eq!(ts("spans", "start_time_unix_nano").values(), &[0, 6_000]);
470 assert_eq!(ts("span_events", "time_unix_nano").values(), &[0]);
471 let d = sealed.table("spans").unwrap();
474 let d = d
475 .column_by_name("duration_nano")
476 .unwrap()
477 .as_any()
478 .downcast_ref::<arrow_array::UInt64Array>()
479 .unwrap();
480 assert_eq!(d.values(), &[0, u64::MAX - 6_000]);
481 }
482
483 #[test]
497 fn the_trait_answers_headroom_and_row_count_the_same_way_the_builder_does() {
498 let req = |attrs: usize| ExportTraceServiceRequest {
499 resource_spans: vec![ResourceSpans {
500 scope_spans: vec![ScopeSpans {
501 spans: vec![Span {
502 name: "wide".into(),
503 start_time_unix_nano: 1,
504 end_time_unix_nano: 2,
505 attributes: (0..attrs)
506 .map(|i| mira_proto::common::v1::KeyValue {
507 key: format!("k{i}"),
508 value: None,
509 })
510 .collect(),
511 ..Default::default()
512 }],
513 ..Default::default()
514 }],
515 ..Default::default()
516 }],
517 };
518 let mut b = TracesBuilder::new();
519 assert!(SignalBuilder::is_empty(&b) && b.num_rows() == 0);
520 assert_eq!(SignalBuilder::approx_bytes(&b), 0);
521
522 assert!(
523 SignalBuilder::has_headroom_for(&b, &req(1)),
524 "a one-attribute span fits in an empty block"
525 );
526 assert!(
527 !SignalBuilder::has_headroom_for(&b, &req(crate::schema::DICT_CAP + 1)),
528 "one more key than the dictionary has slots does not"
529 );
530
531 assert_eq!(SignalBuilder::append_request(&mut b, &req(1)).unwrap(), 1);
532 assert_eq!(b.num_rows(), 1);
533 assert!(!SignalBuilder::is_empty(&b));
534 let bytes = SignalBuilder::approx_bytes(&b);
538 assert!(
539 (100..500).contains(&bytes),
540 "one span and one attribute, estimated at {bytes} bytes"
541 );
542
543 assert!(matches!(
547 SignalBuilder::append_request(&mut b, &req(crate::schema::DICT_CAP + 1)),
548 Err(crate::error::Error::DictionaryFull("span_attrs.key"))
549 ));
550 }
551}