use std::{collections::HashMap, io::Write};
use rand::{distributions::Standard, prelude::Distribution, seq::SliceRandom, Rng};
use rmp_serde::Serializer;
use crate::payload::Error;
use serde::Serialize;
use super::{common::AsciiString, Generator};
const SERVICES: [&str; 7] = [
"tablet",
"phone",
"phone2",
"laptop",
"desktop",
"monitor",
"bigger-monitor",
];
const TAG_NAMES: [&str; 8] = [
"one", "two", "three", "four", "five", "six", "seven", "eight",
];
const SERVICE_KIND: [&str; 4] = ["web", "db", "lambda", "cicd"];
#[derive(serde::Serialize)]
struct Span {
service: String,
name: String,
resource: String,
trace_id: u64,
span_id: u64,
parent_id: u64,
start: i64,
duration: i64,
error: i32,
meta: HashMap<String, String>,
metrics: HashMap<String, f64>,
#[serde(alias = "type")]
kind: String,
meta_struct: HashMap<String, Vec<u8>>,
}
impl Distribution<Span> for Standard {
fn sample<R>(&self, rng: &mut R) -> Span
where
R: Rng + ?Sized,
{
let total_metrics = rng.gen_range(0..6);
let mut metrics: HashMap<String, f64> = HashMap::new();
for k in TAG_NAMES.choose_multiple(rng, total_metrics) {
metrics.insert(String::from(*k), rng.gen());
}
Span {
service: String::from(*SERVICES.choose(rng).unwrap()),
name: AsciiString::default().generate(rng),
resource: String::new(),
trace_id: rng.gen(),
span_id: rng.gen(),
parent_id: rng.gen(),
start: rng.gen(),
duration: rng.gen(),
error: rng.gen_range(0..=1),
meta: HashMap::new(),
metrics,
kind: String::from(*SERVICE_KIND.choose(rng).unwrap()),
meta_struct: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub(crate) enum Encoding {
Json,
#[default]
MsgPack,
}
#[derive(Debug, Default, Clone, Copy)]
#[allow(clippy::module_name_repetitions)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub(crate) struct TraceAgent {
encoding: Encoding,
}
impl TraceAgent {
pub(crate) fn json() -> Self {
Self {
encoding: Encoding::Json,
}
}
pub(crate) fn msg_pack() -> Self {
Self {
encoding: Encoding::MsgPack,
}
}
}
impl crate::payload::Serialize for TraceAgent {
fn to_bytes<W, R>(&self, mut rng: R, max_bytes: usize, writer: &mut W) -> Result<(), Error>
where
R: Rng + Sized,
W: Write,
{
let mut members: Vec<Vec<Span>> = vec![];
let mut remaining = 10_000;
while remaining > 0 {
let total = rng.gen_range(0..=remaining);
let spans: Vec<Span> = Standard.sample_iter(&mut rng).take(total).collect();
members.push(spans);
remaining = remaining.saturating_sub(total);
}
loop {
let encoding = match self.encoding {
Encoding::Json => serde_json::to_vec(&members[0..])?,
Encoding::MsgPack => {
let mut buf = Vec::with_capacity(max_bytes);
members[0..].serialize(&mut Serializer::new(&mut buf))?;
buf
}
};
if encoding.len() > max_bytes {
break;
}
members.push(Standard.sample_iter(&mut rng).take(5_000).collect());
}
let mut high = members.len();
loop {
let encoding = match self.encoding {
Encoding::Json => serde_json::to_vec(&members[0..high])?,
Encoding::MsgPack => {
let mut buf = Vec::with_capacity(max_bytes);
members[0..high].serialize(&mut Serializer::new(&mut buf))?;
buf
}
};
if encoding.len() > max_bytes {
high /= 2;
} else {
writer.write_all(&encoding)?;
break;
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
use rand::{rngs::SmallRng, SeedableRng};
use crate::payload::{Serialize, TraceAgent};
proptest! {
#[test]
fn payload_not_exceed_max_bytes_json(seed: u64, max_bytes: u16) {
let max_bytes = max_bytes as usize;
let rng = SmallRng::seed_from_u64(seed);
let trace_agent = TraceAgent::json();
let mut bytes = Vec::with_capacity(max_bytes);
trace_agent.to_bytes(rng, max_bytes, &mut bytes).unwrap();
debug_assert!(
bytes.len() <= max_bytes,
"{:?}",
std::str::from_utf8(&bytes).unwrap()
);
}
}
proptest! {
#[test]
fn payload_not_exceed_max_bytes_msg_pack(seed: u64, max_bytes: u16) {
let max_bytes = max_bytes as usize;
let rng = SmallRng::seed_from_u64(seed);
let trace_agent = TraceAgent::msg_pack();
let mut bytes = Vec::with_capacity(max_bytes);
trace_agent.to_bytes(rng, max_bytes, &mut bytes).unwrap();
debug_assert!(
bytes.len() <= max_bytes,
"{:?}",
std::str::from_utf8(&bytes).unwrap()
);
}
}
}