Struct fxprof_processed_profile::Profile
source · pub struct Profile { /* private fields */ }
Expand description
Stores the profile data and can be serialized as JSON, via serde::Serialize
.
The profile data is organized into a list of processes with threads. Each thread has its own samples and markers.
use fxprof_processed_profile::{Profile, CategoryHandle, CpuDelta, Frame, FrameInfo, FrameFlags, SamplingInterval, Timestamp};
use std::time::SystemTime;
let mut profile = Profile::new("My app", SystemTime::now().into(), SamplingInterval::from_millis(1));
let process = profile.add_process("App process", 54132, Timestamp::from_millis_since_reference(0.0));
let thread = profile.add_thread(process, 54132000, Timestamp::from_millis_since_reference(0.0), true);
profile.set_thread_name(thread, "Main thread");
let stack = vec![
FrameInfo { frame: Frame::Label(profile.intern_string("Root node")), category_pair: CategoryHandle::OTHER.into(), flags: FrameFlags::empty() },
FrameInfo { frame: Frame::Label(profile.intern_string("First callee")), category_pair: CategoryHandle::OTHER.into(), flags: FrameFlags::empty() }
];
profile.add_sample(thread, Timestamp::from_millis_since_reference(0.0), stack.into_iter(), CpuDelta::ZERO, 1);
let writer = std::io::BufWriter::new(output_file);
serde_json::to_writer(writer, &profile)?;
Implementations§
source§impl Profile
impl Profile
sourcepub fn new(
product: &str,
reference_timestamp: ReferenceTimestamp,
interval: SamplingInterval
) -> Self
pub fn new( product: &str, reference_timestamp: ReferenceTimestamp, interval: SamplingInterval ) -> Self
Create a new profile.
The product
is the name of the main application which was profiled.
The reference_timestamp
is some arbitrary absolute timestamp which all
other timestamps in the profile data are relative to. The interval
is the intended
time delta between samples.
sourcepub fn set_interval(&mut self, interval: SamplingInterval)
pub fn set_interval(&mut self, interval: SamplingInterval)
Change the declared sampling interval.
sourcepub fn set_reference_timestamp(
&mut self,
reference_timestamp: ReferenceTimestamp
)
pub fn set_reference_timestamp( &mut self, reference_timestamp: ReferenceTimestamp )
Change the reference timestamp.
sourcepub fn set_product(&mut self, product: &str)
pub fn set_product(&mut self, product: &str)
Change the product name.
sourcepub fn add_category(
&mut self,
name: &str,
color: CategoryColor
) -> CategoryHandle
pub fn add_category( &mut self, name: &str, color: CategoryColor ) -> CategoryHandle
Add a category and return its handle.
Categories are used for stack frames and markers, as part of a “category pair”.
sourcepub fn add_subcategory(
&mut self,
category: CategoryHandle,
name: &str
) -> CategoryPairHandle
pub fn add_subcategory( &mut self, category: CategoryHandle, name: &str ) -> CategoryPairHandle
Add a subcategory for a category, and return the “category pair” handle.
sourcepub fn add_process(
&mut self,
name: &str,
pid: u32,
start_time: Timestamp
) -> ProcessHandle
pub fn add_process( &mut self, name: &str, pid: u32, start_time: Timestamp ) -> ProcessHandle
Add an empty process. The name, pid and start time can be changed afterwards, but they are required here because they have to be present in the profile JSON.
sourcepub fn add_counter(
&mut self,
process: ProcessHandle,
name: &str,
category: &str,
description: &str
) -> CounterHandle
pub fn add_counter( &mut self, process: ProcessHandle, name: &str, category: &str, description: &str ) -> CounterHandle
Create a counter. Counters let you make graphs with a time axis and a Y axis. One example of a counter is memory usage.
§Example
use fxprof_processed_profile::{Profile, CategoryHandle, CpuDelta, Frame, SamplingInterval, Timestamp};
use std::time::SystemTime;
let mut profile = Profile::new("My app", SystemTime::now().into(), SamplingInterval::from_millis(1));
let process = profile.add_process("App process", 54132, Timestamp::from_millis_since_reference(0.0));
let memory_counter = profile.add_counter(process, "malloc", "Memory", "Amount of allocated memory");
profile.add_counter_sample(memory_counter, Timestamp::from_millis_since_reference(0.0), 0.0, 0);
profile.add_counter_sample(memory_counter, Timestamp::from_millis_since_reference(1.0), 1000.0, 2);
profile.add_counter_sample(memory_counter, Timestamp::from_millis_since_reference(2.0), 800.0, 1);
sourcepub fn set_process_start_time(
&mut self,
process: ProcessHandle,
start_time: Timestamp
)
pub fn set_process_start_time( &mut self, process: ProcessHandle, start_time: Timestamp )
Change the start time of a process.
sourcepub fn set_process_end_time(
&mut self,
process: ProcessHandle,
end_time: Timestamp
)
pub fn set_process_end_time( &mut self, process: ProcessHandle, end_time: Timestamp )
Set the end time of a process.
sourcepub fn set_process_name(&mut self, process: ProcessHandle, name: &str)
pub fn set_process_name(&mut self, process: ProcessHandle, name: &str)
Change the name of a process.
sourcepub fn add_lib(&mut self, library: LibraryInfo) -> LibraryHandle
pub fn add_lib(&mut self, library: LibraryInfo) -> LibraryHandle
Get the LibraryHandle
for a library. This handle is used in Profile::add_lib_mapping
and in the pre-resolved Frame
variants.
Knowing the library information allows symbolication of native stacks once the profile is opened in the Firefox Profiler.
sourcepub fn set_lib_symbol_table(
&mut self,
library: LibraryHandle,
symbol_table: Arc<SymbolTable>
)
pub fn set_lib_symbol_table( &mut self, library: LibraryHandle, symbol_table: Arc<SymbolTable> )
Set the symbol table for a library.
This symbol table can also be specified in the LibraryInfo
which is given to
Profile::add_lib
. However, sometimes you may want to have the LibraryHandle
for a library before you know about all its symbols. In those cases, you can call
Profile::add_lib
with symbol_table
set to None
, and then supply the symbol
table afterwards.
Symbol tables are optional.
sourcepub fn add_lib_mapping(
&mut self,
process: ProcessHandle,
lib: LibraryHandle,
start_avma: u64,
end_avma: u64,
relative_address_at_start: u32
)
pub fn add_lib_mapping( &mut self, process: ProcessHandle, lib: LibraryHandle, start_avma: u64, end_avma: u64, relative_address_at_start: u32 )
For a given process, define where in the virtual memory of this process the given library is mapped.
Existing mappings which overlap with the range start_avma..end_avma
will be removed.
A single library can have multiple mappings in the same process.
The new mapping will be respected by future Profile::add_sample
calls, when resolving
absolute frame addresses to library-relative addresses.
sourcepub fn remove_lib_mapping(&mut self, process: ProcessHandle, start_avma: u64)
pub fn remove_lib_mapping(&mut self, process: ProcessHandle, start_avma: u64)
Mark the library mapping at the specified start address in the specified process as
unloaded, so that future calls to Profile::add_sample
know about the removal.
sourcepub fn clear_process_lib_mappings(&mut self, process: ProcessHandle)
pub fn clear_process_lib_mappings(&mut self, process: ProcessHandle)
Clear all library mappings in the specified process.
sourcepub fn add_kernel_lib_mapping(
&mut self,
lib: LibraryHandle,
start_avma: u64,
end_avma: u64,
relative_address_at_start: u32
)
pub fn add_kernel_lib_mapping( &mut self, lib: LibraryHandle, start_avma: u64, end_avma: u64, relative_address_at_start: u32 )
Add a kernel library mapping. This allows symbolication of kernel stacks once the profile is opened in the Firefox Profiler. Kernel libraries are global and not tied to a process.
Each kernel library covers an address range in the kernel address space, which is
global across all processes. Future calls to Profile::add_sample
with native
frames resolve the frame’s code address with respect to the currently loaded kernel
and process libraries.
sourcepub fn remove_kernel_lib_mapping(&mut self, start_avma: u64)
pub fn remove_kernel_lib_mapping(&mut self, start_avma: u64)
Mark the kernel library at the specified start address as
unloaded, so that future calls to Profile::add_sample
know about the unloading.
sourcepub fn add_thread(
&mut self,
process: ProcessHandle,
tid: u32,
start_time: Timestamp,
is_main: bool
) -> ThreadHandle
pub fn add_thread( &mut self, process: ProcessHandle, tid: u32, start_time: Timestamp, is_main: bool ) -> ThreadHandle
Add an empty thread to the specified process.
sourcepub fn set_thread_name(&mut self, thread: ThreadHandle, name: &str)
pub fn set_thread_name(&mut self, thread: ThreadHandle, name: &str)
Change the name of a thread.
sourcepub fn set_thread_start_time(
&mut self,
thread: ThreadHandle,
start_time: Timestamp
)
pub fn set_thread_start_time( &mut self, thread: ThreadHandle, start_time: Timestamp )
Change the start time of a thread.
sourcepub fn set_thread_end_time(&mut self, thread: ThreadHandle, end_time: Timestamp)
pub fn set_thread_end_time(&mut self, thread: ThreadHandle, end_time: Timestamp)
Set the end time of a thread.
sourcepub fn intern_string(&mut self, s: &str) -> StringHandle
pub fn intern_string(&mut self, s: &str) -> StringHandle
Turn the string into in a StringHandle
, for use in Frame::Label
.
sourcepub fn get_string(&self, handle: StringHandle) -> &str
pub fn get_string(&self, handle: StringHandle) -> &str
Get the string for a string handle. This is sometimes useful when writing tests.
Panics if the handle wasn’t found, which can happen if you pass a handle from a different Profile instance.
sourcepub fn add_sample(
&mut self,
thread: ThreadHandle,
timestamp: Timestamp,
frames: impl Iterator<Item = FrameInfo>,
cpu_delta: CpuDelta,
weight: i32
)
pub fn add_sample( &mut self, thread: ThreadHandle, timestamp: Timestamp, frames: impl Iterator<Item = FrameInfo>, cpu_delta: CpuDelta, weight: i32 )
Add a sample to the given thread.
The sample has a timestamp, a stack, a CPU delta, and a weight.
The stack frames are supplied as an iterator. Every frame has an associated category pair.
The CPU delta is the amount of CPU time that the CPU was busy with work for this thread since the previous sample. It should always be less than or equal the time delta between the sample timestamps.
The weight affects the sample’s stack’s score in the call tree. You usually set
this to 1. You can use weights greater than one if you want to combine multiple
adjacent samples with the same stack into one sample, to save space. However,
this discards any CPU deltas between the adjacent samples, so it’s only really
useful if no CPU time has occurred between the samples, and for that use case the
Profile::add_sample_same_stack_zero_cpu
method should be preferred.
You can can also set the weight to something negative, such as -1, to create a “diff profile”. For example, if you have partitioned your samples into “before” and “after” groups, you can use -1 for all “before” samples and 1 for all “after” samples, and the call tree will show you which stacks occur more frequently in the “after” part of the profile, by sorting those stacks to the top.
sourcepub fn add_sample_same_stack_zero_cpu(
&mut self,
thread: ThreadHandle,
timestamp: Timestamp,
weight: i32
)
pub fn add_sample_same_stack_zero_cpu( &mut self, thread: ThreadHandle, timestamp: Timestamp, weight: i32 )
Add a sample with a CPU delta of zero. Internally, multiple consecutive samples with a delta of zero will be combined into one sample with an accumulated weight.
sourcepub fn add_allocation_sample(
&mut self,
thread: ThreadHandle,
timestamp: Timestamp,
frames: impl Iterator<Item = FrameInfo>,
allocation_address: u64,
allocation_size: i64
)
pub fn add_allocation_sample( &mut self, thread: ThreadHandle, timestamp: Timestamp, frames: impl Iterator<Item = FrameInfo>, allocation_address: u64, allocation_size: i64 )
Add an allocation or deallocation sample to the given thread. This is used to collect stacks showing where allocations and deallocations happened.
When loading profiles with allocation samples in the Firefox Profiler, the UI will display a dropdown above the call tree to switch between regular samples and allocation samples.
An allocation sample has a timestamp, a stack, a memory address, and an allocation size.
The size should be in bytes, with positive values for allocations and negative values for deallocations.
The memory address allows correlating the allocation and deallocation stacks of the same object. This lets the UI display just the stacks for objects which haven’t been deallocated yet (“Retained memory”).
To avoid having to capture stacks for every single allocation, you can sample just a subset of allocations. The sampling should be done based on the allocation size (“probability per byte”). The decision whether to sample should be done at allocation time and remembered for the lifetime of the allocation, so that for each allocated object you either sample both its allocation and deallocation, or neither.
The stack frames are supplied as an iterator. Every frame has an associated category pair.
sourcepub fn add_marker<T: ProfilerMarker>(
&mut self,
thread: ThreadHandle,
category: CategoryHandle,
name: &str,
marker: T,
timing: MarkerTiming
)
pub fn add_marker<T: ProfilerMarker>( &mut self, thread: ThreadHandle, category: CategoryHandle, name: &str, marker: T, timing: MarkerTiming )
Add a marker to the given thread.
sourcepub fn add_marker_with_stack<T: ProfilerMarker>(
&mut self,
thread: ThreadHandle,
category: CategoryHandle,
name: &str,
marker: T,
timing: MarkerTiming,
stack_frames: impl Iterator<Item = FrameInfo>
)
pub fn add_marker_with_stack<T: ProfilerMarker>( &mut self, thread: ThreadHandle, category: CategoryHandle, name: &str, marker: T, timing: MarkerTiming, stack_frames: impl Iterator<Item = FrameInfo> )
Add a marker to the given thread, with a stack.
sourcepub fn add_counter_sample(
&mut self,
counter: CounterHandle,
timestamp: Timestamp,
value_delta: f64,
number_of_operations_delta: u32
)
pub fn add_counter_sample( &mut self, counter: CounterHandle, timestamp: Timestamp, value_delta: f64, number_of_operations_delta: u32 )
Add a data point to a counter. For a memory counter, value_delta
is the number
of bytes that have been allocated / deallocated since the previous counter sample, and
number_of_operations
is the number of malloc
/ free
calls since the previous
counter sample. Both numbers are deltas.
The graph in the profiler UI will connect subsequent data points with diagonal lines. Counters are intended for values that are measured at a time-based sample rate; for example, you could add a counter sample once every millisecond with the current memory usage.
Alternatively, you can emit a new data point only whenever the value changes. In that case you probably want to emit two values per change: one right before (with the old value) and one right at the timestamp of change (with the new value). This way you’ll get more horizontal lines, and the diagonal line will be very short.