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