From 14798d02b2d7756c041e3367f45e0d7153924d27 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 25 Apr 2025 11:41:08 +0800 Subject: [PATCH 01/66] =?UTF-8?q?feat:1=E3=80=81=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=9C=8D=E5=8A=A1=E6=8A=93=E5=8F=96longtrace?= =?UTF-8?q?=202=E3=80=81=E6=8A=93=E5=8F=96longtrace=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/component/SpRecordTrace.ts | 82 +++++++++++++++++++ .../component/setting/SpRecordSetting.ts | 2 + 2 files changed, 84 insertions(+) diff --git a/ide/src/trace/component/SpRecordTrace.ts b/ide/src/trace/component/SpRecordTrace.ts index dfb8d1b8..457e57ca 100644 --- a/ide/src/trace/component/SpRecordTrace.ts +++ b/ide/src/trace/component/SpRecordTrace.ts @@ -118,6 +118,10 @@ export class SpRecordTrace extends BaseElement { private stop = 'StopRecord'; private nowChildItem: HTMLElement | undefined; private longTraceList: Array = []; + private fileList: Array<{ + fileName: string, + file: File + }> = []; private refreshDeviceTimer: number | undefined; private hintEl: HTMLSpanElement | undefined; private selectedTemplate: Map = new Map(); @@ -532,6 +536,7 @@ export class SpRecordTrace extends BaseElement { let isCheckSnapshot = this.spArkTs!.radioBoxType === 0 ? true : false; // 是否check snapshot let isCheckTimeLine = this.spArkTs!.radioBoxType === 1 ? true : false; // 是否 check timeline + let isLongTrace = SpApplication.isLongTrace; let maxDur = this.recordSetting!.maxDur; // 抓取trace的时长 let snapShotDur = this.recordSetting!.snapShot;//截图 SpRecordTrace.snapShotDuration = snapShotDur; @@ -542,6 +547,7 @@ export class SpRecordTrace extends BaseElement { let enableCpuProfiler = this.spArkTs!.isStartCpuProfiler; let params: unknown = { + isLongTrace: isLongTrace, isRecordArkTs: isRecordArkTs, isRecordHitrace: isRecordHitrace, type: '', @@ -613,12 +619,88 @@ export class SpRecordTrace extends BaseElement { this.litSearch!.setPercent('Tracing htrace down', -1); } else if (cmd === 8) { this.litSearch!.setPercent('Downloading Hitrace file...', -1); + } else if (cmd === 9) {// @ts-ignore + let re = JSON.parse(new TextDecoder('utf-8').decode(result)); + let binaryString = window.atob(re.data); + let len = binaryString.length; + let bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + re.data = bytes.buffer; + let fileInfo = { + fileName: re.fileName, + file: new File([re.data], re.fileName) + }; + this.fileList.push(fileInfo); + this.longTraceList.push(fileInfo.fileName); + if (this.fileList.length === re.total) { + this.openLongTraceHandle(); + } } }; WebSocketManager.getInstance()!.registerMessageListener(TypeConstants.ARKTS_TYPE, onmessageCallBack, this.eventCallBack); WebSocketManager.getInstance()!.sendMessage(TypeConstants.ARKTS_TYPE, 1, encoder.encode(JSON.stringify(params))); }; + async openLongTraceHandle() { + this.fileList.sort((a, b) => { + const getNumber = (name: string) => { + const match = name.match(/_(\d+)\.htrace$/); + return match ? parseInt(match[1]) : 0; + }; + return getNumber(a.fileName) - getNumber(b.fileName); + }); + let timStamp = new Date().getTime(); + this.sp!.longTraceHeadMessageList = []; + for (const fileInfo of this.fileList) { + await this.saveLongTrace(fileInfo.file, timStamp); + } + await this.openLongTrace(timStamp); + this.fileList = []; + this.longTraceList = []; + } + + async saveLongTrace(file: File, timStamp: number) { + let traceTypePage = this.getLongTraceTypePage(); + let types = this.sp!.fileTypeList.filter(type => + file.name.toLowerCase().includes(type.toLowerCase()) + ); + let pageNumber = 0; + let fileType = types[0] || 'trace'; + if (types.length === 0) { + let searchNumber = Number( + file.name.substring( + file.name.lastIndexOf('_') + 1, + file.name.lastIndexOf('.') + ) + ) - 1; + pageNumber = traceTypePage.lastIndexOf(searchNumber); + } + this.litSearch!.setPercent(`downloading ${fileType} file`, 101); + await this.saveIndexDBByLongTrace(file, fileType, pageNumber, timStamp); + } + + async openLongTrace(timStamp: number) { + let main = this.parentNode!.parentNode!.querySelector('lit-main-menu') as LitMainMenu; + let children = main.menus as Array; + let child = children[1].children as Array; + let fileHandler = child[0].clickHandler; + if (fileHandler && !SpRecordTrace.cancelRecord) { + this.freshConfigMenuDisable(false); + this.freshMenuDisable(false); + this.buttonDisable(false); + this.recordButtonDisable(false); + fileHandler({ + detail: { + timeStamp: timStamp + } + }, true); + } else { + SpRecordTrace.cancelRecord = false; + } + } + recordTempAddProbe = (ev: CustomEventInit<{ elementId: string }>): void => { if ( FlagsConfig.DEFAULT_CONFIG.find((flagItem) => { diff --git a/ide/src/trace/component/setting/SpRecordSetting.ts b/ide/src/trace/component/setting/SpRecordSetting.ts index 6973ae7a..6ed9c65f 100644 --- a/ide/src/trace/component/setting/SpRecordSetting.ts +++ b/ide/src/trace/component/setting/SpRecordSetting.ts @@ -244,12 +244,14 @@ export class SpRecordSetting extends BaseElement { rootEl.removeChild(longTraceMaxSlide); } this.outputPath!.value = 'hiprofiler_data.htrace'; + this.snapShotNumber!.style.display = 'grid'; } private longTraceModelRadioHandler(rootEl: HTMLDivElement, longTraceMaxSlide: HTMLDivElement): void { SpApplication.isLongTrace = true; rootEl.appendChild(longTraceMaxSlide); this.outputPath!.value = 'long_trace'; + this.snapShotNumber!.style.display = 'none'; } private maxSizeInputHandler(maxSizeSliders: LitSlider, maxSizeParentElement: Element): void { -- Gitee From 7d4622a5231b2bd39ee51b82f39f98af86797427 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 21 Apr 2025 17:23:26 +0800 Subject: [PATCH 02/66] =?UTF-8?q?fix:=E5=8E=BB=E6=8E=89=E7=BB=98=E5=88=B6V?= =?UTF-8?q?M=20Tracker=E9=99=90=E5=88=B6=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- ide/src/trace/component/chart/SpVmTrackerChart.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ide/src/trace/component/chart/SpVmTrackerChart.ts b/ide/src/trace/component/chart/SpVmTrackerChart.ts index da031c88..c6a4b931 100644 --- a/ide/src/trace/component/chart/SpVmTrackerChart.ts +++ b/ide/src/trace/component/chart/SpVmTrackerChart.ts @@ -78,10 +78,6 @@ export class VmTrackerChart { } } } - const result = await querySmapsExits(); - if (result.length <= 0) { - return; - } await this.initVmTrackerFolder(); await this.initSMapsFolder(); const rowNameList: Array = ['Dirty', 'Swapped', 'RSS', 'PSS', 'USS']; -- Gitee From 351cb21a3d737fd29163ca819cf8e0396a8af127 Mon Sep 17 00:00:00 2001 From: zhangzhuozhou Date: Tue, 22 Apr 2025 16:02:12 +0800 Subject: [PATCH 03/66] =?UTF-8?q?feat:trace=5Fstreamer=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=B2=BE=E7=AE=80=E5=90=8E=E7=9A=84CPU,?= =?UTF-8?q?=E5=86=85=E5=AD=98=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzhuozhou --- .../pbreader_cpu_data_parser.cpp | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/trace_streamer/src/parser/pbreader_parser/cpu_data_parser/pbreader_cpu_data_parser.cpp b/trace_streamer/src/parser/pbreader_parser/cpu_data_parser/pbreader_cpu_data_parser.cpp index a941cc36..0e16e304 100644 --- a/trace_streamer/src/parser/pbreader_parser/cpu_data_parser/pbreader_cpu_data_parser.cpp +++ b/trace_streamer/src/parser/pbreader_parser/cpu_data_parser/pbreader_cpu_data_parser.cpp @@ -31,21 +31,19 @@ PbreaderCpuDataParser::~PbreaderCpuDataParser() void PbreaderCpuDataParser::Parse(ProtoReader::BytesView tracePacket, uint64_t ts) { ProtoReader::CpuData_Reader cpuData(tracePacket.data_, tracePacket.size_); - if (!cpuData.has_cpu_usage_info() && !cpuData.has_thread_info()) { + if (!cpuData.has_cpu_usage_info()) { return; } - if (cpuData.has_cpu_usage_info()) { - auto userLoad = cpuData.user_load(); - auto sysLoad = cpuData.sys_load(); - auto process_num = cpuData.process_num(); - ts = streamFilters_->clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, ts); - UpdatePluginTimeRange(TS_CLOCK_REALTIME, ts, ts); - auto cpuUsage = std::make_unique(); - streamFilters_->statFilter_->IncreaseStat(TRACE_CPU_USAGE, STAT_EVENT_RECEIVED); - cpuUsage->SetCpuUsage(ts); - cpuUsage->SetExtInfo(cpuData.total_load(), userLoad, sysLoad, process_num); - cpuData_.push_back(std::move(cpuUsage)); - } + auto userLoad = cpuData.user_load(); + auto sysLoad = cpuData.sys_load(); + auto process_num = cpuData.process_num(); + ts = streamFilters_->clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, ts); + UpdatePluginTimeRange(TS_CLOCK_REALTIME, ts, ts); + auto cpuUsage = std::make_unique(); + streamFilters_->statFilter_->IncreaseStat(TRACE_CPU_USAGE, STAT_EVENT_RECEIVED); + cpuUsage->SetCpuUsage(ts); + cpuUsage->SetExtInfo(cpuData.total_load(), userLoad, sysLoad, process_num); + cpuData_.push_back(std::move(cpuUsage)); } void PbreaderCpuDataParser::Finish() { -- Gitee From a629f6db6fe0d3fc7bf81bac4a904408808372b9 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Tue, 6 May 2025 11:17:21 +0800 Subject: [PATCH 04/66] =?UTF-8?q?rm001=20ts=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- ide/src/base-ui/icon.svg | 17 ++++ ide/src/trace/bean/BoxSelection.ts | 1 + ide/src/trace/component/SpSystemTrace.init.ts | 1 + .../trace/component/trace/base/TraceSheet.ts | 99 ++++++++++++++++--- ide/src/trace/component/trace/base/Utils.ts | 1 + .../sheet/hiperf/TabPanePerfAnalysis.html.ts | 13 +++ .../trace/sheet/hiperf/TabPanePerfAnalysis.ts | 71 +++++++++++++ .../trace/sheet/hiperf/TabPerfFuncAsm.ts | 11 ++- 8 files changed, 200 insertions(+), 14 deletions(-) diff --git a/ide/src/base-ui/icon.svg b/ide/src/base-ui/icon.svg index e93c2d8b..21b0b9e5 100644 --- a/ide/src/base-ui/icon.svg +++ b/ide/src/base-ui/icon.svg @@ -553,4 +553,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index c83bb345..22ecff19 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -164,6 +164,7 @@ export class SelectionParam { hiSysEvents: Array = []; sampleData: Array = []; gpuCounter: Array = []; + isImportSo: boolean = false; // @ts-ignore pushSampleData(it: TraceRow): void { diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 69a4599b..ba1663de 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -661,6 +661,7 @@ function selectHandlerRefreshCheckBox(sp: SpSystemTrace, rows: Array>): void { + Utils.isRangeSelectRefresh = true; let selection = new SelectionParam(); selection.traceId = Utils.currentSelectTrace; selection.cpuStateRowsId = sp.stateRowsId; diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index c1646518..aa0ff8ee 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -119,6 +119,7 @@ export class TraceSheet extends BaseElement { private switchDiv: LitPopover | undefined | null; private processTree: LitTree | undefined | null; private importDiv: HTMLDivElement | undefined | null; + private symbolDiv: HTMLDivElement | undefined | null; private exportBt: LitIcon | undefined | null; private nav: HTMLDivElement | undefined | null; private tabs: HTMLDivElement | undefined | null; @@ -195,6 +196,7 @@ export class TraceSheet extends BaseElement { e.preventDefault(); }); this.importDiv = this.shadowRoot?.querySelector('#import_div'); + this.symbolDiv = this.shadowRoot?.querySelector('#symbol_div'); this.switchDiv = this.shadowRoot?.querySelector('#select-process'); this.processTree = this.shadowRoot?.querySelector('#processTree'); this.optionsDiv = this.shadowRoot?.querySelector('#options'); @@ -609,19 +611,32 @@ export class TraceSheet extends BaseElement { let importFileBt: HTMLInputElement | undefined | null = this.shadowRoot?.querySelector('#import-file'); importFileBt!.addEventListener('change', (event): void => { - let files = importFileBt?.files; - if (files) { - let fileList: Array = []; - for (let file of files) { - fileList.push(file); + WebSocketManager.instance = null; + WebSocketManager.getInstance(); + let timerOut = window.setTimeout(() => { + window.clearTimeout(timerOut); + let errorTipHtml = document.querySelector('body > sp-application')?.shadowRoot?.querySelector('#sp-system-trace') + ?.shadowRoot?.querySelector('div > trace-sheet')?.shadowRoot?.querySelector('#box-perf-analysis > tabpane-perf-analysis')?.shadowRoot?.querySelector('#SO-err-tips'); + let connected = document.querySelector('body > sp-application') + ?.shadowRoot?.querySelector('#main-menu')?.shadowRoot?.querySelector('div.bottom > div.extend_connect') as HTMLDivElement; + if (connected && connected.style.backgroundColor !== 'green') { + errorTipHtml!.innerHTML = 'Please check if the extension service is enabled and try again!'; + importFileBt!.files = null; + importFileBt!.value = ''; + return; } - if (fileList.length > 0) { - importFileBt!.disabled = true; - this.loadSoComplete = false; - window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Import So File' }); - this.uploadSoOrAN(fileList).then(r => { - // @ts-ignore - document.querySelector('body > sp-application').shadowRoot.querySelector('#sp-system-trace').shadowRoot.querySelector('div > trace-sheet').shadowRoot.querySelector('#box-perf-analysis > tabpane-perf-analysis').shadowRoot.querySelector('#SO-err-tips')?.innerHTML = ''; + let files = importFileBt?.files; + if (files) { + let fileList: Array = []; + for (let file of files) { + fileList.push(file); + } + if (fileList.length > 0) { + importFileBt!.disabled = true; + this.loadSoComplete = false; + window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Import So File' }); + this.uploadSoOrAN(fileList).then(r => { + errorTipHtml!.innerHTML = ''; let soFileList = fileList.filter(item => !item.name.includes('.an')); if (soFileList.length === 0) { window.publish(window.SmartEvent.UI.UploadSOFile, {}); @@ -653,7 +668,57 @@ export class TraceSheet extends BaseElement { } importFileBt!.files = null; importFileBt!.value = ''; + }, 500) }); + this.addClickEventToSoSymbolImport(); + } + + private addClickEventToSoSymbolImport(): void{ + let symbolBt: HTMLInputElement | undefined | null = + this.shadowRoot?.querySelector('#so-symbolization'); + symbolBt!.addEventListener('change', (event): void => { + let files = symbolBt?.files; + if (files) { + let fileList: Array = []; + for (let file of files) { + fileList.push(file); + } + if (fileList.length > 0) { + symbolBt!.disabled = true; + window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Import So File' }); + // @ts-ignore + document.querySelector('body > sp-application').shadowRoot.querySelector('#sp-system-trace').shadowRoot.querySelector('div > trace-sheet').shadowRoot.querySelector('#box-perf-analysis > tabpane-perf-analysis').shadowRoot.querySelector('#SO-err-tips')?.innerHTML = ''; + let soFileList = fileList.filter(item => item.name.includes('.so')); + if (soFileList.length === 0) { + window.publish(window.SmartEvent.UI.UploadSOFile, {}); + symbolBt!.disabled = false; + return; + } + threadPool.submit( + 'upload-so', + '', + soFileList, + (res: unknown) => { + symbolBt!.disabled = false; + setTimeout(() => { + // @ts-ignore + if (res.result === 'ok') { + window.publish(window.SmartEvent.UI.UploadSOFile, {}); + } else { + // @ts-ignore + const failedList = res.failedArray.join(','); + window.publish(window.SmartEvent.UI.Error, `parse so file ${failedList} failed!`); + } + }, 500); + }, + 'upload-so' + ); + } + fileList.length = 0; + } + symbolBt!.files = null; + symbolBt!.value = ''; + }) } private async uploadSoOrAN(fileList: Array): Promise { @@ -839,6 +904,13 @@ export class TraceSheet extends BaseElement {
+
+
+ + @@ -1252,6 +1324,7 @@ export class TraceSheet extends BaseElement { param.nativeMemoryCurrentIPid = ipid; } } + param.isImportSo = true; this.rangeSelect(param, true); return true; } else { @@ -1274,8 +1347,10 @@ export class TraceSheet extends BaseElement { selection.threadIds.length > 0) ) { this.importDiv!.style.display = 'flex'; + this.symbolDiv!.style.display = 'flex'; } else { this.importDiv!.style.display = 'none'; + this.symbolDiv!.style.display = 'none'; } } isProcessEqual(treeData: Array<{ pid: number; ipid: number }>): boolean { diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index e9e4435a..dc406525 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -23,6 +23,7 @@ export class Utils { static currentSelectTrace: string | null | undefined; static currentTraceMode: TraceMode = TraceMode.NORMAL; static distributedTrace: string[] = []; + static isRangeSelectRefresh: boolean = false; static DMAFENCECAT_MAP: Map< number, { diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.html.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.html.ts index 02249b1e..787aa7d7 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.html.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.html.ts @@ -66,8 +66,21 @@ export const TabPanePerfAnalysisHtml = ` width: 100%; height: 20px; } +@keyframes textGrowth { + 0% { + font-size: 16px; + transform: scale(0.9); + } + 100% { + font-size: 18px; + transform: scale(1.0); + } +} #SO-err-tips{ color:red; + animation: textGrowth 2.0s ease-in-out infinite alternate; + will-change: transform, font-size; + transform: translateZ(0); }
diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts index c04d2ce9..ba92636a 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts @@ -68,13 +68,21 @@ export class TabPanePerfAnalysis extends BaseElement { private isComplete: boolean = true; private currentSelectionParam: SelectionParam | undefined | null; static tabLoadingList: Array = []; + private vaddrList: Array = []; private selectedTabfileName: string = ''; private clickFuncVaddrList: Array = []; private functionListener!: Function | undefined | null; private currentSoName: string = ''; + private currentProcessItem: unknown; + private currentThreadItem: unknown; + private currentLibrayItem: unknown; set data(val: SelectionParam) { + if (val.isImportSo && this.currentLevel > 0) { + this.disableHomeRedirectAfterSoLoad(val); + return; + } if (val === this.currentSelection) { this.pidData.unshift(this.allProcessCount); this.perfTableProcess!.recycleDataSource = this.pidData; @@ -107,6 +115,66 @@ export class TabPanePerfAnalysis extends BaseElement { } } + private disableHomeRedirectAfterSoLoad(val: SelectionParam): void { + this.getDataByWorker(val, (results: unknown) => { + this.isComplete = true; + // @ts-ignore + this.processData = results; + // @ts-ignore + if (this.currentLevel === 3) { + this.reset(this.tableFunction!, true); + this.getHiperfFunction(this.currentLibrayItem); + let title = ''; + if (this.processName.length > 0) { + title += `${this.processName} / `; + } + if (this.threadName.length > 0) { + title += `${this.threadName} / `; + } + if (this.currentSoName.length > 0) { + title += this.currentSoName; + } + this.titleEl!.textContent = title; + this.perfAnalysisPie?.hideTip(); + this.selectedTabfileName = this.currentSoName; + } else if (this.currentLevel === 1) { + if (this.hideThreadCheckBox!.checked) { + this.hideThread(this.currentProcessItem); + } else { + this.reset(this.perfTableThread!, true); + this.getHiperfThread(this.currentProcessItem, val); + } + // @ts-ignore + this.titleEl!.textContent = this.currentProcessItem.tableName; + // @ts-ignore + this.processName = this.currentProcessItem.tableName; + this.perfAnalysisPie?.hideTip(); + } else if (this.currentLevel === 2) { + this.reset(this.perfTableSo!, true); + this.getHiperfSo(this.currentThreadItem, val); + let pName = this.processName; + // @ts-ignore + if (this.processName.length > 0 && this.currentThreadItem.tableName.length > 0) { + pName = `${this.processName} / `; + } + // @ts-ignore + this.titleEl!.textContent = pName + this.currentThreadItem.tableName; + // @ts-ignore + this.threadName = this.currentThreadItem.tableName; + this.perfAnalysisPie?.hideTip(); + } + const args = [ + { + funcName: 'getVaddrToFile', + funcArgs: [val], + }, + ]; + procedurePool.submitWithName('logic0', 'perf-vaddr', args, undefined, (results: Array) => { + this.vaddrList = results; + }); + }); + } + private initPerfTableListener(): void { for (let perfTable of this.tableArray!) { let querySelector = perfTable.shadowRoot?.querySelector('.table'); @@ -407,6 +475,7 @@ export class TabPanePerfAnalysis extends BaseElement { } private perfProcessLevelClickEvent(it: unknown, val: SelectionParam): void { + this.currentProcessItem = it; if (this.hideThreadCheckBox!.checked) { this.hideThread(it); this.showAssignLevel(this.perfTableSo!, this.perfTableProcess!, 1, this.soData); @@ -478,6 +547,7 @@ export class TabPanePerfAnalysis extends BaseElement { } private perfThreadLevelClickEvent(it: unknown, val: SelectionParam): void { + this.currentThreadItem = it; this.reset(this.perfTableSo!, true); this.showAssignLevel(this.perfTableSo!, this.perfTableThread!, 2, this.soData); this.getHiperfSo(it, val); @@ -556,6 +626,7 @@ export class TabPanePerfAnalysis extends BaseElement { } private perfSoLevelClickEvent(it: unknown): void { + this.currentLibrayItem = it; this.reset(this.tableFunction!, true); this.showAssignLevel(this.tableFunction!, this.perfTableSo!, 3, this.functionData); // @ts-ignore diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts index b0db75e2..25aed797 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPerfFuncAsm.ts @@ -13,6 +13,7 @@ * limitations under the License. */ import { TabPerfFuncAsmHtml } from './TabPerfFuncAsm.html'; +import { Utils } from '../../base/Utils'; import { BaseElement, element } from '../../../../../base-ui/BaseElement'; import { LitTable } from '../../../../../base-ui/table/lit-table'; import { @@ -140,6 +141,9 @@ export class TabPerfFuncAsm extends BaseElement { } set data(data: PerfFunctionAsmParam) { + if (Utils.isRangeSelectRefresh) { + this.functionName = ''; + } if (this.functionName === data.functionName || data.functionName === undefined) { return; } @@ -151,6 +155,7 @@ export class TabPerfFuncAsm extends BaseElement { this.totalCount = data.totalCount; this.updateTotalCount(); this.showLoading(); + Utils.isRangeSelectRefresh = false; // @ts-ignore const vaddrInFile = data.vaddrList[0].vaddrInFile; // 1. 先转成 BigInt @@ -256,8 +261,10 @@ export class TabPerfFuncAsm extends BaseElement { private calcutelateShowUpData(): void { this.funcSampleMap.forEach((selfCount, offsetToVaddr) => { let instructionPosition = offsetToVaddr / 4; - this.formattedAsmIntructionArray[instructionPosition].selfcount = selfCount; - this.formattedAsmIntructionArray[instructionPosition].percent = Math.round((selfCount / this.totalCount) * 10000) / 100; + if (this.formattedAsmIntructionArray[instructionPosition]) { + this.formattedAsmIntructionArray[instructionPosition].selfcount = selfCount; + this.formattedAsmIntructionArray[instructionPosition].percent = Math.round((selfCount / this.totalCount) * 10000) / 100; + } }); this.originalShowUpData = this.formattedAsmIntructionArray; } -- Gitee From 7f313e9578f8b6253b621ae04ab8723781c5fc2f Mon Sep 17 00:00:00 2001 From: JustinYT Date: Thu, 8 May 2025 18:02:50 +0800 Subject: [PATCH 05/66] adapte syscall event parser. Signed-off-by: JustinYT --- trace_streamer/doc/des_tables.md | 18 ++--- trace_streamer/src/filter/BUILD.gn | 1 + trace_streamer/src/filter/syscall_filter.cpp | 57 +++++++++++++++ trace_streamer/src/filter/syscall_filter.h | 43 +++++++++++ .../htrace_parser/htrace_event_parser.cpp | 11 +-- .../bytrace_parser/bytrace_event_parser.cpp | 71 +++++++++++++++++++ .../bytrace_parser/bytrace_event_parser.h | 2 + .../src/table/ftrace/system_call_table.cpp | 31 ++++---- .../trace_stdtype/ftrace/syscall_stdtype.cpp | 14 ++-- .../trace_stdtype/ftrace/syscall_stdtype.h | 46 +++++++----- .../trace_streamer/trace_streamer_filters.cpp | 2 + .../trace_streamer/trace_streamer_filters.h | 2 + .../trace_streamer_selector.cpp | 2 + trace_streamer/src/version.cpp | 4 +- 14 files changed, 253 insertions(+), 51 deletions(-) create mode 100644 trace_streamer/src/filter/syscall_filter.cpp create mode 100644 trace_streamer/src/filter/syscall_filter.h diff --git a/trace_streamer/doc/des_tables.md b/trace_streamer/doc/des_tables.md index fa1e90f3..22fd158c 100755 --- a/trace_streamer/doc/des_tables.md +++ b/trace_streamer/doc/des_tables.md @@ -1469,19 +1469,21 @@ source_arg_set_id: 同一个source_arg_set_id代表一组数据,一般取得 #### 表结构 | Columns Name | SQL TYPE | |---- |---- | -|syscall_num |INT | -|type |TEXT | -|ipid |INT | +|syscall_number|INT | |ts |INT | +|dur |INT | +|itid |INT | +|args |TEXT | |ret |INT | #### 表描述 记录用户空间函数与内核空间函数相互调用记录。 #### 相关字段描述 -- syscall_num:系统调用的序号 -- type:固定取值:enter或者exit -- ipid:线程所属的进程ID -- ts:时间戳 -- ret:返回值,在type为exit时有效 +- syscall_number:系统调用的序号 +- ts:时间戳 +- dur:持续时间,记录系统调用的执行时间 +- itid: 记录发起调用的线程 +- args: 参数,记录系统调用的参数信息 +- ret:返回值,记录系统调用的返回结果 ### sys_event_filter表 #### 表结构 diff --git a/trace_streamer/src/filter/BUILD.gn b/trace_streamer/src/filter/BUILD.gn index cbe1e74f..509be0c3 100644 --- a/trace_streamer/src/filter/BUILD.gn +++ b/trace_streamer/src/filter/BUILD.gn @@ -91,6 +91,7 @@ ohos_source_set("filter") { "process_filter.cpp", "slice_filter.cpp", "stat_filter.cpp", + "syscall_filter.cpp", "system_event_measure_filter.cpp", "task_pool_filter.cpp", ] diff --git a/trace_streamer/src/filter/syscall_filter.cpp b/trace_streamer/src/filter/syscall_filter.cpp new file mode 100644 index 00000000..2ded33ec --- /dev/null +++ b/trace_streamer/src/filter/syscall_filter.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * 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. + */ + +#include "syscall_filter.h" + +#include "process_filter.h" + +namespace SysTuning { +namespace TraceStreamer { +SyscallFilter::SyscallFilter(TraceDataCache *dataCache, const TraceStreamerFilters *filter) + : FilterBase(dataCache, filter) +{ +} +SyscallFilter::~SyscallFilter() {} + +void SyscallFilter::UpdataSyscallEnterExitMap(const SyscallInfoRow &syscallInfoRow) +{ + TS_LOGI("SysEnterEvent: SysEnter ID %u", syscallInfoRow.number); + auto key = std::make_pair(syscallInfoRow.itid, syscallInfoRow.number); + syscallEnterExitMap_[key] = syscallInfoRow; +} + +void SyscallFilter::AppendSysCallInfo(uint32_t pid, uint32_t syscallNr, uint64_t ts, int64_t ret) +{ + TS_LOGI("SysExitEvent: SysEnter ID %u", syscallNr); + auto key = std::make_pair(pid, syscallNr); + auto syscallEnterExitItor = syscallEnterExitMap_.find(key); + if (syscallEnterExitItor != syscallEnterExitMap_.end() && syscallEnterExitItor->second.ts <= ts) { + uint64_t dur = ts - syscallEnterExitItor->second.ts; + syscallEnterExitItor->second.dur = dur; + syscallEnterExitItor->second.ret = ret; + syscallEnterExitItor->second.itid = streamFilters_->processFilter_->UpdateOrCreateThread(ts, pid); + traceDataCache_->GetSysCallData()->AppendSysCallData(syscallEnterExitItor->second); + syscallEnterExitMap_.erase(key); + } else { + TS_LOGW("SysExitEvent: No matching sysExit event found for syscallID = %u.", syscallNr); + } +} + +void SyscallFilter::Clear() +{ + syscallEnterExitMap_.clear(); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/trace_streamer/src/filter/syscall_filter.h b/trace_streamer/src/filter/syscall_filter.h new file mode 100644 index 00000000..93206ecf --- /dev/null +++ b/trace_streamer/src/filter/syscall_filter.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * 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. + */ + +#ifndef SYSCALL_FILTER_H +#define SYSCALL_FILTER_H + +#include +#include + +#include "clock_filter_ex.h" +#include "common_types.h" +#include "filter_base.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace SysTuning::base; +class SyscallFilter : private FilterBase { +public: + SyscallFilter(TraceDataCache *dataCache, const TraceStreamerFilters *filter); + ~SyscallFilter() override; + void UpdataSyscallEnterExitMap(const SyscallInfoRow &syscallInfoRow); + void AppendSysCallInfo(uint32_t pid, uint32_t syscallNr, uint64_t ts, int64_t ret); + void Clear(); + +private: + std::map, SyscallInfoRow> syscallEnterExitMap_; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // SYSCALL_FILTER_H diff --git a/trace_streamer/src/parser/pbreader_parser/htrace_parser/htrace_event_parser.cpp b/trace_streamer/src/parser/pbreader_parser/htrace_parser/htrace_event_parser.cpp index 108f4c45..9f8efdfe 100644 --- a/trace_streamer/src/parser/pbreader_parser/htrace_parser/htrace_event_parser.cpp +++ b/trace_streamer/src/parser/pbreader_parser/htrace_parser/htrace_event_parser.cpp @@ -39,6 +39,7 @@ #include "signal.pbreader.h" #include "slice_filter.h" #include "stat_filter.h" +#include "syscall_filter.h" #include "system_event_measure_filter.h" #include "task.pbreader.h" #include "thread_state_flag.h" @@ -994,16 +995,18 @@ bool HtraceEventParser::SysEnterEvent(const EventInfo &event) const { streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_ENTRY, STAT_EVENT_RECEIVED); ProtoReader::SysEnterFormat_Reader msg(event.detail); - auto ipid = streamFilters_->processFilter_->UpdateOrCreateThread(event.timeStamp, event.tgid); - traceDataCache_->GetSysCallData()->AppendSysCallData(msg.id(), sysEnterName_, ipid, event.timeStamp, 0); + SyscallInfoRow syscallInfoRow; + syscallInfoRow.ts = event.timeStamp; + syscallInfoRow.itid = event.pid; + syscallInfoRow.number = msg.id(); + streamFilters_->syscallFilter_->UpdataSyscallEnterExitMap(syscallInfoRow); return true; } bool HtraceEventParser::SysExitEvent(const EventInfo &event) const { streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_EXIT, STAT_EVENT_RECEIVED); ProtoReader::SysExitFormat_Reader msg(event.detail); - auto ipid = streamFilters_->processFilter_->UpdateOrCreateThread(event.timeStamp, event.tgid); - traceDataCache_->GetSysCallData()->AppendSysCallData(msg.id(), sysExitName_, ipid, event.timeStamp, msg.ret()); + streamFilters_->syscallFilter_->AppendSysCallInfo(event.pid, msg.id(), event.timeStamp, msg.ret()); return true; } diff --git a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.cpp b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.cpp index e22021ff..71978acc 100644 --- a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.cpp +++ b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.cpp @@ -24,6 +24,7 @@ #include "process_filter.h" #include "slice_filter.h" #include "stat_filter.h" +#include "syscall_filter.h" #include "string_to_numerical.h" #include "thread_state_flag.h" #include "ts_common.h" @@ -71,6 +72,10 @@ BytraceEventParser::BytraceEventParser(TraceDataCache *dataCache, const TraceStr bind(&BytraceEventParser::IpiEntryEvent, this, std::placeholders::_1, std::placeholders::_2)}, {config_.eventNameMap_.at(TRACE_EVENT_IPI_EXIT), bind(&BytraceEventParser::IpiExitEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SYS_ENTRY), + bind(&BytraceEventParser::SysEnterEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SYS_EXIT), + bind(&BytraceEventParser::SysExitEvent, this, std::placeholders::_1, std::placeholders::_2)}, }; InterruptEventInitialization(); ClockEventInitialization(); @@ -188,6 +193,72 @@ void BytraceEventParser::StackEventsInitialization() bind(&BytraceEventParser::WorkqueueExecuteEndEvent, this, std::placeholders::_1, std::placeholders::_2)); } +bool BytraceEventParser::SysEnterEvent(const ArgsMap &args, const BytraceLine &line) +{ + Unused(args); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_ENTRY, STAT_EVENT_RECEIVED); + std::string sysEnterStr = base::Strip(line.argsStr); + if (sysEnterStr.empty()) { + TS_LOGD("SysEnterEvent: Empty args string for sysEnterStr, skipping."); + return true; + } + + auto firstSpacePos = sysEnterStr.find(" "); + if (firstSpacePos == std::string::npos) { + TS_LOGD("SysEnterEvent: No space found in sysEnterStr: '%s', skipping.", sysEnterStr.c_str()); + return true; + } + + // eg:NR 240 (f73bfb0c, 80, 2, f73bfab8, 5f5d907, 0) + DataIndex argsDataIndex = INVALID_UINT64; + auto secondSpacePos = sysEnterStr.find(" ", firstSpacePos + 1); + if (secondSpacePos != std::string::npos) { + std::string argsStr = sysEnterStr.substr(secondSpacePos + 1); + argsDataIndex = traceDataCache_->GetDataIndex(argsStr); + } else { + secondSpacePos = sysEnterStr.length(); + } + + uint32_t syscallNumber = std::atoi(sysEnterStr.substr(firstSpacePos, secondSpacePos).c_str()); + SyscallInfoRow syscallInfoRow; + syscallInfoRow.ts = line.ts; + syscallInfoRow.itid = line.pid; + syscallInfoRow.args = argsDataIndex; + syscallInfoRow.number = syscallNumber; + streamFilters_->syscallFilter_->UpdataSyscallEnterExitMap(syscallInfoRow); + return true; +} + +bool BytraceEventParser::SysExitEvent(const ArgsMap &args, const BytraceLine &line) +{ + Unused(args); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_EXIT, STAT_EVENT_RECEIVED); + std::string sysExitStr = base::Strip(line.argsStr); + if (sysExitStr.empty()) { + TS_LOGD("SysExitEvent: Empty args string for sysExitStr, skipping."); + return true; + } + + auto firstSpacePos = sysExitStr.find(" "); + if (firstSpacePos == std::string::npos) { + TS_LOGD("SysExitEvent: No space found in sysExitStr: '%s', skipping.", sysExitStr.c_str()); + return true; + } + + // eg:NR 85 = -22 + int64_t ret = INVALID_INT64; + auto secondSpacePos = sysExitStr.find("= "); + if (secondSpacePos != std::string::npos) { + ret = std::atoi(sysExitStr.substr(secondSpacePos + 2).c_str()); + } else { + secondSpacePos = sysExitStr.length(); + } + + uint32_t sysExitId = std::atoi(sysExitStr.substr(firstSpacePos, secondSpacePos).c_str()); + streamFilters_->syscallFilter_->AppendSysCallInfo(line.pid, sysExitId, line.ts, ret); + return true; +} + bool BytraceEventParser::SchedSwitchEvent(const ArgsMap &args, const BytraceLine &line) const { if (args.empty() || args.size() < MIN_SCHED_SWITCH_ARGS_COUNT) { diff --git a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h index cfef3a2d..d434e488 100644 --- a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h +++ b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h @@ -78,6 +78,8 @@ private: bool BinderTransaction(const ArgsMap &args, const BytraceLine &line) const; bool BinderTransactionReceived(const ArgsMap &args, const BytraceLine &line) const; bool BinderTransactionAllocBufEvent(const ArgsMap &args, const BytraceLine &line) const; + bool SysEnterEvent(const ArgsMap &args, const BytraceLine &line); + bool SysExitEvent(const ArgsMap &args, const BytraceLine &line); void GetDataSegArgs(const BytraceLine &bufLine, ArgsMap &args) const; void InterruptEventInitialization(); void ClockEventInitialization(); diff --git a/trace_streamer/src/table/ftrace/system_call_table.cpp b/trace_streamer/src/table/ftrace/system_call_table.cpp index 6ec56d27..76dbea5e 100644 --- a/trace_streamer/src/table/ftrace/system_call_table.cpp +++ b/trace_streamer/src/table/ftrace/system_call_table.cpp @@ -17,15 +17,16 @@ namespace SysTuning { namespace TraceStreamer { -enum class Index : int32_t { SYSCALL_NUM = 0, TYPE, IPID, TS, RET }; +enum class Index : int32_t { SYSCALL_NUMBER, TS, DUR, ITID, ARGS, RET }; SystemCallTable::SystemCallTable(const TraceDataCache *dataCache) : TableBase(dataCache) { - tableColumn_.push_back(TableBase::ColumnInfo("syscall_num", "INTEGER")); - tableColumn_.push_back(TableBase::ColumnInfo("type", "TEXT")); - tableColumn_.push_back(TableBase::ColumnInfo("ipid", "INTEGER")); + tableColumn_.push_back(TableBase::ColumnInfo("syscall_number", "INTEGER")); tableColumn_.push_back(TableBase::ColumnInfo("ts", "INTEGER")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "INTEGER")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "INTEGER")); + tableColumn_.push_back(TableBase::ColumnInfo("args", "TEXT")); tableColumn_.push_back(TableBase::ColumnInfo("ret", "INTEGER")); - tablePriKey_.push_back("syscall_num"); + tablePriKey_.push_back("ts"); } SystemCallTable::~SystemCallTable() {} @@ -46,19 +47,21 @@ SystemCallTable::Cursor::~Cursor() {} int32_t SystemCallTable::Cursor::Column(int32_t column) const { switch (static_cast(column)) { - case Index::SYSCALL_NUM: - sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().SysCallsData()[CurrentRow()]); - break; - case Index::TYPE: - sqlite3_result_text(context_, dataCache_->GetDataFromDict(sysCallObj_.TypesData()[CurrentRow()]).c_str(), - STR_DEFAULT_LEN, nullptr); - break; - case Index::IPID: - sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().IpidsData()[CurrentRow()]); + case Index::SYSCALL_NUMBER: + sqlite3_result_int(context_, dataCache_->GetConstSysCallData().SysCallNumbersData()[CurrentRow()]); break; case Index::TS: sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().TimeStampData()[CurrentRow()]); break; + case Index::DUR: + sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().DursData()[CurrentRow()]); + break; + case Index::ITID: + sqlite3_result_int(context_, dataCache_->GetConstSysCallData().ItidsData()[CurrentRow()]); + break; + case Index::ARGS: + SetTypeColumnText(dataCache_->GetConstSysCallData().ArgsData()[CurrentRow()], INVALID_UINT64); + break; case Index::RET: sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().RetsData()[CurrentRow()]); break; diff --git a/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.cpp b/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.cpp index 4b66c995..3094cee0 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.cpp +++ b/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.cpp @@ -13,16 +13,16 @@ * limitations under the License. */ #include "syscall_stdtype.h" - namespace SysTuning { namespace TraceStdtype { -size_t SysCall::AppendSysCallData(int64_t sysCallNum, DataIndex type, uint32_t ipid, uint64_t timeStamp, int64_t ret) +size_t SysCall::AppendSysCallData(const SyscallInfoRow& syscallInfoRow) { - sysCallNums_.emplace_back(sysCallNum); - types_.emplace_back(type); - ipids_.emplace_back(ipid); - timeStamps_.emplace_back(timeStamp); - rets_.emplace_back(ret); + sysCallNumbers_.emplace_back(syscallInfoRow.number); + timeStamps_.emplace_back(syscallInfoRow.ts); + durs_.emplace_back(syscallInfoRow.dur); + itids_.emplace_back(syscallInfoRow.itid); + args_.emplace_back(syscallInfoRow.args); + rets_.emplace_back(syscallInfoRow.ret); return Size() - 1; } } // namespace TraceStdtype diff --git a/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.h b/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.h index 0fa2844f..e0834b94 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.h +++ b/trace_streamer/src/trace_data/trace_stdtype/ftrace/syscall_stdtype.h @@ -19,43 +19,57 @@ namespace SysTuning { namespace TraceStdtype { +struct SyscallInfoRow { + uint64_t ts = INVALID_UINT64; + uint64_t dur = INVALID_UINT64; + InternalTid itid = INVALID_UINT32; + uint32_t number = INVALID_UINT32; + DataIndex args = INVALID_UINT64; + int64_t ret = INVALID_INT64; +}; class SysCall : public CacheBase, public BatchCacheBase { public: - size_t AppendSysCallData(int64_t sysCallNum, DataIndex type, uint32_t ipid, uint64_t timeStamp, int64_t ret); - const std::deque &SysCallsData() const + size_t AppendSysCallData(const SyscallInfoRow& syscallNrInfoRow); + const std::deque &SysCallNumbersData() const + { + return sysCallNumbers_; + } + const std::deque &DursData() const { - return sysCallNums_; + return durs_; } - const std::deque &TypesData() const + const std::deque &ItidsData() const { - return types_; + return itids_; } - const std::deque &IpidsData() const + const std::deque &ArgsData() const { - return ipids_; + return args_; } - const std::deque &RetsData() const + const std::deque &RetsData() const { return rets_; } void Clear() override { CacheBase::Clear(); - sysCallNums_.clear(); - types_.clear(); - ipids_.clear(); + sysCallNumbers_.clear(); + durs_.clear(); + itids_.clear(); + args_.clear(); rets_.clear(); } void ClearExportedData() override { - EraseElements(timeStamps_, sysCallNums_, types_, ipids_, rets_); + EraseElements(sysCallNumbers_, timeStamps_, durs_, itids_, args_, rets_); } private: - std::deque sysCallNums_ = {}; - std::deque types_ = {}; - std::deque ipids_ = {}; - std::deque rets_ = {}; + std::deque durs_ = {}; + std::deque sysCallNumbers_ = {}; + std::deque itids_ = {}; + std::deque args_ = {}; + std::deque rets_ = {}; }; } // namespace TraceStdtype } // namespace SysTuning diff --git a/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp b/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp index 31707d32..590125c3 100644 --- a/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp +++ b/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp @@ -31,6 +31,7 @@ #include "process_filter.h" #include "slice_filter.h" #include "stat_filter.h" +#include "syscall_filter.h" #include "system_event_measure_filter.h" #include "task_pool_filter.h" @@ -45,6 +46,7 @@ void TraceStreamerFilters::FilterClear() cpuFilter_->Clear(); irqFilter_->Clear(); frameFilter_->Clear(); + syscallFilter_->Clear(); } } // namespace TraceStreamer } // namespace SysTuning diff --git a/trace_streamer/src/trace_streamer/trace_streamer_filters.h b/trace_streamer/src/trace_streamer/trace_streamer_filters.h index 4e223e90..b82a2218 100644 --- a/trace_streamer/src/trace_streamer/trace_streamer_filters.h +++ b/trace_streamer/src/trace_streamer/trace_streamer_filters.h @@ -30,6 +30,7 @@ class StatFilter; class BinderFilter; class ArgsFilter; class IrqFilter; +class SyscallFilter; class SystemEventMeasureFilter; #ifdef ENABLE_HISYSEVENT class HiSysEventMeasureFilter; @@ -56,6 +57,7 @@ public: std::unique_ptr binderFilter_; std::unique_ptr argsFilter_; std::unique_ptr irqFilter_; + std::unique_ptr syscallFilter_; std::unique_ptr sysEventMemMeasureFilter_; std::unique_ptr sysEventVMemMeasureFilter_; std::unique_ptr sysEventSourceFilter_; diff --git a/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp b/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp index 2d07bb03..4bb54b5f 100644 --- a/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp +++ b/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp @@ -21,6 +21,7 @@ #include #include #include "animation_filter.h" +#include "syscall_filter.h" #include "app_start_filter.h" #include "args_filter.h" #include "binder_filter.h" @@ -157,6 +158,7 @@ void TraceStreamerSelector::InitFilter() streamFilters_->argsFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); streamFilters_->irqFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); streamFilters_->frameFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->syscallFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); streamFilters_->sysEventMemMeasureFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_SYS_MEMORY_FILTER); streamFilters_->sysEventVMemMeasureFilter_ = std::make_unique( diff --git a/trace_streamer/src/version.cpp b/trace_streamer/src/version.cpp index 9544e649..7b3d690b 100644 --- a/trace_streamer/src/version.cpp +++ b/trace_streamer/src/version.cpp @@ -17,7 +17,7 @@ namespace SysTuning { namespace TraceStreamer { size_t g_loadSize = 0; size_t g_fileSize = 0; -const std::string TRACE_STREAMER_VERSION = "4.2.9"; // version -const std::string TRACE_STREAMER_PUBLISH_VERSION = "2025/1/2"; // publish datetime +const std::string TRACE_STREAMER_VERSION = "4.3.4"; // version +const std::string TRACE_STREAMER_PUBLISH_VERSION = "2025/5/9"; // publish datetime } // namespace TraceStreamer } // namespace SysTuning -- Gitee From d627187e4be2f82b33d061cbf256b555c4d43a0f Mon Sep 17 00:00:00 2001 From: JustinYT Date: Sat, 10 May 2025 09:24:04 +0800 Subject: [PATCH 06/66] =?UTF-8?q?IDE=E6=96=B0=E5=A2=9E=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- ide/src/trace/bean/BoxSelection.ts | 31 ++ .../trace/component/SpSystemTrace.event.ts | 6 +- ide/src/trace/component/SpSystemTrace.ts | 3 + .../trace/component/chart/SpProcessChart.ts | 78 ++++- .../component/trace/base/SysCallUtils.ts | 295 ++++++++++++++++++ .../trace/component/trace/base/TraceRow.ts | 68 ++-- .../trace/component/trace/base/TraceSheet.ts | 53 +++- .../component/trace/base/TraceSheetConfig.ts | 11 + ide/src/trace/component/trace/base/Utils.ts | 2 + .../trace/sheet/TabPaneCurrentSelection.ts | 31 ++ .../trace/sheet/process/TabPaneSysCall.ts | 243 +++++++++++++++ .../sheet/process/TabPaneSysCallChild.ts | 195 ++++++++++++ .../process/ThreadSysCallDataReceiver.ts | 68 ++++ .../process/ThreadSysCallDataSender.ts | 82 +++++ .../data-trafic/utils/ExecProtoForWorker.ts | 2 + .../database/data-trafic/utils/QueryEnum.ts | 4 +- .../trace/database/sql/ProcessThread.sql.ts | 102 ++++++ .../database/ui-worker/ProcedureWorker.ts | 2 + .../ui-worker/ProcedureWorkerThreadSysCall.ts | 137 ++++++++ 19 files changed, 1388 insertions(+), 25 deletions(-) create mode 100644 ide/src/trace/component/trace/base/SysCallUtils.ts create mode 100644 ide/src/trace/component/trace/sheet/process/TabPaneSysCall.ts create mode 100644 ide/src/trace/component/trace/sheet/process/TabPaneSysCallChild.ts create mode 100644 ide/src/trace/database/data-trafic/process/ThreadSysCallDataReceiver.ts create mode 100644 ide/src/trace/database/data-trafic/process/ThreadSysCallDataSender.ts create mode 100644 ide/src/trace/database/ui-worker/ProcedureWorkerThreadSysCall.ts diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index 22ecff19..a77c8bae 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -103,6 +103,8 @@ export class SelectionParam { irqCallIds: Array = []; softIrqCallIds: Array = []; funTids: Array = []; + threadSysCallIds: Array = []; + processSysCallIds: Array = []; funAsync: Array<{ name: string; pid: number, tid: number | undefined }> = []; funCatAsync: Array<{ pid: number; threadName: string }> = []; nativeMemory: Array = []; @@ -195,6 +197,16 @@ export class SelectionParam { } } + // @ts-ignore + pushSysCallIds(it: TraceRow): void { + if (it.rowType === TraceRow.ROW_TYPE_THREAD_SYS_CALL) { + const arr = it.rowId?.split('-'); + if (arr && arr.length === 3) { + this.threadSysCallIds.push(parseInt(arr[1])); + } + } + } + // @ts-ignore pushCpuStateFilterIds(it: TraceRow): void { if (it.rowType === TraceRow.ROW_TYPE_CPU_STATE_ALL) { @@ -259,6 +271,14 @@ export class SelectionParam { if (it.rowType === TraceRow.ROW_TYPE_PROCESS || it.rowType === TraceRow.ROW_TYPE_IMPORT) { sp.pushPidToSelection(this, it.rowId!, it.summaryProtoPid); sp.pushPidToSelection(this, it.rowId!); + if (it.getRowSettingCheckStateByKey('SysCall Event')) { + let pid = parseInt(it.rowId!); + if (!isNaN(pid!)) { + if (!this.processSysCallIds.includes(pid!)) { + this.processSysCallIds.push(pid!); + } + } + } if (it.getAttribute('hasStartup') === 'true') { this.startup = true; } @@ -1349,6 +1369,7 @@ export class SelectionParam { this.pushStaticInit(it, sp); this.pushAppStartUp(it, sp); this.pushThread(it, sp); + this.pushSysCallIds(it); this.pushVirtualMemory(it, sp); this.pushFps(it, sp); this.pushCpuAbility(it, sp); @@ -1389,6 +1410,16 @@ export class BoxJumpParam { currentId: string | undefined | null; } +export class SysCallBoxJumpParam { + traceId: string | undefined | null; + leftNs: number = 0; + rightNs: number = 0; + processId: number[] | undefined; + threadId: number[] | undefined; + sysCallId: number | undefined; + isJumpPage: boolean | undefined; +} + export class SliceBoxJumpParam { traceId: string | undefined | null; leftNs: number = 0; diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index 165126d6..da063294 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -63,6 +63,7 @@ import { XpowerGpuFreqStruct, XpowerGpuFreqStructOnClick } from '../database/ui- import { XpowerThreadCountStruct, XpowerThreadCountStructOnClick } from '../database/ui-worker/ProcedureWorkerXpowerThreadCount'; import { XpowerGpuFreqCountStruct, XpowerGpuFreqCountStructOnClick } from '../database/ui-worker/ProcedureWorkerXpowerGpuFreqCount'; import { SnapShotOnClick, SnapShotStruct } from '../database/ui-worker/ProcedureWorkerSnaps'; +import { ThreadSysCallStruct, ThreadSysCallStructOnClick } from '../database/ui-worker/ProcedureWorkerThreadSysCall'; function timeoutJudge(sp: SpSystemTrace): number { let timeoutJudge = window.setTimeout((): void => { @@ -421,6 +422,7 @@ function allStructOnClick(clickRowType: string, sp: SpSystemTrace, row?: TraceRo .then(() => sampleStructOnClick(clickRowType, sp, row as TraceRow, entry as SampleStruct)) .then(() => gpuCounterStructOnClick(clickRowType, sp, entry as GpuCounterStruct)) .then(() => PerfToolsStructOnClick(clickRowType, sp, entry as PerfToolStruct)) + .then(() => ThreadSysCallStructOnClick(clickRowType, sp, entry as ThreadSysCallStruct)) .then(() => SnapShotOnClick(clickRowType, sp, entry as SnapShotStruct)) .then(() => { if (!JankStruct.hoverJankStruct && JankStruct.delJankLineFlag) { @@ -578,7 +580,8 @@ function spSystemTraceDocumentOnMouseMoveMouseUp( sp.hoverStructNull(); } const transformYMatch = sp.canvasPanel?.style.transform.match(/\((\d+)[^\)]+\)/); - const transformY = transformYMatch![1]; + if (transformYMatch && transformYMatch![1]) { + const transformY = transformYMatch![1]; let favoriteHeight = sp.favoriteChartListEL!.getBoundingClientRect().height; // @ts-ignore let memTr = rows.filter((item: unknown) => item.rowType === TraceRow.ROW_TYPE_MEM); @@ -611,6 +614,7 @@ function spSystemTraceDocumentOnMouseMoveMouseUp( tr.findHoverStruct?.(); tr.focusHandler?.(ev); }); + } requestAnimationFrame(() => sp.refreshCanvas(true, 'sp move up')); } diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index c771f929..32bcd136 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -144,6 +144,7 @@ import { XpowerThreadInfoStruct } from '../database/ui-worker/ProcedureWorkerXpo import { XpowerGpuFreqStruct } from '../database/ui-worker/ProcedureWorkerXpowerGpuFreq'; import { LitProgressBar } from '../../base-ui/progress-bar/LitProgressBar'; import { SnapShotStruct } from '../database/ui-worker/ProcedureWorkerSnaps'; +import { ThreadSysCallStruct } from '../database/ui-worker/ProcedureWorkerThreadSysCall'; function dpr(): number { return window.devicePixelRatio || 1; @@ -1370,6 +1371,7 @@ export class SpSystemTrace extends BaseElement { CpuStruct.hoverCpuStruct = undefined; CpuFreqStruct.hoverCpuFreqStruct = undefined; ThreadStruct.hoverThreadStruct = undefined; + ThreadSysCallStruct.hoverStruct = undefined; FuncStruct.hoverFuncStruct = undefined; ProcessMemStruct.hoverProcessMemStruct = undefined; HiPerfCpuStruct.hoverStruct = undefined; @@ -1412,6 +1414,7 @@ export class SpSystemTrace extends BaseElement { CpuFreqStruct.selectCpuFreqStruct = undefined; ThreadStruct.selectThreadStruct = undefined; ThreadStruct.isClickPrio = false; + ThreadSysCallStruct.selectStruct = undefined; FuncStruct.selectFuncStruct = undefined; SpHiPerf.selectCpuStruct = undefined; CpuStateStruct.selectStateStruct = undefined; diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index 019a765c..9da1cc47 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -29,6 +29,7 @@ import { SoRender, SoStruct } from '../../database/ui-worker/ProcedureWorkerSoIn import { FlagsConfig } from '../SpFlags'; import { processDataSender } from '../../database/data-trafic/process/ProcessDataSender'; import { threadDataSender } from '../../database/data-trafic/process/ThreadDataSender'; +import { threadSysCallDataSender } from '../../database/data-trafic/process/ThreadSysCallDataSender'; import { funcDataSender } from '../../database/data-trafic/process/FuncDataSender'; import { processMemDataSender } from '../../database/data-trafic/process/ProcessMemDataSender'; import { processStartupDataSender } from '../../database/data-trafic/process/ProcessStartupDataSender'; @@ -43,6 +44,7 @@ import { queryAllSoInitNames, queryAllSrcSlices, queryEventCountMap } from '../. import { queryProcessByTable, queryProcessContentCount, + querySysCallThreadIds, queryProcessMem, queryProcessSoMaxDepth, queryProcessThreadsByTable, @@ -53,13 +55,13 @@ import { } from '../../database/sql/ProcessThread.sql'; 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'; import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; import { renders } from '../../database/ui-worker/ProcedureWorker'; +import { ThreadSysCallStruct } from '../../database/ui-worker/ProcedureWorkerThreadSysCall'; const FOLD_HEIGHT = 24; export class SpProcessChart { @@ -479,6 +481,12 @@ export class SpProcessChart { }); info('convert tid and maxDepth array to map'); let pidCountArray = await queryProcessContentCount(traceId); + info('fetch syscall event tid'); + let sysCallTidArray = await querySysCallThreadIds(traceId); + Utils.getInstance().sysCallEventTidsMap.clear(); + sysCallTidArray.forEach((it) => { + Utils.getInstance().sysCallEventTidsMap.set(it.tid, it); + }); info('fetch per process pid,switch_count,thread_count,slice_count,mem_count'); pidCountArray.forEach((it) => { //@ts-ignore @@ -602,6 +610,7 @@ export class SpProcessChart { processRow.rowType = TraceRow.ROW_TYPE_PROCESS; processRow.rowParentId = ''; processRow.style.height = '40px'; + this.processRowSettingConfig(processRow); processRow.folder = true; if ( //@ts-ignore @@ -634,6 +643,20 @@ export class SpProcessChart { return processRow; } + processRowSettingConfig(row: TraceRow): void { + row.rowSettingCheckBoxList = ['SysCall Event']; + row.addRowSettingCheckBox(false); + row.rowSetting = 'enable'; + row.rowSettingPopoverDirection = 'bottomLeft'; + row.onRowSettingCheckBoxChangeHandler = (value): void => { + row.childrenList.forEach((childRow) => { + if (childRow.rowType === TraceRow.ROW_TYPE_THREAD_SYS_CALL) { + childRow.rowDiscard = !row.getRowSettingCheckStateByKey('SysCall Event'); + } + }) + }; + } + addProcessRowListener(processRow: TraceRow, actualRow: TraceRow | null): void { let offsetYTimeOut: unknown = undefined; processRow.addEventListener('expansion-change', (e: unknown) => { @@ -981,6 +1004,58 @@ export class SpProcessChart { return null; } + addThreadSysCallRow( + data: { + pid: number | null; + processName: string | null; + }, + processRow: TraceRow, + threadRow: TraceRow, + thread: unknown + ): TraceRow | null { + //@ts-ignore + const ids = Utils.getInstance().sysCallEventTidsMap.get(thread.tid); + if (ids) { + const threadSysCallRow = TraceRow.skeleton(); + threadSysCallRow.rowType = TraceRow.ROW_TYPE_THREAD_SYS_CALL; + threadSysCallRow.rowId = `${data.pid}-${ids.itid}-syscall`; + threadSysCallRow.rowParentId = `${data.pid}`; + threadSysCallRow.rowHidden = !processRow.expansion; + threadSysCallRow.style.width = '100%'; + threadSysCallRow.name = `syscall event ${ids.tid}`; + threadSysCallRow.addTemplateTypes('SysCallEvent'); + threadSysCallRow.setAttribute('children', ''); + threadSysCallRow.supplierFrame = async (): Promise => { + let promiseData = threadSysCallDataSender(ids.itid!, ids.tid!, data.pid!, threadSysCallRow); + if (promiseData === null) { + return new Promise>((resolve) => resolve([])); + } else { + return promiseData.then(); + } + }; + threadSysCallRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; + threadSysCallRow.selectChangeHandler = this.trace.selectChangeHandler; + threadSysCallRow.findHoverStruct = (): void => { + ThreadSysCallStruct.hoverStruct = threadSysCallRow.getHoverStruct(); + }; + threadSysCallRow.onThreadHandler = rowThreadHandler( + 'threadSysCall', + 'context', + { + type: threadSysCallRow.rowId, + translateY: threadSysCallRow.translateY, + }, + threadSysCallRow, + this.trace + ); + threadSysCallRow.rowDiscard = true; + processRow.addChildTraceRowAfter(threadSysCallRow, threadRow); + return threadSysCallRow; + } + return null; + } + + jankSenderCallback( res: JankStruct[], type: string, @@ -1326,6 +1401,7 @@ export class SpProcessChart { // @ts-ignore this.insertRowToDoc(it, j, thread, pRow, tRow, list, tRowArr, actualRow, expectedRow, hangRow, startupRow, soRow); this.addFuncStackRow(it, thread, j, list, tRowArr, tRow, pRow); + this.addThreadSysCallRow(it, pRow, tRow, thread); // @ts-ignore if ((thread.switchCount || 0) === 0) { tRow.rowDiscard = true; diff --git a/ide/src/trace/component/trace/base/SysCallUtils.ts b/ide/src/trace/component/trace/base/SysCallUtils.ts new file mode 100644 index 00000000..3b8609a1 --- /dev/null +++ b/ide/src/trace/component/trace/base/SysCallUtils.ts @@ -0,0 +1,295 @@ +/* + * 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 SysCallMap = new Map([ + [0, "sys_io_setup"], + [1, "sys_io_destroy"], + [2, "sys_io_submit"], + [3, "sys_io_cancel"], + [4, "sys_io_getevents"], + [5, "sys_setxattr"], + [6, "sys_lsetxattr"], + [7, "sys_fsetxattr"], + [8, "sys_getxattr"], + [9, "sys_lgetxattr"], + [10, "sys_fgetxattr"], + [11, "sys_listxattr"], + [12, "sys_llistxattr"], + [13, "sys_flistxattr"], + [14, "sys_removexattr"], + [15, "sys_lremovexattr"], + [16, "sys_fremovexattr"], + [17, "sys_getcwd"], + [18, "sys_lookup_dcookie"], + [19, "sys_eventfd2"], + [20, "sys_epoll_create1"], + [21, "sys_epoll_ctl"], + [22, "sys_epoll_pwait"], + [23, "sys_dup"], + [24, "sys_dup3"], + [25, "sys_fcntl"], + [26, "sys_inotify_init1"], + [27, "sys_inotify_add_watch"], + [28, "sys_inotify_rm_watch"], + [29, "sys_ioctl"], + [30, "sys_ioprio_set"], + [31, "sys_ioprio_get"], + [32, "sys_flock"], + [33, "sys_mknodat"], + [34, "sys_mkdirat"], + [35, "sys_unlinkat"], + [36, "sys_symlinkat"], + [37, "sys_linkat"], + [38, "sys_renameat"], + [39, "sys_umount2"], + [40, "sys_mount"], + [41, "sys_pivot_root"], + [42, "sys_nfsservctl"], + [43, "sys_statfs"], + [44, "sys_fstatfs"], + [45, "sys_truncate"], + [46, "sys_ftruncate"], + [47, "sys_fallocate"], + [48, "sys_faccessat"], + [49, "sys_chdir"], + [50, "sys_fchdir"], + [51, "sys_chroot"], + [52, "sys_fchmod"], + [53, "sys_fchmodat"], + [54, "sys_fchownat"], + [55, "sys_fchown"], + [56, "sys_openat"], + [57, "sys_close"], + [58, "sys_vhangup"], + [59, "sys_pipe2"], + [60, "sys_quotactl"], + [61, "sys_getdents64"], + [62, "sys_lseek"], + [63, "sys_read"], + [64, "sys_write"], + [65, "sys_readlinkat"], + [66, "sys_fstatat"], + [67, "sys_fstat"], + [68, "sys_sync"], + [69, "sys_fsync"], + [70, "sys_fdatasync"], + [71, "sys_sync_file_range"], + [72, "sys_timerfd_create"], + [73, "sys_timerfd_settime"], + [74, "sys_timerfd_gettime"], + [75, "sys_utimensat"], + [76, "sys_acct"], + [77, "sys_capget"], + [78, "sys_capset"], + [79, "sys_personality"], + [93, "sys_exit"], + [94, "sys_exit_group"], + [95, "sys_waitid"], + [96, "sys_set_tid_address"], + [97, "sys_unshare"], + [98, "sys_futex"], + [99, "sys_set_robust_list"], + [100, "sys_get_robust_list"], + [101, "sys_nanosleep"], + [102, "sys_getitimer"], + [103, "sys_setitimer"], + [104, "sys_kexec_load"], + [105, "sys_init_module"], + [106, "sys_delete_module"], + [107, "sys_timer_create"], + [108, "sys_timer_settime"], + [109, "sys_timer_gettime"], + [110, "sys_timer_getoverrun"], + [111, "sys_timer_delete"], + [112, "sys_clock_settime"], + [113, "sys_clock_gettime"], + [114, "sys_clock_getres"], + [115, "sys_clock_nanosleep"], + [116, "sys_syslog"], + [117, "sys_ptrace"], + [118, "sys_sched_setparam"], + [119, "sys_sched_setscheduler"], + [120, "sys_sched_getscheduler"], + [121, "sys_sched_getparam"], + [122, "sys_sched_setaffinity"], + [123, "sys_sched_getaffinity"], + [124, "sys_sched_yield"], + [125, "sys_sched_get_priority_max"], + [126, "sys_sched_get_priority_min"], + [127, "sys_sched_rr_get_interval"], + [128, "sys_restart_syscall"], + [129, "sys_kill"], + [130, "sys_tkill"], + [131, "sys_tgkill"], + [132, "sys_sigaltstack"], + [133, "sys_rt_sigsuspend"], + [134, "sys_rt_sigaction"], + [135, "sys_rt_sigprocmask"], + [136, "sys_rt_sigpending"], + [137, "sys_rt_sigtimedwait"], + [138, "sys_rt_sigqueueinfo"], + [139, "sys_rt_sigreturn"], + [140, "sys_setpriority"], + [141, "sys_getpriority"], + [142, "sys_reboot"], + [143, "sys_setregid"], + [144, "sys_setgid"], + [145, "sys_setreuid"], + [146, "sys_setuid"], + [147, "sys_setresuid"], + [148, "sys_getresuid"], + [149, "sys_setresgid"], + [150, "sys_getresgid"], + [151, "sys_setfsuid"], + [152, "sys_setfsgid"], + [153, "sys_times"], + [154, "sys_setpgid"], + [155, "sys_getpgid"], + [156, "sys_getsid"], + [157, "sys_setsid"], + [158, "sys_getgroups"], + [159, "sys_setgroups"], + [160, "sys_uname"], + [161, "sys_sethostname"], + [162, "sys_setdomainname"], + [163, "sys_getrlimit"], + [164, "sys_setrlimit"], + [165, "sys_getrusage"], + [166, "sys_umask"], + [167, "sys_prctl"], + [168, "sys_getcpu"], + [169, "sys_gettimeofday"], + [170, "sys_settimeofday"], + [171, "sys_adjtimex"], + [172, "sys_getpid"], + [173, "sys_getppid"], + [174, "sys_getuid"], + [175, "sys_geteuid"], + [176, "sys_getgid"], + [177, "sys_getegid"], + [178, "sys_gettid"], + [179, "sys_sysinfo"], + [180, "sys_mq_open"], + [181, "sys_mq_unlink"], + [182, "sys_mq_timedsend"], + [183, "sys_mq_timedreceive"], + [184, "sys_mq_notify"], + [185, "sys_mq_getsetattr"], + [186, "sys_msgget"], + [187, "sys_msgctl"], + [188, "sys_msgrcv"], + [189, "sys_msgsnd"], + [190, "sys_semget"], + [191, "sys_semctl"], + [192, "sys_semop"], + [193, "sys_semtimedop"], + [194, "sys_shmget"], + [195, "sys_shmctl"], + [196, "sys_shmat"], + [197, "sys_shmdt"], + [198, "sys_socket"], + [199, "sys_socketpair"], + [200, "sys_bind"], + [201, "sys_listen"], + [202, "sys_accept"], + [203, "sys_connect"], + [204, "sys_getsockname"], + [205, "sys_getpeername"], + [206, "sys_sendto"], + [207, "sys_recvfrom"], + [208, "sys_setsockopt"], + [209, "sys_getsockopt"], + [210, "sys_shutdown"], + [211, "sys_sendmsg"], + [212, "sys_recvmsg"], + [213, "sys_readahead"], + [214, "sys_brk"], + [215, "sys_munmap"], + [216, "sys_mremap"], + [217, "sys_add_key"], + [218, "sys_request_key"], + [219, "sys_keyctl"], + [220, "sys_clone"], + [221, "sys_execve"], + [222, "sys_mmap"], + [223, "sys_fadvise64"], + [224, "sys_swapon"], + [225, "sys_swapoff"], + [226, "sys_mprotect"], + [227, "sys_msync"], + [228, "sys_mlock"], + [229, "sys_munlock"], + [230, "sys_mlockall"], + [231, "sys_munlockall"], + [232, "sys_mincore"], + [233, "sys_madvise"], + [234, "sys_remap_file_pages"], + [235, "sys_mbind"], + [236, "sys_get_mempolicy"], + [237, "sys_set_mempolicy"], + [238, "sys_migrate_pages"], + [239, "sys_move_pages"], + [240, "sys_rt_tgsigqueueinfo"], + [241, "sys_perf_event_open"], + [242, "sys_accept4"], + [243, "sys_recvmmsg"], + [260, "sys_wait4"], + [261, "sys_prlimit64"], + [262, "sys_fanotify_init"], + [263, "sys_fanotify_mark"], + [264, "sys_name_to_handle_at"], + [265, "sys_open_by_handle_at"], + [266, "sys_clock_adjtime"], + [267, "sys_syncfs"], + [268, "sys_setns"], + [269, "sys_sendmmsg"], + [270, "sys_process_vm_readv"], + [271, "sys_process_vm_writev"], + [272, "sys_kcmp"], + [273, "sys_finit_module"], + [274, "sys_sched_setattr"], + [275, "sys_sched_getattr"], + [276, "sys_renameat2"], + [277, "sys_seccomp"], + [278, "sys_getrandom"], + [279, "sys_memfd_create"], + [280, "sys_bpf"], + [281, "sys_execveat"], + [282, "sys_userfaultfd"], + [283, "sys_membarrier"], + [284, "sys_mlock2"], + [285, "sys_copy_file_range"], + [286, "sys_preadv2"], + [287, "sys_pwritev2"], + [288, "sys_pkey_mprotect"], + [289, "sys_pkey_alloc"], + [290, "sys_pkey_free"], + [291, "sys_statx"], + [292, "sys_io_pgetevents"], + [293, "sys_rseq"], + [294, "sys_kexec_file_load"], + [424, "sys_pidfd_send_signal"], + [425, "sys_io_uring_setup"], + [426, "sys_io_uring_enter"], + [427, "sys_io_uring_register"], + [428, "sys_open_tree"], + [429, "sys_move_mount"], + [430, "sys_fsopen"], + [431, "sys_fsconfig"], + [432, "sys_fsmount"], + [433, "sys_fspick"], + [434, "sys_pidfd_open"], + [435, "sys_clone3"] +]); \ No newline at end of file diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index 7bda9c89..ea5fec73 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -79,6 +79,7 @@ export class TraceRow extends HTMLElement { static ROW_TYPE_APP_STARTUP = 'app-startup'; static ROW_TYPE_STATIC_INIT = 'static-init'; static ROW_TYPE_THREAD = 'thread'; + static ROW_TYPE_THREAD_SYS_CALL = 'thread-sys-call'; static ROW_TYPE_THREAD_NAME = 'sameThread_process'; static ROW_TYPE_MEM = 'mem'; static ROW_TYPE_VIRTUAL_MEMORY_GROUP = 'virtual-memory-group'; @@ -354,12 +355,21 @@ export class TraceRow extends HTMLElement { } set rowDiscard(value: boolean) { + let height = 0; if (value) { this.setAttribute('row-discard', ''); this.style.display = 'none'; + height = 0; } else { this.removeAttribute('row-discard'); this.style.display = 'block'; + height = this.clientHeight; + } + if (this.collect) { + window.publish(window.SmartEvent.UI.RowHeightChange, { + expand: this.funcExpand, + value: height, + }); } } @@ -656,7 +666,7 @@ export class TraceRow extends HTMLElement { } // @ts-ignore - addChildTraceRowAfter(child: TraceRow, targetRow: TraceRow): void { + addChildTraceRowAfter(child: TraceRow, targetRow: TraceRow, hidden: boolean = false): void { // @ts-ignore TraceRowConfig.allTraceRowList.push(child); child.parentRowEl = this; @@ -665,11 +675,11 @@ export class TraceRow extends HTMLElement { child.setAttribute('scene', ''); if (index !== -1) { this.childrenList.splice(index + 1, 0, child); - child.rowHidden = false; + child.rowHidden = hidden; this.fragment.insertBefore(child, this.fragment.childNodes.item(index + 1)); } else { this.childrenList.push(child); - child.rowHidden = false; + child.rowHidden = hidden; this.fragment.append(child); } } @@ -1022,21 +1032,23 @@ export class TraceRow extends HTMLElement { this.describeEl?.appendChild(this.rowCheckFilePop); } - addRowSettingCheckBox(): void { + addRowSettingCheckBox(appendAll: boolean = true): void { let nameEl = this.shadowRoot && (this.shadowRoot.querySelector('.name') as HTMLLabelElement); nameEl && (nameEl.style.maxWidth = '160px'); let collectEl = (this.shadowRoot && this.shadowRoot.querySelector('.collect') as LitIcon); collectEl && (collectEl.style.marginRight = '20px'); this.rowSettingCheckBoxPop = document.createElement('lit-popover') as LitPopover; let checkboxHtml = ''; - checkboxHtml += `
+ if (appendAll) { + checkboxHtml += `
`; + } this._rowSettingCheckBoxList && this._rowSettingCheckBoxList.forEach((item) => { checkboxHtml += `
- +
`; }); - this._rowSettingCheckedBoxList = new Array(this._rowSettingCheckBoxList?.length).fill(true); + this._rowSettingCheckedBoxList = new Array(this._rowSettingCheckBoxList?.length).fill(appendAll); this.rowSettingCheckBoxPop.innerHTML = `
${checkboxHtml}
@@ -1048,21 +1060,25 @@ export class TraceRow extends HTMLElement { item.onchange = (e: unknown): void => { // @ts-ignore this._rowSettingCheckedBoxList[this._rowSettingCheckBoxList?.indexOf(item.value)] = item.checked; - const allChecked = this._rowSettingCheckedBoxList!.every(item => item); - allCheckBox.checked = allChecked; + if (appendAll) { + const allChecked = this._rowSettingCheckedBoxList!.every(item => item); + allCheckBox.checked = allChecked; + } this.onRowSettingCheckBoxChangeHandler?.(this._rowSettingCheckedBoxList!); }; }); - allCheckBox.onchange = (e: unknown): void => { - checkBoxItems.forEach(item => { - // @ts-ignore - item.checked = allCheckBox.checked; - }); - this._rowSettingCheckedBoxList!.forEach((_, index) => { - this._rowSettingCheckedBoxList![index] = allCheckBox.checked; - }); - this.onRowSettingCheckBoxChangeHandler?.(this._rowSettingCheckedBoxList!); - }; + if (appendAll) { + allCheckBox.onchange = (e: unknown): void => { + checkBoxItems.forEach(item => { + // @ts-ignore + item.checked = allCheckBox.checked; + }); + this._rowSettingCheckedBoxList!.forEach((_, index) => { + this._rowSettingCheckedBoxList![index] = allCheckBox.checked; + }); + this.onRowSettingCheckBoxChangeHandler?.(this._rowSettingCheckedBoxList!); + }; + } this.rowSettingCheckBoxPop.id = 'rowSetting'; this.rowSettingCheckBoxPop.className = 'popover setting'; this.rowSettingCheckBoxPop.setAttribute('placement', 'bottomLeft'); @@ -1122,6 +1138,14 @@ export class TraceRow extends HTMLElement { return []; } + getRowSettingCheckStateByKey(key: string): boolean { + const index = this._rowSettingCheckBoxList?.indexOf(key); + if (index != undefined) { + return this._rowSettingCheckedBoxList?.[index] === true; + } + return false; + } + //@ts-ignore expandFunc(rootRow: TraceRow, sp: SpSystemTrace): void { if (this._enableCollapseChart && !this.funcExpand) { @@ -1388,6 +1412,10 @@ export class TraceRow extends HTMLElement { if (this.tipEL) { this.tipEL.style.display = 'none'; } + if (this.rowSettingCheckBoxPop) { + //@ts-ignore + this.rowSettingCheckBoxPop.visible = false; + } } loadingPin1: number = 0; @@ -1395,7 +1423,7 @@ export class TraceRow extends HTMLElement { static currentActiveRows: Array = []; drawFrame(): void { - if (!this.hasAttribute('row-hidden')) { + if (!this.hasAttribute('row-hidden') && !this.hasAttribute('row-discard')) { if (!this.loadingFrame || window.isLastFrame || !this.isComplete) { if (this.needRefresh || window.isLastFrame) { this.loadingFrame = true; diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index aa0ff8ee..386717a3 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -16,7 +16,12 @@ import { BaseElement, element } from '../../../../base-ui/BaseElement'; import { type LitTabs } from '../../../../base-ui/tabs/lit-tabs'; import { LitTabpane } from '../../../../base-ui/tabs/lit-tabpane'; -import { BoxJumpParam, SelectionParam, SliceBoxJumpParam } from '../../../bean/BoxSelection'; +import { + BoxJumpParam, + SelectionParam, + SysCallBoxJumpParam, + SliceBoxJumpParam +} from '../../../bean/BoxSelection'; import { type TabPaneCurrentSelection } from '../sheet/TabPaneCurrentSelection'; import { type TabPaneFlag } from '../timer-shaft/TabPaneFlag'; import { type Flag } from '../timer-shaft/Flag'; @@ -28,6 +33,7 @@ import { type CpuStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerC import { CpuFreqStruct } from '../../../database/ui-worker/ProcedureWorkerFreq'; import { CpuFreqLimitsStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerCpuFreqLimits'; import { type ThreadStruct } from '../../../database/ui-worker/ProcedureWorkerThread'; +import { type ThreadSysCallStruct } from '../../../database/ui-worker/ProcedureWorkerThreadSysCall'; import { type FuncStruct } from '../../../database/ui-worker/ProcedureWorkerFunc'; import { ProcessMemStruct } from '../../../database/ui-worker/ProcedureWorkerMem'; import { CpuStateStruct } from '../../../database/ui-worker/cpu/ProcedureWorkerCpuState'; @@ -110,6 +116,7 @@ import { PerfFunctionAsmParam } from '../../../bean/PerfAnalysis'; import { info, error } from '../../../../log/Log'; import { XpowerThreadCountStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerThreadCount'; import { XpowerGpuFreqCountStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerGpuFreqCount'; +import { TabPaneSysCallChild } from '../sheet/process/TabPaneSysCallChild'; @element('trace-sheet') @@ -303,6 +310,10 @@ export class TraceSheet extends BaseElement { this.getComponentByID('box-slices')?.addEventListener('td-click', (evt: unknown) => { this.tdSliceClickHandler(evt); }); + // @ts-ignore + this.getComponentByID('box-thread-syscall')?.addEventListener('td-click', (evt: unknown) => { + this.tdSysCallClickHandler(evt); + }); } private perfAnalysisListener(evt: MouseEvent): void { @@ -942,6 +953,9 @@ export class TraceSheet extends BaseElement { scrollPrio, callback ); + + displaySysCallData = (data: ThreadSysCallStruct) => + this.displayTab('current-selection').setSysCallData(data); displayMemData = (data: ProcessMemStruct): void => this.displayTab('current-selection').setMemData(data); displayHangData = (data: HangStruct, sp: SpSystemTrace, scrollCallback: Function): Promise => @@ -1457,6 +1471,43 @@ export class TraceSheet extends BaseElement { (pane.children.item(0) as TabPaneBoxChild).data = param; } + tdSysCallClickHandler(e: unknown): void { + // @ts-ignore + this.currentPaneID = e.target.parentElement.id; + //隐藏除了当前Tab页的其他Tab页 + this.shadowRoot!.querySelectorAll('lit-tabpane').forEach((it): boolean => + it.id !== this.currentPaneID ? (it.hidden = true) : (it.hidden = false) + ); //todo:看能不能优化 + let pane = this.getPaneByID('box-thread-syscall-child'); //通过Id找到需要展示的Tab页 + pane.closeable = true; //关闭的ican显示 + pane.hidden = false; + this.litTabs!.activeByKey(pane.key); //显示key值对应的Tab页 + // @ts-ignore + pane.tab = e.detail.name; //设置Tab页标题,有的标题可直接用,有的标题需在此转换成需要展示的字符串 + let param = new SysCallBoxJumpParam(); + param.traceId = this.selection!.traceId; + param.leftNs = this.selection!.leftNs; + param.rightNs = this.selection!.rightNs; + // @ts-ignore + const level = e.detail.level; + if (level === 'Process') { + // @ts-ignore + param.processId = [e.detail.id]; + } else if (level === 'Thread'){ + // @ts-ignore + param.processId = [e.detail.parentId]; + // @ts-ignore + param.threadId = [e.detail.id]; + } else { + // @ts-ignore + param.threadId = [e.detail.parentId]; + // @ts-ignore + param.sysCallId = e.detail.id; + } + param.isJumpPage = true; // @ts-ignore + (pane.children.item(0) as TabPaneSysCallChild).data = param; + } + //Slice Tab点击Occurrences列下的td进行跳转 tdSliceClickHandler(e: unknown): void { // @ts-ignore diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index f5de8615..d49ff7a6 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -91,6 +91,7 @@ import { TabPaneIOTierStatisticsAnalysis } from '../sheet/file-system/TabPaneIOT import { TabPaneVirtualMemoryStatisticsAnalysis } from '../sheet/file-system/TabPaneVirtualMemoryStatisticsAnalysis'; import { TabPaneCurrent } from '../sheet/TabPaneCurrent'; import { TabPaneStartup } from '../sheet/process/TabPaneStartup'; +import { TabPaneSysCall } from '../sheet/process/TabPaneSysCall'; import { TabPaneStaticInit } from '../sheet/process/TabPaneStaticInit'; import { TabPaneTaskFrames } from '../sheet/task/TabPaneTaskFrames'; import { TabPaneFrameDynamic } from '../sheet/frame/TabPaneFrameDynamic'; @@ -151,6 +152,7 @@ import { TabPanePerfAsync } from '../sheet/hiperf/TabPerfAsyncList'; import { TabPaneUserPlugin } from '../sheet/userPlugin/TabPaneUserPlugin'; import { TabPaneDmaFence } from '../sheet/dma-fence/TabPaneDmaFenceSelect'; import { TabPaneSliceChild } from '../sheet/process/TabPaneSliceChild'; +import { TabPaneSysCallChild } from '../sheet/process/TabPaneSysCallChild'; import { TabPerfFuncAsm } from '../sheet/hiperf/TabPerfFuncAsm'; export let tabConfig: { @@ -216,6 +218,15 @@ export let tabConfig: { type: TabPaneStartup, require: (param: SelectionParam) => param.processIds.length > 0 && param.startup, }, + 'box-thread-syscall': { + title: 'SysCall Event', + type: TabPaneSysCall, + require: (param: SelectionParam) => param.processSysCallIds.length > 0 || param.threadSysCallIds.length > 0, + }, + 'box-thread-syscall-child': { + title: '', + type: TabPaneSysCallChild, + }, 'box-process-static-init': { title: 'Static Initialization', type: TabPaneStaticInit, diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index dc406525..e47afafd 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -55,6 +55,7 @@ export class Utils { totalNS: number = 1; private trace1ThreadMap: Map = new Map(); private trace1ProcessMap: Map = new Map(); + sysCallEventTidsMap: Map = new Map(); private trace1SchedSliceMap: Map< string, { @@ -223,6 +224,7 @@ export class Utils { this.trace2ThreadMap.clear(); this.trace1SchedSliceMap.clear(); this.trace2SchedSliceMap.clear(); + this.sysCallEventTidsMap.clear(); Utils.distributedTrace = []; } diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 9e4d5486..46ae779b 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -51,6 +51,7 @@ import { queryDistributedRelationAllData, queryRWakeUpFrom, queryRunnableTimeByRunning, + querySysCallEventDetail, queryThreadStateArgs, queryThreadWakeUp, queryThreadWakeUpFrom, @@ -70,6 +71,7 @@ import { XpowerAppDetailStruct } from '../../../database/ui-worker/ProcedureWork import { XpowerWifiStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerWifi'; import { XpowerThreadCountStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerThreadCount'; import { XpowerGpuFreqCountStruct } from '../../../database/ui-worker/ProcedureWorkerXpowerGpuFreqCount'; +import { ThreadSysCallStruct } from '../../../database/ui-worker/ProcedureWorkerThreadSysCall'; 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.'; @@ -1197,6 +1199,35 @@ export class TabPaneCurrentSelection extends BaseElement { }); } + async setSysCallData(data: ThreadSysCallStruct) { + this.setTableHeight('350px'); + this.initCanvas(); + let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector('#leftTitle'); + this.setTitleAndButtonStyle(); + if (leftTitle) { + leftTitle.innerText = 'SysCall Event'; + } + let list: unknown[] = []; + list.push({ name: 'Name', value: `${data.name} [${data.id}]` }); + list.push({ + name: 'StartTime(Relative)', + value: getTimeString(data.startTs || 0), + }); + list.push({ + name: 'StartTime(Absolute)', + value: ((data.startTs || 0) + Utils.getInstance().getRecordStartNS()) / 1000000000 + 's', + }); + list.push({ name: 'Duration', value: getTimeString(data.dur || 0) }); + const eventValue = await querySysCallEventDetail(data.itid!, data.startTs! + Utils.getInstance().getRecordStartNS(), data.dur!); + if (eventValue[0]) { + list.push({ name: 'Process', value: `${eventValue[0].pName} [${data.pid}]` }); + list.push({ name: 'Thread', value: `${eventValue[0].tName} [${data.tid}]` }); + list.push({ name: 'args', value: eventValue[0].args }); + list.push({ name: 'ret', value: eventValue[0].ret }); + } + this.currentSelectionTbl!.dataSource = list; + } + async setThreadData( data: ThreadStruct, scrollCallback: ((d: unknown) => void) | undefined, diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSysCall.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSysCall.ts new file mode 100644 index 00000000..45304351 --- /dev/null +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSysCall.ts @@ -0,0 +1,243 @@ +/* + * 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 { SelectionParam } from '../../../../bean/BoxSelection'; +import { log } from '../../../../../log/Log'; +import { getProbablyTime } from '../../../../database/logic-worker/ProcedureLogicWorkerCommon'; +import { resizeObserver } from '../SheetUtils'; +import { querySysCallEventWithBoxSelect } from '../../../../database/sql/ProcessThread.sql'; +import { Utils } from '../../base/Utils'; +import { SysCallMap } from '../../base/SysCallUtils'; + +interface SysCallItem { + nameId: number; + name: string; + pName: string; + tName: string; + tid: number; + pid: number; + totalCount: number; + sumDur: number; + durStr?: string; +} + +interface SysCallTreeItem { + name: string; + id: number; + parentId?: number; + totalCount: number; + sumDur: number; + durStr: string; + level: string; + children: SysCallTreeItem[] +} + +@element('tabpane-syscall') +export class TabPaneSysCall extends BaseElement { + private sysCallTbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private sysCallSource: Array = []; + private currentSelectionParam: SelectionParam | undefined; + + set data(sysCallParam: SelectionParam | unknown) { + if (this.currentSelectionParam === sysCallParam) { + return; + } // @ts-ignore + this.currentSelectionParam = sysCallParam; + //@ts-ignore + this.sysCallTbl?.shadowRoot?.querySelector('.table')?.style?.height = `${this.parentElement!.clientHeight - 45}px`; // @ts-ignore + this.range!.textContent = `Selected range: ${((sysCallParam.rightNs - sysCallParam.leftNs) / 1000000.0).toFixed( + 5 + )} ms`; + this.sysCallTbl!.loading = true; + let ipidArr: number[] = []; + Array.from(Utils.getInstance().sysCallEventTidsMap.values()).forEach(it => { + //@ts-ignore + if (sysCallParam?.processSysCallIds?.includes(it.pid)) { + ipidArr.push(it.ipid); + } + }) + //@ts-ignore + querySysCallEventWithBoxSelect(ipidArr, sysCallParam.threadSysCallIds, sysCallParam.leftNs, sysCallParam.rightNs).then( + (retult) => { + this.flatternToTree(retult as SysCallItem[]); + } + ); + } + + private flatternToTree(result: SysCallItem[]): void { + this.sysCallTbl!.loading = false; + if (result !== null && result.length > 0) { + log(`getTabsysCalls result size : ${result.length}`); + let map: Map = new Map(); + result.forEach((item) => { + item.name = SysCallMap.get(item.nameId) || ''; + item.durStr = getProbablyTime(item.sumDur); + this.processSysCallItem(item, map); + }); + let sysCalls: SysCallTreeItem[] = Array.from(map.values()); + this.sysCallSource = sysCalls; + this.sysCallTbl!.recycleDataSource = this.sysCallSource; + } else { + this.sysCallSource = []; + this.sysCallTbl!.recycleDataSource = []; + } + } + + private processSysCallItem(item: SysCallItem, map: Map): void { + const treeItem: SysCallTreeItem = { + id: item.nameId, + name: item.name, + level: 'SysCall', + parentId: item.tid, + totalCount: item.totalCount, + sumDur: item.sumDur, + durStr: item.durStr!, + children:[] + }; + let ps = map.get(item.pid!); + if (ps) { + ps.sumDur += item.sumDur || 0; + ps.durStr = getProbablyTime(ps.sumDur); + ps.totalCount += item.totalCount || 0; + let ts = ps.children?.find(it => it.id === item.tid); + if (ts) { + ts.sumDur += item.sumDur || 0; + ts.durStr = getProbablyTime(ts.sumDur); + ts.totalCount += item.totalCount || 0; + ts.children.push(treeItem); + } else { + ps.children.push({ + id: item.tid, + name: item.tName, + parentId: item.pid, + level: 'Thread', + totalCount: item.totalCount, + sumDur: item.sumDur, + durStr: item.durStr!, + children: [treeItem] + }) + } + } else { + map.set(item.pid!, { + id: item.pid, + level: 'Process', + name: `${item.pName} [${item.pid}]`, + totalCount: item.totalCount, + sumDur: item.sumDur, + durStr: item.durStr!, + children: [{ + id: item.tid, + level: 'Thread', + parentId: item.pid, + name: `${item.tName} [${item.tid}]`, + durStr: item.durStr!, + totalCount: item.totalCount, + sumDur: item.sumDur, + children: [treeItem] + }] + }); + } + } + + initElements(): void { + this.sysCallTbl = this.shadowRoot?.querySelector('#tb-syscall'); + this.range = this.shadowRoot?.querySelector('#syscall-time-range'); + this.sysCallTbl!.addEventListener('column-click', (evt: unknown) => { + // @ts-ignore + this.sortByColumn(evt.detail); + }); + } + + connectedCallback(): void { + super.connectedCallback(); + resizeObserver(this.parentElement!, this.sysCallTbl!); + } + + initHtml(): string { + return ` + +
+
+ +
+
+ + + + + + + + +
+ `; + } + + sortByColumn(sortDetail: unknown): void { + let compare = (itemA: SysCallTreeItem, itemB: SysCallTreeItem): number => { + // @ts-ignore + if (sortDetail.key === 'durStr') { + // @ts-ignore + if (sortDetail.sort === 0) { + return itemA.sumDur - itemB.sumDur; // @ts-ignore + } else if (sortDetail.sort === 1) { + return itemA.sumDur - itemB.sumDur; + } else { + return itemB.sumDur - itemA.sumDur; + } + } else { + // @ts-ignore + if (sortDetail.sort === 0) { + return itemA.totalCount - itemB.totalCount; // @ts-ignore + } else if (sortDetail.sort === 1) { + return itemA.totalCount - itemB.totalCount; + } else { + return itemB.totalCount - itemA.totalCount; + } + } + }; + this.sysCallSource.forEach((syscall) => { + syscall.children?.sort(compare); + }); + const deepSort = (arr: SysCallTreeItem[]) => { + arr.sort(compare); + arr.forEach(item => { + if (item.children) { + deepSort(item.children); + } + }) + }; + deepSort(this.sysCallSource); + this.sysCallTbl!.recycleDataSource = this.sysCallSource; + } +} diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSysCallChild.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSysCallChild.ts new file mode 100644 index 00000000..5b931745 --- /dev/null +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSysCallChild.ts @@ -0,0 +1,195 @@ +/* + * 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 { SysCallBoxJumpParam, SelectionData } from '../../../../bean/BoxSelection'; +import { Utils } from '../../base/Utils'; +import { resizeObserver } from '../SheetUtils'; +import { querySysCallEventWithRange } from '../../../../database/sql/ProcessThread.sql'; +import { SysCallMap } from '../../base/SysCallUtils'; + +interface SysCallChildItem { + pName: string; + tName: string; + pid: number; + tid: number; + nameId: number; + name: string; + startTs: string; + startTime: number; + absoluteTs: number; + dur: number; + args: string; + ret: number +} + +@element('tabpane-syscall-child') +export class TabPaneSysCallChild extends BaseElement { + private boxChildTbl: LitTable | null | undefined; + private boxChildRange: HTMLLabelElement | null | undefined; + private boxChildSource: Array = []; + private boxChildParam: SysCallBoxJumpParam | null | undefined; + + set data(boxChildValue: SysCallBoxJumpParam) { + //切换Tab页 保持childTab数据不变 除非重新点击跳转 + if (boxChildValue === this.boxChildParam || !boxChildValue.isJumpPage) { + return; + } // @ts-ignore + this.boxChildParam = boxChildValue; + //显示框选范围对应的时间 + this.boxChildRange!.textContent = `Selected range: ${parseFloat( + ((boxChildValue.rightNs - boxChildValue.leftNs) / 1000000.0).toFixed(5) + )} ms`; + this.boxChildTbl!.recycleDataSource = []; + this.getDataByDB(boxChildValue); + } + + initElements(): void { + this.boxChildTbl = this.shadowRoot?.querySelector('#tb-syscall-child'); + this.boxChildRange = this.shadowRoot?.querySelector('#time-range'); + this.boxChildTbl!.addEventListener('column-click', (evt): void => { + // @ts-ignore + this.sortByColumn(evt.detail); + }); + //监听row的点击事件,在对应起始时间上画标记棋子 + this.boxChildTbl!.addEventListener('row-click', (evt): void => { + //@ts-ignore + let param = evt.detail.data; + param.isSelected = true; + this.boxChildTbl!.clearAllSelection(param); + this.boxChildTbl!.setCurrentSelection(param); + document.dispatchEvent( + new CustomEvent('triangle-flag', { + detail: { time: [param.startTime], type: 'triangle' }, + }) + ); + }); + } + + connectedCallback(): void { + super.connectedCallback(); + resizeObserver(this.parentElement!, this.boxChildTbl!); + } + + getDataByDB(val: SysCallBoxJumpParam): void { + this.boxChildTbl!.loading = true; + let ipidArr: number[] = []; + let itidArr: number[] = []; + Array.from(Utils.getInstance().sysCallEventTidsMap.values()).forEach(it => { + if (val?.processId?.includes(it.pid)) { + ipidArr.push(it.ipid); + } + if (val?.threadId?.includes(it.tid)) { + itidArr.push(it.itid); + } + }); + querySysCallEventWithRange(ipidArr, itidArr, val.leftNs, val.rightNs, val.sysCallId).then(result => { + this.boxChildTbl!.loading = false; + const arr: Array = []; + result.forEach(it => { + arr.push({ + pName: it.pName, + tName: it.tName, + pid: it.pid, + tid: it.tid, + name: SysCallMap.get(it.nameId) || 'unknown event', + nameId: it.nameId, + startTime: it.startTs, + startTs: Utils.getProbablyTime(it.startTs), + // @ts-ignore + absoluteTs: ((window as unknown).recordStartNS + it.startTs) / 1000000000, + dur: it.dur / 1000000, + args: it.args, + ret: it.ret + }); + }); + this.boxChildSource = arr; + if (this.boxChildTbl) { + // @ts-ignore + this.boxChildTbl.recycleDataSource = arr; + } + }); + } + + initHtml(): string { + return ` + + +
+ + + + + + + + + + + + + + + + + + +
+ `; + } + + sortByColumn(detail: unknown): void { + // @ts-ignore + function compare(property, sort, type) { + return function (boxChildLeftData: SelectionData, boxChildRightData: SelectionData): number { + if (type === 'number') { + return sort === 2 + // @ts-ignore + ? parseFloat(boxChildRightData[property]) - parseFloat(boxChildLeftData[property]) + // @ts-ignore + : parseFloat(boxChildLeftData[property]) - parseFloat(boxChildRightData[property]); + } else { + // @ts-ignore + if (boxChildRightData[property] > boxChildLeftData[property]) { + return sort === 2 ? 1 : -1; + } else { + // @ts-ignore + if (boxChildRightData[property] === boxChildLeftData[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + }; + } + + // @ts-ignore + this.boxChildSource.sort(compare(detail.key, detail.sort, 'string')); + this.boxChildTbl!.recycleDataSource = this.boxChildSource; + } +} diff --git a/ide/src/trace/database/data-trafic/process/ThreadSysCallDataReceiver.ts b/ide/src/trace/database/data-trafic/process/ThreadSysCallDataReceiver.ts new file mode 100644 index 00000000..61af08b9 --- /dev/null +++ b/ide/src/trace/database/data-trafic/process/ThreadSysCallDataReceiver.ts @@ -0,0 +1,68 @@ +// 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 { Args } from '../CommonArgs'; +import { TraficEnum } from '../utils/QueryEnum'; + +export const chartThreadSysCallDataSql = (args: Args):unknown => { + return `SELECT syscall_number as id, itid, ts - ${args.recordStartNS} as startTs, dur + from syscall + where itid = ${args.itid} + and startTs + dur >= ${Math.floor(args.startNS)} + and startTs <= ${Math.floor(args.endNS)} + `; +}; + +export function threadSysCallDataReceiver(data: unknown, proc: Function): void { + //@ts-ignore + let sql = chartThreadSysCallDataSql(data.params); + let res = proc(sql); //@ts-ignore + arrayBufferHandler(data, res, data.params.trafic !== TraficEnum.SharedArrayBuffer, false); +} + +function arrayBufferHandler(data: unknown, res: unknown[], transfer: boolean, isEmpty: boolean): void { + //@ts-ignore + let startTs = new Float64Array(res.length); //@ts-ignore + let dur = new Float64Array(res.length); //@ts-ignore + let id = new Int32Array(res.length); //@ts-ignore + let itid = new Int32Array(res.length); //@ts-ignore + res.forEach((it, i) => { + //@ts-ignore + data.params.trafic === TraficEnum.ProtoBuffer && (it = it.processThreadSysCallData); //@ts-ignore + startTs[i] = it.startTs; //@ts-ignore + dur[i] = it.dur; //@ts-ignore + id[i] = it.id; //@ts-ignore + itid[i] = it.itid; //@ts-ignore + }); + (self as unknown as Worker).postMessage( + { + //@ts-ignore + id: data.id, //@ts-ignorew + action: data.action, + results: transfer + ? { + id: id.buffer, + itid: itid.buffer, + startTs: startTs.buffer, + dur: dur.buffer, + } + : {}, + len: res.length, + transfer: transfer, + isEmpty: isEmpty, + }, + transfer + ? [startTs.buffer, dur.buffer, id.buffer, itid.buffer] + : [] + ); +} diff --git a/ide/src/trace/database/data-trafic/process/ThreadSysCallDataSender.ts b/ide/src/trace/database/data-trafic/process/ThreadSysCallDataSender.ts new file mode 100644 index 00000000..2e296f12 --- /dev/null +++ b/ide/src/trace/database/data-trafic/process/ThreadSysCallDataSender.ts @@ -0,0 +1,82 @@ +// 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 { CHART_OFFSET_LEFT, MAX_COUNT, QueryEnum, TraficEnum } from '../utils/QueryEnum'; +import { getThreadPool } from '../../SqlLite'; +import { TraceRow } from '../../../component/trace/base/TraceRow'; +import { SysCallMap } from '../../../component/trace/base/SysCallUtils'; +import { Utils } from '../../../component/trace/base/Utils'; +import { ThreadSysCallStruct } from '../../ui-worker/ProcedureWorkerThreadSysCall'; + +export function threadSysCallDataSender( + itid: number, + tid: number, + pid: number, + row: TraceRow, + traceId?: string +): Promise { + let trafic: number = TraficEnum.Memory; + let width = row.clientWidth || row.parentRowEl!.clientWidth - CHART_OFFSET_LEFT; + if (trafic === TraficEnum.SharedArrayBuffer && !row.sharedArrayBuffers) { + row.sharedArrayBuffers = { + startTs: new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * MAX_COUNT), + dur: new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * MAX_COUNT), + id: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * MAX_COUNT), + itid: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * MAX_COUNT), + ipid: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * MAX_COUNT), + }; + } + return new Promise((resolve): void => { + getThreadPool(traceId).submitProto( + QueryEnum.ThreadDataSysCall, + { + itid: itid, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + recordStartNS: Utils.getInstance().getRecordStartNS(traceId), + recordEndNS: Utils.getInstance().getRecordEndNS(traceId), + width: width, + trafic: trafic, + sharedArrayBuffers: row.sharedArrayBuffers, + }, + (res: unknown, len: number, transfer: boolean, isEmpty: boolean): void => { + if (isEmpty) { + resolve(true); + } else { + resolve(arrayBufferHandler(transfer ? res : row.sharedArrayBuffers, len, tid, pid)); + } + } + ); + }); +} + +function arrayBufferHandler(buffers: unknown, len: number, tid: number, pid: number): ThreadSysCallStruct[] { + let outArr: ThreadSysCallStruct[] = []; //@ts-ignore + let startTs = new Float64Array(buffers.startTs); //@ts-ignore + let dur = new Float64Array(buffers.dur); //@ts-ignore + let id = new Int32Array(buffers.id); //@ts-ignore + let itid = new Int32Array(buffers.itid); //@ts-ignore + for (let i = 0; i < len; i++) { + //@ts-ignore + outArr.push({ + startTs: startTs[i], + dur: dur[i], + id: id[i], + itid: itid[i], + name: SysCallMap.get(id[i]), + tid: tid, + pid: pid, + } as ThreadSysCallStruct); + } + return outArr; +} diff --git a/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts b/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts index 5531a9f3..f551dee7 100644 --- a/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts +++ b/ide/src/trace/database/data-trafic/utils/ExecProtoForWorker.ts @@ -87,6 +87,7 @@ import { hangDataReceiver } from '../HangDataReceiver'; import { xpowerStatisticDataReceiver } from '../xpower/XpowerStatisticDataReceiver'; import { xpowerDataGpuFreqCountReceiver, xpowerDataGpuFreqReceiver } from '../xpower/XpowerGpuFrequencyRecevier'; import { xpowerDataThreadCountReceiver, xpowerDataThreadInfoReceiver } from '../xpower/XpowerThreadReceiver'; +import { threadSysCallDataReceiver } from '../process/ThreadSysCallDataReceiver'; // @ts-ignore const traficHandlers: Map = new Map([]); // @ts-ignore @@ -100,6 +101,7 @@ traficHandlers.set(QueryEnum.CpuStateData, cpuStateReceiver); traficHandlers.set(QueryEnum.CpuFreqLimitData, cpuFreqLimitReceiver); traficHandlers.set(QueryEnum.ProcessData, processDataReceiver); traficHandlers.set(QueryEnum.ThreadData, threadDataReceiver); +traficHandlers.set(QueryEnum.ThreadDataSysCall, threadSysCallDataReceiver); traficHandlers.set(QueryEnum.FuncData, funcDataReceiver); traficHandlers.set(QueryEnum.HiperfCallChart, hiPerfCallChartDataHandler); traficHandlers.set(QueryEnum.HiperfCallStack, hiPerfCallStackCacheHandler); diff --git a/ide/src/trace/database/data-trafic/utils/QueryEnum.ts b/ide/src/trace/database/data-trafic/utils/QueryEnum.ts index f50d855c..e5cd4375 100644 --- a/ide/src/trace/database/data-trafic/utils/QueryEnum.ts +++ b/ide/src/trace/database/data-trafic/utils/QueryEnum.ts @@ -98,8 +98,8 @@ export enum QueryEnum { XpowerGpuFreqData = 308, XpowerStatisticData = 309, XpowerWifiData = 310, - XpowerAppDetailData = 311 - + XpowerAppDetailData = 311, + ThreadDataSysCall = 312, } export const MAX_COUNT = 2000; export enum TraficEnum { diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index 18daf4b6..5c740177 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -604,6 +604,108 @@ WHERE )` ); +export const querySysCallThreadIds = (traceId?: string): Promise> => + query( + 'querySysCallThreadIds', + `SELECT tid, pid, itid, thread.ipid +FROM + thread left join process on thread.ipid = process.ipid +WHERE + itid IN (SELECT DISTINCT( itid ) FROM syscall ) + `, + {}, + { traceId: traceId} + ); + +export const querySysCallEventDetail = +(itid: number, startTs: number, dur: number, traceId?: string): Promise> => + query( + 'querySysCallEventDetail', + `SELECT tid, thread.name as tName, pid, process.name as pName, args, ret +FROM + (select * from syscall where itid= ${itid} and ts = ${startTs} and dur = ${dur}) A + left join thread on A.itid = thread.itid + left join process on thread.ipid = process.ipid + `, + {}, + { traceId: traceId} + ); + +export const querySysCallEventWithBoxSelect = +(ipidArr: Array, itidArr: Array, leftNs: number, rightNs: number): Promise> => + query( + 'querySysCallEventWithBoxSelect', + `select ifnull(process.name, 'Process') as pName, + ifnull(thread.name, 'Thread') as tName, + process.pid, + thread.tid, + A.nameId, + A.sumDur, + A.totalCount +from ( +select itid, syscall_number as nameId, sum(dur) as sumDur, count(1) as totalCount +from syscall +where + ${itidArr.length > 0 ? 'itid in (' + itidArr.join(',') + ')' : '1 = 1'} + and not ((ts - ${window.recordStartNS} + ifnull(dur,0) < ${leftNs}) or (ts - ${window.recordStartNS} > ${rightNs})) +group by itid, syscall_number +) as A +left join thread on A.itid = thread.itid +left join process on thread.ipid = process.ipid +where + ${ipidArr.length > 0 ? 'thread.ipid in (' + ipidArr.join(',') + ')' : '1 = 1'} + `, + {} + ); + + export const querySysCallEventWithRange = +(ipidArr: Array, itidArr: Array, leftNs: number, rightNs: number, sysCallId?: number): Promise> => + query( + 'querySysCallEventWithRange', + `select ifnull(process.name, 'Process') as pName, + ifnull(thread.name, 'Thread') as tName, + process.pid, + thread.tid, + A.nameId, + A.startTs, + A.dur, + A.args, + A.ret +from ( + select itid, syscall_number as nameId, (ts - ${window.recordStartNS}) as startTs, dur, args, ret + from syscall + where + ${itidArr.length > 0 ? 'itid in (' + itidArr.join(',') + ')' : '1 = 1'} + and ${sysCallId !== undefined ? 'syscall_number = ' + sysCallId : '1 = 1' } + and not ((ts - ${window.recordStartNS} + ifnull(dur,0) < ${leftNs}) or (ts - ${window.recordStartNS} > ${rightNs})) + ) as A + left join thread on A.itid = thread.itid + left join process on thread.ipid = process.ipid +where + ${ipidArr.length > 0 ? 'thread.ipid in (' + ipidArr.join(',') + ')' : '1 = 1'} + `, + {} + ); + export const queryProcessContentCount = (traceId?: string): Promise> => query( `queryProcessContentCount`, diff --git a/ide/src/trace/database/ui-worker/ProcedureWorker.ts b/ide/src/trace/database/ui-worker/ProcedureWorker.ts index cba4720f..b4391b7e 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorker.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorker.ts @@ -78,6 +78,7 @@ import { XpowerWifiRender } from './ProcedureWorkerXpowerWifi'; import { XpowerGpuFreqCountRender } from './ProcedureWorkerXpowerGpuFreqCount'; import { XpowerGpuFreqRender } from './ProcedureWorkerXpowerGpuFreq'; import { SnapShotRender } from './ProcedureWorkerSnaps'; +import { ThreadSysCallRender } from './ProcedureWorkerThreadSysCall'; let dataList: unknown = {}; let dataList2: unknown = {}; @@ -106,6 +107,7 @@ export let renders = { 'heap-snapshot': new HeapSnapshotRender(), mem: new MemRender(), thread: new ThreadRender(), + threadSysCall: new ThreadSysCallRender(), func: new FuncRender(), native: new NativeMemoryRender(), 'HiPerf-Group': new EmptyRender(), diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerThreadSysCall.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerThreadSysCall.ts new file mode 100644 index 00000000..aac9df64 --- /dev/null +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerThreadSysCall.ts @@ -0,0 +1,137 @@ +/* + * 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 { + dataFilterHandler, + BaseStruct, + isFrameContainPoint, + Render, + RequestMessage, + drawString, + drawLoadingFrame, +} from './ProcedureWorkerCommon'; +import { TraceRow } from '../../component/trace/base/TraceRow'; +import { ColorUtils } from '../../component/trace/base/ColorUtils'; +import { SpSystemTrace } from '../../component/SpSystemTrace'; + +export class ThreadSysCallRender extends Render { + renderMainThread( + threadReq: { + context: CanvasRenderingContext2D; + useCache: boolean; + type: string; + translateY: number; + }, + row: TraceRow + ): void { + let threadList = row.dataList; + let threadFilter = row.dataListCache; + dataFilterHandler(threadList, threadFilter, { + startKey: 'startTs', + durKey: 'dur', + startNS: TraceRow.range?.startNS ?? 0, + endNS: TraceRow.range?.endNS ?? 0, + totalNS: TraceRow.range?.totalNS ?? 0, + frame: row.frame, + paddingTop: 3, + useCache: threadReq.useCache || !(TraceRow.range?.refresh ?? false), + }); + drawLoadingFrame(threadReq.context, threadFilter, row); + threadReq.context.beginPath(); + let find: boolean = false; + for (let re of threadFilter) { + re.translateY = threadReq.translateY; + ThreadSysCallStruct.drawThread(threadReq.context, re); + if (row.isHover && re.frame && isFrameContainPoint(re.frame!, row.hoverX, row.hoverY)) { + ThreadSysCallStruct.hoverStruct = re; + find = true; + } + } + threadReq.context.closePath(); + } + + render(threadReq: RequestMessage, threadList: Array, threadFilter: Array): void {} +} + +export function ThreadSysCallStructOnClick( + clickRowType: string, + sp: SpSystemTrace, + entry?: ThreadSysCallStruct +): Promise { + return new Promise((resolve, reject) => { + if (clickRowType === TraceRow.ROW_TYPE_THREAD_SYS_CALL && (ThreadSysCallStruct.hoverStruct || entry)) { + ThreadSysCallStruct.selectStruct = entry || ThreadSysCallStruct.hoverStruct; + sp.timerShaftEL?.drawTriangle(ThreadSysCallStruct.selectStruct!.startTs || 0, 'inverted'); + sp.traceSheetEL?.displaySysCallData(ThreadSysCallStruct.selectStruct!); + sp.timerShaftEL?.modifyFlagList(undefined); + reject(new Error()); + } else { + resolve(null); + } + }); +} +export class ThreadSysCallStruct extends BaseStruct { + static hoverStruct: ThreadSysCallStruct | undefined; + static selectStruct: ThreadSysCallStruct | undefined; + name: string | undefined; + tid: number | undefined; + id: number | undefined; + pid: number | undefined; + itid: number | undefined; + startTs: number | undefined; + dur: number | undefined; + args: string | undefined; + ret: number | undefined; + + static drawThread(threadContext: CanvasRenderingContext2D, data: ThreadSysCallStruct): void { + if (data.frame) { + threadContext.globalAlpha = 1; + threadContext.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)]; + let textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', 0, ColorUtils.FUNC_COLOR.length)]; + if (ThreadSysCallStruct.hoverStruct && data.name === ThreadSysCallStruct.hoverStruct.name) { + threadContext.globalAlpha = 0.7; + } + threadContext.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); + threadContext.fillStyle = ColorUtils.funcTextColor(textColor); + threadContext.textBaseline = 'middle'; + threadContext.font = '8px sans-serif'; + data.frame.width > 7 && drawString(threadContext, data.name!, 2, data.frame, data); + if ( + ThreadSysCallStruct.selectStruct && + ThreadSysCallStruct.equals(ThreadSysCallStruct.selectStruct, data) + ) { + threadContext.strokeStyle = '#232c5d'; + threadContext.lineWidth = 2; + threadContext.strokeRect( + data.frame.x, + data.frame.y, + data.frame.width - 2, + data.frame.height + ); + } + } + } + + static equals(d1: ThreadSysCallStruct, d2: ThreadSysCallStruct): boolean { + return ( + d1 && + d2 && + d1.tid === d2.tid && + d1.name === d2.name && + d1.startTs === d2.startTs && + d1.dur === d2.dur + ); + } +} -- Gitee From d417995b80db3cd5932ba9ca6ee1c912e1a54869 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Sat, 10 May 2025 16:33:47 +0800 Subject: [PATCH 07/66] =?UTF-8?q?trace=E6=96=B0=E5=A2=9Etag=E3=80=81cat?= =?UTF-8?q?=E3=80=81args=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- trace_streamer/doc/des_tables.md | 14 ++++- trace_streamer/src/base/string_help.cpp | 6 +- trace_streamer/src/base/string_help.h | 4 +- trace_streamer/src/filter/slice_filter.cpp | 13 +++-- trace_streamer/src/parser/common_types.h | 14 ++++- .../src/parser/print_event_parser.cpp | 57 ++++++++++++++++++- .../src/parser/print_event_parser.h | 1 + .../src/table/ftrace/callstack_table.cpp | 32 ++++++++++- .../ftrace/callstack_stdtype.cpp | 53 ++++++++++++++++- .../trace_stdtype/ftrace/callstack_stdtype.h | 31 +++++++++- 10 files changed, 206 insertions(+), 19 deletions(-) diff --git a/trace_streamer/doc/des_tables.md b/trace_streamer/doc/des_tables.md index 22fd158c..494ef6ca 100755 --- a/trace_streamer/doc/des_tables.md +++ b/trace_streamer/doc/des_tables.md @@ -18,7 +18,7 @@ TraceStreamer可以将trace数据源转化为易于理解和使用的数据库 | app_startup | 记录了应用启动相关数据| | args | 记录方法参数集合| | bio_latency_sample | 记录IO操作相关方法调用,及调用栈数据| -| callstack | 记录调用堆栈和异步调用信息,其中depth,stack_id和parent_stack_id仅在非异步调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,否则为线程唯一号| +| callstack | 记录调用堆栈和异步调用信息,其中depth仅在非异步调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,child_callid为子线程唯一号| | clk_event_filter | 记录时钟相关的信息| | clock_event_filter | 此结构用来维护时钟事件,cpu与唯一的ID做关联| | clock_snapshot | 时钟号和时间,时钟名的映射表| @@ -359,8 +359,13 @@ js_heap_sample:记录timeline的时间轴信息 |spanId |TEXT | |parentSpanId |TEXT | |flag |TEXT | +|trace_level |TEXT | +|trace_tag |TEXT | +|custom_category |TEXT | +|custom_args |TEXT | +|child_callid |INT | #### 表描述 -记录调用堆栈和异步调用信息,其中depth,stack_id和parent_stack_id仅在非异步的调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,否则为线程唯一号。 +记录调用堆栈和异步调用信息,其中depth仅在非异步的调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,child_callid为子线程唯一号。 #### 字段详细描述 - id: 唯一标识 - ts: 数据事件上报时间戳 @@ -376,6 +381,11 @@ js_heap_sample:记录timeline的时间轴信息 - spanId:分布式调用关联关系,当前帧的id - parentSpanId: 分布式调用关联关系,当前帧的parent的SpanId,对应当前表的spandId - flag:C表示分布式调用发送方,S表示接受方 +- trace_level:决定trace的等级,log和nolog等级不同,其中log表示详细记录所有相关的调用信息,nolog 表示不记录 +- trace_tag:Tag标签,标识请求的来源或类型 +- custom_category:聚合标签,用于关联同类信息 +- custom_args:自定义参数,用于存储与调用相关的额外信息 +- child_callid:当为异步调用,此时callid为进程唯一号,child_callid为子线程唯一号,反之为无效值 ### clk_event_filter表 #### 表结构 diff --git a/trace_streamer/src/base/string_help.cpp b/trace_streamer/src/base/string_help.cpp index f18316d3..8fd7dbdd 100644 --- a/trace_streamer/src/base/string_help.cpp +++ b/trace_streamer/src/base/string_help.cpp @@ -44,19 +44,21 @@ bool EndWith(const std::string &str, const std::string &res) return str.compare(str.size() - res.size(), res.size(), res) == 0; } -std::vector SplitStringToVec(const std::string &str, const std::string &pat) +std::vector SplitStringToVec(const std::string &str, const std::string &pat, uint32_t expectedCount) { std::vector result; size_t curPos = 0; size_t strSize = str.size(); size_t patSize = pat.size(); + uint32_t endFlag = 0; while (curPos < strSize) { auto patPos = str.find(pat, curPos); - if (patPos == std::string::npos) { + if (patPos == std::string::npos || endFlag == expectedCount) { break; } result.emplace_back(str.substr(curPos, patPos - curPos)); curPos = patPos + patSize; + endFlag++; } if (curPos < strSize) { result.emplace_back(str.substr(curPos)); diff --git a/trace_streamer/src/base/string_help.h b/trace_streamer/src/base/string_help.h index c1046410..df4bb6a7 100644 --- a/trace_streamer/src/base/string_help.h +++ b/trace_streamer/src/base/string_help.h @@ -23,7 +23,9 @@ namespace SysTuning { namespace base { char *GetDemangleSymbolIndex(const char *mangled); -std::vector SplitStringToVec(const std::string &str, const std::string &pat); +std::vector SplitStringToVec(const std::string &str, + const std::string &pat, + uint32_t expectedCount = UINT32_MAX); bool StartWith(const std::string &str, const std::string &res); bool EndWith(const std::string &str, const std::string &res); std::string FormatString(const char *p); diff --git a/trace_streamer/src/filter/slice_filter.cpp b/trace_streamer/src/filter/slice_filter.cpp index 00ad01d3..0e14097f 100644 --- a/trace_streamer/src/filter/slice_filter.cpp +++ b/trace_streamer/src/filter/slice_filter.cpp @@ -325,9 +325,13 @@ size_t SliceFilter::StartSlice(uint64_t timeStamp, uint32_t depth = stack.size(); auto slices = traceDataCache_->GetInternalSlicesData(); uint32_t parentId = depth == 0 ? INVALID_UINT32 : slices->IdsData()[stack.back().index]; - CallStackInternalRow callStackInternalRow = {sliceData.timeStamp, static_cast(sliceData.duration), - sliceData.internalTid, sliceData.cat, - sliceData.name, 0}; + CallStackInternalRow callStackInternalRow = {sliceData.timeStamp, + static_cast(sliceData.duration), + sliceData.internalTid, + sliceData.cat, + sliceData.name, + 0, + INVALID_UINT32}; size_t index = slices->AppendInternalSlice(callStackInternalRow, parentId); if (depth >= std::numeric_limits::max()) { return SIZE_MAX; @@ -464,8 +468,9 @@ uint64_t SliceFilter::StartAsyncSlice(uint64_t timeStamp, // the IDE need a depth to paint call slice in different position of the canvas, the depth of async call // do not mean the parent-to-child relationship, it is different from no-async call uint8_t depth = 0; + uint32_t childCallid = parentId; CallStackInternalRow callStackInternalRow = {timeStamp, static_cast(-1), internalTid, cat, nameIndex, - depth}; + depth, childCallid}; size_t index = slices->AppendInternalAsyncSlice(callStackInternalRow, cookie, parentId); asyncEventFilterMap_.insert(std::make_pair(asyncEventSize_, AsyncEvent{timeStamp, index})); return index; diff --git a/trace_streamer/src/parser/common_types.h b/trace_streamer/src/parser/common_types.h index 16a3e3bd..76d48f77 100644 --- a/trace_streamer/src/parser/common_types.h +++ b/trace_streamer/src/parser/common_types.h @@ -90,7 +90,11 @@ public: args_(point.args_), funcPrefixId_(point.funcPrefixId_), funcPrefix_(point.funcPrefix_), - funcArgs_(point.funcArgs_) + funcArgs_(point.funcArgs_), + traceLevel_(point.traceLevel_), + traceTagId_(point.traceTagId_), + customCategoryId_(point.customCategoryId_), + customArgsId_(point.customArgsId_) { } void operator=(const TracePoint &point) @@ -108,6 +112,10 @@ public: funcPrefixId_ = point.funcPrefixId_; funcPrefix_ = point.funcPrefix_; funcArgs_ = point.funcArgs_; + traceLevel_ = point.traceLevel_; + traceTagId_ = point.traceTagId_; + customCategoryId_ = point.customCategoryId_; + customArgsId_ = point.customArgsId_; } char phase_ = '\0'; uint32_t tgid_ = 0; @@ -123,6 +131,10 @@ public: uint32_t funcPrefixId_ = 0; std::string funcPrefix_ = ""; std::string funcArgs_ = ""; + std::string traceLevel_ = ""; + DataIndex traceTagId_ = INVALID_UINT64; + DataIndex customCategoryId_ = INVALID_UINT64; + DataIndex customArgsId_ = INVALID_UINT64; }; enum class SplitDataDataType { SPLIT_FILE_DATA = 0, SPLIT_FILE_JSON }; diff --git a/trace_streamer/src/parser/print_event_parser.cpp b/trace_streamer/src/parser/print_event_parser.cpp index a7271814..6d324cbb 100644 --- a/trace_streamer/src/parser/print_event_parser.cpp +++ b/trace_streamer/src/parser/print_event_parser.cpp @@ -24,6 +24,9 @@ namespace SysTuning { namespace TraceStreamer { const uint8_t POINT_LENGTH = 1; const uint8_t MAX_POINT_LENGTH = 2; +const uint8_t CATEGORY_INDEX = 2; +const uint8_t PARSER_SYNC_SUM = 2; +const uint8_t PARSER_ASYNC_SUM = 3; PrintEventParser::PrintEventParser(TraceDataCache *dataCache, const TraceStreamerFilters *filter) : EventParserBase(dataCache, filter) { @@ -105,6 +108,10 @@ void PrintEventParser::ParseBeginEvent(const std::string &comm, // add distributed data traceDataCache_->GetInternalSlicesData()->SetDistributeInfo(index, point.chainId_, point.spanId_, point.parentSpanId_, point.flag_); + + // add traceMeta data + traceDataCache_->GetInternalSlicesData()->SetTraceMetadata(index, point.traceLevel_, point.traceTagId_, + point.customArgsId_); if (HandleFrameSliceBeginEvent(point.funcPrefixId_, index, point.funcArgs_, line)) { return; } @@ -136,9 +143,16 @@ void PrintEventParser::ParseStartEvent(const std::string &comm, auto cookie = static_cast(point.value_); auto index = streamFilters_->sliceFilter_->StartAsyncSlice(ts, pid, point.tgid_, cookie, traceDataCache_->GetDataIndex(point.name_)); - if (point.name_ == onFrameQueeuStartEvent_ && index != INVALID_UINT64) { + if (index == INVALID_UINT64) { + return; + } + // add traceMeta data + traceDataCache_->GetInternalSlicesData()->SetTraceMetadata(index, point.traceLevel_, point.traceTagId_, + point.customArgsId_, point.customCategoryId_); + + if (point.name_ == onFrameQueeuStartEvent_) { OnFrameQueueStart(ts, index, point.tgid_); - } else if (traceDataCache_->AnimationTraceEnabled() && index != INVALID_UINT64 && + } else if (traceDataCache_->AnimationTraceEnabled() && (base::EndWith(comm, onAnimationProcEvent_) || base::EndWith(comm, newOnAnimationProcEvent_))) { // the comm is taskName streamFilters_->animationFilter_->StartAnimationEvent(line, point, index); @@ -247,6 +261,36 @@ std::string_view PrintEventParser::GetPointNameForBegin(std::string_view pointSt return name; } +void PrintEventParser::ParseSplitTraceMetaData(const std::string &dataStr, TracePoint &outPoint, bool isAsynEvent) const +{ + std::string metaDataStr = base::Strip(dataStr); + uint32_t expectedCount = isAsynEvent ? PARSER_ASYNC_SUM : PARSER_SYNC_SUM; + std::vector traceMetaDatas = base::SplitStringToVec(metaDataStr, "|", expectedCount); + if (traceMetaDatas.size() <= 1) { + TS_LOGD("traceMetaDatas size: %zu, dataStr: %s", traceMetaDatas.size(), dataStr.c_str()); + return; + } + + std::string &marker = traceMetaDatas[1]; + if (!marker.empty()) { + outPoint.traceLevel_ = marker.substr(0, 1); + if (marker.size() > 1) { + std::string traceTag = marker.substr(1); + outPoint.traceTagId_ = traceDataCache_->GetDataIndex(traceTag); + } + } + + if (isAsynEvent && traceMetaDatas.size() > CATEGORY_INDEX) { + std::string customCategory = traceMetaDatas[CATEGORY_INDEX]; + outPoint.customCategoryId_ = traceDataCache_->GetDataIndex(customCategory); + } + + if (traceMetaDatas.size() > expectedCount) { + std::string customArgs = traceMetaDatas[expectedCount]; + outPoint.customArgsId_ = traceDataCache_->GetDataIndex(customArgs); + } +} + ParseResult PrintEventParser::HandlerB(std::string_view pointStr, TracePoint &outPoint, size_t tGidlength) const { outPoint.name_ = GetPointNameForBegin(pointStr, tGidlength); @@ -274,6 +318,9 @@ ParseResult PrintEventParser::HandlerB(std::string_view pointStr, TracePoint &ou } else { outPoint.funcPrefixId_ = traceDataCache_->GetDataIndex(outPoint.name_); } + + // traceMetaDatasSrt: H:name|%X%TAG|customArgs + ParseSplitTraceMetaData(outPoint.name_, outPoint, false); } return PARSE_SUCCESS; } @@ -535,7 +582,11 @@ ParseResult PrintEventParser::HandlerCSF(std::string_view pointStr, TracePoint & outPoint.categoryGroup_ = std::string_view(pointStr.data() + valuePipe + 1, groupLen); } - + // traceMetaDatasSrt: taskId|%X%TAG|customCategory|customArgs + std::string metaDataStr(pointStr.data() + valueIndex); + if (outPoint.phase_ == 'S') { + ParseSplitTraceMetaData(metaDataStr, outPoint, true); + } return PARSE_SUCCESS; } diff --git a/trace_streamer/src/parser/print_event_parser.h b/trace_streamer/src/parser/print_event_parser.h index 2a6aab55..4fbb1634 100644 --- a/trace_streamer/src/parser/print_event_parser.h +++ b/trace_streamer/src/parser/print_event_parser.h @@ -53,6 +53,7 @@ public: void Finish(); void SetTraceType(TraceFileType traceType); void SetTraceClockId(BuiltinClocks clock); + void ParseSplitTraceMetaData(const std::string &dataStr, TracePoint &outPoint, bool isAsynEvent) const; private: using FrameFuncCall = std::function; diff --git a/trace_streamer/src/table/ftrace/callstack_table.cpp b/trace_streamer/src/table/ftrace/callstack_table.cpp index 48a1bda1..ff2d133e 100644 --- a/trace_streamer/src/table/ftrace/callstack_table.cpp +++ b/trace_streamer/src/table/ftrace/callstack_table.cpp @@ -34,7 +34,12 @@ enum class Index : int32_t { CHAIN_IDS, SPAN_IDS, PARENT_SPAN_IDS, - FLAGS + FLAGS, + TRACE_LEVEL, + TRACE_TAG, + CUSTOM_CATEGORY, + CUSTOM_ARGS, + CHILD_CALLID }; CallStackTable::CallStackTable(const TraceDataCache *dataCache) : TableBase(dataCache) { @@ -55,6 +60,11 @@ CallStackTable::CallStackTable(const TraceDataCache *dataCache) : TableBase(data tableColumn_.push_back(TableBase::ColumnInfo("spanId", "TEXT")); tableColumn_.push_back(TableBase::ColumnInfo("parentSpanId", "TEXT")); tableColumn_.push_back(TableBase::ColumnInfo("flag", "TEXT")); + tableColumn_.push_back(TableBase::ColumnInfo("trace_level", "TEXT")); + tableColumn_.push_back(TableBase::ColumnInfo("trace_tag", "TEXT")); + tableColumn_.push_back(TableBase::ColumnInfo("custom_category", "TEXT")); + tableColumn_.push_back(TableBase::ColumnInfo("custom_args", "TEXT")); + tableColumn_.push_back(TableBase::ColumnInfo("child_callid", "INTEGER")); tablePriKey_.push_back("callid"); tablePriKey_.push_back("ts"); tablePriKey_.push_back("depth"); @@ -215,6 +225,26 @@ void CallStackTable::Cursor::HandleTypeColumns(int32_t col) const SetTypeColumnTextNotEmpty(slicesObj_.Flags()[CurrentRow()].empty(), slicesObj_.Flags()[CurrentRow()].c_str()); break; + case Index::TRACE_LEVEL: + SetTypeColumnTextNotEmpty(slicesObj_.TraceLevelsData()[CurrentRow()].empty(), + slicesObj_.TraceLevelsData()[CurrentRow()].c_str()); + break; + case Index::TRACE_TAG: + SetTypeColumnText(slicesObj_.TraceTagsData()[CurrentRow()], INVALID_UINT64); + break; + case Index::CUSTOM_CATEGORY: + SetTypeColumnText(slicesObj_.CustomCategorysData()[CurrentRow()], INVALID_UINT64); + break; + case Index::CUSTOM_ARGS: + SetTypeColumnText(slicesObj_.CustomArgsData()[CurrentRow()], INVALID_UINT64); + break; + case Index::CHILD_CALLID: { + if (slicesObj_.ChildCallidData()[CurrentRow()].has_value()) { + sqlite3_result_int64(context_, + static_cast(slicesObj_.ChildCallidData()[CurrentRow()].value())); + } + break; + } default: TS_LOGF("Unregistered column : %d", col); break; diff --git a/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.cpp b/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.cpp index b13386b5..3bbbe4b8 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.cpp +++ b/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.cpp @@ -21,8 +21,10 @@ size_t CallStack::AppendInternalAsyncSlice(const CallStackInternalRow &callStack const std::optional &parentId) { AppendCommonInfo(callStackInternalRow.startT, callStackInternalRow.durationNs, callStackInternalRow.internalTid); - AppendCallStack(callStackInternalRow.cat, callStackInternalRow.name, callStackInternalRow.depth, parentId); + AppendCallStack(callStackInternalRow.cat, callStackInternalRow.name, callStackInternalRow.depth, + callStackInternalRow.childCallid, parentId); AppendDistributeInfo(); + AppendTraceMetadata(); cookies_.emplace_back(cookid); ids_.emplace_back(id_++); return Size() - 1; @@ -31,10 +33,12 @@ size_t CallStack::AppendInternalSlice(const CallStackInternalRow &callStackInter const std::optional &parentId) { AppendCommonInfo(callStackInternalRow.startT, callStackInternalRow.durationNs, callStackInternalRow.internalTid); - AppendCallStack(callStackInternalRow.cat, callStackInternalRow.name, callStackInternalRow.depth, parentId); + AppendCallStack(callStackInternalRow.cat, callStackInternalRow.name, callStackInternalRow.depth, + callStackInternalRow.childCallid, parentId); ids_.emplace_back(id_++); cookies_.emplace_back(INVALID_INT64); AppendDistributeInfo(); + AppendTraceMetadata(); return Size() - 1; } @@ -45,12 +49,17 @@ void CallStack::AppendCommonInfo(uint64_t startT, uint64_t durationNs, InternalT callIds_.emplace_back(internalTid); colorIndexs_.emplace_back(0); } -void CallStack::AppendCallStack(DataIndex cat, DataIndex name, uint8_t depth, std::optional parentId) +void CallStack::AppendCallStack(DataIndex cat, + DataIndex name, + uint8_t depth, + std::optional childCallid, + std::optional parentId) { parentIds_.emplace_back(parentId); cats_.emplace_back(cat); names_.emplace_back(name); depths_.emplace_back(depth); + childCallid_.emplace_back(childCallid); } void CallStack::SetDistributeInfo(size_t index, const std::string &chainId, @@ -64,6 +73,17 @@ void CallStack::SetDistributeInfo(size_t index, flags_[index] = flag; argSet_[index] = INVALID_UINT32; } +void CallStack::SetTraceMetadata(size_t index, + const std::string &traceLevel, + const DataIndex &tag, + const DataIndex &customArg, + const DataIndex &customCategory) +{ + traceLevels_[index] = traceLevel; + traceTags_[index] = tag; + customArgs_[index] = customArg; + customCategorys_[index] = customCategory; +} void CallStack::AppendDistributeInfo() { chainIds_.emplace_back(""); @@ -72,6 +92,13 @@ void CallStack::AppendDistributeInfo() flags_.emplace_back(""); argSet_.emplace_back(INVALID_UINT32); } +void CallStack::AppendTraceMetadata() +{ + traceLevels_.emplace_back(""); + traceTags_.emplace_back(INVALID_UINT64); + customArgs_.emplace_back(INVALID_UINT64); + customCategorys_.emplace_back(INVALID_UINT64); +} void CallStack::SetDuration(size_t index, uint64_t timeStamp) { durs_[index] = timeStamp - timeStamps_[index]; @@ -161,5 +188,25 @@ const std::deque &CallStack::ArgSetIdsData() const { return argSet_; } +const std::deque &CallStack::TraceLevelsData() const +{ + return traceLevels_; +} +const std::deque &CallStack::TraceTagsData() const +{ + return traceTags_; +} +const std::deque &CallStack::CustomCategorysData() const +{ + return customCategorys_; +} +const std::deque &CallStack::CustomArgsData() const +{ + return customArgs_; +} +const std::deque> &CallStack::ChildCallidData() const +{ + return childCallid_; +} } // namespace TraceStdtype } // namespace SysTuning diff --git a/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.h b/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.h index 033d25db..07b5322b 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.h +++ b/trace_streamer/src/trace_data/trace_stdtype/ftrace/callstack_stdtype.h @@ -29,6 +29,7 @@ struct CallStackInternalRow { DataIndex cat = INVALID_UINT64; DataIndex name = INVALID_UINT64; uint8_t depth = INVALID_UINT8; + uint32_t childCallid = INVALID_UINT32; }; class CallStack : public CacheBase, public CpuCacheBase, public BatchCacheBase { public: @@ -43,7 +44,13 @@ public: const std::string &parentSpanId, const std::string &flag); void AppendDistributeInfo(); + void AppendTraceMetadata(); void SetDuration(size_t index, uint64_t timeStamp); + void SetTraceMetadata(size_t index, + const std::string &traceLevel, + const DataIndex &tag, + const DataIndex &customArg, + const DataIndex &customCategory = INVALID_UINT64); void SetDurationWithFlag(size_t index, uint64_t timeStamp); void SetFlag(size_t index, uint8_t flag); void SetDurationEx(size_t index, uint32_t dur); @@ -67,11 +74,17 @@ public: parentSpanIds_.clear(); flags_.clear(); argSet_.clear(); + traceLevels_.clear(); + traceTags_.clear(); + customCategorys_.clear(); + customArgs_.clear(); + childCallid_.clear(); } void ClearExportedData() override { EraseElements(timeStamps_, ids_, durs_, cats_, cookies_, colorIndexs_, callIds_, names_, depths_, chainIds_, - spanIds_, parentSpanIds_, flags_, argSet_); + spanIds_, parentSpanIds_, flags_, argSet_, traceLevels_, traceTags_, customCategorys_, + customArgs_, childCallid_); } const std::deque> &ParentIdData() const; const std::deque &CatsData() const; @@ -85,10 +98,19 @@ public: const std::deque &ParentSpanIds() const; const std::deque &Flags() const; const std::deque &ArgSetIdsData() const; + const std::deque &TraceLevelsData() const; + const std::deque &TraceTagsData() const; + const std::deque &CustomCategorysData() const; + const std::deque &CustomArgsData() const; + const std::deque> &ChildCallidData() const; private: void AppendCommonInfo(uint64_t startT, uint64_t durationNs, InternalTid internalTid); - void AppendCallStack(DataIndex cat, DataIndex name, uint8_t depth, std::optional parentId); + void AppendCallStack(DataIndex cat, + DataIndex name, + uint8_t depth, + std::optional childCallid, + std::optional parentId); private: std::deque> parentIds_; @@ -103,6 +125,11 @@ private: std::deque parentSpanIds_ = {}; std::deque flags_ = {}; std::deque argSet_ = {}; + std::deque traceLevels_ = {}; + std::deque traceTags_ = {}; + std::deque customCategorys_ = {}; + std::deque customArgs_ = {}; + std::deque> childCallid_; }; } // namespace TraceStdtype } // namespace SysTuning -- Gitee From e20962ce30a6ed7bde3aedb87442d22b0bccdf67 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Wed, 23 Apr 2025 17:55:16 +0800 Subject: [PATCH 08/66] =?UTF-8?q?feat:=E5=86=85=E6=A0=B8trace=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84=E4=BC=98=E5=8C=96=EF=BC=8Ctrace?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A2=9E=E7=BA=A7=E5=88=AB=E3=80=81tag?= =?UTF-8?q?=E3=80=81cat=E3=80=81args=E9=80=82=E9=85=8D=202.=E6=A1=86?= =?UTF-8?q?=E9=80=89Occurrences=E6=B1=87=E6=80=BB=E6=95=B0=E6=8D=AEbug?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- ide/src/trace/bean/FuncStruct.ts | 4 +++ ide/src/trace/component/SpSystemTrace.init.ts | 8 +++++ .../trace/component/chart/SpProcessChart.ts | 33 +++++++++++++---- .../trace/sheet/TabPaneCurrentSelection.ts | 20 +++++++++++ .../trace/sheet/process/TabPaneSliceChild.ts | 6 ++-- ide/src/trace/database/sql/Func.sql.ts | 36 ++++++++++++++----- ide/src/trace/database/sql/SqlLite.sql.ts | 33 +++++++++++++++-- 7 files changed, 120 insertions(+), 20 deletions(-) diff --git a/ide/src/trace/bean/FuncStruct.ts b/ide/src/trace/bean/FuncStruct.ts index 214bf110..3c3a156a 100644 --- a/ide/src/trace/bean/FuncStruct.ts +++ b/ide/src/trace/bean/FuncStruct.ts @@ -46,6 +46,10 @@ export class FuncStruct extends BaseStruct { spanId: string | undefined; parentSpanId: string | undefined; chainFlag: string | undefined; + trace_level: string | undefined; + trace_tag: string | undefined; + custom_args: string | undefined; + category: string | undefined; static draw(funcBeanStructCanvasCtx: CanvasRenderingContext2D, funcBeanStruct: FuncStruct): void { if (funcBeanStruct.frame) { diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index ba1663de..e239e4f9 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -1126,6 +1126,14 @@ function findEntryTypeFunc(sp: SpSystemTrace, findEntry: unknown): void { // @ts-ignore cookie: findEntry.cookie, // @ts-ignore + trace_level: findEntry.trace_level, + // @ts-ignore + trace_tag: findEntry.trace_tag, + // @ts-ignore + custom_args: findEntry.custom_args, + // @ts-ignore + category:findEntry.category, + // @ts-ignore //因异步trace分类出的rowId类型有三种,故新增row_id字段,该字段为异步方法的对应的rowId,支持搜索查询定位到该方法属于那个row,只有缓存的异步trace数据中含该字段 row_id: findEntry.rowId ? findEntry.rowId : null, }, diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index 9da1cc47..ab9af407 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -40,7 +40,7 @@ import { processDeliverInputEventDataSender } from '../../database/data-trafic/p import { processTouchEventDispatchDataSender } from '../../database/data-trafic/process/ProcessTouchEventDispatchDataSender'; import { getMaxDepthByTid, queryProcessAsyncFunc, queryProcessAsyncFuncCat } from '../../database/sql/Func.sql'; import { queryMemFilterIdMaxValue, queryMemFilterIdMinValue } from '../../database/sql/Memory.sql'; -import { queryAllSoInitNames, queryAllSrcSlices, queryEventCountMap } from '../../database/sql/SqlLite.sql'; +import { queryAllSoInitNames, queryAllSrcSlices, queryEventCountMap, queryCallstackDetail } from '../../database/sql/SqlLite.sql'; import { queryProcessByTable, queryProcessContentCount, @@ -99,6 +99,7 @@ export class SpProcessChart { private traceId?: string | undefined; private parentRow: TraceRow | undefined; static asyncFuncCache: unknown[] = []; + private callStackDetail: Map = new Map(); static threadStateList: Map = new Map(); static processRowSortMap: Map = new Map(); private sameThreadFolder!: TraceRow; @@ -129,6 +130,7 @@ export class SpProcessChart { this.distributedDataMap.clear(); this.renderRow = null; SpProcessChart.asyncFuncCache = []; + this.callStackDetail.clear(); if (this.parentRow) { this.parentRow.clearMemory(); this.parentRow = undefined; @@ -380,6 +382,7 @@ export class SpProcessChart { //@ts-ignore it.flag = 'Did not end'; } + this.addCallStackDetail(it as FuncStruct) createDepth(0, i); }); if (funcRow && !funcRow.isComplete) { @@ -526,6 +529,13 @@ export class SpProcessChart { traceId: traceId!, }); }); + let callstackData: Array = await queryCallstackDetail(); + for (let index = 0; index < callstackData.length; index++) { + const indexData = callstackData[index]; + if (indexData && !this.callStackDetail.has(indexData.id!)) { + this.callStackDetail.set(indexData.id!, indexData); + } + } info('The amount of initialized process threads data is : ', this.processThreads!.length); } @@ -1551,6 +1561,7 @@ export class SpProcessChart { funs[index].ipid = thread.upid; funs[index].tid = thread.tid; funs[index].pid = thread.pid; + this.addCallStackDetail(funs[index]) funs[index].funName = this.traceId ? Utils.getInstance().getCallStatckMap().get(`${this.traceId}_${funs[index].id}`) : Utils.getInstance().getCallStatckMap().get(funs[index].id!); if (Utils.isBinder(fun)) { } else { @@ -1756,18 +1767,18 @@ export class SpProcessChart { for (let i = 0; i < asyncFuncList.length; i++) { const el = asyncFuncList[i]; // @ts-ignore - if (el.cat !== null) { + if (el.category !== null) { if (flag) {//business first asyncCatArr.push(el); } else {//thread first //@ts-ignore - if (asyncCatMap.has(`${el.cat}:${el.threadName} ${el.tid}`)) { + if (asyncCatMap.has(`${el.category}:${el.threadName} ${el.tid}`)) { //@ts-ignore - let item: Array = asyncCatMap.get(`${el.cat}:${el.threadName} ${el.tid}`); + let item: Array = asyncCatMap.get(`${el.category}:${el.threadName} ${el.tid}`); item.push(el); } else { //@ts-ignore - asyncCatMap.set(`${el.cat}:${el.threadName} ${el.tid}`, [el]); + asyncCatMap.set(`${el.category}:${el.threadName} ${el.tid}`, [el]); } } } else { @@ -1775,7 +1786,7 @@ export class SpProcessChart { asyncRemoveCatArr.push(el); } } - asyncCat = flag ? Utils.groupBy(asyncCatArr, 'cat') : Object.fromEntries(asyncCatMap); + asyncCat = flag ? Utils.groupBy(asyncCatArr, 'category') : Object.fromEntries(asyncCatMap); return { asyncRemoveCatArr, asyncCat }; } //处理cat字段为null的数据,按funname分类,分别按len>1和=1去处理 @@ -1921,6 +1932,16 @@ export class SpProcessChart { processRow.addChildTraceRow(funcRow); } + addCallStackDetail(item: FuncStruct): void { + if (this.callStackDetail.has(item.id!)) { + const data: FuncStruct | undefined = this.callStackDetail.get(item.id!); + item.custom_args = data!.custom_args; + item.trace_level = data!.trace_level; + item.trace_tag = data!.trace_tag; + item.category = data!.category; + } + } + addAsyncCatFunction(it: { pid: number; processName: string | null }, processRow: TraceRow): void { //@ts-ignore let asyncFuncCatList = this.processAsyncFuncCatMap[it.pid] || []; diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 46ae779b..0cfe6392 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -475,6 +475,7 @@ export class TabPaneCurrentSelection extends BaseElement { } list.push(item); }); + this.addTabSliceDetail(data, list) this.currentSelectionTbl!.dataSource = list; // @ts-ignore let startTimeAbsolute = (data.startTs || 0) + (window as unknown).recordStartNS; @@ -496,6 +497,7 @@ export class TabPaneCurrentSelection extends BaseElement { name: 'Thread', value: (this.transferString(threadName ?? '') || 'NULL') + ' [' + dataTid + '] ', }); + this.addTabSliceDetail(data, list) list.push({ name: 'StartTime(Relative)', value: getTimeString(data.startTs || 0), @@ -601,6 +603,7 @@ export class TabPaneCurrentSelection extends BaseElement { argset.forEach((item) => { list.push({ name: item.keyName, value: item.strValue }); }); + this.addTabSliceDetail(data, list) this.addTabPanelContent(list, data, information); this.currentSelectionTbl!.dataSource = list; }); @@ -655,6 +658,7 @@ export class TabPaneCurrentSelection extends BaseElement { } ); } + this.addTabSliceDetail(data, list) this.addTabPanelContent(list, data, information); this.currentSelectionTbl!.dataSource = list; let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); @@ -688,6 +692,21 @@ export class TabPaneCurrentSelection extends BaseElement { }); } + private addTabSliceDetail(data: FuncStruct, list: unknown[]) { + const properties = [ + {key: 'trace_level', name: 'TraceLevel'}, + {key: 'trace_tag', name: 'TraceTag'}, + {key: 'category', name: 'Category'}, + {key: 'custom_args', name: 'CustomArgs'}, + ]; + properties.forEach(prop => { + if (data[prop.key] && list !== undefined) { + list!.push({name: prop.name, value: data[prop.key]}); + } + }); + return list; + } + private handleAsyncBinder( data: FuncStruct, list: unknown[], @@ -750,6 +769,7 @@ export class TabPaneCurrentSelection extends BaseElement { value: (this.transferString(threadName ?? '') || 'NULL') + ' [' + data.tid + '] ', }); } + this.addTabSliceDetail(data, list) this.addTabPanelContent(list, data, information); this.currentSelectionTbl!.dataSource = list; let funcClick = this.currentSelectionTbl?.shadowRoot?.querySelector('#function-jump'); diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts index e75097d1..c8e6916c 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts @@ -37,8 +37,10 @@ export class TabPaneSliceChild extends BaseElement { //合并SF异步信息,相同pid和tid的name let sfAsyncFuncMap: Map = new Map(); let filterSfAsyncFuncName = boxChildValue.selection!.funAsync; - if (!boxChildValue.param.isSummary!) { - filterSfAsyncFuncName = filterSfAsyncFuncName.filter((item) => item.name === boxChildValue.param.name![0]); + if (boxChildValue.param.name) { + filterSfAsyncFuncName = filterSfAsyncFuncName.filter((item) => + boxChildValue.param.name?.some((boxItem) => item.name === boxItem) + ); } filterSfAsyncFuncName.forEach((it: { name: string; pid: number, tid: number | undefined }) => { if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) { diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index d99a2309..f9ca8595 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -152,14 +152,19 @@ export const queryProcessAsyncFunc = ( P.pid, c.ts-${traceRange.startTs} as startTs, c.dur, - c.cat, + c.custom_category as category, c.id, c.depth, c.argsetid, - c.cookie + c.cookie, + c.trace_level, + c.trace_tag, + c.custom_args, + c.child_callid FROM - (SELECT id, ts, parent_id, dur, depth, argsetid, cookie, cat from callstack where cookie NOT NULL) c - LEFT JOIN thread A ON A.id = c.parent_id + (SELECT id, ts, parent_id, dur, depth, argsetid, cookie, custom_category, child_callid, trace_level, + trace_tag, custom_args from callstack where cookie NOT NULL) c + LEFT JOIN thread A ON A.id = c.child_callid LEFT JOIN process P ON P.id = A.ipid WHERE startTs NOT NULL;`, @@ -179,21 +184,26 @@ export const queryProcessAsyncFuncCat = ( select A.tid, P.pid, - c.cat as threadName, + c.custom_category as threadName, c.name as funName, c.ts-${traceRange.startTs} as startTs, c.dur, c.depth, - c.cookie + c.cookie, + c.trace_level, + c.trace_tag, + c.custom_args, + c.child_callid from - (select callid, name, ts, dur, cat, depth, cookie, parent_id from callstack where cookie not null and cat not null and parent_id is null) C + (select callid, name, ts, dur, custom_category, depth, cookie, parent_id,trace_level,trace_tag, + custom_args,child_callid from callstack where cookie not null and custom_category not null and child_callid is null) c left join - thread A on A.id = C.callid + thread A on A.id = c.callid left join process P on P.id = A.ipid where startTs not null - order by cat; + order by custom_category; `, {} ); @@ -590,6 +600,10 @@ export const querySearchFunc = (search: string): Promise> t.name as threadName, p.pid, c.argsetid, + c.trace_level, + c.trace_tag, + c.custom_args, + c.custom_category as category, 'func' as type from callstack c left join thread t on c.callid = t.id left join process p on t.ipid = p.id left join trace_range r @@ -613,6 +627,10 @@ export const querySearchFunc = (search: string): Promise> t.name as threadName, p.pid, c.argsetid, + c.trace_level, + c.trace_tag, + c.custom_args, + c.custom_category as category, 'func' as type from callstack c left join thread t on c.callid = t.id left join process p on t.ipid = p.id left join trace_range r diff --git a/ide/src/trace/database/sql/SqlLite.sql.ts b/ide/src/trace/database/sql/SqlLite.sql.ts index 2b547526..aa6364e1 100644 --- a/ide/src/trace/database/sql/SqlLite.sql.ts +++ b/ide/src/trace/database/sql/SqlLite.sql.ts @@ -29,6 +29,7 @@ import type { DeviceStruct } from '../../bean/FrameComponentBean'; import { LogStruct } from '../ui-worker/ProcedureWorkerLog'; import { query } from '../SqlLite'; import { Utils } from '../../component/trace/base/Utils'; +import { FuncStruct } from '../ui-worker/ProcedureWorkerFunc'; export const queryEventCountMap = ( traceId?: string @@ -405,7 +406,11 @@ export const queryBinderBySliceId = ( c.depth, c.argsetid, c.name AS funName, - c.cookie + c.cookie, + c.trace_level, + c.trace_tag, + c.custom_category as category, + c.custom_args FROM callstack c, trace_range D @@ -449,8 +454,12 @@ export const queryBinderByArgsId = ( p.pid, c.depth, c.argsetid, - c.name as funName, - c.cookie + c.name as funName, + c.cookie, + c.trace_level, + c.trace_tag, + c.custom_category as category, + c.custom_args from callstack c,trace_range D left join thread t on c.callid = t.id left join process p on p.id = t.ipid @@ -1592,3 +1601,21 @@ export const queryPerfToolsDur = (): Promise> => dur FROM callstack where name = 'H:GRAB'` ); + +export const queryCallstackDetail = (): Promise> => + query( + 'queryCallstackDetail', + `SELECT + id, + trace_level, + trace_tag, + custom_category as category, + custom_args + FROM callstack, trace_range AS TR + where (trace_level is not null or + trace_tag is not null or + custom_args is not null or + custom_category is not null) + and ts - TR.start_ts >= 0 + and TR.end_ts - ts >=0;` + ); -- Gitee From e0cb8de28f1a8b2e263c96b239effdc9aabc885e Mon Sep 17 00:00:00 2001 From: JustinYT Date: Wed, 30 Apr 2025 13:37:26 +0800 Subject: [PATCH 09/66] adjsut hiperf build with hiviewdfx_faultloggerd,libbpf,bounds_checking_function,commonlibrary_c_utils,zlib,bzip2. Signed-off-by: JustinYT --- trace_streamer/build/build_base_func.sh | 1 + trace_streamer/build/ts.gni | 1 + trace_streamer/gn/BUILD.gn | 12 +- trace_streamer/pare_third_party.sh | 129 +------- .../patch_hiperf/hiviewdfx_faultloggerd.patch | 159 ++++++++++ .../prebuilts/patch_llvm/llvm.patch | 2 +- trace_streamer/src/BUILD.gn | 3 +- trace_streamer/src/base/BUILD.gn | 2 +- trace_streamer/src/filter/BUILD.gn | 10 +- .../src/filter/hook_filter/BUILD.gn | 2 +- .../src/parser/ebpf_parser/BUILD.gn | 2 +- .../src/parser/hiperf_parser/BUILD.gn | 290 ++++++++++++++++-- .../parser/hiperf_parser/perf_data_parser.cpp | 18 +- .../parser/hiperf_parser/perf_data_parser.h | 6 +- .../src/parser/pbreader_parser/BUILD.gn | 3 +- .../native_hook_parser/BUILD.gn | 2 +- .../src/parser/rawtrace_parser/BUILD.gn | 2 +- trace_streamer/src/rpc/ffrt_converter.cpp | 263 +++++++++------- trace_streamer/src/rpc/ffrt_converter.h | 58 ++-- trace_streamer/src/trace_data/BUILD.gn | 2 +- trace_streamer/test/BUILD.gn | 2 +- 21 files changed, 658 insertions(+), 311 deletions(-) create mode 100644 trace_streamer/prebuilts/patch_hiperf/hiviewdfx_faultloggerd.patch diff --git a/trace_streamer/build/build_base_func.sh b/trace_streamer/build/build_base_func.sh index f635395e..42292804 100644 --- a/trace_streamer/build/build_base_func.sh +++ b/trace_streamer/build/build_base_func.sh @@ -17,6 +17,7 @@ function help() { echo "Usage: $1 [linux/wasm/windows/macx] [debug] [-e ] [-d ]" echo " -e , enable the default plugins." echo " -d , enable the extend plugins." + echo " -m , enable the macro plugins." echo " -l Show the all plugin list." echo " -h Show the help info." exit diff --git a/trace_streamer/build/ts.gni b/trace_streamer/build/ts.gni index d2fdef54..4e7b0ba7 100644 --- a/trace_streamer/build/ts.gni +++ b/trace_streamer/build/ts.gni @@ -13,6 +13,7 @@ declare_args() { is_independent_compile = false + hiperf_debug = true enable_hiperf = true enable_ebpf = true enable_native_hook = true diff --git a/trace_streamer/gn/BUILD.gn b/trace_streamer/gn/BUILD.gn index 10376217..9cd142f5 100644 --- a/trace_streamer/gn/BUILD.gn +++ b/trace_streamer/gn/BUILD.gn @@ -59,6 +59,12 @@ config("default") { "-Wformat", "-Wno-unused-variable", ] + + # for third part hiperf + defines += [ "HIPERF_DEBUG" ] + + # for third part libbpf + defines += [ "__LITTLE_ENDIAN_BITFIELD" ] if (enable_hiperf) { defines += [ "ENABLE_HIPERF" ] } @@ -191,17 +197,17 @@ config("default") { cflags += [ "-D SUPPORTTHREAD" ] } if (is_win) { - cflags += [ "-D is_mingw" ] + cflags += [ "-D is_mingw=true" ] defines += [ "WIN32_LEAN_AND_MEAN" ] libs += [ "wsock32" ] libs += [ "ws2_32" ] cflags += [ "-Wno-attributes" ] } if (is_linux) { - cflags += [ "-D is_linux" ] + cflags += [ "-D is_linux=true" ] } if (is_mac) { - cflags += [ "-D is_mac" ] + cflags += [ "-D is_mac=true" ] } } diff --git a/trace_streamer/pare_third_party.sh b/trace_streamer/pare_third_party.sh index 498c209f..a0b7cfa8 100755 --- a/trace_streamer/pare_third_party.sh +++ b/trace_streamer/pare_third_party.sh @@ -52,24 +52,14 @@ if [ ! -f "protobuf/BUILD.gn" ];then fi if [ ! -f "zlib/BUILD.gn" ];then - rm -rf zlib - git clone --depth=1 git@gitee.com:openharmony/third_party_zlib.git - if [ -d "third_party_zlib" ];then - mv third_party_zlib zlib - $cp ../prebuilts/patch_zlib/zlibbuild.gn zlib/BUILD.gn - fi + git clone --depth=1 git@gitee.com:openharmony/third_party_zlib.git zlib fi if [ ! -f "bzip2/BUILD.gn" ];then - rm -rf bzip2 - git clone --depth=1 git@gitee.com:openharmony/third_party_bzip2.git - if [ -d "third_party_bzip2" ];then - mv third_party_bzip2 bzip2 - $cp ../prebuilts/patch_bzip2/bzip2build.gn bzip2/BUILD.gn - cd bzip2 - ./install.sh $(pwd) - cd .. - fi + git clone --depth=1 git@gitee.com:openharmony/third_party_bzip2.git bzip2 + cd bzip2 + ./install.sh $(pwd) + cd .. fi if [ ! -f "googletest/BUILD.gn" ];then @@ -90,116 +80,27 @@ if [ ! -f "json/BUILD.gn" ];then fi fi -if [ ! -f "libunwind/BUILD.gn" ];then - rm -rf libunwind - git clone git@gitee.com:openharmony/third_party_libunwind.git - if [ -d "third_party_libunwind" ];then - mv third_party_libunwind libunwind - cd libunwind - git reset --hard 2c16627236d5e62c8fe78e088d21eca3c362c71c - cd .. - $cp ../prebuilts/patch_libunwind/libunwindbuild.gn libunwind/BUILD.gn - fi +if [ ! -d "libbpf" ];then + git clone --depth=1 git@gitee.com:openharmony/third_party_libbpf.git libbpf fi -if [ ! -f "perf_include/libbpf/linux/perf_event.h" ];then - mkdir -p perf_include/libbpf/linux - rm -rf perf_event.h - curl https://gitee.com/openharmony/third_party_libbpf/raw/20221117/github.com/libbpf/libbpf/refs/tags/v0.7.0/include/uapi/linux/perf_event.h > perf_event.h - mv perf_event.h perf_include/libbpf/linux/perf_event.h - $patch -p0 perf_include/libbpf/linux/perf_event.h ../prebuilts/patch_perf_event/perf_event.h.patch +if [ ! -d "hiviewdfx/faultloggerd" ];then + git clone --depth=1 git@gitee.com:openharmony/hiviewdfx_faultloggerd.git hiviewdfx/faultloggerd/ + cd hiviewdfx/faultloggerd + $patch -p1 < ../../../prebuilts/patch_hiperf/hiviewdfx_faultloggerd.patch + cd ../../ fi -if [ ! -d "perf_include/hiviewdfx/faultloggerd" ];then - rm -rf hiviewdfx_faultloggerd perf_include/hiviewdfx/faultloggerd - mkdir -p perf_include/hiviewdfx/faultloggerd/interfaces/innerkits - git clone git@gitee.com:openharmony/hiviewdfx_faultloggerd.git - cd hiviewdfx_faultloggerd - git reset --hard 7296f69c0d418cd9353638f3117296e4b494e4e5 - cd .. - mv hiviewdfx_faultloggerd/common/ perf_include/hiviewdfx/faultloggerd - mv hiviewdfx_faultloggerd/interfaces/common/ perf_include/hiviewdfx/faultloggerd/interfaces - mv hiviewdfx_faultloggerd/interfaces/nonlinux/ perf_include/hiviewdfx/faultloggerd/interfaces - mv hiviewdfx_faultloggerd/interfaces/innerkits/unwinder/ perf_include/hiviewdfx/faultloggerd/interfaces/innerkits - find perf_include/hiviewdfx/faultloggerd -type f -name "*.gn" -delete - $cp ../prebuilts/patch_hiperf/hiviewdfx_BUILD.gn ../third_party/perf_include/hiviewdfx/BUILD.gn - rm -rf hiviewdfx_faultloggerd - rm -rf perf_include/hiviewdfx/common/build - rm -rf perf_include/hiviewdfx/common/cutil - rm perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_regs_x86_64.cpp - $sed -i '/TRAP_BRANCH/s/^/\/\/ /' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_signal.cpp - $sed -i '/TRAP_HWBKPT/s/^/\/\/ /' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_signal.cpp - $sed -i '/is_ohos/s/is_ohos/true/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_mmap.cpp - $sed -i '/is_ohos/s/is_ohos/true/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_regs.h - $sed -i '/#include /a #include "debug_logger.h"' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/unwinder.h - $sed -i '/getpid() == gettid()/s/getpid() == gettid()/false/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/unwinder.cpp - $sed -i '/!realpath(path, realPath)/s/!realpath(path, realPath)/false/g' perf_include/hiviewdfx/faultloggerd/common/dfxutil/dfx_util.cpp - $sed -i '/#include "dfx_util.h"/a #include "utilities.h"' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_mmap.cpp - $sed -i '/#include /a #include "utilities.h"' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_mmap.h - $sed -i '/#if is_ohos && !is_mingw/s/#if is_ohos && !is_mingw/#if is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_elf.h - $sed -i '/#if is_ohos && !is_mingw/s/#if is_ohos && !is_mingw/#if is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_elf.cpp - $sed -i '/#if is_ohos/s/#if is_ohos/#if true/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_elf.h - $sed -i '/#if is_ohos/s/#if is_ohos/#if true/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_elf.cpp - $sed -i '/#ifndef is_ohos_lite/s/#ifndef is_ohos_lite/#if false/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_elf.cpp - $sed -i '/#if is_mingw/s/#if is_mingw/#ifndef is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/dfx_elf.cpp - $sed -i '/#if !is_mingw/s/#if !is_mingw/#if is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_elf_define.h - $sed -i '/#if is_mingw/s/#if is_mingw/#ifndef is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include/dfx_elf_parser.h - $sed -i '/#define DFX_NONLINUX_DEFINE_H/a #ifndef is_linux' perf_include/hiviewdfx/faultloggerd/interfaces/nonlinux/dfx_nonlinux_define.h - $sed -i '/#if is_mingw/s/#if is_mingw/#ifndef is_linux/g' perf_include/hiviewdfx/faultloggerd/interfaces/common/byte_order.h - $sed -i '$a #endif' perf_include/hiviewdfx/faultloggerd/interfaces/nonlinux/dfx_nonlinux_define.h - $cp ../prebuilts/patch_hiperf/string_view_util.h ../third_party/perf_include/hiviewdfx/faultloggerd/common/dfxutil/string_view_util.h -fi if [ ! -f "hiperf/BUILD.gn" ];then - rm -rf hiperf developtools_hiperf - git clone git@gitee.com:openharmony/developtools_hiperf.git - cd developtools_hiperf - git reset --hard 9d189f41d76c1ae6e8e12238aef5ef5b8cdbc09f - cd .. - if [ -d "developtools_hiperf" ];then - mv developtools_hiperf hiperf - $cp ../prebuilts/patch_hiperf/BUILD.gn ../third_party/hiperf/BUILD.gn - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/virtual_thread.h - # $sed -i "s/HIPERF_DEBUG/ALWAYSTRUE/g" hiperf/include/virtual_thread.h - $sed -i "/#include \"report_json_file.h\"/s/^\(.*\)$/\/\/\1/g" hiperf/include/report.h - $sed -i "/#include /s/^\(.*\)$/\/\/\1/g" hiperf/include/debug_logger.h - $sed -i "/#include /s/^\(.*\)$/\/\/\1/g" hiperf/include/utilities.h - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/virtual_thread.h - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/callstack.h - # $sed -i '/unwinder.h/s/^/\/\/ /' hiperf/include/callstack.h - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/symbols_file.h - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/virtual_runtime.h - $sed -i "/FRIEND_TEST/s/^\(.*\)$/\/\/\1/g" hiperf/include/report.h - $sed -i "s/HIPERF_DEBUG/ALWAYSTRUE/g" hiperf/include/virtual_thread.h - # $sed -i "/using __s8 = char;/a #define unw_word_t uint64_t" hiperf/include/nonlinux/linux/types.h - # $sed -i '/^void Report::PrepareConsole(/,/^}/ s/^.*$/\/\/&/; /^void Report::PrepareConsole(/,/return;/ s/^[[:blank:]]*/ /' hiperf/src/report.cpp - # $sed -i '/namespace HiPerf {/avoid Report::PrepareConsole(){ return;}' hiperf/src/report.cpp - # $sed -i '/HITRACE_METER_NAME/s/^/\/\/ /' hiperf/src/callstack.cpp - # $sed -i '/hitrace_meter.h/s/^/\/\/ /' hiperf/src/callstack.cpp - # $sed -i '/dlfcn.h/s/^/\/\/ /' hiperf/src/callstack.cpp - # $sed -i '/dfx_ark.h/s/^/\/\/ /' hiperf/src/callstack.cpp - # $sed -i '/dfx_regs.h/s/^/\/\/ /' hiperf/src/callstack.cpp - $sed -i '/return DoUnwind2/s/^/\/\/ /' hiperf/src/callstack.cpp - # $sed -i '/#if defined(is_ohos) && is_ohos/s/defined(is_ohos) && is_ohos/true/g' hiperf/src/virtual_runtime.cpp - # $sed -i '/#if defined(is_ohos) && is_ohos/s/defined(is_ohos) && is_ohos/true/g' hiperf/include/virtual_runtime.h - # $sed -i '/symbolsTable, elfFile_, elfPath/s/symbolsTable, elfFile_, elfPath/symbolsTable, elfFile_, filePath_/g' hiperf/src/symbols_file.cpp - $sed -i '/spe_decoder.h/s/^/\/\/ /' hiperf/src/virtual_runtime.cpp - $sed -i '/spe_decoder.h/s/^/\/\/ /' hiperf/src/perf_event_record.cpp - fi + git clone --depth=1 -b adjust_build_for_smartperf_host git@gitee.com:li-yiting880505/developtools_hiperf_1.git hiperf fi if [ ! -f "bounds_checking_function/BUILD.gn" ];then - rm -rf bounds_checking_function git clone --depth=1 git@gitee.com:openharmony/third_party_bounds_checking_function.git bounds_checking_function - $cp ../prebuilts/patch_bounds_checking_function/bounds_checking_functionbuild.gn bounds_checking_function/BUILD.gn fi -if [ ! -d "commonlibrary" ];then - rm -rf commonlibrary - git clone --depth=1 git@gitee.com:openharmony/commonlibrary_c_utils.git - if [ -d "commonlibrary_c_utils" ];then - mv commonlibrary_c_utils commonlibrary - rm -rf commonlibrary_c_utils - fi +if [ ! -d "commonlibrary/c_utils" ];then + git clone --depth=1 git@gitee.com:openharmony/commonlibrary_c_utils.git commonlibrary/c_utils fi if [ ! -f "profiler/device/plugins/ftrace_plugin/include/ftrace_common_type.h" ];then diff --git a/trace_streamer/prebuilts/patch_hiperf/hiviewdfx_faultloggerd.patch b/trace_streamer/prebuilts/patch_hiperf/hiviewdfx_faultloggerd.patch new file mode 100644 index 00000000..088df2a3 --- /dev/null +++ b/trace_streamer/prebuilts/patch_hiperf/hiviewdfx_faultloggerd.patch @@ -0,0 +1,159 @@ +diff --git a/common/dfxutil/string_view_util.h b/common/dfxutil/string_view_util.h +index b44a59ea775b368b93391ce19b440f617c309477..7dbd3568df9035edea91e920bf12fa5c58fe116f 100644 +--- a/common/dfxutil/string_view_util.h ++++ b/common/dfxutil/string_view_util.h +@@ -24,6 +24,24 @@ + + namespace OHOS { + namespace HiviewDFX { ++#ifdef is_mac ++class SpinLock { ++public: ++ void lock() ++ { ++ while (locked_.test_and_set(std::memory_order_acquire)) { ++ ; ++ } ++ } ++ void unlock() ++ { ++ locked_.clear(std::memory_order_release); ++ } ++ ++private: ++ std::atomic_flag locked_ = ATOMIC_FLAG_INIT; ++}; ++#endif + class StringViewHold { + public: + static StringViewHold &Get() +@@ -34,50 +52,77 @@ public: + + const char* Hold(STRING_VIEW view) + { ++#ifndef is_mac + pthread_spin_lock(&spin_lock_); ++#else ++ std::lock_guard lockGurand(spinlock_); ++#endif + if (view.size() == 0) { ++#ifndef is_mac + pthread_spin_unlock(&spin_lock_); ++#endif + return ""; + } + + char *p = new (std::nothrow) char[view.size() + 1]; + if (p == nullptr) { ++#ifndef is_mac + pthread_spin_unlock(&spin_lock_); ++#endif + return ""; + } + if (memset_s(p, view.size() + 1, '\0', view.size() + 1) != 0) { ++#ifndef is_mac + pthread_spin_unlock(&spin_lock_); ++#endif + return ""; + } + std::copy(view.data(), view.data() + view.size(), p); + views_.emplace_back(p); ++#ifndef is_mac + pthread_spin_unlock(&spin_lock_); ++#endif + return p; + } + + // only use in UT + void Clean() + { ++#ifndef is_mac + pthread_spin_lock(&spin_lock_); ++#else ++ std::lock_guard lockGurand(spinlock_); ++#endif + for (auto &p : views_) { + delete[] p; + } + views_.clear(); ++#ifndef is_mac + pthread_spin_unlock(&spin_lock_); ++#endif + } ++ + private: ++#ifndef is_mac + StringViewHold() + { + pthread_spin_init(&spin_lock_, PTHREAD_PROCESS_PRIVATE); + } ++#endif + ~StringViewHold() + { + Clean(); ++#ifndef is_mac + pthread_spin_destroy(&spin_lock_); ++#endif + } + + std::vector views_; ++#ifndef is_mac + pthread_spinlock_t spin_lock_; ++#else ++ SpinLock spinlock_; ++#endif + }; + } // namespace HiviewDFX + } // namespace OHOS +diff --git a/interfaces/common/byte_order.h b/interfaces/common/byte_order.h +index 3c40993ec56288deec6e40420a97d182587e9b62..a55d9db076a6fe1476a52a102fb968adb08073d7 100644 +--- a/interfaces/common/byte_order.h ++++ b/interfaces/common/byte_order.h +@@ -16,7 +16,7 @@ + #ifndef BYTE_ORDER_H + #define BYTE_ORDER_H + +-#if is_mingw ++#if is_mingw || is_mac + #define UNWIND_LITTLE_ENDIAN 1234 + #define UNWIND_BIG_ENDIAN 4321 + #define UNWIND_BYTE_ORDER -1 // Unknown +diff --git a/interfaces/innerkits/unwinder/include/dfx_elf_define.h b/interfaces/innerkits/unwinder/include/dfx_elf_define.h +index 6bc9394912c193417cbfe588551b07c255fce62a..a71d76b5641ec347f014736173137cf1115c446b 100644 +--- a/interfaces/innerkits/unwinder/include/dfx_elf_define.h ++++ b/interfaces/innerkits/unwinder/include/dfx_elf_define.h +@@ -17,7 +17,7 @@ + + #include + #include +-#if !is_mingw ++#if !is_mingw && !is_mac + #include + #include + #endif +diff --git a/interfaces/innerkits/unwinder/include/dfx_elf_parser.h b/interfaces/innerkits/unwinder/include/dfx_elf_parser.h +index b4c84437735176d28f7756930a8027152fc08155..86a4bdd197918e6246edf683eec2d213b1414803 100644 +--- a/interfaces/innerkits/unwinder/include/dfx_elf_parser.h ++++ b/interfaces/innerkits/unwinder/include/dfx_elf_parser.h +@@ -16,7 +16,7 @@ + #define DFX_ELF_PARSER_H + + #include +-#if is_mingw ++#if is_mingw || is_mac + #include "dfx_nonlinux_define.h" + #else + #include +diff --git a/interfaces/innerkits/unwinder/src/elf/dfx_elf.cpp b/interfaces/innerkits/unwinder/src/elf/dfx_elf.cpp +index 9398e59acea6722bb1bfebcd0f312ee826a6f5a1..d071f2b934610fb15a921972a9eb97f3c646506f 100644 +--- a/interfaces/innerkits/unwinder/src/elf/dfx_elf.cpp ++++ b/interfaces/innerkits/unwinder/src/elf/dfx_elf.cpp +@@ -20,7 +20,7 @@ + #include + #include + #include +-#if is_mingw ++#if is_mingw || is_mac + #include "dfx_nonlinux_define.h" + #else + #include diff --git a/trace_streamer/prebuilts/patch_llvm/llvm.patch b/trace_streamer/prebuilts/patch_llvm/llvm.patch index 3dbb0d44..fcde9776 100644 --- a/trace_streamer/prebuilts/patch_llvm/llvm.patch +++ b/trace_streamer/prebuilts/patch_llvm/llvm.patch @@ -144,7 +144,7 @@ index 56d5b2ce7dc3..827184b7a5e0 100644 + deps = [ + "//llvm/include/llvm/Config:config", + "//llvm/lib/Demangle", -+ "//third_party/zlib:libz", ++ "//src/parser/hiperf_parser:libz", + ] + } else { + deps = [ diff --git a/trace_streamer/src/BUILD.gn b/trace_streamer/src/BUILD.gn index 2a07f384..c40825e3 100644 --- a/trace_streamer/src/BUILD.gn +++ b/trace_streamer/src/BUILD.gn @@ -106,11 +106,10 @@ ohos_source_set("trace_streamer_source") { "filter:filter", "metrics:metrics_parser", "parser:parser", + "parser/hiperf_parser:libsec_static", "proto_reader:proto_reader", "table:table", "trace_data:trace_data", - "//third_party/bounds_checking_function:libsec_static", - "//third_party/perf_include/hiviewdfx:libfaultloggerd", ] public_configs = [ ":trace_streamer_cfg" ] public_deps = [] diff --git a/trace_streamer/src/base/BUILD.gn b/trace_streamer/src/base/BUILD.gn index f577bc20..2996b3d6 100644 --- a/trace_streamer/src/base/BUILD.gn +++ b/trace_streamer/src/base/BUILD.gn @@ -17,7 +17,7 @@ ohos_source_set("base") { subsystem_name = "${OHOS_PROFILER_SUBSYS_NAME}" part_name = "${OHOS_PROFILER_PART_NAME}" public_deps = [ - "${THIRD_PARTY}/zlib:libz", + "../parser/hiperf_parser:libz", "sqlite_ext:sqliteext", ] include_dirs = [ diff --git a/trace_streamer/src/filter/BUILD.gn b/trace_streamer/src/filter/BUILD.gn index 509be0c3..5939c40b 100644 --- a/trace_streamer/src/filter/BUILD.gn +++ b/trace_streamer/src/filter/BUILD.gn @@ -46,12 +46,12 @@ config("filter_cfg") { "${THIRD_PARTY}/sqlite/include", "${PERF_DIR}/hiperf/include", "${PERF_DIR}/hiperf/include/nonlinux/linux", - "${THIRD_PARTY}/perf_include/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include", - "${THIRD_PARTY}/perf_include/hiviewdfx/faultloggerd/interfaces/nonlinux", - "${THIRD_PARTY}/perf_include/hiviewdfx/faultloggerd/interfaces/common", - "${THIRD_PARTY}/perf_include/hiviewdfx/faultloggerd/common/dfxutil", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/nonlinux", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/common", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/dfxutil", "${PERF_DIR}/hiperf/include/nonlinux", - "${THIRD_PARTY}/perf_include/libbpf", + "${THIRD_PARTY}/libbpf/include/uapi", "${THIRD_PARTY}/bounds_checking_function/include", "${THIRD_PARTY}/json/single_include/nlohmann", "../proto_reader/include", diff --git a/trace_streamer/src/filter/hook_filter/BUILD.gn b/trace_streamer/src/filter/hook_filter/BUILD.gn index 8af32766..12edd4c6 100644 --- a/trace_streamer/src/filter/hook_filter/BUILD.gn +++ b/trace_streamer/src/filter/hook_filter/BUILD.gn @@ -17,7 +17,7 @@ import("../../../build/ts.gni") config("native_hook_filter_cfg") { include_dirs = [ ".", - "${COMMON_LIBRARY}/base/include", + "${COMMON_LIBRARY}/c_utils/base/include", ] } diff --git a/trace_streamer/src/parser/ebpf_parser/BUILD.gn b/trace_streamer/src/parser/ebpf_parser/BUILD.gn index 31f6667c..9446f852 100644 --- a/trace_streamer/src/parser/ebpf_parser/BUILD.gn +++ b/trace_streamer/src/parser/ebpf_parser/BUILD.gn @@ -77,7 +77,7 @@ ohos_static_library("ebpf_parser") { public_configs = [ ":ebpf_parser_cfg" ] public_deps = [ "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/memory_data:memory_data_reader", - "${PERF_DIR}/hiperf:hiperf_src", + "../hiperf_parser:ts_hiperf_src", ] deps = [ "${THIRD_PARTY}/protobuf:protobuf_lite_static", diff --git a/trace_streamer/src/parser/hiperf_parser/BUILD.gn b/trace_streamer/src/parser/hiperf_parser/BUILD.gn index 81a43fd6..0dd179d1 100644 --- a/trace_streamer/src/parser/hiperf_parser/BUILD.gn +++ b/trace_streamer/src/parser/hiperf_parser/BUILD.gn @@ -14,6 +14,242 @@ import("//build/ohos.gni") import("../../../build/ts.gni") +# THIRD_PARTY for bzip2 +config("bzip2_config") { + include_dirs = [ "${THIRD_PARTY}/bzip2" ] +} + +ohos_source_set("bzip2_src") { + sources = [ + "${THIRD_PARTY}/bzip2/blocksort.c", + "${THIRD_PARTY}/bzip2/bzlib.c", + "${THIRD_PARTY}/bzip2/compress.c", + "${THIRD_PARTY}/bzip2/crctable.c", + "${THIRD_PARTY}/bzip2/decompress.c", + "${THIRD_PARTY}/bzip2/huffman.c", + "${THIRD_PARTY}/bzip2/randtable.c", + ] + configs += [ ":bzip2_config" ] + + part_name = "bzip2" + subsystem_name = "thirdparty" +} + +ohos_source_set("libbz2") { + deps = [ ":bzip2_src" ] + public_configs = [ ":bzip2_config" ] + + part_name = "bzip2" + subsystem_name = "thirdparty" +} + +# THIRD_PARTY for zlib +config("zlib_config") { + cflags = [ + "-Wno-incompatible-pointer-types", + "-Werror", + "-Wno-strict-prototypes", + "-Wimplicit-function-declaration", + "-D HAVE_BZIP2", + ] +} + +config("zlib_public_config") { + include_dirs = [ "${THIRD_PARTY}/zlib" ] +} + +sources_zlib_with_ts_common = [ + "${THIRD_PARTY}/zlib/adler32.c", + "${THIRD_PARTY}/zlib/compress.c", + "${THIRD_PARTY}/zlib/contrib/minizip/ioapi.c", + "${THIRD_PARTY}/zlib/contrib/minizip/unzip.c", + "${THIRD_PARTY}/zlib/contrib/minizip/zip.c", + "${THIRD_PARTY}/zlib/crc32.c", + "${THIRD_PARTY}/zlib/deflate.c", + "${THIRD_PARTY}/zlib/gzclose.c", + "${THIRD_PARTY}/zlib/gzlib.c", + "${THIRD_PARTY}/zlib/gzread.c", + "${THIRD_PARTY}/zlib/gzwrite.c", + "${THIRD_PARTY}/zlib/infback.c", + "${THIRD_PARTY}/zlib/inffast.c", + "${THIRD_PARTY}/zlib/inflate.c", + "${THIRD_PARTY}/zlib/inftrees.c", + "${THIRD_PARTY}/zlib/trees.c", + "${THIRD_PARTY}/zlib/uncompr.c", + "${THIRD_PARTY}/zlib/zutil.c", +] +ohos_source_set("libz") { + sources = sources_zlib_with_ts_common + configs += [ ":zlib_config" ] + public_configs = [ ":zlib_public_config" ] + public_deps = [ ":libbz2" ] + part_name = "zlib" + subsystem_name = "thirdparty" +} + +# THIRD_PARTY for bounds_checking_function +sources_libsec_with_ts_common = [ + "${THIRD_PARTY}/bounds_checking_function/src/fscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/fwscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/gets_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/memcpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/memmove_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/memset_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/scanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/securecutil.c", + "${THIRD_PARTY}/bounds_checking_function/src/secureinput_a.c", + "${THIRD_PARTY}/bounds_checking_function/src/secureinput_w.c", + "${THIRD_PARTY}/bounds_checking_function/src/secureprintoutput_a.c", + "${THIRD_PARTY}/bounds_checking_function/src/secureprintoutput_w.c", + "${THIRD_PARTY}/bounds_checking_function/src/snprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/sprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/sscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/strcat_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/strcpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/strncat_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/strncpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/strtok_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/swprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/swscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vfscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vfwscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vsnprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vsprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vsscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vswprintf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vswscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/vwscanf_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wcscat_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wcscpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wcsncat_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wcsncpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wcstok_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wmemcpy_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wmemmove_s.c", + "${THIRD_PARTY}/bounds_checking_function/src/wscanf_s.c", +] + +ohos_source_set("libsec_static") { + sources = sources_libsec_with_ts_common + include_dirs = [ "${THIRD_PARTY}/bounds_checking_function/include" ] + cflags = [ + "-D_INC_STRING_S", + "-D_INC_WCHAR_S", + "-D_SECIMP=//", + "-D_STDIO_S_DEFINED", + "-D_INC_STDIO_S", + "-D_INC_STDLIB_S", + "-D_INC_MEMORY_S", + ] +} + +# THIRD_PARTY for hiviewdfx faultloggerd +config("faultloggerd_config") { + cflags = [ + "-D ALWAYSTRUE", + "-D DFX_NO_PRINT_LOG", + "-D is_host", + ] +} +config("faultloggerd_public_config") { + include_dirs = [ + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/dfxlog", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/dfxutil", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/trace/include", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/common", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/nonlinux", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/include", + "${THIRD_PARTY}/bounds_checking_function/include", + "${COMMON_LIBRARY}/c_utils/base/include", + ] +} +sources_faultloggerd_with_ts_common = [ + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/dfxutil/dfx_util.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/common/trace/dfx_trace_dlsym.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/ark/dfx_hap.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/dfx_elf.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/dfx_elf_parser.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/dfx_symbols.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/dfx_mmap.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/elf_factory.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/elf/elf_factory_selector.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/maps/dfx_map.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/maps/dfx_maps.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/memory/dfx_memory.cpp", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/innerkits/unwinder/src/utils/unwinder_config.cpp", +] +ohos_source_set("libfaultloggerd") { + sources = sources_faultloggerd_with_ts_common + configs += [ ":faultloggerd_config" ] + public_configs = [ ":faultloggerd_public_config" ] + public_deps = [ ":libsec_static" ] + part_name = "faultloggerd" + subsystem_name = "thirdparty" +} + +# THIRD_PARTY for hiperf +sources_platform_with_ts_common = [ + "${PERF_DIR}/hiperf/src/dwarf_encoding.cpp", + "${PERF_DIR}/hiperf/src/option.cpp", + "${PERF_DIR}/hiperf/src/perf_event_record.cpp", + "${PERF_DIR}/hiperf/src/perf_file_format.cpp", + "${PERF_DIR}/hiperf/src/perf_file_reader.cpp", + "${PERF_DIR}/hiperf/src/register.cpp", + "${PERF_DIR}/hiperf/src/report.cpp", + "${PERF_DIR}/hiperf/src/subcommand.cpp", + "${PERF_DIR}/hiperf/src/symbols_file.cpp", + "${PERF_DIR}/hiperf/src/unique_stack_table.cpp", + "${PERF_DIR}/hiperf/src/utilities.cpp", + "${PERF_DIR}/hiperf/src/virtual_runtime.cpp", + "${PERF_DIR}/hiperf/src/virtual_thread.cpp", +] + +if (hiperf_debug) { + sources_platform_with_ts_common += + [ "${PERF_DIR}/hiperf/src/debug_logger.cpp" ] +} + +config("ts_hiperf_config") { + cflags = [ + "-D ALWAYSTRUE", + "-D CONFIG_NO_HILOG", + ] + cflags_cc = [ + "-std=c++17", + "-fvisibility=hidden", + "-Wno-unused-variable", + ] +} +config("ts_hiperf_public_config") { + include_dirs = [ + "${PERF_DIR}/hiperf/include", + "${PERF_DIR}/hiperf/include/nonlinux", + "${PERF_DIR}/hiperf/include/nonlinux/linux", + ] + include_dirs += [ + "${THIRD_PARTY}/bounds_checking_function/include", + "${COMMON_LIBRARY}/c_utils/base/include", + "${THIRD_PARTY}/libbpf/include/uapi", + ] + if (is_mingw) { + cflags = [ "-includeMingW64Fix.h" ] + } +} +ohos_source_set("ts_hiperf_src") { + subsystem_name = "thirdparty" + part_name = "hiperf" + configs -= [ trace_cfg_path ] + configs += [ ":ts_hiperf_config" ] + public_configs = [ ":ts_hiperf_public_config" ] + public_deps = [ + ":libfaultloggerd", + ":libz", + ] + sources = sources_platform_with_ts_common +} + +# for hiperf parser config("hiperf_parser_cfg") { if (!is_independent_compile) { configs = [ "${TS_DIR}/gn:ts_config" ] @@ -46,7 +282,7 @@ config("hiperf_parser_cfg") { "${THIRD_PARTY}/bounds_checking_function/include", "${THIRD_PARTY}/sqlite/include", "${THIRD_PARTY}/protobuf/src", - "${COMMON_LIBRARY}/base/include", + "${COMMON_LIBRARY}/c_utils/base/include", "${THIRD_PARTY}/googletest/googletest/include", ] include_dirs += [ @@ -62,13 +298,10 @@ config("hiperf_parser_cfg") { ] include_dirs += [ "${THIRD_PARTY}/googletest/googletest/include", - "${THIRD_PARTY}/perf_include/libbpf", - "${THIRD_PARTY}/perf_include/include", - "${THIRD_PARTY}/perf_include", - "${THIRD_PARTY}/perf_include/linux", + "${THIRD_PARTY}/libbpf/include/uapi", "../hiperf_parser", "../hiperf_parser/include", - "${COMMON_LIBRARY}/base/include", + "${COMMON_LIBRARY}/c_utils/base/include", "${THIRD_PARTY}/sqlite", ] if (enable_addr2line) { @@ -82,28 +315,29 @@ config("hiperf_parser_cfg") { } } -ohos_static_library("hiperf_parser") { - subsystem_name = "${OHOS_PROFILER_SUBSYS_NAME}" - part_name = "${OHOS_PROFILER_PART_NAME}" - sources = [ "perf_data_parser.cpp" ] - public_configs = [ - ":hiperf_parser_cfg", - "../../filter/perf_filter:hiperf_filter_cfg", - ] - public_deps = [ - "${PERF_DIR}/hiperf:hiperf_src", - "${THIRD_PARTY}/protobuf:protobuf_lite_static", - "${THIRD_PARTY}/protobuf:protobuf_static", - "//third_party/perf_include/hiviewdfx:libfaultloggerd", - ] - if (enable_addr2line) { - public_deps += [ - "${LLVM_ROOT}/lib/DebugInfo/DWARF", - "${LLVM_ROOT}/lib/DebugInfo/PDB", - "${LLVM_ROOT}/lib/DebugInfo/Symbolize", - "${LLVM_ROOT}/lib/Demangle", - "${LLVM_ROOT}/lib/Object", - "${LLVM_ROOT}/lib/Support", +if (enable_hiperf) { + ohos_static_library("hiperf_parser") { + subsystem_name = "${OHOS_PROFILER_SUBSYS_NAME}" + part_name = "${OHOS_PROFILER_PART_NAME}" + sources = [ "perf_data_parser.cpp" ] + public_configs = [ + ":hiperf_parser_cfg", + "../../filter/perf_filter:hiperf_filter_cfg", + ] + public_deps = [ + ":ts_hiperf_src", + "${THIRD_PARTY}/protobuf:protobuf_lite_static", + "${THIRD_PARTY}/protobuf:protobuf_static", ] + if (enable_addr2line) { + public_deps += [ + "${LLVM_ROOT}/lib/DebugInfo/DWARF", + "${LLVM_ROOT}/lib/DebugInfo/PDB", + "${LLVM_ROOT}/lib/DebugInfo/Symbolize", + "${LLVM_ROOT}/lib/Demangle", + "${LLVM_ROOT}/lib/Object", + "${LLVM_ROOT}/lib/Support", + ] + } } } diff --git a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp index 8bfd5bd6..ebee25de 100644 --- a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp +++ b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp @@ -683,17 +683,17 @@ void PerfDataParser::UpdateClockType() TS_LOGI("useClockId_ = %u, clockId_ = %u", useClockId_, clockId_); } } -bool PerfDataParser::RecordCallBack(std::unique_ptr record) +bool PerfDataParser::RecordCallBack(PerfEventRecord &record) { // tell process tree what happend for rebuild symbols - report_->virtualRuntime_.UpdateFromRecord(*record); + report_->virtualRuntime_.UpdateFromRecord(record); - if (record->GetType() == PERF_RECORD_SAMPLE) { - std::unique_ptr sample(static_cast(record.release())); + if (record.GetType() == PERF_RECORD_SAMPLE) { + auto *sample = static_cast(&record); uint32_t callChainId = UpdateCallChainUnCompressed(sample); UpdatePerfSampleData(callChainId, sample); - } else if (record->GetType() == PERF_RECORD_COMM) { - auto recordComm = static_cast(record.get()); + } else if (record.GetType() == PERF_RECORD_COMM) { + auto recordComm = static_cast(&record); auto range = tidToPid_.equal_range(recordComm->data_.tid); for (auto it = range.first; it != range.second; it++) { if (it->second == recordComm->data_.pid) { @@ -708,7 +708,7 @@ bool PerfDataParser::RecordCallBack(std::unique_ptr record) return true; } -uint32_t PerfDataParser::UpdateCallChainUnCompressed(const std::unique_ptr &sample) +uint32_t PerfDataParser::UpdateCallChainUnCompressed(const PerfRecordSample *sample) { std::string stackStr = ""; for (auto &callFrame : sample->callFrames_) { @@ -723,7 +723,7 @@ uint32_t PerfDataParser::UpdateCallChainUnCompressed(const std::unique_ptrdata_.tid)}); - uint32_t depth = 0; +uint32_t depth = 0; for (auto frame = sample->callFrames_.rbegin(); frame != sample->callFrames_.rend(); ++frame) { uint64_t fileId = INVALID_UINT64; auto fileDataIndex = traceDataCache_->dataDict_.GetStringIndex(frame->mapName); @@ -737,7 +737,7 @@ uint32_t PerfDataParser::UpdateCallChainUnCompressed(const std::unique_ptr &sample) +void PerfDataParser::UpdatePerfSampleData(uint32_t callChainId, const PerfRecordSample *sample) { auto perfSampleData = traceDataCache_->GetPerfSampleData(); uint64_t newTimeStamp = 0; diff --git a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.h b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.h index 1c885c6e..b47ebf18 100644 --- a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.h +++ b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.h @@ -103,12 +103,12 @@ private: void UpdateReportWorkloadInfo() const; void UpdateSymbolAndFilesData(); void UpdateClockType(); - bool RecordCallBack(std::unique_ptr record); - void UpdatePerfSampleData(uint32_t callChainId, std::unique_ptr &sample); + bool RecordCallBack(PerfEventRecord &record); + void UpdatePerfSampleData(uint32_t callChainId, const PerfRecordSample *sample); std::tuple GetFileIdWithLikelyFilePath(const std::string &inputFilePath); bool ReloadPerfFile(const std::unique_ptr &symbolsFile, uint64_t &fileId, DataIndex &filePathIndex); void ReloadPerfCallChain(const std::unique_ptr &symbolsFile, uint64_t fileId, DataIndex filePathIndex); - uint32_t UpdateCallChainUnCompressed(const std::unique_ptr &sample); + uint32_t UpdateCallChainUnCompressed(const PerfRecordSample *sample); SplitPerfState DataLengthProcessing(const std::deque &dequeBuffer, perf_event_header &dataHeader, uint64_t size, diff --git a/trace_streamer/src/parser/pbreader_parser/BUILD.gn b/trace_streamer/src/parser/pbreader_parser/BUILD.gn index 26bbe946..789ffc41 100644 --- a/trace_streamer/src/parser/pbreader_parser/BUILD.gn +++ b/trace_streamer/src/parser/pbreader_parser/BUILD.gn @@ -130,7 +130,6 @@ ohos_source_set("pbreader_parser_src") { "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/services:ts_all_type_cpp_standard", "${THIRD_PARTY}/protobuf:protobuf_lite_static", "${THIRD_PARTY}/protobuf:protobuf_static", - "${THIRD_PARTY}/zlib:libz", - "//third_party/perf_include/hiviewdfx:libfaultloggerd", + "../hiperf_parser:libz", ] } diff --git a/trace_streamer/src/parser/pbreader_parser/native_hook_parser/BUILD.gn b/trace_streamer/src/parser/pbreader_parser/native_hook_parser/BUILD.gn index 0e512554..cd10c6bf 100644 --- a/trace_streamer/src/parser/pbreader_parser/native_hook_parser/BUILD.gn +++ b/trace_streamer/src/parser/pbreader_parser/native_hook_parser/BUILD.gn @@ -29,7 +29,7 @@ ohos_static_library("native_hook_parser") { "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/services:ts_all_type_cpp", "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/ftrace_data/${device_kernel_version}:ftrace_data_reader", "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/native_hook:native_hook_data_reader", - "${PERF_DIR}/hiperf:hiperf_src", + "../../hiperf_parser:ts_hiperf_src", ] public_configs = [ ":native_hook_parser_cfg", diff --git a/trace_streamer/src/parser/rawtrace_parser/BUILD.gn b/trace_streamer/src/parser/rawtrace_parser/BUILD.gn index 4786ec41..646ed3af 100755 --- a/trace_streamer/src/parser/rawtrace_parser/BUILD.gn +++ b/trace_streamer/src/parser/rawtrace_parser/BUILD.gn @@ -29,7 +29,7 @@ config("rawtrace_parser_comm") { "${THIRD_PARTY}/bounds_checking_function/include", "${THIRD_PARTY}/json/single_include/nlohmann", "${PERF_DIR}/profiler/device/plugins/ftrace_plugin/include", - "${COMMON_LIBRARY}/base/include", + "${COMMON_LIBRARY}/c_utils/base/include", ] include_dirs += [ "${SRC}/trace_data/trace_stdtype", diff --git a/trace_streamer/src/rpc/ffrt_converter.cpp b/trace_streamer/src/rpc/ffrt_converter.cpp index 72b5f3e0..9065504b 100644 --- a/trace_streamer/src/rpc/ffrt_converter.cpp +++ b/trace_streamer/src/rpc/ffrt_converter.cpp @@ -44,7 +44,7 @@ static bool WriteOutputFile(std::ofstream &outfile, std::vector &co std::vector file_buffer(BUFFER_SIZE); outfile.rdbuf()->pubsetbuf(file_buffer.data(), file_buffer.size()); - for (const auto& line : context_) { + for (const auto &line : context_) { outfile.write(line.c_str(), line.size()); outfile.put('\n'); } @@ -87,7 +87,7 @@ bool FfrtConverter::RecoverTraceAndGenerateNewFile(ConStr &ffrtFileName, std::of return WriteOutputFile(outFile, context_); } -static uint32_t ExtractProcessId(ConStr& log) +static uint32_t ExtractProcessId(ConStr &log) { size_t leftParen = log.find('('); size_t rightParen = log.find(')', leftParen); @@ -120,7 +120,7 @@ static uint32_t ExtractProcessId(ConStr& log) return processId; } -static int ExtractThreadId(ConStr& log) +static int ExtractThreadId(ConStr &log) { static const std::regex pattern(R"( \(\s*\d+\)\s+\[\d)"); std::smatch match; @@ -131,16 +131,14 @@ static int ExtractThreadId(ConStr& log) return INT_MAX; } -static std::string TrimRight(ConStr& str) +static std::string TrimRight(ConStr &str) { - auto end = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char ch) { - return std::isspace(ch); - }).base(); + auto end = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char ch) { return std::isspace(ch); }).base(); return std::string(str.begin(), end); } -static void ExtraceFfrtThreadInfoFromSchedSwitch(ConStr &log, std::string &tName, int& tid) +static void ExtraceFfrtThreadInfoFromSchedSwitch(ConStr &log, std::string &tName, int &tid) { static std::string prevComm = "prev_comm"; static std::string prevPid = "prev_pid="; @@ -183,10 +181,10 @@ static std::string ExtractCpuId(ConStr &log) return ""; } -static std::string ExtractTaskLable(ConStr& log) +static std::string ExtractTaskLable(ConStr &log) { size_t pos = log.find("|H:FFRT"); - if (pos != std::string::npos) { // ohos + if (pos != std::string::npos) { // ohos size_t startPos = pos + std::string("|H:FFRT").length(); size_t endPos = log.find("|", startPos); if (endPos != std::string::npos) { @@ -199,7 +197,7 @@ static std::string ExtractTaskLable(ConStr& log) return log.substr(startPos + 1, endPos - startPos - 1); } } - return ""; // Return empty string if no label found + return ""; // Return empty string if no label found } static void FindPrevAndNextPidForSchedSwitch(ConStr &log, int &prevPid, int &nextPid) @@ -218,7 +216,7 @@ static void FindPrevAndNextPidForSchedSwitch(ConStr &log, int &prevPid, int &nex void FfrtConverter::SetOSPlatformKey(const FfrtTidMap &ffrtTidMap) { - for (const auto& tinfo : ffrtTidMap) { + for (const auto &tinfo : ffrtTidMap) { if (tinfo.second.first.find("ffrtwk") != std::string::npos) { osPlatformKet_ = "nohos"; return; @@ -283,7 +281,7 @@ static void FindProcessNamePositions(ConStr &log, std::vector &indexs) } } -static void SplitLogs(std::vector &indexs, std::vector &newLogs, ConStr& log) +static void SplitLogs(std::vector &indexs, std::vector &newLogs, ConStr &log) { for (int i = 0; i < indexs.size(); i++) { int begin = indexs[i]; @@ -296,7 +294,7 @@ static void SplitLogs(std::vector &indexs, std::vector &new } } -static void GenFfrtPids(FfrtPids& ffrtPids, Info &info, FfrtTidMap &ffrtTidMap, WakeLogs &traceMap) +static void GenFfrtPids(FfrtPids &ffrtPids, Info &info, FfrtTidMap &ffrtTidMap, WakeLogs &traceMap) { for (const auto tid : info.second) { if (ffrtTidMap.find(tid) != ffrtTidMap.end()) { @@ -355,7 +353,7 @@ bool IsOldVersionTrace(ConStr &log) return true; } -void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& ffrtWakeLogs) +void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs) { PidMap pidMap; FfrtTidMap ffrtTidMap; @@ -384,10 +382,10 @@ void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& std::unordered_map> localPidMap; std::unordered_map>> locFfrtTidMap; std::unordered_map> locTraceMap; - std::vector& updates = threadUpdates[threadId]; + std::vector &updates = threadUpdates[threadId]; for (size_t lineno = start; lineno < end && lineno < context_.size(); ++lineno) { - const std::string& log = context_[lineno]; + const std::string &log = context_[lineno]; indexs.clear(); FindProcessNamePositions(log, indexs); @@ -415,13 +413,13 @@ void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& } { std::lock_guard lock(pidMutex); - for (const auto& [pid, tid_set] : localPidMap) { + for (const auto &[pid, tid_set] : localPidMap) { pidMap[pid].insert(tid_set.begin(), tid_set.end()); } } { std::lock_guard lock(tidMutex); - for (const auto& [tid, info] : locFfrtTidMap) { + for (const auto &[tid, info] : locFfrtTidMap) { if (ffrtTidMap.find(tid) == ffrtTidMap.end()) { ffrtTidMap[tid] = info; } @@ -429,8 +427,8 @@ void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& } { std::lock_guard lock(traceMutex); - for (const auto& [tid, traces] : locTraceMap) { - auto& target = traceMap[tid]; + for (const auto &[tid, traces] : locTraceMap) { + auto &target = traceMap[tid]; target.insert(target.end(), traces.begin(), traces.end()); } } @@ -441,35 +439,32 @@ void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& size_t end = std::min((i + 1) * chunkSize, context_.size()); threads.emplace_back(worker, i, start, end); } - for (auto& thread : threads) { + for (auto &thread : threads) { thread.join(); } std::vector allUpdates; - for (const auto& threadUpdate : threadUpdates) { + for (const auto &threadUpdate : threadUpdates) { allUpdates.insert(allUpdates.end(), threadUpdate.begin(), threadUpdate.end()); } std::sort(allUpdates.begin(), allUpdates.end(), - [](const ContextUpdate& a, const ContextUpdate& b) { - return a.position > b.position; - }); - for (const auto& update : allUpdates) { + [](const ContextUpdate &a, const ContextUpdate &b) { return a.position > b.position; }); + for (const auto &update : allUpdates) { context_.erase(context_.begin() + update.position); - context_.insert(context_.begin() + update.position, - update.new_logs.begin(), update.new_logs.end()); + context_.insert(context_.begin() + update.position, update.new_logs.begin(), update.new_logs.end()); } #else uint64_t lineno = 0; bool shouldCheck = true; std::vector indexs; indexs.reserve(ten); - auto classifyLogs = [this, &traceMap, &pidMap, &ffrtTidMap, &ffrtWakeLogs](ConVecStr& newLogs, size_t startLineNo) { + auto classifyLogs = [this, &traceMap, &pidMap, &ffrtTidMap, &ffrtWakeLogs](ConVecStr &newLogs, size_t startLineNo) { for (size_t i = 0; i < newLogs.size(); ++i) { auto logInfo = LogInfo{context_[startLineNo + i], static_cast(startLineNo + i), 0, 0}; FindFfrtProcClassifyLogs(logInfo, traceMap, pidMap, ffrtTidMap, ffrtWakeLogs); } }; while (lineno < context_.size()) { - ConStr& log = context_[lineno]; + ConStr &log = context_[lineno]; indexs.clear(); FindProcessNamePositions(log, indexs); if (shouldCheck && IsOldVersionTrace(log)) { @@ -491,7 +486,7 @@ void FfrtConverter::ClassifyLogsForFfrtWorker(FfrtPids& ffrtPids, FfrtWakeLogs& } #endif SetOSPlatformKey(ffrtTidMap); - for (auto& info : pidMap) { + for (auto &info : pidMap) { GenFfrtPids(ffrtPids, info, ffrtTidMap, traceMap); } } @@ -515,9 +510,7 @@ static size_t FindPatternStart(ConStr &log) ++bracketEnd; } - if (bracketEnd < log.length() && - log[bracketEnd] == '[' && - bracketEnd + 1 < log.length() && + if (bracketEnd < log.length() && log[bracketEnd] == '[' && bracketEnd + 1 < log.length() && isdigit(log[bracketEnd + 1])) { return pos; } @@ -625,8 +618,11 @@ void FfrtConverter::SetTracingMarkerKey(LogInfo logInfo) } } -void FfrtConverter::FindFfrtProcClassifyLogs(LogInfo logInfo, WakeLogs &traceMap, PidMap &pidMap, - FfrtTidMap &ffrtTidMap, FfrtWakeLogs &ffrtWakeLogs) +void FfrtConverter::FindFfrtProcClassifyLogs(LogInfo logInfo, + WakeLogs &traceMap, + PidMap &pidMap, + FfrtTidMap &ffrtTidMap, + FfrtWakeLogs &ffrtWakeLogs) { bool isPrevCommFfrt = logInfo.log.find("prev_comm=ffrt") != std::string::npos; bool isPrevCommFfrtOs = logInfo.log.find("prev_comm=OS_FFRT") != std::string::npos; @@ -748,9 +744,8 @@ static std::string MakeWakeupFakeLog(ConStr &log, const FakeLogArgs &fakeLogArgs { std::string mockTid = GenMockTid(fakeLogArgs.pid, fakeLogArgs.taskRunning); std::stringstream fakeLogStrm; - fakeLogStrm << log.substr(0, log.find(tracingMarkerKey)) << "sched_wakeup: comm=" << - fakeLogArgs.taskLabel << " pid=" << mockTid << " prio=" << - fakeLogArgs.prio << " target_cpu=" << fakeLogArgs.cpuId; + fakeLogStrm << log.substr(0, log.find(tracingMarkerKey)) << "sched_wakeup: comm=" << fakeLogArgs.taskLabel + << " pid=" << mockTid << " prio=" << fakeLogArgs.prio << " target_cpu=" << fakeLogArgs.cpuId; return fakeLogStrm.str(); } @@ -764,19 +759,15 @@ static std::string ReplaceSchedSwitchLog(ConStr &fakeLog, LogInfo logInfo, int g size_t index = fakeLog.find("("); result = " " + label + "-" + mockTid + " " + fakeLog.substr(index); } - result = result.substr(0, result.find("prev_comm=")) + - "prev_comm=" + label + - " " + result.substr(result.find("prev_pid=")); - result = result.substr(0, result.find("prev_pid=")) + - "prev_pid=" + mockTid + - " " + result.substr(result.find("prev_prio=")); + result = result.substr(0, result.find("prev_comm=")) + "prev_comm=" + label + " " + + result.substr(result.find("prev_pid=")); + result = result.substr(0, result.find("prev_pid=")) + "prev_pid=" + mockTid + " " + + result.substr(result.find("prev_prio=")); } else if (logInfo.log.find("next_pid=" + std::to_string(logInfo.tid)) != std::string::npos) { - result = result.substr(0, result.find("next_comm=")) + - "next_comm=" + label + - " " + result.substr(result.find("next_pid=")); - result = result.substr(0, result.find("next_pid=")) + - "next_pid=" + mockTid + - " " + result.substr(result.find("next_prio=")); + result = result.substr(0, result.find("next_comm=")) + "next_comm=" + label + " " + + result.substr(result.find("next_pid=")); + result = result.substr(0, result.find("next_pid=")) + "next_pid=" + mockTid + " " + + result.substr(result.find("next_prio=")); } return result; } @@ -807,11 +798,10 @@ static std::string ReplaceSchedBlockLog(ConStr &fakeLog, int pid, int gid) if (pos != std::string::npos) { pos = fakeLog.find("iowait=", pos); if (pos != std::string::npos) { - result = fakeLog.substr(0, fakeLog.find("pid=")) + "pid=" + - mockTid + " " + fakeLog.substr(pos); + result = fakeLog.substr(0, fakeLog.find("pid=")) + "pid=" + mockTid + " " + fakeLog.substr(pos); } else { - result = fakeLog.substr(0, fakeLog.find("pid=")) + "pid=" + - mockTid + " " + fakeLog.substr(fakeLog.find("io_wait=")); + result = fakeLog.substr(0, fakeLog.find("pid=")) + "pid=" + mockTid + " " + + fakeLog.substr(fakeLog.find("io_wait=")); } } return result; @@ -935,7 +925,7 @@ void FfrtConverter::FindQueueTaskInfo(FfrtPids &ffrtPids, QueueTaskInfo &queueTa { for (auto &pidItem : ffrtPids) { int pid = pidItem.first; - queueTaskInfo[pid] = {}; // Initialize map for this pid + queueTaskInfo[pid] = {}; // Initialize map for this pid for (auto &tidItem : pidItem.second) { std::vector &linenos = tidItem.second.second; @@ -988,12 +978,12 @@ void FfrtConverter::ExceTaskGroups(std::vector &group, WakeLogs &wakeLo if (wakeLogs.find(group[i].gid) != wakeLogs.end()) { int gid = group[i].gid; wakeLogs[firstGid].insert(wakeLogs[firstGid].end(), wakeLogs[gid].begin(), wakeLogs[gid].end()); - for (auto& lineno : group[i].begin) { + for (auto &lineno : group[i].begin) { size_t rightIndex = context_[lineno].find_last_of("|"); if (context_[lineno][rightIndex + 1] == 'I') { - context_[lineno] - = context_[lineno].substr(0, context_[lineno].substr(0, rightIndex).find_last_of("|") + 1) - + std::to_string(firstGid) + context_[lineno].substr(rightIndex) + "\n"; + context_[lineno] = + context_[lineno].substr(0, context_[lineno].substr(0, rightIndex).find_last_of("|") + 1) + + std::to_string(firstGid) + context_[lineno].substr(rightIndex) + "\n"; } else { context_[lineno] = context_[lineno].substr(0, rightIndex + 1) + std::to_string(firstGid) + "\n"; } @@ -1004,7 +994,7 @@ void FfrtConverter::ExceTaskGroups(std::vector &group, WakeLogs &wakeLo void FfrtConverter::HandleTaskGroups(std::vector> &taskGroups, WakeLogs &wakeLogs) { - for (auto& group : taskGroups) { + for (auto &group : taskGroups) { if (group.size() > 1) { int firstGid = group[0].gid; if (wakeLogs.find(firstGid) == wakeLogs.end()) { @@ -1016,15 +1006,15 @@ void FfrtConverter::HandleTaskGroups(std::vector> &taskGrou } } -void FfrtConverter::HandleFfrtQueueTasks(FfrtQueueTasks &ffrtQueueTasks, FfrtWakeLogs& ffrtWakeLogs) +void FfrtConverter::HandleFfrtQueueTasks(FfrtQueueTasks &ffrtQueueTasks, FfrtWakeLogs &ffrtWakeLogs) { for (auto &pidItem : ffrtQueueTasks) { WakeLogs tmp = {}; - WakeLogs &wakeLogs = (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) ? - ffrtWakeLogs[pidItem.first] : tmp; + WakeLogs &wakeLogs = + (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) ? ffrtWakeLogs[pidItem.first] : tmp; for (auto &qidItem : pidItem.second) { - auto cmp = [](tidInfo& value1, tidInfo& value2) {return value1.begin[0] < value2.begin[0];}; + auto cmp = [](tidInfo &value1, tidInfo &value2) { return value1.begin[0] < value2.begin[0]; }; std::sort(qidItem.second.begin(), qidItem.second.end(), cmp); std::vector> taskGroups; @@ -1045,8 +1035,8 @@ void FfrtConverter::HandleMarks(ConStr &log, int lineno, int pid) if (lostMarkPos != std::string::npos || faultMarkPos != std::string::npos) { if (tracingMarkerPos != std::string::npos) { - context_[lineno] = log.substr(0, tracingMarkerPos + tracingMarkerKey_.size()) - + "E|" + std::to_string(pid) + "\n"; + context_[lineno] = + log.substr(0, tracingMarkerPos + tracingMarkerKey_.size()) + "E|" + std::to_string(pid) + "\n"; } } } @@ -1132,8 +1122,10 @@ bool IsFfrtTaskBlockOrFinish(ConStr &log) return true; } -bool FfrtConverter::HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, std::string &label) +bool FfrtConverter::HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + std::string &label) { static const int spaceNum = 7; if (fakLogArg.log.find("|H:FFRT") != std::string::npos) { @@ -1141,13 +1133,14 @@ bool FfrtConverter::HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, WakeLogs &wake if (fakLogArg.taskRunning != -1) { missLog = MakeCoyieldFakeLog(fakLogArg); std::stringstream ss; - ss << " " << fakLogArg.tname << "-" << fakLogArg.tid << " (" << - std::setw(spaceNum) << std::right << fakLogArg.pid << ") [" << - fakLogArg.cpuId << "] .... " << fakLogArg.timestamp << - ": " << tracingMarkerKey_ << "E|" << fakLogArg.pid << "\n"; + ss << " " << fakLogArg.tname << "-" << fakLogArg.tid << " (" << std::setw(spaceNum) << std::right + << fakLogArg.pid << ") [" << fakLogArg.cpuId << "] .... " << fakLogArg.timestamp << ": " + << tracingMarkerKey_ << "E|" << fakLogArg.pid << "\n"; missLog += ss.str(); } - if (label.find("ex_task") != std::string::npos) { return true; } + if (label.find("ex_task") != std::string::npos) { + return true; + } int gid = -1; size_t pos = fakLogArg.log.find_last_of('|'); if (pos != std::string::npos) { @@ -1191,8 +1184,11 @@ bool FfrtConverter::HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, WakeLogs &wake return false; } -bool FfrtConverter::HandlePreLineno(FakeLogArgs &fakArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, ConStr traceBeginMark, ConStr traceEndMark) +bool FfrtConverter::HandlePreLineno(FakeLogArgs &fakArg, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + ConStr traceBeginMark, + ConStr traceEndMark) { std::string label = ExtractTaskLable(fakArg.log); if (HandleFfrtTaskExecute(fakArg, wakeLogs, taskLabels, label)) { @@ -1229,14 +1225,15 @@ bool FfrtConverter::HandlePreLineno(FakeLogArgs &fakArg, WakeLogs &wakeLogs, return false; } -void FfrtConverter::ExceTaskLabelOhos(TaskLabels &taskLabels, FfrtWakeLogs &ffrtWakeLogs, - std::pair pidItem, std::string traceBeginMark, +void FfrtConverter::ExceTaskLabelOhos(TaskLabels &taskLabels, + FfrtWakeLogs &ffrtWakeLogs, + std::pair pidItem, + std::string traceBeginMark, std::string traceEndMark) { taskLabels[pidItem.first] = {}; WakeLogs tmp = {}; - WakeLogs &wakeLogs = (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) - ? ffrtWakeLogs[pidItem.first] : tmp; + WakeLogs &wakeLogs = (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) ? ffrtWakeLogs[pidItem.first] : tmp; for (auto &tidItem : pidItem.second) { std::string tname = tidItem.second.first; @@ -1253,15 +1250,16 @@ void FfrtConverter::ExceTaskLabelOhos(TaskLabels &taskLabels, FfrtWakeLogs &ffrt std::string cpuId = ExtractCpuId(log); std::string timestamp = ExtractTimeStr(log); - FakeLogArgs fakArg{pidItem.first, tidItem.first, taskRunning, prio, lineno, - switchInFakeLog, switchOutFakeLog, log, tname, - taskLabels[pidItem.first][taskRunning], cpuId, timestamp}; + FakeLogArgs fakArg{ + pidItem.first, tidItem.first, taskRunning, prio, lineno, + switchInFakeLog, switchOutFakeLog, log, tname, taskLabels[pidItem.first][taskRunning], + cpuId, timestamp}; HandlePreLineno(fakArg, wakeLogs, taskLabels, traceBeginMark, traceEndMark); } } } -void FfrtConverter::GenTaskLabelsOhos(FfrtPids &ffrtPids, FfrtWakeLogs& ffrtWakeLogs, TaskLabels &taskLabels) +void FfrtConverter::GenTaskLabelsOhos(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs, TaskLabels &taskLabels) { static std::string traceBeginMark = tracingMarkerKey_ + "B"; static std::string traceEndMark = tracingMarkerKey_ + "E"; @@ -1270,7 +1268,7 @@ void FfrtConverter::GenTaskLabelsOhos(FfrtPids &ffrtPids, FfrtWakeLogs& ffrtWake } } -void FfrtConverter::ConvertFrrtThreadToFfrtTaskOhos(FfrtPids &ffrtPids, FfrtWakeLogs& ffrtWakeLogs) +void FfrtConverter::ConvertFrrtThreadToFfrtTaskOhos(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs) { QueueTaskInfo queueTaskInfo; FindQueueTaskInfo(ffrtPids, queueTaskInfo); @@ -1284,8 +1282,11 @@ void FfrtConverter::ConvertFrrtThreadToFfrtTaskOhos(FfrtPids &ffrtPids, FfrtWake GenTaskLabelsOhos(ffrtPids, ffrtWakeLogs, taskLabels); } -bool FfrtConverter::HandleHFfrtTaskExecute(FakeLogArgs &fakeArgs, WakeLogs &wakeLogs, TaskLabels &taskLabels, - std::string label, std::unordered_map &schedWakeFlag) +bool FfrtConverter::HandleHFfrtTaskExecute(FakeLogArgs &fakeArgs, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + std::string label, + std::unordered_map &schedWakeFlag) { static const int spaceNum = 7; if (fakeArgs.log.find("|FFRT") == std::string::npos) { @@ -1295,36 +1296,59 @@ bool FfrtConverter::HandleHFfrtTaskExecute(FakeLogArgs &fakeArgs, WakeLogs &wake if (fakeArgs.taskRunning != -1) { missLog = MakeCoyieldFakeLog(fakeArgs); std::stringstream ss; - ss << " " << fakeArgs.tname << "-" << fakeArgs.tid << " (" << - std::setw(spaceNum) << std::right << fakeArgs.pid << ") [" << - fakeArgs.cpuId << "] .... " << fakeArgs.timestamp << ": " << - tracingMarkerKey_ << "E|" << fakeArgs.pid << "\n"; + ss << " " << fakeArgs.tname << "-" << fakeArgs.tid << " (" << std::setw(spaceNum) << std::right + << fakeArgs.pid << ") [" << fakeArgs.cpuId << "] .... " << fakeArgs.timestamp << ": " << tracingMarkerKey_ + << "E|" << fakeArgs.pid << "\n"; missLog += ss.str(); } - if (label.find("executor_task") != std::string::npos) { return true; } + if (label.find("executor_task") != std::string::npos) { + return true; + } int gid = -1; size_t pos = fakeArgs.log.find_last_of('|'); if (pos != std::string::npos) { - if (pos + 1 >= fakeArgs.log.size()) { return true; } + if (pos + 1 >= fakeArgs.log.size()) { + return true; + } std::string gidStr = fakeArgs.log.substr(pos + 1); auto [ptr, ec] = std::from_chars(gidStr.data(), gidStr.data() + gidStr.size(), gid); - if (ec != std::errc{}) { return true; } + if (ec != std::errc{}) { + return true; + } } if (taskLabels[fakeArgs.pid].find(gid) == taskLabels[fakeArgs.pid].end()) { taskLabels[fakeArgs.pid][gid] = label; } fakeArgs.taskRunning = gid; - FakeLogArgs fakArg2{fakeArgs.pid, fakeArgs.tid, fakeArgs.taskRunning, fakeArgs.prio, fakeArgs.lineno, - fakeArgs.switchInFakeLog, fakeArgs.switchOutFakeLog, fakeArgs.log, fakeArgs.tname, - taskLabels[fakeArgs.pid][fakeArgs.taskRunning], fakeArgs.cpuId, fakeArgs.timestamp}; + FakeLogArgs fakArg2{fakeArgs.pid, + fakeArgs.tid, + fakeArgs.taskRunning, + fakeArgs.prio, + fakeArgs.lineno, + fakeArgs.switchInFakeLog, + fakeArgs.switchOutFakeLog, + fakeArgs.log, + fakeArgs.tname, + taskLabels[fakeArgs.pid][fakeArgs.taskRunning], + fakeArgs.cpuId, + fakeArgs.timestamp}; std::string fakeLog = MakeCostartFakeLog(fakArg2); context_[fakeArgs.lineno] = fakeLog; if (!missLog.empty()) { context_[fakeArgs.lineno] = missLog + context_[fakeArgs.lineno]; } - FakeLogArgs fakArg3{fakeArgs.pid, fakeArgs.tid, fakeArgs.taskRunning, fakeArgs.prio, fakeArgs.lineno, - fakeArgs.switchInFakeLog, fakeArgs.switchOutFakeLog, fakeArgs.log, fakeArgs.tname, - taskLabels[fakeArgs.pid][fakeArgs.taskRunning], fakeArgs.cpuId, fakeArgs.timestamp}; + FakeLogArgs fakArg3{fakeArgs.pid, + fakeArgs.tid, + fakeArgs.taskRunning, + fakeArgs.prio, + fakeArgs.lineno, + fakeArgs.switchInFakeLog, + fakeArgs.switchOutFakeLog, + fakeArgs.log, + fakeArgs.tname, + taskLabels[fakeArgs.pid][fakeArgs.taskRunning], + fakeArgs.cpuId, + fakeArgs.timestamp}; if (wakeLogs.find(fakeArgs.taskRunning) != wakeLogs.end()) { int prevIndex = FindGreaterThan(wakeLogs[fakeArgs.taskRunning], fakeArgs.lineno); if (prevIndex > 0) { @@ -1342,9 +1366,13 @@ static bool IsFfrtTaskBlockOrFinishNohos(ConStr &log) { static const std::string fStr = " F|"; size_t fPos = log.find(fStr); - if (fPos == std::string::npos) { return false; } + if (fPos == std::string::npos) { + return false; + } size_t firstNumberEndPos = log.find('|', fPos + fStr.size()); - if (firstNumberEndPos == std::string::npos) { return false; } + if (firstNumberEndPos == std::string::npos) { + return false; + } std::string firstNumber = log.substr(fPos + 3, firstNumberEndPos - (fPos + fStr.size())); bool isValidNumber = true; for (char c : firstNumber) { @@ -1353,7 +1381,9 @@ static bool IsFfrtTaskBlockOrFinishNohos(ConStr &log) break; } } - if (!isValidNumber) { return false; } + if (!isValidNumber) { + return false; + } size_t typePos = firstNumberEndPos + 1; if (typePos < log.length() && (log[typePos] == 'B' || log[typePos] == 'F')) { size_t thirdPipePos = log.find('|', typePos + 1); @@ -1367,8 +1397,10 @@ static bool IsFfrtTaskBlockOrFinishNohos(ConStr &log) return false; } -bool FfrtConverter::HandlePreLinenoNohos(FakeLogArgs &fakArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, std::unordered_map &schedWakeFlag) +bool FfrtConverter::HandlePreLinenoNohos(FakeLogArgs &fakArg, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + std::unordered_map &schedWakeFlag) { std::string label = ExtractTaskLable(fakArg.log); if (HandleHFfrtTaskExecute(fakArg, wakeLogs, taskLabels, label, schedWakeFlag)) { @@ -1388,24 +1420,25 @@ bool FfrtConverter::HandlePreLinenoNohos(FakeLogArgs &fakArg, WakeLogs &wakeLogs } return false; } -void FfrtConverter::ExceTaskLabelNohos(TaskLabels &taskLabels, FfrtWakeLogs &ffrtWakeLogs, - std::pair pidItem, std::unordered_map &schedWakeFlag) +void FfrtConverter::ExceTaskLabelNohos(TaskLabels &taskLabels, + FfrtWakeLogs &ffrtWakeLogs, + std::pair pidItem, + std::unordered_map &schedWakeFlag) { bool oneF = false; bool twoF = false; taskLabels[pidItem.first] = {}; WakeLogs tmp = {}; - WakeLogs &wakeLogs = (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) - ? ffrtWakeLogs[pidItem.first] : tmp; + WakeLogs &wakeLogs = (ffrtWakeLogs.find(pidItem.first) != ffrtWakeLogs.end()) ? ffrtWakeLogs[pidItem.first] : tmp; - for (auto& tidItem : pidItem.second) { + for (auto &tidItem : pidItem.second) { std::string tname = tidItem.second.first; std::vector linenos = tidItem.second.second; int prio = 120; int taskRunning = -1; for (auto lineno : linenos) { - std::string& log = context_[lineno]; + std::string &log = context_[lineno]; HandleMarks(log, lineno, pidItem.first); HandleSchedSwitch(log, tidItem.first, prio); @@ -1414,9 +1447,9 @@ void FfrtConverter::ExceTaskLabelNohos(TaskLabels &taskLabels, FfrtWakeLogs &ffr std::string timestamp = ExtractTimeStr(log); std::string label = ExtractTaskLable(log); - FakeLogArgs fakArg{pidItem.first, tidItem.first, taskRunning, prio, lineno, - oneF, twoF, log, tname, - taskLabels[pidItem.first][taskRunning], cpuId, timestamp}; + FakeLogArgs fakArg{pidItem.first, tidItem.first, taskRunning, prio, lineno, + oneF, twoF, log, tname, taskLabels[pidItem.first][taskRunning], + cpuId, timestamp}; HandlePreLinenoNohos(fakArg, wakeLogs, taskLabels, schedWakeFlag); } } diff --git a/trace_streamer/src/rpc/ffrt_converter.h b/trace_streamer/src/rpc/ffrt_converter.h index 94fb6b6c..f117a5f2 100644 --- a/trace_streamer/src/rpc/ffrt_converter.h +++ b/trace_streamer/src/rpc/ffrt_converter.h @@ -24,7 +24,7 @@ namespace SysTuning { namespace TraceStreamer { - using ConStr = const std::string; +using ConStr = const std::string; struct tidInfo { std::vector begin; @@ -78,13 +78,15 @@ public: bool RecoverTraceAndGenerateNewFile(ConStr &ffrtFileName, std::ofstream &outFile); private: - void SetOSPlatformKey(const std::unordered_map>> &ffrt_tid_map); - void FindFfrtProcClassifyLogs(LogInfo logInfo, WakeLogs &traceMap, PidMap &pidMap, - FfrtTidMap &ffrtTidMap, FfrtWakeLogs &ffrtWakeLogs); + void FindFfrtProcClassifyLogs(LogInfo logInfo, + WakeLogs &traceMap, + PidMap &pidMap, + FfrtTidMap &ffrtTidMap, + FfrtWakeLogs &ffrtWakeLogs); void ClassifyLogsForFfrtWorker(FfrtPids &ffrt_pids, FfrtWakeLogs &ffrt_wake_logs); - void ConvertFrrtThreadToFfrtTaskOhos(FfrtPids &ffrtPids, FfrtWakeLogs& ffrtWakeLogs); + void ConvertFrrtThreadToFfrtTaskOhos(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs); void ConvertFrrtThreadToFfrtTaskNohos(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs); // trace content @@ -95,19 +97,21 @@ private: void FindQueueTaskInfo(FfrtPids &ffrtPids, QueueTaskInfo &queueTaskInfo); - void HandleFfrtQueueTasks(FfrtQueueTasks &ffrtQueueTasks, FfrtWakeLogs& ffrtWakeLogs); + void HandleFfrtQueueTasks(FfrtQueueTasks &ffrtQueueTasks, FfrtWakeLogs &ffrtWakeLogs); void HandleMarks(ConStr &log, int lineno, int pid); bool HandleFfrtTaskCo(ConStr &log, int lineno, bool &switchInFakeLog, bool &switchOutFakeLog); - bool HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, std::string &label); + bool HandleFfrtTaskExecute(FakeLogArgs &fakLogArg, WakeLogs &wakeLogs, TaskLabels &taskLabels, std::string &label); - void GenTaskLabelsOhos(FfrtPids &ffrtPids, FfrtWakeLogs& ffrtWakeLogs, TaskLabels &taskLabels); + void GenTaskLabelsOhos(FfrtPids &ffrtPids, FfrtWakeLogs &ffrtWakeLogs, TaskLabels &taskLabels); - bool HandlePreLineno(FakeLogArgs &fakArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, ConStr traceBeginMark, ConStr traceEndMark); + bool HandlePreLineno(FakeLogArgs &fakArg, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + ConStr traceBeginMark, + ConStr traceEndMark); void SetTracingMarkerKey(LogInfo logInfo); @@ -117,17 +121,27 @@ private: void HandleTaskGroups(std::vector> &taskGroups, WakeLogs &wakeLogs); - void ExceTaskLabelOhos(TaskLabels &taskLabels, FfrtWakeLogs &ffrtWakeLogs, std::pair pidItem, - std::string traceBeginMark, std::string traceEndMark); - - bool HandleHFfrtTaskExecute(FakeLogArgs &fakeArgs, WakeLogs &wakeLogs, TaskLabels &taskLabels, - std::string label, std::unordered_map &schedWakeFlag); - - bool HandlePreLinenoNohos(FakeLogArgs &fakArg, WakeLogs &wakeLogs, - TaskLabels &taskLabels, std::unordered_map &schedWakeFlag); - - void ExceTaskLabelNohos(TaskLabels &taskLabels, FfrtWakeLogs &ffrtWakeLogs, - std::pair pidItem, std::unordered_map &schedWakeFlag); + void ExceTaskLabelOhos(TaskLabels &taskLabels, + FfrtWakeLogs &ffrtWakeLogs, + std::pair pidItem, + std::string traceBeginMark, + std::string traceEndMark); + + bool HandleHFfrtTaskExecute(FakeLogArgs &fakeArgs, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + std::string label, + std::unordered_map &schedWakeFlag); + + bool HandlePreLinenoNohos(FakeLogArgs &fakArg, + WakeLogs &wakeLogs, + TaskLabels &taskLabels, + std::unordered_map &schedWakeFlag); + + void ExceTaskLabelNohos(TaskLabels &taskLabels, + FfrtWakeLogs &ffrtWakeLogs, + std::pair pidItem, + std::unordered_map &schedWakeFlag); }; } // namespace TraceStreamer } // namespace SysTuning diff --git a/trace_streamer/src/trace_data/BUILD.gn b/trace_streamer/src/trace_data/BUILD.gn index ad0ff736..d2b2664d 100644 --- a/trace_streamer/src/trace_data/BUILD.gn +++ b/trace_streamer/src/trace_data/BUILD.gn @@ -81,7 +81,7 @@ ohos_source_set("trace_data") { "${THIRD_PARTY}/protobuf/src", "${THIRD_PARTY}/bounds_checking_function/include", "${THIRD_PARTY}/json/single_include/nlohmann", - "${THIRD_PARTY}/perf_include/hiviewdfx/faultloggerd/interfaces/nonlinux", + "${THIRD_PARTY}/hiviewdfx/faultloggerd/interfaces/nonlinux", "${SRC}/trace_data/trace_stdtype", "${SRC}/trace_data/trace_stdtype/ftrace", "${SRC}/trace_data/trace_stdtype/ftrace/template", diff --git a/trace_streamer/test/BUILD.gn b/trace_streamer/test/BUILD.gn index 723d0167..f5c7033b 100644 --- a/trace_streamer/test/BUILD.gn +++ b/trace_streamer/test/BUILD.gn @@ -90,8 +90,8 @@ if (is_test) { "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/process_data:ts_process_data_cpp", "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/test_data:test_data_cpp", "${OHOS_TRACE_STREAMER_PROTOS_DIR}/protos/types/plugins/xpower_data:ts_xpower_data_cpp", + "${SRC}/parser/hiperf_parser:libsec_static", "${SRC}/parser/rawtrace_parser:rawtrace_parser", - "${THIRD_PARTY}/bounds_checking_function:libsec_static", "${THIRD_PARTY}/googletest:gtest", "${THIRD_PARTY}/googletest:gtest_main", "${THIRD_PARTY}/protobuf:protobuf_lite_static", -- Gitee From 583ffd193cacd1c0ad3765a8fbeab79544797ee3 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Mon, 28 Apr 2025 15:04:57 +0800 Subject: [PATCH 10/66] =?UTF-8?q?=E8=B0=83=E6=95=B4hiperf=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E5=8C=96=E5=A4=B1=E8=B4=A5=E6=97=B6=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E6=96=B9=E5=BC=8F:=20so+pc=20->=20so+fileAddr.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- .../filter/perf_filter/perf_data_filter.cpp | 23 +++++++++++++------ .../src/filter/perf_filter/perf_data_filter.h | 2 ++ .../parser/hiperf_parser/perf_data_parser.cpp | 5 ++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/trace_streamer/src/filter/perf_filter/perf_data_filter.cpp b/trace_streamer/src/filter/perf_filter/perf_data_filter.cpp index 3709e4ed..5d343157 100644 --- a/trace_streamer/src/filter/perf_filter/perf_data_filter.cpp +++ b/trace_streamer/src/filter/perf_filter/perf_data_filter.cpp @@ -36,6 +36,14 @@ size_t PerfDataFilter::AppendPerfFiles(uint64_t fileId, uint32_t serial, DataInd return size; } +void PerfDataFilter::AppendInvalidVaddrIpToFuncName(uint64_t ip, DataIndex nameIndex) +{ + auto iter = invalidVaddrIpToFuncName_.find(ip); + if (iter == invalidVaddrIpToFuncName_.end()) { + invalidVaddrIpToFuncName_.emplace(ip, nameIndex); + } +} + void PerfDataFilter::BeforeReload() { traceDataCache_->GetPerfCallChainData()->Clear(); @@ -43,6 +51,7 @@ void PerfDataFilter::BeforeReload() fileIdToRowInFileTable_.Clear(); fileIds_.clear(); fileIdToRow_.clear(); + invalidVaddrIpToFuncName_.clear(); } void PerfDataFilter::Finish() { @@ -62,13 +71,12 @@ void PerfDataFilter::Finish() continue; } if (vaddrs[i] == 0 || symbolsIds[i] == -1) { - auto pathIndex = filePath[fileIdToRow_.at(fileIds[i])]; - auto fullPath = traceDataCache_->GetDataFromDict(pathIndex); - auto iPos = fullPath.find_last_of('/'); - fullPath = fullPath.substr(iPos + 1, -1); - auto nameIndex = traceDataCache_->GetDataIndex(fullPath + "@0x" + - base::number(ips[i] & flag, base::INTEGER_RADIX_TYPE_HEX)); - traceDataCache_->GetPerfCallChainData()->SetName(i, nameIndex); + auto iter = invalidVaddrIpToFuncName_.find(ips[i]); + if (iter != invalidVaddrIpToFuncName_.end()) { + traceDataCache_->GetPerfCallChainData()->SetName(i, iter->second); + } else { + TS_LOGE("invalidVaddrIpToFuncName_ can't find ip:%p", ips[i]); + } continue; } // if there has the file Id to which the function belongs,and the symboleid is not -1 and vaddrinfile is not -1. @@ -86,6 +94,7 @@ void PerfDataFilter::Finish() fileIdToRowInFileTable_.Clear(); fileIds_.clear(); fileIdToRow_.clear(); + invalidVaddrIpToFuncName_.clear(); } } // namespace TraceStreamer } // namespace SysTuning diff --git a/trace_streamer/src/filter/perf_filter/perf_data_filter.h b/trace_streamer/src/filter/perf_filter/perf_data_filter.h index 5698da90..e12221c9 100644 --- a/trace_streamer/src/filter/perf_filter/perf_data_filter.h +++ b/trace_streamer/src/filter/perf_filter/perf_data_filter.h @@ -34,6 +34,7 @@ public: public: size_t AppendPerfFiles(uint64_t fileId, uint32_t serial, DataIndex symbols, DataIndex filePath); + void AppendInvalidVaddrIpToFuncName(uint64_t ip, DataIndex nameIndex); void Finish(); void BeforeReload(); @@ -41,6 +42,7 @@ private: DoubleMap fileIdToRowInFileTable_; std::set fileIds_; std::map fileIdToRow_{}; + std::map invalidVaddrIpToFuncName_{}; }; } // namespace TraceStreamer } // namespace SysTuning diff --git a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp index ebee25de..548e0cdc 100644 --- a/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp +++ b/trace_streamer/src/parser/hiperf_parser/perf_data_parser.cpp @@ -732,6 +732,11 @@ uint32_t depth = 0; } PerfCallChainRow perfCallChainRow = {callChainId, depth++, frame->pc, frame->funcOffset, frame->mapOffset, fileId, frame->index}; + if (frame->funcOffset == 0 || frame->index == -1) { + auto iPos = frame->funcName.find_last_of('/'); + auto nameIndex = traceDataCache_->dataDict_.GetStringIndex(frame->funcName.substr(iPos + 1, -1)); + streamFilters_->perfDataFilter_->AppendInvalidVaddrIpToFuncName(frame->pc, nameIndex); + } traceDataCache_->GetPerfCallChainData()->AppendNewPerfCallChain(perfCallChainRow); } return callChainId; -- Gitee From 3f5528f4d1765f9d15995e493a7669230ea90d28 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Wed, 14 May 2025 09:51:49 +0800 Subject: [PATCH 11/66] =?UTF-8?q?=E6=B1=87=E7=BC=96=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=BC=98=E5=8C=96=E4=B8=BA=E4=BA=8C=E8=BF=9B?= =?UTF-8?q?=E5=88=B6=E4=BC=A0=E8=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- .../trace/component/trace/base/TraceSheet.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index 386717a3..11d53087 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -804,21 +804,21 @@ export class TraceSheet extends BaseElement { let blob: Blob | null = file.slice(writeSize, writeSize + sliceLen); let buffer: ArrayBuffer | null = await blob.arrayBuffer(); let data: Uint8Array | null = new Uint8Array(buffer); - - const dataObject = { + const dataObject = { file_name: fileName, buffer_index: bufferIndex, buffer_size: sliceLen, total_size: file.size, - is_last: writeSize + sliceLen >= file.size, - buffer: Array.from(data), + is_last: writeSize + sliceLen >= file.size }; - - const dataString = JSON.stringify(dataObject); + const jsonStr = `${dataString.length}|${dataString}`; const textEncoder = new TextEncoder(); - const encodedData = textEncoder.encode(dataString); - wsInstance!.sendMessage(TypeConstants.DISASSEMBLY_TYPE, Constants.DISASSEMBLY_SAVE_CMD, encodedData); + const jsonData = textEncoder.encode(jsonStr); + let mergeData: Uint8Array = new Uint8Array(jsonData.length + data.length); + mergeData.set(jsonData); + mergeData.set(data, jsonData.length); + wsInstance!.sendMessage(TypeConstants.DISASSEMBLY_TYPE, Constants.DISASSEMBLY_SAVE_CMD, mergeData); writeSize += sliceLen; // 等待服务器端确认当前分片的 ACK await waitForAck(); @@ -912,14 +912,14 @@ export class TraceSheet extends BaseElement {
-
+
-
+
diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts index 55eec1ba..814c862e 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerPerf.ts @@ -1449,6 +1449,9 @@ export class ProcedureLogicWorkerPerf extends LogicHandler { combineCallChainForAnalysis(obj?: unknown): PerfAnalysisSample[] { let sampleCallChainList: Array = []; for (let sample of this.samplesData) { + if (!this.callChainData[sample.sampleId]) { + continue; + } let callChains = [...this.callChainData[sample.sampleId]]; const lastCallChain = callChains[callChains.length - 1]; const threadName = this.threadData[sample.tid].threadName || 'Thread'; diff --git a/ide/src/webSocket/WebSocketManager.ts b/ide/src/webSocket/WebSocketManager.ts index 9318f1fa..b92eaa3c 100644 --- a/ide/src/webSocket/WebSocketManager.ts +++ b/ide/src/webSocket/WebSocketManager.ts @@ -38,11 +38,10 @@ export class WebSocketManager { private sessionId: number | null | undefined; private session: bigint | null | undefined; private heartbeatInterval: number | null | undefined; - private status: string = GetStatuses.UNCONNECTED; + public status: string = GetStatuses.UNCONNECTED; private cacheInfo: Map = new Map(); private reconnect: number = -1; private connectStatus: HTMLElement | null | undefined; - static disaStatus: string = GetStatuses.UNCONNECTED; constructor() { if (WebSocketManager.instance) { @@ -58,8 +57,6 @@ export class WebSocketManager { this.connectStatus = document.querySelector("body > sp-application").shadowRoot.querySelector("#main-menu").shadowRoot.querySelector("div.bottom > div.extend_connect"); this.websocket = new WebSocket(this.url); this.websocket.binaryType = 'arraybuffer'; - // @ts-ignore - setInterval(this.checkConnectionStatus(this.websocket), 5000); this.websocket.onopen = (): void => { this.status = GetStatuses.CONNECTED; // 设置心跳定时器 @@ -80,11 +77,12 @@ export class WebSocketManager { this.websocket.onerror = (error): void => { console.error('error:', error); + this.extendTips(false); }; this.websocket.onclose = (event): void => { this.status = GetStatuses.UNCONNECTED; - this.checkConnectionStatus(this.websocket); + this.extendTips(false); this.finalStatus(); //初始化标志位 this.initLoginInfo(); @@ -92,19 +90,6 @@ export class WebSocketManager { }; } - /** - * 实时监听websockets连接状态 - */ - checkConnectionStatus(websocket: WebSocket | null | undefined) { - // @ts-ignore - if (websocket?.readyState === websocket?.OPEN) { - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'green'; - } else { - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'red'; - } - } /** * 接收webSocket返回的buffer数据 @@ -117,9 +102,22 @@ export class WebSocketManager { } else if (decode.type === TypeConstants.UPDATE_TYPE) {// 升级 this.updateMessage(decode); } else {// type其他 + this.businessMessage(decode); + } + } + + // 扩展服务连接状态提示 + extendTips(flag: boolean): void { + if(flag) { // @ts-ignore this.connectStatus?.style.backgroundColor = 'green'; - this.businessMessage(decode); + // @ts-ignore + this.connectStatus?.title = 'The extended service is connected.'; + }else{ + // @ts-ignore + this.connectStatus?.style.backgroundColor = 'red'; + // @ts-ignore + this.connectStatus?.title = 'The extended service is not connected.'; } } @@ -129,8 +127,6 @@ export class WebSocketManager { this.status = GetStatuses.LOGINED; this.sessionId = decode.session_id; this.session = decode.session; - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'green'; //检查版本 this.getVersion(); } else if (decode.cmd === Constants.SESSION_EXCEED) { // session满了 @@ -152,6 +148,7 @@ export class WebSocketManager { return; } this.status = GetStatuses.READY; + this.extendTips(true); this.finalStatus(); } else if (decode.cmd === Constants.UPDATE_SUCCESS_CMD) { // 升级成功 this.status = GetStatuses.UPGRADESUCCESS; @@ -204,7 +201,7 @@ export class WebSocketManager { updateVersion(): void { // 扩展程序升级 let url = `https://${window.location.host.split(':')[0]}:${window.location.port - }/application/extend/hi-smart-perf-host-extend-update.zip`; + }${window.location.pathname}extend/hi-smart-perf-host-extend-update.zip`; fetch(url).then(response => { if (!response.ok) { throw new Error('No corresponding upgrade compression package found'); @@ -237,7 +234,7 @@ export class WebSocketManager { * listener是不同模块传来接收数据的函数 * 模块调用 */ -registerMessageListener(type: number, callback: Function, eventCallBack: Function, allowMultipleCallback: boolean = false): void { + registerMessageListener(type: number, callback: Function, eventCallBack: Function, allowMultipleCallback: boolean = false): void { let callbackObj = this.distributeMap.get(type); if (!callbackObj) { callbackObj = { @@ -282,7 +279,6 @@ registerMessageListener(type: number, callback: Function, eventCallBack: Functio } else { this.send(type, cmd, data); } - WebSocketManager.disaStatus = this.status; } send(type: number, cmd?: number, data?: Uint8Array): void { @@ -302,9 +298,6 @@ registerMessageListener(type: number, callback: Function, eventCallBack: Functio sendHeartbeat(): void { this.heartbeatInterval = window.setInterval(() => { if (this.status === GetStatuses.READY) { - WebSocketManager.disaStatus = GetStatuses.READY; - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'green'; this.send(TypeConstants.HEARTBEAT_TYPE, undefined, undefined); } }, Constants.INTERVAL_TIME); @@ -360,16 +353,10 @@ registerMessageListener(type: number, callback: Function, eventCallBack: Functio finalStatus(): void { if (this.reconnect !== -1) { if (this.status === GetStatuses.READY) { - WebSocketManager.disaStatus = GetStatuses.READY; - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'green'; // @ts-ignore this.sendMessage(this.reconnect, this.cacheInfo.get(this.reconnect)!.cmd, this.cacheInfo.get(this.reconnect)!.data); return; } - WebSocketManager.disaStatus = GetStatuses.UNCONNECTED; - // @ts-ignore - this.connectStatus?.style.backgroundColor = 'red'; this.distributeMap.get(this.reconnect)!.eventCallBack(this.status); } this.reconnect = -1; -- Gitee From dbd84356b97cfdadea1799cb65307324ae7e69e7 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 16 Jun 2025 19:51:29 +0800 Subject: [PATCH 50/66] =?UTF-8?q?fix:=E6=9C=8D=E5=8A=A1=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- ide/server/server-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/server/server-config.json b/ide/server/server-config.json index 0d231c8a..637b4034 100644 --- a/ide/server/server-config.json +++ b/ide/server/server-config.json @@ -1,4 +1,4 @@ { - "ServeInfo": "smartperf.rnd.huawei.com/statistics", + "ServeInfo": "127.0.0.1:9100/statistics", "MsgPublishFile": "" } \ No newline at end of file -- Gitee From 9e81e77d4e5a5c1ceaee7c11413afaa28f8e0c4c Mon Sep 17 00:00:00 2001 From: danghongquan Date: Tue, 24 Jun 2025 14:56:01 +0800 Subject: [PATCH 51/66] fix:update MIN_CLOCK_SET_RATE_ARGS_COUNT Signed-off-by: danghongquan --- .../ptreader_parser/bytrace_parser/bytrace_event_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h index d434e488..95ac451d 100644 --- a/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h +++ b/trace_streamer/src/parser/ptreader_parser/bytrace_parser/bytrace_event_parser.h @@ -97,7 +97,7 @@ private: static const uint32_t MIN_CPU_IDLE_ARGS_COUNT = 2; static const uint32_t MIN_CPU_FREQUENCY_ARGS_COUNT = 2; static const uint32_t MIN_PROCESS_EXIT_ARGS_COUNT = 2; - static const uint32_t MIN_CLOCK_SET_RATE_ARGS_COUNT = 2; + static const uint32_t MIN_CLOCK_SET_RATE_ARGS_COUNT = 3; static const uint32_t MIN_CLOCK_ENABLE_ARGS_COUNT = 3; static const uint32_t MIN_CLOCK_DISABLE_ARGS_COUNT = 3; static const uint32_t MIN_IRQ_HANDLER_ENTRY_ARGS_COUNT = 2; -- Gitee From 9c323a0d715f9e87bfcfdaba4e59f114b6908876 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 13 Jun 2025 11:36:25 +0800 Subject: [PATCH 52/66] =?UTF-8?q?feat:=E6=8F=92=E4=BB=B6=E6=8A=93=E5=8F=96?= =?UTF-8?q?HisystemEvent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/component/SpRecordTrace.ts | 3 ++ .../trace/component/setting/SpHisysEvent.ts | 29 +++++++++++++++---- ide/src/webSocket/Constants.ts | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ide/src/trace/component/SpRecordTrace.ts b/ide/src/trace/component/SpRecordTrace.ts index af564dc2..27649444 100644 --- a/ide/src/trace/component/SpRecordTrace.ts +++ b/ide/src/trace/component/SpRecordTrace.ts @@ -139,6 +139,7 @@ export class SpRecordTrace extends BaseElement { public static usbGetEvent: string; public static usbGetApp: string; private static usbGetVersion: string; + public static usbGetHisystem: string; static snapShotList: Array = []; static snapShotDuration: number = 0; static isSnapShotCapture: boolean = false; @@ -906,6 +907,8 @@ export class SpRecordTrace extends BaseElement { SpRecordTrace.usbGetApp = jsonRes.resultMessage; } else if (cmd === TypeConstants.USB_GET_VERSION) { SpRecordTrace.usbGetVersion = jsonRes.resultMessage; + } else if (cmd === TypeConstants.USB_GET_HISYSTEM) { + SpRecordTrace.usbGetHisystem = jsonRes.resultMessage; } }; diff --git a/ide/src/trace/component/setting/SpHisysEvent.ts b/ide/src/trace/component/setting/SpHisysEvent.ts index 9cb549b5..1bddae38 100644 --- a/ide/src/trace/component/setting/SpHisysEvent.ts +++ b/ide/src/trace/component/setting/SpHisysEvent.ts @@ -22,6 +22,8 @@ import { HdcDeviceManager } from '../../../hdc/HdcDeviceManager'; import { LitAllocationSelect } from '../../../base-ui/select/LitAllocationSelect'; import { SpHiSysEventHtml } from './SpHisysEvent.html'; import { LitSelectV } from '../../../base-ui/select/LitSelectV'; +import { WebSocketManager } from '../../../webSocket/WebSocketManager'; +import { TypeConstants } from '../../../webSocket/Constants'; @element('sp-hisys-event') export class SpHisysEvent extends BaseElement { @@ -99,11 +101,11 @@ export class SpHisysEvent extends BaseElement { if (SpRecordTrace.serialNumber === '') { this.domainInputEL!.dataSource([], ''); } else { - HdcDeviceManager.fileRecv(this.sysEventConfigPath, () => { }).then((pullRes) => { - pullRes.arrayBuffer().then((buffer) => { - if (buffer.byteLength > 0) { - let dec = new TextDecoder(); - this.eventConfig = JSON.parse(dec.decode(buffer)); + if (SpRecordTrace.useExtend) { + WebSocketManager.getInstance()!.sendMessage(TypeConstants.USB_TYPE, TypeConstants.USB_GET_HISYSTEM, new TextEncoder().encode(SpRecordTrace.serialNumber)); + setTimeout(() => { + if (SpRecordTrace.usbGetHisystem) { + this.eventConfig = JSON.parse(SpRecordTrace.usbGetHisystem); let domainList = Object.keys(this.eventConfig!); if (domainList.length > 0) { this.domainInputEL!.dataSource(domainList, 'ALL-Domain', true); @@ -111,8 +113,23 @@ export class SpHisysEvent extends BaseElement { this.domainInputEL!.dataSource([], ''); } } + }, 1000); + } else { + HdcDeviceManager.fileRecv(this.sysEventConfigPath, () => { }).then((pullRes) => { + pullRes.arrayBuffer().then((buffer) => { + if (buffer.byteLength > 0) { + let dec = new TextDecoder(); + this.eventConfig = JSON.parse(dec.decode(buffer)); + let domainList = Object.keys(this.eventConfig!); + if (domainList.length > 0) { + this.domainInputEL!.dataSource(domainList, 'ALL-Domain', true); + } else { + this.domainInputEL!.dataSource([], ''); + } + } + }); }); - }); + } } this.domainInputEl!.removeAttribute('readonly'); } else { diff --git a/ide/src/webSocket/Constants.ts b/ide/src/webSocket/Constants.ts index 491dd660..b4eb937f 100644 --- a/ide/src/webSocket/Constants.ts +++ b/ide/src/webSocket/Constants.ts @@ -47,4 +47,5 @@ export class TypeConstants { static USB_GET_EVENT = 4; static USB_GET_APP = 5; static USB_GET_VERSION = 6; + static USB_GET_HISYSTEM = 7; } \ No newline at end of file -- Gitee From 1f685d748e9a825e2fa5605536724fa8d7ff4273 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 13 Jun 2025 09:53:38 +0800 Subject: [PATCH 53/66] =?UTF-8?q?fix:statstics=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../trace/sheet/native-memory/TabPaneNMStatstics.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatstics.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatstics.ts index 212d6bd7..83ea5de7 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatstics.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatstics.ts @@ -76,11 +76,6 @@ export class TabPaneNMStatstics extends BaseElement { queryData(nativeStatisticsParam: SelectionParam): void { Promise.all([ queryNativeHookStatistics(nativeStatisticsParam.leftNs, nativeStatisticsParam.rightNs, this.currentSelectIPid), - queryNativeHookStatisticsSubType( - nativeStatisticsParam.leftNs, - nativeStatisticsParam.rightNs, - this.currentSelectIPid - ), queryNativeHookStatisticsMalloc( nativeStatisticsParam.leftNs, nativeStatisticsParam.rightNs, @@ -94,7 +89,7 @@ export class TabPaneNMStatstics extends BaseElement { let index3 = nativeStatisticsParam.nativeMemory.indexOf(this.nativeType[2]); this.setMemoryTypeData(nativeStatisticsParam, values[0], arr); if (index1 !== -1 || index3 !== -1) { - this.setSubTypeTableData(values[1], arr); + this.setSubTypeTableData([values[0][1]], arr); } let type = 0; if (index1 !== -1 || (index2 !== -1 && index3 !== -1)) { @@ -102,7 +97,7 @@ export class TabPaneNMStatstics extends BaseElement { } else { type = index2 !== -1 ? 1 : 2; } - this.setMallocTableData(values[2], arr, type); + this.setMallocTableData(values[1], arr, type); this.nativeStatisticsSource = arr; this.sortByColumn(this.sortColumn, this.sortType); }); -- Gitee From 0e600626218e2acaf3f63513e0b613ce1702158f Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 27 Jun 2025 14:41:54 +0800 Subject: [PATCH 54/66] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../component/trace/sheet/TabPaneFilter.ts | 50 +++++++++---------- .../trace/sheet/parallel/TabPaneMtParallel.ts | 20 ++++---- .../sheet/parallel/TabPaneTimeParallel.ts | 14 +++--- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/TabPaneFilter.ts b/ide/src/trace/component/trace/sheet/TabPaneFilter.ts index 5fdf7b28..a5f621e3 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneFilter.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneFilter.ts @@ -45,7 +45,7 @@ export class CpuStatus { cpu: number = 0; small: boolean = false; medium: boolean = false; - large: boolean = false; + big: boolean = false; } @element('tab-pane-filter') @@ -482,7 +482,7 @@ export class TabPaneFilter extends BaseElement { } //添加cpu列表 - setCoreConfigList(count: number, small: Array, mid: Array, large: Array): void { + setCoreConfigList(count: number, small: Array, mid: Array, big: Array): void { let divEl = this.shadowRoot!.querySelector('#data-core-popover > div > #tb_core_setting'); divEl!.innerHTML = ''; this.createCoreHeaderDiv(divEl); @@ -494,9 +494,9 @@ export class TabPaneFilter extends BaseElement { // @ts-ignore medium: mid.includes(i), // @ts-ignore - large: large.includes(i), + big: big.includes(i), }; - this.createCheckBoxLine(divEl, obj, small, mid, large); + this.createCheckBoxLine(divEl, obj, small, mid, big); } } @@ -519,14 +519,14 @@ export class TabPaneFilter extends BaseElement { mediumLine.textContent = 'M'; mediumLine.style.fontSize = '12px'; mediumLine.style.textAlign = 'center'; - let largeLine = document.createElement('div'); - largeLine.className = 'core_line'; - largeLine.style.fontWeight = 'bold'; - largeLine.textContent = 'L'; - largeLine.style.fontSize = '12px'; - largeLine.style.textAlign = 'center'; + let bigLine = document.createElement('div'); + bigLine.className = 'core_line'; + bigLine.style.fontWeight = 'bold'; + bigLine.textContent = 'B'; + bigLine.style.fontSize = '12px'; + bigLine.style.textAlign = 'center'; // @ts-ignore - tab?.append(...[cpuIdLine, smallLine, mediumLine, largeLine]); + tab?.append(...[cpuIdLine, smallLine, mediumLine, bigLine]); } //添加对应的cpu checkbox,并添加对应的监听事件 @@ -535,7 +535,7 @@ export class TabPaneFilter extends BaseElement { cpuStatus: CpuStatus, small: Array, mid: Array, - large: Array + big: Array ): void { let div = document.createElement('div'); div.textContent = cpuStatus.cpu + ''; @@ -553,43 +553,43 @@ export class TabPaneFilter extends BaseElement { midCheckBox.style.textAlign = 'center'; midCheckBox.style.marginLeft = 'auto'; midCheckBox.style.marginRight = 'auto'; - let largeCheckBox: LitCheckBox = new LitCheckBox(); - largeCheckBox.checked = cpuStatus.large; - largeCheckBox.setAttribute('not-close', ''); - largeCheckBox.style.marginLeft = 'auto'; - largeCheckBox.style.marginRight = 'auto'; + let bigCheckBox: LitCheckBox = new LitCheckBox(); + bigCheckBox.checked = cpuStatus.big; + bigCheckBox.setAttribute('not-close', ''); + bigCheckBox.style.marginLeft = 'auto'; + bigCheckBox.style.marginRight = 'auto'; smallCheckBox.addEventListener('change', (e: unknown) => { midCheckBox.checked = false; - largeCheckBox.checked = false; + bigCheckBox.checked = false; // @ts-ignore cpuStatus.small = e.detail.checked; // @ts-ignore this.canUpdateCheckList(e.detail.checked, small, cpuStatus.cpu); mid = mid.filter((it) => it !== cpuStatus.cpu); - large = large.filter((it) => it !== cpuStatus.cpu); + big = big.filter((it) => it !== cpuStatus.cpu); }); midCheckBox.addEventListener('change', (e: unknown) => { - largeCheckBox.checked = false; + bigCheckBox.checked = false; smallCheckBox.checked = false; // @ts-ignore cpuStatus.medium = e.detail.checked; // @ts-ignore this.canUpdateCheckList(e.detail.checked, mid, cpuStatus.cpu); - large = large.filter((it) => it !== cpuStatus.cpu); + big = big.filter((it) => it !== cpuStatus.cpu); small = small.filter((it) => it !== cpuStatus.cpu); }); - largeCheckBox.addEventListener('change', (e: unknown) => { + bigCheckBox.addEventListener('change', (e: unknown) => { midCheckBox.checked = false; smallCheckBox.checked = false; // @ts-ignore - cpuStatus.large = e.detail.checked; + cpuStatus.big = e.detail.checked; // @ts-ignore - this.canUpdateCheckList(e.detail.checked, large, cpuStatus.cpu); + this.canUpdateCheckList(e.detail.checked, big, cpuStatus.cpu); mid = mid.filter((it) => it !== cpuStatus.cpu); small = small.filter((it) => it !== cpuStatus.cpu); }); // @ts-ignore - divEl!.append(...[div, smallCheckBox, midCheckBox, largeCheckBox]); + divEl!.append(...[div, smallCheckBox, midCheckBox, bigCheckBox]); } //判断checkList数组是否需要push数据或删除数据 diff --git a/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts b/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts index 0da3c2f3..b888b2f5 100644 --- a/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts +++ b/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts @@ -32,7 +32,7 @@ const NUM_DIGITS: number = 3; const CORE_NUM: number = 12; const SMALL_CPU_NUM: Array = [0, 1, 2, 3]; const MID_CPU_NUM12: Array = [4, 5, 6, 7, 8, 9]; -const LARGE_CPU_NUM12: Array = [10, 11]; +const BIG_CPU_NUM12: Array = [10, 11]; const CORE_JSON = { 'group1': [4, 5], 'group2': [6, 7], @@ -43,7 +43,7 @@ export class CpuStatus { cpu: number = 0; small: boolean = false; medium: boolean = false; - large: boolean = false; + big: boolean = false; } @element('tabpane-mt-parallel') export class TabPaneMtParallel extends BaseElement { @@ -57,7 +57,7 @@ export class TabPaneMtParallel extends BaseElement { private leftStartNs: number = 0; private rightEndNs: number = 0; private midCores: Array = []; - private largeCores: Array = []; + private bigCores: Array = []; private smallCores: Array = []; private isCreateCpu: boolean = true; private isCreateGroup: boolean = true; @@ -94,7 +94,7 @@ export class TabPaneMtParallel extends BaseElement { if (this.isCreateCpu) { this.initDefaultConfig(); this.isCreateCpu = false; - this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.largeCores); + this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.bigCores); }; }; this.litSettingPopoverEl!.querySelector('.confirm-button')!.addEventListener('click', (e: unknown) => { @@ -171,7 +171,7 @@ export class TabPaneMtParallel extends BaseElement { updateDataSource(flag: boolean): void { let param = flag ? this.bufferGroupMap.size !== 0 : Utils.getInstance().getWinCpuCount() === CORE_NUM; let value = flag ? this.bufferGroupMap : new Map(Object.entries(CORE_JSON)); - if ((this.midCores.length || this.largeCores.length || this.smallCores.length) && param) { + if ((this.midCores.length || this.bigCores.length || this.smallCores.length) && param) { this.coreTypeMap.clear(); this.dataSourceMap.clear(); this.parallelTable!.loading = true; @@ -186,7 +186,7 @@ export class TabPaneMtParallel extends BaseElement { } } async getMtParallelData(obj: Map) :Promise { - let cpuObj: unknown = { 'L': this.largeCores, 'M': this.midCores, 'S': this.smallCores }; + let cpuObj: unknown = { 'B': this.bigCores, 'M': this.midCores, 'S': this.smallCores }; let processIds: Array = [...new Set(this.selectionParam!.processIds)]; for (const [key, cpuGroup] of obj.entries()) { //判断配的的组是否在同一个核分类中,如果在,返回是那个核分类,反之,返回null @@ -238,7 +238,7 @@ export class TabPaneMtParallel extends BaseElement { } //判断自配的相同物理核是否符合计算MT并行度的要求 - handleSamePhysicsCore(arr: unknown, obj: { 'L': Array; 'M': Array; 'S': Array }): string | null { + handleSamePhysicsCore(arr: unknown, obj: { 'B': Array; 'M': Array; 'S': Array }): string | null { let core = null; // @ts-ignore if (arr.length > 2) { return null } @@ -386,11 +386,11 @@ export class TabPaneMtParallel extends BaseElement { if (Utils.getInstance().getWinCpuCount() === CORE_NUM) { this.smallCores = [...SMALL_CPU_NUM]; this.midCores = [...MID_CPU_NUM12]; - this.largeCores = [...LARGE_CPU_NUM12]; + this.bigCores = [...BIG_CPU_NUM12]; } else { this.smallCores = []; this.midCores = []; - this.largeCores = []; + this.bigCores = []; } } } @@ -424,7 +424,7 @@ export class TabPaneMtParallel extends BaseElement { disabled: Utils.getInstance().getWinCpuCount() === CORE_NUM && str !== 'cut' && this.isReset ? !(switchArr.includes(i)) : - !([...this.smallCores, ...this.midCores, ...this.largeCores].includes(i)) + !([...this.smallCores, ...this.midCores, ...this.bigCores].includes(i)) }; this.creatGroupLineDIv(obj); } diff --git a/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts b/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts index 4e0acbdd..d8de4d19 100644 --- a/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts +++ b/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts @@ -31,7 +31,7 @@ const NUM_DIGITS: number = 3; const CORE_NUM: number = 12; const SMALL_CPU_NUM: Array = [0, 1, 2, 3]; const MID_CPU_NUM12: Array = [4, 5, 6, 7, 8, 9]; -const LARGE_CPU_NUM12: Array = [10, 11]; +const BIG_CPU_NUM12: Array = [10, 11]; @element('tabpane-time-parallel') export class TabPaneTimeParallel extends BaseElement { @@ -44,7 +44,7 @@ export class TabPaneTimeParallel extends BaseElement { private leftStartNs: number = 0; private rightEndNs: number = 0; private midCores: Array = []; - private largeCores: Array = []; + private bigCores: Array = []; private smallCores: Array = []; private initStatus: boolean = true; @@ -70,7 +70,7 @@ export class TabPaneTimeParallel extends BaseElement { if (this.initStatus) { this.initDefaultConfig(); this.initStatus = false; - this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.largeCores); + this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.bigCores); } }; this.litPopoverEl!.querySelector('.confirm-button')!.addEventListener('click', (e: unknown) => { @@ -85,7 +85,7 @@ export class TabPaneTimeParallel extends BaseElement { // @ts-ignore this.litPopoverEl!.visible = false; //当大中小核未分组时,默认查询所有核 - if (!this.midCores.length && !this.largeCores.length && !this.smallCores.length) { + if (!this.midCores.length && !this.bigCores.length && !this.smallCores.length) { this.assignAllCore(); } else { this.assignGroupCore(); @@ -144,11 +144,11 @@ export class TabPaneTimeParallel extends BaseElement { if (Utils.getInstance().getWinCpuCount() === CORE_NUM) { this.smallCores = [...SMALL_CPU_NUM]; this.midCores = [...MID_CPU_NUM12]; - this.largeCores = [...LARGE_CPU_NUM12]; + this.bigCores = [...BIG_CPU_NUM12]; } else { this.smallCores = []; this.midCores = []; - this.largeCores = []; + this.bigCores = []; } } } @@ -166,7 +166,7 @@ export class TabPaneTimeParallel extends BaseElement { let dataSourceMap: Map = new Map(); let processIds: Array = [...new Set(this.selectionParam!.processIds)]; let cpuObj: Object = { - 'L': this.largeCores, + 'B': this.bigCores, 'M': this.midCores, 'S': this.smallCores }; -- Gitee From 70c813e5abece64d921bdf548d3e176792e88e6f Mon Sep 17 00:00:00 2001 From: zhangzepeng Date: Tue, 1 Jul 2025 16:04:02 +0800 Subject: [PATCH 55/66] =?UTF-8?q?ai=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzepeng --- ide/server/main.go | 10 ++++++++++ ide/src/base-ui/menu/LitMainMenu.ts | 2 +- ide/src/trace/SpApplication.ts | 13 +++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ide/server/main.go b/ide/server/main.go index 772b1c4a..ab310beb 100644 --- a/ide/server/main.go +++ b/ide/server/main.go @@ -53,6 +53,7 @@ const HttpPort = 9000 var exPath string var serveInfo string +var aiInfo string var msgPublishData MsgPublishData var hdcPublicKey string var hdcPrivateKey *rsa.PrivateKey @@ -177,6 +178,7 @@ func main() { mux.Handle("/application/upload/", http.StripPrefix("/application/upload/", http.FileServer(http.Dir(filepath.FromSlash(exPath+"/upload"))))) mux.HandleFunc("/application/download-file", downloadHandler) mux.HandleFunc("/application/serverInfo", serverInfo) + mux.HandleFunc("/application/getAiInfo", getAiInfo) mux.HandleFunc("/application/hdcPublicKey", getHdcPublicKey) mux.HandleFunc("/application/encryptHdcMsg", encryptHdcMsg) mux.HandleFunc("/application/signatureHdcMsg", signatureHdcMsg) @@ -257,6 +259,12 @@ func serverInfo(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } +func getAiInfo(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("ai_info", aiInfo) + w.WriteHeader(200) +} + func getHdcPublicKey(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "text/json") @@ -340,6 +348,7 @@ func getMsgPublish(w http.ResponseWriter, r *http.Request) { type ServerConfig struct { ServeInfo string `json:"ServeInfo"` MsgPublishFile string `json:"MsgPublishFile"` + AiInfo string } type MsgPublishData struct { @@ -371,6 +380,7 @@ func readReqServerConfig() { return } serveInfo = sc.ServeInfo + aiInfo = sc.AiInfo msgPublishData.Mux.Lock() msgPublishData.FilePath = sc.MsgPublishFile msgPublishData.Mux.Unlock() diff --git a/ide/src/base-ui/menu/LitMainMenu.ts b/ide/src/base-ui/menu/LitMainMenu.ts index ab22f2d6..433937a1 100644 --- a/ide/src/base-ui/menu/LitMainMenu.ts +++ b/ide/src/base-ui/menu/LitMainMenu.ts @@ -299,7 +299,7 @@ export class LitMainMenu extends BaseElement {
- +
diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index bae7d464..e5d82322 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -291,6 +291,19 @@ export class SpApplication extends BaseElement { SpStatisticsHttpUtil.addUserVisitAction('visit'); } }) + let aiurl = `${window.location.protocol}//${window.location.host.split(':')[0]}:${window.location.port + }${window.location.pathname}getAiInfo`; + fetch(aiurl, { method: 'GET' }).then((res) => { + if (res.headers) { + const headers = res.headers; + let aiAnalysisShow = this.shadowRoot + ?.querySelector('lit-main-menu')! + .shadowRoot!.querySelector('.ai_analysis') as HTMLDivElement; + if (headers.get('ai_info') !== '') { + aiAnalysisShow.style.display = ''; + } + } + }) LongTraceDBUtils.getInstance().createDBAndTable().then(); } -- Gitee From 84c6b229ffa3ccf4d7a24cbfa06494f4b30f6a7c Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 4 Jul 2025 11:00:40 +0800 Subject: [PATCH 56/66] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../doc/quickstart_schedulinganalysis.html | 4 +- .../Schedulinganalysis/CPUdetailsetting.jpg | Bin 97100 -> 121620 bytes .../figures/Schedulinganalysis/CPUsetting.jpg | Bin 30579 -> 27650 bytes .../schedulingAnalysis/CheckCpuSetting.ts | 32 +++++------ .../Top20ThreadCpuUsage.html.ts | 8 +-- .../schedulingAnalysis/Top20ThreadCpuUsage.ts | 54 +++++++++--------- .../component/trace/sheet/TabPaneFilter.ts | 52 ++++++++--------- .../trace/sheet/parallel/TabPaneMtParallel.ts | 18 +++--- .../sheet/parallel/TabPaneTimeParallel.ts | 14 ++--- .../ProcedureLogicWorkerSchedulingAnalysis.ts | 38 ++++++------ 10 files changed, 110 insertions(+), 110 deletions(-) diff --git a/ide/src/doc/quickstart_schedulinganalysis.html b/ide/src/doc/quickstart_schedulinganalysis.html index a4104ab2..e7738ee1 100644 --- a/ide/src/doc/quickstart_schedulinganalysis.html +++ b/ide/src/doc/quickstart_schedulinganalysis.html @@ -1016,7 +1016,7 @@ duration:运行总时长。

Top20线程大中小核占用率

- 选择Thread Analysis标签页,各个CPU通过勾选big或者middle或者small来设置CPU的分类。 + 选择Thread Analysis标签页,各个CPU通过勾选big或者middle或者little来设置CPU的分类。
GitHub Logo
@@ -1080,7 +1080,7 @@ middle core:中核占用时长。

  • -small core:小核占用时长。
    +little core:小核占用时长。
     
  • diff --git a/ide/src/figures/Schedulinganalysis/CPUdetailsetting.jpg b/ide/src/figures/Schedulinganalysis/CPUdetailsetting.jpg index 9683b732eee0a99ca2afd50a42bae44befba67cb..b414073a0879f54e8bfc0bbf4822842950c40e9c 100644 GIT binary patch literal 121620 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

    pMcIZUZcL4@WOQiy3)8PggRMHEV#DHd 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 diff --git a/ide/src/figures/Schedulinganalysis/CPUsetting.jpg b/ide/src/figures/Schedulinganalysis/CPUsetting.jpg index b2d43b9b41cfd5023acfd195cbaf829355ba9f7d..f46651947d5996a0fdeeb97870742ef18497ee74 100644 GIT binary patch literal 27650 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 { midCheckBox.checked = false; - smallCheckBox.checked = false; + littleCheckBox.checked = false; cpuSetting.big = true; CheckCpuSetting.big_cores.push(cpuSetting.cpu); CheckCpuSetting.mid_cores = CheckCpuSetting.mid_cores.filter((it): boolean => it !== cpuSetting.cpu); - CheckCpuSetting.small_cores = CheckCpuSetting.small_cores.filter((it): boolean => it !== cpuSetting.cpu); + CheckCpuSetting.little_cores = CheckCpuSetting.little_cores.filter((it): boolean => it !== cpuSetting.cpu); }); midCheckBox.addEventListener('change', (): void => { bigCheckBox.checked = false; - smallCheckBox.checked = false; + littleCheckBox.checked = false; cpuSetting.middle = true; CheckCpuSetting.mid_cores.push(cpuSetting.cpu); CheckCpuSetting.big_cores = CheckCpuSetting.big_cores.filter((it): boolean => it !== cpuSetting.cpu); - CheckCpuSetting.small_cores = CheckCpuSetting.small_cores.filter((it): boolean => it !== cpuSetting.cpu); + CheckCpuSetting.little_cores = CheckCpuSetting.little_cores.filter((it): boolean => it !== cpuSetting.cpu); }); - smallCheckBox.addEventListener('change', (): void => { + littleCheckBox.addEventListener('change', (): void => { midCheckBox.checked = false; bigCheckBox.checked = false; - cpuSetting.small = true; - CheckCpuSetting.small_cores.push(cpuSetting.cpu); + cpuSetting.little = true; + CheckCpuSetting.little_cores.push(cpuSetting.cpu); CheckCpuSetting.mid_cores = CheckCpuSetting.mid_cores.filter((it): boolean => it !== cpuSetting.cpu); CheckCpuSetting.big_cores = CheckCpuSetting.big_cores.filter((it): boolean => it !== cpuSetting.cpu); }); - this.table?.append(...[div, bigCheckBox, midCheckBox, smallCheckBox]); + this.table?.append(...[div, bigCheckBox, midCheckBox, littleCheckBox]); } createHeaderDiv(): void { @@ -139,14 +139,14 @@ export class CheckCpuSetting extends BaseElement { let column4 = document.createElement('div'); column4.className = 'setting_line'; column4.style.fontWeight = 'bold'; - column4.textContent = 'small'; + column4.textContent = 'little'; this.table?.append(...[column1, column2, column3, column4]); } static resetCpuSettings(): void { CheckCpuSetting.init_setting = false; CheckCpuSetting.big_cores = []; - CheckCpuSetting.small_cores = []; + CheckCpuSetting.little_cores = []; CheckCpuSetting.mid_cores = []; } diff --git a/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.html.ts b/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.html.ts index 34f7f3ac..5f543de0 100644 --- a/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.html.ts +++ b/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.html.ts @@ -80,23 +80,23 @@ export const Top20ThreadCpuUsageHtml = `

    big
    mid
    -
    small
    +
    little
    -
    +
    Top20线程小核占用率
    -
    small
    +
    little
    - +
    diff --git a/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.ts b/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.ts index e1c86a49..c78526ff 100644 --- a/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.ts +++ b/ide/src/trace/component/schedulingAnalysis/Top20ThreadCpuUsage.ts @@ -36,7 +36,7 @@ export class Top20ThreadCpuUsage extends BaseElement { private table: LitTable | null | undefined; private tableBig: LitTable | null | undefined; private tableMid: LitTable | null | undefined; - private tableSmall: LitTable | null | undefined; + private tableLittle: LitTable | null | undefined; private chartTotal: LitChartColumn | null | undefined; private chart2: LitChartColumn | null | undefined; private chart3: LitChartColumn | null | undefined; @@ -49,10 +49,10 @@ export class Top20ThreadCpuUsage extends BaseElement { private data: Array = []; private dataBig: Array = []; private dataMid: Array = []; - private dataSmall: Array = []; + private dataLittle: Array = []; private sort: unknown = { total: { key: '', sort: 0 }, - small: { key: '', sort: 0 }, + little: { key: '', sort: 0 }, mid: { key: '', sort: 0 }, big: { key: '', sort: 0 }, }; @@ -82,9 +82,9 @@ export class Top20ThreadCpuUsage extends BaseElement { `; - private smallColumn = ` - - + private littleColumn = ` + + `; initElements(): void { @@ -93,14 +93,14 @@ export class Top20ThreadCpuUsage extends BaseElement { this.table = this.shadowRoot!.querySelector('#tb-thread-usage'); this.tableBig = this.shadowRoot!.querySelector('#tb-thread-big'); this.tableMid = this.shadowRoot!.querySelector('#tb-thread-mid'); - this.tableSmall = this.shadowRoot!.querySelector('#tb-thread-small'); + this.tableLittle = this.shadowRoot!.querySelector('#tb-thread-little'); this.chartTotal = this.shadowRoot!.querySelector('#chart_total'); this.chart2 = this.shadowRoot!.querySelector('#chart_2'); this.chart3 = this.shadowRoot!.querySelector('#chart_3'); this.chart4 = this.shadowRoot!.querySelector('#chart_4'); this.map = new Map(); this.map.set('total', { chart: this.chartTotal!, table: this.table! }); - this.map.set('small', { chart: this.chart2!, table: this.tableSmall! }); + this.map.set('little', { chart: this.chart2!, table: this.tableLittle! }); this.map.set('mid', { chart: this.chart3!, table: this.tableMid! }); this.map.set('big', { chart: this.chart4!, table: this.tableBig! }); this.setting = this.shadowRoot!.querySelector('#setting'); @@ -110,8 +110,8 @@ export class Top20ThreadCpuUsage extends BaseElement { //@ts-ignore (this.shadowRoot!.querySelector('#total')! as unknown).style.display = 'grid'; //@ts-ignore - (this.shadowRoot!.querySelector('#small')! as unknown).style.display = - CheckCpuSetting.small_cores.length > 0 ? 'grid' : 'none'; + (this.shadowRoot!.querySelector('#little')! as unknown).style.display = + CheckCpuSetting.little_cores.length > 0 ? 'grid' : 'none'; //@ts-ignore (this.shadowRoot!.querySelector('#mid')! as unknown).style.display = CheckCpuSetting.mid_cores.length > 0 ? 'grid' : 'none'; @@ -153,9 +153,9 @@ export class Top20ThreadCpuUsage extends BaseElement { if (key === 'total') { //@ts-ignore this.sortByColumn(evt.detail, tab, this.data); - } else if (key === 'small') { + } else if (key === 'little') { //@ts-ignore - this.sortByColumn(evt.detail, tab, this.dataSmall); + this.sortByColumn(evt.detail, tab, this.dataLittle); } else if (key === 'mid') { //@ts-ignore this.sortByColumn(evt.detail, tab, this.dataMid); @@ -211,8 +211,8 @@ export class Top20ThreadCpuUsage extends BaseElement { key = 'big'; } else if (key === 'midTimeStr') { key = 'mid'; - } else if (key === 'smallTimeStr') { - key = 'small'; + } else if (key === 'littleTimeStr') { + key = 'little'; } else if ( key === 'bigPercent' || key === 'ratio' || @@ -296,7 +296,7 @@ export class Top20ThreadCpuUsage extends BaseElement { //@ts-ignore mid: it.mid, //@ts-ignore - small: it.small, + little: it.little, no: index + 1, visible: 1, //@ts-ignore @@ -304,13 +304,13 @@ export class Top20ThreadCpuUsage extends BaseElement { //@ts-ignore midPercent: it.midPercent, //@ts-ignore - smallPercent: it.smallPercent, + littlePercent: it.littlePercent, //@ts-ignore bigTimeStr: it.bigTimeStr, //@ts-ignore midTimeStr: it.midTimeStr, //@ts-ignore - smallTimeStr: it.smallTimeStr, + littleTimeStr: it.littleTimeStr, hideHandler: (): void => { //@ts-ignore let arr = source.filter((o) => o.visible === 1); @@ -333,8 +333,8 @@ export class Top20ThreadCpuUsage extends BaseElement { private assignmentData(key: string, source: unknown[], obj: { chart: LitChartColumn; table: LitTable }): void { if (key === 'total') { this.data = source; - } else if (key === 'small') { - this.dataSmall = source; + } else if (key === 'little') { + this.dataLittle = source; } else if (key === 'mid') { this.dataMid = source; } else if (key === 'big') { @@ -362,7 +362,7 @@ export class Top20ThreadCpuUsage extends BaseElement { return '#2f72f8'; //@ts-ignore } else if (a.size === 'middle core') { return '#ffab67'; //@ts-ignore - } else if (a.size === 'small core') { + } else if (a.size === 'little core') { return '#a285d2'; } else { return '#0a59f7'; @@ -450,10 +450,10 @@ export class Top20ThreadCpuUsage extends BaseElement { pName: obj.pName, //@ts-ignore tid: obj.tid, //@ts-ignore tName: obj.tName, //@ts-ignore - total: obj.small, - size: 'small core', //@ts-ignore + total: obj.little, + size: 'little core', //@ts-ignore no: obj.no, //@ts-ignore - timeStr: obj.smallTimeStr, + timeStr: obj.littleTimeStr, }); } else { data.push({ @@ -479,7 +479,7 @@ export class Top20ThreadCpuUsage extends BaseElement { { bigCores: CheckCpuSetting.big_cores, midCores: CheckCpuSetting.mid_cores, - smallCores: CheckCpuSetting.small_cores, + littleCores: CheckCpuSetting.little_cores, }, undefined, handler @@ -490,13 +490,13 @@ export class Top20ThreadCpuUsage extends BaseElement { getTableColumns(type: string): string { if (type === 'total') { - return `${this.publicColumns}${this.bigColumn}${this.midColumn}${this.smallColumn}`; + return `${this.publicColumns}${this.bigColumn}${this.midColumn}${this.littleColumn}`; } else if (type === 'big') { return `${this.publicColumns}${this.bigColumn}`; } else if (type === 'mid') { return `${this.publicColumns}${this.midColumn}`; - } else if (type === 'small') { - return `${this.publicColumns}${this.smallColumn}`; + } else if (type === 'little') { + return `${this.publicColumns}${this.littleColumn}`; } else { return ''; } diff --git a/ide/src/trace/component/trace/sheet/TabPaneFilter.ts b/ide/src/trace/component/trace/sheet/TabPaneFilter.ts index a5f621e3..fbaaf9f1 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneFilter.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneFilter.ts @@ -43,7 +43,7 @@ export interface MiningData { export class CpuStatus { cpu: number = 0; - small: boolean = false; + little: boolean = false; medium: boolean = false; big: boolean = false; } @@ -482,7 +482,7 @@ export class TabPaneFilter extends BaseElement { } //添加cpu列表 - setCoreConfigList(count: number, small: Array, mid: Array, big: Array): void { + setCoreConfigList(count: number, little: Array, mid: Array, big: Array): void { let divEl = this.shadowRoot!.querySelector('#data-core-popover > div > #tb_core_setting'); divEl!.innerHTML = ''; this.createCoreHeaderDiv(divEl); @@ -490,13 +490,13 @@ export class TabPaneFilter extends BaseElement { let obj = { cpu: i, // @ts-ignore - small: small.includes(i), + little: little.includes(i), // @ts-ignore medium: mid.includes(i), // @ts-ignore big: big.includes(i), }; - this.createCheckBoxLine(divEl, obj, small, mid, big); + this.createCheckBoxLine(divEl, obj, little, mid, big); } } @@ -507,12 +507,12 @@ export class TabPaneFilter extends BaseElement { cpuIdLine.style.fontSize = '12px'; cpuIdLine.textContent = 'Cpu'; cpuIdLine.style.textAlign = 'center'; - let smallLine = document.createElement('div'); - smallLine.className = 'core_line'; - smallLine.style.fontWeight = 'bold'; - smallLine.textContent = 'S'; - smallLine.style.fontSize = '12px'; - smallLine.style.textAlign = 'center'; + let littleLine = document.createElement('div'); + littleLine.className = 'core_line'; + littleLine.style.fontWeight = 'bold'; + littleLine.textContent = 'L'; + littleLine.style.fontSize = '12px'; + littleLine.style.textAlign = 'center'; let mediumLine = document.createElement('div'); mediumLine.className = 'core_line'; mediumLine.style.fontWeight = 'bold'; @@ -526,14 +526,14 @@ export class TabPaneFilter extends BaseElement { bigLine.style.fontSize = '12px'; bigLine.style.textAlign = 'center'; // @ts-ignore - tab?.append(...[cpuIdLine, smallLine, mediumLine, bigLine]); + tab?.append(...[cpuIdLine, littleLine, mediumLine, bigLine]); } //添加对应的cpu checkbox,并添加对应的监听事件 createCheckBoxLine( divEl: unknown, cpuStatus: CpuStatus, - small: Array, + little: Array, mid: Array, big: Array ): void { @@ -541,12 +541,12 @@ export class TabPaneFilter extends BaseElement { div.textContent = cpuStatus.cpu + ''; div.style.textAlign = 'center'; div.style.fontWeight = 'normal'; - let smallCheckBox: LitCheckBox = new LitCheckBox(); - smallCheckBox.checked = cpuStatus.small; - smallCheckBox.setAttribute('not-close', ''); - smallCheckBox.style.textAlign = 'center'; - smallCheckBox.style.marginLeft = 'auto'; - smallCheckBox.style.marginRight = 'auto'; + let littleCheckBox: LitCheckBox = new LitCheckBox(); + littleCheckBox.checked = cpuStatus.little; + littleCheckBox.setAttribute('not-close', ''); + littleCheckBox.style.textAlign = 'center'; + littleCheckBox.style.marginLeft = 'auto'; + littleCheckBox.style.marginRight = 'auto'; let midCheckBox: LitCheckBox = new LitCheckBox(); midCheckBox.checked = cpuStatus.medium; midCheckBox.setAttribute('not-close', ''); @@ -558,38 +558,38 @@ export class TabPaneFilter extends BaseElement { bigCheckBox.setAttribute('not-close', ''); bigCheckBox.style.marginLeft = 'auto'; bigCheckBox.style.marginRight = 'auto'; - smallCheckBox.addEventListener('change', (e: unknown) => { + littleCheckBox.addEventListener('change', (e: unknown) => { midCheckBox.checked = false; bigCheckBox.checked = false; // @ts-ignore - cpuStatus.small = e.detail.checked; + cpuStatus.little = e.detail.checked; // @ts-ignore - this.canUpdateCheckList(e.detail.checked, small, cpuStatus.cpu); + this.canUpdateCheckList(e.detail.checked, little, cpuStatus.cpu); mid = mid.filter((it) => it !== cpuStatus.cpu); big = big.filter((it) => it !== cpuStatus.cpu); }); midCheckBox.addEventListener('change', (e: unknown) => { bigCheckBox.checked = false; - smallCheckBox.checked = false; + littleCheckBox.checked = false; // @ts-ignore cpuStatus.medium = e.detail.checked; // @ts-ignore this.canUpdateCheckList(e.detail.checked, mid, cpuStatus.cpu); big = big.filter((it) => it !== cpuStatus.cpu); - small = small.filter((it) => it !== cpuStatus.cpu); + little = little.filter((it) => it !== cpuStatus.cpu); }); bigCheckBox.addEventListener('change', (e: unknown) => { midCheckBox.checked = false; - smallCheckBox.checked = false; + littleCheckBox.checked = false; // @ts-ignore cpuStatus.big = e.detail.checked; // @ts-ignore this.canUpdateCheckList(e.detail.checked, big, cpuStatus.cpu); mid = mid.filter((it) => it !== cpuStatus.cpu); - small = small.filter((it) => it !== cpuStatus.cpu); + little = little.filter((it) => it !== cpuStatus.cpu); }); // @ts-ignore - divEl!.append(...[div, smallCheckBox, midCheckBox, bigCheckBox]); + divEl!.append(...[div, littleCheckBox, midCheckBox, bigCheckBox]); } //判断checkList数组是否需要push数据或删除数据 diff --git a/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts b/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts index b888b2f5..123880cc 100644 --- a/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts +++ b/ide/src/trace/component/trace/sheet/parallel/TabPaneMtParallel.ts @@ -30,7 +30,7 @@ import { Utils } from '../../base/Utils'; const UNIT: number = 1000000.0; const NUM_DIGITS: number = 3; const CORE_NUM: number = 12; -const SMALL_CPU_NUM: Array = [0, 1, 2, 3]; +const LITTLE_CPU_NUM: Array = [0, 1, 2, 3]; const MID_CPU_NUM12: Array = [4, 5, 6, 7, 8, 9]; const BIG_CPU_NUM12: Array = [10, 11]; const CORE_JSON = { @@ -41,7 +41,7 @@ const CORE_JSON = { }; export class CpuStatus { cpu: number = 0; - small: boolean = false; + little: boolean = false; medium: boolean = false; big: boolean = false; } @@ -58,7 +58,7 @@ export class TabPaneMtParallel extends BaseElement { private rightEndNs: number = 0; private midCores: Array = []; private bigCores: Array = []; - private smallCores: Array = []; + private littleCores: Array = []; private isCreateCpu: boolean = true; private isCreateGroup: boolean = true; private coreTypeMap: Map = new Map(); @@ -94,7 +94,7 @@ export class TabPaneMtParallel extends BaseElement { if (this.isCreateCpu) { this.initDefaultConfig(); this.isCreateCpu = false; - this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.bigCores); + this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.littleCores, this.midCores, this.bigCores); }; }; this.litSettingPopoverEl!.querySelector('.confirm-button')!.addEventListener('click', (e: unknown) => { @@ -171,7 +171,7 @@ export class TabPaneMtParallel extends BaseElement { updateDataSource(flag: boolean): void { let param = flag ? this.bufferGroupMap.size !== 0 : Utils.getInstance().getWinCpuCount() === CORE_NUM; let value = flag ? this.bufferGroupMap : new Map(Object.entries(CORE_JSON)); - if ((this.midCores.length || this.bigCores.length || this.smallCores.length) && param) { + if ((this.midCores.length || this.bigCores.length || this.littleCores.length) && param) { this.coreTypeMap.clear(); this.dataSourceMap.clear(); this.parallelTable!.loading = true; @@ -186,7 +186,7 @@ export class TabPaneMtParallel extends BaseElement { } } async getMtParallelData(obj: Map) :Promise { - let cpuObj: unknown = { 'B': this.bigCores, 'M': this.midCores, 'S': this.smallCores }; + let cpuObj: unknown = { 'B': this.bigCores, 'M': this.midCores, 'L': this.littleCores }; let processIds: Array = [...new Set(this.selectionParam!.processIds)]; for (const [key, cpuGroup] of obj.entries()) { //判断配的的组是否在同一个核分类中,如果在,返回是那个核分类,反之,返回null @@ -384,11 +384,11 @@ export class TabPaneMtParallel extends BaseElement { initDefaultConfig(): void { if (this.isCreateCpu) { if (Utils.getInstance().getWinCpuCount() === CORE_NUM) { - this.smallCores = [...SMALL_CPU_NUM]; + this.littleCores = [...LITTLE_CPU_NUM]; this.midCores = [...MID_CPU_NUM12]; this.bigCores = [...BIG_CPU_NUM12]; } else { - this.smallCores = []; + this.littleCores = []; this.midCores = []; this.bigCores = []; } @@ -424,7 +424,7 @@ export class TabPaneMtParallel extends BaseElement { disabled: Utils.getInstance().getWinCpuCount() === CORE_NUM && str !== 'cut' && this.isReset ? !(switchArr.includes(i)) : - !([...this.smallCores, ...this.midCores, ...this.bigCores].includes(i)) + !([...this.littleCores, ...this.midCores, ...this.bigCores].includes(i)) }; this.creatGroupLineDIv(obj); } diff --git a/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts b/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts index d8de4d19..34535d25 100644 --- a/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts +++ b/ide/src/trace/component/trace/sheet/parallel/TabPaneTimeParallel.ts @@ -29,7 +29,7 @@ import { Utils } from '../../base/Utils'; const UNIT: number = 1000000.0; const NUM_DIGITS: number = 3; const CORE_NUM: number = 12; -const SMALL_CPU_NUM: Array = [0, 1, 2, 3]; +const LITTLE_CPU_NUM: Array = [0, 1, 2, 3]; const MID_CPU_NUM12: Array = [4, 5, 6, 7, 8, 9]; const BIG_CPU_NUM12: Array = [10, 11]; @@ -45,7 +45,7 @@ export class TabPaneTimeParallel extends BaseElement { private rightEndNs: number = 0; private midCores: Array = []; private bigCores: Array = []; - private smallCores: Array = []; + private littleCores: Array = []; private initStatus: boolean = true; set data(threadStatesParam: SelectionParam) { @@ -70,7 +70,7 @@ export class TabPaneTimeParallel extends BaseElement { if (this.initStatus) { this.initDefaultConfig(); this.initStatus = false; - this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.smallCores, this.midCores, this.bigCores); + this.bottomFilterEl!.setCoreConfigList(Utils.getInstance().getWinCpuCount(), this.littleCores, this.midCores, this.bigCores); } }; this.litPopoverEl!.querySelector('.confirm-button')!.addEventListener('click', (e: unknown) => { @@ -85,7 +85,7 @@ export class TabPaneTimeParallel extends BaseElement { // @ts-ignore this.litPopoverEl!.visible = false; //当大中小核未分组时,默认查询所有核 - if (!this.midCores.length && !this.bigCores.length && !this.smallCores.length) { + if (!this.midCores.length && !this.bigCores.length && !this.littleCores.length) { this.assignAllCore(); } else { this.assignGroupCore(); @@ -142,11 +142,11 @@ export class TabPaneTimeParallel extends BaseElement { initDefaultConfig(): void { if (this.initStatus) { if (Utils.getInstance().getWinCpuCount() === CORE_NUM) { - this.smallCores = [...SMALL_CPU_NUM]; + this.littleCores = [...LITTLE_CPU_NUM]; this.midCores = [...MID_CPU_NUM12]; this.bigCores = [...BIG_CPU_NUM12]; } else { - this.smallCores = []; + this.littleCores = []; this.midCores = []; this.bigCores = []; } @@ -168,7 +168,7 @@ export class TabPaneTimeParallel extends BaseElement { let cpuObj: Object = { 'B': this.bigCores, 'M': this.midCores, - 'S': this.smallCores + 'L': this.littleCores }; for (const [key, val] of Object.entries(cpuObj)) { if (val.length) { diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 9533835a..5c3af47b 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -24,7 +24,7 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { freq: number = 0; bigCores: Array = []; midCores: Array = []; - smallCores: Array = []; + littleCores: Array = []; cpuFreqMap: Map> = new Map>(); cpuIdle0Map: Map> = new Map>(); threadMap: Map = new Map(); @@ -34,7 +34,7 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { clearAll(): void { this.bigCores.length = 0; this.midCores.length = 0; - this.smallCores.length = 0; + this.littleCores.length = 0; this.cpuAnalysisMap.clear(); this.threadMap.clear(); this.processMap.clear(); @@ -283,9 +283,9 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { //@ts-ignore this.midCores = data.params.midCores || []; //@ts-ignore - this.smallCores = data.params.smallCores || []; + this.littleCores = data.params.littleCores || []; //@ts-ignore - this.queryThreadCpuUsage(data.params.bigCores || [], data.params.midCores || [], data.params.smallCores || []); + this.queryThreadCpuUsage(data.params.bigCores || [], data.params.midCores || [], data.params.littleCores || []); } } private schedulingThreadRunTime(data: { id: string; action: string; params: unknown }): void { @@ -526,7 +526,7 @@ where cmf.name = 'cpu_idle' and value != 0 ); } - queryThreadCpuUsage(bigCores: number[], midCores: number[], smallCores: number[]): void { + queryThreadCpuUsage(bigCores: number[], midCores: number[], littleCores: number[]): void { let sql = ` select A.pid,A.tid,A.cpu, sum(A.dur) as total @@ -919,7 +919,7 @@ where cpu not null }) .slice(0, 20); } - private filterThreadCpuUsageArr(arr: unknown, sumBig: number, sumMid: number, sumSmall: number): void { + private filterThreadCpuUsageArr(arr: unknown, sumBig: number, sumMid: number, sumLittle: number): void { //@ts-ignore return arr.reduce((group: unknown, item: { total: number; pid: number; tid: number; cpu: number }) => { const { tid } = item; @@ -934,9 +934,9 @@ where cpu not null cpuType = 'mid'; sumMid += item.total; } - if (this.smallCores.includes(item.cpu)) { - cpuType = 'small'; - sumSmall += item.total; + if (this.littleCores.includes(item.cpu)) { + cpuType = 'little'; + sumLittle += item.total; } if (tidObj) { //@ts-ignore @@ -944,7 +944,7 @@ where cpu not null //@ts-ignore tidObj.mid += cpuType === 'mid' ? item.total : 0; //@ts-ignore - tidObj.small += cpuType === 'small' ? item.total : 0; + tidObj.little += cpuType === 'little' ? item.total : 0; //@ts-ignore tidObj.total += item.total; //@ts-ignore @@ -959,7 +959,7 @@ where cpu not null total: item.total, big: cpuType === 'big' ? item.total : 0, mid: cpuType === 'mid' ? item.total : 0, - small: cpuType === 'small' ? item.total : 0, + little: cpuType === 'little' ? item.total : 0, }; //@ts-ignore group[`${tid}`][`cpu${item.cpu}`] = item.total; @@ -971,8 +971,8 @@ where cpu not null private handlerThreadCpuUsageData(arr: Array): Map { let sumBig = 0; let sumMid = 0; - let sumSmall = 0; - let reduceObj = this.filterThreadCpuUsageArr(arr, sumBig, sumMid, sumSmall); + let sumLittle = 0; + let reduceObj = this.filterThreadCpuUsageArr(arr, sumBig, sumMid, sumLittle); // @ts-ignore let source: unknown[] = Object.values(reduceObj); for (let obj of source) { @@ -981,13 +981,13 @@ where cpu not null // @ts-ignore obj.midPercent = sumMid === 0 ? '0' : ((obj.mid / sumMid) * 100).toFixed(2); // @ts-ignore - obj.smallPercent = sumSmall === 0 ? '0' : ((obj.small / sumSmall) * 100).toFixed(2); + obj.littlePercent = sumLittle === 0 ? '0' : ((obj.little / sumLittle) * 100).toFixed(2); // @ts-ignore obj.bigTimeStr = getProbablyTime(obj.big); // @ts-ignore obj.midTimeStr = getProbablyTime(obj.mid); // @ts-ignore - obj.smallTimeStr = getProbablyTime(obj.small); + obj.littleTimeStr = getProbablyTime(obj.little); } let map: Map> = new Map>(); // @ts-ignore @@ -997,7 +997,7 @@ where cpu not null // @ts-ignore map.set('mid', source.sort((a, b) => b.mid - a.mid).slice(0, 20)); // @ts-ignore - map.set('small', source.sort((a, b) => b.small - a.small).slice(0, 20)); + map.set('little', source.sort((a, b) => b.little - a.little).slice(0, 20)); // @ts-ignore return map; } @@ -1115,13 +1115,13 @@ export class ThreadCpuUsage { total: number = 0; big: number = 0; mid: number = 0; - small: number = 0; + little: number = 0; bigPercent: string = ''; bigTimeStr: string = ''; midPercent: string = ''; midTimeStr: string = ''; - smallPercent: string = ''; - smallTimeStr: string = ''; + littlePercent: string = ''; + littleTimeStr: string = ''; } export class FreqThread { -- Gitee From 30bd3b4424df4b4fb7482228f38db546de8de490 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Fri, 4 Jul 2025 14:05:40 +0800 Subject: [PATCH 57/66] =?UTF-8?q?fix:=E5=88=87=E6=8D=A2dify=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- .../statistics/util/SpStatisticsHttpUtil.ts | 5 ++- ide/src/trace/component/SpAiAnalysisPage.ts | 41 ++++++++----------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/ide/src/statistics/util/SpStatisticsHttpUtil.ts b/ide/src/statistics/util/SpStatisticsHttpUtil.ts index 36e96531..d00889ca 100644 --- a/ide/src/statistics/util/SpStatisticsHttpUtil.ts +++ b/ide/src/statistics/util/SpStatisticsHttpUtil.ts @@ -216,7 +216,8 @@ export class SpStatisticsHttpUtil { method: 'post', signal: controller.signal, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Authorization': 'Bearer app-KzQMtbV8efNFh3C33kCbte27' }, body: JSON.stringify(requestBody) }).then(async res => { @@ -224,7 +225,7 @@ export class SpStatisticsHttpUtil { if (res.status === 200) { let resp = await res.text(); let resj = await JSON.parse(resp); - response.data = resj.reason && resj.reason === 'ok' ? resj.chatbot_reply : '服务器异常,请稍后再试'; + response.data = resj.event && resj.event === 'message' ? resj.answer : '服务器异常,请稍后再试'; } else { response.data = '服务器请求失败'; diff --git a/ide/src/trace/component/SpAiAnalysisPage.ts b/ide/src/trace/component/SpAiAnalysisPage.ts index 3742c462..ab6c5407 100644 --- a/ide/src/trace/component/SpAiAnalysisPage.ts +++ b/ide/src/trace/component/SpAiAnalysisPage.ts @@ -169,9 +169,9 @@ export class SpAiAnalysisPage extends BaseElement { // 给右边栏添加点击事件 // @ts-ignore - rightBarGroup.forEach((barItem: unknown, index: number) => { + rightBarGroup.forEach((barItem: unknown, index: number) => { // @ts-ignore - barItem.barEl.addEventListener('click', (ev: Event) => { + barItem.barEl.addEventListener('click', (ev: Event) => { // @ts-ignore if (barItem.isMustLoadedTrace && !SpApplication.isTraceLoaded) { let importTraceTips = '请先导入trace,再使用诊断功能'; @@ -180,26 +180,26 @@ export class SpAiAnalysisPage extends BaseElement { return; } // this.tipsContent!.style.display = this.isNodata && barItem.barFlag === 'detect' ? 'flex' : 'none'; - this.tipsContainer!.style.display = 'none'; + this.tipsContainer!.style.display = 'none'; // @ts-ignore - this.showPageFlag = barItem.barFlag; + this.showPageFlag = barItem.barFlag; // @ts-ignore - barItem.imgEl.src = barItem.activeImg; + barItem.imgEl.src = barItem.activeImg; // @ts-ignore - barItem.barEl.classList.add('active'); + barItem.barEl.classList.add('active'); // @ts-ignore - barItem.showPage.style.display = 'block'; + barItem.showPage.style.display = 'block'; // @ts-ignore if (this.tipContentArr.indexOf(barItem.barFlag) > -1) { this.tipsContainer!.style.display = 'flex'; - } + } // @ts-ignore for (let i = 0; i < rightBarGroup.length; i++) { - if (i !== index) { + if (i !== index) { // @ts-ignore - rightBarGroup[i].barEl.classList.remove('active'); + rightBarGroup[i].barEl.classList.remove('active'); // @ts-ignore - rightBarGroup[i].imgEl.src = rightBarGroup[i].img; + rightBarGroup[i].imgEl.src = rightBarGroup[i].img; // @ts-ignore rightBarGroup[i].showPage.style.display = 'none'; } @@ -396,26 +396,21 @@ export class SpAiAnalysisPage extends BaseElement { this.createChatBox(); this.createAiChatBox('AI智能分析中...'); this.q_a_window!.scrollTop = this.q_a_window!.scrollHeight; - // 没有token - if (this.chatToken === '') { - await this.getToken90Min('aiTakeToken', true); - } - if (this.chatToken !== '') { - this.answer(); - } + this.answer(); } } // ai对话 async answer(): Promise { let requestBody = { - token: this.chatToken, - question: this.question, - collection: 'smart_perf_test', - scope: 'smartperf' + 'inputs': {}, + 'query': this.question, + 'response_mode': 'blocking', + 'conversation_id': '', + 'user': 'abc-123' }; - await SpStatisticsHttpUtil.askAi(requestBody, 'aiAsk').then(res => { + await SpStatisticsHttpUtil.askAi(requestBody, 'difyAsk').then(res => { if (res.status === 200) { SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_q&a', []); } -- Gitee From 3eda7aee390b0563cc4fb5917e55f220203bd190 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 7 Jul 2025 15:19:53 +0800 Subject: [PATCH 58/66] fix:update Dify Signed-off-by: danghongquan --- ide/src/statistics/util/SpStatisticsHttpUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/statistics/util/SpStatisticsHttpUtil.ts b/ide/src/statistics/util/SpStatisticsHttpUtil.ts index d00889ca..a065c16f 100644 --- a/ide/src/statistics/util/SpStatisticsHttpUtil.ts +++ b/ide/src/statistics/util/SpStatisticsHttpUtil.ts @@ -217,7 +217,7 @@ export class SpStatisticsHttpUtil { signal: controller.signal, headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer app-KzQMtbV8efNFh3C33kCbte27' + 'Authorization': 'Bearer app-6mUvoj5WO5hRaMVLBzV0oCVI' }, body: JSON.stringify(requestBody) }).then(async res => { -- Gitee From e4b1f12ec5fd25c7b29898a348b0013369ab8f4f Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 14 Jul 2025 08:28:11 +0800 Subject: [PATCH 59/66] =?UTF-8?q?fix:AI=E9=97=AE=E7=AD=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- ide/src/statistics/util/SpStatisticsHttpUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/statistics/util/SpStatisticsHttpUtil.ts b/ide/src/statistics/util/SpStatisticsHttpUtil.ts index a065c16f..74d00971 100644 --- a/ide/src/statistics/util/SpStatisticsHttpUtil.ts +++ b/ide/src/statistics/util/SpStatisticsHttpUtil.ts @@ -217,7 +217,7 @@ export class SpStatisticsHttpUtil { signal: controller.signal, headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer app-6mUvoj5WO5hRaMVLBzV0oCVI' + 'Authorization': 'Bearer app-6mUvoj5WO5hRaMVLBzV0oCV1' }, body: JSON.stringify(requestBody) }).then(async res => { -- Gitee From 11506a8665e1e7e21028dcbb287557da87ae7b64 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 14 Jul 2025 08:45:36 +0800 Subject: [PATCH 60/66] fix:Ai Signed-off-by: danghongquan --- ide/src/statistics/util/SpStatisticsHttpUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/statistics/util/SpStatisticsHttpUtil.ts b/ide/src/statistics/util/SpStatisticsHttpUtil.ts index 74d00971..1f7761cc 100644 --- a/ide/src/statistics/util/SpStatisticsHttpUtil.ts +++ b/ide/src/statistics/util/SpStatisticsHttpUtil.ts @@ -217,7 +217,7 @@ export class SpStatisticsHttpUtil { signal: controller.signal, headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer app-6mUvoj5WO5hRaMVLBzV0oCV1' + 'Authorization': 'Bearer app-6mUvoj5WO5hRaMVLBzV0oCVl' }, body: JSON.stringify(requestBody) }).then(async res => { -- Gitee From 2c3aada59da9c7e4c9bc755a0312cfb2a65adf10 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Mon, 14 Jul 2025 17:26:24 +0800 Subject: [PATCH 61/66] fix:heca_freq Signed-off-by: danghongquan --- .../trace/database/data-trafic/ClockDataReceiver.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ide/src/trace/database/data-trafic/ClockDataReceiver.ts b/ide/src/trace/database/data-trafic/ClockDataReceiver.ts index 003775fb..30d84c99 100644 --- a/ide/src/trace/database/data-trafic/ClockDataReceiver.ts +++ b/ide/src/trace/database/data-trafic/ClockDataReceiver.ts @@ -63,10 +63,15 @@ export const chartClockDataSql = (args: Args): string => { export const chartClockDataSqlMem = (args: Args): string => { if (args.sqlType === 'clockFrequency') { return ` - with freq as ( select measure.filter_id, measure.ts, measure.type, measure.value from clock_event_filter - left join measure - where clock_event_filter.name = '${args.clockName}' and clock_event_filter.type = 'clock_set_rate' and clock_event_filter.id = measure.filter_id - order by measure.ts) + with freq as ( select + m.filter_id, + m.ts, + m.type, + m.value from clock_event_filter as c + left join measure as m on c.id = m.filter_id + where c.name = '${args.clockName}' + and c.type = 'clock_set_rate' + order by m.ts) select freq.filter_id as filterId,freq.ts - r.start_ts as startNs,freq.type,freq.value from freq,trace_range r order by startNs; `; } else if (args.sqlType === 'clockState') { -- Gitee From 8b49665809c55fcd94d09341b9f0e22925e62f09 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Wed, 2 Jul 2025 10:00:07 +0800 Subject: [PATCH 62/66] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4clocks=E4=B8=8Bheca?= =?UTF-8?q?=5Ffreq=E6=B3=B3=E9=81=93=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/chart/SpClockChart.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ide/src/trace/component/chart/SpClockChart.ts b/ide/src/trace/component/chart/SpClockChart.ts index a521cb0a..9fae8194 100644 --- a/ide/src/trace/component/chart/SpClockChart.ts +++ b/ide/src/trace/component/chart/SpClockChart.ts @@ -47,8 +47,14 @@ export class SpClockChart { } else { this.trace.rowsEL?.appendChild(folder); } - await this.initData(folder, clockList, traceId); + // heca_freq Frequency移动到数组末尾 + const index = clockList.findIndex(item => item.name === 'heca_freq Frequency'); + if (index !== -1) { + const [item] = clockList.splice(index, 1); + clockList.push(item); + } await this.initDmaFence(folder); + await this.initData(folder, clockList, traceId); } private clockSupplierFrame( -- Gitee From 4d4417ddb3c1383437304ffd49ef1caf77fc9542 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Wed, 2 Jul 2025 10:45:21 +0800 Subject: [PATCH 63/66] =?UTF-8?q?=E8=B0=83=E6=95=B4clocks=E6=8E=92?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/chart/SpClockChart.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/chart/SpClockChart.ts b/ide/src/trace/component/chart/SpClockChart.ts index 9fae8194..707ac3b0 100644 --- a/ide/src/trace/component/chart/SpClockChart.ts +++ b/ide/src/trace/component/chart/SpClockChart.ts @@ -50,8 +50,10 @@ export class SpClockChart { // heca_freq Frequency移动到数组末尾 const index = clockList.findIndex(item => item.name === 'heca_freq Frequency'); if (index !== -1) { - const [item] = clockList.splice(index, 1); - clockList.push(item); + const item = clockList.splice(index, 1); + if (item && item.length) { + clockList.push(item[0]); + } } await this.initDmaFence(folder); await this.initData(folder, clockList, traceId); -- Gitee From cfe20ce7e8499db15873dc44331f2064b2b7278a Mon Sep 17 00:00:00 2001 From: JustinYT Date: Fri, 11 Jul 2025 17:13:19 +0800 Subject: [PATCH 64/66] =?UTF-8?q?=E9=80=82=E9=85=8DT-power=E9=9C=80?= =?UTF-8?q?=E6=B1=82=EF=BC=8C=E6=94=AF=E6=8C=81iframe=E5=B5=8C=E5=85=A5hos?= =?UTF-8?q?t,db=E5=AF=BC=E5=87=BA=E5=88=B0t-power=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- ide/src/trace/SpApplication.ts | 9 +++++-- ide/src/trace/SpApplicationPublicFunc.ts | 26 ++++++++++++++++++++- ide/src/trace/component/trace/base/Utils.ts | 1 + 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index e5d82322..30f3ec44 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -67,6 +67,7 @@ import './component/SpKeyboard'; import { parseKeyPathJson } from './component/Utils'; import { Utils } from './component/trace/base/Utils'; import { + addExportDBToParentEvent, applicationHtml, clearTraceFileCache, findFreeSizeAlgorithm, @@ -76,7 +77,7 @@ import { isZlibFile, postLog, readTraceFileBuffer, - TraceMode, + TraceMode } from './SpApplicationPublicFunc'; import { queryExistFtrace } from './database/sql/SqlLite.sql'; import '../base-ui/chart/scatter/LitChartScatter'; @@ -1351,16 +1352,19 @@ export class SpApplication extends BaseElement { SpApplication.isTraceLoaded = true; if (!isDistributed) { this.importConfigDiv!.style.display = Utils.getInstance().getSchedSliceMap().size > 0 ? 'block' : 'none'; - } + } this.showContent(this.spSystemTrace!); this.litSearch!.setPercent('', 101); this.chartFilter!.setAttribute('mode', ''); this.freshMenuDisable(false); + Utils.currentTraceName = fileName; } else { + info('loadDatabaseArrayBuffer failed'); //@ts-ignore this.litSearch!.setPercent(res.msg || 'This File is not supported!', -1); this.resetMenus(); + Utils.currentTraceName = ''; this.freshMenuDisable(false); } this.progressEL!.loading = false; @@ -2116,6 +2120,7 @@ export class SpApplication extends BaseElement { // 鼠标拖动改变大小 this.aiPageResize(); + addExportDBToParentEvent(); } private aiPageResize(): void { diff --git a/ide/src/trace/SpApplicationPublicFunc.ts b/ide/src/trace/SpApplicationPublicFunc.ts index 73c915d5..03badc8f 100644 --- a/ide/src/trace/SpApplicationPublicFunc.ts +++ b/ide/src/trace/SpApplicationPublicFunc.ts @@ -13,7 +13,8 @@ * limitations under the License. */ -import { getThreadPoolTraceBufferCacheKey } from './database/SqlLite'; +import { Utils as TraceUtil } from './component/trace/base/Utils'; +import { getThreadPoolTraceBufferCacheKey, threadPool } from './database/SqlLite'; export enum TraceMode { NORMAL, @@ -590,4 +591,27 @@ export function isZlibFile(uint8Array: Uint8Array): boolean { } } return true; +} + +/** + * 外部交互,向外传输db文件 + */ +export function addExportDBToParentEvent() { + window.addEventListener('message', e => { + if (e.data.name == 'exportDbToParent') { + threadPool.submit('download-db', '', {}, async (reqBufferDB: ArrayBuffer) => { + if (reqBufferDB && reqBufferDB.byteLength > 0) { + window.parent.postMessage({ + name: `${(TraceUtil.currentTraceName || 'trace').split('.')[0]}.db`, + data: reqBufferDB + }, '*'); + } else { + window.parent.postMessage({ + name: `暂无数据`, + data: null + }, '*'); + } + }, 'download-db'); + } + }); } \ No newline at end of file diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index e47afafd..f8ebc2fb 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -24,6 +24,7 @@ export class Utils { static currentTraceMode: TraceMode = TraceMode.NORMAL; static distributedTrace: string[] = []; static isRangeSelectRefresh: boolean = false; + static currentTraceName: string = ''; static DMAFENCECAT_MAP: Map< number, { -- Gitee From c73a717912e6bca98a658c0d8c71e5f2bb2cb75a Mon Sep 17 00:00:00 2001 From: xutao Date: Mon, 28 Jul 2025 01:50:29 +0000 Subject: [PATCH 65/66] queue task fix Signed-off-by: xutao --- trace_streamer/src/rpc/ffrt_converter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trace_streamer/src/rpc/ffrt_converter.cpp b/trace_streamer/src/rpc/ffrt_converter.cpp index 9065504b..2c0b328b 100644 --- a/trace_streamer/src/rpc/ffrt_converter.cpp +++ b/trace_streamer/src/rpc/ffrt_converter.cpp @@ -879,7 +879,7 @@ static void HandleQueTaskInfoOut(ConStr &log, int lineno, int pid, QueueTaskInfo if (fPos == std::string::npos) { return; } - size_t hPos = log.find("|H:F ", fPos); + size_t hPos = log.find("|H:F", fPos); if (hPos == std::string::npos) { return; } -- Gitee From 13dfc6d0a9ec57c6d82406717148bd3830a87643 Mon Sep 17 00:00:00 2001 From: JustinYT Date: Mon, 28 Jul 2025 13:50:19 +0800 Subject: [PATCH 66/66] =?UTF-8?q?T-power=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JustinYT --- ide/src/trace/SpApplication.ts | 2 ++ ide/src/trace/SpApplicationPublicFunc.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index 30f3ec44..062ea60d 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -75,6 +75,7 @@ import { indexedDataToBufferData, isZipFile, isZlibFile, + loadTraceCompleteEvent, postLog, readTraceFileBuffer, TraceMode @@ -1367,6 +1368,7 @@ export class SpApplication extends BaseElement { Utils.currentTraceName = ''; this.freshMenuDisable(false); } + loadTraceCompleteEvent(); this.progressEL!.loading = false; this.headerDiv!.style.pointerEvents = 'auto'; this.spInfoAndStats!.initInfoAndStatsData(); diff --git a/ide/src/trace/SpApplicationPublicFunc.ts b/ide/src/trace/SpApplicationPublicFunc.ts index 03badc8f..e50657e7 100644 --- a/ide/src/trace/SpApplicationPublicFunc.ts +++ b/ide/src/trace/SpApplicationPublicFunc.ts @@ -614,4 +614,13 @@ export function addExportDBToParentEvent() { }, 'download-db'); } }); +} + +export function loadTraceCompleteEvent() { + if (window?.parent) { + window.parent.postMessage({ + name:'trace_load_complete', + data: null + }, '*'); + } } \ No newline at end of file -- Gitee