license see collectProfileEvents see constructNodes fileoverview see https see https see https import { CPUProfileChunk , CPUProfileChunkNode , CPUProfileChunker , } from '../types/CPUProfile' ; import { DurationEvent } from '../types/EventInterfaces' ; import { HermesCPUProfile , HermesSample , HermesStackFrame , } from '../types/HermesProfile' ; import { EventsPhase } from '../types/Phases' ; export class CpuProfilerModel { _profile : CPUProfileChunk ; _nodesById : Map < number , CPUProfileChunkNode > ; _activeNodeArraysById : Map < number , number [ ] > ; constructor ( profile ) { this . _profile = profile ; this . _nodesById = this . _createNodeMap ( ) ; this . _activeNodeArraysById = this . _createActiveNodeArrays ( ) ; } return { Map < number , CPUProfileChunkNode } _createNodeMap ( ) { type { Map < number , CpuProfile [ 'nodes' ] [ 0 ] > } const map = new Map < number , CPUProfileChunkNode > ( ) ; for ( const node of this . _profile . nodes ) { map . set ( node . id , node ) ; } return map ; } return _createActiveNodeArrays ( ) { const map = new Map < number , number [ ] > ( ) ; param { number } id const getActiveNodes = ( id ) : number [ ] => { if ( map . has ( id ) ) return map . get ( id ) || [ ] ; const node = this . _nodesById . get ( id ) ; if ( ! node ) throw new Error ( `No such node ${ id }` ) ; if ( node . parent ) { const array = getActiveNodes ( node . parent ) . concat ( [ id ] ) ; map . set ( id , array ) ; return array ; } else { return [ id ] ; } } ; for ( const node of this . _profile . nodes ) { map . set ( node . id , getActiveNodes ( node . id ) ) ; } return map ; } /**
     * Returns all the node IDs in a stack when a specific nodeId is at the top of the stack
     * (i.e. a stack's node ID and the node ID of all of its parents).
     */ _getActiveNodeIds ( nodeId ) { const activeNodeIds = this . _activeNodeArraysById . get ( nodeId ) ; if ( ! activeNodeIds ) throw new Error ( `No such node ID ${ nodeId }` ) ; return activeNodeIds ; } param { number } timestamp param { Array < number > } previousNodeIds param { Array < number > } currentNodeIds returns { Array < DurationEvent > } _createStartEndEventsForTransition ( timestamp , previousNodeIds , currentNodeIds ) { const startNodes = currentNodeIds . filter ( id => ! previousNodeIds . includes ( id ) ) . map ( id => this . _nodesById . get ( id ) ! ) ; const endNodes = previousNodeIds . filter ( id => ! currentNodeIds . includes ( id ) ) . map ( id => this . _nodesById . get ( id ) ! ) ; param name const removeLinksIfExist = ( name ) : string => { if ( name . includes ( 'http://' ) ) { name = name . substring ( 0 , name . lastIndexOf ( '(' ) ) ; } return name || 'anonymous' ; } ; param { CPUProfileChunkNode } node return { DurationEvent } const createEvent = ( node ) : DurationEvent => ( { ts : timestamp , pid : this . _profile . pid , tid : Number ( this . _profile . tid ) , ph : EventsPhase . DURATION_EVENTS_BEGIN , name : removeLinksIfExist ( node . callFrame . name ) , cat : node . callFrame . category , args : { ... node . callFrame } } ) ; const startEvents = startNodes . map ( createEvent ) . map ( evt => ( { ... evt , ph : EventsPhase . DURATION_EVENTS_BEGIN } ) ) ; const endEvents = endNodes . map ( createEvent ) . map ( evt => ( { ... evt , ph : EventsPhase . DURATION_EVENTS_END } ) ) ; return [ ... endEvents . reverse ( ) , ... startEvents ] ; } return { DurationEvent } throws createStartEndEvents ( ) { const profile = this . _profile ; const length = profile . samples . length ; if ( profile . timeDeltas . length !== length || profile . samples . length !== length ) throw new Error ( `Invalid CPU profile length` ) ; const events = [ ] ; let timestamp = profile . startTime ; let lastActiveNodeIds = [ ] ; for ( let i = 0 ; i < profile . samples . length ; i ++ ) { const nodeId = profile . samples [ i ] ; const timeDelta = Math . max ( profile . timeDeltas [ i ] , 0 ) ; const node = this . _nodesById . get ( nodeId ) ; if ( ! node ) throw new Error ( `Missing node ${ nodeId }` ) ; timestamp += timeDelta ; const activeNodeIds = this . _getActiveNodeIds ( nodeId ) ; events . push ( ... this . _createStartEndEventsForTransition ( timestamp , lastActiveNodeIds , activeNodeIds ) ) ; lastActiveNodeIds = activeNodeIds ; } events . push ( ... this . _createStartEndEventsForTransition ( timestamp , lastActiveNodeIds , [ ] ) ) ; return events ; } param { CPUProfileChunk } profile static createStartEndEvents ( profile ) { const model = new CpuProfilerModel ( profile ) ; return model . createStartEndEvents ( ) ; } param { HermesCPUProfile } profile throws return { CPUProfileChunk } static collectProfileEvents ( profile ) { if ( profile . samples . length > 0 ) { const { samples , stackFrames } = profile ; const pid = samples [ 0 ] . pid ; const tid = samples [ 0 ] . tid ; const id = '0x1' ; const startTime = Number ( samples [ 0 ] . ts ) ; const { nodes , sampleNumbers , timeDeltas } = this . constructNodes ( samples , stackFrames ) ; return { id , pid , tid , startTime , nodes , samples : sampleNumbers , timeDeltas } ; } else { throw new Error ( 'The hermes profile has zero samples' ) ; } } param { HermesSample } samples param { < string , HermesStackFrame > } stackFrames return { CPUProfileChunker } static constructNodes ( samples , stackFrames ) { samples = samples . map ( ( sample ) => { sample . stackFrameData = stackFrames [ sample . sf ] ; return sample ; } ) ; const stackFrameIds = Object . keys ( stackFrames ) ; const profileNodes = stackFrameIds . map ( ( stackFrameId ) => { const stackFrame = stackFrames [ stackFrameId ] ; return { id : Number ( stackFrameId ) , callFrame : { ... stackFrame , url : stackFrame . name } , parent : stackFrames [ stackFrameId ] . parent } ; } ) ; const returnedSamples = [ ] ; const timeDeltas = [ ] ; let lastTimeStamp = Number ( samples [ 0 ] . ts ) ; samples . forEach ( ( sample , idx ) => { returnedSamples . push ( sample . sf ) ; if ( idx === 0 ) { timeDeltas . push ( 0 ) ; } else { const timeDiff = Number ( sample . ts ) - lastTimeStamp ; lastTimeStamp = Number ( sample . ts ) ; timeDeltas . push ( timeDiff ) ; } } ) ; return { nodes : profileNodes , sampleNumbers : returnedSamples , timeDeltas } ; } }