@@ -104,7 +104,108 @@ function expectTimeout (messagePromise, allowErrors = false) {
104104 )
105105}
106106
107+ class TimelineEventProcessor {
108+ constructor ( strings , encoded ) {
109+ this . strings = strings
110+ this . encoded = encoded
111+ }
112+ }
113+
114+ class NetworkEventProcessor extends TimelineEventProcessor {
115+ constructor ( strings , encoded ) {
116+ super ( strings , encoded )
117+
118+ this . hostKey = strings . dedup ( 'host' )
119+ this . addressKey = strings . dedup ( 'address' )
120+ this . portKey = strings . dedup ( 'port' )
121+ }
122+
123+ processLabel ( label , processedLabels ) {
124+ switch ( label . key ) {
125+ case this . hostKey :
126+ processedLabels . host = label . str
127+ return true
128+ case this . addressKey :
129+ processedLabels . address = label . str
130+ return true
131+ case this . portKey :
132+ processedLabels . port = label . num
133+ return true
134+ default :
135+ return false
136+ }
137+ }
138+
139+ decorateEvent ( ev , pl ) {
140+ // Exactly one of these is defined
141+ assert . isTrue ( ! ! pl . address !== ! ! pl . host , this . encoded )
142+ if ( pl . address ) {
143+ ev . address = this . strings . strings [ pl . address ]
144+ } else {
145+ ev . host = this . strings . strings [ pl . host ]
146+ }
147+ if ( pl . port ) {
148+ ev . port = pl . port
149+ }
150+ }
151+ }
152+
107153async function gatherNetworkTimelineEvents ( cwd , scriptFilePath , eventType , args ) {
154+ return gatherTimelineEvents ( cwd , scriptFilePath , eventType , args , NetworkEventProcessor )
155+ }
156+
157+ class FilesystemEventProcessor extends TimelineEventProcessor {
158+ constructor ( strings , encoded ) {
159+ super ( strings , encoded )
160+
161+ this . fdKey = strings . dedup ( 'fd' )
162+ this . fileKey = strings . dedup ( 'file' )
163+ this . flagKey = strings . dedup ( 'flag' )
164+ this . modeKey = strings . dedup ( 'mode' )
165+ this . pathKey = strings . dedup ( 'path' )
166+ }
167+
168+ processLabel ( label , processedLabels ) {
169+ switch ( label . key ) {
170+ case this . fdKey :
171+ processedLabels . fd = label . num
172+ return true
173+ case this . fileKey :
174+ processedLabels . file = label . str
175+ return true
176+ case this . flagKey :
177+ processedLabels . flag = label . str
178+ return true
179+ case this . modeKey :
180+ processedLabels . mode = label . str
181+ return true
182+ case this . pathKey :
183+ processedLabels . path = label . str
184+ return true
185+ default :
186+ return false
187+ }
188+ }
189+
190+ decorateEvent ( ev , pl ) {
191+ ev . fd = pl . fd
192+ ev . file = this . strings . strings [ pl . file ]
193+ ev . flag = this . strings . strings [ pl . flag ]
194+ ev . mode = this . strings . strings [ pl . mode ]
195+ ev . path = this . strings . strings [ pl . path ]
196+ for ( const [ k , v ] of Object . entries ( ev ) ) {
197+ if ( v === undefined ) {
198+ delete ev [ k ]
199+ }
200+ }
201+ }
202+ }
203+
204+ async function gatherFilesystemTimelineEvents ( cwd , scriptFilePath ) {
205+ return gatherTimelineEvents ( cwd , scriptFilePath , 'fs' , [ ] , FilesystemEventProcessor )
206+ }
207+
208+ async function gatherTimelineEvents ( cwd , scriptFilePath , eventType , args , Processor ) {
108209 const procStart = BigInt ( Date . now ( ) * 1000000 )
109210 const proc = fork ( path . join ( cwd , scriptFilePath ) , args , {
110211 cwd,
@@ -123,60 +224,50 @@ async function gatherNetworkTimelineEvents (cwd, scriptFilePath, eventType, args
123224 const strings = profile . stringTable
124225 const tsKey = strings . dedup ( 'end_timestamp_ns' )
125226 const eventKey = strings . dedup ( 'event' )
126- const hostKey = strings . dedup ( 'host' )
127- const addressKey = strings . dedup ( 'address' )
128- const portKey = strings . dedup ( 'port' )
129- const nameKey = strings . dedup ( 'operation' )
227+ const operationKey = strings . dedup ( 'operation' )
130228 const spanIdKey = strings . dedup ( 'span id' )
131229 const localRootSpanIdKey = strings . dedup ( 'local root span id' )
132230 const eventValue = strings . dedup ( eventType )
133231 const events = [ ]
232+ const processor = new Processor ( strings , encoded )
134233 for ( const sample of profile . sample ) {
135- let ts , event , host , address , port , name , spanId , localRootSpanId
234+ let ts , event , operation , spanId , localRootSpanId
235+ const processedLabels = { }
136236 const unexpectedLabels = [ ]
137237 for ( const label of sample . label ) {
138238 switch ( label . key ) {
139239 case tsKey : ts = label . num ; break
140- case nameKey : name = label . str ; break
240+ case operationKey : operation = label . str ; break
141241 case eventKey : event = label . str ; break
142- case hostKey : host = label . str ; break
143- case addressKey : address = label . str ; break
144- case portKey : port = label . num ; break
145242 case spanIdKey : spanId = label . str ; break
146243 case localRootSpanIdKey : localRootSpanId = label . str ; break
147- default : unexpectedLabels . push ( label . key )
244+ default :
245+ if ( ! processor . processLabel ( label , processedLabels ) ) {
246+ unexpectedLabels . push ( label . key )
247+ }
148248 }
149249 }
150- // Gather only DNS events; ignore sporadic GC events
250+ // Timestamp must be defined and be between process start and end time
251+ assert . isDefined ( ts , encoded )
252+ assert . isTrue ( ts <= procEnd , encoded )
253+ assert . isTrue ( ts >= procStart , encoded )
254+ // Gather only tested events
151255 if ( event === eventValue ) {
152- // Timestamp must be defined and be between process start and end time
153- assert . isDefined ( ts , encoded )
154- assert . isTrue ( ts <= procEnd , encoded )
155- assert . isTrue ( ts >= procStart , encoded )
156256 if ( process . platform !== 'win32' ) {
157257 assert . isDefined ( spanId , encoded )
158258 assert . isDefined ( localRootSpanId , encoded )
159259 } else {
160260 assert . isUndefined ( spanId , encoded )
161261 assert . isUndefined ( localRootSpanId , encoded )
162262 }
163- assert . isDefined ( name , encoded )
263+ assert . isDefined ( operation , encoded )
164264 if ( unexpectedLabels . length > 0 ) {
165265 const labelsStr = JSON . stringify ( unexpectedLabels )
166266 const labelsStrStr = unexpectedLabels . map ( k => strings . strings [ k ] ) . join ( ',' )
167267 assert . fail ( `Unexpected labels: ${ labelsStr } \n${ labelsStrStr } \n${ encoded } ` )
168268 }
169- // Exactly one of these is defined
170- assert . isTrue ( ! ! address !== ! ! host , encoded )
171- const ev = { name : strings . strings [ name ] }
172- if ( address ) {
173- ev . address = strings . strings [ address ]
174- } else {
175- ev . host = strings . strings [ host ]
176- }
177- if ( port ) {
178- ev . port = port
179- }
269+ const ev = { operation : strings . strings [ operation ] }
270+ processor . decorateEvent ( ev , processedLabels )
180271 events . push ( ev )
181272 }
182273 }
@@ -323,14 +414,30 @@ describe('profiler', () => {
323414 assert . equal ( endpoints . size , 3 , encoded )
324415 } )
325416
417+ it ( 'fs timeline events work' , async ( ) => {
418+ const fsEvents = await gatherFilesystemTimelineEvents ( cwd , 'profiler/fstest.js' )
419+ assert . equal ( fsEvents . length , 6 )
420+ const path = fsEvents [ 0 ] . path
421+ const fd = fsEvents [ 1 ] . fd
422+ assert ( path . endsWith ( 'tempfile.txt' ) )
423+ assert . sameDeepMembers ( fsEvents , [
424+ { flag : 'w' , mode : '' , operation : 'open' , path } ,
425+ { fd, operation : 'write' } ,
426+ { fd, operation : 'close' } ,
427+ { file : path , operation : 'writeFile' } ,
428+ { operation : 'readFile' , path } ,
429+ { operation : 'unlink' , path }
430+ ] )
431+ } )
432+
326433 it ( 'dns timeline events work' , async ( ) => {
327434 const dnsEvents = await gatherNetworkTimelineEvents ( cwd , 'profiler/dnstest.js' , 'dns' )
328435 assert . sameDeepMembers ( dnsEvents , [
329- { name : 'lookup' , host : 'example.org' } ,
330- { name : 'lookup' , host : 'example.com' } ,
331- { name : 'lookup' , host : 'datadoghq.com' } ,
332- { name : 'queryA' , host : 'datadoghq.com' } ,
333- { name : 'lookupService' , address : '13.224.103.60' , port : 80 }
436+ { operation : 'lookup' , host : 'example.org' } ,
437+ { operation : 'lookup' , host : 'example.com' } ,
438+ { operation : 'lookup' , host : 'datadoghq.com' } ,
439+ { operation : 'queryA' , host : 'datadoghq.com' } ,
440+ { operation : 'lookupService' , address : '13.224.103.60' , port : 80 }
334441 ] )
335442 } )
336443
@@ -366,8 +473,8 @@ describe('profiler', () => {
366473 // The profiled program should have two TCP connection events to the two
367474 // servers.
368475 assert . sameDeepMembers ( events , [
369- { name : 'connect' , host : '127.0.0.1' , port : port1 } ,
370- { name : 'connect' , host : '127.0.0.1' , port : port2 }
476+ { operation : 'connect' , host : '127.0.0.1' , port : port1 } ,
477+ { operation : 'connect' , host : '127.0.0.1' , port : port2 }
371478 ] )
372479 } finally {
373480 server2 . close ( )
0 commit comments