mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Add graph transform * Add tests * Refactor code * Update test * Fix zipkin block Co-authored-by: David Kaltschmidt <david.kaltschmidt@gmail.com>
120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
import { DataFrame, DataFrameView, NodeGraphDataFrameFieldNames as Fields } from '@grafana/data';
|
|
import { getNonOverlappingDuration, getStats, makeFrames, makeSpanMap } from '../../../core/utils/tracing';
|
|
|
|
interface Row {
|
|
traceID: string;
|
|
spanID: string;
|
|
parentSpanID: string;
|
|
operationName: string;
|
|
serviceName: string;
|
|
serviceTags: string;
|
|
startTime: number;
|
|
duration: number;
|
|
logs: string;
|
|
tags: string;
|
|
}
|
|
|
|
interface Node {
|
|
[Fields.id]: string;
|
|
[Fields.title]: string;
|
|
[Fields.subTitle]: string;
|
|
[Fields.mainStat]: string;
|
|
[Fields.secondaryStat]: string;
|
|
[Fields.color]: number;
|
|
}
|
|
|
|
interface Edge {
|
|
[Fields.id]: string;
|
|
[Fields.target]: string;
|
|
[Fields.source]: string;
|
|
}
|
|
|
|
export function createGraphFrames(data: DataFrame): DataFrame[] {
|
|
const { nodes, edges } = convertTraceToGraph(data);
|
|
const [nodesFrame, edgesFrame] = makeFrames();
|
|
|
|
for (const node of nodes) {
|
|
nodesFrame.add(node);
|
|
}
|
|
for (const edge of edges) {
|
|
edgesFrame.add(edge);
|
|
}
|
|
|
|
return [nodesFrame, edgesFrame];
|
|
}
|
|
|
|
function convertTraceToGraph(data: DataFrame): { nodes: Node[]; edges: Edge[] } {
|
|
const nodes: Node[] = [];
|
|
const edges: Edge[] = [];
|
|
|
|
const view = new DataFrameView<Row>(data);
|
|
|
|
const traceDuration = findTraceDuration(view);
|
|
const spanMap = makeSpanMap((index) => {
|
|
if (index >= data.length) {
|
|
return undefined;
|
|
}
|
|
const span = view.get(index);
|
|
return {
|
|
span: { ...span },
|
|
id: span.spanID,
|
|
parentIds: span.parentSpanID ? [span.parentSpanID] : [],
|
|
};
|
|
});
|
|
|
|
for (let i = 0; i < view.length; i++) {
|
|
const row = view.get(i);
|
|
|
|
const ranges: Array<[number, number]> = spanMap[row.spanID].children.map((c) => {
|
|
const span = spanMap[c].span;
|
|
return [span.startTime, span.startTime + span.duration];
|
|
});
|
|
const childrenDuration = getNonOverlappingDuration(ranges);
|
|
const selfDuration = row.duration - childrenDuration;
|
|
const stats = getStats(row.duration, traceDuration, selfDuration);
|
|
|
|
nodes.push({
|
|
[Fields.id]: row.spanID,
|
|
[Fields.title]: row.serviceName ?? '',
|
|
[Fields.subTitle]: row.operationName,
|
|
[Fields.mainStat]: stats.main,
|
|
[Fields.secondaryStat]: stats.secondary,
|
|
[Fields.color]: selfDuration / traceDuration,
|
|
});
|
|
|
|
// Sometimes some span can be missing. Don't add edges for those.
|
|
if (row.parentSpanID && spanMap[row.parentSpanID].span) {
|
|
edges.push({
|
|
[Fields.id]: row.parentSpanID + '--' + row.spanID,
|
|
[Fields.target]: row.spanID,
|
|
[Fields.source]: row.parentSpanID,
|
|
});
|
|
}
|
|
}
|
|
|
|
return { nodes, edges };
|
|
}
|
|
|
|
/**
|
|
* Get the duration of the whole trace as it isn't a part of the response data.
|
|
* Note: Seems like this should be the same as just longest span, but this is probably safer.
|
|
*/
|
|
function findTraceDuration(view: DataFrameView<Row>): number {
|
|
let traceEndTime = 0;
|
|
let traceStartTime = Infinity;
|
|
|
|
for (let i = 0; i < view.length; i++) {
|
|
const row = view.get(i);
|
|
|
|
if (row.startTime < traceStartTime) {
|
|
traceStartTime = row.startTime;
|
|
}
|
|
|
|
if (row.startTime + row.duration > traceEndTime) {
|
|
traceEndTime = row.startTime + row.duration;
|
|
}
|
|
}
|
|
|
|
return traceEndTime - traceStartTime;
|
|
}
|