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 { SpSystemTrace } from '../SpSystemTrace.js'; 17import { 18 getAsyncEvents, 19 getFunDataByTid, 20 getMaxDepthByTid, 21 queryAllActualData, 22 queryAllExpectedData, 23 queryAllJankProcess, queryAllTaskPoolPid, 24 queryEventCountMap, 25 queryProcess, 26 queryProcessAsyncFunc, 27 queryProcessByTable, 28 queryProcessContentCount, 29 queryProcessData, 30 queryProcessMem, 31 queryProcessMemData, 32 queryProcessSoInitData, 33 queryProcessSoMaxDepth, 34 queryProcessStartup, 35 queryProcessThreads, 36 queryProcessThreadsByTable, 37 queryStartupPidArray, 38 queryThreadData, 39} from '../../database/SqlLite.js'; 40import { Utils } from '../trace/base/Utils.js'; 41import { info } from '../../../log/Log.js'; 42import { TraceRow } from '../trace/base/TraceRow.js'; 43import { renders } from '../../database/ui-worker/ProcedureWorker.js'; 44import { ProcessRender, ProcessStruct } from '../../database/ui-worker/ProcedureWorkerProcess.js'; 45import { ThreadRender, ThreadStruct } from '../../database/ui-worker/ProcedureWorkerThread.js'; 46import { FuncRender, FuncStruct } from '../../database/ui-worker/ProcedureWorkerFunc.js'; 47import { MemRender, ProcessMemStruct } from '../../database/ui-worker/ProcedureWorkerMem.js'; 48import {FolderSupplier, FolderThreadHandler, SpChartManager} from './SpChartManager.js'; 49import { JankRender, JankStruct } from '../../database/ui-worker/ProcedureWorkerJank.js'; 50import { ns2xByTimeShaft } from '../../database/ui-worker/ProcedureWorkerCommon.js'; 51import { AppStartupRender, AppStartupStruct } from '../../database/ui-worker/ProcedureWorkerAppStartup.js'; 52import { SoRender, SoStruct } from '../../database/ui-worker/ProcedureWorkerSoInit.js'; 53import { FlagsConfig } from '../SpFlags.js'; 54 55export class SpProcessChart { 56 private readonly trace: SpSystemTrace; 57 private processAsyncFuncMap: any = {}; 58 private processAsyncFuncArray: any[] = []; 59 private eventCountMap: any; 60 private processThreads: Array<ThreadStruct> = []; 61 private processAsyncEvent: Array<ProcessMemStruct> = []; 62 private processMem: Array<any> = []; 63 private processThreadCountMap: Map<number, number> = new Map(); 64 private processThreadDataCountMap: Map<number, number> = new Map(); 65 private processFuncDataCountMap: Map<number, number> = new Map(); 66 private processMemDataCountMap: Map<number, number> = new Map(); 67 private threadFuncMaxDepthMap: Map<string, number> = new Map(); 68 private startupProcessArr: { pid: number }[] = []; 69 private processSoMaxDepth: { pid: number; maxDepth: number }[] = []; 70 71 constructor(trace: SpSystemTrace) { 72 this.trace = trace; 73 } 74 75 initAsyncFuncData = async (): Promise<void> => { 76 let asyncFuncList: any[] = await queryProcessAsyncFunc(); 77 info('AsyncFuncData Count is: ', asyncFuncList!.length); 78 this.processAsyncFuncArray = asyncFuncList; 79 this.processAsyncFuncMap = Utils.groupBy(asyncFuncList, 'pid'); 80 }; 81 82 initDeliverInputEvent = async (): Promise<void> => { 83 let row = TraceRow.skeleton(); 84 row.setAttribute('disabled-check', ''); 85 row.rowId = 'DeliverInputEvent'; 86 row.index = 0; 87 row.rowType = TraceRow.ROW_TYPE_DELIVER_INPUT_EVENT; 88 row.rowParentId = ''; 89 row.folder = true; 90 row.style.height = '40px'; 91 row.name = 'DeliverInputEvent'; 92 row.supplier = FolderSupplier(); 93 row.onThreadHandler = FolderThreadHandler(row, this.trace); 94 95 let asyncFuncGroup = Utils.groupBy( 96 this.processAsyncFuncArray.filter((it) => it.funName === 'deliverInputEvent'), 97 'tid' 98 ); 99 if (Reflect.ownKeys(asyncFuncGroup).length > 0) { 100 this.trace.rowsEL?.appendChild(row); 101 } 102 Reflect.ownKeys(asyncFuncGroup).map((key: any) => { 103 let asyncFuncGroups: Array<any> = asyncFuncGroup[key]; 104 if (asyncFuncGroups.length > 0) { 105 let isIntersect = (left: any, right: any): boolean => 106 Math.max(left.startTs + left.dur, right.startTs + right.dur) - Math.min(left.startTs, right.startTs) < 107 left.dur + right.dur; 108 let depths: any = []; 109 let createDepth = (currentDepth: number, index: number): void => { 110 if (depths[currentDepth] == undefined || !isIntersect(depths[currentDepth], asyncFuncGroups[index])) { 111 asyncFuncGroups[index].depth = currentDepth; 112 depths[currentDepth] = asyncFuncGroups[index]; 113 } else { 114 createDepth(++currentDepth, index); 115 } 116 }; 117 asyncFuncGroups.forEach((it, i) => { 118 if (it.dur == -1) { 119 it.dur = (TraceRow.range?.endNS || 0) - it.startTs; 120 it.flag = 'Did not end'; 121 } 122 createDepth(0, i); 123 }); 124 let max = Math.max(...asyncFuncGroups.map((it) => it.depth || 0)) + 1; 125 let maxHeight = max * 20; 126 let funcRow = TraceRow.skeleton<FuncStruct>(); 127 funcRow.rowId = `${ asyncFuncGroups[0].funName }-${ key }`; 128 funcRow.asyncFuncName = asyncFuncGroups[0].funName; 129 funcRow.asyncFuncNamePID = key; 130 funcRow.rowType = TraceRow.ROW_TYPE_FUNC; 131 funcRow.rowParentId = `${ row.rowId }`; 132 funcRow.rowHidden = !row.expansion; 133 funcRow.style.width = '100%'; 134 funcRow.style.height = `${ maxHeight }px`; 135 funcRow.setAttribute('height', `${ maxHeight }`); 136 funcRow.name = `${ asyncFuncGroups[0].funName } ${ key }`; 137 funcRow.setAttribute('children', ''); 138 funcRow.supplier = (): Promise<any> => new Promise((resolve) => resolve(asyncFuncGroups)); 139 funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 140 funcRow.selectChangeHandler = this.trace.selectChangeHandler; 141 funcRow.onThreadHandler = (useCache): void => { 142 let context = funcRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 143 funcRow.canvasSave(context); 144 (renders['func'] as FuncRender).renderMainThread( 145 { 146 context: context, 147 useCache: useCache, 148 type: `func-${ asyncFuncGroups[0].funName }-${ key }`, 149 }, 150 funcRow 151 ); 152 funcRow.canvasRestore(context); 153 }; 154 row.addChildTraceRow(funcRow); 155 } 156 }); 157 }; 158 159 async init(): Promise<void> { 160 let threadFuncMaxDepthArray = await getMaxDepthByTid(); 161 info('Gets the maximum tier per thread , tid and maxDepth'); 162 threadFuncMaxDepthArray.forEach((it) => { 163 this.threadFuncMaxDepthMap.set(`${ it.ipid }-${ it.tid }`, it.maxDepth); 164 }); 165 info('convert tid and maxDepth array to map'); 166 let pidCountArray = await queryProcessContentCount(); 167 info('fetch per process pid,switch_count,thread_count,slice_count,mem_count'); 168 pidCountArray.forEach((it) => { 169 this.processThreadDataCountMap.set(it.pid, it.switch_count); 170 this.processThreadCountMap.set(it.pid, it.thread_count); 171 this.processFuncDataCountMap.set(it.pid, it.slice_count); 172 this.processMemDataCountMap.set(it.pid, it.mem_count); 173 }); 174 let queryProcessThreadResult = await queryProcessThreads(); 175 let queryProcessThreadsByTableResult = await queryProcessThreadsByTable(); 176 this.processAsyncEvent = await getAsyncEvents(); 177 info('The amount of initialized process Event data is : ', this.processAsyncEvent!.length); 178 this.processMem = await queryProcessMem(); 179 let loadAppStartup: boolean = FlagsConfig.getFlagsConfigEnableStatus('AppStartup'); 180 if (loadAppStartup) { 181 this.startupProcessArr = await queryStartupPidArray(); 182 this.processSoMaxDepth = await queryProcessSoMaxDepth(); 183 } 184 info('The amount of initialized process memory data is : ', this.processMem!.length); 185 let eventCountList: Array<any> = await queryEventCountMap(); 186 this.eventCountMap = eventCountList.reduce((pre, current) => { 187 pre[`${ current.eventName }`] = current.count; 188 return pre; 189 }, {}); 190 this.processThreads = Utils.removeDuplicates(queryProcessThreadResult, queryProcessThreadsByTableResult, 'tid'); 191 info('The amount of initialized process threads data is : ', this.processThreads!.length); 192 if ( 193 this.eventCountMap['print'] == 0 && 194 this.eventCountMap['tracing_mark_write'] == 0 && 195 this.eventCountMap['sched_switch'] == 0 196 ) { 197 return; 198 } 199 let time = new Date().getTime(); 200 let processes = await queryProcess(); 201 let processFromTable = await queryProcessByTable(); 202 let processList = Utils.removeDuplicates(processes, processFromTable, 'pid'); 203 let allJankProcessData = await queryAllJankProcess(); 204 let allJankProcess: Array<number> = []; 205 let allExpectedProcess: Array<any> = []; 206 let allActualProcess: Array<any> = []; 207 if (allJankProcessData.length > 0) { 208 allJankProcessData.forEach((name, index) => { 209 allJankProcess.push(name.pid!); 210 }); 211 allExpectedProcess = await queryAllExpectedData(); 212 allActualProcess = await queryAllActualData(); 213 } 214 let allTaskPoolPid = await queryAllTaskPoolPid(); 215 let allTaskPoolProcess: Array<number> = []; 216 allTaskPoolPid.forEach(value => { 217 allTaskPoolProcess.push(value.pid); 218 }); 219 info('ProcessList Data size is: ', processList!.length); 220 for (let i = 0 ; i < processList.length ; i++) { 221 const it = processList[i]; 222 if ( 223 (this.processThreadDataCountMap.get(it.pid) || 0) == 0 && 224 (this.processThreadCountMap.get(it.pid) || 0) == 0 && 225 (this.processFuncDataCountMap.get(it.pid) || 0) == 0 && 226 (this.processMemDataCountMap.get(it.pid) || 0) == 0 227 ) { 228 continue; 229 } 230 let processRow = TraceRow.skeleton<ProcessStruct>(); 231 processRow.rowId = `${ it.pid }`; 232 processRow.index = i; 233 processRow.rowType = TraceRow.ROW_TYPE_PROCESS; 234 processRow.rowParentId = ''; 235 processRow.style.height = '40px'; 236 processRow.folder = true; 237 if (SpChartManager.APP_STARTUP_PID_ARR.find(pid => pid === it.pid) !== undefined || it.processName === 'render_service') { 238 processRow.addTemplateTypes('AppStartup'); 239 } 240 processRow.name = `${ it.processName || 'Process' } ${ it.pid }`; 241 processRow.supplier = (): Promise<Array<any>> => queryProcessData(it.pid || -1, 0, TraceRow.range?.totalNS || 0); 242 processRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 243 processRow.selectChangeHandler = this.trace.selectChangeHandler; 244 processRow.onThreadHandler = (useCache): void => { 245 processRow.canvasSave(this.trace.canvasPanelCtx!); 246 if (processRow.expansion) { 247 this.trace.canvasPanelCtx?.clearRect(0, 0, processRow.frame.width, processRow.frame.height); 248 } else { 249 (renders['process'] as ProcessRender).renderMainThread( 250 { 251 context: this.trace.canvasPanelCtx, 252 pid: it.pid, 253 useCache: useCache, 254 type: `process ${ processRow.index } ${ it.processName }`, 255 }, 256 processRow 257 ); 258 } 259 processRow.canvasRestore(this.trace.canvasPanelCtx!); 260 }; 261 this.trace.rowsEL?.appendChild(processRow); 262 263 /** 264 * App Startup row 265 */ 266 let startupRow: TraceRow<AppStartupStruct> | undefined = undefined; 267 let soRow: TraceRow<SoStruct> | undefined = undefined; 268 if (loadAppStartup) { 269 if (this.startupProcessArr.find((sp) => sp.pid === it.pid)) { 270 startupRow = this.addStartUpRow(processRow); 271 } 272 let maxSoDepth = this.processSoMaxDepth.find((md) => md.pid === it.pid); 273 if (maxSoDepth) { 274 soRow = this.addSoInitRow(processRow, maxSoDepth.maxDepth); 275 } 276 } 277 278 /** 279 * Janks Frames 280 */ 281 let actualRow: TraceRow<JankStruct> | null = null; 282 let expectedRow: TraceRow<JankStruct> | null = null; 283 if (allJankProcess.indexOf(it.pid) > -1 && allExpectedProcess.length > 0) { 284 let expectedData = allExpectedProcess.filter((ite) => ite.pid == it.pid); 285 if (expectedData.length > 0) { 286 // @ts-ignore 287 let isIntersect = (a: JanksStruct, b: JanksStruct): boolean => 288 Math.max(a.ts + a.dur, b.ts + b.dur) - Math.min(a.ts, b.ts) < a.dur + b.dur; 289 let depthArray: any = []; 290 for (let j = 0 ; j < expectedData.length ; j++) { 291 let expectedItem = expectedData[j]; 292 if (expectedItem.cmdline != 'render_service') { 293 expectedItem.frame_type = 'app'; 294 } else { 295 expectedItem.frame_type = expectedItem.cmdline; 296 } 297 if (!expectedItem.dur || expectedItem.dur < 0) { 298 continue; 299 } 300 if (depthArray.length === 0) { 301 expectedItem.depth = 0; 302 depthArray.push(expectedItem); 303 } else { 304 if (isIntersect(depthArray[0], expectedItem)) { 305 if (isIntersect(depthArray[depthArray.length - 1], expectedItem)) { 306 expectedItem.depth = depthArray.length; 307 depthArray.push(expectedItem); 308 } 309 } else { 310 expectedItem.depth = 0; 311 depthArray = [expectedItem]; 312 } 313 } 314 } 315 let max = Math.max(...expectedData.map((it) => it.depth || 0)) + 1; 316 let maxHeight = max * 20; 317 expectedRow = TraceRow.skeleton<JankStruct>(); 318 let timeLineType = expectedData[0].type; 319 expectedRow.rowId = `${ timeLineType }-${ it.pid }`; 320 expectedRow.asyncFuncName = it.processName; 321 expectedRow.asyncFuncNamePID = it.pid; 322 expectedRow.rowType = TraceRow.ROW_TYPE_JANK; 323 expectedRow.rowParentId = `${ it.pid }`; 324 expectedRow.rowHidden = !processRow.expansion; 325 expectedRow.style.width = '100%'; 326 expectedRow.style.height = `${ maxHeight }px`; 327 expectedRow.setAttribute('height', `${ maxHeight }`); 328 expectedRow.setAttribute('frame_type', expectedData[0].frame_type); 329 expectedRow.name = 'Expected Timeline'; 330 expectedRow.addTemplateTypes('FrameTimeline'); 331 expectedRow.setAttribute('children', ''); 332 expectedRow.supplier = (): Promise<any> => 333 new Promise((resolve) => { 334 resolve(expectedData); 335 }); 336 expectedRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 337 expectedRow.selectChangeHandler = this.trace.selectChangeHandler; 338 expectedRow.onThreadHandler = (useCache): void => { 339 let context = expectedRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 340 expectedRow!.canvasSave(context); 341 (renders['jank'] as JankRender).renderMainThread( 342 { 343 context: context, 344 useCache: useCache, 345 type: 'expected_frame_timeline_slice', 346 }, 347 expectedRow! 348 ); 349 expectedRow!.canvasRestore(context); 350 }; 351 processRow.addChildTraceRow(expectedRow); 352 let actualData = allActualProcess.filter((ite) => ite.pid == it.pid); 353 if (actualData.length > 0) { 354 let isIntersect = (a: any, b: any): boolean => 355 Math.max(a.ts + a.dur, b.ts + b.dur) - Math.min(a.ts, b.ts) < a.dur + b.dur; 356 let depthArray: any = []; 357 for (let j = 0 ; j < actualData.length ; j++) { 358 let actualItem = actualData[j]; 359 if (actualItem.cmdline != 'render_service') { 360 actualItem.frame_type = 'app'; 361 } else { 362 actualItem.frame_type = actualItem.cmdline; 363 } 364 if (!actualItem.dur || actualItem.dur < 0) { 365 continue; 366 } 367 if (depthArray.length === 0) { 368 actualItem.depth = 0; 369 depthArray.push(actualItem); 370 } else { 371 if (isIntersect(depthArray[0], actualItem)) { 372 if (isIntersect(depthArray[depthArray.length - 1], actualItem)) { 373 actualItem.depth = depthArray.length; 374 depthArray.push(actualItem); 375 } else { 376 actualItem.depth = depthArray.length - 1; 377 depthArray[length - 1] = actualItem; 378 } 379 } else { 380 actualItem.depth = 0; 381 depthArray = [actualItem]; 382 } 383 } 384 } 385 let max = Math.max(...actualData.map((it) => it.depth || 0)) + 1; 386 let maxHeight = max * 20; 387 actualRow = TraceRow.skeleton<JankStruct>(); 388 let timeLineType = actualData[0].type; 389 actualRow.rowId = `${ timeLineType }-${ it.pid }`; 390 actualRow.rowType = TraceRow.ROW_TYPE_JANK; 391 actualRow.rowParentId = `${ it.pid }`; 392 actualRow.rowHidden = !processRow.expansion; 393 actualRow.style.width = '100%'; 394 actualRow.style.height = `${ maxHeight }px`; 395 actualRow.setAttribute('height', `${ maxHeight }`); 396 actualRow.name = 'Actual Timeline'; 397 actualRow.addTemplateTypes('FrameTimeline'); 398 actualRow.setAttribute('frame_type', actualData[0].frame_type); 399 actualRow.setAttribute('children', ''); 400 actualRow.dataList = actualData; 401 actualRow.supplier = (): Promise<any> => new Promise((resolve) => resolve(actualData)); 402 actualRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 403 actualRow.selectChangeHandler = this.trace.selectChangeHandler; 404 actualRow.onThreadHandler = (useCache): void => { 405 let context = actualRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 406 actualRow!.canvasSave(context); 407 (renders['jank'] as JankRender).renderMainThread( 408 { 409 context: context, 410 useCache: useCache, 411 type: 'actual_frame_timeline_slice', 412 }, 413 actualRow! 414 ); 415 actualRow!.canvasRestore(context); 416 }; 417 processRow.addChildTraceRow(actualRow); 418 } 419 } 420 } 421 let offsetYTimeOut: any = undefined; 422 processRow.addEventListener('expansion-change', (e: any) => { 423 JankStruct.delJankLineFlag = false; 424 if (offsetYTimeOut) { 425 clearTimeout(offsetYTimeOut); 426 } 427 if (e.detail.expansion) { 428 offsetYTimeOut = setTimeout(() => { 429 this.trace.linkNodes.forEach((linkNodeItem) => { 430 JankStruct.selectJankStructList?.forEach((selectProcessStruct: any) => { 431 if (e.detail.rowId == selectProcessStruct.pid) { 432 JankStruct.selectJankStruct = selectProcessStruct; 433 JankStruct.hoverJankStruct = selectProcessStruct; 434 } 435 }); 436 if (linkNodeItem[0].rowEL.collect) { 437 linkNodeItem[0].rowEL.translateY = linkNodeItem[0].rowEL.getBoundingClientRect().top - 195; 438 } else { 439 linkNodeItem[0].rowEL.translateY = linkNodeItem[0].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; 440 } 441 linkNodeItem[0].y = linkNodeItem[0].rowEL!.translateY! + linkNodeItem[0].offsetY; 442 if (linkNodeItem[1].rowEL.collect) { 443 linkNodeItem[1].rowEL.translateY = linkNodeItem[1].rowEL.getBoundingClientRect().top - 195; 444 } else { 445 linkNodeItem[1].rowEL.translateY = linkNodeItem[1].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; 446 } 447 linkNodeItem[1].y = linkNodeItem[1].rowEL!.translateY! + linkNodeItem[1].offsetY; 448 if (linkNodeItem[0].rowEL.rowId == e.detail.rowId) { 449 linkNodeItem[0].x = ns2xByTimeShaft(linkNodeItem[0].ns, this.trace.timerShaftEL!); 450 linkNodeItem[0].y = actualRow!.translateY! + linkNodeItem[0].offsetY * 2; 451 linkNodeItem[0].offsetY = linkNodeItem[0].offsetY * 2; 452 linkNodeItem[0].rowEL = actualRow!; 453 } else if (linkNodeItem[1].rowEL.rowId == e.detail.rowId) { 454 linkNodeItem[1].x = ns2xByTimeShaft(linkNodeItem[1].ns, this.trace.timerShaftEL!); 455 linkNodeItem[1].y = actualRow!.translateY! + linkNodeItem[1].offsetY * 2; 456 linkNodeItem[1].offsetY = linkNodeItem[1].offsetY * 2; 457 linkNodeItem[1].rowEL = actualRow!; 458 } 459 }); 460 }, 300); 461 } else { 462 if (JankStruct!.selectJankStruct) { 463 JankStruct.selectJankStructList?.push(<JankStruct> JankStruct!.selectJankStruct); 464 } 465 offsetYTimeOut = setTimeout(() => { 466 this.trace.linkNodes?.forEach((linkProcessItem) => { 467 if (linkProcessItem[0].rowEL.collect) { 468 linkProcessItem[0].rowEL.translateY = linkProcessItem[0].rowEL.getBoundingClientRect().top - 195; 469 } else { 470 linkProcessItem[0].rowEL.translateY = 471 linkProcessItem[0].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; 472 } 473 linkProcessItem[0].y = linkProcessItem[0].rowEL!.translateY! + linkProcessItem[0].offsetY; 474 if (linkProcessItem[1].rowEL.collect) { 475 linkProcessItem[1].rowEL.translateY = linkProcessItem[1].rowEL.getBoundingClientRect().top - 195; 476 } else { 477 linkProcessItem[1].rowEL.translateY = 478 linkProcessItem[1].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; 479 } 480 linkProcessItem[1].y = linkProcessItem[1].rowEL!.translateY! + linkProcessItem[1].offsetY; 481 if (linkProcessItem[0].rowEL.rowParentId == e.detail.rowId) { 482 linkProcessItem[0].x = ns2xByTimeShaft(linkProcessItem[0].ns, this.trace.timerShaftEL!); 483 linkProcessItem[0].y = processRow!.translateY! + linkProcessItem[0].offsetY / 2; 484 linkProcessItem[0].offsetY = linkProcessItem[0].offsetY / 2; 485 linkProcessItem[0].rowEL = processRow!; 486 } else if (linkProcessItem[1].rowEL.rowParentId == e.detail.rowId) { 487 linkProcessItem[1].x = ns2xByTimeShaft(linkProcessItem[1].ns, this.trace.timerShaftEL!); 488 linkProcessItem[1].y = processRow!.translateY! + linkProcessItem[1].offsetY / 2; 489 linkProcessItem[1].offsetY = linkProcessItem[1].offsetY / 2; 490 linkProcessItem[1].rowEL = processRow!; 491 } 492 }); 493 }, 300); 494 } 495 let refreshTimeOut = setTimeout(() => { 496 this.trace.refreshCanvas(true); 497 clearTimeout(refreshTimeOut); 498 }, 360); 499 }); 500 /** 501 * Async Function 502 */ 503 let asyncFuncList = this.processAsyncFuncMap[it.pid] || []; 504 let asyncFuncGroup = Utils.groupBy(asyncFuncList, 'funName'); 505 Reflect.ownKeys(asyncFuncGroup).map((key: any) => { 506 let asyncFunctions: Array<any> = asyncFuncGroup[key]; 507 if (asyncFunctions.length > 0) { 508 let isIntersect = (a: any, b: any): boolean => 509 Math.max(a.startTs + a.dur, b.startTs + b.dur) - Math.min(a.startTs, b.startTs) < a.dur + b.dur; 510 let depthArray: any = []; 511 asyncFunctions.forEach((it, i) => { 512 if (it.dur == -1) { 513 it.dur = (TraceRow.range?.endNS || 0) - it.startTs; 514 it.flag = 'Did not end'; 515 } 516 let currentDepth = 0; 517 let index = i; 518 while ( 519 depthArray[currentDepth] !== undefined && 520 isIntersect(depthArray[currentDepth], asyncFunctions[index]) 521 ) { 522 currentDepth++; 523 } 524 asyncFunctions[index].depth = currentDepth; 525 depthArray[currentDepth] = asyncFunctions[index]; 526 }); 527 let max = Math.max(...asyncFunctions.map((it) => it.depth || 0)) + 1; 528 let maxHeight = max * 20; 529 let funcRow = TraceRow.skeleton<FuncStruct>(); 530 funcRow.rowId = `${ asyncFunctions[0].funName }-${ it.pid }`; 531 funcRow.asyncFuncName = asyncFunctions[0].funName; 532 funcRow.asyncFuncNamePID = it.pid; 533 funcRow.rowType = TraceRow.ROW_TYPE_FUNC; 534 funcRow.rowParentId = `${ it.pid }`; 535 funcRow.rowHidden = !processRow.expansion; 536 funcRow.style.width = '100%'; 537 funcRow.style.height = `${ maxHeight }px`; 538 funcRow.setAttribute('height', `${ maxHeight }`); 539 funcRow.name = `${ asyncFunctions[0].funName }`; 540 funcRow.setAttribute('children', ''); 541 funcRow.supplier = (): Promise<any> => new Promise((resolve) => resolve(asyncFunctions)); 542 funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 543 funcRow.selectChangeHandler = this.trace.selectChangeHandler; 544 funcRow.onThreadHandler = (cacheFlag): void => { 545 let context = funcRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 546 funcRow.canvasSave(context); 547 (renders['func'] as FuncRender).renderMainThread( 548 { 549 context: context, 550 useCache: cacheFlag, 551 type: `func-${ asyncFunctions[0].funName }-${ it.pid }`, 552 }, 553 funcRow 554 ); 555 funcRow.canvasRestore(context); 556 }; 557 processRow.addChildTraceRow(funcRow); 558 } 559 }); 560 561 /** 562 * 添加进程内存信息 563 */ 564 let processMem = this.processMem.filter((mem) => mem.pid === it.pid); 565 processMem.forEach((mem) => { 566 let row = TraceRow.skeleton<ProcessMemStruct>(); 567 row.rowId = `${ mem.trackId }`; 568 row.rowType = TraceRow.ROW_TYPE_MEM; 569 row.rowParentId = `${ it.pid }`; 570 row.rowHidden = !processRow.expansion; 571 row.style.height = '40px'; 572 row.style.width = '100%'; 573 row.name = `${ mem.trackName }`; 574 row.setAttribute('children', ''); 575 row.favoriteChangeHandler = this.trace.favoriteChangeHandler; 576 row.selectChangeHandler = this.trace.selectChangeHandler; 577 row.focusHandler = (): void => { 578 this.trace.displayTip( 579 row, 580 ProcessMemStruct.hoverProcessMemStruct, 581 `<span>${ ProcessMemStruct.hoverProcessMemStruct?.value || '0' }</span>` 582 ); 583 }; 584 row.supplier = (): Promise<Array<ProcessMemStruct>> => 585 queryProcessMemData(mem.trackId).then((resultProcess) => { 586 let maxValue = Math.max(...resultProcess.map((it) => it.value || 0)); 587 for (let j = 0 ; j < resultProcess.length ; j++) { 588 resultProcess[j].maxValue = maxValue; 589 if (j == resultProcess.length - 1) { 590 resultProcess[j].duration = (TraceRow.range?.totalNS || 0) - (resultProcess[j].startTime || 0); 591 } else { 592 resultProcess[j].duration = (resultProcess[j + 1].startTime || 0) - (resultProcess[j].startTime || 0); 593 } 594 if (j > 0) { 595 resultProcess[j].delta = (resultProcess[j].value || 0) - (resultProcess[j - 1].value || 0); 596 } else { 597 resultProcess[j].delta = 0; 598 } 599 } 600 return resultProcess; 601 }); 602 row.onThreadHandler = (useCache): void => { 603 let context = row.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 604 row.canvasSave(context); 605 (renders['mem'] as MemRender).renderMainThread( 606 { 607 context: context, 608 useCache: useCache, 609 type: `mem ${ mem.trackId } ${ mem.trackName }`, 610 }, 611 row 612 ); 613 row.canvasRestore(context); 614 }; 615 processRow.addChildTraceRow(row); 616 }); 617 /** 618 * add thread list 619 */ 620 let threads = this.processThreads.filter((thread) => thread.pid === it.pid && thread.tid != 0); 621 for (let j = 0 ; j < threads.length ; j++) { 622 let thread = threads[j]; 623 let threadRow = TraceRow.skeleton<ThreadStruct>(); 624 threadRow.rowId = `${ thread.tid }`; 625 threadRow.rowType = TraceRow.ROW_TYPE_THREAD; 626 threadRow.rowParentId = `${ it.pid }`; 627 threadRow.rowHidden = !processRow.expansion; 628 threadRow.index = j; 629 threadRow.style.height = '30px'; 630 threadRow.style.width = '100%'; 631 threadRow.name = `${ thread.threadName || 'Thread' } ${ thread.tid }`; 632 threadRow.setAttribute('children', ''); 633 threadRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 634 threadRow.selectChangeHandler = this.trace.selectChangeHandler; 635 threadRow.supplier = (): Promise<Array<ThreadStruct>> => 636 queryThreadData(thread.tid || 0, it.pid || 0).then((res) => { 637 if (res.length <= 0) { 638 threadRow.rowDiscard = true; 639 this.trace.refreshCanvas(true); 640 } 641 return res; 642 }); 643 threadRow.focusHandler = (ev): void => {}; 644 threadRow.onThreadHandler = (useCache): void => { 645 let context = threadRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 646 threadRow.canvasSave(context); 647 (renders['thread'] as ThreadRender).renderMainThread( 648 { 649 context: context, 650 useCache: useCache, 651 type: `thread ${ thread.tid } ${ thread.threadName }`, 652 translateY: threadRow.translateY, 653 }, 654 threadRow 655 ); 656 threadRow.canvasRestore(context); 657 }; 658 if (threadRow.rowId === threadRow.rowParentId) { 659 if (actualRow !== null) { 660 processRow.addChildTraceRowAfter(threadRow, actualRow); 661 } else if (expectedRow !== null) { 662 processRow.addChildTraceRowAfter(threadRow, expectedRow); 663 } else if (soRow) { 664 processRow.addChildTraceRowAfter(threadRow, soRow); 665 } else if (startupRow) { 666 processRow.addChildTraceRowAfter(threadRow, startupRow); 667 } else { 668 processRow.addChildTraceRowSpecifyLocation(threadRow, 0); 669 } 670 } else { 671 processRow.addChildTraceRow(threadRow); 672 } 673 if (this.threadFuncMaxDepthMap.get(`${ thread.upid }-${ thread.tid }`) != undefined) { 674 let max = this.threadFuncMaxDepthMap.get(`${ thread.upid }-${ thread.tid }`) || 1; 675 let maxHeight = max * 20; 676 let funcRow = TraceRow.skeleton<FuncStruct>(); 677 funcRow.rowId = `${ thread.tid }`; 678 funcRow.rowType = TraceRow.ROW_TYPE_FUNC; 679 funcRow.rowParentId = `${ it.pid }`; 680 funcRow.rowHidden = !processRow.expansion; 681 funcRow.checkType = threadRow.checkType; 682 funcRow.style.width = '100%'; 683 funcRow.style.height = `${ maxHeight }px`; 684 funcRow.name = `${ thread.threadName || 'Thread' } ${ thread.tid }`; 685 funcRow.setAttribute('children', ''); 686 if (allTaskPoolProcess.indexOf(it.pid) !== -1) { 687 if (funcRow.name.startsWith('TaskWorkThread') || thread.is_main_thread === 1) { 688 funcRow.addTemplateTypes('Task Pool'); 689 } 690 } 691 funcRow.supplier = (): Promise<Array<FuncStruct>> => 692 getFunDataByTid(thread.tid || 0, thread.upid || 0).then((funs: Array<FuncStruct>) => { 693 if (funs.length > 0) { 694 let isBinder = (data: FuncStruct): boolean => { 695 return ( 696 data.funName != null && 697 (data.funName.toLowerCase().startsWith('binder transaction async') || //binder transaction 698 data.funName.toLowerCase().startsWith('binder async') || 699 data.funName.toLowerCase().startsWith('binder reply')) 700 ); 701 }; 702 funs.forEach((fun) => { 703 if (isBinder(fun)) { 704 } else { 705 if (fun.dur == -1) { 706 fun.dur = (TraceRow.range?.totalNS || 0) - (fun.startTs || 0); 707 fun.flag = 'Did not end'; 708 } 709 } 710 }); 711 } else { 712 funcRow.rowDiscard = true; 713 this.trace.refreshCanvas(true); 714 } 715 return funs; 716 }); 717 funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 718 funcRow.selectChangeHandler = this.trace.selectChangeHandler; 719 funcRow.onThreadHandler = (useCache): void => { 720 let context = funcRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 721 funcRow.canvasSave(context); 722 (renders['func'] as FuncRender).renderMainThread( 723 { 724 context: context, 725 useCache: useCache, 726 type: `func${ thread.tid }${ thread.threadName }`, 727 }, 728 funcRow 729 ); 730 funcRow.canvasRestore(context); 731 }; 732 processRow.addChildTraceRowAfter(funcRow, threadRow); 733 } 734 } 735 await this.trace.chartManager?.frameTimeChart.initAnimatedScenesChart(processRow, it, expectedRow!); 736 } 737 let durTime = new Date().getTime() - time; 738 info('The time to load the Process data is: ', durTime); 739 } 740 741 addStartUpRow(processRow: TraceRow<ProcessStruct>): TraceRow<AppStartupStruct> { 742 processRow.setAttribute('hasStartup', 'true'); 743 let startupRow: TraceRow<AppStartupStruct> = TraceRow.skeleton<AppStartupStruct>(); 744 startupRow.rowId = `app-start-${ processRow.rowId }`; 745 startupRow.rowType = TraceRow.ROW_TYPE_APP_STARTUP; 746 startupRow.rowParentId = `${ processRow.rowId }`; 747 startupRow.rowHidden = !processRow.expansion; 748 startupRow.index = 0; 749 startupRow.style.height = '30px'; 750 startupRow.style.width = `100%`; 751 startupRow.name = `App Startups`; 752 startupRow.setAttribute('children', ''); 753 startupRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 754 startupRow.selectChangeHandler = this.trace.selectChangeHandler; 755 startupRow.supplier = (): Promise<Array<AppStartupStruct>> => 756 queryProcessStartup(parseInt(processRow.rowId!)).then((res) => { 757 if (res.length <= 0) { 758 startupRow.rowDiscard = true; 759 this.trace.refreshCanvas(true); 760 } 761 for (let i = 0 ; i < res.length ; i++) { 762 if (res[i].startName! < 4 && i < res.length - 1) { 763 res[i].endItid = res[i + 1].itid; 764 } 765 } 766 return res; 767 }); 768 startupRow.focusHandler = (ev): void => {}; 769 startupRow.onThreadHandler = (useCache): void => { 770 let context = startupRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 771 startupRow.canvasSave(context); 772 (renders['app-start-up'] as AppStartupRender).renderMainThread( 773 { 774 appStartupContext: context, 775 useCache: useCache, 776 type: `app-startup ${ processRow.rowId }`, 777 }, 778 startupRow 779 ); 780 startupRow.canvasRestore(context); 781 }; 782 processRow.addChildTraceRow(startupRow); 783 return startupRow; 784 } 785 786 addSoInitRow(processRow: TraceRow<ProcessStruct>, maxDepth: number): TraceRow<SoStruct> { 787 processRow.setAttribute('hasStaticInit', 'true'); 788 let maxHeight = (maxDepth + 1) * 20; 789 let soRow: TraceRow<SoStruct> = TraceRow.skeleton<SoStruct>(); 790 soRow.rowId = `app-start-${ processRow.rowId }`; 791 soRow.rowType = TraceRow.ROW_TYPE_STATIC_INIT; 792 soRow.rowParentId = `${ processRow.rowId }`; 793 soRow.rowHidden = !processRow.expansion; 794 soRow.index = 0; 795 soRow.style.height = `${ maxHeight }px`; 796 soRow.style.width = `100%`; 797 soRow.name = `Static Initialization`; 798 soRow.setAttribute('children', ''); 799 soRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 800 soRow.selectChangeHandler = this.trace.selectChangeHandler; 801 soRow.supplier = (): Promise<Array<SoStruct>> => 802 queryProcessSoInitData(parseInt(processRow.rowId!)).then((res) => { 803 if (res.length <= 0) { 804 soRow.rowDiscard = true; 805 this.trace.refreshCanvas(true); 806 } 807 res.forEach(so => { 808 if (so.soName) { 809 so.soName = so.soName.replace('dlopen: ', ''); 810 } 811 }); 812 return res; 813 }); 814 soRow.focusHandler = (ev): void => {}; 815 soRow.onThreadHandler = (useCache): void => { 816 let context = soRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 817 soRow.canvasSave(context); 818 (renders['app-so-init'] as SoRender).renderMainThread( 819 { 820 context: context, 821 useCache: useCache, 822 type: `static-init ${ processRow.rowId }`, 823 }, 824 soRow 825 ); 826 soRow.canvasRestore(context); 827 }; 828 processRow.addChildTraceRow(soRow); 829 return soRow; 830 } 831 832 insertAfter(newEl: HTMLElement, targetEl: HTMLElement): void { 833 let parentEl = targetEl.parentNode; 834 if (parentEl!.lastChild == targetEl) { 835 parentEl!.appendChild(newEl); 836 } else { 837 parentEl!.insertBefore(newEl, targetEl.nextSibling); 838 } 839 } 840} 841