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