From 1b2e04255c2ea6642f5d246102a4efdfdcee837a Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 1 Aug 2024 17:04:06 +0800 Subject: [PATCH 01/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dsf=E5=92=8Cgh?= =?UTF-8?q?=E5=BC=82=E6=AD=A5catname=E7=9B=B8=E5=90=8C=E6=88=96funcname?= =?UTF-8?q?=E7=9B=B8=E5=90=8C=E4=B8=94=E5=9C=A8=E5=90=8C=E4=B8=80=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E6=88=96=E4=B8=8D=E5=90=8C=E8=BF=9B=E7=A8=8B=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=AC=BE=E9=80=89=E4=BA=A7=E7=94=9F=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/bean/BoxSelection.ts | 9 +- .../trace/component/chart/SpProcessChart.ts | 29 ++- .../trace/component/trace/base/TraceRow.ts | 1 + .../trace/component/trace/base/TraceSheet.ts | 7 +- .../trace/sheet/process/TabPaneSliceChild.ts | 139 ++++++++---- .../trace/sheet/process/TabPaneSlices.ts | 183 +++++++++------ ide/src/trace/database/sql/Func.sql.ts | 208 ++++++++++-------- .../trace/database/sql/ProcessThread.sql.ts | 4 +- 8 files changed, 353 insertions(+), 227 deletions(-) diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index d8b22b6d..4e98c120 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -72,7 +72,7 @@ export class SelectionParam { irqCallIds: Array = []; softIrqCallIds: Array = []; funTids: Array = []; - funAsync: Array<{ name: string; pid: number }> = []; + funAsync: Array<{ name: string; pid: number, tid: number | undefined }> = []; funCatAsync: Array<{ pid: number; threadName: string }> = []; nativeMemory: Array = []; nativeMemoryStatistic: Array = []; @@ -251,6 +251,7 @@ export class SelectionParam { this.funAsync.push({ name: th.asyncFuncName, pid: th.asyncFuncNamePID || 0, + tid: th.asyncFuncStartTID }); } else { for (let i = 0; i < th.asyncFuncName.length; i++) { @@ -258,6 +259,7 @@ export class SelectionParam { this.funAsync.push({ name: el, pid: th.asyncFuncNamePID || 0, + tid: th.asyncFuncStartTID }); } } @@ -324,6 +326,7 @@ export class SelectionParam { this.funAsync.push({ name: it.asyncFuncName, pid: it.asyncFuncNamePID || 0, + tid: it.asyncFuncStartTID }); } else { //@ts-ignore @@ -332,6 +335,7 @@ export class SelectionParam { this.funAsync.push({ name: el, pid: it.asyncFuncNamePID || 0, + tid: it.asyncFuncStartTID }); } } @@ -1275,8 +1279,7 @@ export class SliceBoxJumpParam { threadId: Array = []; name: string[] | undefined | null; isJumpPage: boolean | undefined; - asyncNames: Array = []; - asyncCatNames: Array = []; + isSummary: boolean | undefined; } export class SelectionData { diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index e558a624..54e3f77f 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -1317,13 +1317,21 @@ export class SpProcessChart { ({ asyncRemoveCatArr, asyncCat } = this.hanldCatFunc(asyncFuncList, flag));//处理是否cat ({ setArrayLenThanOne, setArrayLenOnlyOne } = this.hanldAsyncFunc(it, asyncRemoveCatArr));//len等于0和大于0的分类 //@ts-ignore - let aggregateData = { ...asyncCat, ...setArrayLenThanOne, ...setArrayLenOnlyOne }; - Reflect.ownKeys(aggregateData).map((key: unknown) => {//处理business first和length大于1的数据 - //@ts-ignore - let param: Array = aggregateData[key]; - //@ts-ignore + let aggregateData = {...setArrayLenThanOne, ...setArrayLenOnlyOne }; + Reflect.ownKeys(aggregateData).map((key: any) => { + let param: Array = aggregateData[key]; this.makeAddAsyncFunction(param, it, processRow, key); - }); + }) + //@ts-ignore + Reflect.ownKeys(asyncCat).map((key: any) => { + //@ts-ignore + let param: Array = asyncCat[key]; + if (flag) {//处理business + this.makeAddAsyncFunction(param, it, processRow, key); + } else {//处理thread + this.makeAddAsyncFunction(param, it, processRow, key, param[0].tid); + } + }) } else { //不聚合异步trace let asyncFuncGroup = Utils.groupBy(asyncFuncList, 'funName'); @@ -1438,7 +1446,8 @@ export class SpProcessChart { asyncFunctions: unknown[], it: { pid: number; processName: string | null }, processRow: TraceRow, - key: string + key: string, + rowSingleTid?: number ): void { let maxDepth: number = -1; let i = 0; @@ -1497,7 +1506,7 @@ export class SpProcessChart { this.toAsyncFuncCache(noEndData[index], `${key}-${it.pid}`); }); } - this.lanesConfig([...normalData, ...noEndData], it, processRow, key); + this.lanesConfig([...normalData, ...noEndData], it, processRow, key, rowSingleTid); } } //初始化异步泳道信息 @@ -1505,7 +1514,8 @@ export class SpProcessChart { asyncFunctions: unknown[], it: { pid: number; processName: string | null }, processRow: TraceRow, - key: string + key: string, + rowSingleTid?: number ): void { const maxHeight = this.calMaxHeight(asyncFunctions); // @ts-ignore @@ -1515,6 +1525,7 @@ export class SpProcessChart { funcRow.rowId = `${key}-${it.pid}`; funcRow.asyncFuncName = asyncFuncName; funcRow.asyncFuncNamePID = it.pid; + funcRow.asyncFuncStartTID = rowSingleTid ? rowSingleTid : undefined; funcRow.rowType = TraceRow.ROW_TYPE_FUNC; funcRow.enableCollapseChart(FOLD_HEIGHT, this.trace); //允许折叠泳道图 funcRow.rowParentId = `${it.pid}`; diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index db2c3a27..81d78d73 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -212,6 +212,7 @@ export class TraceRow extends HTMLElement { currentContext: CanvasRenderingContext2D | undefined | null; static ROW_TYPE_LTPO: string | null | undefined; static ROW_TYPE_HITCH_TIME: string | null | undefined; + asyncFuncStartTID!: number | undefined; constructor( args: { diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index fd44ff5c..ce1ba874 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -1165,10 +1165,9 @@ export class TraceSheet extends BaseElement { param.processId = this.selection!.processIds; param.threadId = this.selection!.funTids;//@ts-ignore2 param.name = e.detail.allName ? e.detail.allName : [e.detail.name];//@ts-ignore2 - param.asyncNames = e.detail.asyncNames;//@ts-ignore2 - param.asyncCatNames = e.detail.asyncCatNames; - param.isJumpPage = true; - (pane.children.item(0) as TabPaneSliceChild).data = param; + param.isJumpPage = true;// @ts-ignore + param.isSummary = e.detail.allName ? true : false; + (pane.children.item(0) as TabPaneSliceChild).data = {param: param, selection: this.selection}; } clearMemory(): void { diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts index 7daca5c5..2116a287 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts @@ -15,30 +15,49 @@ import { BaseElement, element } from '../../../../../base-ui/BaseElement'; import { LitTable } from '../../../../../base-ui/table/lit-table'; -import { SelectionData, SliceBoxJumpParam } from '../../../../bean/BoxSelection'; +import { SelectionData, SelectionParam, SliceBoxJumpParam } from '../../../../bean/BoxSelection'; import { Utils } from '../../base/Utils'; import { resizeObserver } from '../SheetUtils'; -import { getTabDetails, getCatDetails } from '../../../../database/sql/Func.sql'; +import { getTabDetails, getGhDetails, getSfDetails } from '../../../../database/sql/Func.sql'; @element('box-slice-child') export class TabPaneSliceChild extends BaseElement { private sliceChildTbl: LitTable | null | undefined; - private boxChildSource: Array = []; - private sliceChildParam: SliceBoxJumpParam | null | undefined; + private boxChildSource: Array = []; + private sliceChildParam: {param: SliceBoxJumpParam, selection: SelectionParam} | null | undefined; - set data(boxChildValue: SliceBoxJumpParam) { + set data(boxChildValue: {param: SliceBoxJumpParam, selection: SelectionParam | null | undefined}) { //切换Tab页 保持childTab数据不变 除非重新点击跳转 - if (boxChildValue === this.sliceChildParam || !boxChildValue.isJumpPage) { + if (boxChildValue === this.sliceChildParam || !boxChildValue.param.isJumpPage) { return; } // @ts-ignore this.sliceChildParam = boxChildValue; this.sliceChildTbl!.recycleDataSource = []; - this.getDataByDB(boxChildValue); + //合并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]) + } + filterSfAsyncFuncName.forEach((it: { name: string; pid: number, tid: number | undefined }) => { + if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) { + let item = sfAsyncFuncMap.get(`${it.pid}-${it.tid}`); + item?.name.push(it.name); + } else { + sfAsyncFuncMap.set(`${it.pid}-${it.tid}`, { + name: [it.name], + pid: it.pid, + tid: it.tid + }) + } + }) + //@ts-ignore + this.getDataByDB(boxChildValue, sfAsyncFuncMap, boxChildValue.selection!.funCatAsync); } initElements(): void { - this.sliceChildTbl = this.shadowRoot?.querySelector('#tb-slice-child'); + this.sliceChildTbl = this.shadowRoot?.querySelector('#tb-slice-child'); this.sliceChildTbl!.addEventListener('column-click', (evt): void => { // @ts-ignore this.sortByColumn(evt.detail); @@ -63,43 +82,71 @@ export class TabPaneSliceChild extends BaseElement { resizeObserver(this.parentElement!, this.sliceChildTbl!, 25); } - getDataByDB(val: SliceBoxJumpParam): void { - this.sliceChildTbl!.loading = true; - //处理异步方法 - getTabDetails(val.name!, val.processId, val.leftNs, val.rightNs, 'async').then((res1: unknown) => {//@ts-ignore - //处理cat方法 - getCatDetails(val.name!, val.asyncCatNames!, val.processId, val.leftNs, val.rightNs).then((res2) => {//@ts-ignore - //处理同步方法 - getTabDetails(val.name!, val.processId, val.leftNs, val.rightNs, 'sync', val.threadId).then( - (res3: unknown) => {//@ts-ignore - let result: unknown = (res1 || []).concat(res2 || []).concat(res3 || []); - this.sliceChildTbl!.loading = false;//@ts-ignore - if (result.length !== null && result.length > 0) {//@ts-ignore - result.map((e: unknown) => {//@ts-ignore - e.startTime = Utils.getTimeString(e.startNs); - // @ts-ignore - e.absoluteTime = ((window as unknown).recordStartNS + e.startNs) / 1000000000;//@ts-ignore - e.duration = e.duration / 1000000;//@ts-ignore - e.state = Utils.getEndState(e.state)!;//@ts-ignore - e.processName = `${e.process === undefined || e.process === null ? 'process' : e.process}(${e.processId})`;//@ts-ignore - e.threadName = `${e.thread === undefined || e.thread === null ? 'thread' : e.thread}(${e.threadId})`; - });//@ts-ignore - this.boxChildSource = result; - if (this.sliceChildTbl) { - // @ts-ignore - this.sliceChildTbl.recycleDataSource = result; - } - } else { - this.boxChildSource = []; - if (this.sliceChildTbl) { - // @ts-ignore - this.sliceChildTbl.recycleDataSource = []; - } - } - } - ); + getDataByDB( + val: {param: SliceBoxJumpParam, selection: SelectionParam}, + sfAsyncFuncMap: Map, + ghAsyncFunc:{ threadName: string; pid: number}[]): void { + //获取点击跳转,SF异步Func数据 + let result1 = () => { + let promises: unknown[] = []; + sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => { + let res = await getSfDetails(item.name, item.pid, item.tid, val.param.leftNs, val.param.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } + }) + return promises + } + + //获取点击跳转,GH异步Func数据 + let result2 = () => { + let promises: unknown[] = []; + ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => { + let res = await getGhDetails(val.param.name!, item.threadName, item.pid, val.param.leftNs, val.param.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } }); - }); + return promises + } + + //获取同步Func数据,同步Func数据 + let result3 = async () => { + let promises: unknown[] = []; + let res = await getTabDetails(val.param.name!, val.param.processId, val.param.threadId, val.param.leftNs, val.param.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } + return promises + } + this.sliceChildTbl!.loading = true; + Promise.all([result1(), result2(), result3()]).then(res => { + this.sliceChildTbl!.loading = false; + let result: any = (res[0] || []).concat(res[1] || []).concat(res[2] || []); + this.sliceChildTbl!.loading = false; + if (result.length !== null && result.length > 0) { + result.map((e: any) => { + e.startTime = Utils.getTimeString(e.startNs); + // @ts-ignore + e.absoluteTime = ((window as unknown).recordStartNS + e.startNs) / 1000000000; + e.duration = e.duration / 1000000; + e.state = Utils.getEndState(e.state)!; + e.processName = `${e.process === undefined || e.process === null ? 'process' : e.process}[${e.processId}]`; + e.threadName = `${e.thread === undefined || e.thread === null ? 'thread' : e.thread}[${e.threadId}]`; + }); + this.boxChildSource = result; + if (this.sliceChildTbl) { + // @ts-ignore + this.sliceChildTbl.recycleDataSource = result; + } + } else { + this.boxChildSource = []; + if (this.sliceChildTbl) { + // @ts-ignore + this.sliceChildTbl.recycleDataSource = []; + } + } + }) } initHtml(): string { @@ -152,8 +199,8 @@ export class TabPaneSliceChild extends BaseElement { }; } //@ts-ignore - if (detail.key === 'startTime' || detail.key === 'processName' || detail.key === 'threadName' || //@ts-ignore - detail.key === 'name') { + if (detail.key === 'startTime' || detail.key === 'processName'|| detail.key === 'threadName' ||//@ts-ignore + detail.key === 'name') { // @ts-ignore this.boxChildSource.sort(compare(detail.key, detail.sort, 'string'));// @ts-ignore } else if (detail.key === 'absoluteTime' || detail.key === 'duration') {// @ts-ignore diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts index 11ac0d06..2790e37f 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts @@ -33,7 +33,7 @@ export class TabPaneSlices extends BaseElement { private currentSelectionParam: SelectionParam | undefined; private sliceSearchCount: Element | undefined | null; - set data(slicesParam: SelectionParam | unknown) { + set data(slicesParam: SelectionParam) { if (this.currentSelectionParam === slicesParam) { return; } //@ts-ignore @@ -42,20 +42,22 @@ export class TabPaneSlices extends BaseElement { //@ts-ignore ((slicesParam.rightNs - slicesParam.leftNs) / 1000000.0).toFixed(5) )} ms`; - let asyncNames: Array = []; - let asyncPid: Array = []; //@ts-ignore - slicesParam.funAsync.forEach((it: unknown) => { - //@ts-ignore - asyncNames.push(it.name); //@ts-ignore - asyncPid.push(it.pid); - }); - let asyncCatNames: Array = []; - let asyncCatPid: Array = [];//@ts-ignore - slicesParam.funCatAsync.forEach((it: unknown) => { //@ts-ignore - asyncCatNames.push(it.threadName);//@ts-ignore - asyncCatPid.push(it.pid); - }); - this.slicesTbl!.loading = true; + + //合并SF异步信息,相同pid和tid的name + let sfAsyncFuncMap: Map = new Map(); + slicesParam.funAsync.forEach((it: { name: string; pid: number, tid: number | undefined }) => { + if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) { + let item = sfAsyncFuncMap.get(`${it.pid}-${it.tid}`); + item?.name.push(it.name); + } else { + sfAsyncFuncMap.set(`${it.pid}-${it.tid}`, { + name: [it.name], + pid: it.pid, + tid: it.tid + }) + } + }) + let filterNameEL: HTMLInputElement | undefined | null = this.shadowRoot?.querySelector('#filterName'); filterNameEL?.addEventListener('keyup', (ev) => { @@ -63,59 +65,8 @@ export class TabPaneSlices extends BaseElement { ev.stopPropagation(); } }); - //@ts-ignore - getTabSlicesAsyncFunc(asyncNames, asyncPid, slicesParam.leftNs, slicesParam.rightNs).then((res) => {//@ts-ignore - getTabSlicesAsyncCatFunc(asyncCatNames, asyncCatPid, slicesParam.leftNs, slicesParam.rightNs).then((res1) => { - //@ts-ignore - getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs).then( - (res2) => { - this.slicesTbl!.loading = false; - let processSlicesResult = (res || []).concat(res1 || []).concat(res2 || []); - if (processSlicesResult !== null && processSlicesResult.length > 0) { - let sumWall = 0.0; - let sumOcc = 0; - for (let processSliceItem of processSlicesResult) { - //@ts-ignore - processSliceItem.name = processSliceItem.name === null ? '' : processSliceItem.name; - //@ts-ignore - processSliceItem.tabTitle = processSliceItem.name; - //@ts-ignore - sumWall += processSliceItem.wallDuration; - //@ts-ignore - sumOcc += processSliceItem.occurrences; - //@ts-ignore - processSliceItem.wallDuration = parseFloat((processSliceItem.wallDuration / 1000000.0).toFixed(5)); - //@ts-ignore - processSliceItem.avgDuration = parseFloat((processSliceItem.avgDuration / 1000000.0).toFixed(5)); - //@ts-ignore - processSliceItem.asyncNames = asyncNames; - //@ts-ignore - processSliceItem.asyncCatNames = asyncCatNames; - } - let count = new SelectionData(); - count.process = ' '; - count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); - count.occurrences = sumOcc; - count.tabTitle = 'Summary';//@ts-ignore - count.allName = processSlicesResult.map((item: unknown) => item.name); - count.asyncNames = asyncNames; - count.asyncCatNames = asyncCatNames; - processSlicesResult.splice(0, 0, count); //@ts-ignore - this.slicesSource = processSlicesResult; - this.slicesTbl!.recycleDataSource = processSlicesResult; - this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; - if (filterNameEL && filterNameEL.value.trim() !== '') { - this.findName(filterNameEL.value); - } - } else { - this.slicesSource = []; - this.slicesTbl!.recycleDataSource = this.slicesSource; - this.sliceSearchCount!.textContent = '0'; - } - } - ); - }); - }); + + this.getSliceDb(slicesParam, filterNameEL, sfAsyncFuncMap, slicesParam.funCatAsync) } initElements(): void { @@ -158,6 +109,102 @@ export class TabPaneSlices extends BaseElement { spSystemTrace.focusTarget = ''; }); } + + getSliceDb( + slicesParam: SelectionParam, + filterNameEL: HTMLInputElement | undefined | null, + sfAsyncFuncMap: Map, + ghAsyncFunc: { threadName: string; pid: number }[]) { + //获取SF异步Func数据 + let result1 = () => { + let promises: unknown[] = []; + sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => { + let res = await getTabSlicesAsyncFunc(item.name, item.pid, item.tid, slicesParam.leftNs, slicesParam.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } + }) + return promises + } + + //获取GH异步Func数据 + let result2 = () => { + let promises: unknown[] = []; + ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => { + let res = await getTabSlicesAsyncCatFunc(item.threadName, item.pid, slicesParam.leftNs, slicesParam.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } + }); + return promises + } + + //获取同步Func数据 + let result3 = async () => { + let promises: unknown[] = []; + let res = await getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs); + if (res !== undefined && res.length > 0) { + promises.push(...res); + } + return promises + } + + this.slicesTbl!.loading = true; + Promise.all([result1(), result2(), result3()]).then(res => { + let processSlicesResult = (res[0] || []).concat(res[1] || []).concat(res[2] || []); + if (processSlicesResult !== null && processSlicesResult.length > 0) { + let sumWall = 0.0; + let sumOcc = 0; + let processSlicesResultMap: Map = new Map(); + for (let processSliceItem of processSlicesResult) { + //@ts-ignore + processSliceItem.name = processSliceItem.name === null ? '' : processSliceItem.name; + //@ts-ignore + sumWall += processSliceItem.wallDuration; + //@ts-ignore + sumOcc += processSliceItem.occurrences; + //@ts-ignore + processSliceItem.wallDuration = parseFloat((processSliceItem.wallDuration / 1000000.0).toFixed(5)); + //@ts-ignore + if (processSlicesResultMap.has(processSliceItem.name)) {//@ts-ignore + let item = processSlicesResultMap.get(processSliceItem.name); + //@ts-ignore + item.occurrences = item.occurrences + processSliceItem.occurrences; + //@ts-ignore + item.wallDuration = parseFloat((item.wallDuration + processSliceItem.wallDuration).toFixed(5)); + } else { + //@ts-ignore + processSlicesResultMap.set(processSliceItem.name, {//@ts-ignore + ...processSliceItem, //@ts-ignore + tabTitle: processSliceItem.name + }) + } + } + let processSlicesResultsValue = [...processSlicesResultMap.values()]; + processSlicesResultsValue.forEach(element => {//@ts-ignore + element.avgDuration = parseFloat((element.wallDuration / element.occurrences).toFixed(5)); + }); + let count = new SelectionData(); + count.process = ' '; + count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); + count.occurrences = sumOcc; + count.tabTitle = 'Summary'; + count.allName = processSlicesResultsValue.map((item: any) => item.name); + processSlicesResultsValue.splice(0, 0, count); //@ts-ignore + this.slicesSource = processSlicesResultsValue; + this.slicesTbl!.recycleDataSource = processSlicesResultsValue; + this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + ''; + if (filterNameEL && filterNameEL.value.trim() !== '') { + this.findName(filterNameEL.value); + } + } else { + this.slicesSource = []; + this.slicesTbl!.recycleDataSource = this.slicesSource; + this.sliceSearchCount!.textContent = '0'; + } + this.slicesTbl!.loading = false; + }); + } async orgnazitionData(data: Object): Promise { let spApplication = document.querySelector('body > sp-application'); let spSystemTrace = spApplication?.shadowRoot?.querySelector( diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index bb2d52bb..44d8f1dd 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -302,74 +302,56 @@ export const fuzzyQueryFuncRowData = (funcName: string, tIds: number): ); export const getTabSlicesAsyncFunc = ( - asyncNames: Array, - asyncPid: Array, + asyncNames: string[], + asyncPid: number, + asyncTid: number | undefined, leftNS: number, rightNS: number ): //@ts-ignore - Promise> => - query( - 'getTabSlicesAsyncFunc', - `SELECT + Promise> => { + let condition = `${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''}`; + let sql = ` + SELECT c.name AS name, sum( c.dur ) AS wallDuration, - avg( c.dur ) AS avgDuration, count( c.name ) AS occurrences - FROM - thread A, + FROM + (SELECT id, ts, parent_id, dur, name from callstack where cookie NOT NULL) C, trace_range D + LEFT JOIN thread A ON A.id = C.parent_id LEFT JOIN process P ON P.id = A.ipid - LEFT JOIN callstack C ON A.id = C.callid where - C.ts > 0 - and - c.dur >= -1 - and - c.cookie not null - and - P.pid in (${asyncPid.join(',')}) - and - c.name in (${asyncNames.map((it) => "'" + it + "'").join(',')}) - and - not ((C.ts - D.start_ts + C.dur < $leftNS) or (C.ts - D.start_ts > $rightNS)) - group by - c.name + C.ts > 0 + and + c.dur >= -1 + and + P.pid = ${asyncPid} + and + c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')}) + and + not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} + group by + c.name order by - wallDuration desc;`, - { $leftNS: leftNS, $rightNS: rightNS } - ); + wallDuration desc;`; + return query('getTabSlicesAsyncFunc', sql, {}); +} export const getTabDetails = ( asyncNames: Array, asyncPid: Array, + funTids: Array, leftNS: number, - rightNS: number, - key: string, - funTids?: Array + rightNS: number ): //@ts-ignore Promise> => { - let asyncCondition = ''; - let catCondition = ''; - let syncCondition = ''; - if (key === 'async') { - asyncCondition = ` - and c.cookie not null - and c.parent_id not null - `; - } else if (key === 'sync') { - syncCondition = ` + let condition = ` and A.tid in (${funTids!.join(',')}) and c.cookie is null - `; - } - let condition = ` - ${asyncCondition} - ${catCondition} - ${syncCondition} ${`and P.pid in (${asyncPid.join(',')})`} - ${`and c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')})`} - `; - let sql = ` + ${`and c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')})`} + ` + let sql = ` SELECT c.name AS name, c.dur AS duration, @@ -388,18 +370,55 @@ export const getTabDetails = ( c.dur >= -1 and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} - `; - return query('getTabDetails', sql, {}); -}; -export const getCatDetails = ( + ` + return query('getTabDetails', sql, {}); + } +export const getSfDetails = ( asyncNames: Array, - catName: Array, - asyncPid: Array, + asyncPid: number, + asyncTid: number | undefined, leftNS: number, rightNS: number ): //@ts-ignore Promise> => { - let sql = ` + let condition = ` + and c.parent_id not null + ${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''} + ${`and P.pid = ${asyncPid}`} + ${`and c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')})`} + ` + let sql = ` + SELECT + c.name AS name, + c.dur AS duration, + P.pid AS processId, + P.name AS process, + A.tid AS threadId, + A.name AS thread, + c.ts - D.start_ts as startNs + FROM + (SELECT id, ts, parent_id, dur, name from callstack where cookie NOT NULL) C, + trace_range D + LEFT JOIN thread A ON A.id = C.parent_id + LEFT JOIN process P ON P.id = A.ipid + where + C.ts > 0 + and + c.dur >= -1 + and + not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} + ` + return query('getSfDetails', sql, {}); + } + export const getGhDetails = ( + asyncNames: Array, + catName: string, + asyncPid: number, + leftNS: number, + rightNS: number + ): //@ts-ignore + Promise> => { + let sql = ` SELECT c.name AS name, c.dur AS duration, @@ -423,53 +442,54 @@ export const getCatDetails = ( and c.parent_id is null and - P.pid in (${asyncPid.join(',')}) + P.pid = ${asyncPid} and - c.cat in (${catName.map((it) => '\"' + it + '\"').join(',')}) + cat = '${catName}' and - c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')}) + c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')}) and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) - `; - return query('getCatDetails', sql, {}); -}; + ` + return query('getGhDetails', sql, {}); + } export const getTabSlicesAsyncCatFunc = ( - asyncCatNames: Array, - asyncCatPid: Array, + asyncCatNames: string, + asyncCatPid: number, leftNS: number, rightNS: number ): Promise> => - query( - 'getTabSlicesAsyncCatFunc', - ` - select - c.name as name, - sum(c.dur) as wallDuration, - avg(c.dur) as avgDuration, - count(c.name) as occurrences - from - thread A, trace_range D - left join process P on P.id = A.ipid - left join callstack C on A.id = C.callid - where - C.ts > 0 - and - c.dur >= -1 - and - c.cookie not null - and - c.cat not null - and - P.pid in (${asyncCatPid.join(',')}) - and - c.cat in (${asyncCatNames.map((it) => "'" + it + "'").join(',')}) - and - not ((C.ts - D.start_ts + C.dur < $leftNS) or (C.ts - D.start_ts > $rightNS)) - group by - c.name - order by - wallDuration desc;`, - { $leftNS: leftNS, $rightNS: rightNS } +query( + 'getTabSlicesAsyncCatFunc', + ` + select + c.name as name, + sum(c.dur) as wallDuration, + count(c.name) as occurrences + from + thread A, trace_range D + left join process P on P.id = A.ipid + left join callstack C on A.id = C.callid + where + C.ts > 0 + and + c.dur >= -1 + and + c.cookie not null + and + c.cat not null + and + c.parent_id is null + and + P.pid = ${asyncCatPid} + and + c.cat = '${asyncCatNames}' + and + not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) + group by + c.name + order by + wallDuration desc;`, + { $leftNS: leftNS, $rightNS: rightNS } ); export const querySearchFunc = (search: string): Promise> => diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index e02ccd36..5bd29342 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -1146,15 +1146,13 @@ export const getTabSlices = ( pids: Array, leftNS: number, rightNS: number -): //@ts-ignore - Promise> => +): Promise> => query( 'getTabSlices', ` select c.name as name, sum(c.dur) as wallDuration, - avg(c.dur) as avgDuration, count(c.name) as occurrences from thread T, trace_range TR -- Gitee From c02de9779255e6fd03195d96a63f1293fe896898 Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Fri, 2 Aug 2024 15:46:00 +0800 Subject: [PATCH 02/85] search issue fixed Signed-off-by: wupoli3@huawei.com --- ide/src/trace/SpApplication.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index e37aac6a..b76f0df6 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -1937,7 +1937,7 @@ export class SpApplication extends BaseElement { } else { this.progressEL!.loading = false; } - if (this.litSearch!.index > 0) { + if (this.litSearch!.list.length > 0) { let currentEntry = this.litSearch!.list[this.litSearch!.index]; cancelCurrentTraceRowHighlight(this.spSystemTrace!, currentEntry); } -- Gitee From 48cb16192ffd998895c64b1f8a4efb09621e1c51 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Fri, 2 Aug 2024 17:02:34 +0800 Subject: [PATCH 03/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=90=8E=E5=94=A4=E9=86=92=E5=85=B3=E7=B3=BB=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=8D=E5=85=A8=E3=80=81=E5=88=86=E5=B8=83=E5=BC=8F=E4=B8=8D?= =?UTF-8?q?=E6=98=BE=E7=A4=BAcpu=E6=B3=B3=E9=81=93=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E5=B1=95=E5=BC=80=E8=B4=9F=E8=BD=BD=E5=8C=BA=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E5=A4=84=E4=B8=8B=E7=A7=BB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/chart/SpChartManager.ts | 2 +- ide/src/trace/component/trace/base/TraceRow.ts | 6 +++++- ide/src/trace/database/ui-worker/cpu/ProcedureWorkerCPU.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index b6d0b223..be2ac7e0 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -261,7 +261,7 @@ export class SpChartManager { progress(`trace ${traceId} cpu`, 70); let count = await sliceSender(traceId); // @ts-ignore - await this.cpu.init(count.cpu, traceFolder, traceId); + await this.cpu.init(count.count.cpu, traceFolder, traceId); info(`initData trace ${traceId} cpu Data initialized`); progress(`trace ${traceId} cpu freq`, 75); // @ts-ignore diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index db2c3a27..dea81fdb 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -1536,7 +1536,11 @@ export class TraceRow extends HTMLElement { let rectY = myRect.y; let rectHeight = myRect.height; if (!inFavoriteArea && favoriteHeight !== undefined) { - y = e.offsetY + prevScrollY - 90 - favoriteHeight!; + let expand = sessionStorage.getItem('expand'); + let foldHeight = Number(sessionStorage.getItem('foldHeight')); + y = expand === 'true' ? + (e.offsetY + prevScrollY - 148 - favoriteHeight!) : + (e.offsetY + prevScrollY - (148 - foldHeight) - favoriteHeight!); rectY = this.offsetTop; rectHeight = this.clientHeight; } diff --git a/ide/src/trace/database/ui-worker/cpu/ProcedureWorkerCPU.ts b/ide/src/trace/database/ui-worker/cpu/ProcedureWorkerCPU.ts index a883d44e..9419ba8e 100644 --- a/ide/src/trace/database/ui-worker/cpu/ProcedureWorkerCPU.ts +++ b/ide/src/trace/database/ui-worker/cpu/ProcedureWorkerCPU.ts @@ -98,7 +98,7 @@ export class CpuRender { CpuStruct.draw(req.ctx, re, req.translateY); }); req.ctx.closePath(); - if (row.traceId === Utils.currentSelectTrace) { + if ((row.traceId === Utils.currentSelectTrace) || (row.traceId === null && Utils.currentSelectTrace === undefined)) { let currentCpu = parseInt(req.type!.replace('cpu-data-', '')); let wakeup = req.type === `cpu-data-${CpuStruct.selectCpuStruct?.cpu || 0}` ? CpuStruct.selectCpuStruct : undefined; -- Gitee From f90add0792e6253a163a72a3f6f40ff2d7f1e07e Mon Sep 17 00:00:00 2001 From: wangziyi Date: Mon, 5 Aug 2024 20:34:26 +0800 Subject: [PATCH 04/85] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dshift+m=E6=A1=86?= =?UTF-8?q?=E9=80=89=E5=90=8E=E8=BE=93=E5=85=A5remark=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=AF=BC=E8=87=B4Tab=E6=97=A0=E6=B3=95=E6=8B=96=E5=8A=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../component/chart/SpSegmentationChart.ts | 2 + .../SpSchedulingAnalysis.ts | 20 +- .../processAnalysis/TabProcessAnalysis.ts | 151 +++++ .../Top10LongestRunTimeProcess.ts | 482 ++++++++++++++ .../Top10ProcessSwitchCount.ts | 491 ++++++++++++++ .../component/trace/sheet/TabPaneCurrent.ts | 1 + .../trace/sheet/frequsage/TabPaneFreqUsage.ts | 623 +++++------------- .../sheet/frequsage/TabPaneFreqUsageConfig.ts | 2 + .../trace/database/TabPaneFreqUsageWorker.ts | 463 +++++++++++++ .../ProcedureLogicWorkerSchedulingAnalysis.ts | 122 ++++ 10 files changed, 1884 insertions(+), 473 deletions(-) create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts create mode 100644 ide/src/trace/database/TabPaneFreqUsageWorker.ts diff --git a/ide/src/trace/component/chart/SpSegmentationChart.ts b/ide/src/trace/component/chart/SpSegmentationChart.ts index b0d6adc2..e1db3d5f 100644 --- a/ide/src/trace/component/chart/SpSegmentationChart.ts +++ b/ide/src/trace/component/chart/SpSegmentationChart.ts @@ -24,6 +24,7 @@ import { type AllStatesRender, AllstatesStruct } from '../../database/ui-worker/ import { StateGroup } from '../../bean/StateModle'; import { queryAllFuncNames } from '../../database/sql/Func.sql'; import { Utils } from '../trace/base/Utils'; +import { TabPaneFreqUsage } from "../trace/sheet/frequsage/TabPaneFreqUsage"; const UNIT_HEIGHT: number = 20; const MS_TO_US: number = 1000000; const MIN_HEIGHT: number = 2; @@ -221,6 +222,7 @@ export class SpSegmentationChart { SpSegmentationChart.freqInfoMapData.set(v.cpuId, mapData); mapData = new Map(); }); + TabPaneFreqUsage.refresh(); } }; SpSegmentationChart.cpuRow.focusHandler = (ev): void => { diff --git a/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts b/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts index 4257627d..0cf0bcd8 100644 --- a/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts +++ b/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts @@ -17,12 +17,14 @@ import { BaseElement, element } from '../../../base-ui/BaseElement'; import './TabThreadAnalysis'; import './TabCpuAnalysis'; import { TabCpuAnalysis } from './TabCpuAnalysis'; +import './processAnalysis/TabProcessAnalysis'; import { TabThreadAnalysis } from './TabThreadAnalysis'; import { LitTabs } from '../../../base-ui/tabs/lit-tabs'; import { CheckCpuSetting } from './CheckCpuSetting'; import { Top20FrequencyThread } from './Top20FrequencyThread'; import { procedurePool } from '../../database/Procedure'; import { Utils } from '../trace/base/Utils'; +import { TabProcessAnalysis } from './processAnalysis/TabProcessAnalysis'; @element('sp-scheduling-analysis') export class SpSchedulingAnalysis extends BaseElement { @@ -34,11 +36,13 @@ export class SpSchedulingAnalysis extends BaseElement { private tabs: LitTabs | null | undefined; private tabCpuAnalysis: TabCpuAnalysis | null | undefined; private tabThreadAnalysis: TabThreadAnalysis | null | undefined; + private tabProcessAnalysis: TabProcessAnalysis | null | undefined; initElements(): void { this.tabs = this.shadowRoot?.querySelector('#tabs'); this.tabCpuAnalysis = this.shadowRoot?.querySelector('#cpu-analysis'); this.tabThreadAnalysis = this.shadowRoot?.querySelector('#thread-analysis'); + this.tabProcessAnalysis = this.shadowRoot?.querySelector('#process-analysis'); } static resetCpu(): void { @@ -58,6 +62,7 @@ export class SpSchedulingAnalysis extends BaseElement { SpSchedulingAnalysis.cpuCount = Utils.getInstance().getWinCpuCount(); this.tabCpuAnalysis?.init(); this.tabThreadAnalysis?.init(); + this.tabProcessAnalysis?.init(); } } @@ -95,12 +100,15 @@ export class SpSchedulingAnalysis extends BaseElement {
- - - - - - + + + + + + + + +
`; diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts new file mode 100644 index 00000000..5624ce82 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts @@ -0,0 +1,151 @@ +/* + * 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 { SpStatisticsHttpUtil } from '../../../../statistics/util/SpStatisticsHttpUtil'; +import './Top10LongestRunTimeProcess.ts'; +import './Top10ProcessSwitchCount.ts'; +import { Top10LongestRunTimeProcess } from './Top10LongestRunTimeProcess'; +import { Top10ProcessSwitchCount } from './Top10ProcessSwitchCount'; + +@element("tab-process-analysis") +export class TabProcessAnalysis extends BaseElement { + private currentTabID: string | undefined; + private currentTab: BaseElement | undefined; + private btn1: HTMLDivElement | null | undefined; + private btn2: HTMLDivElement | null | undefined; + private Top10LongestRunTimeProcess: Top10LongestRunTimeProcess | undefined | null; + private Top10ProcessSwitchCount: Top10ProcessSwitchCount | undefined | null; + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.btn1 = this.shadowRoot!.querySelector('#btn1'); + this.btn2 = this.shadowRoot!.querySelector('#btn2'); + this.Top10LongestRunTimeProcess = this.shadowRoot!.querySelector('#top10_process_runTime'); + this.Top10ProcessSwitchCount = this.shadowRoot!.querySelector('#top10_process_switchCount'); + this.btn1!.addEventListener('click', (event) => { + this.setClickTab(this.btn1!, this.Top10ProcessSwitchCount!); + }); + this.btn2!.addEventListener('click', (event) => { + this.setClickTab(this.btn2!, this.Top10LongestRunTimeProcess!); + }); + } + + /** + * 初始化操作,清空该标签页下所有面板数据,只有首次导入新trace后执行一次 + */ + init(): void { + this.Top10ProcessSwitchCount?.clearData(); + this.Top10LongestRunTimeProcess?.clearData(); + this.hideCurrentTab(); + this.currentTabID = undefined; + this.setClickTab(this.btn1!, this.Top10ProcessSwitchCount!); + } + + /** + * 隐藏当前面板,将按钮样式设置为未选中状态 + */ + hideCurrentTab(): void { + if (this.currentTabID) { + let clickTab = this.shadowRoot!.querySelector(`#${this.currentTabID}`); + if (clickTab) { + clickTab.className = 'tag_bt'; + } + } + if (this.currentTab) { + this.currentTab.style.display = 'none'; + } + } + + /** + * + * @param btn 当前点击的数据类型按钮 + * @param showContent 需要展示的面板对象 + */ + setClickTab(btn: HTMLDivElement, showContent: Top10ProcessSwitchCount | Top10LongestRunTimeProcess): void { + // 将前次点击的按钮样式设置为未选中样式状态 + if (this.currentTabID) { + let clickTab = this.shadowRoot!.querySelector(`#${this.currentTabID}`); + if (clickTab) { + clickTab.className = 'tag_bt'; + } + } + // 切换当前点击按钮的类名,应用点击样式 + btn.className = 'tab_click'; + // 切换记录的好的当前点击面板id,并设置其显隐 + if (btn.id !== this.currentTabID) { + this.currentTabID = btn.id; + if (this.currentTab) { + this.currentTab.style.display = 'none'; + } + this.currentTab = showContent; + showContent.style.display = 'inline'; + showContent.init(); + } + } + + /** + * 用于将元素节点挂载 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return ` + +
+
Top10切换次数进程
+
Top10运行超长进程
+
+
+ + +
+ `; + } +} \ No newline at end of file diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts new file mode 100644 index 00000000..38d16873 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts @@ -0,0 +1,482 @@ +/* + * 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 { procedurePool } from '../../../database/Procedure'; +import { info } from '../../../../log/Log'; +import { TableNoData } from '../TableNoData'; +import '../TableNoData'; +import { LitProgressBar } from '../../../../base-ui/progress-bar/LitProgressBar'; +import '../../../../base-ui/progress-bar/LitProgressBar'; +import { LitChartColumn } from '../../../../base-ui/chart/column/LitChartColumn'; +import '../../../../base-ui/chart/column/LitChartColumn'; +import { Utils } from '../../trace/base/Utils'; + +@element("top10-longest-runtime-process") +export class Top10LongestRunTimeProcess extends BaseElement { + traceChange: boolean = false; + private processRunTimeTbl: LitTable | null | undefined; + private threadRunTimeTbl: LitTable | null | undefined; + private processRunTimeProgress: LitProgressBar | null | undefined; + private nodataPro: TableNoData | null | undefined; + private processRunTimeData: Array = []; + private threadRunTimeData: Array = []; + private processSwitchCountChart: LitChartColumn | null | undefined; + private threadSwitchCountChart: LitChartColumn | null | undefined; + private nodataThr: TableNoData | null | undefined; + private display_pro: HTMLDivElement | null | undefined; + private display_thr: HTMLDivElement | null | undefined; + private processId: number | undefined; + private display_flag: boolean = true; + private back: HTMLDivElement | null | undefined; + private processMap: Map = new Map(); + private threadMap: Map = new Map(); + + /** + * 初始化操作,若trace发生改变,将所有变量设置为默认值并重新请求数据。若trace未改变,跳出初始化 + */ + init() { + if (!this.traceChange) { + if (this.processRunTimeTbl!.recycleDataSource.length > 0) { + this.processRunTimeTbl?.reMeauseHeight(); + } + return; + } + this.traceChange = false; + this.processRunTimeProgress!.loading = true; + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + this.processMap = Utils.getInstance().getProcessMap(); + this.threadMap = Utils.getInstance().getThreadMap(); + this.queryLogicWorker( + "scheduling-Process Top10RunTime", + "query Process Top10 Run Time Analysis Time:", + this.callBack.bind(this) + ); + } + + /** + * 清除已存储数据 + */ + clearData() { + this.traceChange = true; + this.processSwitchCountChart!.dataSource = []; + this.processRunTimeTbl!.recycleDataSource = []; + this.threadSwitchCountChart!.dataSource = []; + this.threadRunTimeTbl!.recycleDataSource = []; + this.processRunTimeData = []; + this.threadRunTimeData = []; + this.processMap = new Map(); + this.threadMap = new Map(); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { + let processThreadCountTime = new Date().getTime(); + procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + let durTime = new Date().getTime() - processThreadCountTime; + info(log, durTime); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + organizationData(arr: Array): Array { + let result: Array = []; + for (let i = 0; i < arr.length; i++) { + const pStr: string | null = this.processMap.get(arr[i].pid!)!; + const tStr: string | null = this.threadMap.get(arr[i].tid!)!; + result.push({ + NO: i + 1, + pid: arr[i].pid || this.processId, + pName: pStr === null ? 'Process ' : pStr, + dur: arr[i].dur, + tid: arr[i].tid, + tName: tStr === null ? 'Thread ' : tStr + }); + } + return result; + } + + /** + * 提交线程后,结果返回后的回调函数 + * @param res 数据库查询结果 + */ + callBack(res: Array): void { + let result: Array = this.organizationData(res); + if (this.display_flag === true) { + this.processCallback(result); + } else { + this.threadCallback(result); + } + this.processRunTimeProgress!.loading = false; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10进程数据 + * @param result 需要显示在表格中的数据 + */ + processCallback(result: Array): void { + this.nodataPro!.noData = result === undefined || result.length === 0; + this.processRunTimeTbl!.recycleDataSource = result; + this.processRunTimeTbl!.reMeauseHeight(); + this.processRunTimeData = result; + this.processSwitchCountChart!.config = { + data: result, + appendPadding: 10, + xField: 'pid', + yField: 'dur', + seriesField: 'size', + color: (a) => { + return '#0a59f7'; + }, + hoverHandler: (data) => { + if (data) { + this.processRunTimeTbl!.setCurrentHover(data); + } else { + this.processRunTimeTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj[0].obj.pid}
+
Process_Name:${ + // @ts-ignore + obj[0].obj.pName}
+
Run_Time:${ + // @ts-ignore + obj[0].obj.dur}
+
+ `; + }, + label: null, + }; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10线程数据 + * @param result 需要显示在表格中的数据 + */ + threadCallback(result: Array):void { + this.nodataThr!.noData = result === undefined || result.length === 0; + this.threadRunTimeTbl!.recycleDataSource = result; + this.threadRunTimeTbl!.reMeauseHeight(); + this.threadRunTimeData = result; + this.threadSwitchCountChart!.config = { + data: result, + appendPadding: 10, + xField: 'tid', + yField: 'dur', + seriesField: 'size', + color: (a) => { + return '#0a59f7'; + }, + hoverHandler: (data) => { + if (data) { + this.threadRunTimeTbl!.setCurrentHover(data); + } else { + this.threadRunTimeTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj[0].obj.pid}
+
Thread_Id:${ + // @ts-ignore + obj[0].obj.tid}
+
Thread_Name:${ + // @ts-ignore + obj[0].obj.tName}
+
Run_Time:${ + // @ts-ignore + obj[0].obj.dur}
+
+ `; + }, + label: null, + }; + } + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.processRunTimeProgress = this.shadowRoot!.querySelector('#loading'); + this.nodataPro = this.shadowRoot!.querySelector('#nodata_Pro'); + this.processRunTimeTbl = this.shadowRoot!.querySelector('#tb-process-run-time'); + this.processSwitchCountChart = this.shadowRoot!.querySelector('#chart_pro'); + this.nodataThr = this.shadowRoot!.querySelector('#nodata_Thr'); + this.threadRunTimeTbl = this.shadowRoot!.querySelector('#tb-thread-run-time'); + this.threadSwitchCountChart = this.shadowRoot!.querySelector('#chart_thr'); + this.display_pro = this.shadowRoot!.querySelector('#display_pro'); + this.display_thr = this.shadowRoot!.querySelector('#display_thr'); + this.back = this.shadowRoot!.querySelector('#back'); + this.clickEventListener(); + this.hoverEventListener(); + } + + /** + * 点击监听事件函数块 + */ + clickEventListener(): void { + // @ts-ignore + this.processRunTimeTbl!.addEventListener('row-click', (evt: CustomEvent) => { + this.display_flag = false; + let data = evt.detail.data; + this.processId = data.pid; + this.display_thr!.style.display = 'block'; + this.display_pro!.style.display = 'none'; + this.queryLogicWorker( + 'scheduling-Process Top10RunTime', + 'query Thread Top10 Run Time Analysis Time:', + this.callBack.bind(this), + data.pid + ); + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + this.processRunTimeTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.processRunTimeData); + this.processRunTimeTbl!.recycleDataSource = this.processRunTimeData; + }); + this.threadRunTimeTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.threadRunTimeData); + this.threadRunTimeTbl!.recycleDataSource = this.threadRunTimeData; + }); + this.back?.addEventListener('click', (event) => { + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + }); + } + + /** + * 移入事件监听函数块 + */ + hoverEventListener(): void { + // @ts-ignore + this.processRunTimeTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + this.processSwitchCountChart?.showHoverColumn(data.no); + } + }); + // @ts-ignore + this.threadRunTimeTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + this.threadSwitchCountChart?.showHoverColumn(data.no); + } + }); + } + + /** + * 表格数据排序 + * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 + * @param data 表格中需要排序的数据 + */ + sortByColumn(detail: any, data: Array): void { + // @ts-ignore + function compare(processThreadCountProperty, sort, type) { + return function (a: any, b: any) { + if (type === 'number') { + // @ts-ignore + return sort === 2 + ? parseFloat(b[processThreadCountProperty]) - + parseFloat(a[processThreadCountProperty]) + : parseFloat(a[processThreadCountProperty]) - + parseFloat(b[processThreadCountProperty]); + } else { + if (sort === 2) { + return b[processThreadCountProperty] + .toString() + .localeCompare(a[processThreadCountProperty].toString()); + } else { + return a[processThreadCountProperty] + .toString() + .localeCompare(b[processThreadCountProperty].toString()); + } + } + }; + } + if ( detail.key === 'pName' || detail.key === 'tName') { + data.sort( + compare(detail.key, detail.sort, 'string') + ); + } else { + data.sort( + compare(detail.key, detail.sort, 'number') + ); + } + } + + /** + * 用于将元素节点挂载,大函数块拆分为样式、节点 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return this.initStyleHtml() + this.initTagHtml(); + } + + /** + * 样式html代码块 + * @returns 返回样式代码块字符串 + */ + initStyleHtml(): string { + return ` + + ` + } + + /** + * 节点html代码块 + * @returns 返回节点代码块字符串 + */ + initTagHtml(): string { + return ` + +
+ +
+
+
+
+
Top10运行超长进程
+ +
+
+ + + + + + +
+
+
+
+
+ + `; + } +} + +interface Top10RunTimeData { + NO?: number, + pid?: number, + tid?: number, + pName?: string, + tName?: string, + switchCount?: number, + dur?: number +} \ No newline at end of file diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts new file mode 100644 index 00000000..c2b050d8 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts @@ -0,0 +1,491 @@ +/* + * 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 { procedurePool } from '../../../database/Procedure'; +import { info } from '../../../../log/Log'; +import { TableNoData } from '../TableNoData'; +import '../TableNoData'; +import { LitProgressBar } from '../../../../base-ui/progress-bar/LitProgressBar'; +import '../../../../base-ui/progress-bar/LitProgressBar'; +import { LitChartPie } from '../../../../base-ui/chart/pie/LitChartPie'; +import '../../../../base-ui/chart/pie/LitChartPie'; +import { Utils } from '../../trace/base/Utils'; + +@element('top10-process-switch-count') +export class Top10ProcessSwitchCount extends BaseElement { + traceChange: boolean = false; + private processSwitchCountTbl: LitTable | null | undefined; + private threadSwitchCountTbl: LitTable | null | undefined; + private nodataPro: TableNoData | null | undefined; + private nodataThr: TableNoData | null | undefined; + private processSwitchCountData: Array = []; + private threadSwitchCountData: Array = []; + private threadSwitchCountPie: LitChartPie | null | undefined; + private processSwitchCountPie: LitChartPie | null | undefined; + private display_pro: HTMLDivElement | null | undefined; + private display_thr: HTMLDivElement | null | undefined; + private processSwitchCountProgress: LitProgressBar | null | undefined; + private processId: number | undefined; + private display_flag: boolean = true; + private back: HTMLDivElement | null | undefined; + private processMap: Map = new Map(); + private threadMap: Map = new Map(); + + /** + * 初始化操作,若trace发生改变,将所有变量设置为默认值并重新请求数据。若trace未改变,跳出初始化 + */ + init(): void { + if (!this.traceChange) { + if (this.processSwitchCountTbl!.recycleDataSource.length > 0) { + this.processSwitchCountTbl?.reMeauseHeight(); + } + return; + } + this.traceChange = false; + this.processSwitchCountProgress!.loading = true; + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + this.processMap = Utils.getInstance().getProcessMap(); + this.threadMap = Utils.getInstance().getThreadMap(); + this.queryLogicWorker( + 'scheduling-Process Top10Swicount', + 'query Process Top10 Switch Count Analysis Time:', + this.callBack.bind(this) + ); + } + + /** + * 清除已存储数据 + */ + clearData(): void { + this.traceChange = true; + this.processSwitchCountPie!.dataSource = []; + this.processSwitchCountTbl!.recycleDataSource = []; + this.threadSwitchCountPie!.dataSource = []; + this.threadSwitchCountTbl!.recycleDataSource = []; + this.processSwitchCountData = []; + this.threadSwitchCountData = []; + this.processMap = new Map(); + this.threadMap = new Map(); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { + let processThreadCountTime = new Date().getTime(); + procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + let durTime = new Date().getTime() - processThreadCountTime; + info(log, durTime); + } + + /** + * 抽取公共方法,提取数据,用于展示到表格中 + * @param arr 数据库查询结果 + * @returns 整理好的数据,包含进程名,线程名等相关信息 + */ + organizationData(arr: Array): Array { + let result: Array = []; + for (let i = 0; i < arr.length; i++) { + const pStr: string | null = this.processMap.get(arr[i].pid!)!; + const tStr: string | null = this.threadMap.get(arr[i].tid!)!; + result.push({ + NO: i + 1, + pid: arr[i].pid || this.processId, + pName: pStr === null ? 'Process ' : pStr, + switchCount: arr[i].occurrences, + tid: arr[i].tid, + tName: tStr === null ? 'Thread ' : tStr + }); + } + return result; + } + + /** + * 提交线程后,结果返回后的回调函数 + * @param res 数据库查询结果 + */ + callBack(res: Array): void { + let result: Array = this.organizationData(res); + // 判断当前显示的是进程组还是线程组 + if (this.display_flag === true) { + this.processCallback(result); + } else { + this.threadCallback(result); + } + this.processSwitchCountProgress!.loading = false; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10进程数据 + * @param result 需要显示在表格中的数据 + */ + processCallback(result: Array): void { + this.nodataPro!.noData = result === undefined || result.length === 0; + this.processSwitchCountData = result; + this.processSwitchCountPie!.config = { + appendPadding: 10, + data: result, + angleField: 'switchCount', + colorField: 'pid', + radius: 0.8, + label: { + type: 'outer', + }, + hoverHandler: (data) => { + if (data) { + this.processSwitchCountTbl!.setCurrentHover(data); + } else { + this.processSwitchCountTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj.obj.pid}
+
Process_Name:${ + // @ts-ignore + obj.obj.pName}
+
Switch Count:${ + // @ts-ignore + obj.obj.switchCount}
+
+ `; + }, + interactions: [ + { + type: 'element-active', + }, + ], + }; + this.processSwitchCountTbl!.recycleDataSource = result; + this.processSwitchCountTbl!.reMeauseHeight(); + } + + /** + * 大函数块拆解分为两部分,此部分为Top10线程数据 + * @param result 需要显示在表格中的数据 + */ + threadCallback(result: Array): void { + this.nodataThr!.noData = result === undefined || result.length === 0; + this.threadSwitchCountTbl!.recycleDataSource = result; + this.threadSwitchCountTbl!.reMeauseHeight(); + this.threadSwitchCountData = result; + this.threadSwitchCountPie!.config = { + appendPadding: 10, + data: result, + angleField: 'switchCount', + colorField: 'tid', + radius: 0.8, + label: { + type: 'outer', + }, + hoverHandler: (data) => { + if (data) { + this.threadSwitchCountTbl!.setCurrentHover(data); + } else { + this.threadSwitchCountTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Thread_Id:${ + // @ts-ignore + obj.obj.tid}
+
Thread_Name:${ + // @ts-ignore + obj.obj.tName}
+
Switch Count:${ + // @ts-ignore + obj.obj.switchCount}
+
Process_Id:${ + // @ts-ignore + obj.obj.pid}
+
+ `; + }, + interactions: [ + { + type: 'element-active', + }, + ], + }; + } + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.processSwitchCountProgress = this.shadowRoot!.querySelector('#loading'); + this.processSwitchCountTbl = this.shadowRoot!.querySelector('#tb-process-switch-count'); + this.threadSwitchCountTbl = this.shadowRoot!.querySelector('#tb-thread-switch-count'); + this.processSwitchCountPie = this.shadowRoot!.querySelector('#pie_pro'); + this.threadSwitchCountPie = this.shadowRoot!.querySelector('#pie_thr'); + this.nodataPro = this.shadowRoot!.querySelector('#nodata_pro'); + this.nodataThr = this.shadowRoot!.querySelector('#nodata_thr'); + this.display_pro = this.shadowRoot!.querySelector('#display_pro'); + this.display_thr = this.shadowRoot!.querySelector('#display_thr'); + this.back = this.shadowRoot!.querySelector('#back'); + this.clickEventListener(); + this.hoverEventListener(); + } + + /** + * 点击监听事件函数块 + */ + clickEventListener(): void { + // @ts-ignore + this.processSwitchCountTbl!.addEventListener('row-click', (evt: CustomEvent) => { + this.display_flag = false; + let data = evt.detail.data; + this.processId = data.pid; + this.display_thr!.style.display = 'block'; + this.display_pro!.style.display = 'none'; + this.queryLogicWorker( + 'scheduling-Process Top10Swicount', + 'query Process Top10 Switch Count Analysis Time:', + this.callBack.bind(this), + data.pid + ); + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + // @ts-ignore + this.threadSwitchCountTbl!.addEventListener('row-click', (evt: CustomEvent) => { + let data = evt.detail.data; + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + this.processSwitchCountTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.processSwitchCountData); + this.processSwitchCountTbl!.recycleDataSource = this.processSwitchCountData; + }); + this.threadSwitchCountTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.threadSwitchCountData); + this.threadSwitchCountTbl!.recycleDataSource = this.threadSwitchCountData; + }); + this.back!.addEventListener('click', (event) => { + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + }); + + } + + /** + * 移入事件监听函数块 + */ + hoverEventListener(): void { + // @ts-ignore + this.processSwitchCountTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + } + this.processSwitchCountPie?.showHover(); + }); + // @ts-ignore + this.threadSwitchCountTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + } + this.threadSwitchCountPie?.showHover(); + }); + this.addEventListener('mouseenter', () => { + if (this.processSwitchCountTbl!.recycleDataSource.length > 0) { + this.processSwitchCountTbl?.reMeauseHeight(); + } + }); + } + + /** + * 表格数据排序 + * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 + * @param data 表格中需要排序的数据 + */ + sortByColumn(detail: {key: string, sort: number}, data: Array): void { + // @ts-ignore + function compare(processThreadCountProperty, sort, type) { + return function (a: any, b: any) { + if (type === 'number') { + // @ts-ignore + return sort === 2 + ? parseFloat(b[processThreadCountProperty]) - + parseFloat(a[processThreadCountProperty]) + : parseFloat(a[processThreadCountProperty]) - + parseFloat(b[processThreadCountProperty]); + } else { + if (sort === 2) { + return b[processThreadCountProperty] + .toString() + .localeCompare(a[processThreadCountProperty].toString()); + } else { + return a[processThreadCountProperty] + .toString() + .localeCompare(b[processThreadCountProperty].toString()); + } + } + }; + } + if (detail.key === 'pName' || detail.key === 'tName') { + data.sort( + compare(detail.key, detail.sort, 'string') + ); + } else { + data.sort( + compare(detail.key, detail.sort, 'number') + ); + } + } + + /** + * 用于将元素节点挂载,大函数块拆分为样式、节点 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return this.initStyleHtml() + this.initTagHtml(); + } + + /** + * 样式html代码块 + * @returns 返回样式代码块字符串 + */ + initStyleHtml(): string { + return ` + + `; + } + + /** + * 节点html代码块 + * @returns 返回节点代码块字符串 + */ + initTagHtml() :string { + return ` + +
+ +
+
+
+
+
Statistics By Process's Switch Count
+ +
+
+ + + + + + +
+
+
+
+
+ + `; + } +} + +interface Top10ProcSwiCount { + NO?: number, + pid?: number, + tid?: number, + pName?: string, + tName?: string, + switchCount?: number, + occurrences?: number +} \ No newline at end of file diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts index c114111f..7d2b3104 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts @@ -206,6 +206,7 @@ export class TabPaneCurrent extends BaseElement { tr[i].querySelector('#text-input')!.value = this.slicesTimeList[i - 1].text; // // 点击色块修改颜色 tr[i].querySelector('#text-input')?.addEventListener('keyup', (event: unknown) => { + SpSystemTrace.isKeyUp = true; if ( // @ts-ignore this.tableDataSource[i].startTime === this.slicesTimeList[i - 1].startTime && diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts index 70dadfc3..a7c899f6 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts @@ -13,36 +13,52 @@ * limitations under the License. */ -import { BaseElement, element } from '../../../../../base-ui/BaseElement'; -import { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table'; -import { SelectionParam } from '../../../../bean/BoxSelection'; -import '../../../StackBar'; -import { getTabRunningPercent } from '../../../../database/sql/ProcessThread.sql'; -import { queryCpuFreqUsageData, queryCpuFreqFilterId } from '../../../../database/sql/Cpu.sql'; -import { Utils } from '../../base/Utils'; -import { resizeObserver } from '../SheetUtils'; -import { SpSegmentationChart } from '../../../chart/SpSegmentationChart'; -import { type CpuFreqData, type RunningFreqData, type RunningData, type CpuFreqTd } from './TabPaneFreqUsageConfig'; +import { BaseElement, element } from "../../../../../base-ui/BaseElement"; +import { + LitTable, + RedrawTreeForm, +} from "../../../../../base-ui/table/lit-table"; +import { SelectionParam } from "../../../../bean/BoxSelection"; +import "../../../StackBar"; +import { getTabRunningPercent } from "../../../../database/sql/ProcessThread.sql"; +import { + queryCpuFreqUsageData, + queryCpuFreqFilterId, +} from "../../../../database/sql/Cpu.sql"; +import { Utils } from "../../base/Utils"; +import { resizeObserver } from "../SheetUtils"; +import { SpSegmentationChart } from "../../../chart/SpSegmentationChart"; +import { + type CpuFreqData, + type RunningFreqData, + type RunningData, + type CpuFreqTd, +} from "./TabPaneFreqUsageConfig"; -@element('tabpane-frequsage') +@element("tabpane-frequsage") export class TabPaneFreqUsage extends BaseElement { private threadStatesTbl: LitTable | null | undefined; private currentSelectionParam: SelectionParam | undefined; - private result: Array = []; + private worker: Worker | undefined; + static element: TabPaneFreqUsage; set data(threadStatesParam: SelectionParam) { if (this.currentSelectionParam === threadStatesParam) { return; } - this.threadStatesTbl!.loading = true; this.currentSelectionParam = threadStatesParam; this.threadStatesTbl!.recycleDataSource = []; // @ts-ignore this.threadStatesTbl.value = []; - this.result = []; this.queryAllData(threadStatesParam); } + + static refresh(): void { + this.prototype.queryAllData(TabPaneFreqUsage.element.currentSelectionParam!); + } + async queryAllData(threadStatesParam: SelectionParam): Promise { + TabPaneFreqUsage.element.threadStatesTbl!.loading = true; let runningResult: Array = await getTabRunningPercent( threadStatesParam.threadIds, threadStatesParam.processIds, @@ -50,7 +66,8 @@ export class TabPaneFreqUsage extends BaseElement { threadStatesParam.rightNs ); // 查询cpu及id信息 - let cpuIdResult: Array<{ id: number; cpu: number }> = await queryCpuFreqFilterId(); + let cpuIdResult: Array<{ id: number; cpu: number }> = + await queryCpuFreqFilterId(); // 以键值对形式将cpu及id进行对应,后续会将频点数据与其对应cpu进行整合 let IdMap: Map = new Map(); let queryId: Array = []; @@ -73,85 +90,47 @@ export class TabPaneFreqUsage extends BaseElement { } const LEFT_TIME: number = threadStatesParam.leftNs + threadStatesParam.recordStartNs; const RIGHT_TIME: number = threadStatesParam.rightNs + threadStatesParam.recordStartNs; - const args = { leftNs: LEFT_TIME, rightNs: RIGHT_TIME, cpuArray: cpuArray }; - let resultArr: Array = orgnazitionMap(runningResult, cpuFreqData, args); - // 递归拿出来最底层的数据,并以进程层级的数据作为分割 - this.recursion(resultArr); - this.result = JSON.parse(JSON.stringify(this.result)); - mergeTotal(resultArr, fixTotal(this.result)); - this.fixedDeal(resultArr, threadStatesParam.traceId); - this.threadClick(resultArr); - this.threadStatesTbl!.recycleDataSource = resultArr; - this.threadStatesTbl!.loading = false; - } - - /** - * 递归整理数据小数位 - */ - fixedDeal(arr: Array, traceId?: string | null): void { - if (arr == undefined) { - return; - } - const TIME_MUTIPLE: number = 1000000; - // KHz->MHz * ns->ms - const CONS_MUTIPLE: number = 1000000000; - const FREQ_MUTIPLE: number = 1000; - const MIN_PERCENT: number = 2; - const MIN_FREQ: number = 3; - for (let i = 0; i < arr.length; i++) { - let trackId: number; - // 若存在空位元素则进行删除处理 - if (arr[i] === undefined) { - arr.splice(i, 1); - i--; - continue; - } - if (arr[i].thread?.indexOf('P') !== -1) { - trackId = Number(arr[i].thread?.slice(1)!); - arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || 'Process'} ${trackId}`; - } else if (arr[i].thread === 'summary data') { - } else { - trackId = Number(arr[i].thread!.split('_')[1]); - arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || 'Thread'} ${trackId}`; - } - if (arr[i].cpu < 0) { - // @ts-ignore - arr[i].cpu = ''; - } - // @ts-ignore - if (arr[i].frequency < 0) { - arr[i].frequency = ''; - } - // @ts-ignore - arr[i].percent = arr[i].percent.toFixed(MIN_PERCENT); - // @ts-ignore - arr[i].dur = (arr[i].dur / TIME_MUTIPLE).toFixed(MIN_FREQ); - // @ts-ignore - arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ); - if (arr[i].frequency !== '') { - if (arr[i].frequency === 'unknown') { - arr[i].frequency = 'unknown'; - } else { - arr[i].frequency = Number(arr[i].frequency) / FREQ_MUTIPLE; - } - } - this.fixedDeal(arr[i].children!, traceId); - } + const comPower = + SpSegmentationChart.freqInfoMapData.size > 0 + ? SpSegmentationChart.freqInfoMapData + : undefined; + const args = { + runData: runningResult, + cpuFreqData: cpuFreqData, + leftNs: LEFT_TIME, + rightNs: RIGHT_TIME, + cpuArray: cpuArray, + comPower: comPower, + }; + TabPaneFreqUsage.element.worker!.postMessage(args); + TabPaneFreqUsage.element.worker!.onmessage = (event: MessageEvent): void => { + let resultArr: Array = event.data; + TabPaneFreqUsage.element.fixedDeal(resultArr, threadStatesParam.traceId); + TabPaneFreqUsage.element.threadClick(resultArr); + TabPaneFreqUsage.element.threadStatesTbl!.recycleDataSource = resultArr; + TabPaneFreqUsage.element.threadStatesTbl!.loading = false; + }; } /** * 表头点击事件 */ private threadClick(data: Array): void { - let labels = this.threadStatesTbl?.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label'); + let labels = this.threadStatesTbl?.shadowRoot + ?.querySelector(".th > .td")! + .querySelectorAll("label"); if (labels) { for (let i = 0; i < labels.length; i++) { let label = labels[i].innerHTML; - labels[i].addEventListener('click', (e) => { - if (label.includes('Process') && i === 0) { + labels[i].addEventListener("click", (e) => { + if (label.includes("Process") && i === 0) { this.threadStatesTbl!.setStatus(data, false); - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); - } else if (label.includes('Thread') && i === 1) { + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Retract + ); + } else if (label.includes("Thread") && i === 1) { for (let item of data) { // @ts-ignore item.status = true; @@ -159,40 +138,41 @@ export class TabPaneFreqUsage extends BaseElement { this.threadStatesTbl!.setStatus(item.children, false); } } - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); - } else if (label.includes('CPU') && i === 2) { + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Retract + ); + } else if (label.includes("CPU") && i === 2) { this.threadStatesTbl!.setStatus(data, true); - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Expand); + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Expand + ); } }); } } } - /** - * - * @param arr 待整理的数组,会经过递归取到最底层的数据 - */ - recursion(arr: Array): void { - for (let idx = 0; idx < arr.length; idx++) { - if (arr[idx].cpu === -1) { - this.result.push(arr[idx]); - } - if (arr[idx].children) { - this.recursion(arr[idx].children!); - } else { - this.result.push(arr[idx]); - } - } - } initElements(): void { - this.threadStatesTbl = this.shadowRoot?.querySelector('#tb-running-percent'); + this.threadStatesTbl = this.shadowRoot?.querySelector( + "#tb-running-percent" + ); + //开启一个线程计算busyTime + this.worker = new Worker( + new URL("../../../../database/TabPaneFreqUsageWorker", import.meta.url) + ); + TabPaneFreqUsage.element = this; } + connectedCallback(): void { super.connectedCallback(); - resizeObserver(this.parentElement!, this.threadStatesTbl!); + resizeObserver(this.parentElement!, this.threadStatesTbl!, 20); } + initHtml(): string { return ` - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + `; } -} -/** - * - * @param runData 数据库查询上来的running数据,此函数会将数据整理成map结构,分组规则:'pid_tid'为键,running数据数字为值 - * @returns 返回map对象及所有running数据的dur和,后续会依此计算百分比 - */ -function orgnazitionMap( - runData: Array, - cpuFreqData: Array, - args: { - leftNs: number, - rightNs: number, - cpuArray: number[] - } -): Array { - let result: Map> = new Map(); - let sum: number = 0; - // 循环分组 - for (let i = 0; i < runData.length; i++) { - let mapKey: string = runData[i].pid + '_' + runData[i].tid; - // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值 - if (!result.has(mapKey)) { - result.set(mapKey, new Array()); - } - // 整理左右边界数据问题, 因为涉及多线程,所以必须放在循环里 - if (runData[i].ts < args.leftNs && runData[i].ts + runData[i].dur > args.leftNs) { - runData[i].dur = runData[i].ts + runData[i].dur - args.leftNs; - runData[i].ts = args.leftNs; - } - if (runData[i].ts + runData[i].dur > args.rightNs) { - runData[i].dur = args.rightNs - runData[i].ts; - } - // 特殊处理数据表中dur为负值的情况 - if (runData[i].dur < 0) { - runData[i].dur = 0; - } - // 分组整理数据 - result.get(mapKey)?.push({ - pid: runData[i].pid, - tid: runData[i].tid, - cpu: runData[i].cpu, - dur: runData[i].dur, - ts: runData[i].ts, - }); - sum += runData[i].dur; - } - return dealCpuFreqData(cpuFreqData, result, sum, args.cpuArray); -} - -/** - * - * @param cpuFreqData cpu频点数据的数组 - * @param result running数据的map对象 - * @param sum running数据的时间和 - * @returns 返回cpu频点数据map,'pid_tid'为键,频点算力值数据的数组为值 - */ -function dealCpuFreqData( - cpuFreqData: Array, - result: Map>, - sum: number, - cpuList: number[] -): Array { - let runningFreqData: Map> = new Map(); - result.forEach((item, key) => { - let resultList: Array = new Array(); - for (let i = 0; i < item.length; i++) { - for (let j = 0; j < cpuFreqData.length; j++) { - let flag: number; - if (item[i].cpu === cpuFreqData[j].cpu) { - // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间小于频点结束时间减去running数据开始时间的差值的情况 - if ( - item[i].ts > cpuFreqData[j].ts && - item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && - item[i].dur < cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts - ) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 1))!); - item.splice(i, 1); - i--; - break; - } - if ( - item[i].ts > cpuFreqData[j].ts && - item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && - item[i].dur >= cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts - ) { - // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间大于等于频点结束时间减去running数据开始时间的差值的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 2))!); - } - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值小于频点数据持续时间的情况 - if ( - item[i].ts <= cpuFreqData[j].ts && - item[i].ts + item[i].dur > cpuFreqData[j].ts && - item[i].dur + item[i].ts - cpuFreqData[j].ts < cpuFreqData[j].dur - ) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 3))!); - item.splice(i, 1); - i--; - break; - } - if ( - item[i].ts <= cpuFreqData[j].ts && - item[i].ts + item[i].dur > cpuFreqData[j].ts && - item[i].dur + item[i].ts - cpuFreqData[j].ts >= cpuFreqData[j].dur - ) { - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值大于等于频点数据持续时间的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 4))!); - } - if (item[i].ts <= cpuFreqData[j].ts && item[i].ts + item[i].dur <= cpuFreqData[j].ts) { - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间小于等于频点开始时间的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); - item.splice(i, 1); - i--; - break; - } - } else { - if (!cpuList.includes(item[i].cpu)) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); - item.splice(i, 1); - i--; - break; - } - } - } - } - runningFreqData.set(key, mergeSameData(resultList)); - }); - return dealTree(runningFreqData); -} - -/** - * - * @param item running数据 - * @param cpuFreqData 频点数据 - * @param sum running总和 - * @param flag 标志位,根据不同值返回不同结果 - * @returns 返回新的对象 - */ -function returnObj( - item: RunningData, - cpuFreqData: CpuFreqData, - sum: number, - flag: number -): RunningFreqData | undefined { - const PERCENT: number = 100; - const consumption: number = ( - SpSegmentationChart.freqInfoMapData.size > 0 - ? SpSegmentationChart.freqInfoMapData.get(item.cpu)?.get(cpuFreqData.value) - : cpuFreqData.value - )!; - let result: RunningFreqData | undefined; - switch (flag) { - case 1: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * item.dur, - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: item.dur, - percent: (item.dur / sum) * PERCENT, - }; - case 2: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * (cpuFreqData.ts + cpuFreqData.dur - item.ts), - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: cpuFreqData.ts + cpuFreqData.dur - item.ts, - percent: ((cpuFreqData.ts + cpuFreqData.dur - item.ts) / sum) * PERCENT, - }; - case 3: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * (item.dur + item.ts - cpuFreqData.ts), - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: item.dur + item.ts - cpuFreqData.ts, - percent: ((item.dur + item.ts - cpuFreqData.ts) / sum) * PERCENT, - }; - case 4: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * cpuFreqData.dur, - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: cpuFreqData.dur, - percent: (cpuFreqData.dur / sum) * PERCENT, - }; - case 5: - result = { - thread: item.pid + '_' + item.tid, - consumption: 0, - cpu: item.cpu, - frequency: 'unknown', - dur: item.dur, - percent: (item.dur / sum) * PERCENT, - }; - } - return result; -} - -/** - * - * @param resultList 单线程内running数据与cpu频点数据整合成的数组 - */ -function mergeSameData(resultList: Array): Array { - let cpuFreqArr: Array = []; - let cpuArr: Array = []; - //合并同一线程内,当运行所在cpu和频点相同时,dur及percent进行累加求和 - for (let i = 0; i < resultList.length; i++) { - if (!cpuArr.includes(resultList[i].cpu)) { - cpuArr.push(resultList[i].cpu); - cpuFreqArr.push(creatNewObj(resultList[i].cpu)); + /** + * 递归整理数据小数位 + */ + fixedDeal(arr: Array, traceId?: string | null): void { + if (arr == undefined) { + return; } - for (let j = i + 1; j < resultList.length; j++) { - if (resultList[i].cpu === resultList[j].cpu && resultList[i].frequency === resultList[j].frequency) { - resultList[i].dur += resultList[j].dur; - resultList[i].percent += resultList[j].percent; - resultList[i].consumption += resultList[j].consumption; - resultList.splice(j, 1); - j--; + const TIME_MUTIPLE: number = 1000000; + // KHz->MHz * ns->ms + const CONS_MUTIPLE: number = 1000000000; + const MIN_PERCENT: number = 2; + const MIN_FREQ: number = 3; + const MIN_POWER: number = 6; + for (let i = 0; i < arr.length; i++) { + let trackId: number; + // 若存在空位元素则进行删除处理 + if (arr[i] === undefined) { + arr.splice(i, 1); + i--; + continue; } - } - cpuFreqArr.find(function (item) { - if (item.cpu === resultList[i].cpu) { - item.children?.push(resultList[i]); - item.children?.sort((a, b) => b.consumption - a.consumption); - item.dur += resultList[i].dur; - item.percent += resultList[i].percent; - item.consumption += resultList[i].consumption; - item.thread = resultList[i].thread; + if (arr[i].thread?.indexOf("P") !== -1) { + trackId = Number(arr[i].thread?.slice(1)!); + arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || "Process"} ${trackId}`; + } else if (arr[i].thread === "summary data") { + } else { + trackId = Number(arr[i].thread!.split("_")[1]); + arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || "Thread"} ${trackId}`; } - }); - } - cpuFreqArr.sort((a, b) => a.cpu - b.cpu); - return cpuFreqArr; -} - -/** - * - * @param params cpu层级的数据 - * @returns 整理好的进程级数据 - */ -function dealTree(params: Map>): Array { - let result: Array = []; - params.forEach((item, key) => { - let process: RunningFreqData = creatNewObj(-1, false); - let thread: RunningFreqData = creatNewObj(-2); - for (let i = 0; i < item.length; i++) { - thread.children?.push(item[i]); - thread.dur += item[i].dur; - thread.percent += item[i].percent; - thread.consumption += item[i].consumption; - thread.thread = item[i].thread; - } - process.children?.push(thread); - process.dur += thread.dur; - process.percent += thread.percent; - process.consumption += thread.consumption; - process.thread = process.thread! + key.split('_')[0]; - result.push(process); - }); - for (let i = 0; i < result.length; i++) { - for (let j = i + 1; j < result.length; j++) { - if (result[i].thread === result[j].thread) { - result[i].children?.push(result[j].children![0]); - result[i].dur += result[j].dur; - result[i].percent += result[j].percent; - result[i].consumption += result[j].consumption; - result.splice(j, 1); - j--; + if (arr[i].cpu < 0) { + // @ts-ignore + arr[i].cpu = ""; } - } - } - return result; -} - -/** - * - * @param cpu 根据cpu值创建层级结构,cpu < 0为线程、进程层级,其余为cpu层级 - * @returns - */ -function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData { - return { - thread: flag ? '' : 'P', - consumption: 0, - cpu: cpu, - frequency: -1, - dur: 0, - percent: 0, - children: [], - }; -} - -/** - * - * @param arr 需要整理汇总的频点级数据 - * @returns 返回一个total->cpu->频点的三级树结构数组 - */ -function fixTotal(arr: Array): Array { - let result: Array = []; - let flag: number = -1; - // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割 - for (let i = 0; i < arr.length; i++) { - // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据 - if (arr[i].thread?.indexOf('P') !== -1) { - arr[i].children = []; - arr[i].thread = arr[i].thread + '-summary data'; - result.push(arr[i]); - // 标志判定当前数组的长度,也可用.length判断 - flag++; - } else { - // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建 - if (result[flag].children![arr[i].cpu] === undefined) { - result[flag].children![arr[i].cpu] = { - thread: 'summary data', - consumption: 0, - cpu: arr[i].cpu, - frequency: -1, - dur: 0, - percent: 0, - children: [], - }; + // @ts-ignore + if (arr[i].frequency < 0) { + arr[i].frequency = ""; } - // 每有一条数据要放到cpu分组下时,则将该cpu分组的各项数据累和 - result[flag].children![arr[i].cpu].consumption += arr[i].consumption; - result[flag].children![arr[i].cpu].dur += arr[i].dur; - result[flag].children![arr[i].cpu].percent += arr[i].percent; - // 查找当前cpu分组下是否存在与当前数据的频点相同的数据,返回相同数据的索引值 - let index: number = result[flag].children![arr[i].cpu].children?.findIndex( - (item) => item.frequency === arr[i].frequency - )!; - // 若存在相同频点的数据,则进行合并,不同直接push - if (index === -1) { - arr[i].thread = 'summary data'; - result[flag].children![arr[i].cpu].children?.push(arr[i]); + if (!arr[i].cpuload) { + // @ts-ignore + arr[i].cpuload = '0.000000'; } else { - result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption; - result[flag].children![arr[i].cpu].children![index].dur += arr[i].dur; - result[flag].children![arr[i].cpu].children![index].percent += arr[i].percent; + // @ts-ignore + arr[i].cpuload = arr[i].cpuload.toFixed(MIN_POWER); } + // @ts-ignore + arr[i].percent = arr[i].percent.toFixed(MIN_PERCENT); + // @ts-ignore + arr[i].dur = (arr[i].dur / TIME_MUTIPLE).toFixed(MIN_FREQ); + // @ts-ignore + arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ); + // @ts-ignore + arr[i].consumpower = (arr[i].consumpower / TIME_MUTIPLE).toFixed(MIN_FREQ); + if (arr[i].frequency !== "") { + if (arr[i].frequency === "unknown") { + arr[i].frequency = "unknown"; + } else { + arr[i].frequency = arr[i].frequency; + } + } + this.fixedDeal(arr[i].children!, traceId); } } - return result; -} - -/** - * - * @param arr1 前次整理好的区分线程的数据 - * @param arr2 不区分线程的Total数据 - */ -function mergeTotal(arr1: Array, arr2: Array): void { - for (let i = 0; i < arr1.length; i++) { - const num: number = arr2.findIndex((item) => item.thread?.includes(arr1[i].thread!)); - arr2[num].thread = 'summary data'; - arr1[i].children?.unshift(arr2[num]); - arr2.splice(num, 1); - } -} +} \ No newline at end of file diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts index c1b929cc..02df4c38 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts @@ -111,6 +111,8 @@ export interface RunningFreqData { consumption: number; frequency: number | string; percent: number; + consumpower: number; + cpuload: number; children?: Array; } diff --git a/ide/src/trace/database/TabPaneFreqUsageWorker.ts b/ide/src/trace/database/TabPaneFreqUsageWorker.ts new file mode 100644 index 00000000..6ec42caf --- /dev/null +++ b/ide/src/trace/database/TabPaneFreqUsageWorker.ts @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2024 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 { type CpuFreqData, type RunningFreqData, type RunningData } from '../component/trace/sheet/frequsage/TabPaneFreqUsageConfig'; + +let comPower = new Map>(); +let resultArray: Array = []; +let timeZones: number = 0; +let maxCommpuPower: number = 0; + +/** + * + * @param args.runData 数据库查询上来的running数据,此函数会将数据整理成map结构,分组规则:'pid_tid'为键,running数据数字为值 + * @returns 返回map对象及所有running数据的dur和,后续会依此计算百分比 + */ +function orgnazitionMap( + args: { + runData: Array; + cpuFreqData: Array; + leftNs: number; + rightNs: number; + cpuArray: number[]; + } +): Array { + let result: Map> = new Map(); + let sum: number = 0; + // 循环分组 + for (let i = 0; i < args.runData.length; i++) { + let mapKey: string = args.runData[i].pid + "_" + args.runData[i].tid; + // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值 + if (!result.has(mapKey)) { + result.set(mapKey, new Array()); + } + // 整理左右边界数据问题, 因为涉及多线程,所以必须放在循环里 + if ( + args.runData[i].ts < args.leftNs && + args.runData[i].ts + args.runData[i].dur > args.leftNs + ) { + args.runData[i].dur = args.runData[i].ts + args.runData[i].dur - args.leftNs; + args.runData[i].ts = args.leftNs; + } + if (args.runData[i].ts + args.runData[i].dur > args.rightNs) { + args.runData[i].dur = args.rightNs - args.runData[i].ts; + } + // 特殊处理数据表中dur为负值的情况 + if (args.runData[i].dur < 0) { + args.runData[i].dur = 0; + } + // 分组整理数据 + result.get(mapKey)?.push({ + pid: args.runData[i].pid, + tid: args.runData[i].tid, + cpu: args.runData[i].cpu, + dur: args.runData[i].dur, + ts: args.runData[i].ts, + }); + sum += args.runData[i].dur; + } + return dealCpuFreqData(args.cpuFreqData, result, sum, args.cpuArray); +} + +/** + * + * @param cpuFreqData cpu频点数据的数组 + * @param result running数据的map对象 + * @param sum running数据的时间和 + * @returns 返回cpu频点数据map,'pid_tid'为键,频点算力值数据的数组为值 + */ +function dealCpuFreqData( + cpuFreqData: Array, + result: Map>, + sum: number, + cpuList: number[] +): Array { + let runningFreqData: Map> = new Map(); + result.forEach((item, key) => { + let resultList: Array = new Array(); + for (let i = 0; i < item.length; i++) { + for (let j = 0; j < cpuFreqData.length; j++) { + let flag: number; + if (item[i].cpu === cpuFreqData[j].cpu) { + // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间小于频点结束时间减去running数据开始时间的差值的情况 + if ( + item[i].ts > cpuFreqData[j].ts && + item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && + item[i].dur < cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts + ) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 1))!); + item.splice(i, 1); + i--; + break; + } + if ( + item[i].ts > cpuFreqData[j].ts && + item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && + item[i].dur >= cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts + ) { + // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间大于等于频点结束时间减去running数据开始时间的差值的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 2))!); + } + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值小于频点数据持续时间的情况 + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur > cpuFreqData[j].ts && + item[i].dur + item[i].ts - cpuFreqData[j].ts < cpuFreqData[j].dur + ) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 3))!); + item.splice(i, 1); + i--; + break; + } + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur > cpuFreqData[j].ts && + item[i].dur + item[i].ts - cpuFreqData[j].ts >= cpuFreqData[j].dur + ) { + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值大于等于频点数据持续时间的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 4))!); + } + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur <= cpuFreqData[j].ts + ) { + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间小于等于频点开始时间的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); + item.splice(i, 1); + i--; + break; + } + } else { + if (!cpuList.includes(item[i].cpu)) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); + item.splice(i, 1); + i--; + break; + } + } + } + } + runningFreqData.set(key, mergeSameData(resultList)); + }); + return dealTree(runningFreqData); +} + +/** + * + * @param item running数据 + * @param cpuFreqData 频点数据 + * @param sum running总和 + * @param flag 标志位,根据不同值返回不同结果 + * @returns 返回新的对象 + */ +function returnObj( + item: RunningData, + cpuFreqData: CpuFreqData, + sum: number, + flag: number +): RunningFreqData | undefined { + const PERCENT: number = 100; + const FREQ_MUTIPLE: number = 1000; + const computorPower: number = comPower ? comPower.get(item.cpu)?.get(cpuFreqData.value)! : 0; + switch (flag) { + case 1: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * item.dur, + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: item.dur, + percent: (item.dur / sum) * PERCENT, + consumpower: computorPower * item.dur, + cpuload: (computorPower * item.dur) / (timeZones * maxCommpuPower) * PERCENT + }; + case 2: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * (cpuFreqData.ts + cpuFreqData.dur - item.ts), + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: cpuFreqData.ts + cpuFreqData.dur - item.ts, + percent: ((cpuFreqData.ts + cpuFreqData.dur - item.ts) / sum) * PERCENT, + consumpower: computorPower * (cpuFreqData.ts + cpuFreqData.dur - item.ts), + cpuload: (computorPower * (cpuFreqData.ts + cpuFreqData.dur - item.ts)) / (timeZones * maxCommpuPower) * PERCENT + }; + case 3: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * (item.dur + item.ts - cpuFreqData.ts), + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: item.dur + item.ts - cpuFreqData.ts, + percent: ((item.dur + item.ts - cpuFreqData.ts) / sum) * PERCENT, + consumpower: computorPower * (item.dur + item.ts - cpuFreqData.ts), + cpuload: (computorPower * (item.dur + item.ts - cpuFreqData.ts)) / (timeZones * maxCommpuPower) * PERCENT + }; + case 4: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * cpuFreqData.dur, + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: cpuFreqData.dur, + percent: (cpuFreqData.dur / sum) * PERCENT, + consumpower: computorPower * cpuFreqData.dur, + cpuload: (computorPower * cpuFreqData.dur) / (timeZones * maxCommpuPower) * PERCENT + }; + case 5: + return { + thread: item.pid + "_" + item.tid, + consumption: 0, + cpu: item.cpu, + frequency: "unknown", + dur: item.dur, + percent: (item.dur / sum) * PERCENT, + consumpower: 0, + cpuload: 0 + }; + } +} + +/** + * + * @param resultList 单线程内running数据与cpu频点数据整合成的数组 + */ +function mergeSameData( + resultList: Array +): Array { + let cpuFreqArr: Array = []; + let cpuArr: Array = []; + //合并同一线程内,当运行所在cpu和频点相同时,dur及percent进行累加求和 + for (let i = 0; i < resultList.length; i++) { + if (!cpuArr.includes(resultList[i].cpu)) { + cpuArr.push(resultList[i].cpu); + cpuFreqArr.push(creatNewObj(resultList[i].cpu)); + } + for (let j = i + 1; j < resultList.length; j++) { + if ( + resultList[i].cpu === resultList[j].cpu && + resultList[i].frequency === resultList[j].frequency + ) { + resultList[i].dur += resultList[j].dur; + resultList[i].percent += resultList[j].percent; + resultList[i].consumption += resultList[j].consumption; + resultList[i].consumpower += resultList[j].consumpower; + resultList[i].cpuload += resultList[j].cpuload; + resultList.splice(j, 1); + j--; + } + } + cpuFreqArr.find(function (item) { + if (item.cpu === resultList[i].cpu) { + item.children?.push(resultList[i]); + item.children?.sort((a, b) => b.consumption - a.consumption); + item.dur += resultList[i].dur; + item.percent += resultList[i].percent; + item.consumption += resultList[i].consumption; + item.consumpower += resultList[i].consumpower; + item.cpuload += resultList[i].cpuload; + item.thread = resultList[i].thread; + } + }); + } + cpuFreqArr.sort((a, b) => a.cpu - b.cpu); + return cpuFreqArr; +} + +/** + * + * @param params cpu层级的数据 + * @returns 整理好的进程级数据 + */ +function dealTree( + params: Map> +): Array { + let result: Array = []; + params.forEach((item, key) => { + let process: RunningFreqData = creatNewObj(-1, false); + let thread: RunningFreqData = creatNewObj(-2); + for (let i = 0; i < item.length; i++) { + thread.children?.push(item[i]); + thread.dur += item[i].dur; + thread.percent += item[i].percent; + thread.consumption += item[i].consumption; + thread.consumpower += item[i].consumpower; + thread.cpuload += item[i].cpuload; + thread.thread = item[i].thread; + } + process.children?.push(thread); + process.dur += thread.dur; + process.percent += thread.percent; + process.consumption += thread.consumption; + process.consumpower += thread.consumpower; + process.cpuload += thread.cpuload; + process.thread = process.thread! + key.split("_")[0]; + result.push(process); + }); + for (let i = 0; i < result.length; i++) { + for (let j = i + 1; j < result.length; j++) { + if (result[i].thread === result[j].thread) { + result[i].children?.push(result[j].children![0]); + result[i].dur += result[j].dur; + result[i].percent += result[j].percent; + result[i].consumption += result[j].consumption; + result[i].consumpower += result[j].consumpower; + result[i].cpuload += result[j].cpuload; + result.splice(j, 1); + j--; + } + } + } + return result; +} + +/** + * + * @param cpu 根据cpu值创建层级结构,cpu < 0为线程、进程层级,其余为cpu层级 + * @returns + */ +function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData { + return { + thread: flag ? "" : "P", + consumption: 0, + cpu: cpu, + frequency: -1, + dur: 0, + percent: 0, + children: [], + consumpower: 0, + cpuload: 0 + }; +} + +/** + * + * @param arr 需要整理汇总的频点级数据 + * @returns 返回一个total->cpu->频点的三级树结构数组 + */ +function fixTotal(arr: Array): Array { + let result: Array = []; + let flag: number = -1; + // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割 + for (let i = 0; i < arr.length; i++) { + // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据 + if (arr[i].thread?.indexOf("P") !== -1) { + arr[i].children = []; + arr[i].thread = arr[i].thread + "-summary data"; + result.push(arr[i]); + // 标志判定当前数组的长度,也可用.length判断 + flag++; + } else { + // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建 + if (result[flag].children![arr[i].cpu] === undefined) { + result[flag].children![arr[i].cpu] = { + thread: "summary data", + consumption: 0, + cpu: arr[i].cpu, + frequency: -1, + dur: 0, + percent: 0, + children: [], + consumpower: 0, + cpuload: 0 + }; + } + // 每有一条数据要放到cpu分组下时,则将该cpu分组的各项数据累和 + result[flag].children![arr[i].cpu].consumption += arr[i].consumption; + result[flag].children![arr[i].cpu].consumpower += arr[i].consumpower; + result[flag].children![arr[i].cpu].cpuload += arr[i].cpuload; + result[flag].children![arr[i].cpu].dur += arr[i].dur; + result[flag].children![arr[i].cpu].percent += arr[i].percent; + // 查找当前cpu分组下是否存在与当前数据的频点相同的数据,返回相同数据的索引值 + let index: number = result[flag].children![ + arr[i].cpu + ].children?.findIndex((item) => item.frequency === arr[i].frequency)!; + // 若存在相同频点的数据,则进行合并,不同直接push + if (index === -1) { + arr[i].thread = "summary data"; + result[flag].children![arr[i].cpu].children?.push(arr[i]); + } else { + result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption; + result[flag].children![arr[i].cpu].children![index].consumpower += arr[i].consumpower; + result[flag].children![arr[i].cpu].children![index].dur += arr[i].dur; + result[flag].children![arr[i].cpu].children![index].percent += arr[i].percent; + result[flag].children![arr[i].cpu].children![index].cpuload += arr[i].cpuload; + } + } + } + return result; +} + +/** + * + * @param arr1 前次整理好的区分线程的数据 + * @param arr2 不区分线程的Total数据 + */ +function mergeTotal( + arr1: Array, + arr2: Array +): void { + for (let i = 0; i < arr1.length; i++) { + const num: number = arr2.findIndex((item) => + item.thread?.includes(arr1[i].thread!) + ); + arr2[num].thread = "summary data"; + arr1[i].children?.unshift(arr2[num]); + arr2.splice(num, 1); + } +} + + + /** + * + * @param arr 待整理的数组,会经过递归取到最底层的数据 + */ +function recursion(arr: Array): void { + for (let idx = 0; idx < arr.length; idx++) { + if (arr[idx].cpu === -1) { + resultArray.push(arr[idx]); + } + if (arr[idx].children) { + recursion(arr[idx].children!); + } else { + resultArray.push(arr[idx]); + } + } + } + +self.onmessage = (e: MessageEvent): void => { + comPower = e.data.comPower; + resultArray = []; + timeZones = e.data.rightNs - e.data.leftNs; + maxCommpuPower = 0; + if (comPower) { + comPower.forEach(item => { + let maxFreq = 0; + let commpuPower = 0; + for (const i of item.entries()) { + if (i[0] > maxFreq) { + maxFreq = i[0]; + commpuPower = i[1]; + } + } + maxCommpuPower += commpuPower; + }); + } + let result = orgnazitionMap(e.data); + recursion(result); + resultArray = JSON.parse(JSON.stringify(resultArray)); + mergeTotal(result, fixTotal(resultArray)); + self.postMessage(result); +}; \ No newline at end of file diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 18a48438..207ec401 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -104,6 +104,12 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { case 'scheduling-Thread Freq': this.schedulingThreadFreq(data); break; + case 'scheduling-Process Top10Swicount': + this.schedulingProTop10Swicount(data); + break; + case 'scheduling-Process Top10RunTime': + this.schedulingProcessRunTime(data); + break; } } private schedulingClearData(data: { id: string; action: string; params: unknown }): void { @@ -352,6 +358,40 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { this.queryThreadStateByTid(data.params.tid); } } + private schedulingProTop10Swicount(data: any): void { + if (data.params.list) { + let arr = convertJSON(data.params.list) || []; + self.postMessage({ + id: data.id, + action: data.action, + results: arr, + }); + arr = []; + } else { + if (data.params.pid) { + this.queryThrTop10Swicount(data.params.pid); + } else { + this.queryProTop10Swicount(); + } + } + } + private schedulingProcessRunTime(data: any): void { + if (data.params.list) { + let arr = convertJSON(data.params.list) || []; + self.postMessage({ + id: data.id, + action: data.action, + results: arr, + }); + arr = []; + } else { + if (data.params.pid) { + this.queryThrTop10RunTime(data.params.pid); + } else { + this.queryProTop10RunTime(); + } + } + } getProcessAndThread(): void { this.queryData( this.currentEventId, @@ -538,6 +578,88 @@ where cpu not null this.queryData(this.currentEventId, 'scheduling-Thread Freq', sql, {}); } + queryProTop10Swicount() { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10Swicount', + ` + select + pid, + count(tid) as occurrences + from + thread_state + where + state = 'Running' + group by + pid + ORDER BY occurrences desc + LIMIT 10 + `, + {} + ); + } + queryThrTop10Swicount(pid: number) { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10Swicount', + ` + select + tid, + count(tid) as occurrences + from + thread_state + where + state = 'Running' + and pid = ${pid} + group by + tid + ORDER BY occurrences desc + LIMIT 10 + `, + {} + ); + } + queryProTop10RunTime() { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10RunTime', + ` + select + pid, + SUM(dur) As dur + from + thread_state + where + state = 'Running' + GROUP BY pid + ORDER BY dur desc + LIMIT 10 + `, + {} + ); + } + queryThrTop10RunTime(pid: number) { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10RunTime', + ` + select + tid, + SUM(dur) As dur + from + thread_state + where + state = 'Running' + and + pid = ${pid} + GROUP BY tid + ORDER BY dur desc + LIMIT 10 + `, + {} + ); + } + groupIrgDataByCpu(arr: Irq[]): Map { //首先计算 每个频点的持续时间,并根据Cpu来分组 let map: Map> = new Map>(); -- Gitee From e02e5d2cbaae1775fafca76654ab0005f066021a Mon Sep 17 00:00:00 2001 From: liufei Date: Wed, 7 Aug 2024 16:54:18 +0800 Subject: [PATCH 05/85] =?UTF-8?q?fix:native=5Fmemory=E5=BC=B9=E7=AA=97=5Fc?= =?UTF-8?q?all=20info=20=E7=AC=AC=E4=B8=89=E4=B8=AA=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=A1=86=E7=AD=9B=E9=80=89=E4=B8=8D=E5=88=B0=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/component/trace/base/Utils.ts | 2 +- ide/src/trace/database/sql/NativeHook.sql.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index 7a1d41d1..68590dd0 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -715,7 +715,7 @@ export class Utils { } } } - queryNativeHookResponseTypes(val.leftNs, val.rightNs, types, isStatistic).then((res): void => { + queryNativeHookResponseTypes(val.leftNs, val.rightNs, types,val.nativeMemoryCurrentIPid, isStatistic).then((res): void => { procedurePool.submitWithName('logic0', 'native-memory-init-responseType', res, undefined, (): void => { }); }); } diff --git a/ide/src/trace/database/sql/NativeHook.sql.ts b/ide/src/trace/database/sql/NativeHook.sql.ts index 0d84706a..05822a5a 100644 --- a/ide/src/trace/database/sql/NativeHook.sql.ts +++ b/ide/src/trace/database/sql/NativeHook.sql.ts @@ -19,9 +19,10 @@ export const queryNativeHookResponseTypes = ( leftNs: number, rightNs: number, types: Array, + pid: number, isStatistic: boolean ): //@ts-ignore -Promise> => { + Promise> => { const table = isStatistic ? 'native_hook_statistic' : 'native_hook'; const tsKey = isStatistic ? 'ts' : 'start_ts'; const type = isStatistic ? 'type' : 'event_type'; @@ -36,9 +37,10 @@ Promise> => { left join data_dict on A.last_lib_id = data_dict.id where A.${tsKey} - B.start_ts - between ${leftNs} and ${rightNs} and A.${type} in (${types.join(',')}); + between ${leftNs} and ${rightNs} and A.${type} in (${types.join(',')}) + and A.ipid = ${pid}; `, - { $leftNs: leftNs, $rightNs: rightNs, $types: types } + { $leftNs: leftNs, $rightNs: rightNs, $types: types, $pid: pid } ); }; export const queryNativeHookStatistics = ( @@ -136,7 +138,7 @@ export const queryNativeHookSubType = ( rightNs: number, ipid: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryNativeHookSubType', `select distinct( @@ -159,7 +161,7 @@ export const queryNativeHookStatisticSubType = ( rightNs: number, ipid: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryNativeHookStatisticSubType', `SELECT DISTINCT -- Gitee From a444c3ee5d476b10903c65a6e1185715e0b33ed2 Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Thu, 8 Aug 2024 10:06:05 +0800 Subject: [PATCH 06/85] =?UTF-8?q?=E6=A1=86=E9=80=89=E5=90=8E=E9=80=89?= =?UTF-8?q?=E6=8B=A9name=E6=90=9C=E7=B4=A2=E5=90=8E=EF=BC=8C=E6=A1=86?= =?UTF-8?q?=E9=80=89=E5=8C=BA=E5=9F=9F=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8B=96?= =?UTF-8?q?=E5=8A=A8=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/component/SpSystemTrace.event.ts | 6 ++++-- ide/src/trace/component/SpSystemTrace.init.ts | 1 + ide/src/trace/component/SpSystemTrace.ts | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index e633f754..355c6a0b 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -518,12 +518,14 @@ export function spSystemTraceDocumentOnMouseMove(sp: SpSystemTrace, ev: MouseEve handleActions(sp, rows, ev); } -function spSystemTraceDocumentOnMouseMoveMouseDown(sp: SpSystemTrace, search: LitSearch): void { +export function spSystemTraceDocumentOnMouseMoveMouseDown(sp: SpSystemTrace, search: LitSearch): void { sp.refreshCanvas(true, 'sp move down'); if (TraceRow.rangeSelectObject) { if (search && search.searchValue !== '') { search.clear(); - search.valueChangeHandler?.(''); + sp?.visibleRows.forEach((it) => { + it.highlight = false; + }); } } } diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 6a73dbc2..39805c3a 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -579,6 +579,7 @@ function selectHandlerRows(sp: SpSystemTrace, rows: Array>): v } sp.timerShaftEL!.selectionList.push(selection); // 保持选中对象,为后面的再次选中该框选区域做准备。 sp.selectionParam = selection; + sp.refreshCanvas(true); } function resizeObserverHandler(sp: SpSystemTrace): void { // @ts-ignore diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 74c89360..ccbaf82b 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -125,6 +125,7 @@ import spSystemTraceOnClickHandler, { spSystemTraceDocumentOnMouseMove, spSystemTraceDocumentOnMouseOut, spSystemTraceDocumentOnMouseUp, + spSystemTraceDocumentOnMouseMoveMouseDown, } from './SpSystemTrace.event'; import { SampleStruct } from '../database/ui-worker/ProcedureWorkerBpftrace'; import { readTraceFileBuffer } from '../SpApplicationPublicFunc'; @@ -132,6 +133,7 @@ import { PerfToolStruct } from '../database/ui-worker/ProcedureWorkerPerfTool'; import { BaseStruct } from '../bean/BaseStruct'; import { GpuCounterStruct } from '../database/ui-worker/ProcedureWorkerGpuCounter'; import { SpProcessChart } from './chart/SpProcessChart'; +import { LitSearch } from './trace/search/Search'; function dpr(): number { return window.devicePixelRatio || 1; @@ -1164,6 +1166,10 @@ export class SpSystemTrace extends BaseElement { } } this.rangeTraceRow = rows; + let search = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#lit-search'); + if(search?.isClearValue) { + spSystemTraceDocumentOnMouseMoveMouseDown(this, search!); + } this.rangeSelect.selectHandler?.(this.rangeSelect.rangeTraceRow, false); }; inFavoriteArea: boolean | undefined; -- Gitee From bf94d6b818907daa5a8a560fea50c8f0945a2521 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Thu, 8 Aug 2024 20:16:42 +0800 Subject: [PATCH 07/85] =?UTF-8?q?feat:=E5=B0=86=E7=94=B1hash=E8=AE=A1?= =?UTF-8?q?=E7=AE=97func=E6=B3=B3=E9=81=93=E9=A2=9C=E8=89=B2=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=9B=B4=E6=8E=A5=E6=8B=BF=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../trace/component/chart/SpChartManager.ts | 4 ++ .../trace/component/trace/base/ColorUtils.ts | 40 +++++++++---------- ide/src/trace/database/sql/Func.sql.ts | 15 ++++++- .../database/ui-worker/ProcedureWorkerFunc.ts | 16 ++++++-- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index be2ac7e0..e671d995 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -340,11 +340,15 @@ export class SpChartManager { funcNameArray.forEach((it) => { //@ts-ignore Utils.getInstance().getCallStatckMap().set(`${traceId}_${it.id!}`, it.name); + //@ts-ignore + Utils.getInstance().getCallStatckMap().set(it.name, it.colorIndex); }); } else { funcNameArray.forEach((it) => { //@ts-ignore Utils.getInstance().getCallStatckMap().set(it.id, it.name); + //@ts-ignore + Utils.getInstance().getCallStatckMap().set(it.name, it.colorIndex); }); } } diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index 12964507..700a0058 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -19,48 +19,48 @@ export class ColorUtils { public static GREY_COLOR: string = '#f0f0f0'; public static FUNC_COLOR_A: Array = [ - '#8770D3', '#A37775', + '#76D1C0', '#0CBDD4', - '#7DA6F4', - '#A56DF5', - '#E86B6A', + '#ADB7DB', '#69D3E5', - '#998FE6', + '#7DA6F4', + '#B1CDF1', + '#E68C43', '#E3AA7D', - '#76D1C0', + '#36BAA4', + '#E86B6A', '#99C47C', + '#998FE6', + '#8770D3', '#DC8077', - '#36BAA4', '#A1CD94', - '#E68C43', '#66C7BA', - '#B1CDF1', '#E7B75D', + '#A56DF5', '#93D090', - '#ADB7DB', ]; public static FUNC_COLOR_B: Array = [ - '#40b3e7', '#23b0e7', + '#4ca694', '#8d9171', - '#FF0066', - '#7a9160', - '#9fafc4', + '#8091D0', '#8a8a8b', - '#8983B5', + '#FF0066', + '#aa4fba', + '#a16a40', '#78aec2', - '#4ca694', + '#ebc247', + '#9fafc4', '#e05b52', + '#8983B5', + '#40b3e7', '#9bb87a', - '#ebc247', '#c2cc66', - '#a16a40', '#a94eb9', - '#aa4fba', '#B9A683', + '#7a9160', '#789876', - '#8091D0', ]; public static ANIMATION_COLOR: Array = [ diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index 44d8f1dd..229c7ee5 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -110,11 +110,24 @@ export const querySingleFuncNameCycle = ( ); export const queryAllFuncNames = async (traceId?: string): Promise> => { + let list = await query( + 'queryIsColorIndex', + `select + colorIndex + from + callstack + limit 1;`, + {}, + { traceId: traceId, action: 'exec-buf' } + ); + let isColorIndex = list.length !== 0 ? true : false; + let colorIndexStr = isColorIndex ? ',colorIndex' : ''; let allFuncNamesBuffer = await query( 'queryAllFuncNames', `select id, - name + name + ${colorIndexStr} from callstack;`, {}, diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts index 06d4e5fe..95d3ee60 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts @@ -28,6 +28,7 @@ import { FuncStruct as BaseFuncStruct } from '../../bean/FuncStruct'; import { FlagsConfig } from '../../component/SpFlags'; import { TabPaneTaskFrames } from '../../component/trace/sheet/task/TabPaneTaskFrames'; import { SpSystemTrace } from '../../component/SpSystemTrace'; +import { Utils } from '../../component/trace/base/Utils'; export class FuncRender { renderMainThread( @@ -189,6 +190,7 @@ export function funcStructOnClick( }); } export class FuncStruct extends BaseFuncStruct { + static textColor: string; [x: string]: unknown; static hoverFuncStruct: FuncStruct | undefined; static selectFuncStruct: FuncStruct | undefined; @@ -236,14 +238,22 @@ export class FuncStruct extends BaseFuncStruct { if (data.dur === undefined || data.dur === null) { } else { ctx.globalAlpha = 1; - ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; - let textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + //@ts-ignore + if (Utils.getInstance().getCallStatckMap().get(data.funName) !== undefined) { + //@ts-ignore + ctx.fillStyle = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; + //@ts-ignore + this.textColor = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; + } else { + ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + this.textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + } if (FuncStruct.hoverFuncStruct && data.funName === FuncStruct.hoverFuncStruct.funName) { ctx.globalAlpha = 0.7; } ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); if (data.frame.width > 10) { - ctx.fillStyle = ColorUtils.funcTextColor(textColor); + ctx.fillStyle = ColorUtils.funcTextColor(this.textColor); ctx.textBaseline = 'middle'; drawFunString(ctx, `${data.funName || ''}`, 5, data.frame, data); } -- Gitee From bb0982d1ad8a13fd0fa1c9d324dcf6158e4b5006 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Fri, 9 Aug 2024 09:58:49 +0800 Subject: [PATCH 08/85] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= =?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: danghongquan --- ide/src/trace/component/SpSystemTrace.init.ts | 90 ++++++++++--------- ide/src/trace/component/SpSystemTrace.ts | 5 +- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 6a73dbc2..a3295d6e 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -853,8 +853,7 @@ export function spSystemTraceShowStruct( return 0; } let findIndex = spSystemTraceShowStructFindIndex(previous, currentIndex, structs, retargetIndex); - let findEntry: unknown; - findEntry = structs[findIndex]; + let findEntry: unknown = structs[findIndex]; let currentEntry: unknown = undefined; if (currentIndex >= 0) { currentEntry = structs[currentIndex]; @@ -872,49 +871,60 @@ function spSystemTraceShowStructFindIndex( const rangeStart = TraceRow.range!.startNS; const rangeEnd = TraceRow.range!.endNS; let findIndex = -1; - if (retargetIndex) { + if (retargetIndex) {//如果Go有值,直接跳转 findIndex = retargetIndex - 1; } else if (previous) { - for (let i = structs.length - 1; i >= 0; i--) { - let it = structs[i]; - // @ts-ignore - if ((i < currentIndex && it.startTime! >= rangeStart && it.startTime! + it.dur! <= rangeEnd) || - // @ts-ignore - (it.startTime! + it.dur! < rangeStart)) { - findIndex = i; - break; - } + //case1:current.start在start边界以右,需要从当前项往第一项遍历,找到structs[index].start < end + //@ts-ignore + if (structs[currentIndex].startTime! >= rangeStart) { + findIndex = findPreviousOne(currentIndex - 1, 0, structs); + //处理当前项如果是第一项 + findIndex = findIndex === -1 ? structs.length - 1 : findIndex; + } else { + //case2:current.start在start边界以左,需要从最后一项到当前项遍历,找到structs[index].start < end + findIndex = findPreviousOne(structs.length - 1, currentIndex + 1, structs); } - if (findIndex === -1) { - findIndex = structs.length - 1; + } else {//向后查找 + if (currentIndex === -1) {//输入框内输入内容后第一次搜索 + findIndex = findNextOne(0, structs.length - 1, structs); + //处理当所有的项都在start以左 + return findIndex === -1 ? 0 : findIndex; } - } else { - if (currentIndex > 0) { - if (rangeStart > SpSystemTrace.currentStartTime) { - SpSystemTrace.currentStartTime = rangeStart; - } - //右移rangeStart变小重新赋值 - if (SpSystemTrace.currentStartTime > rangeStart) { - SpSystemTrace.currentStartTime = rangeStart;//currentIndex不在可视区时,currentIndex = -1 - if ( - // @ts-ignore - structs[currentIndex].startTime < rangeStart || - // @ts-ignore - structs[currentIndex].startTime! + structs[currentIndex].dur! > rangeEnd - ) { - currentIndex = -1; - } - } + //case1:current.start 在end左侧 从当前项到最后一项遍历,找到startTime>start + //@ts-ignore + if (structs[currentIndex].startTime! < rangeEnd) {//case1 + findIndex = findNextOne(currentIndex + 1, structs.length - 1, structs); + //处理当前项是最后一项 + findIndex = findIndex === -1 ? 0 : findIndex; + } else { + //case2: current.start 在end右侧 从第一项到当前项遍历,找到startTime>start + findIndex = findNextOne(0, currentIndex - 1, structs); } - //在数组中查找比currentIndex大且在range范围内的第一个下标,如果range范围内没有返回-1 - findIndex = structs.findIndex((it, idx) => { - // @ts-ignore - return ((idx > currentIndex && it.startTime! >= rangeStart && it.startTime! + it.dur! <= rangeEnd) || - // @ts-ignore - (it.startTime! > rangeEnd)); - }); - if (findIndex === -1) { - findIndex = 0; + } + return findIndex; +} +//向前查找逻辑 +function findPreviousOne(start: number, end: number, structs: Array): number { + let findIndex = -1; + const rangeEnd = TraceRow.range!.endNS; + for (let i = start; i >= end; i--) { + let it = structs[i]; + if (it.startTime! < rangeEnd) { + findIndex = i; + break; + } + } + return findIndex; +} +//向后查找 +function findNextOne(start: number, end: number, structs: Array): number { + let findIndex = -1; + const rangeStart = TraceRow.range!.startNS; + for (let i = start; i <= end; i++) { + let it = structs[i]; + if (it.startTime > rangeStart) { + findIndex = i; + break; } } return findIndex; diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 0970fb15..557f05ff 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -2270,8 +2270,9 @@ export class SpSystemTrace extends BaseElement { return dataList; } - showStruct(previous: boolean, currentIndex: number, structs: Array, retargetIndex?: number): number { - return spSystemTraceShowStruct(this, previous, currentIndex, structs, retargetIndex); + showStruct(previous: boolean, currentIndex: number, structs: Array, retargetIndex?: number): number { + let tagIndex = spSystemTraceShowStruct(this, previous, currentIndex, structs, retargetIndex); + return tagIndex === -1?currentIndex:tagIndex; } private toTargetDepth = (entry: unknown, funcRowID: string, funcStract: unknown): void => { -- Gitee From 0a4889904416c7c0a1c2269b49b27191da0bd3f7 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Fri, 9 Aug 2024 11:35:27 +0800 Subject: [PATCH 09/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A1=86=E9=80=89?= =?UTF-8?q?=E5=B8=A7=E7=8E=87=E6=98=BE=E7=A4=BA=E8=B4=9F=E6=95=B0=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/trace/base/RangeSelect.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/base/RangeSelect.ts b/ide/src/trace/component/trace/base/RangeSelect.ts index 934c3633..505e71b3 100644 --- a/ide/src/trace/component/trace/base/RangeSelect.ts +++ b/ide/src/trace/component/trace/base/RangeSelect.ts @@ -78,10 +78,10 @@ export class RangeSelect { TraceRow.rangeSelectObject = undefined; // 遍历当前可视区域所有的泳道,如果有render_service进程,查询该进程下对应泳道的方法存起来,以便框选时直接使用 this.trace?.visibleRows.forEach((row) => { - if (row.getAttribute('name')?.startsWith('render_service')) { + if (row.getAttribute('name')?.includes('render_service') || row.parentRowEl?.getAttribute('name')?.includes('render_service')) { if (row.getAttribute('row-type') === 'process') { this.queryRowsData(row.childrenList); - } else { + } else if(row.getAttribute('row-type') === 'func'){ this.queryRowsData(row.parentRowEl!.childrenList); } return; @@ -221,6 +221,9 @@ export class RangeSelect { if (row.frameRateList?.length < 2) { row.frameRateList = []; } else { + if (row.frameRateList[row.frameRateList.length - 1] === null) { + row.frameRateList.pop(); + } let hitchTimeList: Array = []; for (let i = 0; i < SpLtpoChart.sendHitchDataArr.length; i++) { if ( -- Gitee From 9c07dde3973236cf240f5705e7d736e85df80d9a Mon Sep 17 00:00:00 2001 From: leishaogang Date: Thu, 8 Aug 2024 10:05:36 +0800 Subject: [PATCH 10/85] adapt hdc signature Signed-off-by: leishaogang --- ide/server/main.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ide/server/main.go b/ide/server/main.go index dfd1fbdf..80a242fb 100644 --- a/ide/server/main.go +++ b/ide/server/main.go @@ -20,6 +20,7 @@ import ( "crypto" "crypto/rand" "crypto/rsa" + "crypto/sha512" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -176,6 +177,7 @@ func main() { mux.HandleFunc("/application/serverInfo", serverInfo) mux.HandleFunc("/application/hdcPublicKey", getHdcPublicKey) mux.HandleFunc("/application/encryptHdcMsg", encryptHdcMsg) + mux.HandleFunc("/application/signatureHdcMsg", signatureHdcMsg) fs := http.FileServer(http.Dir(exPath + "/")) mux.Handle("/application/", http.StripPrefix("/application/", cors(fs, version))) go func() { @@ -278,6 +280,25 @@ func encryptHdcMsg(w http.ResponseWriter, r *http.Request) { } } +func signatureHdcMsg(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Content-Type", "text/json") + hdcMsg := r.URL.Query().Get("message") + if len(hdcMsg) == 0 { + resp(&w)(false, -1, "Invalid message", nil) + return + } + hashed := sha512.Sum512([]byte(hdcMsg)) + signatures, err := rsa.SignPKCS1v15(nil, hdcPrivateKey, crypto.SHA512, hashed[:]) + if err != nil { + resp(&w)(false, -1, "sign failed", nil) + } else { + resp(&w)(true, 0, "success", map[string]interface{}{ + "signatures": base64.StdEncoding.EncodeToString(signatures), + }) + } +} + func readReqServerConfig() string { readServerConfig, serverConfigErr := os.ReadFile(exPath + "/server-config.txt") if serverConfigErr != nil { -- Gitee From 200702904a6dd1dccc453049ec7abbf6e80b7d82 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Mon, 12 Aug 2024 14:33:48 +0800 Subject: [PATCH 11/85] =?UTF-8?q?feat:=E5=B0=86=E7=94=B1hash=E8=AE=A1?= =?UTF-8?q?=E7=AE=97func=E6=B3=B3=E9=81=93=E9=A2=9C=E8=89=B2=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=9B=B4=E6=8E=A5=E6=8B=BF=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../trace/component/chart/SpChartManager.ts | 4 ++ .../trace/component/trace/base/ColorUtils.ts | 40 +++++++++---------- ide/src/trace/database/sql/Func.sql.ts | 21 ++++++++-- .../database/ui-worker/ProcedureWorkerFunc.ts | 16 ++++++-- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index b6d0b223..5f61bbae 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -340,11 +340,15 @@ export class SpChartManager { funcNameArray.forEach((it) => { //@ts-ignore Utils.getInstance().getCallStatckMap().set(`${traceId}_${it.id!}`, it.name); + //@ts-ignore + Utils.getInstance().getCallStatckMap().set(it.name, it.colorIndex); }); } else { funcNameArray.forEach((it) => { //@ts-ignore Utils.getInstance().getCallStatckMap().set(it.id, it.name); + //@ts-ignore + Utils.getInstance().getCallStatckMap().set(it.name, it.colorIndex); }); } } diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index 12964507..700a0058 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -19,48 +19,48 @@ export class ColorUtils { public static GREY_COLOR: string = '#f0f0f0'; public static FUNC_COLOR_A: Array = [ - '#8770D3', '#A37775', + '#76D1C0', '#0CBDD4', - '#7DA6F4', - '#A56DF5', - '#E86B6A', + '#ADB7DB', '#69D3E5', - '#998FE6', + '#7DA6F4', + '#B1CDF1', + '#E68C43', '#E3AA7D', - '#76D1C0', + '#36BAA4', + '#E86B6A', '#99C47C', + '#998FE6', + '#8770D3', '#DC8077', - '#36BAA4', '#A1CD94', - '#E68C43', '#66C7BA', - '#B1CDF1', '#E7B75D', + '#A56DF5', '#93D090', - '#ADB7DB', ]; public static FUNC_COLOR_B: Array = [ - '#40b3e7', '#23b0e7', + '#4ca694', '#8d9171', - '#FF0066', - '#7a9160', - '#9fafc4', + '#8091D0', '#8a8a8b', - '#8983B5', + '#FF0066', + '#aa4fba', + '#a16a40', '#78aec2', - '#4ca694', + '#ebc247', + '#9fafc4', '#e05b52', + '#8983B5', + '#40b3e7', '#9bb87a', - '#ebc247', '#c2cc66', - '#a16a40', '#a94eb9', - '#aa4fba', '#B9A683', + '#7a9160', '#789876', - '#8091D0', ]; public static ANIMATION_COLOR: Array = [ diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index bb2d52bb..11f19304 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -110,13 +110,26 @@ export const querySingleFuncNameCycle = ( ); export const queryAllFuncNames = async (traceId?: string): Promise> => { + let list = await query( + 'queryIsColorIndex', + `select + colorIndex + from + callstack + limit 1;`, + {}, + { traceId: traceId, action: 'exec-buf' } + ); + let isColorIndex = list.length !== 0 ? true : false; + let colorIndexStr = isColorIndex ? ',colorIndex' : ''; let allFuncNamesBuffer = await query( 'queryAllFuncNames', `select - id, - name - from - callstack;`, + id, + name + ${colorIndexStr} + from + callstack;`, {}, { traceId: traceId, action: 'exec-buf' } ); diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts index 06d4e5fe..61f63319 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts @@ -28,6 +28,7 @@ import { FuncStruct as BaseFuncStruct } from '../../bean/FuncStruct'; import { FlagsConfig } from '../../component/SpFlags'; import { TabPaneTaskFrames } from '../../component/trace/sheet/task/TabPaneTaskFrames'; import { SpSystemTrace } from '../../component/SpSystemTrace'; +import { Utils } from '../../component/trace/base/Utils'; export class FuncRender { renderMainThread( @@ -189,6 +190,7 @@ export function funcStructOnClick( }); } export class FuncStruct extends BaseFuncStruct { + static textColor: string; [x: string]: unknown; static hoverFuncStruct: FuncStruct | undefined; static selectFuncStruct: FuncStruct | undefined; @@ -236,14 +238,22 @@ export class FuncStruct extends BaseFuncStruct { if (data.dur === undefined || data.dur === null) { } else { ctx.globalAlpha = 1; - ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; - let textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + //@ts-ignore + if (Utils.getInstance().getCallStatckMap().get(data.funName) !== undefined) { + //@ts-ignore + ctx.fillStyle = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; + //@ts-ignore + this.textColor = ColorUtils.FUNC_COLOR[Utils.getInstance().getCallStatckMap().get(data.funName)]; + } else { + ctx.fillStyle = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + this.textColor = ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.funName || '', 0, ColorUtils.FUNC_COLOR.length)]; + } if (FuncStruct.hoverFuncStruct && data.funName === FuncStruct.hoverFuncStruct.funName) { ctx.globalAlpha = 0.7; } ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); if (data.frame.width > 10) { - ctx.fillStyle = ColorUtils.funcTextColor(textColor); + ctx.fillStyle = ColorUtils.funcTextColor(this.textColor); ctx.textBaseline = 'middle'; drawFunString(ctx, `${data.funName || ''}`, 5, data.frame, data); } -- Gitee From 88a2119393a83304b4c11cc9b05009bc24cad4e8 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Mon, 12 Aug 2024 15:11:31 +0800 Subject: [PATCH 12/85] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dshift+m=E6=A1=86?= =?UTF-8?q?=E9=80=89=E5=90=8E=E8=BE=93=E5=85=A5remark=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=AF=BC=E8=87=B4Tab=E6=97=A0=E6=B3=95=E6=8B=96=E5=8A=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../component/chart/SpSegmentationChart.ts | 2 + .../SpSchedulingAnalysis.ts | 20 +- .../processAnalysis/TabProcessAnalysis.ts | 151 +++++ .../Top10LongestRunTimeProcess.ts | 482 ++++++++++++++ .../Top10ProcessSwitchCount.ts | 491 ++++++++++++++ .../component/trace/sheet/TabPaneCurrent.ts | 1 + .../trace/sheet/frequsage/TabPaneFreqUsage.ts | 623 +++++------------- .../sheet/frequsage/TabPaneFreqUsageConfig.ts | 2 + .../trace/database/TabPaneFreqUsageWorker.ts | 463 +++++++++++++ .../ProcedureLogicWorkerSchedulingAnalysis.ts | 121 ++++ 10 files changed, 1883 insertions(+), 473 deletions(-) create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts create mode 100644 ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts create mode 100644 ide/src/trace/database/TabPaneFreqUsageWorker.ts diff --git a/ide/src/trace/component/chart/SpSegmentationChart.ts b/ide/src/trace/component/chart/SpSegmentationChart.ts index b0d6adc2..e1db3d5f 100644 --- a/ide/src/trace/component/chart/SpSegmentationChart.ts +++ b/ide/src/trace/component/chart/SpSegmentationChart.ts @@ -24,6 +24,7 @@ import { type AllStatesRender, AllstatesStruct } from '../../database/ui-worker/ import { StateGroup } from '../../bean/StateModle'; import { queryAllFuncNames } from '../../database/sql/Func.sql'; import { Utils } from '../trace/base/Utils'; +import { TabPaneFreqUsage } from "../trace/sheet/frequsage/TabPaneFreqUsage"; const UNIT_HEIGHT: number = 20; const MS_TO_US: number = 1000000; const MIN_HEIGHT: number = 2; @@ -221,6 +222,7 @@ export class SpSegmentationChart { SpSegmentationChart.freqInfoMapData.set(v.cpuId, mapData); mapData = new Map(); }); + TabPaneFreqUsage.refresh(); } }; SpSegmentationChart.cpuRow.focusHandler = (ev): void => { diff --git a/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts b/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts index 4257627d..a258c13c 100644 --- a/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts +++ b/ide/src/trace/component/schedulingAnalysis/SpSchedulingAnalysis.ts @@ -16,6 +16,7 @@ import { BaseElement, element } from '../../../base-ui/BaseElement'; import './TabThreadAnalysis'; import './TabCpuAnalysis'; +import './processAnalysis/TabProcessAnalysis'; import { TabCpuAnalysis } from './TabCpuAnalysis'; import { TabThreadAnalysis } from './TabThreadAnalysis'; import { LitTabs } from '../../../base-ui/tabs/lit-tabs'; @@ -23,6 +24,7 @@ import { CheckCpuSetting } from './CheckCpuSetting'; import { Top20FrequencyThread } from './Top20FrequencyThread'; import { procedurePool } from '../../database/Procedure'; import { Utils } from '../trace/base/Utils'; +import { TabProcessAnalysis } from './processAnalysis/TabProcessAnalysis'; @element('sp-scheduling-analysis') export class SpSchedulingAnalysis extends BaseElement { @@ -34,11 +36,13 @@ export class SpSchedulingAnalysis extends BaseElement { private tabs: LitTabs | null | undefined; private tabCpuAnalysis: TabCpuAnalysis | null | undefined; private tabThreadAnalysis: TabThreadAnalysis | null | undefined; + private tabProcessAnalysis: TabProcessAnalysis | null | undefined; initElements(): void { this.tabs = this.shadowRoot?.querySelector('#tabs'); this.tabCpuAnalysis = this.shadowRoot?.querySelector('#cpu-analysis'); this.tabThreadAnalysis = this.shadowRoot?.querySelector('#thread-analysis'); + this.tabProcessAnalysis = this.shadowRoot?.querySelector('#process-analysis'); } static resetCpu(): void { @@ -58,6 +62,7 @@ export class SpSchedulingAnalysis extends BaseElement { SpSchedulingAnalysis.cpuCount = Utils.getInstance().getWinCpuCount(); this.tabCpuAnalysis?.init(); this.tabThreadAnalysis?.init(); + this.tabProcessAnalysis?.init(); } } @@ -95,12 +100,15 @@ export class SpSchedulingAnalysis extends BaseElement {
- - - - - - + + + + + + + + +
`; diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts new file mode 100644 index 00000000..5624ce82 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts @@ -0,0 +1,151 @@ +/* + * 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 { SpStatisticsHttpUtil } from '../../../../statistics/util/SpStatisticsHttpUtil'; +import './Top10LongestRunTimeProcess.ts'; +import './Top10ProcessSwitchCount.ts'; +import { Top10LongestRunTimeProcess } from './Top10LongestRunTimeProcess'; +import { Top10ProcessSwitchCount } from './Top10ProcessSwitchCount'; + +@element("tab-process-analysis") +export class TabProcessAnalysis extends BaseElement { + private currentTabID: string | undefined; + private currentTab: BaseElement | undefined; + private btn1: HTMLDivElement | null | undefined; + private btn2: HTMLDivElement | null | undefined; + private Top10LongestRunTimeProcess: Top10LongestRunTimeProcess | undefined | null; + private Top10ProcessSwitchCount: Top10ProcessSwitchCount | undefined | null; + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.btn1 = this.shadowRoot!.querySelector('#btn1'); + this.btn2 = this.shadowRoot!.querySelector('#btn2'); + this.Top10LongestRunTimeProcess = this.shadowRoot!.querySelector('#top10_process_runTime'); + this.Top10ProcessSwitchCount = this.shadowRoot!.querySelector('#top10_process_switchCount'); + this.btn1!.addEventListener('click', (event) => { + this.setClickTab(this.btn1!, this.Top10ProcessSwitchCount!); + }); + this.btn2!.addEventListener('click', (event) => { + this.setClickTab(this.btn2!, this.Top10LongestRunTimeProcess!); + }); + } + + /** + * 初始化操作,清空该标签页下所有面板数据,只有首次导入新trace后执行一次 + */ + init(): void { + this.Top10ProcessSwitchCount?.clearData(); + this.Top10LongestRunTimeProcess?.clearData(); + this.hideCurrentTab(); + this.currentTabID = undefined; + this.setClickTab(this.btn1!, this.Top10ProcessSwitchCount!); + } + + /** + * 隐藏当前面板,将按钮样式设置为未选中状态 + */ + hideCurrentTab(): void { + if (this.currentTabID) { + let clickTab = this.shadowRoot!.querySelector(`#${this.currentTabID}`); + if (clickTab) { + clickTab.className = 'tag_bt'; + } + } + if (this.currentTab) { + this.currentTab.style.display = 'none'; + } + } + + /** + * + * @param btn 当前点击的数据类型按钮 + * @param showContent 需要展示的面板对象 + */ + setClickTab(btn: HTMLDivElement, showContent: Top10ProcessSwitchCount | Top10LongestRunTimeProcess): void { + // 将前次点击的按钮样式设置为未选中样式状态 + if (this.currentTabID) { + let clickTab = this.shadowRoot!.querySelector(`#${this.currentTabID}`); + if (clickTab) { + clickTab.className = 'tag_bt'; + } + } + // 切换当前点击按钮的类名,应用点击样式 + btn.className = 'tab_click'; + // 切换记录的好的当前点击面板id,并设置其显隐 + if (btn.id !== this.currentTabID) { + this.currentTabID = btn.id; + if (this.currentTab) { + this.currentTab.style.display = 'none'; + } + this.currentTab = showContent; + showContent.style.display = 'inline'; + showContent.init(); + } + } + + /** + * 用于将元素节点挂载 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return ` + +
+
Top10切换次数进程
+
Top10运行超长进程
+
+
+ + +
+ `; + } +} \ No newline at end of file diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts new file mode 100644 index 00000000..38d16873 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts @@ -0,0 +1,482 @@ +/* + * 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 { procedurePool } from '../../../database/Procedure'; +import { info } from '../../../../log/Log'; +import { TableNoData } from '../TableNoData'; +import '../TableNoData'; +import { LitProgressBar } from '../../../../base-ui/progress-bar/LitProgressBar'; +import '../../../../base-ui/progress-bar/LitProgressBar'; +import { LitChartColumn } from '../../../../base-ui/chart/column/LitChartColumn'; +import '../../../../base-ui/chart/column/LitChartColumn'; +import { Utils } from '../../trace/base/Utils'; + +@element("top10-longest-runtime-process") +export class Top10LongestRunTimeProcess extends BaseElement { + traceChange: boolean = false; + private processRunTimeTbl: LitTable | null | undefined; + private threadRunTimeTbl: LitTable | null | undefined; + private processRunTimeProgress: LitProgressBar | null | undefined; + private nodataPro: TableNoData | null | undefined; + private processRunTimeData: Array = []; + private threadRunTimeData: Array = []; + private processSwitchCountChart: LitChartColumn | null | undefined; + private threadSwitchCountChart: LitChartColumn | null | undefined; + private nodataThr: TableNoData | null | undefined; + private display_pro: HTMLDivElement | null | undefined; + private display_thr: HTMLDivElement | null | undefined; + private processId: number | undefined; + private display_flag: boolean = true; + private back: HTMLDivElement | null | undefined; + private processMap: Map = new Map(); + private threadMap: Map = new Map(); + + /** + * 初始化操作,若trace发生改变,将所有变量设置为默认值并重新请求数据。若trace未改变,跳出初始化 + */ + init() { + if (!this.traceChange) { + if (this.processRunTimeTbl!.recycleDataSource.length > 0) { + this.processRunTimeTbl?.reMeauseHeight(); + } + return; + } + this.traceChange = false; + this.processRunTimeProgress!.loading = true; + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + this.processMap = Utils.getInstance().getProcessMap(); + this.threadMap = Utils.getInstance().getThreadMap(); + this.queryLogicWorker( + "scheduling-Process Top10RunTime", + "query Process Top10 Run Time Analysis Time:", + this.callBack.bind(this) + ); + } + + /** + * 清除已存储数据 + */ + clearData() { + this.traceChange = true; + this.processSwitchCountChart!.dataSource = []; + this.processRunTimeTbl!.recycleDataSource = []; + this.threadSwitchCountChart!.dataSource = []; + this.threadRunTimeTbl!.recycleDataSource = []; + this.processRunTimeData = []; + this.threadRunTimeData = []; + this.processMap = new Map(); + this.threadMap = new Map(); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { + let processThreadCountTime = new Date().getTime(); + procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + let durTime = new Date().getTime() - processThreadCountTime; + info(log, durTime); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + organizationData(arr: Array): Array { + let result: Array = []; + for (let i = 0; i < arr.length; i++) { + const pStr: string | null = this.processMap.get(arr[i].pid!)!; + const tStr: string | null = this.threadMap.get(arr[i].tid!)!; + result.push({ + NO: i + 1, + pid: arr[i].pid || this.processId, + pName: pStr === null ? 'Process ' : pStr, + dur: arr[i].dur, + tid: arr[i].tid, + tName: tStr === null ? 'Thread ' : tStr + }); + } + return result; + } + + /** + * 提交线程后,结果返回后的回调函数 + * @param res 数据库查询结果 + */ + callBack(res: Array): void { + let result: Array = this.organizationData(res); + if (this.display_flag === true) { + this.processCallback(result); + } else { + this.threadCallback(result); + } + this.processRunTimeProgress!.loading = false; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10进程数据 + * @param result 需要显示在表格中的数据 + */ + processCallback(result: Array): void { + this.nodataPro!.noData = result === undefined || result.length === 0; + this.processRunTimeTbl!.recycleDataSource = result; + this.processRunTimeTbl!.reMeauseHeight(); + this.processRunTimeData = result; + this.processSwitchCountChart!.config = { + data: result, + appendPadding: 10, + xField: 'pid', + yField: 'dur', + seriesField: 'size', + color: (a) => { + return '#0a59f7'; + }, + hoverHandler: (data) => { + if (data) { + this.processRunTimeTbl!.setCurrentHover(data); + } else { + this.processRunTimeTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj[0].obj.pid}
+
Process_Name:${ + // @ts-ignore + obj[0].obj.pName}
+
Run_Time:${ + // @ts-ignore + obj[0].obj.dur}
+
+ `; + }, + label: null, + }; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10线程数据 + * @param result 需要显示在表格中的数据 + */ + threadCallback(result: Array):void { + this.nodataThr!.noData = result === undefined || result.length === 0; + this.threadRunTimeTbl!.recycleDataSource = result; + this.threadRunTimeTbl!.reMeauseHeight(); + this.threadRunTimeData = result; + this.threadSwitchCountChart!.config = { + data: result, + appendPadding: 10, + xField: 'tid', + yField: 'dur', + seriesField: 'size', + color: (a) => { + return '#0a59f7'; + }, + hoverHandler: (data) => { + if (data) { + this.threadRunTimeTbl!.setCurrentHover(data); + } else { + this.threadRunTimeTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj[0].obj.pid}
+
Thread_Id:${ + // @ts-ignore + obj[0].obj.tid}
+
Thread_Name:${ + // @ts-ignore + obj[0].obj.tName}
+
Run_Time:${ + // @ts-ignore + obj[0].obj.dur}
+
+ `; + }, + label: null, + }; + } + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.processRunTimeProgress = this.shadowRoot!.querySelector('#loading'); + this.nodataPro = this.shadowRoot!.querySelector('#nodata_Pro'); + this.processRunTimeTbl = this.shadowRoot!.querySelector('#tb-process-run-time'); + this.processSwitchCountChart = this.shadowRoot!.querySelector('#chart_pro'); + this.nodataThr = this.shadowRoot!.querySelector('#nodata_Thr'); + this.threadRunTimeTbl = this.shadowRoot!.querySelector('#tb-thread-run-time'); + this.threadSwitchCountChart = this.shadowRoot!.querySelector('#chart_thr'); + this.display_pro = this.shadowRoot!.querySelector('#display_pro'); + this.display_thr = this.shadowRoot!.querySelector('#display_thr'); + this.back = this.shadowRoot!.querySelector('#back'); + this.clickEventListener(); + this.hoverEventListener(); + } + + /** + * 点击监听事件函数块 + */ + clickEventListener(): void { + // @ts-ignore + this.processRunTimeTbl!.addEventListener('row-click', (evt: CustomEvent) => { + this.display_flag = false; + let data = evt.detail.data; + this.processId = data.pid; + this.display_thr!.style.display = 'block'; + this.display_pro!.style.display = 'none'; + this.queryLogicWorker( + 'scheduling-Process Top10RunTime', + 'query Thread Top10 Run Time Analysis Time:', + this.callBack.bind(this), + data.pid + ); + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + this.processRunTimeTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.processRunTimeData); + this.processRunTimeTbl!.recycleDataSource = this.processRunTimeData; + }); + this.threadRunTimeTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.threadRunTimeData); + this.threadRunTimeTbl!.recycleDataSource = this.threadRunTimeData; + }); + this.back?.addEventListener('click', (event) => { + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + }); + } + + /** + * 移入事件监听函数块 + */ + hoverEventListener(): void { + // @ts-ignore + this.processRunTimeTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + this.processSwitchCountChart?.showHoverColumn(data.no); + } + }); + // @ts-ignore + this.threadRunTimeTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + this.threadSwitchCountChart?.showHoverColumn(data.no); + } + }); + } + + /** + * 表格数据排序 + * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 + * @param data 表格中需要排序的数据 + */ + sortByColumn(detail: any, data: Array): void { + // @ts-ignore + function compare(processThreadCountProperty, sort, type) { + return function (a: any, b: any) { + if (type === 'number') { + // @ts-ignore + return sort === 2 + ? parseFloat(b[processThreadCountProperty]) - + parseFloat(a[processThreadCountProperty]) + : parseFloat(a[processThreadCountProperty]) - + parseFloat(b[processThreadCountProperty]); + } else { + if (sort === 2) { + return b[processThreadCountProperty] + .toString() + .localeCompare(a[processThreadCountProperty].toString()); + } else { + return a[processThreadCountProperty] + .toString() + .localeCompare(b[processThreadCountProperty].toString()); + } + } + }; + } + if ( detail.key === 'pName' || detail.key === 'tName') { + data.sort( + compare(detail.key, detail.sort, 'string') + ); + } else { + data.sort( + compare(detail.key, detail.sort, 'number') + ); + } + } + + /** + * 用于将元素节点挂载,大函数块拆分为样式、节点 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return this.initStyleHtml() + this.initTagHtml(); + } + + /** + * 样式html代码块 + * @returns 返回样式代码块字符串 + */ + initStyleHtml(): string { + return ` + + ` + } + + /** + * 节点html代码块 + * @returns 返回节点代码块字符串 + */ + initTagHtml(): string { + return ` + +
+ +
+
+
+
+
Top10运行超长进程
+ +
+
+ + + + + + +
+
+
+
+
+ + `; + } +} + +interface Top10RunTimeData { + NO?: number, + pid?: number, + tid?: number, + pName?: string, + tName?: string, + switchCount?: number, + dur?: number +} \ No newline at end of file diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts new file mode 100644 index 00000000..c2b050d8 --- /dev/null +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts @@ -0,0 +1,491 @@ +/* + * 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 { procedurePool } from '../../../database/Procedure'; +import { info } from '../../../../log/Log'; +import { TableNoData } from '../TableNoData'; +import '../TableNoData'; +import { LitProgressBar } from '../../../../base-ui/progress-bar/LitProgressBar'; +import '../../../../base-ui/progress-bar/LitProgressBar'; +import { LitChartPie } from '../../../../base-ui/chart/pie/LitChartPie'; +import '../../../../base-ui/chart/pie/LitChartPie'; +import { Utils } from '../../trace/base/Utils'; + +@element('top10-process-switch-count') +export class Top10ProcessSwitchCount extends BaseElement { + traceChange: boolean = false; + private processSwitchCountTbl: LitTable | null | undefined; + private threadSwitchCountTbl: LitTable | null | undefined; + private nodataPro: TableNoData | null | undefined; + private nodataThr: TableNoData | null | undefined; + private processSwitchCountData: Array = []; + private threadSwitchCountData: Array = []; + private threadSwitchCountPie: LitChartPie | null | undefined; + private processSwitchCountPie: LitChartPie | null | undefined; + private display_pro: HTMLDivElement | null | undefined; + private display_thr: HTMLDivElement | null | undefined; + private processSwitchCountProgress: LitProgressBar | null | undefined; + private processId: number | undefined; + private display_flag: boolean = true; + private back: HTMLDivElement | null | undefined; + private processMap: Map = new Map(); + private threadMap: Map = new Map(); + + /** + * 初始化操作,若trace发生改变,将所有变量设置为默认值并重新请求数据。若trace未改变,跳出初始化 + */ + init(): void { + if (!this.traceChange) { + if (this.processSwitchCountTbl!.recycleDataSource.length > 0) { + this.processSwitchCountTbl?.reMeauseHeight(); + } + return; + } + this.traceChange = false; + this.processSwitchCountProgress!.loading = true; + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + this.processMap = Utils.getInstance().getProcessMap(); + this.threadMap = Utils.getInstance().getThreadMap(); + this.queryLogicWorker( + 'scheduling-Process Top10Swicount', + 'query Process Top10 Switch Count Analysis Time:', + this.callBack.bind(this) + ); + } + + /** + * 清除已存储数据 + */ + clearData(): void { + this.traceChange = true; + this.processSwitchCountPie!.dataSource = []; + this.processSwitchCountTbl!.recycleDataSource = []; + this.threadSwitchCountPie!.dataSource = []; + this.threadSwitchCountTbl!.recycleDataSource = []; + this.processSwitchCountData = []; + this.threadSwitchCountData = []; + this.processMap = new Map(); + this.threadMap = new Map(); + } + + /** + * 提交worker线程,进行数据库查询 + * @param option 操作的key值,用于找到并执行对应方法 + * @param log 日志打印内容 + * @param handler 结果回调函数 + * @param pid 需要查询某一进程下线程数据的进程id + */ + queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { + let processThreadCountTime = new Date().getTime(); + procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + let durTime = new Date().getTime() - processThreadCountTime; + info(log, durTime); + } + + /** + * 抽取公共方法,提取数据,用于展示到表格中 + * @param arr 数据库查询结果 + * @returns 整理好的数据,包含进程名,线程名等相关信息 + */ + organizationData(arr: Array): Array { + let result: Array = []; + for (let i = 0; i < arr.length; i++) { + const pStr: string | null = this.processMap.get(arr[i].pid!)!; + const tStr: string | null = this.threadMap.get(arr[i].tid!)!; + result.push({ + NO: i + 1, + pid: arr[i].pid || this.processId, + pName: pStr === null ? 'Process ' : pStr, + switchCount: arr[i].occurrences, + tid: arr[i].tid, + tName: tStr === null ? 'Thread ' : tStr + }); + } + return result; + } + + /** + * 提交线程后,结果返回后的回调函数 + * @param res 数据库查询结果 + */ + callBack(res: Array): void { + let result: Array = this.organizationData(res); + // 判断当前显示的是进程组还是线程组 + if (this.display_flag === true) { + this.processCallback(result); + } else { + this.threadCallback(result); + } + this.processSwitchCountProgress!.loading = false; + } + + /** + * 大函数块拆解分为两部分,此部分为Top10进程数据 + * @param result 需要显示在表格中的数据 + */ + processCallback(result: Array): void { + this.nodataPro!.noData = result === undefined || result.length === 0; + this.processSwitchCountData = result; + this.processSwitchCountPie!.config = { + appendPadding: 10, + data: result, + angleField: 'switchCount', + colorField: 'pid', + radius: 0.8, + label: { + type: 'outer', + }, + hoverHandler: (data) => { + if (data) { + this.processSwitchCountTbl!.setCurrentHover(data); + } else { + this.processSwitchCountTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Process_Id:${ + // @ts-ignore + obj.obj.pid}
+
Process_Name:${ + // @ts-ignore + obj.obj.pName}
+
Switch Count:${ + // @ts-ignore + obj.obj.switchCount}
+
+ `; + }, + interactions: [ + { + type: 'element-active', + }, + ], + }; + this.processSwitchCountTbl!.recycleDataSource = result; + this.processSwitchCountTbl!.reMeauseHeight(); + } + + /** + * 大函数块拆解分为两部分,此部分为Top10线程数据 + * @param result 需要显示在表格中的数据 + */ + threadCallback(result: Array): void { + this.nodataThr!.noData = result === undefined || result.length === 0; + this.threadSwitchCountTbl!.recycleDataSource = result; + this.threadSwitchCountTbl!.reMeauseHeight(); + this.threadSwitchCountData = result; + this.threadSwitchCountPie!.config = { + appendPadding: 10, + data: result, + angleField: 'switchCount', + colorField: 'tid', + radius: 0.8, + label: { + type: 'outer', + }, + hoverHandler: (data) => { + if (data) { + this.threadSwitchCountTbl!.setCurrentHover(data); + } else { + this.threadSwitchCountTbl!.mouseOut(); + } + }, + tip: (obj) => { + return ` +
+
Thread_Id:${ + // @ts-ignore + obj.obj.tid}
+
Thread_Name:${ + // @ts-ignore + obj.obj.tName}
+
Switch Count:${ + // @ts-ignore + obj.obj.switchCount}
+
Process_Id:${ + // @ts-ignore + obj.obj.pid}
+
+ `; + }, + interactions: [ + { + type: 'element-active', + }, + ], + }; + } + + /** + * 元素初始化,将html节点与内部变量进行绑定 + */ + initElements(): void { + this.processSwitchCountProgress = this.shadowRoot!.querySelector('#loading'); + this.processSwitchCountTbl = this.shadowRoot!.querySelector('#tb-process-switch-count'); + this.threadSwitchCountTbl = this.shadowRoot!.querySelector('#tb-thread-switch-count'); + this.processSwitchCountPie = this.shadowRoot!.querySelector('#pie_pro'); + this.threadSwitchCountPie = this.shadowRoot!.querySelector('#pie_thr'); + this.nodataPro = this.shadowRoot!.querySelector('#nodata_pro'); + this.nodataThr = this.shadowRoot!.querySelector('#nodata_thr'); + this.display_pro = this.shadowRoot!.querySelector('#display_pro'); + this.display_thr = this.shadowRoot!.querySelector('#display_thr'); + this.back = this.shadowRoot!.querySelector('#back'); + this.clickEventListener(); + this.hoverEventListener(); + } + + /** + * 点击监听事件函数块 + */ + clickEventListener(): void { + // @ts-ignore + this.processSwitchCountTbl!.addEventListener('row-click', (evt: CustomEvent) => { + this.display_flag = false; + let data = evt.detail.data; + this.processId = data.pid; + this.display_thr!.style.display = 'block'; + this.display_pro!.style.display = 'none'; + this.queryLogicWorker( + 'scheduling-Process Top10Swicount', + 'query Process Top10 Switch Count Analysis Time:', + this.callBack.bind(this), + data.pid + ); + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + // @ts-ignore + this.threadSwitchCountTbl!.addEventListener('row-click', (evt: CustomEvent) => { + let data = evt.detail.data; + data.isSelected = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + }); + this.processSwitchCountTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.processSwitchCountData); + this.processSwitchCountTbl!.recycleDataSource = this.processSwitchCountData; + }); + this.threadSwitchCountTbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail, this.threadSwitchCountData); + this.threadSwitchCountTbl!.recycleDataSource = this.threadSwitchCountData; + }); + this.back!.addEventListener('click', (event) => { + this.display_flag = true; + this.display_pro!.style.display = 'block'; + this.display_thr!.style.display = 'none'; + }); + + } + + /** + * 移入事件监听函数块 + */ + hoverEventListener(): void { + // @ts-ignore + this.processSwitchCountTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + } + this.processSwitchCountPie?.showHover(); + }); + // @ts-ignore + this.threadSwitchCountTbl!.addEventListener('row-hover', (evt: CustomEvent) => { + if (evt.detail.data) { + let data = evt.detail.data; + data.isHover = true; + if (evt.detail.callBack) { + evt.detail.callBack(true); + } + } + this.threadSwitchCountPie?.showHover(); + }); + this.addEventListener('mouseenter', () => { + if (this.processSwitchCountTbl!.recycleDataSource.length > 0) { + this.processSwitchCountTbl?.reMeauseHeight(); + } + }); + } + + /** + * 表格数据排序 + * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 + * @param data 表格中需要排序的数据 + */ + sortByColumn(detail: {key: string, sort: number}, data: Array): void { + // @ts-ignore + function compare(processThreadCountProperty, sort, type) { + return function (a: any, b: any) { + if (type === 'number') { + // @ts-ignore + return sort === 2 + ? parseFloat(b[processThreadCountProperty]) - + parseFloat(a[processThreadCountProperty]) + : parseFloat(a[processThreadCountProperty]) - + parseFloat(b[processThreadCountProperty]); + } else { + if (sort === 2) { + return b[processThreadCountProperty] + .toString() + .localeCompare(a[processThreadCountProperty].toString()); + } else { + return a[processThreadCountProperty] + .toString() + .localeCompare(b[processThreadCountProperty].toString()); + } + } + }; + } + if (detail.key === 'pName' || detail.key === 'tName') { + data.sort( + compare(detail.key, detail.sort, 'string') + ); + } else { + data.sort( + compare(detail.key, detail.sort, 'number') + ); + } + } + + /** + * 用于将元素节点挂载,大函数块拆分为样式、节点 + * @returns 返回字符串形式的元素节点 + */ + initHtml(): string { + return this.initStyleHtml() + this.initTagHtml(); + } + + /** + * 样式html代码块 + * @returns 返回样式代码块字符串 + */ + initStyleHtml(): string { + return ` + + `; + } + + /** + * 节点html代码块 + * @returns 返回节点代码块字符串 + */ + initTagHtml() :string { + return ` + +
+ +
+
+
+
+
Statistics By Process's Switch Count
+ +
+
+ + + + + + +
+
+
+
+
+ + `; + } +} + +interface Top10ProcSwiCount { + NO?: number, + pid?: number, + tid?: number, + pName?: string, + tName?: string, + switchCount?: number, + occurrences?: number +} \ No newline at end of file diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts index c114111f..7d2b3104 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts @@ -206,6 +206,7 @@ export class TabPaneCurrent extends BaseElement { tr[i].querySelector('#text-input')!.value = this.slicesTimeList[i - 1].text; // // 点击色块修改颜色 tr[i].querySelector('#text-input')?.addEventListener('keyup', (event: unknown) => { + SpSystemTrace.isKeyUp = true; if ( // @ts-ignore this.tableDataSource[i].startTime === this.slicesTimeList[i - 1].startTime && diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts index 70dadfc3..a7c899f6 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts @@ -13,36 +13,52 @@ * limitations under the License. */ -import { BaseElement, element } from '../../../../../base-ui/BaseElement'; -import { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table'; -import { SelectionParam } from '../../../../bean/BoxSelection'; -import '../../../StackBar'; -import { getTabRunningPercent } from '../../../../database/sql/ProcessThread.sql'; -import { queryCpuFreqUsageData, queryCpuFreqFilterId } from '../../../../database/sql/Cpu.sql'; -import { Utils } from '../../base/Utils'; -import { resizeObserver } from '../SheetUtils'; -import { SpSegmentationChart } from '../../../chart/SpSegmentationChart'; -import { type CpuFreqData, type RunningFreqData, type RunningData, type CpuFreqTd } from './TabPaneFreqUsageConfig'; +import { BaseElement, element } from "../../../../../base-ui/BaseElement"; +import { + LitTable, + RedrawTreeForm, +} from "../../../../../base-ui/table/lit-table"; +import { SelectionParam } from "../../../../bean/BoxSelection"; +import "../../../StackBar"; +import { getTabRunningPercent } from "../../../../database/sql/ProcessThread.sql"; +import { + queryCpuFreqUsageData, + queryCpuFreqFilterId, +} from "../../../../database/sql/Cpu.sql"; +import { Utils } from "../../base/Utils"; +import { resizeObserver } from "../SheetUtils"; +import { SpSegmentationChart } from "../../../chart/SpSegmentationChart"; +import { + type CpuFreqData, + type RunningFreqData, + type RunningData, + type CpuFreqTd, +} from "./TabPaneFreqUsageConfig"; -@element('tabpane-frequsage') +@element("tabpane-frequsage") export class TabPaneFreqUsage extends BaseElement { private threadStatesTbl: LitTable | null | undefined; private currentSelectionParam: SelectionParam | undefined; - private result: Array = []; + private worker: Worker | undefined; + static element: TabPaneFreqUsage; set data(threadStatesParam: SelectionParam) { if (this.currentSelectionParam === threadStatesParam) { return; } - this.threadStatesTbl!.loading = true; this.currentSelectionParam = threadStatesParam; this.threadStatesTbl!.recycleDataSource = []; // @ts-ignore this.threadStatesTbl.value = []; - this.result = []; this.queryAllData(threadStatesParam); } + + static refresh(): void { + this.prototype.queryAllData(TabPaneFreqUsage.element.currentSelectionParam!); + } + async queryAllData(threadStatesParam: SelectionParam): Promise { + TabPaneFreqUsage.element.threadStatesTbl!.loading = true; let runningResult: Array = await getTabRunningPercent( threadStatesParam.threadIds, threadStatesParam.processIds, @@ -50,7 +66,8 @@ export class TabPaneFreqUsage extends BaseElement { threadStatesParam.rightNs ); // 查询cpu及id信息 - let cpuIdResult: Array<{ id: number; cpu: number }> = await queryCpuFreqFilterId(); + let cpuIdResult: Array<{ id: number; cpu: number }> = + await queryCpuFreqFilterId(); // 以键值对形式将cpu及id进行对应,后续会将频点数据与其对应cpu进行整合 let IdMap: Map = new Map(); let queryId: Array = []; @@ -73,85 +90,47 @@ export class TabPaneFreqUsage extends BaseElement { } const LEFT_TIME: number = threadStatesParam.leftNs + threadStatesParam.recordStartNs; const RIGHT_TIME: number = threadStatesParam.rightNs + threadStatesParam.recordStartNs; - const args = { leftNs: LEFT_TIME, rightNs: RIGHT_TIME, cpuArray: cpuArray }; - let resultArr: Array = orgnazitionMap(runningResult, cpuFreqData, args); - // 递归拿出来最底层的数据,并以进程层级的数据作为分割 - this.recursion(resultArr); - this.result = JSON.parse(JSON.stringify(this.result)); - mergeTotal(resultArr, fixTotal(this.result)); - this.fixedDeal(resultArr, threadStatesParam.traceId); - this.threadClick(resultArr); - this.threadStatesTbl!.recycleDataSource = resultArr; - this.threadStatesTbl!.loading = false; - } - - /** - * 递归整理数据小数位 - */ - fixedDeal(arr: Array, traceId?: string | null): void { - if (arr == undefined) { - return; - } - const TIME_MUTIPLE: number = 1000000; - // KHz->MHz * ns->ms - const CONS_MUTIPLE: number = 1000000000; - const FREQ_MUTIPLE: number = 1000; - const MIN_PERCENT: number = 2; - const MIN_FREQ: number = 3; - for (let i = 0; i < arr.length; i++) { - let trackId: number; - // 若存在空位元素则进行删除处理 - if (arr[i] === undefined) { - arr.splice(i, 1); - i--; - continue; - } - if (arr[i].thread?.indexOf('P') !== -1) { - trackId = Number(arr[i].thread?.slice(1)!); - arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || 'Process'} ${trackId}`; - } else if (arr[i].thread === 'summary data') { - } else { - trackId = Number(arr[i].thread!.split('_')[1]); - arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || 'Thread'} ${trackId}`; - } - if (arr[i].cpu < 0) { - // @ts-ignore - arr[i].cpu = ''; - } - // @ts-ignore - if (arr[i].frequency < 0) { - arr[i].frequency = ''; - } - // @ts-ignore - arr[i].percent = arr[i].percent.toFixed(MIN_PERCENT); - // @ts-ignore - arr[i].dur = (arr[i].dur / TIME_MUTIPLE).toFixed(MIN_FREQ); - // @ts-ignore - arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ); - if (arr[i].frequency !== '') { - if (arr[i].frequency === 'unknown') { - arr[i].frequency = 'unknown'; - } else { - arr[i].frequency = Number(arr[i].frequency) / FREQ_MUTIPLE; - } - } - this.fixedDeal(arr[i].children!, traceId); - } + const comPower = + SpSegmentationChart.freqInfoMapData.size > 0 + ? SpSegmentationChart.freqInfoMapData + : undefined; + const args = { + runData: runningResult, + cpuFreqData: cpuFreqData, + leftNs: LEFT_TIME, + rightNs: RIGHT_TIME, + cpuArray: cpuArray, + comPower: comPower, + }; + TabPaneFreqUsage.element.worker!.postMessage(args); + TabPaneFreqUsage.element.worker!.onmessage = (event: MessageEvent): void => { + let resultArr: Array = event.data; + TabPaneFreqUsage.element.fixedDeal(resultArr, threadStatesParam.traceId); + TabPaneFreqUsage.element.threadClick(resultArr); + TabPaneFreqUsage.element.threadStatesTbl!.recycleDataSource = resultArr; + TabPaneFreqUsage.element.threadStatesTbl!.loading = false; + }; } /** * 表头点击事件 */ private threadClick(data: Array): void { - let labels = this.threadStatesTbl?.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label'); + let labels = this.threadStatesTbl?.shadowRoot + ?.querySelector(".th > .td")! + .querySelectorAll("label"); if (labels) { for (let i = 0; i < labels.length; i++) { let label = labels[i].innerHTML; - labels[i].addEventListener('click', (e) => { - if (label.includes('Process') && i === 0) { + labels[i].addEventListener("click", (e) => { + if (label.includes("Process") && i === 0) { this.threadStatesTbl!.setStatus(data, false); - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); - } else if (label.includes('Thread') && i === 1) { + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Retract + ); + } else if (label.includes("Thread") && i === 1) { for (let item of data) { // @ts-ignore item.status = true; @@ -159,40 +138,41 @@ export class TabPaneFreqUsage extends BaseElement { this.threadStatesTbl!.setStatus(item.children, false); } } - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); - } else if (label.includes('CPU') && i === 2) { + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Retract + ); + } else if (label.includes("CPU") && i === 2) { this.threadStatesTbl!.setStatus(data, true); - this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Expand); + this.threadStatesTbl!.recycleDs = + this.threadStatesTbl!.meauseTreeRowElement( + data, + RedrawTreeForm.Expand + ); } }); } } } - /** - * - * @param arr 待整理的数组,会经过递归取到最底层的数据 - */ - recursion(arr: Array): void { - for (let idx = 0; idx < arr.length; idx++) { - if (arr[idx].cpu === -1) { - this.result.push(arr[idx]); - } - if (arr[idx].children) { - this.recursion(arr[idx].children!); - } else { - this.result.push(arr[idx]); - } - } - } initElements(): void { - this.threadStatesTbl = this.shadowRoot?.querySelector('#tb-running-percent'); + this.threadStatesTbl = this.shadowRoot?.querySelector( + "#tb-running-percent" + ); + //开启一个线程计算busyTime + this.worker = new Worker( + new URL("../../../../database/TabPaneFreqUsageWorker", import.meta.url) + ); + TabPaneFreqUsage.element = this; } + connectedCallback(): void { super.connectedCallback(); - resizeObserver(this.parentElement!, this.threadStatesTbl!); + resizeObserver(this.parentElement!, this.threadStatesTbl!, 20); } + initHtml(): string { return ` - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + `; } -} -/** - * - * @param runData 数据库查询上来的running数据,此函数会将数据整理成map结构,分组规则:'pid_tid'为键,running数据数字为值 - * @returns 返回map对象及所有running数据的dur和,后续会依此计算百分比 - */ -function orgnazitionMap( - runData: Array, - cpuFreqData: Array, - args: { - leftNs: number, - rightNs: number, - cpuArray: number[] - } -): Array { - let result: Map> = new Map(); - let sum: number = 0; - // 循环分组 - for (let i = 0; i < runData.length; i++) { - let mapKey: string = runData[i].pid + '_' + runData[i].tid; - // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值 - if (!result.has(mapKey)) { - result.set(mapKey, new Array()); - } - // 整理左右边界数据问题, 因为涉及多线程,所以必须放在循环里 - if (runData[i].ts < args.leftNs && runData[i].ts + runData[i].dur > args.leftNs) { - runData[i].dur = runData[i].ts + runData[i].dur - args.leftNs; - runData[i].ts = args.leftNs; - } - if (runData[i].ts + runData[i].dur > args.rightNs) { - runData[i].dur = args.rightNs - runData[i].ts; - } - // 特殊处理数据表中dur为负值的情况 - if (runData[i].dur < 0) { - runData[i].dur = 0; - } - // 分组整理数据 - result.get(mapKey)?.push({ - pid: runData[i].pid, - tid: runData[i].tid, - cpu: runData[i].cpu, - dur: runData[i].dur, - ts: runData[i].ts, - }); - sum += runData[i].dur; - } - return dealCpuFreqData(cpuFreqData, result, sum, args.cpuArray); -} - -/** - * - * @param cpuFreqData cpu频点数据的数组 - * @param result running数据的map对象 - * @param sum running数据的时间和 - * @returns 返回cpu频点数据map,'pid_tid'为键,频点算力值数据的数组为值 - */ -function dealCpuFreqData( - cpuFreqData: Array, - result: Map>, - sum: number, - cpuList: number[] -): Array { - let runningFreqData: Map> = new Map(); - result.forEach((item, key) => { - let resultList: Array = new Array(); - for (let i = 0; i < item.length; i++) { - for (let j = 0; j < cpuFreqData.length; j++) { - let flag: number; - if (item[i].cpu === cpuFreqData[j].cpu) { - // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间小于频点结束时间减去running数据开始时间的差值的情况 - if ( - item[i].ts > cpuFreqData[j].ts && - item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && - item[i].dur < cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts - ) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 1))!); - item.splice(i, 1); - i--; - break; - } - if ( - item[i].ts > cpuFreqData[j].ts && - item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && - item[i].dur >= cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts - ) { - // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间大于等于频点结束时间减去running数据开始时间的差值的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 2))!); - } - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值小于频点数据持续时间的情况 - if ( - item[i].ts <= cpuFreqData[j].ts && - item[i].ts + item[i].dur > cpuFreqData[j].ts && - item[i].dur + item[i].ts - cpuFreqData[j].ts < cpuFreqData[j].dur - ) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 3))!); - item.splice(i, 1); - i--; - break; - } - if ( - item[i].ts <= cpuFreqData[j].ts && - item[i].ts + item[i].dur > cpuFreqData[j].ts && - item[i].dur + item[i].ts - cpuFreqData[j].ts >= cpuFreqData[j].dur - ) { - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值大于等于频点数据持续时间的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 4))!); - } - if (item[i].ts <= cpuFreqData[j].ts && item[i].ts + item[i].dur <= cpuFreqData[j].ts) { - // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间小于等于频点开始时间的情况 - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); - item.splice(i, 1); - i--; - break; - } - } else { - if (!cpuList.includes(item[i].cpu)) { - resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); - item.splice(i, 1); - i--; - break; - } - } - } - } - runningFreqData.set(key, mergeSameData(resultList)); - }); - return dealTree(runningFreqData); -} - -/** - * - * @param item running数据 - * @param cpuFreqData 频点数据 - * @param sum running总和 - * @param flag 标志位,根据不同值返回不同结果 - * @returns 返回新的对象 - */ -function returnObj( - item: RunningData, - cpuFreqData: CpuFreqData, - sum: number, - flag: number -): RunningFreqData | undefined { - const PERCENT: number = 100; - const consumption: number = ( - SpSegmentationChart.freqInfoMapData.size > 0 - ? SpSegmentationChart.freqInfoMapData.get(item.cpu)?.get(cpuFreqData.value) - : cpuFreqData.value - )!; - let result: RunningFreqData | undefined; - switch (flag) { - case 1: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * item.dur, - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: item.dur, - percent: (item.dur / sum) * PERCENT, - }; - case 2: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * (cpuFreqData.ts + cpuFreqData.dur - item.ts), - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: cpuFreqData.ts + cpuFreqData.dur - item.ts, - percent: ((cpuFreqData.ts + cpuFreqData.dur - item.ts) / sum) * PERCENT, - }; - case 3: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * (item.dur + item.ts - cpuFreqData.ts), - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: item.dur + item.ts - cpuFreqData.ts, - percent: ((item.dur + item.ts - cpuFreqData.ts) / sum) * PERCENT, - }; - case 4: - result = { - thread: item.pid + '_' + item.tid, - consumption: consumption * cpuFreqData.dur, - cpu: item.cpu, - frequency: cpuFreqData.value, - dur: cpuFreqData.dur, - percent: (cpuFreqData.dur / sum) * PERCENT, - }; - case 5: - result = { - thread: item.pid + '_' + item.tid, - consumption: 0, - cpu: item.cpu, - frequency: 'unknown', - dur: item.dur, - percent: (item.dur / sum) * PERCENT, - }; - } - return result; -} - -/** - * - * @param resultList 单线程内running数据与cpu频点数据整合成的数组 - */ -function mergeSameData(resultList: Array): Array { - let cpuFreqArr: Array = []; - let cpuArr: Array = []; - //合并同一线程内,当运行所在cpu和频点相同时,dur及percent进行累加求和 - for (let i = 0; i < resultList.length; i++) { - if (!cpuArr.includes(resultList[i].cpu)) { - cpuArr.push(resultList[i].cpu); - cpuFreqArr.push(creatNewObj(resultList[i].cpu)); + /** + * 递归整理数据小数位 + */ + fixedDeal(arr: Array, traceId?: string | null): void { + if (arr == undefined) { + return; } - for (let j = i + 1; j < resultList.length; j++) { - if (resultList[i].cpu === resultList[j].cpu && resultList[i].frequency === resultList[j].frequency) { - resultList[i].dur += resultList[j].dur; - resultList[i].percent += resultList[j].percent; - resultList[i].consumption += resultList[j].consumption; - resultList.splice(j, 1); - j--; + const TIME_MUTIPLE: number = 1000000; + // KHz->MHz * ns->ms + const CONS_MUTIPLE: number = 1000000000; + const MIN_PERCENT: number = 2; + const MIN_FREQ: number = 3; + const MIN_POWER: number = 6; + for (let i = 0; i < arr.length; i++) { + let trackId: number; + // 若存在空位元素则进行删除处理 + if (arr[i] === undefined) { + arr.splice(i, 1); + i--; + continue; } - } - cpuFreqArr.find(function (item) { - if (item.cpu === resultList[i].cpu) { - item.children?.push(resultList[i]); - item.children?.sort((a, b) => b.consumption - a.consumption); - item.dur += resultList[i].dur; - item.percent += resultList[i].percent; - item.consumption += resultList[i].consumption; - item.thread = resultList[i].thread; + if (arr[i].thread?.indexOf("P") !== -1) { + trackId = Number(arr[i].thread?.slice(1)!); + arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || "Process"} ${trackId}`; + } else if (arr[i].thread === "summary data") { + } else { + trackId = Number(arr[i].thread!.split("_")[1]); + arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || "Thread"} ${trackId}`; } - }); - } - cpuFreqArr.sort((a, b) => a.cpu - b.cpu); - return cpuFreqArr; -} - -/** - * - * @param params cpu层级的数据 - * @returns 整理好的进程级数据 - */ -function dealTree(params: Map>): Array { - let result: Array = []; - params.forEach((item, key) => { - let process: RunningFreqData = creatNewObj(-1, false); - let thread: RunningFreqData = creatNewObj(-2); - for (let i = 0; i < item.length; i++) { - thread.children?.push(item[i]); - thread.dur += item[i].dur; - thread.percent += item[i].percent; - thread.consumption += item[i].consumption; - thread.thread = item[i].thread; - } - process.children?.push(thread); - process.dur += thread.dur; - process.percent += thread.percent; - process.consumption += thread.consumption; - process.thread = process.thread! + key.split('_')[0]; - result.push(process); - }); - for (let i = 0; i < result.length; i++) { - for (let j = i + 1; j < result.length; j++) { - if (result[i].thread === result[j].thread) { - result[i].children?.push(result[j].children![0]); - result[i].dur += result[j].dur; - result[i].percent += result[j].percent; - result[i].consumption += result[j].consumption; - result.splice(j, 1); - j--; + if (arr[i].cpu < 0) { + // @ts-ignore + arr[i].cpu = ""; } - } - } - return result; -} - -/** - * - * @param cpu 根据cpu值创建层级结构,cpu < 0为线程、进程层级,其余为cpu层级 - * @returns - */ -function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData { - return { - thread: flag ? '' : 'P', - consumption: 0, - cpu: cpu, - frequency: -1, - dur: 0, - percent: 0, - children: [], - }; -} - -/** - * - * @param arr 需要整理汇总的频点级数据 - * @returns 返回一个total->cpu->频点的三级树结构数组 - */ -function fixTotal(arr: Array): Array { - let result: Array = []; - let flag: number = -1; - // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割 - for (let i = 0; i < arr.length; i++) { - // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据 - if (arr[i].thread?.indexOf('P') !== -1) { - arr[i].children = []; - arr[i].thread = arr[i].thread + '-summary data'; - result.push(arr[i]); - // 标志判定当前数组的长度,也可用.length判断 - flag++; - } else { - // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建 - if (result[flag].children![arr[i].cpu] === undefined) { - result[flag].children![arr[i].cpu] = { - thread: 'summary data', - consumption: 0, - cpu: arr[i].cpu, - frequency: -1, - dur: 0, - percent: 0, - children: [], - }; + // @ts-ignore + if (arr[i].frequency < 0) { + arr[i].frequency = ""; } - // 每有一条数据要放到cpu分组下时,则将该cpu分组的各项数据累和 - result[flag].children![arr[i].cpu].consumption += arr[i].consumption; - result[flag].children![arr[i].cpu].dur += arr[i].dur; - result[flag].children![arr[i].cpu].percent += arr[i].percent; - // 查找当前cpu分组下是否存在与当前数据的频点相同的数据,返回相同数据的索引值 - let index: number = result[flag].children![arr[i].cpu].children?.findIndex( - (item) => item.frequency === arr[i].frequency - )!; - // 若存在相同频点的数据,则进行合并,不同直接push - if (index === -1) { - arr[i].thread = 'summary data'; - result[flag].children![arr[i].cpu].children?.push(arr[i]); + if (!arr[i].cpuload) { + // @ts-ignore + arr[i].cpuload = '0.000000'; } else { - result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption; - result[flag].children![arr[i].cpu].children![index].dur += arr[i].dur; - result[flag].children![arr[i].cpu].children![index].percent += arr[i].percent; + // @ts-ignore + arr[i].cpuload = arr[i].cpuload.toFixed(MIN_POWER); } + // @ts-ignore + arr[i].percent = arr[i].percent.toFixed(MIN_PERCENT); + // @ts-ignore + arr[i].dur = (arr[i].dur / TIME_MUTIPLE).toFixed(MIN_FREQ); + // @ts-ignore + arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ); + // @ts-ignore + arr[i].consumpower = (arr[i].consumpower / TIME_MUTIPLE).toFixed(MIN_FREQ); + if (arr[i].frequency !== "") { + if (arr[i].frequency === "unknown") { + arr[i].frequency = "unknown"; + } else { + arr[i].frequency = arr[i].frequency; + } + } + this.fixedDeal(arr[i].children!, traceId); } } - return result; -} - -/** - * - * @param arr1 前次整理好的区分线程的数据 - * @param arr2 不区分线程的Total数据 - */ -function mergeTotal(arr1: Array, arr2: Array): void { - for (let i = 0; i < arr1.length; i++) { - const num: number = arr2.findIndex((item) => item.thread?.includes(arr1[i].thread!)); - arr2[num].thread = 'summary data'; - arr1[i].children?.unshift(arr2[num]); - arr2.splice(num, 1); - } -} +} \ No newline at end of file diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts index c1b929cc..02df4c38 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsageConfig.ts @@ -111,6 +111,8 @@ export interface RunningFreqData { consumption: number; frequency: number | string; percent: number; + consumpower: number; + cpuload: number; children?: Array; } diff --git a/ide/src/trace/database/TabPaneFreqUsageWorker.ts b/ide/src/trace/database/TabPaneFreqUsageWorker.ts new file mode 100644 index 00000000..6ec42caf --- /dev/null +++ b/ide/src/trace/database/TabPaneFreqUsageWorker.ts @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2024 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 { type CpuFreqData, type RunningFreqData, type RunningData } from '../component/trace/sheet/frequsage/TabPaneFreqUsageConfig'; + +let comPower = new Map>(); +let resultArray: Array = []; +let timeZones: number = 0; +let maxCommpuPower: number = 0; + +/** + * + * @param args.runData 数据库查询上来的running数据,此函数会将数据整理成map结构,分组规则:'pid_tid'为键,running数据数字为值 + * @returns 返回map对象及所有running数据的dur和,后续会依此计算百分比 + */ +function orgnazitionMap( + args: { + runData: Array; + cpuFreqData: Array; + leftNs: number; + rightNs: number; + cpuArray: number[]; + } +): Array { + let result: Map> = new Map(); + let sum: number = 0; + // 循环分组 + for (let i = 0; i < args.runData.length; i++) { + let mapKey: string = args.runData[i].pid + "_" + args.runData[i].tid; + // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值 + if (!result.has(mapKey)) { + result.set(mapKey, new Array()); + } + // 整理左右边界数据问题, 因为涉及多线程,所以必须放在循环里 + if ( + args.runData[i].ts < args.leftNs && + args.runData[i].ts + args.runData[i].dur > args.leftNs + ) { + args.runData[i].dur = args.runData[i].ts + args.runData[i].dur - args.leftNs; + args.runData[i].ts = args.leftNs; + } + if (args.runData[i].ts + args.runData[i].dur > args.rightNs) { + args.runData[i].dur = args.rightNs - args.runData[i].ts; + } + // 特殊处理数据表中dur为负值的情况 + if (args.runData[i].dur < 0) { + args.runData[i].dur = 0; + } + // 分组整理数据 + result.get(mapKey)?.push({ + pid: args.runData[i].pid, + tid: args.runData[i].tid, + cpu: args.runData[i].cpu, + dur: args.runData[i].dur, + ts: args.runData[i].ts, + }); + sum += args.runData[i].dur; + } + return dealCpuFreqData(args.cpuFreqData, result, sum, args.cpuArray); +} + +/** + * + * @param cpuFreqData cpu频点数据的数组 + * @param result running数据的map对象 + * @param sum running数据的时间和 + * @returns 返回cpu频点数据map,'pid_tid'为键,频点算力值数据的数组为值 + */ +function dealCpuFreqData( + cpuFreqData: Array, + result: Map>, + sum: number, + cpuList: number[] +): Array { + let runningFreqData: Map> = new Map(); + result.forEach((item, key) => { + let resultList: Array = new Array(); + for (let i = 0; i < item.length; i++) { + for (let j = 0; j < cpuFreqData.length; j++) { + let flag: number; + if (item[i].cpu === cpuFreqData[j].cpu) { + // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间小于频点结束时间减去running数据开始时间的差值的情况 + if ( + item[i].ts > cpuFreqData[j].ts && + item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && + item[i].dur < cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts + ) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 1))!); + item.splice(i, 1); + i--; + break; + } + if ( + item[i].ts > cpuFreqData[j].ts && + item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur && + item[i].dur >= cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts + ) { + // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间大于等于频点结束时间减去running数据开始时间的差值的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 2))!); + } + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值小于频点数据持续时间的情况 + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur > cpuFreqData[j].ts && + item[i].dur + item[i].ts - cpuFreqData[j].ts < cpuFreqData[j].dur + ) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 3))!); + item.splice(i, 1); + i--; + break; + } + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur > cpuFreqData[j].ts && + item[i].dur + item[i].ts - cpuFreqData[j].ts >= cpuFreqData[j].dur + ) { + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值大于等于频点数据持续时间的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 4))!); + } + if ( + item[i].ts <= cpuFreqData[j].ts && + item[i].ts + item[i].dur <= cpuFreqData[j].ts + ) { + // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间小于等于频点开始时间的情况 + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); + item.splice(i, 1); + i--; + break; + } + } else { + if (!cpuList.includes(item[i].cpu)) { + resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!); + item.splice(i, 1); + i--; + break; + } + } + } + } + runningFreqData.set(key, mergeSameData(resultList)); + }); + return dealTree(runningFreqData); +} + +/** + * + * @param item running数据 + * @param cpuFreqData 频点数据 + * @param sum running总和 + * @param flag 标志位,根据不同值返回不同结果 + * @returns 返回新的对象 + */ +function returnObj( + item: RunningData, + cpuFreqData: CpuFreqData, + sum: number, + flag: number +): RunningFreqData | undefined { + const PERCENT: number = 100; + const FREQ_MUTIPLE: number = 1000; + const computorPower: number = comPower ? comPower.get(item.cpu)?.get(cpuFreqData.value)! : 0; + switch (flag) { + case 1: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * item.dur, + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: item.dur, + percent: (item.dur / sum) * PERCENT, + consumpower: computorPower * item.dur, + cpuload: (computorPower * item.dur) / (timeZones * maxCommpuPower) * PERCENT + }; + case 2: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * (cpuFreqData.ts + cpuFreqData.dur - item.ts), + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: cpuFreqData.ts + cpuFreqData.dur - item.ts, + percent: ((cpuFreqData.ts + cpuFreqData.dur - item.ts) / sum) * PERCENT, + consumpower: computorPower * (cpuFreqData.ts + cpuFreqData.dur - item.ts), + cpuload: (computorPower * (cpuFreqData.ts + cpuFreqData.dur - item.ts)) / (timeZones * maxCommpuPower) * PERCENT + }; + case 3: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * (item.dur + item.ts - cpuFreqData.ts), + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: item.dur + item.ts - cpuFreqData.ts, + percent: ((item.dur + item.ts - cpuFreqData.ts) / sum) * PERCENT, + consumpower: computorPower * (item.dur + item.ts - cpuFreqData.ts), + cpuload: (computorPower * (item.dur + item.ts - cpuFreqData.ts)) / (timeZones * maxCommpuPower) * PERCENT + }; + case 4: + return { + thread: item.pid + "_" + item.tid, + consumption: cpuFreqData.value * cpuFreqData.dur, + cpu: item.cpu, + frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, + dur: cpuFreqData.dur, + percent: (cpuFreqData.dur / sum) * PERCENT, + consumpower: computorPower * cpuFreqData.dur, + cpuload: (computorPower * cpuFreqData.dur) / (timeZones * maxCommpuPower) * PERCENT + }; + case 5: + return { + thread: item.pid + "_" + item.tid, + consumption: 0, + cpu: item.cpu, + frequency: "unknown", + dur: item.dur, + percent: (item.dur / sum) * PERCENT, + consumpower: 0, + cpuload: 0 + }; + } +} + +/** + * + * @param resultList 单线程内running数据与cpu频点数据整合成的数组 + */ +function mergeSameData( + resultList: Array +): Array { + let cpuFreqArr: Array = []; + let cpuArr: Array = []; + //合并同一线程内,当运行所在cpu和频点相同时,dur及percent进行累加求和 + for (let i = 0; i < resultList.length; i++) { + if (!cpuArr.includes(resultList[i].cpu)) { + cpuArr.push(resultList[i].cpu); + cpuFreqArr.push(creatNewObj(resultList[i].cpu)); + } + for (let j = i + 1; j < resultList.length; j++) { + if ( + resultList[i].cpu === resultList[j].cpu && + resultList[i].frequency === resultList[j].frequency + ) { + resultList[i].dur += resultList[j].dur; + resultList[i].percent += resultList[j].percent; + resultList[i].consumption += resultList[j].consumption; + resultList[i].consumpower += resultList[j].consumpower; + resultList[i].cpuload += resultList[j].cpuload; + resultList.splice(j, 1); + j--; + } + } + cpuFreqArr.find(function (item) { + if (item.cpu === resultList[i].cpu) { + item.children?.push(resultList[i]); + item.children?.sort((a, b) => b.consumption - a.consumption); + item.dur += resultList[i].dur; + item.percent += resultList[i].percent; + item.consumption += resultList[i].consumption; + item.consumpower += resultList[i].consumpower; + item.cpuload += resultList[i].cpuload; + item.thread = resultList[i].thread; + } + }); + } + cpuFreqArr.sort((a, b) => a.cpu - b.cpu); + return cpuFreqArr; +} + +/** + * + * @param params cpu层级的数据 + * @returns 整理好的进程级数据 + */ +function dealTree( + params: Map> +): Array { + let result: Array = []; + params.forEach((item, key) => { + let process: RunningFreqData = creatNewObj(-1, false); + let thread: RunningFreqData = creatNewObj(-2); + for (let i = 0; i < item.length; i++) { + thread.children?.push(item[i]); + thread.dur += item[i].dur; + thread.percent += item[i].percent; + thread.consumption += item[i].consumption; + thread.consumpower += item[i].consumpower; + thread.cpuload += item[i].cpuload; + thread.thread = item[i].thread; + } + process.children?.push(thread); + process.dur += thread.dur; + process.percent += thread.percent; + process.consumption += thread.consumption; + process.consumpower += thread.consumpower; + process.cpuload += thread.cpuload; + process.thread = process.thread! + key.split("_")[0]; + result.push(process); + }); + for (let i = 0; i < result.length; i++) { + for (let j = i + 1; j < result.length; j++) { + if (result[i].thread === result[j].thread) { + result[i].children?.push(result[j].children![0]); + result[i].dur += result[j].dur; + result[i].percent += result[j].percent; + result[i].consumption += result[j].consumption; + result[i].consumpower += result[j].consumpower; + result[i].cpuload += result[j].cpuload; + result.splice(j, 1); + j--; + } + } + } + return result; +} + +/** + * + * @param cpu 根据cpu值创建层级结构,cpu < 0为线程、进程层级,其余为cpu层级 + * @returns + */ +function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData { + return { + thread: flag ? "" : "P", + consumption: 0, + cpu: cpu, + frequency: -1, + dur: 0, + percent: 0, + children: [], + consumpower: 0, + cpuload: 0 + }; +} + +/** + * + * @param arr 需要整理汇总的频点级数据 + * @returns 返回一个total->cpu->频点的三级树结构数组 + */ +function fixTotal(arr: Array): Array { + let result: Array = []; + let flag: number = -1; + // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割 + for (let i = 0; i < arr.length; i++) { + // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据 + if (arr[i].thread?.indexOf("P") !== -1) { + arr[i].children = []; + arr[i].thread = arr[i].thread + "-summary data"; + result.push(arr[i]); + // 标志判定当前数组的长度,也可用.length判断 + flag++; + } else { + // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建 + if (result[flag].children![arr[i].cpu] === undefined) { + result[flag].children![arr[i].cpu] = { + thread: "summary data", + consumption: 0, + cpu: arr[i].cpu, + frequency: -1, + dur: 0, + percent: 0, + children: [], + consumpower: 0, + cpuload: 0 + }; + } + // 每有一条数据要放到cpu分组下时,则将该cpu分组的各项数据累和 + result[flag].children![arr[i].cpu].consumption += arr[i].consumption; + result[flag].children![arr[i].cpu].consumpower += arr[i].consumpower; + result[flag].children![arr[i].cpu].cpuload += arr[i].cpuload; + result[flag].children![arr[i].cpu].dur += arr[i].dur; + result[flag].children![arr[i].cpu].percent += arr[i].percent; + // 查找当前cpu分组下是否存在与当前数据的频点相同的数据,返回相同数据的索引值 + let index: number = result[flag].children![ + arr[i].cpu + ].children?.findIndex((item) => item.frequency === arr[i].frequency)!; + // 若存在相同频点的数据,则进行合并,不同直接push + if (index === -1) { + arr[i].thread = "summary data"; + result[flag].children![arr[i].cpu].children?.push(arr[i]); + } else { + result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption; + result[flag].children![arr[i].cpu].children![index].consumpower += arr[i].consumpower; + result[flag].children![arr[i].cpu].children![index].dur += arr[i].dur; + result[flag].children![arr[i].cpu].children![index].percent += arr[i].percent; + result[flag].children![arr[i].cpu].children![index].cpuload += arr[i].cpuload; + } + } + } + return result; +} + +/** + * + * @param arr1 前次整理好的区分线程的数据 + * @param arr2 不区分线程的Total数据 + */ +function mergeTotal( + arr1: Array, + arr2: Array +): void { + for (let i = 0; i < arr1.length; i++) { + const num: number = arr2.findIndex((item) => + item.thread?.includes(arr1[i].thread!) + ); + arr2[num].thread = "summary data"; + arr1[i].children?.unshift(arr2[num]); + arr2.splice(num, 1); + } +} + + + /** + * + * @param arr 待整理的数组,会经过递归取到最底层的数据 + */ +function recursion(arr: Array): void { + for (let idx = 0; idx < arr.length; idx++) { + if (arr[idx].cpu === -1) { + resultArray.push(arr[idx]); + } + if (arr[idx].children) { + recursion(arr[idx].children!); + } else { + resultArray.push(arr[idx]); + } + } + } + +self.onmessage = (e: MessageEvent): void => { + comPower = e.data.comPower; + resultArray = []; + timeZones = e.data.rightNs - e.data.leftNs; + maxCommpuPower = 0; + if (comPower) { + comPower.forEach(item => { + let maxFreq = 0; + let commpuPower = 0; + for (const i of item.entries()) { + if (i[0] > maxFreq) { + maxFreq = i[0]; + commpuPower = i[1]; + } + } + maxCommpuPower += commpuPower; + }); + } + let result = orgnazitionMap(e.data); + recursion(result); + resultArray = JSON.parse(JSON.stringify(resultArray)); + mergeTotal(result, fixTotal(resultArray)); + self.postMessage(result); +}; \ No newline at end of file diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 18a48438..b5b3a481 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -104,6 +104,12 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { case 'scheduling-Thread Freq': this.schedulingThreadFreq(data); break; + case 'scheduling-Process Top10Swicount': + this.schedulingProTop10Swicount(data); + break; + case 'scheduling-Process Top10RunTime': + this.schedulingProcessRunTime(data); + break; } } private schedulingClearData(data: { id: string; action: string; params: unknown }): void { @@ -352,6 +358,40 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { this.queryThreadStateByTid(data.params.tid); } } + private schedulingProTop10Swicount(data: any): void { + if (data.params.list) { + let arr = convertJSON(data.params.list) || []; + self.postMessage({ + id: data.id, + action: data.action, + results: arr, + }); + arr = []; + } else { + if (data.params.pid) { + this.queryThrTop10Swicount(data.params.pid); + } else { + this.queryProTop10Swicount(); + } + } + } + private schedulingProcessRunTime(data: any): void { + if (data.params.list) { + let arr = convertJSON(data.params.list) || []; + self.postMessage({ + id: data.id, + action: data.action, + results: arr, + }); + arr = []; + } else { + if (data.params.pid) { + this.queryThrTop10RunTime(data.params.pid); + } else { + this.queryProTop10RunTime(); + } + } + } getProcessAndThread(): void { this.queryData( this.currentEventId, @@ -537,6 +577,87 @@ where cpu not null order by cpu,ts;`; this.queryData(this.currentEventId, 'scheduling-Thread Freq', sql, {}); } + queryProTop10Swicount() { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10Swicount', + ` + select + pid, + count(tid) as occurrences + from + thread_state + where + state = 'Running' + group by + pid + ORDER BY occurrences desc + LIMIT 10 + `, + {} + ); + } + queryThrTop10Swicount(pid: number) { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10Swicount', + ` + select + tid, + count(tid) as occurrences + from + thread_state + where + state = 'Running' + and pid = ${pid} + group by + tid + ORDER BY occurrences desc + LIMIT 10 + `, + {} + ); + } + queryProTop10RunTime() { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10RunTime', + ` + select + pid, + SUM(dur) As dur + from + thread_state + where + state = 'Running' + GROUP BY pid + ORDER BY dur desc + LIMIT 10 + `, + {} + ); + } + queryThrTop10RunTime(pid: number) { + this.queryData( + this.currentEventId, + 'scheduling-Process Top10RunTime', + ` + select + tid, + SUM(dur) As dur + from + thread_state + where + state = 'Running' + and + pid = ${pid} + GROUP BY tid + ORDER BY dur desc + LIMIT 10 + `, + {} + ); + } groupIrgDataByCpu(arr: Irq[]): Map { //首先计算 每个频点的持续时间,并根据Cpu来分组 -- Gitee From 88bcff8417391f4d13dc5c7c4e36ec7ac8751964 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Mon, 12 Aug 2024 16:54:17 +0800 Subject: [PATCH 13/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E5=B0=8F=E6=8B=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=E5=A4=A7=E6=8B=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/trace/sheet/cpu/TabPaneBoxChild.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneBoxChild.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneBoxChild.ts index c5f3477c..8ba6d4b9 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneBoxChild.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneBoxChild.ts @@ -112,11 +112,11 @@ export class TabPaneBoxChild extends BaseElement { // @ts-ignore let processInfo: string | undefined = Utils.getInstance().getProcessMap().get(e.pid); // @ts-ignore - e.processName = `${processInfo === undefined || processInfo === null ? 'process' : processInfo}(${e.pid})`; + e.processName = `${processInfo === undefined || processInfo === null ? 'process' : processInfo}[${e.pid}]`; // @ts-ignore let threadInfo: string | undefined = Utils.getInstance().getThreadMap().get(e.tid); // @ts-ignore - e.threadName = `${threadInfo === undefined || threadInfo === null ? 'thread' : threadInfo}(${e.tid})`; + e.threadName = `${threadInfo === undefined || threadInfo === null ? 'thread' : threadInfo}[${e.tid}]`; // @ts-ignore e.note = '-'; }); -- Gitee From e59dc9df8da11a921bbe2f865a1dc81a08d8188c Mon Sep 17 00:00:00 2001 From: wangyujie Date: Mon, 12 Aug 2024 18:55:25 +0800 Subject: [PATCH 14/85] =?UTF-8?q?feat:=E4=BF=AE=E5=A4=8Dgpu=E5=92=8Cabilit?= =?UTF-8?q?y=E6=B3=B3=E9=81=93=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/database/sql/Memory.sql.ts | 39 +++++++++++++++--------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/ide/src/trace/database/sql/Memory.sql.ts b/ide/src/trace/database/sql/Memory.sql.ts index a66e420f..2ceb95e1 100644 --- a/ide/src/trace/database/sql/Memory.sql.ts +++ b/ide/src/trace/database/sql/Memory.sql.ts @@ -188,23 +188,34 @@ Promise> => where startNS = ${startNs} and ipid = ${ipid};`, {} ); -export const queryMemoryConfig = (): Promise> => - query( +export const queryMemoryConfig = async (): Promise> => { + let keyList = await query( + 'queryIsColorIndex', + `select + key + from + trace_config`, + {}, + ); + //@ts-ignore + let keySql = keyList && keyList.length > 0 && keyList.some(entry => entry.key === 'ipid') ? "AND key = 'ipid'" : ""; + return query( 'queryMemoryConfiig', `SELECT ipid as iPid, process.pid AS pid, - process.name AS processName, - ( - SELECT value - FROM trace_config - WHERE trace_source = 'memory_config' AND key = 'sample_interval') AS interval - FROM - trace_config - LEFT JOIN process ON value = ipid - WHERE - trace_source = 'memory_config' - AND key = 'ipid' - ;` + process.name AS processName, + ( + SELECT value + FROM trace_config + WHERE trace_source = 'memory_config' AND key = 'sample_interval') AS interval + FROM + trace_config + LEFT JOIN process ON value = ipid + WHERE + trace_source = 'memory_config' + ${keySql} + ;` ); +} // VM Tracker Purgeable泳道图 export const queryPurgeableProcessData = ( -- Gitee From 7d208b5528d9c25eb6829d3745e1e607be416772 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 13 Aug 2024 14:51:28 +0800 Subject: [PATCH 15/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BA=86tid?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=97=B6=E6=89=93=E4=B8=8D=E5=BC=80trace?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/component/trace/base/Utils.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index 68590dd0..347a3cb7 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -648,9 +648,12 @@ export class Utils { } for (let i = 0; i < array1.length; i++) { // @ts-ignore - total.push(arr2Map.get(`${array1[i][0]}`)); - // @ts-ignore - arr2Map.delete(`${array1[i][0]}`); + if (arr2Map.get(`${array1[i][0]}`)) { + // @ts-ignore + total.push(arr2Map.get(`${array1[i][0]}`)); + // @ts-ignore + arr2Map.delete(`${array1[i][0]}`); + } }; // 将map中剩余的循环加在total后 // @ts-ignore @@ -715,7 +718,7 @@ export class Utils { } } } - queryNativeHookResponseTypes(val.leftNs, val.rightNs, types,val.nativeMemoryCurrentIPid, isStatistic).then((res): void => { + queryNativeHookResponseTypes(val.leftNs, val.rightNs, types, val.nativeMemoryCurrentIPid, isStatistic).then((res): void => { procedurePool.submitWithName('logic0', 'native-memory-init-responseType', res, undefined, (): void => { }); }); } -- Gitee From a9372a04b0dcb2ca07419de0af0821457d731b4e Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Tue, 13 Aug 2024 14:54:31 +0800 Subject: [PATCH 16/85] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=90=8E=E6=8C=89f?= =?UTF-8?q?=E9=94=AE=E6=B2=A1=E6=9C=89=E5=B1=85=E4=B8=AD=E9=97=AE=E9=A2=98?= =?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: wupoli3@huawei.com --- ide/src/trace/component/SpSystemTrace.event.ts | 4 ++++ ide/src/trace/component/trace/search/Search.ts | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index 355c6a0b..d4ab9cc1 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -617,6 +617,10 @@ export function spSystemTraceDocumentOnKeyPress(this: unknown, sp: SpSystemTrace } } if (keyPress === 'f') { + let search = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#lit-search'); + if(search && search.searchValue !== '' && sp.currentRow !== undefined) { + sp.currentRow = undefined; + } let isSelectSliceOrFlag = false; // 设置当前选中的slicetime let selectSlice: unknown = undefined; diff --git a/ide/src/trace/component/trace/search/Search.ts b/ide/src/trace/component/trace/search/Search.ts index 8d97d054..215fb120 100644 --- a/ide/src/trace/component/trace/search/Search.ts +++ b/ide/src/trace/component/trace/search/Search.ts @@ -197,10 +197,12 @@ export class LitSearch extends BaseElement { }, 200); } - private searchKeyupListener(e: KeyboardEvent): void { + private searchKeyupListener(e: unknown): void { timerId = null; - if (e.code === 'Enter' || e.code === 'NumpadEnter') { + // @ts-ignore + if (e.keyCode === 13 ) { this.updateSearchList(this.search!.value); + // @ts-ignore if (e.shiftKey) { this.dispatchEvent( new CustomEvent('previous-data', { @@ -224,6 +226,7 @@ export class LitSearch extends BaseElement { this.updateSearchHistoryList(this.search!.value); this.valueChangeHandler?.(this.trimSideSpace(this.search!.value)); } + // @ts-ignore e.stopPropagation(); } -- Gitee From 207134eb3a0449172a1ecabb66a92553c43c3e88 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 13 Aug 2024 15:11:36 +0800 Subject: [PATCH 17/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dtid=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=97=B6=E6=89=93=E4=B8=8D=E5=BC=80trace=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/component/trace/base/Utils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ide/src/trace/component/trace/base/Utils.ts b/ide/src/trace/component/trace/base/Utils.ts index 7a1d41d1..f4ef5058 100644 --- a/ide/src/trace/component/trace/base/Utils.ts +++ b/ide/src/trace/component/trace/base/Utils.ts @@ -648,9 +648,12 @@ export class Utils { } for (let i = 0; i < array1.length; i++) { // @ts-ignore - total.push(arr2Map.get(`${array1[i][0]}`)); - // @ts-ignore - arr2Map.delete(`${array1[i][0]}`); + if (arr2Map.get(`${array1[i][0]}`)) { + // @ts-ignore + total.push(arr2Map.get(`${array1[i][0]}`)); + // @ts-ignore + arr2Map.delete(`${array1[i][0]}`); + } }; // 将map中剩余的循环加在total后 // @ts-ignore -- Gitee From 5744879b1c3c15ffc29e565ecfe2cafdb02a5f9c Mon Sep 17 00:00:00 2001 From: wangziyi Date: Tue, 13 Aug 2024 15:21:39 +0800 Subject: [PATCH 18/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9cpu=20state=20cpu?= =?UTF-8?q?=E5=88=97=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../trace/sheet/cpu/TabPaneCounterSample.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneCounterSample.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneCounterSample.ts index 6034ab28..6e63e843 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneCounterSample.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneCounterSample.ts @@ -183,6 +183,17 @@ export class TabPaneCounterSample extends BaseElement { a.timeStr = parseFloat((a.time / 1000000.0).toFixed(6)); counterSampleList.push(a); }); + counterSampleList.sort((a, b): number => { + // @ts-ignore + let countLeftData = Number(a.counter.toString().replace('Cpu', '')); + // @ts-ignore + let countRightData = Number(b.counter.toString().replace('Cpu', '')); + if (countLeftData > countRightData) { + return 1; + } else { + return -1; + } + }); this.counterSampleSource = counterSampleList; this.sortTable(this.counterSortKey, this.counterSortType); }); @@ -251,13 +262,13 @@ export class TabPaneCounterSample extends BaseElement { } } else if (key === 'counter') { // @ts-ignore - if (sortByColumnLeftData.counter > sortByColumnRightData.counter) { - return type === 2 ? -1 : 1; - // @ts-ignore - } else if (sortByColumnLeftData.counter === sortByColumnRightData.counter) { - return 0; + let countLeftData = Number(sortByColumnLeftData.counter.toString().replace('Cpu', '')); + // @ts-ignore + let countRightData = Number(sortByColumnRightData.counter.toString().replace('Cpu', '')); + if (type === 1) { + return countLeftData - countRightData; } else { - return type === 2 ? 1 : -1; + return countRightData - countLeftData; } } else if (key === 'value') { if (type === 1) { -- Gitee From b167ff3e35affe9cbe77eb21b0e00376c631435f Mon Sep 17 00:00:00 2001 From: wangziyi Date: Tue, 13 Aug 2024 15:31:56 +0800 Subject: [PATCH 19/85] =?UTF-8?q?allocation=20lifespan=E6=94=B9=E4=B8=BAal?= =?UTF-8?q?location=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../component/trace/sheet/native-memory/TabPaneNMSampleList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMSampleList.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMSampleList.ts index 15100276..5b587a05 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMSampleList.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMSampleList.ts @@ -312,7 +312,7 @@ export class TabPaneNMSampleList extends BaseElement { }); TabPaneNMSampleList.tblData = this.shadowRoot?.querySelector('#tb-native-data'); TabPaneNMSampleList.filter = this.shadowRoot?.querySelector('#filter'); - this.shadowRoot?.querySelector('#filter')!.setSelectList(TabPaneNMSampleList.nativeType, null); + this.shadowRoot?.querySelector('#filter')!.setSelectList(TabPaneNMSampleList.nativeType, null, 'Allocation Type'); this.shadowRoot?.querySelector('#filter')!.getFilterData((data: FilterData) => { if (data.firstSelect) { TabPaneNMSampleList.filterSelect = data.firstSelect; -- Gitee From ce95d61a5a0ff3244e0a327ee9155ee56847d16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Tue, 13 Aug 2024 20:29:03 +0800 Subject: [PATCH 20/85] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=AF=91golang=20main=E7=A8=8B=E5=BA=8F=E6=97=B6=E6=B0=B8?= =?UTF-8?q?=E4=B9=85=E4=BF=AE=E6=94=B9=E4=BA=86=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- .gitignore | 1 + ide/webpack.config.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2a896624..26eee951 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ ide/dist ide/node_modules ide/package-lock.json ide/third-party +ide/src/trace/proto/SphBaseData.js trace_streamer/prebuilts/emsdk trace_streamer/prebuilts/linux trace_streamer/third_party diff --git a/ide/webpack.config.js b/ide/webpack.config.js index f7702c1c..4dadebe6 100644 --- a/ide/webpack.config.js +++ b/ide/webpack.config.js @@ -88,8 +88,8 @@ function buildMultiPlatform() { const generateFile = platform === 'windows' ? path.normalize(path.join(outPath, '/', `main.exe`)) : path.normalize(path.join(outPath, '/', `main_${platform}`)); - const setEnv = `go env -w CGO_ENABLED=0 && go env -w GOOS=${platform} && go env -w GOARCH=amd64`; - const buildCmd = `${setEnv} && go build -o ${generateFile} ${serverSrc}`; + const setEnv = `CGO_ENABLED=0 GOOS=${platform} GOARCH=amd64`; + const buildCmd = `${setEnv} go build -o ${generateFile} ${serverSrc}`; console.log(`compile ${platform} server ...`); childProcess.execSync(buildCmd); } -- Gitee From 21174a7d2ec5488680188d8256439b92b3fcb733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Tue, 13 Aug 2024 20:36:11 +0800 Subject: [PATCH 21/85] =?UTF-8?q?feat:=20native=20memory=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E6=95=B0=E6=8D=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- .../filter/hook_filter/native_hook_filter.cpp | 14 +++++++- .../filter/hook_filter/native_hook_filter.h | 3 ++ .../htrace/native_memory_stdtype.cpp | 32 +++++++++++++++++++ .../htrace/native_memory_stdtype.h | 2 ++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp b/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp index 6c654713..2ebc72f4 100644 --- a/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp +++ b/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp @@ -220,7 +220,11 @@ void NativeHookFilter::ParseStatisticEvent(uint64_t timeStamp, const ProtoReader // when isOfflineSymblolizationMode_ is false, the stack id is unique callChainId = ipidWithCallChainIdIndex; } - + if (reader.apply_size() == reader.release_size() && callChainIdsSet_.find(callChainId) == callChainIdsSet_.end()) { + return; + } else { + callChainIdsSet_.emplace(callChainId); + } DataIndex memSubType = INVALID_UINT64; if (reader.has_tag_name()) { memSubType = traceDataCache_->GetDataIndex(reader.tag_name().ToStdString()); @@ -1134,6 +1138,14 @@ void NativeHookFilter::FinishParseNativeHookData() // update last lib id UpdateLastCallerPathAndSymbolIndexs(); UpdateThreadNameWithNativeHookData(); + if (isStatisticMode_) { + traceDataCache_->GetNativeHookFrameData()->ClearUselessCallChainIds(callChainIdsSet_); + filePathIndexToFrameTableRowMap_.clear(); + const auto &vec = traceDataCache_->GetConstNativeHookFrameData().FilePaths(); + for (size_t i = 0; i < vec.size(); i++) { + UpdateFilePathIndexToCallStackRowMap(i, vec[i]); + } + } } void NativeHookFilter::UpdateLastCallerPathAndSymbolIndexs() { diff --git a/trace_streamer/src/filter/hook_filter/native_hook_filter.h b/trace_streamer/src/filter/hook_filter/native_hook_filter.h index acc93636..a010e9c5 100644 --- a/trace_streamer/src/filter/hook_filter/native_hook_filter.h +++ b/trace_streamer/src/filter/hook_filter/native_hook_filter.h @@ -14,7 +14,9 @@ */ #ifndef NATIVE_HOOK_FILTER_H #define NATIVE_HOOK_FILTER_H +#include #include +#include #include "common_types.pb.h" #include "native_hook_result.pb.h" #include "numerical_to_string.h" @@ -185,6 +187,7 @@ private: const size_t MAX_CACHE_SIZE = 200000; uint32_t callChainId_ = 0; CommHookData commHookData_; + std::unordered_set callChainIdsSet_; }; } // namespace TraceStreamer } // namespace SysTuning diff --git a/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.cpp b/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.cpp index 7438e12c..835d95ce 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.cpp +++ b/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.cpp @@ -229,6 +229,38 @@ void NativeHookFrame::UpdateVaddrs(std::deque &vaddrs) { vaddrs_.assign(vaddrs.begin(), vaddrs.end()); } +void NativeHookFrame::ClearUselessCallChainIds(const std::unordered_set &callChainIdsSet) +{ + std::deque callChainIdsTmp; + std::deque depthsTmp; + std::deque ipsTmp; + std::deque symbolNamesTmp; + std::deque filePathsTmp; + std::deque offsetsTmp; + std::deque symbolOffsetsTmp; + std::deque vaddrsTmp; + for (size_t i = 0; i < callChainIds_.size(); i++) { + if (callChainIdsSet.find(callChainIds_[i]) == callChainIdsSet.end()) { + continue; + } + callChainIdsTmp.emplace_back(callChainIds_[i]); + depthsTmp.emplace_back(depths_[i]); + ipsTmp.emplace_back(ips_[i]); + symbolNamesTmp.emplace_back(symbolNames_[i]); + filePathsTmp.emplace_back(filePaths_[i]); + offsetsTmp.emplace_back(offsets_[i]); + symbolOffsetsTmp.emplace_back(symbolOffsets_[i]); + vaddrsTmp.emplace_back(vaddrs_[i]); + } + callChainIds_.swap(callChainIdsTmp); + depths_.swap(depthsTmp); + ips_.swap(ipsTmp); + symbolNames_.swap(symbolNamesTmp); + filePaths_.swap(filePathsTmp); + offsets_.swap(offsetsTmp); + symbolOffsets_.swap(symbolOffsetsTmp); + vaddrs_.swap(vaddrsTmp); +} const std::deque &NativeHookFrame::CallChainIds() const { return callChainIds_; diff --git a/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.h b/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.h index c2218b2b..13bffc6c 100644 --- a/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.h +++ b/trace_streamer/src/trace_data/trace_stdtype/htrace/native_memory_stdtype.h @@ -17,6 +17,7 @@ #define NATIVE_MEMORY_STDTYPE_H #include "base_stdtype.h" #include +#include namespace SysTuning { namespace TraceStdtype { @@ -160,6 +161,7 @@ public: void UpdateSymbolId(size_t index, DataIndex symbolId); void UpdateFileId(std::map &filePathIdToFilePathName); void UpdateVaddrs(std::deque &vaddrs); + void ClearUselessCallChainIds(const std::unordered_set &callChainIdsSet); const std::deque &CallChainIds() const; const std::deque &Depths() const; const std::deque &Ips() const; -- Gitee From cc468125d5226d7d261e4259ab474f936ce3d556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Tue, 13 Aug 2024 21:19:39 +0800 Subject: [PATCH 22/85] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81windows?= =?UTF-8?q?=E4=BA=8C=E8=BF=9B=E5=88=B6=E7=A6=BB=E7=BA=BFSo=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- .gitignore | 4 ++++ trace_streamer/.gitignore | 20 ------------------- trace_streamer/src/base/file.cpp | 4 ++-- trace_streamer/src/base/file.h | 4 ++-- trace_streamer/src/main.cpp | 2 +- .../pbreader_parser/pbreader_parser.cpp | 8 +++++--- trace_streamer/src/rpc/rpc_server.cpp | 3 ++- 7 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 trace_streamer/.gitignore diff --git a/.gitignore b/.gitignore index 26eee951..6ec0ed2f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,12 @@ ide/third-party ide/src/trace/proto/SphBaseData.js trace_streamer/prebuilts/emsdk trace_streamer/prebuilts/linux +trace_streamer/prebuilts/windows trace_streamer/third_party +trace_streamer/tools trace_streamer/compile_commands.json +trace_streamer/*.tar.gz +trace_streamer/*.zip ts_tmp.perf.data trace_streamer/.cache tmp_* diff --git a/trace_streamer/.gitignore b/trace_streamer/.gitignore deleted file mode 100644 index b185167f..00000000 --- a/trace_streamer/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -out -.vscode -tmp_* -build-* -*.pro.use* -.DS_Store -._.DS_Store -emsdk -prebuilts/dist -prebuilts/linux -third_party -.idea -tools -tmp -*.tar.gz* -out.perf -perf.data -out.floded -./.db -.history \ No newline at end of file diff --git a/trace_streamer/src/base/file.cpp b/trace_streamer/src/base/file.cpp index 10d33d46..35a79cde 100644 --- a/trace_streamer/src/base/file.cpp +++ b/trace_streamer/src/base/file.cpp @@ -75,7 +75,7 @@ std::string GetExecutionDirectoryPath() std::string str(currPath); return str.substr(0, str.find_last_of('/')); } -#ifdef is_linux +#if defined(is_linux) || defined(_WIN32) std::vector GetFilesNameFromDir(const std::string &path) { std::vector soFiles; @@ -88,7 +88,7 @@ std::vector GetFilesNameFromDir(const std::string &path) } // 遍历目录 for (const auto &entry : std::filesystem::directory_iterator(dirPath)) { - soFiles.emplace_back(entry.path().string()); + soFiles.emplace_back(entry.path().filename().string()); } return soFiles; } diff --git a/trace_streamer/src/base/file.h b/trace_streamer/src/base/file.h index 9f5eedb7..6c7541c8 100644 --- a/trace_streamer/src/base/file.h +++ b/trace_streamer/src/base/file.h @@ -16,7 +16,7 @@ #ifndef INCLUDE_TUNING_BASE_FILE_UTILS_H #define INCLUDE_TUNING_BASE_FILE_UTILS_H -#ifdef is_linux +#if defined(is_linux) || defined(_WIN32) #include #endif #include @@ -42,7 +42,7 @@ ssize_t Read(int32_t fd, uint8_t *dst, size_t dstSize); int32_t OpenFile(const std::string &path, int32_t flags, uint32_t mode = K_FILE_MODE_INVALID); std::string GetExecutionDirectoryPath(); -#ifdef is_linux +#if defined(is_linux) || defined(_WIN32) std::vector GetFilesNameFromDir(const std::string &path); #endif } // namespace base diff --git a/trace_streamer/src/main.cpp b/trace_streamer/src/main.cpp index c4a8cf39..8aff41ae 100644 --- a/trace_streamer/src/main.cpp +++ b/trace_streamer/src/main.cpp @@ -739,7 +739,7 @@ int main(int argc, char **argv) } return 1; } -#ifdef is_linux +#if defined(is_linux) || defined(_WIN32) if (!traceExportOption.soFilesDir.empty()) { auto values = GetFilesNameFromDir(traceExportOption.soFilesDir); ts.ReloadSymbolFiles(traceExportOption.soFilesDir, values); diff --git a/trace_streamer/src/parser/pbreader_parser/pbreader_parser.cpp b/trace_streamer/src/parser/pbreader_parser/pbreader_parser.cpp index f7badb15..921c87c3 100644 --- a/trace_streamer/src/parser/pbreader_parser/pbreader_parser.cpp +++ b/trace_streamer/src/parser/pbreader_parser/pbreader_parser.cpp @@ -178,11 +178,13 @@ void PbreaderParser::InitPluginNameIndex() void PbreaderParser::ParserFileSO(std::string &directory, const std::vector &relativeFilePaths) { for (const auto &filePath : relativeFilePaths) { - auto absoluteFilePath = filePath.substr(directory.length()); auto symbolsFile = - OHOS::Developtools::HiPerf::SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, absoluteFilePath); + OHOS::Developtools::HiPerf::SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, filePath); symbolsFile->setSymbolsFilePath(directory); - symbolsFile->LoadSymbols(nullptr, absoluteFilePath); + auto res = symbolsFile->LoadSymbols(nullptr, filePath); + if (!res) { + continue; + } symbolsFiles_.emplace_back(std::move(symbolsFile)); } } diff --git a/trace_streamer/src/rpc/rpc_server.cpp b/trace_streamer/src/rpc/rpc_server.cpp index 82f5bbe3..924aebc0 100644 --- a/trace_streamer/src/rpc/rpc_server.cpp +++ b/trace_streamer/src/rpc/rpc_server.cpp @@ -740,7 +740,8 @@ bool RpcServer::DownloadELFCallback(const std::string &fileName, return false; } TS_LOGI("symbolsPath = %s, fileName = %s", symbolsPath.c_str(), fileName.c_str()); - symbolsPathFiles_.emplace_back(fileName); + std::filesystem::path stdFileName(fileName); + symbolsPathFiles_.emplace_back(stdFileName.filename().string()); parseELFFile("file send over\r\n", SEND_FINISH); // When the transfer is completed, reload the symbol file, clear the symbol path file list, call the callback // function, and delete the symbol path and all files under it -- Gitee From b6fa0ddd665ad60648fd7311df8742955d25bd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Wed, 14 Aug 2024 10:28:35 +0800 Subject: [PATCH 23/85] =?UTF-8?q?fix:=20=E7=BB=9F=E8=AE=A1=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=95=B0=E6=8D=AE=E5=86=97=E4=BD=99=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=9C=A8wasm=E4=B8=8B=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- .../src/filter/hook_filter/native_hook_filter.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp b/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp index 2ebc72f4..6494113b 100644 --- a/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp +++ b/trace_streamer/src/filter/hook_filter/native_hook_filter.cpp @@ -220,11 +220,14 @@ void NativeHookFilter::ParseStatisticEvent(uint64_t timeStamp, const ProtoReader // when isOfflineSymblolizationMode_ is false, the stack id is unique callChainId = ipidWithCallChainIdIndex; } +#if !defined(IS_WASM) + // 统计模式数据冗余优化在wasm下关闭 if (reader.apply_size() == reader.release_size() && callChainIdsSet_.find(callChainId) == callChainIdsSet_.end()) { return; } else { callChainIdsSet_.emplace(callChainId); } +#endif DataIndex memSubType = INVALID_UINT64; if (reader.has_tag_name()) { memSubType = traceDataCache_->GetDataIndex(reader.tag_name().ToStdString()); @@ -1138,6 +1141,8 @@ void NativeHookFilter::FinishParseNativeHookData() // update last lib id UpdateLastCallerPathAndSymbolIndexs(); UpdateThreadNameWithNativeHookData(); +#if !defined(IS_WASM) + // 统计模式数据冗余优化在wasm下关闭 if (isStatisticMode_) { traceDataCache_->GetNativeHookFrameData()->ClearUselessCallChainIds(callChainIdsSet_); filePathIndexToFrameTableRowMap_.clear(); @@ -1146,6 +1151,7 @@ void NativeHookFilter::FinishParseNativeHookData() UpdateFilePathIndexToCallStackRowMap(i, vec[i]); } } +#endif } void NativeHookFilter::UpdateLastCallerPathAndSymbolIndexs() { -- Gitee From 872cda67637658e695c7c0e5ff19cba07cfa4f5e Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Wed, 14 Aug 2024 16:23:25 +0800 Subject: [PATCH 24/85] =?UTF-8?q?=E4=B8=8B=E6=8B=89=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E6=9D=A1tab=E9=A1=B5=E9=9A=90=E8=97=8F=E5=90=8E=E6=B3=B3?= =?UTF-8?q?=E9=81=93=E4=B8=8B=E6=96=B9=E5=87=BA=E7=8E=B0=E7=A9=BA=E7=99=BD?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/component/SpSystemTrace.init.ts | 1 + ide/src/trace/component/SpSystemTrace.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 39805c3a..a3221622 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -1154,6 +1154,7 @@ export async function spSystemTraceInit( } function expansionChangeHandler(sp: SpSystemTrace, offsetYTimeOut: unknown): (event: unknown) => void { return function (event: unknown) { + sp.scrollH = sp.rowsPaneEL!.scrollHeight; let max = [...sp.rowsPaneEL!.querySelectorAll('trace-row')].reduce((pre, cur) => pre + cur.clientHeight!, 0); let offset = sp.rowsPaneEL!.scrollHeight - max; sp.rowsPaneEL!.scrollTop = sp.rowsPaneEL!.scrollTop - offset; diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index ccbaf82b..40dfe677 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -1833,7 +1833,7 @@ export class SpSystemTrace extends BaseElement { this.subscribeBottomTabVisibleEvent(); } - private scrollH: number = 0; + public scrollH: number = 0; subscribeBottomTabVisibleEvent(): void { //@ts-ignore -- Gitee From 1ea90e3a29a488a56268a436648238159d6f28b1 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Wed, 14 Aug 2024 17:42:45 +0800 Subject: [PATCH 25/85] =?UTF-8?q?fix:=E6=B3=B3=E9=81=93=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../trace/component/trace/base/ColorUtils.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index 700a0058..dbc99d10 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -20,46 +20,46 @@ export class ColorUtils { public static FUNC_COLOR_A: Array = [ '#A37775', + '#B1CDF1', '#76D1C0', '#0CBDD4', - '#ADB7DB', + '#36BAA4', '#69D3E5', + '#E3AA7D', '#7DA6F4', - '#B1CDF1', '#E68C43', - '#E3AA7D', - '#36BAA4', - '#E86B6A', '#99C47C', - '#998FE6', - '#8770D3', + '#A56DF5', + '#E86B6A', '#DC8077', + '#ADB7DB', '#A1CD94', '#66C7BA', + '#998FE6', '#E7B75D', - '#A56DF5', + '#8770D3', '#93D090', ]; public static FUNC_COLOR_B: Array = [ '#23b0e7', + '#aa4fba', '#4ca694', '#8d9171', - '#8091D0', + '#ebc247', '#8a8a8b', + '#78aec2', '#FF0066', - '#aa4fba', '#a16a40', - '#78aec2', - '#ebc247', - '#9fafc4', '#e05b52', - '#8983B5', - '#40b3e7', + '#7a9160', + '#9fafc4', '#9bb87a', + '#8091D0', '#c2cc66', '#a94eb9', + '#8983B5', '#B9A683', - '#7a9160', + '#40b3e7', '#789876', ]; -- Gitee From d5a8918d33256cf3993fa23304dc2d488c441f94 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Thu, 15 Aug 2024 11:28:41 +0800 Subject: [PATCH 26/85] =?UTF-8?q?native=20memory=E4=BF=AE=E6=94=B9addr?= =?UTF-8?q?=E4=B8=BA16=E8=BF=9B=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- .../component/trace/sheet/native-memory/TabPaneNMemory.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts index 2b09465d..9fa387ca 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts @@ -135,6 +135,12 @@ export class TabPaneNMemory extends BaseElement { this.tblData!.recycleDataSource = []; this.setNmMemoryLoading(false); if (results.length > 0) { + results.forEach((item) => { + //@ts-ignore + let tmpNumber = item.addr.split('x'); + //@ts-ignore + item.addr = '0x' + Number(tmpNumber[1]).toString(16); + }) this.memorySource = results; this.memoryTbl!.recycleDataSource = this.memorySource; } else { -- Gitee From 036066f6745f4aa7fee1afa5ff3854f8f37a64c7 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Thu, 15 Aug 2024 11:44:03 +0800 Subject: [PATCH 27/85] =?UTF-8?q?HDC=E9=89=B4=E6=9D=83=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=89=E5=85=A8=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- ide/src/hdc/hdcclient/HdcClient.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ide/src/hdc/hdcclient/HdcClient.ts b/ide/src/hdc/hdcclient/HdcClient.ts index b107c6b1..93c726df 100644 --- a/ide/src/hdc/hdcclient/HdcClient.ts +++ b/ide/src/hdc/hdcclient/HdcClient.ts @@ -39,6 +39,7 @@ export class HdcClient implements DataListener { private isSuccess: boolean = false; private handBody: DataView | undefined; private message: DataMessage | undefined; + private isSigna: boolean = false; constructor( transmissionChannel: TransmissionInterface, @@ -55,7 +56,7 @@ export class HdcClient implements DataListener { this.sessionId = Utils.getSessionId(); log(`sessionId is ${this.sessionId}`); this.isSuccess = false; - await this.handShakeConnect(AuthType.AUTH_NONE, ''); + await this.handShakeConnect(AuthType.AUTH_NONE, 'authtype 1 1'); let timeStamp = new Date().getTime(); while (await this.readHandShakeMsg()) { if (new Date().getTime() - timeStamp > 10000) { @@ -76,13 +77,24 @@ export class HdcClient implements DataListener { case AuthType.AUTH_TOKEN: continue; case AuthType.AUTH_SIGNATURE: - const response = await fetch(`${window.location.origin}/application/encryptHdcMsg?message=` + returnBuf); + const hdcMsgUrl = this.isSigna ? 'signatureHdcMsg' : 'encryptHdcMsg' + const response = await fetch(`${window.location.origin}/application/${hdcMsgUrl}?message=` + returnBuf); const dataBody = await response.json(); - const encryptHdcMsg = dataBody.success && dataBody.data.signatures; - await this.handShakeConnect(AuthType.AUTH_SIGNATURE, encryptHdcMsg); + let signatureHdcMsg = ''; + if (dataBody.success) { + signatureHdcMsg = dataBody.data.signatures; + } else { + break; + } + await this.handShakeConnect(AuthType.AUTH_SIGNATURE, signatureHdcMsg); timeStamp = new Date().getTime(); continue; case AuthType.AUTH_PUBLICKEY: + if (returnBuf === 'authtype 1 1') { + this.isSigna = true; + } else { + this.isSigna = false; + } const responsePub = await fetch(`${window.location.origin}/application/hdcPublicKey`); const data = await responsePub.json(); const publicKey = data.success && (`smartPerf-Host` + String.fromCharCode(12) + data.data.publicKey); -- Gitee From 577f4ad562aa79a2b651aec46324b3c691651f99 Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Fri, 16 Aug 2024 09:40:27 +0800 Subject: [PATCH 28/85] =?UTF-8?q?f=E9=94=AE=E6=97=97=E5=AD=90=E8=B7=91?= =?UTF-8?q?=E9=81=93=E7=82=B9=E9=80=89=E4=B8=89=E7=A7=8D=E5=B1=85=E4=B8=AD?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/component/SpSystemTrace.event.ts | 8 +++++++- ide/src/trace/component/trace/timer-shaft/RangeRuler.ts | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index d4ab9cc1..18b46c8a 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -419,6 +419,9 @@ export default function spSystemTraceOnClickHandler( } sp.queryAllTraceRow().forEach((it): boolean => (it.rangeSelect = false)); sp.selectStructNull(); + sp._slicesList.forEach((slice: { selected: boolean }): void => { + slice.selected = false; + }); // 判断点击的线程是否在唤醒树内 timeoutJudge(sp); allStructOnClick(clickRowType, sp, row, entry); @@ -604,6 +607,9 @@ export function spSystemTraceDocumentOnKeyPress(this: unknown, sp: SpSystemTrace sp.observerScrollHeightEnable = false; if (sp.keyboardEnable) { if (keyPress === 'm') { + if(sp.selectFlag) { + sp.selectFlag!.selected = false; + } sp.slicestime = sp.setSLiceMark(ev.shiftKey); if (sp.slicestime) { if (TraceRow.rangeSelectObject) { @@ -637,7 +643,7 @@ export function spSystemTraceDocumentOnKeyPress(this: unknown, sp: SpSystemTrace isSelectSliceOrFlag = true; } - if (!!sp.selectFlag) { + if (sp.selectFlag && sp.selectFlag.selected) { sp.currentSlicesTime.startTime = sp.selectFlag?.time; sp.currentSlicesTime.endTime = sp.selectFlag?.time; isSelectSliceOrFlag = true; diff --git a/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts b/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts index 8e1b143f..841a2d44 100644 --- a/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts +++ b/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts @@ -634,11 +634,11 @@ export class RangeRuler extends Graph { if (startTime === endTime) { let midNs = (this.range.endNS - this.range.startNS) / 2; if (startTime > midNs && startTime - midNs < this.range.totalNS - this.range.endNS) { - this.range.startNS += startTime - midNs; - this.range.endNS += startTime - midNs; + this.range.startNS = this.range.startNS + startTime - midNs; + this.range.endNS = this.range.endNS + startTime - midNs; } else if (startTime < midNs && midNs - startTime < this.range.startNS) { - this.range.startNS -= midNs - startTime; - this.range.endNS -= midNs - startTime; + this.range.startNS = this.range.startNS - midNs + startTime; + this.range.endNS = this.range.endNS - midNs + startTime; } else if (startTime > midNs && startTime - midNs > this.range.totalNS - this.range.endNS) { this.range.startNS = 2 * startTime - this.range.totalNS; this.range.endNS = this.range.totalNS; -- Gitee From 98717dc90f9e4da0c9d06a20fa1cca47daedc50b Mon Sep 17 00:00:00 2001 From: liufei Date: Fri, 16 Aug 2024 10:31:40 +0800 Subject: [PATCH 29/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dcallchart=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E7=BA=BF=E7=A8=8B=E4=B9=8B=E5=90=8Etab=E9=A1=B5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/bean/BoxSelection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index 4e98c120..5b505e97 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -1011,7 +1011,6 @@ export class SelectionParam { // @ts-ignore pushThread(it: TraceRow, sp: SpSystemTrace): void { - this.perfEventTypeId = TraceRow.ROW_TYPE_HIPERF_THREADTYPE[0] === -2 ? undefined : TraceRow.ROW_TYPE_HIPERF_THREADTYPE[0]; if (it.rowType === TraceRow.ROW_TYPE_THREAD) { sp.pushPidToSelection(this, it.rowParentId!); if (it.dataListCache && it.dataListCache.length) { -- Gitee From 4bca0e27b4d45a81bda20abb67e2185d1a5074bb Mon Sep 17 00:00:00 2001 From: zhangyan Date: Fri, 16 Aug 2024 10:53:33 +0800 Subject: [PATCH 30/85] =?UTF-8?q?feat:=20=E6=AF=8F=E4=B8=AAcpu=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E7=AE=97=E5=8A=9B=E5=80=BC=E5=9C=A8=E7=B4=AF=E5=8A=A0?= =?UTF-8?q?=E5=89=8D=E9=9C=80=E6=B7=BB=E5=8A=A0smtRate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/chart/SpSegmentationChart.ts | 6 +++--- .../trace/sheet/frequsage/TabPaneFreqDataCut.ts | 9 ++++++--- ide/src/trace/database/TabPaneFreqUsageWorker.ts | 11 +++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ide/src/trace/component/chart/SpSegmentationChart.ts b/ide/src/trace/component/chart/SpSegmentationChart.ts index e1db3d5f..0fecf881 100644 --- a/ide/src/trace/component/chart/SpSegmentationChart.ts +++ b/ide/src/trace/component/chart/SpSegmentationChart.ts @@ -34,7 +34,7 @@ export class SpSegmentationChart { static GpuRow: TraceRow | undefined; static binderRow: TraceRow | undefined; static schedRow: TraceRow | undefined; - static freqInfoMapData = new Map>(); + static freqInfoMapData = new Map(); static hoverLine: Array = []; static tabHoverObj: { key: string, cycle: number }; private rowFolder!: TraceRow; @@ -209,7 +209,7 @@ export class SpSegmentationChart { SpSegmentationChart.cpuRow.rowSetting = 'checkFile'; // 拿到了用户传递的数据 SpSegmentationChart.cpuRow.onRowCheckFileChangeHandler = (): void => { - SpSegmentationChart.freqInfoMapData = new Map>(); + SpSegmentationChart.freqInfoMapData = new Map(); if (sessionStorage.getItem('freqInfoData')) { // @ts-ignore let chartData = JSON.parse(JSON.parse(sessionStorage.getItem('freqInfoData'))); @@ -219,7 +219,7 @@ export class SpSegmentationChart { for (let key in v.freqInfo) { mapData.set(Number(key), Number(v.freqInfo[key])); } - SpSegmentationChart.freqInfoMapData.set(v.cpuId, mapData); + SpSegmentationChart.freqInfoMapData.set(v.cpuId, {'smtRate': v.smtRate, mapData}); mapData = new Map(); }); TabPaneFreqUsage.refresh(); diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqDataCut.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqDataCut.ts index c5f431ba..1e9c61cc 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqDataCut.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqDataCut.ts @@ -524,7 +524,8 @@ export class TabPaneFreqDataCut extends BaseElement { let percent = Number(value[j].percent); // @ts-ignore let consumptionMap: Map = - SpSegmentationChart.freqInfoMapData.size > 0 && SpSegmentationChart.freqInfoMapData.get(Number(value[j].cpu)); + //@ts-ignore + SpSegmentationChart.freqInfoMapData.size > 0 && SpSegmentationChart.freqInfoMapData.get(Number(value[j].cpu))?.mapData; // 若存在算力值,则直接取值做计算。若不存在算力值,且频点值不为unknown的情况,则取频点值做计算,若为unknown,则取0做兼容 const consumption: number = Number( consumptionMap && consumptionMap.get(Number(value[j].freq)) @@ -905,7 +906,8 @@ export class TabPaneFreqDataCut extends BaseElement { let percent = Number(value[j].percent); // @ts-ignore let consumptionMap: Map = - SpSegmentationChart.freqInfoMapData.size > 0 && SpSegmentationChart.freqInfoMapData.get(Number(value[j].cpu)); + //@ts-ignore + SpSegmentationChart.freqInfoMapData.size > 0 && SpSegmentationChart.freqInfoMapData.get(Number(value[j].cpu))?.mapData; // 若存在算力值,则直接取值做计算。若不存在算力值,且频点值不为unknown的情况,则取频点值做计算,若为unknown,则取0做兼容 const consumption: number = Number( consumptionMap && consumptionMap.get(Number(value[j].freq)) @@ -1416,7 +1418,8 @@ export class TabPaneFreqDataCut extends BaseElement { // @ts-ignore let freq: Map = SpSegmentationChart.freqInfoMapData.size > 0 && - SpSegmentationChart.freqInfoMapData.get(SpSegmentationChart.freqInfoMapData.size - 1); + //@ts-ignore + SpSegmentationChart.freqInfoMapData.get(SpSegmentationChart.freqInfoMapData.size - 1)?.mapData; // @ts-ignore let yAxis: number = freq && freq.get(Number(maxFreqValue) * 1000) ? freq.get(Number(maxFreqValue) * 1000) : Number(maxFreqValue); diff --git a/ide/src/trace/database/TabPaneFreqUsageWorker.ts b/ide/src/trace/database/TabPaneFreqUsageWorker.ts index 6ec42caf..fc79b946 100644 --- a/ide/src/trace/database/TabPaneFreqUsageWorker.ts +++ b/ide/src/trace/database/TabPaneFreqUsageWorker.ts @@ -15,7 +15,7 @@ import { type CpuFreqData, type RunningFreqData, type RunningData } from '../component/trace/sheet/frequsage/TabPaneFreqUsageConfig'; -let comPower = new Map>(); +let comPower = new Map>(); let resultArray: Array = []; let timeZones: number = 0; let maxCommpuPower: number = 0; @@ -170,7 +170,8 @@ function returnObj( ): RunningFreqData | undefined { const PERCENT: number = 100; const FREQ_MUTIPLE: number = 1000; - const computorPower: number = comPower ? comPower.get(item.cpu)?.get(cpuFreqData.value)! : 0; + //@ts-ignore + const computorPower: number = comPower ? comPower.get(item.cpu)?.mapData.get(cpuFreqData.value)! : 0; switch (flag) { case 1: return { @@ -446,13 +447,15 @@ self.onmessage = (e: MessageEvent): void => { comPower.forEach(item => { let maxFreq = 0; let commpuPower = 0; - for (const i of item.entries()) { + //@ts-ignore + for (const i of item.mapData.entries()) { if (i[0] > maxFreq) { maxFreq = i[0]; commpuPower = i[1]; } } - maxCommpuPower += commpuPower; + //@ts-ignore + maxCommpuPower += commpuPower * item.smtRate; }); } let result = orgnazitionMap(e.data); -- Gitee From a91defeee3888bab5828905b5f9b3ef470a0ebf5 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Fri, 16 Aug 2024 11:30:07 +0800 Subject: [PATCH 31/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9hiperf=E4=B8=ADanalysis?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84other=E6=98=BE=E7=A4=BAundefined?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../trace/sheet/hiperf/TabPanePerfAnalysis.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts index 7ab5b147..ffa642a5 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts @@ -40,6 +40,7 @@ export class TabPanePerfAnalysis extends BaseElement { private perfTableSo: LitTable | null | undefined; private tableFunction: LitTable | null | undefined; private sumCount: number | undefined | null; + private sumEventCount: number | undefined | null; private perfAnalysisRange: HTMLLabelElement | null | undefined; private back: HTMLDivElement | null | undefined; private tabName: HTMLDivElement | null | undefined; @@ -317,6 +318,8 @@ export class TabPanePerfAnalysis extends BaseElement { private processPieChart(val: SelectionParam): void { // @ts-ignore this.sumCount = this.allProcessCount.allCount; + // @ts-ignore + this.sumEventCount = this.allProcessCount.allEventCount; this.perfAnalysisPie!.config = { appendPadding: 0, data: this.getPerfPieChartData(this.pidData), @@ -409,6 +412,8 @@ export class TabPanePerfAnalysis extends BaseElement { } // @ts-ignore this.sumCount = this.allThreadCount.allCount; + // @ts-ignore + this.sumEventCount = this.allSymbolCount.allEventCount; this.perfAnalysisPie!.config = { appendPadding: 0, data: this.getPerfPieChartData(this.threadData), @@ -512,6 +517,8 @@ export class TabPanePerfAnalysis extends BaseElement { private libraryPieChart(): void { // @ts-ignore this.sumCount = this.allLibCount.allCount; + // @ts-ignore + this.sumEventCount = this.allSymbolCount.allEventCount; this.initPerfAnalysisPieConfig(); let pName = this.processName; if (this.processName.length > 0 && this.threadName.length > 0) { @@ -976,6 +983,8 @@ export class TabPanePerfAnalysis extends BaseElement { this.progressEL!.loading = false; // @ts-ignore this.sumCount = this.allSymbolCount.allCount; + // @ts-ignore + this.sumEventCount = this.allSymbolCount.allEventCount; this.perfAnalysisPie!.config = { appendPadding: 0, data: this.getPerfPieChartData(this.functionData), @@ -1054,6 +1063,8 @@ export class TabPanePerfAnalysis extends BaseElement { tableName: 'other', count: 0, percent: 0, + eventCount: 0, + eventPercent: 0, }; for (let i = 0; i < res.length; i++) { if (i < 19) { @@ -1064,6 +1075,10 @@ export class TabPanePerfAnalysis extends BaseElement { other.count += res[i].count; // @ts-ignore other.percent = ((other.count / this.sumCount!) * 100).toFixed(2); + // @ts-ignore + other.eventCount += res[i].eventCount; + // @ts-ignore + other.eventPercent = ((other.eventCount / this.sumEventCount!) * 100).toFixed(2); } } // @ts-ignore -- Gitee From eb1a25c56b8d5f709f83bd910236e66c7ca08a8b Mon Sep 17 00:00:00 2001 From: wangyujie Date: Fri, 16 Aug 2024 11:31:11 +0800 Subject: [PATCH 32/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E5=89=AA=E5=88=80?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/SpApplication.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index b76f0df6..8cbe8ef7 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -2091,7 +2091,7 @@ export class SpApplication extends BaseElement { } }); }); - this.cutTraceFile!.style.display = 'none'; + this.cutTraceFile!.style.display = 'block'; this.mainMenu!.menus = this.mainMenu!.menus; } }); -- Gitee From 92a7613799a713547f4edda2eae992e4a00210d3 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Fri, 16 Aug 2024 11:47:40 +0800 Subject: [PATCH 33/85] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E7=9A=84db=E6=96=87=E4=BB=B6=E5=90=ABmark=E7=9A=84json?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=B9=B6=E8=AF=86=E5=88=AB=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/SpApplication.ts | 45 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index e37aac6a..5d892d01 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -602,22 +602,36 @@ export class SpApplication extends BaseElement { let showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.')); TraceRow.rangeSelectObject = undefined; //@ts-ignore - let typeHeader = ev.slice(0, 6); + let typeStr = ev.slice(0, 100); let reader: FileReader | null = new FileReader(); - reader.readAsText(typeHeader); + reader.readAsText(typeStr); reader.onloadend = (event): void => { - let headerStr: string = `${reader?.result}`; - SpApplication.traceType = headerStr; - if (headerStr.indexOf('SQLite') === 0) { - info('Parse trace headerStr sql mode'); - this.wasm = false; + let isIncludeMark = `${reader?.result}`.includes('MarkPositionJSON'); + let typeHeader; + if (isIncludeMark) { + let markLength = `${reader?.result}`.split('->')[0].replace('MarkPositionJSON', ''); //@ts-ignore - this.handleSqliteMode(ev, showFileName, ev.size, fileName); - } else { - info('Parse trace using wasm mode '); - this.wasm = true; + typeHeader = ev.slice(markLength.length + parseInt(markLength), markLength.length + parseInt(markLength) + 6); + } else{ //@ts-ignore - this.handleWasmMode(ev, showFileName, ev.size, fileName); + typeHeader = ev.slice(0, 6); + } + let fileReader: FileReader | null = new FileReader(); + fileReader.readAsText(typeHeader); + fileReader.onload = (event): void => { + let headerStr: string = `${fileReader?.result}`; + SpApplication.traceType = headerStr; + if (headerStr.indexOf('SQLite') === 0) { + info('Parse trace headerStr sql mode'); + this.wasm = false; + //@ts-ignore + this.handleSqliteMode(ev, showFileName, ev.size, fileName); + } else { + info('Parse trace using wasm mode '); + this.wasm = true; + //@ts-ignore + this.handleWasmMode(ev, showFileName, ev.size, fileName); + } } }; } @@ -973,14 +987,18 @@ export class SpApplication extends BaseElement { reader.onloadend = (ev): void => { SpApplication.loadingProgress = 0; SpApplication.progressStep = 3; + let data = this.markPositionHandler(reader.result as ArrayBuffer); this.spSystemTrace!.loadDatabaseArrayBuffer( - reader.result as ArrayBuffer, + data, '', (command: string, _: number) => { this.setProgress(command); }, false, () => { + if (this.markJson) { + window.publish(window.SmartEvent.UI.ImportRecord, this.markJson); + } this.mainMenu!.menus!.splice(2, this.mainMenu!.menus!.length > 2 ? 1 : 0, { collapsed: false, title: 'Current Trace', @@ -1003,6 +1021,7 @@ export class SpApplication extends BaseElement { this.freshMenuDisable(false); this.spInfoAndStats!.initInfoAndStatsData(); this.cutTraceFile!.style.display = 'none'; + this.exportRecord!.style.display = 'none'; this.headerDiv!.style.pointerEvents = 'auto'; } ); -- Gitee From 04c8218c11853839cf54a43681c155df04ac298c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Fri, 16 Aug 2024 11:56:11 +0800 Subject: [PATCH 34/85] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dwindows=E4=B8=8A?= =?UTF-8?q?main.go=E7=BC=96=E8=AF=91=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- ide/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/webpack.config.js b/ide/webpack.config.js index 4dadebe6..f6e0f8fc 100644 --- a/ide/webpack.config.js +++ b/ide/webpack.config.js @@ -88,8 +88,8 @@ function buildMultiPlatform() { const generateFile = platform === 'windows' ? path.normalize(path.join(outPath, '/', `main.exe`)) : path.normalize(path.join(outPath, '/', `main_${platform}`)); - const setEnv = `CGO_ENABLED=0 GOOS=${platform} GOARCH=amd64`; - const buildCmd = `${setEnv} go build -o ${generateFile} ${serverSrc}`; + const setEnv = `set CGO_ENABLED=0&& set GOOS=${platform}&& set GOARCH=amd64`; + const buildCmd = `${setEnv}&& go build -o ${generateFile} ${serverSrc}`; console.log(`compile ${platform} server ...`); childProcess.execSync(buildCmd); } -- Gitee From a390f4a25b7fa592c01e5e11b137aff9b179e2ff Mon Sep 17 00:00:00 2001 From: wangziyi Date: Fri, 16 Aug 2024 14:59:09 +0800 Subject: [PATCH 35/85] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../ProcedureLogicWorkerSchedulingAnalysis.ts | 83 ------------------- 1 file changed, 83 deletions(-) diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 9402715f..62959946 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -658,89 +658,6 @@ where cpu not null {} ); } - - queryProTop10Swicount() { - this.queryData( - this.currentEventId, - 'scheduling-Process Top10Swicount', - ` - select - pid, - count(tid) as occurrences - from - thread_state - where - state = 'Running' - group by - pid - ORDER BY occurrences desc - LIMIT 10 - `, - {} - ); - } - queryThrTop10Swicount(pid: number) { - this.queryData( - this.currentEventId, - 'scheduling-Process Top10Swicount', - ` - select - tid, - count(tid) as occurrences - from - thread_state - where - state = 'Running' - and pid = ${pid} - group by - tid - ORDER BY occurrences desc - LIMIT 10 - `, - {} - ); - } - queryProTop10RunTime() { - this.queryData( - this.currentEventId, - 'scheduling-Process Top10RunTime', - ` - select - pid, - SUM(dur) As dur - from - thread_state - where - state = 'Running' - GROUP BY pid - ORDER BY dur desc - LIMIT 10 - `, - {} - ); - } - queryThrTop10RunTime(pid: number) { - this.queryData( - this.currentEventId, - 'scheduling-Process Top10RunTime', - ` - select - tid, - SUM(dur) As dur - from - thread_state - where - state = 'Running' - and - pid = ${pid} - GROUP BY tid - ORDER BY dur desc - LIMIT 10 - `, - {} - ); - } - groupIrgDataByCpu(arr: Irq[]): Map { //首先计算 每个频点的持续时间,并根据Cpu来分组 let map: Map> = new Map>(); -- Gitee From b70109a01377d21c854ce4b0fbde669c8f75dc7a Mon Sep 17 00:00:00 2001 From: wangziyi Date: Fri, 16 Aug 2024 16:22:03 +0800 Subject: [PATCH 36/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9hiperf=E7=9A=84analysis?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts index ffa642a5..05037d71 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPanePerfAnalysis.ts @@ -413,7 +413,7 @@ export class TabPanePerfAnalysis extends BaseElement { // @ts-ignore this.sumCount = this.allThreadCount.allCount; // @ts-ignore - this.sumEventCount = this.allSymbolCount.allEventCount; + this.sumEventCount = this.allThreadCount.allEventCount; this.perfAnalysisPie!.config = { appendPadding: 0, data: this.getPerfPieChartData(this.threadData), @@ -518,7 +518,7 @@ export class TabPanePerfAnalysis extends BaseElement { // @ts-ignore this.sumCount = this.allLibCount.allCount; // @ts-ignore - this.sumEventCount = this.allSymbolCount.allEventCount; + this.sumEventCount = this.allLibCount.allEventCount; this.initPerfAnalysisPieConfig(); let pName = this.processName; if (this.processName.length > 0 && this.threadName.length > 0) { -- Gitee From 8a63b41c74444dc875cc3e48fc80889abe4756bf Mon Sep 17 00:00:00 2001 From: zhangyan Date: Mon, 19 Aug 2024 15:13:23 +0800 Subject: [PATCH 37/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=97=97=E5=AD=90?= =?UTF-8?q?=E6=A0=87=E8=AE=B0=E5=90=8E=E8=BE=93=E5=85=A5remark=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=AF=BC=E8=87=B4Tab=E6=97=A0=E6=B3=95=E6=8B=96?= =?UTF-8?q?=E5=8A=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts b/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts index 2d10a063..4b5c287a 100644 --- a/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts +++ b/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts @@ -185,6 +185,7 @@ export class TabPaneFlag extends BaseElement { private textInputKeyUpEventByFlag(index: number, tr: HTMLDivElement): void { tr.querySelector('#text-input')?.addEventListener('keyup', (event: unknown) => { + SpSystemTrace.isKeyUp = true; // @ts-ignore if (this.tableDataSource[index].startTime === this.flagList[index - 1].time && event.code === 'Enter' || event.code === 'NumpadEnter') { // @ts-ignore -- Gitee From 06a31c46febe8837975a81c8df53ff376a6d58c9 Mon Sep 17 00:00:00 2001 From: liufei Date: Mon, 19 Aug 2024 17:06:04 +0800 Subject: [PATCH 38/85] =?UTF-8?q?fix:=E6=A1=86=E9=80=89hiperf=E4=B8=8Enati?= =?UTF-8?q?veMemory=EF=BC=8Ctab=E9=A1=B5=E6=97=A0=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/bean/BoxSelection.ts | 2 ++ .../trace/sheet/native-memory/TabPaneNMCallTree.ts | 8 ++++++++ .../logic-worker/ProcedureLogicWorkerNativeNemory.ts | 2 ++ 3 files changed, 12 insertions(+) diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index 5b505e97..e15d3e89 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -32,6 +32,7 @@ import { TabPaneSummary } from '../component/trace/sheet/ark-ts/TabPaneSummary'; import { JsCpuProfilerStruct } from '../database/ui-worker/ProcedureWorkerCpuProfiler'; import { SampleStruct } from '../database/ui-worker/ProcedureWorkerBpftrace'; import { GpuCounterStruct } from '../database/ui-worker/ProcedureWorkerGpuCounter'; +import { Utils } from '../component/trace/base/Utils'; export class SelectionParam { traceId: string | undefined | null; @@ -390,6 +391,7 @@ export class SelectionParam { } if (this.nativeMemoryCurrentIPid === -1) { this.nativeMemoryCurrentIPid = process.ipid; + Utils.getInstance().setCurrentSelectIPid(this.nativeMemoryCurrentIPid); } if (this.nativeMemoryAllProcess) { if (it.getAttribute('heap-type') === 'native_hook_statistic') { diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts index 3102a541..5c97a56c 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts @@ -95,22 +95,29 @@ export class TabpaneNMCalltree extends BaseElement { if (nmCallTreeParam === this.currentSelection) { return; } + // 火焰图数据 this.nmCallTreeSource = []; + // 框选内容赋值 this.currentSelection = nmCallTreeParam; + // 拿到框选的pid this.currentSelectIPid = nmCallTreeParam.nativeMemoryCurrentIPid; this.init(nmCallTreeParam); } private async init(nmCallTreeParam: SelectionParam): Promise { + // 初始化样式 this.initUI(); + // 初始化选择框内容 await this.initFilterTypes(); let types: Array = []; this.initTypes(nmCallTreeParam, types); const initWidth = this._analysisTabWidth > 0 ? this._analysisTabWidth : this.clientWidth; + // 获取tab页数据 this.getDataByWorkerQuery( { leftNs: nmCallTreeParam.leftNs, rightNs: nmCallTreeParam.rightNs, + // ['AllocEvent','MmapEvent'] types, }, (results: unknown[]): void => { @@ -984,6 +991,7 @@ export class TabpaneNMCalltree extends BaseElement { } getDataByWorkerQuery(args: unknown, handler: Function): void { + // 加载中样式设置 this.loadingList.push(1); this.nmCallTreeProgressEL!.loading = true; // @ts-ignore this.nmCallTreeLoadingPage.style.visibility = 'visible'; diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts index 7904c120..e1eb2735 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts @@ -806,7 +806,9 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { '-' + (callChains[topIndex].fileId || ''); } + // 根节点 let root = this.currentTreeMapData[key]; + // 没有当前项的根节点,就new一个新的,放在currentTreeList if (root === undefined) { root = new NativeHookCallInfo(); root.threadName = nativeHookSample.threadName; -- Gitee From d223a4605bfad0b4cb1cc9c89b62748138558b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=86=E5=B7=9D?= Date: Tue, 20 Aug 2024 09:15:37 +0800 Subject: [PATCH 39/85] =?UTF-8?q?fix:=20=E7=BC=96=E8=AF=91=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 集川 --- ide/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/webpack.config.js b/ide/webpack.config.js index f6e0f8fc..f7702c1c 100644 --- a/ide/webpack.config.js +++ b/ide/webpack.config.js @@ -88,8 +88,8 @@ function buildMultiPlatform() { const generateFile = platform === 'windows' ? path.normalize(path.join(outPath, '/', `main.exe`)) : path.normalize(path.join(outPath, '/', `main_${platform}`)); - const setEnv = `set CGO_ENABLED=0&& set GOOS=${platform}&& set GOARCH=amd64`; - const buildCmd = `${setEnv}&& go build -o ${generateFile} ${serverSrc}`; + const setEnv = `go env -w CGO_ENABLED=0 && go env -w GOOS=${platform} && go env -w GOARCH=amd64`; + const buildCmd = `${setEnv} && go build -o ${generateFile} ${serverSrc}`; console.log(`compile ${platform} server ...`); childProcess.execSync(buildCmd); } -- Gitee From ab2e79423ad8adc1bf3bfa020da2d4d545908cd4 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Tue, 20 Aug 2024 15:31:22 +0800 Subject: [PATCH 40/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9trace=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8Edb=E6=96=87=E4=BB=B6=E7=9A=84energy=E6=B3=B3=E9=81=93?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=8D=E4=B8=80=E8=87=B4=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/chart/SpHiSysEnergyChart.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ide/src/trace/component/chart/SpHiSysEnergyChart.ts b/ide/src/trace/component/chart/SpHiSysEnergyChart.ts index c3a11e36..7b9ebba2 100644 --- a/ide/src/trace/component/chart/SpHiSysEnergyChart.ts +++ b/ide/src/trace/component/chart/SpHiSysEnergyChart.ts @@ -384,6 +384,9 @@ export class SpHiSysEnergyChart { } } } + appNameFromTable.sort((a, b) => { + return a.string_value!.localeCompare(b.string_value!); + }); if (appNameFromTable.length > 0 && SpHiSysEnergyChart.app_name === '') { SpHiSysEnergyChart.app_name = appNameFromTable[0].string_value; } -- Gitee From 2eb227032e0918580d5d41c9df94c032554ce008 Mon Sep 17 00:00:00 2001 From: danghongquan Date: Tue, 20 Aug 2024 16:37:19 +0800 Subject: [PATCH 41/85] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E6=9F=A5=E6=89=BE?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E5=AE=9A=E4=BD=8D=E9=80=BB=E8=BE=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: danghongquan --- ide/src/trace/component/SpSystemTrace.init.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 24e9ead8..e488ddd9 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -786,7 +786,7 @@ function moveRangeToCenterAndHighlight(sp: SpSystemTrace, findEntry: unknown, cu if (findEntry) { //findEntry不在range范围内,会把它移动到泳道最左侧 // @ts-ignore - if (findEntry.startTime > TraceRow.range!.endNS || findEntry.startTime + findEntry.dur < TraceRow.range!.startNS) { + if (findEntry.startTime + findEntry.dur > TraceRow.range!.endNS || findEntry.startTime < TraceRow.range!.startNS) { // @ts-ignore sp.moveRangeToLeft(findEntry.startTime!, findEntry.dur!); } @@ -877,7 +877,7 @@ function spSystemTraceShowStructFindIndex( } else if (previous) { //case1:current.start在start边界以右,需要从当前项往第一项遍历,找到structs[index].start < end //@ts-ignore - if (structs[currentIndex].startTime! >= rangeStart) { + if (structs[currentIndex].startTime! >= Math.round(rangeStart)) { findIndex = findPreviousOne(currentIndex - 1, 0, structs); //处理当前项如果是第一项 findIndex = findIndex === -1 ? structs.length - 1 : findIndex; @@ -893,7 +893,7 @@ function spSystemTraceShowStructFindIndex( } //case1:current.start 在end左侧 从当前项到最后一项遍历,找到startTime>start //@ts-ignore - if (structs[currentIndex].startTime! < rangeEnd) {//case1 + if (structs[currentIndex].startTime! < Math.round(rangeEnd)) {//case1 findIndex = findNextOne(currentIndex + 1, structs.length - 1, structs); //处理当前项是最后一项 findIndex = findIndex === -1 ? 0 : findIndex; -- Gitee From 24c510ab2e8f8ce93406bec519006d3422b1f8c3 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Tue, 20 Aug 2024 19:46:47 +0800 Subject: [PATCH 42/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9cpufreqlimit=E7=9A=84cp?= =?UTF-8?q?u=E5=88=97=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../trace/component/chart/SpProcessChart.ts | 3 --- .../trace/sheet/freq/TabPaneCpuFreqLimits.ts | 22 ++++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index bb1891bb..1f8d1e83 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -1228,9 +1228,6 @@ export class SpProcessChart { row.favoriteChangeHandler = this.trace.favoriteChangeHandler; row.selectChangeHandler = this.trace.selectChangeHandler; row.focusHandler = (): void => { - if (row.hoverY <= 5 || row.hoverY >= 35) { - ProcessMemStruct.hoverProcessMemStruct = undefined; - } this.trace.displayTip( row, ProcessMemStruct.hoverProcessMemStruct, diff --git a/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts b/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts index 46504559..8330f99e 100644 --- a/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts +++ b/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts @@ -104,6 +104,15 @@ export class TabPaneCpuFreqLimits extends BaseElement { item.valueStr = `${ColorUtils.formatNumberComma(item.value!)} kHz`; return item; }); + this.cpuFreqLimitSource.sort((a, b): number => { + let cpuLeftData = Number(a.cpu.toString().replace('Cpu','')); + let cpuRightData = Number(b.cpu.toString().replace('Cpu','')); + if (cpuLeftData > cpuRightData) { + return 1; + }else{ + return -1; + } + }) this.sortCpuFreqLimitTable(this.cpuFreqLimitSortKey, this.cpuFreqLimitSortType); } @@ -151,12 +160,13 @@ export class TabPaneCpuFreqLimits extends BaseElement { compareCpu(cpuFreqLimitA: unknown, cpuFreqLimitB: unknown, type: number): number { // @ts-ignore - if (cpuFreqLimitA.cpu > cpuFreqLimitB.cpu) { - return type === 2 ? -1 : 1; // @ts-ignore - } else if (cpuFreqLimitA.cpu === cpuFreqLimitB.cpu) { - return 0; - } else { - return type === 2 ? 1 : -1; + let cpuLeftData = Number(cpuFreqLimitA.cpu.toString().replace('Cpu','')); + // @ts-ignore + let cpuRightData = Number(cpuFreqLimitB.cpu.toString().replace('Cpu','')); + if (type === 1) { + return cpuLeftData - cpuRightData; + }else{ + return cpuRightData - cpuLeftData; } } -- Gitee From b47c7e84fefa66c728a2050620da3c105ae3c62e Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Tue, 20 Aug 2024 20:43:14 +0800 Subject: [PATCH 43/85] =?UTF-8?q?=E6=A1=86=E9=80=89function=E6=B3=B3?= =?UTF-8?q?=E9=81=93=E7=82=B9=E9=80=89name=E6=90=9C=E7=B4=A2=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E5=86=8D=E6=AC=A1=E6=A1=86=E9=80=89=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/component/SpSystemTrace.event.ts | 4 +--- ide/src/trace/component/trace/search/Search.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index 18b46c8a..6105d2da 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -526,9 +526,7 @@ export function spSystemTraceDocumentOnMouseMoveMouseDown(sp: SpSystemTrace, sea if (TraceRow.rangeSelectObject) { if (search && search.searchValue !== '') { search.clear(); - sp?.visibleRows.forEach((it) => { - it.highlight = false; - }); + search.valueChangeHandler?.(''); } } } diff --git a/ide/src/trace/component/trace/search/Search.ts b/ide/src/trace/component/trace/search/Search.ts index 215fb120..f272f014 100644 --- a/ide/src/trace/component/trace/search/Search.ts +++ b/ide/src/trace/component/trace/search/Search.ts @@ -328,7 +328,7 @@ export class LitSearch extends BaseElement { if (this.hasAttribute('distributed')) { return this.traceSelector?.value; } - return undefined; + return null; } private keyUpListener(): void { -- Gitee From bb10cdd234df50b03e7f19364a51f5b9bc0712f9 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Wed, 21 Aug 2024 14:35:51 +0800 Subject: [PATCH 44/85] =?UTF-8?q?feat:Current=20SelectionTab=E9=A1=B5?= =?UTF-8?q?=E6=96=B0=E5=A2=9Ewakeup=20from=20top?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/sheet/TabPaneCurrentSelection.ts | 63 ++++++++++++++++++- .../trace/database/sql/ProcessThread.sql.ts | 21 ++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 347f96f4..f6a452b5 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -49,6 +49,7 @@ import { import { queryBinderArgsByArgset, queryDistributedRelationAllData, + queryRWakeUpFrom, queryRunnableTimeByRunning, queryThreadStateArgs, queryThreadWakeUp, @@ -128,6 +129,7 @@ export class TabPaneCurrentSelection extends BaseElement { private realTime: number = 0; private bootTime: number = 0; private funcDetailMap: Map> = new Map(); + private topChainStr: string = ''; set data(selection: unknown) { // @ts-ignore @@ -960,6 +962,14 @@ export class TabPaneCurrentSelection extends BaseElement { class="wakeup-click" name="select" color="#7fa1e7" size="20"> `, }); + list.push({ + name: 'wakeup from top', + value: `
+
+ +
`, + }); } if (wakeUps !== null) { wakeUps.map((e) => { @@ -1012,6 +1022,7 @@ export class TabPaneCurrentSelection extends BaseElement { } this.stateClickHandler(preData, nextData, data, scrollWakeUp, scrollCallback, scrollPrio); this.wakeupClickHandler(wakeUps, fromBean, scrollWakeUp); + this.getWakeupChainClickHandler(fromBean, list); }); } @@ -1126,7 +1137,17 @@ export class TabPaneCurrentSelection extends BaseElement { } }); } - + //点击事件获取唤醒链 + private getWakeupChainClickHandler(fromBean: WakeupBean | undefined, list: unknown[]) { + this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-top')?.addEventListener('click', async () => { + this.topChainStr = ''; + //@ts-ignore + let currentThread = list.filter((item) => item.name === "Thread")?.[0].value;//点击的当前线程 + let nextWakeupThread = Utils.getInstance().getThreadMap().get(fromBean!.tid!) || 'Thread';//下一个唤醒线程 + this.topChainStr = `-->${nextWakeupThread}[${fromBean!.tid}]-->${currentThread}`; + this.getRWakeUpChain(fromBean); + }) + } private async prepareThreadInfo(list: unknown[], data: ThreadStruct): Promise { let processName = Utils.getInstance().getProcessMap().get(data.pid!); let threadName = Utils.getInstance().getThreadMap().get(data.tid!); @@ -1922,6 +1943,46 @@ export class TabPaneCurrentSelection extends BaseElement { } return list; } + //递归查找R唤醒链 + getRWakeUpChain(data: WakeupBean | undefined):void { + this.getRWakeUpChainData(data).then((wakeupFrom: unknown) => { + if (wakeupFrom === null) {//当查不到数据时,处理容器状态与样式,展示内容 + let wakeupTopContent = this.currentSelectionTbl?.shadowRoot?.getElementById('wakeup-top-content'); + let wakeupTopIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-top'); + wakeupTopContent!.innerText = 'idle' + this.topChainStr;//处理链顶部 + wakeupTopIcon!.style.display = 'none'; + wakeupTopContent!.style.display = 'block'; + wakeupTopContent!.style.maxHeight = '100px';//设置最大高度,超出出现滚动条 + wakeupTopContent!.style.overflow = 'auto'; + return; + } + //@ts-ignore + this.topChainStr = `-->${wakeupFrom!.thread}[${wakeupFrom!.tid}]` + this.topChainStr;//链的拼接 + // @ts-ignore + this.getRWakeUpChain(wakeupFrom); + }); + } + + /** + * 获取 R的唤醒链 + * @param data + */ + async getRWakeUpChainData(data: unknown): Promise { + let wakeupFrom: WakeupBean | null = null; + //@ts-ignore + let wakeup = await queryRunnableTimeByRunning(data.tid!, data.ts!);//通过链上的Running块,查找前一条R信息 + if (wakeup && wakeup[0]) { + let wakeupTs = wakeup[0].ts as number; + //@ts-ignore + let wf = await queryRWakeUpFrom(data.itid!, wakeupTs);//查找到的前一条R信息,对应的唤醒信息 + if (wf && wf[0]) { + wakeupFrom = wf[0]; + //@ts-ignore + wakeupFrom.thread = Utils.getInstance().getThreadMap().get(wakeupFrom.tid!) || 'Thread'; + } + } + return wakeupFrom; + } initCanvas(): HTMLCanvasElement | null { let canvas = this.shadowRoot!.querySelector('#rightDraw'); diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index 5bd29342..054ed595 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -346,7 +346,26 @@ order by ts desc limit 1 `; return query('queryThreadWakeUpFrom', sql, {}, { traceId: Utils.currentSelectTrace }); }; - +export const queryRWakeUpFrom = (itid: number, startTime: number): Promise> => { + let sql = ` + select + (A.ts - B.start_ts) as ts, + A.tid, + A.itid, + A.arg_setid as argSetID + from + thread_state A, + trace_range B + where + A.state = 'Running' + and A.itid = (select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1) + and A.ts < ${startTime} + order by + ts desc + limit 1 + `; + return query('queryRWakeUpFrom', sql, {}, { traceId: Utils.currentSelectTrace }); +}; export const queryRunnableTimeByRunning = (tid: number, startTime: number): Promise> => { let sql = ` select ts from thread_state,trace_range where ts + dur -start_ts = ${startTime} and state = 'R' and tid=${tid} limit 1 -- Gitee From be9b53a9e1da75903ce45e8304be91aaa8cf66da Mon Sep 17 00:00:00 2001 From: wangyujie Date: Wed, 21 Aug 2024 16:54:55 +0800 Subject: [PATCH 45/85] =?UTF-8?q?fix:=E5=9B=9E=E9=80=80=E5=B7=B2=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=89=AA=E5=88=80=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/SpApplication.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index 8cbe8ef7..b76f0df6 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -2091,7 +2091,7 @@ export class SpApplication extends BaseElement { } }); }); - this.cutTraceFile!.style.display = 'block'; + this.cutTraceFile!.style.display = 'none'; this.mainMenu!.menus = this.mainMenu!.menus; } }); -- Gitee From 5c947860aaa91fc22df073f13be555c0615c4838 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Wed, 21 Aug 2024 17:03:12 +0800 Subject: [PATCH 46/85] =?UTF-8?q?feat:1.=E8=BF=9B=E7=A8=8B=E6=8A=93?= =?UTF-8?q?=E5=8F=96=E6=B7=BB=E5=8A=A0=E5=9B=9E=E8=BD=A6=202.=E5=B0=86hilo?= =?UTF-8?q?g=E5=92=8Chisysevent=E7=AD=89=E6=94=B9=E4=B8=BA=E5=8D=95?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E6=8A=93=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/base-ui/select/LitSelect.ts | 15 ++++--- ide/src/base-ui/select/LitSelectV.ts | 40 +++++++++++++++++-- ide/src/trace/component/setting/SpArkTs.ts | 2 +- .../trace/component/setting/SpHilogRecord.ts | 2 +- .../trace/component/setting/SpHisysEvent.ts | 6 +-- .../trace/component/setting/SpSdkConfig.ts | 2 +- .../trace/component/setting/SpVmTracker.ts | 2 +- 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/ide/src/base-ui/select/LitSelect.ts b/ide/src/base-ui/select/LitSelect.ts index d693e413..6f6768be 100644 --- a/ide/src/base-ui/select/LitSelect.ts +++ b/ide/src/base-ui/select/LitSelect.ts @@ -22,6 +22,7 @@ export class LitSelect extends BaseElement { private focused: unknown; private selectInputEl: unknown; private selectSearchInputEl: HTMLInputElement | null | undefined; + private selectVInputEl: HTMLInputElement | null | undefined; private selectOptions: HTMLDivElement | null | undefined; private selectItem: string = ''; private selectClearEl: unknown; @@ -214,6 +215,13 @@ export class LitSelect extends BaseElement { } initElements(): void { + this.selectVInputEl = this.shadowRoot!.querySelector('#select-input') as HTMLInputElement; + this.selectVInputEl?.addEventListener('keyup', (e) => { + if (e.code === 'Enter' || e.code === 'NumpadEnter') {// @ts-ignore + this.selectVInputEl.blur();// @ts-ignore + this.selectInputEl.value=this.selectVInputEl.value; + } + }); if (this.showSearchInput) { this.shadowRoot!.querySelector('.body-select')!.style.display = 'block'; this.selectSearchInputEl = this.shadowRoot!.querySelector('#search-input') as HTMLInputElement; @@ -238,7 +246,7 @@ export class LitSelect extends BaseElement { ${selectHtmlStr(this.listHeight)}
- +
@@ -448,11 +456,6 @@ export class LitSelect extends BaseElement { this.selectInputEl.onfocus = (ev: unknown): void => { if (this.hasAttribute('disabled')) { return; - } // @ts-ignore - if (this.selectInputEl.value.length > 0) { - // @ts-ignore - this.selectInputEl.placeholder = this.selectInputEl.value; // @ts-ignore - this.selectInputEl.value = ''; } if (this.hasAttribute('show-search')) { // @ts-ignore diff --git a/ide/src/base-ui/select/LitSelectV.ts b/ide/src/base-ui/select/LitSelectV.ts index 7436e0d7..28f3f191 100644 --- a/ide/src/base-ui/select/LitSelectV.ts +++ b/ide/src/base-ui/select/LitSelectV.ts @@ -30,6 +30,7 @@ export class LitSelectV extends BaseElement { private selectVBody: HTMLDivElement | undefined; private valueStr: string = ''; + private currentvalueStr: string = ''; static get observedAttributes(): string[] { return ['value', 'default-value', 'placeholder', 'disabled', 'show-search', 'border', 'mode']; @@ -108,7 +109,7 @@ export class LitSelectV extends BaseElement { return this.hasAttribute('is-all'); } - dataSource(selectVData: Array, valueStr: string): void { + dataSource(selectVData: Array, valueStr: string,isSingle?:boolean): void { this.selectVOptions!.innerHTML = ''; if (selectVData.length > 0) { this.selectVBody!.style.display = 'block'; @@ -124,10 +125,18 @@ export class LitSelectV extends BaseElement { option.textContent = valueStr; this.selectVOptions!.appendChild(option); this.initDataItem(selectVData); - this.initCustomOptions(); + if(isSingle){ + this.initSingleCustomOptions(); + } else { + this.initCustomOptions(); + } } else { this.initDataItem(selectVData); - this.initOptions(); + if(isSingle){ + this.initSingleCustomOptions(); + } else { + this.initOptions(); + } } } else { this.selectVBody!.style.display = 'none'; @@ -159,6 +168,11 @@ export class LitSelectV extends BaseElement { this.selectVBody = this.shadowRoot!.querySelector('.body') as HTMLDivElement; this.selectVOptions = this.shadowRoot!.querySelector('.body-opt') as HTMLDivElement; this.selectVIconEl = this.shadowRoot!.querySelector('.icon'); // @ts-ignore + this.selectVInputEl?.addEventListener('keyup', (e) => { + if (e.code === 'Enter' || e.code === 'NumpadEnter') {// @ts-ignore + this.selectVInputEl.blur(); + } + });// @ts-ignore this.selectVInputEl!.oninput = (ev: InputEvent): void => { // @ts-ignore if (this.selectVInputEl!.value === '00') { @@ -170,6 +184,7 @@ export class LitSelectV extends BaseElement { this.shadowRoot?.querySelectorAll('lit-select-option').forEach((it) => { it.removeAttribute('selected'); this.showItems = []; + this.currentvalueStr = ''; }); } }; // @ts-ignore @@ -275,6 +290,25 @@ export class LitSelectV extends BaseElement { this.selectAll(querySelector); } + initSingleCustomOptions(): void { + let selectedOption = this.shadowRoot?.querySelector( + `lit-select-option[value="${this.currentvalueStr}"]` + ) as LitSelectOption | null; + if (selectedOption) { + selectedOption.setAttribute('selected', ''); + } + this.shadowRoot?.querySelectorAll('lit-select-option').forEach((option) => { + option.addEventListener('onSelected', () => { + this.shadowRoot?.querySelectorAll('lit-select-option').forEach((o) => { + o.removeAttribute('selected'); + }); + option.setAttribute('selected', ''); // @ts-ignore + this.selectVInputEl!.value = option.textContent!; + this.currentvalueStr = option.textContent!; + }); + }); + } + initOptions(): void { this.shadowRoot?.querySelectorAll('lit-select-option').forEach((a) => { a.setAttribute('check', ''); diff --git a/ide/src/trace/component/setting/SpArkTs.ts b/ide/src/trace/component/setting/SpArkTs.ts index fb993c35..72d25056 100644 --- a/ide/src/trace/component/setting/SpArkTs.ts +++ b/ide/src/trace/component/setting/SpArkTs.ts @@ -127,7 +127,7 @@ export class SpArkTs extends BaseElement { } else { Cmd.getDebugProcess().then((processList) => { if (processList.length > 0) { - this.processInput!.dataSource(processList, ''); + this.processInput!.dataSource(processList, '',true); } else { this.processInput!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpHilogRecord.ts b/ide/src/trace/component/setting/SpHilogRecord.ts index 628c23a2..da564a8b 100644 --- a/ide/src/trace/component/setting/SpHilogRecord.ts +++ b/ide/src/trace/component/setting/SpHilogRecord.ts @@ -71,7 +71,7 @@ export class SpHilogRecord extends BaseElement { } else { Cmd.getProcess().then((processList) => { if (processList.length > 0) { - this.processSelectEl!.dataSource(processList, 'ALL-Process'); + this.processSelectEl!.dataSource(processList, 'ALL-Process',true); } else { this.processSelectEl!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpHisysEvent.ts b/ide/src/trace/component/setting/SpHisysEvent.ts index 59e84f44..4e85e8e6 100644 --- a/ide/src/trace/component/setting/SpHisysEvent.ts +++ b/ide/src/trace/component/setting/SpHisysEvent.ts @@ -106,7 +106,7 @@ export class SpHisysEvent extends BaseElement { this.eventConfig = JSON.parse(dec.decode(buffer)); let domainList = Object.keys(this.eventConfig!); if (domainList.length > 0) { - this.domainInputEL!.dataSource(domainList, 'ALL-Domain'); + this.domainInputEL!.dataSource(domainList, 'ALL-Domain',true); } else { this.domainInputEL!.dataSource([], ''); } @@ -132,7 +132,7 @@ export class SpHisysEvent extends BaseElement { if (eventConfigElement) { let eventNameList = Object.keys(eventConfigElement); if (eventNameList?.length > 0 ) { - this.eventNameInputEL!.dataSource(eventNameList, 'ALL-Event'); + this.eventNameInputEL!.dataSource(eventNameList, 'ALL-Event',true); } else { this.eventNameInputEL!.dataSource([], ''); } @@ -147,7 +147,7 @@ export class SpHisysEvent extends BaseElement { let eventList = Object.keys(currentEvent); currentData.push(...eventList); }); - this.eventNameInputEL!.dataSource(currentData, 'ALL-Event'); + this.eventNameInputEL!.dataSource(currentData, 'ALL-Event',true); } else { this.eventNameInputEL!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpSdkConfig.ts b/ide/src/trace/component/setting/SpSdkConfig.ts index 92f565e9..fe2ed6c0 100644 --- a/ide/src/trace/component/setting/SpSdkConfig.ts +++ b/ide/src/trace/component/setting/SpSdkConfig.ts @@ -200,7 +200,7 @@ export class SpSdkConfig extends BaseElement { inputDiv.addEventListener('mousedown', () => { if (this.startSamp) { inputDiv!.removeAttribute('readonly'); - this.selectConfig!.dataSource(this.wasmList, ''); + this.selectConfig!.dataSource(this.wasmList, '',true); } else { inputDiv!.setAttribute('readonly', 'readonly'); return; diff --git a/ide/src/trace/component/setting/SpVmTracker.ts b/ide/src/trace/component/setting/SpVmTracker.ts index 7d059e8e..fbe3b440 100644 --- a/ide/src/trace/component/setting/SpVmTracker.ts +++ b/ide/src/trace/component/setting/SpVmTracker.ts @@ -80,7 +80,7 @@ export class SpVmTracker extends BaseElement { } else { Cmd.getProcess().then((processList) => { if (processList.length > 0) { - this.vmTrackerProcessInput!.dataSource(processList, ''); + this.vmTrackerProcessInput!.dataSource(processList, '',true); } else { this.vmTrackerProcessInput!.dataSource([], ''); } -- Gitee From c8daa238ec82e5ee45f8555dfb3df3937b71c346 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Wed, 21 Aug 2024 17:16:58 +0800 Subject: [PATCH 47/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E6=94=B6=E8=B5=B7=EF=BC=8C=E5=9B=BE=E6=A0=87=E9=87=8D?= =?UTF-8?q?=E5=8F=A0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/trace/SpApplication.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index 8cbe8ef7..6a976b65 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -1802,6 +1802,7 @@ export class SpApplication extends BaseElement { if (this.sidebarButton) { this.sidebarButton.style.width = '0px'; this.importConfigDiv!.style.left = '5px'; + this.contentLeftOption!.style.left = '5px'; this.closeKeyPath!.style.left = '25px'; } if (this.mainMenu) { @@ -1821,6 +1822,7 @@ export class SpApplication extends BaseElement { if (this.sidebarButton) { this.sidebarButton.style.width = '48px'; this.importConfigDiv!.style.left = '45px'; + this.contentLeftOption!.style.left = '45px'; this.closeKeyPath!.style.left = '65px'; } }; -- Gitee From 821ea3f7113238131974da42911bd31c473dda99 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 22 Aug 2024 10:23:57 +0800 Subject: [PATCH 48/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E5=86=85=E5=AD=98?= =?UTF-8?q?=E4=B8=ADAnalysis=E5=AF=BC=E8=88=AA=E6=A0=8Fthread=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/sheet/native-memory/TabPaneNMStatisticAnalysis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatisticAnalysis.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatisticAnalysis.ts index 4cdc85e5..9ce9c1a0 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatisticAnalysis.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMStatisticAnalysis.ts @@ -666,7 +666,7 @@ export class TabPaneNMStatisticAnalysis extends BaseElement { // @ts-ignore let title = typeName; if (!this.hideThreadCheckBox?.checked) { - this.threadName = `Thread(${it.tid})`; + this.threadName = `${it.tableName}`; title += ` / ${this.threadName}`; } this.titleEl!.textContent = title; -- Gitee From 3ce2b963ca7b5c41379abef12389572ef1860775 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 22 Aug 2024 11:24:58 +0800 Subject: [PATCH 49/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/component/trace/sheet/TabPaneCurrentSelection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index f6a452b5..324126b8 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -1143,8 +1143,8 @@ export class TabPaneCurrentSelection extends BaseElement { this.topChainStr = ''; //@ts-ignore let currentThread = list.filter((item) => item.name === "Thread")?.[0].value;//点击的当前线程 - let nextWakeupThread = Utils.getInstance().getThreadMap().get(fromBean!.tid!) || 'Thread';//下一个唤醒线程 - this.topChainStr = `-->${nextWakeupThread}[${fromBean!.tid}]-->${currentThread}`; + let previouosWakeupThread = Utils.getInstance().getThreadMap().get(fromBean!.tid!) || 'Thread';//唤醒当前线程的上个线程 + this.topChainStr = `-->${previouosWakeupThread}[${fromBean!.tid}]-->${currentThread}`; this.getRWakeUpChain(fromBean); }) } -- Gitee From 95994432e8ab4502a1d33927cb9c0af1dc896b6d Mon Sep 17 00:00:00 2001 From: wangziyi Date: Thu, 22 Aug 2024 16:21:10 +0800 Subject: [PATCH 50/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=94=A4=E9=86=92?= =?UTF-8?q?=E6=A0=91=E6=98=BE=E7=A4=BA=E4=B8=8D=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/SpSystemTrace.init.ts | 3 ++ ide/src/trace/component/SpSystemTrace.ts | 21 ++++++++++ .../trace/sheet/TabPaneCurrentSelection.ts | 2 + .../trace/database/sql/ProcessThread.sql.ts | 40 +++++++++++-------- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index e488ddd9..1bc639cd 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -47,6 +47,7 @@ function rightButtonOnClick(sp: SpSystemTrace, rightStar: HTMLElementAlias): unk if (SpSystemTrace.btnTimer) { return; } + sp.checkclick = true; // 唤醒树有值则不再重复添加 const startIndex = CpuStruct.selectCpuStruct!.displayProcess?.indexOf('['); if (SpSystemTrace.wakeupList.length === 0) { @@ -765,6 +766,8 @@ export function spSystemTraceInitElement(sp: SpSystemTrace): void { } sp.tabCpuFreq = sp.traceSheetEL.shadowRoot.querySelector('tabpane-frequency-sample'); sp.tabCpuState = sp.traceSheetEL.shadowRoot.querySelector('tabpane-counter-sample'); + sp.wakeupListTbl = sp.traceSheetEL.shadowRoot?.querySelector("#current-selection > tabpane-current-selection")?. + shadowRoot?.querySelector("#wakeupListTbl"); sp.rangeSelect = new RangeSelect(sp); // @ts-ignore rightButton?.addEventListener('click', rightButtonOnClick(sp, rightStar)); diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 05445a5b..6c1ae96e 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -134,6 +134,7 @@ import { BaseStruct } from '../bean/BaseStruct'; import { GpuCounterStruct } from '../database/ui-worker/ProcedureWorkerGpuCounter'; import { SpProcessChart } from './chart/SpProcessChart'; import { LitSearch } from './trace/search/Search'; +import { LitTable } from '../../base-ui/table/lit-table'; function dpr(): number { return window.devicePixelRatio || 1; @@ -225,6 +226,8 @@ export class SpSystemTrace extends BaseElement { static retargetIndex: number = 0; prevScrollY: number = 0; focusTarget: string = ''; + wakeupListTbl: LitTable | undefined | null; + _checkclick: boolean = false; //判断点击getWakeupList按钮 set snapshotFile(data: FileInfo) { this.snapshotFiles = data; @@ -238,6 +241,18 @@ export class SpSystemTrace extends BaseElement { this._flagList = list; } + get checkclick(): boolean { + return this._checkclick; + } + + set checkclick(value: boolean) { + if (value) { + this._checkclick = true; + } else { + this._checkclick = false; + } + } + //节流处理 throttle(fn: Function, t: number, ev?: unknown): Function { let timerId: unknown = null; @@ -2599,9 +2614,15 @@ export class SpSystemTrace extends BaseElement { } queryCPUWakeUpList(data: WakeupBean): void { + if (this._checkclick) { + this.wakeupListTbl!.loading = true; + } TabPaneCurrentSelection.queryCPUWakeUpListFromBean(data).then((a: unknown) => { if (a === null) { window.publish(window.SmartEvent.UI.WakeupList, SpSystemTrace.wakeupList); + this.wakeupListTbl!.loading = false; + this._checkclick = false; + this.refreshCanvas(true); return null; } // @ts-ignore SpSystemTrace.wakeupList.push(a); // @ts-ignore diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 324126b8..640245a6 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -205,6 +205,7 @@ export class TabPaneCurrentSelection extends BaseElement { if (leftTitle) { leftTitle.innerText = 'Slice Details'; } + this.currentSelectionTbl!.loading = true; let list: unknown[] = []; this.updateUI(data, list); Promise.all([this.queryThreadStateDArgs(data.argSetID), this.queryCPUWakeUpFromData(data)]).then((resArr) => { @@ -219,6 +220,7 @@ export class TabPaneCurrentSelection extends BaseElement { }); } this.currentSelectionTbl!.dataSource = list; + this.currentSelectionTbl!.loading = false; let startTimeAbsolute = (data.startTime || 0) + Utils.getInstance().getRecordStartNS(); this.addClickToTransfBtn(startTimeAbsolute, CPU_TRANSF_BTN_ID, CPU_STARTTIME_ABSALUTED_ID); diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index 054ed595..076d3e50 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -329,23 +329,29 @@ where B.tid = $tid and B.pid = $pid;`, { $tid: tid, $pid: pid } ); -export const queryThreadWakeUpFrom = (itid: number, startTime: number): Promise> => { - let sql = ` -select (A.ts - B.start_ts) as ts, - A.tid, - A.itid, - A.pid, - A.cpu, - A.dur, - A.arg_setid as argSetID -from thread_state A,trace_range B -where A.state = 'Running' -and A.itid = (select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1) -and (A.ts - B.start_ts) < (${startTime} - B.start_ts) -order by ts desc limit 1 - `; - return query('queryThreadWakeUpFrom', sql, {}, { traceId: Utils.currentSelectTrace }); -}; + export const queryThreadWakeUpFrom = async (itid: number, startTime: number): Promise => { + let sql1 = `select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1`; + const result = await query('queryThreadWakeUpFrom', sql1, {}, { traceId: Utils.currentSelectTrace }); + if (result && result.length > 0) { //@ts-ignore + let wakeupFromItid = result[0].wakeup_from; // 获取wakeup_from的值 + let sql2 = ` + select (A.ts - B.start_ts) as ts, + A.tid, + A.itid, + A.pid, + A.cpu, + A.dur, + A.arg_setid as argSetID + from thread_state A, trace_range B + where A.state = 'Running' + and A.itid = ${wakeupFromItid} + and (A.ts - B.start_ts) < (${startTime} - B.start_ts) + order by ts desc limit 1 + `; + return query('queryThreadWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); + } + }; + export const queryRWakeUpFrom = (itid: number, startTime: number): Promise> => { let sql = ` select -- Gitee From 41663168ce977321bd53d2b3da3d167212439a36 Mon Sep 17 00:00:00 2001 From: zhangzhuozhou Date: Thu, 22 Aug 2024 17:10:56 +0800 Subject: [PATCH 51/85] =?UTF-8?q?fix:bytrace=20=E6=A0=B9=E6=8D=AE=20?= =?UTF-8?q?=E2=80=9C=3D=E2=80=9D=20=E5=88=92=E5=88=86=E5=8F=82=E6=95=B0=20?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzhuozhou --- .../ptreader_parser/bytrace_parser/bytrace_event_parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 19285d94..1e3e8fe7 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 @@ -783,7 +783,7 @@ void BytraceEventParser::GetDataSegArgs(const BytraceLine &bufLine, ArgsMap &arg if (first == -1) { first = i; } - if (bufLine.argsStr[i] == '=') { + if (bufLine.argsStr[i] == '=' && second == -1) { second = i + 1; } } -- Gitee From 450d9999bb58c6a239b6c6f663704ff435848092 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Thu, 22 Aug 2024 19:09:48 +0800 Subject: [PATCH 52/85] =?UTF-8?q?=E4=BF=AE=E6=94=B9sched=20priority?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8A=A0=E8=BD=BD=E7=BC=93=E6=85=A2=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- .../trace/sheet/cpu/TabPaneSchedPriority.ts | 15 +++++----- .../trace/database/sql/ProcessThread.sql.ts | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts index 2d3c2435..28eb279a 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts @@ -19,7 +19,7 @@ import { SelectionParam } from '../../../../bean/BoxSelection'; import { resizeObserver } from '../SheetUtils'; import { Utils } from '../../base/Utils'; import { Priority } from '../../../../bean/StateProcessThread'; -import { queryThreadStateArgsByName } from '../../../../database/sql/ProcessThread.sql'; +import { queryArgsById, queryThreadStateArgsById } from '../../../../database/sql/ProcessThread.sql'; import { FlagsConfig } from '../../../SpFlags'; import { sliceSPTSender } from '../../../../database/data-trafic/SliceSender'; @@ -142,12 +142,13 @@ export class TabPaneSchedPriority extends BaseElement { private async fetchAndProcessData(): Promise { if (this.strValueMap.size === 0) { - await queryThreadStateArgsByName('next_info', this.selectionParam?.traceId || undefined). - then((value): void => { - for (const item of value) { - this.strValueMap.set(item.argset, item.strValue); - } - }); + let res = await queryArgsById('next_info', this.selectionParam?.traceId || undefined) + await queryThreadStateArgsById(res[0].id, this.selectionParam?.traceId || undefined). + then((value): void => { + for (const item of value) { + this.strValueMap.set(item.argset, item.strValue); + } + }); } } diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index 076d3e50..b9a8ea96 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -777,6 +777,34 @@ export const queryThreadStateArgsByName = (key: string, traceId?: string): { traceId: traceId } ); +export const queryArgsById = (key: string, traceId?: string): + Promise> => + query( + 'queryArgsById', + `select + id + from data_dict + WHERE data = $key`, + { $key: key }, + { traceId: traceId } + ); + +export const queryThreadStateArgsById = (id: number, traceId?: string): + Promise> => + query( + 'queryThreadStateArgsById', + `select + A.argset, + DD.data as strValue + from + (select argset,value + from args where key = $id) as A left join data_dict as DD + on DD.id = A.value + `, + { $id: id }, + { traceId: traceId } + ); + export const queryThreadWakeUp = (itid: number, startTime: number, dur: number): Promise> => query( -- Gitee From 38cae7b5fe99e1b404b43aea557919eb432bcc41 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Thu, 22 Aug 2024 20:13:54 +0800 Subject: [PATCH 53/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E7=82=B9=E5=87=BBn?= =?UTF-8?q?ative=20memory=E3=80=81hiperf=E6=A0=91=E7=8A=B6=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=8C=E8=B0=83=E7=94=A8=E6=A0=88=E4=B8=8D=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- ide/src/base-ui/table/lit-table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/base-ui/table/lit-table.ts b/ide/src/base-ui/table/lit-table.ts index f485b256..de844eb3 100644 --- a/ide/src/base-ui/table/lit-table.ts +++ b/ide/src/base-ui/table/lit-table.ts @@ -179,7 +179,7 @@ export class LitTable extends HTMLElement { } } if (item.percent) { - const match = item.percent.match(/^(\d+(\.\d+)?)/); + const match = String(item.percent).match(/^(\d+(\.\d+)?)/); if (match && match[1]) { const length = match[1].length; this.maxLength = Math.max(this.maxLength, length); -- Gitee From 69580005a8eefe3d1c3de8f3a456277ece46b144 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 22 Aug 2024 20:22:55 +0800 Subject: [PATCH 54/85] =?UTF-8?q?fix:=20=E5=B1=8F=E8=94=BD=E6=9C=AA?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=95=B0=E6=8D=AE=E7=9A=84=E6=B3=B3=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/chart/SpFrameTimeChart.ts | 5 ++++- ide/src/trace/database/sql/SqlLite.sql.ts | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ide/src/trace/component/chart/SpFrameTimeChart.ts b/ide/src/trace/component/chart/SpFrameTimeChart.ts index cd17cb00..30c95421 100644 --- a/ide/src/trace/component/chart/SpFrameTimeChart.ts +++ b/ide/src/trace/component/chart/SpFrameTimeChart.ts @@ -41,6 +41,7 @@ import { queryFrameApp, queryFrameTimeData, queryPhysicalData, + querySourceTypen, } from '../../database/sql/SqlLite.sql'; import { queryAllProcessNames } from '../../database/sql/ProcessThread.sql'; @@ -241,9 +242,11 @@ export class SpFrameTimeChart { firstRow: TraceRow, secondRow: TraceRow ): Promise { + let sourceTypeName = await querySourceTypen(); this.flagConfig = FlagsConfig.getFlagsConfig('AnimationAnalysis'); let appNameMap: Map = new Map(); - if (this.flagConfig?.AnimationAnalysis === 'Enabled') { + //@ts-ignore + if (this.flagConfig?.AnimationAnalysis === 'Enabled' && sourceTypeName[0].value !== "txt-based-trace") { if (process.processName?.startsWith('render_service')) { let targetRowList = processRow.childrenList.filter( (childRow) => childRow.rowType === 'thread' && childRow.name.startsWith('render_service') diff --git a/ide/src/trace/database/sql/SqlLite.sql.ts b/ide/src/trace/database/sql/SqlLite.sql.ts index 638520aa..15953b07 100644 --- a/ide/src/trace/database/sql/SqlLite.sql.ts +++ b/ide/src/trace/database/sql/SqlLite.sql.ts @@ -1309,6 +1309,17 @@ export const queryAnimationTimeRangeData = (): Promise> => + query( + 'querySourceTypen', + `SELECT + value + FROM + meta + where + name = 'source_type' + ` + ); export const queryFrameDynamicData = (): Promise => query( -- Gitee From a7cfff0f1567900da27a61d9d69c83b93212fe43 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Fri, 23 Aug 2024 10:27:07 +0800 Subject: [PATCH 55/85] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=94=A4=E9=86=92=E9=93=BE=E6=95=B0=E6=8D=AE=E7=9A=84sql?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/sheet/TabPaneCurrentSelection.ts | 2 + .../trace/database/sql/ProcessThread.sql.ts | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 640245a6..44e05056 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -1977,7 +1977,9 @@ export class TabPaneCurrentSelection extends BaseElement { let wakeupTs = wakeup[0].ts as number; //@ts-ignore let wf = await queryRWakeUpFrom(data.itid!, wakeupTs);//查找到的前一条R信息,对应的唤醒信息 + //@ts-ignore if (wf && wf[0]) { + //@ts-ignore wakeupFrom = wf[0]; //@ts-ignore wakeupFrom.thread = Utils.getInstance().getThreadMap().get(wakeupFrom.tid!) || 'Thread'; diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index b9a8ea96..c2486a63 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -352,25 +352,31 @@ where B.tid = $tid and B.pid = $pid;`, } }; -export const queryRWakeUpFrom = (itid: number, startTime: number): Promise> => { - let sql = ` - select - (A.ts - B.start_ts) as ts, - A.tid, - A.itid, - A.arg_setid as argSetID - from - thread_state A, - trace_range B - where - A.state = 'Running' - and A.itid = (select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1) - and A.ts < ${startTime} - order by - ts desc - limit 1 +export const queryRWakeUpFrom = async (itid: number, startTime: number): Promise => { + let sql1 = `select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1`; + const res = await query('queryRWakeUpFrom', sql1, {}, { traceId: Utils.currentSelectTrace }); + if (res && res.length) { + //@ts-ignore + let wakeupFromItid = res[0].wakeup_from; + let sql2 =` + select + (A.ts - B.start_ts) as ts, + A.tid, + A.itid, + A.arg_setid as argSetID + from + thread_state A, + trace_range B + where + A.state = 'Running' + and A.itid = ${wakeupFromItid} + and A.ts < ${startTime} + order by + ts desc + limit 1 `; - return query('queryRWakeUpFrom', sql, {}, { traceId: Utils.currentSelectTrace }); + return query('queryRWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); + } }; export const queryRunnableTimeByRunning = (tid: number, startTime: number): Promise> => { let sql = ` -- Gitee From b1a3106b0aaadfff4f04bfef3ba1fc99a6e48b02 Mon Sep 17 00:00:00 2001 From: liufei Date: Fri, 23 Aug 2024 11:47:35 +0800 Subject: [PATCH 56/85] =?UTF-8?q?fix=EF=BC=9Ahiperf-bottomup=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E9=97=AE=E9=A2=98=EF=BC=8Cnative=5Fmemory=E6=A1=86?= =?UTF-8?q?=E9=80=89tab=E9=A1=B5=E8=B7=B3=E8=BD=AC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- .../trace/sheet/hiperf/TabPerfBottomUp.ts | 21 +++++++++------- .../sheet/native-memory/TabPaneNMCallTree.ts | 21 ++++++++++------ .../sheet/native-memory/TabPaneNMemory.ts | 14 +++++------ .../ProcedureLogicWorkerCommon.ts | 9 +++++++ .../ProcedureLogicWorkerNativeNemory.ts | 24 ++++++++++++------- .../ui-worker/ProcedureWorkerCommon.ts | 4 ++++ 6 files changed, 61 insertions(+), 32 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts index e2eb8338..e16e6aed 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts @@ -24,7 +24,7 @@ import '../../../../../base-ui/progress-bar/LitProgressBar'; import { procedurePool } from '../../../../database/Procedure'; import { type LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar'; import { type PerfBottomUpStruct } from '../../../../bean/PerfBottomUpStruct'; -import { findSearchNode } from '../../../../database/ui-worker/ProcedureWorkerCommon'; +import { findSearchNode, HiPerfStruct } from '../../../../database/ui-worker/ProcedureWorkerCommon'; import { SpSystemTrace } from '../../../SpSystemTrace'; @element('tabpane-perf-bottom-up') @@ -54,10 +54,15 @@ export class TabpanePerfBottomUp extends BaseElement { this.bottomUpFilter!.getFilterData(() => { if (this.searchValue !== this.bottomUpFilter!.filterValue) { this.searchValue = this.bottomUpFilter!.filterValue; + HiPerfStruct.bottomFindCount = 0; findSearchNode(this.bottomUpSource, this.searchValue, false); } - this.bottomUpTable!.setStatus(this.bottomUpSource, true); - this.setBottomUpTableData(this.bottomUpSource); + if (HiPerfStruct.bottomFindCount === 0 && this.bottomUpFilter!.filterValue !== '') { + this.bottomUpTable!.recycleDataSource = [] + } else { + this.bottomUpTable!.setStatus(this.bottomUpSource, true); + this.setBottomUpTableData(this.bottomUpSource); + } }); this.bottomUpFilter?.addEventListener('focus', () => { spSystemTrace.focusTarget = 'bottomUpInput'; @@ -205,14 +210,12 @@ export class TabpanePerfBottomUp extends BaseElement { super.connectedCallback(); new ResizeObserver(() => { // @ts-ignore - this.bottomUpTable?.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - tableOffsetHeight - }px`; + this.bottomUpTable?.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - tableOffsetHeight + }px`; this.bottomUpTable?.reMeauseHeight(); // @ts-ignore - this.stackTable?.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - tableOffsetHeight - spanHeight - }px`; + this.stackTable?.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - tableOffsetHeight - spanHeight + }px`; this.stackTable?.reMeauseHeight(); }).observe(this.parentElement!); } diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts index 5c97a56c..e6630e6a 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts @@ -147,7 +147,7 @@ export class TabpaneNMCalltree extends BaseElement { } else { this.nmCallTreeFilter!.style.display = 'none'; } - procedurePool.submitWithName('logic0', 'native-memory-reset', [], undefined, () => {}); + procedurePool.submitWithName('logic0', 'native-memory-reset', [], undefined, () => { }); this.nmCallTreeFilter!.disabledTransfer(true); this.nmCallTreeFilter!.initializeFilterTree(true, true, this.currentSelection!.nativeMemory.length > 0); this.nmCallTreeFilter!.filterValue = ''; @@ -476,6 +476,12 @@ export class TabpaneNMCalltree extends BaseElement { ) { this.switchFlameChart(nmCallTreeData); } else { + this.nmCallTreeSource = []; + // 树状图数据清空 + this.nmCallTreeTbl!.recycleDataSource = []; + // 火焰图数据清空 + this.nmCallTreeFrameChart!.data = []; + this.switchFlameChart(nmCallTreeData); this.initGetFilterByNMCallTreeFilter(nmCallTreeData); } } @@ -700,6 +706,7 @@ export class TabpaneNMCalltree extends BaseElement { this.setLTableData(result); // @ts-ignore this.nmCallTreeFrameChart!.data = this.nmCallTreeSource; this.switchFlameChart(nmCallTreeData); + result = []; }); } else { this.nmCallTreeTbl!.setStatus(this.nmCallTreeSource, true); @@ -755,16 +762,14 @@ export class TabpaneNMCalltree extends BaseElement { } if (this.nmCallTreeTbl) { // @ts-ignore - this.nmCallTreeTbl.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - 10 - 35 - headLineHeight - }px`; + this.nmCallTreeTbl.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - 10 - 35 - headLineHeight + }px`; } this.nmCallTreeTbl?.reMeauseHeight(); if (this.filesystemTbr) { // @ts-ignore - this.filesystemTbr.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - 45 - 21 - headLineHeight - }px`; + this.filesystemTbr.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - 45 - 21 - headLineHeight + }px`; } this.filesystemTbr?.reMeauseHeight(); // @ts-ignore this.nmCallTreeLoadingPage.style.height = `${this.parentElement!.clientHeight - 24}px`; @@ -824,7 +829,9 @@ export class TabpaneNMCalltree extends BaseElement { }; private switchFlameChart(flameChartData?: unknown): void { + // 树状图 let nmCallTreePageTab = this.shadowRoot?.querySelector('#show_table'); + // 火焰图 let nmCallTreePageChart = this.shadowRoot?.querySelector('#show_chart'); // @ts-ignore if (!flameChartData || flameChartData.icon === 'block') { nmCallTreePageChart?.setAttribute('class', 'show'); diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts index 9fa387ca..9933db85 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts @@ -194,7 +194,7 @@ export class TabPaneNMemory extends BaseElement { this.filterSetSelectList(nmFilterEl!, typeIndexOf); this.filterNativeType = `${typeIndexOf}`; this.rowSelectData = undefined; - this.queryData(val, false); + this.queryData(val, true); }); } else { //@ts-ignore @@ -408,7 +408,7 @@ export class TabPaneNMemory extends BaseElement { let args = new Map(); //@ts-ignore args.set('startTs', this.rowSelectData.startTs); args.set('actionType', 'native-memory-state-change'); - this.startNmMemoryWorker('native-memory-action', args, (results: unknown[]) => {}); + this.startNmMemoryWorker('native-memory-action', args, (results: unknown[]) => { }); TabPaneNMSampleList.addSampleData(this.rowSelectData, this.currentSelection!.nativeMemoryCurrentIPid); this.memoryTbl!.scrollToData(this.rowSelectData); } @@ -425,16 +425,14 @@ export class TabPaneNMemory extends BaseElement { if (this.parentElement?.clientHeight !== 0) { if (this.memoryTbl) { // @ts-ignore - this.memoryTbl.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - 10 - 31 - }px`; + this.memoryTbl.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - 10 - 31 + }px`; } this.memoryTbl?.reMeauseHeight(); if (this.tblData) { // @ts-ignore - this.tblData.shadowRoot.querySelector('.table').style.height = `${ - this.parentElement!.clientHeight - 10 - 31 - }px`; + this.tblData.shadowRoot.querySelector('.table').style.height = `${this.parentElement!.clientHeight - 10 - 31 + }px`; } this.tblData?.reMeauseHeight(); //@ts-ignore this.loadingPage.style.height = `${this.parentElement!.clientHeight - 24}px`; diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts index 9ea320ff..e610e76e 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerCommon.ts @@ -228,14 +228,19 @@ class MerageBeanDataSplit { } resetAllNode(data: MerageBean[], currentTreeList: ChartStruct[], searchValue: string): void { + // 去除全部节点上次筛选的标记 this.clearSearchNode(currentTreeList); + // 去除线程上次筛选的标记 data.forEach((process) => { process.searchShow = true; process.isSearch = false; }); + // 恢复上次筛选 this.resetNewAllNode(data, currentTreeList); if (searchValue !== '') { + // 将筛选匹配的节点做上标记,search = true,否则都是false this.findSearchNode(data, searchValue, false); + // 将searchshow为true的节点整理树结构,其余的不管 this.resetNewAllNode(data, currentTreeList); } } @@ -244,6 +249,7 @@ class MerageBeanDataSplit { data.forEach((process) => { process.children = []; }); + // 所有节点的children都置空 let values = currentTreeList.map((item: ChartStruct) => { item.children = []; return item; @@ -253,6 +259,9 @@ class MerageBeanDataSplit { if (item.parentNode !== undefined) { //@ts-ignore if (item.isStore === 0 && item.searchShow) { + /* + 拿到当前节点的父节点,如果它的父节点没有被搜索,则找到它父节点的父节点 + */ //@ts-ignore let parentNode = item.parentNode; while (parentNode !== undefined && !(parentNode.isStore === 0 && parentNode.searchShow)) { diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts index e1eb2735..2891b95c 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts @@ -792,6 +792,7 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { } totalSize += nativeHookSample.heapSize; totalCount += nativeHookSample.count || 1; + // 根据eventId拿到对应的调用栈 let callChains = this.createThreadSample(nativeHookSample); let topIndex = isTopDown ? 0 : callChains.length - 1; if (callChains.length > 0) { @@ -806,21 +807,26 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { '-' + (callChains[topIndex].fileId || ''); } - // 根节点 + // 有根节点的话就拿到对应的根节点 -----线程 let root = this.currentTreeMapData[key]; // 没有当前项的根节点,就new一个新的,放在currentTreeList if (root === undefined) { root = new NativeHookCallInfo(); root.threadName = nativeHookSample.threadName; + // 把新建的根节点加到map对象 this.currentTreeMapData[key] = root; + // 并且假草根节点数组 this.currentTreeList.push(root); } + // 给顶层节点赋值,symbol,eventId,fileId等等 this.mergeCallChainSample(root, callChains[topIndex], nativeHookSample); if (callChains.length > 1) { + // 递归造树结构 this.merageChildrenByIndex(root, callChains, topIndex, nativeHookSample, isTopDown); } } }); + // 合并线程级别 let rootMerageMap = this.mergeNodeData(totalCount, totalSize); this.handleCurrentTreeList(totalCount, totalSize); this.allThreads = Object.values(rootMerageMap) as NativeHookCallInfo[]; @@ -828,11 +834,13 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { private mergeNodeData(totalCount: number, totalSize: number): CallInfoMap { let rootMerageMap: CallInfoMap = {}; let threads = Object.values(this.currentTreeMapData); + // 遍历所有线程 threads.forEach((merageData: NativeHookCallInfo): void => { if (this.isHideThread) { merageData.tid = 0; merageData.threadName = undefined; } + // 没有父级,生成父级,把当前项放进去 if (rootMerageMap[merageData.tid] === undefined) { let threadMerageData = new NativeHookCallInfo(); //新增进程的节点数据 threadMerageData.canCharge = false; @@ -849,6 +857,7 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { threadMerageData.countArray = [...merageData.countArray]; rootMerageMap[merageData.tid] = threadMerageData; } else { + // 有父级,直接放进去 rootMerageMap[merageData.tid].children.push(merageData); rootMerageMap[merageData.tid].initChildren.push(merageData); rootMerageMap[merageData.tid].count += merageData.count || 1; @@ -876,11 +885,9 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { nmTreeNode.id = id + ''; id++; } - if (nmTreeNode.parentNode) { - if (nmTreeNode.parentNode.id === '') { - nmTreeNode.parentNode.id = id + ''; - id++; - } + if (nmTreeNode.parentNode && nmTreeNode.parentNode.id === '') { + nmTreeNode.parentNode.id = id + ''; + id++; nmTreeNode.parentId = nmTreeNode.parentNode.id; } }); @@ -1118,7 +1125,7 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { let node: NativeHookCallInfo; if ( //@ts-ignore - currentNode.initChildren.filter((child: NativeHookCallInfo): boolean => { + currentNode.children.filter((child: NativeHookCallInfo): boolean => { if ( child.symbolId === callChainDataList[index]?.symbolId && child.fileId === callChainDataList[index]?.fileId @@ -1133,7 +1140,8 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { node = new NativeHookCallInfo(); this.mergeCallChainSample(node, callChainDataList[index], sample); currentNode.children.push(node); - currentNode.initChildren.push(node); + // currentNode.initChildren.push(node); + // 将所有节点存到this.currentTreeList this.currentTreeList.push(node); node.parentNode = currentNode; } diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts index 1cde4209..f40f4f33 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts @@ -1660,6 +1660,7 @@ export function hiPerf2(filter: Array, startNS: number, endNS: num export class HiPerfStruct extends BaseStruct { static hoverStruct: HiPerfStruct | undefined; static selectStruct: HiPerfStruct | undefined; + static bottomFindCount: number = 0; id: number | undefined; callchain_id: number | undefined; timestamp: number | undefined; @@ -2013,6 +2014,9 @@ export function findSearchNode(data: unknown[], search: string, parentSearch: bo parentNode.searchShow = true; parentNode = parentNode.parent; } + if (node.isSearch && search !== '') { + HiPerfStruct.bottomFindCount += 1; + } } else { node.searchShow = false; node.isSearch = false; -- Gitee From f27963874220f42bd84886efcf89a7dd70d703ba Mon Sep 17 00:00:00 2001 From: leishaogang Date: Sat, 24 Aug 2024 11:16:30 +0800 Subject: [PATCH 57/85] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=90=88=E5=B9=B6hangs?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E6=97=B6=E5=BC=95=E5=85=A5=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: leishaogang --- ide/src/trace/component/SpSystemTrace.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 80bac49a..b431143b 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -1213,7 +1213,6 @@ export class SpSystemTrace extends BaseElement { CpuFreqLimitsStruct.hoverCpuFreqLimitsStruct = undefined; FpsStruct.hoverFpsStruct = undefined; ClockStruct.hoverClockStruct = undefined; - HangStruct.selectHangStruct = undefined; IrqStruct.hoverIrqStruct = undefined; HeapStruct.hoverHeapStruct = undefined; JankStruct.hoverJankStruct = undefined; -- Gitee From a3bfc172c529224fd78c886825f8ccd5f9c7fdb3 Mon Sep 17 00:00:00 2001 From: wangyujie Date: Mon, 26 Aug 2024 10:28:12 +0800 Subject: [PATCH 58/85] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dirq=20tab=E9=A1=B5?= =?UTF-8?q?=E7=9A=84dur=E4=B8=8E=E8=B0=83=E5=BA=A6=E5=88=86=E6=9E=90irq?= =?UTF-8?q?=E7=9A=84dur=E4=B8=8D=E7=BB=9F=E4=B8=80=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E4=B8=AA=E6=95=B0=E7=BB=9F=E8=AE=A1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangyujie --- .../trace/sheet/cpu/TabPaneCpuByThread.ts | 31 ++++++++++++++----- .../trace/sheet/irq/TabPaneIrqCounter.ts | 21 ++++++++++--- ide/src/trace/database/sql/Irq.sql.ts | 4 +-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts index eee7cd8d..d03da128 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts @@ -145,8 +145,25 @@ export class TabPaneCpuByThread extends BaseElement { } //当全局Ts等于minEndTs时,只做删除处理 if (globalTs === minEndTs) { - if (minIndex !== -1) { waitArr.splice(minIndex, 1) }; - continue; + if (minIndex !== -1) { + const item = waitArr[minIndex]; + if (item.endTime > item.startTime) { + waitArr.splice(minIndex, 1); + } else { + // wallDuration为0,需要特别处理 + const obj: finalResultBean = { + cat: item.cat, + cpu: item.cpu, + dur: 0, + pid: item.pid ? item.pid : '[NULL]', + tid: item.tid ? item.tid : '[NULL]', + occurrences: item.isFirstObject === 1 ? 1 : 0 + }; + completedArr.push(obj); + waitArr.splice(minIndex, 1); + } + continue; + } } let obj: finalResultBean = { cat: '', @@ -237,7 +254,7 @@ export class TabPaneCpuByThread extends BaseElement { }; data.forEach((obj) => { // 聚合 cpu 数据 - if (obj.cat === "cpu" && obj.dur !== 0) { + if (obj.cat === "cpu") { const tidPidKey = `${obj.tid}-${obj.pid}`; const cpuDurationKey = `cpu${obj.cpu}`; const cpuPercentKey = `cpu${obj.cpu}Ratio`; @@ -264,11 +281,11 @@ export class TabPaneCpuByThread extends BaseElement { } // 聚合 softirq 数据 - if (obj.cat === "softirq" && obj.dur !== 0) { + if (obj.cat === "softirq") { this.updateIrqAndSoftirq(softirqAggregations, obj, cpuByThreadValue); } // 聚合 irq 数据 - if (obj.cat === "irq" && obj.dur !== 0) { + if (obj.cat === "irq") { this.updateIrqAndSoftirq(irqAggregations, obj, cpuByThreadValue); } @@ -285,12 +302,12 @@ export class TabPaneCpuByThread extends BaseElement { } // 添加softirq - if (softirqAggregations.wallDuration > 0) { + if (softirqAggregations.wallDuration >= 0) { result.push({ process: 'softirq', thread: 'softirq', tid: '[NULL]', pid: '[NULL]', ...softirqAggregations, }); } // 添加 irq 数据 - if (irqAggregations.wallDuration) { + if (irqAggregations.wallDuration >= 0) { result.push({ process: 'irq', thread: 'irq', tid: '[NULL]', pid: '[NULL]', ...irqAggregations }); } this.handleFunction(result, cpuByThreadValue); diff --git a/ide/src/trace/component/trace/sheet/irq/TabPaneIrqCounter.ts b/ide/src/trace/component/trace/sheet/irq/TabPaneIrqCounter.ts index d15ff730..0292fe7f 100644 --- a/ide/src/trace/component/trace/sheet/irq/TabPaneIrqCounter.ts +++ b/ide/src/trace/component/trace/sheet/irq/TabPaneIrqCounter.ts @@ -214,8 +214,23 @@ export class TabPaneIrqCounter extends BaseElement { } //当全局Ts等于minEndTs时,只做删除处理 if (globalTs === minEndTs) { - if (minIndex !== -1) { waitArr.splice(minIndex, 1) }; - continue; + if (minIndex !== -1) { + const item = waitArr[minIndex]; + if (item.endTime > item.startTime) { + waitArr.splice(minIndex, 1); + } else { + // wallDuration为0,需要特别处理 + const obj: finalResultBean = { + cat: item.cat, + name: item.name, + wallDuration: 0, + count: item.isFirstObject === 1 ? 1 : 0 + }; + completedArr.push(obj); + waitArr.splice(minIndex, 1); + } + continue; + } } let obj: finalResultBean = { cat: '', @@ -283,7 +298,6 @@ export class TabPaneIrqCounter extends BaseElement { private aggregateData(data: finalResultBean[], isSelectIrq: boolean): void { function groupAndSumDurations(items: finalResultBean[]): finalResultBean[] { const grouped: Record = items.reduce((acc, item) => { - if (item.wallDuration !== 0) { if (item.cat === 'irq' && !isSelectIrq) {//若没有框选irq,则不对其进行处理 return acc; } @@ -305,7 +319,6 @@ export class TabPaneIrqCounter extends BaseElement { acc[item.name].maxDuration = item.wallDuration; acc[item.name].maxDurationFormat = (acc[item.name].maxDuration! / 1000).toFixed(2); } - } return acc; }, {} as Record); return Object.values(grouped); diff --git a/ide/src/trace/database/sql/Irq.sql.ts b/ide/src/trace/database/sql/Irq.sql.ts index 24d6d752..7c03733d 100644 --- a/ide/src/trace/database/sql/Irq.sql.ts +++ b/ide/src/trace/database/sql/Irq.sql.ts @@ -70,7 +70,7 @@ export const queryIrqDataBoxSelect = ( select case when i.cat = 'ipi' then 'IPI' || i.name else i.name end as irqName, 'irq' as cat, sum( - min(${endNS},(i.ts - t.start_ts + iif(i.dur = -1 OR i.dur is null, 0, i.dur))) - min(${startNS},i.ts - t.start_ts) + min(${endNS},(i.ts - t.start_ts + iif(i.dur = -1 or i.dur is null, 0, i.dur))) - max(${startNS},i.ts - t.start_ts) ) as wallDuration, max(dur) as maxDuration, count(1) as count, @@ -94,7 +94,7 @@ export const querySoftIrqDataBoxSelect = ( select i.name as irqName, i.cat, sum( - min(${endNS},(i.ts - t.start_ts + iif(i.dur = -1 OR i.dur is null, 0, i.dur))) - min(${startNS},i.ts - t.start_ts) + min(${endNS},(i.ts - t.start_ts + iif(i.dur = -1 or i.dur is null, 0, i.dur))) - max(${startNS},i.ts - t.start_ts) ) as wallDuration, max(dur) as maxDuration, count(1) as count, -- Gitee From 131f7c90dd8260df5d18a35e283dc0dc0c478268 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Mon, 26 Aug 2024 13:42:54 +0800 Subject: [PATCH 59/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E5=B8=A7=E7=8E=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/trace/component/SpSystemTrace.init.ts | 55 ++++++++++++++ ide/src/trace/component/SpSystemTrace.ts | 3 + .../trace/component/trace/base/RangeSelect.ts | 74 +------------------ 3 files changed, 62 insertions(+), 70 deletions(-) diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 8e09f673..b4d0f6e0 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -35,6 +35,7 @@ import { TimerShaftElement } from './trace/TimerShaftElement'; import { SpChartList } from './trace/SpChartList'; type HTMLElementAlias = HTMLElement | null | undefined; import { Utils } from './trace/base/Utils'; +import { fuzzyQueryFuncRowData, queryFuncRowData } from '../database/sql/Func.sql'; function rightButtonOnClick(sp: SpSystemTrace, rightStar: HTMLElementAlias): unknown { Object.assign(sp, { @@ -1137,6 +1138,10 @@ export async function spSystemTraceInit( sp.traceSheetEL?.displayHangsData(); sp.traceSheetEL?.displaySystemStatesData(); } + // 如果有render_service进程,查询该进程下对应泳道的方法存起来,以便框选时直接使用 + if (it.getAttribute('name')?.includes('render_service') && it.getAttribute('row-type') === 'process') { + queryRowsData(sp, it.childrenList); + } sp.intersectionObserver?.observe(it); }); // trace文件加载完毕,将动效json文件读取并存入缓存 @@ -1207,6 +1212,56 @@ function expansionChangeHandler(sp: SpSystemTrace, offsetYTimeOut: unknown): (ev }, 360); }; } +// 查询render_service对应方法行的所有数据 +// @ts-ignore +function queryRowsData(sp: SpSystemTrace, rowList: Array>): void { + rowList.forEach((row): void => { + if (row.getAttribute('row-type') === 'func') { + if (row.getAttribute('name')?.startsWith('render_service')) { + saveFrameRateData(sp, row, 'H:RSMainThread::DoComposition'); + } else if (row.getAttribute('name')?.startsWith('RSHardwareThrea')) { + saveFrameRateData(sp, row, 'H:Repaint'); + } else if (row.getAttribute('name')?.startsWith('Present')) { + savePresentData(sp, row, 'H:Waiting for Present Fence'); + } + } + }); +} + +// 查到所有的数据存储起来 +// @ts-ignore +function saveFrameRateData(sp: SpSystemTrace, row: TraceRow, funcName: string): void { + let dataList: unknown = []; + queryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { + if (res.length) { + res.forEach((item): void => { + // @ts-ignore + dataList?.push({ startTime: item.startTime!, tid: item.tid }); + }); + if (funcName === 'H:RSMainThread::DoComposition') { + // @ts-ignore + sp.docomList = dataList; + } else { + // @ts-ignore + sp.repaintList = dataList; + } + } + }); +} +// 查到present泳道所有的数据存储起来 +// @ts-ignore +function savePresentData(sp: SpSystemTrace, row: TraceRow, funcName: string): void { + let dataList: unknown = []; + fuzzyQueryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { + if (res.length) { + res.forEach((item): void => { + // @ts-ignore + dataList?.push({ endTime: item.endTime!, tid: item.tid }); + }); // @ts-ignore + sp.presentList = dataList; + } + }); +} function linkNodeHandler(linkNode: PairPoint[], sp: SpSystemTrace): void { if (linkNode[0].rowEL.collect) { linkNode[0].rowEL.translateY = linkNode[0].rowEL.getBoundingClientRect().top - 195; diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index b431143b..e181e38f 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -229,6 +229,9 @@ export class SpSystemTrace extends BaseElement { focusTarget: string = ''; wakeupListTbl: LitTable | undefined | null; _checkclick: boolean = false; //判断点击getWakeupList按钮 + docomList: Array = []; + repaintList: Array = []; + presentList: Array = []; set snapshotFile(data: FileInfo) { this.snapshotFiles = data; diff --git a/ide/src/trace/component/trace/base/RangeSelect.ts b/ide/src/trace/component/trace/base/RangeSelect.ts index 505e71b3..3749a8ea 100644 --- a/ide/src/trace/component/trace/base/RangeSelect.ts +++ b/ide/src/trace/component/trace/base/RangeSelect.ts @@ -19,7 +19,6 @@ import { TimerShaftElement } from '../TimerShaftElement'; import { info } from '../../../../log/Log'; import './Extension'; import { SpSystemTrace } from '../../SpSystemTrace'; -import { fuzzyQueryFuncRowData, queryFuncRowData } from '../../../database/sql/Func.sql'; import { SpLtpoChart } from '../../chart/SpLTPO'; import { isEmpty, isNotEmpty } from './Extension'; @@ -42,9 +41,6 @@ export class RangeSelect { }; private trace: SpSystemTrace | null | undefined; drag = false; - docomList: Array = []; - repaintList: Array = []; - presentList: Array = []; constructor(trace: SpSystemTrace | null | undefined) { this.trace = trace; @@ -76,69 +72,7 @@ export class RangeSelect { this.rangeTraceRow = []; this.isMouseDown = true; TraceRow.rangeSelectObject = undefined; - // 遍历当前可视区域所有的泳道,如果有render_service进程,查询该进程下对应泳道的方法存起来,以便框选时直接使用 - this.trace?.visibleRows.forEach((row) => { - if (row.getAttribute('name')?.includes('render_service') || row.parentRowEl?.getAttribute('name')?.includes('render_service')) { - if (row.getAttribute('row-type') === 'process') { - this.queryRowsData(row.childrenList); - } else if(row.getAttribute('row-type') === 'func'){ - this.queryRowsData(row.parentRowEl!.childrenList); - } - return; - } - }); - } - - // 对应查询方法行所有的数据 - // @ts-ignore - queryRowsData(rowList: Array>): void { - rowList.forEach((row): void => { - if (row.getAttribute('row-type') === 'func') { - if (row.getAttribute('name')?.startsWith('render_service')) { - this.saveFrameRateData(row, 'H:RSMainThread::DoComposition'); - } else if (row.getAttribute('name')?.startsWith('RSHardwareThrea')) { - this.saveFrameRateData(row, 'H:Repaint'); - } else if (row.getAttribute('name')?.startsWith('Present')) { - this.savePresentData(row, 'H:Waiting for Present Fence'); - } - } - }); - } - - // 查到所有的数据存储起来 - // @ts-ignore - saveFrameRateData(row: TraceRow, funcName: string): void { - let dataList: unknown = []; - queryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { - if (res.length) { - res.forEach((item): void => { - // @ts-ignore - dataList?.push({ startTime: item.startTime!, tid: item.tid }); - }); - if (funcName === 'H:RSMainThread::DoComposition') { - // @ts-ignore - this.docomList = dataList; - } else { - // @ts-ignore - this.repaintList = dataList; - } - } - }); - } - // 查到present泳道所有的数据存储起来 - // @ts-ignore - savePresentData(row: TraceRow, funcName: string): void { - let dataList: unknown = []; - fuzzyQueryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { - if (res.length) { - res.forEach((item): void => { - // @ts-ignore - dataList?.push({ endTime: item.endTime!, tid: item.tid }); - }); // @ts-ignore - this.presentList = dataList; - } - }); - } + } mouseUp(mouseEventUp?: MouseEvent): void { if (mouseEventUp) { @@ -166,11 +100,11 @@ export class RangeSelect { ) { row.frameRateList = []; if (row.getAttribute('name')?.startsWith('render_service')) { - this.filterRateData(row, this.docomList); + this.filterRateData(row, this.trace?.docomList); } else if (row.getAttribute('name')?.startsWith('RSHardwareThrea')) { - this.filterRateData(row, this.repaintList); + this.filterRateData(row, this.trace?.repaintList); } else if (row.getAttribute('name')?.startsWith('Present')) { - this.filterPresentData(row, this.presentList); + this.filterPresentData(row, this.trace?.presentList); } } }); -- Gitee From c9d9d3f1bcb8ee678246e79cfe18bcaef4df3f61 Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Mon, 26 Aug 2024 15:55:39 +0800 Subject: [PATCH 60/85] =?UTF-8?q?feat:=20xiaoluban=20AI=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=BC=80=E5=8F=91=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/SpApplication.ts | 1 + ide/src/trace/SpApplicationPublicFunc.ts | 1 + ide/src/trace/component/SpBubblesAI.html.ts | 35 ++++++++++++ ide/src/trace/component/SpBubblesAI.ts | 63 +++++++++++++++++++++ ide/src/trace/component/SpFlags.ts | 51 ++++++++++++++++- 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 ide/src/trace/component/SpBubblesAI.html.ts create mode 100644 ide/src/trace/component/SpBubblesAI.ts diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index b76f0df6..0b78adca 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -79,6 +79,7 @@ import '../base-ui/chart/scatter/LitChartScatter'; import { SpThirdParty } from './component/SpThirdParty'; import './component/SpThirdParty'; import { cancelCurrentTraceRowHighlight } from './component/SpSystemTrace.init'; +import './component/SpBubblesAI'; @element('sp-application') export class SpApplication extends BaseElement { diff --git a/ide/src/trace/SpApplicationPublicFunc.ts b/ide/src/trace/SpApplicationPublicFunc.ts index 5aed2831..0c977163 100644 --- a/ide/src/trace/SpApplicationPublicFunc.ts +++ b/ide/src/trace/SpApplicationPublicFunc.ts @@ -327,6 +327,7 @@ export const applicationHtml: string = ` }
+ diff --git a/ide/src/trace/component/SpBubblesAI.html.ts b/ide/src/trace/component/SpBubblesAI.html.ts new file mode 100644 index 00000000..fe6bb2c2 --- /dev/null +++ b/ide/src/trace/component/SpBubblesAI.html.ts @@ -0,0 +1,35 @@ +/* + * 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 SpBubblesAIHtml = ` +
+
+ +
+
+ `; diff --git a/ide/src/trace/component/SpBubblesAI.ts b/ide/src/trace/component/SpBubblesAI.ts new file mode 100644 index 00000000..9d03886f --- /dev/null +++ b/ide/src/trace/component/SpBubblesAI.ts @@ -0,0 +1,63 @@ +/* + * 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 { SpBubblesAIHtml } from './SpBubblesAI.html'; +import { FlagsConfig, Params } from './SpFlags'; + +@element('sp-bubble-ai') +export class SpBubblesAI extends BaseElement { + initElements(): void { + const xiaoLubanEl: HTMLElement | undefined | null = this.shadowRoot?.querySelector('#xiao-luban-help'); + xiaoLubanEl?.addEventListener('click', () => { + this.xiaoLubanEvent(); + }) + let isShowXiaoLuban: boolean = FlagsConfig.getFlagsConfigEnableStatus('xiaoLuBan'); + if (isShowXiaoLuban) { + xiaoLubanEl?.setAttribute('enabled', ''); + } else { + xiaoLubanEl?.removeAttribute('enabled'); + } + } + + initHtml(): string { + return SpBubblesAIHtml; + } + + private xiaoLubanEvent(): void { + const flagConfig: Params | undefined = FlagsConfig.getFlagsConfig('AI'); + const userId: string | undefined = flagConfig!.userId?.toString(); + const data = { + 'sender': `${userId}`, + 'msgBody': 'rag SmartPerf_ad85b972', + 'msgType': 'text', + 'timestamp': new Date().getTime().toString(), + 'botUser': 'p_xiaoluban', + } + fetch('https://smartperf.rnd.huawei.com/xiaoluban/resource', { + method: 'post', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + }).then(res => { + if (res.status === 200) { + window.open('im:p_xiaoluban', "_self"); + } + }) + } +} + + diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index 98016219..811a0824 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -71,8 +71,28 @@ export class SpFlags extends BaseElement { configSelect.appendChild(configOption); }); configSelect.addEventListener('change', () => { - this.flagSelectListener(configSelect); + if (configSelect.title === 'VSync' || configSelect.title === 'Start&Finish Trace Category') { + this.flagSelectListener(configSelect); + } + if (configSelect.title === 'AI') { + let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); + let xiaoLubanEl: Element | null | undefined = document.querySelector('sp-application')?.shadowRoot?.querySelector('#sp-bubbles') + ?.shadowRoot?.querySelector('#xiao-luban-help'); + if (configSelect.selectedOptions[0].value === 'Enabled') { + if (userIdInput?.value === '') { + userIdInput.style.border = '1px solid red'; + } + xiaoLubanEl?.setAttribute('enabled', ''); + } else { + userIdInput!.style.border = '1px solid #ccc'; + xiaoLubanEl?.removeAttribute('enabled'); + } + } }); + let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); + if (configSelect.title === 'AI' && configSelect.selectedOptions[0].value === 'Enabled' && userIdInput?.value === '') { + userIdInput.style.border = '1px solid red'; + } let description = document.createElement('div'); description.className = 'flag-des-div'; description.textContent = config.describeContent; @@ -157,6 +177,29 @@ export class SpFlags extends BaseElement { configDiv.appendChild(configFooterDiv); } + if (config.title === 'AI') { + let configFooterDiv = document.createElement('div'); + configFooterDiv.className = 'config_footer'; + let userIdLabelEl = document.createElement('label'); + userIdLabelEl.className = 'device_label'; + userIdLabelEl.textContent = 'User Id: '; + let userIdInputEl = document.createElement('input'); + userIdInputEl.value = config.addInfo!.userId; + userIdInputEl.addEventListener('blur', () => { + if (userIdInputEl.value !== '') { + userIdInputEl.style.border = '1px solid #ccc'; + FlagsConfig.updateFlagsConfig('userId', userIdInputEl.value); + } else { + userIdInputEl.style.border = '1px solid red'; + } + }) + userIdInputEl.className = 'device_input'; + userIdInputEl.id = 'user_id_input'; + configFooterDiv.appendChild(userIdLabelEl); + configFooterDiv.appendChild(userIdInputEl); + configDiv.appendChild(configFooterDiv); + } + if (config.title === 'Start&Finish Trace Category') { let configKey = CONFIG_STATE['Start&Finish Trace Category' as keyof typeof CONFIG_STATE][0]; let configFooterDiv = this.createPersonOption(CAT_SORT, configKey, config.addInfo!.catValue, config.title); @@ -284,6 +327,12 @@ export class FlagsConfig { switchOptions: [{ option: 'Enabled', selected: true }, { option: 'Disabled' }], describeContent: 'Raw Trace Cut By StartTs, StartTs = Max(Cpu1 StartTs, Cpu2 StartTs, ..., CpuN StartTs)', }, + { + title: 'AI', + switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], + describeContent: 'Start AI', + addInfo: { userId: '' }, + } ]; static getAllFlagConfig(): Array { -- Gitee From 05a6ecd02e43159b96cfb9f0e566e89946778092 Mon Sep 17 00:00:00 2001 From: zhangzepeng Date: Tue, 27 Aug 2024 17:27:43 +0800 Subject: [PATCH 61/85] =?UTF-8?q?hang=E4=BB=A3=E7=A0=81=E5=BD=B1=E5=93=8Dl?= =?UTF-8?q?inux=E7=B3=BB=E7=BB=9F=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzepeng --- ide/src/trace/component/trace/base/TraceSheetConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index a3f29daf..ad4e8de4 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -64,8 +64,8 @@ import { TabPaneFreqLimit } from '../sheet/freq/TabPaneFreqLimit'; import { TabPaneCpuFreqLimits } from '../sheet/freq/TabPaneCpuFreqLimits'; import { TabpaneNMCalltree } from '../sheet/native-memory/TabPaneNMCallTree'; import { TabPaneClockCounter } from '../sheet/clock/TabPaneClockCounter'; -import { TabPaneHang } from '../sheet/Hang/TabPaneHang'; -import { TabPaneHangSummary } from '../sheet/Hang/TabPaneHangSummary'; +import { TabPaneHang } from '../sheet/hang/TabPaneHang'; +import { TabPaneHangSummary } from '../sheet/hang/TabPaneHangSummary'; import { TabPaneIrqCounter } from '../sheet/irq/TabPaneIrqCounter'; import { TabPaneFrames } from '../sheet/jank/TabPaneFrames'; import { TabPanePerfAnalysis } from '../sheet/hiperf/TabPanePerfAnalysis'; -- Gitee From a1db28bfd54af7efb42d2dbf4be9e1b95bd853b2 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Tue, 27 Aug 2024 20:28:32 +0800 Subject: [PATCH 62/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E6=B3=B3=E9=81=93?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=8F=8A=E8=BF=9B=E7=A8=8B=E4=B8=8Bhang?= =?UTF-8?q?=E6=B3=B3=E9=81=93=E7=9A=84=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/component/chart/SpProcessChart.ts | 133 +++++++----------- 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index 479eaf44..ea5af6f0 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -560,48 +560,18 @@ export class SpProcessChart { actualRow = this.addActualRow(it, processRow, rsProcess); } } - this.renderRow = null; //@ts-ignore - if (this.loadAppStartup) { - if (this.startupProcessArr.find((sp) => sp.pid === it.pid)) { - startupRow = this.addStartUpRow(processRow); - } - let maxSoDepth = this.processSoMaxDepth.find((md) => md.pid === it.pid); - if (maxSoDepth) { - soRow = this.addSoInitRow(processRow, maxSoDepth.maxDepth); - } - } - - let hangsRow: TraceRow | null = null; this.renderRow = null; if (it.processName === 'render_service') { //@ts-ignore this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId); //@ts-ignore this.addProcessMemInfo(it, processRow); //@ts-ignore this.addAsyncFunction(it, processRow);//@ts-ignore - this.addThreadList(it, processRow, expectedRow, actualRow, hangsRow, soRow, startupRow); this.addProcessMemInfo(it, processRow); - if (jankArr.indexOf(it.pid!) > -1) { - expectedRow = this.addExpectedRow(it, processRow, rsProcess); - actualRow = this.addActualRow(it, processRow, rsProcess); - } - hangsRow = this.addHangRow(it, processRow); - this.addProcessRowListener(processRow, actualRow); - this.addAsyncFunction(it, processRow); - this.addAsyncCatFunction(it, processRow); } else { //@ts-ignore this.addAsyncFunction(it, processRow); //@ts-ignore this.addProcessMemInfo(it, processRow); //@ts-ignore this.addThreadList(it, processRow, expectedRow, actualRow, soRow, startupRow, traceId);//@ts-ignore - if (jankArr.indexOf(it.pid!) > -1) { - expectedRow = this.addExpectedRow(it, processRow, rsProcess); - actualRow = this.addActualRow(it, processRow, rsProcess); - } - hangsRow = this.addHangRow(it, processRow); - this.addProcessRowListener(processRow, actualRow); - this.addAsyncFunction(it, processRow); - this.addProcessMemInfo(it, processRow); - this.addThreadList(it, processRow, expectedRow, actualRow, hangsRow, soRow, startupRow); this.addAsyncCatFunction(it, processRow); } this.addProcessRowListener(processRow, actualRow); @@ -901,59 +871,57 @@ export class SpProcessChart { //@ts-ignore addHangRow( data: { - pid: number; + pid: number | null; processName: string | null; }, - row: TraceRow - ): TraceRow | null { - if (!this.hangProcessSet.has(data.pid) || !FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { - return null; - } - let hangsRow = TraceRow.skeleton(); - hangsRow.rowType = TraceRow.ROW_TYPE_HANG_INNER; - hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}` - hangsRow.rowParentId = `${data.pid}`; - hangsRow.rowHidden = !row.expansion; - hangsRow.style.width = '100%'; - hangsRow.name = 'Hangs'; - hangsRow.addTemplateTypes('FrameTimeline'); - hangsRow.setAttribute('children', ''); - hangsRow.supplierFrame = async (): Promise => { - let promiseData = hangDataSender(data.pid, hangsRow); - if (promiseData === null) { - return new Promise>((resolve) => resolve([])); - } else { - return promiseData.then((resultHang: Array) => - resultHang.map(hangItem => ({ - ...hangItem, - pname: data.processName ?? "process", - type: SpHangChart.calculateHangType(hangItem.dur!), - content: SpHangChart.funcNameMap.get(hangItem.id!) - })) + processRow: TraceRow, + funcRow: TraceRow, + thread: unknown + ): void { + if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { + //@ts-ignore + if (data.pid === thread.tid) { + let hangsRow = TraceRow.skeleton(); + hangsRow.rowType = TraceRow.ROW_TYPE_HANG_INNER; + hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}` + hangsRow.rowParentId = `${data.pid}`; + hangsRow.rowHidden = !processRow.expansion; + hangsRow.style.width = '100%'; + hangsRow.name = 'Hangs'; + hangsRow.addTemplateTypes('FrameTimeline'); + hangsRow.setAttribute('children', ''); + hangsRow.supplierFrame = async (): Promise => { + let promiseData = hangDataSender(data.pid!, hangsRow); + if (promiseData === null) { + return new Promise>((resolve) => resolve([])); + } else { + return promiseData.then((resultHang: Array) => + resultHang.map(hangItem => ({ + ...hangItem, + pname: data.processName ?? "process", + type: SpHangChart.calculateHangType(hangItem.dur!), + content: SpHangChart.funcNameMap.get(hangItem.id!) + })) + ); + } + }; + hangsRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; + hangsRow.selectChangeHandler = this.trace.selectChangeHandler; + hangsRow.findHoverStruct = (): void => { + HangStruct.hoverHangStruct = hangsRow.getHoverStruct(); + } + hangsRow.onThreadHandler = rowThreadHandler( + 'hang', + 'context', + { + type: 'hangs_frame_timeline_slice', + }, + hangsRow, + this.trace ); + processRow.addChildTraceRowAfter(hangsRow, funcRow); } - }; - hangsRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; - hangsRow.selectChangeHandler = this.trace.selectChangeHandler; - hangsRow.findHoverStruct = (): void => { - HangStruct.hoverHangStruct = hangsRow.getHoverStruct(); - } - hangsRow.onThreadHandler = rowThreadHandler( - 'hang', - 'context', - { - type: 'hangs_frame_timeline_slice', - }, - hangsRow, - this.trace - ); - - if (this.renderRow) { - row.addChildTraceRowBefore(hangsRow, this.renderRow); - } else { - row.addChildTraceRow(hangsRow); } - return hangsRow; } jankSenderCallback( @@ -1097,7 +1065,6 @@ export class SpProcessChart { pRow: TraceRow, expectedRow: TraceRow | null, actualRow: TraceRow | null, - hangsRow: TraceRow | null, soRow: TraceRow | undefined, startupRow: TraceRow | undefined, traceId?: string @@ -1141,7 +1108,7 @@ export class SpProcessChart { tRow, this.trace ); - this.insertRowToDoc(it, j, thread, pRow, tRow, threads, tRowArr, actualRow, expectedRow, hangsRow, startupRow, soRow); + this.insertRowToDoc(it, j, thread, pRow, tRow, threads, tRowArr, actualRow, expectedRow, startupRow, soRow); this.addFuncStackRow(it, thread, j, threads, tRowArr, tRow, pRow); if ((thread.switchCount || 0) === 0) { tRow.rowDiscard = true; @@ -1179,7 +1146,6 @@ export class SpProcessChart { actualRow: TraceRow | null, //@ts-ignore expectedRow: TraceRow | null, - hangsRow: TraceRow | null, startupRow: TraceRow | null | undefined, soRow: TraceRow | null | undefined ): void { @@ -1198,8 +1164,6 @@ export class SpProcessChart { processRow.addChildTraceRowAfter(threadRow, actualRow); } else if (expectedRow !== null) { processRow.addChildTraceRowAfter(threadRow, expectedRow); - } else if (hangsRow !== null) { - processRow.addChildTraceRowAfter(threadRow, hangsRow); } else if (soRow) { processRow.addChildTraceRowAfter(threadRow, soRow); } else if (startupRow) { @@ -1214,7 +1178,7 @@ export class SpProcessChart { } addFuncStackRow( - process: unknown, + process: { pid: number | null; processName: string | null }, thread: unknown, index: number, threads: Array, @@ -1271,6 +1235,7 @@ export class SpProcessChart { } else { processRow.addChildTraceRowAfter(funcRow, threadRow); } + this.addHangRow(process, processRow, funcRow, thread); } } -- Gitee From a352dfe62fc6cfa1c612818f6beab2bbd3c035a2 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Tue, 27 Aug 2024 20:34:33 +0800 Subject: [PATCH 63/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9HangsTab=E9=A1=B5?= =?UTF-8?q?=E5=AD=97=E6=AE=B5undefined=E4=BB=A5=E5=8F=8Asender=E5=88=97?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- .../trace/sheet/TabPaneCurrentSelection.ts | 1 + .../trace/sheet/hang/TabPaneHang.html.ts | 18 ++++++++++-------- .../component/trace/sheet/hang/TabPaneHang.ts | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 7f9b9dcb..95d56e31 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -831,6 +831,7 @@ export class TabPaneCurrentSelection extends BaseElement { "Send time", "Expect handle time", "Task name/ID", + "Prio", "Sender" ][index], value: item, diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts index 36c93092..f085a042 100644 --- a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.html.ts @@ -102,21 +102,23 @@ option { - + - + - + - + - + - + - + - + + + `; diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts index 247ad3ef..3e107354 100644 --- a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts @@ -274,11 +274,11 @@ export class HangStructInPane { dur: string = '0'; pname: string = 'Process'; type: string; - sendEventTid: string; sendTime: string; expectHandleTime: string; taskNameId: string; + prio: string; caller: string; constructor(parent: HangStruct) { @@ -286,7 +286,7 @@ export class HangStructInPane { this.dur = getTimeString(parent.dur ?? 0); this.pname = `${parent.pname ?? this.pname} ${parent.pid ?? ''}`.trim(); this.type = SpHangChart.calculateHangType(parent.dur ?? 0); - [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.caller] = (parent.content ?? ",0,0,,").split(',').map(i => i.trim()); + [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.prio, this.caller] = (parent.content ?? ",0,0,,,").split(',').map(i => i.trim()); this.sendEventTid = this.sendEventTid.split(':').at(-1)!; } -- Gitee From 1b6e8b17d3f734b3b6416616f6b17c902b4287d7 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Tue, 27 Aug 2024 20:37:18 +0800 Subject: [PATCH 64/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E8=89=B2=E5=9D=97?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E5=AD=97=E4=BD=93=E9=A2=9C=E8=89=B2=E4=BB=A5?= =?UTF-8?q?=E5=8F=8AhangsTab=E9=A1=B5=E6=96=87=E5=AD=97=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/trace/base/ColorUtils.ts | 2 +- ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index 221110e9..cf8cb8f0 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -125,7 +125,7 @@ export class ColorUtils { public static getHangColor(hangType: HangType): string { return ({ "Instant": "#559CFF", - "Circumstantial": "#FFE44D", + "Circumstantial": "#e8be44", "Micro": "#FEB354", "Severe": "#FC7470", "": "#000000", diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts index 4c916874..3f6461a7 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -90,7 +90,7 @@ export class HangStruct extends BaseStruct { static getFrameColor(data: HangStruct): string { return ({ "Instant": "#559CFF", - "Circumstantial": "#FFE44D", + "Circumstantial": "#e8be44", "Micro": "#FEB354", "Severe": "#FC7470", "": "", @@ -111,7 +111,7 @@ export class HangStruct extends BaseStruct { ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, data.frame.height); if (data.frame.width > 10) { - ctx.fillStyle = '#fff'; + ctx.fillStyle = '#000'; drawString(ctx, `${data.type || ''}`, 1, data.frame, data); } -- Gitee From 8284fe736ef46c9da633c3a621c86d9de05d7c6e Mon Sep 17 00:00:00 2001 From: zhangzhuozhou Date: Wed, 28 Aug 2024 09:51:52 +0800 Subject: [PATCH 65/85] =?UTF-8?q?fix:APPStartUP=20=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E4=BA=8C=E8=BF=9B=E5=88=B6=E4=B8=8Ewasm=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=87=BA=E6=95=B0=E6=8D=AE=E9=A1=BA=E5=BA=8F=E4=B8=8D=E4=B8=80?= =?UTF-8?q?=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzhuozhou --- trace_streamer/src/filter/app_start_filter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trace_streamer/src/filter/app_start_filter.h b/trace_streamer/src/filter/app_start_filter.h index ce97cad4..e91befef 100644 --- a/trace_streamer/src/filter/app_start_filter.h +++ b/trace_streamer/src/filter/app_start_filter.h @@ -80,7 +80,7 @@ private: std::deque> startUIAbilityBySCBItems_; std::deque> loadAbilityItems_; appMap mAPPStartupData_; - std::unordered_map mAPPStartupDataWithPid_; + std::map mAPPStartupDataWithPid_; const std::string procTouchCmd_ = "H:client dispatch touchId:"; const std::string startUIAbilityBySCBCmd_ = "H:OHOS::ErrCode OHOS::AAFwk::AbilityManagerClient::StartUIAbilityBySCB"; -- Gitee From ebe687b35cf78c98fe99d42d9c0d27049356763b Mon Sep 17 00:00:00 2001 From: zhangyan Date: Wed, 28 Aug 2024 10:05:19 +0800 Subject: [PATCH 66/85] =?UTF-8?q?fix:=E7=BB=9F=E4=B8=80=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/trace/base/ColorUtils.ts | 2 +- ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/src/trace/component/trace/base/ColorUtils.ts b/ide/src/trace/component/trace/base/ColorUtils.ts index cf8cb8f0..24a14dfd 100644 --- a/ide/src/trace/component/trace/base/ColorUtils.ts +++ b/ide/src/trace/component/trace/base/ColorUtils.ts @@ -125,7 +125,7 @@ export class ColorUtils { public static getHangColor(hangType: HangType): string { return ({ "Instant": "#559CFF", - "Circumstantial": "#e8be44", + "Circumstantial": "#E8BE44", "Micro": "#FEB354", "Severe": "#FC7470", "": "#000000", diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts index 3f6461a7..30843e1e 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -90,7 +90,7 @@ export class HangStruct extends BaseStruct { static getFrameColor(data: HangStruct): string { return ({ "Instant": "#559CFF", - "Circumstantial": "#e8be44", + "Circumstantial": "#E8BE44", "Micro": "#FEB354", "Severe": "#FC7470", "": "", -- Gitee From 241fe3bbeb179d0dc7e3c77fcacb7ca3c43ee01c Mon Sep 17 00:00:00 2001 From: zhangyan Date: Wed, 28 Aug 2024 12:07:38 +0800 Subject: [PATCH 67/85] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=80=89=E4=B8=ADnotif?= =?UTF-8?q?ication=E5=8F=8A=E6=97=B6=E9=97=B4=E5=8D=95=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/chart/SpHangChart.ts | 6 +++--- ide/src/trace/component/setting/SpProbesConfig.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ide/src/trace/component/chart/SpHangChart.ts b/ide/src/trace/component/chart/SpHangChart.ts index ab58a519..870c0ce8 100644 --- a/ide/src/trace/component/chart/SpHangChart.ts +++ b/ide/src/trace/component/chart/SpHangChart.ts @@ -25,7 +25,7 @@ import { BaseStruct } from '../../bean/BaseStruct'; import { Utils } from '../trace/base/Utils'; export type HangType = "Instant" | "Circumstantial" | "Micro" | "Severe" | ""; - +const TIME_MS = 1000000; /// Hangs聚合泳道 export class SpHangChart { private trace: SpSystemTrace; @@ -36,7 +36,7 @@ export class SpHangChart { } static calculateHangType(dur: number): HangType { - const durMS = dur / 1000000; + const durMS = dur / TIME_MS; if (durMS < 33) { return ""; } @@ -145,7 +145,7 @@ export class SpHangChart { let hangStruct = HangStruct.hoverHangStruct; this.trace?.displayTip( traceRow, hangStruct, - `${hangStruct?.type} ${hangStruct?.dur}` + `${hangStruct?.type} ${hangStruct?.dur! / TIME_MS}ms` ); }; traceRow.findHoverStruct = (): void => { diff --git a/ide/src/trace/component/setting/SpProbesConfig.ts b/ide/src/trace/component/setting/SpProbesConfig.ts index 9367fe2d..6b67ca7b 100644 --- a/ide/src/trace/component/setting/SpProbesConfig.ts +++ b/ide/src/trace/component/setting/SpProbesConfig.ts @@ -399,7 +399,7 @@ const hiTraceConfigList = [ { value: 'multimodalinput', isSelect: true }, { value: 'musl', isSelect: false }, { value: 'net', isSelect: false }, - { value: 'notification', isSelect: false }, + { value: 'notification', isSelect: true }, { value: 'nweb', isSelect: false }, { value: 'ohos', isSelect: true }, { value: 'pagecache', isSelect: true }, -- Gitee From 16a99afb24b0673466f6828324bb4aa084a7b536 Mon Sep 17 00:00:00 2001 From: "wupoli3@huawei.com" Date: Wed, 28 Aug 2024 14:50:56 +0800 Subject: [PATCH 68/85] =?UTF-8?q?fix=EF=BC=9A=E6=A1=86=E9=80=89=E5=81=B6?= =?UTF-8?q?=E7=8E=B0=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wupoli3@huawei.com --- ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts index 06d4e5fe..8ef86590 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts @@ -144,6 +144,7 @@ export function funcStructOnClick( let hoverFuncStruct = entry || FuncStruct.hoverFuncStruct; FuncStruct.selectFuncStruct = hoverFuncStruct; sp.timerShaftEL?.drawTriangle(FuncStruct.selectFuncStruct!.startTs || 0, 'inverted'); + TraceRow.rangeSelectObject = undefined; let flagConfig = FlagsConfig.getFlagsConfig('TaskPool'); let showTabArray: Array = ['current-selection']; if (flagConfig!.TaskPool === 'Enabled') { -- Gitee From a702a45e3bb846fc929d212079b7fd3d46cfef24 Mon Sep 17 00:00:00 2001 From: wangziyi Date: Thu, 29 Aug 2024 09:43:03 +0800 Subject: [PATCH 69/85] =?UTF-8?q?fix:hismartperf=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangziyi --- ide/src/base-ui/menu/LitMainMenu.ts | 35 +++++++++++++++++++--------- ide/src/img/logo.png | Bin 3118 -> 309381 bytes 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/ide/src/base-ui/menu/LitMainMenu.ts b/ide/src/base-ui/menu/LitMainMenu.ts index 1410109d..89fa3302 100644 --- a/ide/src/base-ui/menu/LitMainMenu.ts +++ b/ide/src/base-ui/menu/LitMainMenu.ts @@ -44,15 +44,25 @@ const initHtmlStyle: string = ` display: grid; width: 100%; height: 56px; - font-size: 1.4rem; - padding-left: 20px; - gap: 0 20px; + padding-left: 24px; + gap: 0 14px; box-sizing: border-box; grid-template-columns: min-content 1fr min-content; grid-template-rows: auto; color: #47A7E0; background-color: var(--dark-background1); border-bottom: 1px solid var(--dark-background1,#EFEFEF); + align-self: center; + } + .logo-img{ + width: 32px; + height: 32px; + vertical-align: middle; + } + .logo-text{ + font-family: 鸿蒙; + font-size: 20px; + color: #1E4EEA; } .bottom{ width: 100%; @@ -81,7 +91,7 @@ const initHtmlStyle: string = ` .menu-button{ display: flex; align-content: center; - justify-content: right; + justify-content: center; cursor: pointer; height: 47px; width: 48px; @@ -273,14 +283,17 @@ export class LitMainMenu extends BaseElement { return ` ${initHtmlStyle}
- - + + + HiSmartPerf + + - +
+
diff --git a/ide/src/img/logo.png b/ide/src/img/logo.png index 725db32f13c43af3ede6c3e5fe2fa51e2c0ad263..ad56acfc01e8993d567fbf2ab45b092449b1a277 100644 GIT binary patch literal 309381 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
  • o{SWD4u#d|J`LRH9e#lAd z3*8TX_jleJE?r6_f`?qZ1QnOIHYLnsxx4W^=wHmjiJv(xUy-e39 zoFB>34~J{^5uML`+e%Q5ecoG}^WEe2Tm3x$@cVY{3AFWNF{`-_MDHJrS&DOKGd{eo z>sYHVU4zl2dw@6z9+!)f$&Oxw{!2z0r$0VzJKj2?wYSYqJ@Zc6-$vki)yQWLafOZ z$C&!wDy~QJ1s2HGfr|-&=3yRtDc9$J;@Y%dCr}RXXn>y2@82$(>mpS`^!IhyD!r3q zyPmoK$*=y(U;eMY_Qo6h&)4tkK7mhr0@u%opEli{;3-akzi4Z|;r;39?fygYtMGt1 z5p>M>g98Tzt{zG{D}@2qW7u)w$Oe=HX&rPjK$sUk(JU^}&Bcpj4E|nQ$sge5)_Dnn zKlh|NwnLOzah%nGe{T+ZC5~_S^npdBH~p7icPEK*ij=~~?$!j<{SFeX0}SWMX$NkJAk7zp_MK9o->J(!JS}dqm5&@h`Ju~@x&`!>#)hJ$w_RJib$DI zn3EcOO>CCu=M7g^l|fh<>Koy5uhSFq$_p{g-Nth(%k((U#C48_AKzCqJ z;kgqa#4ncA$>NffZOOr_Z0F!wWir=s$=Uq!d(ZrxWBn5?0r{kgzy|NRwZ}ggemU1b z=c6k6lhCe&S|r9*lNoY7>Q@r=NY$4(CjameII-FnAKI0#J^e>Nxa5P;>ldHC2#kTf zP1RQYan{eBMQ1YOsGnG|fQswuYX2p2nRV)!HvPxvY#2A^o+ZrDAPd_*`%hbd-|JxbV{Xg^1%(taKJ6~V;)O&qZ`|H7iKHJ;! zp_PLu4ahf#W|#NuHm)BcxC6b^V537<#>7%J!0f`ecRjooo|4qG$ODg0!FBfz9l@(U zDSHpbdG+-#C?POS(8usX-46}Q;X-g$)mEeWU;g1Oh`MHSO{FI;jG5FMb@8+*1i|?69cNd zF~%oRu?Z3ihPa-$^5xVX+Umn1!ZKgD?Yj6Drt@1E_q1Mr@>k=;q-~r_$7#E_g~{AP z9c`XvTXU0#?g68~uiyH2ea$2oHRRCdp2nGh`d7^L&|u#KB!)MlB__h-NEZ~W}feZ@EY17G%Mf9S9M zH3Ii_pTIA90{1^F`b!?&(@%W&`#<`1bDYc%#n0|P6u*s+TmNKx>gy264}d--#;F5n zj)`?f>@z`!hfb9oD08ayKv^FgIBe`RKYP=vW2XmmRd4`A6TX9PHt%=|pltplV#BuxHQfNM1_N-)gC zy|un8l57IiKlCHWns!gnb&ig07L|gHS^|QMxcX)*9UELrcWLvG#o-#$z4W7$KAd8n z=&u+m23w^@gBuXsbC)iNZqnr?x6155N?YL|tt%o2Wp5dB&lI zvSN7t41?xrf`{eGXor#OL!ujx-Q@eX_16L|f7ANiX3 z4*Qp;%P)S~oxb|r`AzEY^B21ZnL<5ieEjIF^$C*mD{%-0PkeDTKQRJlOl35tYYkjZ z!4HXdTr17z)Ao~tZ(+2RfP;(tK}i2#OBUVoPq-fhRO5(&cg0h$YpA^H`;cvJH921W z(Pc90uC4k(-_AKPWlv1<2TH0}`tMe=YrHiF-Tu=JaC4-u61vdhmz&F}i=M&BKexFH zm)c?GT(wP>bfL$#c(`O%+H2dM8?*Xr{api#T+h#bYx76i9UIU^GukA8fb%gjzmrdz z>0ertGuvWxKe*ZxDa4#%ZP+Zo$7{=B95w8otL@H=y$FjDF*Aivn~Q#0X?^8pZ+h#& zCX+8O`^}d6}Q` zIh*473tB{5o?-e((Wo>A%2KN>gQJZnz}!>PF)+^Sx`_!3-P zWf0O8L*$4jbH08ni~BYEW=?sQ#)EW?ZJ~E|ncY?f?~6b7)!+ZUztg$*>wN;h_zB$q zoBUt=%$|J4d*1icUp?Pl{=zhR|DpI*c-lHabUBU(&V)G*=8K3Sa|DgSapd#KN0Q-`q%R$7ao8Ws(t)K-In=hqhCvqvZJgwe6w=%h8nSGK01{>m-Wm~ zKNJ(w?5p3YlTM$Wn>n~fXbb9&-;rA6F8PS`Z{Y|mofuBCh6FUb2RbwVK{_Rgn?QP1 zcXHMLRCI|xf9ryY?3SFaLoe8s($6o z-uE3{{xPbVJabwLoZhFFup*CUGK^70W=jGZvFD4eMVX^Io#+q{uyAZG=3sA%9RaQ! z?lnZ5dBIc-jSp~PS6@pS8Pd@25!TPY&2$mp=88<|bZz-j<8*?vG=0y2?SzFp1UQdi zG{~a=EcB~R`bYnYjnU2A7cK7DTVezekKZ^&2Zxsx09kon`hOMkJhCrDDfpdb;@ZNy zz5uh=3@4d9)UT4sv47Ew_xAbE{6%JrHM_nU9V~_&HeY!HYwuW)^rgsP#}%*UhONE& z+`VqxYyZYie%p8c2k_n3eFDFf3H0^$OUb?GJi!UP{>>l#_0QgX@gvjPuYQ85?M@ zJ?2pDx!4_EoLUd}k3B!-ayjQSTltwO_G~;q8OzrkVdcGQx#)*Sm|I=uhl^#6wzco2 z`V?Yr*1`?m{rVyyb7xNTyPV_*2ZzKMCzG^Uo6CE1*=w8xq!(j0e{#xy(^EBzlce^P zH1o4@4cD%wNKUL;9)JIUEyvDFNyl1$sxAMJ!n-Pj zU%9rocFiF1Gxk7pW(jnyi+;am`uvqYMVX)J_iJJGA0l%)Ze`Bv^9g}&wvBC5r&jkl z@$>|~{FJku!jI?|=ZH}Lp|6?`+KF22%X~?XmS783@bsnVMbn`J|A?}F=Uv~^!78-5 zZu_vN-Oo~K9GdgTYZJs%UzOkKmAv``ROA;yk~yrr*HzkHNVfz-Zx*o z_&xvF5B#A&aIJOU-6!zLC%`M{zU~vy1peOl{>-oY_-oJpyZO%iciczvYD~ayP=Dh` zQTO|e%i%b`#-Sf7BAdMrIqo=ph@QBO@sSpwYA8K9WS2{Ib7ILZ#tD}?sw+PU)_JKh zv1+y!6;dx)tk!@_7@YH=;nt>uerP-MRzA*I=YHFspL#5YVC!Ul%C8@$+BMZ9sMfKq zp2L+C>Bm-Vu9yC^=d+Rh=lpJSGS?C(_v9vH^>WxwVU`b{zjBU#&}mK)%w^}uFEYn4 zS3mNf`ZY{(f$tp%3W?`zAGJOKX8Z6s!v)PMN=;Al;olON3`XpgAa%i7w$V@G`f*;iwA zjid7~;q2Ro^}i&iy2P6L5wPmJ%!8Ttd)A(lNLJmIpZPiJ8szvjK9gAXAjVdn2Ccw2 zZ{{KZZuiJ6(ZV9cSGD2WGmkld6T>s?{D9x~0~`1**&?W}ov&Pg$NZNT>PtnSe5f|s22EYCCd5SA>_t&8VWKHPoHk(p+&v^tyaC3H5; z!C(9F{Lv=S&%XVuzVCbglg)Ft?i2Xr6S)7U)SrAtpBISN-}g8Fp^ra*?T6<{h{o!>mqJrNC zyzVO3LmkS*qeJ;2mzLH$PGx=gnSD~F2bgqiVbT&>+k#BJIwzqMu?k2$Kgm35T8q4Z z)SC(}F1+a<4Flad?iZ*Sbg|I(Z?5L}x$S(o(^v>Po0Ttr^T9`p&85Z6ng0k8m z-`D)*kN%_~rL$E&dP$?EgJaHL_*4yXn>k#`k$we+v3RkIF-n<_i{m|W>^2q^lm}@p z`*ocFF@)|vE)hF*B|loEebHL86Sf+?cmAFWh{3Hn zdEf6JX1D&w^4Ba=%QJ?~|4ZZf=Wu`ICqMWdzr#B2*ZTxMwF&rj_o-p;s3$sscfI!` zU-A6;v%fO!ed!Zz@pHDV9t>@uZ&)1wIS`A{xtYVQ@#Ogc_dSG7>O*&Hy=qim4o%f^l4v=6i7yO> zd}Gws9T~%0IpHw21ew(Z6JPN+^XvynO6+yb>70x)dH=1TKTNC(Hhd z;x3k8*4A^?(ixv|Qo8ruJWDr*p5p+SZNS_*{}L#4U^e%ywo@0j*mCv0uaUDn`T_AM z4Su=St!1oFq+h^2gR{qn`mfrEqpAA6HMgxw&et=7-|#%Y26aDs zI_f8;VySVd&y0uIJ&i$;11F8ata1})RlifHxe!agUEq1zzE1%G8&m$8KcTH(z+-Pc zyFOCtTR(U#{gW;7tG3mj$pbOQnv$0Et6q{${UMS5+FMgS);PQBA>hWBe)b6;zVWo5 zrm$~*d|*hDKUz@PeT%WT-`9S>fR45B2!>OOdXAj`O_3jzm<3Dwc1Td?Cg^==Q&J@E>y!qWu@;nx2U9XH{(a?e~dh z71_JBntRQ!9U!=KBH?VzUJwkQWmb|d z`j|sp@G<7MX43sQ2zNF+^R%kPNM=E$N3#?#4#b|DpFfcNbJ1@F{N^+4W7n^ee#^bEM*h@K(Q=HG5~$j)B4rlT=hh*IOYOyHr20sM?X><$xDDh*Gpd`e880+sh`B^8aU5e_3IOm zKKA@T7)CBTFXv9=@PUl{Rp)&Eef3^*=zaGOhMd*EicigIT=fVzxN5SUjkFaHW`4tG zP7_$a@v3Pm9A;hl%jgx%_}4d~UG@?`Ly3jEi27$U>{qccN+$ z>jJ~Rkh51~&`;Eeo!g8uxn}3@9|)alW^<9x@(@A zDRb_#mA>ZO>W7UQDeq@FK7KbQVf;TkD==EbFPqAqSv6=+OXE=V(0^jdat=K;+gz$o zeBAr>r;SSEN4W1MGQ;GHf|xyRu<>}4&yM=%G6(67I&Qm~!8ebB(}4ggzG8lTdVXgd z+s{>obD`84UQ=4v^oap|7j~bkb##KR@0firo;^}uMDV6Q`_`Zy+ZH_cgbrNN7xwY@ zfBv7vVhwj}6z)FcoJ;2#4XS^gFVd7({nZM^p;x%sb5}pn4{W`Dh{@5*fXJDI=3^AI zdQ4s^9e01IBf~ZAyS0&7-+HZ*C%EfNQS@63w3!b$2@H?Hy58!iPN2F6^oS1}lQrHs zskhLMNhrj(`l5qeHAtf|!LAK?hbZ9*E{yA}_lXk26`1lzt)G;Vg=+uuqz;(tYpq@S z?)uSZ7I$2oNB>bKPoT$bYox(`|GK0LP5+Hg_0B@mH(lfG=#|k(r?70|#H+vnJHK%_ zoaeW&kcI2DXaDcmzvValsXz3i1dvI;9cMP*MIr5cl_L+o9|2a zABtbWNA2T=t51NQ154_JbfnJ#paX?~lXO7Xj$@rL&7Mqr^BZ1$Q1LbZAF4fb_5lkY zUo6`zpS==tFeRDuX&+w66*L;8-(M`u;;@%iZPvxPj?=sP z_tu#4QFHY@n@y|i38KGnANWxftOyK{j?Lgw=E3XwnMX6=#~y&|@Jt%S7ku>Vc_yq` ziT-#NEkAi?8i0vc|E)(+s<$3i)7@k0uX@IpPvFHppxDhN&9A?yApKW9#{pq60A1O{ zR};YT{In~USapjiGHNqFCoq3YbnMhWh@lz31~B(!Vw>Od-t$t*0L|%X@A)|@qUc+H zPOjWjtj`nf+U3=K$i+W#y?(SAg7(awEsMKE+BV8pbU9S;IA(cQU+3tUI!Zlq`~F!v zqgmOpsZ&;)6CI9udP6{{wT`Xggs%1B@h>su&)kxq=OH@ti8|frKUZ~%>Phc3;OBqT zchcfB4tAF7ptq8DJ~4GPsIlkwuJ6}@DAvXOcYdN41M*yl`N9Q{Yv)FMzC3??;9U69O10BB z?(yL^rup{mKWCeKp@LaCGNK@hdw;m4hw}%edh?e;$juRIusrJ z>Sg^~HxZaG*D8WpuVYi2q5{}azxw63Q_n>66T~&yPk8@@J>-jkBroY3Q$r<8hUbSS zZQ~OeS>f#r$dIb|wH0_jXMEt`7jGT$!|C}C3LeV<6-~*K4u$l^aByJzOhr^-u%a^8uEl$5d|(t`GRzfC<qi75S;}+mV0hr`9YW{gOj_ zddl46EGJuR)sbz0rV#xi3~qp0)h9_@QlIMESL}E?hv-O4=2E)n)ZFsx8EG?xC#d+~ zuleS8wT;n#Inebl3B#5@^TP(d z^Qi?YJaAICPT9B=_?6Gz@$4`DPaph_e+1Nh-6!za1nxf=KQ@&o?R)Xs>%aPAe_~qv zrYGIz=X7&EKy+f{VA0_*M}dxn;{eU^u+H3^4{~!3=!3^Nv2{?)VIkklM;|n_zZn+T zx~Qd{4wd-rvj?C*oG67_h{Zd(wp~-4(n0|NbD#}Mb(BV84mJFS!dg-Cg+CeW^hJ&K zSD!FS_xu!>n4=HUkEJtJlxUeigw%#<@dVfO*0-B$K2!~lsH9Z_(o*LnD8=lj=3$eu z>d$7?2L&`sHgSGQH#WIqun+9S{CY|}`G7&P;IQqxZ1Tn+<~4T}z4k|K&mzyCWb%X+ zfqB>`UwmV)*W96nnov-=cWvddWxs9FMkBU3homzR4}0LZ^&t|$?v?tii+($XLoAA$ zO9YfyTJe~|`kfO`(`Q@rf&LOqKw6HwYMLtg;Mw!95Fkq{`BF^9Ry9I?1kAGS`T6yd z`jgWSpMeVBBz(;3)Vl0pRgTJ4XVMoL{?2UfO6GZ4xBHN~RToG*A-5Rm0F`>xy_#D! zlLOEA^sK|FoGGxlCcfW4txBHdP+R%4#ile1`@G}v*C0|WfQ5zBuV0{JM~6vw9o10W zIH)x?2*>F2V|(^N{pwq_NLF2{!thTQ})<9R6ZkOIo5&zK9#wwLW5Su7GwPyq?3O2!Rhi&9=f5}P0oa&AtF1gl4I&{#G{wu(5*?+2B&ef9#L~tg zqpNLg?JC=<*6z|y4}=00kO1;f0g(hvkeI|ORCEZMQY^t&w`G+zKubpkF^A{fE&czUdI8 zdK#ywZV&#MnTih+&e~PO*Fgb+W;gxjMcIfT$Jp{rDd*V9mWRpcR5tV-#W~b0& z-93B=Ngl_B1wl6?5p$uP{F_6-Bu@;6Qhdym*_u~>SYcjWX5O))Sl|(iT6-zm4V$Zpznq$_I9uX^cg&w`*671RS8i!;_{A6PMJF!pMds z${UktnxHTWC-+&H!ZowxOSh6Zun^7PXg&W)POQHStS8>6myY@q)K6iCi}Hj$U2^}a zmU|^GR*ESNFBsv+=h+OJ3AF$aCf2G8=lD?3+`X_Za9~XPF)zvBqS5kl9VR^CXB@&? zpFJ`97}v#Mrz#vw+H9N^Gi~(e-3-=fibJoui5U%ZIS@9`^rl!)^mINe^1vcE% znsd9&^lG_tQ}?x%r|uukV!8pa&5s5ixqsS099zbZ73ch9-P&5biVtm|Xu9x}Zz&YS zHeAv+{ijsTpYqK?dyT}+9$t>kIrXdhGITk1e>f(;dLHdhTVv(9Aff0)uJUT3;|~Gx zxBL~zoa|qQ#39If{b_r9i?{sF*Z-@};C3E!1;z@rXLc0M>)out1y8&Ed(apD&2DD5 zJ6YwW91kzcd}u|lgQrgNiJl6{eR$RxQ8FJmtQLL7gNQypjHHo04;=E~|3Pzf9xVJX z2k6MDbP7+o_nIf1%um?i32P3=_gXoaqz*nY0PMtuJjC0adF_MoJv@(Gs}71E58IQM zVv~QZJ=veXMYN6br#?KiJQ1%Au0-s$w zOmWKEc2W{Un4BXgS_>jfN+@ghUx8>7AL~q@N#qCyrM}&ndO{lS9Q}| zO1V?+G38IG@l!wBb^g-VeKr3AN=_AvDD9t$;hH}MZ~TS}O+GZI7aU~AC|mbhF`)pY zdYWpy!Cx^eHbHFMxCnEU$)@Ze&s`H=5@YV8yznK9ev}m+RTL+_HmR8~J4d-wM_z?F zd49(Dg+JlsDE2QsF+tY&mw&M&4p|w$;>3TzC8jJZEEzir ze7Moz8vj9H{@F*VFtr{9$6r!TBTPR*f38t2*eD%QSAg=M*E^^c>z|iEvSGX!WO5$+rC3J#n!@ znpP)>Uy2SROg~Fla<{+!{kzUO^2Pt?)j#+_OXllbf$8%!*JFn(aN*^*KWn{Q{P+&1 zxKpX+aGM+`%CHW99)?54JS#Z#AuT7xa9~E?57GQHAU+aDEFMVNdnRC-g%3|3ViG$h z^@hS~@P|xaK1N**w)n`t8PEY(-t)@gx$|h4O*gfX6LBk6aKuf}9ZUGl0CN0r`0h^v zGAV9~&)60!T!R?%m&UQQ+{o2FRWEGxg_i_sga@PR-a_t_HhkzSpKbpAUQKm$uuq?r zJYy6VqpD`w^TL|oTF+nf4NKHr}x9`uMqS7xIdVef4x}j-?_I~ z-ih^{i}mq4S9`0w7K`I|f+c>g?!5chJ$K!6?C71Zy5*^qIhgQIc0q^6xcZx4`+yJp z?Kx*3J-&DL-qGdR`*$zSS{+%Oy<8tZ8$f3*SIe`($?L`9fy>3}Q<2ZP@IDt`TF(W~ za{+WNpg5nwocpNS>OMqDnFnWDdk=X8k{M@u|LsQy`w#$jhN}gfO%@x0ofTZ)}8PvXc2iE*tSDwSd*YQT&@Xu$h_&aA7V~cOGMW%k!)nrsp>L z$)Asj+`A3Awq6%q;uUufQpvox-rSf|*Owg)a$lu& ztrM%|mYiT?{#*>P8pqPZQP9L3a+5D*80N-R=f#!f_NSJ^Sluo%DRt@gu2sj8EdZDk zrRI+{WR3+fw`G%OO?x^I{JE7s7d42{>V>uTH49j4KNFLfgN>O%FvvPmq_nbBi@NYR znfNOYE6ruNr-Nh5L$J8G0GNs^$Tn>0`1u2fm04se#UZs^|$ zNWueha-h{AWsDqZ8#eR_Z(=65ilGS?V({nqVbnRpxpbLn!(aW)87Z!`k!*<3_%jz6 zgokS6gFi7elflPOKQba*;%dp7GIvD4c7qb(6xu46yH^(K#QY%Q-j=BC2+~0%m z9)AI&_pFxd_n@YjSMOQu@4sigxA&ei?z!_Xf8^)B<^w>1>28dxue|<&_Z;6l_vq1k z&fVYN$Cu?Je}BC^e*WHic^*ERo`)~e=b^Cpxp=5EW-VP+*U$HF`uwHNT{n4l_zab{=Qd@dyU)b`2gtJ9cu2n!f$)vT&f;KW=%P}2B7c$4Y>v;>J3R- zt-O~H?VL?nHEhEghiXj{*?h;RdtUU3T3IpBeo;AMOflKIezK8atT%d6(bu zkMSY!7m(VSJDA>1rsS8h4jE*aFL^m2{R;~`)I5B+`T)}bKm`s0$!Vgsk3f3>d(>N4 zc*bRP!Wha}i5Cp-X)i0sGi{g{rvNnZ(l8Pq_U73fZ3BI?AJ*}2A3Ta9IawDR$EPeG z@>(336hHdeNOQvtKLTrQ^cF*$d>qq+qv^b@JvN19eJN9)!H+xyE0dGgFFf@XPQbM1 z!hTIp$wBc;#!v_IVv+L9xTdsotys|^p2Id>(a>J1t@yyhkKB(5@9?QY9OUnCRiD@| z79RrMpCH$F1N>dEdG~s`d^hUzC-}wN|Mu#epEmziIn?SzuiKI~qEdR~Njo{#(LKSiL&p+6Jj{caDi?z!)jpZ`2dWWf86`{#!V zd)x8K!4KF(=U$qv=Z98X_dmw+Q4IP6pdO`q-t61r7dE9D2y9wh1d?36j+y4y$scQM^!^gx#0ajWwBO_pKl&?Y#?t!a{0T=p5Ax<9 z4*%=-g3?wSZfe-qF^V=c<{@B`Ayx~5y?8^wd?im@b4kxqh}a5+5^x&){A5);IoO6* zLKB&@!bBh`gpqTGF13!=DC_)3JNpHv@vfi>cOZY(ON`8`v8*}_KEbp#BUOC)Z{}@1 z`MWN%mQem86qz@vUlpJLy1$>*oPLG3Qs!@OwW1DwJ-eF-p$v;T!DHd4s!{1H}{7<^|nioua<8^ zhM%&#S?*e9J`wz4`j9+mOXymRuK?NsSDYRHDPVk8rRr$AKM$2EHlX8&#(LM zXqWi5H!t{U%Zp@)3 zz{3Q?b^{J$MlsffQ7sS+WS0w1fs$wOs5rvnWyr6`hoZPB7Kv~0+`OlXM;k75 zsr7(A){jOqBY!SbuBw^#UJDL;KA&{Z6N@!X>}P*j7w7&#(Hz2+J{s%wT>$t`K=_Z# z%}Sj;1H;KTI~ysbTpH*~`xxErgEekAdp>rF4kj-=93E zHOt)(u<+$?{P?u~<0KphF=bExwzl6Eud>mEOZ;R#8-e;J~%w>B`G=Kk^qAFEELGjBnTSufR1SHRc%po@H4% z2T$Mwe*7u#>xvJ%o|imDj&}S>{D#Z;luBVPP@9+RP#4*_BD^2|%cKfPV0Pzy%JWk6 zD~<>ew#B8l1`QRy>VFbBdFU@@giZ5zar?e#CF{H|rded^ziEfPs1$JSpA3`C=vQ_X zzRZ$84CB+{Kn7z!&jjM0W>cT-`nmpPz2!e8?Yx*};uDWCGPEZT6F80o=4-z2+86$O z^z(f9Dv(F{@MXTk8Grg$y!V_3oVogSr1+Q}PIG5d^GoT*-PdT_-0;v~=HptG;9=t4 zA3K@J&WD5iCWmG}9N_7LL5Gq69m`IRnK-2ue&Kn|Yw_bUm}S#=0aLIH&&6g=Nre8j z<|NZ{@j9`lwaUQ{)8bwF0^l@>Tl`YM#*=38krN#gb7QT#!RQ3_qqQMMuG_MFc%%JT zKl(Oa*a&N=AFZQL&E!wv$ccs+@=xB{o7bk!cD6T^^B>pr*@}gj9OlGDj~Ptow|F14 z@5Bd{ckV4$@7!M;`-9KCG*s()Ev(u_&`$$1YiX zICI2tQtKfP+7wi;f4B#LGY0Dlqn5v9uvIJva#*f6hh?EvAz?J;;`=9jx_2YlS(LhQ z$1(M{PjQj|2A7g04Jrc*UJ9Z~4QZ!-caCK*k^d@%f znoY?Hmy>+8&zk(1K=yr3U5RUx;z|%MHJ2<~C@7wCDTh{@@k0*$@k93TV<4^#&9$rN zfHdgXg$hZ<)GG1ogWO<``Oi8p=uf2Gen4o3xE zc;)qH-Lrq!#p~7nKg3^r_W1RB|M7Sh9`5JN&lM}d{rEiQ`I8tI>PH{H3)I%TLIIyU z8I_D`jXb|J!FqiDX$l{@O=1>S;0W(E{bN7+NLx5Ws+fh#$rhf8;rFDlvZsQvx@+{T7{ zlFMkn$rCzZSl-}NOY5kiO?c6!?=8=Li7w}2e9X5B06^}&bDs3`ec}qEY`o_4Lu-!C zGm<7RqQc#Z5o`NvCqsJ&H&)*J9_P8N0?1+F-*7s1QBjh97X4hZPSPbG?5hx!+xYwf zSlG1bTnu1U*lQ*h9pZxp!!Z8npWnGuhV}tysMx>61a7cCFWY{(xZ~J8N1kxm&-~E4 zXf}_-RRKO`^Vp#ZTyXjAFUHINoja7$Axeb@sXBkl*)M1vo;n=Hwax^d4mcb9C568& zQHKQ(=&LiT^g0E6j>v{O+W63xqwN`8K3s5wSFPpBzxF53J#uva zad=*yfWIaC@pyI~iwEq%dX{8c&urFuDE)5udGm9d=alaQ2jAE9pN0ih^C1pLy*GT0 z^=?CyXT4qk`Q%;DzvEm!k(p9{TWjKzg8~FbyB422JBp`xQz`z=DO>X5*m~D2Y&q2e zhK9Dk>mY#P9XF&16hFrFp0=u=t{ZZhRE}70agr)}8JJe%>K>;$3^L{|zLAlG-Mhc| zcYji0@Pbj|002M$NklfF!j;~iwyzC7x_)CiBad;}w-=o8m^Uh|9FU8-1MsUr}CU__k^MRCeOy>z3 zcjfLqHZmxA)@?3pHGJ5z)B6vJ>3|@VETHGY*2a=)^l+puGOlcEpB$c%5x3dM5%xLQ zO4sAjl*qs;08Lw*1SC6Sm$6upN7#iM=kAmG6@X&3|EoO4$9k-7xQsIL9l7RkHY4Xk zIW*@QRRZOP+Xk=^PnFcDZj=D$A%f=ZQ_#v&pz<@q<@6 zj`7#)4RdO^%~1xh80Klg;4U^!IU}G zmlW(=OY(+=c+Mc9b}QRITtUr|utO zRyfv>2ho|7`1O0*#8l%a`9~%y4aoW~>+#wbY#=Cq0SLyIDnRh{Oet^C0j(iO#h;dh zIn##O7SGI;4|N)f#Z|}jJ+Xfwy#2#>FTU)FH@pb{9{oHHO9k=-9F}}{HqQ$#zvHR+ z@^cdsmcZd8B^2?EJNE#R(k^s{yZw`Fd5GmVdxksJc3kQ ziD7Uu8=s-u@p(LV{)9Alq6e?eFoyGo2G|&Hx~ILoOHT~q(Ka6gB!gAm3+DQD}?-2G@D;rtX8+)v-peOyWxhX<5!92 zF<0OdUx91B?#IsCKf3xdJf}~>KO^;Jcz7@1yTE6a&w2ZhU3)#-(B`?`dVWO_{%|G+ z?fu|eHr-R@w?eNUmg)g{=Y*9@y&vK>#%B!L=pr<)fh=gtX_0-;t8D07W=$t91HXqzy+3rkYfIs3eA3P_p9%qd2>+-CY5wEWCDK9JK=yqY_ZP6G+gG?-+`G z)1xo?@4jw1|HY52+MyWrR33_4cQ(ljuekj&c*psdZJo2TNgjg4)a%i$IkQ>2bJrO_ zd5Jht&Er$eJvz$S;OQ;f@+o=FAZ~?WXZf@b81SdvsLxsl2xHNo7lzV>G6*EI{=hYz z3ZI@F<8N7{drm^FxvzL!S*D=1wmv%Q^MRuK7cJt*VBjzJ!ixj>2@uzj>#&X~qiK|m z^63YeibXl&3%T$iGtQ8Y0R%a6wHy4Ze%#wv0h_(uX*cK}nUmTV0qwN@@1$$91m$|a6kCF|d)nQv>omy9LNwoeCnf3zhB`}9pHB0lv= z@c+EQ)VmIiAFy|1L-q_)rdIwP>849nf3GmsG@Mh=1N)-)ZXn{WG5qM8*_Ru{@L#$F zWpU~WekQ%X-*GO`_;`iOxVSgFG0`w5n&U&^veNrsV%n*bp2E-pKz_Ww$nSL1+KWD! z0dSvc%*S!{#d?~f#q6p`e8%NJVfw5J+Ibe5!Lky9MD4!jI?2VT4Tg~SYFnIdxAYJ% zieU2yt2KSLcI+Hp)aN=v%45d1n=xHgKh7{3jB|0R^!B)veagyx&3?#>Pyai^=8jydjJSUD z+UD!~zT%Xj_FtahtNd{Oi;?T$#l83znWB z3ya33-<7U$LcHMVH?oILVLc{+0RKn%v(&tsI@XPRpMIY6;3+7W3?`Rg~!fA zrIZDZc~L>FHTfX3?mEIB--B;ak!%iM$|xc5zh;(*CGG~pP)M1}lTxYHhTnQYUD536 z0J^mB`~i7BE(GBMe@wz{0~nm+5?vb~AiB~!#Lx9yM`Ij5wSg0?p@_e(zOlB4_qMe* zHoxPD%w!9VHP5;1M(Wm7Y3=tvbUPBFPX+-^@WR|jlxleWGNG9O?$QFDB(t>;b-7vP z$d~ag7X>Y(G>|TK@l`Y0a@;|E_v2ykyWo?yYcrO!?v`ceHw;s>%5xeEk>ZJxcDcds zUqEz4VJ=9KD&R3nCpHv2canG_=fLoXc1P_sA1Z?}?rzcBbO(L_n;e4Y)PVh7&|ye3 z)?%%{oc~~<<~OaR#wwv<=bK&=pikCMPE?yeLeT6gd3>l^g$U-9RoYCt2GyqH{BCl@ zo3SFzc4A^B?zc`4q)fp1QT^Q@!wCi1o~!^}Dq{-)-J3CPgHK#zB-2;qDq0bNmj32W zoc6Dg)KOj;V-y9-KVv>ps%jCq*PhQhZyAh(zA1c0$ znu>k<5|>rt?+`tCixh=hcO=ic1S$#X;)flFB(T`-zt(?^sUKh$NT=q+#<1hLMt@_9 zZ10B8$u+YBxb>O)UHd2^z1{&fwY(NUm!iY7SCp3}T@7?b75U&hyY*YTK zrOx&6jir4}FsPWhrywMR2NEfN=K8XqsI-Sw%Fjx)XZ%t9OX{=!`dVVt7H+SudR^-8 zZg6^^4$XG>aiEmR=PM=DbIzk@3$WO>RdIer>Z+t5ZehCG8oNab_x(ekKnOa$JCvkv z^EKK7xhz!c6Bo4}>rja+q1ye#%};?qt?fj|d}sDeu|2ylCO@XHwKAxLR;_6BTy)h) zRh{qNhHkwqHqt?=eA*;S1v3r^SzCubuYv0MW8|mstb9g%9i>HcgDPdv_=&UOpk5Nh z)Y6x}J>H0}+3Fswi|KpTmzLw!(hNAh&RfV)>m96V~8o!dM* zjXYMO8sO|L={uXlBfS68+iMu-Wo&&XE?NW2)v8+u*0hKn?z|XY>5ED~Svyf2+(cI0 zU~$>X9by3`H)+K=fCdqK$nvHjTyYNw6r$It++2k6qr)|LDmN4wqGHJLyqqtNNMxOP zl!UZ6K~vxCzQu}rI~WOumD~o9BeQ~-pSiI4Dm~9_OlNLlxOkVq|DpeZ9dwuqiD4n1f;bLhNE3X{4SX=p#vNnVvJ@fx96Nc_)s;N&qXyL{tS z=gEOonA*O;*yIHP3Q-5xTLg37V~srALtXdU-!10X*$0)+A9juQ#{3QI?pKJ9aFSwM z+1PoyeQecoje2pDye10*Z~^Wagf`t}@V~aLrfg#&!bf%o@L7mVHk7K{7on-;GECamo z!=61g4#Ni8YB`HCgH#47WfLtY8+0}sa@Z>uMJj(=^SwOK+ew}O=YLVW^TWC6Mf(py39ucPJ^Eh`Y(V zLQirp(>RAx?(G-=L9uS^>A=6$w7mD+lYkv1eux{AzUIzmnI80aFBhXCVCr?B}le&Nw`Rok_}Ve6;T^cII9d z@jUodmaTm~>aw&KNzT)J_Xgx5aQwyNK{vp|o1x$fHjnSp>3WR6AnHrl^hvv)YqLzk zl}RobM`~uHOs*T3j)ZR5_Yd?V4?X5#z*24Edt8=IEG4>M1mKjVdAvkn5(>~rH#)W1 zgY^rXxms2n60I0VI+vlp>?eKXeQx*?Z|`@&UkDZzTo>nBl1QYrxV0yL<)>6 z`!q6E=cy#J^eo_2__=IM?D0!bYUdOZxm>|r1L?x+HH==>Dw8rVd6g(5K3#iLTahRB z%w{9ZVwFI`sgbyAf+qD@_Ha|$0Eevk6v1#6F2?RWBHAJ#cyo}7(e$P7WqBKH2^JIiX zhUtaJ*E;y{8>B0TWPnv}P1)8j08#MaGmAP3R^(HXcI_`-s<0OYNsL<*i9EQZc{g|< zc>Wa_bn}tKYY8r@Q^VFG3J*RtHkM2$^Us4SJ5Kvk<1wr*vXGdVKF$AQnamnVzUFz& z&(WoQ9b&ZQ4n}}g{9B%PKhlDfT*!5$7}<0E_@t-XU4D7Ep7OF%+a4K~bh{2kBew>{ zzujb(Hbs`s%QH^kKqkY^)*M{kyaerb0NLzPcn67?>rBM($Qd91`p+y{PIUjfKK1_8 zQifv1ypK~FkJe&wsBmE%c6<(O$FUMy`z1DZc3V;L(#Ds1?Qsz{%*QG9wC-{B0zf>l zli5Y}KE`fO39hVPBwhQB{m_r4^Mo9b<8(w=;j+l@LBA6c-v&f)Nbz1!$h@b=CA0t6 ze~kQ!b1CBH1v>Di!^?2-y?1tz({l+7EMVul+0dxgS^EPAwVeg8^guHVlN`aVfrehv z;Vgxni~@M{`mW)kdhMsxE$y&q2v_H%Myl7&?f0O@b;{+eG^n_$6YB2r(3^09;ck# ziuk-36L%oE_p?N_^vef;HY3loXZ%xNZ-+2WeC~!QA1c2F;)Pjf-}uAYs^gdM7E!KBRa^X?&9@g-CxQ5EzylN_e!90d3n&Pm@x z`?fE}paQsqRo&vojT5M%UX}30MAWO7?%0xY{_i4QXYy1+@Lc%%(YR>YS=gQcXNlT? zzmen)pz8Ex)O!%z4SOAWaO}Ucq$yaTj>5k<8`~Bt?6CAd1ZOm%HeBqnXL+TF4#r-b zTVUV3E}pNBbJ-qjxh>Dt0vX81GR*N43=#YQlHkr23L_OZF7 z_niA~zCzO3+z&w{Ds>5==#UzvNa4J2(%nc*hFqQWF80HAp-<+2z0Fk_Q$+}v*>`Pw zpmBe^_1NJ2{M&*z&bpWF`3XoT67vfVx$+JWAz7C zbN`f&I@s+NZQT4*DiCE%75HHLR;b@|5u>9B${V)}F5ZK-nG|n- z=Zn#~76uT%7I7_i+s#|nNb3F+4QyUpI0ed}PoRg4?qpXItr)00O!e*uicfIxZSqGm z`Z?um!8@DE3iy8>2DM;>Ggtk3=NU07AGKh6u)}%3)g@bb2#+>HR>q~?8cx~Ic&R?y zd(B4eCS`gfWi5ErS0@}JyoFKc`ys!d$$_Rgsgj9mez?~TI(v2;!#nrX1Zl~L!o30a z8wNIEfLrn53xfVT)0F+O{>;enIl)T|x#zhmI-3x%4wys z!J@!NG5?7f8n>y=zk+hf_4k1oK@u6Cr{)e!hP83cOhr$81lc6NJa*JL?9|?* zxey<#u4E`Cbu$1`fReHbzMG2QvYwI2r1t(DEVztNCYc}4c;Qj_Q3maU@ubL+o)o$3 z1*&J3H!f4|z1>`_&pK+zrmdZ}+TcRD2LKDaN1UdwKg_Yx6HG~sKh2`!y3R(TUfT_U zZe*`$?xrthJ71>cDd5cLP|&La#^a(tM)CN0z2E-C#r1E7`x)>Ie4{hWEbsC!(~LAz zCc?zJH=bfXX=#jj`}{ke`dD?wg=o@ySuk&OkMlUb@u3)V`7f?`lX(j8Y~@XfVzrKrBAzEL zs+I@&Aw6)wcp)Js1YgwTh08yVttZv-fX=jVD+tU562}AMgso40k_!de&@-&x+TC5#Xe!LR^`%6>BSP;fS*J+OJ{GI zs7W$QE$|5skF%}ANc}Fzn`3kWT~nfM7@_sKt=Bmt(B4Q4-sPN4QF?I`@~q08G})>= zMQ;@mZpNR>2{_gFo#}OSUPUJ2qRjR4Z?;1XOx zZ!T+qqLuG3QJFc_K>Tr$rit@ePUa{gXxY@cWx~@T{PdP6y^ROX3Yl%p5_LZgWaf_L zmW4n58jKkhpyuwICi#P<K$J^${q%YqdK+%*H&^N{`)58`5yp3A2Y6sN4HQ4<4x@F~d%pxY^YuaN)^HVN zHRU_>{RDXUhM(4FtT57;iM8$bg{^(cYKB28R8jmA9W{p*X}JxI{*wZo96c~&nX}p& zU&y0@5q!&uq&Fj_H9na%+;^5mC3l8KH%*jW-;CYM%XDt#JI6&s1;0zt0a^wST%Mv& z77Gkh6y!qnzonSLFlvbxzkqqjgeBy%gq&+2`el-!&{q*UjviZvV(z z8S~#mD=DKuT$M`?K9{$$y}NWC!xF50dnC;?YapN6;Gq%}*z8bQzU}I>Z%y8svjL7C ze~~j96rJ3^l+o^J>O$oB{9(}uiuNmNEs4VSQqc8`%q9l)%GSSl^yq&6c)LREcmNX! z>L#KD4d`FHm3J5hPUm6hJ-NA#-~fqx&g4jhLpSXh@M9kkS0g&_;p;iz2bvt?g$$w_ zf#(-|Mt4MJ5u{`~xT0{=a3U({HKEiNDi~Jec=~j`Km6GX#WM0e(Dc($E+_c~xPg=* z#V>eQZlo@g2{pB7PXwFSN*v<=H3f}V>UM?O*+ZWr1)a!qf8y!bP!Zt%xuTTsPPT}& zK(hW3S3BTm^J&9v!mCxv@j(2|kKnz^mD>N)y33P0*d|7X`jnU*;Kr?m8YtH2laqpJ ze5r!NvsS<<{>Tp152rHa$|KK)R4>g)?UxL;KT*VdxXT6P&3SxDb_GxVA@%YVoo7)6 zErD%)U!V}%k%$5#=hI<-q&Zd1*-SYaxWp^tCq?7NuVoNUE!2Ty%&ebtBe}A{?98D1 z{=JXz+0t@Av{awIUjN=dP26a8cE#F;p|>&AE@Lv4&V@VKg<@`w!C}OD&>m2FJjvge z_h>TaT%ZN>IG>;$;F;u(JRlIGIcAa_~zfFeQ+N4<7;eMz#W z=SG|Y8)Ay`M!x;f-+ovChlO}gNjvV4t9Q=gh;hu|dr*gP#J6Rz zX7_|kgXSD5hn=h#Lzz?~AP|KcQx@ynnOosA*&k*K4;d9<+okORRfGJixY_+qq?V-FGCOsd}6%b?~?BA$wh! zPE7YhIIfpOxT&)In{L^165J{Zt^UO2?-^a6RGPTv&N0R~$Mj2qF5A(uK46RgPMT3k zhp>A49y<6c8^JkW(%Z1fTkQS*%FIB_3*|)nhbXg{aR_oNe|ppZB~6Uy6TK;i~1~`CRb1jhF&FG!!ZGlLNIuV-Rjgd>2G$n#m~O2>&`|GEk>H z8uAziv9V~EM#X{Jr368Kac2I(Wb~{he!Cx`p$gz)w6G!4eaavl<6;b55LK6Yl-4*h2ODOBoiP-2;q6j*J5-O7k}r7Sy(cI0$>G2KKZhM zN?Dul9(^Ga!p_K-mbP$H<)S(gu}}1o#;1;Ro0hSIoq&ZUhl_hH&&fDBKzPx6-vO(k zI%3PwH@O*j6Dsmm@$Zwvt!L&hJ^UwCx4Y9W`O`AcD2DYH26|S^4R5EtqrSk&hrNv> zOhBFLSLB|TZ&9x+^-(l*KkqF>i>){?JzA&;-e=(&ruhMf-;pK^suvh+E>s?da5Ep7IE2;jCH1?g zNIgy*9Midrt4IK_pQ9cCWf)wjt=AcUINN7BI{Hk@mHvJI`GebJTRuO*(=plz&QhCo zVIJQd^EQ79!CXTt_0vo)LcI^~qAS5*TPLHir&3K2Wt}I=qc=zF4jdhx#i zdqEG3L4D=ly9SdYFFv8%AGJYIG4_TftTh0E#GkXF-h_+RvoHmjEbTvups|60t2_nJ zULrp&+O`?9|K58Yu~6_Gspv9)Ovk}AEVh-f0xf*`r;c%gx&=>zxI6)K)K>qJsSwZ7 zM_qF(<^@$o5PAYH!W>sOg@Qolsq7fVR4S47JIB1~uwsW=Ym-9%Zc9bVSrj8kO*hFP z|9}+gPu;Iqpq(8AaEey{d&G%rfvM!U`#N$!ennqXKBvr=oWzC7PBhn72=wA0HP#oQ zY~0UxE#}9O{pkHLv1*hLgpSWCpsw0LuwyH>*yUBO{gHak4a{I((=>aV9nyM`UbPzA zlGh;Sgj&YJ%Hpb@hJ_K%A2Xv>;N9s)PR*);p^`6JbTyrOgyhlN%VQRPO52NW8QtJq zPg^IBv1szaMtPaq!Mf!S`xzfv7h|QGk^r@*LEvvu^olODV&qrGGZoiXw~uIW5FF}_ z>V>#Yo1rospKvDM(~!w>Tr@j8{B^lI6Mx{kDF6k^+@$^~*-z-jdr*fLNV84_`AF22 zp`6OJ!vyM@j;3qhqqGFgc}s=A$Aqs67BLg!+^T*N7&1WBFZo7)EW2NgDR~JBZ<>YJ ziPgQKd>0Eh>3W5!Yj|y$+Z*T~O$a|_(QaYYJH_wv>Z!M>N2HSX>!UD=sFZR{jKRvPH)tSd$}IIECcZtI`Z3-PT6RtDBFCt!6~nwmcP{pS0fVJbFp~kdF@B1RcX8eCcynolcB#cx7Jf{EbI0Gs`K4-C z;KVF)K;;%>&^pJwsYRq{ru}aQ58qMGXVqEzSBDjDMh1W+JpkdmJdP$C0cTQ7*0^_6 z>rREa+_Vv&Zjn=1HO{&u08#xrQpVQ3s+{oj!(`7P0y&|yoN56wr#}| zeUJ(8zaB+}qi&|2)QtZ*=eqC>LA$p;9_MZQAlWcj@_{IjP42D_^Fh7cut;>MuT5e8 zuK(T92-WLqK$U}TZr8tTtiY{Mg4p2bf&8P&m_lC&c_x zRO?WE2*BUXDBJVXbLUiAxR(iIVHZT&I50yVpU)Mr@6O0xpgv>q!$2?fEp@#a!}J5e z-wO+7V{}gVpXmFt&90eyUJuCCuRv;v@}vHdN2}X7dbh#l`-;QCbSVPfXGQ&=&s5l2 zJdhNiHu&~#swMC)To1JY7k?EZ)4PxC$=dqhk^1DvXgRf+Ki*Au7w;Eu2;%IJAMWlC zdr4cj+-f{>)IXYkmmBR$MAHuKWX_}CTj}NvvyMq83p)FyS5@QDNT(7~)E)dXiIdoZ z&mPjODupRfIiD7t!l#_MlGNL}NFHQjx^kD)+ddm=O61u2J1e=zPO9)!WN)x7%5K~U zBLQ$+01^l~!*#3{cbWqQ*JeFt=bn3aKXIFg#*zKu|L|fi*k>QH%l#e*vVudec_$(5 z_|i$>Egy9WYm^D;-ChRU+4P^PB6L>f{orw&rrjLWC##vSa6en~J&>K>@ z^R&JJlGj9>V|(MNLGJLM-6-lsSaP{Jz4z~FMu6=fXp*K0W3DKV4&zZNJ5My^5}V7u z{TpzXmrW6R)jlf=dPk-od^3!E2&d2~!-tZ1H*WXrU_VC$}C*{=gC4IWB)Z9`> z{LLlm0Xl!@k#_Y&Cc`e$jTBiX^eUo<90upD8uzqCfTn`t zsIjMk`|t8PzJlCkt;}7N^+VLGld=_c&o8s?zW6Q)Ev)Bj)fVpui{LH^AJx})Y-JLe zi8eT?3BAXjrxDmNSuWSsc`+ z;D>|L2{4!22=AZH14&C7?)Q4e8__zZk;9M~%T@0mUP=lU4;4EH-!v7C{YZ{C1XJ3x z-?m%n^OB)GcZp99f2p_3mjas}6puz2{4<}1#pkxo7DpE3eie%yQhbL6_z=vLhbWb)ai}=zSH|-)~TmSt? zXa1Mxhlk7XWu*Z*jiV_F%fYU7s_N)6E3gc zCU@&h@vI!rSW$o2^uL#c+FoXZpIMzs{I3d*O)r;yhHpvWUFi%i1i8b$?f>}{@;&)P z^#hM9;F}5zlGV`mUgXvx^v{#q!sMB|;&__lq1^yt`09H>(!OBcq0Qt2>;3F;pFh=? zb8ydE)VXH@?z&4u`c*^Q`yb*^3K9>~$7_!>7XR1uzQ_(vWQI7}r1^q7KtYkGI?p(A ztX~`k&l9Lj-Cl>2ZoDebRjEhU_!Zvo=hSPru%!Z$7e&PZ@3-52$UgNVVx7YOYVv$g zjTy@e7m_wpwQx=~#G!ruVxEexq3SKB?w~bw(Uo8k#sTsPB8Td~(Z$SGdFe)tu->UU zWWY$kol3eN1Bg-VWzGJ$>?J`JB_niC_YYr`$$|vbG=H|J9K$*7LG1P&4d+kUe)g;- zXlW-C;*Yq=RkST+Es`ESj8wTXPKEnl^l?c<4~|v}T81l57A|g?a_>HDWE)gAJ|FpJ zmX#EWdn(4}Cgu{Sk}s2K>p<%wD2%A*Nl9UB?MYwjV*$6%J*VtrV|X6T-&jA!_|K|x z)VfslrW}ZJnOY64`}_)73@dv)-mO``asi{1lm0t6d-%T2oEOu2Z3owPdi@Ykov{=> z0>5ljx?ExPETZ-hN9 zu{QqrjBeojb2i&{2>7#b*V5# z*RsVi5+%v!#37$;PC>!E9WGPGeO8CpC?CCKZaFq2J&rDyyyT(7^-zP{@^#pUeJphi zbOt&%v7YzKhk;%BUyXEd<&78oFFM!c-4rSG>5=XQsxc8oS(0WF_5fFT20x>}8?z)F z7%BiRO(MTuAw5fFK%WiV06vEK8pf$>&=16*K~qp{*818}$J-Y4Mc4OWS&U-5kydZh zV6-byV`5|dl`#lqU=RPB^CLO6qN8GP4 ztU%JU2jE5?koL8}r-FI#okqQnoEl$lv+Unb-c#zqPj+BmJ{fZv|d3qP?}h8e?sZp#54T9(mJfmaQ_k_`=_dexB^^C^Fu2$}-#hZ>XPPCzc2DnN_cirg>u zU^B%X7ymN8_PD2xdg-ko#l;5KBIp=fhrPy{GFvf@YJNnY#rjh}hM_f#E@2i!N|Wkj zj8vCYr6j&$Qa1ZKy-n{quqh9PRu>q-l;;6xlWBJ|A}c+-nXS9VjRrOkLsVjJih?mG zr8V9gIt#n`tF`_+=4gvn;p?cKnvGJ6PcE~RNEWuYlH zw%J{+W8vz?;;eNn)l{~xfWtlRkH`Ju(fa$E(H=-;_)BC4``$(}TMX1njjphDeP;?0 zACyW|hh1)pzv|3Q9T}-6yLA@JRd?9>%QRVz(IdGsyz6(nGi&jk-MnAlMQa7qkSvwFOr1#D~G@(@_M;Pu(%Zq@?cYb&$*uNbX{Z#qYk5A3;PbeN$vZtGF-GWr&$c- z*~AECG(ddgEbJl&Z+*A0(blL6r-Mcn#o736k)TxPrL;0q!|M*(fQU6+DPTU?FeF_KztslSi?nvL& zS5wYO35W8=E?QA*e7nN;&c88laLcvA2fjSXC^BDg| zMV8{v-`(vB`=DctUv8G7^+I-}*CG%U@plH7>^*!{eq8NaE){-@75ZX8^*sEGx$$k8 zr_?9CmAg-^q^J`aLh&#OY)(GE%auWHmu^vua(3>vB-@$9-)S$;Pf!#b#S-MU_Ds3Ek1;tReUHymRRS4KB-buGWZ=D(|bAQ+}oZEShb7- zR@-f@4dJua$ci**-ALv# zuhm3p?%fa#DIt2t@5<&D)GkM7 zZ$$ZaNo+#;L*y~@=SxYBN5uR_J!#A;OAX4RQXfA&>V7IrX;2ZZ5`k~CK(}5~8ox3m zP`+^hG=pv?!MCB5>u>F^cN2U{FvYIh=qbd)WXgyN*s!dINo`xq#D}C-yQFta!VFPLG;>$1bA-mnSilQyo3vm zJ!{9mlV{h1Hr7rn`r6`ix@Xz-)*ZS(_Cqb^ZY;XB~;u&3%-O`e5puA;k2EQ*$1?V3J~2&8c*o8_38N0_;B6s z7I@n~G{Co*Ssz;NKV{S~LtQ5V$hHY(2#-#MWFYb!5((nVeMqB#v?)kPH-V9p6CJ_7 z-1ibLIx-pq7|#1PKx6TfWNP63(Ad(JEtXNlyY|c5_Q&P%M_+M9yT&XlmGOON93PLN9s|gtB_(Qqik$G6N2C6n-$`oM5GOjGGM)Wz*w z0b4yyM+(t4Ow4_t>wV+LtAYtrh5|Kg&*gn5#?Ko+E80(MUiu8L%N63T;%@Gd7C2(B z*!WrHi9dea2@FXje($DzryqUeePpW*vJt3ro2VMxUpE*5w2-y##nzwXt7hEz4gKt% z6%x6x4$)W5r0omNOP=$vzhh;&RaG;Dhex4(R&YQHu&3TeErME%l2x||*rl+3*z#;v zNLW#)r&;eltTySNL%yL7Cifw?p^*TVhW~08PpVTb#yrpqv9`7bmLWh~|F~H}WwnX6 zIkxq|I#CNBCQ&n7v$(4y@y<7A;FI-P<>CG_qqjaGN+99qr&6!<_~2?&Uty6Lb@X8^ z5hQ`s@y@tqZhn8+zl=$m;z0&2)}Ys=Ofj=Dl38H4x6kBSmpI^&k>BW*-7)_0c@vB* zIy-;%wO2(m{DX^?Sa8{Te(QnUi#QJG-0%81{&}{9!^@u#UEQ<4kJJzYtZaDmPIQ56;8n# z^cKJrkKboi;m3sC0A!glE$Gf*K>*+K7C73ZL2k51L`_$szU+>bad9u6z{~!NQeaaP zz6@hE-J#M5W~0T9U(cq!OWKpVikUZ07as2#TcV(w&jR!(U?3S?3lTd1|b zaEn#UFusb@r)-L7=y(k#sRohl8RQM5wt01UDd-?Rg%9Ik) zq~VsN{^Vq%o6uy03Dej#!Vn#RelHkdL;~uh@PMy-DwnU_O6|kX?67^QbZG6zWC-)a zv?O0m*gYx__Zj)ouhzpRJ60*5#40p5%f}tQPjH!B>jp8b*CMqycFvzh`JLF~N^PZ6 zE8r#$QI2Ke{%NCSy$ya z;lPP0b~)s#iDY%zz#q_l;>&p@xrID^0?Mrx=U!74UtZjqjU86~Kx);xC_NnYUxkgb zT{k~ID}L3BcbBQY)%oKX&L2I7{YngHsfqlXDeHm4CRp&J)U$$XRyY&Y7&TaP5(fVp zVj98e!H_O6?NrO1>B;Oc61=Hv^IjFIemz7)>i(P^ttQxUK~i+fA?yA%U)IQJU;AJ4MdgDKo(CE~lj*67_fDmzy2(7XFSe zg5-jOQFj?R$cz$v<)cIO)P)sLoUI$&$uIz_^j(kZH@?|R9s&7AzG(u7*$*Q|ObWWe z_qF#j_I1XqzgV0|N6pFuI46KoqCd`NTNkr!%6O!P>|V9(Rmj@_nWiD^PY5$M`tnUA zMvbZI808dtPMy=Hu9*j+BPC)?9QP>=y6;WvH%97MO!R~(eiE6|zX5h)w_iG2#?ThF zD@*EZYv#CaK_R2}t+!8*O33|xDd~>CMIGto-eb8J4zGYg?obvmzm(tt9sfds`1PNx zQNQEmvnC6>UU^i$gWoFOOGFrQbhQU4aetD*DDbL0s;ZD`OY$j^?F7v}OFbgb@CN%s z3O^+ue{h7+E*lJazOjQ1IkVXRJua*!!F9Wkt}v3Ga^;QW@+L=6T3zbHbosjA8ekb` z{(eSa=|_kBccHIbK-bA*&`nEeO2xe;wyWe*JYfsn<6pfsWJirm^#+&|d15r?GIn~d z6)1srcr=#!vCgnwxv;pq#o>K`{W9=tX;`S5_M!0f^26KsaDijbUG#4*s8m6i#@|kT zA{EXfK~L9kM(kc?##d>|h*Zm?SMiN=p;f5Q3@TYveyX%210rH%%eWleA~!+A?u>@# z=Ku%TZ(PVlS%u$%5w@86fA1JESBl0<>Tw^5hh37~k6Gi!>Gj3efj)#4O_koY+7%(_ zpKUvK$Fo$?`z9bwbt#R8RqDxvk#`}5J2Ngj@M{Totxj+izE0Jvr%J`n>@?-ET3euD znoL0itmCI~tt}j$N0mb}vlNp=)Yy7p2{H*rllm}Q+YIvHm3H}QxwsgHmoh7>PKBF^ z;wD{H%)?2>-QDsew2JJ^V<}RdPtS6RhSc?H^QRI(UM-D%yW@JYOP*=AeCbA5Tee^r zy(#JWzbt^yTG;V$Um#}^Ueo6~U0a+yV7POv@VQ=A-MGo1B+;i^a<2cG9Eb ztDl^KwKE*a{?sJ!+?Z&%?`m4fSwQXImZ75+Tft=P*RuwT3nuj59dD=8t%0MKOG=xz zWSRdP9g_+)6#ViMS7rnCfxex#?FxN5YqWf6oXW2w?B8Oc=Par&>(1ofoWnG}Cf|`& zCO4Df*Yp@+z4Nv{F>U7y_jhRqV#&Y#HFgL-<+z-!)TU-I&rSd2KY5dy{9BCCTc8}4 zSZyw%1P?-X)%@ZVUE*iBa2%xR*cwk2vO3y?`Z=(B;pK{HbENN$XZmq|HIdg98|8v? z8plr)OQ`V#U{Vl%B+PyI2+YSP@zdmhFcg^3WFOKGB_@wPo0y9!@i9gctD82)N0lUQ z3!Fr0DA+lGxWcCgCzB#!~z5aUF zzo=vG+7VK@(3wqX?6f?;DU4dASFuw&+%88WA+LLHH2FNFFnN-``mpFiISum}hS2n7 z_Ix{GL1oV44t`!T+&U{*TC3(GjgD{aBM6 z$;|t;d@i|w_YpLd)3C=C=v6nb1XhT=YafeUtC=iPPWI>;wp;k&0B8H^D1Kgoc6)PBpUQExrek6=|N`ieFf>Z=f*XdFVyz|Re{EroKuC` z@PkaTjyKsuPL&SukJOZlo;jJf>Fzju(sNPw3VmN)Qu%Lr={Pn|9)bK3vZOzM%;fxN z2CU&84U*?>`b;-!*r@5VvG)B%P+|Dq-Gk0@j*Ve;uHb%zc3|`Lz50bwv^SE%!xx9) z93bnzfA%BB#h4L@U2NBqT+Lr>Q*4K^5N$*@DqF*#$*o}K=*m5-%iN&G!GVUm?=xm%>hET}K1dl{4#h25 z+vzyva)SisuDVmQ5Dg@4*VyO2vljP?V*ksHJyFTQ6pgUreJQUt!&WN9^=oP!)S3if zVSm!cN1fRGcHQ%i(7ct`SM}iU9Eq%yI^?{)5gblI{Hx|IhhQ369@?!J zp^yY3pV>Y%@ngFm<=WK3Mr!R{oKM~5A{ta*#U;wF)T>EpaPu=+Fs*Gt{t+uR*Ov&J zdg8wefwgY?%Rh^=FNfVddV$Lww6S0uu5-8Mg^o+~wL|L6%c&{5`@s=5z`T zdMSAi^srjKaO^Bj<4I1iQ51oSV%JXS@ZOKuM=}0+XQw^f%C8n%7bw}o8Z*6-W5zGq zzvI*m4Hvk06-dYT3(y%A6TXuu{T(-6%I4uwBexCL;+fYpvZ~i(&3m{P^b+>}$D>1` zqT^Q2*sm{DVH|qai+p&Ta3808bOh>+>!IW;+y!;d^?-U*U?Rh>B3TsLsb8AL_u*6s z&+iw?f6V2$t|n7+IQjk#!$`|v)olBNsqZ5ogyP@f$}#LsDlkg)w9AaHlm(?WrZC$s zfJ|b%EL%|KiW|~vJP(=}mG0Xc&&Yq!|9e-xXyD;;US;rY>6Vu!tM6g1i8Q zb~!oMhlqCW284?}ZGOLpy?NSWHTCdC?F3ptE1BT^;%wjyzSHaz?)Cp|ET1>S=5SO? z86!=Xi`fI{tp(ifF>Xjf@@EiV6ox!0-CBQ@6saHevix<{^#Y-oFfEDJNzW9lozqW+$@QA#DH z8x;|h?q&%AB}78H8|lv71rd-ArBe_QX^`$*knXOfV`+AQt%qOzzt1(-wJ-MN`OKMf z&&)md#8f+5=tEh)mhv{_2H3n;DY2b!GULJEwYFc(GVr%~r)9bGXc_rv`J7~| z-JyE5r_u-pcD&GAQ*O{m;0s6ci)8^bG6xRSVsFK;tvr!eMwkcU^cXTQCrr zuJB6@DkCdYvO%d+i&ZxN~7oB*di z-L6QtZbNz!PqH`H+U|qS+{J%g)Px7R9}0bEwJX#xn;$rT8VXTSo|C^{eOfh-0{qq8 z!6|a%XK1+V5R-jAa!Tz;VXxMxJmRU+Bx+LsR?qHPw z^C6}*U(B2FQBAy~Z09LN3NEEq+|Rp_-S*!;3*)nr8tt$3Q9Xvc>@d?Zy;}cNgC)wD zv0$Z|v+s%*DIYVzJ$I9Q*Ttg4@2|TnNTz-nOa@uoZfk-iW_#=SQ?k)M2_)p1UvD9K zwHW(PRkl!+p*57!c;c{Ke*OBRApEp9^JU@3O&;-rp1^O9 zV#C^t13BHLvBxXmS8G<2;pgf|mNmGzs_F~a zL#WiOJZ3OdvJC82*KwU{-f{z76S)n}`8OZt?(j8j@LdD@y<~xI)u4xg$0!5rKh)`sSxuRU4&VLkA+ngTQGk5cEcUBNwQT)GWP7~* zyPc3yaX4zYC6=1#P&G_42dv9T%oPjG!y0NN&1}z~zXEfr_}yxrW&)DLB>@!@x~!kR zfKPvYeJ}DzAK8if`$f-UYX5p>&t2G4RaOrHoa5Ix%g_s1sD2K<8pP4pQ3s(fa7CD= zcE@VsN)Jk7gSekAjiyJ|dO!&HM z84()2V?1`^F*p0IrN88p#?vZ{qJf|d8ylx5)_v8p{y=FOoV_`#CFaaxp!6BejY%v1 z8L^yq~GS%w2`qjqTcJ=6YVB*J@5y1}r94+Lle$??be9r*%0L zwa91PeJ8?=Hav8s*&XdmsfvVV9V$gad_V`{l6xqjdM|<8Ic^#--Q2W^*iQw<4aJQ% z7&n)Cm!a#{?^-4(t8w1>${I^f8U?2)dUbDzSzQI7Qf6|Gf28AL(4OX5g2yv4hXtut z+!9RGgT4nLK0U7}*@%64u#>%zmu@Cvn{u#E`aF%0v2^BjnCf$o7hf?5TlLBjmA$IxDAOM@PFnq_qLV#! z`&qYhq?-9lckfzihG<#DKzl~dLjv~+%g&;$ajnjs-&#R*q9%*`BRk>e43}N~$Ye28 zSI2M`Gwq|@beMZh6|>&PTUV1IjYFpVc!@;;U3k38TaQicL-JqOhucdDmGQu#CAZeC z1RKGBaFJ|Dr^ebjiVz08&fVpuMXp7JTti5g^EBr>N?6Cps=fJGkwNYbT-;r^+|_DV z;K8r2u4o1#p}v>}yKg$si=R*-J$hlOudv-8*Hi6Kq>Yfcjf7uM;qd1rtcpm-^O>gE z)AZt-ix?gEVSsb9G=_(ie)`79=CexbnI16G|4;2`lMNp$=Px97A&64kwDxyvL%XER zW9sQ%qGvKso+w{Ff_?@Fi*LPnO-})PP0m2ocTYpO>BYN=S#d%od8KiDuST3P8|mSIUTG6MEro#vCSSTWYy`S|{w}sIYgCt25eiP({M!PR-)8 z_ERZlm^(G%*+KT^%y)x64E^qzsf%VQe?4{n-)ySjjDap3-&P!->=Uez)-4P#z(=6Xt+gI>lBFP_(5m^`+x+ehX z6>w)Kd!r*vkLDfZOY7P&B;I3EA?~VfCXFq$x2|Hny*46yo7_$OWTE$DwpnUl*TXX~ zW;;r?d&suE#!p0UH+99~4d@0u-rb*2Hgd=$4n?SWQar|G;v=6ZzkF45eG?`9{fE_O zJdL6gK#@9LVi8!@8<(*_kHl+6uf>%vRipT)o%8U>HquE88_L5iP9)y~0|UJKWA!4* z2`g902nnjV3S^!k`6e9#c9m5aM-c3zpDc8?enAhQ7SJi16}QmQW} zNDrVs7p&@-H2z%u(0e0(tiBQ$;zX&)U#+43C5y&-VrRWrl;d6Sz*svptlgR=dT^OZ z2=`xxFd@iwAJNAymvo5xPuta^U1#=uK4^~kqANu6`CDdjVP>7nKJ7$+rBKJV`ZDKb zPCN%eYkhaI7^VteasJTyrdF$@4VI?lZ9kTe_byev_{o9S@FZC4xacf?Po?6RcAE*BKV~7 z$o+8TInwQtvyu1rPI;wuzpV)UR8X#A)>4v*{mR+n52zO)N0<8K0A{ASm1_a>s`=*c z-(s(?PR_UFNeXNZ2u|i*Ma|(}mHyWk;Vf>lcd=KR@R}+zC`D|>ivvHfb zK2|?@=oJ&5>B-_bV3+sJdnc?X()7DOO$$V>Tr+3Ly?!gJL6j06ZJp^({@8g!=0Mf6Kl3=sZGaY2p>!K~AU6|Izc@U=1awT@`U z>r6Ck{S`ReH@lb0V-?#~2fYj`6xrDL)#hn0?6M|*88m@`+2Yt32CY>4jdm{{;4WO> zkoDJ?pU1FjF&_bU6)Q09d=XCZExfpB(3%fRJm&=)?@BIWS1dQUPSzN3{l(S8^^kW! z4jXwZIQiHfB#8R5EZ`#^KtqlEFQ?pew6 zI`xZ`(W&?jVAY z9ad=a(eJdPP)=wW`b!?chAF@8xK40me$uygq&nj18m=GKL2AusaKmPwT}k-Xl({cD z6jOKLGoL^gS{7(@_<+Z*1*0#hpowapk@cM|I>wlGZTCk#V0CwxUl!mw9BzbqcLiEz zBCZfpQFLPDCs|N>Z3b_(-ub_zcXd?B!W(Z-M4r+22yhIPK*eGqAeB`jgP#|!-9Jpt^jbs_{QW~6n8AtvCf^S0_&^J;|6?ZCW zlj-qd6jpue;wwX@BEmQCuBFoFd0GXf5;q!F{ju1KnM%+^@wL8;{YOflgK(zVC#Thk6aIYc z0~-@-rs+v=D;2+qqd^$g-|s>ZY8JV|qG&$p$W5_O=g#z=IKkJ3F@M$MP*I~uL~}4a z_@RY`b@e$P>*uIeT)4EHT9O|MMiG8-By(Hi{>IX4x5gpn zc46C8Q;U=k?n{@F7~a$hkpJFJN;etrtv{;)`1UC5i} zapWV?X063|+PxLdxBBHTWDzXFiRzhwH`_lS-TJK3D+;JO#yy2AJJ9>U2CfF zSN_lC?R5w+u%a};`mz_?tbp`vu;lgw*6$Wq>UTr|J*+FTDASldLrM`LvQm2U#;jfn ztqAz4^}7BgaLlW*1oPP`!5uFwfA&E%Xx_;Rp)H};vq4z}ywBMNvke1%X8tA6Bj4wi zmYc?qOWN(Vb$W74w(!glPHVFy-n)@}%}jN`uu6%k8)eeuzWhz6az9b1)pTJRqRsQ;;t0=j9R0m7icN2DHk@kR%TaEK_N%Ttl3-pPX!T^7_{a`Bx-FaRoB)Eu zm%qA6-ViM=vn6a1?EXBiA^uTTbhiH4^Dx3rNtLw-bfacS64w71m-ZbksHZSN!Yj0S z@V$kI5kaVg(TVSSLAD`?1EpbBjn&^)7aGh?9d1_OyvR(;b&Nw~y! zyPywRM*)G)?c@LB^0nf)*o~KI;CfP`F`YOtqT=$Hb_LvwnWFtoc?2i(d3!j?b-PaG z6=-G{*G|C%{07+hX#SVY)CmGGW8cFH5#I;8M@99J)(o%_Jwv4~b=XluD7^Z}zEnql zwS^jQY4ZsrNDN%NbtJt`y{5F@^psP~`(@069AHZ3HG8a|6p;{aGgNT?JeW9Ec6m+( zrA}w-#>hi~Nc{G2}tDMVZnRNuqk(_sBy+|xI8y4H!Y5XcQelnh;9^}2x3xW=U zD>QE_rJAL(rCPWI$CZ+h%?m#g*{0Q$#z=lddy9Q7%4ilQMTVyE?8Gh;(I!hP7eiwDMM5zA=waFqLKRwu?>KG_RDX~p6X zi8RhXe|Mo)8z0aBwr;TC5`IhZCy42(__yc)=8jE8lA25Mz|}KI@WSCU*6LX^V_aj1 zBVwC%CsoKkKUzXG)4Fq9+HR!J?Tzja3V3R2`tz^j@cECHfG3P5IO1%Vy=eofSATz9 zE+$FLrQ05hZ#x8xO(-?z3m%$OZpR%-N$$ix28}RGA@WDPP1(*dpQ5qG(Y(@UQ6y|! zaLrGIhrTBw;DF6M;5=JJ8@%6OU&~=>3}XJ);`oAXdc%h zHC=x`s!v^dU35R*TMK1E_A)}In6sIyDAeaW-WTdBwN^c;fKKCKV*9|a z$y3Unwl{=5=sKSi3Wm~&M#8d&q&T_jqgWmNX-uTctm-Ua00X)R^er&eJGL&HDQKYm zd=5?lfUb-Nje*TV0$M1A630rVl1>M-O`ByrCP+ALX4F|hHVAPs-!!|<@D(54zR~bS zYCWfw8^=4Mfih+On~(Cf11EaAUkVNW_JhM-Hosty+|p}WS56Z#R!_2eCO%DisR93707}b(uSiE$ZcNoxz&NkRv7tgS@++=i{5r8R(*AK?!~7X$Y} z^Q)qGwtJsAzzbery+0MTBa*TVEuMwh79DR==PA0Za!}>f)sOsCT7wCcTdXU^3*5kP zj|;d3@-bQaZC}UaWI)Fz{XmN!Bp_b8M@cjAB5l9+JS!fhyi0JBO#A~?^hwYh1LdjV z#Z;_Sf(>QjaWOb-=bXeG&tMNgd z(NR)j|dlAe z9#V4`dD?5yy)t_`t0;aT#uUVrPbnzm-cS7FPm%O7P5%JDYFF`fk$k~|qj1Hd5}!im z=X=S1@+w&cQSm7eCm}((j;Uv4KQ>mVxaqt5A~F`dpV}#U?;Fq}-IZCex4Yhe+J!ag zk1WxC7ZUpai5ifnxZuO+N-#R89g@XT`r_Q>faP}N9m5L1AQm%+?)kzkDc{{V_Ko?= z1Yjyh+3nS#!7+FKpQ4j;Pi**DcDkb;?CS?*)YKA#7>cq(4t{(abCBalWcBQ432UB1`?4O% zS^MF;EzcD`s3Hc?cwOa=o4VY>+WNdR%S{B)+O;jN5Qm>QZt*snRES0XQ%tV^IpE;y z>DT!6*`3tmS>m$jTJ*>AS1aM(ANGV@ocUkEsa|K|2x@d?Pk<)6GU}fii`A?^rM>Nr zBdk2%(zRe1S+UUH;-A8UsHa{j-zcArq-7(=!5~;&mQGyWe_?b+=w?PJ_I%$QJ$MTc z0sLAy^kXR&o6wo$eg0m}2thYiT8wY)PP}4ur+61Ca5(&YsMn8FtT4~e46PZVrYXDC zaikcMa7}jdH0bk8VH3h`FNvjDLfO0Dj=9zcWkBwzo627wZw}K}aLI+3Yy^Sr$n#MZd!K{>uTi z3jaa3y2yYIG`&GhFj6A^&vv+`_ze}18g{_eHrUkE!j6j>>MzQ)5i7x{&67B;%%?V~ zTH!h-u2d}_QGljDeDY!Rx7LJkU;lYFXL3YLGB#=wQ|6(m^Xi@E$2j`K^Nf8s4>BmL z7~Sndh8z^kX!s9O``2$9Hh4k5eQjqtidApyI{lbY7Wa36F4mpTTDIR&raVaJHkBbU zf5x)m)>7?bgiBr_9{TUmkg3`PO~q}7ZH6D9?gNrAAs>=B^pgtcZqm~)YfBpdWp}-p z17;A`Sy<2M)e|*7GlZ}nIePdGzre42%aM=Js$n)hQth}lKf!G?EAV>LzxXhfDs^N2>eOTPSK8uUY&Lg0 zF%parg6HQG-LHDeFWMS8Cx&oeGd_`G0tfr{oGQvg1FyQeqB;-e z{u%k+2frebuihs>ABQGr^uL|Jhwu3SAA=o^1`Ljm%gLsrUjA|U62sDR=64xY zj;yQ}JYbEMaLy3UfFf$3DvytqM$5nvZvocx1GJ7;ke~{rLb-pcT;#*R7AK@x<&nsY z1%PO>p!t$zu2CASiz)vi<4ItGj@8aO5;i9LTaB>eZVOjCQB!uI<=gr+#(HGrMG${J zA=>f)YU7QH5j|fi`;!Lqy`WDsxhlAcYh^cWaM~FQU9t@zj^z!8W_74N3>+1hKUjvE zriT`Rw6}hrE}Ew`ii^eXZzxMW_-po@YK1q)MGPaTl5yWWdW|KRX2@v;H|=%B@I4%RO?QSE!m*seaGN-UAk=iJ zP*e%F79*|lq+Wt;J-Ppc zdatFthYd;Ic5|3bRfVoY-F)7|#%n_Q2%l6*P%V`QGO75~coj}<6A+P%;t^dETWM?? z>tXesAzFPgz|Cr25y$vo73~pnLEjf#u|i#mJZDRs046S1w!10*xQ5tX zhWC(>oP^^chf?(711HwFqukv{&w@67FQ(x-&XFhlf0o5WgSr#JpYFgOzu|-79_^a= zudRu)hr)>aUORzL0We$l`wlJb*49KPY{+QR4sFAs=cx7+R_~)F{!fz*jP7kIAdEod zR(;e&l^^DByRLl|Ee^mVyv5Q@!Xpmn)a^wd1Q*7lbQ!r>Iwnu|BiiI*&!~AN!$G$Le*DM`0`sF!^}hxU%M`o zY5wE4=5>>la|T-sra>+XccM*pOB`^EBz-*7U+gu)uNA1=VhYP~eyB~6JG~M1@00E!ZmtaO|~9)_`XnuprK&1%cn;w@}Mc*FSvYGSn%3o%7V3 z7CSk}mw4SXmY-RW5xSHT`fcG`9dGxy=3&KsieHcRDfX0;t>(q8>l}j0(i3tw-=@49 zF&)0Q5b;ol8d}k9l(1ubebr1HTpLwF8-y8~qZqnN7~POWyV>)|Q}f_$LX;Te$k6}t zjNwj~;Z6?Ii?}(rHUCkLUoAlWTYVNRB>U&D4a{bfWLBVo4?v_Dr_nBTv_E(sYz(BO zO~qHstJPR2v^_0NY<7XKM2mJl!j41&9hlQ#F7G?^1|>~rZRQi0arV#;ex{TprNr67 zDjT!nO@3fG9t;sJ!P5=il`$+E?e+u>E)bqGV`mMkYsdP%F&ZWUg`4|7Gsf{Nqkf^B zC(Rbco;cp>YYAw^3esJi+PUEFW{ZFs(w2KyH4V=vkT7uq#HrgS#S`#%efZgZbGrp> z1z0#XcvK(;>%S-BN75*CA>viR>~%>*Kx5ONJrAh6VR=h)747tH&4<7aO1iz5+;Oo( z$dp|RAzN-O1ykbyv4WZT3whZaLZ?D&22pqp7yNGtOZ9IP?d?Nuwf!TQEUNU z`E?%9j1?}fhg6;xzUj%i`a)=gVs>uFmWwe<@A?4Fsu;=d`V{>CHFp)EQ}euA>a>!c z;Jn*$d~Sxaiu;McZOz26KX%y*se1$pQOi3K&_)~VMg(s8?w z4;!a3M8*$KKSq^$oGqP~RnbLJy3>YjQ!r?Sa1On|`O%e}WKdUQMx#M~Wl*ylyepeX zS~xZC+P|_~Z2jGZ_D=96Li=_6$dlM)&#J>}n;ynQ2@itmJp&b1jhca0fjwKqQA}mv zSxi{Q+){Gr4fCer!eVYMLON>a`}KRXopVyV5XqnN8^0xY>-yTcC#ej|O{v%j6?6vl zJr%7q&J-AIQiioO99@$9qQ;`YJ)&in2j0(O)3} zIrQoOwni_(Q?Uw6e;Vur2GDc91w)Fxes(k!rc93fy*T!}oc8$Tc~QVevv5B_#!2EY zEF^qPETS$e-g;iSihBukk3#X*@r^Hn!tnljw9I3gO6Q&&A7|PeSdDiXoGr|hwG98h zy))d_*tFZ6Yr`OH_c)Dr(Ejna3U0ZeNA5YuB{4M zns4KSYpK150aUMHn|z{6Bb4Uo=*VDu`CS(~i~rjCyOBe>Qzex+?Vr0*a)+0i7PpDr={UntB~53MZ8WEgGMD|l zn7E{6Uu9R6=0|2V!*cq-_|?{U0f!-PN8;+b;m5ktX&n+ z)xWIgWt;lSCqQ@i)dmA;zJ`}~@0!Ve;NGTr5N6f)LZ;zm5T&$`JD1S7t#jvxy?%YH z$pq)X#7{H0f}o`)JD z{E+4d^9L&c_K+YZ69Sx|fM5N4n0@@<87^bKx0~)upo|}cY-RcEj!1UQlQ08r1=Zk{ z4lb6dho)!g<6czrkzq0c37_l|_9!2=mr+iG!tt$_9zC6?b-?az{ADRfa7ILroPQEs zX16QXho0cyCqeQqEwa|5QIn!OTSJXs>&&d;%#&7ohLLJt z9D9m&3L2Z}DOG}2HueH8d%r5JNj|LaV?MB~l<0YpQFB|i)^b$KB9!`H=S}&^I0hW! zo4T}Yc;!SXEaWaF$e*e7p+c+k7r11H_uGd&wFtIozPdzhgC6RctBFtb#jlzzRM%=g zEwbeGRNBQmo=)QE8A-W-o5fsfZ}O0LZS85k4ip+(_vb?NpV*pax4v1~hT>yE-rJRZ z`z}|r^dLUHeTFdo^LPz=zJh)X&n-5`8n<4Ecqhy5li+2J z&?VqZF16a_*T$K{ClTO`HrukVsTQ~z6Q^Cg7glS4LK3P?%tOg=1>|dc?R5Avy-kve z1S>BAfn80zvgqB4wo}+f=G;Hoaf=u(0KIR1mOX-jOU+ch8y|00DdMX!x@&5*zBbU9 zAWSh`ZpMoEo_>T%_}*{UTx@MrV{M{VtMsijJ)e@E87P;dDP1v$<1xkI!q%I>Gp?r~ zgSfaEI^SE0sln%eE`z?wuC8=ngf%rzd-cqNzvjI#uX>n|Qd4JQ<6KG`W?aYmAatfA zX_NmZx@_l4U&>l81I=m3_WtfaGiggGH@SEL+~S`ou)ajLw2SqRKVFCa^*tIqOCTAD z9b=oiRX`E;QWyN?hAcDmt|y+~M6QA2WBikFW0})OXzxH#%q}?KP`W)PsBPxtzEAHA zt<3%ipkORpQwF+}z0?vZnAl+x86)1OS@ZlP5r8po_Msa9B=G5NgPwTxbRAG zVq1;3On!DRQ@)t+mYdqdHN-@sfqF=dZ#`-Vqp#s zXwes=SvKkql=z!(&0Ri=X^S0(3%ugTxNI4eKg;cAif1MK#n0ezN0b#YqFuCL+iWfi z%H2!ilYC(KIPy59#ZKM1n`x2+OAWB=ivG*i>SC7@EDW5zPkPtscq|wvt;mz~>;max zJ2>RQ`bmrAlgMcFyo$*Q=9JITO|ItAcX%8x9-F9k_-P_`HcYfmoCu0scj;CQXwjNGvNs3!Qif|OKY-+Y33Mm zv|m^N=dtq$@_%4fCM^_Vb`=Nks9KTM%Yl(is_nZR2R{0Jn?XIp#ieV#=XIQO;Cmd? z)za~-08H~?%`tzttsfU2n9(UIKs#@RdU}#Bt%)V6YZ2rOC&!{S(+vp?F_Ow>)D+8OMdWS*kr$;ICI~Y8rt3ai-yZktSP;36yK0w zgdtv-++U8~ZCG18OO%_lR1UJo_dz%BvXl=zKvtD){SP!)O%6?H*OuTq?l#L_f2IXA%oCuHXcg8Ykt3jU9mm$_W3g=QI}b`JFe0yA~BVmH_msnz03z; z&O+oI8T%0Tbi?C;J-0u+D9Fo(rXuxbp(mcAW7~w^&EBYTTaQ7J|KVPh>_OcqMV6;y zFu4=c8Ff8W%&fp}zHP|2PE&5R_PDNG(YHISgzg-}uIXfaDX|U(!dj|QH;li=PyN`M zn5FJqqbi#N=b_ZMcalz?19U`$+#?0$(B90(BFXA(ammlpX#Ix_guDw^x1W^N?GF51 zz26fnz*1Yos^O|apmEd$p&S?$o{kS0^zZG3Dz_6&giz+_-MpDhR{Z?|*=0 zm0u(+))wd-XxVR`KAU)d2He%EiM{BXb3B6Ni;p} zpZ6%pgTTyde|xv>D!akFXb0}NGPma|b7Iyqg>63hu8S2)m;Gg{D}#8|(f~3P;!+CE zD30gjs!)dY+E@n9tKTgeVEJOs+VgOXJSoF|z{SCY^~l*A;~|vKzUi26+q8QlcaIzM z>Q{ePZG2vekICO)ea*6v=zFpq9Cf@>r9)|JgoaNUBc*sy(zt0JoU}$$#7L#p_rv{k z3RYc$bL=Q;56iHcp}r2WWrnHi$uSdd{z|;-{+cUhWkrU=mhm<) zdI;TZ?u}{H&5k3j*$uSRB7Jk1Uc7!y0EDzdrl<}|Q{~-USgD@$62Bj%=hfqNX#Z95 z)~t@ZIr(s>GSe#TP2;SWN<@2rDK5#kZp0p}x@x!${^9oQX{d^G*dnTbXS#`1h^#Ls zD(55vDC=Z;EwZ8>M)sfmVi<7)-ON>8)5B&&yfw){a$a9Q3uKS~T z*Wg$dIIb=XGcCN7z`=KLb@H=*RY^@SYmEM#e_E@WNBgJ)l%h^<@wl)@lV22}+fE$) z=rf7>nkIR57Y(oSa&0FI_)zP{_-&gQBcEtBjXWXyulX)oj)priGW9*6g|CJY75qjGVCTSG1R#BMutKK25P{rv?5hkwvDTcRhe&nE zR?hZ|+c#K)%(zUp-I(<`CGF;g`E)4PZM;r3D#1HKd~#Bd3?X-JvVL0N`PE6~SQ?+K zm+DV%yY+XYI4OhMFY8=}Uusje&uSCbxd-Kgn#2i4mX*dl=^N#=pX>Nn<2o|68fNgJ zY~3V`f*x|H6?CroUI^WS5v<5k>`phJ#^3CJls-Fx(PUz0-VW4yVdZV=m<|9*5ShL6 z%P}xF>ls(wTh>>5K{!5xq0`YHPF~x8c9E{csNpJY>v!EkbVNCNtNS08vRv*LVcFca z_6`Sr?AhPjth3|5>tTT!r-p~62w44=s9ELnVpI&#&pur3Xi1|!jBO8NUoB(qd54-X zF!oq5`($UqY<_>;xH6uS9NDV^uY(HRJMTYP%xY*vzAX+AAmN*yS9|8Q6E=p5;?aSZ z)S1P$TkAhPo|p1<*Rz`N=of|du}XvZS|{+Q#I2m(%|`_HIjO@U)kF7Lmm_#ZtC`QF z{)~k0uC`(mGDbaih?$1M&fvW}=C7D9{AluSyzbm|i?|V|IlpLJQS+WgwJLq_JDp(@ zGSdm|*Z1nsoPjQU%+h<+g>-p#tS?nni%**VAZX;P+Kye^mM|mc0exXuKeb)pKaY>| zP^Wdp?M|ikF!l|4V#VXqga`8dDM_kVG~u}H^8CjcaP^tm8?On4@;OtygZBNN0U26a zoo@^+=mvIqx_lxgnODvTgs`u0mtSn}*OflrGolyYVfYTY?3iexSD$G^vpk_~Z+cc**$HD^ zVhikXx4j;JomxQve%?QZffr6>w`+Gh(qT5t&8#h9XY87?H{w7Abem#8`p_Lvtz+z#ssd~F+IFB%nhibv}KGQ=UFv>9Z7MG}C>go$`; zBO!tO&1WU+Wnp?RD!OtKuGHkXu)x}v;#stW^`B+@Pn}jMz$f^+U`m`f7J?BW_|nu3 z2zJB9=X2{N{U?zmYP7re97a_xtPb^Kc@nsT_z_RDgcg=sgB2X4pz;xIW-u>iz%4tt zioC-HV_~|P2ps6F176q0XRlQZF23aoDt2>2vh3#0BPC0Bw*L?_RfbiAWTv{h-~q+9 zmXGemw{*7VxNk|E2)eT~dBr$8!HOv3Ew(gSqKj-qwNH zYP=5Mex=NL!B{hVaiqjjh~uBKw0!`9w|M)Jiw z$ej$~n8_YV#M8V@z#5S%pBUyqPwR%*xm>$MO~hzuVPG#X8#c3;YE@K-UeI9jj%yRT zaL%*K(U#E$I#G@|*?&wr{!Nz3r_BR0&)|FQ(FykP4zpte*J5^0O7!+#**^Ew<_VD1VM)sU)%}DZRTHi zC`!Qjc+NQHv<*0(`oc#(YaxHPks!MXKg_F6`eP#+p>Dj@e=ll5*!{6viG+G)1WFPTfk!MF6e7OD{p8VFe<#kNE zlYJFubppuyL2BQJOoHq7l_PX#CEy_09%kCE#h&;7p-Ttbbpd_gkY={zJWAtgpkd;9 zi~TMC>#s$3Yr}sgObPK^}J`Qg;9m!9J1A zo2=3{jRFN#W`_fE)1^?^mH2xQI`pb52Q!Qt^z|s%IB}PeNE+kbhfYQZLqX zPp%sb73kr`khP$rhB1>yOLF7BWYlHu_(m;%vy!=BdG&Tro}W_TQWJ?{#s9PbqDH#l zO}}>MM?$7rxg;}7Zw@o!caVW6YagP$Xf^ct@nH;l;UA0htG~Z;mpbP*Sx2Rb;_*g= z>R0n#@IqI_ofWUjzSqCuT*ER}wqAQ}xoZhqtlIyVM{Y+D|DFJJR)W1`BZJoiN9)uibCphc5*6~WV((ATzZwhm>ft&mAXPfy(v03KUM zGDZW;S6xd>6$i$2$iBI8{yl3B+)Dla+(CJtCSYhDk(^+gKn=#X@Ot@`Hgf4((;atXE9Ieof1r%43in)*+{WnQ zhUz$RJ>f3^Gh@sSbGP=QneM{~aVK1gI78T+bOa4+YxrL?D_qm6RGxJUnuL-otTi!puXZyALON0b|GKIa?ql{VJc^aY*A9(iqXauZZ zj#GHEr%Z;|%9%dtq8(G)`JV1P{J(m6L3-^N7-Zs%rzNwlEJ$2#hne_P6`EMABZq$*{_lY}9@f1ZlVW6tj_j z=o*u(pXm$rb9Qk%WEX{~xzE%7w1u(3wQiZoMXPPi*PU14&jce2$?y8VO-pKgGFlb8 z8ua2cgqwuGzs9SYd7D^=_DvyNKQ{vH=P#><2A1O#x~U{SaK}~TJeXkIFVGW}r%sJH zDv7bUCjUGntJZOyAoqn9+wVgwla)*t@qb15bb9ey`s=;Y4%kdk@jGAv!(QGvc;$0T zM!YRTHPGXsj}sl@(u9;=*zqM<0>KIA{^1P{3&KPrU23jA+~{IkT+dYMp7^EEO#{%& zB7sPG=*VpjpcMxp++lGsP+R*XUz${W5@z836NYUeB4} zv-JyWPagbke7c8ryVWX&lZmla_p@Y}>-`-YCxir^nIrkQ8TSlQgN78CI2pGSWE&4t z?7N4&CrNW3l1U*h&lMXBVcRbW|rS=agZ&kRUt%m zC$|hllv=Om1ryC_HIxjjuw4mh%Xcc~BQ%%3r%VNfs^BSiL-%(-Ef-bbNMibp#$>+; zSUzOLG9wy^c6Y;G^TY2b!UZAyy4IT1j0ZE6ed`qQ34O|#hd-@6SqlGfo2^sO^S>b) zdZe@7Hn&XTjk8C$Gcpa&s5oM#^rgOh*5s&P*HB@J(hR z7wz!V2f`F!1w)I?BgITe>#8&;VMpILxj3jx*MO%-4}&1i_r4%I(EVsWYe9&BI9=YN zbBDa!B?@tIaW||r7UGoJHtYPj@mJm7K93K=l)pOr`IKLlI?O306$u9lm_UYXs1%+o z!HB>Y%`xpE8KD<~|J2B1h3+DrG(zlMw&a=k)tJ}}y{%0lnIKK0ldtv6N+gl+rCCM5 zTY=enF6JEzIv}Eq=B@(4^1klUa$JTbJ}2y3$ZQl*0OL3ok|@o!-xbP0x_ICFGUkWj z{9N=HaiRsTzQ$0(_f&R3T$DYOcy(v}pl+2mw;a;g^U9q3&saO|Qc`V1T1)>ib2pSv z{nDd%xpMNc+>rWCh%d}I*S`}&C*}482g1Y02I}FToCjiG5pW6Gf86p1Y$S1fZhgZ& zWkji{mA3kEbGR_(d-KtW3-+!macgg8@WmP^HHn2>cXPCEvt8aGOCYz*xmg_$!75BN zQ(t7}jNq*9^G8ovIZe4u2Ru)G;<$)sMhrgFW#MR>wcGICw5LF8nfAI6tve~S02QH- z|D%`xi&w*mn}C^HyEVR*fh6egp{+?D-*B|f0QPGS3T3G#HLnP@@}!$NrrOd#L?KDO zLo>n_su8o&Glh!GHo+V9r25PFq8+m~1hkhX}!@GRv%R1xH}XlxFk@ZxR&A&oZ?ojxH}YD+}#NpBtRba z|D3(=eV$kAWv**7v)0V~KJy*aPX*ZDIY=~BJj}CS#j?C7VeR9zN(`r+Lq=QkR4;2h z`m(o)VePSYopHpO9&X_-@E^o>tSb*Ypzr(Iy(lvbM$LSlc~2ud=ec(+|GEj9w1=x1 zjxA`0Q;uF(88WqRY4e<)sUd-0*5In(Y>TjdUHbGZbb2V`4U`W+i!43m{ZogI`zMmV zP6TMc4#A;P5y@8k3d4=|QiVkE^c8R5&z@5(_6`o1N1}CU0|bc1fqO>|C&6sdMrRM` z%}ML3a9#I(GXF_|zuo3;j#3P>dU30Ie~0y{)MKqb-*72JyR^yqpC!hu|-Cr0lc$nMPgaSu^9CwZBkN_afK8h6}%o6l^F zQ1;uCJfH8V2X5U&JvX~bxV2kmORS^Vku{sy=fY%mT0%Jkrs;uhY}S)4y(6!P=y8eX z4KKdTwQc5eS^T%%#=6q^&VH05b{wwDv!19axu5<%ep{`;8hjyW)1>3Q{JE{)vL@O7 z*Xgjh8UkbQD@C@BC)$;HvEK3MkI#q8>&lZiJ?ev2MX7Exr?II?tsmUIDX_W~0x_{) zzr;PV11w=A+D1N_&G$#~#ZgAr_K>}rxQ!zB2drE(KQ%A8!Swu= z9Gnm9=o_2A*rqG$=q&*wxjYh{7$R7*tMwk`6{iNkz4TK$H%B@j6aZ;CS{rdDZ z4v7KC{7p(_39@EOSwxwVrW|Ih%hhP#D+9yu6i7cOi*(xG;a@HFv2 zkiI-4g#`cS`@O3C4gw`9Hke5Pf|m%D|Y}}SulsN z!zj**AxMwJgSTcvtrMKjW4cstqo%@s^uXL1{H_?rLcG^Cq8)<|+YngOz=?Zg7VH@j zu3mv`e)y6&m?OW{EUJLC6^J>j;Gbzrb%SsSV0dn0iJxzm9AqQT?kO^#0akx@)VH}+ zat8obxq=_RTZ^DHR7-LpDM!OT$C#!g2yP8ejOSSAxBS;5Q}bF66Myjyb>$n zA+tCW>L7!mz?0b#KTwUbxFq)+n=D^CqYQ)H{r7a3q*d(E9yWV|Fj{Zaf}k;>EcIgU zi;}JMoz=YSe&lbnIIY}r36BIaurK7>WE|0d>u^Z{&UfG*B3peN{}03tJ|xb&B~UO) z$jRa={7c)W-qC&K;(0CR&WGhjdO)P*j|L_ZvwYM{aba{$0l_brV-4GqEv&zr$!t;9 z&lh-`|7G%9&au0 zMs>r=qq3V+c>acNDowrr=6cQM+wrpbc}Z8t_^S8QEF0&;X1$OT-_?ILpx>cZgBDXC zPR6@eJr6tgD%A*A;x7x>Q=hUxszbtd$5*!3ZiPbzI7w4SyF2>b7<`1Mh_AlR0mCYX zduXGE>H=TyT96tkD%{tj1_TDGX7OS5%KBkzh9i6mIVv?15|_O>KM7;gDZsN*n*=^* zCUZv9d4sd4#z5s7dRuWcgLSshuowUbzRHTecUv@*=6;Oo<@9}nc-l6X z^jGimWl&F1`HOBQ&d4@I!*hdt*weN%TjH_?e91s}#H##(2)>^!g~cACMm_dTPeM)a zxbyLEcMH_}e1_|RPiN4Hq6JE7>ao@oP`o1aW^ai4qR6Ax#W}z! z*MMxUwXcOKP7UKtqIDhJI5WTpI)e6_OwMz;-WLBC1o4bPP?GsveV>Hi+~1U{%_wgW z?p`flolxKgUK~ELiF2goa21-2wsq1sf#y%C)`mz6;|urzfu=#95$7)J_|aR)votG) zyIf_WDN!RYSqQ*X)XfS2em==>F~S5^XF5M`|NMn%kXJ_MuBX}m6@$mOdG3Bet+;() z&16z~Abu0{VTs)HH6U4D)2A-7dHLO^$uVM%zs~dD)?M8sR=1U9bNn&T1ioL+IeK+ zL^v2-gs@zQU07*7o*oP{qBcM~=Et_dTcxczGxB~nz}t}DrC;^7RvAr%*o!8`DiQ3r zyek~4Y#dThIKch){pGHA0DD4%e5i@x8Q-U^{M_E*1mS`_iE1> znoX9wUbv6Drg@)2mz>}Dmy$$On{V6rtTv3XKVF`MA`>0 zCH|GKEl**K9hU7q=DukLwTAe4UavjjI8n0%c*tQWP#Q|EEZ&@+svy?H-}OsOVy*10 zocm&u(hErJE49tjFc(eOo;jmycJZX#1?mp`is+#W!w(ws^y^xab)kSTnm1ij6mmF#0(rceFKwLEMu zM2iwG9^kaqAjqV@wB!JDX^pbH>ydp>n3LpYs};#BSy0I}`Uzgz&||CG&(hbeGdC}&pZ83WLMu@{a?%JlllDB zUOQhAc?j6CYnurI=Bk2EQ3)jAq~C>gz0ZbbhW@OrbRB8X)KMt?JxA^OjVgJ7&|lIW zdofNR<9yp4K-BG%vc8Z{QcGJUk54v7dlZ?itLKgf5d+VYP10hK1v?sa-V#iZVuoy_ z*rpE&ZCfKY{?5>sc1_`fFBd!9;zZ!m0JZ{XZEDx))*l`+?d8=3oUD}E_>AOb6_~k> z04_L^e$qxS$l=d20aztb9^Zd3MGzMVcY-s~h3?WlZ?Mr1-F!=S6i5EB-n|-ac5eUC zDx;=k0=%@}lo`qLQf8e%F^B8jm+&E5!#J zfkAryDr@nfs08#J5|0r6$M$!Xj?zlPKhl0^93V({(`hd7q&V~k*-<1F*J~6c)qEBN zD#dy!#R4Kbb0i6U2hEI^DrM?SM$7>U3@Lz4;h2Rsr$Gc-^xExkPD&v1Y`H~!`>d5W znfPS(ZQvOny8cmWdjmE*bZL`|v^spnVzT46d4KyJF~5Y2<)$JmdkLvh)UmT3{MMf` zjZS4aJ~6Bw3@&*5IDF2mrS9|O(+An?Iqa?W3<(=Z@F z!UcLQTGXiSS2m!II#{EcnyjT;y8cr29O#xbV(L4S|H{I##o2jzZO#9}rTrm)jkFK= zPu0oI=bCD@-XQomMN$gyn5InYh)LCUxyh5IyE)KCy8{m?v^|G*Pu?7*_b2^g1&)Y1>ktySetx+R+ z=z8KBTuk0kalJ8cr`1t_)_U>Jio%G%dQLjCN>P!5NF6`CeY`B_ksg(uS#9UdxGdpe zw){<1R_AoBi-yo%VPrm&Y0{9^5lI7rEMG+9_YXn>cea1^bREsPIyPY+QqQS|CDCrF zo(SH_{EVa38uUCBh6{=dM{NPc@iDeCCtBBRk2o?BO}DoJtE)%ei!bV;wZCUEIwKn$ zmrH4j|6G-~=O7KM9@pM&^q;&v(KWg)OBwqQwDK=_#W*#%6MWK8;_TPd84%@nGYPMu z)eVdoip&?4crAvJ(>rymFZDWL1=0O`S6psL%x~Kv=Zwxl3?qw&L^_9GwPx&t;{eam z_mguS21;r?LTTsbD)S$l$14~WFQUvE$jAtBI^6M0hl)%F0ph%aI$2|hv@7j(COAI( zM=C_MTmefvR8@U=7;)5&Dd*b3b>ADQ*Txz#*PKk#>TLN1GG1u-%^Izm_75VyQ5}7; zMzlk>89v>H$y4V%lSdIarXVf{08)Lt-aTu&Wc4-|V(-m#KXvhVF@g*HR$$Qns?~G$ z^bRl8c@lm}{_%;+X|j{}nSN#VT{gtu=1i^-p()Q}g0Q50XxCadpn_OEdS1qwQC z@g)|zmM}zykdZwKP5YFpSlCl75uwY?j^Xs1){!l@=p0( zxRVn0Q}<$EOI7C1xVN4TUSH?I7V5*B)-i(e8#INeRY;%^@wnVKar^1ZmlF^k#&35Y zN^mAR5pMU{1GbOeF>7AEnUJC;AF8NFGD)tRVr=yvnt#Re818#tXv$+$vf=X;zagGO z)3_s~l){@@Mg77IA3NcJ zSLIH*LAqQMtYWOL%^P82pI=qJvbWo?a5WsXpcL+wB%xr87Lst85_9@R~^fo!v?2@Nv(# zhD)H=rT-1^!27u9xKL#R9`{QgOSo=Gg-d=asEVX1ikfLMGt1tb>J4r7@)_sKp>E(*WVhl-ovs?tIODJ|Ef( z*I*gRC<~HG-k*0cMev>m-~pfE|FmtE`N(|wzCW)}>h69j+4ne4rTA-iMiF)YH?(50 z)nh2~j~dFBK#jnh^$fx=d}bq2_JePO6Rrfi{R&+UTNWD^jt zv`8f^_ohX+tv$Y@`uH^$yx%!;&Ghe&8!)A$*C@^=bJZMtvV+*fRoc2YJwEXDM|R%L z0w0m531XvrzGHm_tF05*-;#HeZq1Mxj4jE|a}G$U;6cyUnZmeuP9T}COMue)^w+@5T(B%J=#bWbW^Jc5pab zVz+p>d|eLOe_A<8Y+X<>#2^@--s^5&h_?+sMSxiB_}2P|LcZ*Z(Q)g|(W6DXzBJQe zbJxRIt)oadOlQzEzC=(J4`+NZX#{`Ai^L(APQO~s`5fGR`3o~Bm{L^OHNQKYb-K%#3 zCy)pFJtF;nbW_S;Y+m84L>>(ZDQc{tLI4%d|B1Ie1kHy4TQu#Aa-QDD_{a=}f zVK>xG?GG3A_ibs}%fF8FTc;Ey{r#&N|Mip|t?otsQsh;Cv0no%Gx}S|`6a?A!Na>Y zU#3O%;MWSg(mU(d-SgOD8Aj(8_l(g$Q;W~u$47Si+ri{)>*68Ic_G8qr?9&IW4X)g zbNf?kYJ5YcHfogFM{2us!c<}YNm^!%9KWmxaSvFvAsc)7C@W!{98rvHRLQ3EEv8_2 z@xYd>^j~~+lj0lma?A)*nSjb0i>0JRVOL})7vH7SS4&)V+?>7&TexWz57pMLH>0qg z*tE0PX&Ug0m%gDL#3%`FQx?&P>5Qg;2I-RbDdgh669GbtIYhq$$mkaE-eTwIu!ME$X?SQ0XKe)7hkQmrtkxnKMD%f%QI^Ia$y|Bdgr2BR| zbM-c-j}y)h$a$* zKEtKRf4a=i@4d@#SGdvj15|weSe(mAbdcPlARbPmsYpBMpkAmkYd!!#_LL~a2zsIu z_Kvn12&CwtiO?EO-);%$IM3Suw*&PW;gh*6l2ZA+aN%15#+zbHYGyp4lwZrSWG z>~|bSN^3^FKY%-sQxM&JOcL8K{POcJy8TDy=J|%n-HVml@8(M?DeJ?P>J?-I?l)gX z=`5Y$^NomYCfLx0N$qKxj(Jmi18zPLNk2Ij)m2qspCbisHU7Cp*?DX2yqBS9YiSL> z6*AuajoNhw@Dze!ayh-W`mEu#Kg5k50&ZoVoxcJ#`okgW(v){g`o7!~-qB5_Zrjf` z>wdP`TeD>0kq_VNS(Dm1l;0|qiS27iO=&#q75RfZI>Be&CJ0MFfGpbo&uF7vE^B84 zT-wwKK={^(8*1sjSnO&+4%YJ10YZ~~M9SdrBqr!1rXo&&Clc2L2z2&GcC;^AxL4wA zD;T^R8A7(^_EQJFLj~OyCxD2GbW|O-^{Qtd;6h7!jwi|mUQdZVlE{hGyTBv+HkP`_ zJUcF#Q_4EKZ!s0G#!Y65rG$-neJJieCjx7+9IP>VN$uRO6JUl+QsS%UU4 z=+jL6&?(V;+^ykT9jg=5Q8kJ(dI@1RzyR}_$3>~1DW~uzbbNVLKMVO#>;2m4cRvHh z+L!5Ux>iL<1%4iXY+4`x&UI``ce)don17Y{kDs}2^R2JB1)M%fs`DWb?w^KJQhf!% zZ`#%9I(E{LDBWL|`*O12l@MD^!(}fe`qElGf->kNLI$(CS>-CuApV85*3S0j^34=! z1>_j=9OJQn9)@Y(J8WK(UiuucaI40=a!%+XcQ8(s6`?a~7!$_*O^1;{rg-2LFYV4q zIRNTToEHV{4eWNGwT6*H+LJlHx?a#K9dlskls?Yk#5AbejJpk)+)^UGFbkq3-$r)} zTAuiXg=B6lji4hkPoSqz%79ITXFmW0^EZX(H=#EhsX4klp)5Q?DS{#s1Mxb=(gpp6 z=ba_dXDoa@Y;8BXeaZ!GE{hazIMPK$Uy|iYc)n0JKQ%65smtGQKEtL_Xb_qYICn6; z10iZFcmKhs%xu!*Bm2SMlSHg*LXndzGf^VJK)d1`7O@8rK zD~5`RIyT+wXRSLnozoI8VZW-ay3UvWjY0c!D_3`Z#&>8jLUi?v5(MH=LkTwKQh?8j z@197jpke4QeN6j0C^Sbo@dpA~?3>hY$K|(aH)7(bw&fCF&2?K3m+n;CAie0hu^Ezn zsK6PKGj=a-q-?9uXCU(hY=+Qy;q;Z~l3ZFNT$eguhuD3-W!%7MkboA$m-l3!7KwBp zNX~5EK!H2oT?ii_4B#_{Mv(LP(}V2JDI8v|*g z`vBjoS-x*N0TGJNs?vHntU6~@b!5DgjQncbA2ie?cE^DDIGdt8y}Pz~br>1h`kd;p zZhkLV2p7TM?$j5&eHjKXJl%w;2E(f^Ffa**y|1hRRhK#KFJqAB8i!@FRGz+qEnRu* zklt9*{btbMg_0ePrE>sLH1%)cd&%oP zh-J)B_P z1e|7ovB7R`lB^aGCe4v06#UH}KqcWbQy;NMan5V8RvsE6Y#yc{B78}aW`ffK<9W!e zN=71G=5}$xd4s>b44}m2kZz&+UXQ;pc6rABw4+Kl3#)=4W#{?BcdB$ZCnYFYy~1pq z^edfS5nZvtcWc2yy0DxPu+_=h#%n~yZ|{ekXEDNe^b-#2oyW(f<>wVG3(JK&bB{CR zYv<LQJ@>EBu1Re=Hhl00+{r3V7k3gH|f;Bn+AWhFfcRU(|Gq_eojL?kZxw@0s zjL9*51V3h(B~W3z-&TqP*DsXE_e#fTXU+;j0q={Vg`#tghUr!bkT26nxP-9S$tRg& zf9yw?auo*4K@ibPXbuvOY^zbP=p8r3({va~HV$Q}5|6j#*epygbe||xR5pUhB{Nb( z8YW*FH8qyDdvIIFp9no{j<~}nlM%p^NPbnz9PzZ)lQ28AA^+;eZiD8OjoyOhleI6K zl&XEpP}5zWs~oT!VtsuId>zHsVFNsA5L5siRRfwgqvqIFt3B8kl4H)-+&(vUrekaw zLZ(|WS{6>B0WVHZ&qdmwlcXGMb^KQL#=w_tY^Br@o1fXc(|JR%q=Ab_g z{FLlVerAQAFA(;V^`*@G z#9EgHI)+knuwSgz;F~bh#4kIpV}rcgmpjnF8STyvC1?2Y6QF(j#l@4P58{k}(@qh? zpE_Vke^Mg4eix!C`Tt~g+Mk0Rt|>+@QAL(p6EnRN)yQgNY{w$WDsvy`KIywM3Pk`1 z`E`M7|3sDITU3C5_bX>1PuERoSwA9=8aW_YJ3_jcg7M1V`bCFP`WTzf*s zrQA=donn|S`~{8Sz`Xal!C|z|7{+W@8fnvU=jEL9H9=-x|Ms@mvoOcIK+8X2ifeaq zpfe^x>!zoY>9FYbtBmz{aeN7P_+ZO88z=cXBfViPPVP+k;~)_Dlj!DTE-PU14^>CX z^6cI2#i(yDq)6(Ka<7jqB}zl`{#N>5Jd&HC@1($kKJb~XS$pbyf2vOaU4TV$Z?yyY z*6CPAs(iHvPZ+{*jPn|oNIIT=VYF>e#G2KO|3^XO+0DL@-6lA6T;8{9Ep=9VO(GCu ze8`n+;gmQ+e!kf(foWxmAz44%b7LZ_>Mj+jQw}#!+FJtBEJMC>VIfqiqzS zisL3PC6ZnTh#kYbu&s59!Reb@_PG?M_JE{54yiC)PI^mY$itQ6RCUGne-b;ba;1@B zTI{9aVeJ!AZXQ2cLd{Xywb*Nef9NxM95W(kkcQdA+sE%jXxinHLg~g%$e{i+o%iGI zkB`;Vjd75hwZ6TF^GI*-`ftjqVSTsX0cQl&ot>*mqjCHG+)bWnd{WB5u!=XH21WTf zEoUc>WB%gn$cTN+Vl!v^e`uL`+x2SG%asS<;Z7&YMNn7pK*?)y%I)B8-_5S%`j0pf z5<)YmayS|kIS)u1C9kM!>MF$c)Mxe>bNEvB;)QxTj2Efd$gveSEEeRf%|)&J7z2W1 z>=jaqxGqILDQnPvp2l-sC$}gC42r~{z0^$EY*GmZcEw1}pIaj=#@hnjxzyrZpnw~J zCFXsphre(W;JD%M97LUiY1LAE+Ax_Irjw7s60$p zvecMMh2=v{45sTTuIlT?EfL%HYOTGJZP&;oS(oD%aEEP|KGSoji6x`u)%e553L#Qz zM*)PoOVknygkqol^YFa;UR_+x{O^!t8)quk+;L}sN={AJH=iN7MZ~lLh$}zq( zY2CmZZ3B6L&N0k|*nZKF$;fEvTQm~3VcX_Z&EtOTUpcaCoTv+E@(mg+~}R>9)kg5MG#)t+Gn=s zJwHcH^lLn)OGLj{96YKeNjoQtTtMD|s{v0dnJEWOHtTV=UHsK-(Zp~4>|G7^kHj-4 zeR|{DC&{7BCBy#~FZkE#>N3$K><^!uMl1zZ-gly7l!;uO)znbo1NSeyiPHsV-Mg+S z^2mg6#U1O}-}Ne9rSBj$)@F!bH z@qvPkfb2ep{^If*#o~m~Fl34+k~&zlYQSBQDQV9B>71nPI9$>NIHT>t7LvWxY6={` z?g2%H3_{QPjiMLV))_uNklT9hCY{{R;dCLHxV^{q&kxEO=UtysoVs82jjeE1xL!ts zf?PNIZo;-3P1kN?Y{fP|AdYT8A2HUsj0Te)2-bF`o3gKu>ze1NW4M~#dchIk>Uy?H zzQy$5za5;PISs4y871dIk5ChD1mfN0yZ<=Nvth3I&T7g{&KjQ?-#;F|eEhTH+U%** zeDuz+$}fHc0Kyy`!yCdfLOAwP?#(#!7;irm)J~J;NE)Em0gP^~+bMnnaks)X<(b)# z%ygib4};5gY;vGh-E0l2ZzQJot8CGY%C z?_Ad4olszZ1EPVZ$ww550p7Nbx@GoxaZcFYifHlrL><&qIW+*r=d9J>)JDk+l z+}dZ-)@zX8Dr+Wz6u%WsBm?SbpA21m7ymf_0ZXn(z!&yR5I=1}9n9)H2?u|5{Yzf&XR^7zxP;(1?OMp5)W+XEh&sBM%_{}G|>oQ5@SDzK#)`rsu zx*(k%oT0)YTyqj4;HT~sb(*`|YXW~}34=ApC(TEYX`uAxY-~uTT3}TIcq7eiSG=EE zg&}U3$I1oswG;bOk<6}ME1}{;h@BX5D9p`n9I~Jcm6l#H7JP6YW%5s<(!GArKId(`v&RwaQE|nuIuO25z}@Zwfdn7Tof_@a9uy=30Hja zQT2XH7OUqC6`;iA3RM51 zY6ecGAB;Dz{9sdHmOg;Ery;>Ps!%nek7G2Wd4vBct0HF9`g`YitjK!6^$U`=xq`{o z&G0AY<|W=!;B|bv^q46Q$5Zq5s0-2&{%!FK?>%|%fi6B<9`y49u25&$PFdW7RZw0( ze&pQqoB#E-$7s%_?|DP6>Nd%9DNF*>}9QH8knf%okI2n z15IrdeV5At1DPi_Pd`@+9u~a&ZWLM%i>JQ0D%ZJue$0LV7{@%{ZzK$Fyk)h@(r|Zez_lE6JU!}C3>#CUYrO16_fLL9 z5h}%f9P<59;cEga$uA@xBrQK@130%)+PXg4VQb8Iuxd30=2T6b`Dxy1i_jC5F zX7Zffwb8*}iSh}*ReqbCntfhTpbsU|lhiAY{fgDEm7dsA5CTgX<%G9(ADgI2T-U`* zBECG!tMWcFUjV=8IWc|VQqls?_0s9=9^co>BNf1ia2Br1|;gdFvW-<%EP4n`W&jIJYD3Hz0M+B4z?tgl^~VLLXNMZS{4 zltMSnm#`+&5)mR35X@jXww@CwwLGT{yd&koGrTi_xqKe#T>m=Rd=>K9v(-6&HAiF~ zoD$=*e@pAG%J?IJwZmAjkI*s9Wc_$puGSJdT}#wBnw`jhH~GA9hfak!B&K0};Dxt8 z#uD_Y-c|zm(77(zezkDZ`gD-mZs%`t3He?SPKsL1-1~{(6&r$GAGp>5f8$BGce4on z7bDy8z`_)8dT?tUgkOvTYs?g62A%xtTaze=EKM}$zvwVM+x0wM(Bu(L)3?%xuM zQ;{H7sYBWC+W4+F-KCQ(5~WDkbxH4PZMXjW4O6@T8&*BE@44{$v zlI}81yr8t43OC=5-?4!Ps%48@JFS0|csx)O@_Z6CDF_XSb9_Yq>3;>YyE#A9VEet|%wAL8hqjapJm#vb-9K+dlQ^l`gtR?7kT?1#Drl>)=UJ3w=f*a* z=~bv8_zx=Niun>Tv9{vUb0zqn=gpOCqzl93x`)kErsbQH4| zn}BS+YO#!lXB*c_sSaV`8=|#0At3E8$T8)tmz{ePg=%QY9rn|j-NsTaH*rKFslHB@^UrM6sEW8xzMrY zO9W`mdm>voYt{2hU zh4*@n_O5#KnU{vPvk#%k>j9ZLX1KBu^qZ_8$T zs$gCJ5B612NulY!GgsgO8bxSkvdLqet!VUG0Bci7>c_{_N&b;!?t|ZVG{mKzc@# z>A<;vOfmXYr9#t!r;{A5q;TAfIN#deHXO^RB-$=v`fk$1MRbR6S;oHD`|6z{=D^ud zL#xb4H8(=p6}?H5Oh|uj$4NDFW7<` zKtd5MfQAm$zm)8#LM#iFR0}^PWV3+SMKS01)Wo1gYgbi{>s6tBcSeb%?1jrWF9xxP zT$VMZzm*Rerm34bv@FFvY!j>Z!!+0=#9AK=qUqFW(7dJqMTT0^7TqZrar1h)Ss4od zc+ovs@Sd%)$LrbzHZh`eTenRIv_yNI~4l+{RBSm3;fls zhoycmp`bh!qrp_8ILCCz;8U)KnIMPn0&SMu<4?~fITt2S zh_lVY=he@0xhPcU2jKPQT~sA)?_~$APiF7Gmthzp)+qJxJz!$p8=_-XR?$GaM-4We z2IM&i;E-R|=T04at0{sB3x$dPC&HcsjYbml!Fjc34Y?JWHc@;BXf>k2 z*aW*8f`)0{Ngi86KGdr>(Z?>M?ABZe)D5(Ey!bRIuu zaNzwusD%NK+Z*~eJF=tye$yry4vd`_jbK2Tiyq>vZdeMR-xvB*vnfwfHA=~e5%;yp zSUL2TP44d~Zuj6uUj{sb+g{N~9~v{osh0ivpzZ_@L)MFm{O{I7y~ji{ z16g~jvUq}})gJzG9dN+UxoZzcbKC3j_N>8kR>9jfjZ+ZjZR5Srs%mMe{B&aQ>4sFT zD&YJvv_5H*vvc7SPXw+H^`qv4TmR~8EYbJMRMjWDSAjAmvi~>^Ivoo0A5m`tjd$PX zM?XF{KkZ+89_ghpzuz~tOJY{}f*DbCW5GM%WwA9mAVW=14m+ht1}2DY#m~p7Ch}b! z$7gT{$e46}{CxS;p{d_XiriNk=+}v#eS<+O#b7A?}5iBXyOIxoA zP9bb&7+?xtJeLHOi#yAsbJ^dw@JLk)1gz*=C+0eaKVewJJ1}e1@mqpJXbWAjCTQbr^h2dBfMp
    ~KS@`#P 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

    xy%nH>{ErdE&M>8CmML*mGA8* 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 VovEL>+H>UaG152HtJJlB@;`hC#M1x( delta 3080 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 WUAPc!NAe*60000 Date: Thu, 29 Aug 2024 10:02:19 +0800 Subject: [PATCH 70/85] =?UTF-8?q?feat:flag=E5=AF=B9=E5=BA=94=E7=9A=84Hangs?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E6=B7=BB=E5=8A=A0=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/SpFlags.ts | 10 +++++----- ide/src/trace/component/chart/SpChartManager.ts | 2 +- ide/src/trace/component/chart/SpProcessChart.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index 4f971dd0..7ace813a 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -29,7 +29,7 @@ const CAT_SORT = { const CONFIG_STATE:unknown = { 'VSync': ['vsyncValue', 'VsyncGeneratior'], 'Start&Finish Trace Category': ['catValue', 'Business first'], - 'Hangs': ['hangsSelect', 'Instant'], + 'Hanging Detection': ['hangsSelect', 'Instant'], }; @element('sp-flags') @@ -171,7 +171,7 @@ export class SpFlags extends BaseElement { configDiv.appendChild(configFooterDiv); } - if (config.title === 'Hangs') { + if (config.title === 'Hanging Detection') { let configFooterDiv = this.createHangsOption(); configDiv.appendChild(configFooterDiv); } @@ -255,7 +255,7 @@ export class SpFlags extends BaseElement { let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); let flagsItemJson = JSON.parse(flagsItem!); - let hangs = flagsItemJson['Hangs']; + let hangs = flagsItemJson['Hanging Detection']; if (hangs === 'Enabled') { hangsTypeEl.removeAttribute('disabled'); } else { @@ -317,9 +317,9 @@ export class FlagsConfig { addInfo: { vsyncValue: VSYNC_VAL.VsyncGeneratior }, }, { - title: 'Hangs', + title: 'Hanging Detection', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], - describeContent: '', + describeContent: 'hangs type:Instant(33ms~100ms),Circumstantial(100ms~250ms),Micro(250ms~500ms),Severe(>=500ms)', }, { title: 'LTPO', diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index 41594ebe..0b08083c 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -189,7 +189,7 @@ export class SpChartManager { await this.spHiSysEvent.init(); let idAndNameArr = await queryDmaFenceIdAndCat(); this.handleDmaFenceName(idAndNameArr as { id: number; cat: string; seqno: number; driver: string; context: string }[]); - if (FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { + if (FlagsConfig.getFlagsConfigEnableStatus("Hanging Detection")) { progress('Hang init', 80); await this.hangChart.init(); } diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index ea5af6f0..f210da53 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -878,7 +878,7 @@ export class SpProcessChart { funcRow: TraceRow, thread: unknown ): void { - if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus("Hangs")) { + if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus("Hanging Detection")) { //@ts-ignore if (data.pid === thread.tid) { let hangsRow = TraceRow.skeleton(); -- Gitee From d48c91b380e61c08bfcde730ab3ea59cb5f933bc Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 29 Aug 2024 19:29:04 +0800 Subject: [PATCH 71/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9flags=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E7=8A=B6=E6=80=81=E6=9C=AA=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/SpFlags.ts | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index f9ff0958..04b88774 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -72,8 +72,13 @@ export class SpFlags extends BaseElement { configSelect.appendChild(configOption); }); configSelect.addEventListener('change', () => { - if (configSelect.title === 'VSync' || configSelect.title === 'Start&Finish Trace Category') { - this.flagSelectListener(configSelect); + // @ts-ignore + let title = configSelect.getAttribute('title'); + // @ts-ignore + FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); + //@ts-ignore + if (CONFIG_STATE[config.title]) { + this.flagSelectListener(configSelect, title); } if (configSelect.title === 'AI') { let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); @@ -103,14 +108,9 @@ export class SpFlags extends BaseElement { configDiv.appendChild(description); } //监听flag-select的状态选择 - private flagSelectListener(configSelect: HTMLSelectElement): void { - // @ts-ignore - let title = configSelect.getAttribute('title'); - + private flagSelectListener(configSelect: HTMLSelectElement, title: string | null): void { //@ts-ignore let listSelect = this.shadowRoot?.querySelector(`#${CONFIG_STATE[title]?.[0]}`); - // @ts-ignore - FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); //@ts-ignore if (listSelect) { // @ts-ignore @@ -226,11 +226,11 @@ export class SpFlags extends BaseElement { private createPersonOption(list: unknown, key: string, defaultKey: string, parentOption: string): HTMLDivElement { let configFooterDiv = document.createElement('div'); configFooterDiv.className = 'config_footer'; - let vsyncLableEl = document.createElement('lable'); - vsyncLableEl.className = 'list_lable'; - let vsyncTypeEl = document.createElement('select'); - vsyncTypeEl.setAttribute('id', key); - vsyncTypeEl.className = 'flag-select'; + let lableEl = document.createElement('lable'); + lableEl.className = 'list_lable'; + let typeEl = document.createElement('select'); + typeEl.setAttribute('id', key); + typeEl.className = 'flag-select'; //根据给出的list遍历添加option下来选框 // @ts-ignore for (let k of Object.keys(list)) { @@ -243,24 +243,24 @@ export class SpFlags extends BaseElement { option.selected = true; FlagsConfig.updateFlagsConfig(key, option.value); } - vsyncTypeEl.appendChild(option); + typeEl.appendChild(option); } - vsyncTypeEl.addEventListener('change', function () { + typeEl.addEventListener('change', function () { let selectValue = this.selectedOptions[0].value; FlagsConfig.updateFlagsConfig(key, selectValue); }); let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); let flagsItemJson = JSON.parse(flagsItem!); - let vsync = flagsItemJson[parentOption]; - if (vsync === 'Enabled') { - vsyncTypeEl.removeAttribute('disabled'); + let state = flagsItemJson[parentOption]; + if (state === 'Enabled') { + typeEl.removeAttribute('disabled'); } else { - vsyncTypeEl.setAttribute('disabled', 'disabled'); + typeEl.setAttribute('disabled', 'disabled'); FlagsConfig.updateFlagsConfig(key, defaultKey); } - configFooterDiv.appendChild(vsyncLableEl); - configFooterDiv.appendChild(vsyncTypeEl); + configFooterDiv.appendChild(lableEl); + configFooterDiv.appendChild(typeEl); return configFooterDiv; } -- Gitee From 2aeb20927229afdba9fb4fb4af2013b386784c30 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 29 Aug 2024 19:42:05 +0800 Subject: [PATCH 72/85] =?UTF-8?q?feat:=20=E7=82=B9=E5=87=BBhang=E5=9D=97?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E8=87=B3=E5=AF=B9=E5=BA=94=E7=9A=84func?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/SpFlags.ts | 8 +- .../trace/component/SpSystemTrace.event.ts | 14 +++- ide/src/trace/component/SpSystemTrace.ts | 4 + .../trace/component/chart/SpChartManager.ts | 2 +- .../trace/component/chart/SpProcessChart.ts | 2 +- .../trace/component/trace/base/TraceSheet.ts | 4 +- .../trace/sheet/TabPaneCurrentSelection.ts | 80 +++++++++---------- .../database/data-trafic/HangDataReceiver.ts | 1 + .../database/ui-worker/ProcedureWorkerHang.ts | 21 ++++- 9 files changed, 84 insertions(+), 52 deletions(-) diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index 7ace813a..bc8e93d5 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -29,7 +29,7 @@ const CAT_SORT = { const CONFIG_STATE:unknown = { 'VSync': ['vsyncValue', 'VsyncGeneratior'], 'Start&Finish Trace Category': ['catValue', 'Business first'], - 'Hanging Detection': ['hangsSelect', 'Instant'], + 'Hangs Detection': ['hangsSelect', 'Instant'], }; @element('sp-flags') @@ -171,7 +171,7 @@ export class SpFlags extends BaseElement { configDiv.appendChild(configFooterDiv); } - if (config.title === 'Hanging Detection') { + if (config.title === 'Hangs Detection') { let configFooterDiv = this.createHangsOption(); configDiv.appendChild(configFooterDiv); } @@ -255,7 +255,7 @@ export class SpFlags extends BaseElement { let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); let flagsItemJson = JSON.parse(flagsItem!); - let hangs = flagsItemJson['Hanging Detection']; + let hangs = flagsItemJson['Hangs Detection']; if (hangs === 'Enabled') { hangsTypeEl.removeAttribute('disabled'); } else { @@ -317,7 +317,7 @@ export class FlagsConfig { addInfo: { vsyncValue: VSYNC_VAL.VsyncGeneratior }, }, { - title: 'Hanging Detection', + title: 'Hangs Detection', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], describeContent: 'hangs type:Instant(33ms~100ms),Circumstantial(100ms~250ms),Micro(250ms~500ms),Severe(>=500ms)', }, diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index ac2e6acc..0c8ed206 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -177,6 +177,18 @@ function scrollToFuncHandlerFunc(sp: SpSystemTrace): Function { return funClickHandle; } +function scrollToFunc(sp: SpSystemTrace): Function { + let funClickHandle = (funcStruct: unknown): void => { + // @ts-ignore + if (funcStruct.chainId) { + } + sp.observerScrollHeightEnable = true; + // @ts-ignore + sp.scrollToActFunc(funcStruct, false); + }; + return funClickHandle; +} + function jankClickHandlerFunc(sp: SpSystemTrace): Function { let jankClickHandler = (d: unknown): void => { sp.observerScrollHeightEnable = true; @@ -369,7 +381,7 @@ function allStructOnClick(clickRowType: string, sp: SpSystemTrace, row?: TraceRo .then(() => CpuStateStructOnClick(clickRowType, sp, entry as CpuStateStruct)) .then(() => CpuFreqLimitsStructOnClick(clickRowType, sp, entry as CpuFreqLimitsStruct)) .then(() => ClockStructOnClick(clickRowType, sp, entry as ClockStruct)) - .then(() => HangStructOnClick(clickRowType, sp)) + .then(() => HangStructOnClick(clickRowType, sp, scrollToFunc(sp))) .then(() => DmaFenceStructOnClick(clickRowType, sp, entry as DmaFenceStruct)) .then(() => SnapshotStructOnClick(clickRowType, sp, row as TraceRow, entry as SnapshotStruct)) .then(() => IrqStructOnClick(clickRowType, sp, entry as IrqStruct)) diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index b431143b..5ba6661e 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -1436,6 +1436,10 @@ export class SpSystemTrace extends BaseElement { TraceRow.ROW_TYPE_VM_TRACKER_SMAPS, (): boolean => SnapshotStruct.hoverSnapshotStruct !== null && SnapshotStruct.hoverSnapshotStruct !== undefined, ], + [ + TraceRow.ROW_TYPE_HANG, + (): boolean => HangStruct.hoverHangStruct !== null && HangStruct.hoverHangStruct !== undefined, + ], ]); // @ts-ignore diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index 0b08083c..9295a627 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -189,7 +189,7 @@ export class SpChartManager { await this.spHiSysEvent.init(); let idAndNameArr = await queryDmaFenceIdAndCat(); this.handleDmaFenceName(idAndNameArr as { id: number; cat: string; seqno: number; driver: string; context: string }[]); - if (FlagsConfig.getFlagsConfigEnableStatus("Hanging Detection")) { + if (FlagsConfig.getFlagsConfigEnableStatus("Hangs Detection")) { progress('Hang init', 80); await this.hangChart.init(); } diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index f210da53..bcf03925 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -878,7 +878,7 @@ export class SpProcessChart { funcRow: TraceRow, thread: unknown ): void { - if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus("Hanging Detection")) { + if (this.hangProcessSet.has(data.pid!) && FlagsConfig.getFlagsConfigEnableStatus("Hangs Detection")) { //@ts-ignore if (data.pid === thread.tid) { let hangsRow = TraceRow.skeleton(); diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index 49545e60..be4d31af 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -684,8 +684,8 @@ export class TraceSheet extends BaseElement { ); displayMemData = (data: ProcessMemStruct): void => this.displayTab('current-selection').setMemData(data); - displayHangData = (data: HangStruct, sp: SpSystemTrace): Promise => - this.displayTab('current-selection').setHangData(data, sp); + displayHangData = (data: HangStruct, sp: SpSystemTrace, scrollCallback: Function): Promise => + this.displayTab('current-selection').setHangData(data, sp, scrollCallback); displayClockData = (data: ClockStruct): Promise => this.displayTab('current-selection').setClockData(data); displayDmaFenceData = (data: DmaFenceStruct, rowData: unknown): void =>//展示tab页内容 diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 95d56e31..efc69bfd 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -807,7 +807,7 @@ export class TabPaneCurrentSelection extends BaseElement { this.currentSelectionTbl!.dataSource = list; } - async setHangData(data: HangStruct, sp: SpSystemTrace): Promise { + async setHangData(data: HangStruct, sp: SpSystemTrace, scrollCallback: Function): Promise { await this.setRealTime(); this.setTableHeight('auto'); this.tabCurrentSelectionInit('Hang Details'); @@ -846,48 +846,48 @@ export class TabPaneCurrentSelection extends BaseElement { // @ts-ignore let startTimeAbsolute = (data.startNS || 0) + window.recordStartNS; this.addClickToTransfBtn(startTimeAbsolute, CLOCK_TRANSF_BTN_ID, CLOCK_STARTTIME_ABSALUTED_ID); - - let scrollIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#scroll-to-process'); - scrollIcon?.addEventListener('click', this.hangScrollHandler(data, sp)); + this.hangScrollHandler(data, sp, scrollCallback); } - private hangScrollHandler(data: HangStruct, sp: SpSystemTrace) { - return () => { - const rowId = `${data.pname ?? 'Process'} ${data.pid}`; - const rowParentId = `${data.pid}`; - const rowType = TraceRow.ROW_TYPE_HANG_INNER; - - let row = sp.rowsEL?.querySelector>(`trace-row[row-id='${rowParentId}'][folder]`); - if (row) { - row.expansion = true; - - const innerHangRow = row.childrenList.find((childRow) => childRow.rowType === TraceRow.ROW_TYPE_HANG_INNER) as TraceRow; - sp.currentRow = innerHangRow; - async function completeEntry(t: TabPaneCurrentSelection) { - if (!innerHangRow.dataListCache || innerHangRow.dataListCache.length == 0) { - await innerHangRow.supplierFrame!(); - } - - const findEntry = innerHangRow?.dataListCache.find((hangStruct) => { - return hangStruct.startNS === HangStruct.selectHangStruct?.startNS; - }); - - if (findEntry) { - HangStruct.selectHangStruct = findEntry; - t.setHangData(findEntry, sp); - } - sp.scrollToProcess(rowId, rowParentId, rowType); - sp.refreshCanvas(false); - } - if (innerHangRow.isComplete) { - completeEntry(this); - } - else { - sp.scrollToProcess(rowId, rowParentId, rowType); - innerHangRow.onComplete = () => completeEntry(this); - } + private hangScrollHandler(data: HangStruct, sp: SpSystemTrace, scrollCallback: Function) { + let scrollIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#scroll-to-process'); + scrollIcon?.addEventListener('click', async () => { + //@ts-ignore + let folderRow = sp.shadowRoot?.querySelector>( + `trace-row[row-id='${Utils.getDistributedRowId(data.pid)}'][row-type='process']` + ); + if (folderRow) { + folderRow.expansion = true; } - }; + let funcRow = sp.queryAllTraceRow>( + `trace-row[row-id='${Utils.getDistributedRowId(data.tid)}'][row-type='func']`, + (row) => row.rowId === `${data.tid}` && row.rowType === 'func' + )[0]; + sp.currentRow = funcRow; + if (!funcRow.dataListCache || funcRow.dataListCache.length === 0) { + funcRow.dataListCache = await funcRow.supplierFrame!(); + } + const findEntry = funcRow?.dataListCache.find((funcstruct: unknown) => { + //@ts-ignore + return (funcstruct.startTs === data.startNS && funcstruct.funName === data.content); + }) + scrollCallback({ + //@ts-ignore + pid: findEntry.pid, + //@ts-ignore + tid: findEntry.tid, + type: 'func', + //@ts-ignore + dur: findEntry.dur, + //@ts-ignore + depth: findEntry.depth, + //@ts-ignore + funName: findEntry.funName, + //@ts-ignore + startTs: findEntry.startTs, + keepOpen: true + }) + }) } setPerfToolsData(data: PerfToolStruct): void { diff --git a/ide/src/trace/database/data-trafic/HangDataReceiver.ts b/ide/src/trace/database/data-trafic/HangDataReceiver.ts index 699388fe..df715bd0 100644 --- a/ide/src/trace/database/data-trafic/HangDataReceiver.ts +++ b/ide/src/trace/database/data-trafic/HangDataReceiver.ts @@ -88,6 +88,7 @@ function arrayBufferHandler(data: any, res: HangSQLStruct[], transfer: boolean = id[i] = it.id; startNS[i] = it.startNS; dur[i] = it.dur; + tid[i] = it.tid; pid[i] = it.pid; }); diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts index 30843e1e..74d16630 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -60,11 +60,11 @@ export class HangRender extends Render { } } -export function HangStructOnClick(clickRowType: string, sp: SpSystemTrace): Promise { +export function HangStructOnClick(clickRowType: string, sp: SpSystemTrace, scrollCallback: Function): Promise { return new Promise((resolve, reject) => { if ((clickRowType === TraceRow.ROW_TYPE_HANG || clickRowType === TraceRow.ROW_TYPE_HANG_INNER) && HangStruct.hoverHangStruct) { HangStruct.selectHangStruct = HangStruct.hoverHangStruct; - sp.traceSheetEL?.displayHangData(HangStruct.selectHangStruct, sp); + sp.traceSheetEL?.displayHangData(HangStruct.selectHangStruct, sp, scrollCallback); sp.timerShaftEL?.modifyFlagList(undefined); reject(new Error()); } else { @@ -86,6 +86,7 @@ export class HangStruct extends BaseStruct { type: HangType | undefined; // 手动补充 按时间分类 pname: string | undefined; // 手动补充 content: string | undefined; // 手动补充 在tab页中需要手动解析内容 + name: string | undefined; static getFrameColor(data: HangStruct): string { return ({ @@ -115,7 +116,7 @@ export class HangStruct extends BaseStruct { drawString(ctx, `${data.type || ''}`, 1, data.frame, data); } - if (data === HangStruct.selectHangStruct) { + if (data === HangStruct.selectHangStruct && HangStruct.equals(HangStruct.selectHangStruct, data)) { ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.strokeRect( @@ -133,4 +134,18 @@ export class HangStruct extends BaseStruct { static isHover(data: HangStruct): boolean { return data === HangStruct.hoverHangStruct || data === HangStruct.selectHangStruct; } + static equals(d1: HangStruct, d2: HangStruct): boolean { + return ( + d1 && + d2 && + d1.pid === d2.pid && + d1.tid === d2.tid && + d1.pname === d2.pname && + d1.startNS === d2.startNS && + d1.dur === d2.dur && + d1.type === d2.type && + d1.id === d2.id && + d1.name === d2.name + ); + } } -- Gitee From fe8738b61538e983633501b050154dc6d497181f Mon Sep 17 00:00:00 2001 From: zhangyan Date: Thu, 29 Aug 2024 20:24:04 +0800 Subject: [PATCH 73/85] =?UTF-8?q?=E6=95=B4=E5=90=88=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/SpFlags.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index 04b88774..9cceaa36 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -72,14 +72,7 @@ export class SpFlags extends BaseElement { configSelect.appendChild(configOption); }); configSelect.addEventListener('change', () => { - // @ts-ignore - let title = configSelect.getAttribute('title'); - // @ts-ignore - FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); - //@ts-ignore - if (CONFIG_STATE[config.title]) { - this.flagSelectListener(configSelect, title); - } + this.flagSelectListener(configSelect); if (configSelect.title === 'AI') { let userIdInput: HTMLInputElement | null | undefined = this.shadowRoot?.querySelector('#user_id_input'); let xiaoLubanEl: Element | null | undefined = document.querySelector('sp-application')?.shadowRoot?.querySelector('#sp-bubbles') @@ -108,9 +101,13 @@ export class SpFlags extends BaseElement { configDiv.appendChild(description); } //监听flag-select的状态选择 - private flagSelectListener(configSelect: HTMLSelectElement, title: string | null): void { + private flagSelectListener(configSelect: HTMLSelectElement): void { + // @ts-ignore + let title = configSelect.getAttribute('title'); //@ts-ignore let listSelect = this.shadowRoot?.querySelector(`#${CONFIG_STATE[title]?.[0]}`); + // @ts-ignore + FlagsConfig.updateFlagsConfig(title!, configSelect.selectedOptions[0].value); //@ts-ignore if (listSelect) { // @ts-ignore -- Gitee From 9b2eea0e568309309b901df575041d0da17d6919 Mon Sep 17 00:00:00 2001 From: liufei Date: Fri, 30 Aug 2024 09:42:18 +0800 Subject: [PATCH 74/85] =?UTF-8?q?fix:call=20info=20tab=E9=A1=B5=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=A1=86=E8=BF=9E=E7=BB=AD=E6=90=9C=E7=B4=A2=E4=B8=A4?= =?UTF-8?q?=E6=AC=A1=E7=9B=B8=E5=90=8C=E5=86=85=E5=AE=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- .../trace/sheet/native-memory/TabPaneNMCallTree.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts index e6630e6a..dc21ffc1 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMCallTree.ts @@ -476,12 +476,6 @@ export class TabpaneNMCalltree extends BaseElement { ) { this.switchFlameChart(nmCallTreeData); } else { - this.nmCallTreeSource = []; - // 树状图数据清空 - this.nmCallTreeTbl!.recycleDataSource = []; - // 火焰图数据清空 - this.nmCallTreeFrameChart!.data = []; - this.switchFlameChart(nmCallTreeData); this.initGetFilterByNMCallTreeFilter(nmCallTreeData); } } -- Gitee From c51ca5c517e83f5ff24e5d39eff67bfc28651b1e Mon Sep 17 00:00:00 2001 From: zhangyan Date: Fri, 30 Aug 2024 15:05:25 +0800 Subject: [PATCH 75/85] =?UTF-8?q?feat:=E5=AF=BC=E5=85=A5=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=8C=B9=E9=85=8Dfunc=E3=80=81thread=E3=80=81mem=E5=B9=B6?= =?UTF-8?q?=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/bean/BoxSelection.ts | 7 +- ide/src/trace/component/SpSystemTrace.init.ts | 6 +- ide/src/trace/component/SpSystemTrace.ts | 19 +- .../trace/component/chart/SpChartManager.ts | 8 +- .../chart/SpImportUserPluginsChart.ts | 242 ++++++++++++++++++ .../trace/component/trace/base/TraceRow.ts | 4 + .../trace/sheet/frequsage/TabPaneFreqUsage.ts | 2 +- .../data-trafic/process/FuncDataSender.ts | 2 +- .../data-trafic/process/ThreadDataSender.ts | 2 +- .../database/ui-worker/ProcedureWorkerFunc.ts | 9 +- 10 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 ide/src/trace/component/chart/SpImportUserPluginsChart.ts diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index 2221bcd5..cac94400 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -226,7 +226,8 @@ export class SelectionParam { // @ts-ignore pushProcess(it: TraceRow, sp: SpSystemTrace): void { - if (it.rowType === TraceRow.ROW_TYPE_PROCESS) { + 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.getAttribute('hasStartup') === 'true') { this.startup = true; @@ -322,7 +323,7 @@ export class SelectionParam { pushFunc(it: TraceRow, sp: SpSystemTrace): void { if (it.rowType === TraceRow.ROW_TYPE_FUNC) { TabPaneTaskFrames.TaskArray = []; - sp.pushPidToSelection(this, it.rowParentId!); + sp.pushPidToSelection(this, it.rowParentId!, it.protoPid); if (it.asyncFuncName) { if (typeof it.asyncFuncName === 'string') { this.funAsync.push({ @@ -1015,7 +1016,7 @@ export class SelectionParam { // @ts-ignore pushThread(it: TraceRow, sp: SpSystemTrace): void { if (it.rowType === TraceRow.ROW_TYPE_THREAD) { - sp.pushPidToSelection(this, it.rowParentId!); + sp.pushPidToSelection(this, it.rowParentId!, it.protoPid); if (it.dataListCache && it.dataListCache.length) { //@ts-ignore let hiTid = it.dataListCache[0]!.tid; diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index b4d0f6e0..85b95e18 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -416,8 +416,10 @@ function collectHandlerYes(sp: SpSystemTrace, currentRow: unknown, event: unknow // @ts-ignore !currentRow.name.includes(parentRow.name) ) { - // @ts-ignore - currentRow.name += `(${parentRow.name})`; + //@ts-ignore + currentRow.name = currentRow.protoParentId ? `${currentRow.name} (${currentRow.protoParentId})` : + //@ts-ignore + `${currentRow.name} (${parentRow.name})` } }); } diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index e181e38f..1477ca1b 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -375,13 +375,22 @@ export class SpSystemTrace extends BaseElement { } } - pushPidToSelection(selection: SelectionParam, id: string): void { - let pid = parseInt(id); - if (!isNaN(pid)) { - if (!selection.processIds.includes(pid)) { - selection.processIds.push(pid); + pushPidToSelection(selection: SelectionParam, id: string, originalId?: string | Array): void { + let add = (it: string) => { + let pid = parseInt(it ? it : id); + if (!isNaN(pid!)) { + if (!selection.processIds.includes(pid!)) { + selection.processIds.push(pid!); + } } } + if (Array.isArray(originalId)) { + originalId.forEach(item => { + add(item); + }) + } else { + add(originalId!); + } } // @ts-ignore getCollectRows(condition: (row: TraceRow) => boolean): Array> { diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index 0b08083c..92bdcfc2 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -57,7 +57,8 @@ import { SpBpftraceChart } from './SpBpftraceChart'; import { sliceSender } from '../../database/data-trafic/SliceSender'; import { BaseStruct } from '../../bean/BaseStruct'; import { SpGpuCounterChart } from './SpGpuCounterChart'; -import { SpUserFileChart } from './SpUserPluginChart' +import { SpUserFileChart } from './SpUserPluginChart'; +import { SpImportUserPluginsChart } from './SpImportUserPluginsChart' import { queryDmaFenceIdAndCat } from '../../database/sql/dmaFence.sql'; import { queryAllFuncNames } from '../../database/sql/Func.sql'; @@ -92,6 +93,7 @@ export class SpChartManager { private spPerfOutputDataChart: SpPerfOutputDataChart; private spGpuCounterChart: SpGpuCounterChart; private spUserFileChart: SpUserFileChart; + private spImportUserPluginsChart: SpImportUserPluginsChart; constructor(trace: SpSystemTrace) { this.trace = trace; @@ -120,7 +122,8 @@ export class SpChartManager { this.spBpftraceChart = new SpBpftraceChart(trace); this.spPerfOutputDataChart = new SpPerfOutputDataChart(trace); this.spGpuCounterChart = new SpGpuCounterChart(trace); - this.spUserFileChart = new SpUserFileChart(trace) + this.spUserFileChart = new SpUserFileChart(trace); + this.spImportUserPluginsChart = new SpImportUserPluginsChart(trace); } async initPreprocessData(progress: Function): Promise { progress('load data dict', 50); @@ -162,6 +165,7 @@ export class SpChartManager { } if (FlagsConfig.getFlagsConfigEnableStatus('UserPluginsRow')) { await this.spUserFileChart.init(null) + await this.spImportUserPluginsChart.init(); } if (FlagsConfig.getFlagsConfigEnableStatus('GpuCounter')) { await this.spGpuCounterChart.init([]); diff --git a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts new file mode 100644 index 00000000..da9ca601 --- /dev/null +++ b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SpSystemTrace } from '../SpSystemTrace'; +import { TraceRow } from '../trace/base/TraceRow'; +import { renders } from '../../database/ui-worker/ProcedureWorker'; +import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil'; +import { BaseStruct } from '../../bean/BaseStruct'; +import { folderSupplier, rowThreadHandler } from './SpChartManager'; +import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; +import { TraceRowConfig } from '../trace/base/TraceRowConfig'; +import { ProcessMemStruct } from '../../bean/ProcessMemStruct'; +import { MemRender } from '../../database/ui-worker/ProcedureWorkerMem'; +import { FuncRender, FuncStruct } from '../../database/ui-worker/ProcedureWorkerFunc'; +import { ThreadRender } from '../../database/ui-worker/ProcedureWorkerThread'; +const FOLD_HEIGHT = 24; +export class SpImportUserPluginsChart { + private trace: SpSystemTrace; + static userPluginData: []; + private traceId?: string | undefined; + + constructor(trace: SpSystemTrace) { + this.trace = trace; + } + + async init(traceId?: string) { + this.traceId = traceId; + //@ts-ignore + let folderRow = this.createFolderRow(this.traceId); + folderRow.rowId = 'UserPluginsRows'; + folderRow.rowType = TraceRow.ROW_TYPE_IMPORT; + folderRow.name = 'UserPluginsRows'; + folderRow.selectChangeHandler = this.trace.selectChangeHandler; + this.trace.rowsEL?.appendChild(folderRow); + } + + createFolderRow(traceId?: string): TraceRow { + let folder = TraceRow.skeleton(traceId); + folder.rowParentId = ''; + folder.folder = true; + folder.style.height = '40px'; + folder.rowHidden = folder.expansion; + folder.summaryProtoPid = []; + folder.setAttribute('children', ''); + folder.supplier = folderSupplier(); + folder.onThreadHandler = folderThreadHandler(folder, this.trace); + folder.addRowSampleUpload(); + this.addTraceRowEventListener(folder); + return folder; + } + + /** + * 监听文件上传事件 + * @param row + * @param start_ts + */ + addTraceRowEventListener(row: TraceRow) { + row.uploadEl?.addEventListener('sample-file-change', (e: unknown) => { + this.getJsonData(e).then((res: unknown) => { + if (row.childrenList.length) { this.handleDynamicRowList(row); } + //@ts-ignore + if (res && res.data) { + let len = TraceRowConfig.allTraceRowList.length; + //@ts-ignore + res.data.forEach(item => { + //支持mem,thread, func + if (![TraceRow.ROW_TYPE_MEM, TraceRow.ROW_TYPE_THREAD, TraceRow.ROW_TYPE_FUNC].includes(item.rowType)) { + return; + } + for (let index = 0; index < len - 1; index++) { + const element = TraceRowConfig.allTraceRowList[index]; + if (element.rowType === item.rowType && element.name === item.threadName) { + //@ts-ignore + let parentRows = this.trace.shadowRoot?.querySelectorAll>(`trace-row[row-id='${element.rowParentId}']`); + let childRow = TraceRow.skeleton(); + //@ts-ignore + childRow.rowId = element.rowId; + childRow.rowType = element.rowType; + childRow.protoParentId = parentRows?.[0].name; + childRow.protoPid = element.rowParentId!; + childRow.rowParentId = 'UserPluginsRows'; + childRow.rowHidden = element.rowHidden; + childRow.style.width = childRow.style.width; + childRow.style.height = element.style.height; + childRow.enableCollapseChart(FOLD_HEIGHT, this.trace); + //@ts-ignore + childRow.name = element.name; + childRow.setAttribute('children', ''); + childRow.needRefresh = true; + childRow.favoriteChangeHandler = element.favoriteChangeHandler; + childRow.selectChangeHandler = element.selectChangeHandler; + this.addDrawAttributes(item, childRow, element); + row.summaryProtoPid!.push(childRow.protoPid); + row.addChildTraceRow(childRow); + } + } + }) + row.expansion = true; + } + this.trace.refreshCanvas(false) + }) + }) + } + //清空row-parent-id='UserPluginsRows'动态添加的子Row的list数据 + handleDynamicRowList(row: TraceRow) { + row.summaryProtoPid = []; + // 使用querySelectorAll找到所有row-parent-id='UserPluginsRows'的div元素 + //@ts-ignore + let childRows: Array> = [ + //@ts-ignore + ...this.trace.shadowRoot!.querySelectorAll>(`trace-row[row-parent-id='UserPluginsRows']`), + ]; + // 遍历这些元素 + childRows.forEach((div) => { + // 如果等于,则从父节点中删除该元素 + if (div.parentNode) { + div.parentNode.removeChild(div); + } + }); + //删除上一次导入file添加的row + TraceRowConfig.allTraceRowList = TraceRowConfig.allTraceRowList.filter(item => item.rowParentId !== 'UserPluginsRows'); + row.expansion = false; + row.childrenList = []; + } + + addDrawAttributes(item: { rowType: string, threadName: string }, childRow: TraceRow, element: unknown) { + //@ts-ignore + if (element.supplier) { + childRow.supplier = async () => { + //@ts-ignore + let res = await element.supplier!(); + return res + } + //@ts-ignore + } else if (element.supplierFrame) { + childRow.supplierFrame = async () => { + //@ts-ignore + let res = await element.supplierFrame!(); + return res + } + } + if (item.rowType === TraceRow.ROW_TYPE_MEM) {//处理mem + childRow.findHoverStruct = (): void => { + //@ts-ignore + ProcessMemStruct.hoverProcessMemStruct = childRow.getHoverStruct(false); + }; + childRow.focusHandler = (): void => { + if (childRow.hoverY <= 5 || childRow.hoverY >= 35) { + ProcessMemStruct.hoverProcessMemStruct = undefined; + } + this.trace.displayTip( + childRow, + ProcessMemStruct.hoverProcessMemStruct, + `${ProcessMemStruct.hoverProcessMemStruct?.value || '0'}` + ); + }; + childRow.onThreadHandler = rowThreadHandler('mem', 'context',//@ts-ignore + { type: `mem ${element.rowId} ${element.name}` }, childRow, this.trace); + } else if (item.rowType === TraceRow.ROW_TYPE_FUNC) {//处理func + //@ts-ignore + if (element.asyncFuncName) { + //@ts-ignore + childRow.asyncFuncName = element.asyncFuncName; + //@ts-ignore + childRow.asyncFuncNamePID = element.asyncFuncNamePID; + //@ts-ignore + childRow.asyncFuncStartTID = element.asyncFuncStartTID; + } + childRow.findHoverStruct = (): void => { + //@ts-ignore + FuncStruct.hoverFuncStruct = childRow.getHoverStruct(); + }; + childRow.onThreadHandler = rowThreadHandler('func', 'context',//@ts-ignore + { type: '' }, childRow, this.trace); + } else if (item.rowType === TraceRow.ROW_TYPE_THREAD) {//处理thread + childRow.onThreadHandler = rowThreadHandler('thread', 'context', + { type: '', translateY: childRow.translateY }, childRow, this.trace); + } + } + /** + * 获取上传的文件内容 转为json格式 + * @param file + * @returns + */ + getJsonData(file: unknown): Promise { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + //@ts-ignore + reader.readAsText(file.detail || file); + reader.onloadend = (e: unknown) => { + //@ts-ignore + const fileContent = e.target?.result; + try { + resolve(JSON.parse(fileContent)); + document.dispatchEvent( + new CustomEvent('file-correct') + ) + SpStatisticsHttpUtil.addOrdinaryVisitAction({ + event: 'seach-row', + action: 'seach-row', + }); + } catch (error) { + document.dispatchEvent( + new CustomEvent('file-error') + ) + } + } + }) + } +} +export const folderThreadHandler = (row: TraceRow, trace: SpSystemTrace) => { + return (useCache: boolean): void => { + row.canvasSave(trace.canvasPanelCtx!); + if (row.expansion) { + // @ts-ignore + trace.canvasPanelCtx?.clearRect(0, 0, row.frame.width, row.frame.height); + } else { + (renders.empty as EmptyRender).renderMainThread( + { + context: trace.canvasPanelCtx, + useCache: useCache, + type: '', + }, + row + ); + } + row.canvasRestore(trace.canvasPanelCtx!, trace); + }; +}; + diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index 0ab1d11f..005cc61e 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -59,6 +59,7 @@ export class TraceRow extends HTMLElement { static ROW_TYPE_CPU_FREQ = 'cpu-freq'; static ROW_TYPE_CPU_FREQ_LIMIT = 'cpu-limit-freq'; static ROW_TYPE_CPU_FREQ_ALL = 'cpu-frequency'; + static ROW_TYPE_IMPORT = 'import-match-file'; static ROW_TYPE_CPU_STATE_ALL = 'cpu-State'; static ROW_TYPE_CPU_FREQ_LIMITALL = 'cpu-frequency-limit'; static ROW_TYPE_FPS = 'fps'; @@ -216,6 +217,9 @@ export class TraceRow extends HTMLElement { static ROW_TYPE_LTPO: string | null | undefined; static ROW_TYPE_HITCH_TIME: string | null | undefined; asyncFuncStartTID!: number | undefined; + protoParentId: string | null | undefined; + protoPid: string | undefined; + summaryProtoPid: Array | undefined; constructor( args: { diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts index a7c899f6..dac1df4c 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts @@ -189,7 +189,7 @@ export class TabPaneFreqUsage extends BaseElement { - + diff --git a/ide/src/trace/database/data-trafic/process/FuncDataSender.ts b/ide/src/trace/database/data-trafic/process/FuncDataSender.ts index 58d296b2..3e3350f7 100644 --- a/ide/src/trace/database/data-trafic/process/FuncDataSender.ts +++ b/ide/src/trace/database/data-trafic/process/FuncDataSender.ts @@ -21,7 +21,7 @@ export function funcDataSender(tid: number, ipid: number, row: TraceRow { let trafic: number = TraficEnum.Memory; - let width = row.clientWidth - CHART_OFFSET_LEFT; + 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), diff --git a/ide/src/trace/database/data-trafic/process/ThreadDataSender.ts b/ide/src/trace/database/data-trafic/process/ThreadDataSender.ts index d88eff72..13d16a5f 100644 --- a/ide/src/trace/database/data-trafic/process/ThreadDataSender.ts +++ b/ide/src/trace/database/data-trafic/process/ThreadDataSender.ts @@ -24,7 +24,7 @@ export function threadDataSender( traceId?: string ): Promise { let trafic: number = TraficEnum.Memory; - let width = row.clientWidth - CHART_OFFSET_LEFT; + let width = row.clientWidth || row.parentRowEl!.clientWidth - CHART_OFFSET_LEFT; if (trafic === TraficEnum.SharedArrayBuffer && !row.sharedArrayBuffers) { row.sharedArrayBuffers = { startTime: new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * MAX_COUNT), diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts index e84cc877..d4f2d27e 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts @@ -45,7 +45,8 @@ export class FuncRender { TraceRow.range!.totalNS, row.frame, req.useCache || !TraceRow.range!.refresh, - row.funcExpand + row.funcExpand, + row.rowParentId ); drawLoadingFrame(req.context, funcFilter, row, true); req.context.beginPath(); @@ -91,9 +92,13 @@ export function func( totalNS: number, frame: Rect, use: boolean, - expand: boolean + expand: boolean, + rowParentId: string | null | undefined ): void { if (use && funcFilter.length > 0) { + if (rowParentId === "UserPluginsRows" && !expand) { + funcFilter = funcFilter.filter((it) => it.depth === 0) + } for (let i = 0, len = funcFilter.length; i < len; i++) { if ((funcFilter[i].startTs || 0) + (funcFilter[i].dur || 0) >= startNS && (funcFilter[i].startTs || 0) <= endNS) { FuncStruct.setFuncFrame(funcFilter[i], 0, startNS, endNS, totalNS, frame); -- Gitee From 77d09230fc3ae5a6dcd6469640562a1a1f9b3817 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Fri, 30 Aug 2024 16:22:19 +0800 Subject: [PATCH 76/85] =?UTF-8?q?feat:=E9=80=82=E9=85=8Dgh=E6=B3=B3?= =?UTF-8?q?=E9=81=93=E7=9A=84=E6=A1=86=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/chart/SpImportUserPluginsChart.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts index da9ca601..de06578d 100644 --- a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts +++ b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts @@ -178,6 +178,13 @@ export class SpImportUserPluginsChart { //@ts-ignore childRow.asyncFuncStartTID = element.asyncFuncStartTID; } + //@ts-ignore + if (element.asyncFuncThreadName) { + //@ts-ignore + childRow.asyncFuncThreadName = element.asyncFuncThreadName; + //@ts-ignore + childRow.asyncFuncNamePID = element.asyncFuncNamePID; + } childRow.findHoverStruct = (): void => { //@ts-ignore FuncStruct.hoverFuncStruct = childRow.getHoverStruct(); -- Gitee From 1ddcce686eb66fb7b50696066ff640ad3e9cf5e6 Mon Sep 17 00:00:00 2001 From: zhangyan Date: Sat, 31 Aug 2024 15:14:04 +0800 Subject: [PATCH 77/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9Flagshang=E4=BA=8C?= =?UTF-8?q?=E7=BA=A7=E4=B8=8B=E6=8B=89=E9=80=89=E6=A1=86=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=BA=E4=BF=9D=E5=AD=98=E5=8F=8A=E4=BB=A3=E7=A0=81=E6=95=B4?= =?UTF-8?q?=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/SpFlags.ts | 138 +++++++++++------------------ 1 file changed, 50 insertions(+), 88 deletions(-) diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index 0a8789d4..0f8e2fc9 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -15,21 +15,31 @@ import { BaseElement, element } from '../../base-ui/BaseElement'; import { SpFlagHtml } from './SpFlag.html'; -const VSYNC_VAL = { - 'VsyncGeneratior': 'H:VsyncGenerator', - 'Vsync-rs': 'H:rs_SendVsync', - 'Vsync-app': 'H:app_SendVsync' -}; - -const CAT_SORT = { - 'Business first': 'business', - 'Thread first': 'thread' -}; +const NUM = '000000'; +//vsync二级下拉选框对应的value和content +const VSYNC_CONTENT = [ + { value: 'H:VsyncGenerator', content: "VsyncGeneratior" }, + { value: 'H:rs_SendVsync', content: 'Vsync-rs' }, + { value: 'H:rs_SendVsync', content: 'Vsync-app' } +] +//cat二级下拉选框对应的value和content +const CAT_CONTENT = [ + { value: 'business', content: "Business first" }, + { value: 'thread', content: 'Thread first' } +] +//hang二级下拉选框对应的value和content +const HANG_CONTENT = [ + { value: `33${NUM}`, content: "Instant" }, + { value: `100${NUM}`, content: 'Circumstantial' }, + { value: `250${NUM}`, content: 'Micro' }, + { value: `500${NUM}`, content: 'Severe' } +] +//整合默认值 const CONFIG_STATE:unknown = { 'VSync': ['vsyncValue', 'VsyncGeneratior'], 'Start&Finish Trace Category': ['catValue', 'Business first'], - 'Hangs Detection': ['hangsSelect', 'Instant'], + 'Hangs Detection': ['hangValue', 'Instant'], }; @element('sp-flags') @@ -173,14 +183,22 @@ export class SpFlags extends BaseElement { configFooterDiv.appendChild(deviceHeightEl); configDiv.appendChild(configFooterDiv); } + //@ts-ignore + let configKey = CONFIG_STATE[config.title]?.[0]; + if (config.title === 'VSync') {//初始化Vsync + let configFooterDiv = this.createPersonOption(VSYNC_CONTENT, configKey, config); + configDiv.appendChild(configFooterDiv); + } - if (config.title === 'VSync') { - //@ts-ignore - let configKey = CONFIG_STATE[config.title]?.[0]; - let configFooterDiv = this.createPersonOption(VSYNC_VAL, configKey, config.addInfo!.vsyncValue, config.title); + if (config.title === 'Start&Finish Trace Category') {//初始化Start&Finish Trace Category + let configFooterDiv = this.createPersonOption(CAT_CONTENT, configKey, config); configDiv.appendChild(configFooterDiv); } + if (config.title === 'Hangs Detection') {//初始化Hangs Detection + let configFooterDiv = this.createPersonOption(HANG_CONTENT, configKey, config); + configDiv.appendChild(configFooterDiv); + } if (config.title === 'AI') { let configFooterDiv = document.createElement('div'); configFooterDiv.className = 'config_footer'; @@ -203,24 +221,11 @@ export class SpFlags extends BaseElement { configFooterDiv.appendChild(userIdInputEl); configDiv.appendChild(configFooterDiv); } - - if (config.title === 'Start&Finish Trace Category') { - //@ts-ignore - let configKey = CONFIG_STATE[config.title]?.[0]; - let configFooterDiv = this.createPersonOption(CAT_SORT, configKey, config.addInfo!.catValue, config.title); - configDiv.appendChild(configFooterDiv); - } - - if (config.title === 'Hangs Detection') { - let configFooterDiv = this.createHangsOption(); - configDiv.appendChild(configFooterDiv); - } - this.bodyEl!.appendChild(configDiv); }); } - private createPersonOption(list: unknown, key: string, defaultKey: string, parentOption: string): HTMLDivElement { + private createPersonOption(list: unknown, key: string, config: unknown): HTMLDivElement { let configFooterDiv = document.createElement('div'); configFooterDiv.className = 'config_footer'; let lableEl = document.createElement('lable'); @@ -229,14 +234,14 @@ export class SpFlags extends BaseElement { typeEl.setAttribute('id', key); typeEl.className = 'flag-select'; //根据给出的list遍历添加option下来选框 - // @ts-ignore - for (let k of Object.keys(list)) { - let option = document.createElement('option'); // VsyncGeneratior = H:VsyncGenerator - // @ts-ignore - option.value = list[k]; - option.textContent = k; - // @ts-ignore - if (list[k] === defaultKey) { + //@ts-ignore + for (const settings of list) { + let option = document.createElement('option'); + option.value = settings.value; + option.textContent = settings.content; + //初始化二级按钮状态 + //@ts-ignore + if (option.value === config.addInfo?.[key]) { option.selected = true; FlagsConfig.updateFlagsConfig(key, option.value); } @@ -246,65 +251,21 @@ export class SpFlags extends BaseElement { let selectValue = this.selectedOptions[0].value; FlagsConfig.updateFlagsConfig(key, selectValue); }); - let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); let flagsItemJson = JSON.parse(flagsItem!); - let state = flagsItemJson[parentOption]; - if (state === 'Enabled') { + //@ts-ignore + let vsync = flagsItemJson[config.title]; + if (vsync === 'Enabled') { typeEl.removeAttribute('disabled'); } else { typeEl.setAttribute('disabled', 'disabled'); - FlagsConfig.updateFlagsConfig(key, defaultKey); + //@ts-ignore + FlagsConfig.updateFlagsConfig(key, config.addInfo?.[key]); } configFooterDiv.appendChild(lableEl); configFooterDiv.appendChild(typeEl); return configFooterDiv; } - - /// Flags新增Hangs下拉框 - private createHangsOption(): HTMLDivElement { - let configFooterDiv = document.createElement('div'); - configFooterDiv.className = 'config_footer'; - let hangsLableEl = document.createElement('lable'); - hangsLableEl.className = 'hangs_lable'; - let hangsTypeEl = document.createElement('select'); - hangsTypeEl.setAttribute('id', 'hangsSelect'); - hangsTypeEl.className = 'flag-select'; - - let hangOptions: Array = []; - for (const settings of [ - { value: '33', content: "Instant" }, - { value: '100', content: 'Circumstantial' }, - { value: '250', content: 'Micro' }, - { value: '500', content: 'Severe' } - ]) { - let hangOption = document.createElement('option'); - hangOption.value = settings.value + '000000'; - hangOption.textContent = settings.content; - hangOption.selected = false; - hangOptions.push(hangOption); - hangsTypeEl.appendChild(hangOption); - } - - FlagsConfig.updateFlagsConfig('hangValue', hangOptions[0].value); - hangOptions[0].selected = true; - hangsTypeEl.addEventListener('change', function () { - let selectValue = this.selectedOptions[0].value; - FlagsConfig.updateFlagsConfig('hangValue', selectValue); - }); - - let flagsItem = window.localStorage.getItem(FlagsConfig.FLAGS_CONFIG_KEY); - let flagsItemJson = JSON.parse(flagsItem!); - let hangs = flagsItemJson['Hangs Detection']; - if (hangs === 'Enabled') { - hangsTypeEl.removeAttribute('disabled'); - } else { - hangsTypeEl.setAttribute('disabled', 'disabled'); - } - configFooterDiv.appendChild(hangsLableEl); - configFooterDiv.appendChild(hangsTypeEl); - return configFooterDiv; - } } export type Params = { @@ -354,12 +315,13 @@ export class FlagsConfig { title: 'VSync', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], describeContent: 'VSync Signal drawing', - addInfo: { vsyncValue: VSYNC_VAL.VsyncGeneratior }, + addInfo: { vsyncValue: VSYNC_CONTENT[0].value }, }, { title: 'Hangs Detection', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], describeContent: 'hangs type:Instant(33ms~100ms),Circumstantial(100ms~250ms),Micro(250ms~500ms),Severe(>=500ms)', + addInfo: { hangValue: HANG_CONTENT[0].value }, }, { title: 'LTPO', @@ -370,7 +332,7 @@ export class FlagsConfig { title: 'Start&Finish Trace Category', switchOptions: [{ option: 'Enabled' }, { option: 'Disabled', selected: true }], describeContent: 'Asynchronous trace aggregation', - addInfo: { catValue: CAT_SORT['Business first'] }, + addInfo: { catValue: CAT_CONTENT[0].value }, }, { title: 'UserPluginsRow', -- Gitee From 522bdc69912d2ff09e8ba5f7ef2f2a582fc204cb Mon Sep 17 00:00:00 2001 From: zhangyan Date: Sat, 31 Aug 2024 18:21:18 +0800 Subject: [PATCH 78/85] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9binder=E7=82=B9?= =?UTF-8?q?=E9=80=89Tab=E9=A1=B5=E4=BF=A1=E6=81=AF=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangyan --- ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index efc69bfd..a2c07bb4 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -573,6 +573,7 @@ export class TabPaneCurrentSelection extends BaseElement { } ); } + list.push({ name: item.keyName, value: item.strValue }); }); if (binderSliceId === -1) { list.unshift({ -- Gitee From 4bc017c48a5a9fd10f3fc2e92b03aaa4d1223f2f Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 11:51:43 +0800 Subject: [PATCH 79/85] =?UTF-8?q?fix:=E6=B8=85=E9=99=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/SpApplication.ts | 4 +- .../trace/sheet/TabPaneCurrentSelection.ts | 17 +++-- .../trace/sheet/process/TabPaneSliceChild.ts | 62 +++++++++------- .../trace/sheet/process/TabPaneSlices.ts | 33 ++++----- .../trace/database/TabPaneFreqUsageWorker.ts | 66 ++++++++--------- .../ProcedureLogicWorkerNativeNemory.ts | 4 +- .../ProcedureLogicWorkerSchedulingAnalysis.ts | 8 +-- ide/src/trace/database/sql/Func.sql.ts | 72 +++++++++---------- ide/src/trace/database/sql/Memory.sql.ts | 50 +++++++------ .../trace/database/sql/ProcessThread.sql.ts | 30 ++++---- .../database/ui-worker/ProcedureWorkerFunc.ts | 4 +- 11 files changed, 184 insertions(+), 166 deletions(-) diff --git a/ide/src/trace/SpApplication.ts b/ide/src/trace/SpApplication.ts index 635efdf0..71fe515f 100644 --- a/ide/src/trace/SpApplication.ts +++ b/ide/src/trace/SpApplication.ts @@ -607,13 +607,13 @@ export class SpApplication extends BaseElement { let reader: FileReader | null = new FileReader(); reader.readAsText(typeStr); reader.onloadend = (event): void => { - let isIncludeMark = `${reader?.result}`.includes('MarkPositionJSON'); + let isIncludeMark = `${reader?.result}`.includes('MarkPositionJSON'); let typeHeader; if (isIncludeMark) { let markLength = `${reader?.result}`.split('->')[0].replace('MarkPositionJSON', ''); //@ts-ignore typeHeader = ev.slice(markLength.length + parseInt(markLength), markLength.length + parseInt(markLength) + 6); - } else{ + } else { //@ts-ignore typeHeader = ev.slice(0, 6); } diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts index 91a49807..cd42a631 100644 --- a/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -850,7 +850,7 @@ export class TabPaneCurrentSelection extends BaseElement { this.hangScrollHandler(data, sp, scrollCallback); } - private hangScrollHandler(data: HangStruct, sp: SpSystemTrace, scrollCallback: Function) { + private hangScrollHandler(data: HangStruct, sp: SpSystemTrace, scrollCallback: Function): void { let scrollIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#scroll-to-process'); scrollIcon?.addEventListener('click', async () => { //@ts-ignore @@ -1226,11 +1226,11 @@ export class TabPaneCurrentSelection extends BaseElement { }); } //点击事件获取唤醒链 - private getWakeupChainClickHandler(fromBean: WakeupBean | undefined, list: unknown[]) { + private getWakeupChainClickHandler(fromBean: WakeupBean | undefined, list: unknown[]): void { this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-top')?.addEventListener('click', async () => { this.topChainStr = ''; //@ts-ignore - let currentThread = list.filter((item) => item.name === "Thread")?.[0].value;//点击的当前线程 + let currentThread = list.filter((item) => item.name === 'Thread')?.[0].value;//点击的当前线程 let previouosWakeupThread = Utils.getInstance().getThreadMap().get(fromBean!.tid!) || 'Thread';//唤醒当前线程的上个线程 this.topChainStr = `-->${previouosWakeupThread}[${fromBean!.tid}]-->${currentThread}`; this.getRWakeUpChain(fromBean); @@ -1965,7 +1965,9 @@ export class TabPaneCurrentSelection extends BaseElement { let wakeupTs = wakeup[0].ts as number; let recordStartTs = Utils.getInstance().getRecordStartNS(); let wf = await queryThreadWakeUpFrom(data.id, wakeupTs); + // @ts-ignore if (wf && wf[0]) { + // @ts-ignore wb = wf[0]; if (wb !== null) { wb.wakeupTime = wakeupTs - recordStartTs; @@ -1990,7 +1992,9 @@ export class TabPaneCurrentSelection extends BaseElement { let wakeupTs = wakeup[0].ts as number; let recordStartTs = Utils.getInstance().getRecordStartNS(); let wf = await queryThreadWakeUpFrom(data.itid!, wakeupTs); + // @ts-ignore if (wf && wf[0]) { + // @ts-ignore wb = wf[0]; if (wb !== null) { wb.wakeupTime = wakeupTs - recordStartTs; @@ -2012,6 +2016,7 @@ export class TabPaneCurrentSelection extends BaseElement { let item; // @ts-ignore if (wakeUps !== undefined && wakeUps.length > 0) { + // @ts-ignore item = wakeUps[0]; } return item; @@ -2032,16 +2037,16 @@ export class TabPaneCurrentSelection extends BaseElement { return list; } //递归查找R唤醒链 - getRWakeUpChain(data: WakeupBean | undefined):void { + getRWakeUpChain(data: WakeupBean | undefined): void { this.getRWakeUpChainData(data).then((wakeupFrom: unknown) => { if (wakeupFrom === null) {//当查不到数据时,处理容器状态与样式,展示内容 let wakeupTopContent = this.currentSelectionTbl?.shadowRoot?.getElementById('wakeup-top-content'); let wakeupTopIcon = this.currentSelectionTbl?.shadowRoot?.querySelector('#wakeup-top'); wakeupTopContent!.innerText = 'idle' + this.topChainStr;//处理链顶部 - wakeupTopIcon!.style.display = 'none'; + wakeupTopIcon!.style.display = 'none'; wakeupTopContent!.style.display = 'block'; wakeupTopContent!.style.maxHeight = '100px';//设置最大高度,超出出现滚动条 - wakeupTopContent!.style.overflow = 'auto'; + wakeupTopContent!.style.overflow = 'auto'; return; } //@ts-ignore diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts index 2116a287..9e7e1aaa 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts @@ -24,9 +24,9 @@ import { getTabDetails, getGhDetails, getSfDetails } from '../../../../database/ export class TabPaneSliceChild extends BaseElement { private sliceChildTbl: LitTable | null | undefined; private boxChildSource: Array = []; - private sliceChildParam: {param: SliceBoxJumpParam, selection: SelectionParam} | null | undefined; + private sliceChildParam: { param: SliceBoxJumpParam, selection: SelectionParam } | null | undefined; - set data(boxChildValue: {param: SliceBoxJumpParam, selection: SelectionParam | null | undefined}) { + set data(boxChildValue: { param: SliceBoxJumpParam, selection: SelectionParam | null | undefined }) { //切换Tab页 保持childTab数据不变 除非重新点击跳转 if (boxChildValue === this.sliceChildParam || !boxChildValue.param.isJumpPage) { return; @@ -35,10 +35,10 @@ export class TabPaneSliceChild extends BaseElement { this.sliceChildParam = boxChildValue; this.sliceChildTbl!.recycleDataSource = []; //合并SF异步信息,相同pid和tid的name - let sfAsyncFuncMap:Map = new Map(); + let sfAsyncFuncMap: Map = new Map(); let filterSfAsyncFuncName = boxChildValue.selection!.funAsync; if (!boxChildValue.param.isSummary!) { - filterSfAsyncFuncName = filterSfAsyncFuncName.filter((item) => item.name === boxChildValue.param.name![0]) + filterSfAsyncFuncName = filterSfAsyncFuncName.filter((item) => item.name === boxChildValue.param.name![0]); } filterSfAsyncFuncName.forEach((it: { name: string; pid: number, tid: number | undefined }) => { if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) { @@ -49,15 +49,15 @@ export class TabPaneSliceChild extends BaseElement { name: [it.name], pid: it.pid, tid: it.tid - }) + }); } - }) + }); //@ts-ignore this.getDataByDB(boxChildValue, sfAsyncFuncMap, boxChildValue.selection!.funCatAsync); } initElements(): void { - this.sliceChildTbl = this.shadowRoot?.querySelector('#tb-slice-child'); + this.sliceChildTbl = this.shadowRoot?.querySelector('#tb-slice-child'); this.sliceChildTbl!.addEventListener('column-click', (evt): void => { // @ts-ignore this.sortByColumn(evt.detail); @@ -83,23 +83,23 @@ export class TabPaneSliceChild extends BaseElement { } getDataByDB( - val: {param: SliceBoxJumpParam, selection: SelectionParam}, - sfAsyncFuncMap: Map, - ghAsyncFunc:{ threadName: string; pid: number}[]): void { + val: { param: SliceBoxJumpParam, selection: SelectionParam }, + sfAsyncFuncMap: Map, + ghAsyncFunc: { threadName: string; pid: number }[]): void { //获取点击跳转,SF异步Func数据 - let result1 = () => { + let result1 = (): Array => { let promises: unknown[] = []; sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => { let res = await getSfDetails(item.name, item.pid, item.tid, val.param.leftNs, val.param.rightNs); if (res !== undefined && res.length > 0) { promises.push(...res); } - }) - return promises - } + }); + return promises; + }; //获取点击跳转,GH异步Func数据 - let result2 = () => { + let result2 = (): unknown => { let promises: unknown[] = []; ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => { let res = await getGhDetails(val.param.name!, item.threadName, item.pid, val.param.leftNs, val.param.rightNs); @@ -107,33 +107,41 @@ export class TabPaneSliceChild extends BaseElement { promises.push(...res); } }); - return promises - } + return promises; + }; //获取同步Func数据,同步Func数据 - let result3 = async () => { + let result3 = async (): Promise => { let promises: unknown[] = []; let res = await getTabDetails(val.param.name!, val.param.processId, val.param.threadId, val.param.leftNs, val.param.rightNs); if (res !== undefined && res.length > 0) { promises.push(...res); } - return promises - } + return promises; + }; this.sliceChildTbl!.loading = true; Promise.all([result1(), result2(), result3()]).then(res => { this.sliceChildTbl!.loading = false; - let result: any = (res[0] || []).concat(res[1] || []).concat(res[2] || []); + let result: unknown = (res[0] || []).concat(res[1] || []).concat(res[2] || []); this.sliceChildTbl!.loading = false; + // @ts-ignore if (result.length !== null && result.length > 0) { - result.map((e: any) => { + // @ts-ignore + result.map((e: unknown) => { + // @ts-ignore e.startTime = Utils.getTimeString(e.startNs); // @ts-ignore e.absoluteTime = ((window as unknown).recordStartNS + e.startNs) / 1000000000; + // @ts-ignore e.duration = e.duration / 1000000; + // @ts-ignore e.state = Utils.getEndState(e.state)!; + // @ts-ignore e.processName = `${e.process === undefined || e.process === null ? 'process' : e.process}[${e.processId}]`; + // @ts-ignore e.threadName = `${e.thread === undefined || e.thread === null ? 'thread' : e.thread}[${e.threadId}]`; }); + // @ts-ignore this.boxChildSource = result; if (this.sliceChildTbl) { // @ts-ignore @@ -144,9 +152,9 @@ export class TabPaneSliceChild extends BaseElement { if (this.sliceChildTbl) { // @ts-ignore this.sliceChildTbl.recycleDataSource = []; - } - } - }) + }; + } + }); } initHtml(): string { @@ -199,8 +207,8 @@ export class TabPaneSliceChild extends BaseElement { }; } //@ts-ignore - if (detail.key === 'startTime' || detail.key === 'processName'|| detail.key === 'threadName' ||//@ts-ignore - detail.key === 'name') { + if (detail.key === 'startTime' || detail.key === 'processName' || detail.key === 'threadName' ||//@ts-ignore + detail.key === 'name') { // @ts-ignore this.boxChildSource.sort(compare(detail.key, detail.sort, 'string'));// @ts-ignore } else if (detail.key === 'absoluteTime' || detail.key === 'duration') {// @ts-ignore diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts index 2790e37f..93151038 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSlices.ts @@ -54,9 +54,9 @@ export class TabPaneSlices extends BaseElement { name: [it.name], pid: it.pid, tid: it.tid - }) + }); } - }) + }); let filterNameEL: HTMLInputElement | undefined | null = this.shadowRoot?.querySelector('#filterName'); @@ -66,7 +66,7 @@ export class TabPaneSlices extends BaseElement { } }); - this.getSliceDb(slicesParam, filterNameEL, sfAsyncFuncMap, slicesParam.funCatAsync) + this.getSliceDb(slicesParam, filterNameEL, sfAsyncFuncMap, slicesParam.funCatAsync); } initElements(): void { @@ -114,21 +114,21 @@ export class TabPaneSlices extends BaseElement { slicesParam: SelectionParam, filterNameEL: HTMLInputElement | undefined | null, sfAsyncFuncMap: Map, - ghAsyncFunc: { threadName: string; pid: number }[]) { + ghAsyncFunc: { threadName: string; pid: number }[]): void { //获取SF异步Func数据 - let result1 = () => { + let result1 = (): Array => { let promises: unknown[] = []; sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => { let res = await getTabSlicesAsyncFunc(item.name, item.pid, item.tid, slicesParam.leftNs, slicesParam.rightNs); if (res !== undefined && res.length > 0) { promises.push(...res); } - }) - return promises - } + }); + return promises; + }; //获取GH异步Func数据 - let result2 = () => { + let result2 = (): Array => { let promises: unknown[] = []; ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => { let res = await getTabSlicesAsyncCatFunc(item.threadName, item.pid, slicesParam.leftNs, slicesParam.rightNs); @@ -136,18 +136,18 @@ export class TabPaneSlices extends BaseElement { promises.push(...res); } }); - return promises - } + return promises; + }; //获取同步Func数据 - let result3 = async () => { + let result3 = async (): Promise => { let promises: unknown[] = []; let res = await getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs); if (res !== undefined && res.length > 0) { promises.push(...res); } - return promises - } + return promises; + }; this.slicesTbl!.loading = true; Promise.all([result1(), result2(), result3()]).then(res => { @@ -177,7 +177,7 @@ export class TabPaneSlices extends BaseElement { processSlicesResultMap.set(processSliceItem.name, {//@ts-ignore ...processSliceItem, //@ts-ignore tabTitle: processSliceItem.name - }) + }); } } let processSlicesResultsValue = [...processSlicesResultMap.values()]; @@ -189,7 +189,8 @@ export class TabPaneSlices extends BaseElement { count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); count.occurrences = sumOcc; count.tabTitle = 'Summary'; - count.allName = processSlicesResultsValue.map((item: any) => item.name); + // @ts-ignore + count.allName = processSlicesResultsValue.map((item: unknown) => item.name); processSlicesResultsValue.splice(0, 0, count); //@ts-ignore this.slicesSource = processSlicesResultsValue; this.slicesTbl!.recycleDataSource = processSlicesResultsValue; diff --git a/ide/src/trace/database/TabPaneFreqUsageWorker.ts b/ide/src/trace/database/TabPaneFreqUsageWorker.ts index fc79b946..bd292559 100644 --- a/ide/src/trace/database/TabPaneFreqUsageWorker.ts +++ b/ide/src/trace/database/TabPaneFreqUsageWorker.ts @@ -38,7 +38,7 @@ function orgnazitionMap( let sum: number = 0; // 循环分组 for (let i = 0; i < args.runData.length; i++) { - let mapKey: string = args.runData[i].pid + "_" + args.runData[i].tid; + let mapKey: string = args.runData[i].pid + '_' + args.runData[i].tid; // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值 if (!result.has(mapKey)) { result.set(mapKey, new Array()); @@ -172,10 +172,11 @@ function returnObj( const FREQ_MUTIPLE: number = 1000; //@ts-ignore const computorPower: number = comPower ? comPower.get(item.cpu)?.mapData.get(cpuFreqData.value)! : 0; + let result; switch (flag) { case 1: - return { - thread: item.pid + "_" + item.tid, + result = { + thread: item.pid + '_' + item.tid, consumption: cpuFreqData.value * item.dur, cpu: item.cpu, frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, @@ -185,8 +186,8 @@ function returnObj( cpuload: (computorPower * item.dur) / (timeZones * maxCommpuPower) * PERCENT }; case 2: - return { - thread: item.pid + "_" + item.tid, + result = { + thread: item.pid + '_' + item.tid, consumption: cpuFreqData.value * (cpuFreqData.ts + cpuFreqData.dur - item.ts), cpu: item.cpu, frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, @@ -196,8 +197,8 @@ function returnObj( cpuload: (computorPower * (cpuFreqData.ts + cpuFreqData.dur - item.ts)) / (timeZones * maxCommpuPower) * PERCENT }; case 3: - return { - thread: item.pid + "_" + item.tid, + result = { + thread: item.pid + '_' + item.tid, consumption: cpuFreqData.value * (item.dur + item.ts - cpuFreqData.ts), cpu: item.cpu, frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, @@ -207,8 +208,8 @@ function returnObj( cpuload: (computorPower * (item.dur + item.ts - cpuFreqData.ts)) / (timeZones * maxCommpuPower) * PERCENT }; case 4: - return { - thread: item.pid + "_" + item.tid, + result = { + thread: item.pid + '_' + item.tid, consumption: cpuFreqData.value * cpuFreqData.dur, cpu: item.cpu, frequency: computorPower ? cpuFreqData.value / FREQ_MUTIPLE + ': ' + computorPower : cpuFreqData.value / FREQ_MUTIPLE, @@ -218,17 +219,18 @@ function returnObj( cpuload: (computorPower * cpuFreqData.dur) / (timeZones * maxCommpuPower) * PERCENT }; case 5: - return { - thread: item.pid + "_" + item.tid, + result = { + thread: item.pid + '_' + item.tid, consumption: 0, cpu: item.cpu, - frequency: "unknown", + frequency: 'unknown', dur: item.dur, percent: (item.dur / sum) * PERCENT, consumpower: 0, cpuload: 0 }; } + return result; } /** @@ -304,7 +306,7 @@ function dealTree( process.consumption += thread.consumption; process.consumpower += thread.consumpower; process.cpuload += thread.cpuload; - process.thread = process.thread! + key.split("_")[0]; + process.thread = process.thread! + key.split('_')[0]; result.push(process); }); for (let i = 0; i < result.length; i++) { @@ -331,7 +333,7 @@ function dealTree( */ function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData { return { - thread: flag ? "" : "P", + thread: flag ? '' : 'P', consumption: 0, cpu: cpu, frequency: -1, @@ -354,9 +356,9 @@ function fixTotal(arr: Array): Array { // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割 for (let i = 0; i < arr.length; i++) { // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据 - if (arr[i].thread?.indexOf("P") !== -1) { + if (arr[i].thread?.indexOf('P') !== -1) { arr[i].children = []; - arr[i].thread = arr[i].thread + "-summary data"; + arr[i].thread = arr[i].thread + '-summary data'; result.push(arr[i]); // 标志判定当前数组的长度,也可用.length判断 flag++; @@ -364,7 +366,7 @@ function fixTotal(arr: Array): Array { // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建 if (result[flag].children![arr[i].cpu] === undefined) { result[flag].children![arr[i].cpu] = { - thread: "summary data", + thread: 'summary data', consumption: 0, cpu: arr[i].cpu, frequency: -1, @@ -387,7 +389,7 @@ function fixTotal(arr: Array): Array { ].children?.findIndex((item) => item.frequency === arr[i].frequency)!; // 若存在相同频点的数据,则进行合并,不同直接push if (index === -1) { - arr[i].thread = "summary data"; + arr[i].thread = 'summary data'; result[flag].children![arr[i].cpu].children?.push(arr[i]); } else { result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption; @@ -414,29 +416,29 @@ function mergeTotal( const num: number = arr2.findIndex((item) => item.thread?.includes(arr1[i].thread!) ); - arr2[num].thread = "summary data"; + arr2[num].thread = 'summary data'; arr1[i].children?.unshift(arr2[num]); arr2.splice(num, 1); } } - /** - * - * @param arr 待整理的数组,会经过递归取到最底层的数据 - */ +/** + * + * @param arr 待整理的数组,会经过递归取到最底层的数据 + */ function recursion(arr: Array): void { - for (let idx = 0; idx < arr.length; idx++) { - if (arr[idx].cpu === -1) { - resultArray.push(arr[idx]); - } - if (arr[idx].children) { - recursion(arr[idx].children!); - } else { - resultArray.push(arr[idx]); - } + for (let idx = 0; idx < arr.length; idx++) { + if (arr[idx].cpu === -1) { + resultArray.push(arr[idx]); + } + if (arr[idx].children) { + recursion(arr[idx].children!); + } else { + resultArray.push(arr[idx]); } } +} self.onmessage = (e: MessageEvent): void => { comPower = e.data.comPower; diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts index 2891b95c..71c10c45 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerNativeNemory.ts @@ -1125,7 +1125,7 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { let node: NativeHookCallInfo; if ( //@ts-ignore - currentNode.children.filter((child: NativeHookCallInfo): boolean => { + currentNode.initChildren.filter((child: NativeHookCallInfo): boolean => { if ( child.symbolId === callChainDataList[index]?.symbolId && child.fileId === callChainDataList[index]?.fileId @@ -1140,7 +1140,7 @@ export class ProcedureLogicWorkerNativeMemory extends LogicHandler { node = new NativeHookCallInfo(); this.mergeCallChainSample(node, callChainDataList[index], sample); currentNode.children.push(node); - // currentNode.initChildren.push(node); + currentNode.initChildren.push(node); // 将所有节点存到this.currentTreeList this.currentTreeList.push(node); node.parentNode = currentNode; diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 62959946..2944bd58 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -577,7 +577,7 @@ where cpu not null order by cpu,ts;`; this.queryData(this.currentEventId, 'scheduling-Thread Freq', sql, {}); } - queryProTop10Swicount() { + queryProTop10Swicount(): void { this.queryData( this.currentEventId, 'scheduling-Process Top10Swicount', @@ -597,7 +597,7 @@ where cpu not null {} ); } - queryThrTop10Swicount(pid: number) { + queryThrTop10Swicount(pid: number): void { this.queryData( this.currentEventId, 'scheduling-Process Top10Swicount', @@ -618,7 +618,7 @@ where cpu not null {} ); } - queryProTop10RunTime() { + queryProTop10RunTime(): void { this.queryData( this.currentEventId, 'scheduling-Process Top10RunTime', @@ -637,7 +637,7 @@ where cpu not null {} ); } - queryThrTop10RunTime(pid: number) { + queryThrTop10RunTime(pid: number): void { this.queryData( this.currentEventId, 'scheduling-Process Top10RunTime', diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index b375d1db..cb1f230f 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -322,8 +322,8 @@ export const getTabSlicesAsyncFunc = ( rightNS: number ): //@ts-ignore Promise> => { - let condition = `${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''}`; - let sql = ` + let condition = `${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''}`; + let sql = ` SELECT c.name AS name, sum( c.dur ) AS wallDuration, @@ -340,14 +340,14 @@ export const getTabSlicesAsyncFunc = ( and P.pid = ${asyncPid} and - c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')}) + c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')}) and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} group by c.name order by wallDuration desc;`; - return query('getTabSlicesAsyncFunc', sql, {}); + return query('getTabSlicesAsyncFunc', sql, {}); } export const getTabDetails = ( @@ -355,16 +355,16 @@ export const getTabDetails = ( asyncPid: Array, funTids: Array, leftNS: number, - rightNS: number + rightNS: number ): //@ts-ignore Promise> => { - let condition = ` + let condition = ` and A.tid in (${funTids!.join(',')}) and c.cookie is null ${`and P.pid in (${asyncPid.join(',')})`} - ${`and c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')})`} - ` - let sql = ` + ${`and c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')})`} + `; + let sql = ` SELECT c.name AS name, c.dur AS duration, @@ -383,9 +383,9 @@ export const getTabDetails = ( c.dur >= -1 and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} - ` - return query('getTabDetails', sql, {}); - } + `; + return query('getTabDetails', sql, {}); +}; export const getSfDetails = ( asyncNames: Array, asyncPid: number, @@ -394,13 +394,13 @@ export const getSfDetails = ( rightNS: number ): //@ts-ignore Promise> => { - let condition = ` + let condition = ` and c.parent_id not null ${asyncTid !== null && asyncTid !== undefined ? `and A.tid = ${asyncTid}` : ''} ${`and P.pid = ${asyncPid}`} - ${`and c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')})`} - ` - let sql = ` + ${`and c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')})`} + `; + let sql = ` SELECT c.name AS name, c.dur AS duration, @@ -420,18 +420,18 @@ export const getSfDetails = ( c.dur >= -1 and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) ${condition} - ` - return query('getSfDetails', sql, {}); - } - export const getGhDetails = ( - asyncNames: Array, - catName: string, - asyncPid: number, - leftNS: number, - rightNS: number - ): //@ts-ignore - Promise> => { - let sql = ` + `; + return query('getSfDetails', sql, {}); +}; +export const getGhDetails = ( + asyncNames: Array, + catName: string, + asyncPid: number, + leftNS: number, + rightNS: number +): //@ts-ignore + Promise> => { + let sql = ` SELECT c.name AS name, c.dur AS duration, @@ -459,21 +459,21 @@ export const getSfDetails = ( and cat = '${catName}' and - c.name in (${asyncNames.map((it) => "\"" + it + "\"").join(',')}) + c.name in (${asyncNames.map((it) => '\"' + it + '\"').join(',')}) and not ((C.ts - D.start_ts + C.dur < ${leftNS}) or (C.ts - D.start_ts > ${rightNS})) - ` - return query('getGhDetails', sql, {}); - } + `; + return query('getGhDetails', sql, {}); +}; export const getTabSlicesAsyncCatFunc = ( asyncCatNames: string, asyncCatPid: number, leftNS: number, rightNS: number ): Promise> => -query( - 'getTabSlicesAsyncCatFunc', - ` + query( + 'getTabSlicesAsyncCatFunc', + ` select c.name as name, sum(c.dur) as wallDuration, @@ -502,7 +502,7 @@ query( c.name order by wallDuration desc;`, - { $leftNS: leftNS, $rightNS: rightNS } + { $leftNS: leftNS, $rightNS: rightNS } ); export const querySearchFunc = (search: string): Promise> => diff --git a/ide/src/trace/database/sql/Memory.sql.ts b/ide/src/trace/database/sql/Memory.sql.ts index 2ceb95e1..c1e30912 100644 --- a/ide/src/trace/database/sql/Memory.sql.ts +++ b/ide/src/trace/database/sql/Memory.sql.ts @@ -141,7 +141,7 @@ export const getTabVirtualMemoryType = (startTime: number, endTime: number): Pro ); export const queryNativeMemoryRealTime = (): //@ts-ignore -Promise> => + Promise> => query( 'queryNativeMemoryRealTime', `select cs.ts,cs.clock_name from datasource_clockid dc @@ -152,13 +152,13 @@ Promise> => ); export const queryJsMemoryData = (): //@ts-ignore -Promise> => query('queryJsMemoryData', - 'SELECT 1 WHERE EXISTS(SELECT 1 FROM js_heap_nodes)'); + Promise> => query('queryJsMemoryData', + 'SELECT 1 WHERE EXISTS(SELECT 1 FROM js_heap_nodes)'); export const queryVmTrackerShmData = ( iPid: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryVmTrackerShmData', `SELECT (A.ts - B.start_ts) as startNs, @@ -177,7 +177,7 @@ export const queryVmTrackerShmSelectionData = ( startNs: number, ipid: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryVmTrackerShmSelectionData', `SELECT (A.ts - B.start_ts) as startNS,A.ipid, @@ -198,7 +198,7 @@ export const queryMemoryConfig = async (): Promise> => { {}, ); //@ts-ignore - let keySql = keyList && keyList.length > 0 && keyList.some(entry => entry.key === 'ipid') ? "AND key = 'ipid'" : ""; + let keySql = keyList && keyList.length > 0 && keyList.some(entry => entry.key === 'ipid') ? "AND key = 'ipid'" : ''; return query( 'queryMemoryConfiig', `SELECT ipid as iPid, process.pid AS pid, @@ -215,14 +215,14 @@ export const queryMemoryConfig = async (): Promise> => { ${keySql} ;` ); -} +}; // VM Tracker Purgeable泳道图 export const queryPurgeableProcessData = ( ipid: number, isPin?: boolean ): //@ts-ignore -Promise> => { + Promise> => { const pinSql = isPin ? ' AND a.ref_count > 0' : ''; const names = isPin ? " ('mem.purg_pin')" : "('mem.purg_sum')"; return query( @@ -259,7 +259,7 @@ Promise> => { }; export const queryVirtualMemory = (): //@ts-ignore -Promise> => + Promise> => query('queryVirtualMemory', `select id, @@ -269,7 +269,7 @@ Promise> => export const queryVirtualMemoryData = ( filterId: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryVirtualMemoryData', `select ts-${window.recordStartNS} as startTime,value,filter_id as filterID @@ -375,7 +375,7 @@ export const queryTraceMemoryUnAgg = (): Promise< export const queryMemoryMaxData = ( memoryName: string ): //@ts-ignore -Promise> => + Promise> => query( 'queryMemoryMaxData', `SELECT ifnull(max(m.value),0) as maxValue, @@ -391,7 +391,7 @@ export const getTabPaneVirtualMemoryStatisticsData = ( leftNs: number, rightNs: number ): //@ts-ignore -Promise> => + Promise> => query( 'getTabPaneVirtualMemoryStatisticsData', ` @@ -418,7 +418,7 @@ Promise> => ); export const getFileSysVirtualMemoryChartData = (): //@ts-ignore -Promise> => + Promise> => query( 'getFileSysVirtualMemoryChartData', ` @@ -433,7 +433,7 @@ Promise> => ); export const hasFileSysData = (): //@ts-ignore -Promise> => + Promise> => query( 'hasFileSysData', ` @@ -453,7 +453,7 @@ export const queryEbpfSamplesCount = ( endTime: number, ipids: number[] ): //@ts-ignore -Promise> => + Promise> => query( 'queryEbpfSamplesCount', ` @@ -462,21 +462,19 @@ Promise> => vmCount from (select count(1) as fsCount from file_system_sample s,trace_range t - where s.end_ts between $startTime + t.start_ts and $endTime + t.start_ts ${ - ipids.length > 0 ? `and s.ipid in (${ipids.join(',')})` : '' -}) + where s.end_ts between $startTime + t.start_ts and $endTime + t.start_ts ${ipids.length > 0 ? `and s.ipid in (${ipids.join(',')})` : '' + }) ,(select count(1) as vmCount from paged_memory_sample s,trace_range t -where s.end_ts between $startTime + t.start_ts and $endTime + t.start_ts ${ - ipids.length > 0 ? `and s.ipid in (${ipids.join(',')})` : '' -}); +where s.end_ts between $startTime + t.start_ts and $endTime + t.start_ts ${ipids.length > 0 ? `and s.ipid in (${ipids.join(',')})` : '' + }); `, - { $startTime: startTime, $endTime: endTime } -); + { $startTime: startTime, $endTime: endTime } + ); export const queryisExistsShmData = ( iPid: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryisExistsShmData', `SELECT EXISTS ( @@ -496,7 +494,7 @@ export const queryVmTrackerShmSizeData = ( iPid: number, dur: number ): //@ts-ignore -Promise> => + Promise> => query( 'queryVmTrackerShmSizeData', `SELECT ( A.ts - B.start_ts ) AS startNS, @@ -518,7 +516,7 @@ export const queryisExistsPurgeableData = ( ipid: number, isPin?: boolean ): //@ts-ignore -Promise> => { + Promise> => { const pinSql = isPin ? ' AND a.ref_count > 0' : ''; const names = isPin ? " ('mem.purg_pin')" : "('mem.purg_sum')"; return query( diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index c2486a63..e642a44c 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -329,12 +329,13 @@ where B.tid = $tid and B.pid = $pid;`, { $tid: tid, $pid: pid } ); - export const queryThreadWakeUpFrom = async (itid: number, startTime: number): Promise => { - let sql1 = `select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1`; - const result = await query('queryThreadWakeUpFrom', sql1, {}, { traceId: Utils.currentSelectTrace }); - if (result && result.length > 0) { //@ts-ignore - let wakeupFromItid = result[0].wakeup_from; // 获取wakeup_from的值 - let sql2 = ` +export const queryThreadWakeUpFrom = async (itid: number, startTime: number): Promise => { + let sql1 = `select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1`; + const result = await query('queryThreadWakeUpFrom', sql1, {}, { traceId: Utils.currentSelectTrace }); + let res: unknown = []; + if (result && result.length > 0) { //@ts-ignore + let wakeupFromItid = result[0].wakeup_from; // 获取wakeup_from的值 + let sql2 = ` select (A.ts - B.start_ts) as ts, A.tid, A.itid, @@ -347,18 +348,20 @@ where B.tid = $tid and B.pid = $pid;`, and A.itid = ${wakeupFromItid} and (A.ts - B.start_ts) < (${startTime} - B.start_ts) order by ts desc limit 1 - `; - return query('queryThreadWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); - } - }; + `; + res = query('queryThreadWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); + } + return res +}; export const queryRWakeUpFrom = async (itid: number, startTime: number): Promise => { let sql1 = `select wakeup_from from instant where ts = ${startTime} and ref = ${itid} limit 1`; const res = await query('queryRWakeUpFrom', sql1, {}, { traceId: Utils.currentSelectTrace }); + let result: unknown = []; if (res && res.length) { //@ts-ignore let wakeupFromItid = res[0].wakeup_from; - let sql2 =` + let sql2 = ` select (A.ts - B.start_ts) as ts, A.tid, @@ -375,8 +378,9 @@ export const queryRWakeUpFrom = async (itid: number, startTime: number): Promise ts desc limit 1 `; - return query('queryRWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); + result = query('queryRWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); } + return result; }; export const queryRunnableTimeByRunning = (tid: number, startTime: number): Promise> => { let sql = ` @@ -783,7 +787,7 @@ export const queryThreadStateArgsByName = (key: string, traceId?: string): { traceId: traceId } ); -export const queryArgsById = (key: string, traceId?: string): +export const queryArgsById = (key: string, traceId?: string): Promise> => query( 'queryArgsById', diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts index d4f2d27e..78c8a752 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerFunc.ts @@ -96,8 +96,8 @@ export function func( rowParentId: string | null | undefined ): void { if (use && funcFilter.length > 0) { - if (rowParentId === "UserPluginsRows" && !expand) { - funcFilter = funcFilter.filter((it) => it.depth === 0) + if (rowParentId === 'UserPluginsRows' && !expand) { + funcFilter = funcFilter.filter((it) => it.depth === 0); } for (let i = 0, len = funcFilter.length; i < len; i++) { if ((funcFilter[i].startTs || 0) + (funcFilter[i].dur || 0) >= startNS && (funcFilter[i].startTs || 0) <= endNS) { -- Gitee From 6d19f700a695cf66d2810b2cfd5ba3acb0b247e1 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 16:03:23 +0800 Subject: [PATCH 80/85] =?UTF-8?q?fix:20240903=E4=BB=A3=E7=A0=81=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E6=B1=87=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/base-ui/select/LitSelect.ts | 12 +-- ide/src/base-ui/select/LitSelectV.ts | 44 ++++----- ide/src/hdc/hdcclient/HdcClient.ts | 2 +- ide/src/trace/component/SpBubblesAI.ts | 8 +- ide/src/trace/component/SpFlags.ts | 14 +-- .../trace/component/SpSystemTrace.event.ts | 6 +- ide/src/trace/component/SpSystemTrace.init.ts | 16 ++-- ide/src/trace/component/SpSystemTrace.ts | 16 ++-- .../trace/component/chart/SpChartManager.ts | 2 +- .../trace/component/chart/SpFrameTimeChart.ts | 2 +- .../chart/SpImportUserPluginsChart.ts | 51 ++++++----- .../trace/component/chart/SpProcessChart.ts | 11 ++- .../component/chart/SpSegmentationChart.ts | 2 +- .../processAnalysis/TabProcessAnalysis.ts | 2 +- .../Top10LongestRunTimeProcess.ts | 89 +++++++++++-------- .../Top10ProcessSwitchCount.ts | 62 +++++++------ ide/src/trace/component/setting/SpArkTs.ts | 2 +- .../trace/component/setting/SpHilogRecord.ts | 2 +- .../trace/component/setting/SpHisysEvent.ts | 6 +- .../trace/component/setting/SpSdkConfig.ts | 6 +- .../trace/component/setting/SpVmTracker.ts | 2 +- .../trace/component/trace/base/TraceRow.ts | 10 +-- .../trace/sheet/cpu/TabPaneCpuByThread.ts | 6 +- .../trace/sheet/cpu/TabPaneSchedPriority.ts | 2 +- .../trace/sheet/freq/TabPaneCpuFreqLimits.ts | 14 +-- .../trace/sheet/frequsage/TabPaneFreqUsage.ts | 38 ++++---- .../trace/sheet/hiperf/TabPerfBottomUp.ts | 2 +- 27 files changed, 228 insertions(+), 201 deletions(-) diff --git a/ide/src/base-ui/select/LitSelect.ts b/ide/src/base-ui/select/LitSelect.ts index 6f6768be..aad124e5 100644 --- a/ide/src/base-ui/select/LitSelect.ts +++ b/ide/src/base-ui/select/LitSelect.ts @@ -216,12 +216,12 @@ export class LitSelect extends BaseElement { initElements(): void { this.selectVInputEl = this.shadowRoot!.querySelector('#select-input') as HTMLInputElement; - this.selectVInputEl?.addEventListener('keyup', (e) => { - if (e.code === 'Enter' || e.code === 'NumpadEnter') {// @ts-ignore - this.selectVInputEl.blur();// @ts-ignore - this.selectInputEl.value=this.selectVInputEl.value; - } - }); + this.selectVInputEl?.addEventListener('keyup', (e) => { + if (e.code === 'Enter' || e.code === 'NumpadEnter') {// @ts-ignore + this.selectVInputEl.blur();// @ts-ignore + this.selectInputEl.value = this.selectVInputEl.value; + } + }); if (this.showSearchInput) { this.shadowRoot!.querySelector('.body-select')!.style.display = 'block'; this.selectSearchInputEl = this.shadowRoot!.querySelector('#search-input') as HTMLInputElement; diff --git a/ide/src/base-ui/select/LitSelectV.ts b/ide/src/base-ui/select/LitSelectV.ts index 28f3f191..a6d2e5cd 100644 --- a/ide/src/base-ui/select/LitSelectV.ts +++ b/ide/src/base-ui/select/LitSelectV.ts @@ -109,7 +109,7 @@ export class LitSelectV extends BaseElement { return this.hasAttribute('is-all'); } - dataSource(selectVData: Array, valueStr: string,isSingle?:boolean): void { + dataSource(selectVData: Array, valueStr: string, isSingle?: boolean): void { this.selectVOptions!.innerHTML = ''; if (selectVData.length > 0) { this.selectVBody!.style.display = 'block'; @@ -125,14 +125,14 @@ export class LitSelectV extends BaseElement { option.textContent = valueStr; this.selectVOptions!.appendChild(option); this.initDataItem(selectVData); - if(isSingle){ + if (isSingle) { this.initSingleCustomOptions(); } else { this.initCustomOptions(); } } else { this.initDataItem(selectVData); - if(isSingle){ + if (isSingle) { this.initSingleCustomOptions(); } else { this.initOptions(); @@ -171,7 +171,7 @@ export class LitSelectV extends BaseElement { this.selectVInputEl?.addEventListener('keyup', (e) => { if (e.code === 'Enter' || e.code === 'NumpadEnter') {// @ts-ignore this.selectVInputEl.blur(); - } + } });// @ts-ignore this.selectVInputEl!.oninput = (ev: InputEvent): void => { // @ts-ignore @@ -250,7 +250,7 @@ export class LitSelectV extends BaseElement { `; } - connectedCallback(): void {} + connectedCallback(): void { } initCustomOptions(): void { let querySelector = this.shadowRoot?.querySelector( @@ -291,22 +291,22 @@ export class LitSelectV extends BaseElement { } initSingleCustomOptions(): void { - let selectedOption = this.shadowRoot?.querySelector( - `lit-select-option[value="${this.currentvalueStr}"]` - ) as LitSelectOption | null; - if (selectedOption) { - selectedOption.setAttribute('selected', ''); - } - this.shadowRoot?.querySelectorAll('lit-select-option').forEach((option) => { - option.addEventListener('onSelected', () => { - this.shadowRoot?.querySelectorAll('lit-select-option').forEach((o) => { - o.removeAttribute('selected'); - }); - option.setAttribute('selected', ''); // @ts-ignore - this.selectVInputEl!.value = option.textContent!; - this.currentvalueStr = option.textContent!; - }); - }); + let selectedOption = this.shadowRoot?.querySelector( + `lit-select-option[value="${this.currentvalueStr}"]` + ) as LitSelectOption | null; + if (selectedOption) { + selectedOption.setAttribute('selected', ''); + } + this.shadowRoot?.querySelectorAll('lit-select-option').forEach((option) => { + option.addEventListener('onSelected', () => { + this.shadowRoot?.querySelectorAll('lit-select-option').forEach((o) => { + o.removeAttribute('selected'); + }); + option.setAttribute('selected', ''); // @ts-ignore + this.selectVInputEl!.value = option.textContent!; + this.currentvalueStr = option.textContent!; + }); + }); } initOptions(): void { @@ -369,5 +369,5 @@ export class LitSelectV extends BaseElement { }); } - attributeChangedCallback(name: unknown, oldValue: unknown, newValue: unknown): void {} + attributeChangedCallback(name: unknown, oldValue: unknown, newValue: unknown): void { } } diff --git a/ide/src/hdc/hdcclient/HdcClient.ts b/ide/src/hdc/hdcclient/HdcClient.ts index 93c726df..5393d55c 100644 --- a/ide/src/hdc/hdcclient/HdcClient.ts +++ b/ide/src/hdc/hdcclient/HdcClient.ts @@ -77,7 +77,7 @@ export class HdcClient implements DataListener { case AuthType.AUTH_TOKEN: continue; case AuthType.AUTH_SIGNATURE: - const hdcMsgUrl = this.isSigna ? 'signatureHdcMsg' : 'encryptHdcMsg' + const hdcMsgUrl = this.isSigna ? 'signatureHdcMsg' : 'encryptHdcMsg'; const response = await fetch(`${window.location.origin}/application/${hdcMsgUrl}?message=` + returnBuf); const dataBody = await response.json(); let signatureHdcMsg = ''; diff --git a/ide/src/trace/component/SpBubblesAI.ts b/ide/src/trace/component/SpBubblesAI.ts index 9d03886f..addf5fdf 100644 --- a/ide/src/trace/component/SpBubblesAI.ts +++ b/ide/src/trace/component/SpBubblesAI.ts @@ -23,7 +23,7 @@ export class SpBubblesAI extends BaseElement { const xiaoLubanEl: HTMLElement | undefined | null = this.shadowRoot?.querySelector('#xiao-luban-help'); xiaoLubanEl?.addEventListener('click', () => { this.xiaoLubanEvent(); - }) + }); let isShowXiaoLuban: boolean = FlagsConfig.getFlagsConfigEnableStatus('xiaoLuBan'); if (isShowXiaoLuban) { xiaoLubanEl?.setAttribute('enabled', ''); @@ -45,7 +45,7 @@ export class SpBubblesAI extends BaseElement { 'msgType': 'text', 'timestamp': new Date().getTime().toString(), 'botUser': 'p_xiaoluban', - } + }; fetch('https://smartperf.rnd.huawei.com/xiaoluban/resource', { method: 'post', body: JSON.stringify(data), @@ -54,9 +54,9 @@ export class SpBubblesAI extends BaseElement { } }).then(res => { if (res.status === 200) { - window.open('im:p_xiaoluban', "_self"); + window.open('im:p_xiaoluban', '_self'); } - }) + }); } } diff --git a/ide/src/trace/component/SpFlags.ts b/ide/src/trace/component/SpFlags.ts index a8e6cf63..308ee5c2 100644 --- a/ide/src/trace/component/SpFlags.ts +++ b/ide/src/trace/component/SpFlags.ts @@ -18,22 +18,22 @@ import { SpFlagHtml } from './SpFlag.html'; const NUM = '000000'; //vsync二级下拉选框对应的value和content const VSYNC_CONTENT = [ - { value: 'H:VsyncGenerator', content: "VsyncGeneratior" }, + { value: 'H:VsyncGenerator', content: 'VsyncGeneratior' }, { value: 'H:rs_SendVsync', content: 'Vsync-rs' }, { value: 'H:rs_SendVsync', content: 'Vsync-app' } -] +]; //cat二级下拉选框对应的value和content const CAT_CONTENT = [ - { value: 'business', content: "Business first" }, + { value: 'business', content: 'Business first' }, { value: 'thread', content: 'Thread first' } -] +]; //hang二级下拉选框对应的value和content const HANG_CONTENT = [ - { value: `33${NUM}`, content: "Instant" }, + { value: `33${NUM}`, content: 'Instant' }, { value: `100${NUM}`, content: 'Circumstantial' }, { value: `250${NUM}`, content: 'Micro' }, { value: `500${NUM}`, content: 'Severe' } -] +]; //整合默认值 const CONFIG_STATE: unknown = { @@ -214,7 +214,7 @@ export class SpFlags extends BaseElement { } else { userIdInputEl.style.border = '1px solid red'; } - }) + }); userIdInputEl.className = 'device_input'; userIdInputEl.id = 'user_id_input'; configFooterDiv.appendChild(userIdLabelEl); diff --git a/ide/src/trace/component/SpSystemTrace.event.ts b/ide/src/trace/component/SpSystemTrace.event.ts index 0c8ed206..ab442e53 100644 --- a/ide/src/trace/component/SpSystemTrace.event.ts +++ b/ide/src/trace/component/SpSystemTrace.event.ts @@ -619,9 +619,9 @@ export function spSystemTraceDocumentOnKeyPress(this: unknown, sp: SpSystemTrace sp.observerScrollHeightEnable = false; if (sp.keyboardEnable) { if (keyPress === 'm') { - if(sp.selectFlag) { + if (sp.selectFlag) { sp.selectFlag!.selected = false; - } + } sp.slicestime = sp.setSLiceMark(ev.shiftKey); if (sp.slicestime) { if (TraceRow.rangeSelectObject) { @@ -636,7 +636,7 @@ export function spSystemTraceDocumentOnKeyPress(this: unknown, sp: SpSystemTrace } if (keyPress === 'f') { let search = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#lit-search'); - if(search && search.searchValue !== '' && sp.currentRow !== undefined) { + if (search && search.searchValue !== '' && sp.currentRow !== undefined) { sp.currentRow = undefined; } let isSelectSliceOrFlag = false; diff --git a/ide/src/trace/component/SpSystemTrace.init.ts b/ide/src/trace/component/SpSystemTrace.init.ts index 85b95e18..93f6d623 100644 --- a/ide/src/trace/component/SpSystemTrace.init.ts +++ b/ide/src/trace/component/SpSystemTrace.init.ts @@ -417,9 +417,9 @@ function collectHandlerYes(sp: SpSystemTrace, currentRow: unknown, event: unknow !currentRow.name.includes(parentRow.name) ) { //@ts-ignore - currentRow.name = currentRow.protoParentId ? `${currentRow.name} (${currentRow.protoParentId})` : - //@ts-ignore - `${currentRow.name} (${parentRow.name})` + currentRow.name = currentRow.protoParentId ? `${currentRow.name} (${currentRow.protoParentId})` : + //@ts-ignore + `${currentRow.name} (${parentRow.name})`; } }); } @@ -769,8 +769,8 @@ export function spSystemTraceInitElement(sp: SpSystemTrace): void { } sp.tabCpuFreq = sp.traceSheetEL.shadowRoot.querySelector('tabpane-frequency-sample'); sp.tabCpuState = sp.traceSheetEL.shadowRoot.querySelector('tabpane-counter-sample'); - sp.wakeupListTbl = sp.traceSheetEL.shadowRoot?.querySelector("#current-selection > tabpane-current-selection")?. - shadowRoot?.querySelector("#wakeupListTbl"); + sp.wakeupListTbl = sp.traceSheetEL.shadowRoot?.querySelector('#current-selection > tabpane-current-selection')?. + shadowRoot?.querySelector('#wakeupListTbl'); sp.rangeSelect = new RangeSelect(sp); // @ts-ignore rightButton?.addEventListener('click', rightButtonOnClick(sp, rightStar)); @@ -911,11 +911,12 @@ function spSystemTraceShowStructFindIndex( return findIndex; } //向前查找逻辑 -function findPreviousOne(start: number, end: number, structs: Array): number { +function findPreviousOne(start: number, end: number, structs: Array): number { let findIndex = -1; const rangeEnd = TraceRow.range!.endNS; for (let i = start; i >= end; i--) { let it = structs[i]; + //@ts-ignore if (it.startTime! < rangeEnd) { findIndex = i; break; @@ -924,11 +925,12 @@ function findPreviousOne(start: number, end: number, structs: Array): numbe return findIndex; } //向后查找 -function findNextOne(start: number, end: number, structs: Array): number { +function findNextOne(start: number, end: number, structs: Array): number { let findIndex = -1; const rangeStart = TraceRow.range!.startNS; for (let i = start; i <= end; i++) { let it = structs[i]; + //@ts-ignore if (it.startTime > rangeStart) { findIndex = i; break; diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 34312c6c..5d1c75c9 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -229,9 +229,9 @@ export class SpSystemTrace extends BaseElement { focusTarget: string = ''; wakeupListTbl: LitTable | undefined | null; _checkclick: boolean = false; //判断点击getWakeupList按钮 - docomList: Array = []; + docomList: Array = []; repaintList: Array = []; - presentList: Array = []; + presentList: Array = []; set snapshotFile(data: FileInfo) { this.snapshotFiles = data; @@ -376,18 +376,18 @@ export class SpSystemTrace extends BaseElement { } pushPidToSelection(selection: SelectionParam, id: string, originalId?: string | Array): void { - let add = (it: string) => { + let add = (it: string): void => { let pid = parseInt(it ? it : id); if (!isNaN(pid!)) { if (!selection.processIds.includes(pid!)) { selection.processIds.push(pid!); } } - } + }; if (Array.isArray(originalId)) { originalId.forEach(item => { add(item); - }) + }); } else { add(originalId!); } @@ -1198,7 +1198,7 @@ export class SpSystemTrace extends BaseElement { } this.rangeTraceRow = rows; let search = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#lit-search'); - if(search?.isClearValue) { + if (search?.isClearValue) { spSystemTraceDocumentOnMouseMoveMouseDown(this, search!); } this.rangeSelect.selectHandler?.(this.rangeSelect.rangeTraceRow, false); @@ -2311,7 +2311,7 @@ export class SpSystemTrace extends BaseElement { showStruct(previous: boolean, currentIndex: number, structs: Array, retargetIndex?: number): number { let tagIndex = spSystemTraceShowStruct(this, previous, currentIndex, structs, retargetIndex); - return tagIndex === -1?currentIndex:tagIndex; + return tagIndex === -1 ? currentIndex : tagIndex; } private toTargetDepth = (entry: unknown, funcRowID: string, funcStract: unknown): void => { @@ -2640,7 +2640,7 @@ export class SpSystemTrace extends BaseElement { window.publish(window.SmartEvent.UI.WakeupList, SpSystemTrace.wakeupList); this.wakeupListTbl!.loading = false; this._checkclick = false; - this.refreshCanvas(true); + this.refreshCanvas(true); return null; } // @ts-ignore SpSystemTrace.wakeupList.push(a); // @ts-ignore diff --git a/ide/src/trace/component/chart/SpChartManager.ts b/ide/src/trace/component/chart/SpChartManager.ts index 68a962fd..a852490e 100644 --- a/ide/src/trace/component/chart/SpChartManager.ts +++ b/ide/src/trace/component/chart/SpChartManager.ts @@ -58,7 +58,7 @@ import { sliceSender } from '../../database/data-trafic/SliceSender'; import { BaseStruct } from '../../bean/BaseStruct'; import { SpGpuCounterChart } from './SpGpuCounterChart'; import { SpUserFileChart } from './SpUserPluginChart'; -import { SpImportUserPluginsChart } from './SpImportUserPluginsChart' +import { SpImportUserPluginsChart } from './SpImportUserPluginsChart'; import { queryDmaFenceIdAndCat } from '../../database/sql/dmaFence.sql'; import { queryAllFuncNames } from '../../database/sql/Func.sql'; diff --git a/ide/src/trace/component/chart/SpFrameTimeChart.ts b/ide/src/trace/component/chart/SpFrameTimeChart.ts index 30c95421..7f1b0b76 100644 --- a/ide/src/trace/component/chart/SpFrameTimeChart.ts +++ b/ide/src/trace/component/chart/SpFrameTimeChart.ts @@ -246,7 +246,7 @@ export class SpFrameTimeChart { this.flagConfig = FlagsConfig.getFlagsConfig('AnimationAnalysis'); let appNameMap: Map = new Map(); //@ts-ignore - if (this.flagConfig?.AnimationAnalysis === 'Enabled' && sourceTypeName[0].value !== "txt-based-trace") { + if (this.flagConfig?.AnimationAnalysis === 'Enabled' && sourceTypeName[0].value !== 'txt-based-trace') { if (process.processName?.startsWith('render_service')) { let targetRowList = processRow.childrenList.filter( (childRow) => childRow.rowType === 'thread' && childRow.name.startsWith('render_service') diff --git a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts index de06578d..3baccaff 100644 --- a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts +++ b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts @@ -25,6 +25,7 @@ import { ProcessMemStruct } from '../../bean/ProcessMemStruct'; import { MemRender } from '../../database/ui-worker/ProcedureWorkerMem'; import { FuncRender, FuncStruct } from '../../database/ui-worker/ProcedureWorkerFunc'; import { ThreadRender } from '../../database/ui-worker/ProcedureWorkerThread'; +import { promises } from 'dns'; const FOLD_HEIGHT = 24; export class SpImportUserPluginsChart { private trace: SpSystemTrace; @@ -35,7 +36,7 @@ export class SpImportUserPluginsChart { this.trace = trace; } - async init(traceId?: string) { + async init(traceId?: string): Promise { this.traceId = traceId; //@ts-ignore let folderRow = this.createFolderRow(this.traceId); @@ -66,7 +67,7 @@ export class SpImportUserPluginsChart { * @param row * @param start_ts */ - addTraceRowEventListener(row: TraceRow) { + addTraceRowEventListener(row: TraceRow): void { row.uploadEl?.addEventListener('sample-file-change', (e: unknown) => { this.getJsonData(e).then((res: unknown) => { if (row.childrenList.length) { this.handleDynamicRowList(row); } @@ -104,17 +105,17 @@ export class SpImportUserPluginsChart { this.addDrawAttributes(item, childRow, element); row.summaryProtoPid!.push(childRow.protoPid); row.addChildTraceRow(childRow); - } - } - }) + }; + }; + }); row.expansion = true; - } - this.trace.refreshCanvas(false) - }) - }) + }; + this.trace.refreshCanvas(false); + }); + }); } //清空row-parent-id='UserPluginsRows'动态添加的子Row的list数据 - handleDynamicRowList(row: TraceRow) { + handleDynamicRowList(row: TraceRow): void { row.summaryProtoPid = []; // 使用querySelectorAll找到所有row-parent-id='UserPluginsRows'的div元素 //@ts-ignore @@ -135,21 +136,23 @@ export class SpImportUserPluginsChart { row.childrenList = []; } - addDrawAttributes(item: { rowType: string, threadName: string }, childRow: TraceRow, element: unknown) { + addDrawAttributes(item: { rowType: string, threadName: string }, childRow: TraceRow, element: unknown): void { //@ts-ignore if (element.supplier) { - childRow.supplier = async () => { + //@ts-ignore + childRow.supplier = async (): Promise => { //@ts-ignore let res = await element.supplier!(); - return res - } + return res; + }; //@ts-ignore } else if (element.supplierFrame) { - childRow.supplierFrame = async () => { + //@ts-ignore + childRow.supplierFrame = async (): Promise => { //@ts-ignore let res = await element.supplierFrame!(); - return res - } + return res; + }; } if (item.rowType === TraceRow.ROW_TYPE_MEM) {//处理mem childRow.findHoverStruct = (): void => { @@ -180,10 +183,10 @@ export class SpImportUserPluginsChart { } //@ts-ignore if (element.asyncFuncThreadName) { - //@ts-ignore - childRow.asyncFuncThreadName = element.asyncFuncThreadName; //@ts-ignore - childRow.asyncFuncNamePID = element.asyncFuncNamePID; + childRow.asyncFuncThreadName = element.asyncFuncThreadName; + //@ts-ignore + childRow.asyncFuncNamePID = element.asyncFuncNamePID; } childRow.findHoverStruct = (): void => { //@ts-ignore @@ -206,14 +209,14 @@ export class SpImportUserPluginsChart { let reader = new FileReader(); //@ts-ignore reader.readAsText(file.detail || file); - reader.onloadend = (e: unknown) => { + reader.onloadend = (e: unknown): void => { //@ts-ignore const fileContent = e.target?.result; try { resolve(JSON.parse(fileContent)); document.dispatchEvent( new CustomEvent('file-correct') - ) + ); SpStatisticsHttpUtil.addOrdinaryVisitAction({ event: 'seach-row', action: 'seach-row', @@ -221,10 +224,10 @@ export class SpImportUserPluginsChart { } catch (error) { document.dispatchEvent( new CustomEvent('file-error') - ) + ); } } - }) + }); } } export const folderThreadHandler = (row: TraceRow, trace: SpSystemTrace) => { diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index af5b0d19..4ac0f5cc 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -1383,17 +1383,20 @@ export class SpProcessChart { ({ setArrayLenThanOne, setArrayLenOnlyOne } = this.hanldAsyncFunc(it, asyncRemoveCatArr));//len等于0和大于0的分类 //@ts-ignore let aggregateData = {...setArrayLenThanOne, ...setArrayLenOnlyOne }; - Reflect.ownKeys(aggregateData).map((key: any) => { - let param: Array = aggregateData[key]; + Reflect.ownKeys(aggregateData).map((key: unknown) => { + let param: Array = aggregateData[key]; + //@ts-ignore this.makeAddAsyncFunction(param, it, processRow, key); }) //@ts-ignore - Reflect.ownKeys(asyncCat).map((key: any) => { + Reflect.ownKeys(asyncCat).map((key: unknown) => { //@ts-ignore - let param: Array = asyncCat[key]; + let param: Array = asyncCat[key]; if (flag) {//处理business + //@ts-ignore this.makeAddAsyncFunction(param, it, processRow, key); } else {//处理thread + //@ts-ignore this.makeAddAsyncFunction(param, it, processRow, key, param[0].tid); } }) diff --git a/ide/src/trace/component/chart/SpSegmentationChart.ts b/ide/src/trace/component/chart/SpSegmentationChart.ts index 0fecf881..430d7bf5 100644 --- a/ide/src/trace/component/chart/SpSegmentationChart.ts +++ b/ide/src/trace/component/chart/SpSegmentationChart.ts @@ -24,7 +24,7 @@ import { type AllStatesRender, AllstatesStruct } from '../../database/ui-worker/ import { StateGroup } from '../../bean/StateModle'; import { queryAllFuncNames } from '../../database/sql/Func.sql'; import { Utils } from '../trace/base/Utils'; -import { TabPaneFreqUsage } from "../trace/sheet/frequsage/TabPaneFreqUsage"; +import { TabPaneFreqUsage } from '../trace/sheet/frequsage/TabPaneFreqUsage'; const UNIT_HEIGHT: number = 20; const MS_TO_US: number = 1000000; const MIN_HEIGHT: number = 2; diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts index 5624ce82..4cf79f33 100644 --- a/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/TabProcessAnalysis.ts @@ -20,7 +20,7 @@ import './Top10ProcessSwitchCount.ts'; import { Top10LongestRunTimeProcess } from './Top10LongestRunTimeProcess'; import { Top10ProcessSwitchCount } from './Top10ProcessSwitchCount'; -@element("tab-process-analysis") +@element('tab-process-analysis') export class TabProcessAnalysis extends BaseElement { private currentTabID: string | undefined; private currentTab: BaseElement | undefined; diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts index 38d16873..77da6c9e 100644 --- a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10LongestRunTimeProcess.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { BaseElement, element } from "../../../../base-ui/BaseElement"; +import { BaseElement, element } from '../../../../base-ui/BaseElement'; import { LitTable } from '../../../../base-ui/table/lit-table'; import { procedurePool } from '../../../database/Procedure'; import { info } from '../../../../log/Log'; @@ -25,7 +25,7 @@ import { LitChartColumn } from '../../../../base-ui/chart/column/LitChartColumn' import '../../../../base-ui/chart/column/LitChartColumn'; import { Utils } from '../../trace/base/Utils'; -@element("top10-longest-runtime-process") +@element('top10-longest-runtime-process') export class Top10LongestRunTimeProcess extends BaseElement { traceChange: boolean = false; private processRunTimeTbl: LitTable | null | undefined; @@ -48,7 +48,7 @@ export class Top10LongestRunTimeProcess extends BaseElement { /** * 初始化操作,若trace发生改变,将所有变量设置为默认值并重新请求数据。若trace未改变,跳出初始化 */ - init() { + init(): void { if (!this.traceChange) { if (this.processRunTimeTbl!.recycleDataSource.length > 0) { this.processRunTimeTbl?.reMeauseHeight(); @@ -63,16 +63,16 @@ export class Top10LongestRunTimeProcess extends BaseElement { this.processMap = Utils.getInstance().getProcessMap(); this.threadMap = Utils.getInstance().getThreadMap(); this.queryLogicWorker( - "scheduling-Process Top10RunTime", - "query Process Top10 Run Time Analysis Time:", + 'scheduling-Process Top10RunTime', + 'query Process Top10 Run Time Analysis Time:', this.callBack.bind(this) ); } - + /** * 清除已存储数据 */ - clearData() { + clearData(): void { this.traceChange = true; this.processSwitchCountChart!.dataSource = []; this.processRunTimeTbl!.recycleDataSource = []; @@ -93,7 +93,7 @@ export class Top10LongestRunTimeProcess extends BaseElement { */ queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { let processThreadCountTime = new Date().getTime(); - procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + procedurePool.submitWithName('logic0', option, { pid: pid }, undefined, handler); let durTime = new Date().getTime() - processThreadCountTime; info(log, durTime); } @@ -111,8 +111,8 @@ export class Top10LongestRunTimeProcess extends BaseElement { const pStr: string | null = this.processMap.get(arr[i].pid!)!; const tStr: string | null = this.threadMap.get(arr[i].tid!)!; result.push({ - NO: i + 1, - pid: arr[i].pid || this.processId, + NO: i + 1, + pid: arr[i].pid || this.processId, pName: pStr === null ? 'Process ' : pStr, dur: arr[i].dur, tid: arr[i].tid, @@ -151,28 +151,28 @@ export class Top10LongestRunTimeProcess extends BaseElement { xField: 'pid', yField: 'dur', seriesField: 'size', - color: (a) => { - return '#0a59f7'; + color: (a): string => { + return '#0a59f7'; }, - hoverHandler: (data) => { + hoverHandler: (data): void => { if (data) { this.processRunTimeTbl!.setCurrentHover(data); } else { this.processRunTimeTbl!.mouseOut(); } }, - tip: (obj) => { + tip: (obj): string => { return `

    Process_Id:${ - // @ts-ignore - obj[0].obj.pid}
    + // @ts-ignore + obj[0].obj.pid}
    Process_Name:${ - // @ts-ignore - obj[0].obj.pName}
    + // @ts-ignore + obj[0].obj.pName}
  • Run_Time:${ - // @ts-ignore - obj[0].obj.dur}
    + // @ts-ignore + obj[0].obj.dur}
    `; }, @@ -184,7 +184,7 @@ export class Top10LongestRunTimeProcess extends BaseElement { * 大函数块拆解分为两部分,此部分为Top10线程数据 * @param result 需要显示在表格中的数据 */ - threadCallback(result: Array):void { + threadCallback(result: Array): void { this.nodataThr!.noData = result === undefined || result.length === 0; this.threadRunTimeTbl!.recycleDataSource = result; this.threadRunTimeTbl!.reMeauseHeight(); @@ -195,31 +195,31 @@ export class Top10LongestRunTimeProcess extends BaseElement { xField: 'tid', yField: 'dur', seriesField: 'size', - color: (a) => { - return '#0a59f7'; + color: (a): string => { + return '#0a59f7'; }, - hoverHandler: (data) => { + hoverHandler: (data): void => { if (data) { this.threadRunTimeTbl!.setCurrentHover(data); } else { this.threadRunTimeTbl!.mouseOut(); } }, - tip: (obj) => { + tip: (obj): string => { return `
    Process_Id:${ - // @ts-ignore - obj[0].obj.pid}
    + // @ts-ignore + obj[0].obj.pid}
    Thread_Id:${ - // @ts-ignore - obj[0].obj.tid}
    + // @ts-ignore + obj[0].obj.tid}
    Thread_Name:${ - // @ts-ignore - obj[0].obj.tName}
    + // @ts-ignore + obj[0].obj.tName}
    Run_Time:${ - // @ts-ignore - obj[0].obj.dur}
    + // @ts-ignore + obj[0].obj.dur} `; }, @@ -317,36 +317,47 @@ export class Top10LongestRunTimeProcess extends BaseElement { * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 * @param data 表格中需要排序的数据 */ - sortByColumn(detail: any, data: Array): void { + sortByColumn(detail: unknown, data: Array): void { // @ts-ignore function compare(processThreadCountProperty, sort, type) { - return function (a: any, b: any) { + return function (a: unknown, b: unknown) { if (type === 'number') { // @ts-ignore return sort === 2 + // @ts-ignore ? parseFloat(b[processThreadCountProperty]) - - parseFloat(a[processThreadCountProperty]) + // @ts-ignore + parseFloat(a[processThreadCountProperty]) + // @ts-ignore : parseFloat(a[processThreadCountProperty]) - - parseFloat(b[processThreadCountProperty]); + // @ts-ignore + parseFloat(b[processThreadCountProperty]); } else { if (sort === 2) { + // @ts-ignore return b[processThreadCountProperty] .toString() + // @ts-ignore .localeCompare(a[processThreadCountProperty].toString()); } else { + // @ts-ignore return a[processThreadCountProperty] .toString() + // @ts-ignore .localeCompare(b[processThreadCountProperty].toString()); } } }; } - if ( detail.key === 'pName' || detail.key === 'tName') { + // @ts-ignore + if (detail.key === 'pName' || detail.key === 'tName') { data.sort( + // @ts-ignore compare(detail.key, detail.sort, 'string') ); } else { data.sort( + // @ts-ignore compare(detail.key, detail.sort, 'number') ); } @@ -410,7 +421,7 @@ export class Top10LongestRunTimeProcess extends BaseElement { padding-right: 15px; } - ` + `; } /** diff --git a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts index c2b050d8..a709616f 100644 --- a/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts +++ b/ide/src/trace/component/schedulingAnalysis/processAnalysis/Top10ProcessSwitchCount.ts @@ -93,7 +93,7 @@ export class Top10ProcessSwitchCount extends BaseElement { */ queryLogicWorker(option: string, log: string, handler: (res: Array) => void, pid?: number): void { let processThreadCountTime = new Date().getTime(); - procedurePool.submitWithName('logic0', option, {pid: pid}, undefined, handler); + procedurePool.submitWithName('logic0', option, { pid: pid }, undefined, handler); let durTime = new Date().getTime() - processThreadCountTime; info(log, durTime); } @@ -109,8 +109,8 @@ export class Top10ProcessSwitchCount extends BaseElement { const pStr: string | null = this.processMap.get(arr[i].pid!)!; const tStr: string | null = this.threadMap.get(arr[i].tid!)!; result.push({ - NO: i + 1, - pid: arr[i].pid || this.processId, + NO: i + 1, + pid: arr[i].pid || this.processId, pName: pStr === null ? 'Process ' : pStr, switchCount: arr[i].occurrences, tid: arr[i].tid, @@ -151,25 +151,25 @@ export class Top10ProcessSwitchCount extends BaseElement { label: { type: 'outer', }, - hoverHandler: (data) => { + hoverHandler: (data): void => { if (data) { this.processSwitchCountTbl!.setCurrentHover(data); } else { this.processSwitchCountTbl!.mouseOut(); } }, - tip: (obj) => { + tip: (obj): string => { return `
    Process_Id:${ - // @ts-ignore - obj.obj.pid}
    + // @ts-ignore + obj.obj.pid}
    Process_Name:${ - // @ts-ignore - obj.obj.pName}
    + // @ts-ignore + obj.obj.pName}
    Switch Count:${ - // @ts-ignore - obj.obj.switchCount}
    + // @ts-ignore + obj.obj.switchCount} `; }, @@ -201,28 +201,28 @@ export class Top10ProcessSwitchCount extends BaseElement { label: { type: 'outer', }, - hoverHandler: (data) => { + hoverHandler: (data): void => { if (data) { this.threadSwitchCountTbl!.setCurrentHover(data); } else { this.threadSwitchCountTbl!.mouseOut(); } }, - tip: (obj) => { + tip: (obj): string => { return `
    Thread_Id:${ - // @ts-ignore - obj.obj.tid}
    + // @ts-ignore + obj.obj.tid}
    Thread_Name:${ - // @ts-ignore - obj.obj.tName}
    + // @ts-ignore + obj.obj.tName}
    Switch Count:${ - // @ts-ignore - obj.obj.switchCount}
    + // @ts-ignore + obj.obj.switchCount}
    Process_Id:${ - // @ts-ignore - obj.obj.pid}
    + // @ts-ignore + obj.obj.pid} `; }, @@ -338,25 +338,33 @@ export class Top10ProcessSwitchCount extends BaseElement { * @param detail 点击的列名,以及排序状态0 1 2分别代表不排序、升序排序、降序排序 * @param data 表格中需要排序的数据 */ - sortByColumn(detail: {key: string, sort: number}, data: Array): void { + sortByColumn(detail: { key: string, sort: number }, data: Array): void { // @ts-ignore function compare(processThreadCountProperty, sort, type) { - return function (a: any, b: any) { + return function (a: unknown, b: unknown) { if (type === 'number') { // @ts-ignore return sort === 2 + // @ts-ignore ? parseFloat(b[processThreadCountProperty]) - - parseFloat(a[processThreadCountProperty]) + // @ts-ignore + parseFloat(a[processThreadCountProperty]) + // @ts-ignore : parseFloat(a[processThreadCountProperty]) - - parseFloat(b[processThreadCountProperty]); + // @ts-ignore + parseFloat(b[processThreadCountProperty]); } else { if (sort === 2) { + // @ts-ignore return b[processThreadCountProperty] .toString() + // @ts-ignore .localeCompare(a[processThreadCountProperty].toString()); } else { + // @ts-ignore return a[processThreadCountProperty] .toString() + // @ts-ignore .localeCompare(b[processThreadCountProperty].toString()); } } @@ -426,7 +434,7 @@ export class Top10ProcessSwitchCount extends BaseElement { * 节点html代码块 * @returns 返回节点代码块字符串 */ - initTagHtml() :string { + initTagHtml(): string { return `
    @@ -487,5 +495,5 @@ interface Top10ProcSwiCount { pName?: string, tName?: string, switchCount?: number, - occurrences?: number + occurrences?: number } \ No newline at end of file diff --git a/ide/src/trace/component/setting/SpArkTs.ts b/ide/src/trace/component/setting/SpArkTs.ts index 72d25056..12623ca2 100644 --- a/ide/src/trace/component/setting/SpArkTs.ts +++ b/ide/src/trace/component/setting/SpArkTs.ts @@ -127,7 +127,7 @@ export class SpArkTs extends BaseElement { } else { Cmd.getDebugProcess().then((processList) => { if (processList.length > 0) { - this.processInput!.dataSource(processList, '',true); + this.processInput!.dataSource(processList, '', true); } else { this.processInput!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpHilogRecord.ts b/ide/src/trace/component/setting/SpHilogRecord.ts index da564a8b..1bcc7a70 100644 --- a/ide/src/trace/component/setting/SpHilogRecord.ts +++ b/ide/src/trace/component/setting/SpHilogRecord.ts @@ -71,7 +71,7 @@ export class SpHilogRecord extends BaseElement { } else { Cmd.getProcess().then((processList) => { if (processList.length > 0) { - this.processSelectEl!.dataSource(processList, 'ALL-Process',true); + this.processSelectEl!.dataSource(processList, 'ALL-Process', true); } else { this.processSelectEl!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpHisysEvent.ts b/ide/src/trace/component/setting/SpHisysEvent.ts index cdeb0530..77956729 100644 --- a/ide/src/trace/component/setting/SpHisysEvent.ts +++ b/ide/src/trace/component/setting/SpHisysEvent.ts @@ -106,7 +106,7 @@ export class SpHisysEvent extends BaseElement { this.eventConfig = JSON.parse(dec.decode(buffer)); let domainList = Object.keys(this.eventConfig!); if (domainList.length > 0) { - this.domainInputEL!.dataSource(domainList, 'ALL-Domain',true); + this.domainInputEL!.dataSource(domainList, 'ALL-Domain', true); } else { this.domainInputEL!.dataSource([], ''); } @@ -132,7 +132,7 @@ export class SpHisysEvent extends BaseElement { if (eventConfigElement) { let eventNameList = Object.keys(eventConfigElement); if (eventNameList?.length > 0) { - this.eventNameInputEL!.dataSource(eventNameList, 'ALL-Event',true); + this.eventNameInputEL!.dataSource(eventNameList, 'ALL-Event', true); } else { this.eventNameInputEL!.dataSource([], ''); } @@ -147,7 +147,7 @@ export class SpHisysEvent extends BaseElement { let eventList = Object.keys(currentEvent); currentData.push(...eventList); }); - this.eventNameInputEL!.dataSource(currentData, 'ALL-Event',true); + this.eventNameInputEL!.dataSource(currentData, 'ALL-Event', true); } else { this.eventNameInputEL!.dataSource([], ''); } diff --git a/ide/src/trace/component/setting/SpSdkConfig.ts b/ide/src/trace/component/setting/SpSdkConfig.ts index fe2ed6c0..a8426c1a 100644 --- a/ide/src/trace/component/setting/SpSdkConfig.ts +++ b/ide/src/trace/component/setting/SpSdkConfig.ts @@ -170,7 +170,7 @@ export class SpSdkConfig extends BaseElement { }); } }) - .catch(() => {}); + .catch(() => { }); if (this.worker === null) { // @ts-ignore if (window.useWb) { @@ -178,7 +178,7 @@ export class SpSdkConfig extends BaseElement { } this.worker = new Worker(new URL('../../database/ConfigWorker', import.meta.url)); } - } catch (e) {} + } catch (e) { } } initElements(): void { @@ -200,7 +200,7 @@ export class SpSdkConfig extends BaseElement { inputDiv.addEventListener('mousedown', () => { if (this.startSamp) { inputDiv!.removeAttribute('readonly'); - this.selectConfig!.dataSource(this.wasmList, '',true); + this.selectConfig!.dataSource(this.wasmList, '', true); } else { inputDiv!.setAttribute('readonly', 'readonly'); return; diff --git a/ide/src/trace/component/setting/SpVmTracker.ts b/ide/src/trace/component/setting/SpVmTracker.ts index fbe3b440..c8d53df7 100644 --- a/ide/src/trace/component/setting/SpVmTracker.ts +++ b/ide/src/trace/component/setting/SpVmTracker.ts @@ -80,7 +80,7 @@ export class SpVmTracker extends BaseElement { } else { Cmd.getProcess().then((processList) => { if (processList.length > 0) { - this.vmTrackerProcessInput!.dataSource(processList, '',true); + this.vmTrackerProcessInput!.dataSource(processList, '', true); } else { this.vmTrackerProcessInput!.dataSource([], ''); } diff --git a/ide/src/trace/component/trace/base/TraceRow.ts b/ide/src/trace/component/trace/base/TraceRow.ts index 005cc61e..ccae04b4 100644 --- a/ide/src/trace/component/trace/base/TraceRow.ts +++ b/ide/src/trace/component/trace/base/TraceRow.ts @@ -219,7 +219,7 @@ export class TraceRow extends HTMLElement { asyncFuncStartTID!: number | undefined; protoParentId: string | null | undefined; protoPid: string | undefined; - summaryProtoPid: Array | undefined; + summaryProtoPid: Array | undefined; constructor( args: { @@ -1544,11 +1544,11 @@ export class TraceRow extends HTMLElement { let rectY = myRect.y; let rectHeight = myRect.height; if (!inFavoriteArea && favoriteHeight !== undefined) { - let expand = sessionStorage.getItem('expand'); + let expand = sessionStorage.getItem('expand'); let foldHeight = Number(sessionStorage.getItem('foldHeight')); - y = expand === 'true' ? - (e.offsetY + prevScrollY - 148 - favoriteHeight!) : - (e.offsetY + prevScrollY - (148 - foldHeight) - favoriteHeight!); + y = expand === 'true' ? + (e.offsetY + prevScrollY - 148 - favoriteHeight!) : + (e.offsetY + prevScrollY - (148 - foldHeight) - favoriteHeight!); rectY = this.offsetTop; rectHeight = this.clientHeight; } diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts index d03da128..6f1d0e38 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneCpuByThread.ts @@ -254,7 +254,7 @@ export class TabPaneCpuByThread extends BaseElement { }; data.forEach((obj) => { // 聚合 cpu 数据 - if (obj.cat === "cpu") { + if (obj.cat === 'cpu') { const tidPidKey = `${obj.tid}-${obj.pid}`; const cpuDurationKey = `cpu${obj.cpu}`; const cpuPercentKey = `cpu${obj.cpu}Ratio`; @@ -281,11 +281,11 @@ export class TabPaneCpuByThread extends BaseElement { } // 聚合 softirq 数据 - if (obj.cat === "softirq") { + if (obj.cat === 'softirq') { this.updateIrqAndSoftirq(softirqAggregations, obj, cpuByThreadValue); } // 聚合 irq 数据 - if (obj.cat === "irq") { + if (obj.cat === 'irq') { this.updateIrqAndSoftirq(irqAggregations, obj, cpuByThreadValue); } diff --git a/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts b/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts index 28eb279a..b1a39997 100644 --- a/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts +++ b/ide/src/trace/component/trace/sheet/cpu/TabPaneSchedPriority.ts @@ -142,7 +142,7 @@ export class TabPaneSchedPriority extends BaseElement { private async fetchAndProcessData(): Promise { if (this.strValueMap.size === 0) { - let res = await queryArgsById('next_info', this.selectionParam?.traceId || undefined) + let res = await queryArgsById('next_info', this.selectionParam?.traceId || undefined); await queryThreadStateArgsById(res[0].id, this.selectionParam?.traceId || undefined). then((value): void => { for (const item of value) { diff --git a/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts b/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts index 8330f99e..225de2af 100644 --- a/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts +++ b/ide/src/trace/component/trace/sheet/freq/TabPaneCpuFreqLimits.ts @@ -105,14 +105,14 @@ export class TabPaneCpuFreqLimits extends BaseElement { return item; }); this.cpuFreqLimitSource.sort((a, b): number => { - let cpuLeftData = Number(a.cpu.toString().replace('Cpu','')); - let cpuRightData = Number(b.cpu.toString().replace('Cpu','')); + let cpuLeftData = Number(a.cpu.toString().replace('Cpu', '')); + let cpuRightData = Number(b.cpu.toString().replace('Cpu', '')); if (cpuLeftData > cpuRightData) { return 1; - }else{ + } else { return -1; } - }) + }); this.sortCpuFreqLimitTable(this.cpuFreqLimitSortKey, this.cpuFreqLimitSortType); } @@ -160,12 +160,12 @@ export class TabPaneCpuFreqLimits extends BaseElement { compareCpu(cpuFreqLimitA: unknown, cpuFreqLimitB: unknown, type: number): number { // @ts-ignore - let cpuLeftData = Number(cpuFreqLimitA.cpu.toString().replace('Cpu','')); + let cpuLeftData = Number(cpuFreqLimitA.cpu.toString().replace('Cpu', '')); // @ts-ignore - let cpuRightData = Number(cpuFreqLimitB.cpu.toString().replace('Cpu','')); + let cpuRightData = Number(cpuFreqLimitB.cpu.toString().replace('Cpu', '')); if (type === 1) { return cpuLeftData - cpuRightData; - }else{ + } else { return cpuRightData - cpuLeftData; } } diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts index dac1df4c..f5ba6087 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts @@ -13,29 +13,29 @@ * limitations under the License. */ -import { BaseElement, element } from "../../../../../base-ui/BaseElement"; +import { BaseElement, element } from '../../../../../base-ui/BaseElement'; import { LitTable, RedrawTreeForm, -} from "../../../../../base-ui/table/lit-table"; -import { SelectionParam } from "../../../../bean/BoxSelection"; -import "../../../StackBar"; -import { getTabRunningPercent } from "../../../../database/sql/ProcessThread.sql"; +} from '../../../../../base-ui/table/lit-table'; +import { SelectionParam } from '../../../../bean/BoxSelection'; +import '../../../StackBar'; +import { getTabRunningPercent } from '../../../../database/sql/ProcessThread.sql'; import { queryCpuFreqUsageData, queryCpuFreqFilterId, -} from "../../../../database/sql/Cpu.sql"; -import { Utils } from "../../base/Utils"; -import { resizeObserver } from "../SheetUtils"; -import { SpSegmentationChart } from "../../../chart/SpSegmentationChart"; +} from '../../../../database/sql/Cpu.sql'; +import { Utils } from '../../base/Utils'; +import { resizeObserver } from '../SheetUtils'; +import { SpSegmentationChart } from '../../../chart/SpSegmentationChart'; import { type CpuFreqData, type RunningFreqData, type RunningData, type CpuFreqTd, -} from "./TabPaneFreqUsageConfig"; +} from './TabPaneFreqUsageConfig'; -@element("tabpane-frequsage") +@element('tabpane-frequsage') export class TabPaneFreqUsage extends BaseElement { private threadStatesTbl: LitTable | null | undefined; private currentSelectionParam: SelectionParam | undefined; @@ -117,20 +117,20 @@ export class TabPaneFreqUsage extends BaseElement { */ private threadClick(data: Array): void { let labels = this.threadStatesTbl?.shadowRoot - ?.querySelector(".th > .td")! - .querySelectorAll("label"); + ?.querySelector('.th > .td')! + .querySelectorAll('label'); if (labels) { for (let i = 0; i < labels.length; i++) { let label = labels[i].innerHTML; - labels[i].addEventListener("click", (e) => { - if (label.includes("Process") && i === 0) { + labels[i].addEventListener('click', (e) => { + if (label.includes('Process') && i === 0) { this.threadStatesTbl!.setStatus(data, false); this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement( data, RedrawTreeForm.Retract ); - } else if (label.includes("Thread") && i === 1) { + } else if (label.includes('Thread') && i === 1) { for (let item of data) { // @ts-ignore item.status = true; @@ -143,7 +143,7 @@ export class TabPaneFreqUsage extends BaseElement { data, RedrawTreeForm.Retract ); - } else if (label.includes("CPU") && i === 2) { + } else if (label.includes('CPU') && i === 2) { this.threadStatesTbl!.setStatus(data, true); this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement( @@ -159,11 +159,11 @@ export class TabPaneFreqUsage extends BaseElement { initElements(): void { this.threadStatesTbl = this.shadowRoot?.querySelector( - "#tb-running-percent" + '#tb-running-percent' ); //开启一个线程计算busyTime this.worker = new Worker( - new URL("../../../../database/TabPaneFreqUsageWorker", import.meta.url) + new URL('../../../../database/TabPaneFreqUsageWorker', import.meta.url) ); TabPaneFreqUsage.element = this; } diff --git a/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts b/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts index e16e6aed..ac9e87b1 100644 --- a/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts +++ b/ide/src/trace/component/trace/sheet/hiperf/TabPerfBottomUp.ts @@ -58,7 +58,7 @@ export class TabpanePerfBottomUp extends BaseElement { findSearchNode(this.bottomUpSource, this.searchValue, false); } if (HiPerfStruct.bottomFindCount === 0 && this.bottomUpFilter!.filterValue !== '') { - this.bottomUpTable!.recycleDataSource = [] + this.bottomUpTable!.recycleDataSource = []; } else { this.bottomUpTable!.setStatus(this.bottomUpSource, true); this.setBottomUpTableData(this.bottomUpSource); -- Gitee From e00b1ee2a0136f9616fa65962ab7b5e2cc3b50b4 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 16:09:43 +0800 Subject: [PATCH 81/85] =?UTF-8?q?fix:=E4=BB=A3=E7=A0=81=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E9=81=97=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- .../trace/component/trace/sheet/native-memory/TabPaneNMemory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts index 9933db85..46414ffa 100644 --- a/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts +++ b/ide/src/trace/component/trace/sheet/native-memory/TabPaneNMemory.ts @@ -140,7 +140,7 @@ export class TabPaneNMemory extends BaseElement { let tmpNumber = item.addr.split('x'); //@ts-ignore item.addr = '0x' + Number(tmpNumber[1]).toString(16); - }) + }); this.memorySource = results; this.memoryTbl!.recycleDataSource = this.memorySource; } else { -- Gitee From f1024a1c4d869eee7412d36f435b8184a0eb0e47 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 16:44:03 +0800 Subject: [PATCH 82/85] =?UTF-8?q?fix:=E4=BB=A3=E7=A0=81=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E9=81=97=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- .../component/chart/SpImportUserPluginsChart.ts | 14 +++++++++----- .../component/trace/sheet/hang/TabPaneHang.ts | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts index 3baccaff..27cc14a6 100644 --- a/ide/src/trace/component/chart/SpImportUserPluginsChart.ts +++ b/ide/src/trace/component/chart/SpImportUserPluginsChart.ts @@ -169,9 +169,11 @@ export class SpImportUserPluginsChart { `${ProcessMemStruct.hoverProcessMemStruct?.value || '0'}` ); }; - childRow.onThreadHandler = rowThreadHandler('mem', 'context',//@ts-ignore + childRow.onThreadHandler = rowThreadHandler('mem', 'context', + //@ts-ignore { type: `mem ${element.rowId} ${element.name}` }, childRow, this.trace); - } else if (item.rowType === TraceRow.ROW_TYPE_FUNC) {//处理func + } else if (item.rowType === TraceRow.ROW_TYPE_FUNC) { + //处理func //@ts-ignore if (element.asyncFuncName) { //@ts-ignore @@ -192,9 +194,11 @@ export class SpImportUserPluginsChart { //@ts-ignore FuncStruct.hoverFuncStruct = childRow.getHoverStruct(); }; - childRow.onThreadHandler = rowThreadHandler('func', 'context',//@ts-ignore + childRow.onThreadHandler = rowThreadHandler('func', 'context', + //@ts-ignore { type: '' }, childRow, this.trace); - } else if (item.rowType === TraceRow.ROW_TYPE_THREAD) {//处理thread + } else if (item.rowType === TraceRow.ROW_TYPE_THREAD) { + //处理thread childRow.onThreadHandler = rowThreadHandler('thread', 'context', { type: '', translateY: childRow.translateY }, childRow, this.trace); } @@ -226,7 +230,7 @@ export class SpImportUserPluginsChart { new CustomEvent('file-error') ); } - } + }; }); } } diff --git a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts index b09ded83..4a3d5b1e 100644 --- a/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts +++ b/ide/src/trace/component/trace/sheet/hang/TabPaneHang.ts @@ -286,7 +286,7 @@ export class HangStructInPane { this.dur = getTimeString(parent.dur ?? 0); this.pname = `${parent.pname ?? this.pname} ${parent.pid ?? ''}`.trim(); this.type = SpHangChart.calculateHangType(parent.dur ?? 0); - [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.prio, this.caller] = (parent.content ?? ",0,0,,,").split(',').map(i => i.trim()); + [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.prio, this.caller] = (parent.content ?? ',0,0,,,').split(',').map(i => i.trim()); this.sendEventTid = this.sendEventTid.split(':').at(-1)!; } -- Gitee From a49d8b653cbf586e0b6d474ecaa5af1dfdbe7944 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 19:20:10 +0800 Subject: [PATCH 83/85] =?UTF-8?q?fix:=E4=BB=A3=E7=A0=81=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E6=B8=85=E7=90=86=E9=81=97=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/database/sql/ProcessThread.sql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/database/sql/ProcessThread.sql.ts b/ide/src/trace/database/sql/ProcessThread.sql.ts index e642a44c..1fa345fe 100644 --- a/ide/src/trace/database/sql/ProcessThread.sql.ts +++ b/ide/src/trace/database/sql/ProcessThread.sql.ts @@ -351,7 +351,7 @@ export const queryThreadWakeUpFrom = async (itid: number, startTime: number): Pr `; res = query('queryThreadWakeUpFrom', sql2, {}, { traceId: Utils.currentSelectTrace }); } - return res + return res; }; export const queryRWakeUpFrom = async (itid: number, startTime: number): Promise => { -- Gitee From 342463482950c2357f76dd0590932df4d2bf8ce7 Mon Sep 17 00:00:00 2001 From: liufei Date: Tue, 3 Sep 2024 19:37:54 +0800 Subject: [PATCH 84/85] =?UTF-8?q?fix:=E5=B0=8F=E9=B2=81=E7=8F=AD=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/trace/component/SpBubblesAI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/src/trace/component/SpBubblesAI.ts b/ide/src/trace/component/SpBubblesAI.ts index addf5fdf..662accae 100644 --- a/ide/src/trace/component/SpBubblesAI.ts +++ b/ide/src/trace/component/SpBubblesAI.ts @@ -46,7 +46,7 @@ export class SpBubblesAI extends BaseElement { 'timestamp': new Date().getTime().toString(), 'botUser': 'p_xiaoluban', }; - fetch('https://smartperf.rnd.huawei.com/xiaoluban/resource', { + fetch(`https://${window.location.host}/xiaoluban/resource`, { method: 'post', body: JSON.stringify(data), headers: { -- Gitee From 9a436b55dd0af1fc5c9d4d2322df544d07b9b3a1 Mon Sep 17 00:00:00 2001 From: liufei Date: Wed, 4 Sep 2024 15:11:00 +0800 Subject: [PATCH 85/85] =?UTF-8?q?fix:0904=E4=BB=A3=E7=A0=81=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liufei --- ide/src/base-ui/select/LitSelectV.ts | 3 ++- ide/src/trace/component/SpSystemTrace.ts | 2 +- .../trace/component/chart/SpProcessChart.ts | 6 +++--- .../trace/sheet/frequsage/TabPaneFreqUsage.ts | 20 +++++++++---------- .../trace/sheet/process/TabPaneSliceChild.ts | 2 +- .../ProcedureLogicWorkerSchedulingAnalysis.ts | 16 +++++++++++++-- ide/src/trace/database/sql/Func.sql.ts | 2 +- .../database/ui-worker/ProcedureWorkerHang.ts | 9 ++++++--- 8 files changed, 38 insertions(+), 22 deletions(-) diff --git a/ide/src/base-ui/select/LitSelectV.ts b/ide/src/base-ui/select/LitSelectV.ts index a6d2e5cd..b88b8c86 100644 --- a/ide/src/base-ui/select/LitSelectV.ts +++ b/ide/src/base-ui/select/LitSelectV.ts @@ -302,7 +302,8 @@ export class LitSelectV extends BaseElement { this.shadowRoot?.querySelectorAll('lit-select-option').forEach((o) => { o.removeAttribute('selected'); }); - option.setAttribute('selected', ''); // @ts-ignore + option.setAttribute('selected', ''); + //@ts-ignore this.selectVInputEl!.value = option.textContent!; this.currentvalueStr = option.textContent!; }); diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index 5d1c75c9..8445cf3b 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -2309,7 +2309,7 @@ export class SpSystemTrace extends BaseElement { return dataList; } - showStruct(previous: boolean, currentIndex: number, structs: Array, retargetIndex?: number): number { + showStruct(previous: boolean, currentIndex: number, structs: Array, retargetIndex?: number): number { let tagIndex = spSystemTraceShowStruct(this, previous, currentIndex, structs, retargetIndex); return tagIndex === -1 ? currentIndex : tagIndex; } diff --git a/ide/src/trace/component/chart/SpProcessChart.ts b/ide/src/trace/component/chart/SpProcessChart.ts index 4ac0f5cc..9a5ea115 100644 --- a/ide/src/trace/component/chart/SpProcessChart.ts +++ b/ide/src/trace/component/chart/SpProcessChart.ts @@ -883,7 +883,7 @@ export class SpProcessChart { if (data.pid === thread.tid) { let hangsRow = TraceRow.skeleton(); hangsRow.rowType = TraceRow.ROW_TYPE_HANG_INNER; - hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}` + hangsRow.rowId = `${data.processName ?? 'Process'} ${data.pid}`; hangsRow.rowParentId = `${data.pid}`; hangsRow.rowHidden = !processRow.expansion; hangsRow.style.width = '100%'; @@ -1387,7 +1387,7 @@ export class SpProcessChart { let param: Array = aggregateData[key]; //@ts-ignore this.makeAddAsyncFunction(param, it, processRow, key); - }) + }); //@ts-ignore Reflect.ownKeys(asyncCat).map((key: unknown) => { //@ts-ignore @@ -1399,7 +1399,7 @@ export class SpProcessChart { //@ts-ignore this.makeAddAsyncFunction(param, it, processRow, key, param[0].tid); } - }) + }); } else { //不聚合异步trace let asyncFuncGroup = Utils.groupBy(asyncFuncList, 'funName'); diff --git a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts index f5ba6087..ee8402c3 100644 --- a/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts +++ b/ide/src/trace/component/trace/sheet/frequsage/TabPaneFreqUsage.ts @@ -224,21 +224,21 @@ export class TabPaneFreqUsage extends BaseElement { i--; continue; } - if (arr[i].thread?.indexOf("P") !== -1) { + if (arr[i].thread?.indexOf('P') !== -1) { trackId = Number(arr[i].thread?.slice(1)!); - arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || "Process"} ${trackId}`; - } else if (arr[i].thread === "summary data") { + arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || 'Process'} ${trackId}`; + } else if (arr[i].thread === 'summary data') { } else { - trackId = Number(arr[i].thread!.split("_")[1]); - arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || "Thread"} ${trackId}`; + trackId = Number(arr[i].thread!.split('_')[1]); + arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || 'Thread'} ${trackId}`; } if (arr[i].cpu < 0) { // @ts-ignore - arr[i].cpu = ""; + arr[i].cpu = ''; } // @ts-ignore if (arr[i].frequency < 0) { - arr[i].frequency = ""; + arr[i].frequency = ''; } if (!arr[i].cpuload) { // @ts-ignore @@ -255,9 +255,9 @@ export class TabPaneFreqUsage extends BaseElement { arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ); // @ts-ignore arr[i].consumpower = (arr[i].consumpower / TIME_MUTIPLE).toFixed(MIN_FREQ); - if (arr[i].frequency !== "") { - if (arr[i].frequency === "unknown") { - arr[i].frequency = "unknown"; + if (arr[i].frequency !== '') { + if (arr[i].frequency === 'unknown') { + arr[i].frequency = 'unknown'; } else { arr[i].frequency = arr[i].frequency; } diff --git a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts index 9e7e1aaa..1e3cdfa8 100644 --- a/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts +++ b/ide/src/trace/component/trace/sheet/process/TabPaneSliceChild.ts @@ -23,7 +23,7 @@ import { getTabDetails, getGhDetails, getSfDetails } from '../../../../database/ @element('box-slice-child') export class TabPaneSliceChild extends BaseElement { private sliceChildTbl: LitTable | null | undefined; - private boxChildSource: Array = []; + private boxChildSource: Array = []; private sliceChildParam: { param: SliceBoxJumpParam, selection: SelectionParam } | null | undefined; set data(boxChildValue: { param: SliceBoxJumpParam, selection: SelectionParam | null | undefined }) { diff --git a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts index 2944bd58..9533835a 100644 --- a/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts +++ b/ide/src/trace/database/logic-worker/ProcedureLogicWorkerSchedulingAnalysis.ts @@ -358,34 +358,46 @@ export class ProcedureLogicWorkerSchedulingAnalysis extends LogicHandler { this.queryThreadStateByTid(data.params.tid); } } - private schedulingProTop10Swicount(data: any): void { + private schedulingProTop10Swicount(data: unknown): void { + // @ts-ignore if (data.params.list) { + // @ts-ignore let arr = convertJSON(data.params.list) || []; self.postMessage({ + // @ts-ignore id: data.id, + // @ts-ignore action: data.action, results: arr, }); arr = []; } else { + // @ts-ignore if (data.params.pid) { + // @ts-ignore this.queryThrTop10Swicount(data.params.pid); } else { this.queryProTop10Swicount(); } } } - private schedulingProcessRunTime(data: any): void { + private schedulingProcessRunTime(data: unknown): void { + // @ts-ignore if (data.params.list) { + // @ts-ignore let arr = convertJSON(data.params.list) || []; self.postMessage({ + // @ts-ignore id: data.id, + // @ts-ignore action: data.action, results: arr, }); arr = []; } else { + // @ts-ignore if (data.params.pid) { + // @ts-ignore this.queryThrTop10RunTime(data.params.pid); } else { this.queryProTop10RunTime(); diff --git a/ide/src/trace/database/sql/Func.sql.ts b/ide/src/trace/database/sql/Func.sql.ts index cb1f230f..f2a47dc5 100644 --- a/ide/src/trace/database/sql/Func.sql.ts +++ b/ide/src/trace/database/sql/Func.sql.ts @@ -348,7 +348,7 @@ export const getTabSlicesAsyncFunc = ( order by wallDuration desc;`; return query('getTabSlicesAsyncFunc', sql, {}); -} +}; export const getTabDetails = ( asyncNames: Array, diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts index 74af12b2..203c80e0 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerHang.ts @@ -83,9 +83,12 @@ export class HangStruct extends BaseStruct { dur: number | undefined; tid: number | undefined; pid: number | undefined; - type: HangType | undefined; // 手动补充 按时间分类 - pname: string | undefined; // 手动补充 - content: string | undefined; // 手动补充 在tab页中需要手动解析内容 + // 手动补充 按时间分类 + type: HangType | undefined; + // 手动补充 + pname: string | undefined; + // 手动补充 在tab页中需要手动解析内容 + content: string | undefined; name: string | undefined; static getFrameColor(data: HangStruct): string { -- Gitee