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 }