• 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 { Rect, drawString } from '../../../../database/ui-worker/ProcedureWorkerCommon';
18import { ColorUtils } from '../../base/ColorUtils';
19import { SampleStruct } from '../../../../database/ui-worker/ProcedureWorkerBpftrace';
20import { SpApplication } from '../../../../SpApplication';
21
22const SAMPLE_STRUCT_HEIGHT = 30;
23const Y_PADDING = 4;
24
25@element('tab-sample-instruction')
26export class TabPaneSampleInstruction extends BaseElement {
27  private instructionEle: HTMLCanvasElement | undefined | null;
28  private ctx: CanvasRenderingContext2D | undefined | null;
29  private textEle: HTMLSpanElement | undefined | null;
30  private instructionArray: Array<unknown> = [];
31  private instructionData: Array<unknown> = [];
32  private flattenTreeData: 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-canvas"></canvas>
88        <div id="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-canvas');
98    this.textEle = this.shadowRoot?.querySelector('.headline');
99    this.ctx = this.instructionEle?.getContext('2d');
100    this.floatHint = this.shadowRoot?.querySelector('#float_hint');
101  }
102
103  connectedCallback(): void {
104    super.connectedCallback();
105    this.parentElement!.onscroll = () => {
106      this.canvasScrollTop = this.parentElement!.scrollTop;
107      this.hideTip();
108    };
109    this.instructionEle!.onmousemove = (e): void => {
110      if (!this.isUpdateCanvas) {
111        this.updateCanvasCoord();
112      }
113      this.canvasX = e.clientX - this.startX;
114      this.canvasY = e.clientY - this.startY + this.canvasScrollTop;
115      this.onMouseMove();
116    };
117    this.instructionEle!.onmouseleave = () => {
118      this.hideTip();
119    };
120    document.addEventListener('sample-popver-change', (e: unknown) => {
121      // @ts-ignore
122      const select = Number(e.detail.select);
123      this.isChecked = Boolean(select);
124      this.hoverSampleStruct = undefined;
125      this.drawInstructionData(this.isChecked);
126    });
127    this.listenerResize();
128  }
129  /**
130   * 初始化窗口大小
131   * @param newWidth
132   */
133  updateCanvas(newWidth?: number): void {
134    if (this.instructionEle instanceof HTMLCanvasElement) {
135      this.instructionEle.style.width = `${100}%`;
136      this.instructionEle.style.height = `${(this.maxDepth + 1) * SAMPLE_STRUCT_HEIGHT}px`;
137      if (this.instructionEle.clientWidth === 0 && newWidth) {
138        this.instructionEle.width = newWidth;
139      } else {
140        this.instructionEle.width = this.instructionEle.clientWidth;
141      }
142      this.instructionEle.height = (this.maxDepth + 1) * SAMPLE_STRUCT_HEIGHT;
143      this.updateCanvasCoord();
144    }
145  }
146
147  setSampleInstructionData(clickData: SampleStruct, reqProperty: unknown): void {
148    this.hoverSampleStruct = undefined;
149    // @ts-ignore
150    this.instructionData = reqProperty.uniqueProperty;
151    // @ts-ignore
152    this.flattenTreeData = reqProperty.flattenTreeArray;
153    this.setRelationDataProperty(this.flattenTreeData, clickData);
154    this.updateCanvas(this.clientWidth);
155    this.drawInstructionData(this.isChecked);
156  }
157
158  /**
159   * 鼠标移动
160   */
161  onMouseMove(): void {
162    const lastNode = this.hoverSampleStruct;
163    //查找鼠标所在的node
164    const searchResult = this.searchDataByCoord(this.instructionArray!, this.canvasX, this.canvasY);
165    if (searchResult) {
166      this.hoverSampleStruct = searchResult;
167      //鼠标悬浮的node未改变则不需重新渲染文字
168      if (searchResult !== lastNode) {
169        this.updateTipContent();
170        this.ctx!.clearRect(0, 0, this.instructionEle!.width, this.instructionEle!.height);
171        this.ctx!.beginPath();
172        for (const key in this.instructionArray) {
173          // @ts-ignore
174          for (let i = 0; i < this.instructionArray[key].length; i++) {
175            // @ts-ignore
176            const cur = this.instructionArray[key][i];
177            this.draw(this.ctx!, cur);
178          }
179        }
180        this.ctx!.closePath();
181      }
182      this.showTip();
183    } else {
184      this.hideTip();
185      this.hoverSampleStruct = undefined;
186    }
187  }
188
189  /**
190   *  隐藏悬浮框
191   */
192  hideTip(): void {
193    if (this.floatHint) {
194      this.floatHint.style.display = 'none';
195    }
196  }
197
198  /**
199   * 显示悬浮框
200   */
201  showTip(): void {
202    this.floatHint!.innerHTML = this.hintContent;
203    this.floatHint!.style.display = 'block';
204    let x = this.canvasX;
205    let y = this.canvasY - this.canvasScrollTop;
206    //右边的函数悬浮框显示在左侧
207    if (this.canvasX + this.floatHint!.clientWidth > this.instructionEle!.clientWidth || 0) {
208      x -= this.floatHint!.clientWidth - 1;
209    } else {
210      x += 30;
211    }
212    //最下边的函数悬浮框显示在上方
213    y -= this.floatHint!.clientHeight - 1;
214    this.floatHint!.style.transform = `translate(${x}px, ${y}px)`;
215  }
216
217  /**
218   * 更新悬浮框内容
219   * @returns
220   */
221  updateTipContent(): void {
222    const hoverNode = this.hoverSampleStruct;
223    if (!hoverNode) {
224      return;
225    }
226    // @ts-ignore
227    this.hintContent = `<span class="text">${hoverNode.detail}(${hoverNode.name})</span></br>
228      // @ts-ignore
229      <span class="text">${
230      // @ts-ignore
231      this.isChecked ? hoverNode.hoverCycles : hoverNode.hoverInstructions}
232      </span>
233    `;
234  }
235
236  /**
237   * 设置绘制所需的坐标及宽高
238   * @param sampleNode
239   * @param instructions
240   * @param x
241   */
242  setSampleFrame(sampleNode: SampleStruct, instructions: number, x: number): void {
243    if (!sampleNode.frame) {
244      sampleNode.frame! = new Rect(0, 0, 0, 0);
245    }
246    sampleNode.frame!.x = x;
247    sampleNode.frame!.y = sampleNode.depth! * SAMPLE_STRUCT_HEIGHT;
248    sampleNode.frame!.width = instructions;
249    sampleNode.frame!.height = SAMPLE_STRUCT_HEIGHT;
250  }
251
252  /**
253   * 判断鼠标当前在那个函数上
254   * @param frame
255   * @param x
256   * @param y
257   * @returns
258   */
259  isContains(frame: unknown, x: number, y: number): boolean {
260    // @ts-ignore
261    return x >= frame.x && x <= frame.x + frame.width && frame.y <= y && y <= frame.y + frame.height;
262  }
263
264  /**
265   * 绘制
266   * @param isCycles
267   */
268  drawInstructionData(isCycles: boolean): void {
269    this.isChecked ? (this.textEle!.innerText = 'cycles数据流') : (this.textEle!.innerText = 'instructions数据流');
270    const clientWidth = this.instructionEle!.width;
271    //将数据转换为层级结构
272    const instructionArray = this.flattenTreeData
273      // @ts-ignore
274      .filter((item: SampleStruct) => (isCycles ? item.cycles : item.instructions))
275      .reduce((pre: unknown, cur: unknown) => {
276        // @ts-ignore
277        (pre[`${cur.depth}`] = pre[`${cur.depth}`] || []).push(cur);
278        return pre;
279      }, {});
280    // @ts-ignore
281    for (const key in instructionArray) {
282      // @ts-ignore
283      for (let i = 0; i < instructionArray[key].length; i++) {
284        // @ts-ignore
285        const cur = instructionArray[key][i];
286        //第一级节点直接将宽度设置为容器宽度
287        if (key === '0') {
288          this.setSampleFrame(cur, clientWidth, 0);
289        } else {
290          //获取上一层级节点数据
291          // @ts-ignore
292          const preList = instructionArray[Number(key) - 1];
293          //获取当前节点的父节点
294          const parentNode = preList.find((node: SampleStruct) => node.name === cur.parentName);
295          //计算当前节点下指令数之和 用于计算每个节点所占的宽度比
296          const total = isCycles
297            // @ts-ignore
298            ? instructionArray[key]
299              // @ts-ignore
300              .filter((i: unknown) => i.parentName === parentNode.name)
301              .reduce((pre: number, cur: SampleStruct) => pre + cur.cycles!, 0)
302            // @ts-ignore
303            : instructionArray[key]
304              // @ts-ignore
305              .filter((i: unknown) => i.parentName === parentNode.name)
306              .reduce((pre: number, cur: SampleStruct) => pre + cur.instructions!, 0);
307          const curWidth = isCycles ? cur.cycles : cur.instructions;
308          const width = Math.floor(parentNode.frame.width * (curWidth / total));
309          if (i === 0) {
310            this.setSampleFrame(cur, width, parentNode.frame.x);
311          } else {
312            // @ts-ignore
313            const preNode = instructionArray[key][i - 1];
314            preNode.parentName === parentNode.name
315              ? this.setSampleFrame(cur, width, preNode.frame.x + preNode.frame.width)
316              : this.setSampleFrame(cur, width, parentNode.frame.x);
317          }
318        }
319      }
320    }
321    // @ts-ignore
322    this.instructionArray = instructionArray;
323    this.ctx!.clearRect(0, 0, this.instructionEle!.width, this.instructionEle!.height);
324    this.ctx!.beginPath();
325    for (const key in this.instructionArray) {
326      // @ts-ignore
327      for (let i = 0; i < this.instructionArray[key].length; i++) {
328        // @ts-ignore
329        const cur = this.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 relationData
411   * @param clickData
412   */
413  setRelationDataProperty(relationData: Array<unknown>, clickData: SampleStruct): void {
414    const propertyData = this.instructionData.find((subArr: unknown) =>
415      // @ts-ignore
416      subArr.some((obj: SampleStruct) => obj.begin === clickData.begin)
417    );
418    //获取非unknown数据
419    // @ts-ignore
420    const knownRelation = relationData.filter((relation) => relation.name.indexOf('unknown') < 0);
421    // @ts-ignore
422    propertyData.forEach((property: unknown) => {
423      // @ts-ignore
424      const relation = knownRelation.find((relation) => relation.name === property.func_name);
425      // @ts-ignore
426      relation.instructions = Math.ceil(property.instructions) || 1;
427      // @ts-ignore
428      relation.hoverInstructions = Math.ceil(property.instructions);
429      // @ts-ignore
430      relation.cycles = Math.ceil(property.cycles) || 1;
431      // @ts-ignore
432      relation.hoverCycles = Math.ceil(property.cycles);
433      // @ts-ignore
434      this.maxDepth = Math.max(this.maxDepth, relation.depth);
435    });
436    //获取所有unknown数据
437    let instructionSum = 0;
438    let cyclesSum = 0;
439    let hoverInstructionsSum = 0;
440    let hoverCyclesSum = 0;
441    // @ts-ignore
442    const unknownRelation = relationData.filter((relation) => relation.name.indexOf('unknown') > -1);
443    if (unknownRelation.length > 0) {
444      unknownRelation.forEach((unknownItem) => {
445        instructionSum = 0;
446        cyclesSum = 0;
447        hoverInstructionsSum = 0;
448        hoverCyclesSum = 0;
449        // @ts-ignore
450        const children = unknownItem['children'];
451        for (const key in children) {
452          // @ts-ignore
453          const it = relationData.find((relation) => relation.name === key);
454          // @ts-ignore
455          instructionSum += it['instructions'] ?? 0;
456          // @ts-ignore
457          cyclesSum += it['cycles'] ?? 0;
458          // @ts-ignore
459          hoverInstructionsSum += it['hoverInstructions'] ?? 0;
460          // @ts-ignore
461          hoverCyclesSum += it['hoverCycles'] ?? 0;
462        }
463        // @ts-ignore
464        unknownItem['instructions'] = instructionSum;
465        // @ts-ignore
466        unknownItem['hoverInstructions'] = hoverInstructionsSum;
467        // @ts-ignore
468        unknownItem['cycles'] = cyclesSum;
469        // @ts-ignore
470        unknownItem['hoverCycles'] = hoverCyclesSum;
471      });
472    }
473  }
474
475  /**
476   * 监听页面size变化
477   */
478  listenerResize(): void {
479    new ResizeObserver(() => {
480      if (this.instructionEle!.getBoundingClientRect()) {
481        const box = this.instructionEle!.getBoundingClientRect();
482        const element = this.parentElement!;
483        this.startX = box.left + Math.max(element.scrollLeft, document.body.scrollLeft) - element.clientLeft;
484        this.startY =
485          box.top + Math.max(element.scrollTop, document.body.scrollTop) - element.clientTop + this.canvasScrollTop;
486      }
487    }).observe(this.parentElement!);
488  }
489}
490