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 { SpSystemTrace } from '../SpSystemTrace'; 17import { TraceRow } from '../trace/base/TraceRow'; 18import { renders } from '../../database/ui-worker/ProcedureWorker'; 19import { SampleStruct, SampleRender } from '../../database/ui-worker/ProcedureWorkerBpftrace'; 20import { queryStartTime } from '../../database/sql/SqlLite.sql'; 21import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil'; 22 23export class SpUserFileChart { 24 private trace: SpSystemTrace; 25 private currentFile: undefined; 26 static userPluginData: []; 27 28 constructor(trace: SpSystemTrace) { 29 this.trace = trace; 30 } 31 32 async init(file: File | null) { 33 if (!file) { 34 let startTime = await queryStartTime(); 35 //@ts-ignore 36 let folder = await this.initSample(startTime[0].start_ts, file); 37 this.trace.rowsEL?.appendChild(folder); 38 } else { 39 let folder = await this.initSample(-1, file); 40 this.trace.rowsEL?.appendChild(folder); 41 } 42 } 43 44 async initSample(start_ts: number, file: unknown): Promise<TraceRow<SampleStruct>> { 45 let traceRow = TraceRow.skeleton<SampleStruct>(); 46 traceRow.rowId = 'userPlugin'; 47 traceRow.index = 0; 48 traceRow.rowType = TraceRow.ROW_TYPE_SAMPLE; 49 traceRow.rowParentId = ''; 50 traceRow.folder = false; 51 traceRow.style.height = '40px'; 52 traceRow.name = 'UserPluginRow'; 53 traceRow.selectChangeHandler = this.trace.selectChangeHandler; 54 traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; 55 traceRow.findHoverStruct = () => { 56 SampleStruct.hoverSampleStruct = traceRow.getHoverStruct(); 57 }; 58 //添加上传按钮 59 traceRow.addRowSampleUpload(); 60 this.addTraceRowEventListener(traceRow, start_ts); 61 //单独上传 62 if (file) { 63 this.getJsonData(file).then((res: unknown) => { 64 //@ts-ignore 65 const propertyData = res.data; 66 //@ts-ignore 67 const treeNodes = res.relation.children || [res.relation.RS.children[0]]; 68 SpUserFileChart.userPluginData = treeNodes.data[0] || []; 69 const uniqueProperty = this.removeDuplicates(propertyData); 70 const flattenTreeArray = this.getFlattenTreeData(treeNodes); 71 //@ts-ignore 72 const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20; 73 const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty); 74 //@ts-ignore 75 const startTS = flattenTreeArray[0].property[0].begin; 76 traceRow.supplier = () => 77 new Promise((resolve): void => { 78 //@ts-ignore 79 resolve(sampleProperty) 80 }) 81 traceRow.onThreadHandler = (useCache) => { 82 let context: CanvasRenderingContext2D; 83 if (traceRow.currentContext) { 84 context = traceRow.currentContext; 85 } else { 86 context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 87 } 88 traceRow.canvasSave(context); 89 (renders.sample as SampleRender).renderMainThread( 90 { 91 context: context, 92 useCache: useCache, 93 type: 'bpftrace', 94 start_ts: startTS, 95 uniqueProperty: uniqueProperty, 96 //@ts-ignore 97 flattenTreeArray: flattenTreeArray 98 }, 99 traceRow 100 ); 101 traceRow.canvasRestore(context) 102 }; 103 traceRow.style.height = `${height}px`; 104 }) 105 } else { 106 traceRow.supplier = () => 107 new Promise((resolve): void => { 108 resolve([]); 109 }); 110 traceRow.onThreadHandler = (useCache) => { 111 let context: CanvasRenderingContext2D; 112 if (traceRow.currentContext) { 113 context = traceRow.currentContext; 114 } else { 115 context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 116 } 117 traceRow.canvasSave(context); 118 (renders.sample as SampleRender).renderMainThread( 119 { 120 context: context, 121 useCache: useCache, 122 type: 'bpftrace', 123 start_ts: 0, 124 uniqueProperty: [], 125 flattenTreeArray: [], 126 }, 127 traceRow 128 ); 129 traceRow.canvasRestore(context); 130 }; 131 } 132 return traceRow; 133 } 134 135 /** 136 * 监听文件上传事件 137 * @param row 138 * @param start_ts 139 */ 140 141 //@ts-ignore 142 addTraceRowEventListener(row: TraceRow<unknown>, start_ts: number) { 143 row.uploadEl?.addEventListener('sample-file-change', (e: unknown) => { 144 this.getJsonData(e).then((res: unknown) => { 145 this.resetChartData(row); 146 if (!res) { 147 row.supplier = () => 148 new Promise((resolve): void => { 149 resolve([]) 150 }) 151 this.trace.traceSheetEL?.setMode('hidden'); 152 row.style.height = '40px'; 153 row.name = 'UserPluginRow' 154 } else { 155 //@ts-ignore 156 const propertyData = res.data; 157 //@ts-ignore 158 SpUserFileChart.userPluginData = res.data[0]; 159 //@ts-ignore 160 const treeNodes = res.relation.children || [res.relation.RS.children[0]]; 161 const uniqueProperty = this.removeDuplicates(propertyData); 162 const flattenTreeArray = this.getFlattenTreeData(treeNodes); 163 //@ts-ignore 164 const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20; 165 const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty); 166 //@ts-ignore 167 const startTS = start_ts > 0 ? start_ts : flattenTreeArray[0].property[0].begin; 168 row.supplier = () => 169 new Promise((resolve): void => { 170 resolve(sampleProperty) 171 }) 172 row.onThreadHandler = (useCache) => { 173 let context: CanvasRenderingContext2D; 174 if (row.currentContext) { 175 context = row.currentContext; 176 } else { 177 context = row.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; 178 } 179 row.canvasSave(context); 180 (renders.sample as SampleRender).renderMainThread( 181 { 182 context: context, 183 useCache: useCache, 184 type: 'bpftrace', 185 start_ts: startTS, 186 uniqueProperty: uniqueProperty, 187 //@ts-ignore 188 flattenTreeArray: flattenTreeArray 189 }, 190 row 191 ); 192 row.canvasRestore(context) 193 }; 194 //@ts-ignore 195 row.name = `${res.relation.detail}(${res.relation.function_name})`; 196 row.style.height = `${height}px`; 197 } 198 this.trace.refreshCanvas(false) 199 }) 200 }) 201 } 202 203 /** 204 * 清空缓存 205 * @param row 206 */ 207 //@ts-ignore 208 resetChartData(row: TraceRow<unknown>) { 209 row.dataList = []; 210 row.dataList2 = []; 211 row.dataListCache = []; 212 row.isComplete = false; 213 } 214 215 /** 216 * 获取上传的文件内容 转为json格式 217 * @param file 218 * @returns 219 */ 220 getJsonData(file: unknown): Promise<unknown> { 221 return new Promise((resolve, reject) => { 222 let reader = new FileReader(); 223 //@ts-ignore 224 reader.readAsText(file.detail || file); 225 reader.onloadend = (e: unknown) => { 226 //@ts-ignore 227 const fileContent = e.target?.result; 228 if (fileContent === this.currentFile) { 229 this.currentFile = undefined; 230 resolve(false) 231 } else { 232 this.currentFile = fileContent; 233 } 234 try { 235 resolve(JSON.parse(fileContent)); 236 document.dispatchEvent( 237 new CustomEvent('file-correct') 238 ) 239 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 240 event: 'bpftrace', 241 action: 'bpftrace', 242 }); 243 } catch (error) { 244 document.dispatchEvent( 245 new CustomEvent('file-error') 246 ) 247 } 248 } 249 }) 250 } 251 252 /** 253 * 树结构扁平化 254 * @param treeData 255 * @param depth 256 * @param parentName 257 * @returns 258 */ 259 getFlattenTreeData(treeData: Array<unknown>, depth: number = 0, parentName: string = ''): Array<unknown> { 260 let result: Array<object> = []; 261 treeData.forEach(node => { 262 //@ts-ignore 263 const name: string = node['function_name']; 264 const newNode: unknown = {}; 265 if (name && name.indexOf('unknown') > -1) { 266 //@ts-ignore 267 newNode['children'] = this.getUnknownAllChildrenNames(node); 268 } 269 //@ts-ignore 270 newNode['detail'] = node['detail']; 271 //@ts-ignore 272 newNode['depth'] = depth; 273 //@ts-ignore 274 newNode['name'] = name; 275 //@ts-ignore 276 newNode['parentName'] = parentName; 277 //@ts-ignore 278 newNode['property'] = []; 279 //@ts-ignore 280 result.push(newNode); 281 //@ts-ignore 282 if (node.children) { 283 //@ts-ignore 284 result = result.concat(this.getFlattenTreeData(node.children, depth + 1, node['function_name'])) 285 } 286 }) 287 return result 288 } 289 290 /** 291 * 查找重复项 292 * @param propertyData 293 * @returns 294 */ 295 removeDuplicates(propertyData: Array<unknown>): Array<unknown> { 296 const result: Array<unknown> = []; 297 propertyData.forEach(propertyGroup => { 298 const groups: Array<unknown> = []; 299 //@ts-ignore 300 propertyGroup.forEach((property: unknown) => { 301 //@ts-ignore 302 const duplicateObj = groups.find(group => group['func_name'] === property['func_name']); 303 if (duplicateObj) { 304 //@ts-ignore 305 duplicateObj['begin'] = Math.min(duplicateObj['begin'], property['begin']); 306 //@ts-ignore 307 duplicateObj['end'] = Math.max(duplicateObj['end'], property['end']); 308 } else { 309 groups.push(property) 310 } 311 }) 312 result.push(groups); 313 }) 314 return result 315 } 316 317 /** 318 * 关系树赋值 319 * @param relationData 320 * @param propertyData 321 */ 322 setRelationDataProperty(relationData: Array<unknown>, propertyData: Array<unknown>): Array<unknown> { 323 const sampleProperty = relationData; 324 //数组每一项进行比对 325 propertyData.forEach(propertyGroup => { 326 //@ts-ignore 327 propertyGroup.forEach((property: unknown) => { 328 //@ts-ignore 329 const relation = sampleProperty.find(relation => relation['name'] === property['func_name']); 330 //property属性存储每帧数据 331 //@ts-ignore 332 relation?.property.push({ 333 //@ts-ignore 334 name: property['func_name'], 335 //@ts-ignore 336 detail: relation['detail'], 337 //@ts-ignore 338 end: property['end'], 339 //@ts-ignore 340 begin: property['begin'], 341 //@ts-ignore 342 depth: relation['depth'], 343 //@ts-ignore 344 instructions: property['instructions'], 345 //@ts-ignore 346 cycles: property['cycles'] 347 }) 348 }) 349 }) 350 351 //获取所有名字为unknown的数据 352 //@ts-ignore 353 const unknownRelation = sampleProperty.filter(relation => relation['name'].indexOf('unknown') > -1); 354 //二维数组 用于存放unknown下所有子节点的数据 355 let twoDimensionalArray: Array<unknown> = []; 356 let result: Array<unknown> = []; 357 unknownRelation.forEach(unknownItem => { 358 result = []; 359 twoDimensionalArray = []; 360 //@ts-ignore 361 const children = unknownItem['children']; 362 //先获取到unknwon节点下每个子节点的property 363 Object.keys(children).forEach(key => { 364 //@ts-ignore 365 unknownItem.children[key] = (sampleProperty.find(relation => relation['name'] === key)).property; 366 }) 367 //将每个子节点的property加到二维数组中 368 Object.values(children).forEach((value: unknown) => { 369 //@ts-ignore 370 if (value.length > 0) { 371 twoDimensionalArray.push(value) 372 } 373 }) 374 if (twoDimensionalArray.length > 0) { 375 //取每列的最大值和最小值 376 //@ts-ignore 377 for (let i = 0; i < twoDimensionalArray[0].length; i++) { 378 const data = { 379 //@ts-ignore 380 name: unknownItem['name'], 381 //@ts-ignore 382 detail: unknownItem['detail'], 383 //@ts-ignore 384 begin: (twoDimensionalArray[0][i]).begin, 385 end: 0, 386 //@ts-ignore 387 depth: unknownItem['depth'] 388 } 389 for (let j = 0; j < twoDimensionalArray.length; j++) { 390 //@ts-ignore 391 data['end'] = Math.max((twoDimensionalArray[j][i])['end'], data['end']); 392 //@ts-ignore 393 data['begin'] = Math.min((twoDimensionalArray[j][i])['begin'], data['begin']); 394 } 395 result.push(data); 396 } 397 //@ts-ignore 398 unknownItem.property = result; 399 } 400 }) 401 return sampleProperty; 402 } 403 404 /** 405 * 获取unknown节点下所有孩子节点的名称 406 * @param node 407 * @param names 408 */ 409 getUnknownAllChildrenNames(node: unknown, names: unknown = {}): object { 410 //@ts-ignore 411 if (node['children']) { 412 //@ts-ignore 413 node['children'].forEach((child: unknown) => { 414 //@ts-ignore 415 if (child['function_name'].indexOf('unknown') < 0) { 416 //@ts-ignore 417 names[child['function_name']] = [] 418 } else { 419 this.getUnknownAllChildrenNames(child, names) 420 } 421 }) 422 } 423 //@ts-ignore 424 return names 425 } 426} 427