• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../../../../base-ui/BaseElement';
17import { SelectionParam } from '../../../../bean/BoxSelection';
18import { Rect, drawString } from '../../../../database/ui-worker/ProcedureWorkerCommon';
19import { ColorUtils } from '../../base/ColorUtils';
20import { SampleStruct } from '../../../../database/ui-worker/ProcedureWorkerBpftrace';
21import { SpApplication } from '../../../../SpApplication';
22
23const SAMPLE_STRUCT_HEIGHT = 30;
24const Y_PADDING = 4;
25
26@element('tab-sample-instruction-selection')
27export class TabPaneSampleInstructionSelection extends BaseElement {
28  private instructionEle: HTMLCanvasElement | undefined | null;
29  private ctx: CanvasRenderingContext2D | undefined | null;
30  private textEle: HTMLSpanElement | undefined | null;
31  private instructionArray: Array<unknown> = [];
32  private instructionData: Array<unknown> = [];
33  private isUpdateCanvas = false;
34  private canvasX = -1; // 鼠标当前所在画布x坐标
35  private canvasY = -1; // 鼠标当前所在画布y坐标
36  private startX = 0; // 画布相对于整个界面的x坐标
37  private startY = 0; // 画布相对于整个界面的y坐标
38  private hintContent = ''; //悬浮框内容
39  private floatHint: HTMLDivElement | undefined | null; //悬浮框
40  private canvasScrollTop = 0; // tab页上下滚动位置
41  private hoverSampleStruct: unknown | undefined;
42  private isChecked: boolean = false;
43  private maxDepth = 0;
44
45  initHtml(): string {
46    return `
47      <style>
48        .frame-tip {
49          position: absolute;
50          left: 0;
51          background-color: white;
52          border: 1px solid #F9F9F9;
53          width: auto;
54          font-size: 14px;
55          color: #50809e;
56          padding: 2px 10px;
57          box-sizing: border-box;
58          display: none;
59          max-width: 400px;
60        }
61        .text {
62          max-width: 350px;
63          word-break: break-all;
64        }
65        .title {
66          font-size: 14px;
67          padding: 0 5px;
68        }
69        :host {
70          display: flex;
71          flex-direction: column;
72          padding: 10px;
73        }
74        .root {
75          width: 100%;
76          display: flex;
77          flex-direction: column;
78        }
79        .tip {
80          height: 30px;
81          line-height: 30px;
82          text-align: center;
83          font-size: 14px;
84        }
85      </style>
86      <div class="root">
87        <canvas id="instruct-select-canvas"></canvas>
88        <div id="select_float_hint" class="frame-tip"></div>
89        <div class="tip">
90          <span class="headline">指令数数据流</span>
91        </div>
92      </div>
93    `;
94  }
95
96  initElements(): void {
97    this.instructionEle = this.shadowRoot?.querySelector('#instruct-select-canvas');
98    this.textEle = this.shadowRoot?.querySelector('.headline');
99    this.ctx = this.instructionEle?.getContext('2d');
100    this.floatHint = this.shadowRoot?.querySelector('#select_float_hint');
101  }
102
103  set data(SampleParam: SelectionParam) {
104    this.hoverSampleStruct = undefined;
105    this.instructionData = SampleParam.sampleData;
106    this.getAvgInstructionData(this.instructionData);
107    queueMicrotask(() => {
108      this.updateCanvas(this.clientWidth);
109      this.drawInstructionData(this.isChecked);
110    });
111  }
112
113  connectedCallback(): void {
114    super.connectedCallback();
115    this.parentElement!.onscroll = () => {
116      this.canvasScrollTop = this.parentElement!.scrollTop;
117      this.hideTip();
118    };
119    this.instructionEle!.onmousemove = (e): void => {
120      if (!this.isUpdateCanvas) {
121        this.updateCanvasCoord();
122      }
123      this.canvasX = e.clientX - this.startX;
124      this.canvasY = e.clientY - this.startY + this.canvasScrollTop;
125      this.onMouseMove();
126    };
127    this.instructionEle!.onmouseleave = () => {
128      this.hideTip();
129    };
130    document.addEventListener('sample-popver-change', (e: unknown) => {
131      // @ts-ignore
132      const select = Number(e.detail.select);
133      this.hoverSampleStruct = undefined;
134      this.isChecked = Boolean(select);
135      this.drawInstructionData(this.isChecked);
136    });
137    this.listenerResize();
138  }
139
140  /**
141   * 初始化窗口大小
142   * @param newWidth
143   */
144  updateCanvas(newWidth?: number): void {
145    if (this.instructionEle instanceof HTMLCanvasElement) {
146      this.instructionEle.style.width = `${100}%`;
147      this.instructionEle.style.height = `${(this.maxDepth + 1) * SAMPLE_STRUCT_HEIGHT}px`;
148      if (this.instructionEle.clientWidth === 0 && newWidth) {
149        this.instructionEle.width = newWidth;
150      } else {
151        this.instructionEle.width = this.instructionEle.clientWidth;
152      }
153      this.instructionEle.height = (this.maxDepth + 1) * SAMPLE_STRUCT_HEIGHT;
154    }
155  }
156
157  /**
158   * 鼠标移动
159   */
160  onMouseMove(): void {
161    const lastNode = this.hoverSampleStruct;
162    //查找鼠标所在的node
163    const searchResult = this.searchDataByCoord(this.instructionArray!, this.canvasX, this.canvasY);
164    if (searchResult) {
165      this.hoverSampleStruct = searchResult;
166      //鼠标悬浮的node未改变则不需重新渲染文字
167      if (searchResult !== lastNode) {
168        this.updateTipContent();
169        this.ctx!.clearRect(0, 0, this.instructionEle!.width, this.instructionEle!.height);
170        this.ctx!.beginPath();
171        for (const key in this.instructionArray) {
172          // @ts-ignore
173          for (let i = 0; i < this.instructionArray[key].length; i++) {
174            // @ts-ignore
175            const cur = this.instructionArray[key][i];
176            this.draw(this.ctx!, cur);
177          }
178        }
179        this.ctx!.closePath();
180      }
181      this.showTip();
182    } else {
183      this.hideTip();
184      this.hoverSampleStruct = undefined;
185    }
186  }
187
188  /**
189   *  隐藏悬浮框
190   */
191  hideTip(): void {
192    if (this.floatHint) {
193      this.floatHint.style.display = 'none';
194    }
195  }
196
197  /**
198   * 显示悬浮框
199   */
200  showTip(): void {
201    this.floatHint!.innerHTML = this.hintContent;
202    this.floatHint!.style.display = 'block';
203    let x = this.canvasX;
204    let y = this.canvasY - this.canvasScrollTop;
205    //右边的函数悬浮框显示在左侧
206    if (this.canvasX + this.floatHint!.clientWidth > this.instructionEle!.clientWidth || 0) {
207      x -= this.floatHint!.clientWidth - 1;
208    } else {
209      x += 30;
210    }
211    //最下边的函数悬浮框显示在上方
212    y -= this.floatHint!.clientHeight - 1;
213    this.floatHint!.style.transform = `translate(${x}px, ${y}px)`;
214  }
215
216  /**
217   * 更新悬浮框内容
218   * @returns
219   */
220  updateTipContent(): void {
221    const hoverNode = this.hoverSampleStruct;
222    if (!hoverNode) {
223      return;
224    }
225    // @ts-ignore
226    this.hintContent = `<span class="text">${hoverNode.detail}(${hoverNode.name})</span></br>
227      <span class="text">${
228      // @ts-ignore
229      this.isChecked ? hoverNode.hoverCycles : hoverNode.hoverInstructions}
230      </span>
231    `;
232  }
233
234  /**
235   * 设置绘制所需的坐标及宽高
236   * @param sampleNode
237   * @param instructions
238   * @param x
239   */
240  setSampleFrame(sampleNode: SampleStruct, instructions: number, x: number): void {
241    if (!sampleNode.frame) {
242      sampleNode.frame! = new Rect(0, 0, 0, 0);
243    }
244    sampleNode.frame!.x = x;
245    sampleNode.frame!.y = sampleNode.depth! * SAMPLE_STRUCT_HEIGHT;
246    sampleNode.frame!.width = instructions;
247    sampleNode.frame!.height = SAMPLE_STRUCT_HEIGHT;
248  }
249
250  /**
251   * 判断鼠标当前在那个函数上
252   * @param frame
253   * @param x
254   * @param y
255   * @returns
256   */
257  isContains(frame: unknown, x: number, y: number): boolean {
258    // @ts-ignore
259    return x >= frame.x && x <= frame.x + frame.width && frame.y <= y && y <= frame.y + frame.height;
260  }
261
262  /**
263   * 绘制
264   * @param isCycles
265   */
266  drawInstructionData(isCycles: boolean): void {
267    this.isChecked ? (this.textEle!.innerText = 'cycles数据流') : (this.textEle!.innerText = 'instructions数据流');
268    const clientWidth = this.instructionEle!.width;
269    //将数据转换为层级结构
270    const instructionArray = this.instructionData
271      // @ts-ignore
272      .filter((item: unknown) => (isCycles ? item.cycles : item.instructions))
273      .reduce((pre: unknown, cur: unknown) => {
274        // @ts-ignore
275        (pre[`${cur.depth}`] = pre[`${cur.depth}`] || []).push(cur);
276        return pre;
277      }, {});
278    // @ts-ignore
279    for (const key in instructionArray) {
280      // @ts-ignore
281      for (let i = 0; i < instructionArray[key].length; i++) {
282        // @ts-ignore
283        const cur = instructionArray[key][i];
284        //第一级节点直接将宽度设置为容器宽度
285        if (key === '0') {
286          this.setSampleFrame(cur, clientWidth, 0);
287        } else {
288          //获取上一层级节点数据
289          // @ts-ignore
290          const preList = instructionArray[Number(key) - 1];
291          //获取当前节点的父节点
292          const parentNode = preList.find((node: SampleStruct) => node.name === cur.parentName);
293          //计算当前节点下指令数之和 用于计算每个节点所占的宽度比
294          const total = isCycles
295            // @ts-ignore
296            ? instructionArray[key]
297              // @ts-ignore
298              .filter((i: unknown) => i.parentName === parentNode.name)
299              .reduce((pre: number, cur: SampleStruct) => pre + cur.cycles!, 0)
300            // @ts-ignore
301            : instructionArray[key]
302              // @ts-ignore
303              .filter((i: unknown) => i.parentName === parentNode.name)
304              .reduce((pre: number, cur: SampleStruct) => pre + cur.instructions!, 0);
305          const curWidth = isCycles ? cur.cycles : cur.instructions;
306          const width = Math.floor(parentNode.frame.width * (curWidth / total));
307          if (i === 0) {
308            this.setSampleFrame(cur, width, parentNode.frame.x);
309          } else {
310            // @ts-ignore
311            const preNode = instructionArray[key][i - 1];
312            preNode.parentName === parentNode.name
313              ? this.setSampleFrame(cur, width, preNode.frame.x + preNode.frame.width)
314              : this.setSampleFrame(cur, width, parentNode.frame.x);
315          }
316        }
317      }
318    }
319    // @ts-ignore
320    this.instructionArray = instructionArray;
321
322    this.ctx!.clearRect(0, 0, this.instructionEle!.width, this.instructionEle!.height);
323    this.ctx!.beginPath();
324    // @ts-ignore
325    for (const key in instructionArray) {
326      // @ts-ignore
327      for (let i = 0; i < instructionArray[key].length; i++) {
328        // @ts-ignore
329        const cur = instructionArray[key][i];
330        this.draw(this.ctx!, cur);
331      }
332    }
333    this.ctx!.closePath();
334  }
335
336  /**
337   * 更新canvas坐标
338   */
339  updateCanvasCoord(): void {
340    if (this.instructionEle instanceof HTMLCanvasElement) {
341      this.isUpdateCanvas = this.instructionEle.clientWidth !== 0;
342      if (this.instructionEle.getBoundingClientRect()) {
343        const box = this.instructionEle.getBoundingClientRect();
344        const D = document.documentElement;
345        this.startX = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
346        this.startY = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop + this.canvasScrollTop;
347      }
348    }
349  }
350
351  /**
352   * 获取鼠标悬停的函数
353   * @param nodes
354   * @param canvasX
355   * @param canvasY
356   * @returns
357   */
358  searchDataByCoord(nodes: unknown, canvasX: number, canvasY: number): void | null {
359    // @ts-ignore
360    for (const key in nodes) {
361      // @ts-ignore
362      for (let i = 0; i < nodes[key].length; i++) {
363        // @ts-ignore
364        const cur = nodes[key][i];
365        if (this.isContains(cur.frame, canvasX, canvasY)) {
366          return cur;
367        }
368      }
369    }
370    return null;
371  }
372
373  /**
374   * 绘制方法
375   * @param ctx
376   * @param data
377   */
378  draw(ctx: CanvasRenderingContext2D, data: SampleStruct) {
379    let spApplication = <SpApplication>document.getElementsByTagName('sp-application')[0];
380    if (data.frame) {
381      ctx.globalAlpha = 1;
382      ctx.fillStyle =
383        ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', data.depth!, ColorUtils.FUNC_COLOR.length)];
384      const textColor =
385        ColorUtils.FUNC_COLOR[ColorUtils.hashFunc(data.name || '', data.depth!, ColorUtils.FUNC_COLOR.length)];
386      ctx.lineWidth = 0.4;
387      // @ts-ignore
388      if (this.hoverSampleStruct && data.name == this.hoverSampleStruct.name) {
389        if (spApplication.dark) {
390          ctx.strokeStyle = '#fff';
391        } else {
392          ctx.strokeStyle = '#000';
393        }
394      } else {
395        if (spApplication.dark) {
396          ctx.strokeStyle = '#000';
397        } else {
398          ctx.strokeStyle = '#fff';
399        }
400      }
401      ctx.strokeRect(data.frame.x, data.frame.y, data.frame.width, SAMPLE_STRUCT_HEIGHT - Y_PADDING);
402      ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, SAMPLE_STRUCT_HEIGHT - Y_PADDING);
403      ctx.fillStyle = ColorUtils.funcTextColor(textColor);
404      drawString(ctx, `${data.detail}(${data.name})`, 5, data.frame, data);
405    }
406  }
407
408  /**
409   * 统计框选指令数的平均值
410   * @param instructionData
411   * @returns
412   */
413  getAvgInstructionData(instructionData: Array<unknown>): unknown {
414    // @ts-ignore
415    const length = instructionData[0].property.length;
416    // @ts-ignore
417    const knowData = instructionData.filter((instruction) => instruction.name.indexOf('unknown') < 0);
418    knowData.forEach((instruction) => {
419      // @ts-ignore
420      if (instruction.property.length > 0) {
421        // @ts-ignore
422        const totalInstruction = instruction.property.reduce(
423          (pre: number, cur: SampleStruct) => pre + Math.ceil(cur.instructions!),
424          0
425        );
426        // @ts-ignore
427        const totalCycles = instruction.property.reduce(
428          (pre: number, cur: SampleStruct) => pre + Math.ceil(cur.cycles!),
429          0
430        );
431        // @ts-ignore
432        instruction.instructions = Math.ceil(totalInstruction / length) || 1;
433        // @ts-ignore
434        instruction.cycles = Math.ceil(totalCycles / length) || 1;
435        // @ts-ignore
436        instruction.hoverInstructions = Math.ceil(totalInstruction / length);
437        // @ts-ignore
438        instruction.hoverCycles = Math.ceil(totalCycles / length);
439        // @ts-ignore
440        this.maxDepth = Math.max(this.maxDepth, instruction.depth);
441      }
442    });
443    // @ts-ignore
444    const unknownData = instructionData.filter((instruction) => instruction.name.indexOf('unknown') > -1);
445    let instructionSum = 0;
446    let cyclesSum = 0;
447    let hoverInstructionsSum = 0;
448    let hoverCyclesSum = 0;
449    unknownData.forEach((unknown) => {
450      instructionSum = 0;
451      cyclesSum = 0;
452      hoverInstructionsSum = 0;
453      hoverCyclesSum = 0;
454      // @ts-ignore
455      for (const key in unknown.children) {
456        // @ts-ignore
457        const child = instructionData.find((instruction) => instruction.name === key);
458        // @ts-ignore
459        instructionSum += child.instructions ?? 0;
460        // @ts-ignore
461        cyclesSum += child.cycles ?? 0;
462        // @ts-ignore
463        hoverInstructionsSum += child.hoverInstructions ?? 0;
464        // @ts-ignore
465        hoverCyclesSum += child.hoverCycles ?? 0;
466      }
467      // @ts-ignore
468      unknown.instructions = instructionSum;
469      // @ts-ignore
470      unknown.cycles = cyclesSum;
471      // @ts-ignore
472      unknown.hoverInstructions = hoverInstructionsSum;
473      // @ts-ignore
474      unknown.hoverCycles = hoverCyclesSum;
475    });
476    return instructionData;
477  }
478
479  /**
480   * 监听页面size变化
481   */
482  listenerResize(): void {
483    new ResizeObserver(() => {
484      if (this.instructionEle!.getBoundingClientRect()) {
485        const box = this.instructionEle!.getBoundingClientRect();
486        const element = document.documentElement;
487        this.startX = box.left + Math.max(element.scrollLeft, document.body.scrollLeft) - element.clientLeft;
488        this.startY =
489          box.top + Math.max(element.scrollTop, document.body.scrollTop) - element.clientTop + this.canvasScrollTop;
490      }
491    }).observe(this.parentElement!);
492  }
493}
494