import { HighResTimer , calculateElapsedTime } from './utils' ; import { EventEmitter } from 'events' ; export type TimingsReport = { [ stepName ] : [ number ] | number ; } ; export const stepStartedEventName = 'stepStarted' ; export const milestoneReachedEventName = 'milestoneReached' ; export const stepCompletedEventName = 'stepCompleted' ; export const requestCompletedEventName = 'requestCompleted' ; export interface IStepStartedEventArguments { stepName : string ; } export interface IMilestoneReachedEventArguments { milestoneName : string ; } export interface IStepCompletedEventArguments { stepName : string ; } export interface IRequestCompletedEventArguments { requestName : string ; startTime : number ; timeTakenInMilliseconds : number ; } export interface IObservableEvents < T > { events : T ; } export interface IStepStartedEventsEmitter { on ( event , listener ) : this ; on ( event , listener ) : this ; removeListener ( event , listener ) : this ; removeListener ( event , listener ) : this ; } export interface FinishedStartingUpEventArguments { requestedContentWasDetected : boolean ; reasonForNotDetected : string ; } export interface IFinishedStartingUpEventsEmitter { on ( event , listener ) : this ; once ( event , listener ) : this ; removeListener ( event , listener ) : this ; removeListener ( event , listener ) : this ; } export class StepProgressEventsEmitter extends EventEmitter implements IStepStartedEventsEmitter , IFinishedStartingUpEventsEmitter { constructor ( private readonly _nestedEmitters = [ ] as IStepStartedEventsEmitter [ ] ) { super ( ) ; } public emitStepStarted ( stepName ) { this . emit ( stepStartedEventName , { stepName : stepName } as IStepStartedEventArguments ) ; } public emitMilestoneReached ( milestoneName ) { this . emit ( milestoneReachedEventName , { milestoneName : milestoneName } as IMilestoneReachedEventArguments ) ; } public emitStepCompleted ( stepName ) { this . emit ( stepCompletedEventName , { stepName : stepName } as IStepCompletedEventArguments ) ; } public emitRequestCompleted ( requestName , requestStartTime , timeTakenByRequestInMilliseconds ) { this . emit ( requestCompletedEventName , { requestName : requestName , startTime : requestStartTime , timeTakenInMilliseconds : timeTakenByRequestInMilliseconds } as IRequestCompletedEventArguments ) ; } public on ( event , listener ) { super . on ( event , listener ) ; this . _nestedEmitters . forEach ( nestedEmitter => nestedEmitter . on ( event as any , listener as any ) ) ; return this ; } public removeListener ( event , listener ) { super . removeListener ( event , listener ) ; this . _nestedEmitters . forEach ( nestedEmitter => nestedEmitter . removeListener ( event as any , listener as any ) ) ; return this ; } } class SubscriptionManager { private _removeSubscriptionActions = [ ] as ( ( ) => void ) [ ] ; public on ( eventEmitter , event , listener ) { eventEmitter . on ( event , listener ) ; this . _removeSubscriptionActions . push ( ( ) => eventEmitter . removeListener ( event , listener ) ) ; } public removeAll ( ) { for ( const removeSubscriptionAction of this . _removeSubscriptionActions ) { removeSubscriptionAction ( ) ; } this . _removeSubscriptionActions = [ ] as ( ( ) => void ) [ ] ; } } export interface IAllRequestProperties { [ propertyName ] : number [ ] ; } export class ExecutionTimingsReporter { private readonly _allStartTime : HighResTimer ; private readonly _eventsExecutionTimesInMilliseconds : { [ stepName ] : [ number ] ; } = { } ; private readonly _stepsList = [ ] as string [ ] ; private readonly _subscriptionManager = new SubscriptionManager ( ) ; private readonly _requestProperties = { } as IAllRequestProperties ; private _currentStepStartTime : HighResTimer ; private _currentStepName = 'BeforeFirstStep' ; constructor ( ) { this . _currentStepStartTime = this . _allStartTime = process . hrtime ( ) ; } private recordPreviousStepAndConfigureNewStep ( newStepName ) { this . recordTimeTaken ( this . _currentStepName , this . _currentStepStartTime ) ; this . _stepsList . push ( this . _currentStepName ) ; this . _currentStepStartTime = process . hrtime ( ) ; this . _currentStepName = newStepName ; } private recordTimeTaken ( eventName , sinceWhen ) { const timeTakenInMilliseconds = calculateElapsedTime ( sinceWhen ) ; this . addElementToArrayProperty ( this . _eventsExecutionTimesInMilliseconds , eventName , timeTakenInMilliseconds ) ; } private recordTotalTimeUntilMilestone ( milestoneName ) { this . recordTimeTaken ( milestoneName , this . _allStartTime ) ; } public generateReport ( ) { this . recordPreviousStepAndConfigureNewStep ( 'AfterLastStep' ) ; this . _subscriptionManager . removeAll ( ) ; return Object . assign ( { } , { Steps : this . _stepsList , All : calculateElapsedTime ( this . _allStartTime ) } , this . _requestProperties , this . _eventsExecutionTimesInMilliseconds ) ; } public recordRequestCompleted ( requestName , startTime , timeTakenInMilliseconds ) { const propertyPrefix = `Request.${ requestName }.` ; this . addElementToArrayProperty ( this . _requestProperties , propertyPrefix + 'startTime' , startTime ) ; this . addElementToArrayProperty ( this . _requestProperties , propertyPrefix + 'timeTakenInMilliseconds' , timeTakenInMilliseconds ) ; } private addElementToArrayProperty < T > ( object , propertyName , elementToAdd ) { const propertiesArray = object [ propertyName ] = object [ propertyName ] || [ ] as T [ ] ; propertiesArray . push ( elementToAdd ) ; } public subscribeTo ( eventEmitter ) { this . _subscriptionManager . on ( eventEmitter , stepStartedEventName , ( args ) => { this . recordPreviousStepAndConfigureNewStep ( args . stepName ) ; } ) ; this . _subscriptionManager . on ( eventEmitter , milestoneReachedEventName , ( args ) => { this . recordTotalTimeUntilMilestone ( args . milestoneName ) ; } ) ; this . _subscriptionManager . on ( eventEmitter , stepCompletedEventName , ( args ) => { this . recordTotalTimeUntilMilestone ( `WaitingAfter.${ args . stepName }` ) ; } ) ; this . _subscriptionManager . on ( eventEmitter , requestCompletedEventName , ( args ) => { this . recordRequestCompleted ( args . requestName , args . startTime , args . timeTakenInMilliseconds ) ; } ) ; } }