use std::collections::{HashMap, HashSet};
use std::collections::hash_map::{Values, ValuesMut};
use std::time::Duration;
use std::path::Path;
use crate::Writable;
use crate::event::{Event, EventData};
use crate::intern::{InternedStrings, InternedCallstacks};
#[cfg(feature = "scripting")]
pub mod scripting;
#[cfg(feature = "scripting")]
pub use scripting::ScriptedUniversalExporter;
use crate::helpers::callstack::CallstackHelper;
use modulemetadata::ModuleMetadata;
use pe_file::PEModuleMetadata;
use process::MetricValue::{self, Span};
use crate::ruwind::UnwindType;
use chrono::{DateTime, Utc};
mod lookup;
pub mod record;
use record::ExportRecordType;
use record::ExportRecordData;
use record::ExportRecord;
pub mod attributes;
use attributes::ExportAttributes;
use attributes::ExportAttributePair;
use attributes::ExportAttributeWalker;
use attributes::VersionOpCodeAttributeSource;
use attributes::TraceContextAttributeSource;
use attributes::ActivityIdAttributeSource;
use attributes::CgroupAttributeSource;
pub mod span;
use span::ExportSpan;
pub mod os;
use os::OSExportMachine;
use os::OSExportSampler;
use os::OSExportSettings;
#[cfg(target_os = "linux")]
pub use os::linux::ExportSettingsLinuxExt;
pub const KERNEL_START:u64 = 0xFFFF800000000000;
pub const KERNEL_END:u64 = 0xFFFFFFFFFFFFFFFF;
const NANOS_IN_SEC:u64 = 1000000000;
pub type ExportDevNode = crate::unwind::ModuleKey;
pub mod graph;
pub mod formats;
pub mod modulemetadata;
pub mod pe_file;
pub mod universal;
use modulemetadata::ModuleMetadataLookup;
pub use universal::{
UniversalExporter,
};
pub mod symbols;
pub use symbols::{
ExportSymbolReader,
KernelSymbolReader,
ExportSymbol,
DynamicSymbol,
};
pub mod process;
pub use process::{
ExportProcess,
ExportProcessSample,
ExportProcessReplay,
};
pub mod mappings;
pub use mappings::{
ExportMapping,
};
#[derive(Default)]
#[allow(dead_code)]
struct ExportCSwitch {
start_time: u64,
sample: Option<ExportProcessSample>,
}
#[derive(Default)]
struct ExportProxy {
errors: Vec<anyhow::Error>,
events: HashMap<usize, Event>,
}
impl ExportProxy {
fn proxy_event_data(
&mut self,
event_id: usize,
full_data: &[u8],
event_data: &[u8]) {
if let Some(event) = self.events.get_mut(&event_id) {
self.errors.clear();
event.process(
full_data,
event_data,
&mut self.errors);
for error in &self.errors {
eprintln!("Error: Event '{}': {}", event.name(), error);
}
}
}
fn add_event(
&mut self,
event: Event) {
if let Some(proxy_id) = event.get_proxy_id() {
self.events.insert(proxy_id, event);
}
}
}
struct ExportSampler {
exporter: Writable<ExportMachine>,
attribute_sources: Writable<Vec<Box<dyn ExportAttributeSource>>>,
frames: Vec<u64>,
os: OSExportSampler,
disable_callstacks: bool,
version_override: Option<u16>,
id_override: Option<usize>,
op_code_override: Option<u16>,
span_id_override: Option<[u8; 8]>,
trace_id_override: Option<[u8; 16]>,
activity_id_override: Option<[u8; 16]>,
related_activity_id_override: Option<[u8; 16]>,
}
pub trait ExportSamplerOSHooks {
fn os_event_callstack(
&mut self,
data: &EventData) -> anyhow::Result<()>;
fn os_event_time(
&self,
data: &EventData) -> anyhow::Result<u64>;
fn os_event_pid(
&self,
data: &EventData) -> anyhow::Result<u32>;
fn os_event_tid(
&self,
data: &EventData) -> anyhow::Result<u32>;
fn os_event_cpu(
&self,
data: &EventData) -> anyhow::Result<u16>;
fn os_event_version(
&self,
data: &EventData) -> anyhow::Result<Option<u16>>;
fn os_event_id(
&self,
data: &EventData) -> anyhow::Result<Option<usize>>;
fn os_event_op_code(
&self,
data: &EventData) -> anyhow::Result<Option<u16>>;
fn os_event_span_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 8]>>;
fn os_event_trace_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>>;
fn os_event_activity_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>>;
fn os_event_related_activity_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>>;
fn os_event_cgroup(
&self,
_data: &EventData) -> anyhow::Result<Option<u64>> {
Ok(None)
}
}
impl ExportSampler {
fn new(
exporter: &Writable<ExportMachine>,
os: OSExportSampler) -> Self {
let attribute_sources = exporter.borrow_mut().attribute_sources.clone();
Self {
exporter: exporter.clone(),
attribute_sources,
os,
frames: Vec::new(),
version_override: None,
id_override: None,
op_code_override: None,
span_id_override: None,
trace_id_override: None,
activity_id_override: None,
related_activity_id_override: None,
disable_callstacks: false,
}
}
const fn override_version(
&mut self,
version: Option<u16>) {
self.version_override = version;
}
#[allow(dead_code)]
const fn override_id(
&mut self,
id: Option<usize>) {
self.id_override = id;
}
const fn override_op_code(
&mut self,
op_code: Option<u16>) {
self.op_code_override = op_code;
}
const fn override_span_id(
&mut self,
span_id: Option<[u8; 8]>) {
self.span_id_override = span_id;
}
const fn override_trace_id(
&mut self,
trace_id: Option<[u8; 16]>) {
self.trace_id_override = trace_id;
}
const fn override_activity_id(
&mut self,
activity_id: Option<[u8; 16]>) {
self.activity_id_override = activity_id;
}
const fn override_related_activity_id(
&mut self,
related_activity_id: Option<[u8; 16]>) {
self.related_activity_id_override = related_activity_id;
}
fn version(
&self,
data: &EventData) -> anyhow::Result<Option<u16>> {
match self.version_override {
Some(version) => Ok(Some(version)),
None => self.os_event_version(data),
}
}
fn id(
&self,
data: &EventData) -> anyhow::Result<Option<usize>> {
match self.id_override {
Some(id) => Ok(Some(id)),
None => self.os_event_id(data),
}
}
fn op_code(
&self,
data: &EventData) -> anyhow::Result<Option<u16>> {
match self.op_code_override {
Some(op_code) => Ok(Some(op_code)),
None => self.os_event_op_code(data),
}
}
fn span_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 8]>> {
match self.span_id_override {
Some(span_id) => Ok(Some(span_id)),
None => self.os_event_span_id(data),
}
}
fn trace_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
match self.trace_id_override {
Some(trace_id) => Ok(Some(trace_id)),
None => self.os_event_trace_id(data),
}
}
fn activity_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
match self.activity_id_override {
Some(activity_id) => Ok(Some(activity_id)),
None => self.os_event_activity_id(data),
}
}
fn related_activity_id(
&self,
data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
match self.related_activity_id_override {
Some(related_activity_id) => Ok(Some(related_activity_id)),
None => self.os_event_related_activity_id(data),
}
}
fn attribute_sources(&self) -> Writable<Vec<Box<dyn ExportAttributeSource>>> {
self.attribute_sources.clone()
}
fn make_sample(
&mut self,
data: &EventData,
value: MetricValue,
tid: u32,
kind: u16) -> anyhow::Result<ExportProcessSample> {
self.frames.clear();
self.os_event_callstack(data)?;
if self.disable_callstacks {
self.frames.truncate(1);
}
let time = self.os_event_time(data)?;
let cpu = self.os_event_cpu(data)?;
let sample = self.exporter.borrow_mut().make_sample(
time,
value,
tid,
cpu,
kind,
&self.frames);
Ok(sample)
}
fn add_custom_sample(
&mut self,
pid: u32,
sample: ExportProcessSample) -> anyhow::Result<()> {
self.exporter.borrow_mut().add_custom_sample(pid, sample)
}
fn add_custom_sample_with_record(
&mut self,
pid: u32,
sample: ExportProcessSample,
record_type: u16,
record_data: &[u8]) -> anyhow::Result<()> {
self.exporter.borrow_mut().add_custom_sample_with_record(
pid,
sample,
record_type,
record_data)
}
fn add_span(
&mut self,
span: ExportSpan) -> anyhow::Result<MetricValue> {
Ok(self.exporter.borrow_mut().span_to_value(span))
}
pub(crate) fn label_attribute(
&mut self,
name: &str,
label: &str) -> ExportAttributePair {
self.exporter.borrow_mut().label_attribute(name, label)
}
pub(crate) fn value_attribute(
&mut self,
name: &str,
value: u64) -> ExportAttributePair {
self.exporter.borrow_mut().value_attribute(name, value)
}
pub(crate) fn record_type(
&mut self,
record_type: ExportRecordType) -> u16 {
self.exporter.borrow_mut().record_type(record_type)
}
pub(crate) fn record_data(
&mut self,
record_type_id: u16,
data: &[u8]) ->usize {
self.exporter.borrow_mut().record_data(record_type_id, data)
}
pub(crate) fn kind(
&mut self,
kind: &str) -> u16 {
self.exporter.borrow_mut().sample_kind(kind)
}
fn push_unique_attributes(
&mut self,
attributes: ExportAttributes) -> usize {
self.exporter.borrow_mut().push_unique_attributes(attributes)
}
}
pub struct ExportBuiltContext<'a> {
exporter: &'a mut ExportMachine,
session: &'a mut os::Session,
event: &'a Event,
sample_kind: Option<u16>,
record_type: Option<u16>,
}
impl<'a> ExportBuiltContext<'a> {
const fn new(
exporter: &'a mut ExportMachine,
event: &'a Event,
session: &'a mut os::Session) -> Self {
Self {
exporter,
session,
event,
sample_kind: None,
record_type: None,
}
}
const fn take_sample_kind(&mut self) -> Option<u16> { self.sample_kind.take() }
const fn take_record_type(&mut self) -> Option<u16> { self.record_type.take() }
pub fn event(&self) -> &Event { self.event }
pub fn exporter_mut(&mut self) -> &mut ExportMachine { self.exporter }
pub fn session_mut(&mut self) -> &mut os::Session { self.session }
pub fn use_event_for_kind(
&mut self,
record: bool) {
let kind = self.set_sample_kind(self.event.name());
if record {
self.set_record_type(
ExportRecordType::from_event(
kind,
self.event));
}
}
pub fn duration_to_qpc(
&self,
duration: Duration) -> u64 {
let ns = duration.as_nanos() as f64;
let freq = ExportMachine::qpc_freq();
let ns_per_tick = freq as f64 / NANOS_IN_SEC as f64;
(ns * ns_per_tick).floor() as u64
}
pub fn set_sample_kind(
&mut self,
kind: &str) -> u16 {
let kind = self.exporter.sample_kind(kind);
self.sample_kind = Some(kind);
kind
}
pub fn set_record_type(
&mut self,
record_type: ExportRecordType) {
let record_type = self.exporter.record_type(record_type);
self.record_type = Some(record_type);
}
}
pub struct ExportSampleBuilder<'a> {
context: &'a ExportTraceContext<'a>,
kind: u16,
tid: Option<u32>,
pid: Option<u32>,
record_type: u16,
record_data: Option<&'a [u8]>,
attributes_id: Option<usize>,
event_data: Option<std::ops::Range<usize>>,
}
impl<'a> ExportSampleBuilder<'a> {
pub fn with_tid(
&mut self,
tid: u32) -> &mut Self {
self.tid = Some(tid);
self
}
pub fn with_pid(
&mut self,
pid: u32) -> &mut Self {
self.pid = Some(pid);
self
}
pub fn with_kind(
&mut self,
kind: u16) -> &mut Self {
self.kind = kind;
self
}
pub fn with_record_type(
&mut self,
record_type: u16) -> &mut Self {
self.record_type = record_type;
self
}
pub fn with_record_data(
&mut self,
record_data: &'a [u8]) -> &mut Self {
self.record_data = Some(record_data);
self
}
pub fn with_record_event_data(
&mut self,
range: std::ops::Range<usize>) -> &mut Self {
self.event_data = Some(range);
self
}
pub fn with_record_all_event_data(
&mut self) -> &mut Self {
let event_data_len = self.context.data.event_data().len();
self.with_record_event_data(0..event_data_len)
}
pub fn with_attributes(
&mut self,
attributes_id: Option<usize>) -> &mut Self {
if let Some(attributes_id) = attributes_id {
self.attributes_id = Some(attributes_id);
}
self
}
pub fn record_data(&self) -> Option<(u16, &'a [u8])> {
match self.record_data {
Some(record_data) => {
Some((self.record_type, record_data))
},
None => {
match &self.event_data {
Some(range) => {
Some((
self.record_type,
&self.context.data.event_data()[range.start..range.end]))
},
None => None,
}
},
}
}
pub fn tid(&self) -> anyhow::Result<u32> {
match self.tid {
Some(tid) => Ok(tid),
None => self.context.tid(),
}
}
pub fn pid(&self) -> anyhow::Result<u32> {
match self.pid {
Some(pid) => Ok(pid),
None => self.context.pid(),
}
}
pub fn save_span(
&self,
span: ExportSpan) -> anyhow::Result<()> {
let span = self.context.sampler.borrow_mut().add_span(span)?;
self.save_value(span)
}
pub fn save_value(
&self,
value: MetricValue) -> anyhow::Result<()> {
let tid = self.tid()?;
let pid = self.pid()?;
let mut sampler = self.context.sampler.borrow_mut();
let mut sample = sampler.make_sample(
self.context.data,
value,
tid,
self.kind)?;
if let Some(attributes_id) = self.attributes_id {
sample.attach_attributes(attributes_id);
}
match self.record_data() {
Some((record_type, record_data)) => {
sampler.add_custom_sample_with_record(
pid,
sample,
record_type,
record_data)
},
None => {
sampler.add_custom_sample(
pid,
sample)
}
}
}
}
pub trait ExportAttributeSource {
fn initialize(
&mut self,
machine: &mut ExportMachine);
fn add_attributes(
&mut self,
trace: &mut ExportTraceContext,
attributes: &mut ExportAttributes) -> anyhow::Result<()>;
}
pub type BoxedExportTraceAttributesHook = Box<dyn FnMut(&mut ExportTraceContext, usize) -> anyhow::Result<usize>>;
pub struct ExportTraceContext<'a> {
sampler: Writable<ExportSampler>,
proxy: Writable<ExportProxy>,
sample_kind: u16,
record_type: u16,
data: &'a EventData<'a>,
}
impl<'a> ExportTraceContext<'a> {
const fn new(
sampler: Writable<ExportSampler>,
proxy: Writable<ExportProxy>,
sample_kind: u16,
record_type: u16,
data: &'a EventData) -> Self {
Self {
sampler,
proxy,
sample_kind,
record_type,
data,
}
}
pub fn data(&self) -> &'a EventData<'_> { self.data }
pub fn cpu(&self) -> anyhow::Result<u16> {
self.sampler.borrow().os_event_cpu(self.data)
}
pub fn time(&self) -> anyhow::Result<u64> {
self.sampler.borrow().os_event_time(self.data)
}
pub fn pid(&self) -> anyhow::Result<u32> {
self.sampler.borrow().os_event_pid(self.data)
}
pub fn tid(&self) -> anyhow::Result<u32> {
self.sampler.borrow().os_event_tid(self.data)
}
pub fn id(&self) -> anyhow::Result<Option<usize>> {
self.sampler.borrow().id(self.data)
}
pub fn op_code(&self) -> anyhow::Result<Option<u16>> {
self.sampler.borrow().op_code(self.data)
}
pub fn version(&self) -> anyhow::Result<Option<u16>> {
self.sampler.borrow().version(self.data)
}
pub fn span_id(&self) -> anyhow::Result<Option<[u8; 8]>> {
self.sampler.borrow().span_id(self.data)
}
pub fn trace_id(&self) -> anyhow::Result<Option<[u8; 16]>> {
self.sampler.borrow().trace_id(self.data)
}
pub fn activity_id(&self) -> anyhow::Result<Option<[u8; 16]>> {
self.sampler.borrow().activity_id(self.data)
}
pub fn related_activity_id(&self) -> anyhow::Result<Option<[u8; 16]>> {
self.sampler.borrow().related_activity_id(self.data)
}
pub fn cgroup(&self) -> anyhow::Result<Option<u64>> {
self.sampler.borrow().os_event_cgroup(self.data)
}
pub fn record_type(
&mut self,
record_type: ExportRecordType) -> u16 {
self.sampler.borrow_mut().record_type(record_type)
}
pub fn record_data(
&mut self,
record_type_id: u16,
data: &[u8]) -> usize {
self.sampler.borrow_mut().record_data(record_type_id, data)
}
pub fn kind(
&mut self,
kind: &str) -> u16 {
self.sampler.borrow_mut().kind(kind)
}
pub fn proxy_data(
&mut self,
event_id: usize,
full_data: &[u8],
event_data: &[u8]) {
self.proxy.borrow_mut().proxy_event_data(
event_id,
full_data,
event_data);
}
pub fn proxy_event_data(
&mut self,
event_id: usize,
range: std::ops::Range<usize>) {
self.proxy_data(
event_id,
self.data.full_data(),
&self.data.event_data()[range]);
}
pub fn override_version(
&mut self,
version: Option<u16>) {
self.sampler.borrow_mut().override_version(version);
}
pub fn override_callstacks(
&mut self,
disable_callstacks: bool) {
self.sampler.borrow_mut().disable_callstacks = disable_callstacks;
}
pub fn override_op_code(
&mut self,
op_code: Option<u16>) {
self.sampler.borrow_mut().override_op_code(op_code);
}
pub fn override_trace_id(
&mut self,
trace_id: Option<[u8; 16]>) {
self.sampler.borrow_mut().override_trace_id(trace_id);
}
pub fn override_span_id(
&mut self,
span_id: Option<[u8; 8]>) {
self.sampler.borrow_mut().override_span_id(span_id);
}
pub fn override_activity_id(
&mut self,
activity_id: Option<[u8; 16]>) {
self.sampler.borrow_mut().override_activity_id(activity_id);
}
pub fn override_related_activity_id(
&mut self,
related_activity_id: Option<[u8; 16]>) {
self.sampler.borrow_mut().override_related_activity_id(related_activity_id);
}
pub fn default_attributes(&mut self) -> anyhow::Result<Option<usize>> {
let sources = self.sampler.borrow().attribute_sources();
let mut attributes = ExportAttributes::default();
for source in sources.borrow_mut().iter_mut() {
source.add_attributes(self, &mut attributes)?;
}
let associated_ids = attributes.associated_ids();
let new_attributes = attributes.attributes();
let attribute_id = if !new_attributes.is_empty() || associated_ids.len() > 1 {
Some(self.sampler.borrow_mut().push_unique_attributes(attributes))
} else if associated_ids.len() == 1 {
Some(associated_ids[0])
} else {
None
};
Ok(attribute_id)
}
pub fn sample_builder(&mut self) -> ExportSampleBuilder<'_> {
ExportSampleBuilder {
context: self,
tid: None,
pid: None,
kind: self.sample_kind,
record_type: self.record_type,
record_data: None,
attributes_id: None,
event_data: None,
}
}
pub fn label_attribute(
&mut self,
name: &str,
label: &str) -> ExportAttributePair {
self.sampler.borrow_mut().label_attribute(name, label)
}
pub fn value_attribute(
&mut self,
name: &str,
value: u64) -> ExportAttributePair {
self.sampler.borrow_mut().value_attribute(name, value)
}
pub fn push_unique_attributes(
&mut self,
attributes: ExportAttributes) -> usize {
self.sampler.borrow_mut().push_unique_attributes(attributes)
}
}
type BoxedBuiltCallback = Box<dyn FnMut(&mut ExportBuiltContext) -> anyhow::Result<()>>;
type BoxedTraceCallback = Box<dyn FnMut(&mut ExportTraceContext) -> anyhow::Result<()>>;
struct ExportEventCallback {
event: Option<Event>,
built: BoxedBuiltCallback,
trace: BoxedTraceCallback,
}
impl ExportEventCallback {
fn new(
event: Event,
built: impl FnMut(&mut ExportBuiltContext) -> anyhow::Result<()> + 'static,
trace: impl FnMut(&mut ExportTraceContext) -> anyhow::Result<()> + 'static) -> Self {
Self {
event: Some(event),
built: Box::new(built),
trace: Box::new(trace),
}
}
}
pub struct ExportSampleFilterContext<'a> {
kinds: &'a Vec<String>,
spans: &'a Vec<ExportSpan>,
record_types: &'a Vec<ExportRecordType>,
record_type_id: u16,
record_data: Option<&'a [u8]>,
strings: &'a InternedStrings,
attributes: &'a Vec<ExportAttributes>,
proc: &'a ExportProcess,
sample: &'a ExportProcessSample,
}
impl<'a> ExportSampleFilterContext<'a> {
fn run_hooks(
&self,
hooks: &Vec<Box<dyn Fn(&ExportSampleFilterContext) -> ExportFilterAction>>) -> bool {
for hook in hooks {
match hook(&self) {
ExportFilterAction::Keep => { },
ExportFilterAction::Drop => { return false; },
}
}
return true;
}
pub fn sample(&self) -> &ExportProcessSample { self.sample }
pub fn sample_span(&self) -> Option<&ExportSpan> {
if let MetricValue::Span(id) = self.sample.value() {
if id < self.spans.len() {
return Some(&self.spans[id]);
}
}
None
}
pub fn span_name(
&self,
span: &ExportSpan) -> &str {
span.name(self.strings)
}
pub fn pid(&self) -> u32 { self.proc.pid() }
pub fn cgroup_id(&self) -> u64 {
let attributes_id = self.sample.attributes_id();
if attributes_id == 0 {
return 0;
}
let cgroup_name_id = match self.strings.find_id("CGroup") {
Some(id) => id,
None => return 0,
};
let mut seen = HashSet::new();
let mut stack = vec![attributes_id];
while let Some(id) = stack.pop() {
if !seen.insert(id) {
continue;
}
if id >= self.attributes.len() {
continue;
}
let attrs = &self.attributes[id];
for pair in attrs.attributes() {
if pair.name() == cgroup_name_id {
if let Some(value) = pair.value() {
return value;
}
}
}
for associated_id in attrs.associated_ids() {
stack.push(*associated_id);
}
}
0
}
pub fn sample_record_data(&self) -> Option<ExportRecordData<'_>> {
if self.record_data.is_none() {
return None;
}
Some(ExportRecordData::new(
self.record_type_id,
&self.record_types[self.record_type_id as usize],
&self.record_data.unwrap()))
}
pub fn sample_kind_str(&self) -> &str {
let kind = self.sample.kind() as usize;
if kind >= self.kinds.len() {
return "Unknown";
}
&self.kinds[kind]
}
pub fn comm_name(&self) -> &str {
match self.proc.comm_id() {
Some(id) => {
match self.strings.from_id(id) {
Ok(name) => { name },
Err(_) => { "Unknown" },
}
},
None => { "Unknown" },
}
}
}
macro_rules! filter_sample_ret_on_drop {
($self: expr, $proc:expr, $sample:expr, $record_type_id:expr, $record_data:expr) => {
if !$self.sample_hooks.is_empty() {
let context = ExportSampleFilterContext {
kinds: &$self.kinds,
strings: &$self.strings,
record_types: &$self.record_types,
record_type_id: $record_type_id,
record_data: $record_data,
spans: &$self.spans,
attributes: &$self.attributes,
proc: $proc,
sample: $sample,
};
if !context.run_hooks(&$self.sample_hooks) {
return Ok(());
}
}
}
}
macro_rules! create_record {
($self: expr, $record_type:expr, $record_data:expr) => {
{
let record_id = $self.records.len();
let offset = $self.record_data.len();
let len = $record_data.len() as u32;
$self.records.push(
ExportRecord::new(
$record_type,
offset,
len));
$self.record_data.extend_from_slice($record_data);
record_id
}
}
}
pub struct ExportSettings {
string_buckets: usize,
callstack_buckets: usize,
cpu_profiling: bool,
cpu_freq: u64,
cswitches: bool,
unwinder: bool,
hard_page_faults: bool,
soft_page_faults: bool,
callstack_helper: Option<CallstackHelper>,
#[allow(dead_code)]
os: OSExportSettings,
events: Option<Vec<ExportEventCallback>>,
sample_hooks: Option<Vec<Box<dyn Fn(&ExportSampleFilterContext) -> ExportFilterAction>>>,
target_pids: Option<Vec<i32>>,
target_cpus: Option<Vec<u16>>,
attribute_sources: Option<Vec<Box<dyn ExportAttributeSource>>>,
proxy_id: usize,
}
impl Default for ExportSettings {
fn default() -> Self {
os::default_export_settings()
}
}
impl ExportSettings {
#[allow(unused_mut)]
pub fn new(mut callstack_helper: CallstackHelper) -> Self {
let unwinder = callstack_helper.has_unwinder();
Self {
string_buckets: 64,
callstack_buckets: 512,
cpu_profiling: false,
cpu_freq: 1000,
cswitches: false,
hard_page_faults: false,
soft_page_faults: false,
callstack_helper: Some(callstack_helper.with_external_lookup()),
unwinder,
os: OSExportSettings::new(),
events: None,
sample_hooks: None,
target_pids: None,
target_cpus: None,
attribute_sources: None,
proxy_id: 0,
}
}
pub fn for_each_event(
&self,
mut closure: impl FnMut(&Event)) {
if let Some(events) = &self.events {
for event in events {
if let Some(event) = &event.event {
closure(event);
}
}
}
}
pub fn has_unwinder(&self) -> bool { self.unwinder }
pub fn new_proxy_event(
&mut self,
name: String,
id: usize) -> Event {
self.proxy_id += 1;
let mut event = Event::new(id, name);
event.set_proxy_id(self.proxy_id);
event
}
pub fn with_sample_hook(
self,
hook: impl Fn(&ExportSampleFilterContext) -> ExportFilterAction + 'static) -> Self {
let mut clone = self;
let hook = Box::new(hook);
match clone.sample_hooks.as_mut() {
Some(hooks) => { hooks.push(hook); },
None => { clone.sample_hooks = Some(vec![hook]); }
}
clone
}
pub fn with_attribute_source(
self,
source: Box<dyn ExportAttributeSource>) -> Self {
let mut clone = self;
match clone.attribute_sources.as_mut() {
Some(attribute_sources) => { attribute_sources.push(source); },
None => { clone.attribute_sources = Some(vec![source]); }
}
clone
}
pub fn with_version_attributes(self) -> Self {
self.with_attribute_source(
Box::new(VersionOpCodeAttributeSource::default()))
}
pub fn with_trace_context_attributes(self) -> Self {
self.with_attribute_source(
Box::new(TraceContextAttributeSource::default()))
}
pub fn with_activity_id_attributes(self) -> Self {
self.with_attribute_source(
Box::new(ActivityIdAttributeSource::default()))
}
pub fn with_cgroup_attributes(self) -> Self {
self.with_attribute_source(
Box::new(CgroupAttributeSource::default()))
}
pub fn with_event(
self,
event: Event,
built: impl FnMut(&mut ExportBuiltContext) -> anyhow::Result<()> + 'static,
trace: impl FnMut(&mut ExportTraceContext) -> anyhow::Result<()> + 'static) -> Self {
let mut clone = self;
let callback = ExportEventCallback::new(
event,
built,
trace);
match clone.events.as_mut() {
Some(events) => { events.push(callback); },
None => { clone.events = Some(vec![callback]); }
}
clone
}
pub fn with_string_buckets(
self,
buckets: usize) -> Self {
let mut clone = self;
clone.string_buckets = buckets;
clone
}
pub fn with_cpu_profiling(
self,
freq: u64) -> Self {
let mut clone = self;
clone.cpu_profiling = true;
clone.cpu_freq = freq;
clone
}
pub fn with_callstack_buckets(
self,
buckets: usize) -> Self {
let mut clone = self;
clone.callstack_buckets = buckets;
clone
}
#[cfg(target_os = "linux")]
pub fn with_callstack_stack_size(
mut self,
bytes: u32) -> Self {
if let Some(mut helper) = self.callstack_helper.take() {
self.callstack_helper = Some(helper.with_stack_size(bytes));
}
self
}
pub fn with_cswitches(self) -> Self {
let mut clone = self;
clone.cswitches = true;
clone
}
pub fn with_hard_page_faults(self) -> Self {
let mut clone = self;
clone.hard_page_faults = true;
clone
}
pub fn with_soft_page_faults(self) -> Self {
let mut clone = self;
clone.soft_page_faults = true;
clone
}
pub fn with_target_pid(
self,
pid: i32) -> Self {
let mut clone = self;
match clone.target_pids.as_mut() {
Some(pids) => { pids.push(pid); },
None => { clone.target_pids = Some(vec![pid]); }
}
clone
}
pub fn with_target_cpu(
self,
cpu: u16) -> Self {
let mut clone = self;
match clone.target_cpus.as_mut() {
Some(cpus) => { cpus.push(cpu); },
None => { clone.target_cpus = Some(vec![cpu]); }
}
clone
}
pub fn cpu_freq(&self) -> u64 { self.cpu_freq }
}
pub enum ExportFilterAction {
Keep,
Drop,
}
pub struct ExportMachine {
settings: ExportSettings,
strings: InternedStrings,
callstacks: InternedCallstacks,
pub(crate) os: OSExportMachine,
procs: HashMap<u32, ExportProcess>,
records: Vec<ExportRecord>,
attributes: Vec<ExportAttributes>,
spans: Vec<ExportSpan>,
record_data: Vec<u8>,
module_metadata: ModuleMetadataLookup,
kinds: Vec<String>,
record_types: Vec<ExportRecordType>,
map_index: usize,
drop_closures: Vec<Box<dyn FnMut()>>,
start_date: Option<DateTime<Utc>>,
start_qpc: Option<u64>,
end_qpc: Option<u64>,
duration: Option<Duration>,
sample_hooks: Vec<Box<dyn Fn(&ExportSampleFilterContext) -> ExportFilterAction>>,
attribute_sources: Writable<Vec<Box<dyn ExportAttributeSource>>>,
}
pub trait ExportMachineSessionHooks {
fn hook_export_machine(
&mut self) -> anyhow::Result<Writable<ExportMachine>>;
}
pub trait ExportMachineOSHooks {
fn os_add_kernel_mappings_with(
&mut self,
kernel_symbols: &mut impl ExportSymbolReader);
fn os_capture_file_symbol_metadata(&mut self);
fn os_resolve_local_file_symbols(&mut self);
fn os_resolve_local_anon_symbols(&mut self);
fn os_add_mmap_exec(
&mut self,
pid: u32,
mapping: &mut ExportMapping,
filename: &str) -> anyhow::Result<()>;
fn os_add_comm_exec(
&mut self,
pid: u32,
comm: &str) -> anyhow::Result<()>;
fn os_add_dynamic_symbol(
&mut self,
symbol: &DynamicSymbol) -> anyhow::Result<()>;
fn os_qpc_time() -> u64;
fn os_qpc_freq() -> u64;
fn os_cpu_count() -> u32;
fn os_system_page_size() -> u64;
}
pub type CommMap = HashMap<Option<usize>, Vec<u32>>;
const NO_FRAMES: [u64; 1] = [0; 1];
impl ExportMachine {
pub fn new(mut settings: ExportSettings) -> Self {
let mut strings = InternedStrings::new(settings.string_buckets);
let callstacks = InternedCallstacks::new(settings.callstack_buckets);
let sample_hooks = settings.sample_hooks.take().unwrap_or_default();
let mut attribute_sources = settings.attribute_sources.take().unwrap_or_default();
let mut records = Vec::new();
let mut attributes = Vec::new();
let mut record_types = Vec::new();
strings.to_id("");
records.push(ExportRecord::default());
record_types.push(ExportRecordType::default());
attributes.push(ExportAttributes::default());
let mut machine = Self {
settings,
strings,
callstacks,
os: OSExportMachine::new(),
procs: HashMap::new(),
records,
attributes,
spans: Vec::new(),
record_data: Vec::new(),
module_metadata: ModuleMetadataLookup::new(),
kinds: Vec::new(),
record_types,
map_index: 0,
drop_closures: Vec::new(),
start_date: None,
start_qpc: None,
end_qpc: None,
duration: None,
sample_hooks,
attribute_sources: Writable::new(Vec::new()),
};
for mut source in attribute_sources.drain(..) {
source.initialize(&mut machine);
machine.attribute_sources.borrow_mut().push(source);
}
machine
}
pub fn start_date(&self) -> Option<DateTime<Utc>> { self.start_date }
pub fn start_qpc(&self) -> Option<u64> { self.start_qpc }
pub fn end_qpc(&self) -> Option<u64> { self.end_qpc }
pub fn duration(&self) -> Option<Duration> { self.duration }
pub fn settings(&self) -> &ExportSettings { &self.settings }
pub fn qpc_time() -> u64 { Self::os_qpc_time() }
pub fn qpc_freq() -> u64 { Self::os_qpc_freq() }
pub fn system_page_size() -> u64 { Self::os_system_page_size() }
pub fn qpc_to_ns(
freq: u64,
mut qpc: u64) -> u64 {
let mut ns: u64 = 0;
if freq != 0 {
while qpc >= freq {
ns += NANOS_IN_SEC;
qpc -= freq;
}
ns += qpc * NANOS_IN_SEC / freq;
}
ns
}
pub fn qpc_to_duration(
freq: u64,
qpc: u64) -> Duration {
Duration::from_nanos(Self::qpc_to_ns(freq, qpc))
}
pub fn cpu_count() -> u32 { Self::os_cpu_count() }
pub fn get_mapping_metadata(
&self,
mapping: &ExportMapping) -> Option<&ModuleMetadata> {
match mapping.node() {
Some(node) => { self.module_metadata.get(node) },
None => { None }
}
}
pub fn add_sample_hook(
&mut self,
hook: impl Fn(&ExportSampleFilterContext) -> ExportFilterAction + 'static) {
self.sample_hooks.push(Box::new(hook));
}
pub fn replay_by_time(
&mut self,
predicate: impl Fn(&ExportProcess) -> bool,
mut callback: impl FnMut(&ExportMachine, &ExportProcessReplay) -> anyhow::Result<()>) -> anyhow::Result<()> {
let mut replay_procs = Vec::new();
for process in self.processes_mut() {
if !predicate(process) {
continue;
}
process.sort_samples_by_time();
process.sort_mappings_by_time();
}
for process in self.processes() {
if !predicate(process) {
continue;
}
replay_procs.push(process.to_replay());
}
loop {
let mut earliest = u64::MAX;
for replay in &replay_procs {
if replay.done() {
continue;
}
let time = replay.time();
if time < earliest {
earliest = time;
}
}
if earliest == u64::MAX {
break;
}
for replay in &mut replay_procs {
if replay.done() {
continue;
}
if replay.time() == earliest {
(callback)(&self, replay)?;
replay.advance();
}
}
}
Ok(())
}
pub fn mark_start(&mut self) {
self.mark_start_direct(
Utc::now(),
Self::os_qpc_time());
}
pub fn mark_start_direct(
&mut self,
start_date: DateTime<Utc>,
start_qpc: u64) {
self.start_date = Some(start_date);
self.start_qpc = Some(start_qpc);
}
pub fn mark_end(&mut self) {
if let Some(start_qpc) = self.start_qpc {
let end_qpc = Self::os_qpc_time();
let qpc_freq = Self::os_qpc_freq();
let duration = Self::qpc_to_duration(qpc_freq, end_qpc - start_qpc);
self.end_qpc = Some(end_qpc);
self.duration = Some(duration);
}
}
pub fn sample_kinds(&self) -> &Vec<String> { &self.kinds }
pub fn record_types(&self) -> &Vec<ExportRecordType> { &self.record_types }
pub fn strings(&self) -> &InternedStrings { &self.strings }
pub fn callstacks(&self) -> &InternedCallstacks { &self.callstacks }
pub fn processes(&self) -> Values<'_, u32, ExportProcess> { self.procs.values() }
pub fn attributes(
&self,
attributes_id: usize,
walker: &mut ExportAttributeWalker) {
walker.start();
walker.push_id(attributes_id);
while let Some(id) = walker.pop_id() {
if id <= self.attributes.len() {
let attributes = &self.attributes[id];
walker.push_attributes(attributes.attributes());
for associated_id in attributes.associated_ids() {
walker.push_id(*associated_id);
}
}
}
}
pub fn sample_attributes(
&self,
sample: &ExportProcessSample,
walker: &mut ExportAttributeWalker) {
self.attributes(
sample.attributes_id(),
walker);
}
pub fn try_get_record_data(
&self,
record_id: usize) -> Option<&[u8]> {
if record_id >= self.records.len() {
return None;
}
let record = &self.records[record_id];
Some(&self.record_data[record.start()..record.end()])
}
pub fn sample_record_data(
&self,
sample: &ExportProcessSample) -> ExportRecordData<'_> {
let record = &self.records[sample.record_id()];
let record_type_id = record.record_type();
ExportRecordData::new(
record_type_id,
&self.record_types[record_type_id as usize],
&self.record_data[record.start()..record.end()])
}
pub fn sample_span(
&self,
sample: &ExportProcessSample) -> Option<&ExportSpan> {
self.span_from_value(sample.value())
}
pub fn span_from_value(
&self,
value: MetricValue) -> Option<&ExportSpan> {
if let Span(id) = value {
if id < self.spans.len() {
return Some(&self.spans[id]);
}
}
None
}
pub fn span_to_value(
&mut self,
span: ExportSpan) -> MetricValue {
let id = self.spans.len();
self.spans.push(span);
MetricValue::Span(id)
}
pub fn find_sample_kind(
&self,
target_kind: &str) -> Option<u16> {
for (i, kind) in self.kinds.iter().enumerate() {
if kind == target_kind {
return Some(i as u16);
}
}
None
}
pub fn find_process(
&self,
pid: u32) -> Option<&ExportProcess> {
self.procs.get(&pid)
}
pub fn split_processes_by_comm(
&self) -> CommMap {
let mut map = CommMap::new();
for (pid, process) in &self.procs {
map.entry(process.comm_id())
.and_modify(|e| { e.push(*pid) })
.or_insert_with(|| { vec![*pid] });
}
map
}
pub fn processes_mut(&mut self) -> ValuesMut<'_, u32, ExportProcess> {
self.procs.values_mut()
}
pub fn push_unique_attributes(
&mut self,
mut attributes: ExportAttributes) -> usize {
let id = self.attributes.len();
attributes.shrink();
self.attributes.push(attributes);
id
}
pub fn label_attribute(
&mut self,
name: &str,
label: &str) -> ExportAttributePair {
ExportAttributePair::new_label(
name,
label,
&mut self.strings)
}
pub fn value_attribute(
&mut self,
name: &str,
value: u64) -> ExportAttributePair {
ExportAttributePair::new_value(
name,
value,
&mut self.strings)
}
pub fn record_attribute(
&mut self,
name: &str,
data: &[u8]) -> ExportAttributePair {
let record_id = create_record!(self, 0, data);
ExportAttributePair::new_record(
name,
record_id,
&mut self.strings)
}
pub fn record_data(
&mut self,
record_type: u16,
data: &[u8]) -> usize {
create_record!(self, record_type, data)
}
pub fn record_type(
&mut self,
record_type: ExportRecordType) -> u16 {
for (i, existing) in self.record_types.iter().enumerate() {
if existing == &record_type {
return i as u16;
}
}
let count = self.record_types.len() as u16;
self.record_types.push(record_type);
count
}
pub fn sample_kind(
&mut self,
name: &str) -> u16 {
for (i, kind_name) in self.kinds.iter().enumerate() {
if kind_name == name {
return i as u16;
}
}
let count = self.kinds.len() as u16;
self.kinds.push(name.to_owned());
count
}
pub fn intern(
&mut self,
value: &str) -> usize {
self.strings.to_id(value)
}
pub fn add_kernel_mappings_with(
&mut self,
kernel_symbols: &mut impl ExportSymbolReader) {
self.os_add_kernel_mappings_with(kernel_symbols)
}
pub fn add_kernel_mappings(
&mut self) {
let mut kernel_symbols = KernelSymbolReader::new();
self.add_kernel_mappings_with(&mut kernel_symbols);
}
pub fn add_dynamic_symbol(
&mut self,
symbol: &DynamicSymbol) -> anyhow::Result<()> {
self.os_add_dynamic_symbol(symbol)
}
pub fn capture_file_symbol_metadata(&mut self) {
self.os_capture_file_symbol_metadata();
}
pub fn resolve_local_file_symbols(&mut self) {
self.os_resolve_local_file_symbols();
}
pub fn resolve_local_anon_symbols(&mut self) {
for proc in self.procs.values_mut() {
proc.add_dynamic_symbol_mappings(&mut self.map_index);
}
self.os_resolve_local_anon_symbols();
}
pub fn capture_and_resolve_symbols(&mut self) {
self.capture_file_symbol_metadata();
self.add_kernel_mappings();
self.resolve_local_file_symbols();
self.resolve_local_anon_symbols();
}
fn process_mut(
&mut self,
pid: u32) -> &mut ExportProcess {
self.procs.entry(pid).or_insert_with(|| ExportProcess::new(pid))
}
pub fn add_mmap_exec(
&mut self,
time: u64,
pid: u32,
addr: u64,
len: u64,
pgoffset: u64,
maj: u32,
min: u32,
ino: u64,
filename: &str) -> anyhow::Result<()> {
if pid == 0 {
return Ok(());
}
let anon = filename.is_empty() ||
filename.starts_with('[') ||
filename.starts_with("/memfd:") ||
filename.starts_with("//anon");
let unwind_type =
if anon || filename.ends_with(".dll") || filename.ends_with(".exe") {
UnwindType::Prolog
} else {
UnwindType::DWARF
};
let mut mapping = ExportMapping::new(
time,
self.intern(filename),
addr,
addr + len - 1,
pgoffset,
anon,
self.map_index,
unwind_type);
if !anon {
let node = ExportDevNode::from_parts(maj, min, ino);
mapping.set_node(node);
}
self.os_add_mmap_exec(
pid,
&mut mapping,
filename)?;
self.map_index += 1;
self.process_mut(pid).add_mapping(mapping);
Ok(())
}
pub fn add_comm_exec(
&mut self,
pid: u32,
comm: &str,
time_qpc: u64) -> anyhow::Result<()> {
if pid == 0 {
return Ok(());
}
let comm_id = self.intern(comm);
let proc = self.process_mut(pid);
proc.set_comm_id(comm_id);
proc.set_create_time_qpc(time_qpc);
self.os_add_comm_exec(
pid,
comm)
}
pub fn add_comm_exit(
&mut self,
pid: u32,
time_qpc: u64) -> anyhow::Result<()> {
if pid == 0 {
return Ok(());
}
self.process_mut(pid).set_exit_time_qpc(time_qpc);
Ok(())
}
pub fn make_sample(
&mut self,
time: u64,
value: MetricValue,
tid: u32,
cpu: u16,
kind: u16,
frames: &[u64]) -> ExportProcessSample {
let mut frames = frames;
if frames.is_empty() {
frames = &NO_FRAMES;
}
let ip = frames[0];
let callstack_id = self.callstacks.to_id(&frames[1..]);
ExportProcessSample::new(
time,
value,
cpu,
kind,
tid,
ip,
callstack_id)
}
pub fn add_process_sample(
&mut self,
pid: u32,
sample: ExportProcessSample) ->anyhow::Result<()> {
let proc = self.procs.entry(pid).or_insert_with(|| ExportProcess::new(pid));
filter_sample_ret_on_drop!(self, &proc, &sample, 0, None);
proc.add_sample(sample);
Ok(())
}
pub fn add_sample(
&mut self,
time: u64,
value: MetricValue,
pid: u32,
tid: u32,
cpu: u16,
kind: u16,
frames: &[u64]) -> anyhow::Result<()> {
let sample = self.make_sample(
time,
value,
tid,
cpu,
kind,
frames);
let proc = self.procs.entry(pid).or_insert_with(|| ExportProcess::new(pid));
filter_sample_ret_on_drop!(self, &proc, &sample, 0, None);
proc.add_sample(sample);
Ok(())
}
fn attach_record_to_sample(
&mut self,
pid: u32,
mut sample: ExportProcessSample,
record_type: u16,
record_data: &[u8]) -> anyhow::Result<()> {
if sample.has_record() {
anyhow::bail!("Record is already attached.");
}
let proc = self.procs.entry(pid).or_insert_with(|| ExportProcess::new(pid));
filter_sample_ret_on_drop!(self, &proc, &sample, record_type, Some(record_data));
let record_id = create_record!(self, record_type, record_data);
sample.attach_record(record_id);
proc.add_sample(sample);
Ok(())
}
pub fn add_sample_with_record(
&mut self,
time: u64,
value: MetricValue,
pid: u32,
tid: u32,
cpu: u16,
kind: u16,
record_type: u16,
record_data: &[u8],
frames: &[u64]) -> anyhow::Result<()> {
let sample = self.make_sample(
time,
value,
tid,
cpu,
kind,
frames);
self.attach_record_to_sample(
pid,
sample,
record_type,
record_data)
}
pub fn add_custom_sample(
&mut self,
pid: u32,
sample: ExportProcessSample) -> anyhow::Result<()> {
let proc = self.procs.entry(pid).or_insert_with(|| ExportProcess::new(pid));
filter_sample_ret_on_drop!(self, &proc, &sample, 0, None);
proc.add_sample(sample);
Ok(())
}
pub fn add_custom_sample_with_record(
&mut self,
pid: u32,
sample: ExportProcessSample,
record_type: u16,
record_data: &[u8]) -> anyhow::Result<()> {
self.attach_record_to_sample(
pid,
sample,
record_type,
record_data)
}
pub fn load_pe_metadata(
&mut self) {
for proc in self.procs.values() {
for map in proc.mappings() {
if let Some(key) = map.node() {
if self.module_metadata.contains(key) {
continue;
}
if map.anon() {
continue;
}
if let Ok(filename) = self.strings.from_id(map.filename_id()) {
if filename.ends_with(".dll") || filename.ends_with(".exe") {
if let ModuleMetadata::PE(pe_metadata) = self.module_metadata.entry(*key)
.or_insert(ModuleMetadata::PE(PEModuleMetadata::new())) {
if let Ok(file) = proc.open_file(Path::new(filename)) {
let _ = pe_metadata.get_metadata_direct(file, &mut self.strings);
}
}
}
}
}
}
}
}
pub fn add_drop_closure(
&mut self,
closure: impl FnMut() + 'static) {
self.drop_closures.push(Box::new(closure));
}
pub fn cleanup(&mut self) {
for closure in &mut self.drop_closures {
closure();
}
self.drop_closures.clear();
}
}
impl Drop for ExportMachine {
fn drop(&mut self) {
self.cleanup();
}
}
pub trait ExportBuilderHelp {
fn with_exporter_events(
self,
settings: &ExportSettings) -> Self;
}
pub trait ExportSessionHelp {
fn build_exporter(
&mut self,
settings: ExportSettings) -> anyhow::Result<Writable<ExportMachine>>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::*;
#[test]
fn sample_records() {
let mut machine = ExportMachine::new(ExportSettings::default());
let mut e = Event::new(1, "test".into());
let format = e.format_mut();
format.add_field(
EventField::new(
"1".into(), "unsigned char".into(),
LocationType::Static, 0, 1));
let kind = machine.sample_kind("test");
let record_type = machine.record_type(ExportRecordType::from_event(kind, &e));
let mut record_data: [u8; 1] = [b'Z'];
let mut frames = Vec::new();
frames.push(1);
frames.push(2);
frames.push(3);
machine.add_sample_with_record(
0,
MetricValue::Count(0),
0,
0,
0,
kind,
record_type,
&record_data,
&frames).unwrap();
record_data[0] = b'A';
machine.add_sample_with_record(
0,
MetricValue::Count(0),
0,
0,
0,
kind,
record_type,
&record_data,
&frames).unwrap();
let proc = machine.find_process(0).unwrap();
let samples = proc.samples();
assert_eq!(2, samples.len());
let sample = &samples[0];
let data = machine.sample_record_data(sample);
let record_type = data.record_type();
let record_data = data.record_data();
assert_eq!(1, record_type.id());
assert_eq!("test", record_type.name());
assert_eq!(1, record_data.len());
assert_eq!(b'Z', record_data[0]);
let sample = &samples[1];
let data = machine.sample_record_data(sample);
let record_type = data.record_type();
let record_data = data.record_data();
assert_eq!(1, record_type.id());
assert_eq!("test", record_type.name());
assert_eq!(1, record_data.len());
assert_eq!(b'A', record_data[0]);
}
#[test]
fn record_type() {
let mut machine = ExportMachine::new(ExportSettings::default());
let mut e = Event::new(1, "test".into());
let format = e.format_mut();
format.add_field(
EventField::new(
"1".into(), "unsigned char".into(),
LocationType::Static, 0, 1));
let kind = machine.sample_kind("test");
let first = machine.record_type(ExportRecordType::from_event(kind, &e));
assert_ne!(0, first);
assert_eq!(first, machine.record_type(ExportRecordType::from_event(kind, &e)));
let mut e = Event::new(1, "test".into());
let format = e.format_mut();
format.add_field(
EventField::new(
"1".into(), "unsigned char".into(),
LocationType::Static, 0, 1));
format.add_field(
EventField::new(
"2".into(), "unsigned char".into(),
LocationType::Static, 0, 1));
let second = machine.record_type(ExportRecordType::from_event(kind, &e));
assert_ne!(0, second);
assert_ne!(first, second);
assert_eq!(second, machine.record_type(ExportRecordType::from_event(kind, &e)));
}
#[test]
fn replay_by_time() {
let mut machine = ExportMachine::new(ExportSettings::default());
let proc = machine.process_mut(1);
let first = ExportProcessSample::new(1, MetricValue::Count(0), 0, 0, 0, 0, 0);
let second = ExportProcessSample::new(3, MetricValue::Count(0), 0, 0, 0, 0, 0);
let third = ExportProcessSample::new(5, MetricValue::Count(0), 0, 0, 0, 0, 0);
let forth = ExportProcessSample::new(7, MetricValue::Count(0), 0, 0, 0, 0, 0);
proc.add_sample(forth);
proc.add_sample(second);
proc.add_sample(first);
proc.add_sample(third);
let proc = machine.process_mut(2);
let first = ExportProcessSample::new(2, MetricValue::Count(0), 0, 0, 0, 0, 0);
let second = ExportProcessSample::new(4, MetricValue::Count(0), 0, 0, 0, 0, 0);
let third = ExportProcessSample::new(6, MetricValue::Count(0), 0, 0, 0, 0, 0);
let forth = ExportProcessSample::new(8, MetricValue::Count(0), 0, 0, 0, 0, 0);
proc.add_sample(forth);
proc.add_sample(second);
proc.add_sample(first);
proc.add_sample(third);
let mut time = 0;
machine.replay_by_time(
|_process| true,
|_machine, event| {
if event.time() % 2 == 0 {
assert_eq!(2, event.process().pid());
} else {
assert_eq!(1, event.process().pid());
}
assert_eq!(event.time() - 1, time);
time = event.time();
Ok(())
}).expect("Should work");
}
#[cfg(target_os = "windows")]
#[test]
fn proxy() {
let mut settings = ExportSettings::default();
let count = Writable::new(0usize);
let mut first = settings.new_proxy_event("1".into(), 1);
let mut second = settings.new_proxy_event("2".into(), 2);
let mut third = settings.new_proxy_event("3".into(), 3);
let e_count = count.clone();
first.add_callback(move |data| {
*e_count.borrow_mut() += 1;
assert_eq!(b'1', data.event_data()[0]);
assert_eq!(1, data.event_data().len());
Ok(())
});
let e_count = count.clone();
second.add_callback(move |data| {
*e_count.borrow_mut() += 1;
assert_eq!(b'2', data.event_data()[0]);
assert_eq!(1, data.event_data().len());
Ok(())
});
let e_count = count.clone();
third.add_callback(move |data| {
*e_count.borrow_mut() += 1;
assert_eq!(b'3', data.event_data()[0]);
assert_eq!(1, data.event_data().len());
Ok(())
});
assert_eq!(1, first.id());
assert_eq!(2, second.id());
assert_eq!(3, third.id());
let mut proxy = ExportProxy::default();
proxy.add_event(first);
proxy.add_event(second);
proxy.add_event(third);
let machine = Writable::new(ExportMachine::new(settings));
let session = crate::etw::EtwSession::new();
let sampler = ExportSampler::new(
&machine,
OSExportSampler::new(&session));
let mut data = Vec::new();
data.push(b'1');
data.push(b'2');
data.push(b'3');
let format = EventFormat::new();
let event_data = EventData::new(
&data,
&data,
&format);
let mut context = ExportTraceContext::new(
Writable::new(sampler),
Writable::new(proxy),
0,
0,
&event_data);
context.proxy_event_data(1, 0..1);
context.proxy_event_data(2, 1..2);
context.proxy_event_data(3, 2..3);
assert_eq!(3, *count.borrow());
}
#[test]
fn attributes() {
let settings = ExportSettings::default();
let mut machine = ExportMachine::new(settings);
let mut frames = Vec::new();
let mut sample = machine.make_sample(
0,
MetricValue::Count(1),
0,
0,
0,
&mut frames);
let mut walker = ExportAttributeWalker::default();
machine.sample_attributes(&sample, &mut walker);
assert!(walker.attributes().is_empty());
let mut sample_attributes = ExportAttributes::default();
sample_attributes.push(machine.label_attribute("Parent", "true"));
let attribute_id = machine.push_unique_attributes(sample_attributes);
sample.attach_attributes(attribute_id);
machine.sample_attributes(&sample, &mut walker);
assert_eq!(1, walker.attributes().len());
let mut sample_attributes = ExportAttributes::default();
sample_attributes.push(machine.label_attribute("Child", "true"));
sample_attributes.push_association(attribute_id);
let mut sample = machine.make_sample(
0,
MetricValue::Count(1),
0,
0,
0,
&mut frames);
let attribute_id = machine.push_unique_attributes(sample_attributes);
sample.attach_attributes(attribute_id);
machine.sample_attributes(&sample, &mut walker);
let attributes = walker.attributes();
assert_eq!(2, attributes.len());
let parent_str_id = machine.intern("Parent");
let child_str_id = machine.intern("Child");
let true_str_id = machine.intern("true");
assert_eq!(child_str_id, attributes[0].name());
assert_eq!(true_str_id, attributes[0].label().expect("Should be label attribute"));
assert_eq!(parent_str_id, attributes[1].name());
assert_eq!(true_str_id, attributes[1].label().expect("Should be label attribute"));
}
}