import { IDeduplicateTags , IKeyValuePair , IOptions , ISpan , ITrace } from './interfaces' ; import { IPluginTimes , formatTime , getTimeParams } from '@kobsio/plugin-core' ; import TreeNode from './TreeNode' ; export const getInitialOptions = ( search , isInitial ) : IOptions => { const params = new URLSearchParams ( search ) ; const limit = params . get ( 'limit' ) ; const maxDuration = params . get ( 'maxDuration' ) ; const minDuration = params . get ( 'minDuration' ) ; const operation = params . get ( 'operation' ) ; const service = params . get ( 'service' ) ; const tags = params . get ( 'tags' ) ; return { limit : limit ? limit : '20' , maxDuration : maxDuration ? maxDuration : '' , minDuration : minDuration ? minDuration : '' , operation : operation ? operation : '' , service : service ? service : '' , tags : tags ? tags : '' , times : getTimeParams ( params , isInitial ) , } ; } ; interface ITags { [ key ] : string ; } export const encodeTags = ( tags ) : string => { const t = tags . split ( ' ' ) ; const jsonTags = { } ; for ( let i = 0 ; i < t . length ; i ++ ) { const keyValue = t [ i ] . split ( '=' ) ; if ( keyValue . length === 2 ) { jsonTags [ keyValue [ 0 ] ] = keyValue [ 1 ] ; } } return encodeURIComponent ( JSON . stringify ( jsonTags ) ) ; } ; export const formatAxisBottom = ( times ) : string => { if ( times . timeEnd - times . timeStart < 3600 ) { return '%H:%M:%S' ; } else if ( times . timeEnd - times . timeStart < 86400 ) { return '%H:%M' ; } else if ( times . timeEnd - times . timeStart < 604800 ) { return '%m-%d %H:%M' ; } return '%m-%d' ; } ; export const doesTraceContainsError = ( trace ) : boolean => { for ( let i = 0 ; i < trace . spans . length ; i ++ ) { if ( doesSpanContainsError ( trace . spans [ i ] ) ) { return true ; } } return false ; } ; export const doesSpanContainsError = ( span ) : boolean => { for ( let i = 0 ; i < span . tags . length ; i ++ ) { if ( span . tags [ i ] . key === 'error' && span . tags [ i ] . value === true ) { return true ; } } return false ; } ; export const formatTraceTime = ( time ) : string => { return formatTime ( Math . floor ( time / 1000000 ) ) ; } ; const getTraceName = ( spans ) : string => { let candidateSpan ; const allIDs = new Set ( spans . map ( ( { spanID } ) => spanID ) ) ; for ( let i = 0 ; i < spans . length ; i ++ ) { const hasInternalRef = spans [ i ] . references && spans [ i ] . references . some ( ( { traceID , spanID } ) => traceID === spans [ i ] . traceID && allIDs . has ( spanID ) ) ; if ( hasInternalRef ) continue ; if ( ! candidateSpan ) { candidateSpan = spans [ i ] ; continue ; } const thisRefLength = ( spans [ i ] . references && spans [ i ] . references . length ) || 0 ; const candidateRefLength = ( candidateSpan . references && candidateSpan . references . length ) || 0 ; if ( thisRefLength < candidateRefLength || ( thisRefLength === candidateRefLength && spans [ i ] . startTime < candidateSpan . startTime ) ) { candidateSpan = spans [ i ] ; } } return candidateSpan ? `${ candidateSpan . process . serviceName }: ${ candidateSpan . operationName }` : '' ; } ; const deduplicateTags = ( spanTags ) : IDeduplicateTags => { const warningsHash = new Map < string , string > ( ) ; const tags = spanTags . reduce < IKeyValuePair [ ] > ( ( uniqueTags , tag ) => { if ( ! uniqueTags . some ( ( t ) => t . key === tag . key && t . value === tag . value ) ) { uniqueTags . push ( tag ) ; } else { warningsHash . set ( `${ tag . key }:${ tag . value }` , `Duplicate tag "${ tag . key }:${ tag . value }"` ) ; } return uniqueTags ; } , [ ] ) ; const warnings = Array . from ( warningsHash . values ( ) ) ; return { tags , warnings } ; } ; const getTraceSpanIdsAsTree = ( trace ) : TreeNode => { const nodesById = new Map ( trace . spans . map ( ( span ) => [ span . spanID , new TreeNode ( span . spanID ) ] ) ) ; const spansById = new Map ( trace . spans . map ( ( span ) => [ span . spanID , span ] ) ) ; const root = new TreeNode ( '__root__' ) ; trace . spans . forEach ( ( span ) => { const node = nodesById . get ( span . spanID ) ; if ( Array . isArray ( span . references ) && span . references . length ) { const { refType , spanID : parentID } = span . references [ 0 ] ; if ( refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM' ) { const parent = nodesById . get ( parentID ) || root ; parent . children . push ( node ) ; } else { throw new Error ( `Unrecognized ref type: ${ refType }` ) ; } } else { root . children . push ( node ) ; } } ) ; const comparator = ( nodeA , nodeB ) : number => { const a = spansById . get ( nodeA . value ) ; const b = spansById . get ( nodeB . value ) ; if ( ! a || ! b ) return - 1 ; return + ( a . startTime > b . startTime ) || + ( a . startTime === b . startTime ) - 1 ; } ; trace . spans . forEach ( ( span ) => { const node = nodesById . get ( span . spanID ) ; if ( node && node . children . length > 1 ) { node . children . sort ( comparator ) ; } } ) ; root . children . sort ( comparator ) ; return root ; } ; export const transformTraceData = ( data ) : ITrace | null => { let { traceID } = data ; if ( ! traceID ) { return null ; } traceID = traceID . toLowerCase ( ) ; let traceEndTime = 0 ; let traceStartTime = Number . MAX_SAFE_INTEGER ; const spanIdCounts = new Map ( ) ; const spanMap = new Map < string , ISpan > ( ) ; data . spans = data . spans . filter ( ( span ) => Boolean ( span . startTime ) ) ; const max = data . spans . length ; for ( let i = 0 ; i < max ; i ++ ) { const span = data . spans [ i ] as ISpan ; const { startTime , duration , processID } = span ; let spanID = span . spanID ; if ( startTime < traceStartTime ) { traceStartTime = startTime ; } if ( startTime + duration > traceEndTime ) { traceEndTime = startTime + duration ; } const idCount = spanIdCounts . get ( spanID ) ; if ( idCount != null ) { spanIdCounts . set ( spanID , idCount + 1 ) ; spanID = `${ spanID }_${ idCount }` ; span . spanID = spanID ; } else { spanIdCounts . set ( spanID , 1 ) ; } span . process = data . processes [ processID ] ; spanMap . set ( spanID , span ) ; } const tree = getTraceSpanIdsAsTree ( data ) ; const spans = [ ] ; const svcCounts = { } ; tree . walk ( ( spanID , node , depth = 0 ) => { if ( spanID === '__root__' ) { return ; } const span = spanMap . get ( spanID ) as ISpan ; if ( ! span ) { return ; } const { serviceName } = span . process ; svcCounts [ serviceName ] = ( svcCounts [ serviceName ] || 0 ) + 1 ; span . relativeStartTime = span . startTime - traceStartTime ; span . depth = depth - 1 ; span . hasChildren = node . children . length > 0 ; span . warnings = span . warnings || [ ] ; span . tags = span . tags || [ ] ; span . references = span . references || [ ] ; const tagsInfo = deduplicateTags ( span . tags ) ; span . tags = tagsInfo . tags ; span . warnings = span . warnings . concat ( tagsInfo . warnings ) ; span . references . forEach ( ( ref , index ) => { const refSpan = spanMap . get ( ref . spanID ) as ISpan ; if ( refSpan ) { ref . span = refSpan ; if ( index > 0 ) { refSpan . subsidiarilyReferencedBy = refSpan . subsidiarilyReferencedBy || [ ] ; refSpan . subsidiarilyReferencedBy . push ( { refType : ref . refType , span , spanID , traceID , } ) ; } } } ) ; spans . push ( span ) ; } ) ; const traceName = getTraceName ( spans ) ; const services = Object . keys ( svcCounts ) . map ( ( name ) => ( { name , numberOfSpans : svcCounts [ name ] } ) ) ; return { duration : traceEndTime - traceStartTime , endTime : traceEndTime , processes : data . processes , services , spans , startTime : traceStartTime , traceID , traceName , } ; } ;