From a27a34b42d518a44d844729fae05a5c22b6e8b14 Mon Sep 17 00:00:00 2001 From: LiYuxi Date: Fri, 21 Jun 2024 17:02:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0hangs=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=B3=B3=E9=81=93=E5=8F=8A=E5=AD=90=E6=B3=B3=E9=81=93=E8=89=B2?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: LiYuxi --- ide/src/trace/bean/BoxSelection.ts | 16 ++ ide/src/trace/component/SpFlags.ts | 75 ++++++- .../trace/component/SpSystemTrace.event.ts | 2 + ide/src/trace/component/SpSystemTrace.ts | 2 + .../trace/component/chart/SpChartManager.ts | 5 + ide/src/trace/component/chart/SpHangChart.ts | 196 ++++++++++++++++++ .../trace/component/trace/base/RangeSelect.ts | 4 + .../trace/component/trace/base/TraceRow.ts | 2 + .../trace/component/trace/base/TraceSheet.ts | 3 + .../component/trace/base/TraceSheetConfig.ts | 6 + .../trace/sheet/TabPaneCurrentSelection.ts | 26 +++ .../trace/sheet/hang/TabPaneHangCounter.ts | 192 +++++++++++++++++ ide/src/trace/database/TraceWorker.ts | 2 + .../database/data-trafic/ClockDataReceiver.ts | 42 ++-- .../database/data-trafic/HangDataReceiver.ts | 116 +++++++++++ .../database/data-trafic/HangDataSender.ts | 68 ++++++ .../data-trafic/utils/AllMemoryCache.ts | 3 + .../data-trafic/utils/ExecProtoForWorker.ts | 2 + .../database/data-trafic/utils/QueryEnum.ts | 1 + ide/src/trace/database/sql/Hang.sql.ts | 59 ++++++ .../database/ui-worker/ProcedureWorker.ts | 2 + .../database/ui-worker/ProcedureWorkerHang.ts | 128 ++++++++++++ 22 files changed, 931 insertions(+), 21 deletions(-) create mode 100644 ide/src/trace/component/chart/SpHangChart.ts create mode 100644 ide/src/trace/component/trace/sheet/hang/TabPaneHangCounter.ts create mode 100644 ide/src/trace/database/data-trafic/HangDataReceiver.ts create mode 100644 ide/src/trace/database/data-trafic/HangDataSender.ts create mode 100644 ide/src/trace/database/sql/Hang.sql.ts create mode 100644 ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index 2b6bf58f..442700be 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -69,6 +69,7 @@ export class SelectionParam { string, ((arg: unknown) => Promise> | undefined) | undefined >(); + hangMapData: Map Promise> | undefined) | undefined> = new Map() irqCallIds: Array = []; softIrqCallIds: Array = []; funTids: Array = []; @@ -1091,6 +1092,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) { + // this.hangMapData.set(it.rowId || '', it.getCacheData); + // } + // } + // @ts-ignore pushGpuMemoryVmTracker(it: TraceRow, sp: SpSystemTrace): void { if (it.rowType === TraceRow.ROW_TYPE_GPU_MEMORY_VMTRACKER) { @@ -1200,6 +1215,7 @@ export class SelectionParam { this.pushVmTracker(it, sp); this.pushVmTrackerShm(it, sp); this.pushClock(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 d50ac6e7..a047a0ad 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -76,6 +76,23 @@ export class SpFlags extends BaseElement { vsyncSelect?.setAttribute('disabled', 'disabled'); } + + let hangsSelect = this.shadowRoot?.querySelector('#hangsSelect'); + if (title === 'Hangs' && configSelect.selectedOptions[0].value === 'Enabled') { + hangsSelect?.removeAttribute('disabled'); + } + if (title === 'Hangs' && configSelect.selectedOptions[0].value === 'Disabled') { + hangsSelect?.childNodes.forEach((child: ChildNode) => { + let selectEl = child as HTMLOptionElement; + if (child.textContent === 'Micro') { + selectEl.selected = true; + FlagsConfig.updateFlagsConfig('hangValue', selectEl.value); + } else { + selectEl.selected = false; + } + }); + hangsSelect?.setAttribute('disabled', 'disabled'); + } }); let description = document.createElement('div'); description.className = 'flag-des-div'; @@ -134,6 +151,11 @@ export class SpFlags extends BaseElement { configDiv.appendChild(configFooterDiv); } + if (config.title === 'Hangs') { + let configFooterDiv = this.createHangsOption(); + configDiv.appendChild(configFooterDiv); + } + this.bodyEl!.appendChild(configDiv); }); } @@ -165,9 +187,6 @@ export class SpFlags extends BaseElement { FlagsConfig.updateFlagsConfig('vsyncValue', vsyncGenOption.value); vsyncTypeEl.addEventListener('change', function () { let selectValue = this.selectedOptions[0].value; - console.log(this); - console.log(this.selectedOptions[0]); - console.log(this.selectedOptions[0].value); FlagsConfig.updateFlagsConfig('vsyncValue', selectValue); }); @@ -184,6 +203,51 @@ export class SpFlags extends BaseElement { configFooterDiv.appendChild(vsyncTypeEl); return configFooterDiv; } + + private createHangsOption(): HTMLDivElement { + console.log("start createHangsOption.") + 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 hangInstantOption = document.createElement('option'); + hangInstantOption.value = 'Instant;Circumstantial;Micro;Severe'; + hangInstantOption.textContent = 'Instant'; + hangInstantOption.selected = true; + hangsTypeEl.appendChild(hangInstantOption) + + let hangMicroOption = document.createElement('option'); + hangMicroOption.value = 'Micro;Severe'; + hangMicroOption.textContent = 'micro'; + hangMicroOption.selected = true; + hangsTypeEl.appendChild(hangMicroOption) + + FlagsConfig.updateFlagsConfig('hangValue', hangInstantOption.value); + hangsTypeEl.addEventListener('change', function () { + let selectValue = this.selectedOptions[0].value; + console.log(this); + console.log(this.selectedOptions[0]); + console.log(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'); + FlagsConfig.updateFlagsConfig('hangValue', hangInstantOption.value); + } + configFooterDiv.appendChild(hangsLableEl); + configFooterDiv.appendChild(hangsTypeEl); + return configFooterDiv; + } } export type Params = { @@ -239,6 +303,11 @@ export class FlagsConfig { switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], describeContent: 'VSync Signal drawing', }, + { + 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 ef1744b7..97835c82 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -45,6 +45,7 @@ import { FlagsConfig } from './SpFlags'; import { LitMainMenu } from '../../base-ui/menu/LitMainMenu'; import { PerfToolsStructOnClick } from '../database/ui-worker/ProcedureWorkerPerfTool'; import { BaseStruct } from '../bean/BaseStruct'; +import { HangStructOnClick } from '../database/ui-worker/ProcedureWorkerHang'; function timeoutJudge(sp: SpSystemTrace): number { let timeoutJudge = window.setTimeout((): void => { @@ -341,6 +342,7 @@ function allStructOnClick(clickRowType: string, sp: SpSystemTrace, row?: TraceRo .then(() => CpuStateStructOnClick(clickRowType, sp)) .then(() => CpuFreqLimitsStructOnClick(clickRowType, sp)) .then(() => ClockStructOnClick(clickRowType, sp)) + .then(() => HangStructOnClick(clickRowType, sp)) //@ts-ignore .then(() => SnapshotStructOnClick(clickRowType, sp, row!)) .then(() => IrqStructOnClick(clickRowType, sp)) diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index b42eec42..c9d7850f 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -124,6 +124,7 @@ import { SampleStruct } from '../database/ui-worker/ProcedureWorkerBpftrace'; import { readTraceFileBuffer } from '../SpApplicationPublicFunc'; import { PerfToolStruct } from '../database/ui-worker/ProcedureWorkerPerfTool'; import { BaseStruct } from '../bean/BaseStruct'; +import { HangStruct } from '../database/ui-worker/ProcedureWorkerHang'; function dpr(): number { return window.devicePixelRatio || 1; @@ -1085,6 +1086,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 62c1bc61..f96fafda 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, @@ -82,6 +83,7 @@ export class SpChartManager { private logChart: SpLogChart; private spHiSysEvent: SpHiSysEventChart; private spSegmentationChart: SpSegmentationChart; + private hangChart: SpHangChart; private spBpftraceChart: SpBpftraceChart; private tranceRange = { startTs: 0, endTs: 0 }; private spPerfOutputDataChart: SpPerfOutputDataChart; @@ -110,6 +112,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.spUserFileChart = new SpUserFileChart(trace) @@ -170,6 +173,8 @@ export class SpChartManager { await this.initCpu(progress); await this.logChart.init(); await this.spHiSysEvent.init(); + progress('HangChart inin', 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 00000000..1f5086d5 --- /dev/null +++ b/ide/src/trace/component/chart/SpHangChart.ts @@ -0,0 +1,196 @@ +/* + * 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 { HangRender, HangStruct } from '../../database/ui-worker/ProcedureWorkerHang'; +import { ColorUtils } from '../trace/base/ColorUtils'; +import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; +import { Utils } from '../trace/base/Utils'; +import { queryHangFuncName, queryRealHangData } from '../../database/sql/Hang.sql'; +import { realHangDataSender } from '../../database/data-trafic/HangDataSender'; +import { BaseStruct } from '../../bean/BaseStruct'; + +export type HangType = "Instant" | "Circumstantial" | "Micro" | "Severe" | "" + +export class SpHangChart { + private trace: SpSystemTrace; + private 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 { + for (const funcNameItem of await queryHangFuncName()) { + this.funcNameMap.set(funcNameItem.id, funcNameItem.name) + } + 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 = realHangDataSender(it.id, traceRow) + if (promiseData === null) { + // @ts-ignore + return new Promise>((resolve) => resolve([])); + } else { + // @ts-ignore + return promiseData.then((resultHang: Array) => { + for (const hangItem of resultHang) { + hangItem.pname = it.name + hangItem.type = SpHangChart.calculateHangType(hangItem.dur!) + hangItem.content = this.funcNameMap.get(hangItem.id!) + } + return resultHang; + }); + } + }; + } + + 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 as HangRender).renderMainThread( + { + context: context, + useCache: useCache, + type: it.name, + index: hangId, + processName: it.name + }, + traceRow + ); + traceRow.canvasRestore(context, this.trace); + }; + } + + async initData(folder: TraceRow): Promise { + let hangStartTime = new Date().getTime(); + let realHangList = await queryRealHangData(); + if (realHangList.length === 0) { + return; + } + this.trace.rowsEL?.appendChild(folder); + for (let i = 0; i < realHangList.length; i++) { + const it: { + id: number, + name: string, + num: number + } = realHangList[i]; + let traceRow = TraceRow.skeleton(); + traceRow.rowId = it.name ?? '' + it.id; + traceRow.rowType = TraceRow.ROW_TYPE_HANG; + traceRow.rowParentId = folder.rowId; + traceRow.style.height = '40px'; + traceRow.name = it.name ?? '-'; + 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> => realHangDataSender(it.id, traceRow, args) + traceRow.focusHandler = (ev): void => { + this.trace?.displayTip( + traceRow, + HangStruct.hoverHangStruct, + `${JSON.stringify(HangStruct.hoverHangStruct?.content ?? "Hatsune Miku")}` + ); + }; + 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/trace/base/RangeSelect.ts b/ide/src/trace/component/trace/base/RangeSelect.ts index a7e2b0b0..47e0fa83 100644 --- a/ide/src/trace/component/trace/base/RangeSelect.ts +++ b/ide/src/trace/component/trace/base/RangeSelect.ts @@ -278,6 +278,7 @@ export class RangeSelect { document.body.style.cursor = 'default'; } if (this.isHover && this.isMouseDown) { + // 似乎调用不到 this.handleRangeSelectAndDraw(rows, ev); return; } @@ -291,6 +292,7 @@ export class RangeSelect { } // @ts-ignore private handleRangeSelect(rows: Array>): void { + // console.log("handleRangeSelect", arguments) let rangeSelect: RangeSelectStruct | undefined; let favoriteRect = this.trace?.favoriteChartListEL?.getBoundingClientRect(); let favoriteLimit = favoriteRect!.top + favoriteRect!.height; @@ -352,11 +354,13 @@ export class RangeSelect { } private handleDrawForNotMouseDown(): void { + // console.log("handleDrawForNotMouseDown", arguments) this.timerShaftEL!.sportRuler!.isRangeSelect = isNotEmpty(this.rangeTraceRow) ?? false; this.timerShaftEL!.sportRuler!.draw(); } // @ts-ignore private handleRangeSelectAndDraw(rows: Array>, ev: MouseEvent): void { + // console.log("handleRangeSelectAndDraw", arguments) let rangeSelect: RangeSelectStruct | undefined; this.rangeTraceRow = rows.filter((it) => { if (it.rangeSelect) { diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index 43655a21..7702083a 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -110,8 +110,10 @@ 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_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 27459b04..f2fbde57 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 IrqStruct } from '../../../database/ui-worker/ProcedureWorkerIrq'; import { type JankStruct } from '../../../database/ui-worker/ProcedureWorkerJank'; @@ -633,6 +634,8 @@ export class TraceSheet extends BaseElement { ); displayMemData = (data: ProcessMemStruct): void => this.displayTab('current-selection').setMemData(data); + displayHangData = (data: HangStruct): Promise => + this.displayTab('current-selection').setHangData(data); displayClockData = (data: ClockStruct): Promise => this.displayTab('current-selection').setClockData(data); displayPerfToolsData = (data: PerfToolStruct): void => diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index 4e14c5eb..2b014e49 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -64,6 +64,7 @@ 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 { TabPaneHangCounter } from '../sheet/Hang/TabPaneHangCounter'; import { TabPaneIrqCounter } from '../sheet/irq/TabPaneIrqCounter'; import { TabPaneFrames } from '../sheet/jank/TabPaneFrames'; import { TabPanePerfAnalysis } from '../sheet/hiperf/TabPanePerfAnalysis'; @@ -212,6 +213,11 @@ export let tabConfig: unknown = { type: TabPaneClockCounter, require: (param: SelectionParam) => param.clockMapData.size > 0, }, + // 'box-hang-counters': { + // title: 'Hang Counters', + // type: TabPaneHangCounter, + // 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 c7f08564..1467e99f 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -58,6 +58,7 @@ import { queryWakeupListPriority } from '../../../database/sql/Cpu.sql'; import { TabPaneCurrentSelectionHtml } from './TabPaneCurrentSelection.html'; import { queryRealTime } from '../../../database/sql/Clock.sql'; import { PerfToolStruct } from '../../../database/ui-worker/ProcedureWorkerPerfTool'; +import { HangStruct } from '../../../database/ui-worker/ProcedureWorkerHang'; 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.'; @@ -545,6 +546,31 @@ export class TabPaneCurrentSelection extends BaseElement { this.addClickToTransfBtn(startTimeAbsolute, CLOCK_TRANSF_BTN_ID, CLOCK_STARTTIME_ABSALUTED_ID); } + async setHangData(data: HangStruct): Promise { + // TODO: + // console.log("setHangData", arguments) + // await this.setRealTime(); + // this.setTableHeight('auto'); + // //时钟信息 + // this.tabCurrentSelectionInit('Counter 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: 'Value', + // value: ColorUtils.formatNumberComma(data.value || 0), + // }); + // list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); + // list.push({ name: "Fake Data", value: "Useless Info." }) + // this.currentSelectionTbl!.dataSource = list; + // // @ts-ignore + // let startTimeAbsolute = (data.startNS || 0) + (window as unknown).recordStartNS; + // this.addClickToTransfBtn(startTimeAbsolute, CLOCK_TRANSF_BTN_ID, CLOCK_STARTTIME_ABSALUTED_ID); + } + setPerfToolsData(data: PerfToolStruct): void { this.setTableHeight('auto'); //Perf Tools info diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHangCounter.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHangCounter.ts new file mode 100644 index 00000000..3bb170df --- /dev/null +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHangCounter.ts @@ -0,0 +1,192 @@ +/* + * 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 { LitTable } from '../../../../../base-ui/table/lit-table'; +import { SelectionData, SelectionParam } from '../../../../bean/BoxSelection'; +import { resizeObserver } from '../SheetUtils'; + +@element('tabpane-hang-counter') +export class TabPaneHangCounter extends BaseElement { + private hangCounterTbl: LitTable | null | undefined; + private hangCounterRange: HTMLLabelElement | null | undefined; + private hangCounterSource: Array = []; + + set data(hangCounterValue: SelectionParam) { + //@ts-ignore + this.hangCounterTbl?.shadowRoot?.querySelector('.table')?.style?.height = `${ + this.parentElement!.clientHeight - 45 + }px`; + this.hangCounterRange!.textContent = `Selected range: ${parseFloat( + ((hangCounterValue.rightNs - hangCounterValue.leftNs) / 1000000.0).toFixed(5) + )} ms`; + this.getCounterData(hangCounterValue).then(); + } + + async getCounterData(hangCounterValue: SelectionParam): Promise { + let dataSource: Array = []; + let collect = hangCounterValue.hangMapData; + let sumCount = 0; + this.hangCounterTbl!.loading = true; + for (let key of collect.keys()) { + let counters = collect.get(key); + let res = await counters?.({ + startNS: hangCounterValue.leftNs, + endNS: hangCounterValue.rightNs, + queryAll: true, + }); + let sd = this.createSelectCounterData(key, res || [], hangCounterValue.leftNs, hangCounterValue.rightNs); + sumCount += Number.parseInt(sd.count || '0'); + dataSource.push(sd); + } + let sumData = new SelectionData(); + sumData.count = sumCount.toString(); + sumData.process = ' '; + dataSource.splice(0, 0, sumData); + this.hangCounterTbl!.loading = false; + this.hangCounterSource = dataSource; + this.hangCounterTbl!.recycleDataSource = dataSource; + } + + initElements(): void { + this.hangCounterTbl = this.shadowRoot?.querySelector('#tb-counter'); + this.hangCounterRange = this.shadowRoot?.querySelector('#time-range'); + this.hangCounterTbl!.addEventListener('column-click', (evt): void => { + // @ts-ignore + this.sortByColumn(evt.detail); + }); + } + + connectedCallback(): void { + super.connectedCallback(); + resizeObserver(this.parentElement!, this.hangCounterTbl!); + } + + initHtml(): string { + return ` + + + + + + + + + + + + + + + + + + + + + + + `; + } + + createSelectCounterData(name: string, list: Array, leftNs: number, rightNs: number): SelectionData { + let selectCounterData = new SelectionData(); + if (list.length > 0) { + let range = rightNs - leftNs; + let first = list[0]; + // @ts-ignore + selectCounterData.trackId = first.filterId; + selectCounterData.name = name; + // @ts-ignore + selectCounterData.first = `${first.value}`; + selectCounterData.count = `${list.length}`; + // @ts-ignore + selectCounterData.last = `${list[list.length - 1].value}`; + selectCounterData.delta = `${parseInt(selectCounterData.last) - parseInt(selectCounterData.first)}`; + selectCounterData.rate = (parseInt(selectCounterData.delta) / ((range * 1.0) / 1000000000)).toFixed(4); + // @ts-ignore + selectCounterData.min = `${first.value}`; + selectCounterData.max = '0'; + let weightAvg = 0.0; + for (let i = 0; i < list.length; i++) { + let counter = list[i]; + // @ts-ignore + if (counter.value < parseInt(selectCounterData.min)) { + // @ts-ignore + selectCounterData.min = counter.value.toString(); + } + // @ts-ignore + if (counter.value > parseInt(selectCounterData.max)) { + // @ts-ignore + selectCounterData.max = counter.value.toString(); + } + // @ts-ignore + let start = i === 0 ? leftNs : counter.startNS; + // @ts-ignore + let end = i === list.length - 1 ? rightNs : list[i + 1].startNS; + // @ts-ignore + weightAvg += counter.value * (((end - start) * 1.0) / range); + } + selectCounterData.avgWeight = weightAvg.toFixed(2); + } + return selectCounterData; + } + + sortByColumn(detail: unknown): void { + // @ts-ignore + function compare(property, sort, type) { + return function (hangCounterLeftData: SelectionData, hangCounterRightData: SelectionData): number { + if (hangCounterLeftData.process === ' ' || hangCounterRightData.process === ' ') { + return 0; + } + if (type === 'number') { + return sort === 2 // @ts-ignore + ? parseFloat(hangCounterRightData[property]) - parseFloat(hangCounterLeftData[property]) // @ts-ignore + : parseFloat(hangCounterLeftData[property]) - parseFloat(hangCounterRightData[property]); + } else { + // @ts-ignore + if (hangCounterRightData[property] > hangCounterLeftData[property]) { + return sort === 2 ? 1 : -1; + } else { + // @ts-ignore + if (hangCounterRightData[property] === hangCounterLeftData[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + }; + } + // @ts-ignore + if (detail.key === 'name') { + // @ts-ignore + this.hangCounterSource.sort(compare(detail.key, detail.sort, 'string')); + } else { + // @ts-ignore + this.hangCounterSource.sort(compare(detail.key, detail.sort, 'number')); + } + this.hangCounterTbl!.recycleDataSource = this.hangCounterSource; + } +} diff --git a/ide/src/trace/database/TraceWorker.ts b/ide/src/trace/database/TraceWorker.ts index 5b7bf056..4458a59a 100644 --- a/ide/src/trace/database/TraceWorker.ts +++ b/ide/src/trace/database/TraceWorker.ts @@ -545,6 +545,7 @@ function initTraceRange(thirdMode: unknown): void { function onmessageByExecAction(e: MessageEvent): void { query(e.data.name, e.data.sql, e.data.params); let jsonArray = convertJSON(); + //console.log('onmessageByExecAction - jsonArray', jsonArray, e) // @ts-ignore self.postMessage({ id: e.data.id, @@ -1083,6 +1084,7 @@ self.onmessage = async (e: MessageEvent): Promise => { } else if (e.data.action === 'exec') { onmessageByExecAction(e); } else if (e.data.action === 'exec-proto') { + // console.log('TraceWorker - onmessage', e) onmessageByExecProtoAction(e); } else if (e.data.action === 'exec-buf') { onmessageByExecBufAction(e); diff --git a/ide/src/trace/database/data-trafic/ClockDataReceiver.ts b/ide/src/trace/database/data-trafic/ClockDataReceiver.ts index 79dc5e01..952ecc14 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,23 +164,27 @@ function arrayBufferHandler(data: unknown, res: unknown[], transfer: boolean): v // @ts-ignore value[i] = it.value; }); + + let arg1 = { + // @ts-ignore + id: data.id, + // @ts-ignore + action: data.action, + results: transfer + ? { + dur: dur.buffer, + startNS: startNS.buffer, + value: value.buffer, + filterId: filterId.buffer, + } + : {}, + len: res.length, + transfer: transfer, + }; + let arg2 = transfer ? [dur.buffer, startNS.buffer, value.buffer, filterId.buffer] : []; + (self as unknown as Worker).postMessage( - { - // @ts-ignore - id: data.id, - // @ts-ignore - action: data.action, - results: transfer - ? { - dur: dur.buffer, - startNS: startNS.buffer, - value: value.buffer, - filterId: filterId.buffer, - } - : {}, - len: res.length, - transfer: transfer, - }, - transfer ? [dur.buffer, startNS.buffer, value.buffer, filterId.buffer] : [] + arg1, + arg2 ); } 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 00000000..f9ee3d65 --- /dev/null +++ b/ide/src/trace/database/data-trafic/HangDataReceiver.ts @@ -0,0 +1,116 @@ +// 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 >= 33000000 + AND c.name LIKE 'H:Et%' + 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 00000000..4d505d6b --- /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'; + +export function realHangDataSender( + 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 => { + threadPool.submitProto( + QueryEnum.HangData, + { + pid: processId, + // + 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 ca92c457..502492bd 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 ed4cf2cd..57c549a9 100644 --- a/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts +++ b/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts @@ -79,6 +79,7 @@ import { clearMemoryCache } from './AllMemoryCache'; import { cpuFreqDataReceiver } from '../cpu/CpuFreqDataReceiver'; import { lostFrameReceiver } from './../LostFrameReceiver'; import { sliceReceiver, sliceSPTReceiver } from '../SliceReceiver'; +import { hangDataReceiver } from '../HangDataReceiver'; // @ts-ignore const traficHandlers: Map = new Map([]); // @ts-ignore @@ -101,6 +102,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 4f268ff8..28045d3c 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 00000000..bccd3e2e --- /dev/null +++ b/ide/src/trace/database/sql/Hang.sql.ts @@ -0,0 +1,59 @@ +/* + * 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 { query } from '../SqlLite'; + +export const queryRealHangData = (): Promise> => + query( + 'queryHangsData', + ` +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 c.dur >= 33000000 +GROUP BY + p.pid +`.trim() + ) + +export const queryHangFuncName = (): Promise> => + query('queryHangFuncName', + ` +SELECT + c.id as id, + c.name as name +FROM + callstack c +WHERE + c.dur >= 33000000 + 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 8151dfb3..3bbdb39f 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'; @@ -113,6 +114,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 00000000..a1cbdd59 --- /dev/null +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -0,0 +1,128 @@ +/* + * 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, isFrameContainPoint, Render } from './ProcedureWorkerCommon' +import { TraceRow } from '../../component/trace/base/TraceRow' +import { SpSystemTrace } from '../../component/SpSystemTrace' +import { HangType } from '../../component/chart/SpHangChart' + +export class HangRender extends Render { + renderMainThread( + hangReq: { + context: CanvasRenderingContext2D + useCache: boolean + type: string + index: number + processName: string + }, + 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: 5, + 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() + let s = hangReq.processName + let textMetrics = hangReq.context.measureText(s) + hangReq.context.globalAlpha = 0.8 + hangReq.context.fillStyle = '#f0f0f0' + hangReq.context.fillRect(0, 5, textMetrics.width + 8, 18) + hangReq.context.globalAlpha = 1 + hangReq.context.fillStyle = '#333' + hangReq.context.textBaseline = 'middle' + hangReq.context.fillText(s, 4, 5 + 9) + } +} + +export function HangStructOnClick(clickRowType: string, sp: SpSystemTrace): Promise { + return new Promise((resolve, reject) => { + if (clickRowType === TraceRow.ROW_TYPE_HANG && HangStruct.hoverHangStruct) { + HangStruct.selectHangStruct = HangStruct.hoverHangStruct + sp.traceSheetEL?.displayHangData(HangStruct.selectHangStruct) + sp.timerShaftEL?.modifyFlagList(undefined) + reject(new Error()) + } else { + resolve(null) + } + }) +} + +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(hangContext: CanvasRenderingContext2D, data: HangStruct): void { + if (data.frame) { + hangContext.fillStyle = HangStruct.getFrameColor(data) + hangContext.strokeStyle = HangStruct.getFrameColor(data) + + hangContext.globalAlpha = 0.6 + hangContext.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height) + if (HangStruct.isHover(data)) { + hangContext.lineWidth = 3 + hangContext.globalAlpha = 1 + hangContext.strokeRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height) + } + hangContext.globalAlpha = 1 + hangContext.lineWidth = 1 + } + } + + static isHover(data: HangStruct): boolean { + return data === HangStruct.hoverHangStruct || data === HangStruct.selectHangStruct + } +} -- Gitee