• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { AllocationLogic } from './Allocation.js';
17import { AllocationFunction, ConstructorComparison, ConstructorItem, ConstructorType } from '../model/UiStruct.js';
18import {
19  DetachedNessState,
20  EdgeType,
21  FileStruct,
22  HeapEdge,
23  HeapNode,
24  HeapSample,
25  HeapTraceFunctionInfo,
26  NodeType,
27} from '../model/DatabaseStruct.js';
28import { HeapNodeToConstructorItem } from '../utils/Utils.js';
29
30const BASE_SYSTEM_DISTANCE = 100000000;
31const CAN_BE_QUERIED = 1;
32const DETACHED_DOM_NODE = 2;
33const PAGE_PROJECT = 4;
34
35export class HeapLoader {
36  private fileId: number;
37  private fileStruct: FileStruct;
38  private allocationLogic: AllocationLogic;
39  private nodeMap: Map<number, HeapNode>;
40  private edges: Array<HeapEdge>;
41  private rootNode: HeapNode | undefined;
42  private nodeCount: number;
43  private edgeCount: number;
44  private nodes: Array<HeapNode>;
45  private retainingNodes: Uint32Array;
46  private retainingEdges: Uint32Array;
47  private firstRetainerIndex: Uint32Array;
48  private dominatorTree: Uint32Array;
49  private firstDominatedNodesIdx: Uint32Array;
50  private dominatedNodes: Uint32Array;
51  private allClasses?: Map<string, ConstructorItem>;
52  private diffToOtherFile: Map<number, Map<string, ConstructorComparison>>;
53
54  constructor(fileStruct: FileStruct) {
55    this.fileStruct = fileStruct;
56    this.fileId = fileStruct.id;
57    this.nodeCount = fileStruct.snapshotStruct.nodeCount;
58    this.edgeCount = fileStruct.snapshotStruct.edgeCount;
59    this.nodeMap = fileStruct.snapshotStruct.nodeMap;
60    this.edges = fileStruct.snapshotStruct.edges;
61    this.allocationLogic = new AllocationLogic(this.fileStruct);
62    this.nodes = new Array<HeapNode>(this.nodeCount);
63    this.nodeMap.forEach((value) => {
64      this.nodes[value.nodeIndex] = value;
65    });
66    this.nodes.sort((a, b) => a.nodeIndex - b.nodeIndex);
67    this.rootNode = this.nodes[0];
68    this.retainingNodes = new Uint32Array(this.edgeCount); //每一个值为node index
69    this.retainingEdges = new Uint32Array(this.edgeCount); // 每一个值为edge index
70    this.firstRetainerIndex = new Uint32Array(this.nodeCount + 1);
71    this.dominatorTree = new Uint32Array(this.nodeCount);
72    this.firstDominatedNodesIdx = new Uint32Array(this.nodeCount + 1);
73    this.dominatedNodes = new Uint32Array(this.nodeCount - 1);
74    this.diffToOtherFile = new Map<number, Map<string, ConstructorComparison>>();
75    this.preprocess();
76  }
77
78  get allocation() {
79    return this.allocationLogic;
80  }
81
82  private preprocess() {
83    if (!this.rootNode) {
84      return;
85    }
86    this.buildNodeEdge();
87    this.buildRetainers();
88    this.distributeDOMState();
89    this.calFlags();
90    this.calDistances();
91    this.calRetainedSize();
92    // use to cal class Retained Size
93    this.buildDominatedNode();
94    this.buildSamples();
95  }
96
97  /**
98   * set node parents node
99   * node has multi parent because bottom up combine multi node
100   * @param node selected node
101   */
102  loadAllocationParent(node: AllocationFunction) {
103    this.allocationLogic.getParent(node);
104  }
105
106  /**
107   * get Bottom Up Function List
108   * @returns bottomUpList
109   */
110  getAllocationFunctionList(): Array<AllocationFunction> {
111    return this.allocationLogic.getFunctionList();
112  }
113
114  /**
115   * get full stack for node
116   * @param allocationNodeId node.traceNodeId
117   * @returns stack list
118   */
119  getAllocationStack(traceNodeId: number): Array<HeapTraceFunctionInfo> {
120    return this.allocationLogic.getNodeStack(traceNodeId);
121  }
122
123  getFunctionNodeIds(id: number) {
124    return this.allocationLogic.getFunctionNodeIds(id);
125  }
126
127  getAllocation() {
128    return this.allocationLogic;
129  }
130
131  private buildNodeEdge(): void {
132    for (let node of this.nodes) {
133      for (let i = 0; i < node.edgeCount; i++) {
134        let edgeIndex = node.firstEdgeIndex + i;
135        if (this.edges.length > edgeIndex) {
136          node.addEdge(this.edges[edgeIndex]);
137        }
138      }
139    }
140  }
141
142  private buildRetainers(): void {
143    // Iterate over edges and count how many times each node is retained by other nodes
144    for (let edge of this.edges) {
145      let toNode = this.nodeMap.get(edge.toNodeId);
146      if (toNode) {
147        ++this.firstRetainerIndex[toNode.nodeIndex];
148      }
149    }
150    // Assign the first retainer slot index for each node
151    let firstUnusedRetainerSlot = 0;
152    for (let node of this.nodes) {
153      let retainCount = this.firstRetainerIndex[node.nodeIndex];
154      this.firstRetainerIndex[node.nodeIndex] = firstUnusedRetainerSlot;
155      this.retainingNodes[firstUnusedRetainerSlot] = retainCount;
156      firstUnusedRetainerSlot += retainCount;
157    }
158    this.firstRetainerIndex[this.nodeCount] = this.retainingNodes.length;
159
160    // Fill the retainer slots with the retaining nodes and edges
161    for (let node of this.nodes) {
162      for (let edge of node.edges) {
163        let childNode = this.nodeMap.get(edge.toNodeId);
164        if (!childNode) {
165          continue;
166        }
167        if (node.nodeIndex !== this.rootNode?.nodeIndex) {
168          childNode.retainsNodeIdx.push(node.nodeIndex);
169          childNode.retainsEdgeIdx.push(edge.edgeIndex);
170        }
171
172        let firstRetainerSlotIndex = this.firstRetainerIndex[childNode.nodeIndex];
173
174        let nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + --this.retainingNodes[firstRetainerSlotIndex];
175        this.retainingNodes[nextUnusedRetainerSlotIndex] = node.nodeIndex;
176        this.retainingEdges[nextUnusedRetainerSlotIndex] = edge.edgeIndex;
177      }
178    }
179  }
180
181  private distributeDOMState(): void {
182    // 1.存储传播的状态。虽然已知节点的状态已经设置,
183    // 但仍然需要经过处理以调整其名称并将其放入各自的队列中。
184    let domState = new DOMState(this.nodeCount);
185    for (let node of this.nodes) {
186      if (node.detachedness === DetachedNessState.UNKNOWN) {
187        continue;
188      }
189      this.processNode(domState, node, node.detachedness);
190    }
191
192    // 2. 如果父节点被Attached,那么子节点也被Attached。
193    while (domState.attached.length > 0) {
194      let nodeId = domState.attached.pop() as number;
195      let node = this.nodeMap.get(nodeId);
196      if (node) {
197        this.iterateFilteredChildren(domState, node, DetachedNessState.ATTACHED);
198      }
199    }
200
201    // 3. 如果父节点为Attached,那么子节点将继承父节点的状态。
202    while (domState.detached.length > 0) {
203      let nodeId = domState.detached.pop() as number;
204      let node = this.nodeMap.get(nodeId);
205      if (node && node.detachedness !== DetachedNessState.ATTACHED) {
206        this.iterateFilteredChildren(domState, node, DetachedNessState.DETACHED);
207      }
208    }
209  }
210
211  private calFlags(): void {
212    this.markQueryableNodes();
213    this.markPageOwnedNodes();
214  }
215
216  private buildOrderIdxAndDominateTree(): Uint32Array {
217    let stackNodes = new Uint32Array(this.nodeCount); // node index
218    let stackCurrentEdge = new Uint32Array(this.nodeCount); // edge index
219    let orderIdx2NodeIdx = new Uint32Array(this.nodeCount);
220    let nodeIdx2OrderIdx = new Uint32Array(this.nodeCount);
221    let visited = new Uint8Array(this.nodeCount);
222    let postOrderIdx = 0;
223    let stack = 0;
224    stackNodes[0] = this.rootNode!.nodeIndex;
225    visited[this.rootNode!.nodeIndex] = 1;
226
227    let iteration = 0;
228    while (true) {
229      ++iteration;
230      while (stack >= 0 && stack < this.nodeCount) {
231        let nodeIndex = stackNodes[stack];
232        let node = this.nodes[nodeIndex];
233        if (!node) {
234          continue;
235        }
236        let edgeIndex = stackCurrentEdge[stack];
237        let edgeEnd = node.firstEdgeIndex + node.edgeCount;
238        if (edgeIndex < edgeEnd) {
239          stackCurrentEdge[stack] += 1;
240          let edge = this.edges[edgeIndex];
241          if (!this.isEssentialEdge(edge, node.id)) {
242            continue;
243          }
244          let childNode = this.nodeMap.get(edge.toNodeId);
245          if (!childNode || visited[childNode!.nodeIndex]) {
246            continue;
247          }
248          //Skip the edges from non-page-object nodes to page-object nodes
249          let childNodeFlag = childNode.flag & PAGE_PROJECT;
250          if (node.id != this.rootNode!.id && childNodeFlag && !node.flag) {
251            continue;
252          }
253          ++stack;
254          stackNodes[stack] = childNode.nodeIndex;
255          stackCurrentEdge[stack] = childNode.firstEdgeIndex;
256          visited[childNode.nodeIndex] = 1;
257        } else {
258          nodeIdx2OrderIdx[node.nodeIndex] = postOrderIdx;
259          orderIdx2NodeIdx[postOrderIdx++] = node.nodeIndex;
260          --stack;
261        }
262      }
263      if (postOrderIdx == this.nodeCount || iteration > 1) {
264        break;
265      }
266
267      // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
268      // visited after all orphan nodes and their subgraphs.
269      --postOrderIdx;
270      stack = 0;
271      stackNodes[0] = this.rootNode!.nodeIndex;
272      stackCurrentEdge[0] = this.nodes[this.rootNode!.nodeIndex + 1].firstEdgeIndex;
273      for (let node of this.nodes) {
274        if (visited[node.nodeIndex] || !this.hasOnlyWeakRetainers(node)) {
275          continue;
276        }
277        stackNodes[++stack] = node.nodeIndex;
278        stackCurrentEdge[stack] = node.firstEdgeIndex;
279        visited[node.nodeIndex] = 1;
280      }
281    }
282    // If we already processed all orphan nodes that have only weak retainers and still have some orphans...
283    if (postOrderIdx !== this.nodeCount) {
284      // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
285      // visited after all orphan nodes and their subgraphs.
286      --postOrderIdx;
287      for (let i = 0; i < this.nodeCount; i++) {
288        if (visited[i]) {
289          continue;
290        }
291        // Fix it by giving the node a postorder index anyway.
292        nodeIdx2OrderIdx[i] = postOrderIdx;
293        orderIdx2NodeIdx[postOrderIdx++] = i;
294      }
295      nodeIdx2OrderIdx[this.rootNode!.nodeIndex] = postOrderIdx;
296      orderIdx2NodeIdx[postOrderIdx++] = this.rootNode!.nodeIndex;
297    }
298    this.buildDominatorTree(orderIdx2NodeIdx, nodeIdx2OrderIdx);
299    return orderIdx2NodeIdx;
300  }
301
302  // The algorithm is based on the article:
303  // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
304  // Softw. Pract. Exper. 4 (2001), pp. 1-10.
305  private buildDominatorTree(orderIdx2NodeIdx: Uint32Array, nodeIdx2OrderIdx: Uint32Array): void {
306    let rootOrderedIdx = this.nodeCount - 1;
307    let dominators = new Uint32Array(this.nodeCount).fill(this.nodeCount);
308    dominators[rootOrderedIdx] = rootOrderedIdx;
309
310    // The affected array is used to mark entries which dominators
311    // have to be racalculated because of changes in their retainers.
312    let affected = new Uint8Array(this.nodeCount);
313
314    // 标记root节点的子节点为affected.
315    for (let edge of this.rootNode!.edges) {
316      if (!this.isEssentialEdge(edge, this.rootNode!.id)) {
317        continue;
318      }
319      let childNode = this.nodeMap.get(edge.toNodeId);
320      if (childNode) {
321        affected[nodeIdx2OrderIdx[childNode.nodeIndex]] = 1;
322      }
323    }
324
325    let changed = true;
326    let nodeIdx;
327    while (changed) {
328      changed = false;
329      for (let orderIdx = rootOrderedIdx - 1; orderIdx >= 0; --orderIdx) {
330        // If dominator of the entry has already been set to root,
331        // then it can't propagate any further.
332        if (affected[orderIdx] === 0) {
333          continue;
334        }
335        affected[orderIdx] = 0;
336        if (dominators[orderIdx] === rootOrderedIdx) {
337          continue;
338        }
339        nodeIdx = orderIdx2NodeIdx[orderIdx];
340        let node = this.nodes[nodeIdx];
341        let nodeFlag = node.flag & PAGE_PROJECT;
342        let newDominatorIdx = this.nodeCount;
343        let retainerStart = this.firstRetainerIndex[nodeIdx];
344        let retainerEnd = this.firstRetainerIndex[nodeIdx + 1];
345        let orphanNode = true;
346        for (let idx = retainerStart; idx < retainerEnd; idx++) {
347          let retainerNodeIdx = this.retainingNodes[idx];
348          let retainerEdgeIdx = this.retainingEdges[idx];
349          let node = this.nodes[retainerNodeIdx];
350          let edge = this.edges[retainerEdgeIdx];
351          if (!this.isEssentialEdge(edge, node.id)) {
352            continue;
353          }
354          orphanNode = false;
355          let retainerNodeFlag = node.flag & PAGE_PROJECT;
356          if (retainerNodeIdx !== this.rootNode?.nodeIndex && nodeFlag && !retainerNodeFlag) {
357            continue;
358          }
359          let retainerOrderIdx = nodeIdx2OrderIdx[retainerNodeIdx];
360          if (dominators[retainerOrderIdx] !== this.nodeCount) {
361            if (newDominatorIdx === this.nodeCount) {
362              newDominatorIdx = retainerOrderIdx;
363            } else {
364              while (retainerOrderIdx !== newDominatorIdx) {
365                while (retainerOrderIdx < newDominatorIdx) {
366                  retainerOrderIdx = dominators[retainerOrderIdx];
367                }
368                while (newDominatorIdx < retainerOrderIdx) {
369                  newDominatorIdx = dominators[newDominatorIdx];
370                }
371              }
372            }
373            // If idom has already reached the root, it doesn't make sense
374            // to check other retainers.
375            if (newDominatorIdx === rootOrderedIdx) {
376              break;
377            }
378          }
379        }
380        // Make root dominator of orphans.
381        if (orphanNode) {
382          newDominatorIdx = rootOrderedIdx;
383        }
384        if (newDominatorIdx !== this.nodeCount && dominators[orderIdx] !== newDominatorIdx) {
385          dominators[orderIdx] = newDominatorIdx;
386          changed = true;
387          nodeIdx = orderIdx2NodeIdx[orderIdx];
388          let node = this.nodes[nodeIdx];
389          for (let edge of node.edges) {
390            let childNode = this.nodeMap.get(edge.toNodeId);
391            if (childNode) {
392              affected[nodeIdx2OrderIdx[childNode.nodeIndex]] = 1;
393            }
394          }
395        }
396      }
397    }
398
399    for (let orderIdx = 0; orderIdx < dominators.length; orderIdx++) {
400      nodeIdx = orderIdx2NodeIdx[orderIdx];
401      this.dominatorTree[nodeIdx] = orderIdx2NodeIdx[dominators[orderIdx]];
402    }
403  }
404
405  private calDistances(): void {
406    if (!this.rootNode) return;
407    let nodesToVisit = new Uint32Array(this.nodeCount);
408    let nodesToVisitLen = 0;
409    // root node's edges distance is 1
410    for (let edge of this.rootNode.edges) {
411      let node = this.nodeMap.get(edge.toNodeId);
412      if (!node) {
413        continue;
414      }
415      if (node.isUserRoot() || node.isDocumentDOMTreesRoot()) {
416        node.distance = 1;
417        nodesToVisit[nodesToVisitLen++] = node.id;
418      }
419    }
420    this.bfs(nodesToVisit, nodesToVisitLen);
421
422    // 如果roo节点下有能访问到的子节点,将root节点的distance设置为100000000,否则为0
423    this.rootNode.distance = nodesToVisitLen > 0 ? BASE_SYSTEM_DISTANCE : 0;
424    // 设置root节点下访问不到的哪些独立节点的distance,从100000000开始计数
425    nodesToVisit[0] = this.rootNode.id;
426    nodesToVisitLen = 1;
427    this.bfs(nodesToVisit, nodesToVisitLen);
428  }
429
430  private calRetainedSize(): void {
431    let orderIdx2NodeIdx = this.buildOrderIdxAndDominateTree();
432    // Propagate retained sizes for each node excluding root.
433    for (let idx = 0; idx < this.nodeCount - 1; idx++) {
434      let node = this.nodes[orderIdx2NodeIdx[idx]];
435      let dominatorNode = this.nodes[this.dominatorTree[node.nodeIndex]];
436      dominatorNode.retainedSize += node.retainedSize;
437    }
438  }
439
440  private buildDominatedNode() {
441    //赋值两个数组:
442    //-dominatedNodes是一个连续数组,其中每个节点拥有一个与相应的被支配节点的间隔(可以为空)。
443    //—indexArray 是dominatedNodes中与_nodeIndex位置相同的索引数组。
444    let fromNodeIdx = 0;
445    let toNodeIdx = this.nodeCount;
446
447    if (this.rootNode?.nodeIndex === 0) {
448      fromNodeIdx = 1;
449    } else if (this.rootNode?.nodeIndex == toNodeIdx - 1) {
450      toNodeIdx -= 1;
451    } else {
452      throw new Error('Root node is expected to be either first or last');
453    }
454    for (let nodeIdx = fromNodeIdx; nodeIdx < toNodeIdx; ++nodeIdx) {
455      ++this.firstDominatedNodesIdx[this.dominatorTree[nodeIdx]];
456    }
457    // Put in the first slot of each dominatedNodes slice the count of entries
458    // that will be filled.
459    let firstDominatedNodeIdx = 0;
460    for (let idx = 0; idx < this.nodeCount; idx++) {
461      let dominateCount = (this.dominatedNodes[firstDominatedNodeIdx] = this.firstDominatedNodesIdx[idx]);
462      this.firstDominatedNodesIdx[idx] = firstDominatedNodeIdx;
463      firstDominatedNodeIdx += dominateCount;
464    }
465    this.firstDominatedNodesIdx[this.nodeCount] = this.dominatedNodes.length;
466    // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at
467    // index 0) as it is the only node that dominates itself.
468    for (let nodeIdx = fromNodeIdx; nodeIdx < toNodeIdx; nodeIdx++) {
469      let dominatorIdx = this.dominatorTree[nodeIdx];
470      let dominatorRefIdx = this.firstDominatedNodesIdx[dominatorIdx];
471      dominatorRefIdx += --this.dominatedNodes[dominatorRefIdx];
472      this.dominatedNodes[dominatorRefIdx] = nodeIdx;
473    }
474  }
475
476  private buildSamples() {
477    let samples = this.fileStruct.snapshotStruct.samples;
478    if (!samples.length) {
479      return;
480    }
481    for (let node of this.nodes) {
482      if (node.id % 2 === 0) {
483        continue;
484      }
485      let rangeIdx = this.binarySearchNodeInSamples(node.id, samples);
486      if (rangeIdx === samples.length) {
487        continue;
488      }
489      samples[rangeIdx].size += node.selfSize;
490    }
491  }
492
493  getMinAndMaxNodeId() {
494    return {
495      minNodeId: this.nodes[0].id,
496      maxNodeId: this.nodes[this.nodeCount - 1].id,
497    };
498  }
499
500  private binarySearchNodeInSamples(nodeId: number, samples: Array<HeapSample>): number {
501    let left = 0;
502    let right = samples.length - 1;
503
504    while (left <= right) {
505      const mid = Math.floor((left + right) / 2);
506      const currentSample = samples[mid];
507      if (currentSample.lastAssignedId === nodeId) {
508        return left;
509      } else if (currentSample.lastAssignedId < nodeId) {
510        left = mid + 1;
511      } else {
512        right = mid - 1;
513      }
514    }
515    return left;
516  }
517
518  private filterForBpf(node: HeapNode, edge: HeapEdge): boolean {
519    if (node.type === NodeType.HIDDEN) {
520      return edge.nameOrIndex !== 'sloppy_function_map' || node.name !== 'system / NativeContext';
521    }
522    if (node.type === NodeType.ARRAY) {
523      if (node.name !== '(map descriptors)') {
524        return true;
525      }
526      const index = parseInt(edge.nameOrIndex, 10);
527      return index < 2 || index % 3 !== 1;
528    }
529    return true;
530  }
531
532  /**
533   * 广度优先算法遍历所有能从root节点访问到的节点,设置节点的的distance
534   * @param nodesToVisit 能被访问到的node id 数组
535   * @param nodesToVisitLen 有效的node数量
536   */
537  private bfs(nodesToVisit: Uint32Array, nodesToVisitLen: number) {
538    let index = 0;
539    while (index < nodesToVisitLen) {
540      let nodeId = nodesToVisit[index++];
541      let node = this.nodeMap.get(nodeId);
542      if (!node) {
543        continue;
544      }
545      let distance = node.distance + 1;
546
547      for (let edge of node.edges) {
548        if (edge.type === EdgeType.WEAK || !this.nodeMap.has(edge.toNodeId)) {
549          continue;
550        }
551        let childNode = this.nodeMap.get(edge.toNodeId);
552        // if distance is set,not set again
553        if (!childNode || childNode.distance != -5 || !this.filterForBpf(node, edge)) {
554          continue;
555        }
556        childNode.distance = distance;
557        nodesToVisit[nodesToVisitLen++] = childNode.id;
558      }
559    }
560    if (nodesToVisitLen > this.nodeCount) {
561      throw new Error('BFS failed. Nodes to visit ' + nodesToVisitLen + ' is more than nodes count ' + this.nodeCount);
562    }
563  }
564
565  private processNode(domState: DOMState, node: HeapNode, newState: number) {
566    if (domState.visited[node.nodeIndex]) {
567      return;
568    }
569    if (node.type !== NodeType.NATIVE) {
570      domState.visited[node.nodeIndex] = 1;
571      return;
572    }
573
574    node.detachedness = newState;
575
576    if (newState === DetachedNessState.ATTACHED) {
577      domState.attached.push(node.id);
578    } else if (newState === DetachedNessState.DETACHED) {
579      node.displayName = 'Detached ' + node.name;
580      // mark detached dom
581      node.flag |= DETACHED_DOM_NODE;
582      domState.detached.push(node.id);
583    }
584    domState.visited[node.nodeIndex] = 1;
585  }
586
587  /**
588   * Iterates children of a node.
589   */
590  private iterateFilteredChildren(domState: DOMState, node: HeapNode, newState: number): void {
591    for (let edge of node.edges) {
592      if (edge.type === EdgeType.HIDDEN || edge.type === EdgeType.WEAK || edge.type === EdgeType.INVISIBLE) {
593        continue;
594      }
595      let childNode = this.nodeMap.get(edge.toNodeId);
596      if (childNode) {
597        this.processNode(domState, childNode, newState);
598      }
599    }
600  }
601
602  /**
603   * mark the node can reachable from root node
604   */
605  private markQueryableNodes(): void {
606    let list = new Array<HeapNode>();
607    let flag = CAN_BE_QUERIED;
608    for (let edge of this.rootNode!.edges) {
609      let childNode = this.nodeMap.get(edge.toNodeId);
610      if (childNode && childNode.isUserRoot()) {
611        list.push(childNode);
612      }
613    }
614
615    while (list.length) {
616      let node = list.pop() as HeapNode;
617      if (!node || node.flag & flag) {
618        continue;
619      }
620      node.flag |= flag;
621      for (let edge of node.edges) {
622        let childNode = this.nodeMap.get(edge.toNodeId);
623        if (
624          !childNode ||
625          childNode.flag & flag ||
626          // @ts-ignore
627          [EdgeType.HIDDEN, EdgeType.INVISIBLE, EdgeType.INTERNAL, EdgeType.WEAK].includes(edge.type)
628        ) {
629          continue;
630        }
631        list.push(childNode);
632      }
633    }
634  }
635
636  private markPageOwnedNodes(): void {
637    let nodesToVisitLen = 0;
638    let nodesToVisit = new Array<HeapNode>(this.nodeCount);
639    let flag = PAGE_PROJECT;
640    if (!this.rootNode) {
641      return;
642    }
643
644    for (let edge of this.rootNode.edges) {
645      let node = this.nodeMap.get(edge.toNodeId);
646      if (!node) {
647        continue;
648      }
649      if (edge.type === EdgeType.ELEMENT) {
650        if (!node.isDocumentDOMTreesRoot()) {
651          continue;
652        }
653      } else if (edge.type !== EdgeType.SHORTCUT) {
654        continue;
655      }
656      nodesToVisit[nodesToVisitLen++] = node;
657      node.flag |= flag;
658    }
659
660    while (nodesToVisitLen) {
661      let node = nodesToVisit[--nodesToVisitLen];
662      for (let edge of node.edges) {
663        let childNode = this.nodeMap.get(edge.toNodeId);
664        if (!childNode || childNode.flag & flag || edge.type === EdgeType.WEAK) {
665          continue;
666        }
667        nodesToVisit[nodesToVisitLen++] = childNode;
668        childNode.flag |= flag;
669      }
670    }
671  }
672
673  private isEssentialEdge(edge: HeapEdge, nodeId: number): boolean {
674    return edge.type !== EdgeType.WEAK && (edge.type !== EdgeType.SHORTCUT || nodeId === this.rootNode!.id);
675  }
676
677  private hasOnlyWeakRetainers(node: HeapNode): boolean {
678    let retainerStart = this.firstRetainerIndex[node.nodeIndex];
679    let retainerEnd = this.firstRetainerIndex[node.nodeIndex + 1];
680    for (let index = retainerStart; index < retainerEnd; index++) {
681      let retainingEdgeIdx = this.retainingEdges[index];
682      let edge = this.edges[retainingEdgeIdx];
683      if (edge.type !== EdgeType.WEAK && edge.type !== EdgeType.SHORTCUT) {
684        return false;
685      }
686    }
687    return true;
688  }
689
690  private calClassDiff(targetClass: ConstructorItem, baseClass?: ConstructorItem) {
691    let diff = new ConstructorComparison();
692    diff.type = ConstructorType.ComparisonType;
693    diff.fileId = this.fileId;
694    diff.targetFileId = targetClass.fileId;
695    diff.nodeName = targetClass.nodeName;
696    let i = 0;
697    let j = 0;
698    let baseLen = baseClass ? baseClass.childCount : 0;
699    let targetLen = targetClass.childCount;
700    targetClass.classChildren.sort((a, b) => a.id - b.id);
701    if (baseClass) {
702      baseClass.classChildren.sort((a, b) => a.id - b.id);
703    }
704    // The overlap between the base class and the target class
705    while (i < targetLen && j < baseLen) {
706      let baseNode = baseClass!.classChildren[j];
707      let targetNode = targetClass.classChildren[i];
708      if (targetNode.id < baseNode.id) {
709        diff.deletedIdx.push(targetNode.index);
710        diff.removedCount++;
711        diff.removedSize += targetNode.shallowSize;
712        i++;
713      } else if (targetNode.id > baseNode.id) {
714        diff.addedIndx.push(baseNode.index);
715        diff.addedCount++;
716        diff.addedSize += baseNode.shallowSize;
717        j++;
718      } else {
719        i++;
720        j++;
721      }
722    }
723    // base more then target
724    while (i < targetLen) {
725      let targetNode = targetClass!.classChildren[i];
726      diff.deletedIdx.push(targetNode.index);
727      diff.removedCount++;
728      diff.removedSize += targetNode.shallowSize;
729      i++;
730    }
731    //
732    while (j < baseLen) {
733      let baseNode = baseClass!.classChildren[j];
734      diff.addedIndx.push(baseNode.index);
735      diff.addedCount++;
736      diff.addedSize += baseNode.shallowSize;
737      j++;
738    }
739    diff.deltaCount = diff.addedCount - diff.removedCount;
740    diff.deltaSize = diff.addedSize - diff.removedSize;
741    if (diff.addedCount == 0 && diff.removedCount == 0) {
742      return null;
743    }
744    diff.childCount = diff.addedCount + diff.removedCount;
745    diff.hasNext = true;
746    return diff;
747  }
748
749  public getClassesForSummary(minNodeId?: number, maxNodeId?: number): Map<string, ConstructorItem> {
750    let hasFiler = typeof minNodeId === 'number' && typeof maxNodeId === 'number';
751    function filter(nodeId: number): boolean {
752      if (hasFiler) {
753        if (hasFiler && nodeId >= minNodeId! && nodeId <= maxNodeId!) {
754          return true;
755        } else {
756          return false;
757        }
758      } else {
759        return true;
760      }
761    }
762    if (!hasFiler && this.allClasses) {
763      return this.allClasses;
764    }
765    let classes = new Map<string, ConstructorItem>();
766    // combine node with className
767    for (let node of this.nodes) {
768      if (!filter(node.id) || (node.selfSize === 0 && node.type !== NodeType.NATIVE)) {
769        continue;
770      }
771
772      if (!classes.has(node.className())) {
773        let classItem = HeapNodeToConstructorItem(node);
774        classItem.fileId = this.fileId;
775        classItem.childCount = 1;
776        classItem.type = ConstructorType.ClassType;
777        classItem.retainedSize = 0;
778        classItem.nodeName = node.className();
779        classItem.hasNext = true;
780        classes.set(node.className(), classItem);
781
782        let instanceItem = classItem.clone();
783        instanceItem.type = ConstructorType.InstanceType;
784        instanceItem.id = node.id;
785        instanceItem.index = node.nodeIndex;
786        instanceItem.childCount = node.edgeCount;
787        instanceItem.retainedSize = node.retainedSize;
788        instanceItem.hasNext = instanceItem.childCount > 0;
789        instanceItem.traceNodeId = node.traceNodeId;
790        classItem.classChildren.push(instanceItem);
791      } else {
792        let classItem = classes.get(node.className());
793        if (!classItem) {
794          continue;
795        }
796        // set min node distance to class distance
797        classItem.distance = Math.min(classItem.distance, node.distance);
798        ++classItem.childCount;
799        classItem.shallowSize += node.selfSize;
800
801        let nodeItem = HeapNodeToConstructorItem(node);
802        nodeItem.fileId = this.fileId;
803        nodeItem.type = ConstructorType.InstanceType;
804        nodeItem.id = node.id;
805        nodeItem.index = node.nodeIndex;
806        nodeItem.childCount = node.edgeCount;
807        nodeItem.hasNext = nodeItem.childCount > 0;
808
809        classItem.classChildren.push(nodeItem);
810      }
811    }
812
813    // cal class retained size
814    let list = [this.rootNode];
815    const sizes = [-1];
816    const classesName = [];
817    let seenClassName = new Map<string, boolean>();
818
819    while (list.length) {
820      let node = list.pop();
821      if (!node) {
822        continue;
823      }
824      let nodeClassName = node.className();
825      let seen = Boolean(seenClassName.get(nodeClassName));
826      let dominatorFromIdx = this.firstDominatedNodesIdx[node.nodeIndex];
827      let dominatorToIdx = this.firstDominatedNodesIdx[node.nodeIndex + 1];
828
829      if (!seen && (!hasFiler || filter(node.id)) && (node.selfSize || node.type === NodeType.NATIVE)) {
830        let classItem = classes.get(nodeClassName);
831        if (classItem) {
832          classItem.retainedSize += node.retainedSize;
833          if (dominatorFromIdx !== dominatorToIdx) {
834            seenClassName.set(nodeClassName, true);
835            sizes.push(list.length);
836            classesName.push(nodeClassName);
837          }
838        }
839      }
840
841      for (let idx = dominatorFromIdx; idx < dominatorToIdx; idx++) {
842        let nodeIdx = this.dominatedNodes[idx];
843        let domNode = this.nodes[nodeIdx];
844        list.push(domNode);
845      }
846
847      while (sizes[sizes.length - 1] === list.length) {
848        sizes.pop();
849        nodeClassName = classesName.pop() as string;
850        seenClassName.set(nodeClassName, false);
851      }
852    }
853    if (!hasFiler) {
854      this.allClasses = classes;
855    }
856    return classes;
857  }
858
859  /**
860   * compare base file and target file, calculate delta size and count to target class
861   * @param targetFileId to compare file's id
862   * @param targetFileClasses to compare file's constructor
863   */
864  public getClassesForComparison(targetFileId: number, targetFileClasses: Map<string, ConstructorItem>) {
865    // Return the result if it has been obtained before
866    if (this.diffToOtherFile.has(targetFileId)) {
867      return this.diffToOtherFile.get(targetFileId);
868    }
869    // get base file class if not init before
870    if (!this.allClasses) {
871      this.allClasses = this.getClassesForSummary();
872    }
873
874    //deal target class
875    let diffMap = new Map<string, ConstructorComparison>();
876    for (let targetClass of targetFileClasses.values()) {
877      let classes = this.allClasses.get(targetClass.nodeName);
878      let different = this.calClassDiff(targetClass, classes);
879      if (different) {
880        diffMap.set(targetClass.nodeName, different);
881      }
882    }
883
884    // deal base class which is not in target
885    for (let classItem of this.allClasses.values()) {
886      if (targetFileClasses.has(classItem.nodeName)) {
887        continue;
888      }
889      let different = this.calClassDiff(classItem);
890      if (different) {
891        diffMap.set(classItem.nodeName, different);
892      }
893    }
894    this.diffToOtherFile.set(targetFileId, diffMap);
895    return diffMap;
896  }
897
898  /**
899   * Summary get Node Children
900   * @param item select Node
901   * @returns child Nodes
902   */
903  public getNextNode(item: ConstructorItem): Array<ConstructorItem> {
904    if (item.children.length > 0) {
905      return item.children;
906    }
907    // get children from edge
908    let node = this.nodes[item.index];
909    let childNodes = new Array<ConstructorItem>();
910    for (let edge of node.edges) {
911      let childNode = this.nodeMap.get(edge.toNodeId);
912      if (!childNode) {
913        continue;
914      }
915      let instanceItem = HeapNodeToConstructorItem(childNode);
916      instanceItem.childCount = instanceItem.edgeCount = childNode.edgeCount;
917      instanceItem.edgeName = edge.nameOrIndex;
918      instanceItem.hasNext = instanceItem.childCount > 0;
919      instanceItem.traceNodeId = childNode.traceNodeId;
920      instanceItem.type = ConstructorType.FiledType;
921      instanceItem.parent = item;
922      childNodes.push(instanceItem);
923    }
924
925    let clickNode = childNodes[0].parent;
926    // If there are duplicate IDs in the third layer and beyond, they will not be expanded again
927    if (clickNode!.type == ConstructorType.FiledType) {
928      function findParents(clickNode: any, parents: any): any {
929        if (!clickNode.parent) {
930          return parents;
931        }
932        // add the parent of the current node to the result array
933        parents.push(clickNode);
934        for (let childNode of childNodes) {
935          for (let p of parents) {
936            if (p.id === childNode!.id) {
937              childNode.hasNext = false;
938            }
939          }
940        }
941        return findParents(clickNode.parent, parents);
942      }
943      findParents(clickNode, []);
944    }
945    let filterChildNodes = new Array<ConstructorItem>();
946    for (let item of childNodes) {
947      if (item.id !== this.rootNode!.id) {
948        filterChildNodes.push(item);
949      }
950    }
951    item.children = filterChildNodes;
952    return filterChildNodes;
953  }
954
955  /**
956   * get nodes which referenced this node
957   * @param constructor current node
958   * @returns reference nodes
959   */
960  public getRetains(item: ConstructorItem): Array<ConstructorItem> {
961    if (item.retains.length > 0) {
962      return item.retains;
963    }
964    if (item.type === ConstructorType.ClassType) {
965      return [];
966    }
967    let node = this.nodes[item.index];
968    let retains = new Array<ConstructorItem>();
969    if (node && node.retainsEdgeIdx.length === node.retainsNodeIdx.length) {
970      for (let i = 0; i < node.retainsNodeIdx.length; i++) {
971        let retainsNode = this.nodes[node.retainsNodeIdx[i]];
972        let retainEdge = this.edges[node.retainsEdgeIdx[i]];
973
974        if (retainEdge.type == EdgeType.WEAK) {
975          continue;
976        }
977        let retainsItem = HeapNodeToConstructorItem(retainsNode);
978        retainsItem.edgeName = retainEdge.nameOrIndex;
979        retainsItem.edgeType = retainEdge.type;
980        retainsItem.type = ConstructorType.RetainersType;
981        retainsItem.childCount = retainsNode.retainsNodeIdx.length;
982        retainsItem.hasNext = retainsNode.retainsNodeIdx.length > 0;
983        if (item!.type == ConstructorType.RetainersType) {
984          retainsItem.parent = item;
985        }
986        retains.push(retainsItem);
987      }
988    }
989
990    // Because the node with id 1 needs to be deleted, there is only one child and id 1 does not need to expand the symbol
991    for (let childNode of retains) {
992      let node = this.nodes[childNode.index];
993      if (node && node.retainsEdgeIdx.length === node.retainsNodeIdx.length) {
994        for (let i = 0; i < node.retainsNodeIdx.length; i++) {
995          let retainsNode = this.nodes[node.retainsNodeIdx[i]];
996          if (node.retainsNodeIdx.length == 1 && retainsNode.id == this.rootNode!.id) {
997            childNode.hasNext = false;
998          }
999        }
1000      }
1001    }
1002
1003    if (retains.length > 0 && retains[0].parent) {
1004      let clickNode = retains[0].parent;
1005      // If there are duplicate IDs in the third layer and beyond, they will not be expanded again
1006      if (clickNode!.type == ConstructorType.RetainersType) {
1007        function findParents(clickNode: any, parents: any): any {
1008          if (!clickNode.parent) {
1009            return parents;
1010          }
1011          // add the parent of the current node to the result array
1012          parents.push(clickNode);
1013          for (let childNode of retains) {
1014            for (let p of parents) {
1015              if (p.id === childNode!.id) {
1016                childNode.hasNext = false;
1017              }
1018            }
1019          }
1020          return findParents(clickNode.parent, parents);
1021        }
1022        findParents(clickNode, []);
1023      }
1024    }
1025
1026    retains.sort(function (a: any, b: any) {
1027      return a.distance - b.distance;
1028    });
1029
1030    let filterRetains = new Array<ConstructorItem>();
1031    for (let item of retains) {
1032      if (item.id !== this.rootNode!.id) {
1033        filterRetains.push(item);
1034      }
1035    }
1036    return filterRetains;
1037  }
1038
1039  public getNodes(): Array<HeapNode> {
1040    return this.nodes;
1041  }
1042
1043  public clear() {
1044    this.allClasses?.clear();
1045    this.diffToOtherFile.clear();
1046    this.nodeMap.clear();
1047    this.edges.length = 0;
1048    this.nodes.length = 0;
1049  }
1050}
1051
1052class DOMState {
1053  visited: Uint8Array;
1054  attached: Array<number>;
1055  detached: Array<number>;
1056
1057  constructor(nodeSize: number) {
1058    this.visited = new Uint8Array(nodeSize);
1059    this.attached = new Array<number>();
1060    this.detached = new Array<number>();
1061  }
1062}
1063