forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request elastic#5 from AlexanderWert/cpa-onweek
Implemented critical path calculation in Kibana frontend
- Loading branch information
Showing
3 changed files
with
324 additions
and
30 deletions.
There are no files selected for viewing
191 changes: 191 additions & 0 deletions
191
x-pack/plugins/apm/public/components/app/trace_explorer/cpa_helper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
import { Transaction } from "../../../../typings/es_schemas/ui/transaction"; | ||
import { Span } from "../../../../typings/es_schemas/ui/span"; | ||
import { groupBy, sortBy } from 'lodash'; | ||
import hash from 'object-hash' | ||
|
||
const ROOT_ID = 'root'; | ||
|
||
interface ITraceItem { | ||
name: string; | ||
id: string; | ||
parentId?: string; | ||
start: number; | ||
end: number; | ||
span?: Span; | ||
transaction?: Transaction; | ||
} | ||
|
||
interface ITrace { | ||
root: ITraceItem; | ||
childrenByParentId: Record<string, ITraceItem[]>; | ||
} | ||
|
||
interface TraceSegment { | ||
item: ITraceItem; | ||
intervalStart: number; | ||
intervalEnd: number; | ||
parentHash: string; | ||
} | ||
|
||
export interface ICriticalPathItem { | ||
hash: string; | ||
name: string; | ||
selfDuration: number; | ||
duration: number; | ||
parentHash: string; | ||
isRoot: boolean; | ||
} | ||
|
||
export interface ICriticalPath { | ||
elemetnsByHash: Record<string, ICriticalPathItem>; | ||
roots: ICriticalPathItem[]; | ||
} | ||
|
||
export const calculateCriticalPath = (criticalPathData: Array<Transaction | Span>) : ICriticalPath => { | ||
const tracesMap = groupBy(criticalPathData, (item) => item.trace.id); | ||
const criticalPaths = Object.entries(tracesMap).map((entry) => getTrace(entry[1])).filter(t => t !== undefined) | ||
.map(trace => calculateCriticalPathForTrace(trace)) | ||
const numOfcriticalPaths = criticalPaths.length; | ||
const criticalPath: Record<string, ICriticalPathItem> = {}; | ||
|
||
criticalPaths.forEach(cp => { | ||
cp?.forEach(cpi => { | ||
var obj = criticalPath[cpi.hash]; | ||
if(!obj){ | ||
criticalPath[cpi.hash] = cpi; | ||
obj = cpi; | ||
obj.selfDuration = obj.selfDuration / numOfcriticalPaths | ||
obj.duration = obj.duration / numOfcriticalPaths | ||
} else { | ||
obj.selfDuration += cpi.selfDuration / numOfcriticalPaths; | ||
obj.duration += cpi.duration / numOfcriticalPaths; | ||
} | ||
}); | ||
}); | ||
|
||
return { | ||
elemetnsByHash: criticalPath, | ||
roots: Object.entries(criticalPath).map((entry) => entry[1]).filter(cpi => cpi.isRoot) | ||
}; | ||
}; | ||
|
||
|
||
const getTrace = (criticalPathData: Array<Transaction | Span>) : ITrace | undefined => { | ||
const traceItems = criticalPathData.map(item => { | ||
const docType: 'span' | 'transaction' = item.processor.event; | ||
switch (docType) { | ||
case 'span': { | ||
const span = item as Span; | ||
return { | ||
name: span.span.name, | ||
span: span, | ||
transaction: undefined, | ||
id: span.span.id, | ||
parentId: span.parent?.id, | ||
start: span.timestamp.us, | ||
end: span.timestamp.us + span.span.duration.us | ||
}; | ||
} | ||
case 'transaction': | ||
const transaction = item as Transaction; | ||
return { | ||
name: transaction.transaction.name, | ||
span: undefined, | ||
transaction: transaction, | ||
id: transaction.transaction.id, | ||
parentId: transaction.parent?.id, | ||
start: transaction.timestamp.us, | ||
end: transaction.timestamp.us + transaction.transaction.duration.us | ||
}; | ||
} | ||
}); | ||
|
||
|
||
const itemsByParent = groupBy(traceItems, (item) => (item.parentId ? item.parentId : ROOT_ID)); | ||
const rootItem = itemsByParent[ROOT_ID]; | ||
if(rootItem){ | ||
return { | ||
root: rootItem[0], | ||
childrenByParentId: itemsByParent | ||
}; | ||
} else { | ||
return undefined; | ||
} | ||
|
||
|
||
|
||
} | ||
|
||
const calculateCriticalPathForTrace = (trace: ITrace | undefined) => { | ||
if(trace){ | ||
const calculateCriticalPathForChildren : Array<TraceSegment> = [{ | ||
item: trace.root, | ||
intervalStart: trace.root.start, | ||
intervalEnd: trace.root.end, | ||
parentHash: ROOT_ID | ||
}]; | ||
|
||
const criticalPathSegments : ICriticalPathItem[] = []; | ||
|
||
while( calculateCriticalPathForChildren.length > 0){ | ||
const nextSegment = calculateCriticalPathForChildren.pop(); | ||
if(nextSegment){ | ||
const result = criticalPathForItem(trace, nextSegment) | ||
calculateCriticalPathForChildren.push(...result.childrenOnCriticalPath); | ||
criticalPathSegments.push(result.criticalPathItem); | ||
} | ||
} | ||
|
||
return criticalPathSegments; | ||
} | ||
} | ||
|
||
|
||
const criticalPathForItem = ( trace: ITrace, segment: TraceSegment ) => { | ||
var criticalPathDurationSum = 0; | ||
const item = segment.item; | ||
const directChildren = trace.childrenByParentId[item.id]; | ||
|
||
var childrenOnCriticalPath : TraceSegment[] = []; | ||
const thisHash = hash({'name': item.name, 'parent': segment.parentHash}); | ||
if(directChildren && directChildren.length > 0){ | ||
const orderedChildren = [...directChildren].sort((a,b) => (b.end - a.end)); | ||
var scanTimestamp = segment.intervalEnd; | ||
orderedChildren.forEach(child => { | ||
const childStart = Math.max(child.start, segment.intervalStart); | ||
const childEnd = Math.min(child.end, scanTimestamp); | ||
if(childStart >= scanTimestamp) { | ||
// ignore this child as it is not on the critical path | ||
} else { | ||
if(childEnd < scanTimestamp){ | ||
criticalPathDurationSum += scanTimestamp - childEnd; | ||
} | ||
childrenOnCriticalPath.push({ | ||
item: child, | ||
intervalStart: childStart, | ||
intervalEnd: childEnd, | ||
parentHash: thisHash | ||
}); | ||
scanTimestamp = childStart; | ||
} | ||
}); | ||
if(scanTimestamp > segment.intervalStart){ | ||
criticalPathDurationSum += scanTimestamp - segment.intervalStart; | ||
} | ||
} else { | ||
criticalPathDurationSum += segment.intervalEnd - segment.intervalStart; | ||
} | ||
|
||
return { | ||
criticalPathItem: { | ||
hash: thisHash, | ||
name: item.name, | ||
selfDuration: criticalPathDurationSum, | ||
duration: segment.intervalEnd - segment.intervalStart, | ||
parentHash: segment.parentHash, | ||
isRoot: segment.parentHash === ROOT_ID | ||
}, | ||
childrenOnCriticalPath: childrenOnCriticalPath | ||
}; | ||
}; | ||
|
72 changes: 72 additions & 0 deletions
72
x-pack/plugins/apm/public/components/app/trace_explorer/critical_path_flamegraph/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
|
||
import React from 'react'; | ||
import { | ||
Chart, | ||
Datum, | ||
Partition, | ||
PartitionLayout, | ||
PrimitiveValue, | ||
Settings, | ||
TooltipInfo, | ||
PartialTheme, | ||
} from '@elastic/charts'; | ||
|
||
import { useChartTheme } from '@kbn/observability-plugin/public'; | ||
import { useTheme } from '../../../../hooks/use_theme'; | ||
import { ICriticalPath } from '../cpa_helper'; | ||
|
||
export const CriticalPathFlamegraph = ({criticalPath}:{ | ||
criticalPath: ICriticalPath; | ||
}) => { | ||
|
||
const theme = useTheme(); | ||
|
||
const chartSize = { | ||
//height: layers.length * 20, | ||
width: '100%', | ||
}; | ||
|
||
const chartTheme = useChartTheme(); | ||
const themeOverrides: PartialTheme = { | ||
chartMargins: { top: 0, bottom: 0, left: 0, right: 0 }, | ||
partition: { | ||
fillLabel: { | ||
fontFamily: theme.eui.euiCodeFontFamily, | ||
clipText: true, | ||
}, | ||
fontFamily: theme.eui.euiCodeFontFamily, | ||
minFontSize: 9, | ||
maxFontSize: 9, | ||
}, | ||
}; | ||
|
||
return ( | ||
<></> | ||
/* | ||
<Chart size={chartSize}> | ||
<Settings | ||
theme={[themeOverrides, ...chartTheme]} | ||
tooltip={{ | ||
customTooltip: (info) => ( | ||
<CustomTooltip | ||
{...info} | ||
valueUnit={valueUnit} | ||
nodes={data?.nodes ?? {}} | ||
/> | ||
), | ||
}} | ||
/> | ||
<Partition | ||
id="profile_graph" | ||
data={points} | ||
layers={layers} | ||
drilldown | ||
maxRowCount={1} | ||
layout={PartitionLayout.icicle} | ||
valueAccessor={(d: Datum) => d.value as number} | ||
valueFormatter={() => ''} | ||
/> | ||
</Chart> | ||
*/ | ||
); | ||
} |
Oops, something went wrong.