1// Copyright (c) 2021 Huawei Device Co., Ltd. 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14import { TraficEnum } from '../utils/QueryEnum'; 15 16interface HiPerfSampleType { 17 callchainId: number; 18 startTs: number; 19 eventCount: number; 20 threadId: number; 21 cpuId: number; 22 eventTypeId: number; 23} 24 25const dataCache: { 26 startTs: Array<number>; 27 dur: Array<number>; 28 depth: Array<number>; 29 eventCount: Array<number>; 30 symbolId: Array<number>; 31 fileId: Array<number>; 32 callchainId: Array<number>; 33 selfDur: Array<number>; 34 name: Array<number>; 35 callstack: Map<string, unknown>; 36 sampleList: Array<HiPerfSampleType>; 37 maxDepth: number; 38} = { 39 callstack: new Map<string, unknown>(), 40 sampleList: [], 41 maxDepth: 1, 42 startTs: [], 43 dur: [], 44 depth: [], 45 eventCount: [], 46 symbolId: [], 47 fileId: [], 48 callchainId: [], 49 selfDur: [], 50 name: [], 51}; 52 53export const chartHiperfCallChartDataSql = (args: unknown): string => { 54 const sql = ` 55 select callchain_id as callchainId, 56 timestamp_trace - ${ 57 // @ts-ignore 58 args.recordStartNS 59 } as startTs, 60 event_count as eventCount, 61 A.thread_id as threadId, 62 cpu_id as cpuId, 63 event_type_id as eventTypeId 64 from perf_sample A 65 where callchain_id != -1 and A.thread_id != 0 66 order by cpuId, startTs`; 67 return sql; 68}; 69 70export function hiPerfCallChartDataHandler(data: unknown, proc: Function): void { 71 // @ts-ignore 72 if (data.params.isCache) { 73 // @ts-ignore 74 let res: Array<unknown> = proc(chartHiperfCallChartDataSql(data.params)); 75 for (let i = 0; i < res.length; i++) { 76 if (i > 0) { 77 // @ts-ignore 78 if (res[i].cpuId === res[i - 1].cpuId) { 79 // @ts-ignore 80 res[i - 1].dur = res[i].startTs - res[i - 1].startTs; 81 } else { 82 // @ts-ignore 83 res[i - 1].dur = data.params.recordEndNS - data.params.recordStartNS - res[i - 1].startTs; 84 } 85 } 86 if (i === res.length - 1) { 87 // @ts-ignore 88 res[i].dur = data.params.recordEndNS - data.params.recordStartNS - res[i].startTs; 89 } 90 } 91 // @ts-ignore 92 dataCache.sampleList = res; 93 (self as unknown as Worker).postMessage( 94 { 95 len: 0, 96 // @ts-ignore 97 id: data.id, 98 // @ts-ignore 99 action: data.action, 100 results: 'ok', 101 }, 102 [] 103 ); 104 } else { 105 let res: Array<unknown> = []; 106 // @ts-ignore 107 if (!data.params.isComplete) { 108 res = dataCache.sampleList.filter((it) => { 109 // @ts-ignore 110 let cpuThreadFilter = data.params.type === 0 ? it.cpuId === data.params.id : it.threadId === data.params.id; 111 // @ts-ignore 112 let eventTypeFilter = data.params.eventTypeId === -2 ? true : it.eventTypeId === data.params.eventTypeId; 113 return cpuThreadFilter && eventTypeFilter; 114 }); 115 } 116 // @ts-ignore 117 arrayBufferHandler(data, res, true, !data.params.isComplete); 118 } 119} 120 121export function hiPerfCallStackCacheHandler(data: unknown, proc: Function): void { 122 // @ts-ignore 123 if (data.params.isCache) { 124 hiPerfCallChartClearCache(true); 125 arrayBufferCallStackHandler(data, proc(hiPerfCallStackDataCacheSql())); 126 } 127} 128 129function arrayBufferHandler(data: unknown, res: unknown[], transfer: boolean, loadData: boolean): void { 130 if (loadData) { 131 // @ts-ignore 132 if (data.params.type !== 0) { 133 // @ts-ignore 134 res.sort((a, b) => a.startTs - b.startTs); 135 for (let i = 0; i < res.length; i++) { 136 if (i < res.length - 1) { 137 // @ts-ignore 138 res[i].dur = res[i + 1].startTs - res[i].startTs; 139 } else { 140 // @ts-ignore 141 res[i].dur = data.params.endNS - data.params.startNS - res[i].startTs; 142 } 143 } 144 } 145 // @ts-ignore 146 let result = combinePerfSampleByCallChainId(res, data.params); 147 hiPerfCallChartClearCache(false); 148 const getArrayData = (combineData: Array<unknown>): void => { 149 for (let item of combineData) { 150 // @ts-ignore 151 if (item.depth > -1) { 152 // @ts-ignore 153 dataCache.startTs.push(item.startTime); 154 // @ts-ignore 155 dataCache.dur.push(item.totalTime); 156 // @ts-ignore 157 dataCache.depth.push(item.depth); 158 // @ts-ignore 159 dataCache.eventCount.push(item.eventCount); 160 // @ts-ignore 161 dataCache.symbolId.push(item.symbolId); 162 // @ts-ignore 163 dataCache.fileId.push(item.fileId); 164 // @ts-ignore 165 dataCache.callchainId.push(item.callchainId); 166 // @ts-ignore 167 dataCache.name.push(item.name); 168 // @ts-ignore 169 let self = item.totalTime || 0; 170 // @ts-ignore 171 if (item.children) { 172 // @ts-ignore 173 (item.children as Array<unknown>).forEach((child) => { 174 // @ts-ignore 175 self -= child.totalTime; 176 }); 177 } 178 dataCache.selfDur.push(self); 179 } 180 // @ts-ignore 181 if (item.depth + 1 > dataCache.maxDepth) { 182 // @ts-ignore 183 dataCache.maxDepth = item.depth + 1; 184 } 185 // @ts-ignore 186 if (item.children && item.children.length > 0) { 187 // @ts-ignore 188 getArrayData(item.children); 189 } 190 } 191 }; 192 getArrayData(result); 193 } 194 setTimeout((): void => { 195 arrayBufferCallback(data, transfer); 196 }, 150); 197} 198 199function arrayBufferCallback(data: unknown, transfer: boolean): void { 200 // @ts-ignore 201 let params = data.params; 202 let dataFilter = filterPerfCallChartData(params.startNS, params.endNS, params.totalNS, params.frame, params.expand); 203 let len = dataFilter.startTs.length; 204 let perfCallChart = new PerfCallChart(len); 205 for (let i = 0; i < len; i++) { 206 perfCallChart.startTs[i] = dataFilter.startTs[i]; 207 perfCallChart.dur[i] = dataFilter.dur[i]; 208 perfCallChart.depth[i] = dataFilter.depth[i]; 209 perfCallChart.eventCount[i] = dataFilter.eventCount[i]; 210 perfCallChart.symbolId[i] = dataFilter.symbolId[i]; 211 perfCallChart.fileId[i] = dataFilter.fileId[i]; 212 perfCallChart.callchainId[i] = dataFilter.callchainId[i]; 213 perfCallChart.selfDur[i] = dataFilter.selfDur[i]; 214 perfCallChart.name[i] = dataFilter.name[i]; 215 } 216 postPerfCallChartMessage(data, transfer, perfCallChart, len); 217} 218function postPerfCallChartMessage(data: unknown, transfer: boolean, perfCallChart: PerfCallChart, len: number): void { 219 (self as unknown as Worker).postMessage( 220 { 221 // @ts-ignore 222 id: data.id, 223 // @ts-ignore 224 action: data.action, 225 results: transfer 226 ? { 227 startTs: perfCallChart.startTs.buffer, 228 dur: perfCallChart.dur.buffer, 229 depth: perfCallChart.depth.buffer, 230 callchainId: perfCallChart.callchainId.buffer, 231 eventCount: perfCallChart.eventCount.buffer, 232 symbolId: perfCallChart.symbolId.buffer, 233 fileId: perfCallChart.fileId.buffer, 234 selfDur: perfCallChart.selfDur.buffer, 235 name: perfCallChart.name.buffer, 236 maxDepth: dataCache.maxDepth, 237 } 238 : {}, 239 len: len, 240 }, 241 transfer 242 ? [ 243 perfCallChart.startTs.buffer, 244 perfCallChart.dur.buffer, 245 perfCallChart.depth.buffer, 246 perfCallChart.callchainId.buffer, 247 perfCallChart.eventCount.buffer, 248 perfCallChart.symbolId.buffer, 249 perfCallChart.fileId.buffer, 250 perfCallChart.selfDur.buffer, 251 perfCallChart.name.buffer, 252 ] 253 : [] 254 ); 255} 256 257export function filterPerfCallChartData( 258 startNS: number, 259 endNS: number, 260 totalNS: number, 261 frame: unknown, 262 expand: boolean 263): DataSource { 264 let dataSource = new DataSource(); 265 let data: unknown = {}; 266 dataCache.startTs.reduce((pre, current, index) => { 267 if ( 268 dataCache.dur[index] > 0 && 269 current + dataCache.dur[index] >= startNS && 270 current <= endNS && 271 ((!expand && dataCache.depth[index] === 0) || expand) 272 ) { 273 let x = 0; 274 if (current > startNS && current < endNS) { 275 x = Math.trunc(ns2x(current, startNS, endNS, totalNS, frame)); 276 } else { 277 x = 0; 278 } 279 let key = `${x}-${dataCache.depth[index]}`; 280 // @ts-ignore 281 let preIndex = pre[key]; 282 if (preIndex !== undefined) { 283 // @ts-ignore 284 pre[key] = dataCache.dur[preIndex] > dataCache.dur[index] ? preIndex : index; 285 } else { 286 // @ts-ignore 287 pre[key] = index; 288 } 289 } 290 return pre; 291 }, data); 292 setDataSource(data, dataSource); 293 return dataSource; 294} 295function setDataSource(data: unknown, dataSource: DataSource): void { 296 // @ts-ignore 297 Reflect.ownKeys(data).map((kv: string | symbol): void => { 298 // @ts-ignore 299 let index = data[kv as string] as number; 300 // @ts-ignore 301 dataSource.startTs.push(dataCache.startTs[index]); 302 dataSource.dur.push(dataCache.dur[index]); 303 dataSource.depth.push(dataCache.depth[index]); 304 dataSource.eventCount.push(dataCache.eventCount[index]); 305 dataSource.symbolId.push(dataCache.symbolId[index]); 306 dataSource.fileId.push(dataCache.fileId[index]); 307 dataSource.callchainId.push(dataCache.callchainId[index]); 308 dataSource.selfDur.push(dataCache.selfDur[index]); 309 dataSource.name.push(dataCache.name[index]); 310 }); 311} 312// 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等 313function combinePerfSampleByCallChainId(sampleList: Array<unknown>, params: unknown): unknown[] { 314 return combineChartData( 315 sampleList.map((sample) => { 316 let perfSample: unknown = {}; 317 // @ts-ignore 318 perfSample.children = []; 319 // @ts-ignore 320 perfSample.children[0] = {}; 321 // @ts-ignore 322 perfSample.depth = -1; 323 // @ts-ignore 324 perfSample.callchainId = sample.callchainId; 325 // @ts-ignore 326 perfSample.threadId = sample.threadId; 327 // @ts-ignore 328 perfSample.id = sample.id; 329 // @ts-ignore 330 perfSample.cpuId = sample.cpuId; 331 // @ts-ignore 332 perfSample.startTime = sample.startTs; 333 // @ts-ignore 334 perfSample.endTime = sample.startTs + sample.dur; 335 // @ts-ignore 336 perfSample.totalTime = sample.dur; 337 // @ts-ignore 338 perfSample.eventCount = sample.eventCount; 339 return perfSample; 340 }), 341 params 342 ); 343} 344 345function combineChartData(samples: unknown, params: unknown): Array<unknown> { 346 let combineSample: unknown = []; 347 // 遍历sample表查到的数据,并且为其匹配相应的callchain数据 348 // @ts-ignore 349 for (let sample of samples) { 350 let stackTop = dataCache.callstack.get(`${sample.callchainId}-0`); 351 if (stackTop) { 352 let stackTopSymbol = JSON.parse(JSON.stringify(stackTop)); 353 stackTopSymbol.startTime = sample.startTime; 354 stackTopSymbol.endTime = sample.endTime; 355 stackTopSymbol.totalTime = sample.totalTime; 356 stackTopSymbol.threadId = sample.threadId; 357 stackTopSymbol.cpuId = sample.cpuId; 358 stackTopSymbol.eventCount = sample.eventCount; 359 setDur(stackTopSymbol); 360 sample.children = []; 361 sample.children.push(stackTopSymbol); 362 // 每一项都和combineSample对比 363 // @ts-ignore 364 if (combineSample.length === 0) { 365 // @ts-ignore 366 combineSample.push(sample); 367 } else { 368 // @ts-ignore 369 let pre = combineSample[combineSample.length - 1]; 370 // @ts-ignore 371 if (params.type === 0) { 372 if (pre.threadId === sample.threadId && pre.endTime === sample.startTime) { 373 // @ts-ignore 374 combinePerfCallData(combineSample[combineSample.length - 1], sample); 375 } else { 376 // @ts-ignore 377 combineSample.push(sample); 378 } 379 } else { 380 // @ts-ignore 381 combinePerfCallData(combineSample[combineSample.length - 1], sample); 382 } 383 } 384 } 385 } 386 // @ts-ignore 387 return combineSample; 388} 389 390// 递归设置dur,startTime,endTime 391function setDur(data: unknown): void { 392 // @ts-ignore 393 if (data.children && data.children.length > 0) { 394 // @ts-ignore 395 data.children[0].totalTime = data.totalTime; 396 // @ts-ignore 397 data.children[0].startTime = data.startTime; 398 // @ts-ignore 399 data.children[0].endTime = data.endTime; 400 // @ts-ignore 401 data.children[0].threadId = data.threadId; 402 // @ts-ignore 403 data.children[0].cpuId = data.cpuId; 404 // @ts-ignore 405 data.children[0].eventCount = data.eventCount; 406 // @ts-ignore 407 setDur(data.children[0]); 408 } else { 409 return; 410 } 411} 412 413// hiperf火焰图合并逻辑 414function combinePerfCallData(data1: unknown, data2: unknown): void { 415 if (fixMergeRuler(data1, data2)) { 416 // @ts-ignore 417 data1.endTime = data2.endTime; 418 // @ts-ignore 419 data1.totalTime = data1.endTime - data1.startTime; 420 // @ts-ignore 421 data1.eventCount += data2.eventCount; 422 // @ts-ignore 423 if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) { 424 // @ts-ignore 425 if (fixMergeRuler(data1.children[data1.children.length - 1], data2.children[0])) { 426 // @ts-ignore 427 combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]); 428 } else { 429 // @ts-ignore 430 if (data1.children[data1.children.length - 1].depth === data2.children[0].depth) { 431 // @ts-ignore 432 data1.children.push(data2.children[0]); 433 } 434 } 435 // @ts-ignore 436 } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) { 437 // @ts-ignore 438 data1.endTime = data2.endTime; 439 // @ts-ignore 440 data1.totalTime = data1.endTime - data1.startTime; 441 // @ts-ignore 442 data1.children = []; 443 // @ts-ignore 444 data1.children.push(data2.children[0]); 445 } else { 446 } 447 } 448 return; 449} 450 451/** 452 * 合并规则 453 * @param data1 454 * @param data2 455 */ 456function fixMergeRuler(data1: unknown, data2: unknown): boolean { 457 // @ts-ignore 458 return data1.depth === data2.depth && data1.name === data2.name; 459} 460 461export const hiPerfCallStackDataCacheSql = (): string => { 462 return `select c.callchain_id as callchainId, 463 c.file_id as fileId, 464 c.depth, 465 c.symbol_id as symbolId, 466 c.name 467 from perf_callchain c 468 where callchain_id != -1;`; 469}; 470 471export function hiPerfCallChartClearCache(clearStack: boolean): void { 472 if (clearStack) { 473 dataCache.callstack.clear(); 474 dataCache.sampleList.length = 0; 475 } 476 dataCache.startTs = []; 477 dataCache.dur = []; 478 dataCache.depth = []; 479 dataCache.eventCount = []; 480 dataCache.symbolId = []; 481 dataCache.fileId = []; 482 dataCache.callchainId = []; 483 dataCache.selfDur = []; 484 dataCache.name = []; 485 dataCache.maxDepth = 1; 486} 487 488function arrayBufferCallStackHandler(data: unknown, res: unknown[]): void { 489 for (const stack of res) { 490 let item = stack; 491 // @ts-ignore 492 if (data.params.trafic === TraficEnum.ProtoBuffer) { 493 item = { 494 // @ts-ignore 495 callchainId: stack.hiperfCallStackData.callchainId || 0, 496 // @ts-ignore 497 fileId: stack.hiperfCallStackData.fileId || 0, 498 // @ts-ignore 499 depth: stack.hiperfCallStackData.depth || 0, 500 // @ts-ignore 501 symbolId: stack.hiperfCallStackData.symbolId || 0, 502 // @ts-ignore 503 name: stack.hiperfCallStackData.name || 0, 504 }; 505 } 506 // @ts-ignore 507 dataCache.callstack.set(`${item.callchainId}-${item.depth}`, item); 508 // @ts-ignore 509 let parentSymbol = dataCache.callstack.get(`${item.callchainId}-${item.depth - 1}`); 510 // @ts-ignore 511 if (parentSymbol && parentSymbol.callchainId === item.callchainId && parentSymbol.depth === item.depth - 1) { 512 // @ts-ignore 513 parentSymbol.children = []; 514 // @ts-ignore 515 parentSymbol.children.push(item); 516 } 517 } 518 for (let key of Array.from(dataCache.callstack.keys())) { 519 if (!key.endsWith('-0')) { 520 dataCache.callstack.delete(key); 521 } 522 } 523 (self as unknown as Worker).postMessage( 524 { 525 // @ts-ignore 526 id: data.id, 527 // @ts-ignore 528 action: data.action, 529 results: 'ok', 530 len: res.length, 531 }, 532 [] 533 ); 534} 535 536function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: unknown): number { 537 if (endNS === 0) { 538 endNS = duration; 539 } 540 // @ts-ignore 541 let xSizeHiperf: number = ((ns - startNS) * rect.width) / (endNS - startNS); 542 if (xSizeHiperf < 0) { 543 xSizeHiperf = 0; 544 // @ts-ignore 545 } else if (xSizeHiperf > rect.width) { 546 // @ts-ignore 547 xSizeHiperf = rect.width; 548 } 549 return xSizeHiperf; 550} 551class PerfCallChart { 552 startTs: Float64Array; 553 dur: Float64Array; 554 depth: Int32Array; 555 eventCount: Int32Array; 556 symbolId: Int32Array; 557 fileId: Int32Array; 558 callchainId: Int32Array; 559 selfDur: Int32Array; 560 name: Int32Array; 561 constructor(len: number) { 562 this.startTs = new Float64Array(len); 563 this.dur = new Float64Array(len); 564 this.depth = new Int32Array(len); 565 this.eventCount = new Int32Array(len); 566 this.symbolId = new Int32Array(len); 567 this.fileId = new Int32Array(len); 568 this.callchainId = new Int32Array(len); 569 this.selfDur = new Int32Array(len); 570 this.name = new Int32Array(len); 571 } 572} 573class DataSource { 574 startTs: Array<number>; 575 dur: Array<number>; 576 depth: Array<number>; 577 eventCount: Array<number>; 578 symbolId: Array<number>; 579 fileId: Array<number>; 580 callchainId: Array<number>; 581 selfDur: Array<number>; 582 name: Array<number>; 583 constructor() { 584 this.startTs = []; 585 this.dur = []; 586 this.depth = []; 587 this.eventCount = []; 588 this.symbolId = []; 589 this.fileId = []; 590 this.callchainId = []; 591 this.selfDur = []; 592 this.name = []; 593 } 594} 595