1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseElement, element } from '../../../../base-ui/BaseElement.js'; 17import { LitTable } from '../../../../base-ui/table/lit-table.js'; 18import '../../../../base-ui/table/lit-table-column.js'; 19 20import { 21 queryBinderArgsByArgset, 22 queryBinderByArgsId, 23 queryBinderBySliceId, 24 queryFlowsData, 25 queryGpuDur, 26 queryPrecedingData, 27 queryRunnableTimeByRunning, 28 queryThreadStateArgs, 29 queryThreadWakeUp, 30 queryThreadWakeUpFrom, 31 queryThreadByItid, queryAnimationFrameFps, 32} from '../../../database/SqlLite.js'; 33import { WakeupBean } from '../../../bean/WakeupBean.js'; 34import { SpApplication } from '../../../SpApplication.js'; 35import { TraceRow } from '../base/TraceRow.js'; 36import { CpuStruct } from '../../../database/ui-worker/ProcedureWorkerCPU.js'; 37import { ThreadStruct } from '../../../database/ui-worker/ProcedureWorkerThread.js'; 38import { FuncStruct } from '../../../database/ui-worker/ProcedureWorkerFunc.js'; 39import { ProcessMemStruct } from '../../../database/ui-worker/ProcedureWorkerMem.js'; 40import { ClockStruct } from '../../../database/ui-worker/ProcedureWorkerClock.js'; 41import { ColorUtils } from '../base/ColorUtils.js'; 42import { IrqStruct } from '../../../database/ui-worker/ProcedureWorkerIrq.js'; 43import { BinderArgBean } from '../../../bean/BinderArgBean.js'; 44import { JankStruct } from '../../../database/ui-worker/ProcedureWorkerJank.js'; 45import { LitIcon } from '../../../../base-ui/icon/LitIcon.js'; 46import { Utils } from '../base/Utils.js'; 47import { SpSystemTrace } from '../../SpSystemTrace.js'; 48import { AppStartupStruct } from '../../../database/ui-worker/ProcedureWorkerAppStartup.js'; 49import { SoStruct } from '../../../database/ui-worker/ProcedureWorkerSoInit.js'; 50import { SelectionParam } from '../../../bean/BoxSelection.js'; 51import { FrameAnimationStruct } from '../../../database/ui-worker/ProcedureWorkerFrameAnimation.js'; 52 53const INPUT_WORD = 54 '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.'; 55 56export function getTimeString(ns: number): string { 57 if (ns === 0) { 58 return '0'; 59 } 60 let currentTimeNs = ns; 61 let hour1 = 3600_000_000_000; 62 let minute1 = 60_000_000_000; 63 let second1 = 1_000_000_000; // 1 second 64 let millisecond1 = 1_000_000; // 1 millisecond 65 let microsecond1 = 1_000; // 1 microsecond 66 let res = ''; 67 if (currentTimeNs >= hour1) { 68 res += Math.floor(currentTimeNs / hour1) + 'h '; 69 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / hour1) * hour1; 70 } 71 if (currentTimeNs >= minute1) { 72 res += Math.floor(currentTimeNs / minute1) + 'm '; 73 currentTimeNs = currentTimeNs - Math.floor(ns / minute1) * minute1; 74 } 75 if (currentTimeNs >= second1) { 76 res += Math.floor(currentTimeNs / second1) + 's '; 77 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / second1) * second1; 78 } 79 if (currentTimeNs >= millisecond1) { 80 res += Math.floor(currentTimeNs / millisecond1) + 'ms '; 81 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / millisecond1) * millisecond1; 82 } 83 if (currentTimeNs >= microsecond1) { 84 res += Math.floor(currentTimeNs / microsecond1) + 'μs '; 85 currentTimeNs = currentTimeNs - Math.floor(currentTimeNs / microsecond1) * microsecond1; 86 } 87 if (currentTimeNs > 0) { 88 res += currentTimeNs + 'ns '; 89 } 90 return res; 91} 92 93@element('tabpane-current-selection') 94export class TabPaneCurrentSelection extends BaseElement { 95 weakUpBean: WakeupBean | null | undefined; 96 private currentSelectionTbl: LitTable | null | undefined; 97 private tableObserver: MutationObserver | undefined; 98 private scrollView: HTMLDivElement | null | undefined; 99 // @ts-ignore 100 private dpr: any = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1; 101 102 set data(currentSelectionValue: any) { 103 if (currentSelectionValue !== undefined && currentSelectionValue.constructor 104 && currentSelectionValue.constructor.name !== 'SelectionParam') { 105 this.setCpuData(currentSelectionValue); 106 } 107 } 108 109 setCpuData( 110 data: CpuStruct, 111 callback: ((data: WakeupBean | null) => void) | undefined = undefined, 112 scrollCallback?: (data: CpuStruct) => void 113 ): void { 114 this.setTableHeight('550px'); 115 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 116 if (leftTitle) { 117 leftTitle.innerText = 'Slice Details'; 118 } 119 let list: any[] = []; 120 let process = this.transferString(data.processName || 'Process'); 121 let processId = data.processId || data.tid; 122 let state = ''; 123 if (data.end_state) { 124 state = Utils.getEndState(data.end_state); 125 } else if (data.end_state === '' || data.end_state === null) { 126 state = ''; 127 } else { 128 state = 'Unknown State'; 129 } 130 list.push({ 131 name: 'Process', 132 value: `${ process || 'Process' } [${ processId }]`, 133 }); 134 let name = this.transferString(data.name ?? ''); 135 if (data.processId) { 136 list.push({ 137 name: 'Thread', 138 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 139<div style="white-space:pre-wrap">${ name || 'Process' } [${ data.tid }]</div> 140<lit-icon style="cursor:pointer;margin-left: 5px" id="thread-id" name="select" color="#7fa1e7" size="20"></lit-icon> 141</div>`, 142 }); 143 } else { 144 list.push({ 145 name: 'Thread', 146 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 147<div style="white-space:pre-wrap">${ name || 'Process' } [${ data.tid }]</div> 148</div>`, 149 }); 150 } 151 152 list.push({ name: 'CmdLine', value: `${ data.processCmdLine }` }); 153 list.push({ 154 name: 'StartTime(Relative)', 155 value: getTimeString(data.startTime || 0), 156 }); 157 list.push({ 158 name: 'StartTime(Absolute)', 159 value: ((data.startTime || 0) + (window as any).recordStartNS) / 1000000000, 160 }); 161 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 162 list.push({ name: 'Prio', value: data.priority || 0 }); 163 list.push({ name: 'End State', value: state }); 164 Promise.all([this.queryThreadStateDArgs(data.argSetID), this.queryCPUWakeUpFromData(data)]).then((resArr) => { 165 let args = resArr[0]; 166 let bean = resArr[1]; 167 if (callback) { 168 callback(bean); 169 } 170 if (args.length > 0) { 171 args.forEach((arg) => { 172 list.push({ name: arg.keyName, value: arg.strValue }); 173 }); 174 } 175 this.currentSelectionTbl!.dataSource = list; 176 let rightArea: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#table-right'); 177 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 178 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 179 ?.querySelector('#rightButton') 180 ?.shadowRoot?.querySelector('#custom-button'); 181 let rightStar: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#right-star'); 182 let threadClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#thread-id'); 183 threadClick?.addEventListener('click', () => { 184 //cpu点击 185 if (scrollCallback) { 186 scrollCallback(data); 187 } 188 }); 189 let canvas = this.initCanvas(); 190 if (bean !== null) { 191 this.weakUpBean = bean; 192 if (rightArea !== null && rightArea) { 193 rightArea.style.visibility = 'visible'; 194 } 195 if (rightTitle !== null && rightTitle) { 196 rightTitle.style.visibility = 'visible'; 197 rightButton!.style.visibility = 'visible'; 198 rightStar!.style.visibility = 'hidden'; 199 SpSystemTrace.btnTimer = null; 200 } 201 this.drawRight(canvas, bean); 202 } else { 203 this.weakUpBean = null; 204 if (rightArea !== null && rightArea) { 205 rightArea.style.visibility = 'hidden'; 206 } 207 if (rightTitle !== null && rightTitle) { 208 rightTitle.style.visibility = 'hidden'; 209 rightButton!.style.visibility = 'hidden'; 210 rightStar!.style.visibility = 'hidden'; 211 } 212 } 213 }); 214 } 215 216 setFunctionData(data: FuncStruct, scrollCallback: Function): void { 217 //方法信息 218 this.tabCurrentSelectionInit('Slice Details'); 219 let list: any[] = []; 220 let name = this.transferString(data.funName ?? ''); 221 let isBinder = FuncStruct.isBinder(data); 222 let isAsyncBinder = isBinder && FuncStruct.isBinderAsync(data); 223 if (data.argsetid !== undefined && data.argsetid !== null) { 224 this.setTableHeight('550px'); 225 if (isAsyncBinder) { 226 Promise.all([ 227 queryBinderByArgsId(data.argsetid!, data.startTs!, !data.funName!.endsWith('rcv')), 228 queryBinderArgsByArgset(data.argsetid), 229 ]).then((result) => { 230 let asyncBinderRes = result[0]; 231 let argsBinderRes = result[1]; 232 let asyncBinderStract: any; 233 if (asyncBinderRes.length > 0) { 234 asyncBinderRes[0].type = TraceRow.ROW_TYPE_FUNC; 235 asyncBinderStract = asyncBinderRes[0]; 236 } 237 if (argsBinderRes.length > 0) { 238 argsBinderRes.forEach((item) => { 239 list.push({ 240 name: item.keyName, 241 value: item.strValue, 242 }); 243 }); 244 } 245 if (asyncBinderStract !== undefined) { 246 list.unshift({ 247 name: 'Name', 248 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 249<div style="white-space:pre-wrap">${ name || 'binder' }</div> 250<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 251</div>`, 252 }); 253 } else { 254 list.unshift({ name: 'Name', value: name }); 255 } 256 this.addTabPanelContent(list, data); 257 this.currentSelectionTbl!.dataSource = list; 258 let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); 259 funcClick?.addEventListener('click', () => { 260 scrollCallback(asyncBinderStract); 261 }); 262 }); 263 } else if (isBinder) { 264 queryBinderArgsByArgset(data.argsetid).then((argset) => { 265 let binderSliceId = -1; 266 argset.forEach((item) => { 267 if (item.keyName === 'destination slice id') { 268 binderSliceId = Number(item.strValue); 269 list.unshift({ 270 name: 'Name', 271 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 272<div style="white-space:pre-wrap">${ name || 'binder' }</div> 273<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 274</div>`, 275 }); 276 } 277 list.push({ name: item.keyName, value: item.strValue }); 278 }); 279 if (binderSliceId === -1) { 280 list.unshift({ name: 'Name', value: name }); 281 } 282 this.addTabPanelContent(list, data); 283 this.currentSelectionTbl!.dataSource = list; 284 let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); 285 funcClick?.addEventListener('click', () => { 286 if (!Number.isNaN(binderSliceId) && binderSliceId !== -1) { 287 queryBinderBySliceId(binderSliceId).then((result: any[]) => { 288 if (result.length > 0) { 289 result[0].type = TraceRow.ROW_TYPE_FUNC; 290 scrollCallback(result[0]); 291 } 292 }); 293 } 294 }); 295 }); 296 } else { 297 queryBinderArgsByArgset(data.argsetid).then((argset) => { 298 list.push({ name: 'Name', value: name }); 299 argset.forEach((item) => { 300 list.push({ name: item.keyName, value: item.strValue }); 301 }); 302 this.addTabPanelContent(list, data); 303 this.currentSelectionTbl!.dataSource = list; 304 }); 305 } 306 } else { 307 this.setTableHeight('auto'); 308 list.push({ name: 'Name', value: name }); 309 list.push({ 310 name: 'StartTime', 311 value: getTimeString(data.startTs || 0), 312 }); 313 list.push({ 314 name: 'Duration', 315 value: getTimeString(data.dur || 0), 316 }); 317 list.push({ name: 'depth', value: data.depth }); 318 this.currentSelectionTbl!.dataSource = list; 319 } 320 } 321 322 private addTabPanelContent(contentList: any[], data: FuncStruct): void{ 323 contentList.push({ 324 name: 'StartTime', 325 value: getTimeString(data.startTs || 0), 326 }); 327 contentList.push({ 328 name: 'Duration', 329 value: getTimeString(data.dur || 0), 330 }); 331 contentList.push({ name: 'depth', value: data.depth }); 332 contentList.push({ name: 'arg_set_id', value: data.argsetid }); 333 } 334 335 private tabCurrentSelectionInit(leftTitleStr: string): void { 336 this.initCanvas(); 337 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 338 this.setTitleAndButtonStyle(); 339 if (leftTitle) { 340 leftTitle.innerText = leftTitleStr; 341 } 342 } 343 344 private setTitleAndButtonStyle(): void{ 345 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 346 let rightButton: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightButton')?. 347 shadowRoot?.querySelector('#custom-button'); 348 let rightStar: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#right-star'); 349 if (rightTitle) { 350 rightTitle.style.visibility = 'hidden'; 351 rightButton!.style.visibility = 'hidden'; 352 rightStar!.style.visibility = 'hidden'; 353 } 354 } 355 356 setClockData(data: ClockStruct): void { 357 this.setTableHeight('auto'); 358 //时钟信息 359 this.tabCurrentSelectionInit('Counter Details'); 360 let list: any[] = []; 361 list.push({ 362 name: 'Start time', 363 value: getTimeString(data.startNS || 0), 364 }); 365 list.push({ 366 name: 'Value', 367 value: ColorUtils.formatNumberComma(data.value || 0), 368 }); 369 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 370 this.currentSelectionTbl!.dataSource = list; 371 } 372 373 setMemData(data: ProcessMemStruct): void { 374 this.setTableHeight('auto'); 375 //时钟信息 376 this.initCanvas(); 377 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 378 if (leftTitle) { 379 leftTitle.innerText = 'Counter Details'; 380 } 381 let list: any[] = []; 382 list.push({ 383 name: 'Start time', 384 value: getTimeString(data.startTime || 0), 385 }); 386 list.push({ name: 'Value', value: data.value }); 387 list.push({ name: 'Delta', value: data.delta }); 388 list.push({ 389 name: 'Duration', 390 value: getTimeString(data.duration || 0), 391 }); 392 this.currentSelectionTbl!.dataSource = list; 393 } 394 395 setIrqData(data: IrqStruct): void { 396 this.setTableHeight('550px'); 397 this.initCanvas(); 398 this.setTitleAndButtonStyle(); 399 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 400 if (leftTitle) { 401 leftTitle.innerText = 'Counter Details'; 402 } 403 let list: any[] = []; 404 list.push({ 405 name: 'Start time', 406 value: getTimeString(data.startNS || 0), 407 }); 408 list.push({ name: 'Name', value: data.name }); 409 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 410 queryBinderArgsByArgset(data.argSetId || 0).then((argsBinderRes) => { 411 if (argsBinderRes.length > 0) { 412 argsBinderRes.forEach((item) => { 413 list.push({ name: item.keyName, value: item.strValue }); 414 }); 415 } 416 this.currentSelectionTbl!.dataSource = list; 417 }); 418 } 419 420 setThreadData( 421 data: ThreadStruct, 422 scrollCallback: ((d: any) => void) | undefined, 423 scrollWakeUp: (d: any) => void | undefined, 424 scrollPreviousData: (d: any) => void | undefined, 425 scrollNextData: (d: any) => void | undefined 426 ): void { 427 //线程信息 428 this.setTableHeight('550px'); 429 this.initCanvas(); 430 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 431 this.setTitleAndButtonStyle(); 432 if (leftTitle) { 433 leftTitle.innerText = 'Thread State'; 434 } 435 let list: any[] = []; 436 list.push({ 437 name: 'StartTime', 438 value: getTimeString(data.startTime || 0), 439 }); 440 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 441 let state; 442 if (data.state) { 443 state = Utils.getEndState(data.state); 444 } else if (data.state === '' || data.state === null) { 445 state = ''; 446 } else { 447 state = 'Unknown State'; 448 } 449 if ('Running' === state) { 450 state = state + ' on CPU ' + data.cpu; 451 } 452 if (data.cpu === null || data.cpu === undefined) { 453 list.push({ name: 'State', value: `${ state }` }); 454 } else { 455 list.push({ 456 name: 'State', 457 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 458 <div style="white-space:pre-wrap">${ state }</div> 459 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 460 </div>`, 461 }); 462 } 463 let slice = Utils.SCHED_SLICE_MAP.get(`${ data.id }-${ data.startTime }`); 464 if (slice) { 465 list.push({ name: 'Prio', value: `${ slice.priority }` }); 466 } 467 let processName = Utils.PROCESS_MAP.get(data.pid!); 468 if (processName === null || processName === undefined || processName === '' || processName.toLowerCase() == 'null') { 469 processName = Utils.THREAD_MAP.get(data.tid!) || 'null'; 470 } 471 list.push({ 472 name: 'Process', 473 value: this.transferString(processName ?? '') + ' [' + data.pid + '] ', 474 }); 475 let ThreadRow = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-system-trace')!.shadowRoot?.querySelector<TraceRow<ThreadStruct>>( 476 `trace-row[row-id='${data.tid}'][row-type='thread']` 477 ); 478 ThreadRow?.dataList.forEach((item, index) => { 479 if (item === data && index !== 0) { 480 let previousState = ThreadRow?.dataList[index - 1].state; 481 if (previousState === 'R') { 482 previousState = 'Runnable'; 483 } 484 if (previousState === 'S') { 485 previousState = 'Sleeping'; 486 } 487 list.push({ 488 name: 'Previous State', 489 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 490 <div style="white-space:pre-wrap">${previousState}</div> 491 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="previous-state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 492 </div>`, 493 }); 494 } 495 if (item === data && index !== ThreadRow?.dataList.length! - 1) { 496 let nextState = ThreadRow?.dataList[index + 1].state; 497 if (nextState === 'R') { 498 nextState = 'Runnable'; 499 } 500 if (nextState === 'S') { 501 nextState = 'Sleeping'; 502 } 503 list.push({ 504 name: 'Next State', 505 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 506 <div style="white-space:pre-wrap">${nextState}</div> 507 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="next-state-click" name="select" color="#7fa1e7" size="20"></lit-icon> 508 </div>`, 509 }); 510 } 511 } 512 ); 513 let cpu = new CpuStruct(); 514 cpu.id = data.id; 515 cpu.startTime = data.startTime; 516 Promise.all([ 517 this.queryThreadWakeUpFromData(data.id!, data.startTime!, data.dur!), 518 this.queryThreadWakeUpData(data.id!, data.startTime!, data.dur!), 519 this.queryThreadStateDArgs(data.argSetID), 520 ]).then((result) => { 521 let fromBean = result[0]; 522 let wakeUps = result[1]; 523 let args = result[2]; 524 if (fromBean !== null && fromBean !== undefined && fromBean.pid !== 0 && fromBean.tid !== 0) { 525 list.push({ 526 name: 'wakeup from tid', 527 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 528 <div style="white-space:pre-wrap">${ fromBean.tid }</div> 529 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-from" class="wakeup-click" name="select" color="#7fa1e7" size="20"></lit-icon> 530 </div>`, 531 }); 532 } 533 if (wakeUps !== null) { 534 wakeUps.map((e) => { 535 list.push({ 536 name: 'wakeup tid', 537 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 538 <div style="white-space:pre-wrap">${ e.tid }</div> 539 <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-${ e.tid }" class="wakeup-click" name="select" color="#7fa1e7" size="20"></lit-icon> 540 </div>`, 541 }); 542 }); 543 } 544 if (args.length > 0) { 545 args.forEach((arg) => { 546 list.push({ name: arg.keyName, value: arg.strValue }); 547 }); 548 } 549 this.currentSelectionTbl!.dataSource = list; 550 this.currentSelectionTbl?.shadowRoot?.querySelector('#next-state-click')?.addEventListener('click', () => { 551 if (scrollNextData) { 552 scrollNextData(data); 553 } 554 }); 555 this.currentSelectionTbl?.shadowRoot?.querySelector('#previous-state-click')?.addEventListener('click', () => { 556 if (scrollPreviousData) { 557 scrollPreviousData(data); 558 } 559 }); 560 this.currentSelectionTbl?.shadowRoot?.querySelector('#state-click')?.addEventListener('click', () => { 561 //线程点击 562 if (scrollCallback) { 563 scrollCallback(data); 564 } 565 }); 566 this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-from')?.addEventListener('click', () => { 567 //点击跳转,唤醒和被唤醒的 线程 568 if (fromBean && scrollWakeUp) { 569 scrollWakeUp({ 570 processId: fromBean.pid, 571 tid: fromBean.tid, 572 startTime: fromBean.ts, 573 }); 574 } 575 }); 576 if (wakeUps) { 577 wakeUps.map((up) => { 578 this.currentSelectionTbl?.shadowRoot?.querySelector(`#wakeup-${ up.tid }`)?.addEventListener('click', () => { 579 //点击跳转,唤醒和被唤醒的 线程 580 if (up && scrollWakeUp !== undefined) { 581 scrollWakeUp({ 582 tid: up.tid, 583 startTime: up.ts, 584 processId: up.pid, 585 }); 586 } 587 }); 588 }); 589 } 590 }); 591 } 592 593 setJankData( 594 data: JankStruct, 595 callback: ((data: Array<any>) => void) | undefined = undefined, 596 scrollCallback: ((d: any) => void) | undefined 597 ): void { 598 //线程信息 599 this.setTableHeight('750px'); 600 this.tabCurrentSelectionInit('Slice Details'); 601 let list: any[] = []; 602 this.setJankCommonMessage(list, data); 603 if (data.type == '0') { 604 this.setJankType(data, list); 605 let jankJumperList = new Array<JankTreeNode>(); 606 if (data.frame_type === 'render_service') { 607 queryGpuDur(data.id!).then((it) => { 608 if (it.length > 0) { 609 list.push({ name: `<div>Gpu Duration</div>`, value: getTimeString(it[0].gpu_dur) }); 610 } 611 }); 612 if (data.src_slice) { 613 queryFlowsData(data.src_slice!.split(',')).then((it) => { 614 if (it.length > 0) { 615 list.push({ 616 name: `<div style="padding:5px 0 5px 0;">FrameTimeLine flows</div>`, 617 value: '', 618 }); 619 it.forEach((a: any) => { 620 let appNode = new JankTreeNode(a.name, a.pid, 'app'); 621 appNode.children.push(new JankTreeNode(a.name, a.pid, 'frameTime')); 622 jankJumperList.push(appNode); 623 list.push({ 624 name: `<div>Slice</div>`, 625 value: 626 a.cmdline + 627 ' [' + 628 a.name + 629 ']' + 630 `<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>`, 631 }); 632 }); 633 list.push({ 634 name: `<div style="padding:5px 0 5px 0;">Following flows</div>`, 635 value: '', 636 }); 637 it.forEach((a: any) => { 638 list.push({ 639 name: `<div>Slice</div>`, 640 value: 641 a.cmdline + 642 ' [' + 643 a.name + 644 ']' + 645 `<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>`, 646 }); 647 }); 648 this.currentSelectionTbl!.dataSource = list; 649 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 650 } 651 }); 652 } else { 653 this.currentSelectionTbl!.dataSource = list; 654 } 655 } else if (data.frame_type === 'app') { 656 list.push({ 657 name: `<div style="padding:5px 0 5px 0;">FrameTimeLine flows</div>`, 658 value: '', 659 }); 660 list.push({ 661 name: `<div>Slice</div>`, 662 value: 663 data.cmdline + 664 ' [' + 665 data.name + 666 ']' + 667 `<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>`, 668 }); 669 let timeLineNode = new JankTreeNode(data.name!, data.pid!, 'frameTime'); 670 jankJumperList.push(timeLineNode); 671 if (data.dst_slice) { 672 queryPrecedingData(data.dst_slice).then((it) => { 673 if (it.length > 0) { 674 list.push({ 675 name: `<div style="padding:5px 0 5px 0;">Preceding flows</div>`, 676 value: '', 677 }); 678 it.forEach((a: any) => { 679 let rsNode = new JankTreeNode(a.name, a.pid, 'render_service'); 680 jankJumperList.push(rsNode); 681 list.push({ 682 name: `<div>Slice</div>`, 683 value: 684 a.cmdline + 685 ' [' + 686 a.name + 687 ']' + 688 `<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>`, 689 }); 690 }); 691 this.currentSelectionTbl!.dataSource = list; 692 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 693 } 694 }); 695 } else { 696 this.currentSelectionTbl!.dataSource = list; 697 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 698 } 699 } else if (data.frame_type === 'frameTime') { 700 queryGpuDur(data.id!).then((it) => { 701 if (it.length > 0) { 702 list.push({ 703 name: `<div>Gpu Duration</div>`, 704 value: getTimeString(it[0].gpu_dur), 705 }); 706 } 707 if (data.name) { 708 list.push({ 709 name: `<div style="padding:5px 0 5px 0;">App Frame</div>`, 710 value: '', 711 }); 712 list.push({ 713 name: `<div>Process</div>`, 714 value: data.cmdline + ' ' + data.pid, 715 }); 716 list.push({ 717 name: `<div>start time</div>`, 718 value: getTimeString(data.ts || 0), 719 }); 720 list.push({ 721 name: `<div>end time</div>`, 722 value: getTimeString(data!.ts! + data.dur! || 0), 723 }); 724 } 725 if (data.rs_name) { 726 list.push({ 727 name: `<div style="padding:5px 0 5px 0;">RenderService Frame</div>`, 728 value: '', 729 }); 730 list.push({ 731 name: `<div>Process</div>`, 732 value: data.rs_name + ' ' + data.rs_pid, 733 }); 734 list.push({ 735 name: `<div>start time</div>`, 736 value: getTimeString(data.rs_ts || 0), 737 }); 738 list.push({ 739 name: `<div>end time</div>`, 740 value: getTimeString(data.rs_ts! + data.rs_dur! || 0), 741 }); 742 } 743 list.push({ 744 name: `<div style="padding:5px 0 5px 0;">Following</div>`, 745 value: '', 746 }); 747 list.push({ 748 name: `<div>Slice</div>`, 749 value: 750 data.cmdline + 751 ' [' + 752 data.name + 753 ']' + 754 `<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>`, 755 }); 756 let appNode = new JankTreeNode(data.name!, data.pid!, 'app'); 757 let rsNode = new JankTreeNode(data.rs_vsync!, data.rs_pid!, 'render_service'); 758 appNode.children.push(rsNode); 759 jankJumperList.push(appNode); 760 this.currentSelectionTbl!.dataSource = list; 761 this.addJankScrollCallBackEvent(scrollCallback, callback, jankJumperList); 762 }); 763 } 764 } else { 765 this.currentSelectionTbl!.dataSource = list; 766 } 767 } 768 769 setStartupData(data: AppStartupStruct, scrollCallback: Function): void { 770 this.setTableHeight('550px'); 771 this.initCanvas(); 772 let rightButton: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightButton')?.shadowRoot?. 773 querySelector('#custom-button'); 774 let startUpRightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 775 if (startUpRightTitle) { 776 startUpRightTitle.style.visibility = 'hidden'; 777 rightButton!.style.visibility = 'hidden'; 778 } 779 let startUpLeftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 780 if (startUpLeftTitle) { 781 startUpLeftTitle.innerText = 'Details'; 782 } 783 let list: any[] = []; 784 list.push({ name: 'Name', value: AppStartupStruct.getStartupName(data.startName) }); 785 list.push({ 786 name: 'StartTime(Relative)', 787 value: ` 788 <div style="display: flex;white-space: nowrap;align-items: center"> 789<div style="white-space:pre-wrap">${ getTimeString(data.startTs || 0) }</div> 790<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="start-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 791</div>`, 792 }); 793 list.push({ 794 name: 'StartTime(Absolute)', 795 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000, 796 }); 797 if (data.dur && data.dur > 0) { 798 list.push({ 799 name: 'EndTime(Relative)', 800 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 801<div style="white-space:pre-wrap">${ getTimeString((data.startTs || 0) + (data.dur || 0)) }</div> 802<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="end-jump" name="select" color="#7fa1e7" size="20"></lit-icon> 803</div>`, 804 }); 805 list.push({ 806 name: 'EndTime(Absolute)', 807 value: ((data.startTs || 0) + (data.dur || 0) + (window as any).recordStartNS) / 1000000000, 808 }); 809 } else { 810 list.push({ 811 name: 'EndTime(Relative)', 812 value: `Unknown Time`, 813 }); 814 list.push({ 815 name: 'EndTime(Absolute)', 816 value: 'Unknown Time', 817 }); 818 } 819 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 820 this.currentSelectionTbl!.dataSource = list; 821 let startIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#start-jump'); 822 let endIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#end-jump'); 823 let scrollClick = (type: number): void => { 824 let recordNs: number = (window as any).recordStartNS; 825 let useEnd = type === 1 && data.startName! < 4; 826 queryThreadByItid( 827 useEnd ? data.endItid! : data.itid!, 828 useEnd ? recordNs + data.startTs! + data.dur! : recordNs + data.startTs! 829 ).then((result) => { 830 if (result.length > 0) { 831 let pt: { pid: number; tid: number; dur: number, name: string, depth: number } = result[0]; 832 scrollCallback({ 833 pid: pt.pid, 834 tid: pt.tid, 835 type: 'func', 836 dur: pt.dur, 837 depth: pt.depth, 838 funName: pt.name, 839 startTime: useEnd ? (data.startTs || 0) + (data.dur || 0) : data.startTs, 840 keepOpen: true, 841 }); 842 } 843 }); 844 }; 845 if (startIcon) { 846 startIcon.addEventListener('click', () => scrollClick(0)); 847 } 848 if (endIcon) { 849 endIcon.addEventListener('click', () => scrollClick(1)); 850 } 851 } 852 853 setStaticInitData(data: SoStruct, scrollCallback: Function): void { 854 this.setTableHeight('550px'); 855 this.initCanvas(); 856 let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#rightTitle'); 857 let rightButton: HTMLElement | null | undefined = this?.shadowRoot 858 ?.querySelector('#rightButton') 859 ?.shadowRoot?.querySelector('#custom-button'); 860 if (rightTitle) { 861 rightTitle.style.visibility = 'hidden'; 862 rightButton!.style.visibility = 'hidden'; 863 } 864 let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); 865 if (leftTitle) { 866 leftTitle.innerText = 'Details'; 867 } 868 let list: any[] = []; 869 list.push({ name: 'Name', value: data.soName }); 870 list.push({ 871 name: 'StartTime(Relative)', 872 value: `<div style="white-space: nowrap;display: flex;align-items: center"> 873<div style="white-space:pre-wrap">${ getTimeString(data.startTs || 0) }</div> 874<lit-icon id="start-jump" style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" name="select" color="#7fa1e7" size="20"></lit-icon> 875</div>`, 876 }); 877 list.push({ 878 name: 'StartTime(Absolute)', 879 value: ((data.startTs || 0) + (window as any).recordStartNS) / 1000000000, 880 }); 881 list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); 882 this.currentSelectionTbl!.dataSource = list; 883 let startIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#start-jump'); 884 if (startIcon) { 885 startIcon.addEventListener('click', () => { 886 let recordNs: number = (window as any).recordStartNS; 887 queryThreadByItid(data.itid!, recordNs + data.startTs!).then((result) => { 888 if (result.length > 0) { 889 let pt: { pid: number; tid: number; dur: number, name: string, depth: number } = result[0]; 890 scrollCallback({ 891 pid: pt.pid, 892 tid: pt.tid, 893 type: 'func', 894 dur: pt.dur, 895 depth: pt.depth, 896 funName: pt.name, 897 startTime: data.startTs, 898 keepOpen: true, 899 }); 900 } 901 }); 902 }); 903 } 904 } 905 906 async setFrameAnimationData( 907 data: FrameAnimationStruct 908 ): Promise<void> { 909 this.setTableHeight('550px'); 910 this.tabCurrentSelectionInit('Animation Details'); 911 let list = []; 912 let dataTs: number = data.ts < 0 ? 0 : data.ts; 913 list.push({ name: 'Name', value: data.animationId }); 914 list.push({ name: 'Start time', value: `${ Utils.getTimeString(dataTs) }` }); 915 list.push({ name: 'End time', value: `${ Utils.getTimeString(dataTs + (data.dur || 0)) }` }); 916 list.push({ name: 'Duration', value: `${ Utils.getTimeString(data.dur || 0) }`, }); 917 if (data.status === 'Completion delay') { 918 let result = await queryAnimationFrameFps(dataTs, dataTs + data.dur); 919 if (result.length > 0) { 920 let fixedNumber: number = 2; 921 let fpsValue: number = result[0].fps / (data.dur / 1000_000_000) ; 922 list.push({ name: 'FPS', value: `${ fpsValue.toFixed(fixedNumber) || 0 }` }); 923 } 924 } 925 this.currentSelectionTbl!.dataSource = list; 926 } 927 928 private setJankType(data: JankStruct, list: any[]): void { 929 if (data.jank_tag === 1) { 930 if (data.frame_type === 'render_service') { 931 list.push({ name: 'Jank Type', value: 'RenderService Deadline Missed' }); 932 } else if (data.frame_type === 'app') { 933 list.push({ name: 'Jank Type', value: 'APP Deadline Missed' }); 934 } else if (data.frame_type === 'frameTime') { 935 list.push({ name: 'Jank Type', value: 'Deadline Missed' }); 936 } 937 } else if (data.jank_tag === 3) { 938 list.push({ name: 'Jank Type', value: 'Deadline Missed' }); 939 } else { 940 list.push({ name: 'Jank Type', value: 'NONE' }); 941 } 942 } 943 944 private setJankCommonMessage(list: any[], data: JankStruct): void { 945 list.push({ name: 'Name', value: data.name }); 946 list.push({ name: 'StartTime', value: getTimeString(data.ts || 0) }); 947 list.push({ name: 'Absolute Time', value: ((window as any).recordStartNS + data.ts) / 1000000000 }); 948 list.push({ name: 'Duration', value: data.dur ? getTimeString(data.dur) : ' ' }); 949 if (data.frame_type !== 'frameTime') { 950 list.push({ name: 'Process', value: data.cmdline + ' ' + data.pid }); 951 } 952 } 953 954 private setTableHeight(height: string): void { 955 this.scrollView!.scrollTop = 0; 956 this.currentSelectionTbl!.style.height = height; 957 } 958 959 private addJankScrollCallBackEvent( 960 scrollCallback: ((d: any) => void) | undefined, 961 callback: ((data: Array<any>) => void) | undefined, 962 jankJumperList: JankTreeNode[] 963 ): void { 964 let all = this.currentSelectionTbl?.shadowRoot?.querySelectorAll(`.jank_cla`); 965 all!.forEach((a) => { 966 a.addEventListener('click', () => { 967 if (scrollCallback) { 968 scrollCallback({ 969 rowId: a.id, 970 name: a.getAttribute('slice_name'), 971 pid: a.getAttribute('pid'), 972 }); 973 } 974 }); 975 }); 976 if (callback) { 977 callback(jankJumperList); 978 } 979 } 980 981 async queryThreadStateDArgs(argSetID: number | undefined): Promise<BinderArgBean[]> { 982 let list: Array<BinderArgBean> = []; 983 if (argSetID !== undefined) { 984 list = await queryThreadStateArgs(argSetID); 985 } 986 return list; 987 } 988 989 /** 990 * 查询出 线程被唤醒的 线程信息 991 * @param data 992 */ 993 async queryCPUWakeUpFromData(data: CpuStruct): Promise<WakeupBean | null> { 994 let wb: WakeupBean | null = null; 995 if (data.id === undefined || data.startTime === undefined) { 996 return null; 997 } 998 let wakeup = await queryRunnableTimeByRunning(data.tid!, data.startTime); 999 if (wakeup && wakeup[0]) { 1000 let wakeupTs = wakeup[0].ts as number; 1001 let recordStartTs = (window as any).recordStartNS; 1002 let wf = await queryThreadWakeUpFrom(data.id, wakeupTs); 1003 if (wf && wf[0]) { 1004 wb = wf[0]; 1005 if (wb !== null) { 1006 wb.wakeupTime = wakeupTs - recordStartTs; 1007 wb.process = Utils.PROCESS_MAP.get(wb.pid!); 1008 wb.thread = Utils.THREAD_MAP.get(wb.tid!); 1009 wb.schedulingLatency = (data.startTime || 0) - (wb.wakeupTime || 0); 1010 if (wb.process === null) { 1011 wb.process = wb.thread; 1012 } 1013 if (wb.pid === undefined) { 1014 wb.pid = wb.tid; 1015 } 1016 wb.schedulingDesc = INPUT_WORD; 1017 } 1018 } 1019 } 1020 return wb; 1021 } 1022 1023 /** 1024 * 查询出 线程被唤醒的 线程链信息 1025 * @param data 1026 */ 1027 static async queryCPUWakeUpListFromBean(data: WakeupBean): Promise<WakeupBean | null> { 1028 let wb: WakeupBean | null = null; 1029 let wakeup = await queryRunnableTimeByRunning(data.tid!, data.ts!); 1030 if (wakeup && wakeup[0]) { 1031 let wakeupTs = wakeup[0].ts as number; 1032 let recordStartTs = (window as any).recordStartNS; 1033 let wf = await queryThreadWakeUpFrom(data.itid!, wakeupTs); 1034 if (wf && wf[0]) { 1035 wb = wf[0]; 1036 if (wb !== null) { 1037 wb.wakeupTime = wakeupTs - recordStartTs; 1038 wb.process = Utils.PROCESS_MAP.get(wb.pid!); 1039 wb.thread = Utils.THREAD_MAP.get(wb.tid!); 1040 wb.schedulingLatency = (data.ts || 0) - (wb.wakeupTime || 0); 1041 if (wb.process === null) { 1042 wb.process = wb.thread; 1043 } 1044 if (wb.pid === undefined) { 1045 wb.pid = wb.tid; 1046 } 1047 wb.schedulingDesc = INPUT_WORD; 1048 } 1049 } 1050 } 1051 return wb; 1052 } 1053 1054 /** 1055 * 查询出 线程唤醒了哪些线程信息 1056 */ 1057 async queryThreadWakeUpFromData(itid: number, startTime: number, dur: number): Promise<WakeupBean | undefined> { 1058 let wakeUps = await queryThreadWakeUpFrom(itid, startTime + (window as any).recordStartNS); 1059 if (wakeUps !== undefined && wakeUps.length > 0) { 1060 return wakeUps[0]; 1061 } 1062 } 1063 1064 /** 1065 * 查询出 线程唤醒了哪些线程信息 1066 */ 1067 async queryThreadWakeUpData(itid: number, startTime: number, dur: number): Promise<Array<WakeupBean>> { 1068 let list: Array<WakeupBean> = []; 1069 if (itid === undefined || startTime === undefined) { 1070 return list; 1071 } 1072 let wakeUps = await queryThreadWakeUp(itid, startTime, dur); // 3,4835380000 1073 if (wakeUps !== undefined && wakeUps.length > 0) { 1074 list.push(...wakeUps); 1075 } 1076 return list; 1077 } 1078 1079 initCanvas(): HTMLCanvasElement | null { 1080 let canvas = this.shadowRoot!.querySelector<HTMLCanvasElement>('#rightDraw'); 1081 let width = getComputedStyle(this.currentSelectionTbl!).getPropertyValue('width'); 1082 let height = getComputedStyle(this.currentSelectionTbl!).getPropertyValue('height'); 1083 if (canvas !== null) { 1084 canvas.width = Math.round(Number(width.replace('px', '')) * this.dpr); 1085 canvas.height = Math.round(Number(height.replace('px', '')) * this.dpr); 1086 canvas.style.width = width; 1087 canvas.style.height = height; 1088 canvas.getContext('2d')!.scale(this.dpr, this.dpr); 1089 } 1090 SpApplication.skinChange = (val: boolean): void => { 1091 this.drawRight(canvas, this.weakUpBean!); 1092 }; 1093 return canvas; 1094 } 1095 1096 drawRight(cavs: HTMLCanvasElement | null, wakeupBean: WakeupBean | null): void { 1097 if (cavs === null) { 1098 return; 1099 } 1100 let context = cavs.getContext('2d'); 1101 if (context !== null) { 1102 //绘制竖线 1103 if (document.querySelector<SpApplication>('sp-application')!.dark) { 1104 context.strokeStyle = '#ffffff'; 1105 context.fillStyle = '#ffffff'; 1106 } else { 1107 context.strokeStyle = '#000000'; 1108 context.fillStyle = '#000000'; 1109 } 1110 context.lineWidth = 2; 1111 context.moveTo(10, 15); 1112 context.lineTo(10, 125); 1113 context.stroke(); 1114 //绘制菱形 1115 context.lineWidth = 1; 1116 context.beginPath(); 1117 context.moveTo(10, 30); 1118 context.lineTo(4, 40); 1119 context.lineTo(10, 50); 1120 context.lineTo(16, 40); 1121 context.lineTo(10, 30); 1122 context.closePath(); 1123 context.fill(); 1124 context.font = 12 + 'px sans-serif'; 1125 //绘制wake up 文字 1126 let strList = []; 1127 strList.push('wakeup @ ' + getTimeString(wakeupBean?.wakeupTime || 0) + ' on CPU ' + wakeupBean?.cpu + ' by'); 1128 strList.push('P:' + wakeupBean?.process + ' [ ' + wakeupBean?.pid + ' ]'); 1129 strList.push('T:' + wakeupBean?.thread + ' [ ' + wakeupBean?.tid + ' ]'); 1130 strList.forEach((str, index) => { 1131 if (context !== null) { 1132 context.fillText(str, 40, 40 + 16 * index); 1133 } 1134 }); 1135 context.lineWidth = 2; 1136 context.lineJoin = 'bevel'; 1137 context.moveTo(10, 95); 1138 context.lineTo(20, 90); 1139 context.moveTo(10, 95); 1140 context.lineTo(20, 100); 1141 context.moveTo(10, 95); 1142 context.lineTo(80, 95); 1143 context.lineTo(70, 90); 1144 context.moveTo(80, 95); 1145 context.lineTo(70, 100); 1146 context.stroke(); 1147 //绘制latency 1148 context.font = 12 + 'px sans-serif'; 1149 context.fillText('Scheduling latency:' + getTimeString(wakeupBean?.schedulingLatency || 0), 90, 100); 1150 //绘制最下方提示语句 1151 context.font = 10 + 'px sans-serif'; 1152 INPUT_WORD.split('\n').forEach((str, index) => { 1153 context?.fillText(str, 90, 120 + 12 * index); 1154 }); 1155 } 1156 } 1157 1158 transferString(str: string): string { 1159 let s = ''; 1160 if (str.length === 0) { 1161 return ''; 1162 } 1163 s = str.replace(/&/g, '&'); 1164 s = s.replace(/</g, '<'); 1165 s = s.replace(/>/g, '>'); 1166 s = s.replace(/\'/g, '''); 1167 s = s.replace(/\"/g, '&#quat;'); 1168 return s; 1169 } 1170 1171 initElements(): void { 1172 this.currentSelectionTbl = this.shadowRoot?.querySelector<LitTable>('#selectionTbl'); 1173 this.scrollView = this.shadowRoot?.querySelector<HTMLDivElement>('#scroll_view'); 1174 this.currentSelectionTbl?.addEventListener('column-click', (ev: any) => {}); 1175 } 1176 1177 addTableObserver(): void { 1178 let leftTable = this.shadowRoot?.querySelector('.table-left'); 1179 this.tableObserver?.observe(leftTable!, { 1180 attributes: true, 1181 attributeFilter: ['style'], 1182 attributeOldValue: true, 1183 }); 1184 } 1185 1186 initHtml(): string { 1187 return ` 1188 <style> 1189 .table-title{ 1190 top: 0; 1191 background: var(--dark-background,#ffffff); 1192 position: sticky; 1193 width: 100%; 1194 display: flex; 1195 } 1196 .table-title > h2{ 1197 font-size: 16px; 1198 font-weight: 400; 1199 visibility: visible; 1200 width: 50%; 1201 padding: 0 10px; 1202 } 1203 #rightTitle{ 1204 width: 50%; 1205 display: flex; 1206 justify-content: space-between; 1207 padding: 0 10px; 1208 font-size: 16px; 1209 font-weight: 400; 1210 visibility: visible; 1211 } 1212 #rightTitle > h2{ 1213 font-size: 16px; 1214 font-weight: 400; 1215 } 1216 #rightButton{ 1217 padding-top:12px; 1218 } 1219 .right{ 1220 display: flex; 1221 } 1222 #right-star{ 1223 padding-top: 10px; 1224 visibility: hidden; 1225 } 1226 .scroll-area{ 1227 display: flex; 1228 flex-direction: row; 1229 flex: 1; 1230 } 1231 .table-left{ 1232 width: 50%; 1233 height: auto; 1234 padding: 0 10px; 1235 } 1236 .table-right{ 1237 width: 50%; 1238 } 1239 </style> 1240 <div id="scroll_view" style="display: flex;flex-direction: column;width: 100%;height: 100%;overflow: auto"> 1241 <div style="width: 100%;height: auto;position: relative"> 1242 <div class="table-title"> 1243 <h2 id="leftTitle"></h2> 1244 <div id="rightTitle" > 1245 <h2 id="rightText">Scheduling Latency</h2> 1246 <div class="right"> 1247 <lit-button id="rightButton" height="32px" width="164px" color="black" font_size="14px" border="1px solid black" 1248 >GetWakeupList</lit-button> 1249 <lit-icon id="right-star" class="collect" name="star-fill" size="30"></lit-icon> 1250 </div> 1251 </div> 1252 </div> 1253 </div> 1254 <div class="scroll-area"> 1255 <lit-table id="selectionTbl" class="table-left" no-head hideDownload> 1256 <lit-table-column title="name" data-index="name" key="name" align="flex-start" width="180px"> 1257 <template><div>{{name}}</div></template> 1258 </lit-table-column> 1259 <lit-table-column title="value" data-index="value" key="value" align="flex-start" > 1260 <template><div style="display: flex;">{{value}}</div></template> 1261 </lit-table-column> 1262 </lit-table> 1263 <div class="table-right"> 1264 <canvas id="rightDraw" style="width: 100%;height: 100%;"></canvas> 1265 </div> 1266 </div> 1267 </div> 1268 </div> 1269 `; 1270 } 1271} 1272 1273export class JankTreeNode { 1274 name: string = ''; 1275 pid: number = -1; 1276 frame_type: string = ''; 1277 type: number = 0; 1278 1279 constructor(name: string, pid: number, frame_type: string) { 1280 this.name = name; 1281 this.pid = pid; 1282 this.frame_type = frame_type; 1283 } 1284 1285 children: Array<JankTreeNode> = []; 1286} 1287