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