diff --git a/ide/src/trace/bean/perfStruct.ts b/ide/src/trace/bean/perfStruct.ts new file mode 100644 index 0000000000000000000000000000000000000000..80345f1cacf6ffc5905f9129755898ffa633f0fd --- /dev/null +++ b/ide/src/trace/bean/perfStruct.ts @@ -0,0 +1,55 @@ +const ROW_TYPE = 'hiperf'; + + +export class perfFireChartStruct { + thread_id:number; + name: string; + depth: number; + selfTime: number; + totalTime: number; + id: number; + + constructor( + id: number, + name: string, + depth: number, + selfTime: number, + totalTime: number, + thread_id:number, + ) { + this.id = id; + this.name = name; + this.depth = depth; + this.selfTime = selfTime; + this.totalTime = totalTime; + this.thread_id = thread_id; + } +} + +// 绘图所需树结构模板 +export class hiPerfchartFrame extends perfFireChartStruct { + startTime: number; + endTime: number; + children: Array; + isSelect: boolean = false; + line: number = 0; + column: number = 0; + thread_id: number=0; + + constructor( + id: number, + name: string, + startTime: number, + endTime: number, + totalTime: number, + depth: number, + thread_id:number, + ) { + super(id, name, depth, 0, totalTime,thread_id); + this.id = id; + this.startTime = startTime; + this.endTime = endTime; + this.thread_id = thread_id; + this.children = new Array(); + } +} \ No newline at end of file diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 8cd7d4d2883b52405c14a8c30b551ae450f7aa36..4c25011f35c890d7f918e5fdf741d1955770635f 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -103,6 +103,7 @@ import { TabPaneCounterSample } from './trace/sheet/cpu/TabPaneCounterSample.js' import { LitSearch } from './trace/search/Search.js'; import { TabPaneFlag } from './trace/timer-shaft/TabPaneFlag.js'; import { LitTabpane } from '../../base-ui/tabs/lit-tabpane.js'; +import { HiPerfCallChartStruct } from '../database/ui-worker/ProcedureWorkerHiPerfCallChart.js'; function dpr() { return window.devicePixelRatio || 1; @@ -2473,6 +2474,11 @@ export class SpSystemTrace extends BaseElement { JsCpuProfilerStruct.hoverJsCpuProfilerStruct !== null && JsCpuProfilerStruct.hoverJsCpuProfilerStruct !== undefined, ], + [TraceRow.ROW_TYPE_PERF_CALLCHART, + ()=> + HiPerfCallChartStruct.hoverPerfCallCutStruct !==null && + HiPerfCallChartStruct.hoverPerfCallCutStruct !== undefined, + ], [ TraceRow.ROW_TYPE_PURGEABLE_TOTAL_ABILITY, () => SnapshotStruct.hoverSnapshotStruct !== null && SnapshotStruct.hoverSnapshotStruct !== undefined, @@ -4406,7 +4412,7 @@ export class SpSystemTrace extends BaseElement { } if (this.tipEL) { this.tipEL.innerHTML = html; - if (row.rowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER) { + if (row.rowType === TraceRow.ROW_TYPE_JS_CPU_PROFILER ||row.rowType === TraceRow.ROW_TYPE_PERF_CALLCHART) { this.tipEL.style.maxWidth = row.clientWidth / 3 + 'px'; this.tipEL.style.wordBreak = ' break-all'; this.tipEL.style.height = 'unset'; diff --git a/ide/src/trace/component/chart/SpHiPerf.ts b/ide/src/trace/component/chart/SpHiPerf.ts index 6058aee8c386d07a023cffceea5d896983c17b63..312fb1029a7bdf74c0f210e6e05315fc4120159b 100644 --- a/ide/src/trace/component/chart/SpHiPerf.ts +++ b/ide/src/trace/component/chart/SpHiPerf.ts @@ -28,6 +28,7 @@ import { import { Utils } from '../trace/base/Utils.js'; import { PerfThread } from '../../bean/PerfProfile.js'; import { HiperfCpuRender, HiPerfCpuStruct } from '../../database/ui-worker/ProcedureWorkerHiPerfCPU.js'; +import { HiperfCallChartRender, HiPerfCallChartStruct } from '../../database/ui-worker/ProcedureWorkerHiPerfCallChart.js'; import { HiperfThreadRender, HiPerfThreadStruct } from '../../database/ui-worker/ProcedureWorkerHiPerfThread.js'; import { HiperfProcessRender, HiPerfProcessStruct } from '../../database/ui-worker/ProcedureWorkerHiPerfProcess.js'; import { info } from '../../../log/Log.js'; @@ -37,6 +38,8 @@ import { renders } from '../../database/ui-worker/ProcedureWorker.js'; import { EmptyRender } from '../../database/ui-worker/ProcedureWorkerCPU.js'; import { type HiPerfReportStruct } from '../../database/ui-worker/ProcedureWorkerHiPerfReport.js'; import { SpChartManager } from './SpChartManager.js'; +import { procedurePool } from '../../database/Procedure.js'; +import { hiPerfchartFrame } from '../../bean/perfStruct.js'; export interface ResultData { existA: boolean | null | undefined; @@ -58,6 +61,10 @@ export class SpHiPerf { private group: any; private rowList: TraceRow[] | undefined; private eventTypeList: Array<{ id: number; report_value: string }> = []; + private allCombineDataMap = new Map(); + + public perfCallDataList: any = []; + public threadDataList: any = []; constructor(trace: SpSystemTrace) { this.trace = trace; @@ -70,10 +77,14 @@ export class SpHiPerf { this.eventTypeList = await queryHiPerfEventList(); info('PerfThread Data size is: ', this.perfThreads!.length); this.group = Utils.groupBy(this.perfThreads || [], 'pid'); + Reflect.ownKeys(this.group).forEach((v, i) => { + this.threadDataList.push((this.group[v] as Array).filter((item: any) => { return item.pid === item.tid })[0]) + }) this.cpuData = await queryHiPerfCpuMergeData2(); this.maxCpuId = this.cpuData.length > 0 ? this.cpuData[0].cpu_id : -Infinity; if (this.cpuData.length > 0) { await this.initFolder(); + await this.initCallChart() await this.initCpuMerge(); await this.initCpu(); await this.initProcess(); @@ -191,6 +202,39 @@ export class SpHiPerf { this.rowList?.push(cpuMergeRow); } + // callchart泳道 + async initCallChart() { + let perfCallCutRow = TraceRow.skeleton(); + perfCallCutRow.rowId = `HiPerf-callchart`; + perfCallCutRow.index = 0; + perfCallCutRow.rowType = TraceRow.ROW_TYPE_PERF_CALLCHART; + perfCallCutRow.rowParentId = 'HiPerf'; + perfCallCutRow.rowHidden = !this.rowFolder.expansion; + perfCallCutRow.folder = false; + perfCallCutRow.name = 'callchart'; + perfCallCutRow.setAttribute('children', ''); + perfCallCutRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; + perfCallCutRow.selectChangeHandler = this.trace.selectChangeHandler; + this.rowFolder.addChildTraceRow(perfCallCutRow); + perfCallCutRow.focusHandler = (): void => { + this.trace?.displayTip( + perfCallCutRow!, + HiPerfCallChartStruct.hoverPerfCallCutStruct, + `Name: + ${HiPerfCallChartStruct.hoverPerfCallCutStruct?.name || ''}
+ Self Time: + ${HiPerfCallChartStruct.hoverPerfCallCutStruct?.totalTime || ''}
+ Event Type: + ${HiPerfCallChartStruct.hoverPerfCallCutStruct?.totalTime || ''}
` + ); + }; + this.rowList?.push(perfCallCutRow); + perfCallCutRow.findHoverStruct = () => { + HiPerfCallChartStruct.hoverPerfCallCutStruct = perfCallCutRow.getHoverStruct(); + }; + await this.setCallTotalRow(perfCallCutRow, this.cpuData, this.threadDataList); + } + async initCpu() { for (let i = 0; i <= this.maxCpuId; i++) { let perfCpuRow = TraceRow.skeleton(); @@ -335,6 +379,131 @@ export class SpHiPerf { }); } + // callchart级联单选按钮 + async setCallTotalRow(row: any, cpuData: any = Array, threadData: any = Array) { + row.addTemplateTypes('Hiperf-callchart'); + row.rowSetting = 'enable'; + row.rowSettingList = [ + { + key: "cpu", + title: 'cpu', + children: [ + ...cpuData.reverse().map( + ( + it: any + ): { + key: string; + title: string; + } => { + return { + key: `${it.cpu_id}c`, + title: `cpu${it.cpu_id}`, + }; + } + ), + ] + }, + { + key: "thread", + title: 'thread', + children: [ + ...threadData.map( + (it: any): { + key: string; + title: string; + } => { + return { + key: `${it.tid}t`, + title: `${it.threadName || 'thread'}[${it.tid}] ` + } + } + ) + ] + } + + ]; + row.onRowSettingChangeHandler = (setting: any): void => { + if (setting && setting.length > 0) { + // 0:cpu,1:thread + this.initHiPerfChartData(setting[0].indexOf('c') > -1 ? 0 : 1, Number(setting[0].indexOf('c') > -1 ? + setting[0].substring(0, setting[0].indexOf('c')) : setting[0].substring(0, setting[0].indexOf('t'))), row); + row.name = `callchart[${setting[0].indexOf('c') > -1 ? 'cpu' : 'thread'}${setting[0].substring(0, setting[0].length - 1)}]`; + } + row.clearCanvas(); + row.onThreadHandler = (useCache: any) => { + row.dataList = this.perfCallDataList; + let context: CanvasRenderingContext2D; + if (row.currentContext) { + context = row.currentContext; + } else { + context = row.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; + } + row.canvasSave(context); + (renders['Hiperf-callchart'] as HiperfCallChartRender).renderMainThread( + { + context: context, + useCache: useCache, + type: `Hiperf-callchart`, + }, + row + ); + row.canvasRestore(context); + }; + }; + } + + /* + * callchart请求数据 + */ + async initHiPerfChartData(type: number, id: number, perfCpuRow: any) { + await procedurePool.submitWithName( + 'logic0', + 'perf-fire', + [ + type, id + ], + undefined, + (res: Array) => { + let allCombineData: Array = []; + this.getAllCombineData(res, allCombineData); + this.allCombineDataMap = new Map(); + for (let data of allCombineData) { + this.allCombineDataMap.set(data.id, data); + } + // let max = Math.max(...allCombineData.map((it) => it.depth || 0)) + 1; + let max = 0; + for (let i = 0; i < allCombineData.length; i++) { + if (allCombineData[i].depth > max) { + max = allCombineData[i].depth + } else { + + } + i++; + } + let maxHeight = max * 20; + perfCpuRow!.style.height = `${maxHeight}px`; + perfCpuRow.supplier = (): Promise> => + new Promise>((resolve) => resolve(allCombineData)); + this.perfCallDataList = allCombineData + } + ) + } + + getAllCombineData( + combineData: Array, + allCombineData: Array + ): void { + for (let data of combineData) { + if (data.name != 'name') { + allCombineData.push(data); + } + if (data.children && data.children.length > 0) { + this.getAllCombineData(data.children, allCombineData); + } + } + } + + updateChartData() { this.rowList?.forEach((it) => { it.dataList = []; diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index 5852e647234ecb81188962b28f02e483ccb97ae1..0bde914f3d23ff976cf62efc3df0db7c064f65a2 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -55,6 +55,7 @@ export class TraceRow extends HTMLElement { static ROW_TYPE_NATIVE_MEMORY = 'native-memory'; static ROW_TYPE_HIPERF = 'hiperf'; static ROW_TYPE_DELIVER_INPUT_EVENT = 'DeliverInputEvent'; + static ROW_TYPE_PERF_CALLCHART = 'Hiperf-callchart'; static ROW_TYPE_HIPERF_CPU = 'hiperf-cpu'; static ROW_TYPE_HIPERF_PROCESS = 'hiperf-process'; static ROW_TYPE_HIPERF_THREAD = 'hiperf-thread'; @@ -678,7 +679,7 @@ export class TraceRow extends HTMLElement { this.funcExpand = true; this.nameEL!.onclick = () => { - if (this.rowType === TraceRow.ROW_TYPE_FUNC) { + if (this.rowType === TraceRow.ROW_TYPE_FUNC || TraceRow.ROW_TYPE_PERF_CALLCHART) { if (this.funcExpand) { this.funcMaxHeight = this.clientHeight; this.style.height = '20px'; diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts index 578e4fee2ee156c7f779e84387af37f523192b7f..285735beeea55582f91c611e3e4f06c91d770827 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts @@ -37,6 +37,25 @@ export class Msg { data: Array = []; } +export class hiperfSymbol { + id: number = 0; + startTime: number = 0; + endTime: number = 0; + cpu_id: number = 0; + depth: number = 0; + children?: Array; + callchain_id: number = 0; + thread_id:number = 0; + name:string=''; + + public clone(): hiperfSymbol { + const cloneSymbol = new hiperfSymbol(); + cloneSymbol.children = new Array(); + cloneSymbol.depth = this.depth; + return cloneSymbol; + } +} + export class MerageBean extends ChartStruct { #parentNode: MerageBean | undefined = undefined; #total = 0; @@ -596,6 +615,9 @@ export class DataCache { public perfCallChainMap: Map = new Map(); public jsCallChain: Array | undefined; public jsSymbolMap = new Map(); + public perfCallFireMap = new Map(); + public perfCallChain: Array | undefined; + public perfSymbolMap = new Map() public static getInstance(): DataCache { if (!this.instance) { diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts index 5aefb8e1eefb5995f15ef44e23817f9ce3d531fb..c4b66c101e19fdf644a845d5f9568d89b2887fb5 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts @@ -13,8 +13,9 @@ * limitations under the License. */ -import { LogicHandler, ChartStruct, convertJSON, DataCache, PerfCall } from './ProcedureLogicWorkerCommon.js'; +import { LogicHandler, ChartStruct, convertJSON, DataCache, PerfCall,hiperfSymbol } from './ProcedureLogicWorkerCommon.js'; import { PerfBottomUpStruct } from '../../bean/PerfBottomUpStruct.js'; +import { hiPerfchartFrame } from '../../bean/perfStruct.js'; const systemRuleName = '/system/'; const numRuleName = '/max/min/'; @@ -36,9 +37,11 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { currentEventId: string = ''; isAnalysis: boolean = false; isPerfBottomUp: boolean = false; + perfCallData: any[] = []; eventTypeId: string = ''; private dataCache = DataCache.getInstance(); + private samplesCpu = Array(); handle(data: any): void { this.currentEventId = data.id; @@ -48,6 +51,28 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { this.dataCache.perfCountToMs = data.params.fValue; this.initPerfFiles(); break; + case 'perf-fire': + this.perfCallData = data.params + this.initPerfFire(); + break; + case 'perf-call-chain': + if (!this.dataCache.perfCallChain || this.dataCache.perfCallChain.length === 0) { + this.dataCache.perfCallChain = convertJSON(data.params.list) || []; + this.createCallChain(); + } + this.queryCallData(this.perfCallData); + break; + // 查perf_sample表并处理 + case 'perf-sample-cpu': + // 拿到的sample的数据 + this.samplesCpu = convertJSON(data.params.list) || []; + // 处理sample数据 + self.postMessage({ + id: data.id, + action: data.action, + results: this.combinePerfSampleBycallChainId(this.samplesCpu) + }); + break; case 'perf-queryPerfFiles': let files = convertJSON(data.params.list) || []; files.forEach((file: any) => { @@ -144,6 +169,29 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { ); } + initPerfFire() { + this.clearAll(); + this.queryData( + this.currentEventId, + 'perf-call-chain', + `select name, + depth, + callchain_id from perf_callchain`, + {} + ) + } + + queryCallData(data: any) { + const sql = `SELECT id, + callchain_id, + timestamp_trace - start_ts AS timeTip, + thread_id, + cpu_id + FROM + perf_sample,trace_range where ${data[0] == 0 ? 'cpu_id=' : 'thread_id='}${data[1]}`; + this.queryData(this.currentEventId!, 'perf-sample-cpu', sql, {}); + } + initPerfThreads() { this.queryData( this.currentEventId, @@ -227,6 +275,125 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { ); } + // 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等 + combinePerfSampleBycallChainId(sampleList: Array) { + let arr: any = new Array(); + let num = undefined; + for (let i = 0; i < sampleList.length; i++) { + // 若不是相同的callchain_id,赋值 + if (sampleList[i].callchain_id != num) { + arr.push(new hiPrefSample()); + arr[arr.length - 1].children = new Array(); + arr[arr.length - 1].children[0] = new hiperfSymbol(); + arr[arr.length - 1].depth = -1; + arr[arr.length - 1].name = 'name'; + arr[arr.length - 1].callchain_id = sampleList[i].callchain_id; + arr[arr.length - 1].thread_id = sampleList[i].thread_id; + arr[arr.length - 1].id = sampleList[i].id; + if (i !== 0 && i !== sampleList.length - 1 && sampleList[i].callchain_id !== sampleList[i - 1].callchain_id && sampleList[i].callchain_id !== sampleList[i + 1].callchain_id) { + arr[arr.length - 1].startTime = sampleList[i - 1].timeTip; + } else { + arr[arr.length - 1].startTime = sampleList[i].timeTip + } + } + arr[arr.length - 1].endTime = sampleList[i].timeTip + arr[arr.length - 1].totalTime = arr[arr.length - 1].endTime - arr[arr.length - 1].startTime + num = sampleList[i].callchain_id + } + return this.combineChartData(arr); + // return arr; + } + /** + * 建立callChain每个函数的联系,设置depth跟children + */ + private createCallChain(): void { + const jsSymbolMap = this.dataCache.perfCallFireMap; + for (const item of this.dataCache.perfCallChain!) { + jsSymbolMap.set(item.callchain_id.toString() + item.depth, item); + let parentSymbol = jsSymbolMap.get(item.callchain_id.toString() + (item.depth - 1)); + if (parentSymbol) { + switch (parentSymbol.callchain_id) { + case item.callchain_id: + switch (parentSymbol.depth) { + case item.depth - 1: + parentSymbol.children = new Array(); + parentSymbol.children?.push(item) + break; + } + break; + } + } + } + } + + combineChartData(samples: any): Array { + let combineSample: any = []; + // 遍历sample表查到的数据,并且为其匹配相应的callchain数据 + for (let sample of samples) { + let stackTopSymbol = JSON.parse(JSON.stringify(this.dataCache.perfCallFireMap.get(sample.callchain_id.toString() + '0'))) || new hiperfSymbol(); + stackTopSymbol.startTime = sample.startTime; + stackTopSymbol.endTime = sample.endTime; + stackTopSymbol.totalTime = sample.totalTime; + stackTopSymbol.thread_id = sample.thread_id; + stackTopSymbol.cpu_id = sample.thread_id; + this.setDur(stackTopSymbol) + sample.children = new Array(); + sample.children.push(stackTopSymbol) + // 每一项都和combineSample对比 + if (combineSample.length === 0) { + combineSample.push(sample) + } else { + if (this.perfCallData[0] === 0) { + if (combineSample[combineSample.length - 1].thread_id === sample.thread_id) { + this.combinePerfCallData(combineSample[combineSample.length - 1], sample) + } else { combineSample.push(sample) }; + } else { + if (combineSample[combineSample.length - 1].cpu_id === sample.cpu_id) { + this.combinePerfCallData(combineSample[combineSample.length - 1], sample) + } else { combineSample.push(sample) }; + } + } + } + return combineSample; + } + + // 递归设置dur,startTime,endTime + setDur(data: any) { + if (data.children && data.children.length > 0) { + data.children[0].totalTime = data.totalTime; + data.children[0].startTime = data.startTime; + data.children[0].endTime = data.endTime; + data.children[0].thread_id = data.thread_id; + data.children[0].cpu_id = data.cpu_id; + this.setDur(data.children[0]) + } else { + return + } + } + + // hiperf火焰图合并逻辑 + combinePerfCallData(data1: any, data2: any) { + if (data1.depth === data2.depth && data1.name === data2.name) { + data1.endTime = data2.endTime; + data1.totalTime = data1.endTime - data1.startTime; + if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) { + if (data1.children[data1.children.length - 1].depth === data2.children[0].depth && data1.children[data1.children.length - 1].name !== data2.children[0].name) { + data1.children.push(data2.children[0]) + } else { + this.combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]); + } + } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) { + data1.endTime = data2.endTime; + data1.totalTime = data1.endTime - data1.endTime; + data1.children = new Array(); + data1.children.push(data2.children[0]); + } else { + } + } + else { } + return + } + clearAll() { this.filesData = {}; this.samplesData = {}; @@ -1190,3 +1357,18 @@ export function timeMsFormat2p(ns: number) { } return perfResult; } + + +class hiPrefSample { + name: string = ""; + depth: number = 0; + callchain_id: number = 0; + totalTime: number = 0; + thread_id: number = 0; + id: number = 0; + startTime: number = 0; + endTime: number = 0; + timeTip: number = 0; + cpu_id: number = 0; + stack?: Array; +} \ 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 bfeef7d29c5227d57ad61572dfb30c8b86b15ad9..7ae2190c07e5c0fc5b23d65052d5555c7d5afb5f 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorker.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorker.ts @@ -56,6 +56,7 @@ import { FrameSpacingRender } from './ProcedureWorkerFrameSpacing.js'; import { JsCpuProfilerRender } from './ProcedureWorkerCpuProfiler.js'; import { SnapshotRender } from './ProcedureWorkerSnapshot.js'; import { LogRender } from './ProcedureWorkerLog.js'; +import { HiperfCallChartRender } from './ProcedureWorkerHiPerfCallChart.js'; let dataList: any = {}; let dataList2: any = {}; @@ -86,6 +87,7 @@ export let renders: any = { 'HiPerf-Group': new EmptyRender(), monitorGroup: new EmptyRender(), 'HiPerf-Cpu': new HiperfCpuRender(), + 'Hiperf-callchart':new HiperfCallChartRender(), 'HiPerf-Process': new HiperfProcessRender(), 'HiPerf-Thread': new HiperfThreadRender(), 'HiPerf-Report-Event': new HiperfEventRender(), diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHiPerfCallChart.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHiPerfCallChart.ts new file mode 100644 index 0000000000000000000000000000000000000000..53e0ff6cba4135e463962b1bad8f4f444a4519f4 --- /dev/null +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHiPerfCallChart.ts @@ -0,0 +1,202 @@ +/* + * 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 { ColorUtils } from '../../component/trace/base/ColorUtils.js'; +import { + BaseStruct, + type Rect, + ns2x, + drawString, + Render, + isFrameContainPoint +} from './ProcedureWorkerCommon.js'; +import { TraceRow } from '../../component/trace/base/TraceRow.js'; +import { HiPerfThreadStruct } from './ProcedureWorkerHiPerfThread.js'; +import { hiPerfchartFrame } from '../../bean/perfStruct.js'; + +export class HiperfCallChartRender extends Render { + renderMainThread(req: any, row: TraceRow): void { + let list = row.dataList; + let filter = row.dataListCache; + hiperf( + list, + filter, + TraceRow.range!.startNS, + TraceRow.range!.endNS, + TraceRow.range!.totalNS, + row.frame, + req.useCache || !TraceRow.range!.refresh + ); + req.context.beginPath();; + let find = false; + let offset = 5; + for (let re of filter) { + HiPerfCallChartStruct.draw(req.context, re); + if (row.isHover) { + if (re.endTime - re.startTime === 0 || + re.endTime - re.startTime == null || + re.endTime - re.startTime === undefined) { + if (re.frame && + row.hoverX >= re.frame.x - offset && + row.hoverX <= re.frame.x + re.frame.width + offset && + row.hoverY >= re.frame.y && + row.hoverY <= re.frame.y + re.frame.height) { + HiPerfCallChartStruct.hoverPerfCallCutStruct = re; + find = true; + } + } else { + if (re.frame && isFrameContainPoint(re.frame, row.hoverX, row.hoverY)) { + HiPerfCallChartStruct.hoverPerfCallCutStruct = re; + find = true + } + } + } + if (!find && row.isHover) { + HiPerfCallChartStruct.hoverPerfCallCutStruct = undefined + } + } + req.context.closePath(); + } +} + +// 火焰图数据模板 +let padding = 1; +export class HiPerfCallChartStruct extends BaseStruct { + static selectStruct: HiPerfCallChartStruct | undefined; + static hoverPerfCallCutStruct: HiPerfCallChartStruct | undefined; + id: number = 0; + name: string = ''; + startTime: number = 0; + endTime: number = 0; + depth: number = 0; + children!: Array; + isSelect: boolean = false; + totalTime: number = 0; + callchain_id:number = 0; + + static setPerfFrame( + hiPerfNode: any, + startNS: number, + endNS: number, + totalNS: number, + frame: Rect + ): void { + let x1: number, x2: number; + if ((hiPerfNode.startTime || 0) > startNS && (hiPerfNode.startTime || 0) < endNS) { + x1 = ns2x(hiPerfNode.startTime || 0, startNS, endNS, totalNS, frame); + } else { + x1 = 0; + } + if ( + (hiPerfNode.startTime || 0) + (hiPerfNode.totalTime || 0) > startNS && + (hiPerfNode.startTime || 0) + (hiPerfNode.totalTime || 0) < endNS + ) { + x2 = ns2x( + (hiPerfNode.startTime || 0) + (hiPerfNode.totalTime || 0), + startNS, + endNS, + totalNS, + frame + ); + } else { + x2 = frame.width; + } + if (!hiPerfNode.frame) { + hiPerfNode.frame = {}; + } + let getV: number = x2 - x1 < 1 ? 1 : x2 - x1; + hiPerfNode.frame.x = Math.floor(x1); + hiPerfNode.frame.y = hiPerfNode.depth * 20; + hiPerfNode.frame.width = Math.ceil(getV); + hiPerfNode.frame.height = 20; + } + + static draw(ctx: CanvasRenderingContext2D, data: HiPerfCallChartStruct): void { + if (data.frame) { + if (data.endTime - data.startTime === undefined || data.endTime - data.startTime === null) { + } else { + ctx.globalAlpha = 1; + if (data.name === '(program)') { + ctx.fillStyle = '#ccc'; + } else if (data.name === '(idle)') { + ctx.fillStyle = '#f0f0f0'; + } else { + ctx.fillStyle = + ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)]; + } + let miniHeight = 20; + if (HiPerfCallChartStruct.hoverPerfCallCutStruct && data === HiPerfCallChartStruct.hoverPerfCallCutStruct) { + ctx.globalAlpha = 0.7; + } + ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2); + if (data.frame.width > 8) { + ctx.lineWidth = 1; + ctx.fillStyle = '#fff'; + ctx.textBaseline = 'middle'; + drawString(ctx, `${data.name || ''}`, 4, data.frame, data); + } + if (data === HiPerfCallChartStruct.selectStruct) { + ctx.strokeStyle = '#000'; + ctx.lineWidth = 2; + ctx.strokeRect( + data.frame.x + 1, + data.frame.y + 1, + data.frame.width - 2, + miniHeight - padding * 2 - 2 + ); + } + } + } + } +} + +export function hiperf( + list: Array, + filter: Array, + startNS: number, + endNS: number, + totalNS: number, + frame: Rect, + use: boolean +): void { + if (use && filter.length > 0) { + for (let i = 0, len = filter.length; i < len; i++) { + if ((filter[i].startTime || 0) + (filter[i].totalTime || 0) >= startNS && (filter[i].startTime || 0) <= endNS) { + HiPerfCallChartStruct.setPerfFrame(filter[i], startNS, endNS, totalNS, frame); + } else { + filter[i].frame = null; + } + } + return; + } + filter.length = 0; + if (list) { + let groups = list + .filter((it) => (it.startTime ?? 0) + (it.totalTime ?? 0) >= startNS && (it.startTime ?? 0) <= endNS) + .map((it) => { + HiPerfCallChartStruct.setPerfFrame(it, startNS, endNS, totalNS, frame); + return it; + }) + .reduce((pre, current) => { + (pre[`${current.frame.x}-${current.depth}`] = pre[`${current.frame.x}-${current.depth}`] || []).push(current); + return pre; + }, {}); + Reflect.ownKeys(groups).map((kv) => { + // 从小到大排序 + let arr = groups[kv].sort((a: hiPerfchartFrame, b: hiPerfchartFrame) => b.totalTime - a.totalTime); + filter.push(arr[0]); + }); + } +}