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