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