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 */ 15import { JsCpuProfilerChartFrame, JsCpuProfilerTabStruct } from '../../bean/JsStruct'; 16import { DataCache, type JsProfilerSymbol, LogicHandler, convertJSON } from './ProcedureLogicWorkerCommon'; 17 18const ROOT_ID = 1; 19const LAMBDA_FUNCTION_NAME = '(anonymous)'; 20export class ProcedureLogicWorkerJsCpuProfiler extends LogicHandler { 21 private currentEventId!: string; 22 private dataCache = DataCache.getInstance(); 23 private samples = Array<JsCpuProfilerSample>(); // Array index equals id; 24 private chartId = 0; 25 private tabDataId = 0; 26 private chartData: Array<JsCpuProfilerChartFrame> = []; 27 private leftNs: number = 0; 28 private rightNs: number = 0; 29 30 public handle(msg: any): void { 31 this.currentEventId = msg.id; 32 if (msg && msg.type) { 33 switch (msg.type) { 34 case 'jsCpuProfiler-call-chain': 35 this.jsCpuProfilerCallChain(msg); 36 break; 37 case 'jsCpuProfiler-call-tree': 38 this.jsCpuProfilerCallTree(msg); 39 break; 40 case 'jsCpuProfiler-bottom-up': 41 this.jsCpuProfilerBottomUp(msg); 42 break; 43 case 'jsCpuProfiler-statistics': 44 this.jsCpuProfilerStatistics(msg); 45 break; 46 } 47 } 48 } 49 private jsCpuProfilerCallChain(msg: any): void { 50 if (!this.dataCache.jsCallChain || this.dataCache.jsCallChain.length === 0) { 51 this.dataCache.jsCallChain = convertJSON(msg.params.list) || []; 52 this.createCallChain(); 53 } 54 } 55 private jsCpuProfilerCallTree(msg: any): void { 56 this.tabDataId = 0; 57 self.postMessage({ 58 id: msg.id, 59 action: msg.action, 60 results: this.combineTopDownData(msg.params, null), 61 }); 62 } 63 private jsCpuProfilerBottomUp(msg: any): void { 64 this.tabDataId = 0; 65 self.postMessage({ 66 id: msg.id, 67 action: msg.action, 68 results: this.combineBottomUpData(msg.params), 69 }); 70 } 71 private jsCpuProfilerStatistics(msg: any): void { 72 if (!this.dataCache.jsCallChain || this.dataCache.jsCallChain.length === 0) { 73 this.initCallChain(); 74 } 75 if (msg.params.data) { 76 this.chartData = msg.params.data; 77 this.leftNs = msg.params.leftNs; 78 this.rightNs = msg.params.rightNs; 79 } 80 if (msg.params.list) { 81 this.samples = convertJSON(msg.params.list) || []; 82 this.setChartDataType(); 83 self.postMessage({ 84 id: msg.id, 85 action: msg.action, 86 results: this.calStatistic(this.chartData, this.leftNs, this.rightNs), 87 }); 88 } else { 89 this.queryChartData(); 90 } 91 } 92 public clearAll(): void { 93 this.dataCache.clearAll(); 94 this.samples.length = 0; 95 this.chartData.length = 0; 96 } 97 98 private calStatistic( 99 chartData: Array<JsCpuProfilerChartFrame>, 100 leftNs: number | undefined, 101 rightNs: number | undefined 102 ): Map<SampleType, number> { 103 const typeMap = new Map<SampleType, number>(); 104 const samplesIdsArr: Array<any> = []; 105 const samplesIds = this.findSamplesIds(chartData, [], []); 106 for (const id of samplesIds) { 107 const sample = this.samples[id]; 108 if (!sample || sample.type === undefined) { 109 continue; 110 } 111 let sampleTotalTime = sample.dur; 112 if (leftNs && rightNs) { 113 // 不在框选范围内的不做处理 114 if (sample.startTime > rightNs || sample.endTime < leftNs) { 115 continue; 116 } 117 // 在框选范围内的被只框选到一部分的根据框选范围调整时间 118 const startTime = sample.startTime < leftNs ? leftNs : sample.startTime; 119 const endTime = sample.endTime > rightNs ? rightNs : sample.endTime; 120 sampleTotalTime = endTime - startTime; 121 } 122 123 if (!samplesIdsArr.includes(sample)) { 124 samplesIdsArr.push(sample); 125 let typeDur = typeMap.get(sample.type); 126 if (typeDur) { 127 typeMap.set(sample.type, typeDur + sampleTotalTime); 128 } else { 129 typeMap.set(sample.type, sampleTotalTime); 130 } 131 } 132 } 133 return typeMap; 134 } 135 136 private findSamplesIds( 137 chartData: Array<JsCpuProfilerChartFrame>, 138 lastLayerData: Array<JsCpuProfilerChartFrame>, 139 samplesIds: Array<number> 140 ): number[] { 141 for (const data of chartData) { 142 if (data.isSelect && data.selfTime > 0 && !lastLayerData.includes(data)) { 143 lastLayerData.push(data); 144 samplesIds.push(...data.samplesIds); 145 } else if (data.children.length > 0) { 146 this.findSamplesIds(data.children, lastLayerData, samplesIds); 147 } 148 } 149 return samplesIds; 150 } 151 152 private setChartDataType(): void { 153 for (let sample of this.samples) { 154 const chartData = this.dataCache.jsSymbolMap.get(sample.functionId); 155 if (chartData?.id === ROOT_ID) { 156 sample.type = SampleType.OTHER; 157 continue; 158 } 159 if (chartData) { 160 let type: string; 161 chartData.name = this.dataCache.dataDict.get(chartData.nameId) || LAMBDA_FUNCTION_NAME; 162 if (chartData.name) { 163 type = chartData.name.substring(chartData.name!.lastIndexOf('(') + 1, chartData.name!.lastIndexOf(')')); 164 switch (type) { 165 case 'NAPI': 166 sample.type = SampleType.NAPI; 167 break; 168 case 'ARKUI_ENGINE': 169 sample.type = SampleType.ARKUI_ENGINE; 170 break; 171 case 'BUILTIN': 172 sample.type = SampleType.BUILTIN; 173 break; 174 case 'GC': 175 sample.type = SampleType.GC; 176 break; 177 case 'AINT': 178 sample.type = SampleType.AINT; 179 break; 180 case 'CINT': 181 sample.type = SampleType.CINT; 182 break; 183 case 'AOT': 184 sample.type = SampleType.AOT; 185 break; 186 case 'RUNTIME': 187 sample.type = SampleType.RUNTIME; 188 break; 189 default: 190 if (chartData.name !== '(program)') { 191 sample.type = SampleType.OTHER; 192 } 193 break; 194 } 195 } 196 } 197 } 198 } 199 200 /** 201 * 建立callChain每个函数的联系,设置depth跟children 202 */ 203 private createCallChain(): void { 204 const jsSymbolMap = this.dataCache.jsSymbolMap; 205 for (const item of this.dataCache.jsCallChain!) { 206 jsSymbolMap.set(item.id, item); 207 } 208 } 209 210 /** 211 * 同级使用广度优先算法,非同级使用深度优先算法,遍历泳道图树结构所有数据, 212 * 将name,url,depth,parent相同的函数合并,构建成一个top down的树结构 213 * @param combineSample 泳道图第一层数据,非第一层为null 214 * @param parent 泳道图合并过的函数,第一层为null 215 * @returns 返回第一层树结构(第一层数据通过children囊括了所有的函数) 216 */ 217 private combineTopDownData( 218 combineSample: Array<JsCpuProfilerChartFrame> | null, 219 parent: JsCpuProfilerTabStruct | null 220 ): Array<JsCpuProfilerTabStruct> { 221 const sameSymbolMap = new Map<string, JsCpuProfilerTabStruct>(); 222 const currentLevelData = new Array<JsCpuProfilerTabStruct>(); 223 const chartArray = combineSample || parent?.chartFrameChildren; 224 if (!chartArray) { 225 return []; 226 } 227 // 同级广度优先 便于数据合并 228 for (const chartFrame of chartArray) { 229 if (!chartFrame.isSelect) { 230 continue; 231 } 232 // 该递归函数已经保证depth跟parent相同,固只需要判断name跟url相同即可 233 let symbolKey = chartFrame.nameId + ' ' + chartFrame.urlId; 234 // // lambda 表达式需要根据行列号区分是不是同一个函数 235 if (chartFrame.nameId === 0) { 236 symbolKey += ' ' + chartFrame.line + ' ' + chartFrame.column; 237 } 238 let tabCallFrame: JsCpuProfilerTabStruct; 239 if (sameSymbolMap.has(symbolKey)) { 240 tabCallFrame = sameSymbolMap.get(symbolKey)!; 241 tabCallFrame.totalTime += chartFrame.totalTime; 242 tabCallFrame.selfTime += chartFrame.selfTime; 243 } else { 244 tabCallFrame = this.chartFrameToTabStruct(chartFrame); 245 sameSymbolMap.set(symbolKey, tabCallFrame); 246 currentLevelData.push(tabCallFrame); 247 if (parent) { 248 parent.children.push(tabCallFrame); 249 } 250 } 251 tabCallFrame.chartFrameChildren?.push(...chartFrame.children); 252 } 253 // 非同级深度优先,便于设置children,同时保证下一级函数depth跟parent都相同 254 for (const data of currentLevelData) { 255 this.combineTopDownData(null, data); 256 data.chartFrameChildren = []; 257 } 258 if (combineSample) { 259 // 第一层为返回给Tab页的数据 260 return currentLevelData; 261 } else { 262 return []; 263 } 264 } 265 266 /** 267 * copy整体调用链,从栈顶函数一直copy到栈底函数, 268 * 给Parent设置selfTime,totalTime设置为children的selfTime,totalTime 269 * */ 270 private copyParent(frame: JsCpuProfilerChartFrame, chartFrame: JsCpuProfilerChartFrame): void { 271 frame.children = []; 272 if (chartFrame.parent) { 273 const copyParent = this.cloneChartFrame(chartFrame.parent); 274 copyParent.selfTime = frame.selfTime; 275 copyParent.totalTime = frame.totalTime; 276 frame.children.push(copyParent); 277 this.copyParent(copyParent, chartFrame.parent); 278 } 279 } 280 281 /** 282 * 步骤1:框选/点选的chart树逆序 283 * 步骤2:将name,url,parent,层级相同的函数合并 284 * @param chartTreeArray ui传递的树结构 285 * @returns 合并的Array<JsCpuProfilerChartFrame>树结构 286 */ 287 private combineBottomUpData(chartTreeArray: Array<JsCpuProfilerChartFrame>): Array<JsCpuProfilerTabStruct> { 288 const reverseTreeArray = new Array<JsCpuProfilerChartFrame>(); 289 // 将树结构逆序,parent变成children 290 this.reverseChartFrameTree(chartTreeArray, reverseTreeArray); 291 // 将逆序的树结构合并返回 292 return this.combineTopDownData(reverseTreeArray, null); 293 } 294 295 /** 296 * 树结构逆序 297 * @param chartTreeArray 正序的树结构 298 * @param reverseTreeArray 逆序的树结构 299 */ 300 private reverseChartFrameTree( 301 chartTreeArray: Array<JsCpuProfilerChartFrame>, 302 reverseTreeArray: Array<JsCpuProfilerChartFrame> 303 ): void { 304 const that = this; 305 function recursionTree(chartFrame: JsCpuProfilerChartFrame) { 306 // isSelect为框选/点选范围内的函数,其他都不需要处理 307 if (!chartFrame.isSelect) { 308 return; 309 } 310 //界面第一层只显示栈顶函数,只有栈顶函数的selfTime > 0 311 if (chartFrame.selfTime > 0) { 312 const copyFrame = that.cloneChartFrame(chartFrame); 313 // 每个栈顶函数的parent的时间为栈顶函数的时间 314 copyFrame.selfTime = chartFrame.selfTime; 315 copyFrame.totalTime = chartFrame.totalTime; 316 reverseTreeArray.push(copyFrame); 317 // 递归处理parent的的totalTime selfTime 318 that.copyParent(copyFrame, chartFrame); 319 } 320 321 if (chartFrame.children.length > 0) { 322 for (const children of chartFrame.children) { 323 children.parent = chartFrame; 324 recursionTree(children); 325 } 326 } 327 } 328 329 //递归树结构 330 for (const chartFrame of chartTreeArray) { 331 recursionTree(chartFrame); 332 } 333 } 334 /** 335 * 将泳道图数据JsCpuProfilerChartFrame转化为JsCpuProfilerTabStruct 作为绘制Ta页的结构 336 * @param chartCallChain 泳道图函数信息 337 * @returns JsCpuProfilerTabStruct 338 */ 339 private chartFrameToTabStruct(chartCallChain: JsCpuProfilerChartFrame): JsCpuProfilerTabStruct { 340 const tabData = new JsCpuProfilerTabStruct( 341 chartCallChain.nameId, 342 chartCallChain.selfTime, 343 chartCallChain.totalTime, 344 chartCallChain.depth, 345 chartCallChain.urlId, 346 chartCallChain.line, 347 chartCallChain.column, 348 chartCallChain.scriptName, 349 this.tabDataId++ 350 ); 351 return tabData; 352 } 353 354 private cloneChartFrame(frame: JsCpuProfilerChartFrame): JsCpuProfilerChartFrame { 355 const copyFrame = new JsCpuProfilerChartFrame( 356 frame.id, 357 frame.nameId, 358 frame.startTime, 359 frame.endTime, 360 frame.totalTime, 361 frame.depth, 362 frame.urlId, 363 frame.line, 364 frame.column 365 ); 366 copyFrame.parentId = frame.parentId; 367 copyFrame.isSelect = true; 368 copyFrame.scriptName = frame.scriptName; 369 return copyFrame; 370 } 371 372 private initCallChain(): void { 373 const sql = `SELECT function_id AS id, 374 function_index AS nameId, 375 script_id AS scriptId, 376 url_index AS urlId, 377 line_number as line, 378 column_number as column, 379 hit_count AS hitCount, 380 children AS childrenString, 381 parent_id AS parentId 382 FROM 383 js_cpu_profiler_node`; 384 this.queryData(this.currentEventId!, 'jsCpuProfiler-call-chain', sql, {}); 385 } 386 387 private queryChartData(): void { 388 const sql = `SELECT id, 389 function_id AS functionId, 390 start_time - start_ts AS startTime, 391 end_time - start_ts AS endTime, 392 dur 393 FROM js_cpu_profiler_sample,trace_range`; 394 this.queryData(this.currentEventId!, 'jsCpuProfiler-statistics', sql, {}); 395 } 396} 397 398export class JsCpuProfilerSample { 399 id: number = 0; 400 functionId: number = 0; 401 functionIndex: number = 0; 402 startTime: number = 0; 403 endTime: number = 0; 404 dur: number = 0; 405 type: SampleType = SampleType.OTHER; 406 stack?: Array<JsProfilerSymbol>; 407 cpuProfilerData?: JsCpuProfilerSample; 408} 409 410export enum SampleType { 411 OTHER = 'OTHER', 412 NAPI = 'NAPI', 413 ARKUI_ENGINE = 'ARKUI_ENGINE', 414 BUILTIN = 'BUILTIN', 415 GC = 'GC', 416 AINT = 'AINT', 417 CINT = 'CINT', 418 AOT = 'AOT', 419 RUNTIME = 'RUNTIME', 420} 421