import { i18n } from '@kbn/i18n' ; import tinycolor from 'tinycolor2' ; import _ from 'lodash' ; import { BreakdownItem , Index , Operation , Shard , Targets } from '../../types' ; import { IndexMap } from './types' ; import { MAX_TREE_DEPTH } from './constants' ; export const comparator = ( v1 , v2 ) => { if ( v1 < v2 ) { return 1 ; } return v1 > v2 ? - 1 : 0 ; } ; function getToolTip ( key ) { switch ( key ) { case 'build_scorer' : return i18n . translate ( 'xpack.searchProfiler.buildScorerTimeDescription' , { defaultMessage : 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.' , } ) ; case 'create_weight' : return i18n . translate ( 'xpack.searchProfiler.createWeightTimeDescription' , { defaultMessage : 'The time taken to create the Weight object, which holds temporary information during scoring.' , } ) ; case 'next_doc' : return i18n . translate ( 'xpack.searchProfiler.nextDocTimeDescription' , { defaultMessage : 'The time taken to advance the iterator to the next matching document.' , } ) ; case 'score' : return i18n . translate ( 'xpack.searchProfiler.scoreTimeDescription' , { defaultMessage : 'The time taken in actually scoring the document against the query.' , } ) ; case 'match' : return i18n . translate ( 'xpack.searchProfiler.matchTimeDescription' , { defaultMessage : 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).' , } ) ; case 'advance' : return i18n . translate ( 'xpack.searchProfiler.advanceTimeDescription' , { defaultMessage : 'The time taken to advance the iterator to the next document.' , } ) ; default : return '' ; } } export function timeInMilliseconds ( data ) { if ( data . time_in_nanos ) { return data . time_in_nanos / 1000000 ; } if ( typeof data . time === 'string' ) { return Number ( data . time . replace ( 'ms' , '' ) ) ; } return Number ( data . time ) ; } export function calcTimes ( data , parentId ? ) { let totalTime = 0 ; for ( const child of data ) { totalTime += timeInMilliseconds ( child ) ; child . breakdown = normalizeBreakdown ( child . breakdown ) ; let childrenTime = 0 ; if ( child . children != null && child . children . length !== 0 ) { childrenTime = calcTimes ( child . children , child . id ) ! ; child . hasChildren = true ; } child . selfTime = timeInMilliseconds ( child ) - childrenTime ; } return totalTime ; } export function normalizeBreakdown ( breakdown ) { const final = [ ] ; const total = Object . keys ( breakdown ) . reduce ( ( partialTotal , currentKey ) => { if ( currentKey . indexOf ( '_count' ) === - 1 ) { partialTotal += breakdown [ currentKey ] ; } return partialTotal ; } , 0 ) ; Object . keys ( breakdown ) . sort ( ) . forEach ( ( key ) => { let relative = 0 ; if ( key . indexOf ( '_count' ) === - 1 ) { relative = ( ( breakdown [ key ] / total ) * 100 ) . toFixed ( 1 ) as any ; } final . push ( { key , time : breakdown [ key ] , relative , color : tinycolor . mix ( '#F5F5F5' , '#FFAFAF' , relative ) . toHexString ( ) , tip : getToolTip ( key ) , } ) ; } ) ; return final . sort ( ( a , b ) => { if ( comparator ( a . time , b . time ) !== 0 ) { return comparator ( a . time , b . time ) ; } return - 1 * comparator ( a . key as any , b . key as any ) ; } ) ; } export function normalizeIndices ( indices , target ) { let sortQueryComponents ; if ( target === 'searches' ) { sortQueryComponents = ( a , b ) => { const aTime = _ . sumBy ( a . searches ! , ( search ) => { return search . treeRoot ! . time ; } ) ; const bTime = _ . sumBy ( b . searches ! , ( search ) => { return search . treeRoot ! . time ; } ) ; return comparator ( aTime , bTime ) ; } ; } else if ( target === 'aggregations' ) { sortQueryComponents = ( a , b ) => { const aTime = _ . sumBy ( a . aggregations ! , ( agg ) => { return agg . treeRoot ! . time ; } ) ; const bTime = _ . sumBy ( b . aggregations ! , ( agg ) => { return agg . treeRoot ! . time ; } ) ; return comparator ( aTime , bTime ) ; } ; } for ( const index of Object . values ( indices ) ) { index . shards . sort ( sortQueryComponents ) ; for ( const shard of index . shards ) { shard . relative = ( ( shard . time / index . time ) * 100 ) . toFixed ( 2 ) ; shard . color = tinycolor . mix ( '#F5F5F5' , '#FFAFAF' , shard . relative as any ) . toHexString ( ) ; } } } export function normalizeTime ( operation , totalTime ) { operation . timePercentage = ( ( timeInMilliseconds ( operation ) / totalTime ) * 100 ) . toFixed ( 2 ) ; operation . absoluteColor = tinycolor . mix ( '#F5F5F5' , '#FFAFAF' , + operation . timePercentage ) . toHexString ( ) ; } export function initTree < T > ( data , totalTime , depth = 0 , parent = null ) { if ( MAX_TREE_DEPTH + 1 === depth ) { if ( parent ) { parent ! . hasChildren = false ; parent ! . children = [ ] ; } return ; } for ( const child of data ) { if ( ! child . description ) { child . description = child . lucene ! ; child . lucene = null ; child . type = child . query_type ! ; child . query_type = null ; } normalizeTime ( child , totalTime ) ; child . parent = parent ; child . time = timeInMilliseconds ( child ) ; child . lucene = child . description ; child . query_type = child . type ! . split ( '.' ) . pop ( ) ! ; child . visible = + child . timePercentage > 20 ; child . depth = depth ; if ( child . children != null && child . children . length !== 0 ) { initTree ( child . children , totalTime , depth + 1 , child ) ; } } data . sort ( ( a , b ) => comparator ( timeInMilliseconds ( a ) , timeInMilliseconds ( b ) ) ) ; } export const sortIndices = ( data ) => { const sortedIndices = [ ] ; for ( const index of Object . values ( data ) ) { sortedIndices . push ( index ) ; } sortedIndices . sort ( ( a , b ) => comparator ( a . time , b . time ) ) ; return sortedIndices ; } ;