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 let result = combinePerfSampleByCallChainId(res, data.params); 133 hiPerfCallChartClearCache(false); 134 const getArrayData = (combineData: Array<unknown>): void => { 135 for (let item of combineData) { 136 // @ts-ignore 137 if (item.depth > -1) { 138 // @ts-ignore 139 dataCache.startTs.push(item.startTime); 140 // @ts-ignore 141 dataCache.dur.push(item.totalTime); 142 // @ts-ignore 143 dataCache.depth.push(item.depth); 144 // @ts-ignore 145 dataCache.eventCount.push(item.eventCount); 146 // @ts-ignore 147 dataCache.symbolId.push(item.symbolId); 148 // @ts-ignore 149 dataCache.fileId.push(item.fileId); 150 // @ts-ignore 151 dataCache.callchainId.push(item.callchainId); 152 // @ts-ignore 153 dataCache.name.push(item.name); 154 // @ts-ignore 155 let self = item.totalTime || 0; 156 // @ts-ignore 157 if (item.children) { 158 // @ts-ignore 159 (item.children as Array<unknown>).forEach((child) => { 160 // @ts-ignore 161 self -= child.totalTime; 162 }); 163 } 164 dataCache.selfDur.push(self); 165 } 166 // @ts-ignore 167 if (item.depth + 1 > dataCache.maxDepth) { 168 // @ts-ignore 169 dataCache.maxDepth = item.depth + 1; 170 } 171 // @ts-ignore 172 if (item.children && item.children.length > 0) { 173 // @ts-ignore 174 getArrayData(item.children); 175 } 176 } 177 }; 178 getArrayData(result); 179 } 180 setTimeout((): void => { 181 arrayBufferCallback(data, transfer); 182 }, 150); 183} 184 185function arrayBufferCallback(data: unknown, transfer: boolean): void { 186 // @ts-ignore 187 let params = data.params; 188 let dataFilter = filterPerfCallChartData(params.startNS, params.endNS, params.totalNS, params.frame, params.expand); 189 let len = dataFilter.startTs.length; 190 let perfCallChart = new PerfCallChart(len); 191 for (let i = 0; i < len; i++) { 192 perfCallChart.startTs[i] = dataFilter.startTs[i]; 193 perfCallChart.dur[i] = dataFilter.dur[i]; 194 perfCallChart.depth[i] = dataFilter.depth[i]; 195 perfCallChart.eventCount[i] = dataFilter.eventCount[i]; 196 perfCallChart.symbolId[i] = dataFilter.symbolId[i]; 197 perfCallChart.fileId[i] = dataFilter.fileId[i]; 198 perfCallChart.callchainId[i] = dataFilter.callchainId[i]; 199 perfCallChart.selfDur[i] = dataFilter.selfDur[i]; 200 perfCallChart.name[i] = dataFilter.name[i]; 201 } 202 postPerfCallChartMessage(data, transfer, perfCallChart, len); 203} 204function postPerfCallChartMessage(data: unknown, transfer: boolean, perfCallChart: PerfCallChart, len: number): void { 205 (self as unknown as Worker).postMessage( 206 { 207 // @ts-ignore 208 id: data.id, 209 // @ts-ignore 210 action: data.action, 211 results: transfer 212 ? { 213 startTs: perfCallChart.startTs.buffer, 214 dur: perfCallChart.dur.buffer, 215 depth: perfCallChart.depth.buffer, 216 callchainId: perfCallChart.callchainId.buffer, 217 eventCount: perfCallChart.eventCount.buffer, 218 symbolId: perfCallChart.symbolId.buffer, 219 fileId: perfCallChart.fileId.buffer, 220 selfDur: perfCallChart.selfDur.buffer, 221 name: perfCallChart.name.buffer, 222 maxDepth: dataCache.maxDepth, 223 } 224 : {}, 225 len: len, 226 }, 227 transfer 228 ? [ 229 perfCallChart.startTs.buffer, 230 perfCallChart.dur.buffer, 231 perfCallChart.depth.buffer, 232 perfCallChart.callchainId.buffer, 233 perfCallChart.eventCount.buffer, 234 perfCallChart.symbolId.buffer, 235 perfCallChart.fileId.buffer, 236 perfCallChart.selfDur.buffer, 237 perfCallChart.name.buffer, 238 ] 239 : [] 240 ); 241} 242 243export function filterPerfCallChartData( 244 startNS: number, 245 endNS: number, 246 totalNS: number, 247 frame: unknown, 248 expand: boolean 249): DataSource { 250 let dataSource = new DataSource(); 251 let data: unknown = {}; 252 dataCache.startTs.reduce((pre, current, index) => { 253 if ( 254 dataCache.dur[index] > 0 && 255 current + dataCache.dur[index] >= startNS && 256 current <= endNS && 257 ((!expand && dataCache.depth[index] === 0) || expand) 258 ) { 259 let x = 0; 260 if (current > startNS && current < endNS) { 261 x = Math.trunc(ns2x(current, startNS, endNS, totalNS, frame)); 262 } else { 263 x = 0; 264 } 265 let key = `${x}-${dataCache.depth[index]}`; 266 // @ts-ignore 267 let preIndex = pre[key]; 268 if (preIndex !== undefined) { 269 // @ts-ignore 270 pre[key] = dataCache.dur[preIndex] > dataCache.dur[index] ? preIndex : index; 271 } else { 272 // @ts-ignore 273 pre[key] = index; 274 } 275 } 276 return pre; 277 }, data); 278 setDataSource(data, dataSource); 279 return dataSource; 280} 281function setDataSource(data: unknown, dataSource: DataSource): void { 282 // @ts-ignore 283 Reflect.ownKeys(data).map((kv: string | symbol): void => { 284 // @ts-ignore 285 let index = data[kv as string] as number; 286 // @ts-ignore 287 dataSource.startTs.push(dataCache.startTs[index]); 288 dataSource.dur.push(dataCache.dur[index]); 289 dataSource.depth.push(dataCache.depth[index]); 290 dataSource.eventCount.push(dataCache.eventCount[index]); 291 dataSource.symbolId.push(dataCache.symbolId[index]); 292 dataSource.fileId.push(dataCache.fileId[index]); 293 dataSource.callchainId.push(dataCache.callchainId[index]); 294 dataSource.selfDur.push(dataCache.selfDur[index]); 295 dataSource.name.push(dataCache.name[index]); 296 }); 297} 298// 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等 299function combinePerfSampleByCallChainId(sampleList: Array<unknown>, params: unknown): unknown[] { 300 return combineChartData( 301 sampleList.map((sample) => { 302 let perfSample: unknown = {}; 303 // @ts-ignore 304 perfSample.children = []; 305 // @ts-ignore 306 perfSample.children[0] = {}; 307 // @ts-ignore 308 perfSample.depth = -1; 309 // @ts-ignore 310 perfSample.callchainId = sample.callchainId; 311 // @ts-ignore 312 perfSample.threadId = sample.threadId; 313 // @ts-ignore 314 perfSample.id = sample.id; 315 // @ts-ignore 316 perfSample.cpuId = sample.cpuId; 317 // @ts-ignore 318 perfSample.startTime = sample.startTs; 319 // @ts-ignore 320 perfSample.endTime = sample.startTs + sample.dur; 321 // @ts-ignore 322 perfSample.totalTime = sample.dur; 323 // @ts-ignore 324 perfSample.eventCount = sample.eventCount; 325 return perfSample; 326 }), 327 params 328 ); 329} 330 331function combineChartData(samples: unknown, params: unknown): Array<unknown> { 332 let combineSample: unknown = []; 333 // 遍历sample表查到的数据,并且为其匹配相应的callchain数据 334 // @ts-ignore 335 for (let sample of samples) { 336 let stackTop = dataCache.callstack.get(`${sample.callchainId}-0`); 337 if (stackTop) { 338 let stackTopSymbol = JSON.parse(JSON.stringify(stackTop)); 339 stackTopSymbol.startTime = sample.startTime; 340 stackTopSymbol.endTime = sample.endTime; 341 stackTopSymbol.totalTime = sample.totalTime; 342 stackTopSymbol.threadId = sample.threadId; 343 stackTopSymbol.cpuId = sample.cpuId; 344 stackTopSymbol.eventCount = sample.eventCount; 345 setDur(stackTopSymbol); 346 sample.children = []; 347 sample.children.push(stackTopSymbol); 348 // 每一项都和combineSample对比 349 // @ts-ignore 350 if (combineSample.length === 0) { 351 // @ts-ignore 352 combineSample.push(sample); 353 } else { 354 // @ts-ignore 355 let pre = combineSample[combineSample.length - 1]; 356 // @ts-ignore 357 if (params.type === 0) { 358 if (pre.threadId === sample.threadId && pre.endTime === sample.startTime) { 359 // @ts-ignore 360 combinePerfCallData(combineSample[combineSample.length - 1], sample); 361 } else { 362 // @ts-ignore 363 combineSample.push(sample); 364 } 365 } else { 366 if (pre.cpuId === sample.cpuId && pre.endTime === sample.startTime) { 367 // @ts-ignore 368 combinePerfCallData(combineSample[combineSample.length - 1], sample); 369 } else { 370 // @ts-ignore 371 combineSample.push(sample); 372 } 373 } 374 } 375 } 376 } 377 // @ts-ignore 378 return combineSample; 379} 380 381// 递归设置dur,startTime,endTime 382function setDur(data: unknown): void { 383 // @ts-ignore 384 if (data.children && data.children.length > 0) { 385 // @ts-ignore 386 data.children[0].totalTime = data.totalTime; 387 // @ts-ignore 388 data.children[0].startTime = data.startTime; 389 // @ts-ignore 390 data.children[0].endTime = data.endTime; 391 // @ts-ignore 392 data.children[0].threadId = data.threadId; 393 // @ts-ignore 394 data.children[0].cpuId = data.cpuId; 395 // @ts-ignore 396 data.children[0].eventCount = data.eventCount; 397 // @ts-ignore 398 setDur(data.children[0]); 399 } else { 400 return; 401 } 402} 403 404// hiperf火焰图合并逻辑 405function combinePerfCallData(data1: unknown, data2: unknown): void { 406 if (fixMergeRuler(data1, data2)) { 407 // @ts-ignore 408 data1.endTime = data2.endTime; 409 // @ts-ignore 410 data1.totalTime = data1.endTime - data1.startTime; 411 // @ts-ignore 412 data1.eventCount += data2.eventCount; 413 // @ts-ignore 414 if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) { 415 // @ts-ignore 416 if (fixMergeRuler(data1.children[data1.children.length - 1], data2.children[0])) { 417 // @ts-ignore 418 combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]); 419 } else { 420 // @ts-ignore 421 if (data1.children[data1.children.length - 1].depth === data2.children[0].depth) { 422 // @ts-ignore 423 data1.children.push(data2.children[0]); 424 } 425 } 426 // @ts-ignore 427 } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) { 428 // @ts-ignore 429 data1.endTime = data2.endTime; 430 // @ts-ignore 431 data1.totalTime = data1.endTime - data1.startTime; 432 // @ts-ignore 433 data1.children = []; 434 // @ts-ignore 435 data1.children.push(data2.children[0]); 436 } else { 437 } 438 } 439 return; 440} 441 442/** 443 * 合并规则 444 * @param data1 445 * @param data2 446 */ 447function fixMergeRuler(data1: unknown, data2: unknown): boolean { 448 // @ts-ignore 449 return data1.depth === data2.depth && data1.name === data2.name; 450} 451 452export const hiPerfCallStackDataCacheSql = (): string => { 453 return `select c.callchain_id as callchainId, 454 c.file_id as fileId, 455 c.depth, 456 c.symbol_id as symbolId, 457 c.name 458 from perf_callchain c 459 where callchain_id != -1;`; 460}; 461 462export function hiPerfCallChartClearCache(clearStack: boolean): void { 463 if (clearStack) { 464 dataCache.callstack.clear(); 465 dataCache.sampleList.length = 0; 466 } 467 dataCache.startTs = []; 468 dataCache.dur = []; 469 dataCache.depth = []; 470 dataCache.eventCount = []; 471 dataCache.symbolId = []; 472 dataCache.fileId = []; 473 dataCache.callchainId = []; 474 dataCache.selfDur = []; 475 dataCache.name = []; 476 dataCache.maxDepth = 1; 477} 478 479function arrayBufferCallStackHandler(data: unknown, res: unknown[]): void { 480 for (const stack of res) { 481 let item = stack; 482 // @ts-ignore 483 if (data.params.trafic === TraficEnum.ProtoBuffer) { 484 item = { 485 // @ts-ignore 486 callchainId: stack.hiperfCallStackData.callchainId || 0, 487 // @ts-ignore 488 fileId: stack.hiperfCallStackData.fileId || 0, 489 // @ts-ignore 490 depth: stack.hiperfCallStackData.depth || 0, 491 // @ts-ignore 492 symbolId: stack.hiperfCallStackData.symbolId || 0, 493 // @ts-ignore 494 name: stack.hiperfCallStackData.name || 0, 495 }; 496 } 497 // @ts-ignore 498 dataCache.callstack.set(`${item.callchainId}-${item.depth}`, item); 499 // @ts-ignore 500 let parentSymbol = dataCache.callstack.get(`${item.callchainId}-${item.depth - 1}`); 501 // @ts-ignore 502 if (parentSymbol && parentSymbol.callchainId === item.callchainId && parentSymbol.depth === item.depth - 1) { 503 // @ts-ignore 504 parentSymbol.children = []; 505 // @ts-ignore 506 parentSymbol.children.push(item); 507 } 508 } 509 for (let key of Array.from(dataCache.callstack.keys())) { 510 if (!key.endsWith('-0')) { 511 dataCache.callstack.delete(key); 512 } 513 } 514 (self as unknown as Worker).postMessage( 515 { 516 // @ts-ignore 517 id: data.id, 518 // @ts-ignore 519 action: data.action, 520 results: 'ok', 521 len: res.length, 522 }, 523 [] 524 ); 525} 526 527function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: unknown): number { 528 if (endNS === 0) { 529 endNS = duration; 530 } 531 // @ts-ignore 532 let xSizeHiperf: number = ((ns - startNS) * rect.width) / (endNS - startNS); 533 if (xSizeHiperf < 0) { 534 xSizeHiperf = 0; 535 // @ts-ignore 536 } else if (xSizeHiperf > rect.width) { 537 // @ts-ignore 538 xSizeHiperf = rect.width; 539 } 540 return xSizeHiperf; 541} 542class PerfCallChart { 543 startTs: Float64Array; 544 dur: Float64Array; 545 depth: Int32Array; 546 eventCount: Int32Array; 547 symbolId: Int32Array; 548 fileId: Int32Array; 549 callchainId: Int32Array; 550 selfDur: Int32Array; 551 name: Int32Array; 552 constructor(len: number) { 553 this.startTs = new Float64Array(len); 554 this.dur = new Float64Array(len); 555 this.depth = new Int32Array(len); 556 this.eventCount = new Int32Array(len); 557 this.symbolId = new Int32Array(len); 558 this.fileId = new Int32Array(len); 559 this.callchainId = new Int32Array(len); 560 this.selfDur = new Int32Array(len); 561 this.name = new Int32Array(len); 562 } 563} 564class DataSource { 565 startTs: Array<number>; 566 dur: Array<number>; 567 depth: Array<number>; 568 eventCount: Array<number>; 569 symbolId: Array<number>; 570 fileId: Array<number>; 571 callchainId: Array<number>; 572 selfDur: Array<number>; 573 name: Array<number>; 574 constructor() { 575 this.startTs = []; 576 this.dur = []; 577 this.depth = []; 578 this.eventCount = []; 579 this.symbolId = []; 580 this.fileId = []; 581 this.callchainId = []; 582 this.selfDur = []; 583 this.name = []; 584 } 585} 586