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 }