graphTransform.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { DataFrame, NodeGraphDataFrameFieldNames as Fields } from '@grafana/data';
  2. import { getNonOverlappingDuration, getStats, makeFrames, makeSpanMap } from '../../../core/utils/tracing';
  3. import { Span, TraceResponse } from './types';
  4. interface Node {
  5. [Fields.id]: string;
  6. [Fields.title]: string;
  7. [Fields.subTitle]: string;
  8. [Fields.mainStat]: string;
  9. [Fields.secondaryStat]: string;
  10. [Fields.color]: number;
  11. }
  12. interface Edge {
  13. [Fields.id]: string;
  14. [Fields.target]: string;
  15. [Fields.source]: string;
  16. }
  17. export function createGraphFrames(data: TraceResponse): DataFrame[] {
  18. const { nodes, edges } = convertTraceToGraph(data);
  19. const [nodesFrame, edgesFrame] = makeFrames();
  20. for (const node of nodes) {
  21. nodesFrame.add(node);
  22. }
  23. for (const edge of edges) {
  24. edgesFrame.add(edge);
  25. }
  26. return [nodesFrame, edgesFrame];
  27. }
  28. function convertTraceToGraph(data: TraceResponse): { nodes: Node[]; edges: Edge[] } {
  29. const nodes: Node[] = [];
  30. const edges: Edge[] = [];
  31. const traceDuration = findTraceDuration(data.spans);
  32. const spanMap = makeSpanMap((index) => {
  33. if (index >= data.spans.length) {
  34. return undefined;
  35. }
  36. const span = data.spans[index];
  37. return {
  38. span,
  39. id: span.spanID,
  40. parentIds: span.references?.filter((r) => r.refType === 'CHILD_OF').map((r) => r.spanID) || [],
  41. };
  42. });
  43. for (const span of data.spans) {
  44. const process = data.processes[span.processID];
  45. const ranges: Array<[number, number]> = spanMap[span.spanID].children.map((c) => {
  46. const span = spanMap[c].span;
  47. return [span.startTime, span.startTime + span.duration];
  48. });
  49. const childrenDuration = getNonOverlappingDuration(ranges);
  50. const selfDuration = span.duration - childrenDuration;
  51. const stats = getStats(span.duration / 1000, traceDuration / 1000, selfDuration / 1000);
  52. nodes.push({
  53. [Fields.id]: span.spanID,
  54. [Fields.title]: process?.serviceName ?? '',
  55. [Fields.subTitle]: span.operationName,
  56. [Fields.mainStat]: stats.main,
  57. [Fields.secondaryStat]: stats.secondary,
  58. [Fields.color]: selfDuration / traceDuration,
  59. });
  60. const parentSpanID = span.references?.find((r) => r.refType === 'CHILD_OF')?.spanID;
  61. // Sometimes some span can be missing. Don't add edges for those.
  62. if (parentSpanID && spanMap[parentSpanID].span) {
  63. edges.push({
  64. [Fields.id]: parentSpanID + '--' + span.spanID,
  65. [Fields.target]: span.spanID,
  66. [Fields.source]: parentSpanID,
  67. });
  68. }
  69. }
  70. return { nodes, edges };
  71. }
  72. /**
  73. * Get the duration of the whole trace as it isn't a part of the response data.
  74. * Note: Seems like this should be the same as just longest span, but this is probably safer.
  75. */
  76. function findTraceDuration(spans: Span[]): number {
  77. let traceEndTime = 0;
  78. let traceStartTime = Infinity;
  79. for (const span of spans) {
  80. if (span.startTime < traceStartTime) {
  81. traceStartTime = span.startTime;
  82. }
  83. if (span.startTime + span.duration > traceEndTime) {
  84. traceEndTime = span.startTime + span.duration;
  85. }
  86. }
  87. return traceEndTime - traceStartTime;
  88. }