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

     1  import type { Flamebearer } from '@pyroscope/models/src';
     2  import { flamebearersToTree, TreeNode } from './flamebearersToTree';
     3  
     4  interface FlamebearerData {
     5    maxSelf: number;
     6    levels: number[][];
     7    names: string[];
     8  }
     9  
    10  export const treeToFlamebearer = (tree: TreeNode): FlamebearerData => {
    11    const flamebearerData: FlamebearerData = {
    12      maxSelf: 100,
    13      names: [],
    14      levels: [],
    15    };
    16  
    17    const processNode = (node: TreeNode, level: number, offsetLeft: number) => {
    18      const { name, children, self, total, offset } = node;
    19      flamebearerData.names.push(name);
    20      flamebearerData.levels[level] ||= [];
    21      flamebearerData.maxSelf = Math.max(flamebearerData.maxSelf, self[0] || 0);
    22      flamebearerData.levels[level] = flamebearerData.levels[level].concat([
    23        offsetLeft,
    24        total[0] || 0,
    25        self[0] || 0,
    26        flamebearerData.names.length - 1,
    27      ]);
    28  
    29      for (let i = 0; i < children.length; i += 1) {
    30        const ol = processNode(children[i], level + 1, offsetLeft);
    31        offsetLeft += ol;
    32      }
    33      return offset || total[0] || 0;
    34    };
    35  
    36    processNode(tree, 0, 0);
    37  
    38    return flamebearerData;
    39  };
    40  
    41  const arrayToTree = (nodesArray: TreeNode[], total: number): TreeNode => {
    42    const result = {} as TreeNode;
    43    let nestedObj = result;
    44  
    45    nodesArray.forEach(({ name, ...rest }) => {
    46      const nextNode = { name, ...rest, total: [total] };
    47      nestedObj.children = [nextNode];
    48      nestedObj = nextNode;
    49    });
    50  
    51    return result.children[0];
    52  };
    53  
    54  function dedupTree(node: TreeNode) {
    55    const childrenMap = new Map<string, TreeNode>();
    56    for (let i = 0; i < node.children.length; i += 1) {
    57      if (!childrenMap.has(node.children[i].name)) {
    58        childrenMap.set(node.children[i].name, node.children[i]);
    59      }
    60    }
    61  
    62    for (let i = 0; i < node.children.length; i += 1) {
    63      const currentNode = node.children[i];
    64      const existingNode = childrenMap.get(node.children[i].name);
    65      if (existingNode && existingNode !== currentNode) {
    66        existingNode.total[0] += currentNode.total[0];
    67        existingNode.self[0] += currentNode.self[0];
    68        existingNode.children = existingNode.children.concat(
    69          currentNode.children
    70        );
    71      }
    72    }
    73    node.children = Array.from(childrenMap.values());
    74    for (let i = 0; i < node.children.length; i += 1) {
    75      dedupTree(node.children[i]);
    76    }
    77  }
    78  
    79  export function calleesFlamebearer(
    80    f: Flamebearer,
    81    nodeName: string
    82  ): Flamebearer {
    83    const tree = flamebearersToTree(f);
    84    const result: Flamebearer = {
    85      format: 'single',
    86      numTicks: 0,
    87      maxSelf: 100,
    88      sampleRate: 100,
    89      names: [],
    90      levels: [],
    91      units: f.units,
    92      spyName: f.spyName,
    93    };
    94  
    95    const totalNode = {
    96      name: nodeName,
    97      key: `/${nodeName}`,
    98      total: [],
    99      self: [0],
   100      children: [],
   101    } as TreeNode;
   102    const processTree = (node: TreeNode) => {
   103      if (node.name === nodeName) {
   104        result.numTicks += node.total[0];
   105  
   106        totalNode.total = [result.numTicks];
   107        totalNode.children = totalNode.children.concat(node.children);
   108      }
   109      for (let i = 0; i < node.children.length; i += 1) {
   110        processTree(node.children[i]);
   111      }
   112    };
   113    processTree(tree);
   114    dedupTree(totalNode);
   115  
   116    return { ...result, ...treeToFlamebearer(totalNode) };
   117  }
   118  
   119  export function callersFlamebearer(
   120    f: Flamebearer,
   121    nodeName: string
   122  ): Flamebearer {
   123    const tree = flamebearersToTree(f);
   124    const result: Flamebearer = {
   125      format: 'single',
   126      maxSelf: 100,
   127      sampleRate: 100,
   128      numTicks: 0,
   129      names: [],
   130      levels: [],
   131      units: f.units,
   132      spyName: f.spyName,
   133    };
   134  
   135    const targetFunctionTotals: number[] = [];
   136    const subtrees: TreeNode[][] = [];
   137  
   138    const totalNode = {
   139      name: nodeName,
   140      key: `/${nodeName}`,
   141      total: [0],
   142      self: [0],
   143      children: [],
   144    } as TreeNode;
   145    const processTree = (node: TreeNode, parentNodes: TreeNode[] = []) => {
   146      const currentSubtree = parentNodes.concat([
   147        { ...node, children: [] } as TreeNode,
   148      ]);
   149  
   150      if (node.name === nodeName) {
   151        subtrees.push(currentSubtree);
   152        targetFunctionTotals.push(node.total[0]);
   153        result.numTicks += node.total[0];
   154      }
   155  
   156      for (let i = 0; i < node.children.length; i += 1) {
   157        processTree(node.children[i], currentSubtree);
   158      }
   159    };
   160    processTree(tree);
   161  
   162    // 1. we first make a regular tree
   163    subtrees.forEach((v, i) => {
   164      totalNode.children.push(arrayToTree(v.reverse(), targetFunctionTotals[i]));
   165    });
   166  
   167    // 2. that allows us to use the same dedup function
   168    dedupTree(totalNode);
   169  
   170    const flamebearer = treeToFlamebearer(totalNode);
   171  
   172    // 3. then we reverse levels so that tree goes from bottom to top
   173    flamebearer.levels = flamebearer.levels.reverse().slice(0, -1);
   174  
   175    return { ...result, ...flamebearer };
   176  }
   177  
   178  export function calleesProfile(f: Flamebearer, nodeName: string): Flamebearer {
   179    const copy = JSON.parse(JSON.stringify(f));
   180    const calleesResultFlamebearer = calleesFlamebearer(copy, nodeName);
   181  
   182    return calleesResultFlamebearer;
   183  }
   184  
   185  export function callersProfile(f: Flamebearer, nodeName: string): Flamebearer {
   186    const copy = JSON.parse(JSON.stringify(f));
   187  
   188    const callersResultFlamebearer = callersFlamebearer(copy, nodeName);
   189  
   190    return callersResultFlamebearer;
   191  }