@@ -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,87 +251,12 @@ class EventsProfiler {
177
251
}
178
252
179
253
profile ( restart , startDate , endDate ) {
180
- const stringTable = new StringTable ( )
181
- const locations = [ ]
182
- const functions = [ ]
183
-
184
- // A synthetic single-frame location to serve as the location for timeline
185
- // samples. We need these as the profiling backend (mimicking official pprof
186
- // tool's behavior) ignores these.
187
- const locationId = ( ( ) => {
188
- const fn = new Function ( { id : functions . length + 1 , name : stringTable . dedup ( '' ) } )
189
- functions . push ( fn )
190
- const line = new Line ( { functionId : fn . id } )
191
- const location = new Location ( { id : locations . length + 1 , line : [ line ] } )
192
- locations . push ( location )
193
- return [ location . id ]
194
- } ) ( )
195
-
196
- const decorators = { }
197
- const timestampLabelKey = stringTable . dedup ( END_TIMESTAMP_LABEL )
198
-
199
- const dateOffset = BigInt ( Math . round ( performance . timeOrigin * MS_TO_NS ) )
200
- const lateEntries = [ ]
201
- const perfEndDate = endDate . getTime ( ) - performance . timeOrigin
202
- const samples = this . entries . map ( ( item ) => {
203
- const eventType = item . entryType
204
- let decorator = decorators [ eventType ]
205
- if ( ! decorator ) {
206
- const DecoratorCtor = decoratorTypes [ eventType ]
207
- if ( DecoratorCtor ) {
208
- decorator = new DecoratorCtor ( stringTable )
209
- decorator . eventTypeLabel = labelFromStrStr ( stringTable , 'event' , eventType )
210
- decorators [ eventType ] = decorator
211
- } else {
212
- // Shouldn't happen but it's better to not rely on observer only getting
213
- // requested event types.
214
- return null
215
- }
216
- }
217
- const { startTime, duration } = item
218
- if ( startTime >= perfEndDate ) {
219
- // An event past the current recording end date; save it for the next
220
- // profile. Not supposed to happen as long as there's no async activity
221
- // between capture of the endDate value in profiler.js _collect() and
222
- // here, but better be safe than sorry.
223
- lateEntries . push ( item )
224
- return null
225
- }
226
- const endTime = startTime + duration
227
- const sampleInput = {
228
- value : [ Math . round ( duration * MS_TO_NS ) ] ,
229
- locationId,
230
- label : [
231
- decorator . eventTypeLabel ,
232
- new Label ( { key : timestampLabelKey , num : dateOffset + BigInt ( Math . round ( endTime * MS_TO_NS ) ) } )
233
- ]
234
- }
235
- decorator . decorateSample ( sampleInput , item )
236
- return new Sample ( sampleInput )
237
- } ) . filter ( v => v )
238
-
239
- this . entries = lateEntries
240
-
241
- const timeValueType = new ValueType ( {
242
- type : stringTable . dedup ( pprofValueType ) ,
243
- unit : stringTable . dedup ( pprofValueUnit )
244
- } )
245
-
246
254
if ( ! restart ) {
247
255
this . stop ( )
248
256
}
249
-
250
- return new Profile ( {
251
- sampleType : [ timeValueType ] ,
252
- timeNanos : endDate . getTime ( ) * MS_TO_NS ,
253
- periodType : timeValueType ,
254
- period : 1 ,
255
- durationNanos : ( endDate . getTime ( ) - startDate . getTime ( ) ) * MS_TO_NS ,
256
- sample : samples ,
257
- location : locations ,
258
- function : functions ,
259
- stringTable
260
- } )
257
+ const profile = this . eventSerializer . createProfile ( startDate , endDate )
258
+ this . eventSerializer = new EventSerializer ( )
259
+ return profile
261
260
}
262
261
263
262
encode ( profile ) {
0 commit comments