/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { SpSystemTrace } from '../SpSystemTrace'; import { TraceRow } from '../trace/base/TraceRow'; import { renders } from '../../database/ui-worker/ProcedureWorker'; import { SampleStruct, SampleRender } from '../../database/ui-worker/ProcedureWorkerBpftrace'; import { queryStartTime } from '../../database/sql/SqlLite.sql'; import { SpStatisticsHttpUtil } from '../../../statistics/util/SpStatisticsHttpUtil'; export class SpUserFileChart { private trace: SpSystemTrace; private currentFile: undefined; static userPluginData: []; constructor(trace: SpSystemTrace) { this.trace = trace; } async init(file: File | null) { if (!file) { let startTime = await queryStartTime(); //@ts-ignore let folder = await this.initSample(startTime[0].start_ts, file); this.trace.rowsEL?.appendChild(folder); } else { let folder = await this.initSample(-1, file); this.trace.rowsEL?.appendChild(folder); } } async initSample(start_ts: number, file: unknown): Promise> { let traceRow = TraceRow.skeleton(); traceRow.rowId = 'userPlugin'; traceRow.index = 0; traceRow.rowType = TraceRow.ROW_TYPE_SAMPLE; traceRow.rowParentId = ''; traceRow.folder = false; traceRow.style.height = '40px'; traceRow.name = 'UserPluginRow'; traceRow.selectChangeHandler = this.trace.selectChangeHandler; traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; traceRow.findHoverStruct = () => { SampleStruct.hoverSampleStruct = traceRow.getHoverStruct(); }; //添加上传按钮 traceRow.addRowSampleUpload(); this.addTraceRowEventListener(traceRow, start_ts); //单独上传 if (file) { this.getJsonData(file).then((res: unknown) => { //@ts-ignore const propertyData = res.data; //@ts-ignore const treeNodes = res.relation.children || [res.relation.RS.children[0]]; SpUserFileChart.userPluginData = treeNodes.data[0] || []; const uniqueProperty = this.removeDuplicates(propertyData); const flattenTreeArray = this.getFlattenTreeData(treeNodes); //@ts-ignore const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20; const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty); //@ts-ignore const startTS = flattenTreeArray[0].property[0].begin; traceRow.supplier = () => new Promise((resolve): void => { //@ts-ignore resolve(sampleProperty) }) traceRow.onThreadHandler = (useCache) => { let context: CanvasRenderingContext2D; if (traceRow.currentContext) { context = traceRow.currentContext; } else { context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } traceRow.canvasSave(context); (renders.sample as SampleRender).renderMainThread( { context: context, useCache: useCache, type: 'bpftrace', start_ts: startTS, uniqueProperty: uniqueProperty, //@ts-ignore flattenTreeArray: flattenTreeArray }, traceRow ); traceRow.canvasRestore(context) }; traceRow.style.height = `${height}px`; }) } else { traceRow.supplier = () => new Promise((resolve): void => { resolve([]); }); traceRow.onThreadHandler = (useCache) => { let context: CanvasRenderingContext2D; if (traceRow.currentContext) { context = traceRow.currentContext; } else { context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } traceRow.canvasSave(context); (renders.sample as SampleRender).renderMainThread( { context: context, useCache: useCache, type: 'bpftrace', start_ts: 0, uniqueProperty: [], flattenTreeArray: [], }, traceRow ); traceRow.canvasRestore(context); }; } return traceRow; } /** * 监听文件上传事件 * @param row * @param start_ts */ //@ts-ignore addTraceRowEventListener(row: TraceRow, start_ts: number) { row.uploadEl?.addEventListener('sample-file-change', (e: unknown) => { this.getJsonData(e).then((res: unknown) => { this.resetChartData(row); if (!res) { row.supplier = () => new Promise((resolve): void => { resolve([]) }) this.trace.traceSheetEL?.setMode('hidden'); row.style.height = '40px'; row.name = 'UserPluginRow' } else { //@ts-ignore const propertyData = res.data; //@ts-ignore SpUserFileChart.userPluginData = res.data[0]; //@ts-ignore const treeNodes = res.relation.children || [res.relation.RS.children[0]]; const uniqueProperty = this.removeDuplicates(propertyData); const flattenTreeArray = this.getFlattenTreeData(treeNodes); //@ts-ignore const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20; const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty); //@ts-ignore const startTS = start_ts > 0 ? start_ts : flattenTreeArray[0].property[0].begin; row.supplier = () => new Promise((resolve): void => { resolve(sampleProperty) }) row.onThreadHandler = (useCache) => { let context: CanvasRenderingContext2D; if (row.currentContext) { context = row.currentContext; } else { context = row.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } row.canvasSave(context); (renders.sample as SampleRender).renderMainThread( { context: context, useCache: useCache, type: 'bpftrace', start_ts: startTS, uniqueProperty: uniqueProperty, //@ts-ignore flattenTreeArray: flattenTreeArray }, row ); row.canvasRestore(context) }; //@ts-ignore row.name = `${res.relation.detail}(${res.relation.function_name})`; row.style.height = `${height}px`; } this.trace.refreshCanvas(false) }) }) } /** * 清空缓存 * @param row */ //@ts-ignore resetChartData(row: TraceRow) { row.dataList = []; row.dataList2 = []; row.dataListCache = []; row.isComplete = false; } /** * 获取上传的文件内容 转为json格式 * @param file * @returns */ getJsonData(file: unknown): Promise { return new Promise((resolve, reject) => { let reader = new FileReader(); //@ts-ignore reader.readAsText(file.detail || file); reader.onloadend = (e: unknown) => { //@ts-ignore const fileContent = e.target?.result; if (fileContent === this.currentFile) { this.currentFile = undefined; resolve(false) } else { this.currentFile = fileContent; } try { resolve(JSON.parse(fileContent)); document.dispatchEvent( new CustomEvent('file-correct') ) SpStatisticsHttpUtil.addOrdinaryVisitAction({ event: 'bpftrace', action: 'bpftrace', }); } catch (error) { document.dispatchEvent( new CustomEvent('file-error') ) } } }) } /** * 树结构扁平化 * @param treeData * @param depth * @param parentName * @returns */ getFlattenTreeData(treeData: Array, depth: number = 0, parentName: string = ''): Array { let result: Array = []; treeData.forEach(node => { //@ts-ignore const name: string = node['function_name']; const newNode: unknown = {}; if (name && name.indexOf('unknown') > -1) { //@ts-ignore newNode['children'] = this.getUnknownAllChildrenNames(node); } //@ts-ignore newNode['detail'] = node['detail']; //@ts-ignore newNode['depth'] = depth; //@ts-ignore newNode['name'] = name; //@ts-ignore newNode['parentName'] = parentName; //@ts-ignore newNode['property'] = []; //@ts-ignore result.push(newNode); //@ts-ignore if (node.children) { //@ts-ignore result = result.concat(this.getFlattenTreeData(node.children, depth + 1, node['function_name'])) } }) return result } /** * 查找重复项 * @param propertyData * @returns */ removeDuplicates(propertyData: Array): Array { const result: Array = []; propertyData.forEach(propertyGroup => { const groups: Array = []; //@ts-ignore propertyGroup.forEach((property: unknown) => { //@ts-ignore const duplicateObj = groups.find(group => group['func_name'] === property['func_name']); if (duplicateObj) { //@ts-ignore duplicateObj['begin'] = Math.min(duplicateObj['begin'], property['begin']); //@ts-ignore duplicateObj['end'] = Math.max(duplicateObj['end'], property['end']); } else { groups.push(property) } }) result.push(groups); }) return result } /** * 关系树赋值 * @param relationData * @param propertyData */ setRelationDataProperty(relationData: Array, propertyData: Array): Array { const sampleProperty = relationData; //数组每一项进行比对 propertyData.forEach(propertyGroup => { //@ts-ignore propertyGroup.forEach((property: unknown) => { //@ts-ignore const relation = sampleProperty.find(relation => relation['name'] === property['func_name']); //property属性存储每帧数据 //@ts-ignore relation?.property.push({ //@ts-ignore name: property['func_name'], //@ts-ignore detail: relation['detail'], //@ts-ignore end: property['end'], //@ts-ignore begin: property['begin'], //@ts-ignore depth: relation['depth'], //@ts-ignore instructions: property['instructions'], //@ts-ignore cycles: property['cycles'] }) }) }) //获取所有名字为unknown的数据 //@ts-ignore const unknownRelation = sampleProperty.filter(relation => relation['name'].indexOf('unknown') > -1); //二维数组 用于存放unknown下所有子节点的数据 let twoDimensionalArray: Array = []; let result: Array = []; unknownRelation.forEach(unknownItem => { result = []; twoDimensionalArray = []; //@ts-ignore const children = unknownItem['children']; //先获取到unknwon节点下每个子节点的property Object.keys(children).forEach(key => { //@ts-ignore unknownItem.children[key] = (sampleProperty.find(relation => relation['name'] === key)).property; }) //将每个子节点的property加到二维数组中 Object.values(children).forEach((value: unknown) => { //@ts-ignore if (value.length > 0) { twoDimensionalArray.push(value) } }) if (twoDimensionalArray.length > 0) { //取每列的最大值和最小值 //@ts-ignore for (let i = 0; i < twoDimensionalArray[0].length; i++) { const data = { //@ts-ignore name: unknownItem['name'], //@ts-ignore detail: unknownItem['detail'], //@ts-ignore begin: (twoDimensionalArray[0][i]).begin, end: 0, //@ts-ignore depth: unknownItem['depth'] } for (let j = 0; j < twoDimensionalArray.length; j++) { //@ts-ignore data['end'] = Math.max((twoDimensionalArray[j][i])['end'], data['end']); //@ts-ignore data['begin'] = Math.min((twoDimensionalArray[j][i])['begin'], data['begin']); } result.push(data); } //@ts-ignore unknownItem.property = result; } }) return sampleProperty; } /** * 获取unknown节点下所有孩子节点的名称 * @param node * @param names */ getUnknownAllChildrenNames(node: unknown, names: unknown = {}): object { //@ts-ignore if (node['children']) { //@ts-ignore node['children'].forEach((child: unknown) => { //@ts-ignore if (child['function_name'].indexOf('unknown') < 0) { //@ts-ignore names[child['function_name']] = [] } else { this.getUnknownAllChildrenNames(child, names) } }) } //@ts-ignore return names } }