Pyroscope Rust SDK
repository·main·Indexed 42 days ago
https://github.com/grafana/pyroscope-rsA profiling tool for Rust applications to monitor performance and identify bottlenecks. Version 2.1.1 provides a Pyroscope Agent for collecting profiling data, support for the pprof-rs and jemalloc backends, and tools for managing stack traces, symbols, and profiling reports.
What's inside Pyroscope
- Pyroscope Profiler is a tool designed to profile Rust applications. It allows developers to collect profiling data from their Rust code to identify performance bottlenecks and understand resource usage.
Structure profiling data with Report and ReportBatch
mainProfiling data is organized into
Reports andReportBatches.Report
A
Reportcontains a collection ofStackTraces and their associated hit counts (HashMap<StackTrace, usize>), along with sharedMetadata.ReportBatch
A
ReportBatchgroups reports by a specificprofile_type(e.g.,"process_cpu"or"memory"). Thedatafield in a batch can be one of two types via theReportDataenum:Reports(Vec<Report>): Structured stack-trace reports that require pprof encoding before transmission.RawPprof(Vec<u8>): Pre-encoded pprof bytes (e.g., from a backend likejemalloc) that may already be gzipped.
Manage backend lifecycle using BackendImpl
mainThe
BackendImpl<S>struct manages the lifecycle and thread-safe access to aBackendimplementation using a state-machine pattern enforced by the Rust type system. This ensures that operations likereport()can only be called when the backend is in theBackendReadystate.Lifecycle Workflow
- Creation: Create a new instance using
BackendImpl::new(Box::new(your_backend)). This returns aBackendImpl<BackendUninitialized>. - Initialization: Call
.initialize()on the uninitialized instance. This transitions the state toBackendImpl<BackendReady>. - Usage:
- Use
.add_tag(tag)or.remove_tag(tag)on anyBackendAccessiblestate (Uninitialized or Ready). - Use
.report()only on theBackendReadystate.
- Use
- Shutdown: Call
.shutdown()on the ready instance to clean up and consume the backend.
// Example lifecycle flow let backend_box = Box::new(MyCustomBackend::new()); let uninitialized = BackendImpl::new(backend_box); // Transition to Ready let mut ready = uninitialized.initialize()?; // Perform operations ready.add_tag(my_tag)?; let report = ready.report()?; // Shutdown ready.shutdown()?;- Creation: Create a new instance using
Understand the Frames and UnresolvedFrames data structures
mainThe profiling data is structured into two main types:
UnresolvedFrames: Represents a raw sample. It contains aSmallVecof frames (up toMAX_DEPTH), thethread_id, asample_timestamp, and thethread_namestored as a fixed-size byte array.Frames: Represents a resolved sample. It contains aVec<Vec<Symbol>>where each inner vector represents the symbols associated with a specific stack frame, aStringfor thethread_name, thethread_id, and thesample_timestamp.
Manage profiling metadata with Tag and Metadata
mainMetadata in Pyroscope is used to attach searchable information to
StackTraceorReportobjects. Metadata is composed of a set ofTagobjects.- Tag: A simple key-value pair (
key=value). - Metadata: A collection of
Tags. You can retrieve a unique identifier for metadata usingget_id(), which returns a hash of the metadata content.
To add information to a report or stack trace, create a
Tagand useMetadata::add_tag().let mut metadata = Metadata::default(); metadata.add_tag(Tag::new("service".to_string(), "api-gateway".to_string())); let id = metadata.get_id();- Tag: A simple key-value pair (
Represent stack traces with StackTrace and StackFrame
mainA
StackTracerepresents a single execution path and consists of a sequence ofStackFrames.StackFrame
Each frame contains details about the code location:
module: The module name.name: The function name.filename: The filename.relative_path/absolute_path: File paths.line: The line number.
StackTrace
A
StackTraceincludes:pid: Optional Process ID.thread_id: Optional Thread ID.thread_name: Optional Thread Name.frames: A vector ofStackFrames.metadata:Metadataattached to the trace.
When creating a
StackTraceviaStackTrace::new(), the inclusion ofpid,thread_id, orthread_namein themetadatadepends on the providedBackendConfigsettings (report_pid,report_thread_id, andreport_thread_name).let frame = StackFrame::new( Some("my_module".to_string()), Some("my_function".to_string()), Some("main.rs".to_string()), None, None, Some(42), ); let stack_trace = StackTrace::new( &config, // BackendConfig Some(1234), // pid None, // thread_id Some("worker-thread".to_string()), // thread_name vec![frame], );How the PyroscopeAgent lifecycle works
mainThe
PyroscopeAgentuses a type-state pattern to ensure methods are only called when the agent is in a valid state. The lifecycle transitions are:PyroscopeAgentBare: The initial state before construction.PyroscopeAgentReady: Created after calling.build()on the builder. The agent is initialized but not yet profiling.PyroscopeAgentRunning: Created after calling.start()on aReadyagent. The agent is actively profiling and uploading data.
Transitions:
Builder$\rightarrow$.build()$\rightarrow$Ready$\rightarrow$.start()$\rightarrow$Running$\rightarrow$.stop()$\rightarrow$Ready.
Initialize and start a PyroscopeAgent
mainTo use the profiler, use the
PyroscopeAgentBuilderto construct an agent with a specific backend, then call.build()to initialize it, and.start()to begin profiling. The agent runs in a background thread and periodically uploads data based on the configuredupload_interval.use pyroscope::pyroscope::PyroscopeAgentBuilder; use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig}; fn main() -> Result<(), Box<dyn std::error::Error>> { let agent = PyroscopeAgentBuilder::new( "http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0", pprof_backend(PprofConfig::default(), BackendConfig::default()), ) .build()?; let agent_running = agent.start()?; // Agent is now profiling in the background... Ok(()) }Configure and build a Pyroscope Agent
mainUse
PyroscopeAgentBuilderto configure a new agent. You must provide the Pyroscope server URL, the application name, a sample rate, the agent name, the application version, and a backend configuration (such aspprof_backend).use pyroscope::pyroscope::PyroscopeAgentBuilder; use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig}; // Example configuration let agent = PyroscopeAgentBuilder::new( "http://localhost:4040", "myapp", 100, // sample rate "pyroscope-rs", env!("CARGO_PKG_VERSION"), pprof_backend(PprofConfig::default(), BackendConfig::default()), ) .build()?;Add the Pyroscope Library and pprof-rs backend to Cargo.toml
mainTo use the Pyroscope Rust integration with the
pprof-rsbackend, add the following dependency to yourCargo.tomlfile. Ensure you enable thebackend-pprof-rsfeature.[dependencies] pyroscope = { version = "2.0.0", features = ["backend-pprof-rs"] }Start, stop, and shutdown the Pyroscope Agent
mainOnce an agent is built, you can control the profiling lifecycle using the following methods:
- Start profiling: Call
.start()?on the agent to begin collecting data and sending it to the server. This returns a running agent handle. - Stop profiling: Call
.stop()?on the running agent handle to pause profiling. This returns a handle representing the stopped state. - Shutdown: Call
.shutdown()on the stopped agent handle to clean up resources. It is important to call this before the variable is dropped.
// Start profiling let agent_running = agent.start()?; // Stop profiling let agent_ready = agent_running.stop()?; // Shutdown the agent agent_ready.shutdown();- Start profiling: Call
Configure Backend reporting options
mainThe
BackendConfigstruct allows you to specify which metadata should be included in the profiling reports. Use these boolean flags to enable or disable specific identifiers:report_thread_id: Whether to include the thread ID.report_thread_name: Whether to include the thread name.report_pid: Whether to include the process ID (PID).
pub struct BackendConfig { pub report_thread_id: bool, pub report_thread_name: bool, pub report_pid: bool, }