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 { SpSystemTrace } from '../SpSystemTrace'; 16import { TraceRow } from '../trace/base/TraceRow'; 17import { renders } from '../../database/ui-worker/ProcedureWorker'; 18import { type EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; 19import { type HeapTimelineRender, HeapTimelineStruct } from '../../database/ui-worker/ProcedureWorkerHeapTimeline'; 20import { HeapDataInterface, type ParseListener } from '../../../js-heap/HeapDataInterface'; 21import { LoadDatabase } from '../../../js-heap/LoadDatabase'; 22import { type FileInfo } from '../../../js-heap/model/UiStruct'; 23import { type HeapSnapshotRender, HeapSnapshotStruct } from '../../database/ui-worker/ProcedureWorkerHeapSnapshot'; 24import { Utils } from '../trace/base/Utils'; 25import { type JsCpuProfilerChartFrame } from '../../bean/JsStruct'; 26import { type JsCpuProfilerRender, JsCpuProfilerStruct } from '../../database/ui-worker/ProcedureWorkerCpuProfiler'; 27import { ns2s } from '../../database/ui-worker/ProcedureWorkerCommon'; 28import { cpuProfilerDataSender } from '../../database/data-trafic/ArkTsSender'; 29import { queryJsCpuProfilerConfig, queryJsCpuProfilerData } from '../../database/sql/Cpu.sql'; 30import { queryJsMemoryData } from '../../database/sql/Memory.sql'; 31import { type HeapSample } from '../../../js-heap/model/DatabaseStruct'; 32import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil'; 33 34const TYPE_SNAPSHOT = 0; 35const TYPE_TIMELINE = 1; 36const LAMBDA_FUNCTION_NAME = '(anonymous)'; 37export class SpArkTsChart implements ParseListener { 38 private trace: SpSystemTrace; // @ts-ignore 39 private folderRow: TraceRow<unknown> | undefined; 40 private jsCpuProfilerRow: TraceRow<JsCpuProfilerStruct> | undefined; 41 private heapTimelineRow: TraceRow<HeapTimelineStruct> | undefined; 42 private heapSnapshotRow: TraceRow<HeapSnapshotStruct> | undefined; 43 private loadJsDatabase: LoadDatabase; 44 private allCombineDataMap = new Map<number, JsCpuProfilerChartFrame>(); 45 private process: string = ''; 46 47 constructor(trace: SpSystemTrace) { 48 this.trace = trace; 49 this.loadJsDatabase = LoadDatabase.getInstance(); 50 } 51 52 public get chartFrameMap(): Map<number, JsCpuProfilerChartFrame> { 53 return this.allCombineDataMap; 54 } 55 56 private cpuProfilerSupplierFrame(): void { 57 // @ts-ignore 58 this.jsCpuProfilerRow!.supplierFrame = (): Promise<Array<unknown>> => { 59 return cpuProfilerDataSender(this.jsCpuProfilerRow!).then((res: unknown) => { 60 // @ts-ignore 61 let maxHeight = res.maxDepth * 20; 62 this.jsCpuProfilerRow!.style.height = `${maxHeight}px`; // @ts-ignore 63 if (res.dataList.length > 0) { 64 this.allCombineDataMap = new Map<number, JsCpuProfilerChartFrame>(); // @ts-ignore 65 for (let data of res.dataList) { 66 this.allCombineDataMap.set(data.id, data); 67 SpSystemTrace.jsProfilerMap.set(data.id, data); 68 } // @ts-ignore 69 res.dataList.forEach((data: unknown) => { 70 // @ts-ignore 71 data.children = []; // @ts-ignore 72 if (data.childrenIds.length > 0) { 73 // @ts-ignore 74 for (let id of data.childrenIds) { 75 let child = SpSystemTrace.jsProfilerMap.get(Number(id)); // @ts-ignore 76 data.children.push(child); 77 } 78 } // @ts-ignore 79 data.name = SpSystemTrace.DATA_DICT.get(data.nameId) || LAMBDA_FUNCTION_NAME; // @ts-ignore 80 data.url = SpSystemTrace.DATA_DICT.get(data.urlId) || 'unknown'; // @ts-ignore 81 if (data.url && data.url !== 'unknown') { 82 // @ts-ignore 83 let dirs = data.url.split('/'); // @ts-ignore 84 data.scriptName = dirs.pop() || ''; 85 } 86 }); 87 } // @ts-ignore 88 return res.dataList; 89 }); 90 }; 91 } 92 93 private folderThreadHandler(): void { 94 this.folderRow!.onThreadHandler = (useCache): void => { 95 this.folderRow!.canvasSave(this.trace.canvasPanelCtx!); 96 if (this.folderRow!.expansion) { 97 // @ts-ignore 98 this.trace.canvasPanelCtx?.clearRect(0, 0, this.folderRow!.frame.width, this.folderRow!.frame.height); 99 } else { 100 (renders.empty as EmptyRender).renderMainThread( 101 { 102 context: this.trace.canvasPanelCtx, 103 useCache: useCache, 104 type: '', 105 }, 106 this.folderRow! 107 ); 108 } 109 this.folderRow!.canvasRestore(this.trace.canvasPanelCtx!, this.trace); 110 }; 111 } 112 113 public async initFolder(): Promise<void> { 114 let jsConfig = await queryJsCpuProfilerConfig(); 115 let jsCpu = await queryJsCpuProfilerData(); 116 let jsMemory = await queryJsMemoryData(); 117 if (jsMemory.length > 0 || jsCpu.length > 0) { 118 this.folderRow = TraceRow.skeleton(); 119 //@ts-ignore 120 this.process = jsConfig[0].pid; 121 this.folderRow.rowId = this.process; 122 this.folderRow.rowType = TraceRow.ROW_TYPE_ARK_TS; 123 this.folderRow.style.height = '40px'; 124 this.folderRow.rowParentId = ''; 125 this.folderRow.folder = true; 126 this.folderRow.name = `Ark Ts ${this.process}`; 127 this.folderRow.addTemplateTypes('ArkTs'); 128 this.folderRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 129 this.folderRow.selectChangeHandler = this.trace.selectChangeHandler; 130 this.folderRow.supplierFrame = (): Promise<Array<unknown>> => 131 new Promise<Array<unknown>>((resolve) => resolve([])); 132 this.folderThreadHandler(); 133 this.trace.rowsEL?.appendChild(this.folderRow); //@ts-ignore 134 if (this.folderRow && jsConfig[0].type !== -1 && jsMemory.length > 0) { 135 this.folderRow.addTemplateTypes('Memory'); 136 if ( 137 //@ts-ignore 138 jsConfig[0].type === TYPE_SNAPSHOT 139 ) { 140 // snapshot 141 await this.initSnapshotChart(); 142 } else if ( 143 //@ts-ignore 144 jsConfig[0].type === TYPE_TIMELINE 145 ) { 146 // timeline 147 await this.initTimelineChart(); 148 } 149 } 150 //@ts-ignore 151 if (this.folderRow && jsConfig[0].enableCpuProfiler === 1 && jsCpu.length > 0) { 152 await this.initJsCpuChart(); 153 } 154 if ((this.heapSnapshotRow || this.heapTimelineRow) && jsMemory.length > 0) { 155 await this.loadJsDatabase.loadDatabase(this); 156 } 157 if (this.jsCpuProfilerRow && jsCpu.length > 0) { 158 this.cpuProfilerSupplierFrame(); 159 } 160 // 统计arkTs插件 161 let requsetBody = { 162 eventData:{ 163 plugin:['arkts-plugin'] 164 } 165 }; 166 SpStatisticsHttpUtil.recordPluginUsage(requsetBody); 167 } 168 } 169 170 private async initTimelineChart(): Promise<void> { 171 this.heapTimelineRow = TraceRow.skeleton<HeapTimelineStruct>(); 172 this.heapTimelineRow.rowParentId = this.process; 173 this.heapTimelineRow.rowHidden = !this.folderRow!.expansion; 174 this.heapTimelineRow.style.height = '40px'; 175 this.heapTimelineRow.name = 'Heaptimeline'; 176 this.heapTimelineRow.folder = false; 177 this.heapTimelineRow.rowType = TraceRow.ROW_TYPE_HEAP_TIMELINE; 178 this.heapTimelineRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 179 this.heapTimelineRow.selectChangeHandler = this.trace.selectChangeHandler; 180 this.heapTimelineRow.setAttribute('children', ''); 181 this.heapTimelineRow!.focusHandler = (): void => { 182 this.trace?.displayTip( 183 this.heapTimelineRow!, 184 HeapTimelineStruct.hoverHeapTimelineStruct, 185 `<span>Size: ${Utils.getBinaryByteWithUnit(HeapTimelineStruct.hoverHeapTimelineStruct?.size || 0)}</span>` 186 ); 187 }; 188 this.heapTimelineRow!.findHoverStruct = (): void => { 189 HeapTimelineStruct.hoverHeapTimelineStruct = this.heapTimelineRow!.getHoverStruct(); 190 }; 191 this.folderRow!.addChildTraceRow(this.heapTimelineRow!); 192 } 193 194 private async initSnapshotChart(): Promise<void> { 195 this.heapSnapshotRow = TraceRow.skeleton<HeapSnapshotStruct>(); 196 this.heapSnapshotRow.rowParentId = this.process; 197 this.heapSnapshotRow.rowHidden = !this.folderRow!.expansion; 198 this.heapSnapshotRow.style.height = '40px'; 199 this.heapSnapshotRow.name = 'Heapsnapshot'; 200 this.heapSnapshotRow.rowId = 'heapsnapshot'; 201 this.heapSnapshotRow.folder = false; 202 203 this.heapSnapshotRow.rowType = TraceRow.ROW_TYPE_HEAP_SNAPSHOT; 204 this.heapSnapshotRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 205 this.heapSnapshotRow.selectChangeHandler = this.trace.selectChangeHandler; 206 this.heapSnapshotRow.setAttribute('children', ''); 207 this.heapSnapshotRow!.focusHandler = (): void => { 208 this.trace?.displayTip( 209 this.heapSnapshotRow!, 210 HeapSnapshotStruct.hoverSnapshotStruct, 211 `<span>Name: ${HeapSnapshotStruct.hoverSnapshotStruct?.name || ''}</span> 212 <span>Size: ${Utils.getBinaryByteWithUnit(HeapSnapshotStruct.hoverSnapshotStruct?.size || 0)}</span>` 213 ); 214 }; 215 this.heapSnapshotRow!.findHoverStruct = (): void => { 216 HeapSnapshotStruct.hoverSnapshotStruct = this.heapSnapshotRow!.getHoverStruct(); 217 }; 218 this.folderRow!.addChildTraceRow(this.heapSnapshotRow); 219 } 220 221 private heapLineThreadHandler(samples: HeapSample[]): void { 222 this.heapTimelineRow!.onThreadHandler = (useCache): void => { 223 let context: CanvasRenderingContext2D; 224 if (this.heapTimelineRow?.currentContext) { 225 context = this.heapTimelineRow!.currentContext; 226 } else { 227 context = this.heapTimelineRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 228 } 229 this.heapTimelineRow!.canvasSave(context); 230 (renders['heap-timeline'] as HeapTimelineRender).renderMainThread( 231 { 232 context: context, 233 useCache: useCache, 234 type: 'heap-timeline', 235 samples: samples, 236 }, 237 this.heapTimelineRow! 238 ); 239 this.heapTimelineRow!.canvasRestore(context, this.trace); 240 }; 241 } 242 243 private heapSnapshotThreadHandler(): void { 244 this.heapSnapshotRow!.onThreadHandler = (useCache): void => { 245 let context: CanvasRenderingContext2D; 246 if (this.heapSnapshotRow?.currentContext) { 247 context = this.heapSnapshotRow!.currentContext; 248 } else { 249 context = this.heapSnapshotRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 250 } 251 this.heapSnapshotRow!.canvasSave(context); 252 (renders['heap-snapshot'] as HeapSnapshotRender).renderMainThread( 253 { 254 context: context, 255 useCache: useCache, 256 type: 'heap-snapshot', 257 }, 258 this.heapSnapshotRow! 259 ); 260 this.heapSnapshotRow!.canvasRestore(context, this.trace); 261 }; 262 } 263 264 public async parseDone(fileModule: Array<FileInfo>): Promise<void> { 265 if (fileModule.length > 0) { 266 let heapFile = HeapDataInterface.getInstance().getFileStructs(); 267 let file = heapFile[0]; 268 this.trace.snapshotFile = file; 269 if (file.type === TYPE_TIMELINE) { 270 let samples = HeapDataInterface.getInstance().getSamples(file.id); 271 this.heapTimelineRow!.rowId = `heaptimeline${file.id}`; // @ts-ignore 272 this.heapTimelineRow!.supplierFrame = (): Promise<unknown> => 273 new Promise<unknown>((resolve) => resolve(samples)); 274 this.heapLineThreadHandler(samples); 275 } else if (file.type === TYPE_SNAPSHOT) { 276 // @ts-ignore 277 this.heapSnapshotRow!.supplierFrame = (): Promise<Array<unknown>> => 278 new Promise<Array<unknown>>((resolve) => resolve(heapFile)); 279 this.heapSnapshotThreadHandler(); 280 } 281 } 282 } 283 284 private initJsCpuChart = async (): Promise<void> => { 285 this.jsCpuProfilerRow = TraceRow.skeleton<JsCpuProfilerStruct>(); 286 this.jsCpuProfilerRow.rowParentId = this.process; 287 this.jsCpuProfilerRow.rowHidden = !this.folderRow!.expansion; 288 this.jsCpuProfilerRow.name = 'CpuProfiler'; 289 this.jsCpuProfilerRow.rowId = 'JsCpuProfiler'; 290 this.jsCpuProfilerRow.folder = false; 291 this.jsCpuProfilerRow.rowType = TraceRow.ROW_TYPE_JS_CPU_PROFILER; 292 this.jsCpuProfilerRow!.style.height = '40px'; 293 this.jsCpuProfilerRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 294 this.jsCpuProfilerRow.selectChangeHandler = this.trace.selectChangeHandler; 295 this.jsCpuProfilerRow.setAttribute('children', ''); 296 this.jsCpuProfilerRow.focusHandler = (): void => { 297 this.trace?.displayTip( 298 this.jsCpuProfilerRow!, 299 JsCpuProfilerStruct.hoverJsCpuProfilerStruct, 300 `<span style='font-weight: bold;'>Name: </span> 301 <span>${JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.name || ''}</span><br> 302 <span style='font-weight: bold;'>Self Time: </span> 303 <span>${ns2s(JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.selfTime || 0)}</span><br> 304 <span style='font-weight: bold;'>Total Time: </span> 305 <span>${ns2s(JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.totalTime || 0)}</span><br> 306 <span style='font-weight: bold;'>Url: </span> 307 <span>${JsCpuProfilerStruct.hoverJsCpuProfilerStruct?.url || 0}</span>` 308 ); 309 }; 310 this.jsCpuProfilerRow!.findHoverStruct = (): void => { 311 JsCpuProfilerStruct.hoverJsCpuProfilerStruct = this.jsCpuProfilerRow!.getHoverStruct(); 312 }; 313 this.jsCpuProfilerRow.onThreadHandler = (useCache): void => { 314 let context: CanvasRenderingContext2D; 315 if (this.jsCpuProfilerRow?.currentContext) { 316 context = this.jsCpuProfilerRow!.currentContext; 317 } else { 318 context = this.jsCpuProfilerRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 319 } 320 this.jsCpuProfilerRow!.canvasSave(context); 321 (renders['js-cpu-profiler'] as JsCpuProfilerRender).renderMainThread( 322 { 323 context: context, 324 useCache: useCache, 325 type: 'js-cpu-profiler', 326 }, 327 this.jsCpuProfilerRow! 328 ); 329 this.jsCpuProfilerRow!.canvasRestore(context, this.trace); 330 }; 331 this.folderRow!.addChildTraceRow(this.jsCpuProfilerRow); 332 }; 333} 334