• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16
17
18class TraceWorkerPerfDataQuery {
19    systmeRuleName = "/system/"
20    numRuleName = "/max/min/"
21    filesData: any = {}
22    samplesData: any = {}
23    threadData: any = {}
24    callChainData: any = {}
25    splitMapData: any = {}
26    currentTreeMapData: any = {}
27    currentTreeList: any[] = []
28    searchValue: string = ""
29    dataSource:PerfCallChainMerageData[] = []
30    allProcess:PerfCallChainMerageData[] = []
31    callChainMap: Map<number,PerfCall> = new Map<number, PerfCall>()
32
33    initPerfFiles(queryFunc:Function) {
34        this.clearAll()
35        let files = queryFunc("queryPerfFiles", `select file_id as fileId,symbol,path from perf_files`, {})
36        files.forEach((file:any) => {
37            this.filesData[file.fileId] = this.filesData[file.fileId] || []
38            PerfFile.setFileName(file)
39            this.filesData[file.fileId].push(file)
40        })
41        let threads = queryFunc("queryPerfThread", `select a.thread_id as tid,a.thread_name as threadName,a.process_id as pid,b.thread_name as processName from perf_thread a left join (select * from perf_thread where thread_id = process_id) b on a.process_id = b.thread_id`, {})
42        threads.forEach((thread:any) => {
43            this.threadData[thread.tid] = thread
44        })
45        let countRes = queryFunc("queryPerfCallchainsCount",`select count(*) as count from perf_callchain where symbol_id != -1 and vaddr_in_file != 0`,{})
46        if (countRes.length != 0) {
47            let count: number = (countRes[0] as any).count;
48            let pageSize = 500000;
49            let pages = Math.ceil(count / pageSize);
50            let callChains: any[] = []
51            for (let i = 0; i < pages; i++) {
52                let arr = queryFunc("queryPerfCallchains",`select c.name,c.sample_id as sampleId,c.callchain_id as callChainId,c.vaddr_in_file as vaddrInFile,c.file_id as fileId,c.symbol_id as symbolId,s.thread_state as threadState,s.thread_id as tid  from perf_callchain c left join perf_sample s on c.sample_id = s.sample_id
53where c.symbol_id != -1 and c.vaddr_in_file != 0 limit $limit offset $offset`,
54                    {$limit: pageSize, $offset:  i * pageSize})
55                callChains = callChains.concat(arr)
56            }
57            this.initCallChainBottomUp(callChains)
58        }
59    }
60
61    clearAll(){
62        this.filesData = {}
63        this.samplesData = {}
64        this.threadData= {}
65        this.callChainData = {}
66        this.splitMapData = {}
67        this.currentTreeMapData = {}
68        this.currentTreeList = []
69        this.searchValue = ""
70        this.dataSource = []
71        this.allProcess = []
72        this.callChainMap = new Map<number, PerfCall>()
73    }
74
75    initCallChainBottomUp(callChains: PerfCallChain[]) {
76        callChains.forEach((callChain, index) => {
77            if (this.threadData[callChain.tid] == undefined) {
78                return
79            }
80            this.setCallChainName(callChain);
81            this.addGroupData(callChain)
82            if (index + 1 < callChains.length && callChains[index + 1].sampleId == callChain.sampleId) {
83                PerfCallChain.setPreviousNode(callChain, callChains[index + 1])
84            }
85            if (callChains.length == index + 1 || callChains[index + 1].sampleId != callChain.sampleId) {
86                this.addProcessThreadStateData(callChain)
87            }
88        })
89    }
90
91    setCallChainName(callChain: PerfCallChain) {//设置调用栈的名称
92        callChain.canCharge = true;
93        callChain.pid = this.threadData[callChain.tid].pid;
94        if (callChain.symbolId == -1) {
95            if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > 0) {
96                callChain.fileName = this.filesData[callChain.fileId][0].fileName
97                callChain.path = this.filesData[callChain.fileId][0].path
98            } else {
99                callChain.fileName = "unkown"
100            }
101        } else {
102            if (this.filesData[callChain.fileId] && this.filesData[callChain.fileId].length > callChain.symbolId) {
103                callChain.fileName = this.filesData[callChain.fileId][callChain.symbolId].fileName
104                callChain.path = this.filesData[callChain.fileId][callChain.symbolId].path
105            } else {
106                callChain.fileName = "unkown"
107            }
108        }
109    }
110
111    addProcessThreadStateData(callChain: PerfCallChain) {//当调用栈为调用的根节点时
112        this.addPerfCallData(callChain)
113        let threadCallChain = new PerfCallChain()//新增的线程数据
114        threadCallChain.depth = 0
115        PerfCallChain.merageCallChain(threadCallChain, callChain)
116        threadCallChain.canCharge = false
117        threadCallChain.name = this.threadData[callChain.tid].threadName||"Thead" + "(" + callChain.tid + ")"
118        let threadStateCallChain = new PerfCallChain()//新增的线程状态数据
119        PerfCallChain.merageCallChain(threadStateCallChain, callChain)
120        threadStateCallChain.name = callChain.threadState || "Unkown State"
121        threadStateCallChain.fileName = threadStateCallChain.name == "-" ? "Unkown Thead State" : ""
122        threadStateCallChain.canCharge = false
123        this.addGroupData(threadStateCallChain)
124        this.addGroupData(threadCallChain)
125        PerfCallChain.setNextNode(threadCallChain, threadStateCallChain)
126        PerfCallChain.setNextNode(threadStateCallChain, callChain)
127    }
128
129    addPerfCallData(callChain: PerfCallChain){
130        let perfCall = new PerfCall()
131        perfCall.depth = this.callChainData[callChain.sampleId]?.length||0
132        perfCall.sampleId = callChain.sampleId
133        perfCall.name = callChain.name
134        this.callChainMap.set(callChain.sampleId,perfCall)
135    }
136
137    addGroupData(callChain: PerfCallChain) {
138        this.callChainData[callChain.sampleId] = this.callChainData[callChain.sampleId] || []
139        this.callChainData[callChain.sampleId].push(callChain)
140    }
141
142    getCallChainsBySampleIds(sampleIds: string[], isTopDown: boolean) {
143        this.allProcess = this.groupNewTreeNoId(sampleIds, isTopDown)
144        return this.allProcess
145    }
146
147
148    groupNewTreeNoId(sampleIds: string[], isTopDown: boolean):any[] {
149        this.currentTreeMapData = {}
150        this.currentTreeList = []
151        for (let i = 0; i < sampleIds.length; i++) {
152            let callChains = this.callChainData[sampleIds[i]]
153            if (callChains == undefined) continue
154            let topIndex = isTopDown ? (callChains.length - 1) : 0;
155            if (callChains.length > 0) {
156                let root = this.currentTreeMapData[callChains[topIndex].name + callChains[topIndex].pid];
157                if (root == undefined) {
158                    root = new PerfCallChainMerageData();
159                    this.currentTreeMapData[callChains[topIndex].name + callChains[topIndex].pid] = root;
160                    this.currentTreeList.push(root)
161                }
162                PerfCallChainMerageData.merageCallChain(root, callChains[topIndex], isTopDown);
163                this.merageChildren(root, callChains[topIndex], isTopDown);
164            }
165        }
166        let rootMerageMap: any = {}
167        Object.values(this.currentTreeMapData).forEach((merageData: any) => {
168            if (rootMerageMap[merageData.pid] == undefined) {
169                let processMerageData = new PerfCallChainMerageData()//新增进程的节点数据
170                processMerageData.canCharge = false
171                processMerageData.symbolName = this.threadData[merageData.tid].processName||`Process(${merageData.pid})`
172                processMerageData.symbol = processMerageData.symbolName
173                processMerageData.tid = merageData.tid
174                processMerageData.children.push(merageData)
175                processMerageData.initChildren.push(merageData)
176                processMerageData.dur = merageData.dur;
177                processMerageData.count = merageData.dur;
178                processMerageData.total = sampleIds.length;
179                rootMerageMap[merageData.pid] = processMerageData
180            } else {
181                rootMerageMap[merageData.pid].children.push(merageData)
182                rootMerageMap[merageData.pid].initChildren.push(merageData)
183                rootMerageMap[merageData.pid].dur += merageData.dur;
184                rootMerageMap[merageData.pid].count += merageData.dur;
185                rootMerageMap[merageData.pid].total = sampleIds.length;
186            }
187            merageData.parentNode = rootMerageMap[merageData.pid]//子节点添加父节点的引用
188        })
189        let id = 0;
190        this.currentTreeList.forEach((node) => {
191            node.total = sampleIds.length;
192            if (node.id == "") {
193                node.id = id + ""
194                id++
195            }
196            if(node.parentNode){
197                if (node.parentNode.id == "") {
198                    node.parentNode.id = id + ""
199                    id++
200                }
201                node.parentId = node.parentNode.id
202            }
203        })
204        return Object.values(rootMerageMap)
205    }
206
207    merageChildren(currentNode: PerfCallChainMerageData, callChain: any, isTopDown: boolean) {
208        let nextNodeKey = isTopDown ? "nextNode" : "previousNode"
209        if (callChain[nextNodeKey] == undefined) return
210        let node;
211        if (currentNode.initChildren.filter((child: PerfCallChainMerageData) => {
212            if (child.symbolName == callChain[nextNodeKey]?.name) {
213                node = child;
214                PerfCallChainMerageData.merageCallChain(child, callChain[nextNodeKey], isTopDown)
215                return true;
216            }
217            return false;
218        }).length == 0) {
219            node = new PerfCallChainMerageData()
220            PerfCallChainMerageData.merageCallChain(node, callChain[nextNodeKey], isTopDown)
221            currentNode.children.push(node)
222            currentNode.initChildren.push(node)
223            this.currentTreeList.push(node)
224            node.parentNode = currentNode
225        }
226        if (node) this.merageChildren(node, callChain[nextNodeKey], isTopDown)
227    }
228
229    //所有的操作都是针对整个树结构的 不区分特定的数据
230    splitTree(data: PerfCallChainMerageData[], name: string, isCharge: boolean, isSymbol: boolean) {
231        data.forEach((process) => {
232            process.children = []
233            if (isCharge) {
234                this.recursionChargeInitTree(process, name, isSymbol)
235            } else {
236                this.recursionPruneInitTree(process, name, isSymbol)
237            }
238        })
239        this.resetAllNode(data)
240    }
241
242    recursionChargeInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean) {
243        if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) {
244            (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node)
245            node.isStore++;
246        }
247        if (node.initChildren.length > 0) {
248            node.initChildren.forEach((child) => {
249                this.recursionChargeInitTree(child, symbolName, isSymbol)
250            })
251        }
252    }
253
254    //symbol lib charge
255    recursionChargeTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean) {
256        if ((isSymbol && node.symbolName == symbolName) || (!isSymbol && node.libName == symbolName)) {
257            node.currentTreeParentNode && node.currentTreeParentNode.children.splice(node.currentTreeParentNode.children.indexOf(node), 1, ...node.children);
258            node.children.forEach((child) => {
259                child.currentTreeParentNode = node.currentTreeParentNode
260            })
261        }
262        if (node.children.length > 0) {
263            node.children.forEach((child) => {
264                this.recursionChargeTree(child, symbolName, isSymbol)
265            })
266        }
267    }
268
269    recursionPruneInitTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean) {
270        if (isSymbol && node.symbolName == symbolName || (!isSymbol && node.libName == symbolName)) {
271            (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(node)
272            node.isStore++;
273            this.pruneChildren(node, symbolName)
274        } else if (node.initChildren.length > 0) {
275            node.initChildren.forEach((child) => {
276                this.recursionPruneInitTree(child, symbolName, isSymbol)
277            })
278        }
279    }
280
281    //symbol lib prune
282    recursionPruneTree(node: PerfCallChainMerageData, symbolName: string, isSymbol: boolean) {
283        if (isSymbol && node.symbolName == symbolName || (!isSymbol && node.libName == symbolName)) {
284            node.currentTreeParentNode && node.currentTreeParentNode.children.splice(node.currentTreeParentNode.children.indexOf(node), 1);
285        } else {
286            node.children.forEach((child) => {
287                this.recursionPruneTree(child, symbolName, isSymbol)
288            })
289        }
290    }
291
292    recursionChargeByRule(node: PerfCallChainMerageData, ruleName: string, rule: (node: PerfCallChainMerageData) => boolean) {
293        if (node.initChildren.length > 0) {
294            node.initChildren.forEach((child) => {
295                if (rule(child)) {
296                    (this.splitMapData[ruleName] = this.splitMapData[ruleName] || []).push(child)
297                    child.isStore++;
298                }
299                this.recursionChargeByRule(child, ruleName, rule)
300            })
301        }
302    }
303
304    pruneChildren(node: PerfCallChainMerageData, symbolName: string) {
305        if (node.initChildren.length > 0) {
306            node.initChildren.forEach((child) => {
307                child.isStore++;
308                (this.splitMapData[symbolName] = this.splitMapData[symbolName] || []).push(child);
309                this.pruneChildren(child, symbolName)
310            })
311        }
312    }
313
314    hideSystemLibrary(){
315        this.allProcess.forEach((item)=>{
316            item.children = []
317            this.recursionChargeByRule(item,this.systmeRuleName,(node)=>{
318                return node.path.startsWith(this.systmeRuleName)
319            })
320        })
321    }
322
323    hideNumMaxAndMin(startNum:number,endNum:string){
324        let max = endNum == "∞"?Number.POSITIVE_INFINITY :parseInt(endNum)
325        this.allProcess.forEach((item)=>{
326            item.children = []
327            this.recursionChargeByRule(item,this.numRuleName,(node)=>{
328                return node.dur < startNum || node.dur > max
329            })
330        })
331    }
332
333    clearSplitMapData(symbolName: string) {
334        delete this.splitMapData[symbolName]
335    }
336
337    resotreAllNode(symbols: string[]) {
338        symbols.forEach((symbol) => {
339            let list = this.splitMapData[symbol];
340            if (list != undefined) {
341                list.forEach((item: any) => {
342                    item.isStore--
343                })
344            }
345        })
346    }
347
348    resetAllNode(data: PerfCallChainMerageData[]) {
349        this.clearSearchNode()
350        data.forEach((process) => {
351            process.searchShow = true
352        })
353        this.resetNewAllNode(data)
354        if (this.searchValue != "") {
355            this.findSearchNode(data, this.searchValue, false)
356            this.resetNewAllNode(data)
357        }
358    }
359
360    resetNewAllNode(data: PerfCallChainMerageData[]) {
361        data.forEach((process) => {
362            process.children = []
363        })
364        let values = this.currentTreeList.map((item: any) => {
365            item.children = []
366            return item
367        })
368        values.forEach((item: any) => {
369            if (item.parentNode != undefined) {
370                if (item.isStore == 0 && item.searchShow) {
371                    let parentNode = item.parentNode
372                    while (parentNode != undefined && !(parentNode.isStore == 0 && parentNode.searchShow)) {
373                        parentNode = parentNode.parentNode
374                    }
375                    if (parentNode) {
376                        item.currentTreeParentNode = parentNode
377                        parentNode.children.push(item)
378                    }
379                }
380            }
381        })
382    }
383
384    findSearchNode(data: PerfCallChainMerageData[], search: string, parentSearch: boolean) {
385        data.forEach((node) => {
386            if ((node.symbol&&node.symbol.includes(search)) || parentSearch) {
387                node.searchShow = true
388                let parentNode = node.currentTreeParentNode
389                while (parentNode != undefined && !parentNode.searchShow) {
390                    parentNode.searchShow = true
391                    parentNode = parentNode.currentTreeParentNode
392                }
393            } else {
394                node.searchShow = false
395            }
396            if (node.children.length > 0) {
397                this.findSearchNode(node.children, search, node.searchShow)
398            }
399        })
400    }
401
402    clearSearchNode() {
403        this.currentTreeList.forEach((node) => {
404            node.searchShow = true
405        })
406    }
407
408    splitAllProcess(list:any[]){
409        list.forEach((item:any)=>{
410            this.allProcess.forEach((process)=>{
411                if(item.select == "0"){
412                    this.recursionChargeInitTree(process, item.name, item.type == "symbol")
413                }else {
414                    this.recursionPruneInitTree(process, item.name, item.type == "symbol")
415                }
416            })
417            if(!item.checked){
418                this.resotreAllNode([item.name])
419            }
420        })
421    }
422
423    resolvingAction(params:any[]) {
424        if (params.length > 0) {
425            params.forEach((item) => {
426                if (item.funcName && item.funcArgs) {
427                    switch (item.funcName) {
428                        case "getCallChainsBySampleIds":
429                            this.allProcess = this.getCallChainsBySampleIds(item.funcArgs[0], item.funcArgs[1])
430                            break
431                        case "hideSystemLibrary":
432                            this.hideSystemLibrary();
433                            break
434                        case "hideNumMaxAndMin":
435                            this.hideNumMaxAndMin(item.funcArgs[0], item.funcArgs[1])
436                            break
437                        case "splitAllProcess":
438                            this.splitAllProcess(item.funcArgs[0])
439                            break
440                        case "resetAllNode":
441
442                            this.resetAllNode(this.allProcess)
443                            break
444                        case "resotreAllNode":
445                            this.resotreAllNode(item.funcArgs[0])
446                            break
447                        case "clearSplitMapData":
448                            this.clearSplitMapData(item.funcArgs[0])
449                            break
450                        case "splitTree":
451                            this.splitTree(this.allProcess, item.funcArgs[0], item.funcArgs[1], item.funcArgs[2]);
452                            break
453                        case "setSearchValue":
454                            this.searchValue = item.funcArgs[0]
455                            break
456                    }
457                }
458            })
459            this.dataSource = this.allProcess.filter((process) => {
460                return process.children && process.children.length > 0
461            })
462        }
463        return this.dataSource
464    }
465}
466
467let perfDataQuery = new TraceWorkerPerfDataQuery()
468
469enum ChartMode {
470    Call,
471    Byte,
472    Count,
473}
474
475class PerfFile {
476    fileId: number = 0;
477    symbol: string = ""
478    path: string = ""
479    fileName: string = ""
480
481    static setFileName(data: PerfFile) {
482        if (data.path) {
483            let number = data.path.lastIndexOf("/");
484            if (number > 0) {
485                data.fileName = data.path.substring(number + 1)
486                return
487            }
488        }
489        data.fileName = data.path
490    }
491
492    setFileName() {
493        if (this.path) {
494            let number = this.path.lastIndexOf("/");
495            if (number > 0) {
496                this.fileName = this.path.substring(number + 1)
497                return
498            }
499        }
500        this.fileName = this.path
501    }
502}
503
504class PerfThread {
505    tid: number = 0;
506    pid: number = 0;
507    threadName: string = "";
508    processName: string = "";
509}
510
511class PerfCallChain {
512    tid: number = 0;
513    pid: number = 0;
514    name: string = ""
515    fileName: string = "";
516    threadState: string = "";
517    startNS: number = 0;
518    dur: number = 0;
519    sampleId: number = 0;
520    callChainId: number = 0;
521    vaddrInFile: number = 0;
522    fileId: number = 0;
523    symbolId: number = 0;
524    path: string = "";
525    parentId: string = ""//合并之后区分的id
526    id: string = ""
527    topDownMerageId: string = ""//top down合并使用的id
528    topDownMerageParentId: string = ""//top down合并使用的id
529    bottomUpMerageId: string = ""//bottom up合并使用的id
530    bottomUpMerageParentId: string = ""//bottom up合并使用的id
531    depth: number = 0;
532    canCharge:boolean = true
533    previousNode: PerfCallChain | undefined = undefined;//将list转换为一个链表结构
534    nextNode: PerfCallChain | undefined = undefined;
535
536    static setNextNode(currentNode: PerfCallChain, nextNode: PerfCallChain) {
537        currentNode.nextNode = nextNode
538        nextNode.previousNode = currentNode
539    }
540
541    static setPreviousNode(currentNode: PerfCallChain, prevNode: PerfCallChain) {
542        currentNode.previousNode = prevNode
543        prevNode.nextNode = currentNode
544    }
545
546    static merageCallChain(currentNode: PerfCallChain, callChain: PerfCallChain) {
547        currentNode.startNS = callChain.startNS
548        currentNode.tid = callChain.tid
549        currentNode.pid = callChain.pid
550        currentNode.sampleId = callChain.sampleId
551        currentNode.dur = callChain.dur
552    }
553
554}
555
556class Rect {
557    x: number = 0
558    y: number = 0
559    width: number = 0
560    height: number = 0
561
562    constructor(x: number, y: number, width: number, height: number) {
563        this.x = x;
564        this.y = y;
565        this.width = width;
566        this.height = height;
567    }
568
569    static contains(rect: Rect, x: number, y: number): boolean {
570        return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height;
571    }
572
573    static containsWithPadding(rect: Rect, x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean {
574        return rect.x + paddingLeftRight <= x
575            && x <= rect.x + rect.width - paddingLeftRight
576            && rect.y + paddingTopBottom <= y
577            && y <= rect.y + rect.height - paddingTopBottom;
578    }
579
580    static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean {
581        return rect.x - l <= x
582            && x <= rect.x + rect.width + r
583            && rect.y - t <= y
584            && y <= rect.y + rect.height + b;
585    }
586
587    static intersect(r1: Rect, rect: Rect): boolean {
588        let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width;
589        let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height;
590        let minX = r1.x <= rect.x ? r1.x : rect.x;
591        let minY = r1.y <= rect.y ? r1.y : rect.y;
592        if (maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height) {
593            return true;
594        } else {
595            return false;
596        }
597    }
598
599    contains(x: number, y: number): boolean {
600        return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
601    }
602
603    containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean {
604        return this.x + paddingLeftRight <= x
605            && x <= this.x + this.width - paddingLeftRight
606            && this.y + paddingTopBottom <= y
607            && y <= this.y + this.height - paddingTopBottom;
608    }
609
610    containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean {
611        return this.x - l <= x
612            && x <= this.x + this.width + r
613            && this.y - t <= y
614            && y <= this.y + this.height + b;
615    }
616
617    /**
618     * 判断是否相交
619     * @param rect
620     */
621    intersect(rect: Rect): boolean {
622        let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width;
623        let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height;
624        let minX = this.x <= rect.x ? this.x : rect.x;
625        let minY = this.y <= rect.y ? this.y : rect.y;
626        if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) {
627            return true;
628        } else {
629            return false;
630        }
631    }
632}
633
634class BaseStruct {
635    frame: Rect | undefined
636    isHover: boolean = false;
637}
638
639class ChartStruct extends BaseStruct {
640    static hoverFuncStruct: ChartStruct | undefined;
641    static selectFuncStruct: ChartStruct | undefined;
642    static lastSelectFuncStruct: ChartStruct | undefined;
643    static padding: number = 1;
644    needShow = false;
645    depth: number = 0;
646    symbol: string = '';
647    size: number = 0;
648    count: number = 0;
649    type: ChartMode = ChartMode.Call;
650    parent: ChartStruct | undefined;
651    children: Array<ChartStruct> = [];
652}
653
654class PerfCallChainMerageData extends ChartStruct {
655    #parentNode: PerfCallChainMerageData | undefined = undefined
656    #total = 0
657    id: string = "";
658    parentId: string = "";
659    currentTreeParentNode: PerfCallChainMerageData | undefined = undefined;
660    symbolName: string = "";
661    symbol: string = ""
662    libName: string = ""
663    path: string = ""
664    self: string = "0s"
665    weight: string = ""
666    weightPercent: string = ""
667    selfDur: number = 0;
668    dur: number = 0;
669    tid: number = 0;
670    pid: number = 0;
671    isStore = 0;
672    canCharge:boolean = true
673    children: PerfCallChainMerageData[] = []
674    initChildren: PerfCallChainMerageData[] = []
675    type: number = 0;
676    vaddrInFile: number = 0;
677    isSelected: boolean = false;
678    searchShow: boolean = true;
679
680    set parentNode(data: PerfCallChainMerageData | undefined) {
681        this.currentTreeParentNode = data;
682        this.#parentNode = data;
683    }
684
685    get parentNode() {
686        return this.#parentNode
687    }
688
689    set total(data: number) {
690        this.#total = data;
691        this.weight = `${timeMsFormat2p(this.dur * (1))}`
692        this.weightPercent = `${(this.dur / data * 100).toFixed(1)}%`
693    }
694
695    get total() {
696        return this.#total;
697    }
698
699    static merageCallChain(currentNode: PerfCallChainMerageData, callChain: PerfCallChain, isTopDown: boolean) {
700        if (currentNode.symbolName == "") {
701            currentNode.symbol = `${callChain.name}  ${callChain.fileName ? `(${callChain.fileName})` : ""}`
702            currentNode.symbolName = callChain.name
703            currentNode.pid = callChain.pid
704            currentNode.tid = callChain.tid
705            currentNode.libName = callChain.fileName
706            currentNode.vaddrInFile = callChain.vaddrInFile;
707            currentNode.canCharge = callChain.canCharge
708            if (callChain.path) {
709                currentNode.path = callChain.path
710            }
711        }
712        if (callChain[isTopDown ? "nextNode" : "previousNode"] == undefined) {
713            currentNode.selfDur++;
714            currentNode.self = timeMsFormat2p(currentNode.selfDur)
715        }
716        currentNode.dur++;
717        currentNode.count++;
718    }
719}
720
721class PerfSample {
722    sampleId: number = 0;
723    time: number = 0;
724    timeString: string = "";
725    core: number = 0;
726    coreName: string = "";
727    state: string = "";
728    pid: number = 0;
729    processName: string = "";
730    tid: number = 0;
731    threadName: string = "";
732    depth: number = 0;
733    addr: string = "";
734    fileId: number = 0;
735    symbolId: number = 0;
736    backtrace: Array<string> = [];
737}
738
739class PerfStack {
740    symbol: string = "";
741    path: string = "";
742    fileId: number = 0;
743    type: number = 0;
744    vaddrInFile: number = 0;
745}
746
747class PerfCmdLine {
748    report_value: string = "";
749}
750
751class PerfCall{
752    sampleId: number = 0;
753    depth: number = 0;
754    name: string = "";
755}
756
757function timeMsFormat2p(ns: number) {
758    let currentNs = ns
759    let hour1 = 3600_000
760    let minute1 = 60_000
761    let second1 = 1_000; // 1 second
762    let res = ""
763    if (currentNs >= hour1) {
764        res += Math.floor(currentNs / hour1).toFixed(2) + "h"
765        return res
766    }
767    if (currentNs >= minute1) {
768        res += Math.floor(currentNs / minute1).toFixed(2) + "min"
769        return res
770    }
771    if (currentNs >= second1) {
772        res += Math.floor(currentNs / second1).toFixed(2) + "s"
773        return res
774    }
775    if (currentNs > 0) {
776        res += currentNs.toFixed(2) + "ms";
777        return res
778    }
779    if (res == "") {
780        res = "0s";
781    }
782    return res
783}