• 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';
17import { AllocationFunction, ConstructorComparison, ConstructorItem, ConstructorType } from '../model/UiStruct';
18import {
19  DetachedNessState,
20  EdgeType,
21  FileStruct,
22  HeapEdge,
23  HeapNode,
24  HeapSample,
25  HeapTraceFunctionInfo,
26  NodeType,
27} from '../model/DatabaseStruct';
28import { HeapNodeToConstructorItem } from '../utils/Utils';
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): void => {
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(): AllocationLogic {
79    return this.allocationLogic;
80  }
81
82  private preprocess(): void {
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): void {
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): number[] {
124    return this.allocationLogic.getFunctionNodeIds(id);
125  }
126
127  getAllocation(): AllocationLogic {
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 buildOrderIdxOrphans(
217    postOrderIdx: number,
218    visited: Uint8Array,
219    nodeIdx2OrderIdx: Uint32Array,
220    orderIdx2NodeIdx: Uint32Array
221  ): void {
222    if (postOrderIdx !== this.nodeCount) {
223      // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
224      // visited after all orphan nodes and their subgraphs.
225      --postOrderIdx;
226      for (let i = 0; i < this.nodeCount; i++) {
227        if (visited[i]) {
228          continue;
229        }
230        // Fix it by giving the node a postorder index anyway.
231        nodeIdx2OrderIdx[i] = postOrderIdx;
232        orderIdx2NodeIdx[postOrderIdx++] = i;
233      }
234      nodeIdx2OrderIdx[this.rootNode!.nodeIndex] = postOrderIdx;
235      orderIdx2NodeIdx[postOrderIdx++] = this.rootNode!.nodeIndex;
236    }
237  }
238
239  private buildOrderIdxRootNode(stackNodes: Uint32Array, stackCurrentEdge: Uint32Array, visited: Uint8Array): number {
240    let stack = 0;
241    stackNodes[0] = this.rootNode!.nodeIndex;
242    stackCurrentEdge[0] = this.nodes[this.rootNode!.nodeIndex + 1].firstEdgeIndex;
243    for (let node of this.nodes) {
244      if (visited[node.nodeIndex] || !this.hasOnlyWeakRetainers(node)) {
245        continue;
246      }
247      stackNodes[++stack] = node.nodeIndex;
248      stackCurrentEdge[stack] = node.firstEdgeIndex;
249      visited[node.nodeIndex] = 1;
250    }
251    return stack;
252  }
253
254  private buildOrderIdxInit(): {
255    stackNodes: Uint32Array;
256    stackCurrentEdge: Uint32Array;
257    orderIdx2NodeIdx: Uint32Array;
258    nodeIdx2OrderIdx: Uint32Array;
259    visited: Uint8Array;
260  } {
261    const state = {
262      stackNodes: new Uint32Array(this.nodeCount),
263      stackCurrentEdge: new Uint32Array(this.nodeCount),
264      orderIdx2NodeIdx: new Uint32Array(this.nodeCount),
265      nodeIdx2OrderIdx: new Uint32Array(this.nodeCount),
266      visited: new Uint8Array(this.nodeCount),
267    };
268    state.stackNodes[0] = this.rootNode!.nodeIndex;
269    state.visited[this.rootNode!.nodeIndex] = 1;
270    return state;
271  }
272  private buildOrderIdxAndDominateTree(): Uint32Array {
273    const params = this.buildOrderIdxInit();
274    let iteration = 0;
275    let postOrderIdx = 0;
276    let stack = 0;
277    while (true) {
278      ++iteration;
279      while (stack >= 0 && stack < this.nodeCount) {
280        let nodeIndex = params.stackNodes[stack];
281        let node = this.nodes[nodeIndex];
282        if (!node) {
283          continue;
284        }
285        let edgeIndex = params.stackCurrentEdge[stack];
286        let edgeEnd = node.firstEdgeIndex + node.edgeCount;
287        if (edgeIndex < edgeEnd) {
288          params.stackCurrentEdge[stack] += 1;
289          let edge = this.edges[edgeIndex];
290          if (!this.isEssentialEdge(edge, node.id)) {
291            continue;
292          }
293          let childNode = this.nodeMap.get(edge.toNodeId);
294          if (!childNode || params.visited[childNode!.nodeIndex]) {
295            continue;
296          }
297          //Skip the edges from non-page-object nodes to page-object nodes
298          let childNodeFlag = childNode.flag & PAGE_PROJECT;
299          if (node.id !== this.rootNode!.id && childNodeFlag && !node.flag) {
300            continue;
301          }
302          ++stack;
303          params.stackNodes[stack] = childNode.nodeIndex;
304          params.stackCurrentEdge[stack] = childNode.firstEdgeIndex;
305          params.visited[childNode.nodeIndex] = 1;
306        } else {
307          params.nodeIdx2OrderIdx[node.nodeIndex] = postOrderIdx;
308          params.orderIdx2NodeIdx[postOrderIdx++] = node.nodeIndex;
309          --stack;
310        }
311      }
312      if (postOrderIdx === this.nodeCount || iteration > 1) {
313        break;
314      }
315
316      // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
317      // visited after all orphan nodes and their subgraphs.
318      --postOrderIdx;
319      stack = this.buildOrderIdxRootNode(params.stackNodes, params.stackCurrentEdge, params.visited);
320    }
321    // If we already processed all orphan nodes that have only weak retainers and still have some orphans...
322    this.buildOrderIdxOrphans(postOrderIdx, params.visited, params.nodeIdx2OrderIdx, params.orderIdx2NodeIdx);
323    this.buildDominatorTree(params.orderIdx2NodeIdx, params.nodeIdx2OrderIdx);
324    return params.orderIdx2NodeIdx;
325  }
326
327  private affectedNode(affected: Uint8Array, nodeIdx2OrderIdx: Uint32Array): void {
328    // 标记root节点的子节点为affected.
329    for (let edge of this.rootNode!.edges) {
330      if (!this.isEssentialEdge(edge, this.rootNode!.id)) {
331        continue;
332      }
333      let childNode = this.nodeMap.get(edge.toNodeId);
334      if (childNode) {
335        affected[nodeIdx2OrderIdx[childNode.nodeIndex]] = 1;
336      }
337    }
338  }
339
340  private buildDominatorTreeLogic(
341    nodeIdx: number,
342    rootOrderedIdx: number,
343    dominators: Uint32Array,
344    nodeIdx2OrderIdx: Uint32Array
345  ): number {
346    let node = this.nodes[nodeIdx];
347    let nodeFlag = node.flag & PAGE_PROJECT;
348    let newDominatorIdx = this.nodeCount;
349    let retainerStart = this.firstRetainerIndex[nodeIdx];
350    let retainerEnd = this.firstRetainerIndex[nodeIdx + 1];
351    let orphanNode = true;
352    for (let idx = retainerStart; idx < retainerEnd; idx++) {
353      let retainerNodeIdx = this.retainingNodes[idx];
354      let retainerEdgeIdx = this.retainingEdges[idx];
355      let node = this.nodes[retainerNodeIdx];
356      let edge = this.edges[retainerEdgeIdx];
357      if (!this.isEssentialEdge(edge, node.id)) {
358        continue;
359      }
360      orphanNode = false;
361      let retainerNodeFlag = node.flag & PAGE_PROJECT;
362      if (retainerNodeIdx !== this.rootNode?.nodeIndex && nodeFlag && !retainerNodeFlag) {
363        continue;
364      }
365      let retainerOrderIdx = nodeIdx2OrderIdx[retainerNodeIdx];
366      if (dominators[retainerOrderIdx] !== this.nodeCount) {
367        if (newDominatorIdx === this.nodeCount) {
368          newDominatorIdx = retainerOrderIdx;
369        } else {
370          while (retainerOrderIdx !== newDominatorIdx) {
371            while (retainerOrderIdx < newDominatorIdx) {
372              retainerOrderIdx = dominators[retainerOrderIdx];
373            }
374            while (newDominatorIdx < retainerOrderIdx) {
375              newDominatorIdx = dominators[newDominatorIdx];
376            }
377          }
378        }
379        // If idom has already reached the root, it doesn't make sense
380        // to check other retainers.
381        if (newDominatorIdx === rootOrderedIdx) {
382          break;
383        }
384      }
385    }
386    // Make root dominator of orphans.
387    if (orphanNode) {
388      newDominatorIdx = rootOrderedIdx;
389    }
390    return newDominatorIdx;
391  }
392  // The algorithm is based on the article:
393  // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
394  // Softw. Pract. Exper. 4 (2001), pp. 1-10.
395  private buildDominatorTree(orderIdx2NodeIdx: Uint32Array, nodeIdx2OrderIdx: Uint32Array): void {
396    let rootOrderedIdx = this.nodeCount - 1;
397    let dominators = new Uint32Array(this.nodeCount).fill(this.nodeCount);
398    dominators[rootOrderedIdx] = rootOrderedIdx;
399
400    // The affected array is used to mark entries which dominators
401    // have to be racalculated because of changes in their retainers.
402    let affected = new Uint8Array(this.nodeCount);
403
404    // 标记root节点的子节点为affected.
405    this.affectedNode(affected, nodeIdx2OrderIdx);
406
407    let changed = true;
408    let nodeIdx;
409    while (changed) {
410      changed = false;
411      for (let orderIdx = rootOrderedIdx - 1; orderIdx >= 0; --orderIdx) {
412        // If dominator of the entry has already been set to root,
413        // then it can't propagate unknown further.
414        if (affected[orderIdx] === 0) {
415          continue;
416        }
417        affected[orderIdx] = 0;
418        if (dominators[orderIdx] === rootOrderedIdx) {
419          continue;
420        }
421        nodeIdx = orderIdx2NodeIdx[orderIdx];
422        const newDominatorIdx = this.buildDominatorTreeLogic(nodeIdx, rootOrderedIdx, dominators, nodeIdx2OrderIdx);
423
424        if (newDominatorIdx !== this.nodeCount && dominators[orderIdx] !== newDominatorIdx) {
425          dominators[orderIdx] = newDominatorIdx;
426          changed = true;
427          nodeIdx = orderIdx2NodeIdx[orderIdx];
428          let node = this.nodes[nodeIdx];
429          for (let edge of node.edges) {
430            let childNode = this.nodeMap.get(edge.toNodeId);
431            if (childNode) {
432              affected[nodeIdx2OrderIdx[childNode.nodeIndex]] = 1;
433            }
434          }
435        }
436      }
437    }
438
439    for (let orderIdx = 0; orderIdx < dominators.length; orderIdx++) {
440      nodeIdx = orderIdx2NodeIdx[orderIdx];
441      this.dominatorTree[nodeIdx] = orderIdx2NodeIdx[dominators[orderIdx]];
442    }
443  }
444
445  private calDistances(): void {
446    if (!this.rootNode) {
447      return;
448    }
449    let nodesToVisit = new Uint32Array(this.nodeCount);
450    let nodesToVisitLen = 0;
451    // root node's edges distance is 1
452    for (let edge of this.rootNode.edges) {
453      let node = this.nodeMap.get(edge.toNodeId);
454      if (!node) {
455        continue;
456      }
457      if (node.isUserRoot() || node.isDocumentDOMTreesRoot()) {
458        node.distance = 1;
459        nodesToVisit[nodesToVisitLen++] = node.id;
460      }
461    }
462    this.bfs(nodesToVisit, nodesToVisitLen);
463
464    // 如果roo节点下有能访问到的子节点,将root节点的distance设置为100000000,否则为0
465    this.rootNode.distance = nodesToVisitLen > 0 ? BASE_SYSTEM_DISTANCE : 0;
466    // 设置root节点下访问不到的哪些独立节点的distance,从100000000开始计数
467    nodesToVisit[0] = this.rootNode.id;
468    nodesToVisitLen = 1;
469    this.bfs(nodesToVisit, nodesToVisitLen);
470  }
471
472  private calRetainedSize(): void {
473    let orderIdx2NodeIdx = this.buildOrderIdxAndDominateTree();
474    // Propagate retained sizes for each node excluding root.
475    for (let idx = 0; idx < this.nodeCount - 1; idx++) {
476      let node = this.nodes[orderIdx2NodeIdx[idx]];
477      let dominatorNode = this.nodes[this.dominatorTree[node.nodeIndex]];
478      dominatorNode.retainedSize += node.retainedSize;
479    }
480  }
481
482  private buildDominatedNode(): void {
483    //赋值两个数组:
484    //-dominatedNodes是一个连续数组,其中每个节点拥有一个与相应的被支配节点的间隔(可以为空)。
485    //—indexArray 是dominatedNodes中与_nodeIndex位置相同的索引数组。
486    let fromNodeIdx = 0;
487    let toNodeIdx = this.nodeCount;
488
489    if (this.rootNode?.nodeIndex === 0) {
490      fromNodeIdx = 1;
491    } else if (this.rootNode?.nodeIndex === toNodeIdx - 1) {
492      toNodeIdx -= 1;
493    } else {
494      throw new Error('Root node is expected to be either first or last');
495    }
496    for (let nodeIdx = fromNodeIdx; nodeIdx < toNodeIdx; ++nodeIdx) {
497      ++this.firstDominatedNodesIdx[this.dominatorTree[nodeIdx]];
498    }
499    // Put in the first slot of each dominatedNodes slice the count of entries
500    // that will be filled.
501    let firstDominatedNodeIdx = 0;
502    for (let idx = 0; idx < this.nodeCount; idx++) {
503      let dominateCount = (this.dominatedNodes[firstDominatedNodeIdx] = this.firstDominatedNodesIdx[idx]);
504      this.firstDominatedNodesIdx[idx] = firstDominatedNodeIdx;
505      firstDominatedNodeIdx += dominateCount;
506    }
507    this.firstDominatedNodesIdx[this.nodeCount] = this.dominatedNodes.length;
508    // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at
509    // index 0) as it is the only node that dominates itself.
510    for (let nodeIdx = fromNodeIdx; nodeIdx < toNodeIdx; nodeIdx++) {
511      let dominatorIdx = this.dominatorTree[nodeIdx];
512      let dominatorRefIdx = this.firstDominatedNodesIdx[dominatorIdx];
513      dominatorRefIdx += --this.dominatedNodes[dominatorRefIdx];
514      this.dominatedNodes[dominatorRefIdx] = nodeIdx;
515    }
516  }
517
518  private buildSamples(): void {
519    let samples = this.fileStruct.snapshotStruct.samples;
520    if (!samples.length) {
521      return;
522    }
523    for (let node of this.nodes) {
524      if (node.id % 2 === 0) {
525        continue;
526      }
527      let rangeIdx = this.binarySearchNodeInSamples(node.id, samples);
528      if (rangeIdx === samples.length) {
529        continue;
530      }
531      samples[rangeIdx].size += node.selfSize;
532    }
533  }
534
535  getMinAndMaxNodeId(): { minNodeId: number; maxNodeId: number } {
536    return {
537      minNodeId: this.nodes[0].id,
538      maxNodeId: this.nodes[this.nodeCount - 1].id,
539    };
540  }
541
542  private binarySearchNodeInSamples(nodeId: number, samples: Array<HeapSample>): number {
543    let left = 0;
544    let right = samples.length - 1;
545
546    while (left <= right) {
547      const mid = Math.floor((left + right) / 2);
548      const currentSample = samples[mid];
549      if (currentSample.lastAssignedId === nodeId) {
550        return left;
551      } else if (currentSample.lastAssignedId < nodeId) {
552        left = mid + 1;
553      } else {
554        right = mid - 1;
555      }
556    }
557    return left;
558  }
559
560  private filterForBpf(node: HeapNode, edge: HeapEdge): boolean {
561    if (node.type === NodeType.HIDDEN) {
562      return edge.nameOrIndex !== 'sloppy_function_map' || node.name !== 'system / NativeContext';
563    }
564    if (node.type === NodeType.ARRAY) {
565      if (node.name !== '(map descriptors)') {
566        return true;
567      }
568      const index = parseInt(edge.nameOrIndex, 10);
569      return index < 2 || index % 3 !== 1;
570    }
571    return true;
572  }
573
574  /**
575   * 广度优先算法遍历所有能从root节点访问到的节点,设置节点的的distance
576   * @param nodesToVisit 能被访问到的node id 数组
577   * @param nodesToVisitLen 有效的node数量
578   */
579  private bfs(nodesToVisit: Uint32Array, nodesToVisitLen: number): void {
580    let index = 0;
581    while (index < nodesToVisitLen) {
582      let nodeId = nodesToVisit[index++];
583      let node = this.nodeMap.get(nodeId);
584      if (!node) {
585        continue;
586      }
587      let distance = node.distance + 1;
588
589      for (let edge of node.edges) {
590        if (edge.type === EdgeType.WEAK || !this.nodeMap.has(edge.toNodeId)) {
591          continue;
592        }
593        let childNode = this.nodeMap.get(edge.toNodeId);
594        // if distance is set,not set again
595        if (!childNode || childNode.distance !== -5 || !this.filterForBpf(node, edge)) {
596          continue;
597        }
598        childNode.distance = distance;
599        nodesToVisit[nodesToVisitLen++] = childNode.id;
600      }
601    }
602    if (nodesToVisitLen > this.nodeCount) {
603      throw new Error(`BFS failed. Nodes to visit ${nodesToVisitLen} is more than nodes count ${this.nodeCount}`);
604    }
605  }
606
607  private processNode(domState: DOMState, node: HeapNode, newState: number): void {
608    if (domState.visited[node.nodeIndex]) {
609      return;
610    }
611    if (node.type !== NodeType.NATIVE) {
612      domState.visited[node.nodeIndex] = 1;
613      return;
614    }
615
616    node.detachedness = newState;
617
618    if (newState === DetachedNessState.ATTACHED) {
619      domState.attached.push(node.id);
620    } else if (newState === DetachedNessState.DETACHED) {
621      node.displayName = `Detached ${node.name}`;
622      // mark detached dom
623      node.flag |= DETACHED_DOM_NODE;
624      domState.detached.push(node.id);
625    }
626    domState.visited[node.nodeIndex] = 1;
627  }
628
629  /**
630   * Iterates children of a node.
631   */
632  private iterateFilteredChildren(domState: DOMState, node: HeapNode, newState: number): void {
633    for (let edge of node.edges) {
634      if (edge.type === EdgeType.HIDDEN || edge.type === EdgeType.WEAK || edge.type === EdgeType.INVISIBLE) {
635        continue;
636      }
637      let childNode = this.nodeMap.get(edge.toNodeId);
638      if (childNode) {
639        this.processNode(domState, childNode, newState);
640      }
641    }
642  }
643
644  /**
645   * mark the node can reachable from root node
646   */
647  private markQueryableNodes(): void {
648    let list: HeapNode[] = [];
649    let flag = CAN_BE_QUERIED;
650    for (let edge of this.rootNode!.edges) {
651      let childNode = this.nodeMap.get(edge.toNodeId);
652      if (childNode && childNode.isUserRoot()) {
653        list.push(childNode);
654      }
655    }
656
657    while (list.length) {
658      let node = list.pop() as HeapNode;
659      if (!node || node.flag & flag) {
660        continue;
661      }
662      node.flag |= flag;
663      for (let edge of node.edges) {
664        let childNode = this.nodeMap.get(edge.toNodeId);
665        if (
666          !childNode ||
667          childNode.flag & flag ||
668          // @ts-ignore
669          [EdgeType.HIDDEN, EdgeType.INVISIBLE, EdgeType.INTERNAL, EdgeType.WEAK].includes(edge.type)
670        ) {
671          continue;
672        }
673        list.push(childNode);
674      }
675    }
676  }
677
678  private markPageOwnedNodes(): void {
679    let nodesToVisitLen = 0;
680    let nodesToVisit = new Array<HeapNode>(this.nodeCount);
681    let flag = PAGE_PROJECT;
682    if (!this.rootNode) {
683      return;
684    }
685
686    for (let edge of this.rootNode.edges) {
687      let node = this.nodeMap.get(edge.toNodeId);
688      if (!node) {
689        continue;
690      }
691      if (edge.type === EdgeType.ELEMENT) {
692        if (!node.isDocumentDOMTreesRoot()) {
693          continue;
694        }
695      } else if (edge.type !== EdgeType.SHORTCUT) {
696        continue;
697      }
698      nodesToVisit[nodesToVisitLen++] = node;
699      node.flag |= flag;
700    }
701
702    while (nodesToVisitLen) {
703      let node = nodesToVisit[--nodesToVisitLen];
704      for (let edge of node.edges) {
705        let childNode = this.nodeMap.get(edge.toNodeId);
706        if (!childNode || childNode.flag & flag || edge.type === EdgeType.WEAK) {
707          continue;
708        }
709        nodesToVisit[nodesToVisitLen++] = childNode;
710        childNode.flag |= flag;
711      }
712    }
713  }
714
715  private isEssentialEdge(edge: HeapEdge, nodeId: number): boolean {
716    return edge.type !== EdgeType.WEAK && (edge.type !== EdgeType.SHORTCUT || nodeId === this.rootNode!.id);
717  }
718
719  private hasOnlyWeakRetainers(node: HeapNode): boolean {
720    let retainerStart = this.firstRetainerIndex[node.nodeIndex];
721    let retainerEnd = this.firstRetainerIndex[node.nodeIndex + 1];
722    for (let index = retainerStart; index < retainerEnd; index++) {
723      let retainingEdgeIdx = this.retainingEdges[index];
724      let edge = this.edges[retainingEdgeIdx];
725      if (edge.type !== EdgeType.WEAK && edge.type !== EdgeType.SHORTCUT) {
726        return false;
727      }
728    }
729    return true;
730  }
731
732  private initDiff(targetClass: ConstructorItem, baseClass?: ConstructorItem): ConstructorComparison {
733    const diff = new ConstructorComparison();
734    diff.type = ConstructorType.ComparisonType;
735    diff.fileId = this.fileId;
736    diff.targetFileId = targetClass.fileId;
737    diff.nodeName = targetClass.nodeName;
738    targetClass.classChildren.sort((a, b) => a.id - b.id);
739    if (baseClass) {
740      baseClass.classChildren.sort((a, b) => a.id - b.id);
741    }
742    return diff;
743  }
744
745  private calClassDiff(targetClass: ConstructorItem, baseClass?: ConstructorItem): ConstructorComparison | null {
746    let i = 0;
747    let j = 0;
748    let baseLen = baseClass ? baseClass.childCount : 0;
749    let targetLen = targetClass.childCount;
750    const diff = this.initDiff(targetClass, baseClass);
751    // The overlap between the base class and the target class
752    while (i < targetLen && j < baseLen) {
753      let baseNode = baseClass!.classChildren[j];
754      let targetNode = targetClass.classChildren[i];
755      if (targetNode.id < baseNode.id) {
756        diff.deletedIdx.push(targetNode.index);
757        diff.removedCount++;
758        diff.removedSize += targetNode.shallowSize;
759        i++;
760      } else if (targetNode.id > baseNode.id) {
761        diff.addedIndx.push(baseNode.index);
762        diff.addedCount++;
763        diff.addedSize += baseNode.shallowSize;
764        j++;
765      } else {
766        i++;
767        j++;
768      }
769    }
770    // base more then target
771    while (i < targetLen) {
772      let targetNode = targetClass!.classChildren[i];
773      diff.deletedIdx.push(targetNode.index);
774      diff.removedCount++;
775      diff.removedSize += targetNode.shallowSize;
776      i++;
777    }
778    //
779    while (j < baseLen) {
780      let baseNode = baseClass!.classChildren[j];
781      diff.addedIndx.push(baseNode.index);
782      diff.addedCount++;
783      diff.addedSize += baseNode.shallowSize;
784      j++;
785    }
786    diff.deltaCount = diff.addedCount - diff.removedCount;
787    diff.deltaSize = diff.addedSize - diff.removedSize;
788    if (diff.addedCount === 0 && diff.removedCount === 0) {
789      return null;
790    }
791    diff.childCount = diff.addedCount + diff.removedCount;
792    diff.hasNext = true;
793    return diff;
794  }
795
796  private calClassRetainedSize(hasFiler: boolean, classes: Map<string, ConstructorItem>, filter: Function): void {
797    // cal class retained size
798    let list = [this.rootNode];
799    const sizes = [-1];
800    const classesName = [];
801    let seenClassName = new Map<string, boolean>();
802
803    while (list.length) {
804      let node = list.pop();
805      if (!node) {
806        continue;
807      }
808      let nodeClassName = node.className();
809      let seen = Boolean(seenClassName.get(nodeClassName));
810      let dominatorFromIdx = this.firstDominatedNodesIdx[node.nodeIndex];
811      let dominatorToIdx = this.firstDominatedNodesIdx[node.nodeIndex + 1];
812
813      if (!seen && (!hasFiler || filter(node.id)) && (node.selfSize || node.type === NodeType.NATIVE)) {
814        let classItem = classes.get(nodeClassName);
815        if (classItem) {
816          classItem.retainedSize += node.retainedSize;
817          if (dominatorFromIdx !== dominatorToIdx) {
818            seenClassName.set(nodeClassName, true);
819            sizes.push(list.length);
820            classesName.push(nodeClassName);
821          }
822        }
823      }
824
825      for (let idx = dominatorFromIdx; idx < dominatorToIdx; idx++) {
826        let nodeIdx = this.dominatedNodes[idx];
827        let domNode = this.nodes[nodeIdx];
828        list.push(domNode);
829      }
830
831      while (sizes[sizes.length - 1] === list.length) {
832        sizes.pop();
833        nodeClassName = classesName.pop() as string;
834        seenClassName.set(nodeClassName, false);
835      }
836    }
837    if (!hasFiler) {
838      this.allClasses = classes;
839    }
840  }
841
842  private combineNodeWithClassName(classes: Map<string, ConstructorItem>, filter: Function): void {
843    for (let node of this.nodes) {
844      if (!filter(node.id) || (node.selfSize === 0 && node.type !== NodeType.NATIVE)) {
845        continue;
846      }
847
848      if (!classes.has(node.className())) {
849        let classItem = HeapNodeToConstructorItem(node);
850        classItem.fileId = this.fileId;
851        classItem.childCount = 1;
852        classItem.type = ConstructorType.ClassType;
853        classItem.retainedSize = 0;
854        classItem.nodeName = node.className();
855        classItem.hasNext = true;
856        classes.set(node.className(), classItem);
857
858        let instanceItem = classItem.clone();
859        instanceItem.type = ConstructorType.InstanceType;
860        instanceItem.id = node.id;
861        instanceItem.index = node.nodeIndex;
862        instanceItem.childCount = node.edgeCount;
863        instanceItem.retainedSize = node.retainedSize;
864        instanceItem.hasNext = instanceItem.childCount > 0;
865        instanceItem.traceNodeId = node.traceNodeId;
866        classItem.classChildren.push(instanceItem);
867      } else {
868        let classItem = classes.get(node.className());
869        if (!classItem) {
870          continue;
871        }
872        // set min node distance to class distance
873        classItem.distance = Math.min(classItem.distance, node.distance);
874        ++classItem.childCount;
875        classItem.shallowSize += node.selfSize;
876
877        let nodeItem = HeapNodeToConstructorItem(node);
878        nodeItem.fileId = this.fileId;
879        nodeItem.type = ConstructorType.InstanceType;
880        nodeItem.id = node.id;
881        nodeItem.index = node.nodeIndex;
882        nodeItem.childCount = node.edgeCount;
883        nodeItem.hasNext = nodeItem.childCount > 0;
884
885        classItem.classChildren.push(nodeItem);
886      }
887    }
888  }
889
890  public getClassesForSummary(minNodeId?: number, maxNodeId?: number): Map<string, ConstructorItem> {
891    let hasFiler = typeof minNodeId === 'number' && typeof maxNodeId === 'number';
892    function filter(nodeId: number): boolean {
893      if (hasFiler) {
894        if (hasFiler && nodeId >= minNodeId! && nodeId <= maxNodeId!) {
895          return true;
896        } else {
897          return false;
898        }
899      } else {
900        return true;
901      }
902    }
903    if (!hasFiler && this.allClasses) {
904      return this.allClasses;
905    }
906    let classes = new Map<string, ConstructorItem>();
907    this.combineNodeWithClassName(classes, filter);
908    this.calClassRetainedSize(hasFiler, classes, filter);
909    return classes;
910  }
911
912  /**
913   * compare base file and target file, calculate delta size and count to target class
914   * @param targetFileId to compare file's id
915   * @param targetFileClasses to compare file's constructor
916   */
917  public getClassesForComparison(
918    targetFileId: number,
919    targetFileClasses: Map<string, ConstructorItem>
920  ): Map<string, ConstructorComparison> | undefined {
921    // Return the result if it has been obtained before
922    if (this.diffToOtherFile.has(targetFileId)) {
923      return this.diffToOtherFile.get(targetFileId);
924    }
925    // get base file class if not init before
926    if (!this.allClasses) {
927      this.allClasses = this.getClassesForSummary();
928    }
929
930    //deal target class
931    let diffMap = new Map<string, ConstructorComparison>();
932    for (let targetClass of targetFileClasses.values()) {
933      let classes = this.allClasses.get(targetClass.nodeName);
934      let different = this.calClassDiff(targetClass, classes);
935      if (different) {
936        diffMap.set(targetClass.nodeName, different);
937      }
938    }
939
940    // deal base class which is not in target
941    for (let classItem of this.allClasses.values()) {
942      if (targetFileClasses.has(classItem.nodeName)) {
943        continue;
944      }
945      let different = this.calClassDiff(classItem);
946      if (different) {
947        diffMap.set(classItem.nodeName, different);
948      }
949    }
950    this.diffToOtherFile.set(targetFileId, diffMap);
951    return diffMap;
952  }
953
954  /**
955   * Summary get Node Children
956   * @param item select Node
957   * @returns child Nodes
958   */
959  public getNextNode(item: ConstructorItem): Array<ConstructorItem> {
960    if (item.children.length > 0) {
961      return item.children;
962    }
963    // get children from edge
964    let node = this.nodes[item.index];
965    let childNodes: ConstructorItem[] = [];
966    for (let edge of node.edges) {
967      let childNode = this.nodeMap.get(edge.toNodeId);
968      if (!childNode) {
969        continue;
970      }
971      let instanceItem = HeapNodeToConstructorItem(childNode);
972      instanceItem.childCount = instanceItem.edgeCount = childNode.edgeCount;
973      instanceItem.edgeName = edge.nameOrIndex;
974      instanceItem.hasNext = instanceItem.childCount > 0;
975      instanceItem.traceNodeId = childNode.traceNodeId;
976      instanceItem.type = ConstructorType.FiledType;
977      instanceItem.parent = item;
978      childNodes.push(instanceItem);
979    }
980
981    let clickNode = childNodes[0].parent;
982    // If there are duplicate IDs in the third layer and beyond, they will not be expanded again
983    if (clickNode!.type === ConstructorType.FiledType) {
984      this.findParentsFunc(childNodes, clickNode);
985    }
986    let filterChildNodes: ConstructorItem[] = [];
987    for (let item of childNodes) {
988      if (item.id !== this.rootNode!.id) {
989        filterChildNodes.push(item);
990      }
991    }
992    item.children = filterChildNodes;
993    return filterChildNodes;
994  }
995
996  private findParentsFunc(childNodes: ConstructorItem[], clickNode: ConstructorItem): void {
997    function findParents(clickNode: unknown, parents: unknown): unknown {
998      //@ts-ignore
999      if (!clickNode.parent) {
1000        return parents;
1001      }
1002      // add the parent of the current node to the result array
1003      //@ts-ignore
1004      parents.push(clickNode);
1005      for (let childNode of childNodes) {
1006        //@ts-ignore
1007        for (let heapParent of parents) {
1008          if (heapParent.id === childNode!.id) {
1009            childNode.hasNext = false;
1010          }
1011        }
1012      } //@ts-ignore
1013      return findParents(clickNode.parent, parents);
1014    }
1015    findParents(clickNode, []);
1016  }
1017
1018  private combineRetains(node: HeapNode, item: ConstructorItem, retains: Array<ConstructorItem>): void {
1019    for (let i = 0; i < node.retainsNodeIdx.length; i++) {
1020      let retainsNode = this.nodes[node.retainsNodeIdx[i]];
1021      let retainEdge = this.edges[node.retainsEdgeIdx[i]];
1022
1023      if (retainEdge.type === EdgeType.WEAK) {
1024        continue;
1025      }
1026      let retainsItem = HeapNodeToConstructorItem(retainsNode);
1027      retainsItem.edgeName = retainEdge.nameOrIndex;
1028      retainsItem.edgeType = retainEdge.type;
1029      retainsItem.type = ConstructorType.RetainersType;
1030      retainsItem.childCount = retainsNode.retainsNodeIdx.length;
1031      retainsItem.hasNext = retainsNode.retainsNodeIdx.length > 0;
1032      if (item!.type === ConstructorType.RetainersType) {
1033        retainsItem.parent = item;
1034      }
1035      retains.push(retainsItem);
1036    }
1037  }
1038
1039  /**
1040   * get nodes which referenced this node
1041   * @param constructor current node
1042   * @returns reference nodes
1043   */
1044  public getRetains(item: ConstructorItem): Array<ConstructorItem> {
1045    if (item.retains.length > 0) {
1046      return item.retains;
1047    }
1048    if (item.type === ConstructorType.ClassType) {
1049      return [];
1050    }
1051    let node = this.nodes[item.index];
1052    let retains: ConstructorItem[] = [];
1053    if (node && node.retainsEdgeIdx.length === node.retainsNodeIdx.length) {
1054      this.combineRetains(node, item, retains);
1055    }
1056
1057    // 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
1058    for (let childNode of retains) {
1059      let node = this.nodes[childNode.index];
1060      if (node && node.retainsEdgeIdx.length === node.retainsNodeIdx.length) {
1061        for (let i = 0; i < node.retainsNodeIdx.length; i++) {
1062          let retainsNode = this.nodes[node.retainsNodeIdx[i]];
1063          if (node.retainsNodeIdx.length === 1 && retainsNode.id === this.rootNode!.id) {
1064            childNode.hasNext = false;
1065          }
1066        }
1067      }
1068    }
1069
1070    if (retains.length > 0 && retains[0].parent) {
1071      let clickNode = retains[0].parent;
1072      // If there are duplicate IDs in the third layer and beyond, they will not be expanded again
1073      if (clickNode!.type === ConstructorType.RetainersType) {
1074        this.findParentsFunc(retains, clickNode);
1075      }
1076    }
1077
1078    retains.sort(function (a: unknown, b: unknown) {
1079      //@ts-ignore
1080      return a.distance - b.distance;
1081    });
1082
1083    let filterRetains: ConstructorItem[] = [];
1084    for (let item of retains) {
1085      if (item.id !== this.rootNode!.id) {
1086        filterRetains.push(item);
1087      }
1088    }
1089    return filterRetains;
1090  }
1091
1092  public getNodes(): Array<HeapNode> {
1093    return this.nodes;
1094  }
1095
1096  public clear(): void {
1097    this.allClasses?.clear();
1098    this.diffToOtherFile.clear();
1099    this.nodeMap.clear();
1100    this.edges.length = 0;
1101    this.nodes.length = 0;
1102  }
1103}
1104
1105class DOMState {
1106  visited: Uint8Array;
1107  attached: Array<number>;
1108  detached: Array<number>;
1109
1110  constructor(nodeSize: number) {
1111    this.visited = new Uint8Array(nodeSize);
1112    this.attached = [];
1113    this.detached = [];
1114  }
1115}
1116