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 { ConstructorComparison } from '../../../../js-heap/model/UiStruct'; 15import { TraficEnum } from '../utils/QueryEnum'; 16 17interface HiPerfSampleType { 18 callchainId: number; 19 startTs: number; 20 eventCount: number; 21 threadId: number; 22 cpuId: number; 23 eventTypeId: number; 24} 25 26const dataCache: { 27 startTs: Array<number>; 28 dur: Array<number>; 29 depth: Array<number>; 30 eventCount: Array<number>; 31 symbolId: Array<number>; 32 fileId: Array<number>; 33 callchainId: Array<number>; 34 selfDur: Array<number>; 35 name: Array<number>; 36 callstack: Map<string, any>; 37 sampleList: Array<HiPerfSampleType>; 38 maxDepth: number; 39} = { 40 callstack: new Map<string, any>(), 41 sampleList: [], 42 maxDepth: 1, 43 startTs: [], 44 dur: [], 45 depth: [], 46 eventCount: [], 47 symbolId: [], 48 fileId: [], 49 callchainId: [], 50 selfDur: [], 51 name: [], 52}; 53 54export const chartHiperfCallChartDataSql = (args: any): string => { 55 const sql = ` 56 select callchain_id as callchainId, 57 timestamp_trace - ${args.recordStartNS} as startTs, 58 event_count as eventCount, 59 A.thread_id as threadId, 60 cpu_id as cpuId, 61 event_type_id as eventTypeId 62 from perf_sample A 63 where callchain_id != -1 and A.thread_id != 0 64 order by cpuId, startTs`; 65 return sql; 66}; 67 68export function hiPerfCallChartDataHandler(data: any, proc: Function): void { 69 if (data.params.isCache) { 70 let res: Array<any> = proc(chartHiperfCallChartDataSql(data.params)); 71 for (let i = 0; i < res.length; i++) { 72 if (i > 0) { 73 if (res[i].cpuId === res[i - 1].cpuId) { 74 res[i - 1].dur = res[i].startTs - res[i - 1].startTs; 75 } else { 76 res[i - 1].dur = data.params.endNS - res[i - 1].startTs; 77 } 78 } 79 if (i === res.length - 1) { 80 res[i].dur = data.params.endNS - res[i].startTs; 81 } 82 } 83 dataCache.sampleList = res; 84 (self as unknown as Worker).postMessage( 85 { 86 len: 0, 87 id: data.id, 88 action: data.action, 89 results: 'ok', 90 }, 91 [] 92 ); 93 } else { 94 let res: Array<any> = []; 95 if (!data.params.isComplete) { 96 res = dataCache.sampleList.filter((it) => { 97 let cpuThreadFilter = data.params.type === 0 ? it.cpuId === data.params.id : it.threadId === data.params.id; 98 let eventTypeFilter = data.params.eventTypeId === -2 ? true : it.eventTypeId === data.params.eventTypeId; 99 return cpuThreadFilter && eventTypeFilter; 100 }); 101 } 102 arrayBufferHandler(data, res, true, !data.params.isComplete); 103 } 104} 105 106export function hiPerfCallStackCacheHandler(data: any, proc: Function): void { 107 if (data.params.isCache) { 108 hiPerfCallChartClearCache(true); 109 arrayBufferCallStackHandler(data, proc(hiPerfCallStackDataCacheSql())); 110 } 111} 112 113function arrayBufferHandler(data: any, res: any[], transfer: boolean, loadData: boolean): void { 114 if (loadData) { 115 let result = combinePerfSampleByCallChainId(res, data.params); 116 hiPerfCallChartClearCache(false); 117 const getArrayData = (combineData: Array<any>): void => { 118 for (let item of combineData) { 119 if (item.depth > -1) { 120 dataCache.startTs.push(item.startTime); 121 dataCache.dur.push(item.totalTime); 122 dataCache.depth.push(item.depth); 123 dataCache.eventCount.push(item.eventCount); 124 dataCache.symbolId.push(item.symbolId); 125 dataCache.fileId.push(item.fileId); 126 dataCache.callchainId.push(item.callchainId); 127 dataCache.name.push(item.name); 128 let self = item.totalTime || 0; 129 if (item.children) { 130 (item.children as Array<any>).forEach((child) => { 131 self -= child.totalTime; 132 }); 133 } 134 dataCache.selfDur.push(self); 135 } 136 if (item.depth + 1 > dataCache.maxDepth) { 137 dataCache.maxDepth = item.depth + 1; 138 } 139 if (item.children && item.children.length > 0) { 140 getArrayData(item.children); 141 } 142 } 143 }; 144 getArrayData(result); 145 } 146 setTimeout((): void => { 147 arrayBufferCallback(data, transfer); 148 }, 150); 149} 150 151function arrayBufferCallback(data: any, transfer: boolean): void { 152 let params = data.params; 153 let dataFilter = filterPerfCallChartData(params.startNS, params.endNS, params.totalNS, params.frame, params.expand); 154 let len = dataFilter.startTs.length; 155 let perfCallChart = new PerfCallChart(len); 156 for (let i = 0; i < len; i++) { 157 perfCallChart.startTs[i] = dataFilter.startTs[i]; 158 perfCallChart.dur[i] = dataFilter.dur[i]; 159 perfCallChart.depth[i] = dataFilter.depth[i]; 160 perfCallChart.eventCount[i] = dataFilter.eventCount[i]; 161 perfCallChart.symbolId[i] = dataFilter.symbolId[i]; 162 perfCallChart.fileId[i] = dataFilter.fileId[i]; 163 perfCallChart.callchainId[i] = dataFilter.callchainId[i]; 164 perfCallChart.selfDur[i] = dataFilter.selfDur[i]; 165 perfCallChart.name[i] = dataFilter.name[i]; 166 } 167 postPerfCallChartMessage(data, transfer, perfCallChart, len); 168} 169function postPerfCallChartMessage(data: any, transfer: boolean, perfCallChart: PerfCallChart, len: number) { 170 (self as unknown as Worker).postMessage( 171 { 172 id: data.id, 173 action: data.action, 174 results: transfer 175 ? { 176 startTs: perfCallChart.startTs.buffer, 177 dur: perfCallChart.dur.buffer, 178 depth: perfCallChart.depth.buffer, 179 callchainId: perfCallChart.callchainId.buffer, 180 eventCount: perfCallChart.eventCount.buffer, 181 symbolId: perfCallChart.symbolId.buffer, 182 fileId: perfCallChart.fileId.buffer, 183 selfDur: perfCallChart.selfDur.buffer, 184 name: perfCallChart.name.buffer, 185 maxDepth: dataCache.maxDepth, 186 } 187 : {}, 188 len: len, 189 }, 190 transfer 191 ? [ 192 perfCallChart.startTs.buffer, 193 perfCallChart.dur.buffer, 194 perfCallChart.depth.buffer, 195 perfCallChart.callchainId.buffer, 196 perfCallChart.eventCount.buffer, 197 perfCallChart.symbolId.buffer, 198 perfCallChart.fileId.buffer, 199 perfCallChart.selfDur.buffer, 200 perfCallChart.name.buffer, 201 ] 202 : [] 203 ); 204} 205 206export function filterPerfCallChartData( 207 startNS: number, 208 endNS: number, 209 totalNS: number, 210 frame: any, 211 expand: boolean 212): DataSource { 213 let dataSource = new DataSource(); 214 let data: any = {}; 215 dataCache.startTs.reduce((pre, current, index) => { 216 if ( 217 dataCache.dur[index] > 0 && 218 current + dataCache.dur[index] >= startNS && 219 current <= endNS && 220 ((!expand && dataCache.depth[index] === 0) || expand) 221 ) { 222 let x = 0; 223 if (current > startNS && current < endNS) { 224 x = Math.trunc(ns2x(current, startNS, endNS, totalNS, frame)); 225 } else { 226 x = 0; 227 } 228 let key = `${x}-${dataCache.depth[index]}`; 229 let preIndex = pre[key]; 230 if (preIndex !== undefined) { 231 pre[key] = dataCache.dur[preIndex] > dataCache.dur[index] ? preIndex : index; 232 } else { 233 pre[key] = index; 234 } 235 } 236 return pre; 237 }, data); 238 setDataSource(data, dataSource); 239 return dataSource; 240} 241function setDataSource(data: any, dataSource: DataSource) { 242 Reflect.ownKeys(data).map((kv: string | symbol): void => { 243 let index = data[kv as string] as number; 244 dataSource.startTs.push(dataCache.startTs[index]); 245 dataSource.dur.push(dataCache.dur[index]); 246 dataSource.depth.push(dataCache.depth[index]); 247 dataSource.eventCount.push(dataCache.eventCount[index]); 248 dataSource.symbolId.push(dataCache.symbolId[index]); 249 dataSource.fileId.push(dataCache.fileId[index]); 250 dataSource.callchainId.push(dataCache.callchainId[index]); 251 dataSource.selfDur.push(dataCache.selfDur[index]); 252 dataSource.name.push(dataCache.name[index]); 253 }); 254} 255// 将perf_sample表的数据根据callchain_id分组并赋值startTime,endTime等等 256function combinePerfSampleByCallChainId(sampleList: Array<any>, params: any): any[] { 257 return combineChartData( 258 sampleList.map((sample) => { 259 let perfSample: any = {}; 260 perfSample.children = new Array<any>(); 261 perfSample.children[0] = {}; 262 perfSample.depth = -1; 263 perfSample.callchainId = sample.callchainId; 264 perfSample.threadId = sample.threadId; 265 perfSample.id = sample.id; 266 perfSample.cpuId = sample.cpuId; 267 perfSample.startTime = sample.startTs; 268 perfSample.endTime = sample.startTs + sample.dur; 269 perfSample.totalTime = sample.dur; 270 perfSample.eventCount = sample.eventCount; 271 return perfSample; 272 }), 273 params 274 ); 275} 276 277function combineChartData(samples: any, params: any): Array<any> { 278 let combineSample: any = []; 279 // 遍历sample表查到的数据,并且为其匹配相应的callchain数据 280 for (let sample of samples) { 281 let stackTop = dataCache.callstack.get(`${sample.callchainId}-0`); 282 if (stackTop) { 283 let stackTopSymbol = JSON.parse(JSON.stringify(stackTop)); 284 stackTopSymbol.startTime = sample.startTime; 285 stackTopSymbol.endTime = sample.endTime; 286 stackTopSymbol.totalTime = sample.totalTime; 287 stackTopSymbol.threadId = sample.threadId; 288 stackTopSymbol.cpuId = sample.cpuId; 289 stackTopSymbol.eventCount = sample.eventCount; 290 setDur(stackTopSymbol); 291 sample.children = new Array<any>(); 292 sample.children.push(stackTopSymbol); 293 // 每一项都和combineSample对比 294 if (combineSample.length === 0) { 295 combineSample.push(sample); 296 } else { 297 let pre = combineSample[combineSample.length - 1]; 298 if (params.type === 0) { 299 if (pre.threadId === sample.threadId && pre.endTime === sample.startTime) { 300 combinePerfCallData(combineSample[combineSample.length - 1], sample); 301 } else { 302 combineSample.push(sample); 303 } 304 } else { 305 if (pre.cpuId === sample.cpuId && pre.endTime === sample.startTime) { 306 combinePerfCallData(combineSample[combineSample.length - 1], sample); 307 } else { 308 combineSample.push(sample); 309 } 310 } 311 } 312 } 313 } 314 return combineSample; 315} 316 317// 递归设置dur,startTime,endTime 318function setDur(data: any): void { 319 if (data.children && data.children.length > 0) { 320 data.children[0].totalTime = data.totalTime; 321 data.children[0].startTime = data.startTime; 322 data.children[0].endTime = data.endTime; 323 data.children[0].threadId = data.threadId; 324 data.children[0].cpuId = data.cpuId; 325 data.children[0].eventCount = data.eventCount; 326 setDur(data.children[0]); 327 } else { 328 return; 329 } 330} 331 332// hiperf火焰图合并逻辑 333function combinePerfCallData(data1: any, data2: any): void { 334 if (fixMergeRuler(data1, data2)) { 335 data1.endTime = data2.endTime; 336 data1.totalTime = data1.endTime - data1.startTime; 337 data1.eventCount += data2.eventCount; 338 if (data1.children && data1.children.length > 0 && data2.children && data2.children.length > 0) { 339 if (fixMergeRuler(data1.children[data1.children.length - 1], data2.children[0])) { 340 combinePerfCallData(data1.children[data1.children.length - 1], data2.children[0]); 341 } else { 342 if (data1.children[data1.children.length - 1].depth === data2.children[0].depth) { 343 data1.children.push(data2.children[0]); 344 } 345 } 346 } else if (data2.children && data2.children.length > 0 && (!data1.children || data1.children.length === 0)) { 347 data1.endTime = data2.endTime; 348 data1.totalTime = data1.endTime - data1.startTime; 349 data1.children = new Array<any>(); 350 data1.children.push(data2.children[0]); 351 } else { 352 } 353 } 354 return; 355} 356 357/** 358 * 合并规则 359 * @param data1 360 * @param data2 361 */ 362function fixMergeRuler(data1: any, data2: any): boolean { 363 return data1.depth === data2.depth && data1.name === data2.name; 364} 365 366export const hiPerfCallStackDataCacheSql = (): string => { 367 return `select c.callchain_id as callchainId, 368 c.file_id as fileId, 369 c.depth, 370 c.symbol_id as symbolId, 371 c.name 372 from perf_callchain c 373 where callchain_id != -1;`; 374}; 375 376export function hiPerfCallChartClearCache(clearStack: boolean): void { 377 if (clearStack) { 378 dataCache.callstack.clear(); 379 dataCache.sampleList.length = 0; 380 } 381 dataCache.startTs = []; 382 dataCache.dur = []; 383 dataCache.depth = []; 384 dataCache.eventCount = []; 385 dataCache.symbolId = []; 386 dataCache.fileId = []; 387 dataCache.callchainId = []; 388 dataCache.selfDur = []; 389 dataCache.name = []; 390 dataCache.maxDepth = 1; 391} 392 393function arrayBufferCallStackHandler(data: any, res: any[]): void { 394 for (const stack of res) { 395 let item = stack; 396 if (data.params.trafic === TraficEnum.ProtoBuffer) { 397 item = { 398 callchainId: stack.hiperfCallStackData.callchainId || 0, 399 fileId: stack.hiperfCallStackData.fileId || 0, 400 depth: stack.hiperfCallStackData.depth || 0, 401 symbolId: stack.hiperfCallStackData.symbolId || 0, 402 name: stack.hiperfCallStackData.name || 0, 403 }; 404 } 405 dataCache.callstack.set(`${item.callchainId}-${item.depth}`, item); 406 let parentSymbol = dataCache.callstack.get(`${item.callchainId}-${item.depth - 1}`); 407 if (parentSymbol && parentSymbol.callchainId === item.callchainId && parentSymbol.depth === item.depth - 1) { 408 parentSymbol.children = new Array<any>(); 409 parentSymbol.children.push(item); 410 } 411 } 412 for (let key of Array.from(dataCache.callstack.keys())) { 413 if (!key.endsWith('-0')) { 414 dataCache.callstack.delete(key); 415 } 416 } 417 (self as unknown as Worker).postMessage( 418 { 419 id: data.id, 420 action: data.action, 421 results: 'ok', 422 len: res.length, 423 }, 424 [] 425 ); 426} 427 428function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any): number { 429 if (endNS === 0) { 430 endNS = duration; 431 } 432 let xSizeHiperf: number = ((ns - startNS) * rect.width) / (endNS - startNS); 433 if (xSizeHiperf < 0) { 434 xSizeHiperf = 0; 435 } else if (xSizeHiperf > rect.width) { 436 xSizeHiperf = rect.width; 437 } 438 return xSizeHiperf; 439} 440class PerfCallChart { 441 startTs: Float64Array; 442 dur: Float64Array; 443 depth: Int32Array; 444 eventCount: Int32Array; 445 symbolId: Int32Array; 446 fileId: Int32Array; 447 callchainId: Int32Array; 448 selfDur: Int32Array; 449 name: Int32Array; 450 constructor(len: number) { 451 this.startTs = new Float64Array(len); 452 this.dur = new Float64Array(len); 453 this.depth = new Int32Array(len); 454 this.eventCount = new Int32Array(len); 455 this.symbolId = new Int32Array(len); 456 this.fileId = new Int32Array(len); 457 this.callchainId = new Int32Array(len); 458 this.selfDur = new Int32Array(len); 459 this.name = new Int32Array(len); 460 } 461} 462class DataSource { 463 startTs: Array<number>; 464 dur: Array<number>; 465 depth: Array<number>; 466 eventCount: Array<number>; 467 symbolId: Array<number>; 468 fileId: Array<number>; 469 callchainId: Array<number>; 470 selfDur: Array<number>; 471 name: Array<number>; 472 constructor() { 473 this.startTs = []; 474 this.dur = []; 475 this.depth = []; 476 this.eventCount = []; 477 this.symbolId = []; 478 this.fileId = []; 479 this.callchainId = []; 480 this.selfDur = []; 481 this.name = []; 482 } 483} 484