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 { DataCache, JsProfilerSymbol, convertJSON } from '../../database/logic-worker/ProcedureLogicWorkerCommon'; 17import { JsCpuProfilerChartFrame, type JsCpuProfilerUIStruct } from '../../bean/JsStruct'; 18import { JsCpuProfilerSample, SampleType } from '../logic-worker/ProcedureLogicWorkerJsCpuProfiler'; 19import { TraficEnum } from './utils/QueryEnum'; 20 21const dataCache = DataCache.getInstance(); 22const ROOT_ID = 1; 23let samples = Array<JsCpuProfilerSample>(); // Array index equals id; 24const jsCallChain: Array<any> = []; 25let chartId: number = 0; 26const jsDataCache: { 27 childrenIds: Array<Array<number>>; 28 column: Array<number>; 29 depth: Array<number>; 30 endTime: Array<number>; 31 id: Array<number>; 32 line: Array<number>; 33 nameId: Array<number>; 34 parentId: Array<number>; 35 samplesIds: Array<Array<number>>; 36 selfTime: Array<number>; 37 startTime: Array<number>; 38 totalTime: Array<number>; 39 urlId: Array<number>; 40 maxDepth: number; 41} = { 42 samplesIds: [], 43 childrenIds: [], 44 column: [], 45 depth: [], 46 endTime: [], 47 id: [], 48 line: [], 49 nameId: [], 50 parentId: [], 51 selfTime: [], 52 startTime: [], 53 totalTime: [], 54 urlId: [], 55 maxDepth: 1, 56}; 57 58export const initCallChainDataSql = (args: any) => { 59 const sql = `SELECT function_id AS id, 60 0 As functionId, 61 0 AS startTime, 62 0 As endTime, 63 0 As dur, 64 function_index AS nameId, 65 url_index AS urlId, 66 line_number as line, 67 column_number as column, 68 hit_count AS hitCount, 69 children AS childrenString, 70 parent_id AS parentId 71 FROM js_cpu_profiler_node`; 72 return sql; 73}; 74 75export const queryChartDataSqlMem = (args: any) => { 76 const sql = `SELECT id, 77 function_id AS functionId, 78 start_time - ${args.recordStartNS} AS startTime, 79 end_time - ${args.recordStartNS} AS endTime, 80 dur 81 FROM js_cpu_profiler_sample`; 82 return sql; 83}; 84 85export function cpuProfilerDataReceiver(data: any, proc: Function): void { 86 let sql = initCallChainDataSql(data.params); 87 let res = proc(sql); 88 if (res.length > 0) { 89 if (!dataCache.jsCallChain || dataCache.jsCallChain.length === 0) { 90 dataCache.jsCallChain = res; 91 createCallChain(); 92 } 93 let sql = queryChartDataSqlMem(data.params); 94 let chartData = proc(sql); 95 if (chartData.length > 0) { 96 samples = convertJSON(chartData); 97 if (data.params.trafic === TraficEnum.ProtoBuffer) { 98 arrayBufferHandler(data, samples, true); 99 } 100 } 101 } 102} 103 104/** 105 * 建立callChain每个函数的联系,设置depth跟children 106 */ 107function createCallChain(): void { 108 const jsSymbolMap = dataCache.jsSymbolMap; 109 const symbol = new JsProfilerSymbol(); 110 for (const data of dataCache.jsCallChain!) { 111 let item = data.cpuProfilerData || symbol; 112 if (!item.childrenString) { 113 item.childrenString = ''; 114 } 115 jsSymbolMap.set(item.id, item); 116 //root不需要显示,depth为-1 117 if (item.id === ROOT_ID) { 118 item.depth = -1; 119 } 120 if (item.parentId > 0) { 121 let parentSymbol = jsSymbolMap.get(item.parentId); 122 if (parentSymbol) { 123 if (!parentSymbol.children) { 124 parentSymbol.children = new Array<JsProfilerSymbol>(); 125 parentSymbol.childrenIds = new Array<number>(); 126 parentSymbol.childrenString = ''; 127 } 128 parentSymbol.children.push(item); 129 parentSymbol.childrenString! += `${item.id},`; 130 parentSymbol.childrenIds.push(item.id); 131 item.depth = parentSymbol.depth + 1; 132 } 133 } 134 } 135} 136 137function combineChartData(res: Array<JsCpuProfilerSample>): Array<JsCpuProfilerChartFrame> { 138 const combineSample = new Array<JsCpuProfilerChartFrame>(); 139 const symbol = new JsCpuProfilerSample(); 140 for (let data of res) { 141 let sample = data.cpuProfilerData || symbol; 142 const stackTopSymbol = dataCache.jsSymbolMap.get(sample.functionId); 143 // root 节点不需要显示 144 if (stackTopSymbol?.id === ROOT_ID) { 145 sample.type = SampleType.OTHER; 146 continue; 147 } 148 if (stackTopSymbol) { 149 // 获取栈顶函数的整条调用栈为一个数组 下标0为触发的栈底函数 150 sample.stack = getFullCallChainOfNode(stackTopSymbol); 151 if (combineSample.length === 0) { 152 // 首次combineSample没有数据时,用第一条数据创建一个调用树 153 createNewChartFrame(sample, combineSample); 154 } else { 155 const lastCallChart = combineSample[combineSample.length - 1]; 156 if (isSymbolEqual(sample.stack[0], lastCallChart) && lastCallChart.endTime === sample.startTime) { 157 combineCallChain(lastCallChart, sample); 158 } else { 159 // 一个调用链栈底函数与前一个不同时,需要新加入到combineSample 160 createNewChartFrame(sample, combineSample); 161 } 162 } 163 } 164 } 165 return combineSample; 166} 167 168/** 169 * 根据每个sample的栈顶函数,获取完整的调用栈 170 * @param node 栈顶函数 171 * @returns 完整的调用栈 172 */ 173function getFullCallChainOfNode(node: JsProfilerSymbol): Array<JsProfilerSymbol> { 174 const callChain = new Array<JsProfilerSymbol>(); 175 callChain.push(node); 176 while (node.parentId !== 0) { 177 const parent = dataCache.jsSymbolMap.get(node.parentId); 178 // id 1 is root Node 179 if (!parent || parent.id <= ROOT_ID) { 180 break; 181 } 182 callChain.push(parent); 183 node = parent; 184 } 185 callChain.reverse(); 186 return callChain; 187} 188 189function createNewChartFrame(sample: JsCpuProfilerSample, combineSample: Array<JsCpuProfilerChartFrame>): void { 190 let lastSymbol: JsCpuProfilerChartFrame; 191 for (const [idx, symbol] of sample.stack!.entries()) { 192 if (idx === 0) { 193 lastSymbol = symbolToChartFrame(sample, symbol); 194 combineSample.push(lastSymbol); 195 } else { 196 const callFrame = symbolToChartFrame(sample, symbol); 197 lastSymbol!.children.push(callFrame); 198 lastSymbol!.childrenIds.push(callFrame.id); 199 callFrame.parentId = lastSymbol!.id; 200 lastSymbol = callFrame; 201 } 202 if (idx + 1 === sample.stack?.length) { 203 lastSymbol.selfTime = sample.dur; 204 } 205 } 206} 207 208/** 209 * 创建一个JsCpuProfilerChartFrame 作为绘制泳道图的结构 210 * @param sample 数据库样本数据 211 * @param symbol 样本的每一个函数 212 * @returns JsCpuProfilerChartFrame 213 */ 214function symbolToChartFrame(sample: JsCpuProfilerSample, symbol: JsProfilerSymbol): JsCpuProfilerChartFrame { 215 const chartFrame = new JsCpuProfilerChartFrame( 216 chartId++, 217 symbol.nameId, 218 sample.startTime, 219 sample.endTime, 220 sample.dur, 221 symbol.depth, 222 symbol.urlId, 223 symbol.line, 224 symbol.column 225 ); 226 chartFrame.samplesIds.push(sample.id); 227 return chartFrame; 228} 229 230function isSymbolEqual(symbol: JsProfilerSymbol, uiData: JsCpuProfilerUIStruct): boolean { 231 return symbol.nameId === uiData.nameId && symbol.urlId === uiData.urlId; 232} 233 234/** 235 * 相邻的两个sample的name,url,depth相同,且上一个的endTime等于下一个的startTime, 236 * 则两个sample的调用栈合并 237 * @param lastCallTree 上一个已经合并的树结构调用栈 238 * @param sample 当前样本数据 239 */ 240function combineCallChain(lastCallTree: JsCpuProfilerChartFrame, sample: JsCpuProfilerSample): void { 241 let lastCallTreeSymbol = lastCallTree; 242 let parentCallFrame: JsCpuProfilerChartFrame; 243 let isEqual = true; 244 for (const [idx, symbol] of sample.stack!.entries()) { 245 // 是否为每次采样的栈顶函数 246 const isLastSymbol = idx + 1 === sample.stack?.length; 247 if ( 248 isEqual && 249 isSymbolEqual(symbol, lastCallTreeSymbol) && 250 lastCallTreeSymbol.depth === idx && 251 lastCallTreeSymbol.endTime === sample.startTime 252 ) { 253 // 如果函数名跟depth匹配,则更新函数的持续时间 254 lastCallTreeSymbol.endTime = sample.endTime; 255 lastCallTreeSymbol.totalTime = sample.endTime - lastCallTreeSymbol.startTime; 256 lastCallTreeSymbol.samplesIds.push(sample.id); 257 let lastChildren = lastCallTreeSymbol.children; 258 parentCallFrame = lastCallTreeSymbol; 259 if (lastChildren && lastChildren.length > 0) { 260 lastCallTreeSymbol = lastChildren[lastChildren.length - 1]; 261 } 262 isEqual = true; 263 } else { 264 // 如果不匹配,则作为新的分支添加到lastCallTree 265 const deltaFrame = symbolToChartFrame(sample, symbol); 266 parentCallFrame!.children.push(deltaFrame); 267 parentCallFrame!.childrenIds.push(deltaFrame.id); 268 deltaFrame.parentId = parentCallFrame!.id; 269 parentCallFrame = deltaFrame; 270 isEqual = false; 271 } 272 // 每次采样的栈顶函数的selfTime为该次采样数据的时间 273 if (isLastSymbol) { 274 parentCallFrame.selfTime += sample.dur; 275 } 276 } 277} 278 279function arrayBufferHandler(data: any, res: any[], transfer: boolean): void { 280 let result = combineChartData(res); 281 clearJsCacheData(); 282 const getArrayData = (combineData: Array<any>): void => { 283 for (let item of combineData) { 284 if (item.depth > -1) { 285 jsDataCache.id.push(item.id); 286 jsDataCache.startTime.push(item.startTime); 287 jsDataCache.endTime.push(item.endTime); 288 jsDataCache.selfTime.push(item.selfTime); 289 jsDataCache.totalTime.push(item.totalTime); 290 jsDataCache.column.push(item.column); 291 jsDataCache.line.push(item.line); 292 jsDataCache.depth.push(item.depth); 293 jsDataCache.parentId.push(item.parentId); 294 jsDataCache.nameId.push(item.nameId); 295 jsDataCache.samplesIds.push([...item.samplesIds]); 296 jsDataCache.urlId.push(item.urlId); 297 jsDataCache.childrenIds.push([...item.childrenIds]); 298 if (item.depth + 1 > jsDataCache.maxDepth) { 299 jsDataCache.maxDepth = item.depth + 1; 300 } 301 if (item.children && item.children.length > 0) { 302 getArrayData(item.children); 303 } 304 } 305 } 306 }; 307 getArrayData(result); 308 setTimeout((): void => { 309 arrayBufferCallback(data, transfer); 310 }, 150); 311} 312function arrayBufferCallback(data: any, transfer: boolean): void { 313 let dataFilter = jsDataCache; 314 let len = dataFilter!.startTime!.length; 315 const arkTs = new ArkTS(len); 316 for (let i = 0; i < len; i++) { 317 arkTs.column[i] = dataFilter.column[i]; 318 arkTs.depth[i] = dataFilter.depth[i]; 319 arkTs.endTime[i] = dataFilter.endTime[i]; 320 arkTs.id[i] = dataFilter.id[i]; 321 arkTs.line[i] = dataFilter.line[i]; 322 arkTs.nameId[i] = dataFilter.nameId[i]; 323 arkTs.parentId[i] = dataFilter.parentId[i]; 324 arkTs.samplesIds[i] = [...dataFilter.samplesIds[i]]; 325 arkTs.selfTime[i] = dataFilter.selfTime[i]; 326 arkTs.startTime[i] = dataFilter.startTime[i]; 327 arkTs.totalTime[i] = dataFilter.totalTime[i]; 328 arkTs.urlId[i] = dataFilter.urlId[i]; 329 arkTs.childrenIds[i] = [...dataFilter.childrenIds[i]]; 330 } 331 postMessage(data, transfer, arkTs, len); 332 // 合并完泳道图数据之后,Tab页不再需要缓存数据 333 if (jsCallChain) { 334 dataCache.jsCallChain!.length = 0; 335 } 336 dataCache.jsSymbolMap!.clear(); 337} 338function postMessage(data: any, transfer: boolean, arkTs: ArkTS, len: number): void { 339 (self as unknown as Worker).postMessage( 340 { 341 id: data.id, 342 action: data.action, 343 results: transfer 344 ? { 345 column: arkTs.column.buffer, 346 depth: arkTs.depth.buffer, 347 endTime: arkTs.endTime.buffer, 348 id: arkTs.id.buffer, 349 line: arkTs.line.buffer, 350 nameId: arkTs.nameId.buffer, 351 parentId: arkTs.parentId.buffer, 352 samplesIds: arkTs.samplesIds, 353 selfTime: arkTs.selfTime.buffer, 354 startTime: arkTs.startTime.buffer, 355 totalTime: arkTs.totalTime.buffer, 356 urlId: arkTs.urlId.buffer, 357 childrenIds: arkTs.childrenIds, 358 maxDepth: jsDataCache.maxDepth, 359 } 360 : {}, 361 len: len, 362 }, 363 transfer 364 ? [ 365 arkTs.column.buffer, 366 arkTs.depth.buffer, 367 arkTs.endTime.buffer, 368 arkTs.id.buffer, 369 arkTs.line.buffer, 370 arkTs.parentId.buffer, 371 arkTs.selfTime.buffer, 372 arkTs.startTime.buffer, 373 arkTs.totalTime.buffer, 374 ] 375 : [] 376 ); 377} 378 379function ns2x(ns: number, startNS: number, endNS: number, duration: number, width: any): number { 380 if (endNS === 0) { 381 endNS = duration; 382 } 383 let xSize: number = ((ns - startNS) * width) / (endNS - startNS); 384 xSize = xSize < 0 ? 0 : xSize > width ? width : xSize; 385 return xSize; 386} 387 388function clearJsCacheData(): void { 389 chartId = 0; 390 jsDataCache.childrenIds = []; 391 jsDataCache.column = []; 392 jsDataCache.depth = []; 393 jsDataCache.endTime = []; 394 jsDataCache.id = []; 395 jsDataCache.line = []; 396 jsDataCache.nameId = []; 397 jsDataCache.parentId = []; 398 jsDataCache.samplesIds = []; 399 jsDataCache.selfTime = []; 400 jsDataCache.startTime = []; 401 jsDataCache.totalTime = []; 402 jsDataCache.urlId = []; 403 jsDataCache.maxDepth = 1; 404} 405 406// eslint-disable-next-line max-lines-per-function 407function filterCpuProfilerChartData(startNS: number, endNS: number, totalNS: number, width: number): any { 408 let dataSource: any; 409 if (jsDataCache) { 410 let outArr: Array<any> = []; 411 let column = new Int32Array(jsDataCache.column); 412 let depth = new Int32Array(jsDataCache.depth); 413 let endTime = new Float64Array(jsDataCache.endTime); 414 let id = new Int32Array(jsDataCache.id); 415 let line = new Int32Array(jsDataCache.line); 416 let nameId = new Int32Array(jsDataCache.nameId); 417 let parentId = new Int32Array(jsDataCache.parentId); 418 let samplesIds = jsDataCache.samplesIds; 419 let selfTime = new Float64Array(jsDataCache.selfTime); 420 let startTime = new Float64Array(jsDataCache.startTime); 421 let totalTime = new Float64Array(jsDataCache.totalTime); 422 let urlId = new Int32Array(jsDataCache.urlId); 423 let childrenIds = jsDataCache.childrenIds; 424 for (let i = 0; i < jsDataCache.startTime.length; i++) { 425 outArr.push({ 426 column: column[i], 427 depth: depth[i], 428 endTime: endTime[i], 429 id: id[i], 430 line: line[i], 431 nameId: nameId[i], 432 parentId: parentId[i], 433 samplesIds: samplesIds[i], 434 selfTime: selfTime[i], 435 startTime: startTime[i], 436 totalTime: totalTime[i], 437 urlId: urlId[i], 438 childrenIds: childrenIds[i], 439 } as any); 440 } 441 let groups: any = {}; 442 filterDataByStartTime(startNS, endNS, totalNS, width, groups); 443 setDataSource(dataSource, groups); 444 } 445 return dataSource; 446} 447function filterDataByStartTime(startNS: number, endNS: number, totalNS: number, width: number, groups: any) { 448 jsDataCache.startTime.reduce((pre, current, index) => { 449 if (jsDataCache.totalTime[index] > 0 && current + jsDataCache.totalTime[index] >= startNS && current <= endNS) { 450 let x = 0; 451 if (current > startNS && current < endNS) { 452 x = Math.trunc(ns2x(current, startNS, endNS, totalNS, width)); 453 } else { 454 x = 0; 455 } 456 let key = `${x}-${jsDataCache.depth[index]}`; 457 let preIndex = pre[key]; 458 if (preIndex !== undefined) { 459 pre[key] = jsDataCache.totalTime[preIndex] > jsDataCache.totalTime[index] ? preIndex : index; 460 } else { 461 pre[key] = index; 462 } 463 } 464 return pre; 465 }, groups); 466} 467function setDataSource(dataSource: any, groups: any): void { 468 Reflect.ownKeys(groups).map(kv => { 469 let index = groups[kv as string]; 470 dataSource.column.push(jsDataCache.column[index]); 471 dataSource.depth.push(jsDataCache.depth[index]); 472 dataSource.endTime.push(jsDataCache.endTime[index]); 473 dataSource.id.push(jsDataCache.id[index]); 474 dataSource.line.push(jsDataCache.line[index]); 475 dataSource.nameId.push(jsDataCache.nameId[index]); 476 dataSource.parentId.push(jsDataCache.parentId[index]); 477 dataSource.samplesIds.push(jsDataCache.samplesIds[index]); 478 dataSource.selfTime.push(jsDataCache.selfTime[index]); 479 dataSource.startTime.push(jsDataCache.startTime[index]); 480 dataSource.totalTime.push(jsDataCache.totalTime[index]); 481 dataSource.urlId.push(jsDataCache.urlId[index]); 482 dataSource.childrenIds.push(jsDataCache.childrenIds[index]); 483 }); 484} 485 486class ArkTS { 487 column: Int32Array; 488 depth: Int32Array; 489 endTime: Float64Array; 490 id: Int32Array; 491 line: Int32Array; 492 nameId: Int32Array; 493 parentId: Int32Array; 494 samplesIds: Array<any>; 495 selfTime: Float64Array; 496 startTime: Float64Array; 497 totalTime: Float64Array; 498 urlId: Int32Array; 499 childrenIds: Array<any>; 500 501 constructor(len: number) { 502 this.column = new Int32Array(len); 503 this.depth = new Int32Array(len); 504 this.endTime = new Float64Array(len); 505 this.id = new Int32Array(len); 506 this.line = new Int32Array(len); 507 this.nameId = new Int32Array(len); 508 this.parentId = new Int32Array(len); 509 this.samplesIds = []; 510 this.selfTime = new Float64Array(len); 511 this.startTime = new Float64Array(len); 512 this.totalTime = new Float64Array(len); 513 this.urlId = new Int32Array(len); 514 this.childrenIds = []; 515 } 516} 517