The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/tempo/graphTransform.ts

119 lines
3.2 KiB

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;
}