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