UNPKG

@grafana/flamegraph

Version:

Grafana flamegraph visualization component

1 lines 7.97 kB
{"version":3,"file":"treeTransforms.mjs","sources":["../../../src/FlameGraph/treeTransforms.ts"],"sourcesContent":["import { groupBy } from 'lodash';\n\nimport { LevelItem } from './dataTransform';\n\ntype DataInterface = {\n getLabel: (index: number) => string;\n};\n\n// Merge parent subtree of the roots for the callers tree in the sandwich view of the flame graph.\nexport function mergeParentSubtrees(roots: LevelItem[], data: DataInterface): LevelItem[][] {\n const newRoots = getParentSubtrees(roots);\n return mergeSubtrees(newRoots, data, 'parents');\n}\n\n// Returns a subtrees per root that will have the parents resized to the same value as the root. When doing callers\n// tree we need to keep proper sizes of the parents, before we merge them, so we correctly attribute to the parents\n// only the value it contributed to the root.\n// So if we have something like:\n// [0/////////////]\n// [1//][4/////][6]\n// [2] [5/////]\n// [6] [6/][8/]\n// [7]\n// Taking all the node with '6' will create:\n// [0][0/]\n// [1][4/]\n// [2][5/][0]\n// [6][6/][6]\n// Which we can later merge.\nfunction getParentSubtrees(roots: LevelItem[]) {\n return roots.map((r) => {\n if (!r.parents?.length) {\n return r;\n }\n\n const newRoot = {\n ...r,\n children: [],\n };\n const stack: Array<{ child: undefined | LevelItem; parent: LevelItem }> = [\n { child: newRoot, parent: r.parents[0] },\n ];\n\n while (stack.length) {\n const args = stack.shift()!;\n const newNode = {\n ...args.parent,\n children: args.child ? [args.child] : [],\n parents: [],\n };\n\n if (args.child) {\n newNode.value = args.child.value;\n args.child.parents = [newNode];\n }\n\n if (args.parent.parents?.length) {\n stack.push({ child: newNode, parent: args.parent.parents[0] });\n }\n }\n return newRoot;\n });\n}\n\n// Merge subtrees into a single tree. Returns an array of levels for easy rendering. It assumes roots are mergeable,\n// meaning they represent the same unit of work (same label). Then we walk the tree in a specified direction,\n// merging nodes with the same label and same parent/child into single bigger node. This copies the tree (and all nodes)\n// as we are creating new merged nodes and modifying the parents/children.\nexport function mergeSubtrees(\n roots: LevelItem[],\n data: DataInterface,\n direction: 'parents' | 'children' = 'children'\n): LevelItem[][] {\n const oppositeDirection = direction === 'parents' ? 'children' : 'parents';\n const levels: LevelItem[][] = [];\n\n // Loop instead of recursion to be sure we don't blow stack size limit and save some memory. Each stack item is\n // basically a list of arrays you would pass to each level of recursion.\n const stack: Array<{ previous: undefined | LevelItem; items: LevelItem[]; level: number }> = [\n { previous: undefined, items: roots, level: 0 },\n ];\n\n while (stack.length) {\n const args = stack.shift()!;\n const indexes = args.items.flatMap((i) => i.itemIndexes);\n const newItem: LevelItem = {\n // We use the items value instead of value from the data frame, cause we could have changed it in the process\n value: args.items.reduce((acc, i) => acc + i.value, 0),\n itemIndexes: indexes,\n // these will change later\n children: [],\n parents: [],\n start: 0,\n level: args.level,\n };\n\n levels[args.level] = levels[args.level] || [];\n levels[args.level].push(newItem);\n\n if (args.previous) {\n // Not the first level, so we need to make sure we update previous items to keep the child/parent relationships\n // and compute correct new start offset for the item.\n newItem[oppositeDirection] = [args.previous];\n const prevSiblingsVal =\n args.previous[direction]?.reduce((acc, node) => {\n return acc + node.value;\n }, 0) || 0;\n newItem.start = args.previous.start + prevSiblingsVal;\n args.previous[direction]!.push(newItem);\n }\n\n const nextItems = args.items.flatMap((i) => i[direction] || []);\n // Group by label which for now is the only identifier by which we decide if node represents the same unit of work.\n const nextGroups = groupBy(nextItems, (c) => data.getLabel(c.itemIndexes[0]));\n for (const g of Object.values(nextGroups)) {\n stack.push({ previous: newItem, items: g, level: args.level + 1 });\n }\n }\n\n // Reverse the levels if we are doing callers tree, so we return levels in the correct order.\n if (direction === 'parents') {\n levels.reverse();\n levels.forEach((level, index) => {\n level.forEach((item) => {\n item.level = index;\n });\n });\n }\n\n return levels;\n}\n"],"names":[],"mappings":"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"}