@@ -15,6 +15,9 @@ use prost::Message;
1515pub use cast:: CastFrom ;
1616pub use cast:: TryCastFrom ;
1717
18+ #[ cfg( feature = "flamegraph" ) ]
19+ pub use inferno:: flamegraph:: Options as FlamegraphOptions ;
20+
1821/// Start times of the profiler.
1922#[ derive( Copy , Clone , Debug ) ]
2023pub enum ProfStartTime {
@@ -51,7 +54,7 @@ impl StringTable {
5154}
5255
5356#[ path = "perftools.profiles.rs" ]
54- mod pprof_types ;
57+ mod proto ;
5558
5659/// A single sample in the profile. The stack is a list of addresses.
5760#[ derive( Clone , Debug ) ]
@@ -104,8 +107,21 @@ impl StackProfile {
104107 period_type : ( & str , & str ) ,
105108 anno_key : Option < String > ,
106109 ) -> Vec < u8 > {
107- use crate :: pprof_types as proto;
110+ let profile = self . to_pprof_proto ( sample_type, period_type, anno_key) ;
111+ let encoded = profile. encode_to_vec ( ) ;
112+
113+ let mut gz = GzEncoder :: new ( Vec :: new ( ) , Compression :: default ( ) ) ;
114+ gz. write_all ( & encoded) . unwrap ( ) ;
115+ gz. finish ( ) . unwrap ( )
116+ }
108117
118+ /// Converts the profile into the pprof Protobuf format (see `pprof/profile.proto`).
119+ fn to_pprof_proto (
120+ & self ,
121+ sample_type : ( & str , & str ) ,
122+ period_type : ( & str , & str ) ,
123+ anno_key : Option < String > ,
124+ ) -> proto:: Profile {
109125 let mut profile = proto:: Profile :: default ( ) ;
110126 let mut strings = StringTable :: new ( ) ;
111127
@@ -192,7 +208,7 @@ impl StackProfile {
192208 let addr = u64:: cast_from ( * addr) - 1 ;
193209
194210 let loc_id = * location_ids. entry ( addr) . or_insert_with ( || {
195- // pprof_types .proto says the location id may be the address, but Polar Signals
211+ // profile .proto says the location id may be the address, but Polar Signals
196212 // insists that location ids are sequential, starting with 1.
197213 let id = u64:: cast_from ( profile. location . len ( ) ) + 1 ;
198214
@@ -275,11 +291,54 @@ impl StackProfile {
275291
276292 profile. string_table = strings. finish ( ) ;
277293
278- let encoded = profile. encode_to_vec ( ) ;
294+ profile
295+ }
279296
280- let mut gz = GzEncoder :: new ( Vec :: new ( ) , Compression :: default ( ) ) ;
281- gz. write_all ( & encoded) . unwrap ( ) ;
282- gz. finish ( ) . unwrap ( )
297+ /// Converts the profile into a flamegraph SVG, using the given options.
298+ #[ cfg( feature = "flamegraph" ) ]
299+ pub fn to_flamegraph ( & self , opts : & mut FlamegraphOptions ) -> anyhow:: Result < Vec < u8 > > {
300+ use std:: collections:: HashMap ;
301+
302+ // We start from a symbolized Protobuf profile. We just pass in empty type names, since
303+ // they're not used in the final flamegraph.
304+ let profile = self . to_pprof_proto ( ( "" , "" ) , ( "" , "" ) , None ) ;
305+
306+ // Index locations, functions, and strings.
307+ let locations: HashMap < u64 , proto:: Location > =
308+ profile. location . into_iter ( ) . map ( |l| ( l. id , l) ) . collect ( ) ;
309+ let functions: HashMap < u64 , proto:: Function > =
310+ profile. function . into_iter ( ) . map ( |f| ( f. id , f) ) . collect ( ) ;
311+ let strings = profile. string_table ;
312+
313+ // Resolve stacks as function name vectors, and sum sample values per stack. Also reverse
314+ // the stack, since inferno expects it bottom-up.
315+ let mut stacks: HashMap < Vec < & str > , i64 > = HashMap :: new ( ) ;
316+ for sample in profile. sample {
317+ let mut stack = Vec :: with_capacity ( sample. location_id . len ( ) ) ;
318+ for location in sample. location_id . into_iter ( ) . rev ( ) {
319+ let location = locations. get ( & location) . expect ( "missing location" ) ;
320+ for line in location. line . iter ( ) . rev ( ) {
321+ let function = functions. get ( & line. function_id ) . expect ( "missing function" ) ;
322+ let name = strings. get ( function. name as usize ) . expect ( "missing string" ) ;
323+ stack. push ( name. as_str ( ) ) ;
324+ }
325+ }
326+ let value = sample. value . first ( ) . expect ( "missing value" ) ;
327+ * stacks. entry ( stack) . or_default ( ) += value;
328+ }
329+
330+ // Construct stack lines for inferno.
331+ let mut lines = stacks
332+ . into_iter ( )
333+ . map ( |( stack, value) | format ! ( "{} {}" , stack. join( ";" ) , value) )
334+ . collect :: < Vec < _ > > ( ) ;
335+ lines. sort ( ) ;
336+
337+ // Generate the flamegraph SVG.
338+ let mut bytes = Vec :: new ( ) ;
339+ let lines = lines. iter ( ) . map ( |line| line. as_str ( ) ) ;
340+ inferno:: flamegraph:: from_lines ( opts, lines, & mut bytes) ?;
341+ Ok ( bytes)
283342 }
284343}
285344
0 commit comments