diff --git a/ide/src/trace/bean/BoxSelection.ts b/ide/src/trace/bean/BoxSelection.ts index a3d73099ac25fd6e9d085947bab0ad3b44261ad6..080ba368b065bf53cf336ebe2da99b63b50ad43e 100644 --- a/ide/src/trace/bean/BoxSelection.ts +++ b/ide/src/trace/bean/BoxSelection.ts @@ -31,6 +31,7 @@ export class SelectionParam { diskIOLatency: boolean = false; fsCount: number = 0; vmCount: number = 0; + isCurrentPane: boolean = false; cpus: Array = []; cpuStateFilterIds: Array = []; diff --git a/ide/src/trace/component/SpSystemTrace.ts b/ide/src/trace/component/SpSystemTrace.ts index d7f27ce16c363f53799377e318dbfdfad63539fd..16d1e12e83d9dd3bcbbc44d1b86cb2f565454c97 100644 --- a/ide/src/trace/component/SpSystemTrace.ts +++ b/ide/src/trace/component/SpSystemTrace.ts @@ -26,7 +26,7 @@ import { SelectionParam } from '../bean/BoxSelection.js'; import { procedurePool } from '../database/Procedure.js'; import { SpApplication } from '../SpApplication.js'; import { Flag } from './trace/timer-shaft/Flag.js'; -import { SportRuler } from './trace/timer-shaft/SportRuler.js'; +import { SportRuler, SlicesTime } from './trace/timer-shaft/SportRuler.js'; import { SpHiPerf } from './chart/SpHiPerf.js'; import { SearchSdkBean, SearchThreadProcessBean } from '../bean/SearchFuncBean.js'; import { error, info } from '../../log/Log.js'; @@ -65,6 +65,9 @@ import { Utils } from './trace/base/Utils.js'; import { IrqStruct } from '../database/ui-worker/ProcedureWorkerIrq.js'; import { JanksStruct } from '../bean/JanksStruct.js'; import { JankStruct } from '../database/ui-worker/ProcedureWorkerJank.js'; +import { tabConfig } from './trace/base/TraceSheetConfig.js'; +import { TabPaneCurrent } from './trace/sheet/TabPaneCurrent.js'; +import { LitTabpane } from '../../base-ui/tabs/lit-tabpane.js'; import { HeapStruct } from '../database/ui-worker/ProcedureWorkerHeap.js'; import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil.js'; import { HeapSnapshotStruct } from '../database/ui-worker/ProcedureWorkerHeapSnapshot.js'; @@ -78,6 +81,22 @@ import { TabPaneCurrentSelection } from './trace/sheet/TabPaneCurrentSelection.j function dpr() { return window.devicePixelRatio || 1; } +//节流处理 +function throttle(fn: any, t: number, ev: any): any { + let timer: any = null; + return function () { + if (!timer) { + timer = setTimeout(function () { + if (ev) { + fn(ev); + } else { + fn(); + } + timer = null; + }, t); + } + } +} @element('sp-system-trace') export class SpSystemTrace extends BaseElement { @@ -111,7 +130,8 @@ export class SpSystemTrace extends BaseElement { static btnTimer: any = null; isMousePointInSheet = false; hoverFlag: Flag | undefined | null = undefined; - selectFlag: Flag | undefined | null = undefined; + selectFlag: Flag | undefined | null; + slicestime: SlicesTime | undefined | null = null; public timerShaftEL: TimerShaftElement | null | undefined; private traceSheetEL: TraceSheet | undefined | null; private rangeSelect!: RangeSelect; @@ -124,6 +144,7 @@ export class SpSystemTrace extends BaseElement { canvasPanelCtx: CanvasRenderingContext2D | undefined | null; linkNodes: PairPoint[][] = []; public currentClickRow: HTMLDivElement | undefined | null; + private litTabs: LitTabs | undefined | null; eventMap: any = {}; private isSelectClick: boolean = false; private selectionParam: SelectionParam | undefined; @@ -192,6 +213,14 @@ export class SpSystemTrace extends BaseElement { this.refreshCanvas(true); } }); + document?.addEventListener('slices-change', (event: any) => { + this.timerShaftEL?.modifySlicesList(event.detail); + if (event.detail.hidden) { + this.slicestime = null; + this.traceSheetEL?.setAttribute('mode', 'hidden'); + this.refreshCanvas(true); + } + }); if (this.timerShaftEL?.collecBtn) { this.timerShaftEL.collecBtn.onclick = () => { if (this.timerShaftEL!.collecBtn!.hasAttribute('close')) { @@ -234,8 +263,8 @@ export class SpSystemTrace extends BaseElement { `trace-row[row-id='${rowParentId}']` ); parentRows?.forEach((parentRow) => { - if (parentRow?.name && parentRow?.name != currentRow.name && !parentRow.rowType!.startsWith('cpu') && !parentRow.rowType!.startsWith('thread')) { - console.log(parentRow.name); + if (parentRow?.name && parentRow?.name != currentRow.name && !parentRow.rowType!.startsWith('cpu') + && !parentRow.rowType!.startsWith('thread') && !parentRow.rowType!.startsWith('func')) { currentRow.name += "(" + parentRow.name + ")" } }) @@ -711,6 +740,7 @@ export class SpSystemTrace extends BaseElement { } else { this.traceSheetEL?.rangeSelect(selection); } + this.timerShaftEL!.selectionList.push(selection);// 保持选中对象,为后面的再次选中该框选区域做准备。 this.selectionParam = selection; }; // @ts-ignore @@ -739,7 +769,8 @@ export class SpSystemTrace extends BaseElement { if (TraceRow.rangeSelectObject && SpSystemTrace.sliceRangeMark) { this.timerShaftEL?.setSlicesMark( TraceRow.rangeSelectObject.startNS || 0, - TraceRow.rangeSelectObject.endNS || 0 + TraceRow.rangeSelectObject.endNS || 0, + false ); SpSystemTrace.sliceRangeMark = undefined; window.publish(window.SmartEvent.UI.RefreshCanvas, {}); @@ -790,7 +821,7 @@ export class SpSystemTrace extends BaseElement { window.subscribe(window.SmartEvent.UI.SliceMark, (data) => { this.sliceMarkEventHandler(data); }); - window.subscribe(window.SmartEvent.UI.TraceRowComplete, (tr) => {}); + window.subscribe(window.SmartEvent.UI.TraceRowComplete, (tr) => { }); window.subscribe(window.SmartEvent.UI.RefreshCanvas, () => { this.refreshCanvas(false); }); @@ -928,6 +959,19 @@ export class SpSystemTrace extends BaseElement { this.refreshCanvas(true, 'flagChange'); }; + timerShaftELRangeClick = (sliceTime: SlicesTime | undefined | null) => { + if (sliceTime) { + setTimeout(() => { + this.traceSheetEL?.displayCurrent(sliceTime); // 给当前pane准备数据 + let selection = this.timerShaftEL!.selectionMap.get(sliceTime.id); + if (selection) { + selection.isCurrentPane = true; // 设置当前面板为可以显示的状态 + this.traceSheetEL?.rangeSelect(selection); // 显示选中区域对应的面板 + } + }, 0); + } + }; + timerShaftELRangeChange = (e: any) => { TraceRow.range = e; if (TraceRow.rangeSelectObject) { @@ -1044,14 +1088,18 @@ export class SpSystemTrace extends BaseElement { y: 0, width: this.timerShaftEL?.canvas?.clientWidth, height: this.canvasPanel?.clientHeight, - }); + }, + this.timerShaftEL! + ); //draw flag line segment for favorite canvas drawFlagLineSegment(this.canvasFavoritePanelCtx, this.hoverFlag, this.selectFlag, { x: 0, y: 0, width: this.timerShaftEL?.canvas?.clientWidth, height: this.canvasFavoritePanel?.clientHeight, - }); + }, + this.timerShaftEL! + ); //draw wakeup for main canvas drawWakeUp( this.canvasPanelCtx, @@ -1139,11 +1187,15 @@ export class SpSystemTrace extends BaseElement { x > (TraceRow.rangeSelectObject?.startX || 0) && x < (TraceRow.rangeSelectObject?.endX || 0) ) { - let time = Math.round( - (x * (TraceRow.range?.endNS! - TraceRow.range?.startNS!)) / this.timerShaftEL!.canvas!.offsetWidth + + let findSlicestime = this.timerShaftEL!.sportRuler?.findSlicesTime(x, y); // 查找帽子 + if (!findSlicestime) { // 如果没有找到帽子,则绘制一个三角形的旗子 + let time = Math.round( + (x * (TraceRow.range?.endNS! - TraceRow.range?.startNS!)) / + this.timerShaftEL!.canvas!.offsetWidth + TraceRow.range?.startNS! - ); - this.timerShaftEL!.sportRuler!.drawTriangle(time, 'triangle'); + ); + this.timerShaftEL!.sportRuler!.drawTriangle(time, 'triangle'); + } } else { this.rangeSelect.mouseDown(ev); this.rangeSelect.drag = true; @@ -1175,17 +1227,8 @@ export class SpSystemTrace extends BaseElement { this.rangeSelect.isMouseDown = false; if ((window as any).isSheetMove) return; if (this.isMouseInSheet(ev)) return; - let x = ev.offsetX - this.timerShaftEL!.canvas!.offsetLeft; - let y = ev.offsetY; - if ( - this.timerShaftEL!.sportRuler!.frame.contains(x, y) && - x > (TraceRow.rangeSelectObject?.startX || 0) && - x < (TraceRow.rangeSelectObject?.endX || 0) - ) { - } else { - this.rangeSelect.mouseUp(ev); - this.timerShaftEL?.documentOnMouseUp(ev); - } + this.rangeSelect.mouseUp(ev); + this.timerShaftEL?.documentOnMouseUp(ev); ev.preventDefault(); ev.stopPropagation(); }; @@ -1216,7 +1259,24 @@ export class SpSystemTrace extends BaseElement { this.observerScrollHeightEnable = false; if (this.keyboardEnable) { if (keyPress == 'm') { - this.setSLiceMark(); + this.slicestime = this.setSLiceMark(ev.shiftKey); + // 设置currentPane可以显示,并且修改调色板颜色和刚刚绘制的帽子颜色保持一致。 + this.traceSheetEL = this.shadowRoot?.querySelector('.trace-sheet'); + let currentPane = this.traceSheetEL?.displayTab('tabpane-current'); + if (this.slicestime) { + currentPane?.setCurrentSlicesTime(this.slicestime) + } + // 显示对应的面板信息 + this.timerShaftEL!.selectionList.forEach((selection, index) => { + if (this.timerShaftEL!.selectionList.length - 1 == index) { + // 把最新添加的 SelectionParam 对象设置为可以显示当前面板 + selection.isCurrentPane = true; + this.traceSheetEL?.rangeSelect(selection); + } else { + // 其他 SelectionParam 对象设置为不显示当前面板 + selection.isCurrentPane = false; + } + }); } let keyPressWASD = keyPress === 'w' || keyPress === 'a' || keyPress === 's' || keyPress === 'd'; if (keyPressWASD) { @@ -1229,37 +1289,52 @@ export class SpSystemTrace extends BaseElement { } }; - setSLiceMark() { + setSLiceMark(shiftKey: boolean): SlicesTime | null | undefined { if (CpuStruct.selectCpuStruct) { - this.timerShaftEL?.setSlicesMark( + this.slicestime = this.timerShaftEL?.setSlicesMark( CpuStruct.selectCpuStruct.startTime || 0, - (CpuStruct.selectCpuStruct.startTime || 0) + (CpuStruct.selectCpuStruct.dur || 0) + (CpuStruct.selectCpuStruct.startTime || 0) + + (CpuStruct.selectCpuStruct.dur || 0), + shiftKey ); } else if (ThreadStruct.selectThreadStruct) { - this.timerShaftEL?.setSlicesMark( + this.slicestime = this.timerShaftEL?.setSlicesMark( ThreadStruct.selectThreadStruct.startTime || 0, - (ThreadStruct.selectThreadStruct.startTime || 0) + (ThreadStruct.selectThreadStruct.dur || 0) + (ThreadStruct.selectThreadStruct.startTime || 0) + + (ThreadStruct.selectThreadStruct.dur || 0), + shiftKey ); } else if (FuncStruct.selectFuncStruct) { - this.timerShaftEL?.setSlicesMark( + this.slicestime = this.timerShaftEL?.setSlicesMark( FuncStruct.selectFuncStruct.startTs || 0, - (FuncStruct.selectFuncStruct.startTs || 0) + (FuncStruct.selectFuncStruct.dur || 0) + (FuncStruct.selectFuncStruct.startTs || 0) + + (FuncStruct.selectFuncStruct.dur || 0), + shiftKey ); } else if (IrqStruct.selectIrqStruct) { - this.timerShaftEL?.setSlicesMark( + this.slicestime = this.timerShaftEL?.setSlicesMark( IrqStruct.selectIrqStruct.startNS || 0, - (IrqStruct.selectIrqStruct.startNS || 0) + (IrqStruct.selectIrqStruct.dur || 0) + (IrqStruct.selectIrqStruct.startNS || 0) + + (IrqStruct.selectIrqStruct.dur || 0), + shiftKey ); } else if (TraceRow.rangeSelectObject) { - this.timerShaftEL?.setSlicesMark(TraceRow.rangeSelectObject.startNS || 0, TraceRow.rangeSelectObject.endNS || 0); + this.slicestime = this.timerShaftEL?.setSlicesMark( + TraceRow.rangeSelectObject.startNS || 0, + TraceRow.rangeSelectObject.endNS || 0, + shiftKey + ); } else if (JankStruct.selectJankStruct) { - this.timerShaftEL?.setSlicesMark( + this.slicestime = this.timerShaftEL?.setSlicesMark( JankStruct.selectJankStruct.ts || 0, - (JankStruct.selectJankStruct.ts || 0) + (JankStruct.selectJankStruct.dur || 0) + (JankStruct.selectJankStruct.ts || 0) + + (JankStruct.selectJankStruct.dur || 0), + shiftKey ); } else { - this.timerShaftEL?.setSlicesMark(); + this.slicestime = this.timerShaftEL?.setSlicesMark(); } + return this.slicestime; } stopWASD = () => { @@ -2002,12 +2077,56 @@ export class SpSystemTrace extends BaseElement { } } + myMouseMove = (ev: MouseEvent) => { + if (ev.ctrlKey) { + ev.preventDefault(); + SpSystemTrace.offsetMouse = + ev.clientX - SpSystemTrace.mouseCurrentPosition; + let eventA = new KeyboardEvent('keypress', { + key: 'a', + code: '65', + keyCode: 65, + }); + let eventD = new KeyboardEvent('keypress', { + key: 'd', + code: '68', + keyCode: 68, + }); + if (ev.button == 0) { + if ( + SpSystemTrace.offsetMouse < 0 && + SpSystemTrace.moveable + ) { + // 向右拖动,则泳道图右移 + this.timerShaftEL!.documentOnKeyPress(eventD); + setTimeout(() => { + this.timerShaftEL!.documentOnKeyUp(eventD); + }, 350); + } + if ( + SpSystemTrace.offsetMouse > 0 && + SpSystemTrace.moveable + ) { + // 向左拖动,则泳道图左移 + this.timerShaftEL!.documentOnKeyPress(eventA); + setTimeout(() => { + this.timerShaftEL!.documentOnKeyUp(eventA); + }, 350); + } + } + SpSystemTrace.moveable = false; + } + } + + + connectedCallback() { this.initPointToEvent(); /** * 监听时间轴区间变化 */ this.timerShaftEL!.rangeChangeHandler = this.timerShaftELRangeChange; + this.timerShaftEL!.rangeClickHandler = this.timerShaftELRangeClick this.timerShaftEL!.flagChangeHandler = this.timerShaftELFlagChange; this.timerShaftEL!.flagClickHandler = this.timerShaftELFlagClickHandler; /** @@ -2054,39 +2173,7 @@ export class SpSystemTrace extends BaseElement { */ this.addEventListener( 'mousemove', - (e) => { - if (e.ctrlKey) { - e.preventDefault(); - SpSystemTrace.offsetMouse = e.clientX - SpSystemTrace.mouseCurrentPosition; - let eventA = new KeyboardEvent('keypress', { - key: 'a', - code: '65', - keyCode: 65, - }); - let eventD = new KeyboardEvent('keypress', { - key: 'd', - code: '68', - keyCode: 68, - }); - if (e.button == 0) { - if (SpSystemTrace.offsetMouse < 0 && SpSystemTrace.moveable) { - // 向右拖动,则泳道图右移 - this.timerShaftEL!.documentOnKeyPress(eventD); - setTimeout(() => { - this.timerShaftEL!.documentOnKeyUp(eventD); - }, 350); - } - if (SpSystemTrace.offsetMouse > 0 && SpSystemTrace.moveable) { - // 向左拖动,则泳道图左移 - this.timerShaftEL!.documentOnKeyPress(eventA); - setTimeout(() => { - this.timerShaftEL!.documentOnKeyUp(eventA); - }, 350); - } - } - SpSystemTrace.moveable = false; - } - }, + ev => throttle(this.myMouseMove, 350, ev)(), { passive: false } ); @@ -2108,12 +2195,6 @@ export class SpSystemTrace extends BaseElement { { passive: false } ); - /** - * 泳道图中添加ctrl+鼠标滚轮事件,对泳道图进行放大缩小。 - * 鼠标滚轮事件转化为键盘事件,keyPress和keyUp两个事件需要配合使用, - * 否则泳道图会一直放大或一直缩小。 - * setTimeout()函数中的时间参数可以控制鼠标滚轮的频率。 - */ document.addEventListener( 'wheel', (e) => { diff --git a/ide/src/trace/component/trace/TimerShaftElement.ts b/ide/src/trace/component/trace/TimerShaftElement.ts index 83b01e80f53ec213adcd9bd7285b5cf2331f4917..06cf4f6097993dfccb78b0f16908808128ced823 100644 --- a/ide/src/trace/component/trace/TimerShaftElement.ts +++ b/ide/src/trace/component/trace/TimerShaftElement.ts @@ -17,10 +17,17 @@ import { BaseElement, element } from '../../../base-ui/BaseElement.js'; import { TimeRuler } from './timer-shaft/TimeRuler.js'; import { Rect } from './timer-shaft/Rect.js'; import { RangeRuler, TimeRange } from './timer-shaft/RangeRuler.js'; -import { SportRuler } from './timer-shaft/SportRuler.js'; +import { SlicesTime, SportRuler } from './timer-shaft/SportRuler.js'; import { procedurePool } from '../../database/Procedure.js'; import { Flag } from './timer-shaft/Flag.js'; import { info } from '../../../log/Log.js'; +import { tabConfig } from './base/TraceSheetConfig.js'; +import { TraceSheet } from './base/TraceSheet.js'; +import { LitTabs } from '../../../base-ui/tabs/lit-tabs.js'; +import { LitTabpane } from '../../../base-ui/tabs/lit-tabpane.js'; +import { TabPaneCurrent } from './sheet/TabPaneCurrent.js'; +import { SelectionParam } from '../../bean/BoxSelection.js'; +import { SpSystemTrace } from '../SpSystemTrace.js'; //随机生成十六位进制颜色 export function randomRgbColor() { @@ -79,6 +86,7 @@ export class TimerShaftElement extends BaseElement { public loadComplete: boolean = false; public collecBtn: HTMLElement | null | undefined; rangeChangeHandler: ((timeRange: TimeRange) => void) | undefined = undefined; + rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined = undefined; flagChangeHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined = undefined; flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined = undefined; @@ -100,7 +108,12 @@ export class TimerShaftElement extends BaseElement { private _totalNS: number = 10_000_000_000; private _startNS: number = 0; private _endNS: number = 10_000_000_000; - + private traceSheetEL: TraceSheet | undefined | null; + private sliceTime: SlicesTime | undefined | null; + public selectionList: Array = []; + public selectionMap: Map = new Map(); + + get sportRuler(): SportRuler | undefined { return this._sportRuler; } @@ -153,6 +166,9 @@ export class TimerShaftElement extends BaseElement { this.rangeRuler.markBObj.frame.x = this.rangeRuler.frame.width; this.rangeRuler.cpuUsage = []; this.sportRuler!.flagList.length = 0; + this.sportRuler!.slicesTimeList.length = 0; + this.selectionList.length = 0; + this.selectionMap.clear(); this.sportRuler!.isRangeSelect = false; this.setSlicesMark(); } @@ -171,10 +187,6 @@ export class TimerShaftElement extends BaseElement { window.subscribe(window.SmartEvent.UI.TimeRange, (b) => this.setRangeNS(b.startNS, b.endNS)); } - getRangeRuler() { - return this.rangeRuler; - } - connectedCallback() { if (this.canvas) { if (this.isOffScreen) { @@ -201,8 +213,10 @@ export class TimerShaftElement extends BaseElement { }, (flag) => { this.flagClickHandler?.(flag); - } - ); + }, + (slicetime) => { + this.rangeClickHandler?.(slicetime); + }); } if (!this.rangeRuler) { this.rangeRuler = new RangeRuler( @@ -251,6 +265,10 @@ export class TimerShaftElement extends BaseElement { return this.rangeRuler?.getRange(); } + getRangeRuler() { + return this.rangeRuler; + } + updateWidth(width: number) { this.dpr = window.devicePixelRatio || 1; this.canvas!.width = width - (this.totalEL?.clientWidth || 0); @@ -282,11 +300,20 @@ export class TimerShaftElement extends BaseElement { }; documentOnMouseMove = (ev: MouseEvent) => { - this.rangeRuler?.mouseMove(ev); - if (this.sportRuler?.edgeDetection(ev)) { - this.sportRuler?.mouseMove(ev); + let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标的x轴坐标 + let y = ev.offsetY; // 鼠标的y轴坐标 + let findSlicestime = this.sportRuler?.findSlicesTime(x, y); // 查找帽子 + if (!findSlicestime) { // 如果在该位置没有找到一个“帽子”,则可以显示一个旗子。 + this.sportRuler?.showHoverFlag(); + this.rangeRuler?.mouseMove(ev); + if (this.sportRuler?.edgeDetection(ev)) { + this.sportRuler?.mouseMove(ev); + } else { + this.sportRuler?.mouseOut(ev); + } } else { - this.sportRuler?.mouseOut(ev); + this.sportRuler?.clearHoverFlag(); + this.sportRuler?.modifyFlagList(null);//重新绘制旗子,清除hover flag } }; @@ -355,6 +382,10 @@ export class TimerShaftElement extends BaseElement { this._sportRuler?.modifyFlagList(flag); } + modifySlicesList(slicestime: SlicesTime | null | undefined) { + this._sportRuler?.modifySicesTimeList(slicestime); + } + cancelPressFrame() { this.rangeRuler?.cancelPressFrame(); } @@ -375,8 +406,21 @@ export class TimerShaftElement extends BaseElement { this._sportRuler?.removeTriangle(type); } - setSlicesMark(startTime: null | number = null, endTime: null | number = null) { - this._sportRuler?.setSlicesMark(startTime, endTime); + setSlicesMark(startTime: null | number = null, endTime: null | number = null, shiftKey: null | boolean = false): SlicesTime | null | undefined { + let sliceTime = this._sportRuler?.setSlicesMark(startTime, endTime, shiftKey); + if (sliceTime && sliceTime != undefined) { + this.traceSheetEL?.displayCurrent(sliceTime); // 给当前pane准备数据 + + // 取最新创建的那个selection对象 + let selection = this.selectionList[this.selectionList.length - 1]; + if (selection) { + selection.isCurrentPane = true; // 设置当前面板为可以显示的状态 + //把刚刚创建的slicetime和selection对象关联起来,以便后面再次选中“跑道”的时候显示对应的面板。 + this.selectionMap.set(sliceTime.id, selection); + this.traceSheetEL?.rangeSelect(selection); // 显示选中区域对应的面板 + } + } + return sliceTime; } @@ -455,7 +499,7 @@ export class TimerShaftElement extends BaseElement { 10 0
- +
diff --git a/ide/src/trace/component/trace/base/RangeSelect.ts b/ide/src/trace/component/trace/base/RangeSelect.ts index fb9c98d80849f932811f66ef25891418ebc78562..4ab5da2558d09bb5c36eecb7ae84d352621b2032 100644 --- a/ide/src/trace/component/trace/base/RangeSelect.ts +++ b/ide/src/trace/component/trace/base/RangeSelect.ts @@ -232,10 +232,6 @@ export class RangeSelect { rt = new Rect(xMin, Math.min(this.startY2, this.endY2), xMax - xMin, Math.abs(this.startY2 - this.endY2)); } else { bound = it.getBoundingClientRect(); - if (spacerRect.height > 0 && bound.y + bound.height < spacerRect.y + spacerRect.height) { - it.rangeSelect = false; - return false; - } itRect = Rect.getIntersect( bound, new Rect(rowsRect.x, rowsRect.y + spacerRect.height, rowsRect.width, rowsRect.height - spacerRect.height) diff --git a/ide/src/trace/component/trace/base/TraceSheet.ts b/ide/src/trace/component/trace/base/TraceSheet.ts index 7c1306ac196e9517c8bf8f8227280553ab0be23d..8ee02db5afd7e85390c080f3bc9018c16620a307 100644 --- a/ide/src/trace/component/trace/base/TraceSheet.ts +++ b/ide/src/trace/component/trace/base/TraceSheet.ts @@ -41,7 +41,8 @@ import { HeapSnapshotStruct } from '../../../database/ui-worker/ProcedureWorkerH import { TabPaneComparison } from '../sheet/snapshot/TabPaneComparison.js'; import { TabPaneSummary } from '../sheet/snapshot/TabPaneSummary.js'; import { TabPaneNMStatisticAnalysis } from '../sheet/native-memory/TabPaneNMStatisticAnalysis.js'; - +import { TabPaneCurrent } from '../sheet/TabPaneCurrent.js'; +import { SlicesTime } from '../timer-shaft/SportRuler.js'; @element('trace-sheet') export class TraceSheet extends BaseElement { private litTabs: LitTabs | undefined | null; @@ -339,7 +340,8 @@ export class TraceSheet extends BaseElement { `; } - + displayCurrent = (data: SlicesTime) => + this.displayTab('tabpane-current').setCurrentSlicesTime(data); displayThreadData = ( data: ThreadStruct, scrollCallback: ((e: ThreadStruct) => void) | undefined, @@ -518,7 +520,7 @@ export class TraceSheet extends BaseElement { clearMemory() { let allTabs = Array.from(this.shadowRoot?.querySelectorAll('#tabs lit-tabpane').values() || []); - allTabs.forEach( tab => { + allTabs.forEach(tab => { if (tab) { let tables = Array.from( (tab.firstChild as BaseElement).shadowRoot?.querySelectorAll('lit-table') || [] diff --git a/ide/src/trace/component/trace/base/TraceSheetConfig.ts b/ide/src/trace/component/trace/base/TraceSheetConfig.ts index 6a2f819acdd682645eee2e1a1584e6f1cd893e66..22eee39f141a77c266b9729424dea93595107f90 100644 --- a/ide/src/trace/component/trace/base/TraceSheetConfig.ts +++ b/ide/src/trace/component/trace/base/TraceSheetConfig.ts @@ -72,8 +72,14 @@ import { TabPanePerfAnalysis } from '../sheet/hiperf/TabPanePerfAnalysis.js'; import { TabPaneNMStatisticAnalysis } from '../sheet/native-memory/TabPaneNMStatisticAnalysis.js'; import { TabPaneFilesystemStatisticsAnalysis } from '../sheet/file-system/TabPaneFilesystemStatisticsAnalysis.js'; import { TabPaneIOTierStatisticsAnalysis } from '../sheet/file-system/TabPaneIOTierStatisticsAnalysis.js'; +import { TabPaneCurrent } from '../sheet/TabPaneCurrent.js'; import { TabPaneVirtualMemoryStatisticsAnalysis } from '../sheet/file-system/TabPaneVirtualMemoryStatisticsAnalysis.js'; export let tabConfig: any = { + 'tabpane-current': { + title: 'Current Selection', + type: TabPaneCurrent, + require: (param: SelectionParam) => param.isCurrentPane, + }, //current selection 'current-selection': { title: 'Current Selection', type: TabPaneCurrentSelection, diff --git a/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts new file mode 100644 index 0000000000000000000000000000000000000000..82a42797330a003bc0c6640292dc6e3aa6693418 --- /dev/null +++ b/ide/src/trace/component/trace/sheet/TabPaneCurrent.ts @@ -0,0 +1,119 @@ +/* + * 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.js'; +import { SlicesTime } from '../timer-shaft/SportRuler'; + +@element('tabpane-current') +export class TabPaneCurrent extends BaseElement { + private slicestime: SlicesTime | null = null; + initElements(): void { + this.shadowRoot?.querySelector('#color-input')?.addEventListener('change', (event: any) => { + if (this.slicestime) { + this.slicestime.color = event?.target.value; + document.dispatchEvent(new CustomEvent('slices-change', { detail: this.slicestime })); + } + }); + this.shadowRoot?.querySelector('#text-input')?.addEventListener('keyup', (event: any) => { + event.stopPropagation(); + if (event.keyCode == '13') { + if (this.slicestime) { + window.publish(window.SmartEvent.UI.KeyboardEnable, { + enable: true, + }); + this.slicestime.text = event?.target.value; + document.dispatchEvent( + new CustomEvent('slices-change', { + detail: this.slicestime, + }) + ); + } + } + }); + this.shadowRoot?.querySelector('#text-input')?.addEventListener('blur', (event: any) => { + (window as any).flagInputFocus = false; + window.publish(window.SmartEvent.UI.KeyboardEnable, { + enable: true, + }); + }); + this.shadowRoot?.querySelector('#text-input')?.addEventListener('focus', (event: any) => { + (window as any).flagInputFocus = true; + window.publish(window.SmartEvent.UI.KeyboardEnable, { + enable: false, + }); + }); + this.shadowRoot?.querySelector('#remove')?.addEventListener('click', (event: any) => { + if (this.slicestime) { + this.slicestime.hidden = true; + document.dispatchEvent(new CustomEvent('slices-change', { detail: this.slicestime })); + } + }); + } + + setCurrentSlicesTime(slicestime: SlicesTime) { + this.slicestime = slicestime; + this.shadowRoot!.querySelector('#color-input')!.value = this.slicestime.color; + this.shadowRoot!.querySelector('#text-input')!.value = this.slicestime.text; + } + + initHtml(): string { + return ` + +
+
Annotation at
+ + Change color: + +
+ `; + } +} diff --git a/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts b/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts index 3d1bce6d170cd014c7e823e379115d3949ffc8ec..ec16e5b5d7c90eece3b3f1a69656a7d70cd0cc64 100644 --- a/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts +++ b/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts @@ -497,6 +497,7 @@ export class RangeRuler extends Graph { keyPressW() { let animW = () => { if (this.scale === 50) { + this.fillX(); this.range.refresh = true; this.notifyHandler(this.range); this.range.refresh = false; @@ -515,6 +516,7 @@ export class RangeRuler extends Graph { keyPressS() { let animS = () => { if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) { + this.fillX(); this.range.refresh = true; this.notifyHandler(this.range); this.range.refresh = false; @@ -532,13 +534,15 @@ export class RangeRuler extends Graph { keyPressA() { let animA = () => { - if (this.range.startNS == 0) { + + if (this.range.startNS <= 0) { + this.fillX(); this.range.refresh = true; this.notifyHandler(this.range); this.range.refresh = false; return; } - let s = (this.scale / this.p) * this.currentDuration * 1.2; + let s = (this.scale / this.p) * this.currentDuration * 0.4; this.range.startNS -= s; this.range.endNS -= s; this.fillX(); @@ -552,6 +556,7 @@ export class RangeRuler extends Graph { keyPressD() { let animD = () => { if (this.range.endNS >= this.range.totalNS) { + this.fillX(); this.range.refresh = true; this.notifyHandler(this.range); this.range.refresh = false; diff --git a/ide/src/trace/component/trace/timer-shaft/SportRuler.ts b/ide/src/trace/component/trace/timer-shaft/SportRuler.ts index 4a99b542b3fd82ef64ce88b6872686b1545aa6cb..83c7192907524094e335b061342948a15ce7e31a 100644 --- a/ide/src/trace/component/trace/timer-shaft/SportRuler.ts +++ b/ide/src/trace/component/trace/timer-shaft/SportRuler.ts @@ -20,10 +20,57 @@ import { Flag } from './Flag.js'; import { ns2s, ns2x, randomRgbColor, TimerShaftElement } from '../TimerShaftElement.js'; import { TraceRow } from '../base/TraceRow.js'; import { SpApplication } from '../../../SpApplication.js'; +import { Utils } from '../base/Utils.js'; +export enum StType { + TEMP, //临时的 + PERM, // 永久的 +} + +export class SlicesTime { + private _id: string; + startTime: number | null | undefined; + endTime: number | null | undefined; + startNS: number; + endNS: number; + color: string = ''; + startX: number; + endX: number; + selected: boolean = true; + hidden: boolean = false; + text: string = ''; + type: number = StType.PERM; // 默认类型为永久的 + constructor( + startTime: number | null | undefined, + endTime: number | null | undefined, + startNS: number, + endNS: number, + startX: number, + endX: number, + color: string, + selected: boolean = true + ) { + this._id = Utils.uuid(); + this.startTime = startTime; + this.endTime = endTime; + this.startNS = startNS; + this.endNS = endNS; + this.color = color; + this.startX = startX; + this.endX = endX; + this.selected = selected; + } + + get id(): string { + return this._id; + } +} + +const TRIWIDTH = 10; // 定义三角形的边长 export class SportRuler extends Graph { static isMouseInSportRuler = false; public flagList: Array = []; + public slicesTimeList: Array = []; isRangeSelect: boolean = false; //region selection private hoverFlag: Flag = new Flag(-1, 0, 0, 0, 0); private lineColor: string | null = null; @@ -33,6 +80,7 @@ export class SportRuler extends Graph { | ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined; private readonly flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined; + private readonly rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined; private invertedTriangleTime: number | null | undefined = null; private slicesTime: { startTime: number | null | undefined; @@ -48,11 +96,13 @@ export class SportRuler extends Graph { timerShaftEL: TimerShaftElement, frame: Rect, notifyHandler: (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void, - flagClickHandler: (flag: Flag | undefined | null) => void + flagClickHandler: (flag: Flag | undefined | null) => void, + rangeClickHandler: (sliceTime: SlicesTime | undefined | null) => void ) { super(timerShaftEL.canvas, timerShaftEL.ctx!, frame); this.notifyHandler = notifyHandler; this.flagClickHandler = flagClickHandler; + this.rangeClickHandler = rangeClickHandler; this.timerShaftEL = timerShaftEL; } @@ -79,6 +129,24 @@ export class SportRuler extends Graph { } this.draw(); } + modifySicesTimeList(slicestime: SlicesTime | null | undefined) { + if (slicestime) { + let i = this.slicesTimeList.findIndex((it) => it.id == slicestime.id); + if (slicestime.hidden) { + this.slicesTimeList.splice(i, 1); + let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id); + this.timerShaftEL?.selectionMap.delete(slicestime.id); + if (selectionParam) { + this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1); + } + } else { + this.slicesTimeList[i] = slicestime; + } + } else { + this.slicesTimeList.forEach((it) => (it.selected = false)); + } + this.draw(); + } draw(): void { this.rulerW = this.canvas!.offsetWidth; @@ -160,7 +228,9 @@ export class SportRuler extends Graph { document.querySelector('sp-application')!.dark ? '#FFFFFF' : '#000000' ); } - this.drawSlicesMark(this.slicesTime?.startTime, this.slicesTime?.endTime); + this.slicesTimeList.forEach((slicesTime) => { + this.drawSlicesMarks(slicesTime); + }); } drawTriangle(time: number, type: string) { @@ -229,6 +299,7 @@ export class SportRuler extends Graph { ); } + // 绘制一个倒三角 ▼ drawInvertedTriangle(time: number, color: string = '#000000') { if (time != null && typeof time != undefined) { let x = Math.round((this.rulerW * (time - this.range.startNS)) / (this.range.endNS - this.range.startNS)); @@ -244,50 +315,117 @@ export class SportRuler extends Graph { } } - setSlicesMark(startTime: number | null = null, endTime: number | null = null) { + setSlicesMark( + startTime: number | null = null, + endTime: number | null = null, + shiftKey: boolean | null = null + ): SlicesTime | null { + let newSlicestime: SlicesTime | null = null; if (startTime != null && typeof startTime != undefined && endTime != null && typeof endTime != undefined) { this.slicesTime = { startTime: startTime <= endTime ? startTime : endTime, endTime: startTime <= endTime ? endTime : startTime, color: null, }; + let startX = Math.round( + (this.rulerW * (startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) + ); + + let endX = Math.round((this.rulerW * (endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)); + let color = randomRgbColor(); + this.slicesTime.color = color; + newSlicestime = new SlicesTime( + this.slicesTime.startTime, + this.slicesTime.endTime, + this.range.startNS, + this.range.endNS, + startX, + endX, + color, + true + ); + + if (!shiftKey) { + this.clearTempSlicesTime(); // 清除临时对象 + + // 如果没有按下shift键,则把当前slicestime对象的类型设为临时类型。 + newSlicestime.type = StType.TEMP; + } + this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false)); + newSlicestime.selected = true; + this.slicesTimeList.push(newSlicestime); } else { + this.clearTempSlicesTime();// 清除临时对象 this.slicesTime = { startTime: null, endTime: null, color: null }; } this.range.slicesTime = this.slicesTime; + this.draw(); this.timerShaftEL?.render(); + return newSlicestime; + } + + // 清除临时对象 + clearTempSlicesTime() { + // 清除以前放入的临时对象 + this.slicesTimeList.forEach((slicestime, index) => { + slicestime.selected = false; + if (slicestime.type == StType.TEMP) { + this.slicesTimeList.splice(index, 1); + let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id); + if (selectionParam && selectionParam != undefined) { + this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1); + this.timerShaftEL?.selectionMap.delete(slicestime.id); + } + } + }); } clearHoverFlag() { this.hoverFlag.hidden = true; } - drawSlicesMark(startTime: number | null = null, endTime: number | null = null) { - if (startTime != null && typeof startTime != undefined && endTime != null && typeof endTime != undefined) { + showHoverFlag() { + this.hoverFlag.hidden = false; + } + + //功能描述: 绘制多个帽子(两个三角形中间加一条横线) + // ________________ + // |/ \| + drawSlicesMarks(slicesTime: SlicesTime) { + if ( + slicesTime.startTime != null && + typeof slicesTime.startTime != undefined && + slicesTime.endTime != null && + typeof slicesTime.endTime != undefined + ) { let startX = Math.round( - (this.rulerW * (startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) + (this.rulerW * (slicesTime.startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); - let endX = Math.round((this.rulerW * (endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)); + + let endX = Math.round( + (this.rulerW * (slicesTime.endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) + ); + + // 放大、缩小、左右移动之后重置小三角的x轴坐标 + slicesTime.startX = startX; + slicesTime.endX = endX; + this.c.beginPath(); - if (document.querySelector('sp-application')!.dark) { - this.c.strokeStyle = '#FFF'; - this.c.fillStyle = '#FFF'; - this.range.slicesTime.color = '#FFF'; - } else { - this.c.strokeStyle = '#344596'; - this.c.fillStyle = '#344596'; - this.range.slicesTime.color = '#344596'; - } - this.c.moveTo(startX + 9, 132); - this.c.lineTo(startX, 141); + this.c.strokeStyle = slicesTime.color; + this.c.fillStyle = slicesTime.color; + this.range.slicesTime.color = slicesTime.color; //紫色 + + this.c.moveTo(startX + TRIWIDTH, 132); + this.c.lineTo(startX, 142); this.c.lineTo(startX, 132); - this.c.lineTo(startX + 9, 132); + this.c.lineTo(startX + TRIWIDTH, 132); - this.c.lineTo(endX - 9, 132); + this.c.lineTo(endX - TRIWIDTH, 132); this.c.lineTo(endX, 132); - this.c.lineTo(endX, 141); - this.c.lineTo(endX - 9, 132); + this.c.lineTo(endX, 142); + this.c.lineTo(endX - TRIWIDTH, 132); this.c.closePath(); + slicesTime.selected && this.c.fill(); this.c.stroke(); this.c.beginPath(); @@ -299,7 +437,7 @@ export class SportRuler extends Graph { this.c.fillStyle = '#000'; } let lineWidth = endX - startX; - let txt = ns2s((endTime || 0) - (startTime || 0)); + let txt = ns2s((slicesTime.endTime || 0) - (slicesTime.startTime || 0)); this.c.moveTo(startX, this.frame.y + 22); this.c.lineTo(endX, this.frame.y + 22); this.c.moveTo(startX, this.frame.y + 22 - 5); @@ -358,24 +496,57 @@ export class SportRuler extends Graph { } } + /** + * 查找鼠标所在位置是否存在"帽子"对象,为了操作方便,框选时把三角形的边长宽度左右各加一个像素。 + * @param x 水平坐标值 + * @returns + */ + findSlicesTime(x: number, y: number): SlicesTime | null { + let slicestime = this.slicesTimeList.find((slicesTime) => { + return ( + ((x >= slicesTime.startX - 1 && x <= slicesTime.startX + TRIWIDTH + 1) || // 选中了帽子的左边三角形区域 + (x >= slicesTime.endX - TRIWIDTH - 1 && x <= slicesTime.endX + 1)) && // 选中了帽子的右边三角形区域 + y >= 132 && + y <= 142 + ); + }); + + if (!slicestime) { + return null; + } + return slicestime; + } mouseUp(ev: MouseEvent) { if (this.edgeDetection(ev)) { - this.flagList.forEach((it) => (it.selected = false)); - let x = ev.offsetX - (this.canvas?.offsetLeft || 0); - let findFlag = this.flagList.find((it) => x >= it.x && x <= it.x + 18); - if (findFlag) { - findFlag.selected = true; + let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标点击的x轴坐标 + let y = ev.offsetY; // 鼠标点击的y轴坐标 + let findSlicestime = this.findSlicesTime(x, y); // 查找帽子 + if (findSlicestime) { + // 如果找到帽子,则选中帽子。 + this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false)); + findSlicestime.selected = true; + this.rangeClickHandler && this.rangeClickHandler(findSlicestime); } else { - let flagAtRulerTime = Math.round(((this.range.endNS - this.range.startNS) * x) / this.rulerW); - if (flagAtRulerTime > 0 && this.range.startNS + flagAtRulerTime < this.range.endNS) { - this.flagList.push(new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), true)); + // 如果没有找到帽子,则绘制旗子,此处避免旗子和帽子重叠。 + // 查找旗子 + let findFlag = this.flagList.find((it) => x >= it.x && x <= it.x + 18); + this.flagList.forEach((it) => (it.selected = false)); + if (findFlag) { + findFlag.selected = true; + } else { + let flagAtRulerTime = Math.round(((this.range.endNS - this.range.startNS) * x) / this.rulerW); + if (flagAtRulerTime > 0 && this.range.startNS + flagAtRulerTime < this.range.endNS) { + let flag = new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), true,''); + this.flagList.push(flag); + } } + this.flagClickHandler && this.flagClickHandler(this.flagList.find((it) => it.selected)); // 绘制旗子 } - this.flagClickHandler && this.flagClickHandler(this.flagList.find((it) => it.selected)); } } mouseMove(ev: MouseEvent) { + let x = ev.offsetX - (this.canvas?.offsetLeft || 0); if (this.edgeDetection(ev)) { let x = ev.offsetX - (this.canvas?.offsetLeft || 0); let flg = this.flagList.find((it) => x >= it.x && x <= it.x + 18); diff --git a/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts b/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts index 392a8a5ff99b6f6a592eedad26ca5623cfec5e37..7697dfecf7ad59d8defe8be8487b3cdfee115033 100644 --- a/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts +++ b/ide/src/trace/database/ui-worker/ProcedureWorkerCommon.ts @@ -457,7 +457,7 @@ export function drawFlagLine( } } -export function drawFlagLineSegment(ctx: any, hoverFlag: any, selectFlag: any, frame: any) { +export function drawFlagLineSegment(ctx: any, hoverFlag: any, selectFlag: any, frame: any, tse: TimerShaftElement) { if (ctx) { if (hoverFlag) { ctx.beginPath(); @@ -484,31 +484,22 @@ export function drawFlagLineSegment(ctx: any, hoverFlag: any, selectFlag: any, f ctx.stroke(); ctx.closePath(); } - if (TraceRow.range!.slicesTime && TraceRow.range!.slicesTime.startTime && TraceRow.range!.slicesTime.endTime) { - ctx.beginPath(); - ctx.lineWidth = 1; - ctx.strokeStyle = TraceRow.range!.slicesTime.color || '#dadada'; - let x1 = ns2x( - TraceRow.range!.slicesTime.startTime, - TraceRow.range!.startNS, - TraceRow.range!.endNS, - TraceRow.range!.totalNS, - frame - ); - let x2 = ns2x( - TraceRow.range!.slicesTime.endTime, - TraceRow.range!.startNS, - TraceRow.range!.endNS, - TraceRow.range!.totalNS, - frame - ); - ctx.moveTo(Math.floor(x1), 0); - ctx.lineTo(Math.floor(x1), frame.height); - ctx.moveTo(Math.floor(x2), 0); - ctx.lineTo(Math.floor(x2), frame.height); - ctx.stroke(); - ctx.closePath(); - } + tse.sportRuler!.slicesTimeList.forEach((slicesTime) => { + if (slicesTime && slicesTime.startTime && slicesTime.endTime) { + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = slicesTime.color || '#dadada'; + let x1 = ns2x(slicesTime.startTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame); + let x2 = ns2x(slicesTime.endTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame); + // 划线逻辑 + ctx.moveTo(Math.floor(x1), 0); + ctx.lineTo(Math.floor(x1), frame.height); //左边的线 + ctx.moveTo(Math.floor(x2), 0); + ctx.lineTo(Math.floor(x2), frame.height); // 右边的线 + ctx.stroke(); + ctx.closePath(); + } + }); } }