use std::fmt::{Display, Formatter};
use serde::ser::{Serialize, SerializeMap, Serializer};
use crate::cpu_delta::CpuDelta;
use crate::serialization_helpers::{SerializableSingleValueColumn, SliceWithPermutation};
use crate::timestamp::{
SerializableTimestampSliceAsDeltas, SerializableTimestampSliceAsDeltasWithPermutation,
Timestamp,
};
#[derive(Debug, Clone)]
pub struct SampleTable {
sample_weight_type: WeightType,
sample_weights: Vec<i32>,
sample_timestamps: Vec<Timestamp>,
sample_stack_indexes: Vec<Option<usize>>,
sample_cpu_deltas: Vec<CpuDelta>,
is_sorted_by_time: bool,
last_sample_timestamp: Timestamp,
}
#[derive(Debug, Clone)]
pub enum WeightType {
Samples,
TracingMs,
Bytes,
}
impl Display for WeightType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WeightType::Samples => write!(f, "samples"),
WeightType::TracingMs => write!(f, "tracing-ms"),
WeightType::Bytes => write!(f, "bytes"),
}
}
}
impl Serialize for WeightType {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
WeightType::Samples => serializer.serialize_str("samples"),
WeightType::TracingMs => serializer.serialize_str("tracing-ms"),
WeightType::Bytes => serializer.serialize_str("bytes"),
}
}
}
impl SampleTable {
pub fn new() -> Self {
Self {
sample_weight_type: WeightType::Samples,
sample_weights: Vec::new(),
sample_timestamps: Vec::new(),
sample_stack_indexes: Vec::new(),
sample_cpu_deltas: Vec::new(),
is_sorted_by_time: true,
last_sample_timestamp: Timestamp::from_nanos_since_reference(0),
}
}
pub fn add_sample(
&mut self,
timestamp: Timestamp,
stack_index: Option<usize>,
cpu_delta: CpuDelta,
weight: i32,
) {
self.sample_weights.push(weight);
self.sample_timestamps.push(timestamp);
self.sample_stack_indexes.push(stack_index);
self.sample_cpu_deltas.push(cpu_delta);
if timestamp < self.last_sample_timestamp {
self.is_sorted_by_time = false;
}
self.last_sample_timestamp = timestamp;
}
pub fn set_weight_type(&mut self, t: WeightType) {
self.sample_weight_type = t;
}
pub fn modify_last_sample(&mut self, timestamp: Timestamp, weight: i32) {
*self.sample_weights.last_mut().unwrap() += weight;
*self.sample_timestamps.last_mut().unwrap() = timestamp;
}
}
impl Serialize for SampleTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = self.sample_timestamps.len();
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("length", &len)?;
map.serialize_entry("weightType", &self.sample_weight_type.to_string())?;
if self.is_sorted_by_time {
map.serialize_entry("stack", &self.sample_stack_indexes)?;
map.serialize_entry(
"timeDeltas",
&SerializableTimestampSliceAsDeltas(&self.sample_timestamps),
)?;
map.serialize_entry("weight", &self.sample_weights)?;
map.serialize_entry("threadCPUDelta", &self.sample_cpu_deltas)?;
} else {
let mut indexes: Vec<usize> = (0..self.sample_timestamps.len()).collect();
indexes.sort_unstable_by_key(|index| self.sample_timestamps[*index]);
map.serialize_entry(
"stack",
&SliceWithPermutation(&self.sample_stack_indexes, &indexes),
)?;
map.serialize_entry(
"timeDeltas",
&SerializableTimestampSliceAsDeltasWithPermutation(
&self.sample_timestamps,
&indexes,
),
)?;
map.serialize_entry(
"weight",
&SliceWithPermutation(&self.sample_weights, &indexes),
)?;
map.serialize_entry(
"threadCPUDelta",
&SliceWithPermutation(&self.sample_cpu_deltas, &indexes),
)?;
}
map.end()
}
}
#[derive(Debug, Clone, Default)]
pub struct NativeAllocationsTable {
time: Vec<Timestamp>,
stack: Vec<Option<usize>>,
allocation_size: Vec<i64>,
allocation_address: Vec<u64>,
}
impl NativeAllocationsTable {
pub fn add_sample(
&mut self,
timestamp: Timestamp,
stack_index: Option<usize>,
allocation_address: u64,
allocation_size: i64,
) {
self.time.push(timestamp);
self.stack.push(stack_index);
self.allocation_address.push(allocation_address);
self.allocation_size.push(allocation_size);
}
}
impl Serialize for NativeAllocationsTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let len = self.time.len();
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("time", &self.time)?;
map.serialize_entry("weight", &self.allocation_size)?;
map.serialize_entry("weightType", &WeightType::Bytes)?;
map.serialize_entry("stack", &self.stack)?;
map.serialize_entry("memoryAddress", &self.allocation_address)?;
map.serialize_entry("threadId", &SerializableSingleValueColumn(0, len))?;
map.serialize_entry("length", &len)?;
map.end()
}
}
#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
use serde_json::json;
use super::*;
#[test]
fn test_serialize_native_allocations() {
let mut native_allocations_table = NativeAllocationsTable::default();
native_allocations_table.add_sample(
Timestamp::from_millis_since_reference(274_363.248_375),
None,
5969772544,
147456,
);
assert_json_eq!(
native_allocations_table,
json!({
"time": [
274363.248375
],
"weight": [
147456
],
"weightType": "bytes",
"stack": [
null
],
"memoryAddress": [
5969772544u64
],
"threadId": [
0
],
"length": 1
})
);
}
}