@@ -15,6 +15,8 @@ const MS_TO_NS = 1000000
15
15
const pprofValueType = 'timeline'
16
16
const pprofValueUnit = 'nanoseconds'
17
17
18
+ const dateOffset = BigInt ( Math . round ( performance . timeOrigin * MS_TO_NS ) )
19
+
18
20
function labelFromStr ( stringTable , key , valStr ) {
19
21
return new Label ( { key, str : stringTable . dedup ( valStr ) } )
20
22
}
@@ -146,6 +148,76 @@ if (node16) {
146
148
decoratorTypes . net = NetDecorator
147
149
}
148
150
151
+ // Translates performance entries into pprof samples.
152
+ class EventSerializer {
153
+ constructor ( ) {
154
+ this . stringTable = new StringTable ( )
155
+ this . samples = [ ]
156
+ this . locations = [ ]
157
+ this . functions = [ ]
158
+ this . decorators = { }
159
+
160
+ // A synthetic single-frame location to serve as the location for timeline
161
+ // samples. We need these as the profiling backend (mimicking official pprof
162
+ // tool's behavior) ignores these.
163
+ const fn = new Function ( { id : this . functions . length + 1 , name : this . stringTable . dedup ( '' ) } )
164
+ this . functions . push ( fn )
165
+ const line = new Line ( { functionId : fn . id } )
166
+ const location = new Location ( { id : this . locations . length + 1 , line : [ line ] } )
167
+ this . locations . push ( location )
168
+ this . locationId = [ location . id ]
169
+
170
+ this . timestampLabelKey = this . stringTable . dedup ( END_TIMESTAMP_LABEL )
171
+ }
172
+
173
+ addEvent ( item ) {
174
+ const { entryType, startTime, duration } = item
175
+ let decorator = this . decorators [ entryType ]
176
+ if ( ! decorator ) {
177
+ const DecoratorCtor = decoratorTypes [ entryType ]
178
+ if ( DecoratorCtor ) {
179
+ decorator = new DecoratorCtor ( this . stringTable )
180
+ decorator . eventTypeLabel = labelFromStrStr ( this . stringTable , 'event' , entryType )
181
+ this . decorators [ entryType ] = decorator
182
+ } else {
183
+ // Shouldn't happen but it's better to not rely on observer only getting
184
+ // requested event types.
185
+ return
186
+ }
187
+ }
188
+ const endTime = startTime + duration
189
+ const sampleInput = {
190
+ value : [ Math . round ( duration * MS_TO_NS ) ] ,
191
+ locationId : this . locationId ,
192
+ label : [
193
+ decorator . eventTypeLabel ,
194
+ new Label ( { key : this . timestampLabelKey , num : dateOffset + BigInt ( Math . round ( endTime * MS_TO_NS ) ) } )
195
+ ]
196
+ }
197
+ decorator . decorateSample ( sampleInput , item )
198
+ this . samples . push ( new Sample ( sampleInput ) )
199
+ }
200
+
201
+ createProfile ( startDate , endDate ) {
202
+ const timeValueType = new ValueType ( {
203
+ type : this . stringTable . dedup ( pprofValueType ) ,
204
+ unit : this . stringTable . dedup ( pprofValueUnit )
205
+ } )
206
+
207
+ return new Profile ( {
208
+ sampleType : [ timeValueType ] ,
209
+ timeNanos : endDate . getTime ( ) * MS_TO_NS ,
210
+ periodType : timeValueType ,
211
+ period : 1 ,
212
+ durationNanos : ( endDate . getTime ( ) - startDate . getTime ( ) ) * MS_TO_NS ,
213
+ sample : this . samples ,
214
+ location : this . locations ,
215
+ function : this . functions ,
216
+ stringTable : this . stringTable
217
+ } )
218
+ }
219
+ }
220
+
149
221
/**
150
222
* This class generates pprof files with timeline events sourced from Node.js
151
223
* performance measurement APIs.
@@ -155,15 +227,17 @@ class EventsProfiler {
155
227
this . type = 'events'
156
228
this . _flushIntervalNanos = ( options . flushInterval || 60000 ) * 1e6 // 60 sec
157
229
this . _observer = undefined
158
- this . entries = [ ]
230
+ this . eventSerializer = new EventSerializer ( )
159
231
}
160
232
161
233
start ( ) {
162
234
// if already started, do nothing
163
235
if ( this . _observer ) return
164
236
165
237
function add ( items ) {
166
- this . entries . push ( ...items . getEntries ( ) )
238
+ for ( const item of items . getEntries ( ) ) {
239
+ this . eventSerializer . addEvent ( item )
240
+ }
167
241
}
168
242
this . _observer = new PerformanceObserver ( add . bind ( this ) )
169
243
this . _observer . observe ( { entryTypes : Object . keys ( decoratorTypes ) } )
@@ -177,89 +251,12 @@ class EventsProfiler {
177
251
}
178
252
179
253
profile ( restart , startDate , endDate ) {
180
- if ( this . entries . length === 0 ) {
181
- // No events in the period; don't produce a profile
182
- return null
183
- }
184
-
185
- const stringTable = new StringTable ( )
186
- const locations = [ ]
187
- const functions = [ ]
188
-
189
- // A synthetic single-frame location to serve as the location for timeline
190
- // samples. We need these as the profiling backend (mimicking official pprof
191
- // tool's behavior) ignores these.
192
- const locationId = ( ( ) => {
193
- const fn = new Function ( { id : functions . length + 1 , name : stringTable . dedup ( '' ) } )
194
- functions . push ( fn )
195
- const line = new Line ( { functionId : fn . id } )
196
- const location = new Location ( { id : locations . length + 1 , line : [ line ] } )
197
- locations . push ( location )
198
- return [ location . id ]
199
- } ) ( )
200
-
201
- const decorators = { }
202
- for ( const [ eventType , DecoratorCtor ] of Object . entries ( decoratorTypes ) ) {
203
- const decorator = new DecoratorCtor ( stringTable )
204
- decorator . eventTypeLabel = labelFromStrStr ( stringTable , 'event' , eventType )
205
- decorators [ eventType ] = decorator
206
- }
207
- const timestampLabelKey = stringTable . dedup ( END_TIMESTAMP_LABEL )
208
-
209
- const dateOffset = BigInt ( Math . round ( performance . timeOrigin * MS_TO_NS ) )
210
- const lateEntries = [ ]
211
- const perfEndDate = endDate . getTime ( ) - performance . timeOrigin
212
- const samples = this . entries . map ( ( item ) => {
213
- const decorator = decorators [ item . entryType ]
214
- if ( ! decorator ) {
215
- // Shouldn't happen but it's better to not rely on observer only getting
216
- // requested event types.
217
- return null
218
- }
219
- const { startTime, duration } = item
220
- if ( startTime >= perfEndDate ) {
221
- // An event past the current recording end date; save it for the next
222
- // profile. Not supposed to happen as long as there's no async activity
223
- // between capture of the endDate value in profiler.js _collect() and
224
- // here, but better be safe than sorry.
225
- lateEntries . push ( item )
226
- return null
227
- }
228
- const endTime = startTime + duration
229
- const sampleInput = {
230
- value : [ Math . round ( duration * MS_TO_NS ) ] ,
231
- locationId,
232
- label : [
233
- decorator . eventTypeLabel ,
234
- new Label ( { key : timestampLabelKey , num : dateOffset + BigInt ( Math . round ( endTime * MS_TO_NS ) ) } )
235
- ]
236
- }
237
- decorator . decorateSample ( sampleInput , item )
238
- return new Sample ( sampleInput )
239
- } ) . filter ( v => v )
240
-
241
- this . entries = lateEntries
242
-
243
- const timeValueType = new ValueType ( {
244
- type : stringTable . dedup ( pprofValueType ) ,
245
- unit : stringTable . dedup ( pprofValueUnit )
246
- } )
247
-
248
254
if ( ! restart ) {
249
255
this . stop ( )
250
256
}
251
-
252
- return new Profile ( {
253
- sampleType : [ timeValueType ] ,
254
- timeNanos : endDate . getTime ( ) * MS_TO_NS ,
255
- periodType : timeValueType ,
256
- period : 1 ,
257
- durationNanos : ( endDate . getTime ( ) - startDate . getTime ( ) ) * MS_TO_NS ,
258
- sample : samples ,
259
- location : locations ,
260
- function : functions ,
261
- stringTable
262
- } )
257
+ const profile = this . eventSerializer . createProfile ( startDate , endDate )
258
+ this . eventSerializer = new EventSerializer ( )
259
+ return profile
263
260
}
264
261
265
262
encode ( profile ) {
0 commit comments