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 } 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 36 set data(slicesParam: SelectionParam | unknown) { 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 let asyncNames: Array<string> = []; 46 let asyncPid: Array<number> = []; //@ts-ignore 47 slicesParam.funAsync.forEach((it: unknown) => { 48 //@ts-ignore 49 asyncNames.push(it.name); //@ts-ignore 50 asyncPid.push(it.pid); 51 }); 52 let asyncCatNames: Array<string> = []; 53 let asyncCatPid: Array<number> = [];//@ts-ignore 54 slicesParam.funCatAsync.forEach((it: unknown) => { //@ts-ignore 55 asyncCatNames.push(it.threadName);//@ts-ignore 56 asyncCatPid.push(it.pid); 57 }); 58 this.slicesTbl!.loading = true; 59 let filterNameEL: HTMLInputElement | undefined | null = 60 this.shadowRoot?.querySelector<HTMLInputElement>('#filterName'); 61 filterNameEL?.addEventListener('keyup', (ev) => { 62 if (ev.key.toLocaleLowerCase() === String.fromCharCode(47)) { 63 ev.stopPropagation(); 64 } 65 }); 66 //@ts-ignore 67 getTabSlicesAsyncFunc(asyncNames, asyncPid, slicesParam.leftNs, slicesParam.rightNs).then((res) => {//@ts-ignore 68 getTabSlicesAsyncCatFunc(asyncCatNames, asyncCatPid, slicesParam.leftNs, slicesParam.rightNs).then((res1) => { 69 //@ts-ignore 70 getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs).then( 71 (res2) => { 72 this.slicesTbl!.loading = false; 73 let processSlicesResult = (res || []).concat(res1 || []).concat(res2 || []); 74 if (processSlicesResult !== null && processSlicesResult.length > 0) { 75 let sumWall = 0.0; 76 let sumOcc = 0; 77 for (let processSliceItem of processSlicesResult) { 78 //@ts-ignore 79 processSliceItem.name = processSliceItem.name === null ? '' : processSliceItem.name; 80 //@ts-ignore 81 processSliceItem.tabTitle = processSliceItem.name; 82 //@ts-ignore 83 sumWall += processSliceItem.wallDuration; 84 //@ts-ignore 85 sumOcc += processSliceItem.occurrences; 86 //@ts-ignore 87 processSliceItem.wallDuration = parseFloat((processSliceItem.wallDuration / 1000000.0).toFixed(5)); 88 //@ts-ignore 89 processSliceItem.avgDuration = parseFloat((processSliceItem.avgDuration / 1000000.0).toFixed(5)); 90 //@ts-ignore 91 processSliceItem.asyncNames = asyncNames; 92 //@ts-ignore 93 processSliceItem.asyncCatNames = asyncCatNames; 94 } 95 let count = new SelectionData(); 96 count.process = ' '; 97 count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); 98 count.occurrences = sumOcc; 99 count.tabTitle = 'Summary';//@ts-ignore 100 count.allName = processSlicesResult.map((item: unknown) => item.name); 101 count.asyncNames = asyncNames; 102 count.asyncCatNames = asyncCatNames; 103 processSlicesResult.splice(0, 0, count); //@ts-ignore 104 this.slicesSource = processSlicesResult; 105 this.slicesTbl!.recycleDataSource = processSlicesResult; 106 this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; 107 if (filterNameEL && filterNameEL.value.trim() !== '') { 108 this.findName(filterNameEL.value); 109 } 110 } else { 111 this.slicesSource = []; 112 this.slicesTbl!.recycleDataSource = this.slicesSource; 113 this.sliceSearchCount!.textContent = '0'; 114 } 115 } 116 ); 117 }); 118 }); 119 } 120 121 initElements(): void { 122 this.sliceSearchCount = this.shadowRoot?.querySelector<LitTable>('#search-count'); 123 this.slicesTbl = this.shadowRoot?.querySelector<LitTable>('#tb-slices'); 124 this.slicesRange = this.shadowRoot?.querySelector('#time-range'); 125 let slicesInput = this.shadowRoot?.querySelector('#filterName'); 126 let spApplication = document.querySelector('body > sp-application'); 127 let spSystemTrace = spApplication?.shadowRoot?.querySelector( 128 'div > div.content > sp-system-trace' 129 ) as SpSystemTrace; 130 this.slicesTbl!.addEventListener('column-click', (evt) => { 131 // @ts-ignore 132 this.sortByColumn(evt.detail); 133 }); 134 // @ts-ignore 135 let data; 136 this.slicesTbl!.addEventListener('row-click', (evt) => { 137 // @ts-ignore 138 data = evt.detail.data; 139 }); 140 this.slicesTbl!.addEventListener('click', () => { 141 FuncStruct.funcSelect = false; 142 // @ts-ignore 143 data && this.orgnazitionData(data); 144 }); 145 this.slicesTbl!.addEventListener('contextmenu', () => { 146 FuncStruct.funcSelect = true; 147 // @ts-ignore 148 data && this.orgnazitionData(data); 149 }); 150 slicesInput?.addEventListener('input', (e) => { 151 // @ts-ignore 152 this.findName(e.target.value); 153 }); 154 slicesInput?.addEventListener('focus', (e) => { 155 spSystemTrace.focusTarget = 'slicesInput'; 156 }); 157 slicesInput?.addEventListener('blur', (e) => { 158 spSystemTrace.focusTarget = ''; 159 }); 160 } 161 async orgnazitionData(data: Object): Promise<void> { 162 let spApplication = document.querySelector('body > sp-application'); 163 let spSystemTrace = spApplication?.shadowRoot?.querySelector( 164 'div > div.content > sp-system-trace' 165 ) as SpSystemTrace; 166 let search = spApplication!.shadowRoot?.querySelector('#lit-search') as LitSearch; 167 spSystemTrace?.visibleRows.forEach((it) => { 168 it.highlight = false; 169 it.draw(); 170 }); 171 spSystemTrace?.timerShaftEL?.removeTriangle('inverted'); 172 // @ts-ignore 173 let asyncFuncArr = spSystemTrace!.seachAsyncFunc(data.name); 174 // @ts-ignore 175 await spSystemTrace!.searchFunction([], asyncFuncArr, data.name).then((mixedResults) => { 176 if (mixedResults && mixedResults.length === 0) { 177 return; 178 } 179 // @ts-ignore 180 search.list = mixedResults.filter((item) => item.funName === data.name); //@ts-ignore 181 const sliceRowList: Array<TraceRow<unknown>> = []; 182 // 框选的slice泳道 183 for (let row of spSystemTrace.rangeSelect.rangeTraceRow!) { 184 if (row.rowType === 'func') { 185 sliceRowList.push(row); 186 } 187 if (row.childrenList) { 188 for (const childrenRow of row.childrenList) { 189 if (childrenRow.rowType === 'func') { 190 sliceRowList.push(childrenRow); 191 } 192 } 193 } 194 } 195 if (sliceRowList.length === 0) { 196 return; 197 } 198 this.slicesTblFreshSearchSelect(search, sliceRowList, data, spSystemTrace); 199 }); 200 } 201 202 private slicesTblFreshSearchSelect( 203 search: LitSearch, //@ts-ignore 204 sliceRowList: Array<TraceRow<unknown>>, 205 data: unknown, 206 spSystemTrace: SpSystemTrace 207 ): void { 208 let input = search.shadowRoot?.querySelector('input') as HTMLInputElement; 209 let rangeSelectList: Array<unknown> = []; // 框选范围的数据 210 // search 到的内容与框选泳道的内容取并集 211 for (const searchItem of search.list) { 212 for (const traceRow of sliceRowList) { 213 if ( 214 // @ts-ignore 215 Math.max(TraceRow.rangeSelectObject?.startNS!, searchItem.startTime) <= 216 // @ts-ignore 217 Math.min(TraceRow.rangeSelectObject?.endNS!, searchItem.startTime + searchItem.dur) && 218 !rangeSelectList.includes(searchItem) 219 ) { 220 // 异步调用栈 221 if (traceRow.asyncFuncName) { 222 if ( 223 // @ts-ignore 224 `${searchItem.pid}` === `${traceRow.asyncFuncNamePID}` && 225 traceRow.traceId === Utils.currentSelectTrace 226 ) { 227 rangeSelectList.push(searchItem); 228 } 229 } else { 230 // 线程调用栈 231 // @ts-ignore 232 if (Utils.getDistributedRowId(searchItem.tid) === traceRow.rowId) { 233 rangeSelectList.push(searchItem); 234 } 235 } 236 } 237 } 238 } 239 240 if (rangeSelectList.length === 0) { 241 return; 242 } //@ts-ignore 243 input.value = data.name; 244 //@ts-ignore 245 search.currenSearchValue = data.name; 246 search.list = rangeSelectList; 247 search.total = search.list.length; 248 search.index = spSystemTrace!.showStruct(false, -1, search.list); 249 search.isClearValue = true; 250 } 251 252 connectedCallback(): void { 253 super.connectedCallback(); 254 resizeObserver(this.parentElement!, this.slicesTbl!); 255 } 256 257 initHtml(): string { 258 return ` 259 <style> 260 .slice-label{ 261 height: 20px; 262 } 263 :host{ 264 display: flex; 265 padding: 10px 10px; 266 flex-direction: column; 267 } 268 #filterName:focus{ 269 outline: none; 270 } 271 </style> 272 <div style="display:flex; justify-content:space-between;"> 273 <div style="width: 40%;"> 274 <input id="filterName" type="text" style="width:60%;height:18px;border:1px solid #c3c3c3;border-radius:9px" placeholder="Search" value="" /> 275 <span style="font-size: 10pt;margin-bottom: 5px">Count: <span id="search-count">0<span></span> 276 </div> 277 <label id="time-range" class="slice-label" style="text-align: end;font-size: 10pt;margin-bottom: 5px">Selected range:0.0 ms</label> 278 </div> 279 <lit-table id="tb-slices" style="height: auto"> 280 <lit-table-column class="slices-column" title="Name" width="500px" data-index="name" 281 key="name" align="flex-start" order> 282 </lit-table-column> 283 <lit-table-column class="slices-column" title="Wall duration(ms)" width="1fr" data-index="wallDuration" 284 key="wallDuration" align="flex-start" order > 285 </lit-table-column> 286 <lit-table-column class="slices-column" title="Avg Wall duration(ms)" width="1fr" data-index="avgDuration" 287 key="avgDuration" align="flex-start" order > 288 </lit-table-column> 289 <lit-table-column class="slices-column" title="Occurrences" width="1fr" data-index="occurrences" 290 key="occurrences" align="flex-start" order tdJump> 291 </lit-table-column> 292 </lit-table> 293 `; 294 } 295 296 sortByColumn(slicesDetail: unknown): void { 297 // @ts-ignore 298 function compare(property, slicesSort, type) { 299 return function (slicesLeftData: SelectionData, slicesRightData: SelectionData) { 300 if (slicesLeftData.process === ' ' || slicesRightData.process === ' ') { 301 return 0; 302 } 303 if (type === 'number') { 304 // @ts-ignore 305 return slicesSort === 2 306 ? // @ts-ignore 307 parseFloat(slicesRightData[property]) - parseFloat(slicesLeftData[property]) 308 : // @ts-ignore 309 parseFloat(slicesLeftData[property]) - parseFloat(slicesRightData[property]); 310 } else { 311 // @ts-ignore 312 if (slicesRightData[property] > slicesLeftData[property]) { 313 return slicesSort === 2 ? 1 : -1; 314 } else { 315 // @ts-ignore 316 if (slicesRightData[property] === slicesLeftData[property]) { 317 return 0; 318 } else { 319 return slicesSort === 2 ? -1 : 1; 320 } 321 } 322 } 323 }; 324 } 325 // 拷贝当前表格显示的数据 326 let sortData: Array<SelectionData> = JSON.parse(JSON.stringify(this.slicesTbl!.recycleDataSource)); 327 // 取出汇总数据,同时将排序数据去掉汇总数据进行后续排序 328 let headData: SelectionData = sortData.splice(0, 1)[0]; 329 //@ts-ignore 330 if (slicesDetail.key === 'name') { 331 //@ts-ignore 332 sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'string')); 333 } else { 334 //@ts-ignore 335 sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'number')); 336 } 337 // 排序完成后将汇总数据插入到头部 338 sortData.unshift(headData); 339 this.slicesTbl!.recycleDataSource = sortData; 340 this.sliceSearchCount!.textContent = sortData.length - 1 + ''; 341 } 342 343 findName(str: string): void { 344 let searchData: Array<SelectionData> = []; 345 let sumWallDuration: number = 0; 346 let sumOccurrences: number = 0; 347 let nameSet: Array<string> = []; 348 if (str === '') { 349 this.slicesTbl!.recycleDataSource = this.slicesSource; 350 this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; 351 } else { 352 this.slicesSource.forEach((item) => { 353 if (item.name.toLowerCase().indexOf(str.toLowerCase()) !== -1) { 354 searchData.push(item); 355 nameSet.push(item.name); 356 sumWallDuration += item.wallDuration; 357 sumOccurrences += item.occurrences; 358 } 359 }); 360 let count: SelectionData = new SelectionData(); 361 count.process = ''; 362 count.name = ''; 363 count.allName = nameSet; 364 count.tabTitle = 'Summary'; 365 count.wallDuration = Number(sumWallDuration.toFixed(3)); 366 count.occurrences = sumOccurrences; 367 searchData.unshift(count); 368 this.slicesTbl!.recycleDataSource = searchData; 369 this.sliceSearchCount!.textContent = searchData.length - 1 + ''; 370 } 371 } 372} 373