• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2024 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 { BaseElement } from '../../base-ui/BaseElement.js';
17import { TabPaneTreeHtml } from './MemoryTree.html.js';
18export class MemoryTree extends BaseElement {
19
20  constructor() {
21    super();
22    this.treeDataSource = [];
23    this.maxTreeDataSource = [];
24    this.treeNodes = [];
25    this.expandedNodeList = new Set();
26    this.selectTreeDepth = 0;
27    this.expansionClickEvent = () => {
28      this.expandedNodeList.clear();
29      if (this.expansionUpIcon.name === 'down') {
30        this.selectTreeDepth = 0;
31        this.expansionUpIcon.name = 'up';
32        this.expansionDownIcon.name = 'down';
33      } else {
34        this.selectTreeDepth = 4;
35        this.expansionUpIcon.name = 'down';
36        this.expansionDownIcon.name = 'up';
37      }
38      this.refreshSelectDepth(this.treeNodes);
39      this.refreshRowNodeTable(true);
40    };
41  }
42
43  set data(treeDatas) {
44    this.treeDataSource = [];
45    this.expandedNodeList.clear();
46    this.expansionUpIcon.name = 'up';
47    this.expansionDownIcon.name = 'down';
48    this.apSummaryTable.innerHTML = '';
49    let firstTimestamp = treeDatas[0].timestamp;
50    let data = treeDatas.filter(item => item.stage !== '')
51     .map(item => (Object.assign(Object.assign({}, item),
52     { timestamp: item.timestamp - firstTimestamp,
53      parentStage: item.parentStage === undefined ? '' : item.parentStage })));
54    this.filterMaxData(data);
55    this.treeDataSource = data;
56    if (this.treeDataSource.length !== 0) {
57      this.refreshRowNodeTable();
58    }
59  }
60
61  initElements() {
62    this.apSummaryTable = this.shadowRoot.querySelector('#tab-summary');
63    this.expansionDiv = this.shadowRoot.querySelector('.expansion-div');
64    this.expansionUpIcon = this.shadowRoot.querySelector('.expansion-up-icon');
65    this.expansionDownIcon = this.shadowRoot.querySelector('.expansion-down-icon');
66    let summaryTreeLevel = ['ParentStage', '->Stage', '->Timestamp'];
67    this.shadowRoot.querySelectorAll('.head-label').forEach((summaryTreeHead) => {
68      summaryTreeHead.addEventListener('click', () => {
69        this.selectTreeDepth = summaryTreeLevel.indexOf(summaryTreeHead.textContent);
70        this.expandedNodeList.clear();
71        this.refreshSelectDepth(this.treeNodes);
72        this.refreshRowNodeTable(true);
73      });
74    });
75    this.apSummaryTable.onscroll = () => {
76      let treeTableEl = this.shadowRoot.querySelector('.log-tree-table');
77      if (treeTableEl) {
78        treeTableEl.scrollTop = this.apSummaryTable.scrollTop || 0;
79      }
80    };
81  }
82
83  filterMaxData(data) {
84    const groupedData = data.reduce((groups, item) => {
85    const key = `${item.stage}-${item.parentStage}`;
86    if (!Object.prototype.hasOwnProperty.call(groups, key)) {
87        groups[key] = [];
88    }
89    groups[key].push(item);
90    return groups;
91    }, {});
92    const maxData = [];
93    for (const key of Object.keys(groupedData)) {
94        const group = groupedData[key];
95        const maxRss = Math.max(...group.map(item => item.rss));
96        const maxRssItem = group.find(item => item.rss === maxRss);
97        if (maxRssItem) {
98          maxData.push(Object.assign(Object.assign({}, maxRssItem)));
99        }
100     }
101     this.maxTreeDataSource = maxData;
102  }
103
104  initHtml() {
105    return TabPaneTreeHtml;
106  }
107
108  connectedCallback() {
109    super.connectedCallback();
110    new ResizeObserver(() => {
111      this.parentElement.style.overflow = 'hidden';
112      this.refreshRowNodeTable();
113    }).observe(this.parentElement);
114    this.expansionDiv.addEventListener('click', this.expansionClickEvent);
115  }
116
117  disconnectedCallback() {
118    super.disconnectedCallback();
119    this.expansionDiv.removeEventListener('click', this.expansionClickEvent);
120  }
121
122  refreshSelectDepth(treeNodes) {
123    treeNodes.forEach((item) => {
124      if (item.depth < this.selectTreeDepth) {
125        this.expandedNodeList.add(item.id);
126        if (item.children.length > 0) {
127          this.refreshSelectDepth(item.children);
128        }
129      }
130    });
131  }
132
133  getStageMax(map) {
134   this.maxTreeDataSource.forEach(item => {
135   let stage = item.stage + item.parentStage;
136   if (map && map.get(stage)) {
137     if (item.rss > 0) {
138       map.get(stage).rss = item.rss;
139     }
140    } else {
141      map.set(stage, { rss: item.rss });
142    }
143    return map;
144    });
145  }
146
147  createTd(rowNode, rowNodeTextEL, type) {
148    let td = document.createElement('td');
149    if (type === 'rss') {
150      td.textContent = rowNode.rss;
151      td.className = 'rss-column-td';
152      if (rowNode.depth === 0) {
153        rowNodeTextEL.style.color = '#00000';
154        td.style.color = '#00000';
155      }
156    }
157    return td;
158  }
159
160  createRowNodeTableEL(rowNodeList, tableTreeEl, tableRssEl, rowColor = '') {
161    let unitPadding = 20;
162    let leftPadding = 5;
163    let map = new Map();
164    this.getStageMax(map);
165    rowNodeList.forEach((rowNode) => {
166    let tableTreeRowEl = document.createElement('tr');
167    tableTreeRowEl.className = 'tree-row-tr';
168    tableTreeRowEl.title = rowNode.parentStageName + '';
169    let leftSpacingEl = document.createElement('td');
170    leftSpacingEl.style.paddingLeft = `${rowNode.depth * unitPadding + leftPadding}px`;
171    tableTreeRowEl.appendChild(leftSpacingEl);
172    this.addToggleIconEl(rowNode, tableTreeRowEl);
173    let rowNodeTextEL = document.createElement('td');
174    rowNodeTextEL.textContent = rowNode.parentStageName + '';
175    rowNodeTextEL.className = 'row-name-td';
176    tableTreeRowEl.appendChild(rowNodeTextEL);
177    tableTreeEl.appendChild(tableTreeRowEl);
178    let rssRow = document.createElement('tr');
179    rssRow.title = rowNode.rss;
180    let rssTd = this.createTd(rowNode, rowNodeTextEL, 'rss');
181    rssRow.appendChild(rssTd);
182    tableRssEl.appendChild(rssRow);
183    if (map.get(rowNode.stage + rowNode.parentStage)) {
184      if (this.convertToMB(map.get(rowNode.stage + rowNode.parentStage).rss) === rowNode.rss) {
185        rssTd.style.color = '#FF4040';
186      }
187    }
188    if (rowNode.children && this.expandedNodeList.has(rowNode.id)) {
189      this.createRowNodeTableEL(rowNode.children, tableTreeEl, tableRssEl, rssTd.style.color);
190    }
191   });
192  }
193
194  addToggleIconEl(rowNode, tableRowEl) {
195    let toggleIconEl = document.createElement('td');
196    let expandIcon = document.createElement('lit-icon');
197    expandIcon.classList.add('tree-icon');
198    if (rowNode.children && rowNode.children.length > 0) {
199      toggleIconEl.appendChild(expandIcon);
200      expandIcon.name = this.expandedNodeList.has(rowNode.id) ? 'minus-square' : 'plus-square';
201      toggleIconEl.classList.add('expand-icon');
202      toggleIconEl.addEventListener('click', () => {
203        let scrollTop = this.apSummaryTable.scrollTop ?? 0;
204        this.changeNode(rowNode.id);
205        this.apSummaryTable.scrollTop = scrollTop;
206      });
207    }
208    tableRowEl.appendChild(toggleIconEl);
209  }
210
211  changeNode(currentNode) {
212    if (this.expandedNodeList.has(currentNode)) {
213      this.expandedNodeList.delete(currentNode);
214    } else {
215      this.expandedNodeList.add(currentNode);
216    }
217    this.refreshRowNodeTable();
218  }
219
220  refreshRowNodeTable(useCacheRefresh = false) {
221    this.apSummaryTable.innerHTML = '';
222    if (this.apSummaryTable && this.parentElement) {
223      this.apSummaryTable.style.height = `${this.parentElement.clientHeight - 100}px`;
224    }
225    if (!useCacheRefresh) {
226      this.treeNodes = this.buildTreeTblNodes(this.treeDataSource);
227    }
228    let tableFragmentEl = document.createDocumentFragment();
229    let tableTreeEl = document.createElement('div');
230    tableTreeEl.className = 'log-tree-table';
231    let tableRssEl = document.createElement('div');
232    if (this.parentElement) {
233      tableTreeEl.style.height = `${this.parentElement.clientHeight - 40}px`;
234    }
235    this.createRowNodeTableEL(this.treeNodes, tableTreeEl, tableRssEl, '');
236    let emptyTr = document.createElement('tr');
237    emptyTr.className = 'tree-row-tr';
238    tableTreeEl === null || tableTreeEl === void 0 ? void 0 : tableTreeEl.appendChild(emptyTr);
239    let emptyRssTr = document.createElement('tr');
240    emptyRssTr.className = 'tree-row-tr';
241    tableRssEl === null || tableRssEl === void 0 ? void 0 : tableRssEl.appendChild(emptyRssTr);
242    tableFragmentEl.appendChild(tableTreeEl);
243    tableFragmentEl.appendChild(tableRssEl);
244    this.apSummaryTable.appendChild(tableFragmentEl);
245	this.apSummaryTable.scrollTop = this.apSummaryTable.scrollTop + 1;
246  }
247
248  convertToMB(value) {
249    return (value / (1024 * 1024)).toFixed(8) + 'MB';
250  }
251
252  buildTreeTblNodes(apTreeNodes) {
253    let id = 0;
254    let root = { id: id, depth: 0, children: [], parentStageName: 'All', parentStage: '',
255      stage: '', rss: ''};
256    apTreeNodes.forEach((item) => {
257      id++;
258      let parentNode = root.children.find((node) => node.parentStageName === item.parentStage);
259      if (!parentNode) {
260        id++;
261        parentNode = {id: id, depth: 0, children: [], parentStageName: item.parentStage, parentStage: '-',
262          stage: '-', rss: '-'
263        };
264        root.children.push(parentNode);
265      }
266      let childrenNode = parentNode.children.find((node) => node.parentStageName === item.stage);
267      if (!childrenNode) {
268        id++;
269        childrenNode = {id: id, depth: 1, children: [], parentStageName: item.stage, parentStage: '-',
270          stage: '-', rss: '-'
271        };
272        parentNode.children.push(childrenNode);
273      }
274      let timeNode = childrenNode.children.find((node) => node.parentStageName === item.timestamp);
275      if (!timeNode) {
276        id++;
277        timeNode = { id: id, depth: 2, children: [], parentStageName: item.timestamp + '(ms)',
278          parentStage: item.parentStage, stage: item.stage,
279          rss: this.convertToMB(item.rss)
280        };
281        childrenNode.children.push(timeNode);
282      }
283    });
284    return root.children.sort((leftData, rightData) => {
285      return leftData.stage.localeCompare(rightData.stage);
286    });
287  }
288}
289
290if (!customElements.get('memory-dotting-tree')) {
291   customElements.define('memory-dotting-tree', MemoryTree);
292}
293export class TreeBean {
294  constructor() {
295    this.rss = 0;
296  }
297}
298