* Copyright (C) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SpSystemTrace } from '../SpSystemTrace';
import { Utils } from '../trace/base/Utils';
import { info } from '../../../log/Log';
import { TraceRow } from '../trace/base/TraceRow';
import { ProcessRender, ProcessStruct } from '../../database/ui-worker/ProcedureWorkerProcess';
import { ThreadRender, ThreadStruct } from '../../database/ui-worker/ProcedureWorkerThread';
import { FuncRender, FuncStruct } from '../../database/ui-worker/ProcedureWorkerFunc';
import { MemRender, ProcessMemStruct } from '../../database/ui-worker/ProcedureWorkerMem';
import { folderSupplier, folderThreadHandler, getRowContext, rowThreadHandler, SpChartManager } from './SpChartManager';
import { JankRender, JankStruct } from '../../database/ui-worker/ProcedureWorkerJank';
import { isFrameContainPoint, ns2xByTimeShaft, PairPoint } from '../../database/ui-worker/ProcedureWorkerCommon';
import { AppStartupRender, AppStartupStruct } from '../../database/ui-worker/ProcedureWorkerAppStartup';
import { SoRender, SoStruct } from '../../database/ui-worker/ProcedureWorkerSoInit';
import { FlagsConfig } from '../SpFlags';
import { processDataSender } from '../../database/data-trafic/process/ProcessDataSender';
import { threadDataSender } from '../../database/data-trafic/process/ThreadDataSender';
import { threadSysCallDataSender } from '../../database/data-trafic/process/ThreadSysCallDataSender';
import { funcDataSender } from '../../database/data-trafic/process/FuncDataSender';
import { processMemDataSender } from '../../database/data-trafic/process/ProcessMemDataSender';
import { processStartupDataSender } from '../../database/data-trafic/process/ProcessStartupDataSender';
import { processSoInitDataSender } from '../../database/data-trafic/process/ProcessSoInitDataSender';
import { processExpectedDataSender } from '../../database/data-trafic/process/ProcessExpectedDataSender';
import { processActualDataSender } from '../../database/data-trafic/process/ProcessActualDataSender';
import { processDeliverInputEventDataSender } from '../../database/data-trafic/process/ProcessDeliverInputEventDataSender';
import { processTouchEventDispatchDataSender } from '../../database/data-trafic/process/ProcessTouchEventDispatchDataSender';
import { getMaxDepthByTid, queryProcessAsyncFunc, queryProcessAsyncFuncCat } from '../../database/sql/Func.sql';
import { queryMemFilterIdMaxValue, queryMemFilterIdMinValue } from '../../database/sql/Memory.sql';
import { queryAllSoInitNames, queryAllSrcSlices, queryEventCountMap, queryCallstackDetail } from '../../database/sql/SqlLite.sql';
import {
queryProcessByTable,
queryProcessContentCount,
querySysCallThreadIds,
queryProcessMem,
queryProcessSoMaxDepth,
queryProcessThreadsByTable,
queryStartupPidArray,
queryRsProcess,
queryTaskPoolProcessIds,
queryDistributedRelationData,
} from '../../database/sql/ProcessThread.sql';
import { queryAllJankProcess } from '../../database/sql/Janks.sql';
import { BaseStruct } from '../../bean/BaseStruct';
import { HangStruct } from '../../database/ui-worker/ProcedureWorkerHang';
import { hangDataSender } from '../../database/data-trafic/HangDataSender';
import { SpHangChart } from './SpHangChart';
import { queryHangData } from '../../database/sql/Hang.sql';
import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU';
import { renders } from '../../database/ui-worker/ProcedureWorker';
import { ThreadSysCallStruct } from '../../database/ui-worker/ProcedureWorkerThreadSysCall';
import { IOStruct, IORender } from '../../database/ui-worker/ProcedureWorkerIO';
import { ColorUtils } from '../trace/base/ColorUtils';
import { ioSender } from '../../database/data-trafic/IOSender';
import { procedurePool } from '../../database/Procedure';
import { queryAllIOProcess } from '../../database/sql/IO.sql';
const IO_LANE_HEIGHT = '48px';
const FOLD_HEIGHT = 24;
export class SpProcessChart {
private readonly trace: SpSystemTrace;
private processAsyncFuncMap: unknown = {};
private processAsyncFuncArray: unknown[] = [];
private processAsyncFuncCatMap: unknown = {};
private eventCountMap: unknown;
private processThreads: Array<ThreadStruct> = [];
private processMem: Array<unknown> = [];
private processThreadCountMap: Map<number, number> = new Map();
private processThreadDataCountMap: Map<number, number> = new Map();
private processFuncDataCountMap: Map<number, number> = new Map();
private processMemDataCountMap: Map<number, number> = new Map();
private threadFuncMaxDepthMap: Map<string, number> = new Map();
private startupProcessArr: { pid: number }[] = [];
private processSoMaxDepth: { pid: number; maxDepth: number }[] = [];
private filterIdMaxValue: Map<number, number> = new Map();
private filterIdMinValue: Map<number, number> = new Map();
private soInitNameMap: Map<number, string> = new Map();
private processSrcSliceMap: Map<number, string> = new Map();
private distributedDataMap: Map<
string,
{
chainId: string;
spanId: string;
parentSpanId: string;
chainFlag: string;
traceId: string
}
> = new Map();
private renderRow: TraceRow<BaseStruct> | null = null;
private loadAppStartup: boolean = false;
private isDistributed: boolean = false;
private traceId?: string | undefined;
private parentRow: TraceRow<BaseStruct> | undefined;
static asyncFuncCache: unknown[] = [];
private callStackDetail: Map<number, FuncStruct> = new Map();
static threadStateList: Map<string, unknown> = new Map();
static processRowSortMap: Map<string, unknown> = new Map();
private sameThreadFolder!: TraceRow<ProcessStruct>;
private hangProcessSet: Set<number> = new Set();
constructor(trace: SpSystemTrace) {
this.trace = trace;
}
clearCache(): void {
this.processAsyncFuncArray = [];
this.processAsyncFuncMap = {};
this.processAsyncFuncCatMap = {};
this.eventCountMap = {};
this.processThreads = [];
this.processMem = [];
this.processThreadCountMap.clear();
this.processThreadDataCountMap.clear();
this.processFuncDataCountMap.clear();
this.processMemDataCountMap.clear();
this.threadFuncMaxDepthMap.clear();
this.startupProcessArr = [];
this.processSoMaxDepth = [];
this.filterIdMaxValue.clear();
this.filterIdMinValue.clear();
this.soInitNameMap.clear();
this.processSrcSliceMap.clear();
this.distributedDataMap.clear();
this.renderRow = null;
SpProcessChart.asyncFuncCache = [];
this.callStackDetail.clear();
if (this.parentRow) {
this.parentRow.clearMemory();
this.parentRow = undefined;
}
}
initAsyncFuncData = async (traceRange: { startTs: number; endTs: number }, traceId?: string): Promise<void> => {
let asyncFuncList: unknown[] = await queryProcessAsyncFunc(traceRange, traceId);
for (const func of asyncFuncList) {
func.funName = this.traceId ? Utils.getInstance().getCallStatckMap().get(`${this.traceId}_${func.id}`) : Utils.getInstance().getCallStatckMap().get(func.id);
func.threadName = Utils.getInstance().getThreadMap(traceId).get(func.tid);
}
info('AsyncFuncData Count is: ', asyncFuncList!.length);
this.processAsyncFuncArray = asyncFuncList;
this.processAsyncFuncMap = Utils.groupBy(asyncFuncList, 'pid');
let asyncFuncCatList: unknown[] = await queryProcessAsyncFuncCat(traceRange);
info('AsyncFuncCatData Count is: ', asyncFuncCatList!.length);
this.processAsyncFuncCatMap = Utils.groupBy(asyncFuncCatList, 'pid');
};
initDeliverInputEvent = async (): Promise<void> => {
let row = TraceRow.skeleton();
row.rowId = 'DeliverInputEvent';
row.index = 0;
row.rowType = TraceRow.ROW_TYPE_DELIVER_INPUT_EVENT;
row.rowParentId = '';
row.folder = true;
row.style.height = '40px';
row.name = 'DeliverInputEvent';
row.supplier = folderSupplier();
row.onThreadHandler = folderThreadHandler(row, this.trace);
let asyncFuncGroup = Utils.groupBy(
this.processAsyncFuncArray.filter((it) => it.funName === 'deliverInputEvent'),
'pid'
);
if (Reflect.ownKeys(asyncFuncGroup).length > 0) {
this.trace.rowsEL?.appendChild(row);
}
Reflect.ownKeys(asyncFuncGroup).map((key: unknown) => {
let asyncFuncGroups: Array<unknown> = asyncFuncGroup[key];
if (asyncFuncGroups.length > 0) {
row.addChildTraceRow(this.createDeliverInputEventRow(row, key, asyncFuncGroups));
}
});
};
private createDeliverInputEventRow(
parentRow: TraceRow<unknown>,
key: number,
asyncFuncGroups: Array<unknown>
): TraceRow<FuncStruct> {
let funcRow = TraceRow.skeleton<FuncStruct>();
funcRow.rowId = `${asyncFuncGroups[0].funName}-${key}`;
funcRow.asyncFuncName = asyncFuncGroups[0].funName;
funcRow.asyncFuncNamePID = key;
funcRow.rowType = TraceRow.ROW_TYPE_FUNC;
funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace);
funcRow.rowParentId = `${parentRow.rowId}`;
funcRow.rowHidden = !parentRow.expansion;
funcRow.style.width = '100%';
funcRow.name = `${asyncFuncGroups[0].funName} ${key}`;
funcRow.setAttribute('children', '');
funcRow.supplierFrame = async (): Promise<FuncStruct[]> => {
const res = await processDeliverInputEventDataSender(key, funcRow!);
this.deliverInputEventSendCallback(res, funcRow, asyncFuncGroups);
return res;
};
funcRow.findHoverStruct = (): void => {
FuncStruct.hoverFuncStruct = funcRow.getHoverStruct();
};
funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
funcRow.selectChangeHandler = this.trace.selectChangeHandler;
funcRow.onThreadHandler = rowThreadHandler<FuncRender>(
'func',
'context',
{
type: `func-${asyncFuncGroups[0].funName}-${key}`,
},
funcRow,
this.trace
);
return funcRow;
}
private deliverInputEventSendCallback(
res: Array<unknown>,
funcRow: TraceRow<unknown>,
asyncFuncGroups: Array<unknown>
): void {
let isIntersect = (
left: unknown,
right: unknown
): boolean =>
Math.max(left.startTs + left.dur, right.startTs + right.dur) - Math.min(left.startTs, right.startTs) <
left.dur + right.dur;
let depths: unknown = [];
let createDepth = (currentDepth: number, index: number): void => {
if (depths[currentDepth] === undefined || !isIntersect(depths[currentDepth], res[index])) {
res[index].depth = currentDepth;
depths[currentDepth] = res[index];
} else {
createDepth(++currentDepth, index);
}
};
res.forEach((it, i): void => {
res[i].funName = this.traceId ? Utils.getInstance().getCallStatckMap().get(`${this.traceId}_${res[i].id!}`) : Utils.getInstance().getCallStatckMap().get(res[i].id!);
res[i].threadName = Utils.getInstance().getThreadMap().get(res[i].tid!);
if (it.dur === -1 || it.dur === null || it.dur === undefined) {
it.dur = (TraceRow.range?.endNS || 0) - it.startTs;
it.flag = 'Did not end';
}
createDepth(0, i);
});
if (funcRow && !funcRow.isComplete) {
let max = Math.max(...asyncFuncGroups.map((it) => it.depth || 0)) + 1;
let maxHeight = max * 18 + 6;
funcRow.style.height = `${maxHeight}px`;
funcRow.setAttribute('height', `${maxHeight}`);
}
}
initTouchEventDispatch = async (): Promise<void> => {
let row = TraceRow.skeleton() as TraceRow<ProcessStruct>;
row.rowId = 'TouchEventDispatch';
row.index = 0;
row.rowType = TraceRow.ROW_TYPE_TOUCH_EVENT_DISPATCH;
row.rowParentId = '';
row.folder = true;
row.style.height = '40px';
row.name = 'TouchEventDispatch';
row.supplier = folderSupplier();
row.onThreadHandler = folderThreadHandler(row, this.trace);
let asyncFuncGroup = Utils.groupBy(
this.processAsyncFuncArray.filter((it) => it.funName === 'H:touchEventDispatch' || it.funName === 'H:TouchEventDispatch'),
'pid'
);
if (Reflect.ownKeys(asyncFuncGroup).length > 0) {
this.trace.rowsEL?.appendChild(row);
}
Reflect.ownKeys(asyncFuncGroup).map((key: unknown) => {
let asyncFuncGroups: Array<unknown> = asyncFuncGroup[key];
if (asyncFuncGroups.length > 0) {
row.addChildTraceRow(this.createTouchEventDispatchRow(row, key, asyncFuncGroups));
}
});
};
private createTouchEventDispatchRow(
parentRow: TraceRow<ProcessStruct>,
key: number,
asyncFuncGroups: Array<unknown>
): TraceRow<FuncStruct> {
let funcRow = TraceRow.skeleton<FuncStruct>();
funcRow.rowId = `${asyncFuncGroups[0].funName}-${key}`;
funcRow.asyncFuncName = asyncFuncGroups[0].funName;
funcRow.asyncFuncNamePID = key;
funcRow.rowType = TraceRow.ROW_TYPE_FUNC;
funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace);
funcRow.rowParentId = `${parentRow.rowId}`;
funcRow.rowHidden = !parentRow.expansion;
funcRow.style.width = '100%';
funcRow.style.height = '24px';
funcRow.name = `${asyncFuncGroups[0].funName} ${key}`;
funcRow.setAttribute('children', '');
funcRow.supplierFrame = (): Promise => {
return processTouchEventDispatchDataSender(key, funcRow!).then((res: Array<unknown>) => {
this.touchEventDispatchSendCallback(res, funcRow, asyncFuncGroups);
return res;
});
};
funcRow.findHoverStruct = (): void => {
FuncStruct.hoverFuncStruct = funcRow.getHoverStruct();
};
funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
funcRow.selectChangeHandler = this.trace.selectChangeHandler;
funcRow.onThreadHandler = rowThreadHandler<FuncRender>(
'func',
'context',
{
type: `func-${asyncFuncGroups[0].funName}-${key}`,
},
funcRow,
this.trace
);
return funcRow;
}
private touchEventDispatchSendCallback(res: Array<unknown>, funcRow: TraceRow<unknown>, asyncFuncGroups: Array<unknown>): void {
let isIntersect = (left: unknown, right: unknown): boolean =>
Math.max(left.startTs + left.dur, right.startTs + right.dur) - Math.min(left.startTs, right.startTs) <
left.dur + right.dur;
let depths: unknown = [];
let createDepth = (currentDepth: number, index: number): void => {
if (depths[currentDepth] === undefined || !isIntersect(depths[currentDepth], res[index])) {
res[index].depth = currentDepth;
depths[currentDepth] = res[index];
} else {
createDepth(++currentDepth, index);
}
};
res.forEach((it, i) => {
res[i].funName = this.traceId ? Utils.getInstance().getCallStatckMap().get(`${this.traceId}_${res[i].id!}`) : Utils.getInstance().getCallStatckMap().get(res[i].id!);
res[i].threadName = Utils.getInstance().getThreadMap().get(res[i].tid!);
if (it.dur === -1 || it.dur === null || it.dur === undefined) {
it.dur = (TraceRow.range?.endNS || 0) - it.startTs;
it.flag = 'Did not end';
}
this.addCallStackDetail(it as FuncStruct)
createDepth(0, i);
});
if (funcRow && !funcRow.isComplete) {
let max = Math.max(...asyncFuncGroups.map((it) => it.depth || 0)) + 1;
let maxHeight = max * 18 + 6;
funcRow.style.height = `${maxHeight}px`;
funcRow.setAttribute('height', `${maxHeight}`);
}
}
async init(isDistributed: boolean, parentRow?: TraceRow<unknown>, traceId?: string): Promise<void> {
this.traceId = traceId;
this.parentRow = parentRow;
this.isDistributed = isDistributed;
await this.prepareData(traceId);
if (
this.eventCountMap.print === 0 &&
this.eventCountMap.tracing_mark_write === 0 &&
this.eventCountMap.sched_switch === 0
) {
return;
}
let time = new Date().getTime();
let processSortArray = Array.from(SpProcessChart.processRowSortMap);
processSortArray.sort((a: Array<unknown>, b: Array<unknown>) =>
b[1] - a[1]
);
let processFromTable = await queryProcessByTable(traceId);
let processList = Utils.sortThreadRow(processSortArray, processFromTable, 'process');
SpProcessChart.processRowSortMap.clear();
let allJankProcess: Array<number> = [];
let allTaskPoolPid: Array<{ pid: number }> = [];
let allIOPid: Array<number> = [];
let renderServiceProcess: unknown[] = [];
if (!this.isDistributed) {
let allJankProcessData = await queryAllJankProcess();
if (allJankProcessData.length > 0) {
allJankProcessData.forEach((name, index) => {
allJankProcess.push(name.pid!);
});
}
if (FlagsConfig.getFlagsConfigEnableStatus('TaskPool')) {
allTaskPoolPid = await queryTaskPoolProcessIds();
}
renderServiceProcess = await queryRsProcess();
let allIOProcessData = await queryAllIOProcess();
if (allIOProcessData.length > 0) {
allIOProcessData.forEach((name) => {
allIOPid.push(name.pid!);
});
}
}
info('ProcessList Data size is: ', processList!.length);
this.hangProcessSet = new Set<number>((await queryHangData()).map(item => item.id));
await this.initProcessRow(processList, allTaskPoolPid, allJankProcess, renderServiceProcess, allIOPid, traceId);
let durTime = new Date().getTime() - time;
info('The time to load the Process data is: ', durTime);
}
private async prepareData(traceId?: string): Promise<void> {
if (!this.isDistributed) {
let maxValues = await queryMemFilterIdMaxValue();
let minValues = await queryMemFilterIdMinValue();
maxValues.forEach((it) => {
this.filterIdMaxValue.set(it.filterId, it.maxValue);
});
minValues.forEach((it) => {
this.filterIdMinValue.set(it.filterId, it.minValue);
});
let soInitNamesArray = await queryAllSoInitNames();
soInitNamesArray.forEach((it) => {
this.soInitNameMap.set(it.id, it.name);
});
let processSrcSliceArray = await queryAllSrcSlices();
processSrcSliceArray.forEach((it) => {
this.processSrcSliceMap.set(it.id, it.src);
});
this.processMem = await queryProcessMem();
info('The amount of initialized process memory data is : ', this.processMem!.length);
this.loadAppStartup = FlagsConfig.getFlagsConfigEnableStatus('AppStartup');
info('Prepare App startup data ');
if (this.loadAppStartup) {
this.startupProcessArr = await queryStartupPidArray();
this.processSoMaxDepth = await queryProcessSoMaxDepth();
}
}
let threadFuncMaxDepthArray = await getMaxDepthByTid(traceId);
info('Gets the maximum tier per thread , tid and maxDepth');
threadFuncMaxDepthArray.forEach((it) => {
this.threadFuncMaxDepthMap.set(`${it.ipid}-${it.tid}`, it.maxDepth);
});
info('convert tid and maxDepth array to map');
let pidCountArray = await queryProcessContentCount(traceId);
info('fetch syscall event tid');
let sysCallTidArray = await querySysCallThreadIds(traceId);
Utils.getInstance().sysCallEventTidsMap.clear();
sysCallTidArray.forEach((it) => {
Utils.getInstance().sysCallEventTidsMap.set(it.tid, it);
});
info('fetch per process pid,switch_count,thread_count,slice_count,mem_count');
pidCountArray.forEach((it) => {
this.processThreadDataCountMap.set(it.pid, it.switch_count);
this.processThreadCountMap.set(it.pid, it.thread_count);
this.processFuncDataCountMap.set(it.pid, it.slice_count);
this.processMemDataCountMap.set(it.pid, it.mem_count);
});
let eventCountList: Array<unknown> = await queryEventCountMap(traceId);
this.eventCountMap = eventCountList.reduce((pre, current) => {
pre[`${current.eventName}`] = current.count;
return pre;
}, {});
let threadArray = Array.from(SpProcessChart.threadStateList);
threadArray.sort((a: Array<unknown>, b: Array<unknown>) =>
b[1] - a[1]);
let queryProcessThreadsByTableResult = await queryProcessThreadsByTable(traceId);
this.processThreads = Utils.sortThreadRow(threadArray, queryProcessThreadsByTableResult, 'thread');
SpProcessChart.threadStateList.clear();
let distributedDataLists = await queryDistributedRelationData(traceId);
distributedDataLists.forEach((item) => {
this.distributedDataMap.set(`${item.id}_${traceId}`, {
chainId: item.chainId,
spanId: item.spanId,
parentSpanId: item.parentSpanId,
chainFlag: item.chainFlag,
traceId: traceId!,
});
});
let callstackData: Array<FuncStruct> = await queryCallstackDetail();
for (let index = 0; index < callstackData.length; index++) {
const indexData = callstackData[index];
if (indexData && !this.callStackDetail.has(indexData.id!)) {
this.callStackDetail.set(indexData.id!, indexData);
}
}
info('The amount of initialized process threads data is : ', this.processThreads!.length);
}
private async initProcessRow(
pArr: Array<unknown>,
allTaskPoolPid: Array<{ pid: number }>,
jankArr: Array<number>,
rsProcess: Array<unknown>,
allIOPid: Array<number>,
traceId?: string
): Promise<void> {
for (let i = 0; i < pArr.length; i++) {
const it = pArr[i] as {
pid: number;
processName: string | null
};
if (
(this.processThreadDataCountMap.get(it.pid) || 0) === 0 &&
(this.processThreadCountMap.get(it.pid) || 0) === 0 &&
(this.processFuncDataCountMap.get(it.pid) || 0) === 0 &&
(this.processMemDataCountMap.get(it.pid) || 0) === 0
) {
continue;
}
let processRow = this.createProcessRow(i, it, allTaskPoolPid);
if (this.parentRow) {
this.parentRow.addChildTraceRow(processRow);
} else {
this.trace.rowsEL?.appendChild(processRow);
}
let startupRow: TraceRow<AppStartupStruct> | undefined = undefined;
let soRow: TraceRow<SoStruct> | undefined = undefined;
let actualRow: TraceRow<JankStruct> | null = null;
let expectedRow: TraceRow<JankStruct> | null = null;
let currentPid = it.pid;
if (!this.isDistributed) {
if (allIOPid.indexOf(currentPid) > -1) {
await this.addIOReadWriteRows(it, processRow);
}
if (this.loadAppStartup) {
if (this.startupProcessArr.find((sp) => sp.pid === currentPid)) {
startupRow = this.addStartUpRow(processRow);
}
let maxSoDepth = this.processSoMaxDepth.find((md) => md.pid === currentPid);
if (maxSoDepth) {
soRow = this.addSoInitRow(processRow, maxSoDepth.maxDepth);
}
}
if (jankArr.indexOf(currentPid) > -1) {
expectedRow = this.addExpectedRow(it, processRow, rsProcess);
actualRow = this.addActualRow(it, processRow, rsProcess);
}
}
this.renderRow = null;
if (it.processName === 'render_service' || it.processName === 'render_process') {
this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId);
this.addProcessMemInfo(it, processRow);
this.addAsyncFunction(it, processRow);
this.addAsyncCatFunction(it, processRow);
} else {
this.addAsyncFunction(it, processRow);
this.addProcessMemInfo(it, processRow);
this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId);
this.addAsyncCatFunction(it, processRow);
}
this.addProcessRowListener(processRow, actualRow);
if (!this.isDistributed) {
await this.trace.chartManager?.frameTimeChart.initAnimatedScenesChart(processRow, it, expectedRow!, actualRow!);
}
}
}
private async addIOReadWriteRows(process: { pid: number; processName: string | null }, processRow: TraceRow<ProcessStruct>): Promise<void> {
this.addLogicalReadRow(process, processRow, IO_LANE_HEIGHT);
this.addLogicalWriteRow(process, processRow, IO_LANE_HEIGHT);
this.addPhysicalReadRow(process, processRow, IO_LANE_HEIGHT);
this.addPhysicalWriteRow(process, processRow, IO_LANE_HEIGHT);
}
private addLogicalReadRow(
process: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
laneHeight: string
): void {
let logicalReadRow = TraceRow.skeleton<IOStruct>();
logicalReadRow.rowId = `io-logical-read-${process.pid}`;
logicalReadRow.index = 1;
logicalReadRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM_IO;
logicalReadRow.rowParentId = processRow.rowId;
logicalReadRow.rowHidden = !processRow.expansion;
logicalReadRow.style.height = laneHeight;
logicalReadRow.setAttribute('children', '');
logicalReadRow.name = 'Logical Read';
logicalReadRow.supplierFrame = async (): Promise<IOStruct[]> => {
const res = await ioSender(0, TraceRow.range?.scale || 50, logicalReadRow, process.pid);
return res;
};
logicalReadRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
logicalReadRow.selectChangeHandler = this.trace.selectChangeHandler;
logicalReadRow.focusHandler = (): void => this.focusIOHandler(logicalReadRow);
logicalReadRow.findHoverStruct = (): void => {
IOStruct.hoverIOStruct = logicalReadRow.getHoverStruct(false) || null;
};
logicalReadRow.onThreadHandler = (useCache): void => {
let context: CanvasRenderingContext2D;
if (logicalReadRow.currentContext) {
context = logicalReadRow.currentContext;
} else {
context = logicalReadRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
}
logicalReadRow.canvasSave(context);
(renders['io'] as IORender).renderMainThread(
{
context: context,
useCache: useCache,
type: 'io-logical-read',
chartColor: ColorUtils.MD_PALETTE[0],
},
logicalReadRow
);
logicalReadRow.canvasRestore(context, this.trace);
};
processRow.addChildTraceRow(logicalReadRow);
}
private addLogicalWriteRow(
process: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
laneHeight: string
): void {
let logicalWriteRow = TraceRow.skeleton<IOStruct>();
logicalWriteRow.rowId = `io-logical-write-${process.pid}`;
logicalWriteRow.index = 2;
logicalWriteRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM_IO;
logicalWriteRow.rowParentId = processRow.rowId;
logicalWriteRow.rowHidden = !processRow.expansion;
logicalWriteRow.style.height = laneHeight;
logicalWriteRow.setAttribute('children', '');
logicalWriteRow.name = 'Logical Write';
logicalWriteRow.supplierFrame = async (): Promise<IOStruct[]> => {
const res = await ioSender(1, TraceRow.range?.scale || 50, logicalWriteRow, process.pid);
return res;
};
logicalWriteRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
logicalWriteRow.selectChangeHandler = this.trace.selectChangeHandler;
logicalWriteRow.focusHandler = (): void => this.focusIOHandler(logicalWriteRow);
logicalWriteRow.findHoverStruct = (): void => {
IOStruct.hoverIOStruct = logicalWriteRow.getHoverStruct(false) || null;
};
logicalWriteRow.onThreadHandler = (useCache): void => {
let context: CanvasRenderingContext2D;
if (logicalWriteRow.currentContext) {
context = logicalWriteRow.currentContext;
} else {
context = logicalWriteRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
}
logicalWriteRow.canvasSave(context);
(renders['io'] as IORender).renderMainThread(
{
context: context,
useCache: useCache,
type: 'io-logical-write',
chartColor: ColorUtils.MD_PALETTE[8],
},
logicalWriteRow
);
logicalWriteRow.canvasRestore(context, this.trace);
};
processRow.addChildTraceRow(logicalWriteRow);
}
private addPhysicalReadRow(
process: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
laneHeight: string
): void {
let physicalReadRow = TraceRow.skeleton<IOStruct>();
physicalReadRow.rowId = `io-physical-read-${process.pid}`;
physicalReadRow.index = 3;
physicalReadRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM_IO;
physicalReadRow.rowParentId = processRow.rowId;
physicalReadRow.rowHidden = !processRow.expansion;
physicalReadRow.style.height = laneHeight;
physicalReadRow.setAttribute('children', '');
physicalReadRow.name = 'Physical Read';
physicalReadRow.supplierFrame = async (): Promise<IOStruct[]> => {
const res = await ioSender(2, TraceRow.range?.scale || 50, physicalReadRow, process.pid);
return res;
};
physicalReadRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
physicalReadRow.selectChangeHandler = this.trace.selectChangeHandler;
physicalReadRow.focusHandler = (): void => this.focusIOHandler(physicalReadRow);
physicalReadRow.findHoverStruct = (): void => {
IOStruct.hoverIOStruct = physicalReadRow.getHoverStruct(false) || null;
};
physicalReadRow.onThreadHandler = (useCache): void => {
let context: CanvasRenderingContext2D;
if (physicalReadRow.currentContext) {
context = physicalReadRow.currentContext;
} else {
context = physicalReadRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
}
physicalReadRow.canvasSave(context);
(renders['io'] as IORender).renderMainThread(
{
context: context,
useCache: useCache,
type: 'io-physical-read',
chartColor: ColorUtils.MD_PALETTE[2],
},
physicalReadRow
);
physicalReadRow.canvasRestore(context, this.trace);
};
processRow.addChildTraceRow(physicalReadRow);
}
private addPhysicalWriteRow(
process: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
laneHeight: string
): void {
let physicalWriteRow = TraceRow.skeleton<IOStruct>();
physicalWriteRow.rowId = `io-physical-write-${process.pid}`;
physicalWriteRow.index = 4;
physicalWriteRow.rowType = TraceRow.ROW_TYPE_FILE_SYSTEM_IO;
physicalWriteRow.rowParentId = processRow.rowId;
physicalWriteRow.rowHidden = !processRow.expansion;
physicalWriteRow.style.height = laneHeight;
physicalWriteRow.setAttribute('children', '');
physicalWriteRow.name = 'Physical Write';
physicalWriteRow.supplierFrame = async (): Promise<IOStruct[]> => {
const res = await ioSender(3, TraceRow.range?.scale || 50, physicalWriteRow, process.pid);
return res;
};
physicalWriteRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
physicalWriteRow.selectChangeHandler = this.trace.selectChangeHandler;
physicalWriteRow.focusHandler = (): void => this.focusIOHandler(physicalWriteRow);
physicalWriteRow.findHoverStruct = (): void => {
IOStruct.hoverIOStruct = physicalWriteRow.getHoverStruct(false) || null;
};
physicalWriteRow.onThreadHandler = (useCache): void => {
let context: CanvasRenderingContext2D;
if (physicalWriteRow.currentContext) {
context = physicalWriteRow.currentContext;
} else {
context = physicalWriteRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
}
physicalWriteRow.canvasSave(context);
(renders['io'] as IORender).renderMainThread(
{
context: context,
useCache: useCache,
type: 'io-physical-write',
chartColor: ColorUtils.MD_PALETTE[10],
},
physicalWriteRow
);
physicalWriteRow.canvasRestore(context, this.trace);
};
processRow.addChildTraceRow(physicalWriteRow);
}
private focusIOHandler(row: TraceRow<IOStruct>): void {
let num = 0;
let tip = '';
if (IOStruct.hoverIOStruct) {
num = IOStruct.hoverIOStruct.size ?? 0;
let group10Ms = IOStruct.hoverIOStruct.group10Ms ?? false;
if (num > 0) {
if (group10Ms) {
tip = `<span>${num} (10.00ms)</span>`;
} else {
tip = `<span>${num}</span>`;
}
}
}
this.trace?.displayTip(row, IOStruct.hoverIOStruct, tip);
}
private createProcessRow(
index: number,
process: unknown,
allTaskPoolPid: Array<{ pid: number }>
): TraceRow<ProcessStruct> {
let processRow = TraceRow.skeleton<ProcessStruct>(this.traceId);
processRow.rowId = `${process.pid}`;
processRow.index = index;
processRow.rowType = TraceRow.ROW_TYPE_PROCESS;
processRow.rowParentId = '';
processRow.style.height = '40px';
if (FlagsConfig.getFlagsConfigEnableStatus('System Calls')) {
this.processRowSettingConfig(processRow);
}
processRow.folder = true;
if (
SpChartManager.APP_STARTUP_PID_ARR.find((pid) => pid === process.pid) !== undefined ||
process.processName === 'render_service'
) {
processRow.addTemplateTypes('AppStartup');
}
if (allTaskPoolPid.find((process) => process.pid === process.pid) !== undefined) {
processRow.addTemplateTypes('TaskPool');
}
processRow.name = `${process.processName || 'Process'} ${process.pid}`;
processRow.supplierFrame = (): Promise<Array<unknown>> => {
return processDataSender(process.pid || -1, processRow, this.traceId);
};
processRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
processRow.selectChangeHandler = this.trace.selectChangeHandler;
processRow.onThreadHandler = rowThreadHandler<ProcessRender>(
'process',
'context',
{
pid: process.pid,
type: `process ${processRow.index} ${process.processName}`,
},
processRow,
this.trace
);
return processRow;
}
processRowSettingConfig(row: TraceRow<ProcessStruct>): void {
row.rowSettingCheckBoxList = ['SysCall Event'];
row.addRowSettingCheckBox(false);
row.rowSetting = 'enable';
row.rowSettingPopoverDirection = 'bottomLeft';
row.onRowSettingCheckBoxChangeHandler = (value): void => {
row.childrenList.forEach((childRow) => {
if (childRow.rowType === TraceRow.ROW_TYPE_THREAD_SYS_CALL) {
childRow.rowDiscard = !row.getRowSettingCheckStateByKey('SysCall Event');
}
})
};
}
addProcessRowListener(processRow: TraceRow<ProcessStruct>, actualRow: TraceRow<JankStruct> | null): void {
let offsetYTimeOut: unknown = undefined;
processRow.addEventListener('expansion-change', (e: unknown) => {
JankStruct.delJankLineFlag = false;
if (offsetYTimeOut) {
clearTimeout(offsetYTimeOut);
}
if (JankStruct.selectJankStruct !== null && JankStruct.selectJankStruct !== undefined) {
if (e.detail.expansion) {
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes.forEach((linkNodeItem) => this.handler1(e, linkNodeItem, actualRow));
}, 300);
} else {
if (JankStruct!.selectJankStruct) {
JankStruct.selectJankStructList?.push(<JankStruct>JankStruct!.selectJankStruct);
}
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes?.forEach((linkProcessItem) => this.handler2(e, linkProcessItem, processRow));
}, 300);
}
} else if (FuncStruct.selectFuncStruct) {
if (e.detail.expansion) {
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes.forEach((linkNodeItem) => this.handler3(e, linkNodeItem));
}, 300);
} else {
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes?.forEach((linkProcessItem) => {
this.handler4(e, linkProcessItem, processRow);
JankStruct.selectJankStructList = [];
});
}, 300);
}
this.trace.resetDistributedLine();
} else {
if (e.detail.expansion) {
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes.forEach((linkNodeItem) => this.handler3(e, linkNodeItem));
}, 300);
} else {
if (ThreadStruct!.selectThreadStruct) {
ThreadStruct.selectThreadStructList?.push(<ThreadStruct>ThreadStruct!.selectThreadStruct);
}
offsetYTimeOut = setTimeout(() => {
this.trace.linkNodes?.forEach((linkProcessItem) => {
this.handler4(e, linkProcessItem, processRow);
JankStruct.selectJankStructList = [];
});
}, 300);
}
}
let refreshTimeOut = setTimeout(() => {
this.trace.refreshCanvas(true);
clearTimeout(refreshTimeOut);
}, 360);
});
}
handler1(e: unknown, linkItem: PairPoint[], actualRow: TraceRow<JankStruct> | null): void {
JankStruct.selectJankStructList?.forEach((selectProcessStruct: unknown) => {
if (e.detail.rowId === selectProcessStruct.pid) {
JankStruct.selectJankStruct = selectProcessStruct;
JankStruct.hoverJankStruct = selectProcessStruct;
}
});
this.updatePairPointTranslateY(linkItem[0]);
linkItem[0].y = linkItem[0].rowEL!.translateY! + linkItem[0].offsetY;
this.updatePairPointTranslateY(linkItem[1]);
linkItem[1].y = linkItem[1].rowEL!.translateY! + linkItem[1].offsetY;
if (actualRow) {
if (linkItem[0].rowEL.rowId === e.detail.rowId) {
linkItem[0].x = ns2xByTimeShaft(linkItem[0].ns, this.trace.timerShaftEL!);
linkItem[0].y = actualRow!.translateY! + linkItem[0].offsetY * 2;
linkItem[0].offsetY = linkItem[0].offsetY * 2;
linkItem[0].rowEL = actualRow!;
} else if (linkItem[1].rowEL.rowId === e.detail.rowId) {
linkItem[1].x = ns2xByTimeShaft(linkItem[1].ns, this.trace.timerShaftEL!);
linkItem[1].y = actualRow!.translateY! + linkItem[1].offsetY * 2;
linkItem[1].offsetY = linkItem[1].offsetY * 2;
linkItem[1].rowEL = actualRow!;
}
}
}
handler2(e: unknown, linkItem: PairPoint[], processRow: TraceRow<ProcessStruct>): void {
this.updatePairPointTranslateY(linkItem[0]);
linkItem[0].y = linkItem[0].rowEL!.translateY! + linkItem[0].offsetY;
this.updatePairPointTranslateY(linkItem[1]);
linkItem[1].y = linkItem[1].rowEL!.translateY! + linkItem[1].offsetY;
if (linkItem[0].rowEL.rowParentId === e.detail.rowId) {
this.updatePairPoint(linkItem[0], processRow);
} else if (linkItem[1].rowEL.rowParentId === e.detail.rowId) {
this.updatePairPoint(linkItem[1], processRow);
}
}
handler3(e: unknown, linkItem: PairPoint[]): void {
ThreadStruct.selectThreadStructList?.forEach((selectProcessStruct: unknown) => {
if (e.detail.rowId === selectProcessStruct.pid) {
ThreadStruct.selectThreadStruct = selectProcessStruct;
ThreadStruct.hoverThreadStruct = selectProcessStruct;
}
});
linkItem[0].backrowEL = linkItem[0].sourcebackrowEL!.parentRowEl?.expansion ?
linkItem[0].sourcebackrowEL! : linkItem[0].sourcebackrowEL!.parentRowEl;
linkItem[1].backrowEL = linkItem[1].sourcebackrowEL!.parentRowEl?.expansion ?
linkItem[1].sourcebackrowEL! : linkItem[1].sourcebackrowEL!.parentRowEl;
this.trace.handleCollectFunc([linkItem]);
linkItem[0].offsetY = linkItem[0].sourcebackrowEL!.parentRowEl?.expansion ?
linkItem[0].sourceOffsetY! / 2 : linkItem[0].offsetY;
linkItem[1].offsetY = linkItem[1].sourcebackrowEL!.parentRowEl?.expansion ?
linkItem[1].sourceOffsetY! / 2 : linkItem[1].offsetY;
if (linkItem[0].sourcebackrowEL!.parentRowEl?.expansion && linkItem[0].backrowEL) {
linkItem[0].x = ns2xByTimeShaft(linkItem[0].ns, this.trace.timerShaftEL!);
linkItem[0].y = linkItem[0].rowEL.translateY + linkItem[0].offsetY;
linkItem[0].offsetY = linkItem[0].offsetY * 2;
linkItem[0].rowEL = linkItem[0].backrowEL;
}
if (linkItem[1].sourcebackrowEL!.parentRowEl?.expansion && linkItem[1].backrowEL) {
linkItem[1].x = ns2xByTimeShaft(linkItem[1].ns, this.trace.timerShaftEL!);
linkItem[1].y = linkItem[1].rowEL!.translateY! + linkItem[1].offsetY;
linkItem[1].offsetY = linkItem[1].offsetY * 2;
linkItem[1].rowEL = linkItem[1].backrowEL;
}
}
handler4(e: unknown, linkItem: PairPoint[], processRow: TraceRow<ProcessStruct>): void {
if (e.detail.rowId === 'sameThreadProcess') {
if (linkItem[0].rowEL.parentRowEl?.rowId === e.detail.rowId) {
this.updatePairPoint(linkItem[0], processRow);
}
if (linkItem[1].rowEL.parentRowEl?.rowId === e.detail.rowId) {
this.updatePairPoint(linkItem[1], processRow);
}
}
if (e.detail.rowId !== 'sameThreadProcess') {
if (linkItem[0].rowEL.rowParentId === e.detail.rowId) {
this.updatePairPoint(linkItem[0], processRow);
}
if (linkItem[1].rowEL.rowParentId === e.detail.rowId) {
this.updatePairPoint(linkItem[1], processRow);
}
}
this.trace.handleCollectFunc([linkItem]);
}
updatePairPointTranslateY(pair: PairPoint): void {
let linkNodeYTimeOut: unknown = undefined;
if (linkNodeYTimeOut) {
clearTimeout(linkNodeYTimeOut);
}
if (pair.rowEL.collect) {
pair.rowEL.translateY = pair.rowEL.getBoundingClientRect().top - 195;
linkNodeYTimeOut = setTimeout(() => {
if (this.trace.favoriteChartListEL?.collect1Expand === false) {
if (pair.rowEL.collectGroup === '1') {
pair.rowEL.translateY = 23;
}
}
if (this.trace.favoriteChartListEL?.collect2Expand === false) {
if (pair.rowEL.collectGroup === '2') {
if (this.trace.groupTitle1?.style.display === 'none') {
pair.rowEL.translateY = 23;
} else if (this.trace.groupTitle1?.style.display === 'flex' || this.trace.groupTitle1?.style.display === 'block') {
pair.rowEL.translateY = Number(this.trace.groupTitle1?.clientHeight) + Number(this.trace.collectEl1?.clientHeight) + 27;
}
}
}
}, 300);
} else {
pair.rowEL.translateY = pair.rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop;
}
let refreshTimeOut = setTimeout(() => {
this.trace.refreshCanvas(true);
clearTimeout(refreshTimeOut);
}, 300);
}
updatePairPoint(pair: PairPoint, processRow: TraceRow<ProcessStruct>): void {
if (!pair.rowEL.collect) {
pair.x = ns2xByTimeShaft(pair.ns, this.trace.timerShaftEL!);
pair.y = processRow!.translateY! + pair.offsetY / 2;
pair.rowEL = processRow!;
pair.offsetY = pair.rowEL._frame!.height / 2;
pair.backrowEL = processRow!;
}
}
addExpectedRow(
process: unknown,
processRow: TraceRow<unknown>,
renderServiceProcess: Array<unknown>
): TraceRow<JankStruct> {
let expectedRow = TraceRow.skeleton<JankStruct>();
expectedRow.asyncFuncName = process.processName;
expectedRow.asyncFuncNamePID = process.pid;
expectedRow.rowType = TraceRow.ROW_TYPE_JANK;
expectedRow.rowParentId = `${process.pid}`;
expectedRow.rowHidden = !processRow.expansion;
expectedRow.style.width = '100%';
expectedRow.name = 'Expected Timeline';
expectedRow.addTemplateTypes('FrameTimeline');
expectedRow.setAttribute('children', '');
expectedRow.supplierFrame = async (): Promise<JankStruct[]> => {
let res = await processExpectedDataSender(process.pid, expectedRow!);
this.jankSenderCallback(res, 'expected', process, expectedRow, renderServiceProcess);
return res;
};
expectedRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
expectedRow.selectChangeHandler = this.trace.selectChangeHandler;
expectedRow.onThreadHandler = rowThreadHandler<JankRender>(
'jank',
'context',
{
type: 'expected_frame_timeline_slice',
},
expectedRow,
this.trace
);
if (this.renderRow) {
processRow.addChildTraceRowBefore(expectedRow, this.renderRow);
} else {
processRow.addChildTraceRow(expectedRow);
}
return expectedRow;
}
addActualRow(
process: unknown,
processRow: TraceRow<unknown>,
renderServiceProcess: Array<unknown>
): TraceRow<JankStruct> {
let actualRow = TraceRow.skeleton<JankStruct>();
actualRow.rowType = TraceRow.ROW_TYPE_JANK;
actualRow.rowParentId = `${process.pid}`;
actualRow.rowHidden = !processRow.expansion;
actualRow.style.width = '100%';
actualRow.name = 'Actual Timeline';
actualRow.addTemplateTypes('FrameTimeline');
actualRow.setAttribute('children', '');
actualRow.supplierFrame = async (): Promise<JankStruct[]> => {
let res = await processActualDataSender(process.pid, actualRow!);
this.jankSenderCallback(res, 'actual', process, actualRow, renderServiceProcess);
return res;
};
actualRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
actualRow.selectChangeHandler = this.trace.selectChangeHandler;
actualRow.onThreadHandler = rowThreadHandler<JankRender>(
'jank',
'context',
{
type: 'actual_frame_timeline_slice',
},
actualRow,
this.trace
);
if (this.renderRow) {
processRow.addChildTraceRowBefore(actualRow, this.renderRow);
} else {
processRow.addChildTraceRow(actualRow);
}
return actualRow;
}
addHangRow(
data: {
pid: number | null;
processName: string | null;
},
processRow: TraceRow<ProcessStruct>,
threadRow: TraceRow<ThreadStruct>,
thread: unknown
): TraceRow<HangStruct> | null {
if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus('Hangs Detection')) {
if (data.pid === thread.tid) {
let hangsRow = TraceRow.skeleton<HangStruct>();
hangsRow.rowType = TraceRow.ROW_TYPE_HANG_INNER;
hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}`;
hangsRow.rowParentId = `${data.pid}`;
hangsRow.rowHidden = !processRow.expansion;
hangsRow.style.width = '100%';
hangsRow.name = 'Hangs';
hangsRow.addTemplateTypes('FrameTimeline');
hangsRow.setAttribute('children', '');
hangsRow.supplierFrame = async (): Promise<HangStruct[]> => {
let promiseData = hangDataSender(data.pid!, hangsRow);
if (promiseData === null) {
return new Promise<Array<HangStruct>>((resolve) => resolve([]));
} else {
return promiseData.then((resultHang: Array<HangStruct>) =>
resultHang.map(hangItem => ({
...hangItem,
pname: data.processName ?? 'process',
type: SpHangChart.calculateHangType(hangItem.dur!),
content: SpHangChart.funcNameMap.get(hangItem.id!)
}))
);
}
};
hangsRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
hangsRow.selectChangeHandler = this.trace.selectChangeHandler;
hangsRow.findHoverStruct = (): void => {
HangStruct.hoverHangStruct = hangsRow.getHoverStruct();
};
hangsRow.onThreadHandler = rowThreadHandler<HangStruct>(
'hang',
'context',
{
type: 'hangs_frame_timeline_slice',
},
hangsRow,
this.trace
);
processRow.addChildTraceRowBefore(hangsRow, threadRow);
return hangsRow;
}
}
return null;
}
addThreadSysCallRow(
data: {
pid: number | null;
processName: string | null;
},
processRow: TraceRow<ProcessStruct>,
threadRow: TraceRow<ThreadStruct>,
thread: unknown
): TraceRow<ThreadSysCallStruct> | null {
const ids = Utils.getInstance().sysCallEventTidsMap.get(thread.tid);
if (ids) {
const threadSysCallRow = TraceRow.skeleton<ThreadSysCallStruct>();
threadSysCallRow.rowType = TraceRow.ROW_TYPE_THREAD_SYS_CALL;
threadSysCallRow.rowId = `${data.pid}-${ids.itid}-syscall`;
threadSysCallRow.rowParentId = `${data.pid}`;
threadSysCallRow.rowHidden = !processRow.expansion;
threadSysCallRow.style.width = '100%';
threadSysCallRow.name = `syscall event ${ids.tid}`;
threadSysCallRow.addTemplateTypes('SysCallEvent');
threadSysCallRow.setAttribute('children', '');
threadSysCallRow.supplierFrame = async (): Promise<ThreadSysCallStruct[]> => {
let promiseData = threadSysCallDataSender(ids.itid!, ids.tid!, data.pid!, threadSysCallRow);
if (promiseData === null) {
return new Promise<Array<ThreadSysCallStruct>>((resolve) => resolve([]));
} else {
return promiseData.then();
}
};
threadSysCallRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
threadSysCallRow.selectChangeHandler = this.trace.selectChangeHandler;
threadSysCallRow.findHoverStruct = (): void => {
ThreadSysCallStruct.hoverStruct = threadSysCallRow.getHoverStruct();
};
threadSysCallRow.onThreadHandler = rowThreadHandler<ThreadSysCallStruct>(
'threadSysCall',
'context',
{
type: threadSysCallRow.rowId,
translateY: threadSysCallRow.translateY,
},
threadSysCallRow,
this.trace
);
threadSysCallRow.rowDiscard = true;
processRow.addChildTraceRowAfter(threadSysCallRow, threadRow);
return threadSysCallRow;
}
return null;
}
jankSenderCallback(
res: JankStruct[],
type: string,
process: unknown,
row: TraceRow<JankStruct>,
renderServiceProcess: Array<unknown>,
): void {
let maxDepth: number = 1;
let unitHeight: number = 20;
for (let j = 0; j < res.length; j++) {
let struct = res[j];
if (struct.depth! >= maxDepth) {
maxDepth = struct.depth! + 1;
}
if (type === 'actual') {
struct.src_slice = this.processSrcSliceMap.get(res[j].id!);
if (struct.tid && struct.tid !== struct.pid) {
struct.name = `${struct.name}-${struct.tid}`;
}
}
struct.cmdline = Utils.getInstance().getProcessMap().get(res[j].pid!);
if (res[j].pid! === renderServiceProcess[0].pid) {
struct.cmdline = 'render_service';
struct.frameType = struct.cmdline;
} else {
struct.frameType = 'app';
}
}
if (row && !row.isComplete && res.length > 0) {
let maxHeight: number = maxDepth * unitHeight;
row.style.height = `${maxHeight}px`;
row.setAttribute('height', `${maxHeight}`);
if (res[0]) {
let timeLineType = res[0].type;
row.rowId = `${timeLineType}-${process.pid}`;
row.setAttribute('frame_type', res[0].frameType || '');
if (type === 'actual') {
row.dataList = res;
}
}
}
}
addStartUpRow(processRow: TraceRow<ProcessStruct>): TraceRow<AppStartupStruct> {
processRow.setAttribute('hasStartup', 'true');
let startupRow: TraceRow<AppStartupStruct> = TraceRow.skeleton<AppStartupStruct>();
startupRow.rowId = `app-start-${processRow.rowId}`;
startupRow.rowType = TraceRow.ROW_TYPE_APP_STARTUP;
startupRow.rowParentId = `${processRow.rowId}`;
startupRow.rowHidden = !processRow.expansion;
startupRow.index = 0;
startupRow.style.height = '30px';
startupRow.style.width = '100%';
startupRow.name = 'App Startups';
startupRow.findHoverStruct = (): void => {
AppStartupStruct.hoverStartupStruct = startupRow.getHoverStruct();
};
startupRow.setAttribute('children', '');
startupRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
startupRow.selectChangeHandler = this.trace.selectChangeHandler;
startupRow.supplierFrame = (): Promise<Array<AppStartupStruct>> =>
processStartupDataSender(parseInt(processRow.rowId!), startupRow).then((res) => {
if (res.length <= 0) {
this.trace.refreshCanvas(true);
}
for (let i = 0; i < res.length; i++) {
if (res[i].startName! < 6 && i < res.length - 1) {
res[i].endItid = res[i + 1].itid;
}
}
return res;
});
startupRow.onThreadHandler = rowThreadHandler<AppStartupRender>(
'app-start-up',
'appStartupContext',
{
type: `app-startup ${processRow.rowId}`,
},
startupRow,
this.trace
);
processRow.addChildTraceRow(startupRow);
return startupRow;
}
addSoInitRow(processRow: TraceRow<ProcessStruct>, maxDepth: number): TraceRow<SoStruct> {
processRow.setAttribute('hasStaticInit', 'true');
let maxHeight = (maxDepth + 1) * 20;
let soRow: TraceRow<SoStruct> = TraceRow.skeleton<SoStruct>();
soRow.rowId = `app-start-${processRow.rowId}`;
soRow.rowType = TraceRow.ROW_TYPE_STATIC_INIT;
soRow.rowParentId = `${processRow.rowId}`;
soRow.rowHidden = !processRow.expansion;
soRow.index = 0;
soRow.style.height = `${maxHeight}px`;
soRow.style.width = '100%';
soRow.name = 'Static Initialization';
soRow.setAttribute('children', '');
soRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
soRow.selectChangeHandler = this.trace.selectChangeHandler;
soRow.findHoverStruct = (): void => {
SoStruct.hoverSoStruct = soRow.getHoverStruct();
};
soRow.supplierFrame = (): Promise<Array<SoStruct>> =>
processSoInitDataSender(parseInt(processRow.rowId!), soRow).then((res) => {
if (res.length <= 0) {
this.trace.refreshCanvas(true);
}
res.forEach((so, index) => {
let soName = this.soInitNameMap.get(res[index].id!);
if (soName) {
so.soName = soName.replace('dlopen: ', '');
}
});
return res;
});
soRow.onThreadHandler = rowThreadHandler<SoRender>(
'app-so-init',
'context',
{
type: `static-init ${processRow.rowId}`,
},
soRow,
this.trace
);
processRow.addChildTraceRow(soRow);
return soRow;
}
insertAfter(newEl: HTMLElement, targetEl: HTMLElement): void {
let parentEl = targetEl.parentNode;
if (parentEl!.lastChild === targetEl) {
parentEl!.appendChild(newEl);
} else {
parentEl!.insertBefore(newEl, targetEl.nextSibling);
}
}
async addThreadList(
it: { pid: number | null; processName: string | null },
pRow: TraceRow<ProcessStruct>,
expectedRow: TraceRow<JankStruct> | null,
actualRow: TraceRow<JankStruct> | null,
soRow: TraceRow<SoStruct> | undefined,
startupRow: TraceRow<AppStartupStruct> | undefined,
traceId?: string
): Promise<void> {
let threads = this.processThreads.filter((thread) => thread.pid === it.pid && thread.tid !== 0);
const sameThreadCounts: Record<string, number> = {};
const sameThreadList: unknown[] = [];
const differentThreadList: unknown[] = [];
threads.forEach(item => {
const sameThread = item.threadName;
if (sameThread !== undefined) {
if (sameThread in sameThreadCounts && item.tid !== item.pid) {
sameThreadCounts[sameThread]++;
} else {
sameThreadCounts[sameThread] = 1;
}
}
});
threads.forEach((item) => {
const sameThread = item.threadName;
if (sameThreadCounts[sameThread!] > 128 && item.tid !== item.pid) {
sameThreadList.push(item);
} else {
differentThreadList.push(item);
}
});
differentThreadList.length && this.addDifferentThread(it, pRow, expectedRow, actualRow, soRow, startupRow, differentThreadList, traceId!);
if (sameThreadList.length) {
let sameThreadFolder = this.initSameThreadFolder(it, pRow, sameThreadList, traceId!);
if (sameThreadFolder) {
pRow.addChildTraceRow(this.sameThreadFolder);
this.addProcessRowListener(this.sameThreadFolder, actualRow);
}
await this.initSameThreadData(sameThreadFolder, it, expectedRow, actualRow, soRow, startupRow, sameThreadList, traceId);
}
}
initSameThreadFolder(
it: { pid: number | null; processName: string | null },
pRow: TraceRow<ProcessStruct>,
list: Array<unknown>,
traceId?: string
): TraceRow<ProcessStruct> {
let sameThreadRow = TraceRow.skeleton<ProcessStruct>();
sameThreadRow.rowId = 'sameThreadProcess';
sameThreadRow.rowParentId = `${it.pid}`;
sameThreadRow.rowHidden = !pRow.expansion;
sameThreadRow.rowType = TraceRow.ROW_TYPE_THREAD_NAME;
sameThreadRow.folder = true;
sameThreadRow.name = list[0].threadName;
sameThreadRow.folderPaddingLeft = 20;
sameThreadRow.style.height = '40px';
sameThreadRow.style.width = '100%';
sameThreadRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
sameThreadRow.selectChangeHandler = this.trace.selectChangeHandler;
sameThreadRow.supplierFrame = (): Promise<Array<ProcessStruct>> =>
new Promise<Array<ProcessStruct>>((resolve) => resolve([]));
sameThreadRow.onThreadHandler = (useCache): void => {
let context: CanvasRenderingContext2D;
if (sameThreadRow.currentContext) {
context = sameThreadRow.currentContext;
} else {
context = sameThreadRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
}
sameThreadRow.canvasSave(context);
if (sameThreadRow.expansion) {
context?.clearRect(0, 0, sameThreadRow.frame.width, sameThreadRow.frame.height);
} else {
(renders.empty as EmptyRender).renderMainThread(
{
context: context,
useCache: useCache,
type: '',
},
sameThreadRow
);
}
sameThreadRow.canvasRestore(context, this.trace);
};
this.sameThreadFolder = sameThreadRow;
return this.sameThreadFolder;
}
async initSameThreadData(sameThreadFolder: TraceRow<ProcessStruct>, it: { pid: number | null; processName: string | null },
expectedRow: TraceRow<JankStruct> | null,
actualRow: TraceRow<JankStruct> | null,
soRow: TraceRow<SoStruct> | undefined,
startupRow: TraceRow<AppStartupStruct> | undefined, sameThreadList: Array<unknown>, traceId?: string): Promise<void> {
let tRowArr: Array<TraceRow<BaseStruct>> = [];
for (let j = 0; j < sameThreadList.length; j++) {
let thread = sameThreadList[j];
let tRow = TraceRow.skeleton<ThreadStruct>(this.traceId);
tRow.rowId = `${thread.tid}`;
tRow.rowType = TraceRow.ROW_TYPE_THREAD;
tRow.rowParentId = `${thread.pid}`;
tRow.rowHidden = !sameThreadFolder.expansion;
tRow.index = j;
tRow.style.height = '18px';
tRow.style.width = '100%';
tRow.name = `${thread.threadName || 'Thread'} ${thread.tid}`;
tRow.namePrefix = `${thread.threadName || 'Thread'}`;
tRow.setAttribute('children', '');
tRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
tRow.selectChangeHandler = this.trace.selectChangeHandler;
tRow.findHoverStruct = (): void => this.threadRowFindHoverStruct(tRow);
tRow.supplierFrame = async (): Promise<Array<ThreadStruct>> => {
const res = await threadDataSender(thread.tid || 0, it.pid || 0, tRow, this.traceId);
if (res === true) {
return [];
}
let rs = res as ThreadStruct[];
if (rs.length <= 0 && !tRow.isComplete) {
this.trace.refreshCanvas(true);
}
return rs;
};
tRow.onThreadHandler = rowThreadHandler<ThreadRender>(
'thread',
'context',
{
type: `thread ${thread.tid} ${thread.threadName}`,
translateY: tRow.translateY,
},
tRow,
this.trace
);
this.insertRowToDoc(it, j, thread, sameThreadFolder, tRow, sameThreadList, tRowArr, actualRow, expectedRow, startupRow, soRow);
this.addFuncStackRow(it, thread, j, sameThreadList, tRowArr, tRow, sameThreadFolder);
if ((thread.switchCount || 0) === 0) {
tRow.rowDiscard = true;
}
sameThreadFolder.addChildTraceRow(tRow);
}
}
addDifferentThread(it: { pid: number | null; processName: string | null },
pRow: TraceRow<ProcessStruct>,
expectedRow: TraceRow<JankStruct> | null,
actualRow: TraceRow<JankStruct> | null,
soRow: TraceRow<SoStruct> | undefined,
startupRow: TraceRow<AppStartupStruct> | undefined,
list: Array<unknown>, traceId?: string): void {
let tRowArr: Array<TraceRow<BaseStruct>> = [];
for (let j = 0; j < list.length; j++) {
let thread = list[j];
let tRow = TraceRow.skeleton<ThreadStruct>(this.traceId);
tRow.rowId = `${thread.tid}`;
tRow.rowType = TraceRow.ROW_TYPE_THREAD;
tRow.rowParentId = `${it.pid}`;
tRow.rowHidden = !pRow.expansion;
tRow.index = j;
tRow.style.height = '18px';
tRow.style.width = '100%';
tRow.name = `${thread.threadName || 'Thread'} ${thread.tid}`;
tRow.namePrefix = `${thread.threadName || 'Thread'}`;
tRow.setAttribute('children', '');
tRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
tRow.selectChangeHandler = this.trace.selectChangeHandler;
tRow.findHoverStruct = (): void => this.threadRowFindHoverStruct(tRow);
tRow.supplierFrame = async (): Promise<Array<ThreadStruct>> => {
const res = await threadDataSender(thread.tid || 0, it.pid || 0, tRow, this.traceId);
if (res === true) {
return [];
}
let rs = res as ThreadStruct[];
if (rs.length <= 0 && !tRow.isComplete) {
this.trace.refreshCanvas(true);
}
return rs;
};
tRow.onThreadHandler = rowThreadHandler<ThreadRender>(
'thread',
'context',
{
type: `thread ${thread.tid} ${thread.threadName}`,
translateY: tRow.translateY,
},
tRow,
this.trace
);
let hangRow: TraceRow<HangStruct> | null = null;
this.insertRowToDoc(it, j, thread, pRow, tRow, list, tRowArr, actualRow, expectedRow, hangRow, startupRow, soRow);
this.addFuncStackRow(it, thread, j, list, tRowArr, tRow, pRow);
this.addThreadSysCallRow(it, pRow, tRow, thread);
if ((thread.switchCount || 0) === 0) {
tRow.rowDiscard = true;
}
}
}
threadRowFindHoverStruct(threadRow: TraceRow<ThreadStruct>): void {
let arr = threadRow.dataListCache.filter(
(re) => re.frame && isFrameContainPoint(re.frame, threadRow.hoverX, threadRow.hoverY, true, false)
);
let runItem = arr.find((it) => it.state === 'Running');
if (runItem) {
ThreadStruct.hoverThreadStruct = runItem;
} else {
let otherItem = arr.find((it) => it.state !== 'S');
if (otherItem) {
ThreadStruct.hoverThreadStruct = otherItem;
} else {
ThreadStruct.hoverThreadStruct = arr[0];
}
}
}
insertRowToDoc(
it: unknown,
index: number,
thread: ThreadStruct,
processRow: TraceRow<ProcessStruct>,
threadRow: TraceRow<ThreadStruct>,
threads: ThreadStruct[],
threadRowArr: TraceRow<unknown>[],
actualRow: TraceRow<unknown> | null,
expectedRow: TraceRow<unknown> | null,
hangRow: TraceRow<HangStruct> | null,
startupRow: TraceRow<AppStartupStruct> | null | undefined,
soRow: TraceRow<SoStruct> | null | undefined
): void {
if (it.processName === 'render_service') {
if (threadRow.name === `${it.processName} ${it.pid}`) {
this.renderRow = threadRow;
}
let flag = threads.length === index + 1 && !this.threadFuncMaxDepthMap.has(`${thread.upid}-${thread.tid}`);
processRow.sortRenderServiceData(threadRow, threadRow, threadRowArr, flag);
} else {
if (threadRow.rowId === threadRow.rowParentId) {
if (actualRow !== null) {
processRow.addChildTraceRowAfter(threadRow, actualRow);
} else if (expectedRow !== null) {
processRow.addChildTraceRowAfter(threadRow, expectedRow);
} else if (soRow) {
processRow.addChildTraceRowAfter(threadRow, soRow);
} else if (startupRow) {
processRow.addChildTraceRowAfter(threadRow, startupRow);
} else if (hangRow !== null) {
processRow.addChildTraceRowAfter(threadRow, hangRow);
} else {
processRow.addChildTraceRowSpecifyLocation(threadRow, 0);
}
} else {
processRow.addChildTraceRow(threadRow);
}
}
}
addFuncStackRow(
process: { pid: number | null; processName: string | null },
thread: unknown,
index: number,
threads: Array<unknown>,
threadRowArr: Array<unknown>,
threadRow: TraceRow<ThreadStruct>,
processRow: TraceRow<ProcessStruct>
): void {
if (this.threadFuncMaxDepthMap.get(`${thread.upid}-${thread.tid}`) !== undefined) {
this.addHangRow(process, processRow, threadRow, thread);
let max = this.threadFuncMaxDepthMap.get(`${thread.upid}-${thread.tid}`) || 1;
let maxHeight = max * 18 + 6;
let funcRow = TraceRow.skeleton<FuncStruct>(this.traceId);
funcRow.rowId = `${thread.tid}`;
funcRow.rowType = TraceRow.ROW_TYPE_FUNC;
funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace);
funcRow.rowParentId = `${thread.pid}`;
funcRow.rowHidden = !processRow.expansion;
funcRow.checkType = threadRow.checkType;
funcRow.style.width = '100%';
funcRow.style.height = `${maxHeight}px`;
funcRow.name = `${thread.threadName || 'Thread'} ${thread.tid}`;
funcRow.namePrefix = `${thread.threadName || 'Thread'}`;
funcRow.setAttribute('children', '');
funcRow.supplierFrame = async (): Promise<Array<FuncStruct>> => {
const rs = await funcDataSender(thread.tid || 0, thread.upid || 0, funcRow, this.traceId);
return this.funDataSenderCallback(rs, funcRow, thread);
};
funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
funcRow.selectChangeHandler = this.trace.selectChangeHandler;
funcRow.findHoverStruct = (): void => {
FuncStruct.hoverFuncStruct = funcRow.getHoverStruct();
};
funcRow.onThreadHandler = rowThreadHandler<FuncRender>(
'func',
'context',
{
type: `func${thread.tid}${thread.threadName}`,
},
funcRow,
this.trace
);
if (process.processName === 'render_service') {
let flag = threads.length === index + 1;
processRow.sortRenderServiceData(funcRow, threadRow, threadRowArr, flag);
} else {
processRow.addChildTraceRowAfter(funcRow, threadRow);
}
}
}
funDataSenderCallback(
rs: Array<unknown> | boolean,
funcRow: TraceRow<FuncStruct>,
thread: ThreadStruct
): FuncStruct[] {
if (rs === true) {
funcRow.rowDiscard = true;
return [];
} else {
let funs = rs as FuncStruct[];
if (funs.length > 0) {
funs.forEach((fun, index) => {
funs[index].itid = thread.utid;
funs[index].ipid = thread.upid;
funs[index].tid = thread.tid;
funs[index].pid = thread.pid;
this.addCallStackDetail(funs[index])
funs[index].funName = this.traceId ? Utils.getInstance().getCallStatckMap().get(`${this.traceId}_${funs[index].id}`) : Utils.getInstance().getCallStatckMap().get(funs[index].id!);
if (Utils.isBinder(fun)) {
} else {
if (fun.nofinish) {
fun.flag = 'Did not end';
}
}
if (fun.id && this.distributedDataMap.has(`${fun.id}_${this.traceId}`)) {
let distributedData = this.distributedDataMap.get(`${fun.id}_${this.traceId}`);
funs[index].chainId = distributedData!.chainId;
funs[index].spanId = distributedData!.spanId;
funs[index].parentSpanId = distributedData!.parentSpanId;
funs[index].chainFlag = distributedData!.chainFlag;
funs[index].traceId = this.traceId;
}
});
} else {
this.trace.refreshCanvas(true);
}
return funs;
}
}
addProcessMemInfo(it: { pid: number | null; processName: string | null }, processRow: TraceRow<ProcessStruct>): void {
let processMem = this.processMem.filter((mem) => mem.pid === it.pid);
processMem.forEach((mem) => {
let row = TraceRow.skeleton<ProcessMemStruct>();
row.rowId = `${mem.trackId}`;
row.rowType = TraceRow.ROW_TYPE_MEM;
row.rowParentId = `${it.pid}`;
row.rowHidden = !processRow.expansion;
row.style.height = '40px';
row.style.width = '100%';
row.name = `${mem.trackName}`;
row.setAttribute('children', '');
row.favoriteChangeHandler = this.trace.favoriteChangeHandler;
row.selectChangeHandler = this.trace.selectChangeHandler;
row.focusHandler = (): void => {
this.trace.displayTip(
row,
ProcessMemStruct.hoverProcessMemStruct,
`<span>${ProcessMemStruct.hoverProcessMemStruct?.value || '0'}</span>`
);
};
row.findHoverStruct = (): void => {
ProcessMemStruct.hoverProcessMemStruct = row.getHoverStruct(false);
};
row.supplierFrame = (): Promise<Array<ProcessMemStruct>> =>
processMemDataSender(mem.trackId, row).then((resultProcess) => {
let maxValue = this.filterIdMaxValue.get(mem.trackId) || 0;
let minValue = this.filterIdMinValue.get(mem.trackId) || 0;
for (let j = 0; j < resultProcess.length; j++) {
resultProcess[j].maxValue = maxValue;
resultProcess[j].minValue = minValue;
if (j === resultProcess.length - 1) {
resultProcess[j].duration = (TraceRow.range?.totalNS || 0) - (resultProcess[j].startTime || 0);
} else {
resultProcess[j].duration = (resultProcess[j + 1].startTime || 0) - (resultProcess[j].startTime || 0);
}
if (j > 0) {
resultProcess[j].delta = (resultProcess[j].value || 0) - (resultProcess[j - 1].value || 0);
} else {
resultProcess[j].delta = 0;
}
}
return resultProcess;
});
row.onThreadHandler = rowThreadHandler<MemRender>(
'mem',
'context',
{
type: `mem ${mem.trackId} ${mem.trackName}`,
},
row,
this.trace
);
if (this.renderRow && row.name === 'H:PreferredFrameRate') {
processRow.addChildTraceRowBefore(row, this.renderRow);
} else {
processRow.addChildTraceRow(row);
}
});
}
private calMaxHeight(asyncFunctions: unknown[]): number {
let max = 0;
asyncFunctions.forEach((it) => {
const depth = it.depth || 0;
if (depth > max) {
max = depth;
}
});
max += 1;
return max * 18 + 6;
}
private toAsyncFuncCache(object: unknown, name: string): void {
let modifiedObject = { ...object };
modifiedObject.startTime = modifiedObject.startTs;
Reflect.deleteProperty(modifiedObject, 'startTs');
modifiedObject.rowId = name;
modifiedObject.type = 'func';
SpProcessChart.asyncFuncCache.push({ ...modifiedObject });
}
addAsyncFunction(it: { pid: number; processName: string | null }, processRow: TraceRow<ProcessStruct>): void {
let isCategoryAsyncfunc: boolean = FlagsConfig.getFlagsConfigEnableStatus('Start&Finish Trace Category');
let asyncRemoveCatArr: unknown[];
let asyncCat: unknown;
let setArrayLenThanOne: unknown;
let setArrayLenOnlyOne: unknown;
let asyncFuncList = this.processAsyncFuncMap[it.pid] || [];
if (!asyncFuncList.length) {
return;
}
let flag = FlagsConfig.getSecondarySelectValue('catValue') === 'business';
if (isCategoryAsyncfunc) {
({ asyncRemoveCatArr, asyncCat } = this.hanldCatFunc(asyncFuncList, flag));
({ setArrayLenThanOne, setArrayLenOnlyOne } = this.hanldAsyncFunc(it, asyncRemoveCatArr));
let aggregateData = { ...setArrayLenThanOne, ...setArrayLenOnlyOne };
Reflect.ownKeys(aggregateData).map((key: unknown) => {
let param: Array<unknown> = aggregateData[key];
this.makeAddAsyncFunction(param, it, processRow, key);
});
Reflect.ownKeys(asyncCat).map((key: unknown) => {
let param: Array<unknown> = asyncCat[key];
if (flag) {
this.makeAddAsyncFunction(param, it, processRow, key);
} else {
this.makeAddAsyncFunction(param, it, processRow, key, param[0].tid);
}
});
} else {
let asyncFuncGroup = Utils.groupBy(asyncFuncList, 'funName');
Reflect.ownKeys(asyncFuncGroup).map((key: unknown) => {
let asyncFunctions: Array<unknown> = asyncFuncGroup[key];
if (asyncFunctions.length > 0) {
let isIntersect = (a: unknown, b: unknown): boolean =>
Math.max(a.startTs + a.dur, b.startTs + b.dur) - Math.min(a.startTs, b.startTs) < a.dur + b.dur;
let depthArray: unknown = [];
asyncFunctions.forEach((it, i) => {
if (it.dur === -1 || it.dur === null || it.dur === undefined) {
it.dur = (TraceRow.range?.endNS || 0) - it.startTs;
it.flag = 'Did not end';
}
let currentDepth = 0;
let index = i;
while (
depthArray[currentDepth] !== undefined &&
isIntersect(depthArray[currentDepth], asyncFunctions[index])
) {
currentDepth++;
}
asyncFunctions[index].depth = currentDepth;
depthArray[currentDepth] = asyncFunctions[index];
this.toAsyncFuncCache(asyncFunctions[index], `${asyncFunctions[i].funName}-${it.pid}`);
});
}
this.lanesConfig(asyncFunctions, it, processRow, `${asyncFunctions[0].funName}`);
});
}
}
hanldCatFunc(
asyncFuncList: Array<unknown>,
flag: boolean
): { asyncRemoveCatArr: Array<unknown>, asyncCat: unknown } {
let asyncCat;
let asyncCatArr = new Array();
let asyncCatMap: Map<string, unknown> = new Map<string, unknown>();
let asyncRemoveCatArr = new Array();
for (let i = 0; i < asyncFuncList.length; i++) {
const el = asyncFuncList[i];
if (el.category !== null) {
if (flag) {
asyncCatArr.push(el);
} else {
if (asyncCatMap.has(`${el.category}:${el.threadName} ${el.tid}`)) {
let item: Array<unknown> = asyncCatMap.get(`${el.category}:${el.threadName} ${el.tid}`);
item.push(el);
} else {
asyncCatMap.set(`${el.category}:${el.threadName} ${el.tid}`, [el]);
}
}
} else {
asyncRemoveCatArr.push(el);
}
}
asyncCat = flag ? Utils.groupBy(asyncCatArr, 'category') : Object.fromEntries(asyncCatMap);
return { asyncRemoveCatArr, asyncCat };
}
hanldAsyncFunc(
it: { pid: number; processName: string | null },
asyncRemoveCatList: Array<unknown>
): { setArrayLenThanOne: unknown, setArrayLenOnlyOne: unknown } {
let asyncFuncGroup = Utils.groupBy(asyncRemoveCatList, 'funName');
let funcArr: unknown[] = [];
let setArrayLenThanOne: unknown = {};
let setArrayLenOnlyOne: unknown = {};
Reflect.ownKeys(asyncFuncGroup).map((key: unknown) => {
let asyncFunctions: Array<unknown> = asyncFuncGroup[key];
if (asyncFunctions.length > 1) {
setArrayLenThanOne[key] = asyncFunctions;
} else if (asyncFunctions.length === 1) {
funcArr.push(...asyncFunctions);
}
});
let asyncFuncTidGroup = Utils.groupBy(funcArr, 'tid');
Reflect.ownKeys(asyncFuncTidGroup).map((key: unknown) => {
let asyncTidFunc: Array<unknown> = asyncFuncTidGroup[key];
setArrayLenOnlyOne[`H:${asyncTidFunc[0].threadName} ${asyncTidFunc[0].tid}`] = asyncTidFunc;
});
return { setArrayLenThanOne, setArrayLenOnlyOne };
}
makeAddAsyncFunction(
asyncFunctions: unknown[],
it: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
key: string,
rowSingleTid?: number
): void {
let maxDepth: number = -1;
let i = 0;
let mapDepth = new Map();
let noEndData = new Array();
let normalData = new Array();
if (asyncFunctions.length) {
while (i < asyncFunctions.length) {
let param = asyncFunctions[i];
if (param.dur !== null) {
let itemEndTime = param.startTs + param.dur;
let flag = false;
for (let [key, val] of mapDepth.entries()) {
if (val.et < param.startTs) {
flag = true;
val.et = itemEndTime;
param.depth = Number(key);
break;
}
}
if (!flag) {
maxDepth++;
mapDepth.set(`${maxDepth}`, { et: itemEndTime });
param.depth = maxDepth;
}
this.toAsyncFuncCache(param, `${key}-${it.pid}`);
normalData.push(param);
} else {
noEndData.push(param);
}
i++;
}
if (noEndData.length) {
noEndData.forEach((it: unknown, i: unknown) => {
if (it.dur === -1 || it.dur === null || it.dur === undefined) {
it.dur = (TraceRow.range?.endNS || 0) - it.startTs;
it.nofinish = true;
it.flag = 'Did not end';
}
let index = i;
maxDepth++;
noEndData[index].depth = maxDepth;
this.toAsyncFuncCache(noEndData[index], `${key}-${it.pid}`);
});
}
this.lanesConfig([...normalData, ...noEndData], it, processRow, key, rowSingleTid);
}
}
lanesConfig(
asyncFunctions: unknown[],
it: { pid: number; processName: string | null },
processRow: TraceRow<ProcessStruct>,
key: string,
rowSingleTid?: number
): void {
const maxHeight = this.calMaxHeight(asyncFunctions);
const namesSet = new Set(asyncFunctions.map((item) => item.funName));
const asyncFuncName = Array.from(namesSet);
let funcRow = TraceRow.skeleton<FuncStruct>();
funcRow.rowId = `${key}-${it.pid}`;
funcRow.asyncFuncName = asyncFuncName;
funcRow.asyncFuncNamePID = it.pid;
funcRow.asyncFuncStartTID = rowSingleTid ? rowSingleTid : undefined;
funcRow.rowType = TraceRow.ROW_TYPE_FUNC;
funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace);
funcRow.rowParentId = `${it.pid}`;
funcRow.rowHidden = !processRow.expansion;
funcRow.style.width = '100%';
funcRow.style.height = `${maxHeight}px`;
funcRow.setAttribute('height', `${maxHeight}`);
funcRow.name = key;
funcRow.setAttribute('children', '');
funcRow.findHoverStruct = (): void => {
FuncStruct.hoverFuncStruct = funcRow.getHoverStruct();
};
funcRow.supplier = (): Promise<unknown> => new Promise((resolve) => resolve(asyncFunctions));
funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
funcRow.selectChangeHandler = this.trace.selectChangeHandler;
funcRow.onThreadHandler = rowThreadHandler<FuncRender>(
'func',
'context',
{
type: `func-${funcRow.rowId}`,
},
funcRow,
this.trace
);
processRow.addChildTraceRow(funcRow);
}
addCallStackDetail(item: FuncStruct): void {
if (this.callStackDetail.has(item.id!)) {
const data: FuncStruct | undefined = this.callStackDetail.get(item.id!);
item.custom_args = data!.custom_args;
item.trace_level = data!.trace_level;
item.trace_tag = data!.trace_tag;
item.category = data!.category;
}
}
addAsyncCatFunction(it: { pid: number; processName: string | null }, processRow: TraceRow<ProcessStruct>): void {
let asyncFuncCatList = this.processAsyncFuncCatMap[it.pid] || [];
let asyncFuncGroup: unknown = Utils.groupBy(asyncFuncCatList, 'threadName');
Reflect.ownKeys(asyncFuncGroup).map((key: unknown) => {
let asyncFunctions: Array<unknown> = asyncFuncGroup[key];
if (asyncFunctions.length > 0) {
let isIntersect = (a: unknown, b: unknown): boolean =>
Math.max(a.startTs + a.dur, b.startTs + b.dur) - Math.min(a.startTs, b.startTs) < a.dur + b.dur;
let depthArray: unknown = [];
asyncFunctions.forEach((it, i) => {
if (it.dur === -1 || it.dur === null || it.dur === undefined) {
it.dur = (TraceRow.range?.endNS || 0) - it.startTs;
it.flag = 'Did not end';
it.nofinish = true;
}
let currentDepth = 0;
let index = i;
while (depthArray[currentDepth] !== undefined && isIntersect(depthArray[currentDepth], asyncFunctions[index])) {
currentDepth++;
}
asyncFunctions[index].depth = currentDepth;
depthArray[currentDepth] = asyncFunctions[index];
this.toAsyncFuncCache(asyncFunctions[index], `${asyncFunctions[0].threadName}`);
});
const maxHeight = this.calMaxHeight(asyncFunctions);
let funcRow = TraceRow.skeleton<FuncStruct>();
funcRow.rowId = `${asyncFunctions[0].threadName}`;
funcRow.asyncFuncThreadName = asyncFunctions[0].threadName;
funcRow.asyncFuncNamePID = it.pid;
funcRow.rowType = TraceRow.ROW_TYPE_FUNC;
funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace);
funcRow.rowParentId = `${it.pid}`;
funcRow.rowHidden = !processRow.expansion;
funcRow.style.width = '100%';
funcRow.style.height = `${maxHeight}px`;
funcRow.setAttribute('height', `${maxHeight}`);
funcRow.name = `${asyncFunctions[0].threadName}`;
funcRow.setAttribute('children', '');
funcRow.findHoverStruct = (): void => {
FuncStruct.hoverFuncStruct = funcRow.getHoverStruct();
};
funcRow.supplier = (): Promise<unknown> => new Promise((resolve) => resolve(asyncFunctions));
funcRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
funcRow.selectChangeHandler = this.trace.selectChangeHandler;
funcRow.onThreadHandler = rowThreadHandler<FuncRender>('func', 'context', {
type: `func-${asyncFunctions[0].threadName}-${it.pid}`,
}, funcRow, this.trace);
processRow.addChildTraceRow(funcRow);
}
});
}
}