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