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

     1  import type { Profile } from '@pyroscope/models/src';
     2  import {
     3    decodeFlamebearer,
     4    deltaDiffWrapperReverse,
     5  } from '../FlameGraph/decode';
     6  import { flamebearersToTree, TreeNode } from './flamebearersToTree';
     7  
     8  function subtractFlamebearer(
     9    f1: Profile['flamebearer'],
    10    f2: Profile['flamebearer']
    11  ): Profile['flamebearer'] {
    12    const result: Profile['flamebearer'] = {
    13      numTicks: 0,
    14      maxSelf: 0,
    15      names: [],
    16      levels: [],
    17    };
    18  
    19    const tree = flamebearersToTree(f1, f2);
    20  
    21    const updateNumbers = (node: TreeNode): number => {
    22      // self is easy
    23      node.self[0] = Math.max((node.self[0] || 0) - (node.self[1] || 0), 0);
    24      result.numTicks += node.self[0];
    25  
    26      // total needs to be recalculated using children
    27      if (node.children.length === 0) {
    28        node.total[0] = Math.max((node.total[0] || 0) - (node.total[1] || 0), 0);
    29      } else {
    30        let total = node.self[0];
    31        for (let i = 0; i < node.children.length; i += 1) {
    32          total += updateNumbers(node.children[i]);
    33        }
    34        node.total[0] = total;
    35      }
    36  
    37      return node.total[0];
    38    };
    39  
    40    updateNumbers(tree);
    41  
    42    const processNode = (node: TreeNode, level: number, offset: number) => {
    43      const { name, children, self, total } = node;
    44      result.levels[level] ||= [];
    45      const newSelf = self[0];
    46      const newTotal = total[0];
    47      result.maxSelf = Math.max(result.maxSelf, newSelf);
    48      if (newTotal === 0) {
    49        return 0;
    50      }
    51      result.names.push(name);
    52      result.levels[level] = result.levels[level].concat([
    53        offset,
    54        newTotal,
    55        newSelf,
    56        result.names.length - 1,
    57      ]);
    58      for (let i = 0; i < children.length; i += 1) {
    59        const offsetAddition = processNode(children[i], level + 1, offset);
    60        offset += offsetAddition;
    61      }
    62      return newTotal;
    63    };
    64  
    65    processNode(tree, 0, 0);
    66    return result;
    67  }
    68  
    69  // this functions expects two compressed (before delta diff) profiles,
    70  //   and returns a compressed profile
    71  export function subtract(p1: Profile, p2: Profile): Profile {
    72    p1 = decodeFlamebearer(p1);
    73    p2 = decodeFlamebearer(p2);
    74  
    75    const resultFlamebearer = subtractFlamebearer(p1.flamebearer, p2.flamebearer);
    76    resultFlamebearer.levels = deltaDiffWrapperReverse(
    77      'single',
    78      resultFlamebearer.levels
    79    );
    80    const metadata = { ...p1.metadata };
    81    metadata.format = 'single';
    82    return {
    83      version: 1,
    84      flamebearer: resultFlamebearer,
    85      metadata,
    86    };
    87  }