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

source

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.

source

pub fn set_interval(&mut self, interval: SamplingInterval)

Change the declared sampling interval.

source

pub fn set_reference_timestamp( &mut self, reference_timestamp: ReferenceTimestamp )

Change the reference timestamp.

source

pub fn set_product(&mut self, product: &str)

Change the product name.

source

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”.

source

pub fn add_subcategory( &mut self, category: CategoryHandle, name: &str ) -> CategoryPairHandle

Add a subcategory for a category, and return the “category pair” handle.

source

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.

source

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);
source

pub fn set_process_start_time( &mut self, process: ProcessHandle, start_time: Timestamp )

Change the start time of a process.

source

pub fn set_process_end_time( &mut self, process: ProcessHandle, end_time: Timestamp )

Set the end time of a process.

source

pub fn set_process_name(&mut self, process: ProcessHandle, name: &str)

Change the name of a process.

source

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.

source

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.

source

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.

source

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.

source

pub fn clear_process_lib_mappings(&mut self, process: ProcessHandle)

Clear all library mappings in the specified process.

source

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.

source

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.

source

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.

source

pub fn set_thread_name(&mut self, thread: ThreadHandle, name: &str)

Change the name of a thread.

source

pub fn set_thread_start_time( &mut self, thread: ThreadHandle, start_time: Timestamp )

Change the start time of a thread.

source

pub fn set_thread_end_time(&mut self, thread: ThreadHandle, end_time: Timestamp)

Set the end time of a thread.

source

pub fn intern_string(&mut self, s: &str) -> StringHandle

Turn the string into in a StringHandle, for use in Frame::Label.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

Trait Implementations§

source§

impl Debug for Profile

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Serialize for Profile

source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.