@@ -2,12 +2,13 @@ use rustc_hash::FxHashMap;
22use std:: fs;
33use std:: io:: BufWriter ;
44use std:: path:: PathBuf ;
5- use std:: time:: Duration ;
6- use std:: time:: SystemTime ;
5+ use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
76
87use analyzeme:: { ProfilingData , Timestamp } ;
98
9+ use serde:: ser:: SerializeSeq ;
1010use serde:: { Serialize , Serializer } ;
11+ use serde_json:: json;
1112use std:: cmp;
1213use structopt:: StructOpt ;
1314
@@ -43,7 +44,11 @@ struct Event {
4344
4445#[ derive( StructOpt , Debug ) ]
4546struct Opt {
46- file_prefix : PathBuf ,
47+ #[ structopt( required_unless = "dir" ) ]
48+ file_prefix : Vec < PathBuf > ,
49+ /// all event trace files in dir will be merged to one chrome_profiler.json file
50+ #[ structopt( long = "dir" ) ]
51+ dir : Option < PathBuf > ,
4752 /// collapse threads without overlapping events
4853 #[ structopt( long = "collapse-threads" ) ]
4954 collapse_threads : bool ,
@@ -114,53 +119,98 @@ fn generate_thread_to_collapsed_thread_mapping(
114119fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
115120 let opt = Opt :: from_args ( ) ;
116121
117- let data = ProfilingData :: new ( & opt. file_prefix ) ?;
118-
119122 let chrome_file = BufWriter :: new ( fs:: File :: create ( "chrome_profiler.json" ) ?) ;
123+ let mut serializer = serde_json:: Serializer :: new ( chrome_file) ;
120124
121- //find the earlier timestamp (it should be the first event)
122- //subtract one tick so that the start of the event shows in Chrome
123- let first_event_timestamp = make_start_timestamp ( & data) ;
125+ let mut seq = serializer. serialize_seq ( None ) ?;
124126
125- let mut serializer = serde_json:: Serializer :: new ( chrome_file) ;
126- let thread_to_collapsed_thread = generate_thread_to_collapsed_thread_mapping ( & opt, & data) ;
127- let mut event_iterator = data. iter ( ) ;
128-
129- //create an iterator so we can avoid allocating a Vec with every Event for serialization
130- let json_event_iterator = std:: iter:: from_fn ( || {
131- while let Some ( event) = event_iterator. next ( ) {
132- // Chrome does not seem to like how many QueryCacheHit events we generate
133- // only handle startStop events for now
134- if let Timestamp :: Interval { start, end } = event. timestamp {
135- let duration = end. duration_since ( start) . unwrap ( ) ;
136- if let Some ( minimum_duration) = opt. minimum_duration {
137- if duration. as_micros ( ) < minimum_duration {
138- continue ;
139- }
127+ let dir_paths = file_prefixes_in_dir ( & opt) ?;
128+
129+ for file_prefix in opt. file_prefix . iter ( ) . chain ( dir_paths. iter ( ) ) {
130+ let data = ProfilingData :: new ( & file_prefix) ?;
131+
132+ let thread_to_collapsed_thread = generate_thread_to_collapsed_thread_mapping ( & opt, & data) ;
133+
134+ // Chrome does not seem to like how many QueryCacheHit events we generate
135+ // only handle Interval events for now
136+ for event in data. iter ( ) . filter ( |e| !e. timestamp . is_instant ( ) ) {
137+ let duration = event. duration ( ) . unwrap ( ) ;
138+ if let Some ( minimum_duration) = opt. minimum_duration {
139+ if duration. as_micros ( ) < minimum_duration {
140+ continue ;
140141 }
141- return Some ( Event {
142- name : event. label . clone ( ) . into_owned ( ) ,
143- category : event. event_kind . clone ( ) . into_owned ( ) ,
144- event_type : EventType :: Complete ,
145- timestamp : start. duration_since ( first_event_timestamp) . unwrap ( ) ,
146- duration,
147- process_id : 0 ,
148- thread_id : * thread_to_collapsed_thread
149- . get ( & event. thread_id )
150- . unwrap_or ( & event. thread_id ) ,
151- args : None ,
152- } ) ;
153142 }
143+ let crox_event = Event {
144+ name : event. label . clone ( ) . into_owned ( ) ,
145+ category : event. event_kind . clone ( ) . into_owned ( ) ,
146+ event_type : EventType :: Complete ,
147+ timestamp : event. timestamp . start ( ) . duration_since ( UNIX_EPOCH ) . unwrap ( ) ,
148+ duration,
149+ process_id : data. metadata . process_id ,
150+ thread_id : * thread_to_collapsed_thread
151+ . get ( & event. thread_id )
152+ . unwrap_or ( & event. thread_id ) ,
153+ args : None ,
154+ } ;
155+ seq. serialize_element ( & crox_event) ?;
154156 }
157+ // add crate name for the process_id
158+ let index_of_crate_name = data
159+ . metadata
160+ . cmd
161+ . find ( " --crate-name " )
162+ . map ( |index| index + 14 ) ;
163+ if let Some ( index) = index_of_crate_name {
164+ let ( _, last) = data. metadata . cmd . split_at ( index) ;
165+ let ( crate_name, _) = last. split_at ( last. find ( " " ) . unwrap_or ( last. len ( ) ) ) ;
166+
167+ let process_name = json ! ( {
168+ "name" : "process_name" ,
169+ "ph" : "M" ,
170+ "ts" : 0 ,
171+ "tid" : 0 ,
172+ "cat" : "" ,
173+ "pid" : data. metadata. process_id,
174+ "args" : {
175+ "name" : crate_name
176+ }
177+ } ) ;
178+ seq. serialize_element ( & process_name) ?;
179+ }
180+ // sort the processes after start time
181+ let process_name = json ! ( {
182+ "name" : "process_sort_index" ,
183+ "ph" : "M" ,
184+ "ts" : 0 ,
185+ "tid" : 0 ,
186+ "cat" : "" ,
187+ "pid" : data. metadata. process_id,
188+ "args" : {
189+ "sort_index" : data. metadata. start_time. duration_since( UNIX_EPOCH ) . unwrap( ) . as_micros( ) as u64
190+ }
191+ } ) ;
192+ seq. serialize_element ( & process_name) ?;
193+ }
155194
156- None
157- } ) ;
158-
159- serializer. collect_seq ( json_event_iterator) ?;
195+ seq. end ( ) ?;
160196
161197 Ok ( ( ) )
162198}
163199
200+ fn file_prefixes_in_dir ( opt : & Opt ) -> Result < Vec < PathBuf > , std:: io:: Error > {
201+ let mut result = Vec :: new ( ) ;
202+ if let Some ( dir_path) = & opt. dir {
203+ for entry in fs:: read_dir ( dir_path) ? {
204+ let entry = entry?;
205+ let path = entry. path ( ) ;
206+ if path. extension ( ) . filter ( |e| * e == "events" ) . is_some ( ) {
207+ result. push ( path)
208+ }
209+ }
210+ }
211+ Ok ( result)
212+ }
213+
164214fn timestamp_to_min_max ( timestamp : Timestamp ) -> ( SystemTime , SystemTime ) {
165215 match timestamp {
166216 Timestamp :: Instant ( t) => ( t, t) ,
@@ -171,22 +221,3 @@ fn timestamp_to_min_max(timestamp: Timestamp) -> (SystemTime, SystemTime) {
171221 }
172222 }
173223}
174-
175- // FIXME: Move this to `ProfilingData` and base it on the `start_time` field
176- // from metadata.
177- fn make_start_timestamp ( data : & ProfilingData ) -> SystemTime {
178- // We cannot assume the first event in the stream actually is the first
179- // event because interval events are emitted at their end. So in theory it
180- // is possible that the event with the earliest starting time is the last
181- // event in the stream (i.e. if there is an interval event that spans the
182- // entire execution of the profile).
183- //
184- // Let's be on the safe side and iterate the whole stream.
185- let min = data
186- . iter ( )
187- . map ( |e| timestamp_to_min_max ( e. timestamp ) . 0 )
188- . min ( )
189- . unwrap ( ) ;
190-
191- min - Duration :: from_micros ( 1 )
192- }
0 commit comments