diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index d8b22b6d4454d88c3f3314e029a2ababa9019e77..644b4037649361ab06f83f6083e59afbee52810f 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -69,6 +69,7 @@ export class SelectionParam { ((arg: unknown) => Promise> | undefined) | undefined >(); dmaFenceNameData: Array = [];//新增框选dma_fence数据 + hangMapData: Map Promise> | undefined) | undefined> = new Map(); irqCallIds: Array = []; softIrqCallIds: Array = []; funTids: Array = []; @@ -1136,6 +1137,20 @@ export class SelectionParam { } } + // @ts-ignore + pushHang(it: TraceRow, sp: SpSystemTrace): void { + if (it.rowType === TraceRow.ROW_TYPE_HANG_GROUP) { + it.childrenList.forEach((it) => { + it.rangeSelect = true; + it.checkType = '2'; + this.hangMapData.set(it.rowId || '', it.getCacheData); + }); + } + if (it.rowType === TraceRow.ROW_TYPE_HANG || it.rowType === TraceRow.ROW_TYPE_HANG_INNER) { + this.hangMapData.set(it.rowId || '', it.getCacheData); + } + } + // @ts-ignore pushGpuMemoryVmTracker(it: TraceRow, sp: SpSystemTrace): void { if (it.rowType === TraceRow.ROW_TYPE_GPU_MEMORY_VMTRACKER) { @@ -1246,6 +1261,7 @@ export class SelectionParam { this.pushVmTrackerShm(it, sp); this.pushClock(it, sp); this.pushDmaFence(it, sp); + this.pushHang(it, sp); this.pushGpuMemoryVmTracker(it, sp); this.pushDmaVmTracker(it, sp); this.pushPugreable(it, sp); diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index b1768ec88c3d2679a0d787822babad3148ce27d5..4f971dd0d8f8bf112d2613d8f4097d91bd7535d3 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -28,7 +28,8 @@ const CAT_SORT = { const CONFIG_STATE:unknown = { 'VSync': ['vsyncValue', 'VsyncGeneratior'], - 'Start&Finish Trace Category': ['catValue', 'Business first'] + 'Start&Finish Trace Category': ['catValue', 'Business first'], + 'Hangs': ['hangsSelect', 'Instant'], }; @element('sp-flags') @@ -82,9 +83,10 @@ export class SpFlags extends BaseElement { configDiv.appendChild(description); } //监听flag-select的状态选择 - private flagSelectListener(configSelect: unknown): void { + private flagSelectListener(configSelect: HTMLSelectElement): void { // @ts-ignore let title = configSelect.getAttribute('title'); + //@ts-ignore let listSelect = this.shadowRoot?.querySelector(`#${CONFIG_STATE[title]?.[0]}`); // @ts-ignore @@ -169,6 +171,11 @@ export class SpFlags extends BaseElement { configDiv.appendChild(configFooterDiv); } + if (config.title === 'Hangs') { + let configFooterDiv = this.createHangsOption(); + configDiv.appendChild(configFooterDiv); + } + this.bodyEl!.appendChild(configDiv); }); } @@ -213,6 +220,51 @@ export class SpFlags extends BaseElement { configFooterDiv.appendChild(vsyncTypeEl); return configFooterDiv; } + + /// Flags新增Hangs下拉框 + private createHangsOption(): HTMLDivElement { + let configFooterDiv = document.createElement('div'); + configFooterDiv.className = 'config_footer'; + let hangsLableEl = document.createElement('lable'); + hangsLableEl.className = 'hangs_lable'; + let hangsTypeEl = document.createElement('select'); + hangsTypeEl.setAttribute('id', 'hangsSelect'); + hangsTypeEl.className = 'flag-select'; + + let hangOptions: Array = []; + for (const settings of [ + { value: '33', content: "Instant" }, + { value: '100', content: 'Circumstantial' }, + { value: '250', content: 'Micro' }, + { value: '500', content: 'Severe' } + ]) { + let hangOption = document.createElement('option'); + hangOption.value = settings.value + '000000'; + hangOption.textContent = settings.content; + hangOption.selected = false; + hangOptions.push(hangOption); + hangsTypeEl.appendChild(hangOption); + } + + FlagsConfig.updateFlagsConfig('hangValue', hangOptions[0].value); + hangOptions[0].selected = true; + hangsTypeEl.addEventListener('change', function () { + let selectValue = this.selectedOptions[0].value; + FlagsConfig.updateFlagsConfig('hangValue', selectValue); + }); + + let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); + let flagsItemJson = JSON.parse(flagsItem!); + let hangs = flagsItemJson['Hangs']; + if (hangs === 'Enabled') { + hangsTypeEl.removeAttribute('disabled'); + } else { + hangsTypeEl.setAttribute('disabled', 'disabled'); + } + configFooterDiv.appendChild(hangsLableEl); + configFooterDiv.appendChild(hangsTypeEl); + return configFooterDiv; + } } export type Params = { @@ -264,6 +316,11 @@ export class FlagsConfig { describeContent: 'VSync Signal drawing', addInfo: { vsyncValue: VSYNC_VAL.VsyncGeneratior }, }, + { + title: 'Hangs', + switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], + describeContent: '', + }, { title: 'LTPO', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index e633f754b21990abb465489f026181b1eb7d67f2..a187e5a45e82538093e4d67748a0cdb8fc1bf7d9 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -52,6 +52,7 @@ import { PerfToolsStructOnClick, PerfToolStruct } from '../database/ui-worker/Pr import { Utils } from './trace/base/Utils'; import { BaseStruct } from '../bean/BaseStruct'; import { GpuCounterStruct, gpuCounterStructOnClick } from '../database/ui-worker/ProcedureWorkerGpuCounter'; +import { HangStructOnClick } from '../database/ui-worker/ProcedureWorkerHang'; function timeoutJudge(sp: SpSystemTrace): number { let timeoutJudge = window.setTimeout((): void => { @@ -368,6 +369,7 @@ function allStructOnClick(clickRowType: string, sp: SpSystemTrace, row?: TraceRo .then(() => CpuStateStructOnClick(clickRowType, sp, entry as CpuStateStruct)) .then(() => CpuFreqLimitsStructOnClick(clickRowType, sp, entry as CpuFreqLimitsStruct)) .then(() => ClockStructOnClick(clickRowType, sp, entry as ClockStruct)) + .then(() => HangStructOnClick(clickRowType, sp)) .then(() => DmaFenceStructOnClick(clickRowType, sp, entry as DmaFenceStruct)) .then(() => SnapshotStructOnClick(clickRowType, sp, row as TraceRow, entry as SnapshotStruct)) .then(() => IrqStructOnClick(clickRowType, sp, entry as IrqStruct)) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 6a73dbc21acfed425426551e3cb6331d91e56a71..cabe4017cba2d6c3012c4df065d1c9972241a20a 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -1120,6 +1120,7 @@ export async function spSystemTraceInit( } if (sp.loadTraceCompleted) { sp.traceSheetEL?.displaySystemLogsData(); + sp.traceSheetEL?.displayHangsData(); sp.traceSheetEL?.displaySystemStatesData(); } sp.intersectionObserver?.observe(it); diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 0970fb159303332f92b1924587626bfb43ed6d6a..dfac1a593d20a200e1c5231d58d389200f10f069 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -132,6 +132,7 @@ import { PerfToolStruct } from '../database/ui-worker/ProcedureWorkerPerfTool'; import { BaseStruct } from '../bean/BaseStruct'; import { GpuCounterStruct } from '../database/ui-worker/ProcedureWorkerGpuCounter'; import { SpProcessChart } from './chart/SpProcessChart'; +import { HangStruct } from '../database/ui-worker/ProcedureWorkerHang'; function dpr(): number { return window.devicePixelRatio || 1; @@ -1222,6 +1223,7 @@ export class SpSystemTrace extends BaseElement { CpuStateStruct.selectStateStruct = undefined; CpuFreqLimitsStruct.selectCpuFreqLimitsStruct = undefined; ClockStruct.selectClockStruct = undefined; + HangStruct.selectHangStruct = undefined; IrqStruct.selectIrqStruct = undefined; JankStruct.selectJankStruct = undefined; HeapStruct.selectHeapStruct = undefined; diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index b6d0b22303576f972d80a8bb8801600831b6533c..d93a831045be0a8b0bb526f63ad915ec2a6118d5 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -43,6 +43,7 @@ import { SpHiSysEventChart } from './SpHiSysEventChart'; import { SpAllAppStartupsChart } from './SpAllAppStartups'; import { procedurePool } from '../../database/Procedure'; import { SpSegmentationChart } from './SpSegmentationChart'; +import { SpHangChart } from './SpHangChart'; import { SpPerfOutputDataChart } from './SpPerfOutputDataChart'; import { queryAppStartupProcessIds, @@ -86,6 +87,7 @@ export class SpChartManager { private logChart: SpLogChart; private spHiSysEvent: SpHiSysEventChart; private spSegmentationChart: SpSegmentationChart; + private hangChart: SpHangChart; private spBpftraceChart: SpBpftraceChart; private spPerfOutputDataChart: SpPerfOutputDataChart; private spGpuCounterChart: SpGpuCounterChart; @@ -114,6 +116,7 @@ export class SpChartManager { this.spAllAppStartupsChart = new SpAllAppStartupsChart(trace); this.SpLtpoChart = new SpLtpoChart(trace); this.spSegmentationChart = new SpSegmentationChart(trace); + this.hangChart = new SpHangChart(trace); this.spBpftraceChart = new SpBpftraceChart(trace); this.spPerfOutputDataChart = new SpPerfOutputDataChart(trace); this.spGpuCounterChart = new SpGpuCounterChart(trace); @@ -186,6 +189,10 @@ export class SpChartManager { await this.spHiSysEvent.init(); let idAndNameArr = await queryDmaFenceIdAndCat(); this.handleDmaFenceName(idAndNameArr as { id: number; cat: string; seqno: number; driver: string; context: string }[]); + if (FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { + progress('Hang init', 80); + await this.hangChart.init(); + } progress('Clock init', 82); await this.clockChart.init(); progress('Irq init', 84); diff --git a/ide/src/trace/component/chart/SpHangChart.ts b/ide/src/trace/component/chart/SpHangChart.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab58a519f0f1e24846fcf6e01ea47c00665b6e47 --- /dev/null +++ b/ide/src/trace/component/chart/SpHangChart.ts @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2024 Shenzhen Kaihong Digital Industry Development 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 { TraceRow } from '../trace/base/TraceRow'; +import { renders } from '../../database/ui-worker/ProcedureWorker'; +import { info } from '../../../log/Log'; +import { HangStruct } from '../../database/ui-worker/ProcedureWorkerHang'; +import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; +import { queryHangData } from '../../database/sql/Hang.sql'; +import { hangDataSender } from '../../database/data-trafic/HangDataSender'; +import { BaseStruct } from '../../bean/BaseStruct'; +import { Utils } from '../trace/base/Utils'; + +export type HangType = "Instant" | "Circumstantial" | "Micro" | "Severe" | ""; + +/// Hangs聚合泳道 +export class SpHangChart { + private trace: SpSystemTrace; + static funcNameMap: Map = new Map(); + + constructor(trace: SpSystemTrace) { + this.trace = trace; + } + + static calculateHangType(dur: number): HangType { + const durMS = dur / 1000000; + if (durMS < 33) { + return ""; + } + else if (durMS < 100) { + return "Instant"; + } + else if (durMS < 250) { + return "Circumstantial"; + } + else if (durMS < 500) { + return "Micro"; + } + else { + return "Severe"; + } + } + + async init(): Promise { + SpHangChart.funcNameMap = Utils.getInstance().getCallStatckMap(); + let folder = await this.initFolder(); + await this.initData(folder); + } + + private hangSupplierFrame( + traceRow: TraceRow, + it: { + id: number; + name: string; + num: number; + } + ): void { + traceRow.supplierFrame = (): Promise => { + let promiseData = hangDataSender(it.id, traceRow) + if (promiseData === null) { + return new Promise>((resolve) => resolve([])); + } else { + return promiseData.then((resultHang: Array) => + resultHang.map(hangItem => ({ + ...hangItem, + pname: it.name, + type: SpHangChart.calculateHangType(hangItem.dur!), + content: SpHangChart.funcNameMap.get(hangItem.id!) + })) + ); + } + }; + } + + private hangThreadHandler( + traceRow: TraceRow, + it: { + id: number; + name: string; + num: number; + }, + hangId: number + ): void { + traceRow.onThreadHandler = (useCache): void => { + let context: CanvasRenderingContext2D; + if (traceRow.currentContext) { + context = traceRow.currentContext; + } else { + context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; + } + traceRow.canvasSave(context); + renders.hang.renderMainThread( + { + context: context, + useCache: useCache, + type: it.name, + index: hangId, + }, + traceRow + ); + traceRow.canvasRestore(context, this.trace); + }; + } + + /// 初始化聚合泳道信息 + async initData(folder: TraceRow): Promise { + let hangStartTime = new Date().getTime(); + let hangList = await queryHangData(); + if (hangList.length === 0) { + return; + } + this.trace.rowsEL?.appendChild(folder); + for (let i = 0; i < hangList.length; i++) { + const it: { + id: number, + name: string, + num: number, + } = hangList[i]; + let traceRow = TraceRow.skeleton(); + traceRow.rowId = `${it.name ?? 'Process'} ${it.id}`; + traceRow.rowType = TraceRow.ROW_TYPE_HANG; + traceRow.rowParentId = folder.rowId; + traceRow.style.height = '40px'; + traceRow.name = `${it.name ?? 'Process'} ${it.id}`; + traceRow.rowHidden = !folder.expansion; + traceRow.setAttribute('children', ''); + traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; + traceRow.selectChangeHandler = this.trace.selectChangeHandler; + this.hangSupplierFrame(traceRow, it); + traceRow.getCacheData = (args: unknown): Promise> => hangDataSender(it.id, traceRow, args); + traceRow.focusHandler = (ev): void => { + let hangStruct = HangStruct.hoverHangStruct; + this.trace?.displayTip( + traceRow, hangStruct, + `${hangStruct?.type} ${hangStruct?.dur}` + ); + }; + traceRow.findHoverStruct = (): void => { + HangStruct.hoverHangStruct = traceRow.getHoverStruct(); + }; + this.hangThreadHandler(traceRow, it, i); + folder.addChildTraceRow(traceRow); + } + let durTime = new Date().getTime() - hangStartTime; + info('The time to load the HangData is: ', durTime); + } + + async initFolder(): Promise> { + let hangFolder = TraceRow.skeleton(); + hangFolder.rowId = 'Hangs'; + hangFolder.index = 0; + hangFolder.rowType = TraceRow.ROW_TYPE_HANG_GROUP; + hangFolder.rowParentId = ''; + hangFolder.style.height = '40px'; + hangFolder.folder = true; + hangFolder.name = 'Hangs'; + hangFolder.favoriteChangeHandler = this.trace.favoriteChangeHandler; + hangFolder.selectChangeHandler = this.trace.selectChangeHandler; // @ts-ignore + hangFolder.supplier = (): Promise => new Promise>((resolve) => resolve([])); + hangFolder.onThreadHandler = (useCache): void => { + hangFolder.canvasSave(this.trace.canvasPanelCtx!); + if (hangFolder.expansion) { + // @ts-ignore + this.trace.canvasPanelCtx?.clearRect(0, 0, hangFolder.frame.width, hangFolder.frame.height); + } else { + (renders.empty as EmptyRender).renderMainThread( + { + context: this.trace.canvasPanelCtx, + useCache: useCache, + type: '', + }, + hangFolder + ); + } + hangFolder.canvasRestore(this.trace.canvasPanelCtx!, this.trace); + }; + return hangFolder; + } +} diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index 054d97f5520a062a7d1e177d751dfc222b71809c..fcfbcd7f10f0bf117c6ac1218f65e1b893cc5572 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -54,6 +54,10 @@ import { import { queryAllJankProcess } from '../../database/sql/Janks.sql'; import { BaseStruct } from '../../bean/BaseStruct'; import { promises } from 'dns'; +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'; const FOLD_HEIGHT = 24; export class SpProcessChart { @@ -93,6 +97,7 @@ export class SpProcessChart { static threadStateList: Map = new Map(); static processRowSortMap: Map = new Map(); + private hangProcessSet: Set = new Set(); constructor(trace: SpSystemTrace) { this.trace = trace; } @@ -419,8 +424,13 @@ export class SpProcessChart { } renderServiceProcess = await queryRsProcess(); } + // @ts-ignore info('ProcessList Data size is: ', processList!.length); + + + this.hangProcessSet = new Set((await queryHangData()).map(item => item.id)); + // @ts-ignore await this.initProcessRow(processList, allTaskPoolPid, allJankProcess, renderServiceProcess, traceId); let durTime = new Date().getTime() - time; @@ -510,12 +520,14 @@ export class SpProcessChart { traceId?: string ): Promise { for (let i = 0; i < pArr.length; i++) { - const it = pArr[i]; + const it = pArr[i] as { + pid: number; + processName: string | null + }; if ( - //@ts-ignore - (this.processThreadDataCountMap.get(it.pid) || 0) === 0 && //@ts-ignore - (this.processThreadCountMap.get(it.pid) || 0) === 0 && //@ts-ignore - (this.processFuncDataCountMap.get(it.pid) || 0) === 0 && //@ts-ignore + (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; @@ -549,17 +561,47 @@ export class SpProcessChart { } } this.renderRow = null; //@ts-ignore + if (this.loadAppStartup) { + if (this.startupProcessArr.find((sp) => sp.pid === it.pid)) { + startupRow = this.addStartUpRow(processRow); + } + let maxSoDepth = this.processSoMaxDepth.find((md) => md.pid === it.pid); + if (maxSoDepth) { + soRow = this.addSoInitRow(processRow, maxSoDepth.maxDepth); + } + } + + let hangsRow: TraceRow | null = null; + this.renderRow = null; if (it.processName === 'render_service') { //@ts-ignore this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId); //@ts-ignore this.addProcessMemInfo(it, processRow); //@ts-ignore this.addAsyncFunction(it, processRow);//@ts-ignore + this.addThreadList(it, processRow, expectedRow, actualRow, hangsRow, soRow, startupRow); + this.addProcessMemInfo(it, processRow); + if (jankArr.indexOf(it.pid!) > -1) { + expectedRow = this.addExpectedRow(it, processRow, rsProcess); + actualRow = this.addActualRow(it, processRow, rsProcess); + } + hangsRow = this.addHangRow(it, processRow); + this.addProcessRowListener(processRow, actualRow); + this.addAsyncFunction(it, processRow); this.addAsyncCatFunction(it, processRow); } else { //@ts-ignore this.addAsyncFunction(it, processRow); //@ts-ignore this.addProcessMemInfo(it, processRow); //@ts-ignore this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId);//@ts-ignore + if (jankArr.indexOf(it.pid!) > -1) { + expectedRow = this.addExpectedRow(it, processRow, rsProcess); + actualRow = this.addActualRow(it, processRow, rsProcess); + } + hangsRow = this.addHangRow(it, processRow); + this.addProcessRowListener(processRow, actualRow); + this.addAsyncFunction(it, processRow); + this.addProcessMemInfo(it, processRow); + this.addThreadList(it, processRow, expectedRow, actualRow, hangsRow, soRow, startupRow); this.addAsyncCatFunction(it, processRow); } this.addProcessRowListener(processRow, actualRow); @@ -856,12 +898,70 @@ export class SpProcessChart { return actualRow; } + //@ts-ignore + addHangRow( + data: { + pid: number; + processName: string | null; + }, + row: TraceRow + ): TraceRow | null { + if (!this.hangProcessSet.has(data.pid) || !FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { + return null; + } + let hangsRow = TraceRow.skeleton(); + hangsRow.rowType = TraceRow.ROW_TYPE_HANG_INNER; + hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}` + hangsRow.rowParentId = `${data.pid}`; + hangsRow.rowHidden = !row.expansion; + hangsRow.style.width = '100%'; + hangsRow.name = 'Hangs'; + hangsRow.addTemplateTypes('FrameTimeline'); + hangsRow.setAttribute('children', ''); + hangsRow.supplierFrame = async (): Promise => { + let promiseData = hangDataSender(data.pid, hangsRow); + if (promiseData === null) { + return new Promise>((resolve) => resolve([])); + } else { + return promiseData.then((resultHang: Array) => + 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( + 'hang', + 'context', + { + type: 'hangs_frame_timeline_slice', + }, + hangsRow, + this.trace + ); + + if (this.renderRow) { + row.addChildTraceRowBefore(hangsRow, this.renderRow); + } else { + row.addChildTraceRow(hangsRow); + } + return hangsRow; + } + jankSenderCallback( res: JankStruct[], type: string, process: unknown, row: TraceRow, - renderServiceProcess: Array + renderServiceProcess: Array, ): void { let maxDepth: number = 1; let unitHeight: number = 20; @@ -997,6 +1097,7 @@ export class SpProcessChart { pRow: TraceRow, expectedRow: TraceRow | null, actualRow: TraceRow | null, + hangsRow: TraceRow | null, soRow: TraceRow | undefined, startupRow: TraceRow | undefined, traceId?: string @@ -1040,7 +1141,7 @@ export class SpProcessChart { tRow, this.trace ); - this.insertRowToDoc(it, j, thread, pRow, tRow, threads, tRowArr, actualRow, expectedRow, startupRow, soRow); + this.insertRowToDoc(it, j, thread, pRow, tRow, threads, tRowArr, actualRow, expectedRow, hangsRow, startupRow, soRow); this.addFuncStackRow(it, thread, j, threads, tRowArr, tRow, pRow); if ((thread.switchCount || 0) === 0) { tRow.rowDiscard = true; @@ -1078,6 +1179,7 @@ export class SpProcessChart { actualRow: TraceRow | null, //@ts-ignore expectedRow: TraceRow | null, + hangsRow: TraceRow | null, startupRow: TraceRow | null | undefined, soRow: TraceRow | null | undefined ): void { @@ -1096,6 +1198,8 @@ export class SpProcessChart { processRow.addChildTraceRowAfter(threadRow, actualRow); } else if (expectedRow !== null) { processRow.addChildTraceRowAfter(threadRow, expectedRow); + } else if (hangsRow !== null) { + processRow.addChildTraceRowAfter(threadRow, hangsRow); } else if (soRow) { processRow.addChildTraceRowAfter(threadRow, soRow); } else if (startupRow) { diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index 12964507095811f2ee29bc3ce066d2fb70e1914c..b66f7a475c9e18bc6ed7861d17160d7f2079f103 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -14,6 +14,7 @@ */ import { CpuStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerCPU'; +import { HangType } from '../../chart/SpHangChart'; export class ColorUtils { public static GREY_COLOR: string = '#f0f0f0'; @@ -121,6 +122,16 @@ export class ColorUtils { return logColor; } + public static getHangColor(hangType: HangType): string { + return ({ + "Instant": "#559CFF", + "Circumstantial": "#FFE44D", + "Micro": "#FEB354", + "Severe": "#FC7470", + "": "#000000", + })[hangType]; + } + public static getHisysEventColor(level: string | number): string { let eventColor: string = '#00000'; switch (level) { diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index db2c3a27b4a57a42c0aa1b538d6d0058e9a2c285..2c8a68e03e219dfbb4d3c6bf163a4647e610992e 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -114,8 +114,11 @@ export class TraceRow extends HTMLElement { static ROW_TYPE_GPU_MEMORY_VMTRACKER = 'gpu-memory-vmTracker'; static ROW_TYPE_GPU_RESOURCE_VMTRACKER = 'sys-memory-gpu-resource'; static ROW_TYPE_VMTRACKER_SHM = 'VmTracker-shm'; + static ROW_TYPE_HANG_GROUP = 'hang-group'; static ROW_TYPE_CLOCK_GROUP = 'clock-group'; static ROW_TYPE_COLLECT_GROUP = 'collect-group'; + static ROW_TYPE_HANG = 'hang'; + static ROW_TYPE_HANG_INNER = 'hang-inner'; static ROW_TYPE_CLOCK = 'clock'; static ROW_TYPE_IRQ_GROUP = 'irq-group'; static ROW_TYPE_IRQ = 'irq'; diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index fd44ff5c58ee1c7b88717422a463a1b3f76905e0..00903414908ece2ac76ec884ca55515c6f64cccc 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -31,6 +31,7 @@ import { type ThreadStruct } from '../../../database/ui-worker/ProcedureWorkerTh import { type FuncStruct } from '../../../database/ui-worker/ProcedureWorkerFunc'; import { ProcessMemStruct } from '../../../database/ui-worker/ProcedureWorkerMem'; import { CpuStateStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerCpuState'; +import { type HangStruct } from '../../../database/ui-worker/ProcedureWorkerHang'; import { type ClockStruct } from '../../../database/ui-worker/ProcedureWorkerClock'; import { type DmaFenceStruct } from '../../../database/ui-worker/ProcedureWorkerDmaFence'; import { type IrqStruct } from '../../../database/ui-worker/ProcedureWorkerIrq'; @@ -363,7 +364,7 @@ export class TraceSheet extends BaseElement { let element = tabConfig[id]; let pane = this.shadowRoot!.querySelector(`#${id as string}`); if (element.require) { - pane!.hidden = !element.require(this.selection); + pane!.hidden = !element.require(this.selection!); } else { pane!.hidden = true; } @@ -683,6 +684,8 @@ export class TraceSheet extends BaseElement { ); displayMemData = (data: ProcessMemStruct): void => this.displayTab('current-selection').setMemData(data); + displayHangData = (data: HangStruct, sp: SpSystemTrace): Promise => + this.displayTab('current-selection').setHangData(data, sp); displayClockData = (data: ClockStruct): Promise => this.displayTab('current-selection').setClockData(data); displayDmaFenceData = (data: DmaFenceStruct, rowData: unknown): void =>//展示tab页内容 @@ -910,6 +913,17 @@ export class TraceSheet extends BaseElement { } } }; + + displayHangsData = (): void => { + let tblHangPanel = this.shadowRoot?.querySelector("lit-tabpane[id='box-hang']"); + if (tblHangPanel) { + let tblHang = tblHangPanel.querySelector('tab-hang'); + if (tblHang) { + tblHang.initTabSheetEl(this); + } + } + }; + displaySampleData = (data: SampleStruct, reqProperty: unknown): void => { this.displayTab('box-sample-instruction').setSampleInstructionData(data, reqProperty); this.optionsDiv!.style.display = 'flex'; diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index 851d71a233fee6aae7967bd16a7e7faf5dd35866..a3f29daf94582770dbbce5419a2ae94e4d8c8a8d 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -64,6 +64,8 @@ import { TabPaneFreqLimit } from '../sheet/freq/TabPaneFreqLimit'; import { TabPaneCpuFreqLimits } from '../sheet/freq/TabPaneCpuFreqLimits'; import { TabpaneNMCalltree } from '../sheet/native-memory/TabPaneNMCallTree'; import { TabPaneClockCounter } from '../sheet/clock/TabPaneClockCounter'; +import { TabPaneHang } from '../sheet/Hang/TabPaneHang'; +import { TabPaneHangSummary } from '../sheet/Hang/TabPaneHangSummary'; import { TabPaneIrqCounter } from '../sheet/irq/TabPaneIrqCounter'; import { TabPaneFrames } from '../sheet/jank/TabPaneFrames'; import { TabPanePerfAnalysis } from '../sheet/hiperf/TabPanePerfAnalysis'; @@ -137,7 +139,13 @@ import { TabPaneUserPlugin } from '../sheet/userPlugin/TabPaneUserPlugin'; import { TabPaneDmaFence } from '../sheet/dma-fence/TabPaneDmaFenceSelect'; import { TabPaneSliceChild } from '../sheet/process/TabPaneSliceChild'; -export let tabConfig: unknown = { +export let tabConfig: { + [key: string]: { + title: string + type: any + require?: (param: SelectionParam) => boolean + } +} = { 'current-selection': { title: 'Current Selection', type: TabPaneCurrentSelection, @@ -244,6 +252,16 @@ export let tabConfig: unknown = { type: TabPaneClockCounter, require: (param: SelectionParam) => param.clockMapData.size > 0, }, + 'box-hang': { + title: 'Hangs', + type: TabPaneHang, + require: (param: SelectionParam) => param.hangMapData.size > 0, + }, + 'box-hang-summary': { + title: 'Hang Summary', + type: TabPaneHangSummary, + require: (param: SelectionParam) => param.hangMapData.size > 0, + }, 'box-irq-counters': { title: 'Irq Counters', type: TabPaneIrqCounter, diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 347f96f40e6dfecf6928c3038641e61e0eee8295..8bfb89ce97c630d6cd326b75d23686fc38aa576c 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -63,6 +63,8 @@ import { PerfToolStruct } from '../../../database/ui-worker/ProcedureWorkerPerfT import { TraceMode } from '../../../SpApplicationPublicFunc'; import { threadPool, threadPool2 } from '../../../database/SqlLite'; import { threadNearData } from '../../../database/data-trafic/SliceSender'; +import { HangStruct } from '../../../database/ui-worker/ProcedureWorkerHang'; +import { BaseStruct } from '../../../bean/BaseStruct'; const INPUT_WORD = 'This is the interval from when the task became eligible to run \n(e.g.because of notifying a wait queue it was a suspended on) to\n when it started running.'; @@ -801,6 +803,88 @@ export class TabPaneCurrentSelection extends BaseElement { this.currentSelectionTbl!.dataSource = list; } + async setHangData(data: HangStruct, sp: SpSystemTrace): Promise { + await this.setRealTime(); + this.setTableHeight('auto'); + this.tabCurrentSelectionInit('Hang Details'); + let list: unknown[] = []; + list.push({ + name: 'StartTime(Relative)', + value: getTimeString(data.startNS || 0), + }); + this.createStartTimeNode(list, data.startNS || 0, CLOCK_TRANSF_BTN_ID, CLOCK_STARTTIME_ABSALUTED_ID); + list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); + list.push({ + name: 'Hang type', + value: `
+
${data.type}
+ +
` + }); + data.content!.split(',').map((item, index) => ({ + name: [ + "Sender tid", + "Send time", + "Expect handle time", + "Task name/ID", + "Sender" + ][index], + value: item, + })).forEach((item, index) => { + if (index === 0) { + item.value = item.value.split(':').at(-1)! + } + list.push(item); + }) + + this.currentSelectionTbl!.dataSource = list; + // @ts-ignore + let startTimeAbsolute = (data.startNS || 0) + window.recordStartNS; + this.addClickToTransfBtn(startTimeAbsolute, CLOCK_TRANSF_BTN_ID, CLOCK_STARTTIME_ABSALUTED_ID); + + let scrollIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#scroll-to-process'); + scrollIcon?.addEventListener('click', this.hangScrollHandler(data, sp)); + } + + private hangScrollHandler(data: HangStruct, sp: SpSystemTrace) { + return () => { + const rowId = `${data.pname ?? 'Process'} ${data.pid}`; + const rowParentId = `${data.pid}`; + const rowType = TraceRow.ROW_TYPE_HANG_INNER; + + let row = sp.rowsEL?.querySelector>(`trace-row[row-id='${rowParentId}'][folder]`); + if (row) { + row.expansion = true; + + const innerHangRow = row.childrenList.find((childRow) => childRow.rowType === TraceRow.ROW_TYPE_HANG_INNER) as TraceRow; + sp.currentRow = innerHangRow; + async function completeEntry(t: TabPaneCurrentSelection) { + if (!innerHangRow.dataListCache || innerHangRow.dataListCache.length == 0) { + await innerHangRow.supplierFrame!(); + } + + const findEntry = innerHangRow?.dataListCache.find((hangStruct) => { + return hangStruct.startNS === HangStruct.selectHangStruct?.startNS; + }); + + if (findEntry) { + HangStruct.selectHangStruct = findEntry; + t.setHangData(findEntry, sp); + } + sp.scrollToProcess(rowId, rowParentId, rowType); + sp.refreshCanvas(false); + } + if (innerHangRow.isComplete) { + completeEntry(this); + } + else { + sp.scrollToProcess(rowId, rowParentId, rowType); + innerHangRow.onComplete = () => completeEntry(this); + } + } + }; + } + setPerfToolsData(data: PerfToolStruct): void { this.setTableHeight('auto'); //Perf Tools info diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts new file mode 100644 index 0000000000000000000000000000000000000000..36c93092ebabbf48fc3cd49fb76d056eb71a3b5e --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts @@ -0,0 +1,122 @@ +/* + * 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. + */ + +export const TabPaneHangHtml = ` + +
+ +
+
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + +`; diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts new file mode 100644 index 0000000000000000000000000000000000000000..247ad3efda2aded877ab7f1024df5dd060a7a8b9 --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts @@ -0,0 +1,296 @@ +/* + * 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 { BaseElement, element } from '../../../../../base-ui/BaseElement'; +import { SelectionParam } from '../../../../bean/BoxSelection'; +import { TraceRow } from '../../base/TraceRow'; +import { TraceSheet } from '../../base/TraceSheet'; +import { Flag } from '../../timer-shaft/Flag'; +import { SpSystemTrace } from '../../../SpSystemTrace'; +import { ns2Timestamp, ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon'; +import { ColorUtils } from '../../base/ColorUtils'; +import { LitPageTable } from '../../../../../base-ui/table/LitPageTable'; +import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar'; +import { TabPaneHangHtml } from './TabPaneHang.html'; +import { HangStruct } from '../../../../database/ui-worker/ProcedureWorkerHang'; +import { queryAllHangs } from '../../../../database/sql/Hang.sql'; +import { HangType, SpHangChart } from '../../../chart/SpHangChart'; +import { getTimeString } from '../TabPaneCurrentSelection'; + +/// Hangs 框选Tab页1 +@element('tab-hang') +export class TabPaneHang extends BaseElement { + // Elements + private spSystemTrace: SpSystemTrace | undefined | null; + private traceSheetEl: TraceSheet | undefined | null; + private levelFilterInput: HTMLSelectElement | undefined | null; + private searchFilterInput: HTMLInputElement | undefined | null; + private processFilter: HTMLInputElement | undefined | null; + private hangTableTitle: HTMLDivElement | undefined | null; + private hangTbl: LitPageTable | undefined | null; + + private tableTimeHandle: (() => void) | undefined; + private tableTitleTimeHandle: (() => void) | undefined; + private systemHangSource: HangStructInPane[] = []; + private filterData: HangStructInPane[] = []; + + private optionLevel: string[] = ['Instant', 'Circumstantial', 'Micro', 'Severe']; + private allowTag: Set = new Set(); + private progressEL: LitProgressBar | null | undefined; + private timeOutId: number | undefined; + + /// 框选时段范围时触发 + set data(selectionParam: SelectionParam) { + if (this.hangTbl) { + this.hangTbl.recycleDataSource = []; + this.filterData = []; + } + window.clearTimeout(this.timeOutId); + queryAllHangs().then((ret) => { + const filter = new Set([...selectionParam.hangMapData.keys()].map(key => key.split(' ').at(-1))) + ret = ret.filter(struct => ( + filter.has(`${struct.pid ?? 0}`) + && ((struct.startNS ?? 0) <= selectionParam.rightNs) + && (selectionParam.leftNs <= ((struct.startNS ?? 0) + (struct.dur ?? 0))) + )); + + if (ret.length == 0) { + this.progressEL!.loading = false; + } + this.systemHangSource = ret.map(HangStructInPane.new); + this.refreshTable(); + }); + } + + init(): void { + this.levelFilterInput = this.shadowRoot?.querySelector('#level-filter'); + this.hangTableTitle = this.shadowRoot?.querySelector('#hang-title'); + this.searchFilterInput = this.shadowRoot?.querySelector('#search-filter'); + this.processFilter = this.shadowRoot?.querySelector('#process-filter'); + this.spSystemTrace = document.querySelector('body > sp-application')?.shadowRoot?.querySelector('#sp-system-trace'); + this.tableTimeHandle = this.delayedRefresh(this.refreshTable); + this.tableTitleTimeHandle = this.delayedRefresh(this.refreshHangsTitle); + this.hangTbl = this.shadowRoot?.querySelector('#tb-hang'); + this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar; + this.hangTbl!.getItemTextColor = (data): string => { + const hangData = data as HangStructInPane; + return ColorUtils.getHangColor(hangData.type as HangType); + }; + this.hangTbl!.itemTextHandleMap.set('startNS', (startTs) => { + // @ts-ignore + return ns2Timestamp(startTs); + }); + this.hangTbl!.addEventListener('row-hover', (e): void => { + // @ts-ignore + let data = e.detail.data as HangStructInPane; + if (data) { + let pointX: number = ns2x( + data.startNS || 0, + TraceRow.range!.startNS, + TraceRow.range!.endNS, + TraceRow.range!.totalNS, + new Rect(0, 0, TraceRow.FRAME_WIDTH, 0), + ); + this.traceSheetEl!.systemLogFlag = new Flag( + Math.floor(pointX), 0, 0, 0, data.startNS, '#999999', '', true, '', + ); + this.spSystemTrace?.refreshCanvas(false); + } + }); + let tbl = this.hangTbl?.shadowRoot?.querySelector('.table'); + tbl!.addEventListener('scroll', () => { + this.tableTitleTimeHandle?.(); + }); + } + + initElements(): void { + this.init(); + this.searchFilterInput!.oninput = (): void => { + this.tableTimeHandle?.(); + }; + this.processFilter!.oninput = (): void => { + this.tableTimeHandle?.(); + }; + this.levelFilterInput!.onchange = (): void => { + this.tableTimeHandle?.(); + }; + } + + connectedCallback(): void { + super.connectedCallback(); + new ResizeObserver((): void => { + this.parentElement!.style.overflow = 'hidden'; + if (this.hangTbl) { + // @ts-ignore + this.hangTbl.shadowRoot.querySelector('.table').style.height = + this.parentElement!.clientHeight - 20 - 45 + 'px'; + } + if (this.filterData.length > 0) { + this.refreshTable(); + this.tableTitleTimeHandle?.(); + } + }).observe(this.parentElement!); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + } + + initHtml(): string { + return TabPaneHangHtml; + } + + refreshHangTab(): void { + let tbl = this.hangTbl?.shadowRoot?.querySelector('.table'); + let height = 0; + if (tbl) { + const trs = tbl.querySelectorAll('.tr') + trs.forEach((trEl: HTMLElement, index: number): void => { + if (index === 0) { + let frontTotalRowSize = Math.round((tbl!.scrollTop / trEl.clientHeight) * 100) / 100; + if (frontTotalRowSize.toString().indexOf('.') >= 0) { + let rowCount = frontTotalRowSize.toString().split('.'); + height += trEl.clientHeight - (Number(rowCount[1]) / 100) * trEl.clientHeight; + } + } + let allTdEl = trEl.querySelectorAll('.td'); + allTdEl[0].style.color = "#3D88C7"; + allTdEl[0].style.textDecoration = 'underline'; + allTdEl[0].style.textDecorationColor = "#3D88C7"; + }); + } + } + + refreshHangsTitle(): void { + let tbl = this.hangTbl?.shadowRoot?.querySelector('.table'); + let height = 0; + let firstRowHeight = 27; + let tableHeadHeight = 26; + this.refreshHangTab(); + if (this.hangTbl && this.hangTbl.currentRecycleList.length > 0) { + let startDataIndex = this.hangTbl.startSkip + 1; + let endDataIndex = startDataIndex; + let crossTopHeight = tbl!.scrollTop % firstRowHeight; + let topShowHeight = crossTopHeight === 0 ? 0 : firstRowHeight - crossTopHeight; + if (topShowHeight < firstRowHeight * 0.3) { + startDataIndex++; + } + let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight; + while (height < tableHeight) { + if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) { + break; + } + height += firstRowHeight; + endDataIndex++; + } + if (tableHeight - height - topShowHeight > firstRowHeight * 0.3) { + endDataIndex++; + } + if (endDataIndex >= this.filterData.length) { + endDataIndex = this.filterData.length; + } else { + endDataIndex = this.hangTbl.startSkip === 0 ? endDataIndex - 1 : endDataIndex; + } + this.hangTableTitle!.textContent = `Hangs [${this.hangTbl.startSkip === 0 ? 1 : startDataIndex}, + ${endDataIndex}] / ${this.filterData.length || 0}`; + } else { + this.hangTableTitle!.textContent = 'Hangs [0, 0] / 0'; + } + if (this.hangTbl!.recycleDataSource.length > 0) { + this.progressEL!.loading = false; + } + } + + initTabSheetEl(traceSheet: TraceSheet): void { + this.traceSheetEl = traceSheet; + this.levelFilterInput!.selectedIndex = 0; + this.allowTag.clear(); + this.processFilter!.value = ''; + this.searchFilterInput!.value = ''; + } + + private updateFilterData(): void { + if (this.systemHangSource?.length > 0) { + this.filterData = this.systemHangSource.filter((data) => this.isFilterHang(data)); + } + if (this.hangTbl) { + // @ts-ignore + this.hangTbl.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 20 - 45 + 'px'; + } + if (this.filterData.length > 0) { + this.hangTbl!.recycleDataSource = this.filterData; + } else { + this.hangTbl!.recycleDataSource = []; + } + this.refreshHangsTitle(); + } + + private isFilterHang(data: HangStructInPane): boolean { + let type = this.levelFilterInput?.selectedIndex ?? 0; + let search = this.searchFilterInput?.value.toLocaleLowerCase() ?? ''; + let process = this.processFilter?.value.toLocaleLowerCase() ?? ''; + return ( + (type === 0 || this.optionLevel.indexOf(data.type) >= type) && + (search === '' || data.caller.toLocaleLowerCase().indexOf(search) >= 0) && + (process === '' || data.pname.toLocaleLowerCase().indexOf(process) >= 0) + ); + } + + private refreshTable(): void { + if (this.traceSheetEl) { + this.traceSheetEl.systemLogFlag = undefined; + this.spSystemTrace?.refreshCanvas(false); + this.updateFilterData(); + } + } + + private delayedRefresh(optionFn: Function, dur: number = tableTimeOut): () => void { + return (...args: []): void => { + window.clearTimeout(this.timeOutId); + this.timeOutId = window.setTimeout((): void => { + optionFn.apply(this, ...args); + }, dur); + }; + } +} + +let defaultIndex: number = 1; +let tableTimeOut: number = 50; + +export class HangStructInPane { + startNS: number = 0; + dur: string = '0'; + pname: string = 'Process'; + type: string; + + sendEventTid: string; + sendTime: string; + expectHandleTime: string; + taskNameId: string; + caller: string; + + constructor(parent: HangStruct) { + this.startNS = parent.startNS ?? this.startNS; + this.dur = getTimeString(parent.dur ?? 0); + this.pname = `${parent.pname ?? this.pname} ${parent.pid ?? ''}`.trim(); + this.type = SpHangChart.calculateHangType(parent.dur ?? 0); + [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.caller] = (parent.content ?? ",0,0,,").split(',').map(i => i.trim()); + this.sendEventTid = this.sendEventTid.split(':').at(-1)!; + } + + static new(parent: HangStruct): HangStructInPane { + return new HangStructInPane(parent); + } +} \ No newline at end of file diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.html.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.html.ts new file mode 100644 index 0000000000000000000000000000000000000000..1719855685994ffbf28a61833fc6372404b940eb --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.html.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +export const TabPaneHangSummaryHtml = ` +
+
+
+ + +
+ + + +
+ +
+
+ + `; diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb5977a69a147f6b2b286dff04356255e6e85dcd --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHangSummary.ts @@ -0,0 +1,285 @@ +/* + * 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 { BaseElement, element } from '../../../../../base-ui/BaseElement'; +import { SelectionParam } from '../../../../bean/BoxSelection'; +import { ColorUtils } from '../../base/ColorUtils'; +import { LitTable } from '../../../../../base-ui/table/lit-table'; +import { LitIcon } from '../../../../../base-ui/icon/LitIcon'; +import { TabPaneHangSummaryHtml } from '../hang/TabPaneHangSummary.html'; +import { NUM_30, NUM_40 } from '../../../../bean/NumBean'; +import { HangStruct } from '../../../../database/ui-worker/ProcedureWorkerHang'; +import { HangType, SpHangChart } from '../../../chart/SpHangChart'; +import { queryAllHangs } from '../../../../database/sql/Hang.sql'; + +/// Hangs 框选Tab页2 +@element('tab-hang-summary') +export class TabPaneHangSummary extends BaseElement { + private hangSummaryTable: HTMLDivElement | undefined | null; + private summaryDownLoadTbl: LitTable | undefined | null; + private systemHangSource: HangStruct[] = []; + private hangTreeNodes: HangTreeNode[] = []; + private expansionDiv: HTMLDivElement | undefined | null; + private expansionUpIcon: LitIcon | undefined | null; + private expansionDownIcon: LitIcon | undefined | null; + private expandedNodeList: Set = new Set(); + private hangLevel: string[] = ['Instant', 'Circumstantial', 'Micro', 'Severe']; + private selectTreeDepth: number = 0; + private currentSelection: SelectionParam | undefined; + + /// 框选时段范围时触发 + set data(selectionParam: SelectionParam) { + if (selectionParam === this.currentSelection) { + return; + } + this.currentSelection = selectionParam; + this.expandedNodeList.clear(); + this.expansionUpIcon!.name = 'up'; + this.expansionDownIcon!.name = 'down'; + this.hangSummaryTable!.innerHTML = ''; + this.summaryDownLoadTbl!.recycleDataSource = []; + queryAllHangs().then((ret) => { + const filter = new Set([...selectionParam.hangMapData.keys()].map(key => key.split(' ').at(-1))); + ret = ret.filter(struct => ( + filter.has(`${struct.pid ?? 0}`) + && ((struct.startNS ?? 0) <= selectionParam.rightNs) + && (selectionParam.leftNs <= ((struct.startNS ?? 0) + (struct.dur ?? 0))) + )); + this.systemHangSource = ret; + if (filter.size > 0 && selectionParam) { + this.refreshRowNodeTable(); + } + }); + } + + initElements(): void { + this.hangSummaryTable = this.shadowRoot?.querySelector('#tab-summary'); + this.summaryDownLoadTbl = this.shadowRoot?.querySelector('#tb-hang-summary'); + this.expansionDiv = this.shadowRoot?.querySelector('.expansion-div'); + this.expansionUpIcon = this.shadowRoot?.querySelector('.expansion-up-icon'); + this.expansionDownIcon = this.shadowRoot?.querySelector('.expansion-down-icon'); + let summaryTreeLevel: string[] = ['Type', '/Process', '/Hang']; + this.shadowRoot?.querySelectorAll('.head-label').forEach((summaryTreeHead): void => { + summaryTreeHead.addEventListener('click', (): void => { + this.selectTreeDepth = summaryTreeLevel.indexOf(summaryTreeHead.textContent!); + this.expandedNodeList.clear(); + this.refreshSelectDepth(this.hangTreeNodes); + this.refreshRowNodeTable(true); + }); + }); + this.hangSummaryTable!.onscroll = (): void => { + let hangTreeTableEl = this.shadowRoot?.querySelector('.hang-tree-table'); + if (hangTreeTableEl) { + hangTreeTableEl.scrollTop = this.hangSummaryTable?.scrollTop || 0; + } + }; + } + + initHtml(): string { + return TabPaneHangSummaryHtml; + } + + connectedCallback(): void { + super.connectedCallback(); + new ResizeObserver((): void => { + this.parentElement!.style.overflow = 'hidden'; + this.refreshRowNodeTable(); + }).observe(this.parentElement!); + this.expansionDiv?.addEventListener('click', this.expansionClickEvent); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + this.expansionDiv?.removeEventListener('click', this.expansionClickEvent); + } + + expansionClickEvent = (): void => { + this.expandedNodeList.clear(); + if (this.expansionUpIcon?.name === 'down') { + this.selectTreeDepth = 0; + this.expansionUpIcon!.name = 'up'; + this.expansionDownIcon!.name = 'down'; + } else { + this.selectTreeDepth = 4; + this.expansionUpIcon!.name = 'down'; + this.expansionDownIcon!.name = 'up'; + } + this.refreshSelectDepth(this.hangTreeNodes); + this.refreshRowNodeTable(true); + } + + private refreshSelectDepth(hangTreeNodes: HangTreeNode[]): void { + hangTreeNodes.forEach((item): void => { + if (item.depth < this.selectTreeDepth) { + this.expandedNodeList.add(item.id); + if (item.children.length > 0) { + this.refreshSelectDepth(item.children); + } + } + }); + } + + private createRowNodeTableEL( + rowNodeList: HangTreeNode[], + tableTreeEl: HTMLDivElement, + tableCountEl: HTMLDivElement, + rowColor: string = '', + ): void { + let unitPadding: number = 20; + let leftPadding: number = 5; + rowNodeList.forEach((rowNode): void => { + let tableTreeRowEl: HTMLElement = document.createElement('tr'); + tableTreeRowEl.className = 'tree-row-tr'; + tableTreeRowEl.title = rowNode.name + ''; + let leftSpacingEl: HTMLElement = document.createElement('td'); + leftSpacingEl.style.paddingLeft = `${rowNode.depth * unitPadding + leftPadding}px`; + tableTreeRowEl.appendChild(leftSpacingEl); + this.addToggleIconEl(rowNode, tableTreeRowEl); + let rowNodeTextEL: HTMLElement = document.createElement('td'); + rowNodeTextEL.textContent = rowNode.name + ''; + rowNodeTextEL.className = 'row-name-td'; + tableTreeRowEl.appendChild(rowNodeTextEL); + tableTreeEl.appendChild(tableTreeRowEl); + let tableCountRowEl: HTMLElement = document.createElement('tr'); + tableCountRowEl.title = rowNode.count.toString(); + let countEL: HTMLElement = document.createElement('td'); + countEL.textContent = rowNode.count.toString(); + countEL.className = 'count-column-td'; + if (rowNode.depth === 0) { + rowNodeTextEL.style.color = ColorUtils.getHangColor((rowNode.name as HangType) ?? ""); + countEL.style.color = ColorUtils.getHangColor((rowNode.name as HangType) ?? ""); + } else { + rowNodeTextEL.style.color = rowColor; + countEL.style.color = rowColor; + } + tableCountRowEl.appendChild(countEL); + tableCountEl.appendChild(tableCountRowEl); + if (rowNode.children && this.expandedNodeList.has(rowNode.id)) { + this.createRowNodeTableEL(rowNode.children, tableTreeEl, tableCountEl, countEL.style.color); + } + }); + } + + private addToggleIconEl(rowNode: HangTreeNode, tableRowEl: HTMLElement): void { + let toggleIconEl: HTMLElement = document.createElement('td'); + let expandIcon = document.createElement('lit-icon'); + expandIcon.classList.add('tree-icon'); + if (rowNode.children && rowNode.children.length > 0) { + toggleIconEl.appendChild(expandIcon); + // @ts-ignore + expandIcon.name = this.expandedNodeList.has(rowNode.id) ? 'minus-square' : 'plus-square'; + toggleIconEl.classList.add('expand-icon'); + toggleIconEl.addEventListener('click', (): void => { + let scrollTop = this.hangSummaryTable?.scrollTop ?? 0; + this.changeNode(rowNode.id); + this.hangSummaryTable!.scrollTop = scrollTop; + }) + } + tableRowEl.appendChild(toggleIconEl); + } + + private changeNode(currentNode: number): void { + if (this.expandedNodeList.has(currentNode)) { + this.expandedNodeList.delete(currentNode); + } else { + this.expandedNodeList.add(currentNode); + } + this.refreshRowNodeTable(true); + } + + private refreshRowNodeTable(useCacheRefresh: boolean = false): void { + this.hangSummaryTable!.innerHTML = ''; + if (this.hangSummaryTable && this.parentElement) { + this.hangSummaryTable.style.height = `${this.parentElement!.clientHeight - NUM_30}px`; + } + if (!useCacheRefresh) { + this.hangTreeNodes = this.buildTreeTbhNodes(this.systemHangSource); + if (this.hangTreeNodes.length > 0) { + this.summaryDownLoadTbl!.recycleDataSource = this.hangTreeNodes; + } else { + this.summaryDownLoadTbl!.recycleDataSource = []; + } + } + let tableFragmentEl: DocumentFragment = document.createDocumentFragment(); + let tableTreeEl: HTMLDivElement = document.createElement('div'); + tableTreeEl.className = 'hang-tree-table'; + let tableCountEl: HTMLDivElement = document.createElement('div'); + if (this.parentElement) { + tableTreeEl.style.height = `${this.parentElement!.clientHeight - NUM_40}px`; + } + this.createRowNodeTableEL(this.hangTreeNodes, tableTreeEl, tableCountEl, ''); + let emptyTr = document.createElement('tr'); + emptyTr.className = 'tree-row-tr'; + tableTreeEl?.appendChild(emptyTr); + let emptyCountTr = document.createElement('tr'); + emptyCountTr.className = 'tree-row-tr'; + tableCountEl?.appendChild(emptyCountTr); + tableFragmentEl.appendChild(tableTreeEl); + tableFragmentEl.appendChild(tableCountEl); + this.hangSummaryTable!.appendChild(tableFragmentEl); + } + + private buildTreeTbhNodes(hangTreeNodes: HangStruct[]): HangTreeNode[] { + let root: HangTreeNode = { + id: 0, depth: 0, children: [], + name: 'All', count: 0, + }; + let id = 1; + hangTreeNodes = hangTreeNodes.map(node => ({ + ...node, + type: SpHangChart.calculateHangType(node.dur ?? 0), + })); + for (const item of hangTreeNodes) { + let typeNode = root.children.find((node) => node.name === item.type); + if (typeNode) { + typeNode.count += 1; + } else { + typeNode = { + id: id += 1, depth: 0, children: [], count: 1, + name: item.type ?? "Undefined Type?", + }; + root.children.push(typeNode); + } + + let processNode = typeNode.children.find((node) => node.name === item.pname); + if (processNode) { + processNode.count += 1; + } else { + processNode = { + id: id += 1, depth: 1, children: [], count: 1, + name: item.pname ?? "Process", + }; + typeNode.children.push(processNode); + } + + let contentNode = { + id: id += 1, depth: 2, children: [], count: 1, + name: item.content ?? "", + }; + processNode.children.push(contentNode); + } + + root.children.sort((a, b) => this.hangLevel.indexOf(b.name) - this.hangLevel.indexOf(a.name)); + return root.children; + } +} + +export interface HangTreeNode { + id: number; + depth: number; + children: HangTreeNode[]; + name: string; + count: number; +}; diff --git a/ide/src/trace/component/trace/sheet/hilog/TabPaneHiLogs.ts b/ide/src/trace/component/trace/sheet/hilog/TabPaneHiLogs.ts index 9d08c068eec4cf8b78449561af2ecf080c7b8c16..dba01ffc2e98355b49b396260c750f41a4d95f6f 100644 --- a/ide/src/trace/component/trace/sheet/hilog/TabPaneHiLogs.ts +++ b/ide/src/trace/component/trace/sheet/hilog/TabPaneHiLogs.ts @@ -203,9 +203,9 @@ export class TabPaneHiLogs extends BaseElement { } } let allTdEl = trEl.querySelectorAll('.td'); - allTdEl[0].style.color = '#3D88C7'; + allTdEl[0].style.color = "#3D88C7"; allTdEl[0].style.textDecoration = 'underline'; - allTdEl[0].style.textDecorationColor = '#3D88C7'; + allTdEl[0].style.textDecorationColor = "#3D88C7"; trEl.addEventListener('mouseout', (): void => { this.traceSheetEl!.systemLogFlag = undefined; this.spSystemTrace?.refreshCanvas(false); diff --git a/ide/src/trace/database/data-trafic/ClockDataReceiver.ts b/ide/src/trace/database/data-trafic/ClockDataReceiver.ts index 79dc5e013ea49d22613b5511deeba925a987b396..003775fb86bb8cf593fb595d2f0ae70472ee6df5 100644 --- a/ide/src/trace/database/data-trafic/ClockDataReceiver.ts +++ b/ide/src/trace/database/data-trafic/ClockDataReceiver.ts @@ -94,7 +94,9 @@ export function clockDataReceiver(data: unknown, proc: Function): void { // @ts-ignore if (!clockList.has(data.params.sqlType + data.params.clockName)) { // @ts-ignore - list = proc(chartClockDataSqlMem(data.params)); + let sql = chartClockDataSqlMem(data.params); + // @ts-ignore + list = proc(sql); for (let j = 0; j < list.length; j++) { if (j === list.length - 1) { // @ts-ignore @@ -162,6 +164,7 @@ function arrayBufferHandler(data: unknown, res: unknown[], transfer: boolean): v // @ts-ignore value[i] = it.value; }); + (self as unknown as Worker).postMessage( { // @ts-ignore diff --git a/ide/src/trace/database/data-trafic/CommonArgs.ts b/ide/src/trace/database/data-trafic/CommonArgs.ts index 8994ae61a459be41b863da03d93e72b8e2e26753..ef61ea0a063abd4961a2325fa269ee19eff167ab 100644 --- a/ide/src/trace/database/data-trafic/CommonArgs.ts +++ b/ide/src/trace/database/data-trafic/CommonArgs.ts @@ -41,4 +41,5 @@ export interface Args { windowId: number; isPin: number; scratchId: number; + minDur: number; } diff --git a/ide/src/trace/database/data-trafic/HangDataReceiver.ts b/ide/src/trace/database/data-trafic/HangDataReceiver.ts new file mode 100644 index 0000000000000000000000000000000000000000..699388fe52edd1cd247b35af72c0eb68af06815f --- /dev/null +++ b/ide/src/trace/database/data-trafic/HangDataReceiver.ts @@ -0,0 +1,117 @@ +// Copyright (c) 2021 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 { TraficEnum } from './utils/QueryEnum'; +import { clockList, hangList } from './utils/AllMemoryCache'; +import { Args } from './CommonArgs'; + +export const chartHangDataSql = (args: Args): string => ` +SELECT + c.id as id, + c.ts - r.start_ts as startNS, + c.dur as dur, + t.tid as tid, + t.name as tname, + p.pid as pid, + p.name as pname +FROM + callstack c, trace_range r +LEFT JOIN thread t ON + t.itid = c.callid +LEFT JOIN process p ON + p.ipid = t.ipid +WHERE + c.dur >= ${args.minDur} + AND c.name LIKE 'H:Et:%' + AND t.is_main_thread = 1 + AND p.pid = ${args.pid} +`.trim(); + +export interface HangSQLStruct { + id: number; + startNS: number; + dur: number; + tid: number; + pid: number; +} + +export function hangDataReceiver(data: any, proc: Function): void { + if (data.params.trafic === TraficEnum.Memory) { + let res: HangSQLStruct[]; + let list: HangSQLStruct[]; + + if (!hangList.has(data.params.pid)) { + let sql = chartHangDataSql(data.params); + list = proc(sql); + hangList.set(data.params.pid, list); + } + else { + list = hangList.get(data.params.pid) || []; + } + + if (data.params.queryAll) { + res = list.filter( + //@ts-ignore + (it) => it.startNS + it.dur >= data.params.selectStartNS && it.startNS <= data.params.selectEndNS + ); + } + else { + res = list; + } + + arrayBufferHandler(data, res, true); + } + else { + let sql = chartHangDataSql(data.params); + let res: HangSQLStruct[] = proc(sql); + arrayBufferHandler(data, res, data.params.trafic !== TraficEnum.SharedArrayBuffer); + } +} + +function arrayBufferHandler(data: any, res: HangSQLStruct[], transfer: boolean = true): void { + let id = new Int32Array(transfer ? res.length : data.params.sharedArrayBuffers.id); + let startNS = new Float64Array(transfer ? res.length : data.params.sharedArrayBuffers.startNS); + let dur = new Float64Array(transfer ? res.length : data.params.sharedArrayBuffers.dur); + let tid = new Int32Array(transfer ? res.length : data.params.sharedArrayBuffers.tid); + let pid = new Int32Array(transfer ? res.length : data.params.sharedArrayBuffers.pid); + res.forEach((it, i) => { + id[i] = it.id; + startNS[i] = it.startNS; + dur[i] = it.dur; + pid[i] = it.pid; + }); + + let arg1 = { + id: data.id, + action: data.action, + results: { + id: id.buffer, + startNS: startNS.buffer, + dur: dur.buffer, + tid: tid.buffer, + pid: pid.buffer, + }, + len: res.length, + transfer: transfer, + }; + let arg2 = [ + id.buffer, + startNS.buffer, + dur.buffer, + tid.buffer, + pid.buffer, + ]; + (self as unknown as Worker).postMessage( + arg1, arg2, + ); +} \ No newline at end of file diff --git a/ide/src/trace/database/data-trafic/HangDataSender.ts b/ide/src/trace/database/data-trafic/HangDataSender.ts new file mode 100644 index 0000000000000000000000000000000000000000..660bed271b844a936e6e3b5661a22c887979b097 --- /dev/null +++ b/ide/src/trace/database/data-trafic/HangDataSender.ts @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 Shenzhen Kaihong Digital Industry Development 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 { CHART_OFFSET_LEFT, MAX_COUNT, QueryEnum, TraficEnum } from './utils/QueryEnum'; +import { threadPool } from '../SqlLite'; +import { TraceRow } from '../../component/trace/base/TraceRow'; +import { HangStruct } from '../ui-worker/ProcedureWorkerHang'; +import { FlagsConfig } from '../../component/SpFlags'; + +export function hangDataSender( + processId: number = 0, + row: TraceRow, + args?: any, +): Promise { + let trafic: number = TraficEnum.Memory; + let width = row.clientWidth - CHART_OFFSET_LEFT; + return new Promise((resolve, reject): void => { + let flagsItemJson = JSON.parse(window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY)!); + let minDur = parseInt(flagsItemJson.hangValue); + threadPool.submitProto( + QueryEnum.HangData, + { + pid: processId, + minDur: minDur, + queryAll: args && args.queryAll, + selectStartNS: args ? args.startNS : 0, + selectEndNS: args ? args.endNS : 0, + selectTotalNS: args ? args.endNS - args.startNS : 0, + trafic: trafic, + width: width, + }, + (res: unknown, len: number, transfer: boolean): void => { + resolve(arrayBufferHandler(transfer ? res : row.sharedArrayBuffers, len)); + } + ); + }); +} + +function arrayBufferHandler(buffers: any, len: number): HangStruct[] { + let outArr: HangStruct[] = []; + let id = new Int32Array(buffers.id); + let startNS = new Float64Array(buffers.startNS); + let dur = new Float64Array(buffers.dur); + let tid = new Int32Array(buffers.tid); + let pid = new Int32Array(buffers.pid); + for (let i = 0; i < len; i += 1) { + outArr.push({ + id: id[i], + startNS: startNS[i], + dur: dur[i], + tid: tid[i], + pid: pid[i], + } as HangStruct); + } + return outArr; +} diff --git a/ide/src/trace/database/data-trafic/utils/AllMemoryCache.ts b/ide/src/trace/database/data-trafic/utils/AllMemoryCache.ts index d458819daefc55f8bc0c881d26b55fd97999a0c9..2088bc1522f95fce42e4d5e53a7a80fcd335bf0a 100644 --- a/ide/src/trace/database/data-trafic/utils/AllMemoryCache.ts +++ b/ide/src/trace/database/data-trafic/utils/AllMemoryCache.ts @@ -18,12 +18,15 @@ import { resetAbilityMonitor } from '../AbilityMonitorReceiver'; import { resetAbility } from '../VmTrackerDataReceiver'; import { resetDynamicEffect } from '../FrameDynamicEffectReceiver'; import { resetEnergyEvent } from '../EnergySysEventReceiver'; +import { HangSQLStruct } from '../HangDataReceiver'; // thread_state 表缓存 export const sliceList: Map> = new Map(); //cpu 泳道 memory 缓存 export const cpuList: Map> = new Map(); //clock 泳道 memory 模式缓存 export const clockList: Map> = new Map(); +//hang 泳道 memory 模式缓存 +export const hangList: Map> = new Map(); //cpu freq 泳道 memory模式缓存 export const cpuFreqList: Map> = new Map(); //cpu freq limit 泳道 memory模式缓存 diff --git a/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts b/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts index f39a460454dff88f5f4ebcb2544c9df5fe244d48..8ead725cc196d39c166415ed61bacb60ac4a5b6f 100644 --- a/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts +++ b/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts @@ -81,6 +81,7 @@ import { cpuFreqDataReceiver } from '../cpu/CpuFreqDataReceiver'; import { lostFrameReceiver } from './../LostFrameReceiver'; import { sliceReceiver, sliceSPTReceiver } from '../SliceReceiver'; import { dmaFenceReceiver } from './../dmaFenceReceiver'; +import { hangDataReceiver } from '../HangDataReceiver'; // @ts-ignore const traficHandlers: Map = new Map([]); // @ts-ignore @@ -103,6 +104,7 @@ traficHandlers.set(QueryEnum.HiperfThreadData, hiperfThreadDataReceiver); traficHandlers.set(QueryEnum.NativeMemoryChartCacheNormal, nativeMemoryDataHandler); traficHandlers.set(QueryEnum.NativeMemoryChartCacheStatistic, nativeMemoryDataHandler); traficHandlers.set(QueryEnum.NativeMemoryChartData, nativeMemoryDataHandler); +traficHandlers.set(QueryEnum.HangData, hangDataReceiver); traficHandlers.set(QueryEnum.ClockData, clockDataReceiver); traficHandlers.set(QueryEnum.IrqData, irqDataReceiver); traficHandlers.set(QueryEnum.VirtualMemoryData, virtualMemoryDataReceiver); diff --git a/ide/src/trace/database/data-trafic/utils/QueryEnum.ts b/ide/src/trace/database/data-trafic/utils/QueryEnum.ts index e51ccb00a62e912e2c1933ee85be3fcd908cd946..b24135acc755adc5ebec29eb8c0aa116054c7397 100644 --- a/ide/src/trace/database/data-trafic/utils/QueryEnum.ts +++ b/ide/src/trace/database/data-trafic/utils/QueryEnum.ts @@ -18,6 +18,7 @@ export enum QueryEnum { CpuStateData = 1, CpuFreqData = 2, CpuFreqLimitData = 3, + HangData = 3.5, ClockData = 4, IrqData = 5, ProcessData = 6, diff --git a/ide/src/trace/database/sql/Hang.sql.ts b/ide/src/trace/database/sql/Hang.sql.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1a856d1adb04f30d7fc32d09b435d80749a04f1 --- /dev/null +++ b/ide/src/trace/database/sql/Hang.sql.ts @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Shenzhen Kaihong Digital Industry Development 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 { FlagsConfig } from '../../component/SpFlags'; +import { query } from '../SqlLite'; +import { HangStruct } from '../ui-worker/ProcedureWorkerHang'; + +function getMinDur(): string { + let flagsItemJson = JSON.parse(window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY)!) + let minDur = flagsItemJson.hangValue + return minDur +} + +export const queryHangData = (): Promise> => query( + 'queryHangData', + ` +SELECT + p.pid as id, + p.name as name, + count(*) as num +FROM + callstack c +LEFT JOIN thread t ON + t.itid = c.callid +LEFT JOIN process p ON + p.ipid = t.ipid +WHERE + c.name LIKE 'H:Et:%' + AND t.is_main_thread = 1 + AND c.dur >= ${getMinDur()} +GROUP BY + p.pid +`.trim() +) + + +export const queryAllHangs = (): Promise> => query( + 'queryAllHangs', + ` +SELECT + c.id as id, + c.ts - r.start_ts as startNS, + c.dur as dur, + t.tid as tid, + p.pid as pid, + p.name as pname, + c.name as content +FROM + callstack c, trace_range r +LEFT JOIN thread t ON + t.itid = c.callid +LEFT JOIN process p ON + p.ipid = t.ipid +WHERE + c.dur >= ${getMinDur()} + AND t.is_main_thread = 1 + AND c.name LIKE 'H:Et:%' +`.trim() +) \ No newline at end of file diff --git a/ide/src/trace/database/ui-worker/ProcedureWorker.ts b/ide/src/trace/database/ui-worker/ProcedureWorker.ts index 2116d04efc8773e245b41850fabedf2ff6dbccd8..0443c5b7336f0d634d8eaf2f4b196a0cc7566b95 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorker.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorker.ts @@ -39,6 +39,7 @@ import { EnergySystemRender } from './ProcedureWorkerEnergySystem'; import { EnergyPowerRender } from './ProcedureWorkerEnergyPower'; import { EnergyStateRender } from './ProcedureWorkerEnergyState'; import { CpuFreqLimitRender } from './cpu/ProcedureWorkerCpuFreqLimits'; +import { HangRender } from './ProcedureWorkerHang'; import { ClockRender } from './ProcedureWorkerClock'; import { IrqRender } from './ProcedureWorkerIrq'; import { JankRender } from './ProcedureWorkerJank'; @@ -116,6 +117,7 @@ export let renders = { energySystem: new EnergySystemRender(), energyPower: new EnergyPowerRender(), energyState: new EnergyStateRender(), + hang: new HangRender(), clock: new ClockRender(), irq: new IrqRender(), jank: new JankRender(), diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c91687468ab1b0a11fe8094918293df83655aad --- /dev/null +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 Shenzhen Kaihong Digital Industry Development 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 { BaseStruct, dataFilterHandler, drawLoadingFrame, drawString, isFrameContainPoint, Render } from './ProcedureWorkerCommon'; +import { TraceRow } from '../../component/trace/base/TraceRow'; +import { SpSystemTrace } from '../../component/SpSystemTrace'; +import { HangType } from '../../component/chart/SpHangChart'; + +/// Render类 用于处理Hang子泳道的绘制逻辑 +export class HangRender extends Render { + renderMainThread( + hangReq: { + context: CanvasRenderingContext2D; + useCache: boolean; + type: string; + index: number; + }, + row: TraceRow, + ): void { + HangStruct.index = hangReq.index; + let hangList = row.dataList; + let hangFilter = row.dataListCache; + let filterConfig = { + startKey: 'startNS', + durKey: 'dur', + startNS: TraceRow.range?.startNS ?? 0, + endNS: TraceRow.range?.endNS ?? 0, + totalNS: TraceRow.range?.totalNS ?? 0, + frame: row.frame, + paddingTop: 2, + useCache: hangReq.useCache || !(TraceRow.range?.refresh ?? false), + } + dataFilterHandler(hangList, hangFilter, filterConfig); + drawLoadingFrame(hangReq.context, hangFilter, row); + hangReq.context.beginPath(); + let find = false; + for (let re of hangFilter) { + HangStruct.draw(hangReq.context, re); + if (row.isHover && re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { + HangStruct.hoverHangStruct = re; + find = true; + } + } + if (!find && row.isHover) { + HangStruct.hoverHangStruct = undefined; + } + hangReq.context.closePath(); + } +} + +export function HangStructOnClick(clickRowType: string, sp: SpSystemTrace): Promise { + return new Promise((resolve, reject) => { + if ((clickRowType === TraceRow.ROW_TYPE_HANG || clickRowType === TraceRow.ROW_TYPE_HANG_INNER) && HangStruct.hoverHangStruct) { + HangStruct.selectHangStruct = HangStruct.hoverHangStruct; + sp.traceSheetEL?.displayHangData(HangStruct.selectHangStruct, sp); + sp.timerShaftEL?.modifyFlagList(undefined); + reject(new Error()); + } else { + resolve(null); + } + }); +} + +/// BaseStruct类 存储每个Hang事件详细信息 管理Hang色块绘制细节 +export class HangStruct extends BaseStruct { + static hoverHangStruct: HangStruct | undefined; + static selectHangStruct: HangStruct | undefined; + static index = 0; + id: number | undefined; + startNS: number | undefined; + dur: number | undefined; + tid: number | undefined; + pid: number | undefined; + type: HangType | undefined; // 手动补充 按时间分类 + pname: string | undefined; // 手动补充 + content: string | undefined; // 手动补充 在tab页中需要手动解析内容 + + static getFrameColor(data: HangStruct): string { + return ({ + "Instant": "#559CFF", + "Circumstantial": "#FFE44D", + "Micro": "#FEB354", + "Severe": "#FC7470", + "": "", + })[data.type!]; + } + + static draw(ctx: CanvasRenderingContext2D, data: HangStruct): void { + if (data.frame) { + ctx.fillStyle = HangStruct.getFrameColor(data); + ctx.strokeStyle = HangStruct.getFrameColor(data); + + ctx.globalAlpha = 1; + ctx.lineWidth = 1; + + if (data === HangStruct.hoverHangStruct) { + ctx.globalAlpha = 0.7; + } + + ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); + if (data.frame.width > 10) { + ctx.fillStyle = '#fff'; + drawString(ctx, `${data.type || ''}`, 1, data.frame, data); + } + + if (data === HangStruct.selectHangStruct) { + ctx.strokeStyle = '#000'; + ctx.lineWidth = 2; + ctx.strokeRect( + data.frame.x + 1, + data.frame.y + 1, + data.frame.width - 2, + data.frame.height - 2, + ); + } + + ctx.globalAlpha = 1; + } + } + + static isHover(data: HangStruct): boolean { + return data === HangStruct.hoverHangStruct || data === HangStruct.selectHangStruct; + } +}