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'; 17import { LitTable } from '../../../../../base-ui/table/lit-table'; 18import { SelectionData, SelectionParam } from '../../../../bean/BoxSelection'; 19import { SpSystemTrace } from '../../../SpSystemTrace'; 20import { TraceRow } from '../../base/TraceRow'; 21import { LitSearch } from '../../search/Search'; 22import { resizeObserver } from '../SheetUtils'; 23import { getTabSlicesAsyncFunc, getTabSlicesAsyncCatFunc, getParentDetail, getFuncChildren } from '../../../../database/sql/Func.sql'; 24import { getTabSlices } from '../../../../database/sql/ProcessThread.sql'; 25import { FuncStruct } from '../../../../database/ui-worker/ProcedureWorkerFunc'; 26import { Utils } from '../../base/Utils'; 27 28@element('tabpane-slices') 29export class TabPaneSlices extends BaseElement { 30 private slicesTbl: LitTable | null | undefined; 31 private slicesRange: HTMLLabelElement | null | undefined; 32 private slicesSource: Array<SelectionData> = []; 33 private currentSelectionParam: SelectionParam | undefined; 34 private sliceSearchCount: Element | undefined | null; 35 private isDbClick: boolean = false; 36 set data(slicesParam: SelectionParam) { 37 if (this.currentSelectionParam === slicesParam) { 38 return; 39 } //@ts-ignore 40 this.currentSelectionParam = slicesParam; 41 this.slicesRange!.textContent = `Selected range: ${parseFloat( 42 //@ts-ignore 43 ((slicesParam.rightNs - slicesParam.leftNs) / 1000000.0).toFixed(5) 44 )} ms`; 45 46 //合并SF异步信息,相同pid和tid的name 47 let sfAsyncFuncMap: Map<string, { name: string[]; pid: number, tid: number | undefined }> = new Map(); 48 slicesParam.funAsync.forEach((it: { name: string; pid: number, tid: number | undefined }) => { 49 if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) { 50 let item = sfAsyncFuncMap.get(`${it.pid}-${it.tid}`); 51 item?.name.push(it.name); 52 } else { 53 sfAsyncFuncMap.set(`${it.pid}-${it.tid}`, { 54 name: [it.name], 55 pid: it.pid, 56 tid: it.tid 57 }); 58 } 59 }); 60 61 let filterNameEL: HTMLInputElement | undefined | null = 62 this.shadowRoot?.querySelector<HTMLInputElement>('#filterName'); 63 filterNameEL?.addEventListener('keyup', (ev) => { 64 if (ev.key.toLocaleLowerCase() === String.fromCharCode(47)) { 65 ev.stopPropagation(); 66 } 67 }); 68 69 this.getSliceDb(slicesParam, filterNameEL, sfAsyncFuncMap, slicesParam.funCatAsync); 70 } 71 72 initElements(): void { 73 this.sliceSearchCount = this.shadowRoot?.querySelector<LitTable>('#search-count'); 74 this.slicesTbl = this.shadowRoot?.querySelector<LitTable>('#tb-slices'); 75 this.slicesRange = this.shadowRoot?.querySelector('#time-range'); 76 let slicesInput = this.shadowRoot?.querySelector('#filterName'); 77 let spApplication = document.querySelector('body > sp-application'); 78 let spSystemTrace = spApplication?.shadowRoot?.querySelector( 79 'div > div.content > sp-system-trace' 80 ) as SpSystemTrace; 81 this.slicesTbl!.addEventListener('column-click', (evt) => { 82 // @ts-ignore 83 this.sortByColumn(evt.detail); 84 }); 85 // @ts-ignore 86 let data; 87 this.slicesTbl!.addEventListener('row-click', (evt) => { 88 // @ts-ignore 89 data = evt.detail.data; 90 if (!this.isDbClick) { 91 this.isDbClick = true; 92 FuncStruct.funcSelect = false; 93 // @ts-ignore 94 data && this.orgnazitionData(data, false); 95 } 96 }); 97 this.slicesTbl!.addEventListener('contextmenu', () => { 98 FuncStruct.funcSelect = true; 99 // @ts-ignore 100 data && this.orgnazitionData(data); 101 }); 102 slicesInput?.addEventListener('input', (e) => { 103 // @ts-ignore 104 this.findName(e.target.value); 105 }); 106 } 107 108 getSliceDb( 109 slicesParam: SelectionParam, 110 filterNameEL: HTMLInputElement | undefined | null, 111 sfAsyncFuncMap: Map<string, { name: string[]; pid: number, tid: number | undefined }>, 112 ghAsyncFunc: { threadName: string; pid: number }[]): void { 113 //获取SF异步Func数据 114 let result1 = (): Array<unknown> => { 115 let promises: unknown[] = []; 116 sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => { 117 let res = await getTabSlicesAsyncFunc(item.name, item.pid, item.tid, slicesParam.leftNs, slicesParam.rightNs); 118 if (res !== undefined && res.length > 0) { 119 promises.push(...res); 120 } 121 }); 122 return promises; 123 }; 124 125 //获取GH异步Func数据 126 let result2 = (): Array<unknown> => { 127 let promises: unknown[] = []; 128 ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => { 129 let res = await getTabSlicesAsyncCatFunc(item.threadName, item.pid, slicesParam.leftNs, slicesParam.rightNs); 130 if (res !== undefined && res.length > 0) { 131 promises.push(...res); 132 } 133 }); 134 return promises; 135 }; 136 137 //获取同步Func数据 138 let result3 = async (): Promise<unknown> => { 139 let promises: unknown[] = []; 140 let res = await getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs); 141 if (res !== undefined && res.length > 0) { 142 promises.push(...res); 143 } 144 return promises; 145 }; 146 147 this.slicesTbl!.loading = true; 148 Promise.all([result1(), result2(), result3()]).then(async res => { 149 let processSlicesResult = (res[0] || []).concat(res[1] || []).concat(res[2] || []); 150 if (processSlicesResult !== null && processSlicesResult.length > 0) { 151 let funcIdArr: Array<number> = []; 152 let minStartTS = Infinity; 153 let maxEndTS = -Infinity; 154 // @ts-ignore 155 let parentDetail: [{ 156 startTS: number, 157 endTS: number, 158 depth: number, 159 id: number, 160 name: string 161 }] = await getParentDetail( 162 slicesParam.processIds, 163 slicesParam.funTids, 164 slicesParam.leftNs, 165 slicesParam.rightNs 166 ); 167 // @ts-ignore 168 parentDetail.forEach(item => { 169 funcIdArr.push(item.id); 170 if (item.depth === 0) { 171 if (item.startTS < minStartTS) { 172 minStartTS = item.startTS; 173 } 174 if (item.endTS > maxEndTS) { 175 maxEndTS = item.endTS; 176 } 177 } 178 }); 179 180 let FuncChildrenList = await getFuncChildren(funcIdArr, slicesParam.processIds, slicesParam.funTids, minStartTS, maxEndTS, false); 181 let childDurMap: Map<number, Map<number, number>> = new Map(); 182 FuncChildrenList.forEach((it: unknown) => { // @ts-ignore 183 if (!childDurMap.has(it.parentId)) { // @ts-ignore 184 childDurMap.set(it.parentId, it.duration); 185 } else { // @ts-ignore 186 let dur = childDurMap.get(it.parentId); // @ts-ignore 187 dur += it.duration; // @ts-ignore 188 childDurMap.set(it.parentId, dur!); 189 } 190 }); 191 let sumWall = 0.0; 192 let sumOcc = 0; 193 let sumSelf = 0.0; 194 let processSlicesResultMap: Map<string, unknown> = new Map(); 195 for (let processSliceItem of processSlicesResult) { 196 //@ts-ignore 197 processSliceItem.selfTime = //@ts-ignore 198 childDurMap.has(processSliceItem.id) ? //@ts-ignore 199 parseFloat(((processSliceItem.wallDuration - childDurMap.get(processSliceItem.id)) / 1000000).toFixed(5)) : //@ts-ignore 200 parseFloat((processSliceItem.wallDuration / 1000000).toFixed(5)); 201 //@ts-ignore 202 processSliceItem.name = processSliceItem.name === null ? '' : processSliceItem.name; 203 //@ts-ignore 204 sumWall += processSliceItem.wallDuration; 205 //@ts-ignore 206 sumOcc += processSliceItem.occurrences; 207 //@ts-ignore 208 sumSelf += processSliceItem.selfTime; 209 //@ts-ignore 210 processSliceItem.wallDuration = parseFloat((processSliceItem.wallDuration / 1000000.0).toFixed(5)); 211 //@ts-ignore 212 if (processSlicesResultMap.has(processSliceItem.name)) {//@ts-ignore 213 let item = processSlicesResultMap.get(processSliceItem.name); 214 //@ts-ignore 215 item.occurrences = item.occurrences + processSliceItem.occurrences; 216 //@ts-ignore 217 item.wallDuration = parseFloat((item.wallDuration + processSliceItem.wallDuration).toFixed(5)); 218 } else { 219 //@ts-ignore 220 processSlicesResultMap.set(processSliceItem.name, {//@ts-ignore 221 ...processSliceItem, //@ts-ignore 222 tabTitle: processSliceItem.name 223 }); 224 } 225 } 226 let processSlicesResultsValue = [...processSlicesResultMap.values()]; 227 processSlicesResultsValue.forEach(element => {//@ts-ignore 228 element.avgDuration = parseFloat((element.wallDuration / element.occurrences).toFixed(5)); 229 }); 230 let count = new SelectionData(); 231 count.process = ' '; 232 count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); 233 count.occurrences = sumOcc; 234 count.selfTime = parseFloat(sumSelf.toFixed(5)); 235 count.tabTitle = 'Summary'; 236 //@ts-ignore 237 count.avgDuration = parseFloat((count.wallDuration / count.occurrences).toFixed(5)); 238 // @ts-ignore 239 count.allName = processSlicesResultsValue.map((item: unknown) => item.name); 240 processSlicesResultsValue.splice(0, 0, count); //@ts-ignore 241 this.slicesSource = processSlicesResultsValue; 242 this.slicesTbl!.recycleDataSource = processSlicesResultsValue; 243 this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; 244 if (filterNameEL && filterNameEL.value.trim() !== '') { 245 this.findName(filterNameEL.value); 246 } 247 } else { 248 this.slicesSource = []; 249 this.slicesTbl!.recycleDataSource = this.slicesSource; 250 this.sliceSearchCount!.textContent = '0'; 251 } 252 this.slicesTbl!.loading = false; 253 }); 254 } 255 // @ts-ignore 256 async orgnazitionData(data: Object, isDbClick?: false): unknown { 257 // @ts-ignore 258 if (data!.tabTitle === 'Summary') { 259 FuncStruct.funcSelect = true; 260 } 261 let spApplication = document.querySelector('body > sp-application'); 262 let spSystemTrace = spApplication?.shadowRoot?.querySelector( 263 'div > div.content > sp-system-trace' 264 ) as SpSystemTrace; 265 let search = spApplication!.shadowRoot?.querySelector('#lit-search') as LitSearch; 266 spSystemTrace?.visibleRows.forEach((it) => { 267 it.highlight = false; 268 }); 269 spSystemTrace?.timerShaftEL?.removeTriangle('inverted'); 270 // @ts-ignore 271 let asyncFuncArr = spSystemTrace!.seachAsyncFunc(data.name); 272 // @ts-ignore 273 await spSystemTrace!.searchFunction([], asyncFuncArr, data.name).then((mixedResults) => { 274 if (mixedResults && mixedResults.length === 0) { 275 FuncStruct.funcSelect = true; 276 if (!isDbClick) { 277 this.isDbClick = false; 278 } 279 return; 280 } 281 // @ts-ignore 282 search.list = mixedResults.filter((item) => item.funName === data.name); //@ts-ignore 283 const sliceRowList: Array<TraceRow<unknown>> = []; 284 // 框选的slice泳道 285 for (let row of spSystemTrace.rangeSelect.rangeTraceRow!) { 286 if (row.rowType === 'func') { 287 sliceRowList.push(row); 288 } 289 if (row.childrenList) { 290 for (const childrenRow of row.childrenList) { 291 if (childrenRow.rowType === 'func') { 292 sliceRowList.push(childrenRow); 293 } 294 } 295 } 296 } 297 if (sliceRowList.length === 0) { 298 return; 299 } 300 this.slicesTblFreshSearchSelect(search, sliceRowList, data, spSystemTrace); 301 spSystemTrace?.visibleRows.forEach((it) => { 302 it.draw(); 303 this.isDbClick = false; 304 }); 305 }); 306 } 307 308 private slicesTblFreshSearchSelect( 309 search: LitSearch, //@ts-ignore 310 sliceRowList: Array<TraceRow<unknown>>, 311 data: unknown, 312 spSystemTrace: SpSystemTrace 313 ): void { 314 let input = search.shadowRoot?.querySelector('input') as HTMLInputElement; 315 let rangeSelectList: Array<unknown> = []; // 框选范围的数据 316 // search 到的内容与框选泳道的内容取并集 317 for (const searchItem of search.list) { 318 for (const traceRow of sliceRowList) { 319 if ( 320 // @ts-ignore 321 Math.max(TraceRow.rangeSelectObject?.startNS!, searchItem.startTime) <= 322 // @ts-ignore 323 Math.min(TraceRow.rangeSelectObject?.endNS!, searchItem.startTime + searchItem.dur) && 324 !rangeSelectList.includes(searchItem) 325 ) { 326 // 异步调用栈 327 if (traceRow.asyncFuncName) { 328 if ( 329 // @ts-ignore 330 `${searchItem.pid}` === `${traceRow.asyncFuncNamePID}` && 331 traceRow.traceId === Utils.currentSelectTrace 332 ) { 333 rangeSelectList.push(searchItem); 334 } 335 } else { 336 // 线程调用栈 337 // @ts-ignore 338 if (Utils.getDistributedRowId(searchItem.tid) === traceRow.rowId) { 339 rangeSelectList.push(searchItem); 340 } 341 } 342 } 343 } 344 } 345 346 if (rangeSelectList.length === 0) { 347 return; 348 } //@ts-ignore 349 input.value = data.name; 350 //@ts-ignore 351 search.currenSearchValue = data.name; 352 search.list = rangeSelectList; 353 search.total = search.list.length; 354 search.index = spSystemTrace!.showStruct(false, -1, search.list); 355 search.isClearValue = true; 356 } 357 358 connectedCallback(): void { 359 super.connectedCallback(); 360 resizeObserver(this.parentElement!, this.slicesTbl!); 361 } 362 363 initHtml(): string { 364 return ` 365 <style> 366 .slice-label{ 367 height: 20px; 368 } 369 :host{ 370 display: flex; 371 padding: 10px 10px; 372 flex-direction: column; 373 } 374 #filterName:focus{ 375 outline: none; 376 } 377 </style> 378 <div style="display:flex; justify-content:space-between;"> 379 <div style="width: 40%;"> 380 <input id="filterName" type="text" style="width:60%;height:18px;border:1px solid #c3c3c3;border-radius:9px" placeholder="Search" value="" /> 381 <span style="font-size: 10pt;margin-bottom: 5px">Count: <span id="search-count">0<span></span> 382 </div> 383 <label id="time-range" class="slice-label" style="text-align: end;font-size: 10pt;margin-bottom: 5px">Selected range:0.0 ms</label> 384 </div> 385 <lit-table id="tb-slices" style="height: auto"> 386 <lit-table-column class="slices-column" title="Name" width="500px" data-index="name" 387 key="name" align="flex-start" order> 388 </lit-table-column> 389 <lit-table-column class="slices-column" title="Wall duration(ms)" width="1fr" data-index="wallDuration" 390 key="wallDuration" align="flex-start" order > 391 </lit-table-column> 392 <lit-table-column class="slices-column" title="Avg Wall duration(ms)" width="1fr" data-index="avgDuration" 393 key="avgDuration" align="flex-start" order > 394 </lit-table-column> 395 <lit-table-column class="slices-column" title="Occurrences" width="1fr" data-index="occurrences" 396 key="occurrences" align="flex-start" order tdJump> 397 </lit-table-column> 398 <lit-table-column class="slices-column" title="selfTime(ms)" width="1fr" data-index="selfTime" key="selfTime" align="flex-start" order> 399 </lit-table-column> 400 </lit-table> 401 `; 402 } 403 404 sortByColumn(slicesDetail: unknown): void { 405 // @ts-ignore 406 function compare(property, slicesSort, type) { 407 return function (slicesLeftData: SelectionData, slicesRightData: SelectionData) { 408 if (slicesLeftData.process === ' ' || slicesRightData.process === ' ') { 409 return 0; 410 } 411 if (type === 'number') { 412 // @ts-ignore 413 return slicesSort === 2 414 ? // @ts-ignore 415 parseFloat(slicesRightData[property]) - parseFloat(slicesLeftData[property]) 416 : // @ts-ignore 417 parseFloat(slicesLeftData[property]) - parseFloat(slicesRightData[property]); 418 } else { 419 // @ts-ignore 420 if (slicesRightData[property] > slicesLeftData[property]) { 421 return slicesSort === 2 ? 1 : -1; 422 } else { 423 // @ts-ignore 424 if (slicesRightData[property] === slicesLeftData[property]) { 425 return 0; 426 } else { 427 return slicesSort === 2 ? -1 : 1; 428 } 429 } 430 } 431 }; 432 } 433 // 拷贝当前表格显示的数据 434 let sortData: Array<SelectionData> = JSON.parse(JSON.stringify(this.slicesTbl!.recycleDataSource)); 435 // 取出汇总数据,同时将排序数据去掉汇总数据进行后续排序 436 let headData: SelectionData = sortData.splice(0, 1)[0]; 437 //@ts-ignore 438 if (slicesDetail.key === 'name') { 439 //@ts-ignore 440 sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'string')); 441 } else { 442 //@ts-ignore 443 sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'number')); 444 } 445 // 排序完成后将汇总数据插入到头部 446 sortData.unshift(headData); 447 this.slicesTbl!.recycleDataSource = sortData; 448 this.sliceSearchCount!.textContent = sortData.length - 1 + ''; 449 } 450 451 findName(str: string): void { 452 let searchData: Array<SelectionData> = []; 453 let sumWallDuration: number = 0; 454 let sumOccurrences: number = 0; 455 let nameSet: Array<string> = []; 456 if (str === '') { 457 this.slicesTbl!.recycleDataSource = this.slicesSource; 458 this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; 459 } else { 460 this.slicesSource.forEach((item) => { 461 if (item.name.toLowerCase().indexOf(str.toLowerCase()) !== -1) { 462 searchData.push(item); 463 nameSet.push(item.name); 464 sumWallDuration += item.wallDuration; 465 sumOccurrences += item.occurrences; 466 } 467 }); 468 let count: SelectionData = new SelectionData(); 469 count.process = ''; 470 count.name = ''; 471 count.allName = nameSet; 472 count.tabTitle = 'Summary'; 473 count.wallDuration = Number(sumWallDuration.toFixed(3)); 474 count.occurrences = sumOccurrences; 475 searchData.unshift(count); 476 this.slicesTbl!.recycleDataSource = searchData; 477 this.sliceSearchCount!.textContent = searchData.length - 1 + ''; 478 } 479 } 480} 481