use super::BackendConfig;
use crate::error::Result;
use std::{
collections::{hash_map::DefaultHasher, BTreeSet, HashMap},
hash::{Hash, Hasher},
};
#[derive(Debug, PartialOrd, Ord, Eq, PartialEq, Hash, Clone)]
pub struct Tag {
pub key: String,
pub value: String,
}
impl Tag {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
impl std::fmt::Display for Tag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}={}", self.key, self.value)
}
}
#[derive(Debug, Default, Clone)]
pub struct StackBuffer {
pub data: HashMap<StackTrace, usize>,
}
impl StackBuffer {
pub fn new(data: HashMap<StackTrace, usize>) -> Self {
Self { data }
}
pub fn record(&mut self, stack_trace: StackTrace) -> Result<()> {
*self.data.entry(stack_trace).or_insert(0) += 1;
Ok(())
}
pub fn record_with_count(&mut self, stack_trace: StackTrace, count: usize) -> Result<()> {
*self.data.entry(stack_trace).or_insert(0) += count;
Ok(())
}
pub fn clear(&mut self) {
self.data.clear();
}
}
impl From<StackBuffer> for Vec<Report> {
fn from(stack_buffer: StackBuffer) -> Self {
stack_buffer
.data
.into_iter()
.fold(
HashMap::new(),
|acc: HashMap<usize, Report>, (stacktrace, count): (StackTrace, usize)| {
let mut acc = acc;
if let Some(report) = acc.get_mut(&stacktrace.metadata.get_id()) {
report.record_with_count(stacktrace, count);
} else {
let report = Report::new(HashMap::new());
let report_id = stacktrace.metadata.get_id();
let mut report = report.metadata(stacktrace.metadata.clone());
report.record_with_count(stacktrace, count);
acc.insert(report_id, report);
}
acc
},
)
.into_values()
.collect()
}
}
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub struct Metadata {
pub tags: BTreeSet<Tag>,
}
impl Metadata {
pub fn add_tag(&mut self, tag: Tag) {
self.tags.insert(tag);
}
pub fn get_id(&self) -> usize {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish() as usize
}
}
pub enum ReportData {
Reports(Vec<Report>),
RawPprof(Vec<u8>),
}
pub struct ReportBatch {
pub profile_type: String,
pub data: ReportData,
}
#[derive(Debug, Default, Clone)]
pub struct Report {
pub data: HashMap<StackTrace, usize>,
pub metadata: Metadata,
}
impl Hash for Report {
fn hash<H: Hasher>(&self, state: &mut H) {
self.metadata.hash(state);
}
}
impl Report {
pub fn new(data: HashMap<StackTrace, usize>) -> Self {
Self {
data,
metadata: Metadata::default(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (&StackTrace, &usize)> {
self.data.iter()
}
pub fn metadata(self, metadata: Metadata) -> Self {
Self {
data: self.data,
metadata,
}
}
pub fn record(&mut self, stack_trace: StackTrace) {
*self.data.entry(stack_trace).or_insert(0) += 1;
}
pub fn record_with_count(&mut self, stack_trace: StackTrace, count: usize) {
*self.data.entry(stack_trace).or_insert(0) += count;
}
}
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)]
pub struct StackTrace {
pub pid: Option<u32>,
pub thread_id: Option<crate::utils::ThreadId>,
pub thread_name: Option<String>,
pub frames: Vec<StackFrame>,
pub metadata: Metadata,
}
impl std::fmt::Display for StackTrace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
&self
.frames
.iter()
.rev()
.map(|frame| format!("{}", frame))
.collect::<Vec<_>>()
.join(";")
)
}
}
impl StackTrace {
pub fn new(
config: &BackendConfig,
pid: Option<u32>,
thread_id: Option<crate::utils::ThreadId>,
thread_name: Option<String>,
frames: Vec<StackFrame>,
) -> Self {
let mut metadata = Metadata::default();
if config.report_pid {
if let Some(pid) = pid {
metadata.add_tag(Tag::new("pid".to_owned(), pid.to_string()));
}
}
if config.report_thread_id {
if let Some(thread_id) = &thread_id {
metadata.add_tag(Tag::new("thread_id".to_owned(), thread_id.to_string()));
}
}
if config.report_thread_name {
if let Some(thread_name) = thread_name.clone() {
metadata.add_tag(Tag::new("thread_name".to_owned(), thread_name));
}
}
Self {
pid,
thread_id,
thread_name,
frames,
metadata,
}
}
pub fn iter(&self) -> impl Iterator<Item = &StackFrame> {
self.frames.iter()
}
}
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)]
pub struct StackFrame {
pub module: Option<String>,
pub name: Option<String>,
pub filename: Option<String>,
pub relative_path: Option<String>,
pub absolute_path: Option<String>,
pub line: Option<u32>,
}
impl StackFrame {
pub fn new(
module: Option<String>,
name: Option<String>,
filename: Option<String>,
relative_path: Option<String>,
absolute_path: Option<String>,
line: Option<u32>,
) -> Self {
Self {
module,
name,
filename,
relative_path,
absolute_path,
line,
}
}
}
impl std::fmt::Display for StackFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{} - {}",
self.filename.as_ref().unwrap_or(&"".to_string()),
self.line.unwrap_or(0),
self.name.as_ref().unwrap_or(&"".to_string())
)
}
}