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 {SpApplication} from "../SpApplication"; 17 18export function ns2s(ns: number): string { 19 let second1 = 1_000_000_000; // 1 second 20 let millisecond1 = 1_000_000; // 1 millisecond 21 let microsecond1 = 1_000; // 1 microsecond 22 let nanosecond1 = 1000.0; 23 let res; 24 if (ns >= second1) { 25 res = (ns / 1000 / 1000 / 1000).toFixed(1) + " s"; 26 } else if (ns >= millisecond1) { 27 res = (ns / 1000 / 1000).toFixed(1) + " ms"; 28 } else if (ns >= microsecond1) { 29 res = (ns / 1000).toFixed(1) + " μs"; 30 } else if (ns > 0) { 31 res = ns.toFixed(1) + " ns"; 32 } else { 33 res = ns.toFixed(1) + " s"; 34 } 35 return res; 36} 37 38export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any) { 39 // @ts-ignore 40 if (endNS == 0) { 41 endNS = duration; 42 } 43 let xSize: number = (ns - startNS) * rect.width / (endNS - startNS); 44 if (xSize < 0) { 45 xSize = 0; 46 } else if (xSize > rect.width) { 47 xSize = rect.width; 48 } 49 return xSize; 50} 51 52export class Rect { 53 x: number = 0 54 y: number = 0 55 width: number = 0 56 height: number = 0 57 58 constructor(x: number, y: number, width: number, height: number) { 59 this.x = x; 60 this.y = y; 61 this.width = width; 62 this.height = height; 63 } 64 65 static contains(rect: Rect, x: number, y: number): boolean { 66 return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height; 67 } 68 69 static containsWithPadding(rect: Rect, x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { 70 return rect.x + paddingLeftRight <= x 71 && x <= rect.x + rect.width - paddingLeftRight 72 && rect.y + paddingTopBottom <= y 73 && y <= rect.y + rect.height - paddingTopBottom; 74 } 75 76 static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean { 77 return rect.x - l <= x 78 && x <= rect.x + rect.width + r 79 && rect.y - t <= y 80 && y <= rect.y + rect.height + b; 81 } 82 83 static intersect(r1: Rect, rect: Rect): boolean { 84 let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width; 85 let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height; 86 let minX = r1.x <= rect.x ? r1.x : rect.x; 87 let minY = r1.y <= rect.y ? r1.y : rect.y; 88 if (maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height) { 89 return true; 90 } else { 91 return false; 92 } 93 } 94 95 contains(x: number, y: number): boolean { 96 return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height; 97 } 98 99 containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { 100 return this.x + paddingLeftRight <= x 101 && x <= this.x + this.width - paddingLeftRight 102 && this.y + paddingTopBottom <= y 103 && y <= this.y + this.height - paddingTopBottom; 104 } 105 106 containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean { 107 return this.x - l <= x 108 && x <= this.x + this.width + r 109 && this.y - t <= y 110 && y <= this.y + this.height + b; 111 } 112 113 /** 114 * 判断是否相交 115 * @param rect 116 */ 117 intersect(rect: Rect): boolean { 118 let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width; 119 let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height; 120 let minX = this.x <= rect.x ? this.x : rect.x; 121 let minY = this.y <= rect.y ? this.y : rect.y; 122 if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) { 123 return true; 124 } else { 125 return false; 126 } 127 } 128} 129 130export class Point { 131 x: number = 0 132 y: number = 0 133 134 constructor(x: number, y: number) { 135 this.x = x; 136 this.y = y; 137 } 138} 139 140export class BaseStruct { 141 frame: Rect | undefined 142 isHover: boolean = false; 143} 144 145 146export class ColorUtils { 147 public static GREY_COLOR: string = "#f0f0f0" 148 /** 149 * Color array of all current columns 150 */ 151 public static MD_PALETTE: Array<string> = [ 152 "#3391ff",// red 153 "#0076ff",// pink 154 "#66adff",// purple 155 "#2db3aa",// deep purple 156 "#008078",// indigo 157 "#73e6de",// blue 158 "#535da6",// light blue 159 "#38428c", // cyan 160 "#7a84cc",// teal 161 "#ff9201",// green 162 "#ff7500",// light green 163 "#ffab40",// lime 164 "#2db4e2",// amber 0xffc105 165 "#0094c6", // orange 166 "#7cdeff",// deep orange 167 "#ffd44a", // brown 168 "#fbbf00",// blue gray 169 "#ffe593",// yellow 0xffec3d 170 ]; 171 public static FUNC_COLOR: Array<string> = [ 172 "#46B1E3", 173 "#0094c6", // orange 174 "#38428c", 175 "#38778c", 176 "#90708c", 177 "#9c27b0", 178 "#0076ff",// pink 179 "#66adff",// purple 180 "#2db4e2", 181 "#7cdeff",// deep orange 182 "#73e6de",// blue 183 "#bcaaa4", 184 "#2db3aa", // deep purple 185 "#008078", // blue 186 "#795548", 187 // "#ffab40",// lime 188 // "#c0ca33", 189 "#ff7500",// light green 190 "#ec407a", 191 ]; 192 193 /** 194 * Get the color value according to the length of the string 195 * 196 * @param str str 197 * @param max max 198 * @return int 199 */ 200 public static hash(str: string, max: number): number { 201 let colorA: number = 0x811c9dc5; 202 let colorB: number = 0xfffffff; 203 let colorC: number = 16777619; 204 let colorD: number = 0xffffffff; 205 let hash: number = colorA & colorB; 206 207 for (let index: number = 0; index < str.length; index++) { 208 hash ^= str.charCodeAt(index); 209 hash = (hash * colorC) & colorD; 210 } 211 return Math.abs(hash) % max; 212 } 213 214 public static hashFunc(str: string, depth: number, max: number): number { 215 let colorA: number = 0x811c9dc5; 216 let colorB: number = 0xfffffff; 217 let colorC: number = 16777619; 218 let colorD: number = 0xffffffff; 219 let hash: number = colorA & colorB; 220 let st = str.replace(/[0-9]+/g, ""); 221 for (let index: number = 0; index < st.length; index++) { 222 hash ^= st.charCodeAt(index); 223 hash = (hash * colorC) & colorD; 224 } 225 return (Math.abs(hash) + depth) % max; 226 } 227 228 /** 229 * Get color according to tid 230 * 231 * @param tid tid 232 * @return Color 233 */ 234 public static colorForTid(tid: number): string { 235 let colorIdx: number = ColorUtils.hash(`${tid}`, ColorUtils.MD_PALETTE.length); 236 return ColorUtils.MD_PALETTE[colorIdx]; 237 } 238 239 public static formatNumberComma(str: number): string { 240 let l = str.toString().split("").reverse(); 241 let t: string = ""; 242 for (let i = 0; i < l.length; i++) { 243 t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : ""); 244 } 245 return t.split("").reverse().join("") 246 } 247} 248 249export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<any>, height: number, lineColor: string) { 250 if (ctx) { 251 ctx.lineWidth = 1; 252 ctx.strokeStyle = lineColor || "#dadada"; 253 xs?.forEach(it => { 254 ctx.moveTo(Math.floor(it), 0) 255 ctx.lineTo(Math.floor(it), height) 256 }) 257 ctx.stroke(); 258 } 259} 260 261export function drawFlagLine(ctx: any, hoverFlag: any, selectFlag: any, startNS: number, endNS: number, totalNS: number, frame: any, slicesTime: { startTime: number | null, endTime: number | null, color: string | null }) { 262 if (ctx) { 263 if (hoverFlag) { 264 ctx.beginPath(); 265 ctx.lineWidth = 2; 266 ctx.strokeStyle = hoverFlag?.color || "#dadada"; 267 ctx.moveTo(Math.floor(hoverFlag.x), 0) 268 ctx.lineTo(Math.floor(hoverFlag.x), frame.height) 269 ctx.stroke(); 270 ctx.closePath(); 271 } 272 if (selectFlag) { 273 ctx.beginPath(); 274 ctx.lineWidth = 2; 275 ctx.strokeStyle = selectFlag?.color || "#dadada"; 276 selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame); 277 ctx.moveTo(Math.floor(selectFlag.x), 0) 278 ctx.lineTo(Math.floor(selectFlag.x), frame.height) 279 ctx.stroke(); 280 ctx.closePath(); 281 } 282 if (slicesTime && slicesTime.startTime && slicesTime.endTime) { 283 ctx.beginPath(); 284 ctx.lineWidth = 1; 285 ctx.strokeStyle = slicesTime.color || "#dadada"; 286 let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame); 287 let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame); 288 ctx.moveTo(Math.floor(x1), 0) 289 ctx.lineTo(Math.floor(x1), frame.height) 290 ctx.moveTo(Math.floor(x2), 0) 291 ctx.lineTo(Math.floor(x2), frame.height) 292 ctx.stroke(); 293 ctx.closePath(); 294 } 295 } 296} 297 298/** 299 * get framechart color by percent 300 * @param widthPercentage proportion of function 301 * @returns rbg 302 */ 303export function getHeatColor(widthPercentage: number) { 304 return { 305 r: Math.floor(245 + 10 * (1 - widthPercentage)), 306 g: Math.floor(110 + 105 * (1 - widthPercentage)), 307 b: 100, 308 }; 309} 310 311export enum ChartMode { 312 Call, 313 Byte, 314 Count, 315} 316 317export class ChartStruct extends BaseStruct { 318 static hoverFuncStruct: ChartStruct | undefined; 319 static selectFuncStruct: ChartStruct | undefined; 320 static lastSelectFuncStruct: ChartStruct | undefined; 321 static padding: number = 1; 322 needShow = false; 323 depth: number = 0; 324 symbol: string = ''; 325 size: number = 0; 326 count: number = 0; 327 type: ChartMode = ChartMode.Call; 328 parent: ChartStruct | undefined; 329 children: Array<ChartStruct> = []; 330 331 /** 332 * set function position 333 * @param node current function struct 334 * @param canvas_frame canvas 335 * @param total all rect size 336 */ 337 static setFuncFrame(node: ChartStruct, canvas_frame: Rect, total: number, mode: ChartMode) { 338 if (!node.frame) { 339 node.frame = new Rect(0, 0, 0, 0); 340 } 341 // filter depth is 0 342 if (node.parent) { 343 let idx = node.parent.children.indexOf(node); 344 if (idx == 0) { 345 node.frame!.x = node.parent.frame!.x; 346 } else { 347 // set x by left frame. left frame is parent.children[idx - 1] 348 node.frame.x = node.parent.children[idx - 1].frame!.x + node.parent.children[idx - 1].frame!.width 349 } 350 if (mode == ChartMode.Byte) { 351 node.frame!.width = Math.floor(node.size / total * canvas_frame.width); 352 } else { 353 node.frame!.width = Math.floor(node.count / total * canvas_frame.width); 354 } 355 node.frame!.y = node.parent.frame!.y + 20; 356 node.frame!.height = 20; 357 } 358 } 359 360 /** 361 * draw rect 362 * @param ctx CanvasRenderingContext2D 363 * @param data rect which is need draw 364 * @param percent function size or count / total size or count 365 */ 366 static draw(ctx: CanvasRenderingContext2D, data: ChartStruct, percent: number) { 367 let spApplication = <SpApplication>document.getElementsByTagName("sp-application")[0] 368 if (data.frame) { 369 // draw rect 370 let miniHeight = 20; 371 if (ChartStruct.isSelected(data)) { 372 ctx.fillStyle = `rgba(${82}, ${145}, ${255}, 0.9)`; 373 } else { 374 let color = getHeatColor(percent); 375 ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, 0.9)`; 376 } 377 ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - ChartStruct.padding * 2); 378 //draw border 379 if (ChartStruct.isHover(data)) { 380 if (spApplication.dark) { 381 ctx.strokeStyle = "#fff"; 382 } else { 383 ctx.strokeStyle = "#000"; 384 } 385 } else { 386 if (spApplication.dark) { 387 ctx.strokeStyle = "#000"; 388 } else { 389 ctx.strokeStyle = "#fff"; 390 } 391 } 392 ctx.lineWidth = 0.4; 393 ctx.strokeRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - ChartStruct.padding * 2); 394 395 //draw symbol name 396 if (data.frame.width > 10) { 397 if (percent > 0.6 || ChartStruct.isSelected(data)) { 398 ctx.fillStyle = "#fff"; 399 } else { 400 ctx.fillStyle = "#000"; 401 } 402 ChartStruct.drawString(ctx, data.symbol || '', 5, data.frame); 403 } 404 405 } 406 } 407 408 /** 409 * draw function string in rect 410 * @param ctx CanvasRenderingContext2D 411 * @param str function Name 412 * @param textPadding textPadding 413 * @param frame canvas area 414 * @returns is draw 415 */ 416 static drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect): boolean { 417 let textMetrics = ctx.measureText(str); 418 let charWidth = Math.round(textMetrics.width / str.length) 419 if (textMetrics.width < frame.width - textPadding * 2) { 420 let x2 = Math.floor(frame.width / 2 - textMetrics.width / 2 + frame.x + textPadding) 421 ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) 422 return true; 423 } 424 if (frame.width - textPadding * 2 > charWidth * 4) { 425 let chatNum = (frame.width - textPadding * 2) / charWidth; 426 let x1 = frame.x + textPadding 427 ctx.fillText(str.substring(0, chatNum - 4) + '...', x1, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) 428 return true; 429 } 430 return false; 431 } 432 433 static isHover(data: ChartStruct): boolean { 434 return ChartStruct.hoverFuncStruct == data; 435 } 436 437 static isSelected(data: ChartStruct): boolean { 438 return ChartStruct.lastSelectFuncStruct == data; 439 } 440 441 442} 443