* 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();
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<TraceRow<SampleStruct>> {
let traceRow = TraceRow.skeleton<SampleStruct>();
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) => {
const propertyData = res.data;
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);
const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20;
const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty);
const startTS = flattenTreeArray[0].property[0].begin;
traceRow.supplier = () =>
new Promise((resolve): void => {
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,
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
*/
addTraceRowEventListener(row: TraceRow<unknown>, 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 {
const propertyData = res.data;
SpUserFileChart.userPluginData = res.data[0];
const treeNodes = res.relation.children || [res.relation.RS.children[0]];
const uniqueProperty = this.removeDuplicates(propertyData);
const flattenTreeArray = this.getFlattenTreeData(treeNodes);
const height = (Math.max(...flattenTreeArray.map((obj: unknown) => obj.depth)) + 1) * 20;
const sampleProperty = this.setRelationDataProperty(flattenTreeArray, uniqueProperty);
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,
flattenTreeArray: flattenTreeArray
},
row
);
row.canvasRestore(context)
};
row.name = `${res.relation.detail}(${res.relation.function_name})`;
row.style.height = `${height}px`;
}
this.trace.refreshCanvas(false)
})
})
}
* 清空缓存
* @param row
*/
resetChartData(row: TraceRow<unknown>) {
row.dataList = [];
row.dataList2 = [];
row.dataListCache = [];
row.isComplete = false;
}
* 获取上传的文件内容 转为json格式
* @param file
* @returns
*/
getJsonData(file: unknown): Promise<unknown> {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsText(file.detail || file);
reader.onloadend = (e: unknown) => {
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<unknown>, depth: number = 0, parentName: string = ''): Array<unknown> {
let result: Array<object> = [];
treeData.forEach(node => {
const name: string = node['function_name'];
const newNode: unknown = {};
if (name && name.indexOf('unknown') > -1) {
newNode['children'] = this.getUnknownAllChildrenNames(node);
}
newNode['detail'] = node['detail'];
newNode['depth'] = depth;
newNode['name'] = name;
newNode['parentName'] = parentName;
newNode['property'] = [];
result.push(newNode);
if (node.children) {
result = result.concat(this.getFlattenTreeData(node.children, depth + 1, node['function_name']))
}
})
return result
}
* 查找重复项
* @param propertyData
* @returns
*/
removeDuplicates(propertyData: Array<unknown>): Array<unknown> {
const result: Array<unknown> = [];
propertyData.forEach(propertyGroup => {
const groups: Array<unknown> = [];
propertyGroup.forEach((property: unknown) => {
const duplicateObj = groups.find(group => group['func_name'] === property['func_name']);
if (duplicateObj) {
duplicateObj['begin'] = Math.min(duplicateObj['begin'], property['begin']);
duplicateObj['end'] = Math.max(duplicateObj['end'], property['end']);
} else {
groups.push(property)
}
})
result.push(groups);
})
return result
}
* 关系树赋值
* @param relationData
* @param propertyData
*/
setRelationDataProperty(relationData: Array<unknown>, propertyData: Array<unknown>): Array<unknown> {
const sampleProperty = relationData;
propertyData.forEach(propertyGroup => {
propertyGroup.forEach((property: unknown) => {
const relation = sampleProperty.find(relation => relation['name'] === property['func_name']);
relation?.property.push({
name: property['func_name'],
detail: relation['detail'],
end: property['end'],
begin: property['begin'],
depth: relation['depth'],
instructions: property['instructions'],
cycles: property['cycles']
})
})
})
const unknownRelation = sampleProperty.filter(relation => relation['name'].indexOf('unknown') > -1);
let twoDimensionalArray: Array<unknown> = [];
let result: Array<unknown> = [];
unknownRelation.forEach(unknownItem => {
result = [];
twoDimensionalArray = [];
const children = unknownItem['children'];
Object.keys(children).forEach(key => {
unknownItem.children[key] = (sampleProperty.find(relation => relation['name'] === key)).property;
})
Object.values(children).forEach((value: unknown) => {
if (value.length > 0) {
twoDimensionalArray.push(value)
}
})
if (twoDimensionalArray.length > 0) {
for (let i = 0; i < twoDimensionalArray[0].length; i++) {
const data = {
name: unknownItem['name'],
detail: unknownItem['detail'],
begin: (twoDimensionalArray[0][i]).begin,
end: 0,
depth: unknownItem['depth']
}
for (let j = 0; j < twoDimensionalArray.length; j++) {
data['end'] = Math.max((twoDimensionalArray[j][i])['end'], data['end']);
data['begin'] = Math.min((twoDimensionalArray[j][i])['begin'], data['begin']);
}
result.push(data);
}
unknownItem.property = result;
}
})
return sampleProperty;
}
* 获取unknown节点下所有孩子节点的名称
* @param node
* @param names
*/
getUnknownAllChildrenNames(node: unknown, names: unknown = {}): object {
if (node['children']) {
node['children'].forEach((child: unknown) => {
if (child['function_name'].indexOf('unknown') < 0) {
names[child['function_name']] = []
} else {
this.getUnknownAllChildrenNames(child, names)
}
})
}
return names
}
}