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