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_frames = 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() }
];
let stack = profile.intern_stack_frames(thread, stack_frames.into_iter());
profile.add_sample(thread, Timestamp::from_millis_since_reference(0.0), stack, 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 set_os_name(&mut self, os_name: &str)
pub fn set_os_name(&mut self, os_name: &str)
Set the name of the operating system.
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.
Every category has a default subcategory; you can convert a Category
into
its corresponding CategoryPairHandle
for the default category using category.into()
.
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_counter_color(&mut self, counter: CounterHandle, color: GraphColor)
pub fn set_counter_color(&mut self, counter: CounterHandle, color: GraphColor)
Set the color to use when rendering the counter.
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 set_thread_tid(&mut self, thread: ThreadHandle, tid: u32)
pub fn set_thread_tid(&mut self, thread: ThreadHandle, tid: u32)
Set the tid (thread ID) of a thread.
Sourcepub fn set_thread_show_markers_in_timeline(
&mut self,
thread: ThreadHandle,
v: bool,
)
pub fn set_thread_show_markers_in_timeline( &mut self, thread: ThreadHandle, v: bool, )
Set whether to show a timeline which displays MarkerLocations::TIMELINE_OVERVIEW
markers for this thread.
Main threads always have such a timeline view and always display such markers, but non-main threads only do so when specified using this method.
Sourcepub fn set_thread_samples_weight_type(
&mut self,
thread: ThreadHandle,
t: WeightType,
)
pub fn set_thread_samples_weight_type( &mut self, thread: ThreadHandle, t: WeightType, )
Set the weighting type of samples of a thread.
Default is WeightType::Samples.
Sourcepub fn add_initial_visible_thread(&mut self, thread: ThreadHandle)
pub fn add_initial_visible_thread(&mut self, thread: ThreadHandle)
Add a thread as initially visible in the UI.
If not called, the UI uses its own ranking heuristic to choose which threads are visible.
Sourcepub fn clear_initial_visible_threads(&mut self)
pub fn clear_initial_visible_threads(&mut self)
Clear the list of threads marked as initially visible in the UI.
Sourcepub fn add_initial_selected_thread(&mut self, thread: ThreadHandle)
pub fn add_initial_selected_thread(&mut self, thread: ThreadHandle)
Add a thread as initially selected in the UI.
If not called, the UI uses its own heuristic to choose which threads are initially selected.
Sourcepub fn clear_initial_selected_threads(&mut self)
pub fn clear_initial_selected_threads(&mut self)
Clear the list of threads marked as initially selected in the UI.
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 intern_frame(
&mut self,
thread: ThreadHandle,
frame_info: FrameInfo,
) -> FrameHandle
pub fn intern_frame( &mut self, thread: ThreadHandle, frame_info: FrameInfo, ) -> FrameHandle
Get the frame handle for a stack frame.
The returned handle can only be used with this thread.
Sourcepub fn intern_stack(
&mut self,
thread: ThreadHandle,
parent: Option<StackHandle>,
frame: FrameHandle,
) -> StackHandle
pub fn intern_stack( &mut self, thread: ThreadHandle, parent: Option<StackHandle>, frame: FrameHandle, ) -> StackHandle
Get the stack handle for a stack with the given frame
and parent
,
for the given thread.
The returned stack handle can be used with Profile::add_sample
and
Profile::set_marker_stack
, but only for samples / markers of the same
thread.
If parent
is None
, this creates a root stack node. Otherwise, parent
is the caller of the returned stack node.
Sourcepub fn intern_stack_frames(
&mut self,
thread: ThreadHandle,
frames: impl Iterator<Item = FrameInfo>,
) -> Option<StackHandle>
pub fn intern_stack_frames( &mut self, thread: ThreadHandle, frames: impl Iterator<Item = FrameInfo>, ) -> Option<StackHandle>
Get the stack handle for a stack whose frames are given by an iterator.
The stack frames yielded by the iterator need to be ordered from caller-most to callee-most.
Returns None
if the stack has zero frames.
Sourcepub fn add_sample(
&mut self,
thread: ThreadHandle,
timestamp: Timestamp,
stack: Option<StackHandle>,
cpu_delta: CpuDelta,
weight: i32,
)
pub fn add_sample( &mut self, thread: ThreadHandle, timestamp: Timestamp, stack: Option<StackHandle>, 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.
To get the stack handle, you can use Profile::intern_stack
or
Profile::intern_stack_frames
.
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,
stack: Option<StackHandle>,
allocation_address: u64,
allocation_size: i64,
)
pub fn add_allocation_sample( &mut self, thread: ThreadHandle, timestamp: Timestamp, stack: Option<StackHandle>, 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.
To get the stack handle, you can use Profile::intern_stack
or
Profile::intern_stack_frames
.
Sourcepub fn register_marker_type(
&mut self,
schema: RuntimeSchemaMarkerSchema,
) -> MarkerTypeHandle
pub fn register_marker_type( &mut self, schema: RuntimeSchemaMarkerSchema, ) -> MarkerTypeHandle
Registers a marker type for a RuntimeSchemaMarkerSchema
. You only need to call this for
marker types whose schema is dynamically created at runtime.
After you register the marker type, you’ll save its MarkerTypeHandle
somewhere, and then
store it in every marker you create of this type. The marker then needs to return the
handle from its implementation of Marker::marker_type
.
For marker types whose schema is known at compile time, you’ll want to implement
StaticSchemaMarker
instead, and you don’t need to call this method.
Sourcepub fn static_schema_marker_type<T: StaticSchemaMarker>(
&mut self,
) -> MarkerTypeHandle
pub fn static_schema_marker_type<T: StaticSchemaMarker>( &mut self, ) -> MarkerTypeHandle
Returns the marker type handle for a type that implements StaticSchemaMarker
.
You usually don’t need to call this, ever. It is called by the blanket impl
of Marker::marker_type
for all types which implement StaticSchemaMarker
.
Sourcepub fn add_marker<T: Marker>(
&mut self,
thread: ThreadHandle,
timing: MarkerTiming,
marker: T,
) -> MarkerHandle
pub fn add_marker<T: Marker>( &mut self, thread: ThreadHandle, timing: MarkerTiming, marker: T, ) -> MarkerHandle
Add a marker to the given thread.
The marker handle that’s returned by this method can be used in Profile::set_marker_stack
.
use fxprof_processed_profile::{
Profile, CategoryHandle, Marker, MarkerFieldFlags, MarkerFieldFormat, MarkerTiming,
StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, ThreadHandle, Timestamp,
};
let name = profile.intern_string("Marker name");
let text = profile.intern_string("Marker text");
let my_marker = TextMarker { name, text };
profile.add_marker(thread, MarkerTiming::Interval(start_time, end_time), my_marker);
#[derive(Debug, Clone)]
pub struct TextMarker {
pub name: StringHandle,
pub text: StringHandle,
}
impl StaticSchemaMarker for TextMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "Text";
const CHART_LABEL: Option<&'static str> = Some("{marker.data.text}");
const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.text}");
const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField {
key: "text",
label: "Contents",
format: MarkerFieldFormat::String,
flags: MarkerFieldFlags::SEARCHABLE,
}];
fn name(&self, _profile: &mut Profile) -> StringHandle {
self.name
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
CategoryHandle::OTHER
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
self.text
}
fn number_field_value(&self, _field_index: u32) -> f64 {
unreachable!()
}
}
Sourcepub fn set_marker_stack(
&mut self,
thread: ThreadHandle,
marker: MarkerHandle,
stack: Option<StackHandle>,
)
pub fn set_marker_stack( &mut self, thread: ThreadHandle, marker: MarkerHandle, stack: Option<StackHandle>, )
Sets a marker’s stack. Every marker can have an optional stack, regardless of its marker type.
A marker’s stack is shown in its tooltip, and in the sidebar in the marker table panel if a marker with a stack is selected.
To get the stack handle, you can use Profile::intern_stack
or
Profile::intern_stack_frames
.
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.
Sourcepub fn set_symbolicated(&mut self, v: bool)
pub fn set_symbolicated(&mut self, v: bool)
Set whether the profile is already symbolicated.
Read: whether symbols are resolved.
If your samples refer to labels instead of addresses, it is safe to set to true.
Setting to true prevents the Firefox Profiler from attempting to resolve symbols.
By default, this is set to false. This causes the Firefox Profiler
to look up symbols for any address-based Frame
, i.e. any frame
which is not a Frame::Label
.
If you use address-based frames and supply your own symbols using
Profile::add_lib
or Profile::set_lib_symbol_table
, you can
choose to set this to true and avoid another symbol lookup, or you
can leave it set to false if there is a way to obtain richer symbol
information than the information supplied in those symbol tables.
For example, when samply creates a profile which includes JIT frames,
and there is a Jitdump file with symbol information about those JIT
frames, samply uses Profile::set_lib_symbol_table
to provide
the function names for the JIT functions. But it does not call
Profile::set_symbolicated
with true, because the Jitdump files may
include additional information that’s not in the SymbolTable
,
specifically the Jitdump file may have file name and line number information.
This information is only added into the profile by the Firefox Profiler’s
resolution of symbols: The Firefox Profiler requests symbol information
for the JIT frame addresses from samply’s symbol server, at which point
samply obtains the richer information from the Jitdump file and returns
it via the symbol server response.