import { compose , forEach , keys , reduce , toPairs } from 'ramda' ; import { IOClients } from '../clients/IOClients' ; import { MetricsAccumulator } from '../metrics/MetricsAccumulator' ; import { LogLevel , logOnceToDevConsole } from '../service/logger' ; import { EventContext , EventHandler , ParamsContext , RecorderState , RouteHandler , ServiceContext , } from '../service/worker/runtime/typings' ; export const hrToMillis = ( [ seconds , nanoseconds ] ) => Math . round ( ( seconds * 1e3 ) + ( nanoseconds / 1e6 ) ) ; export const hrToMillisFloat = ( [ seconds , nanoseconds ] ) => { return ( seconds * 1e3 ) + ( nanoseconds / 1e6 ) ; } ; export const nanosecondsToSeconds = ( nanoseconds ) => { return nanoseconds / 1e9 ; } ; export const hrToNano = ( [ seconds , nanoseconds ] ) => seconds * 1e9 + nanoseconds ; export const formatNano = ( nanoseconds ) : string => `${ ( nanoseconds / 1e9 ) . toFixed ( 0 ) }s ${ ( ( nanoseconds / 1e6 ) % 1e3 ) . toFixed ( 0 ) }ms` ; export const reduceHrToNano = reduce ( ( acc , hr ) => acc + hrToNano ( hr ) , 0 as number ) ; export const shrinkTimings = ( name ) => name . replace ( /graphql/g , 'gql' ) . replace ( /server/g , 'srv' ) ; type TimingFormat = ReturnType < typeof parseTimingName > ; export const formatTimingName = ( { hopNumber , target , source } ) => `${ Number . isNaN ( hopNumber as any ) ? '' : hopNumber }.${ source || '' }#${ target || '' }` ; export const parseTimingName = ( timing ) => { const [ hopNumber , sourceAndTarget ] = timing ? timing . split ( '.' ) : [ null , null ] ; const [ source , target ] = sourceAndTarget ? sourceAndTarget . split ( '#' ) : [ null , null ] ; return { hopNumber : Number . isNaN ( hopNumber as any ) ? null : Number ( hopNumber ) , source , target , } ; } ; export const reduceTimings = ( timingsObj ) => compose < Record < string , string > , Array < [ string , string ] > , string > ( reduce ( ( acc , [ key , dur ] ) => `${ key };dur=${ dur }, ${ acc }` , '' ) , toPairs ) ( timingsObj ) ; function recordTimings ( start , name , timings , middlewareMetrics , success ) { const end = process . hrtime ( start ) ; const elapsed = [ end [ 0 ] - timings . total [ 0 ] , end [ 1 ] - timings . total [ 1 ] , ] ; if ( success ) { timings [ name ] = elapsed ; } timings . total = end ; const label = `middleware-${ name }` ; metrics . batch ( label , success ? elapsed : undefined , { success : success ? 1 : 0 } ) ; const middlewareMetricsKeys = keys ( middlewareMetrics ) as string [ ] ; if ( success && middlewareMetricsKeys . length > 0 ) { forEach ( ( k ) => { const metricEnd = middlewareMetrics [ k ] ; delete middlewareMetrics [ k ] ; const metricName = `${ label }-${ k }` ; timings [ metricName ] = metricEnd ; metrics . batch ( metricName , metricEnd ) ; } , middlewareMetricsKeys ) ; } } export function timer < T extends IOClients , U extends RecorderState , V extends ParamsContext > ( middleware ) { if ( ( middleware as any ) . skipTimer ) { return middleware ; } if ( ! middleware . name ) { logOnceToDevConsole ( `Please use a named function as handler for better metrics. ${ middleware . toString ( ) }` , LogLevel . Warn ) ; } return async ( ctx , next ) => { if ( ! ctx . serverTiming ) { ctx . serverTiming = { } ; } if ( ! ctx . timings ) { ctx . timings = { total : [ 0 , 0 ] , } ; } if ( ! ctx . metrics ) { ctx . metrics = { } ; } const start = process . hrtime ( ) ; try { await middleware ( ctx , next ) ; recordTimings ( start , middleware . name , ctx . timings , ctx . metrics , true ) ; } catch ( e ) { recordTimings ( start , middleware . name , ctx . timings , ctx . metrics , false ) ; throw e ; } } ; } export function timerForEvents < T extends IOClients , U > ( middleware ) { if ( ( middleware as any ) . skipTimer ) { return middleware ; } if ( ! middleware . name ) { logOnceToDevConsole ( `Please use a named function as handler for better metrics. ${ middleware . toString ( ) }` , LogLevel . Warn ) ; } return async ( ctx , next ) => { if ( ! ctx . timings ) { ctx . timings = { total : [ 0 , 0 ] , } ; } if ( ! ctx . metrics ) { ctx . metrics = { } ; } const start = process . hrtime ( ) ; try { await middleware ( ctx , next ) ; recordTimings ( start , middleware . name , ctx . timings , ctx . metrics , true ) ; } catch ( e ) { recordTimings ( start , middleware . name , ctx . timings , ctx . metrics , false ) ; throw e ; } } ; } declare global { const metrics ; namespace NodeJS { interface Global { metrics : MetricsAccumulator ; } } }