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 { TraceRow } from './base/TraceRow'; 18import { dpr } from './base/Extension'; 19import { 20 drawFlagLineSegment, 21 drawLines, 22 drawLinkLines, 23 drawLogsLineSegment, 24 drawWakeUp, 25 drawWakeUpList, 26 PairPoint, 27 Rect, 28} from '../../database/ui-worker/ProcedureWorkerCommon'; 29import { Flag } from './timer-shaft/Flag'; 30import { TimerShaftElement } from './TimerShaftElement'; 31import { CpuStruct } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; 32import { WakeupBean } from '../../bean/WakeupBean'; 33import { LitIcon } from '../../../base-ui/icon/LitIcon'; 34 35const maxScale = 0.8; //收藏最大高度为界面最大高度的80% 36const topHeight = 150; // 顶部cpu使用率部分高度固定为150px 37const minHeight = 40; //泳道最低高度为40 38const mouseMoveRange = 5; 39 40@element('sp-chart-list') 41export class SpChartList extends BaseElement { 42 private static COLLECT_G1 = '1'; 43 private static COLLECT_G2 = '2'; 44 private collectEl1: HTMLDivElement | null | undefined; 45 private collectEl2: HTMLDivElement | null | undefined; 46 private groupTitle1: HTMLDivElement | null | undefined; 47 private groupTitle2: HTMLDivElement | null | undefined; 48 private icon1: LitIcon | null | undefined; 49 private icon2: LitIcon | null | undefined; 50 private removeCollectIcon1: LitIcon | null | undefined; 51 private removeCollectIcon2: LitIcon | null | undefined; 52 private rootEl: HTMLDivElement | null | undefined; 53 private fragmentGroup1: DocumentFragment = document.createDocumentFragment(); 54 private fragmentGroup2: DocumentFragment = document.createDocumentFragment(); 55 private canvas: HTMLCanvasElement | null | undefined; //绘制收藏泳道图 56 private canvasCtx: CanvasRenderingContext2D | undefined | null; 57 private canResize: boolean = false; 58 private isPress: boolean = false; 59 private startPageY = 0; 60 private startClientHeight: number = 0; 61 private scrollTimer: any; 62 private collect1Expand: boolean = true; 63 private collect2Expand: boolean = true; 64 private collectRowList1: Array<TraceRow<any>> = []; 65 private collectRowList2: Array<TraceRow<any>> = []; 66 private maxHeight = 0; 67 private manualHeight = 0; 68 69 initElements(): void { 70 this.collectEl1 = this.shadowRoot?.querySelector<HTMLDivElement>('#collect-group-1'); 71 this.collectEl2 = this.shadowRoot?.querySelector<HTMLDivElement>('#collect-group-2'); 72 this.groupTitle1 = this.shadowRoot?.querySelector<HTMLDivElement>('#group-1-title'); 73 this.groupTitle2 = this.shadowRoot?.querySelector<HTMLDivElement>('#group-2-title'); 74 this.icon1 = this.shadowRoot?.querySelector<LitIcon>('#group_1_expand'); 75 this.icon2 = this.shadowRoot?.querySelector<LitIcon>('#group_2_expand'); 76 this.removeCollectIcon1 = this.shadowRoot?.querySelector<LitIcon>('#group_1_collect'); 77 this.removeCollectIcon2 = this.shadowRoot?.querySelector<LitIcon>('#group_2_collect'); 78 this.rootEl = this.shadowRoot?.querySelector<HTMLDivElement>('.root'); 79 this.canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('.panel-canvas'); 80 this.canvasCtx = this.canvas?.getContext('2d'); 81 window.subscribe(window.SmartEvent.UI.RowHeightChange, (data: { expand: number; value: number }) => { 82 this.resizeHeight(); 83 if (!data.expand) { 84 let offset = this.scrollTop - data.value; 85 offset = offset < 0 ? 0 : offset; 86 this.scrollTop = offset; 87 } 88 this.refreshFavoriteCanvas(); 89 }); 90 this.initChartListListener(); 91 } 92 93 private initChartListListener(): void { 94 this.icon1?.addEventListener('click', () => { 95 this.collect1Expand = !this.collect1Expand; 96 if (this.collect1Expand) { 97 this.icon1!.style.transform = 'rotateZ(0deg)'; 98 this.collectEl1?.appendChild(this.fragmentGroup1); 99 } else { 100 this.icon1!.style.transform = 'rotateZ(-90deg)'; 101 this.collectRowList1.forEach((row) => this.fragmentGroup1.appendChild(row)); 102 } 103 this.resizeHeight(); 104 }); 105 this.icon2?.addEventListener('click', () => { 106 this.collect2Expand = !this.collect2Expand; 107 if (this.collect2Expand) { 108 this.icon2!.style.transform = 'rotateZ(0deg)'; 109 this.collectEl2?.appendChild(this.fragmentGroup2); 110 this.resizeHeight(); 111 this.scrollTop = this.scrollHeight; 112 } else { 113 this.icon2!.style.transform = 'rotateZ(-90deg)'; 114 this.collectRowList2.forEach((row) => this.fragmentGroup2.appendChild(row)); 115 this.resizeHeight(); 116 this.scrollTop = 0; 117 } 118 }); 119 this.removeCollectIcon1?.addEventListener('click', () => { 120 for (let i = 0; i < this.collectRowList1.length; i++) { 121 this.collectRowList1[i].collectEL?.click(); 122 i--; 123 } 124 }); 125 this.removeCollectIcon2?.addEventListener('click', () => { 126 for (let i = 0; i < this.collectRowList2.length; i++) { 127 this.collectRowList2[i].collectEL?.click(); 128 i--; 129 } 130 }); 131 } 132 133 private resizeHeight(): void { 134 this.maxHeight = 0; 135 this.collectEl1!.childNodes.forEach((item) => (this.maxHeight += (item as any).clientHeight)); 136 this.collectEl2!.childNodes.forEach((item) => (this.maxHeight += (item as any).clientHeight)); 137 if (this.groupTitle1) { 138 this.maxHeight += this.groupTitle1.clientHeight; 139 } 140 if (this.groupTitle2) { 141 this.maxHeight += this.groupTitle2.clientHeight; 142 } 143 144 this.maxHeight = Math.min(this.getMaxLimitHeight(), this.maxHeight); 145 if (this.manualHeight > 0) { 146 this.style.height = `${Math.min(this.maxHeight, this.manualHeight)}px`; 147 } else { 148 this.style.height = `${this.maxHeight}px`; 149 } 150 } 151 152 private getMaxLimitHeight(): number { 153 return (this.parentElement!.clientHeight - topHeight) * maxScale; 154 } 155 156 getCollectRows(filter?: (row: TraceRow<any>) => boolean): Array<TraceRow<any>> | [] { 157 if (filter) { 158 return [...this.collectRowList1.filter(filter), ...this.collectRowList2.filter(filter)]; 159 } else { 160 return this.getAllCollectRows(); 161 } 162 } 163 164 expandSearchRowGroup(row: TraceRow<any>): void { 165 this.updateGroupDisplay(); 166 if (row.collectGroup === SpChartList.COLLECT_G1) { 167 if (!this.collect1Expand) { 168 this.collect1Expand = true; 169 this.icon1!.style.transform = 'rotateZ(0deg)'; 170 this.collectEl1?.appendChild(this.fragmentGroup1); 171 } 172 } else { 173 if (!this.collect2Expand) { 174 this.collect2Expand = true; 175 this.icon2!.style.transform = 'rotateZ(0deg)'; 176 this.collectEl2?.appendChild(this.fragmentGroup2); 177 this.scrollTop = this.scrollHeight; 178 } 179 } 180 this.resizeHeight(); 181 } 182 183 getCollectRow(filter: (row: TraceRow<any>) => boolean): TraceRow<any> | undefined { 184 return this.collectRowList1.find(filter) || this.collectRowList2.find(filter); 185 } 186 187 getAllCollectRows(): Array<TraceRow<any>> { 188 return [...this.collectRowList1, ...this.collectRowList2]; 189 } 190 191 getAllSelectCollectRows(): Array<TraceRow<any>> { 192 const rows: Array<TraceRow<any>> = []; 193 for (const row of this.collectRowList1) { 194 if (row.checkType === '2') { 195 rows.push(row); 196 } 197 } 198 for (const row of this.collectRowList2) { 199 if (row.checkType === '2') { 200 rows.push(row); 201 } 202 } 203 return rows; 204 } 205 206 insertRowBefore(node: Node, child: Node): void { 207 if (child === null || (child as TraceRow<any>).collectGroup === (node as TraceRow<any>).collectGroup) { 208 if ((node as TraceRow<any>).collectGroup === SpChartList.COLLECT_G1) { 209 this.collectEl1!.insertBefore(node, child); 210 this.collectRowList1 = Array.from(this.collectEl1!.children) as TraceRow<any>[]; 211 } else { 212 this.collectEl2!.insertBefore(node, child); 213 this.collectRowList2 = Array.from(this.collectEl2!.children) as TraceRow<any>[]; 214 } 215 } 216 } 217 218 reset(): void { 219 this.maxHeight = 0; 220 this.clearRect(); 221 this.collect1Expand = true; 222 this.collect2Expand = true; 223 this.icon1!.style.transform = 'rotateZ(0deg)'; 224 this.icon2!.style.transform = 'rotateZ(0deg)'; 225 this.collectRowList1.forEach((row) => { 226 row.clearMemory(); 227 }); 228 this.collectRowList2.forEach((row) => { 229 row.clearMemory(); 230 }); 231 this.collectRowList1 = []; 232 this.collectRowList2 = []; 233 this.fragmentGroup1 = document.createDocumentFragment(); 234 this.fragmentGroup2 = document.createDocumentFragment(); 235 this.collectEl1!.innerHTML = ''; 236 this.collectEl2!.innerHTML = ''; 237 this.updateGroupDisplay(); 238 this.style.height = 'auto'; 239 } 240 241 context(): CanvasRenderingContext2D | undefined | null { 242 return this.canvasCtx; 243 } 244 245 getCanvas(): HTMLCanvasElement | null | undefined { 246 return this.canvas; 247 } 248 249 connectedCallback(): void { 250 super.connectedCallback(); 251 window.addEventListener('mousedown', this.onMouseDown); 252 window.addEventListener('mouseup', this.onMouseUp); 253 window.addEventListener('mousemove', this.onMouseMove); 254 this.addEventListener('scroll', this.onScroll, { passive: true }); 255 } 256 257 disconnectedCallback(): void { 258 super.disconnectedCallback(); 259 window.removeEventListener('mousedown', this.onMouseDown); 260 window.removeEventListener('mouseup', this.onMouseUp); 261 window.removeEventListener('mousemove', this.onMouseMove); 262 this.removeEventListener('scroll', this.onScroll); 263 } 264 265 onScroll = (ev: Event): void => { 266 this.canvas!.style.transform = `translateY(${this.scrollTop}px)`; 267 if (this.scrollTimer) { 268 clearTimeout(this.scrollTimer); 269 } 270 this.scrollTimer = setTimeout(() => { 271 TraceRow.range!.refresh = true; 272 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 273 }, 100); 274 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 275 }; 276 277 onMouseDown = (ev: MouseEvent): void => { 278 this.isPress = true; 279 this.startPageY = ev.pageY; 280 this.startClientHeight = this.clientHeight; 281 if (this.containPoint(ev)) { 282 if ( 283 this.getBoundingClientRect().bottom > ev.pageY - mouseMoveRange && 284 this.getBoundingClientRect().bottom < ev.pageY + mouseMoveRange 285 ) { 286 this.style.cursor = 'row-resize'; 287 this.canResize = true; 288 } else { 289 this.style.cursor = 'default'; 290 this.canResize = false; 291 } 292 (window as any).collectResize = this.canResize; 293 } 294 }; 295 296 onMouseMove = (ev: MouseEvent): void => { 297 if (this.containPoint(ev)) { 298 let inResizeArea = 299 this.getBoundingClientRect().bottom > ev.pageY - mouseMoveRange && 300 this.getBoundingClientRect().bottom < ev.pageY + mouseMoveRange; 301 if ((this.isPress && this.canResize) || inResizeArea) { 302 this.style.cursor = 'row-resize'; 303 } else { 304 this.style.cursor = 'default'; 305 } 306 } 307 //防止点击触发move时间 308 if (Math.abs(ev.pageY - this.startPageY) < 2) { 309 return; 310 } 311 if (this.canResize && this.isPress) { 312 (window as any).collectResize = true; 313 // 拖动超过所有泳道最大高度 或小于一个泳道的高度,不支持拖动 314 let newHeight = this.startClientHeight + ev.pageY - this.startPageY; 315 if (newHeight > this.maxHeight) { 316 newHeight = this.maxHeight; 317 } 318 if (newHeight > this.getMaxLimitHeight()) { 319 newHeight = this.getMaxLimitHeight(); 320 } 321 if (newHeight < minHeight) { 322 newHeight = minHeight; 323 } 324 this!.style.height = `${newHeight}px`; 325 this.manualHeight = newHeight; 326 } else { 327 (window as any).collectResize = false; 328 } 329 }; 330 331 onMouseUp = (ev: MouseEvent): void => { 332 this.isPress = false; 333 this.canResize = false; 334 this.style.cursor = 'default'; 335 (window as any).collectResize = false; 336 this.refreshFavoriteCanvas(); 337 }; 338 339 insertRow(row: TraceRow<any>, group: string, updateGroup: boolean): void { 340 this.style.display = 'flex'; 341 let collectGroup = !updateGroup && row.collectGroup ? row.collectGroup : group; 342 if (row.collectGroup !== SpChartList.COLLECT_G1 && row.collectGroup !== SpChartList.COLLECT_G2) { 343 row.collectGroup = group; 344 } 345 if (updateGroup) { 346 row.collectGroup = group; 347 } 348 if (collectGroup === SpChartList.COLLECT_G1) { 349 if (!this.collect1Expand) { 350 this.collect1Expand = true; 351 this.icon1!.style.transform = 'rotateZ(0deg)'; 352 } 353 if (this.collectRowList1.indexOf(row) === -1) { 354 this.collectRowList1.push(row); 355 } 356 if (!this.fragmentGroup1.contains(row)) { 357 this.fragmentGroup1.appendChild(row); 358 } 359 this.collectEl1?.appendChild(this.fragmentGroup1); 360 this.scrollTo({ top: this.collectEl1?.clientHeight }); 361 } else { 362 if (!this.collect2Expand) { 363 this.collect2Expand = true; 364 this.icon2!.style.transform = 'rotateZ(0deg)'; 365 } 366 if (this.collectRowList2.indexOf(row) === -1) { 367 this.collectRowList2.push(row); 368 } 369 if (!this.fragmentGroup2.contains(row)) { 370 this.fragmentGroup2.appendChild(row); 371 } 372 this.collectEl2!.appendChild(this.fragmentGroup2); 373 this.scrollTo({ top: this.scrollHeight }); 374 } 375 this.updateGroupDisplay(); 376 this.resizeHeight(); 377 this.refreshFavoriteCanvas(); 378 row.currentContext = this.canvasCtx; 379 } 380 381 deleteRow(row: TraceRow<any>, clearCollectGroup: boolean): void { 382 if (row.collectGroup === SpChartList.COLLECT_G1) { 383 this.collectRowList1.splice(this.collectRowList1.indexOf(row), 1); 384 if (!this.fragmentGroup1.contains(row)) { 385 this.fragmentGroup1.appendChild(row); 386 } 387 this.fragmentGroup1.removeChild(row); 388 } else { 389 this.collectRowList2.splice(this.collectRowList2.indexOf(row), 1); 390 if (!this.fragmentGroup2.contains(row)) { 391 this.fragmentGroup2.appendChild(row); 392 } 393 this.fragmentGroup2.removeChild(row); 394 } 395 if (clearCollectGroup) { 396 row.collectGroup = undefined; 397 } 398 this.updateGroupDisplay(); 399 this.resizeHeight(); 400 this.scrollTop = 0; 401 this.refreshFavoriteCanvas(); 402 row.currentContext = undefined; 403 if (this.collectRowList1.length === 0 && this.collectRowList2.length === 0) { 404 this.style.height = 'auto'; 405 this.style.display = 'none'; 406 this.manualHeight = 0; 407 } 408 } 409 410 hideCollectArea(): void { 411 if (this.collect1Expand) { 412 this.collectRowList1.forEach((row) => this.fragmentGroup1.appendChild(row)); 413 } 414 if (this.collect2Expand) { 415 this.collectRowList2.forEach((row) => this.fragmentGroup2.appendChild(row)); 416 } 417 this.groupTitle1!.style.display = 'none'; 418 this.groupTitle2!.style.display = 'none'; 419 this.resizeHeight(); 420 } 421 422 showCollectArea(): void { 423 if (this.collect1Expand) { 424 this.collectEl1?.appendChild(this.fragmentGroup1); 425 } 426 if (this.collect2Expand) { 427 this.collectEl2?.appendChild(this.fragmentGroup2); 428 } 429 this.updateGroupDisplay(); 430 this.resizeHeight(); 431 } 432 433 updateGroupDisplay(): void { 434 this.groupTitle1!.style.display = this.collectRowList1.length === 0 ? 'none' : 'flex'; 435 this.groupTitle2!.style.display = this.collectRowList2.length === 0 ? 'none' : 'flex'; 436 } 437 438 hasCollectRow(): boolean { 439 return this.collectRowList2.length > 0 || this.collectRowList1.length > 0; 440 } 441 442 clearRect(): void { 443 this.canvasCtx?.clearRect(0, 0, this.canvas?.clientWidth ?? 0, this.canvas?.clientHeight ?? 0); 444 } 445 446 drawLines(xs: number[] | undefined, color: string): void { 447 drawLines(this.canvasCtx!, xs ?? [], this.clientHeight, color); 448 } 449 450 drawFlagLineSegment( 451 hoverFlag: Flag | undefined | null, 452 selectFlag: Flag | undefined | null, 453 tse: TimerShaftElement 454 ): void { 455 drawFlagLineSegment( 456 this.canvasCtx, 457 hoverFlag, 458 selectFlag, 459 { 460 x: 0, 461 y: 0, 462 width: TraceRow.FRAME_WIDTH, 463 height: this.canvas?.clientHeight, 464 }, 465 tse 466 ); 467 } 468 469 drawWakeUp(): void { 470 drawWakeUp( 471 this.canvasCtx, 472 CpuStruct.wakeupBean, 473 TraceRow.range!.startNS, 474 TraceRow.range!.endNS, 475 TraceRow.range!.totalNS, 476 { 477 x: 0, 478 y: 0, 479 width: TraceRow.FRAME_WIDTH, 480 height: this.canvas!.clientHeight!, 481 } as Rect 482 ); 483 } 484 485 drawWakeUpList(bean: WakeupBean): void { 486 drawWakeUpList(this.canvasCtx, bean, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, { 487 x: 0, 488 y: 0, 489 width: TraceRow.FRAME_WIDTH, 490 height: this.canvas!.clientHeight!, 491 } as Rect); 492 } 493 494 drawLogsLineSegment(bean: Flag | null | undefined, timeShaft: TimerShaftElement): void { 495 drawLogsLineSegment( 496 this.canvasCtx, 497 bean, 498 { 499 x: 0, 500 y: 0, 501 width: TraceRow.FRAME_WIDTH, 502 height: this.canvas!.clientHeight, 503 }, 504 timeShaft 505 ); 506 } 507 508 drawLinkLines(nodes: PairPoint[][], tse: TimerShaftElement, isFavorite: boolean, favoriteHeight: number): void { 509 drawLinkLines(this.canvasCtx!, nodes, tse, isFavorite, favoriteHeight); 510 } 511 512 refreshFavoriteCanvas(): void { 513 this.canvas!.style.width = `${this.clientWidth - 248}px`; 514 this.canvas!.style.left = `248px`; 515 this.canvas!.width = this.canvas?.clientWidth! * dpr(); 516 this.canvas!.height = this.clientHeight * dpr(); 517 this.canvas!.getContext('2d')!.scale(dpr(), dpr()); 518 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 519 } 520 521 private getHtmlCss(): string { 522 return `<style> 523 :host{ 524 display: none; 525 width: 100%; 526 height: auto; 527 overflow-anchor: none; 528 z-index: 1; 529 box-shadow: 0 10px 10px #00000044; 530 position: relative; 531 overflow: auto; 532 overflow-x: hidden; 533 scroll-behavior: smooth; 534 } 535 .root{ 536 width: 100%; 537 box-sizing: border-box; 538 } 539 .panel-canvas{ 540 position: absolute; 541 top: 0; 542 right: 0; 543 bottom: 0; 544 box-sizing: border-box; 545 } 546 .icon:hover { 547 color:#ecb93f; 548 } 549 .icon { 550 margin-right: 10px; 551 cursor: pointer; 552 } 553 </style>`; 554 } 555 556 initHtml(): string { 557 return ` 558 ${this.getHtmlCss()} 559<canvas id="canvas-panel" class="panel-canvas" ondragstart="return false"></canvas> 560<div class="root"> 561 <div id="group-1-title" style="background-color: #efefef;padding: 10px;align-items: center"> 562 <lit-icon id="group_1_expand" class="icon" name="caret-down" size="19"></lit-icon> 563 <span style="width: 184px;font-size: 10px;color: #898989">G1</span> 564 <lit-icon id="group_1_collect" name="star-fill" style="color: #5291FF;cursor: pointer" size="19"></lit-icon> 565 </div> 566 <div id="collect-group-1"></div> 567 <div id="group-2-title" style="background-color: #efefef;padding: 10px;align-items: center"> 568 <lit-icon id="group_2_expand" class="icon" name="caret-down" size="19"></lit-icon> 569 <span style="width: 184px;font-size: 10px;color: #898989">G2</span> 570 <lit-icon id="group_2_collect" name="star-fill" style="color: #f56940;cursor: pointer" size="19"></lit-icon> 571 </div> 572 <div id="collect-group-2"></div> 573</div> 574`; 575 } 576} 577