| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  | import { DataFrame, NodeGraphDataFrameFieldNames as Fields } from '@grafana/data'; | 
					
						
							| 
									
										
										
										
											2024-01-22 20:47:50 +08:00
										 |  |  | import { getNonOverlappingDuration, getStats, makeFrames, makeSpanMap } from '@grafana/o11y-ds-frontend'; | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 21:33:13 +08:00
										 |  |  | import { Span, TraceResponse } from './types'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 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: TraceResponse): DataFrame[] { | 
					
						
							|  |  |  |   const { nodes, edges } = convertTraceToGraph(data); | 
					
						
							| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  |   const [nodesFrame, edgesFrame] = makeFrames(); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   for (const node of nodes) { | 
					
						
							|  |  |  |     nodesFrame.add(node); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const edge of edges) { | 
					
						
							|  |  |  |     edgesFrame.add(edge); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return [nodesFrame, edgesFrame]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function convertTraceToGraph(data: TraceResponse): { nodes: Node[]; edges: Edge[] } { | 
					
						
							|  |  |  |   const nodes: Node[] = []; | 
					
						
							|  |  |  |   const edges: Edge[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const traceDuration = findTraceDuration(data.spans); | 
					
						
							| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const spanMap = makeSpanMap((index) => { | 
					
						
							|  |  |  |     if (index >= data.spans.length) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const span = data.spans[index]; | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       span, | 
					
						
							|  |  |  |       id: span.spanID, | 
					
						
							|  |  |  |       parentIds: span.references?.filter((r) => r.refType === 'CHILD_OF').map((r) => r.spanID) || [], | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   for (const span of data.spans) { | 
					
						
							|  |  |  |     const process = data.processes[span.processID]; | 
					
						
							| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const ranges: Array<[number, number]> = spanMap[span.spanID].children.map((c) => { | 
					
						
							|  |  |  |       const span = spanMap[c].span; | 
					
						
							|  |  |  |       return [span.startTime, span.startTime + span.duration]; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     const childrenDuration = getNonOverlappingDuration(ranges); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |     const selfDuration = span.duration - childrenDuration; | 
					
						
							| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  |     const stats = getStats(span.duration / 1000, traceDuration / 1000, selfDuration / 1000); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     nodes.push({ | 
					
						
							|  |  |  |       [Fields.id]: span.spanID, | 
					
						
							|  |  |  |       [Fields.title]: process?.serviceName ?? '', | 
					
						
							|  |  |  |       [Fields.subTitle]: span.operationName, | 
					
						
							| 
									
										
										
										
											2021-05-20 16:01:28 +08:00
										 |  |  |       [Fields.mainStat]: stats.main, | 
					
						
							|  |  |  |       [Fields.secondaryStat]: stats.secondary, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |       [Fields.color]: selfDuration / traceDuration, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const parentSpanID = span.references?.find((r) => r.refType === 'CHILD_OF')?.spanID; | 
					
						
							| 
									
										
										
										
											2021-05-11 15:59:06 +08:00
										 |  |  |     // Sometimes some span can be missing. Don't add edges for those.
 | 
					
						
							|  |  |  |     if (parentSpanID && spanMap[parentSpanID].span) { | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |       edges.push({ | 
					
						
							|  |  |  |         [Fields.id]: parentSpanID + '--' + span.spanID, | 
					
						
							|  |  |  |         [Fields.target]: span.spanID, | 
					
						
							|  |  |  |         [Fields.source]: 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(spans: Span[]): number { | 
					
						
							|  |  |  |   let traceEndTime = 0; | 
					
						
							|  |  |  |   let traceStartTime = Infinity; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const span of spans) { | 
					
						
							|  |  |  |     if (span.startTime < traceStartTime) { | 
					
						
							|  |  |  |       traceStartTime = span.startTime; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (span.startTime + span.duration > traceEndTime) { | 
					
						
							|  |  |  |       traceEndTime = span.startTime + span.duration; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return traceEndTime - traceStartTime; | 
					
						
							|  |  |  | } |