UNPKG

@grafana/flamegraph

Version:

Grafana flamegraph visualization component

1 lines 8.04 kB
{"version":3,"file":"treeTransforms.mjs","sources":["../../../src/FlameGraph/treeTransforms.ts"],"sourcesContent":["import { groupBy } from 'lodash';\n\nimport { type 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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"}