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 { type LitTable } from '../../../../base-ui/table/lit-table'; 18import '../../../../base-ui/table/lit-table-column'; 19import { AllAppStartupStruct } from '../../../database/ui-worker/ProcedureWorkerAllAppStartup'; 20 21import { type WakeupBean } from '../../../bean/WakeupBean'; 22import { SpApplication } from '../../../SpApplication'; 23import { TraceRow } from '../base/TraceRow'; 24import { CpuStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerCPU'; 25import { ThreadStruct } from '../../../database/ui-worker/ProcedureWorkerThread'; 26import { FuncStruct } from '../../../database/ui-worker/ProcedureWorkerFunc'; 27import { ProcessMemStruct } from '../../../database/ui-worker/ProcedureWorkerMem'; 28import { ClockStruct } from '../../../database/ui-worker/ProcedureWorkerClock'; 29import { ColorUtils } from '../base/ColorUtils'; 30import { IrqStruct } from '../../../database/ui-worker/ProcedureWorkerIrq'; 31import { BinderArgBean } from '../../../bean/BinderArgBean'; 32import { JankStruct } from '../../../database/ui-worker/ProcedureWorkerJank'; 33import { Utils } from '../base/Utils'; 34import { SpSystemTrace } from '../../SpSystemTrace'; 35import { AppStartupStruct } from '../../../database/ui-worker/ProcedureWorkerAppStartup'; 36import { SoStruct } from '../../../database/ui-worker/ProcedureWorkerSoInit'; 37import { type SelectionParam } from '../../../bean/BoxSelection'; 38import { 39 type FrameAnimationStruct 40} from '../../../database/ui-worker/ProcedureWorkerFrameAnimation'; 41import { 42 queryBinderByArgsId, 43 queryBinderBySliceId, 44 queryFlowsData, 45 queryPrecedingData, 46 queryThreadByItid 47} from '../../../database/sql/SqlLite.sql'; 48import { 49 queryBinderArgsByArgset, 50 queryRunnableTimeByRunning, 51 queryThreadNearData, 52 queryThreadStateArgs, 53 queryThreadWakeUp, 54 queryThreadWakeUpFrom 55} from '../../../database/sql/ProcessThread.sql'; 56import { queryGpuDur } from '../../../database/sql/Gpu.sql'; 57import { queryWakeupListPriority } from '../../../database/sql/Cpu.sql'; 58import { TabPaneCurrentSelectionHtml } from './TabPaneCurrentSelection.html'; 59 60const INPUT_WORD = 61 'This is the interval from when the task became eligible to run \n(e.g.because of notifying a wait queue it was a suspended on) to\n when it started running.'; 62 63export function getTimeString(ns: number): string { 64 65 if (ns === 0) { 66 return '0'; 67 } 68 let currentTimeNs = ns; 69 let hour1 = 3600_000_000_000; 70 let minute1 = 60_000_000_000; 71 let second1 = 1_000_000_000; // 1 second 72 let millisecond1 = 1_000_000; // 1 millisecond 73 let microsecond1 = 1_000; // 1 microsecond 74 let res = ''; 75 if (currentTimeNs >= hour1) { 76 77 res += Math.floor(currentTimeNs / hour1) + 'h '; 78 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / hour1) * hour1; 79 } 80 if (currentTimeNs >= minute1) { 81 res += Math.floor(currentTimeNs / minute1) + 'm '; 82 currentTimeNs = currentTimeNs - Math.floor(ns / minute1) * minute1; 83 } 84 if (currentTimeNs >= second1) { 85 res += Math.floor(currentTimeNs / second1) + 's '; 86 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / second1) * second1; 87 } 88 if (currentTimeNs >= millisecond1) { 89 res += Math.floor(currentTimeNs / millisecond1) + 'ms '; 90 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / millisecond1) * millisecond1; 91 } 92 if (currentTimeNs >= microsecond1) { 93 res += Math.floor(currentTimeNs / microsecond1) + 'μs '; 94 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / microsecond1) * microsecond1; 95 } 96 if (currentTimeNs > 0) { 97 res += currentTimeNs.toFixed(0) + 'ns '; 98 } 99 return res; 100} 101 102@element('tabpane-current-selection') 103export class TabPaneCurrentSelection extends BaseElement { 104 weakUpBean: WakeupBean | null | undefined; 105 selectWakeupBean: any; 106 private currentSelectionTbl: LitTable | null | undefined; 107 private tableObserver: MutationObserver | undefined; 108 private wakeupListTbl: LitTable | undefined | null; 109 private scrollView: HTMLDivElement | null | undefined; 110 // @ts-ignore 111 private dpr: any = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1; 112 113 set data(currentSelectionValue: any) { 114 if ( 115 currentSelectionValue !== undefined && 116 currentSelectionValue.constructor && 117 currentSelectionValue.constructor.name !== 'SelectionParam' 118 ) { 119 this.setCpuData(currentSelectionValue); 120 } 121 } 122 123 setCpuData( 124 data: CpuStruct, 125 callback: ((data: WakeupBean | null) => void) | undefined = undefined, 126 scrollCallback?: (data: CpuStruct) => void 127 ): void { 128 this.setTableHeight('650px'); 129 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 130 if (leftTitle) { 131 leftTitle.innerText = 'Slice Details'; 132 } 133 let list: any[] = []; 134 this.updateUI(data, list); 135 Promise.all([this.queryThreadStateDArgs(data.argSetID), this.queryCPUWakeUpFromData(data)]).then((resArr) => { 136 let args = resArr[0]; 137 let bean = resArr[1]; 138 if (callback) { 139 callback(bean); 140 } 141 if (args.length > 0) { 142 args.forEach((arg) => { 143 list.push({name: arg.keyName, value: arg.strValue}); 144 }); 145 } 146 this.currentSelectionTbl!.dataSource = list; 147 let rightArea: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#table-right'); 148 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 149 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 150 ?.querySelector('#rightButton') 151 ?.shadowRoot?.querySelector('#custom-button'); 152 let rightStar: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#right-star'); 153 this.threadClickEvent(scrollCallback, data); 154 let canvas = this.initCanvas(); 155 if (bean !== null) { 156 this.selectWakeupBean = { 157 process: `${this.transferString(data.processName || 'Process')}(${data.processId})`, 158 thread: `${this.transferString(data.name || 'Thread')}(${data.tid})`, 159 cpu: data.cpu, 160 dur: data.dur, 161 priority: data.priority, 162 isSelected: false, 163 }; 164 165 this.weakUpBean = bean; 166 this.updateRightTitleUI(rightArea!, rightTitle!, rightButton!, rightStar!); 167 this.drawRight(canvas, bean); 168 } else { 169 this.handleNullBeanUI(rightArea!, rightTitle!, rightButton!, rightStar!); 170 } 171 }); 172 } 173 174 private threadClickEvent(scrollCallback: ((data: CpuStruct) => void) | undefined, data: CpuStruct): void { 175 let threadClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#thread-id'); 176 threadClick?.addEventListener('click', () => { 177 //cpu点击 178 if (scrollCallback) { 179 data.state = 'Running'; 180 scrollCallback(data); 181 } 182 }); 183 } 184 185 private updateRightTitleUI(rightArea: HTMLElement, rightTitle: HTMLElement, rightButton: HTMLElement, 186 rightStar: HTMLElement) { 187 if (rightArea !== null && rightArea) { 188 rightArea.style.visibility = 'visible'; 189 } 190 if (rightTitle !== null && rightTitle) { 191 rightTitle.style.visibility = 'visible'; 192 rightButton!.style.visibility = 'visible'; 193 rightStar!.style.visibility = 'hidden'; 194 SpSystemTrace.btnTimer = null; 195 } 196 } 197 198 private handleNullBeanUI(rightArea: HTMLElement, rightTitle: HTMLElement, rightButton: HTMLElement, 199 rightStar: HTMLElement) { 200 this.weakUpBean = null; 201 if (rightArea !== null && rightArea) { 202 rightArea.style.visibility = 'hidden'; 203 } 204 if (rightTitle !== null && rightTitle) { 205 rightTitle.style.visibility = 'hidden'; 206 rightButton!.style.visibility = 'hidden'; 207 rightStar!.style.visibility = 'hidden'; 208 } 209 } 210 211 private updateUI(data: CpuStruct, list: any[]) { 212 let process = this.transferString(data.processName || 'Process'); 213 let processId = data.processId || data.tid; 214 let state = ''; 215 if (data.end_state) { 216 state = Utils.getEndState(data.end_state); 217 } else if (data.end_state === '' || data.end_state === null) { 218 state = ''; 219 } else { 220 state = 'Unknown State'; 221 } 222 list.push({ 223 name: 'Process', 224 value: `${process || 'Process'} [${processId}]`, 225 }); 226 let name = this.transferString(data.name ?? ''); 227 if (data.processId) { 228 list.push({ 229 name: 'Thread', 230 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 231<div style="white-space:pre-wrap">${name || 'Process'} [${data.tid}]</div> 232<lit-icon style="cursor:pointer;margin-left: 5px" id="thread-id" name="select" color="#7fa1e7" size="20"></lit-icon> 233</div>`, 234 }); 235 } else { 236 list.push({ 237 name: 'Thread', 238 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 239<div style="white-space:pre-wrap">${name || 'Process'} [${data.tid}]</div> 240</div>`, 241 }); 242 } 243 list.push({name: 'StartTime(Relative)', value: getTimeString(data.startTime || 0),}); 244 list.push({ 245 name: 'StartTime(Absolute)', 246 value: ((data.startTime || 0) + (window as any).recordStartNS) / 1000000000 + 's', 247 }); 248 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 249 list.push({name: 'Prio', value: data.priority || 0}); 250 list.push({name: 'End State', value: state}); 251 } 252 253 setFunctionData(data: FuncStruct, scrollCallback: Function): void { 254 //方法信息 255 this.tabCurrentSelectionInit('Slice Details'); 256 let list: any[] = []; 257 let name = this.transferString(data.funName ?? ''); 258 let isBinder = FuncStruct.isBinder(data); 259 let isAsyncBinder = isBinder && FuncStruct.isBinderAsync(data); 260 if (data.argsetid !== undefined && data.argsetid !== null && data.argsetid >= 0) { 261 this.setTableHeight('700px'); 262 if (isAsyncBinder) { 263 this.handleAsyncBinder(data, list, name, scrollCallback); 264 } else if (isBinder) { 265 this.handleBinder(data, list, name, scrollCallback); 266 } else { 267 this.handleNonBinder(data, list, name); 268 } 269 } else { 270 this.setTableHeight('auto'); 271 list.push({name: 'Name', value: name}); 272 list.push({ 273 name: 'StartTime(Relative)', 274 value: getTimeString(data.startTs || 0), 275 }); 276 list.push({ 277 name: 'StartTime(Absolute)', 278 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 279 }); 280 list.push({ 281 name: 'Duration', 282 value: getTimeString(data.dur || 0), 283 }); 284 list.push({name: 'depth', value: data.depth}); 285 this.currentSelectionTbl!.dataSource = list; 286 } 287 } 288 289 private handleNonBinder(data: FuncStruct, list: any[], name: string): void { 290 queryBinderArgsByArgset(data.argsetid!).then((argset) => { 291 list.push({name: 'Name', value: name}); 292 argset.forEach((item) => { 293 list.push({name: item.keyName, value: item.strValue}); 294 }); 295 this.addTabPanelContent(list, data); 296 this.currentSelectionTbl!.dataSource = list; 297 }); 298 } 299 300 private handleBinder(data: FuncStruct, list: any[], name: string, scrollCallback: Function): void { 301 queryBinderArgsByArgset(data.argsetid!).then((argset) => { 302 let binderSliceId = -1; 303 argset.forEach((item) => { 304 if (item.keyName === 'destination slice id') { 305 binderSliceId = Number(item.strValue); 306 list.unshift({ 307 name: 'Name', 308 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 309<div style="white-space:pre-wrap">${name || 'binder'}</div> 310<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 311</div>`, 312 }); 313 } 314 list.push({name: item.keyName, value: item.strValue}); 315 }); 316 if (binderSliceId === -1) { 317 list.unshift({name: 'Name', value: name}); 318 } 319 this.addTabPanelContent(list, data); 320 this.currentSelectionTbl!.dataSource = list; 321 let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); 322 funcClick?.addEventListener('click', () => { 323 if (!Number.isNaN(binderSliceId) && binderSliceId !== -1) { 324 queryBinderBySliceId(binderSliceId).then((result: any[]) => { 325 if (result.length > 0) { 326 result[0].type = TraceRow.ROW_TYPE_FUNC; 327 scrollCallback(result[0]); 328 } 329 }); 330 } 331 }); 332 }); 333 } 334 335 private handleAsyncBinder(data: FuncStruct, list: any[], name: string, scrollCallback: Function): void { 336 Promise.all([ 337 queryBinderByArgsId(data.argsetid!, data.startTs!, !data.funName!.endsWith('rcv')), 338 queryBinderArgsByArgset(data.argsetid!), 339 ]).then((result) => { 340 let asyncBinderRes = result[0]; 341 let argsBinderRes = result[1]; 342 let asyncBinderStract: any; 343 if (asyncBinderRes.length > 0) { 344 asyncBinderRes[0].type = TraceRow.ROW_TYPE_FUNC; 345 asyncBinderStract = asyncBinderRes[0]; 346 } 347 if (argsBinderRes.length > 0) { 348 argsBinderRes.forEach((item) => { 349 list.push({ 350 name: item.keyName, 351 value: item.strValue, 352 }); 353 }); 354 } 355 if (asyncBinderStract !== undefined) { 356 list.unshift({ 357 name: 'Name', 358 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 359<div style="white-space:pre-wrap">${name || 'binder'}</div> 360<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 361</div>`, 362 }); 363 } else { 364 list.unshift({name: 'Name', value: name}); 365 } 366 this.addTabPanelContent(list, data); 367 this.currentSelectionTbl!.dataSource = list; 368 let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); 369 funcClick?.addEventListener('click', () => { 370 scrollCallback(asyncBinderStract); 371 }); 372 }); 373 } 374 375 private addTabPanelContent(contentList: any[], data: FuncStruct): void { 376 contentList.push({ 377 name: 'StartTime(Relative)', 378 value: getTimeString(data.startTs || 0), 379 }); 380 contentList.push({ 381 name: 'StartTime(Absolute)', 382 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 383 }); 384 contentList.push({ 385 name: 'Duration', 386 value: getTimeString(data.dur || 0), 387 }); 388 contentList.push({name: 'depth', value: data.depth}); 389 if (data.argsetid && data.argsetid > -1) { 390 contentList.push({name: 'arg_set_id', value: data.argsetid}); 391 } 392 } 393 394 private tabCurrentSelectionInit(leftTitleStr: string): void { 395 this.initCanvas(); 396 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 397 this.setTitleAndButtonStyle(); 398 if (leftTitle) { 399 leftTitle.innerText = leftTitleStr; 400 } 401 } 402 403 private setTitleAndButtonStyle(): void { 404 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 405 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 406 ?.querySelector('#rightButton') 407 ?.shadowRoot?.querySelector('#custom-button'); 408 let rightStar: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#right-star'); 409 if (rightTitle) { 410 rightTitle.style.visibility = 'hidden'; 411 rightButton!.style.visibility = 'hidden'; 412 rightStar!.style.visibility = 'hidden'; 413 } 414 } 415 416 setClockData(data: ClockStruct): void { 417 this.setTableHeight('auto'); 418 //时钟信息 419 this.tabCurrentSelectionInit('Counter Details'); 420 let list: any[] = []; 421 list.push({ 422 name: 'StartTime(Relative)', 423 value: getTimeString(data.startNS || 0), 424 }); 425 list.push({ 426 name: 'StartTime(Absolute)', 427 value: ((data.startNS || 0) + (window as any).recordStartNS) / 1000000000 + 's', 428 }); 429 list.push({ 430 name: 'Value', 431 value: ColorUtils.formatNumberComma(data.value || 0), 432 }); 433 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 434 this.currentSelectionTbl!.dataSource = list; 435 } 436 437 setMemData(data: ProcessMemStruct): void { 438 this.setTableHeight('auto'); 439 //时钟信息 440 this.initCanvas(); 441 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 442 if (leftTitle) { 443 leftTitle.innerText = 'Counter Details'; 444 } 445 let list: object[] = []; 446 list.push({ 447 name: 'StartTime(Relative)', 448 value: getTimeString(data.startTime || 0), 449 }); 450 list.push({ 451 name: 'StartTime(Absolute)', 452 value: ((data.startTime || 0) + (window as any).recordStartNS) / 1000000000 + 's', 453 }); 454 list.push({name: 'Value', value: data.value}); 455 list.push({name: 'Delta', value: data.delta}); 456 list.push({ 457 name: 'Duration', 458 value: getTimeString(data.duration || 0), 459 }); 460 this.currentSelectionTbl!.dataSource = list; 461 } 462 463 setIrqData(data: IrqStruct): void { 464 this.setTableHeight('550px'); 465 this.initCanvas(); 466 this.setTitleAndButtonStyle(); 467 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 468 if (leftTitle) { 469 leftTitle.innerText = 'Counter Details'; 470 } 471 let list: object[] = []; 472 list.push({ 473 name: 'StartTime(Relative)', 474 value: getTimeString(data.startNS || 0), 475 }); 476 list.push({ 477 name: 'StartTime(Absolute)', 478 value: ((data.startNS || 0) + (window as any).recordStartNS) / 1000000000 + 's', 479 }); 480 list.push({name: 'Name', value: data.name}); 481 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 482 queryBinderArgsByArgset(data.argSetId || 0).then((argsBinderRes) => { 483 if (argsBinderRes.length > 0) { 484 argsBinderRes.forEach((item) => { 485 list.push({name: item.keyName, value: item.strValue}); 486 }); 487 } 488 this.currentSelectionTbl!.dataSource = list; 489 }); 490 } 491 492 setThreadData( 493 data: ThreadStruct, 494 scrollCallback: ((d: any) => void) | undefined, 495 scrollWakeUp: (d: any) => void | undefined, 496 callback: ((data: Array<any>) => void) | undefined = undefined 497 ): void { 498 //线程信息 499 this.setTableHeight('550px'); 500 this.initCanvas(); 501 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 502 this.setTitleAndButtonStyle(); 503 if (leftTitle) { 504 leftTitle.innerText = 'Thread State'; 505 } 506 let list: any[] = []; 507 let jankJumperList = new Array<ThreadTreeNode>(); 508 this.prepareThreadInfo(list, data); 509 let cpu = new CpuStruct(); 510 cpu.id = data.id; 511 cpu.startTime = data.startTime; 512 this.queryThreadDetails(data, list, jankJumperList, callback, scrollWakeUp, scrollCallback); 513 } 514 515 private sortByNearData(nearData: any[], data: ThreadStruct, list: any[]): any[] { 516 let preData: any = undefined; 517 let nextData: any = undefined; 518 nearData.sort((near1, near2) => near1.startTime - near2.startTime).forEach((near) => { 519 if (near.itid === data.id) { 520 if (near.startTime < data.startTime!) { 521 preData = near; 522 list.push({ 523 name: 'Previous State', 524 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 525 <div style="white-space:pre-wrap">${Utils.getEndState(near.state)}</div> 526 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="previous-state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 527 </div>`, 528 }); 529 } else { 530 nextData = near; 531 list.push({ 532 name: 'Next State', 533 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 534 <div style="white-space:pre-wrap">${Utils.getEndState(near.state)}</div> 535 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="next-state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 536 </div>`, 537 }); 538 } 539 } 540 }); 541 return [preData, nextData]; 542 } 543 544 private setWakeupData(fromBean: WakeupBean | undefined, wakeUps: WakeupBean[], list: any[]): void { 545 if (fromBean !== null && fromBean !== undefined && fromBean.pid !== 0 && fromBean.tid !== 0) { 546 list.push({ 547 name: 'wakeup from tid', 548 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 549 <div style="white-space:pre-wrap">${fromBean.tid}</div> 550 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-from" 551 class="wakeup-click" name="select" color="#7fa1e7" size="20"></lit-icon> 552 </div>`, 553 }); 554 } 555 if (wakeUps !== null) { 556 wakeUps.map((e) => { 557 list.push({ 558 name: 'wakeup tid', 559 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 560 <div style="white-space:pre-wrap">${e.tid}</div> 561 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-${e.tid}" 562 class="wakeup-click" name="select" color="#7fa1e7" size="20"></lit-icon> 563 </div>`, 564 }); 565 }); 566 } 567 } 568 569 private queryThreadDetails( 570 data: ThreadStruct, 571 list: any[], 572 jankJumperList: ThreadTreeNode[], 573 callback: ((data: Array<any>) => void) | undefined, 574 scrollWakeUp: (d: any) => (void | undefined), 575 scrollCallback: ((d: any) => void) | undefined 576 ): void { 577 Promise.all([ 578 this.queryThreadWakeUpFromData(data.id!, data.startTime!, data.dur!), 579 this.queryThreadWakeUpData(data.id!, data.startTime!, data.dur!), 580 this.queryThreadStateDArgs(data.argSetID), 581 queryThreadNearData(data.id!, data.startTime!), 582 ]).then((result) => { 583 let fromBean = result[0]; 584 let wakeUps = result[1]; 585 let args = result[2]; 586 let [preData, nextData] = this.sortByNearData(result[3], data, list); 587 this.setWakeupData(fromBean, wakeUps, list); 588 let timeLineNode = new ThreadTreeNode(data.tid!, data.pid!, data.startTime!); 589 jankJumperList.push(timeLineNode); 590 if (args.length > 0) { 591 args.forEach((arg) => { 592 list.push({name: arg.keyName, value: arg.strValue}); 593 }); 594 } 595 this.currentSelectionTbl!.dataSource = list; 596 if (callback) { 597 callback(jankJumperList); 598 } 599 this.stateClickHandler(preData, nextData, data, scrollWakeUp, scrollCallback); 600 this.wakeupClickHandler(wakeUps, fromBean, scrollWakeUp); 601 }); 602 } 603 604 private wakeupClickHandler( 605 wakeUps: WakeupBean[], 606 fromBean: WakeupBean | undefined, 607 scrollWakeUp: (d: any) => (void | undefined) 608 ): void { 609 this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-from')?.addEventListener('click', () => { 610 //点击跳转,唤醒和被唤醒的 线程 611 if (fromBean && scrollWakeUp) { 612 scrollWakeUp({ 613 processId: fromBean.pid, 614 tid: fromBean.tid, 615 startTime: fromBean.ts, 616 dur: fromBean.dur, 617 cpu: fromBean.cpu, 618 id: fromBean.itid, 619 state: 'Running', 620 argSetID: fromBean.argSetID, 621 }); 622 } 623 }); 624 if (wakeUps) { 625 wakeUps.map((up) => { 626 this.currentSelectionTbl?.shadowRoot?.querySelector(`#wakeup-${up.tid}`)?.addEventListener('click', () => { 627 //点击跳转,唤醒和被唤醒的 线程 628 if (up && scrollWakeUp !== undefined) { 629 scrollWakeUp({ 630 processId: up.pid, 631 tid: up.tid, 632 startTime: up.ts, 633 dur: up.dur, 634 cpu: up.cpu, 635 id: up.itid, 636 state: up.state, 637 argSetID: up.argSetID, 638 }); 639 } 640 }); 641 }); 642 } 643 } 644 645 private stateClickHandler( 646 preData: any, 647 nextData: any, 648 data: ThreadStruct, 649 scrollWakeUp: (d: any) => (void | undefined), 650 scrollCallback: ((d: any) => void) | undefined 651 ): void { 652 this.currentSelectionTbl?.shadowRoot?.querySelector('#next-state-click')?.addEventListener('click', () => { 653 if (nextData && scrollWakeUp !== undefined) { 654 scrollWakeUp({ 655 processId: nextData.pid, 656 tid: nextData.tid, 657 startTime: nextData.startTime, 658 dur: nextData.dur, 659 cpu: nextData.cpu, 660 id: nextData.itid, 661 state: nextData.state, 662 argSetID: nextData.argSetID, 663 }); 664 } 665 }); 666 667 this.currentSelectionTbl?.shadowRoot?.querySelector('#previous-state-click')?.addEventListener('click', () => { 668 if (preData && scrollWakeUp !== undefined) { 669 scrollWakeUp({ 670 processId: preData.pid, 671 tid: preData.tid, 672 startTime: preData.startTime, 673 dur: preData.dur, 674 cpu: preData.cpu, 675 id: preData.itid, 676 state: preData.state, 677 argSetID: preData.argSetID, 678 }); 679 } 680 }); 681 682 this.currentSelectionTbl?.shadowRoot?.querySelector('#state-click')?.addEventListener('click', () => { 683 //线程点击 684 if (scrollCallback) { 685 scrollCallback(data); 686 } 687 }); 688 } 689 690 private prepareThreadInfo(list: any[], data: ThreadStruct): void { 691 list.push({ 692 name: 'StartTime(Relative)', 693 value: getTimeString(data.startTime || 0), 694 }); 695 list.push({ 696 name: 'StartTime(Absolute)', 697 value: ((data.startTime || 0) + (window as any).recordStartNS) / 1000000000 + 's', 698 }); 699 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 700 let state; 701 if (data.state) { 702 state = Utils.getEndState(data.state); 703 } else if (data.state === '' || data.state === null) { 704 state = ''; 705 } else { 706 state = 'Unknown State'; 707 } 708 if ('Running' === state) { 709 state = state + ' on CPU ' + data.cpu; 710 list.push({ 711 name: 'State', 712 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 713 <div style="white-space:pre-wrap">${state}</div> 714 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 715 </div>`, 716 }); 717 } else { 718 list.push({name: 'State', value: `${state}`}); 719 } 720 let slice = Utils.SCHED_SLICE_MAP.get(`${data.id}-${data.startTime}`); 721 if (slice) { 722 list.push({name: 'Prio', value: `${slice.priority}`}); 723 } 724 let processName = Utils.PROCESS_MAP.get(data.pid!); 725 if ( 726 processName === null || 727 processName === undefined || 728 processName === '' || 729 processName.toLowerCase() == 'null' 730 ) { 731 processName = Utils.THREAD_MAP.get(data.tid!) || 'null'; 732 } 733 list.push({ 734 name: 'Process', 735 value: this.transferString(processName ?? '') + ' [' + data.pid + '] ', 736 }); 737 } 738 739 setJankData( 740 data: JankStruct, 741 callback: ((data: Array<any>) => void) | undefined = undefined, 742 scrollCallback: ((d: any) => void) | undefined 743 ): void { 744 //线程信息 745 this.setTableHeight('750px'); 746 this.tabCurrentSelectionInit('Slice Details'); 747 let list: any[] = []; 748 this.setJankCommonMessage(list, data); 749 if (data.type == '0') { 750 this.handleTypeJank(data, list, scrollCallback, callback); 751 } else { 752 this.currentSelectionTbl!.dataSource = list; 753 } 754 } 755 756 private handleTypeJank(data: JankStruct, list: any[], scrollCallback: ((d: any) => void) | undefined, 757 callback: ((data: Array<any>) => void) | undefined): void { 758 this.setJankType(data, list); 759 let jankJumperList = new Array<JankTreeNode>(); 760 if (data.frame_type === 'render_service') { 761 queryGpuDur(data.id!).then((it) => { 762 if (it.length > 0) { 763 list.push({name: 'Gpu Duration', value: getTimeString(it[0].gpu_dur)}); 764 } 765 }); 766 this.handleRenderServiceJank(data, list, jankJumperList, scrollCallback, callback); 767 } else if (data.frame_type === 'app') { 768 this.handleAppJank(list, data, jankJumperList, scrollCallback, callback); 769 } else if (data.frame_type === 'frameTime') { 770 this.handleFrameTimeJank(data, list, jankJumperList, scrollCallback, callback); 771 } 772 } 773 774 private handleFrameTimeJank(data: JankStruct, list: any[], jankJumperList: JankTreeNode[], 775 scrollCallback: ((d: any) => void) | undefined, callback: ((data: Array<any>) => void) | undefined): void { 776 queryGpuDur(data.id!).then((it) => { 777 if (it.length > 0) { 778 list.push({ 779 name: 'Gpu Duration', 780 value: getTimeString(it[0].gpu_dur), 781 }); 782 } 783 this.addAppFrameDetails(data, list); 784 this.addRenderServiceFrameDetails(data, list); 785 this.addFollowingDetails(list, data); 786 let appNode = new JankTreeNode(data.name!, data.pid!, 'app'); 787 let rsNode = new JankTreeNode(data.rs_vsync!, data.rs_pid!, 'render_service'); 788 appNode.children.push(rsNode); 789 jankJumperList.push(appNode); 790 this.currentSelectionTbl!.dataSource = list; 791 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 792 }); 793 } 794 795 private addRenderServiceFrameDetails(data: JankStruct, list: any[]): void { 796 if (data.rs_name) { 797 list.push({ 798 name: 'RenderService Frame', 799 value: '', 800 }); 801 list.push({ 802 name: 'Process', 803 value: 'render_service ' + data.rs_pid, 804 }); 805 list.push({ 806 name: 'StartTime(Relative)', 807 value: getTimeString(data.rs_ts || 0), 808 }); 809 list.push({ 810 name: 'StartTime(Absolute)', 811 value: ((data.rs_ts || 0) + (window as any).recordStartNS) / 1000000000 + 's', 812 }); 813 list.push({ 814 name: 'end time', 815 value: getTimeString(data.rs_ts! + data.rs_dur! || 0), 816 }); 817 } 818 } 819 820 private addFollowingDetails(list: any[], data: JankStruct): void { 821 list.push({ 822 name: 'Following', 823 value: '', 824 }); 825 list.push({ 826 name: 'Slice', 827 value: 828 data.cmdline + 829 ' [' + 830 data.name + 831 ']' + 832 `<lit-icon class="jank_cla" style="display: inline-flex;cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="${data.type}-${data.pid}" slice_name="${data.name}" pid="${data.pid}" name="select" color="#7fa1e7" size="20"></lit-icon>`, 833 }); 834 } 835 836 private addAppFrameDetails(data: JankStruct, list: any[]): void { 837 if (data.name) { 838 list.push({ 839 name: 'App Frame', 840 value: '', 841 }); 842 list.push({ 843 name: 'Process', 844 value: data.cmdline + ' ' + data.pid, 845 }); 846 list.push({ 847 name: 'StartTime(Relative)', 848 value: getTimeString(data.ts || 0), 849 }); 850 list.push({ 851 name: 'StartTime(Absolute)', 852 value: ((data.ts || 0) + (window as any).recordStartNS) / 1000000000 + 's', 853 }); 854 list.push({ 855 name: 'end time', 856 value: getTimeString(data!.ts! + data.dur! || 0), 857 }); 858 } 859 } 860 861 private handleAppJank(list: any[], data: JankStruct, jankJumperList: JankTreeNode[], 862 scrollCallback: ((d: any) => void) | undefined, callback: ((data: Array<any>) => void) | undefined): void { 863 list.push({ 864 name: 'FrameTimeLine flows', 865 value: '', 866 }); 867 list.push({ 868 name: 'Slice', 869 value: 870 data.cmdline + 871 ' [' + 872 data.name + 873 ']' + 874 `<lit-icon class="jank_cla" style="display: inline-flex;cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="actual frameTime" slice_name="${data.name}" pid="${data.pid}" name="select" color="#7fa1e7" size="20"></lit-icon>`, 875 }); 876 let timeLineNode = new JankTreeNode(data.name!, data.pid!, 'frameTime'); 877 jankJumperList.push(timeLineNode); 878 if (data.dst_slice) { 879 queryPrecedingData(data.dst_slice).then((it) => { 880 if (it.length > 0) { 881 list.push({ 882 name: 'Preceding flows', 883 value: '', 884 }); 885 it.forEach((a: any) => { 886 let rsNode = new JankTreeNode(a.name, a.pid, 'render_service'); 887 jankJumperList.push(rsNode); 888 list.push({ 889 name: 'Slice', 890 value: 891 a.cmdline + 892 ' [' + 893 a.name + 894 ']' + 895 `<lit-icon class="jank_cla" style="display: inline-flex;cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="${a.type}-${a.pid}" slice_name="${a.name}" pid="${a.pid}" name="select" color="#7fa1e7" size="20"></lit-icon>`, 896 }); 897 }); 898 this.currentSelectionTbl!.dataSource = list; 899 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 900 } 901 }); 902 } else { 903 this.currentSelectionTbl!.dataSource = list; 904 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 905 } 906 } 907 908 private handleRenderServiceJank(data: JankStruct, list: any[], jankJumperList: JankTreeNode[], 909 scrollCallback: ((d: any) => void) | undefined, callback: ((data: Array<any>) => void) | undefined): void { 910 if (data.src_slice) { 911 queryFlowsData(data.src_slice!.split(',')).then((it) => { 912 if (it.length > 0) { 913 list.push({ 914 name: 'FrameTimeLine flows', 915 value: '', 916 }); 917 it.forEach((a: any) => { 918 let appNode = new JankTreeNode(a.name, a.pid, 'app'); 919 appNode.children.push(new JankTreeNode(a.name, a.pid, 'frameTime')); 920 jankJumperList.push(appNode); 921 list.push({ 922 name: 'Slice', 923 value: 924 a.cmdline + 925 ' [' + 926 a.name + 927 ']' + 928 `<lit-icon class="jank_cla" style="display: inline-flex;cursor: pointer;transform: scaleX(-1);margin-left: 5px" id="actual frameTime" slice_name="${a.name}" pid="${a.pid}" name="select" color="#7fa1e7" size="20"></lit-icon>`, 929 }); 930 }); 931 list.push({ 932 name: 'Following flows', 933 value: '', 934 }); 935 it.forEach((a: any) => { 936 list.push({ 937 name: 'Slice', 938 value: 939 a.cmdline + 940 ' [' + 941 a.name + 942 ']' + 943 `<lit-icon class="jank_cla" style="display: inline-flex;cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="${a.type}-${a.pid}" slice_name="${a.name}" pid="${a.pid}" name="select" color="#7fa1e7" size="20"></lit-icon>`, 944 }); 945 }); 946 this.currentSelectionTbl!.dataSource = list; 947 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 948 } 949 }); 950 } else { 951 this.currentSelectionTbl!.dataSource = list; 952 } 953 } 954 955 setAllStartupData(data: AllAppStartupStruct, scrollCallback: Function): void { 956 this.setTableHeight('550px'); 957 this.initCanvas(); 958 let allStartUpLeftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 959 let allStartUpmiddleTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightText'); 960 let allStartUpRightButton: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightButton'); 961 if (allStartUpmiddleTitle) allStartUpmiddleTitle.style.visibility = 'hidden'; 962 if (allStartUpRightButton) allStartUpRightButton.style.visibility = 'hidden'; 963 if (allStartUpLeftTitle) { 964 allStartUpLeftTitle.innerText = 'Details'; 965 } 966 let list: any[] = []; 967 list.push({name: 'Name', value: data.stepName!}); 968 list.push({ 969 name: 'StartTime(Relative)', 970 value: getTimeString(data.startTs || 0), 971 }); 972 list.push({ 973 name: 'StartTime(Absolute)', 974 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 975 }); 976 list.push({ 977 name: 'EndTime(Relative)', 978 value: getTimeString((data.startTs || 0) + (data.dur || 0)), 979 }); 980 list.push({ 981 name: 'EndTime(Abslute)', 982 value: ((data.startTs || 0) + (data.dur || 0) + (window as any).recordStartNS) / 1000000000 + 's', 983 }); 984 list.push({ 985 name: 'Dur', 986 value: getTimeString(data.dur || 0), 987 }); 988 this.currentSelectionTbl!.dataSource = list; 989 } 990 991 setStartupData(data: AppStartupStruct, scrollCallback: Function): void { 992 this.setTableHeight('550px'); 993 this.initCanvas(); 994 this.setStartUpStyle(); 995 let list: any[] = []; 996 list.push({name: 'Name', value: AppStartupStruct.getStartupName(data.startName)}); 997 list.push({ 998 name: 'StartTime(Relative)', 999 value: ` 1000 <div style="display: flex;white-space: nowrap;align-items: center"> 1001<div style="white-space:pre-wrap">${getTimeString(data.startTs || 0)}</div> 1002<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="start-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 1003</div>`, 1004 }); 1005 list.push({ 1006 name: 'StartTime(Absolute)', 1007 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1008 }); 1009 if (data.dur && data.dur > 0) { 1010 list.push({ 1011 name: 'EndTime(Relative)', 1012 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 1013<div style="white-space:pre-wrap">${getTimeString((data.startTs || 0) + (data.dur || 0))}</div> 1014<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="end-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 1015</div>`, 1016 }); 1017 list.push({ 1018 name: 'EndTime(Absolute)', 1019 value: ((data.startTs || 0) + (data.dur || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1020 }); 1021 } else { 1022 list.push({ 1023 name: 'EndTime(Relative)', 1024 value: `Unknown Time`, 1025 }); 1026 list.push({ 1027 name: 'EndTime(Absolute)', 1028 value: 'Unknown Time', 1029 }); 1030 } 1031 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 1032 this.currentSelectionTbl!.dataSource = list; 1033 this.attachScrollHandlers(data, scrollCallback); 1034 } 1035 1036 private setStartUpStyle() { 1037 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 1038 ?.querySelector('#rightButton') 1039 ?.shadowRoot?.querySelector('#custom-button'); 1040 let startUpRightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 1041 if (startUpRightTitle) { 1042 startUpRightTitle.style.visibility = 'hidden'; 1043 rightButton!.style.visibility = 'hidden'; 1044 } 1045 let startUpLeftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 1046 if (startUpLeftTitle) { 1047 startUpLeftTitle.innerText = 'Details'; 1048 } 1049 } 1050 1051 private attachScrollHandlers(data: AppStartupStruct, scrollCallback: Function): void { 1052 let startIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#start-jump'); 1053 let endIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#end-jump'); 1054 let scrollClick = (type: number): void => { 1055 let recordNs: number = (window as any).recordStartNS; 1056 let useEnd = type === 1 && data.startName! < 6; 1057 queryThreadByItid( 1058 useEnd ? data.endItid! : data.itid!, 1059 useEnd ? recordNs + data.startTs! + data.dur! : recordNs + data.startTs! 1060 ).then((result) => { 1061 if (result.length > 0) { 1062 let pt: { 1063 pid: number; 1064 tid: number; 1065 dur: number; 1066 name: string; 1067 depth: number 1068 } = result[0]; 1069 scrollCallback({ 1070 pid: pt.pid, 1071 tid: pt.tid, 1072 type: 'func', 1073 dur: pt.dur, 1074 depth: pt.depth, 1075 funName: pt.name, 1076 startTs: useEnd ? (data.startTs || 0) + (data.dur || 0) : data.startTs, 1077 keepOpen: true, 1078 }); 1079 } 1080 }); 1081 }; 1082 if (startIcon) { 1083 startIcon.addEventListener('click', () => scrollClick(0)); 1084 } 1085 if (endIcon) { 1086 endIcon.addEventListener('click', () => scrollClick(1)); 1087 } 1088 } 1089 1090 setStaticInitData(data: SoStruct, scrollCallback: Function): void { 1091 this.setTableHeight('550px'); 1092 this.initCanvas(); 1093 this.setStaticInitStyle(); 1094 let list: any[] = []; 1095 list.push({name: 'Name', value: data.soName}); 1096 list.push({ 1097 name: 'StartTime(Relative)', 1098 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 1099<div style="white-space:pre-wrap">${getTimeString(data.startTs || 0)}</div> 1100<lit-icon id="start-jump" style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" name="select" color="#7fa1e7" size="20"></lit-icon> 1101</div>`, 1102 }); 1103 list.push({ 1104 name: 'StartTime(Absolute)', 1105 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1106 }); 1107 list.push({name: 'Duration', value: getTimeString(data.dur || 0)}); 1108 this.currentSelectionTbl!.dataSource = list; 1109 this.startIconClickEvent(data, scrollCallback); 1110 } 1111 1112 private setStaticInitStyle(): void { 1113 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 1114 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 1115 ?.querySelector('#rightButton') 1116 ?.shadowRoot?.querySelector('#custom-button'); 1117 if (rightTitle) { 1118 rightTitle.style.visibility = 'hidden'; 1119 rightButton!.style.visibility = 'hidden'; 1120 } 1121 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 1122 if (leftTitle) { 1123 leftTitle.innerText = 'Details'; 1124 } 1125 } 1126 1127 private startIconClickEvent(data: SoStruct, scrollCallback: Function): void { 1128 let startIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#start-jump'); 1129 if (startIcon) { 1130 startIcon.addEventListener('click', () => { 1131 let recordNs: number = (window as any).recordStartNS; 1132 queryThreadByItid(data.itid!, recordNs + data.startTs!).then((result) => { 1133 if (result.length > 0) { 1134 let pt: { 1135 pid: number; 1136 tid: number; 1137 dur: number; 1138 name: string; 1139 depth: number 1140 } = result[0]; 1141 scrollCallback({ 1142 pid: pt.pid, 1143 tid: pt.tid, 1144 type: 'func', 1145 dur: pt.dur, 1146 depth: pt.depth, 1147 funName: pt.name, 1148 startTs: data.startTs, 1149 keepOpen: true, 1150 }); 1151 } 1152 }); 1153 }); 1154 } 1155 } 1156 1157 async setFrameAnimationData(data: FrameAnimationStruct): Promise<void> { 1158 this.setTableHeight('550px'); 1159 this.tabCurrentSelectionInit('Animation Details'); 1160 let list = []; 1161 let dataTs: number = data.startTs < 0 ? 0 : data.startTs; 1162 list.push({name: 'Name', value: data.name}); 1163 list.push({name: 'Start time(Relative)', value: `${Utils.getTimeString(dataTs)}`}); 1164 list.push({ 1165 name: 'Start time(Absolute)', 1166 value: ((dataTs || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1167 }); 1168 list.push({ 1169 name: 'End time(Relative)', 1170 value: `${Utils.getTimeString(dataTs + (data.dur || 0))}` 1171 }); 1172 list.push({ 1173 name: 'End time(Absolute)', 1174 value: (dataTs + (data.dur || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1175 }); 1176 list.push({name: 'Duration', value: `${Utils.getTimeString(data.dur || 0)}`}); 1177 if (data.status === 'Completion delay') { 1178 let frameFpsMessage = data.frameInfo?.split(':'); 1179 if (frameFpsMessage) { 1180 if (frameFpsMessage[1] !== '0') { 1181 list.push({name: 'FPS', value: `${frameFpsMessage[1]}`}); 1182 } else { 1183 let fixedNumber: number = 2; 1184 let fpsValue: number = Number(frameFpsMessage[0]) / (data.dur / 1000_000_000); 1185 list.push({name: 'FPS', value: `${fpsValue.toFixed(fixedNumber) || 0}`}); 1186 } 1187 } 1188 } 1189 this.currentSelectionTbl!.dataSource = list; 1190 } 1191 1192 private setJankType(data: JankStruct, list: any[]): void { 1193 if (data.jank_tag === 1) { 1194 if (data.frame_type === 'render_service') { 1195 list.push({name: 'Jank Type', value: 'RenderService Deadline Missed'}); 1196 } else if (data.frame_type === 'app') { 1197 list.push({name: 'Jank Type', value: 'APP Deadline Missed'}); 1198 } else if (data.frame_type === 'frameTime') { 1199 list.push({name: 'Jank Type', value: 'Deadline Missed'}); 1200 } 1201 } else if (data.jank_tag === 3) { 1202 list.push({name: 'Jank Type', value: 'Deadline Missed'}); 1203 } else { 1204 list.push({name: 'Jank Type', value: 'NONE'}); 1205 } 1206 } 1207 1208 private setJankCommonMessage(list: any[], data: JankStruct): void { 1209 list.push({name: 'Name', value: data.name}); 1210 list.push({name: 'StartTime(Relative)', value: getTimeString(data.ts || 0)}); 1211 list.push({ 1212 name: 'StartTime(Absolute)', 1213 value: ((data.ts || 0) + (window as any).recordStartNS) / 1000000000 + 's', 1214 }); 1215 list.push({name: 'Duration', value: data.dur ? getTimeString(data.dur) : ' '}); 1216 if (data.frame_type !== 'frameTime') { 1217 list.push({name: 'Process', value: data.cmdline + ' ' + data.pid}); 1218 } 1219 } 1220 1221 private setTableHeight(height: string): void { 1222 this.scrollView!.scrollTop = 0; 1223 this.currentSelectionTbl!.style.height = height; 1224 this.wakeupListTbl!.style.display = 'none'; 1225 } 1226 1227 private addJankScrollCallBackEvent( 1228 scrollCallback: ((d: any) => void) | undefined, 1229 callback: ((data: Array<any>) => void) | undefined, 1230 jankJumperList: JankTreeNode[] 1231 ): void { 1232 let all = this.currentSelectionTbl?.shadowRoot?.querySelectorAll(`.jank_cla`); 1233 all!.forEach((a) => { 1234 a.addEventListener('click', () => { 1235 if (scrollCallback) { 1236 scrollCallback({ 1237 rowId: a.id, 1238 name: a.getAttribute('slice_name'), 1239 pid: a.getAttribute('pid'), 1240 }); 1241 } 1242 }); 1243 }); 1244 if (callback) { 1245 callback(jankJumperList); 1246 } 1247 } 1248 1249 async queryThreadStateDArgs(argSetID: number | undefined): Promise<BinderArgBean[]> { 1250 let list: Array<BinderArgBean> = []; 1251 if (argSetID !== undefined && argSetID > 0) { 1252 list = await queryThreadStateArgs(argSetID); 1253 } 1254 return list; 1255 } 1256 1257 /** 1258 * 查询出 线程被唤醒的 线程信息 1259 * @param data 1260 */ 1261 async queryCPUWakeUpFromData(data: CpuStruct): Promise<WakeupBean | null> { 1262 let wb: WakeupBean | null = null; 1263 if (data.id === undefined || data.startTime === undefined) { 1264 return null; 1265 } 1266 let wakeup = await queryRunnableTimeByRunning(data.tid!, data.startTime); 1267 if (wakeup && wakeup[0]) { 1268 let wakeupTs = wakeup[0].ts as number; 1269 let recordStartTs = (window as any).recordStartNS; 1270 let wf = await queryThreadWakeUpFrom(data.id, wakeupTs); 1271 if (wf && wf[0]) { 1272 wb = wf[0]; 1273 if (wb !== null) { 1274 wb.wakeupTime = wakeupTs - recordStartTs; 1275 wb.process = Utils.PROCESS_MAP.get(wb.pid!) || 'Process'; 1276 wb.thread = Utils.THREAD_MAP.get(wb.tid!) || 'Thread'; 1277 wb.schedulingLatency = (data.startTime || 0) - (wb.wakeupTime || 0); 1278 wb.schedulingDesc = INPUT_WORD; 1279 } 1280 } 1281 } 1282 return wb; 1283 } 1284 1285 /** 1286 * 查询出 线程被唤醒的 线程链信息 1287 * @param data 1288 */ 1289 static async queryCPUWakeUpListFromBean(data: WakeupBean): Promise<WakeupBean | null> { 1290 let wb: WakeupBean | null = null; 1291 let wakeup = await queryRunnableTimeByRunning(data.tid!, data.ts!); 1292 if (wakeup && wakeup[0]) { 1293 let wakeupTs = wakeup[0].ts as number; 1294 let recordStartTs = (window as any).recordStartNS; 1295 let wf = await queryThreadWakeUpFrom(data.itid!, wakeupTs); 1296 if (wf && wf[0]) { 1297 wb = wf[0]; 1298 if (wb !== null) { 1299 wb.wakeupTime = wakeupTs - recordStartTs; 1300 wb.process = Utils.PROCESS_MAP.get(wb.pid!) || 'Process'; 1301 wb.thread = Utils.THREAD_MAP.get(wb.tid!) || 'Thread'; 1302 wb.schedulingLatency = (data.ts || 0) - (wb.wakeupTime || 0); 1303 wb.schedulingDesc = INPUT_WORD; 1304 } 1305 } 1306 } 1307 return wb; 1308 } 1309 1310 /** 1311 * 查询出 线程唤醒了哪些线程信息 1312 */ 1313 async queryThreadWakeUpFromData(itid: number, startTime: number, dur: number): Promise<WakeupBean | undefined> { 1314 let wakeUps = await queryThreadWakeUpFrom(itid, startTime + (window as any).recordStartNS); 1315 if (wakeUps !== undefined && wakeUps.length > 0) { 1316 return wakeUps[0]; 1317 } 1318 } 1319 1320 /** 1321 * 查询出 线程唤醒了哪些线程信息 1322 */ 1323 async queryThreadWakeUpData(itid: number, startTime: number, dur: number): Promise<Array<WakeupBean>> { 1324 let list: Array<WakeupBean> = []; 1325 if (itid === undefined || startTime === undefined) { 1326 return list; 1327 } 1328 let wakeUps = await queryThreadWakeUp(itid, startTime, dur); // 3,4835380000 1329 if (wakeUps !== undefined && wakeUps.length > 0) { 1330 list.push(...wakeUps); 1331 } 1332 return list; 1333 } 1334 1335 initCanvas(): HTMLCanvasElement | null { 1336 let canvas = this.shadowRoot!.querySelector<HTMLCanvasElement>('#rightDraw'); 1337 let width = getComputedStyle(this.currentSelectionTbl!).getPropertyValue('width'); 1338 if (canvas !== null) { 1339 canvas.width = Math.round(Number(width.replace('px', '')) * this.dpr); 1340 canvas.height = Math.round(Number(200 * this.dpr)); 1341 canvas.style.width = width; 1342 canvas.style.height = '200px'; 1343 canvas.getContext('2d')!.scale(this.dpr, this.dpr); 1344 } 1345 SpApplication.skinChange = (val: boolean): void => { 1346 this.drawRight(canvas, this.weakUpBean!); 1347 }; 1348 return canvas; 1349 } 1350 1351 drawRight(cavs: HTMLCanvasElement | null, wakeupBean: WakeupBean | null): void { 1352 if (cavs === null) { 1353 return; 1354 } 1355 let context = cavs.getContext('2d'); 1356 if (context !== null) { 1357 //绘制竖线 1358 this.drawVerticalLine(context); 1359 //绘制菱形 1360 context.lineWidth = 1; 1361 context.beginPath(); 1362 context.moveTo(10, 30); 1363 context.lineTo(4, 40); 1364 context.lineTo(10, 50); 1365 context.lineTo(16, 40); 1366 context.lineTo(10, 30); 1367 context.closePath(); 1368 context.fill(); 1369 context.font = 12 + 'px sans-serif'; 1370 //绘制wake up 文字 1371 let strList = []; 1372 strList.push('wakeup @ ' + getTimeString(wakeupBean?.wakeupTime || 0) + ' on CPU ' + wakeupBean?.cpu + ' by'); 1373 strList.push('P:' + wakeupBean?.process + ' [ ' + wakeupBean?.pid + ' ]'); 1374 strList.push('T:' + wakeupBean?.thread + ' [ ' + wakeupBean?.tid + ' ]'); 1375 strList.forEach((str, index) => { 1376 if (context !== null) { 1377 context.fillText(str, 40, 40 + 16 * index); 1378 } 1379 }); 1380 context.lineWidth = 2; 1381 context.lineJoin = 'bevel'; 1382 context.moveTo(10, 95); 1383 context.lineTo(20, 90); 1384 context.moveTo(10, 95); 1385 context.lineTo(20, 100); 1386 context.moveTo(10, 95); 1387 context.lineTo(80, 95); 1388 context.lineTo(70, 90); 1389 context.moveTo(80, 95); 1390 context.lineTo(70, 100); 1391 context.stroke(); 1392 //绘制latency 1393 context.font = 12 + 'px sans-serif'; 1394 context.fillText('Scheduling latency:' + getTimeString(wakeupBean?.schedulingLatency || 0), 90, 100); 1395 //绘制最下方提示语句 1396 context.font = 10 + 'px sans-serif'; 1397 INPUT_WORD.split('\n').forEach((str, index) => { 1398 context?.fillText(str, 90, 120 + 12 * index); 1399 }); 1400 } 1401 } 1402 1403 private drawVerticalLine(context: CanvasRenderingContext2D): void { 1404 if (document.querySelector<SpApplication>('sp-application')!.dark) { 1405 context.strokeStyle = '#ffffff'; 1406 context.fillStyle = '#ffffff'; 1407 } else { 1408 context.strokeStyle = '#000000'; 1409 context.fillStyle = '#000000'; 1410 } 1411 context.lineWidth = 2; 1412 context.moveTo(10, 15); 1413 context.lineTo(10, 125); 1414 context.stroke(); 1415 } 1416 1417 transferString(str: string): string { 1418 let s = ''; 1419 if (str.length === 0) { 1420 return ''; 1421 } 1422 s = str.replace(/&/g, '&'); 1423 s = s.replace(/</g, '<'); 1424 s = s.replace(/>/g, '>'); 1425 s = s.replace(/\'/g, '''); 1426 s = s.replace(/\"/g, '&#quat;'); 1427 return s; 1428 } 1429 1430 initElements(): void { 1431 this.currentSelectionTbl = this.shadowRoot?.querySelector<LitTable>('#selectionTbl'); 1432 this.wakeupListTbl = this.shadowRoot?.querySelector<LitTable>('#wakeupListTbl'); 1433 this.scrollView = this.shadowRoot?.querySelector<HTMLDivElement>('#scroll_view'); 1434 this.currentSelectionTbl?.addEventListener('column-click', (ev: any) => { 1435 }); 1436 window.subscribe(window.SmartEvent.UI.WakeupList, (data: Array<WakeupBean>) => this.showWakeupListTableData(data)); 1437 } 1438 1439 showWakeupListTableData(data: Array<WakeupBean>) { 1440 this.wakeupListTbl!.style.display = 'flex'; 1441 let cpus: number[] = []; 1442 let itids: number[] = []; 1443 let ts: number[] = []; 1444 let maxPriority = 0; 1445 let maxPriorityDuration = 0; 1446 let maxDuration = 0; 1447 data.forEach((it) => { 1448 cpus.push(it.cpu!); 1449 itids.push(it.itid!); 1450 ts.push(it.ts!); 1451 }); 1452 queryWakeupListPriority(itids, ts, cpus).then((res) => { 1453 let resource = data.map((it) => { 1454 let wake = { 1455 process: `${it.process}(${it.pid})`, 1456 thread: `${it.thread}(${it.tid})`, 1457 cpu: it.cpu, 1458 dur: it.dur, 1459 priority: 0, 1460 isSelected: false, 1461 }; 1462 let find = res.find((re) => re.cpu === it.cpu && re.itid === it.itid && re.ts === it.ts); 1463 if (find) { 1464 wake.priority = find.priority; 1465 } 1466 maxDuration = Math.max(maxDuration, it.dur!); 1467 maxPriority = Math.max(maxPriority, wake.priority); 1468 return wake; 1469 }); 1470 if (this.selectWakeupBean) { 1471 // 点击第一层唤醒树时向数组头部添加当前点击信息 1472 if (data[0].schedulingLatency) { 1473 resource.unshift(this.selectWakeupBean); 1474 } 1475 maxDuration = Math.max(maxDuration, this.selectWakeupBean.dur); 1476 maxPriority = Math.max(maxPriority, this.selectWakeupBean.priority); 1477 } 1478 resource.forEach((it) => { 1479 if (it.priority === maxPriority) { 1480 maxPriorityDuration = Math.max(it.dur || 0, maxPriorityDuration); 1481 } 1482 }); 1483 this.updateTableSettings(maxPriority, maxPriorityDuration, maxDuration); 1484 this.wakeupListTbl!.recycleDataSource = resource; 1485 }); 1486 } 1487 1488 private updateTableSettings(maxPriority: number, maxPriorityDuration: number, maxDuration: number) { 1489 this.wakeupListTbl!.getItemTextColor = (data: any) => { 1490 if ((data.priority === maxPriority && data.dur === maxPriorityDuration) || data.dur === maxDuration) { 1491 return '#f44336'; 1492 } else { 1493 return '#262626'; 1494 } 1495 }; 1496 } 1497 1498 addTableObserver(): void { 1499 let leftTable = this.shadowRoot?.querySelector('.table-left'); 1500 this.tableObserver?.observe(leftTable!, { 1501 attributes: true, 1502 attributeFilter: ['style'], 1503 attributeOldValue: true, 1504 }); 1505 } 1506 1507 initHtml(): string { 1508 return TabPaneCurrentSelectionHtml; 1509 } 1510} 1511 1512export class JankTreeNode { 1513 name: string = ''; 1514 pid: number = -1; 1515 frame_type: string = ''; 1516 type: number = 0; 1517 1518 constructor(name: string, pid: number, frame_type: string) { 1519 this.name = name; 1520 this.pid = pid; 1521 this.frame_type = frame_type; 1522 } 1523 1524 children: Array<JankTreeNode> = []; 1525} 1526 1527export class ThreadTreeNode { 1528 tid: number = 0; 1529 pid: number = -1; 1530 startTime: number = 1; 1531 1532 constructor(tid: number, pid: number, startTime: number) { 1533 this.tid = tid; 1534 this.pid = pid; 1535 this.startTime = startTime; 1536 } 1537} 1538