Skip to main content

micromegas_tracing/
parsing.rs

1//! Manual parsing of dynamically sized events
2use anyhow::{Context, Result};
3use bumpalo::Bump;
4use micromegas_transit::{
5    CustomReaderMap, UserDefinedType, parse_pod_instance, read_advance_string_in,
6    try_advance_window, try_read_consume_pod,
7    value::{Object, Value},
8};
9use std::{collections::HashMap, sync::Arc};
10
11use crate::property_set::PROPERTY_SET_DEP_TYPE_NAME;
12
13// Member names: 'static string literals reborrow into the parse arena lifetime for free.
14const DATA: &str = "data";
15const DESC: &str = "desc";
16const FORMAT: &str = "format";
17const ID: &str = "id";
18const LEVEL: &str = "level";
19const MSG: &str = "msg";
20const NAME: &str = "name";
21const PROPERTIES: &str = "properties";
22const TARGET: &str = "target";
23const TIME: &str = "time";
24const VALUE: &str = "value";
25
26const PROPERTY_SET_TYPE_NAME: &str = "property_set";
27
28fn parse_log_string_event<'a>(
29    bump: &'a Bump,
30    udt: &'a UserDefinedType,
31    _udts: &'a [UserDefinedType],
32    dependencies: &HashMap<u64, Value<'a>>,
33    mut object_window: &'a [u8],
34) -> Result<Value<'a>> {
35    let desc_id: u64 = try_read_consume_pod(&mut object_window).with_context(|| "desc_id")?;
36    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
37    // legacy format: the remaining bytes are the (utf8) message
38    let msg = std::str::from_utf8(object_window).with_context(|| "parsing legacy string")?;
39    let desc = *dependencies
40        .get(&desc_id)
41        .with_context(|| format!("desc member {desc_id} of LogStringEvent not found"))?;
42    let members = bump.alloc_slice_copy(&[
43        (TIME, Value::I64(time)),
44        (MSG, Value::String(msg)),
45        (DESC, desc),
46    ]);
47    Ok(Value::Object(bump.alloc(Object {
48        type_name: udt.name.as_str(),
49        members,
50    })))
51}
52
53fn parse_log_string_event_v2<'a>(
54    bump: &'a Bump,
55    udt: &'a UserDefinedType,
56    _udts: &'a [UserDefinedType],
57    dependencies: &HashMap<u64, Value<'a>>,
58    mut object_window: &'a [u8],
59) -> Result<Value<'a>> {
60    let desc_id: u64 = try_read_consume_pod(&mut object_window).with_context(|| "desc_id")?;
61    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
62    let msg = read_advance_string_in(bump, &mut object_window).with_context(|| "parsing string")?;
63    let desc = *dependencies
64        .get(&desc_id)
65        .with_context(|| format!("desc member {desc_id} of LogStringEvent not found"))?;
66    let members = bump.alloc_slice_copy(&[
67        (TIME, Value::I64(time)),
68        (MSG, Value::String(msg)),
69        (DESC, desc),
70    ]);
71    Ok(Value::Object(bump.alloc(Object {
72        type_name: udt.name.as_str(),
73        members,
74    })))
75}
76
77fn parse_log_string_interop_event_v3<'a>(
78    bump: &'a Bump,
79    udt: &'a UserDefinedType,
80    udts: &'a [UserDefinedType],
81    dependencies: &HashMap<u64, Value<'a>>,
82    mut object_window: &'a [u8],
83) -> Result<Value<'a>> {
84    let string_ref_metadata = udts
85        .iter()
86        .find(|t| *t.name == "StaticStringRef")
87        .with_context(
88            || "Can't parse log string interop event with no metadata for StaticStringRef",
89        )?;
90    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
91    let level: u8 = try_read_consume_pod(&mut object_window).with_context(|| "level")?;
92    let target_window = object_window
93        .get(0..string_ref_metadata.size)
94        .with_context(|| "object window shorter than StaticStringRef metadata")?;
95    let target = parse_pod_instance(bump, string_ref_metadata, udts, dependencies, target_window)
96        .with_context(|| "parse_pod_instance")?;
97    object_window = try_advance_window(object_window, string_ref_metadata.size)?;
98    let msg = read_advance_string_in(bump, &mut object_window)?;
99    let members = bump.alloc_slice_copy(&[
100        (TIME, Value::I64(time)),
101        (LEVEL, Value::U8(level)),
102        (TARGET, target),
103        (MSG, Value::String(msg)),
104    ]);
105    Ok(Value::Object(bump.alloc(Object {
106        type_name: udt.name.as_str(),
107        members,
108    })))
109}
110
111fn parse_tagged_log_interop_event<'a>(
112    bump: &'a Bump,
113    udt: &'a UserDefinedType,
114    udts: &'a [UserDefinedType],
115    dependencies: &HashMap<u64, Value<'a>>,
116    mut object_window: &'a [u8],
117) -> Result<Value<'a>> {
118    let string_ref_metadata = udts
119        .iter()
120        .find(|t| *t.name == "StaticStringRef")
121        .with_context(
122            || "Can't parse log string interop event with no metadata for StaticStringRef",
123        )?;
124    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
125    let level: u8 = try_read_consume_pod(&mut object_window).with_context(|| "level")?;
126    let target_window = object_window
127        .get(0..string_ref_metadata.size)
128        .with_context(|| "object window shorter than StaticStringRef metadata")?;
129    let target = parse_pod_instance(bump, string_ref_metadata, udts, dependencies, target_window)
130        .with_context(|| "parse_pod_instance")?;
131    object_window = try_advance_window(object_window, string_ref_metadata.size)?;
132    let properties_id: u64 =
133        try_read_consume_pod(&mut object_window).with_context(|| "properties_id")?;
134    let properties = *dependencies
135        .get(&properties_id)
136        .with_context(|| "fetching properties in parse_tagged_log_interop_event")?;
137    let msg = read_advance_string_in(bump, &mut object_window)?;
138    let members = bump.alloc_slice_copy(&[
139        (TIME, Value::I64(time)),
140        (LEVEL, Value::U8(level)),
141        (TARGET, target),
142        (PROPERTIES, properties),
143        (MSG, Value::String(msg)),
144    ]);
145    Ok(Value::Object(bump.alloc(Object {
146        type_name: udt.name.as_str(),
147        members,
148    })))
149}
150
151fn parse_tagged_log_string<'a>(
152    bump: &'a Bump,
153    udt: &'a UserDefinedType,
154    _udts: &'a [UserDefinedType],
155    dependencies: &HashMap<u64, Value<'a>>,
156    mut object_window: &'a [u8],
157) -> Result<Value<'a>> {
158    let desc_id: u64 = try_read_consume_pod(&mut object_window).with_context(|| "desc_id")?;
159    let desc = *dependencies
160        .get(&desc_id)
161        .with_context(|| "fetching desc in parse_tagged_log_string")?;
162    let properties_id: u64 =
163        try_read_consume_pod(&mut object_window).with_context(|| "properties_id")?;
164    let properties = *dependencies
165        .get(&properties_id)
166        .with_context(|| "fetching property set in parse_tagged_log_string")?;
167    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
168    let msg = read_advance_string_in(bump, &mut object_window)?;
169
170    let members = bump.alloc_slice_copy(&[
171        (TIME, Value::I64(time)),
172        (DESC, desc),
173        (PROPERTIES, properties),
174        (MSG, Value::String(msg)),
175    ]);
176    Ok(Value::Object(bump.alloc(Object {
177        type_name: udt.name.as_str(),
178        members,
179    })))
180}
181
182fn parse_log_string_interop_event<'a>(
183    bump: &'a Bump,
184    udt: &'a UserDefinedType,
185    udts: &'a [UserDefinedType],
186    dependencies: &HashMap<u64, Value<'a>>,
187    mut object_window: &'a [u8],
188) -> Result<Value<'a>> {
189    let stringid_metadata = udts
190        .iter()
191        .find(|t| *t.name == "StringId")
192        .with_context(|| "Can't parse log string interop event with no metadata for StringId")?;
193    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
194    let level: u32 = try_read_consume_pod(&mut object_window).with_context(|| "level")?;
195    let target_window = object_window
196        .get(0..stringid_metadata.size)
197        .with_context(|| "object window shorter than StringId metadata")?;
198    let target = parse_pod_instance(bump, stringid_metadata, udts, dependencies, target_window)
199        .with_context(|| "parse_pod_instance")?;
200    object_window = try_advance_window(object_window, stringid_metadata.size)?;
201    // legacy dyn string: the remaining bytes are the (utf8) message
202    let msg =
203        std::str::from_utf8(object_window).with_context(|| "parsing legacy interop string")?;
204    let members = bump.alloc_slice_copy(&[
205        (TIME, Value::I64(time)),
206        (LEVEL, Value::U32(level)),
207        (TARGET, target),
208        (MSG, Value::String(msg)),
209    ]);
210    Ok(Value::Object(bump.alloc(Object {
211        type_name: udt.name.as_str(),
212        members,
213    })))
214}
215
216fn parse_property_set<'a>(
217    bump: &'a Bump,
218    _udt: &'a UserDefinedType,
219    udts: &'a [UserDefinedType],
220    dependencies: &HashMap<u64, Value<'a>>,
221    mut window: &'a [u8],
222) -> Result<Value<'a>> {
223    let property_layout = udts
224        .iter()
225        .find(|t| *t.name == "Property")
226        .with_context(|| "could not find Property layout")?;
227
228    let object_id: u64 = try_read_consume_pod(&mut window).with_context(|| "object_id")?;
229    let nb_properties =
230        try_read_consume_pod::<u32>(&mut window).with_context(|| "nb_properties")? as usize;
231    let property_size = property_layout.size;
232    // Reject a corrupt count before reserving arena capacity: a real block holds
233    // at most window.len()/property_size properties, so a bogus large count must
234    // not trigger a huge (process-aborting) bump reservation.
235    if property_size == 0 || nb_properties > window.len() / property_size {
236        anyhow::bail!(
237            "invalid property_set: nb_properties={nb_properties} exceeds {}-byte window",
238            window.len()
239        );
240    }
241    let mut members = bumpalo::collections::Vec::with_capacity_in(nb_properties, bump);
242    for i in 0..nb_properties {
243        let begin = i * property_size;
244        let property_window = &window[begin..begin + property_size];
245        if let Value::Object(obj) =
246            parse_pod_instance(bump, property_layout, udts, dependencies, property_window)?
247        {
248            members.push((
249                obj.get::<&str>("name")?,
250                Value::String(obj.get::<&str>("value")?),
251            ));
252        } else {
253            anyhow::bail!("invalid property in propertyset");
254        }
255    }
256
257    let set: &Object = bump.alloc(Object {
258        type_name: PROPERTY_SET_TYPE_NAME,
259        members: members.into_bump_slice(),
260    });
261    let outer = bump.alloc_slice_copy(&[(ID, Value::U64(object_id)), (VALUE, Value::Object(set))]);
262    Ok(Value::Object(bump.alloc(Object {
263        type_name: PROPERTY_SET_DEP_TYPE_NAME.as_str(),
264        members: outer,
265    })))
266}
267
268fn parse_image_event<'a>(
269    bump: &'a Bump,
270    udt: &'a UserDefinedType,
271    _udts: &'a [UserDefinedType],
272    _dependencies: &HashMap<u64, Value<'a>>,
273    mut object_window: &'a [u8],
274) -> Result<Value<'a>> {
275    let time: i64 = try_read_consume_pod(&mut object_window).with_context(|| "time")?;
276    let name =
277        read_advance_string_in(bump, &mut object_window).with_context(|| "parsing image name")?;
278    let format =
279        read_advance_string_in(bump, &mut object_window).with_context(|| "parsing image format")?;
280    let len: u32 = try_read_consume_pod(&mut object_window).with_context(|| "len")?;
281    if len as usize > object_window.len() {
282        anyhow::bail!(
283            "invalid image blob: len={len} exceeds {}-byte window",
284            object_window.len()
285        );
286    }
287    // zero-copy: the blob borrows the (whole-block) source buffer; the consumer
288    // copies it into Arrow inside the parse_block callback.
289    let data: &[u8] = &object_window[..len as usize];
290    let members = bump.alloc_slice_copy(&[
291        (TIME, Value::I64(time)),
292        (NAME, Value::String(name)),
293        (FORMAT, Value::String(format)),
294        (DATA, Value::Bytes(data)),
295    ]);
296    Ok(Value::Object(bump.alloc(Object {
297        type_name: udt.name.as_str(),
298        members,
299    })))
300}
301
302/// Dictionnary of custom readers for dynamically sized events
303pub fn make_custom_readers() -> CustomReaderMap {
304    let mut custom_readers: CustomReaderMap = HashMap::new();
305    custom_readers.insert("ImageEvent".into(), Arc::new(parse_image_event));
306    custom_readers.insert("LogStringEvent".into(), Arc::new(parse_log_string_event));
307    custom_readers.insert(
308        "LogStringEventV2".into(),
309        Arc::new(parse_log_string_event_v2),
310    );
311    custom_readers.insert(
312        "LogStringInteropEventV2".into(),
313        Arc::new(parse_log_string_interop_event),
314    );
315    custom_readers.insert(
316        "LogStringInteropEventV3".into(),
317        Arc::new(parse_log_string_interop_event_v3),
318    );
319    custom_readers.insert("TaggedLogString".into(), Arc::new(parse_tagged_log_string));
320    custom_readers.insert(
321        PROPERTY_SET_DEP_TYPE_NAME.to_string(),
322        Arc::new(parse_property_set),
323    );
324    custom_readers.insert(
325        "TaggedLogInteropEvent".into(),
326        Arc::new(parse_tagged_log_interop_event),
327    );
328    custom_readers
329}