use crate::id::EventId;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FieldType {
U8,
U16,
U32,
U64,
I8,
I16,
I32,
I64,
Bool,
Bytes,
Enum {
repr: u8,
},
Interned {
dynamic: bool,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum FieldRole {
#[default]
None,
Key,
SpanId,
ParentSpanId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Phase {
#[default]
None,
Enter,
Exit,
}
#[derive(Clone, Copy, Debug)]
pub struct EnumLabel {
pub value: u64,
pub label: &'static str,
}
#[derive(Clone, Copy, Debug)]
pub struct FieldSchema {
pub name: &'static str,
pub description: Option<&'static str>,
pub ty: FieldType,
pub offset: u16,
pub width: u16,
pub role: FieldRole,
pub unit: Option<&'static str>,
pub sentinel: Option<u64>,
pub enum_labels: &'static [EnumLabel],
}
#[derive(Clone, Copy, Debug)]
pub struct EventSchema {
pub id: EventId,
pub qualified_name: &'static str,
pub description: Option<&'static str>,
pub record_size: u16,
pub phase: Phase,
pub fields: &'static [FieldSchema],
}
#[doc(hidden)]
pub const fn layout_u16(value: usize) -> u16 {
assert!(
value <= u16::MAX as usize,
"event layout exceeds 65535 bytes (u16::MAX): backbeat's schema stores field offsets, \
widths, and record sizes as u16, so an event this large cannot be described"
);
value as u16
}
impl EventSchema {
pub fn field(&self, name: &str) -> Option<&FieldSchema> {
self.fields.iter().find(|f| f.name == name)
}
pub fn keys(&self) -> impl Iterator<Item = &FieldSchema> {
self.fields.iter().filter(|f| f.role == FieldRole::Key)
}
pub fn span_id(&self) -> Option<&FieldSchema> {
self.fields.iter().find(|f| f.role == FieldRole::SpanId)
}
pub fn parent_span(&self) -> Option<&FieldSchema> {
self.fields
.iter()
.find(|f| f.role == FieldRole::ParentSpanId)
}
pub const fn compute_id(qualified_name: &str, phase: Phase, fields: &[FieldSchema]) -> EventId {
let mut h = crate::id::Fnv::new();
h = h.str(qualified_name);
h = h.byte(phase as u8);
let mut i = 0;
while i < fields.len() {
let f = &fields[i];
h = h.str(f.name);
h = h.opt_str(f.description);
h = h.field_type(f.ty);
h = h.u16(f.offset);
h = h.u16(f.width);
h = h.byte(f.role as u8);
h = h.opt_str(f.unit);
if let Some(v) = f.sentinel {
h = h.byte(1).u64(v);
}
let mut j = 0;
while j < f.enum_labels.len() {
let l = &f.enum_labels[j];
h = h.u64(l.value);
h = h.str(l.label);
j += 1;
}
i += 1;
}
EventId(h.finish())
}
}
impl FieldType {
pub const fn fixed_width(self) -> Option<u16> {
Some(match self {
FieldType::U8 | FieldType::I8 | FieldType::Bool => 1,
FieldType::U16 | FieldType::I16 => 2,
FieldType::U32 | FieldType::I32 | FieldType::Interned { .. } => 4,
FieldType::U64 | FieldType::I64 => 8,
FieldType::Enum { repr } => repr as u16,
FieldType::Bytes => return None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const SCHEMA: EventSchema = EventSchema {
id: EventId::of("test::Demo"),
qualified_name: "test::Demo",
description: Some("A demo event."),
record_size: 16,
phase: Phase::None,
fields: &[
FieldSchema {
name: "packet_number",
description: Some("The packet number."),
ty: FieldType::U64,
offset: 0,
width: 8,
role: FieldRole::Key,
unit: None,
sentinel: None,
enum_labels: &[],
},
FieldSchema {
name: "direction",
description: None,
ty: FieldType::Enum { repr: 1 },
offset: 8,
width: 1,
role: FieldRole::None,
unit: None,
sentinel: None,
enum_labels: &[
EnumLabel {
value: 0,
label: "in",
},
EnumLabel {
value: 1,
label: "out",
},
],
},
],
};
#[test]
fn field_lookup_and_keys() {
assert!(SCHEMA.field("packet_number").is_some());
assert!(SCHEMA.field("nope").is_none());
let keys: Vec<_> = SCHEMA.keys().map(|f| f.name).collect();
assert_eq!(keys, ["packet_number"]);
}
#[test]
fn absent_sentinel_does_not_change_id() {
const FIELDS: &[FieldSchema] = &[FieldSchema {
name: "packet_number",
description: None,
ty: FieldType::U64,
offset: 0,
width: 8,
role: FieldRole::Key,
unit: None,
sentinel: None,
enum_labels: &[],
}];
let id = EventSchema::compute_id("test::Demo", Phase::None, FIELDS);
assert_eq!(id.get(), 0x544e_1269_4249_631b);
}
#[test]
fn present_sentinel_forks_id() {
const NONE: &[FieldSchema] = &[FieldSchema {
name: "x",
description: None,
ty: FieldType::U64,
offset: 0,
width: 8,
role: FieldRole::None,
unit: None,
sentinel: None,
enum_labels: &[],
}];
const SOME: &[FieldSchema] = &[FieldSchema {
name: "x",
description: None,
ty: FieldType::U64,
offset: 0,
width: 8,
role: FieldRole::None,
unit: None,
sentinel: Some(u64::MAX),
enum_labels: &[],
}];
let none = EventSchema::compute_id("n::E", Phase::None, NONE);
let some = EventSchema::compute_id("n::E", Phase::None, SOME);
assert_ne!(none, some, "declaring a sentinel must fork the id");
}
#[test]
fn fixed_widths() {
assert_eq!(FieldType::U64.fixed_width(), Some(8));
assert_eq!(
FieldType::Interned { dynamic: false }.fixed_width(),
Some(4)
);
assert_eq!(FieldType::Enum { repr: 2 }.fixed_width(), Some(2));
assert_eq!(FieldType::Bytes.fixed_width(), None);
}
}