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