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