@@ -2,12 +2,13 @@ use rustc_hash::FxHashMap;
2
2
use std:: fs;
3
3
use std:: io:: BufWriter ;
4
4
use std:: path:: PathBuf ;
5
- use std:: time:: Duration ;
6
- use std:: time:: SystemTime ;
5
+ use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
7
6
8
7
use analyzeme:: { ProfilingData , Timestamp } ;
9
8
9
+ use serde:: ser:: SerializeSeq ;
10
10
use serde:: { Serialize , Serializer } ;
11
+ use serde_json:: json;
11
12
use std:: cmp;
12
13
use structopt:: StructOpt ;
13
14
@@ -43,7 +44,11 @@ struct Event {
43
44
44
45
#[ derive( StructOpt , Debug ) ]
45
46
struct 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 > ,
47
52
/// collapse threads without overlapping events
48
53
#[ structopt( long = "collapse-threads" ) ]
49
54
collapse_threads : bool ,
@@ -114,53 +119,98 @@ fn generate_thread_to_collapsed_thread_mapping(
114
119
fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
115
120
let opt = Opt :: from_args ( ) ;
116
121
117
- let data = ProfilingData :: new ( & opt. file_prefix ) ?;
118
-
119
122
let chrome_file = BufWriter :: new ( fs:: File :: create ( "chrome_profiler.json" ) ?) ;
123
+ let mut serializer = serde_json:: Serializer :: new ( chrome_file) ;
120
124
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 ) ?;
124
126
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 ;
140
141
}
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
- } ) ;
153
142
}
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) ?;
154
156
}
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
+ }
155
194
156
- None
157
- } ) ;
158
-
159
- serializer. collect_seq ( json_event_iterator) ?;
195
+ seq. end ( ) ?;
160
196
161
197
Ok ( ( ) )
162
198
}
163
199
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
+
164
214
fn timestamp_to_min_max ( timestamp : Timestamp ) -> ( SystemTime , SystemTime ) {
165
215
match timestamp {
166
216
Timestamp :: Instant ( t) => ( t, t) ,
@@ -171,22 +221,3 @@ fn timestamp_to_min_max(timestamp: Timestamp) -> (SystemTime, SystemTime) {
171
221
}
172
222
}
173
223
}
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