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 { BaseElement, element } from '../../../../../base-ui/BaseElement.js'; 17import { LitTable } from '../../../../../base-ui/table/lit-table.js'; 18import { SelectionParam } from '../../../../bean/BoxSelection.js'; 19import { query, queryNativeHookEventTid } from '../../../../database/SqlLite.js'; 20import { NativeHookCallInfo, NativeHookStatistics } from '../../../../bean/NativeHook.js'; 21import '../TabPaneFilter.js'; 22import { FilterData, TabPaneFilter } from '../TabPaneFilter.js'; 23import '../../../chart/FrameChart.js'; 24import '../../../../../base-ui/slicer/lit-slicer.js'; 25import { FrameChart } from '../../../chart/FrameChart.js'; 26import { ChartMode } from '../../../../bean/FrameChartStruct.js'; 27import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar.js'; 28import { procedurePool } from '../../../../database/Procedure.js'; 29 30@element('tabpane-native-callinfo') 31export class TabPaneNMCallInfo extends BaseElement { 32 private callInfoTbl: LitTable | null | undefined; 33 private tblData: LitTable | null | undefined; 34 private progressEL: LitProgressBar | null | undefined; 35 private loadingList: number[] = []; 36 private callInfoLoadingPage: any; 37 private callInfoSource: Array<NativeHookCallInfo> = []; 38 private rightSource: Array<NativeHookCallInfo> = []; 39 private queryResult: Array<NativeHookStatistics> = []; 40 private native_type: Array<string> = ['All Heap & Anonymous VM', 'All Heap', 'All Anonymous VM']; 41 private filterAllocationType: string = '0'; 42 private filterNativeType: string = '0'; 43 private currentSelection: SelectionParam | undefined; 44 private frameChart: FrameChart | null | undefined; 45 private isChartShow: boolean = false; 46 private sortColumn: string = ''; 47 private sortType: number = 0; 48 49 set data(callInfoParam: SelectionParam | any) { 50 if (callInfoParam == this.currentSelection) { 51 return; 52 } 53 this.currentSelection = callInfoParam; 54 this.initFilterTypes(); 55 let types: Array<string> = []; 56 if (callInfoParam.nativeMemory.indexOf(this.native_type[0]) != -1) { 57 types.push("'AllocEvent'"); 58 types.push("'MmapEvent'"); 59 } else { 60 if (callInfoParam.nativeMemory.indexOf(this.native_type[1]) != -1) { 61 types.push("'AllocEvent'"); 62 } 63 if (callInfoParam.nativeMemory.indexOf(this.native_type[2]) != -1) { 64 types.push("'MmapEvent'"); 65 } 66 } 67 // @ts-ignore 68 this.callInfoTbl?.shadowRoot?.querySelector('.table').style.height = this.parentElement.clientHeight - 20 - 31 + 'px'; 69 // @ts-ignore 70 this.tblData?.shadowRoot?.querySelector('.table').style.height = this.parentElement.clientHeight + 'px'; 71 // @ts-ignore 72 this.tblData?.recycleDataSource = []; 73 // @ts-ignore 74 this.callInfoTbl?.recycleDataSource = []; 75 this.progressEL!.loading = true; 76 this.callInfoLoadingPage.style.visibility = 'visible'; 77 queryNativeHookEventTid(callInfoParam.leftNs, callInfoParam.rightNs, types).then((result) => { 78 this.queryResult = result; 79 this.getDataByNativeMemoryWorker(callInfoParam); 80 }); 81 } 82 83 getDataByNativeMemoryWorker(callInfoParam: SelectionParam | any) { 84 let callInfoArgs = new Map<string, any>(); 85 callInfoArgs.set('data', this.queryResult); 86 callInfoArgs.set('filterAllocType', this.filterAllocationType); 87 callInfoArgs.set('filterEventType', this.filterNativeType); 88 callInfoArgs.set('leftNs', callInfoParam.leftNs); 89 callInfoArgs.set('rightNs', callInfoParam.rightNs); 90 callInfoArgs.set('actionType', 'call-info'); 91 this.startWorker(callInfoArgs, (results: any[]) => { 92 this.tblData!.recycleDataSource = []; 93 this.progressEL!.loading = false; 94 if (results.length > 0) { 95 this.callInfoSource = results; 96 this.sortTreeByColumn(this.sortColumn, this.sortType); 97 this.frameChart!.mode = ChartMode.Byte; 98 this.frameChart!.data = this.callInfoSource; 99 this.frameChart?.updateCanvas(true, this.clientWidth); 100 this.frameChart?.calculateChartData(); 101 } else { 102 this.callInfoSource = []; 103 this.callInfoTbl!.recycleDataSource = []; 104 this.frameChart!.data = []; 105 this.frameChart!.clearCanvas(); 106 } 107 }); 108 } 109 110 startWorker(callInfoArgs: Map<string, any>, handler: Function) { 111 this.loadingList.push(1); 112 this.progressEL!.loading = true; 113 this.callInfoLoadingPage.style.visibility = 'visible'; 114 procedurePool.submitWithName('logic1', 'native-memory-action', callInfoArgs, undefined, (res: any) => { 115 handler(res); 116 this.loadingList.splice(0, 1); 117 if (this.loadingList.length == 0) { 118 this.progressEL!.loading = false; 119 this.callInfoLoadingPage.style.visibility = 'hidden'; 120 } 121 }); 122 } 123 124 getParentTree( 125 src: Array<NativeHookCallInfo>, 126 target: NativeHookCallInfo, 127 parents: Array<NativeHookCallInfo> 128 ): boolean { 129 for (let hook of src) { 130 if (hook.id == target.id) { 131 parents.push(hook); 132 return true; 133 } else { 134 if (this.getParentTree(hook.children as Array<NativeHookCallInfo>, target, parents)) { 135 parents.push(hook); 136 return true; 137 } 138 } 139 } 140 return false; 141 } 142 143 getChildTree(src: Array<NativeHookCallInfo>, eventId: number, children: Array<NativeHookCallInfo>): boolean { 144 for (let hook of src) { 145 if (hook.eventId == eventId && hook.children.length == 0) { 146 children.push(hook); 147 return true; 148 } else { 149 if (this.getChildTree(hook.children as Array<NativeHookCallInfo>, eventId, children)) { 150 children.push(hook); 151 return true; 152 } 153 } 154 } 155 return false; 156 } 157 158 setRightTableData(hook: NativeHookCallInfo) { 159 let parents: Array<NativeHookCallInfo> = []; 160 let children: Array<NativeHookCallInfo> = []; 161 this.getParentTree(this.callInfoSource, hook, parents); 162 let maxEventId = hook.eventId; 163 let maxHeap = 0; 164 165 function findMaxStack(hook: NativeHookCallInfo) { 166 if (hook.children.length == 0) { 167 if (hook.size > maxHeap) { 168 maxHeap = hook.size; 169 maxEventId = hook.eventId; 170 } 171 } else { 172 hook.children.map((hookChild) => { 173 findMaxStack(<NativeHookCallInfo>hookChild); 174 }); 175 } 176 } 177 178 findMaxStack(hook); 179 this.getChildTree(hook.children as Array<NativeHookCallInfo>, maxEventId, children); 180 this.rightSource = parents.reverse().concat(children.reverse()); 181 let len = this.rightSource.length; 182 // @ts-ignore 183 this.tblData?.dataSource = len == 0 ? [] : this.rightSource.slice(1, len); 184 } 185 186 initFilterTypes() { 187 let filter = this.shadowRoot?.querySelector<TabPaneFilter>('#filter'); 188 this.queryResult = []; 189 filter!.firstSelect = '0'; 190 filter!.secondSelect = '0'; 191 this.filterAllocationType = '0'; 192 this.filterNativeType = '0'; 193 } 194 195 sortTreeByColumn(column: string, sort: number) { 196 this.sortColumn = column; 197 this.sortType = sort; 198 this.callInfoTbl!.recycleDataSource = this.sortTree(this.callInfoSource, column, sort); 199 } 200 201 sortTree(arr: Array<NativeHookCallInfo>, column: string, sort: number): Array<NativeHookCallInfo> { 202 let sortArr = arr.sort((callInfoLeftData, callInfoRightData) => { 203 if (column == 'size') { 204 if (sort == 0) { 205 return callInfoLeftData.eventId - callInfoRightData.eventId; 206 } else if (sort == 1) { 207 return callInfoLeftData.size - callInfoRightData.size; 208 } else { 209 return callInfoRightData.size - callInfoLeftData.size; 210 } 211 } else { 212 if (sort == 0) { 213 return callInfoLeftData.eventId - callInfoRightData.eventId; 214 } else if (sort == 1) { 215 return callInfoLeftData.count - callInfoRightData.count; 216 } else { 217 return callInfoRightData.count - callInfoLeftData.count; 218 } 219 } 220 }); 221 sortArr.map((call) => { 222 call.children = this.sortTree(call.children as Array<NativeHookCallInfo>, column, sort); 223 }); 224 return sortArr; 225 } 226 227 showButtomMenu(isShow: boolean) { 228 let filter = this.shadowRoot?.querySelector<TabPaneFilter>('#filter')!; 229 if (isShow) { 230 filter.setAttribute('first', ''); 231 filter.setAttribute('second', ''); 232 } else { 233 filter.removeAttribute('first'); 234 filter.removeAttribute('second'); 235 } 236 } 237 238 initElements(): void { 239 this.callInfoLoadingPage = this.shadowRoot?.querySelector('.nm-call-info-loading'); 240 this.progressEL = this.shadowRoot?.querySelector('.nm-call-info-progress') as LitProgressBar; 241 this.callInfoTbl = this.shadowRoot?.querySelector<LitTable>('#tb-native-callinfo'); 242 this.tblData = this.shadowRoot?.querySelector<LitTable>('#tb-native-data'); 243 this.frameChart = this.shadowRoot?.querySelector<FrameChart>('#framechart'); 244 let pageTab = this.shadowRoot?.querySelector('#show_table'); 245 let pageChart = this.shadowRoot?.querySelector('#show_chart'); 246 this.frameChart?.addChartClickListener((showMenu: boolean) => { 247 this.parentElement!.scrollTo(0, 0); 248 this.showButtomMenu(showMenu); 249 }); 250 251 this.callInfoTbl!.addEventListener('row-click', (e) => { 252 // @ts-ignore 253 let data = e.detail.data as NativeHookCallInfo; 254 this.setRightTableData(data); 255 data.isSelected = true; 256 this.tblData?.clearAllSelection(data); 257 this.tblData?.setCurrentSelection(data); 258 // @ts-ignore 259 if ((e.detail as any).callBack) { 260 // @ts-ignore 261 (e.detail as any).callBack(true); 262 } 263 }); 264 this.tblData!.addEventListener('row-click', (e) => { 265 // @ts-ignore 266 let detail = e.detail.data as NativeHookCallInfo; 267 this.callInfoTbl?.clearAllSelection(detail); 268 detail.isSelected = true; 269 this.callInfoTbl!.scrollToData(detail); 270 // @ts-ignore 271 if ((e.detail as any).callBack) { 272 // @ts-ignore 273 (e.detail as any).callBack(true); 274 } 275 }); 276 this.callInfoTbl!.addEventListener('column-click', (evt) => { 277 this.sortTreeByColumn( 278 // @ts-ignore 279 evt.detail.key == 'countValue' || evt.detail.key == 'countPercent' ? 'countValue' : 'size', evt.detail.sort 280 ); 281 }); 282 283 this.shadowRoot?.querySelector<TabPaneFilter>('#filter')!.getFilterData((data: FilterData) => { 284 this.filterAllocationType = data.firstSelect || '0'; 285 this.filterNativeType = data.secondSelect || '0'; 286 this.getDataByNativeMemoryWorker(this.currentSelection); 287 if (data.icon == 'block') { 288 pageChart?.setAttribute('class', 'show'); 289 pageTab?.setAttribute('class', ''); 290 this.isChartShow = true; 291 this.frameChart!.data = this.callInfoSource; 292 this.frameChart?.calculateChartData(); 293 } else if (data.icon == 'tree') { 294 pageChart?.setAttribute('class', ''); 295 pageTab?.setAttribute('class', 'show'); 296 this.isChartShow = false; 297 this.frameChart!.clearCanvas(); 298 this.callInfoTbl!.reMeauseHeight(); 299 } 300 }); 301 this.initFilterTypes(); 302 } 303 304 connectedCallback() { 305 super.connectedCallback(); 306 this.parentElement!.onscroll = () => { 307 this.frameChart!.tabPaneScrollTop = this.parentElement!.scrollTop; 308 }; 309 let filterHeight = 0; 310 new ResizeObserver((entries) => { 311 let nmCallInfoTabFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement; 312 if (nmCallInfoTabFilter.clientHeight > 0) filterHeight = nmCallInfoTabFilter.clientHeight; 313 if (this.parentElement!.clientHeight > filterHeight) { 314 nmCallInfoTabFilter.style.display = 'flex'; 315 } else { 316 nmCallInfoTabFilter.style.display = 'none'; 317 } 318 if (this.parentElement?.clientHeight != 0) { 319 if (!this.isChartShow) { 320 // @ts-ignore 321 this.callInfoTbl?.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight + 'px'; 322 this.callInfoTbl?.reMeauseHeight(); 323 } else { 324 // @ts-ignore 325 this.frameChart?.updateCanvas(false, entries[0].contentRect.width); 326 this.frameChart?.calculateChartData(); 327 } 328 // @ts-ignore 329 this.callInfoTbl?.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 10 - 31 + 'px'; 330 this.callInfoTbl?.reMeauseHeight(); 331 // @ts-ignore 332 this.tblData?.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 10 - 31 + 'px'; 333 this.tblData?.reMeauseHeight(); 334 this.callInfoLoadingPage.style.height = this.parentElement!.clientHeight - 24 + 'px'; 335 } 336 }).observe(this.parentElement!); 337 } 338 339 initHtml(): string { 340 return ` 341 <style> 342 :host{ 343 padding: 10px 10px 0 10px; 344 display: flex; 345 flex-direction: column; 346 } 347 .nm-call-info-filter { 348 border: solid rgb(216,216,216) 1px; 349 float: left; 350 position: fixed; 351 bottom: 0; 352 width: 100%; 353 } 354 selector{ 355 display: none; 356 } 357 .nm-call-info-progress{ 358 bottom: 33px; 359 position: absolute; 360 height: 1px; 361 left: 0; 362 right: 0; 363 } 364 .nm-call-info-loading{ 365 position: absolute; 366 left: 0; 367 right: 0; 368 bottom: 0; 369 width:100%; 370 background:transparent; 371 z-index: 999999; 372 } 373 .show{ 374 display: flex; 375 flex: 1; 376 } 377 </style> 378 <div class="nm-call-info-content" style="display: flex;flex-direction: row"> 379 <selector id='show_table' class="show"> 380 <lit-slicer style="width:100%"> 381 <div style="width: 65%"> 382 <lit-table id="tb-native-callinfo" style="height: auto" tree> 383 <lit-table-column class="nm-call-info-column" width="60%" title="Symbol Name" data-index="symbolName" key="symbolName" align="flex-start"> 384 </lit-table-column> 385 <lit-table-column class="nm-call-info-column" width="1fr" title="Size" data-index="heapSizeStr" key="heapSizeStr" align="flex-start" order> 386 </lit-table-column> 387 <lit-table-column class="nm-call-info-column" width="1fr" title="%" data-index="heapPercent" key="heapPercent" align="flex-start" order> 388 </lit-table-column> 389 <lit-table-column class="nm-call-info-column" width="1fr" title="Count" data-index="countValue" key="countValue" align="flex-start" order> 390 </lit-table-column> 391 <lit-table-column class="nm-call-info-column" width="1fr" title="%" data-index="countPercent" key="countPercent" align="flex-start" order> 392 </lit-table-column> 393 <lit-table-column class="nm-call-info-column" width="1fr" title=" " data-index="type" key="type" align="flex-start" > 394 <template> 395 <img src="img/library.png" size="20" v-if=" type == 1 "> 396 <img src="img/function.png" size="20" v-if=" type == 0 "> 397 <div v-if=" type == - 1 "></div> 398 </template> 399 </lit-table-column> 400 </lit-table> 401 </div> 402 <lit-slicer-track ></lit-slicer-track> 403 <lit-table id="tb-native-data" no-head style="height: auto;border-left: 1px solid var(--dark-border1,#e2e2e2)" hideDownload> 404 <lit-table-column class="nm-call-info-column" title="" width="60px" data-index="type" key="type" align="flex-start" > 405 <template> 406 <img src="img/library.png" size="20" v-if=" type == 1 "> 407 <img src="img/function.png" size="20" v-if=" type == 0 "> 408 </template> 409 </lit-table-column> 410 <lit-table-column class="nm-call-info-column" width="1fr" title="" data-index="symbolName" key="symbolName" align="flex-start"> 411 </lit-table-column> 412 </lit-table> 413 </lit-slicer> 414 </selector> 415 <selector class="nm-call-info-selector" id='show_chart'> 416 <tab-framechart id='framechart' style='width: 100%;height: auto'> </tab-framechart> 417 </selector> 418 <lit-progress-bar class="progress nm-call-info-progress"></lit-progress-bar> 419 <tab-pane-filter id="filter" class="nm-call-info-filter" icon first second></tab-pane-filter> 420 <div class="loading nm-call-info-loading"></div> 421 </div> 422 `; 423 } 424} 425