github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/packages/pyroscope-flamegraph/src/convert/convertJaegerTraceToProfile.ts (about)

     1  /* eslint-disable import/prefer-default-export */
     2  import groupBy from 'lodash.groupby';
     3  import map from 'lodash.map';
     4  import type { Profile, Trace, TraceSpan } from '@pyroscope/models/src';
     5  import { deltaDiffWrapperReverse } from '../FlameGraph/decode';
     6  
     7  interface Span extends TraceSpan {
     8    children: Span[];
     9    total: number;
    10    self: number;
    11  }
    12  
    13  export function convertJaegerTraceToProfile(trace: Trace): Profile {
    14    const resultFlamebearer = {
    15      numTicks: 0,
    16      maxSelf: 0,
    17      names: [] as string[],
    18      levels: [] as number[][],
    19    };
    20  
    21    // Step 1: converting spans to a tree
    22  
    23    const spans: Record<string, Span> = {};
    24    const root: Span = { children: [] } as unknown as Span;
    25    (trace.spans as Span[]).forEach((span) => {
    26      span.children = [];
    27      spans[span.spanID] = span;
    28    });
    29  
    30    (trace.spans as Span[]).forEach((span) => {
    31      let node = root;
    32      if (span.references && span.references.length > 0) {
    33        node = spans[span.references[0].spanID] || root;
    34      }
    35  
    36      node.children.push(span);
    37    });
    38  
    39    // Step 2: group spans with same name
    40  
    41    function groupSpans(span: Span, d: number) {
    42      (span.children || []).forEach((x) => groupSpans(x, d + 1));
    43  
    44      let childrenDur = 0;
    45      const groups = groupBy(span.children || [], (x) => x.operationName);
    46      span.children = map(groups, (group) => {
    47        const res = group[0];
    48        for (let i = 1; i < group.length; i += 1) {
    49          res.duration += group[i].duration;
    50        }
    51        childrenDur += res.duration;
    52        return res;
    53      });
    54      span.total = span.duration || childrenDur;
    55      span.self = Math.max(0, span.total - childrenDur);
    56    }
    57    groupSpans(root, 0);
    58  
    59    // Step 3: traversing the tree
    60  
    61    function processNode(span: Span, level: number, offset: number) {
    62      resultFlamebearer.numTicks ||= span.total;
    63      resultFlamebearer.levels[level] ||= [];
    64      resultFlamebearer.levels[level].push(offset);
    65      resultFlamebearer.levels[level].push(span.total);
    66      resultFlamebearer.levels[level].push(span.self);
    67      resultFlamebearer.names.push(
    68        (span.processID
    69          ? `${trace.processes[span.processID].serviceName}: `
    70          : '') + (span.operationName || 'total')
    71      );
    72      resultFlamebearer.levels[level].push(resultFlamebearer.names.length - 1);
    73  
    74      (span.children || []).forEach((x) => {
    75        offset += processNode(x, level + 1, offset);
    76      });
    77      return span.total;
    78    }
    79  
    80    processNode(root, 0, 0);
    81  
    82    resultFlamebearer.levels = deltaDiffWrapperReverse(
    83      'single',
    84      resultFlamebearer.levels
    85    );
    86  
    87    return {
    88      version: 1,
    89      flamebearer: resultFlamebearer,
    90      metadata: {
    91        format: 'single',
    92        units: 'trace_samples',
    93        spyName: 'tracing',
    94        sampleRate: 1000000,
    95      },
    96    };
    97  }