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 { CpuStruct, WakeupBean } from './ProcedureWorkerCPU.js'; 17import { TraceRow } from '../../component/trace/base/TraceRow.js'; 18import { TimerShaftElement } from '../../component/trace/TimerShaftElement'; 19 20export abstract class Render { 21 abstract renderMainThread(req: any, row: TraceRow<any>): void; 22} 23 24export abstract class PerfRender { 25 abstract render(req: RequestMessage, list: Array<any>, filter: Array<any>, dataList2: Array<any>): void; 26} 27 28export class RequestMessage { 29 type: string | undefined | null; 30 lazyRefresh: boolean | undefined; 31 intervalPerf: any; 32 canvas: any; 33 context: any; 34 params: any; 35 online: any; 36 buf: any; 37 isRangeSelect: any; 38 isHover: any; 39 xs: any; 40 frame: any; 41 flagMoveInfo: any; 42 flagSelectedInfo: any; 43 hoverX: any; 44 hoverY: any; 45 startNS: any; 46 endNS: any; 47 totalNS: any; 48 slicesTime: 49 | { 50 startTime: number | null; 51 endTime: number | null; 52 color: string | null; 53 } 54 | undefined; 55 range: any; 56 scale: any; 57 chartColor: any; 58 canvasWidth: any; 59 canvasHeight: any; 60 useCache: any; 61 lineColor: any; 62 wakeupBean: WakeupBean | undefined | null; 63 id: any; 64 postMessage: 65 | { 66 (message: any, targetOrigin: string, transfer?: Transferable[]): void; 67 (message: any, options?: WindowPostMessageOptions): void; 68 } 69 | undefined; 70} 71 72export function ns2s(ns: number): string { 73 let second1 = 1_000_000_000; // 1 second 74 let millisecond = 1_000_000; // 1 millisecond 75 let microsecond = 1_000; // 1 microsecond 76 let res; 77 if (ns >= second1) { 78 res = (ns / 1000 / 1000 / 1000).toFixed(1) + ' s'; 79 } else if (ns >= millisecond) { 80 res = (ns / 1000 / 1000).toFixed(1) + ' ms'; 81 } else if (ns >= microsecond) { 82 res = (ns / 1000).toFixed(1) + ' μs'; 83 } else if (ns > 0) { 84 res = ns.toFixed(1) + ' ns'; 85 } else { 86 res = ns.toFixed(1) + ' s'; 87 } 88 return res; 89} 90 91export function isFrameContainPoint(frame: Rect, x: number, y: number): boolean { 92 return x >= frame.x && x <= frame.x + frame.width && y >= frame.y && y <= frame.y + frame.height; 93} 94 95class FilterConfig { 96 startNS: number = 0; 97 endNS: number = 0; 98 totalNS: number = 0; 99 frame: any = null; 100 useCache: boolean = false; 101 startKey: string = 'startNS'; 102 durKey: string = 'dur'; 103 paddingTop: number = 0; 104} 105 106export function fillCacheData(filterData: Array<any>, condition: FilterConfig): boolean { 107 if (condition.useCache && filterData.length > 0) { 108 let pns = (condition.endNS - condition.startNS) / condition.frame.width; 109 let y = condition.frame.y + condition.paddingTop; 110 let height = condition.frame.height - condition.paddingTop * 2; 111 for (let i = 0, len = filterData.length; i < len; i++) { 112 let it = filterData[i]; 113 if ( 114 (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS && 115 (it[condition.startKey] || 0) < condition.endNS 116 ) { 117 if (!filterData[i].frame) { 118 filterData[i].frame = {}; 119 filterData[i].frame.y = y; 120 filterData[i].frame.height = height; 121 } 122 setNodeFrame( 123 filterData[i], 124 pns, 125 condition.startNS, 126 condition.endNS, 127 condition.frame, 128 condition.startKey, 129 condition.durKey 130 ); 131 } else { 132 filterData[i].frame = null; 133 } 134 } 135 return true; 136 } 137 return false; 138} 139 140export function findRange(fullData: Array<any>, condition: FilterConfig): Array<any> { 141 let left = 0, 142 right = 0; 143 for (let i = 0, j = fullData.length - 1, ib = true, jb = true; i < fullData.length, j >= 0; i++, j--) { 144 if (fullData[j][condition.startKey] <= condition.endNS && jb) { 145 right = j; 146 jb = false; 147 } 148 if (fullData[i][condition.startKey] + fullData[i][condition.durKey] >= condition.startNS && ib) { 149 left = i; 150 ib = false; 151 } 152 if (!ib && !jb) { 153 break; 154 } 155 } 156 let slice = fullData.slice(left, right + 1); 157 return slice; 158} 159 160export function dataFilterHandler(fullData: Array<any>, filterData: Array<any>, condition: FilterConfig) { 161 if (fillCacheData(filterData, condition)) return; 162 if (fullData) { 163 filterData.length = 0; 164 let pns = (condition.endNS - condition.startNS) / condition.frame.width; //每个像素多少ns 165 let y = condition.frame.y + condition.paddingTop; 166 let height = condition.frame.height - condition.paddingTop * 2; 167 let slice = findRange(fullData, condition); 168 let sum = 0; 169 for (let i = 0; i < slice.length; i++) { 170 if (!slice[i].frame) { 171 slice[i].frame = {}; 172 slice[i].frame.y = y; 173 slice[i].frame.height = height; 174 } 175 if (i === slice.length - 1) { 176 if (slice[i][condition.durKey] === undefined || slice[i][condition.durKey] === null) { 177 slice[i][condition.durKey] = (condition.endNS || 0) - (slice[i][condition.startKey] || 0); 178 } 179 } else { 180 if (slice[i][condition.durKey] === undefined || slice[i][condition.durKey] === null) { 181 slice[i][condition.durKey] = (slice[i + 1][condition.startKey] || 0) - (slice[i][condition.startKey] || 0); 182 } 183 } 184 if (slice[i][condition.durKey] >= pns || slice.length < 100) { 185 slice[i].v = true; 186 setNodeFrame( 187 slice[i], 188 pns, 189 condition.startNS, 190 condition.endNS, 191 condition.frame, 192 condition.startKey, 193 condition.durKey 194 ); 195 } else { 196 if (i > 0) { 197 let c = slice[i][condition.startKey] - slice[i - 1][condition.startKey] - slice[i - 1][condition.durKey]; 198 if (c < pns && sum < pns) { 199 sum += c + slice[i - 1][condition.durKey]; 200 slice[i].v = false; 201 } else { 202 slice[i].v = true; 203 setNodeFrame( 204 slice[i], 205 pns, 206 condition.startNS, 207 condition.endNS, 208 condition.frame, 209 condition.startKey, 210 condition.durKey 211 ); 212 sum = 0; 213 } 214 } 215 } 216 } 217 filterData.push(...slice.filter((it) => it.v)); 218 } 219} 220 221function setNodeFrame( 222 node: any, 223 pns: number, 224 startNS: number, 225 endNS: number, 226 frame: any, 227 startKey: string, 228 durKey: string 229) { 230 if ((node[startKey] || 0) < startNS) { 231 node.frame.x = 0; 232 } else { 233 node.frame.x = Math.floor(((node[startKey] || 0) - startNS) / pns); 234 } 235 if ((node[startKey] || 0) + (node[durKey] || 0) > endNS) { 236 node.frame.width = frame.width - node.frame.x; 237 } else { 238 node.frame.width = Math.ceil(((node[startKey] || 0) + (node[durKey] || 0) - startNS) / pns - node.frame.x); 239 } 240 if (node.frame.width < 1) { 241 node.frame.width = 1; 242 } 243} 244 245export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any) { 246 // @ts-ignore 247 if (endNS == 0) { 248 endNS = duration; 249 } 250 let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS); 251 if (xSize < 0) { 252 xSize = 0; 253 } else if (xSize > rect.width) { 254 xSize = rect.width; 255 } 256 return xSize; 257} 258 259export function ns2xByTimeShaft(ns: number, tse: TimerShaftElement) { 260 let startNS = tse.getRange()!.startNS; 261 let endNS = tse.getRange()!.endNS; 262 let duration = tse.getRange()!.totalNS; 263 if (endNS == 0) { 264 endNS = duration; 265 } 266 let width = tse.getBoundingClientRect().width - 258; 267 let xSize: number = ((ns - startNS) * width) / (endNS - startNS); 268 if (xSize < 0) { 269 xSize = 0; 270 } else if (xSize > width) { 271 xSize = width; 272 } 273 return xSize; 274} 275 276export class Rect { 277 x: number = 0; 278 y: number = 0; 279 width: number = 0; 280 height: number = 0; 281 constructor(x: number, y: number, width: number, height: number) { 282 this.x = x; 283 this.y = y; 284 this.width = width; 285 this.height = height; 286 } 287 288 static intersect(r1: Rect, rect: Rect): boolean { 289 let minX = r1.x <= rect.x ? r1.x : rect.x; 290 let minY = r1.y <= rect.y ? r1.y : rect.y; 291 let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width; 292 let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height; 293 if (maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height) { 294 return true; 295 } else { 296 return false; 297 } 298 } 299 300 static contains(rect: Rect, x: number, y: number): boolean { 301 return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height; 302 } 303 304 static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean { 305 return rect.x - l <= x && x <= rect.x + rect.width + r && rect.y - t <= y && y <= rect.y + rect.height + b; 306 } 307 308 static containsWithPadding( 309 rect: Rect, 310 x: number, 311 y: number, 312 paddingLeftRight: number, 313 paddingTopBottom: number 314 ): boolean { 315 return ( 316 rect.x + paddingLeftRight <= x && 317 rect.y + paddingTopBottom <= y && 318 x <= rect.x + rect.width - paddingLeftRight && 319 y <= rect.y + rect.height - paddingTopBottom 320 ); 321 } 322 323 /** 324 * 判断是否相交 325 * @param rect 326 */ 327 intersect(rect: Rect): boolean { 328 let minX = this.x <= rect.x ? this.x : rect.x; 329 let minY = this.y <= rect.y ? this.y : rect.y; 330 let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width; 331 let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height; 332 if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) { 333 return true; 334 } else { 335 return false; 336 } 337 } 338 339 contains(x: number, y: number): boolean { 340 return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height; 341 } 342 343 containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean { 344 return this.x - l <= x && x <= this.x + this.width + r && this.y - t <= y && y <= this.y + this.height + b; 345 } 346 347 containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { 348 return ( 349 this.x + paddingLeftRight <= x && 350 x <= this.x + this.width - paddingLeftRight && 351 this.y + paddingTopBottom <= y && 352 y <= this.y + this.height - paddingTopBottom 353 ); 354 } 355} 356 357export class Point { 358 x: number = 0; 359 y: number = 0; 360 isRight: boolean = true; 361 362 constructor(x: number, y: number, isRight: boolean = true) { 363 this.x = x; 364 this.y = y; 365 this.isRight = isRight; 366 } 367} 368 369export enum LineType { 370 brokenLine, 371 bezierCurve, 372} 373 374export class PairPoint { 375 x: number = 0; 376 ns: number = 0; 377 y: number = 0; 378 offsetY: number = 0; 379 rowEL: TraceRow<any>; 380 isRight: boolean = true; 381 lineType?: LineType; 382 business: string = ''; 383 hidden?: boolean = false; 384 constructor( 385 rowEL: TraceRow<any>, 386 x: number, 387 y: number, 388 ns: number, 389 offsetY: number, 390 isRight: boolean, 391 business: string 392 ) { 393 this.rowEL = rowEL; 394 this.x = x; 395 this.y = y; 396 this.ns = ns; 397 this.offsetY = offsetY; 398 this.isRight = isRight; 399 this.business = business; 400 } 401} 402 403export class BaseStruct { 404 translateY: number | undefined; 405 frame: Rect | undefined; 406 isHover: boolean = false; 407} 408 409export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<any>, height: number, lineColor: string) { 410 if (ctx) { 411 ctx.beginPath(); 412 ctx.lineWidth = 1; 413 ctx.strokeStyle = lineColor || '#dadada'; 414 xs?.forEach((it) => { 415 ctx.moveTo(Math.floor(it), 0); 416 ctx.lineTo(Math.floor(it), height); 417 }); 418 ctx.stroke(); 419 ctx.closePath(); 420 } 421} 422 423export function drawFlagLine( 424 commonCtx: any, 425 hoverFlag: any, 426 selectFlag: any, 427 startNS: number, 428 endNS: number, 429 totalNS: number, 430 frame: any, 431 slicesTime: 432 | { 433 startTime: number | null | undefined; 434 endTime: number | null | undefined; 435 color: string | null | undefined; 436 } 437 | undefined 438) { 439 if (commonCtx) { 440 if (hoverFlag) { 441 commonCtx.beginPath(); 442 commonCtx.lineWidth = 2; 443 commonCtx.strokeStyle = hoverFlag?.color || '#dadada'; 444 commonCtx.moveTo(Math.floor(hoverFlag.x), 0); 445 commonCtx.lineTo(Math.floor(hoverFlag.x), frame.height); 446 commonCtx.stroke(); 447 commonCtx.closePath(); 448 } 449 if (selectFlag) { 450 commonCtx.beginPath(); 451 commonCtx.lineWidth = 2; 452 commonCtx.strokeStyle = selectFlag?.color || '#dadada'; 453 selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame); 454 commonCtx.moveTo(Math.floor(selectFlag.x), 0); 455 commonCtx.lineTo(Math.floor(selectFlag.x), frame.height); 456 commonCtx.stroke(); 457 commonCtx.closePath(); 458 } 459 if (slicesTime && slicesTime.startTime && slicesTime.endTime) { 460 commonCtx.beginPath(); 461 commonCtx.lineWidth = 1; 462 commonCtx.strokeStyle = slicesTime.color || '#dadada'; 463 let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame); 464 let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame); 465 commonCtx.moveTo(Math.floor(x1), 0); 466 commonCtx.lineTo(Math.floor(x1), frame.height); 467 commonCtx.moveTo(Math.floor(x2), 0); 468 commonCtx.lineTo(Math.floor(x2), frame.height); 469 commonCtx.stroke(); 470 commonCtx.closePath(); 471 } 472 } 473} 474 475export function drawFlagLineSegment(ctx: any, hoverFlag: any, selectFlag: any, frame: any, tse: TimerShaftElement) { 476 if (ctx) { 477 if (hoverFlag) { 478 ctx.beginPath(); 479 ctx.lineWidth = 2; 480 ctx.strokeStyle = hoverFlag?.color || '#dadada'; 481 ctx.moveTo(Math.floor(hoverFlag.x), 0); 482 ctx.lineTo(Math.floor(hoverFlag.x), frame.height); 483 ctx.stroke(); 484 ctx.closePath(); 485 } 486 if (selectFlag) { 487 ctx.beginPath(); 488 ctx.lineWidth = 2; 489 ctx.strokeStyle = selectFlag?.color || '#dadada'; 490 selectFlag.x = ns2x( 491 selectFlag.time, 492 TraceRow.range!.startNS, 493 TraceRow.range!.endNS, 494 TraceRow.range!.totalNS, 495 frame 496 ); 497 ctx.moveTo(Math.floor(selectFlag.x), 0); 498 ctx.lineTo(Math.floor(selectFlag.x), frame.height); 499 ctx.stroke(); 500 ctx.closePath(); 501 } 502 tse.sportRuler!.slicesTimeList.forEach((slicesTime) => { 503 if (slicesTime && slicesTime.startTime && slicesTime.endTime) { 504 ctx.beginPath(); 505 ctx.lineWidth = 1; 506 ctx.strokeStyle = slicesTime.color || '#dadada'; 507 let x1 = ns2x(slicesTime.startTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame); 508 let x2 = ns2x(slicesTime.endTime, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, frame); 509 // 划线逻辑 510 ctx.moveTo(Math.floor(x1), 0); 511 ctx.lineTo(Math.floor(x1), frame.height); //左边的线 512 ctx.moveTo(Math.floor(x2), 0); 513 ctx.lineTo(Math.floor(x2), frame.height); // 右边的线 514 ctx.stroke(); 515 ctx.closePath(); 516 } 517 }); 518 } 519} 520 521export function drawSelection(context: any, params: any) { 522 if (params.isRangeSelect && params.rangeSelectObject) { 523 params.rangeSelectObject!.startX = Math.floor( 524 ns2x(params.rangeSelectObject!.startNS!, params.startNS, params.endNS, params.totalNS, params.frame) 525 ); 526 params.rangeSelectObject!.endX = Math.floor( 527 ns2x(params.rangeSelectObject!.endNS!, params.startNS, params.endNS, params.totalNS, params.frame) 528 ); 529 if (context) { 530 context.globalAlpha = 0.5; 531 context.fillStyle = '#666666'; 532 context.fillRect( 533 params.rangeSelectObject!.startX!, 534 params.frame.y, 535 params.rangeSelectObject!.endX! - params.rangeSelectObject!.startX!, 536 params.frame.height 537 ); 538 context.globalAlpha = 1; 539 } 540 } 541} 542 543// draw range select 544export function drawSelectionRange(context: any, params: TraceRow<any>) { 545 if (params.rangeSelect && TraceRow.rangeSelectObject) { 546 TraceRow.rangeSelectObject!.startX = Math.floor( 547 ns2x( 548 TraceRow.rangeSelectObject!.startNS!, 549 TraceRow.range?.startNS ?? 0, 550 TraceRow.range?.endNS ?? 0, 551 TraceRow.range?.totalNS ?? 0, 552 params.frame 553 ) 554 ); 555 TraceRow.rangeSelectObject!.endX = Math.floor( 556 ns2x( 557 TraceRow.rangeSelectObject!.endNS!, 558 TraceRow.range?.startNS ?? 0, 559 TraceRow.range?.endNS ?? 0, 560 TraceRow.range?.totalNS ?? 0, 561 params.frame 562 ) 563 ); 564 if (context) { 565 context.globalAlpha = 0.5; 566 context.fillStyle = '#666666'; 567 context.fillRect( 568 TraceRow.rangeSelectObject!.startX!, 569 params.frame.y, 570 TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!, 571 params.frame.height 572 ); 573 context.globalAlpha = 1; 574 } 575 } 576} 577 578export function drawWakeUp( 579 context: CanvasRenderingContext2D | any, 580 wake: WakeupBean | undefined | null, 581 startNS: number, 582 endNS: number, 583 totalNS: number, 584 frame: Rect, 585 selectCpuStruct: CpuStruct | undefined = undefined, 586 currentCpu: number | undefined = undefined, 587 noVerticalLine = false 588) { 589 if (wake) { 590 let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame)); 591 context.beginPath(); 592 context.lineWidth = 2; 593 context.fillStyle = '#000000'; 594 if (x1 > 0 && x1 < frame.x + frame.width) { 595 if (!noVerticalLine) { 596 context.moveTo(x1, frame.y); 597 context.lineTo(x1, frame.y + frame.height); 598 } 599 if (currentCpu == wake.cpu) { 600 let centerY = Math.floor(frame.y + frame.height / 2); 601 context.moveTo(x1, centerY - 6); 602 context.lineTo(x1 + 4, centerY); 603 context.lineTo(x1, centerY + 6); 604 context.lineTo(x1 - 4, centerY); 605 context.lineTo(x1, centerY - 6); 606 context.fill(); 607 } 608 } 609 if (selectCpuStruct) { 610 let x2 = Math.floor(ns2x(selectCpuStruct.startTime || 0, startNS, endNS, totalNS, frame)); 611 let y = frame.y + frame.height - 10; 612 context.moveTo(x1, y); 613 context.lineTo(x2, y); 614 615 let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0)); 616 let distance = x2 - x1; 617 if (distance > 12) { 618 context.moveTo(x1, y); 619 context.lineTo(x1 + 6, y - 3); 620 context.moveTo(x1, y); 621 context.lineTo(x1 + 6, y + 3); 622 context.moveTo(x2, y); 623 context.lineTo(x2 - 6, y - 3); 624 context.moveTo(x2, y); 625 context.lineTo(x2 - 6, y + 3); 626 let measure = context.measureText(s); 627 let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent; 628 let xStart = x1 + Math.floor(distance / 2 - measure.width / 2); 629 if (distance > measure.width + 4) { 630 context.fillStyle = '#ffffff'; 631 context.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4); 632 context.font = '10px solid'; 633 context.fillStyle = '#000000'; 634 context.textBaseline = 'bottom'; 635 context.fillText(s, xStart, y - 2); 636 } 637 } 638 } 639 context.strokeStyle = '#000000'; 640 context.stroke(); 641 context.closePath(); 642 } 643} 644 645const wid = 5; 646const linkLineColor = '#ff0000'; 647export function drawLinkLines( 648 context: CanvasRenderingContext2D, 649 nodes: PairPoint[][], 650 tm: TimerShaftElement, 651 isFavorite: boolean 652) { 653 let percentage = 654 (tm.getRange()!.totalNS - Math.abs(tm.getRange()!.endNS - tm.getRange()!.startNS)) / tm.getRange()!.totalNS; 655 let maxWidth = tm.getBoundingClientRect().width - 268; 656 for (let i = 0; i < nodes.length; i++) { 657 let it = nodes[i]; 658 if (it[0].hidden) { 659 continue; 660 } 661 if (isFavorite) { 662 if (!it[0].rowEL.collect && !it[1].rowEL.collect) { 663 continue; 664 } 665 } 666 switch (it[0].lineType) { 667 case LineType.brokenLine: 668 drawBrokenLine(it, maxWidth, context); 669 break; 670 case LineType.bezierCurve: 671 drawBezierCurve(it, maxWidth, context, percentage); 672 break; 673 default: 674 drawBezierCurve(it, maxWidth, context, percentage); 675 } 676 } 677} 678 679function drawBezierCurve(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D, percentage: number) { 680 let start = it[0].x > it[1].x ? it[1] : it[0]; 681 let end = it[0].x > it[1].x ? it[0] : it[1]; 682 if (start && end) { 683 //左移到边界,不画线 684 if (start.x <= 0) { 685 start.x = -100; 686 } 687 if (end.x <= 0) { 688 end.x = -100; 689 } 690 //右移到边界,不画线 691 if (start.x >= maxWidth) { 692 start.x = maxWidth + 100; 693 } 694 if (end.x >= maxWidth) { 695 end.x = maxWidth + 100; 696 } 697 context.beginPath(); 698 context.lineWidth = 2; 699 context.fillStyle = linkLineColor; 700 context.strokeStyle = linkLineColor; 701 let x0; 702 let y0; 703 let x1; 704 let x2; 705 let y1; 706 let y2; 707 let x3; 708 let y3; 709 x0 = start.x ?? 0; 710 y0 = start.y ?? 0; 711 x3 = end.x ?? 0; 712 y3 = end.y ?? 0; 713 if (end.isRight) { 714 x2 = x3 - 100 * percentage; 715 } else { 716 x2 = x3 + 100 * percentage; 717 } 718 y2 = y3 - 40 * percentage; 719 if (start.isRight) { 720 x1 = x0 - 100 * percentage; 721 } else { 722 x1 = x0 + 100 * percentage; 723 } 724 y1 = y0 + 40 * percentage; 725 //向右箭头终点在x轴正向有偏移 726 if (!start.isRight) { 727 x0 -= 5; 728 } 729 context.moveTo(x0, y0); 730 //箭头向左还是向右 731 if (start.isRight) { 732 context.lineTo(x0 - wid, y0 + wid); 733 context.moveTo(x0, y0); 734 context.lineTo(x0 - wid, y0 - wid); 735 } else { 736 context.lineTo(x0 + wid, y0 + wid); 737 context.moveTo(x0, y0); 738 context.lineTo(x0 + wid, y0 - wid); 739 } 740 context.moveTo(x0, y0); 741 context.bezierCurveTo(x1, y1, x2, y2, x3, y3); 742 context.moveTo(x3, y3); 743 //箭头向左还是向右 744 if (end.isRight) { 745 context.lineTo(x3 - wid, y3 + wid); 746 context.moveTo(x3, y3); 747 context.lineTo(x3 - wid, y3 - wid); 748 } else { 749 context.lineTo(x3 + wid, y3 + wid); 750 context.moveTo(x3, y3); 751 context.lineTo(x3 + wid, y3 - wid); 752 } 753 context.moveTo(x3, y3); 754 context.stroke(); 755 context.closePath(); 756 } 757} 758 759function drawBrokenLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D) { 760 let start = it[0].x > it[1].x ? it[1] : it[0]; 761 let end = it[0].x > it[1].x ? it[0] : it[1]; 762 if (start && end) { 763 if (start.x <= 0) { 764 start.x = -100; 765 } 766 if (end.x <= 0) { 767 end.x = -100; 768 } 769 //右移到边界,不画线 770 if (start.x >= maxWidth) { 771 start.x = maxWidth + 100; 772 } 773 if (end.x >= maxWidth) { 774 end.x = maxWidth + 100; 775 } 776 context.beginPath(); 777 context.lineWidth = 2; 778 context.fillStyle = '#46B1E3'; 779 context.strokeStyle = '#46B1E3'; 780 let x0; 781 let y0; 782 let x1; 783 let y1; 784 let x2; 785 let y2; 786 x0 = start.x ?? 0; 787 y0 = start.y ?? 0; 788 y2 = end.y ?? 0; 789 x2 = end.x ?? 0; 790 let leftEndpointX; 791 let leftEndpointY; 792 let rightEndpointX; 793 let rightEndpointY; 794 795 if (start.y < end.y) { 796 x1 = start.x ?? 0; 797 y1 = end.y ?? 0; 798 leftEndpointX = x2 - wid; 799 leftEndpointY = y2 - wid; 800 rightEndpointX = x2 - wid; 801 rightEndpointY = y2 + wid; 802 } else { 803 x2 = end.x - wid ?? 0; 804 x1 = end.x - wid ?? 0; 805 y1 = start.y ?? 0; 806 leftEndpointX = x2 - wid; 807 leftEndpointY = y2 + wid; 808 rightEndpointX = x2 + wid; 809 rightEndpointY = y2 + wid; 810 } 811 context.moveTo(x0, y0); 812 context.lineTo(x1, y1); 813 context.lineTo(x2, y2); 814 context.stroke(); 815 context.closePath(); 816 context.beginPath(); 817 context.lineWidth = 2; 818 context.fillStyle = '#46B1E3'; 819 context.strokeStyle = '#46B1E3'; 820 context.moveTo(x2, y2); 821 context.lineTo(leftEndpointX, leftEndpointY); 822 context.lineTo(rightEndpointX, rightEndpointY); 823 context.lineTo(x2, y2); 824 context.fill(); 825 context.closePath(); 826 } 827} 828 829export function drawLoading( 830 ctx: CanvasRenderingContext2D, 831 startNS: number, 832 endNS: number, 833 totalNS: number, 834 frame: any, 835 left: number, 836 right: number 837) {} 838 839export function drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect, data: any) { 840 if (data.textMetricsWidth === undefined) { 841 data.textMetricsWidth = ctx.measureText(str).width; 842 } 843 let charWidth = Math.round(data.textMetricsWidth / str.length); 844 let fillTextWidth = frame.width - textPadding * 2; 845 if (data.textMetricsWidth < fillTextWidth) { 846 let x2 = Math.floor(frame.width / 2 - data.textMetricsWidth / 2 + frame.x + textPadding); 847 ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2), fillTextWidth); 848 } else { 849 if (fillTextWidth >= charWidth) { 850 let chatNum = fillTextWidth / charWidth; 851 let x1 = frame.x + textPadding; 852 if (chatNum < 2) { 853 ctx.fillText(str.substring(0, 1), x1, Math.floor(frame.y + frame.height / 2), fillTextWidth); 854 } else { 855 ctx.fillText(str.substring(0, chatNum - 1) + '...', x1, Math.floor(frame.y + frame.height / 2), fillTextWidth); 856 } 857 } 858 } 859} 860 861export function hiPerf( 862 arr: Array<any>, 863 arr2: Array<any>, 864 res: Array<any>, 865 startNS: number, 866 endNS: number, 867 frame: any, 868 groupBy10MS: boolean, 869 use: boolean 870) { 871 if (use && res.length > 0) { 872 let pns = (endNS - startNS) / frame.width; 873 let y = frame.y; 874 for (let i = 0; i < res.length; i++) { 875 let it = res[i]; 876 if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) { 877 if (!it.frame) { 878 it.frame = {}; 879 it.frame.y = y; 880 } 881 it.frame.height = it.height; 882 HiPerfStruct.setFrame(it, pns, startNS, endNS, frame); 883 } else { 884 it.frame = null; 885 } 886 } 887 return; 888 } 889 res.length = 0; 890 if (arr) { 891 let list = groupBy10MS ? arr2 : arr; 892 let pns = (endNS - startNS) / frame.width; 893 let y = frame.y; 894 for (let i = 0, len = list.length; i < len; i++) { 895 let it = list[i]; 896 if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) { 897 if (!list[i].frame) { 898 list[i].frame = {}; 899 list[i].frame.y = y; 900 } 901 list[i].frame.height = it.height; 902 HiPerfStruct.setFrame(list[i], pns, startNS, endNS, frame); 903 if (groupBy10MS) { 904 if ( 905 i > 0 && 906 (list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && 907 (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0) && 908 (list[i - 1].frame?.height || 0) == (list[i].frame?.height || 0) 909 ) { 910 } else { 911 res.push(list[i]); 912 } 913 } else { 914 if (i > 0 && Math.abs((list[i - 1].frame?.x || 0) - (list[i].frame?.x || 0)) < 4) { 915 } else { 916 res.push(list[i]); 917 } 918 } 919 } 920 } 921 } 922} 923 924export class HiPerfStruct extends BaseStruct { 925 static hoverStruct: HiPerfStruct | undefined; 926 static selectStruct: HiPerfStruct | undefined; 927 id: number | undefined; 928 callchain_id: number | undefined; 929 timestamp: number | undefined; 930 thread_id: number | undefined; 931 event_count: number | undefined; 932 event_type_id: number | undefined; 933 cpu_id: number | undefined; 934 thread_state: string | undefined; 935 startNS: number | undefined; 936 endNS: number | undefined; 937 dur: number | undefined; 938 height: number | undefined; 939 940 static drawRoundRectPath(cxt: Path2D, x: number, y: number, width: number, height: number, radius: number) { 941 cxt.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2); 942 cxt.lineTo(x + radius, y + height); 943 cxt.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI); 944 cxt.lineTo(x + 0, y + radius); 945 cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2); 946 cxt.lineTo(x + width - radius, y + 0); 947 cxt.arc(x + width - radius, y + radius, radius, (Math.PI * 3) / 2, Math.PI * 2); 948 cxt.lineTo(x + width, y + height - radius); 949 cxt.moveTo(x + width / 3, y + height / 5); 950 cxt.lineTo(x + width / 3, y + (height / 5) * 4); 951 cxt.moveTo(x + width / 3, y + height / 5); 952 cxt.bezierCurveTo( 953 x + width / 3 + 7, 954 y + height / 5 - 2, 955 x + width / 3 + 7, 956 y + height / 5 + 6, 957 x + width / 3, 958 y + height / 5 + 4 959 ); 960 } 961 962 static draw(ctx: CanvasRenderingContext2D, path: Path2D, data: any, groupBy10MS: boolean) { 963 if (data.frame) { 964 if (groupBy10MS) { 965 let width = data.frame.width; 966 path.rect(data.frame.x, 40 - (data.height || 0), width, data.height || 0); 967 } else { 968 path.moveTo(data.frame.x + 7, 20); 969 HiPerfStruct.drawRoundRectPath(path, data.frame.x - 7, 20 - 7, 14, 14, 3); 970 path.moveTo(data.frame.x, 27); 971 path.lineTo(data.frame.x, 33); 972 } 973 } 974 } 975 976 static setFrame(node: any, pns: number, startNS: number, endNS: number, frame: any) { 977 if ((node.startNS || 0) < startNS) { 978 node.frame.x = 0; 979 } else { 980 node.frame.x = Math.floor(((node.startNS || 0) - startNS) / pns); 981 } 982 if ((node.startNS || 0) + (node.dur || 0) > endNS) { 983 node.frame.width = frame.width - node.frame.x; 984 } else { 985 node.frame.width = Math.ceil(((node.startNS || 0) + (node.dur || 0) - startNS) / pns - node.frame.x); 986 } 987 if (node.frame.width < 1) { 988 node.frame.width = 1; 989 } 990 } 991 992 static groupBy10MS(array: Array<any>, intervalPerf: number, maxCpu?: number | undefined): Array<any> { 993 let obj = array 994 .map((it) => { 995 it.timestamp_group = Math.trunc(it.startNS / 1_000_000_0) * 1_000_000_0; 996 return it; 997 }) 998 .reduce((pre, current) => { 999 (pre[current['timestamp_group']] = pre[current['timestamp_group']] || []).push(current); 1000 return pre; 1001 }, {}); 1002 let arr = []; 1003 for (let aKey in obj) { 1004 let ns = parseInt(aKey); 1005 let height: number = 0; 1006 if (maxCpu != undefined) { 1007 height = Math.floor((obj[aKey].length / (10 / intervalPerf) / maxCpu) * 40); 1008 } else { 1009 height = Math.floor((obj[aKey].length / (10 / intervalPerf)) * 40); 1010 } 1011 arr.push({ 1012 startNS: ns, 1013 dur: 1_000_000_0, 1014 height: height, 1015 }); 1016 } 1017 return arr; 1018 } 1019} 1020 1021function setMemFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { 1022 let x1: number; 1023 let x2: number; 1024 if ((node.startTime || 0) <= startNS) { 1025 x1 = 0; 1026 } else { 1027 x1 = ns2x(node.startTime || 0, startNS, endNS, totalNS, frame); 1028 } 1029 if ((node.startTime || 0) + (node.duration || 0) >= endNS) { 1030 x2 = frame.width; 1031 } else { 1032 x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame); 1033 } 1034 let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; 1035 if (!node.frame) { 1036 node.frame = {}; 1037 } 1038 node.frame.x = Math.floor(x1); 1039 node.frame.y = Math.floor(frame.y + padding); 1040 node.frame.width = Math.ceil(getV); 1041 node.frame.height = Math.floor(frame.height - padding * 2); 1042} 1043 1044export function mem( 1045 list: Array<any>, 1046 memFilter: Array<any>, 1047 startNS: number, 1048 endNS: number, 1049 totalNS: number, 1050 frame: any, 1051 use: boolean 1052) { 1053 if (use && memFilter.length > 0) { 1054 for (let i = 0, len = memFilter.length; i < len; i++) { 1055 if ( 1056 (memFilter[i].startTime || 0) + (memFilter[i].duration || 0) > startNS && 1057 (memFilter[i].startTime || 0) < endNS 1058 ) { 1059 setMemFrame(memFilter[i], 5, startNS, endNS, totalNS, frame); 1060 } else { 1061 memFilter[i].frame = null; 1062 } 1063 } 1064 return; 1065 } 1066 memFilter.length = 0; 1067 if (list) { 1068 for (let i = 0, len = list.length; i < len; i++) { 1069 let it = list[i]; 1070 if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) { 1071 setMemFrame(list[i], 5, startNS, endNS, totalNS, frame); 1072 if ( 1073 i > 0 && 1074 (list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && 1075 (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0) 1076 ) { 1077 } else { 1078 memFilter.push(list[i]); 1079 } 1080 } 1081 } 1082 } 1083} 1084 1085export function drawWakeUpList( 1086 context: CanvasRenderingContext2D | any, 1087 wake: WakeupBean | undefined | null, 1088 startNS: number, 1089 endNS: number, 1090 totalNS: number, 1091 frame: Rect, 1092 wakeup: WakeupBean | undefined = undefined, 1093 currentCpu: number | undefined | null = undefined, 1094 noVerticalLine = false, 1095) { 1096 if (wake) { 1097 let x1 = Math.floor( 1098 ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame) 1099 ); 1100 context.beginPath(); 1101 context.lineWidth = 2; 1102 context.fillStyle = '#000000'; 1103 if (x1 > 0 && x1 < frame.x + frame.width) { 1104 if (!noVerticalLine) { 1105 context.moveTo(x1, frame.y); 1106 context.lineTo(x1, frame.y + frame.height); 1107 } 1108 if (currentCpu == wake.cpu) { 1109 let centerY = Math.floor(frame.y + frame.height / 2); 1110 context.moveTo(x1, centerY - 6); 1111 context.lineTo(x1 + 4, centerY); 1112 context.lineTo(x1, centerY + 6); 1113 context.lineTo(x1 - 4, centerY); 1114 context.lineTo(x1, centerY - 6); 1115 context.fill(); 1116 } 1117 } 1118 if (wakeup) { 1119 let x2 = Math.floor( 1120 ns2x( 1121 wakeup.ts || 0, 1122 startNS, 1123 endNS, 1124 totalNS, 1125 frame 1126 ) 1127 ); 1128 let y = frame.y + frame.height - 10; 1129 context.moveTo(x1, y); 1130 context.lineTo(x2, y); 1131 context.moveTo(x2, y - 25); 1132 context.lineTo(x2, y + 5); 1133 1134 let s = ns2s( 1135 (wakeup.ts || 0) - (wake.wakeupTime || 0) 1136 ); 1137 let distance = x2 - x1; 1138 if (distance > 12) { 1139 context.moveTo(x1, y); 1140 context.lineTo(x1 + 6, y - 3); 1141 context.moveTo(x1, y); 1142 context.lineTo(x1 + 6, y + 3); 1143 context.moveTo(x2, y); 1144 context.lineTo(x2 - 6, y - 3); 1145 context.moveTo(x2, y); 1146 context.lineTo(x2 - 6, y + 3); 1147 let measure = context.measureText(s); 1148 let tHeight = 1149 measure.actualBoundingBoxAscent + 1150 measure.actualBoundingBoxDescent; 1151 let xStart = x1 + Math.floor(distance / 2 - measure.width / 2); 1152 if (distance > measure.width + 4) { 1153 context.fillStyle = '#ffffff'; 1154 context.fillRect( 1155 xStart - 2, 1156 y - 4 - tHeight, 1157 measure.width + 4, 1158 tHeight + 4 1159 ); 1160 context.font = '10px solid'; 1161 context.fillStyle = '#000000'; 1162 context.textBaseline = 'bottom'; 1163 context.fillText(s, xStart, y - 2); 1164 } 1165 } 1166 } 1167 context.strokeStyle = '#000000'; 1168 context.stroke(); 1169 context.closePath(); 1170 } 1171} 1172