• 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
16import {BaseElement, element} from "../../../../base-ui/BaseElement.js";
17import {CpuStruct} from "../../../bean/CpuStruct.js";
18import {LitTable} from "../../../../base-ui/table/lit-table.js";
19import "../../../../base-ui/table/lit-table-column.js";
20
21import {
22    queryBinderArgsByArgset, queryBinderByArgsId, queryBinderBySliceId,
23    queryThreadWakeUp,
24    queryThreadWakeUpFrom,
25    queryWakeUpFromThread_WakeThread,
26    queryWakeUpFromThread_WakeTime,
27} from "../../../database/SqlLite.js";
28import {WakeupBean} from "../../../bean/WakeupBean.js";
29import {ThreadStruct} from "../../../bean/ThreadStruct.js";
30import {ProcessMemStruct} from "../../../bean/ProcessMemStruct.js";
31import {FuncStruct} from "../../../bean/FuncStruct.js";
32import {SpApplication} from "../../../SpApplication.js";
33import {TraceRow} from "../base/TraceRow.js";
34
35const STATUS_MAP: any = {
36    D: "Uninterruptible Sleep",
37    S: "Sleeping",
38    R: "Runnable",
39    "Running": "Running",
40    "R+": "Runnable (Preempted)",
41    DK: "Uninterruptible Sleep + Wake Kill",
42    I: "Task Dead",
43    T: "Traced",
44    t: "Traced",
45    X: "Exit (Dead)",
46    Z: "Exit (Zombie)",
47    K: "Wake Kill",
48    W: "Waking",
49    P: "Parked",
50    N: "No Load"
51}
52const INPUT_WORD = "This is the interval from when the task became eligible to run \n(e.g.because of notifying a wait queue it was a suspended on) to\n when it started running."
53
54export function getTimeString(ns: number): string {
55    let currentNs = ns
56    let hour1 = 3600_000_000_000
57    let minute1 = 60_000_000_000
58    let second1 = 1_000_000_000; // 1 second
59    let millisecond1 = 1_000_000; // 1 millisecond
60    let microsecond1 = 1_000; // 1 microsecond
61    let res = "";
62    if (currentNs >= hour1) {
63        res += Math.floor(currentNs / hour1) + "h ";
64        currentNs = currentNs - Math.floor(currentNs / hour1) * hour1
65    }
66    if (currentNs >= minute1) {
67        res += Math.floor(currentNs / minute1) + "m ";
68        currentNs = currentNs - Math.floor(ns / minute1) * minute1
69    }
70    if (currentNs >= second1) {
71        res += Math.floor(currentNs / second1) + "s ";
72        currentNs = currentNs - Math.floor(currentNs / second1) * second1
73    }
74    if (currentNs >= millisecond1) {
75        res += Math.floor(currentNs / millisecond1) + "ms ";
76        currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1
77    }
78    if (currentNs >= microsecond1) {
79        res += Math.floor(currentNs / microsecond1) + "μs ";
80        currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1
81    }
82    if (currentNs > 0) {
83        res += currentNs + "ns ";
84    }
85    return res
86}
87
88@element('tabpane-current-selection')
89export class TabPaneCurrentSelection extends BaseElement {
90    weakUpBean: WakeupBean | null | undefined;
91    private tbl: LitTable | null | undefined;
92    private tableObserver: MutationObserver | undefined
93    // @ts-ignore
94    private dpr: any = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1;
95
96    set data(value: any) {
97        this.setCpuData(value)
98    }
99
100    setCpuData(data: CpuStruct, callback: ((data: WakeupBean | null) => void) | undefined = undefined, scrollCallback?: (data: CpuStruct) => void) {
101        let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle");
102        if (leftTitle) {
103            leftTitle.innerText = "Slice Details"
104        }
105        let list: any[] = []
106        let process = this.transferString( data.processName || "Process");
107        let processId = data.processId || data.tid;
108        let state = ""
109        if (data.end_state) {
110            state = STATUS_MAP[data.end_state]
111        } else if (data.end_state == "" || data.end_state == null) {
112            state = ""
113        } else {
114            state = "Unknown State"
115        }
116
117        list.push({name: 'Process', value: `${process || 'Process'} [${processId}]`})
118        let name = this.transferString(data.name ?? "");
119        if(data.processId){
120            list.push({
121                name: 'Thread', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
122<div style="white-space:pre-wrap">${name || 'Process'} [${data.tid}]</div>
123<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="thread-id" name="select" color="#7fa1e7" size="20"></lit-icon>
124</div>`
125            })
126        }else {
127            list.push({
128                name: 'Thread', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
129<div style="white-space:pre-wrap">${name || 'Process'} [${data.tid}]</div>
130</div>`
131            })
132        }
133
134        list.push({name: 'CmdLine', value: `${data.processCmdLine}`})
135        list.push({name: 'StartTime', value: getTimeString(data.startTime || 0)})
136        list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
137        list.push({name: 'Prio', value: data.priority || 0})
138        list.push({name: 'End State', value: state})
139        this.queryCPUWakeUpFromData(data).then((bean) => {
140            if (callback) {
141                callback(bean)
142            }
143            this.tbl!.dataSource = list
144            let rightArea: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#right-table");
145            let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle");
146            let threadClick = this.tbl?.shadowRoot?.querySelector("#thread-id")
147            threadClick?.addEventListener("click", () => {
148                //cpu点击
149                if (scrollCallback) {
150                    scrollCallback(data)
151                }
152            })
153            let canvas = this.initCanvas();
154            if (bean != null) {
155                this.weakUpBean = bean;
156                if (rightArea != null && rightArea) {
157                    rightArea.style.visibility = "visible"
158                }
159                if (rightTitle != null && rightTitle) {
160                    rightTitle.style.visibility = "visible"
161                }
162                this.drawRight(canvas, bean)
163            } else {
164                this.weakUpBean = null;
165                if (rightArea != null && rightArea) {
166                    rightArea.style.visibility = "hidden"
167                }
168                if (rightTitle != null && rightTitle) {
169                    rightTitle.style.visibility = "hidden"
170                }
171            }
172        })
173    }
174
175    setFunctionData(data: FuncStruct,scrollCallback:Function) {//方法信息
176        this.initCanvas()
177        let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle");
178        let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle");
179        if (rightTitle) {
180            rightTitle.style.visibility = "hidden"
181        }
182        if (leftTitle) {
183            leftTitle.innerText = "Slice Details"
184        }
185        let list: any[] = []
186        let name = this.transferString(data.funName ?? "");
187        let isBinder = FuncStruct.isBinder(data);
188        let isAsyncBinder = isBinder&&FuncStruct.isBinderAsync(data);
189        if (data.argsetid != undefined&&data.argsetid != null) {
190            if(isAsyncBinder){
191                Promise.all([queryBinderByArgsId(data.argsetid!,data.startTs!,!data.funName!.endsWith("rcv")),queryBinderArgsByArgset(data.argsetid)]).then((result)=>{
192                    let asyncBinderRes = result[0]
193                    let argsBinderRes = result[1]
194                    let asyncBinderStract:any;
195                    if(asyncBinderRes.length > 0){
196                        asyncBinderRes[0].type = TraceRow.ROW_TYPE_FUNC
197                        asyncBinderStract = asyncBinderRes[0]
198                    }
199                    if(argsBinderRes.length > 0){
200                        argsBinderRes.forEach((item) => {
201                            list.push({name: item.keyName, value: item.strValue})
202                        })
203                    }
204                    if(asyncBinderStract != undefined){
205                        list.unshift({
206                            name: 'Name', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
207<div style="white-space:pre-wrap">${name || 'binder'}</div>
208<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon>
209</div>`
210                        })
211                    }else {
212                        list.unshift({name: 'Name', value:name})
213                    }
214                    list.push({name: 'StartTime', value: getTimeString(data.startTs || 0)})
215                    list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
216                    list.push({name: 'depth', value: data.depth})
217                    list.push({name: 'arg_set_id', value: data.argsetid})
218                    this.tbl!.dataSource = list;
219                    let funcClick = this.tbl?.shadowRoot?.querySelector("#function-jump")
220                    funcClick?.addEventListener("click", () => {
221                        scrollCallback(asyncBinderStract)
222                    })
223                })
224            } else if(isBinder){
225                queryBinderArgsByArgset(data.argsetid).then((argset) => {
226                    let binderSliceId = -1;
227                    argset.forEach((item) => {
228                        if(item.keyName == 'destination slice id'){
229                            binderSliceId = Number(item.strValue)
230                            list.unshift({
231                                name: 'Name', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
232<div style="white-space:pre-wrap">${name || 'binder'}</div>
233<lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="function-jump" name="select" color="#7fa1e7" size="20"></lit-icon>
234</div>`
235                            })
236                        }
237                        list.push({name: item.keyName, value: item.strValue})
238                    })
239                    if(binderSliceId  == -1) {
240                       list.unshift({name: 'Name', value:name})
241                    }
242                    list.push({name: 'StartTime', value: getTimeString(data.startTs || 0)})
243                    list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
244                    list.push({name: 'depth', value: data.depth})
245                    list.push({name: 'arg_set_id', value: data.argsetid})
246                    this.tbl!.dataSource = list;
247                    let funcClick = this.tbl?.shadowRoot?.querySelector("#function-jump")
248                    funcClick?.addEventListener("click", () => {
249                        if (!Number.isNaN(binderSliceId)&&binderSliceId!=-1) {
250                            queryBinderBySliceId(binderSliceId).then((result:any[])=>{
251                                if(result.length > 0){
252                                    result[0].type = TraceRow.ROW_TYPE_FUNC
253                                    scrollCallback(result[0])
254                                }
255                            })
256                        }
257                    })
258                });
259            } else {
260                queryBinderArgsByArgset(data.argsetid).then((argset) => {
261                    list.push({name: 'Name', value:name})
262                    argset.forEach((item) => {
263                        list.push({name: item.keyName, value: item.strValue})
264                    })
265                    list.push({name: 'StartTime', value: getTimeString(data.startTs || 0)})
266                    list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
267                    list.push({name: 'depth', value: data.depth})
268                    list.push({name: 'arg_set_id', value: data.argsetid})
269                    this.tbl!.dataSource = list;
270                })
271            }
272        }else {
273            list.push({name: 'Name', value:name})
274            list.push({name: 'StartTime', value: getTimeString(data.startTs || 0)})
275            list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
276            list.push({name: 'depth', value: data.depth})
277            this.tbl!.dataSource = list;
278        }
279    }
280
281    setMemData(data: ProcessMemStruct) {//时钟信息
282        this.initCanvas()
283        let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle");
284        if (leftTitle) {
285            leftTitle.innerText = "Counter Details"
286        }
287        let list: any[] = []
288        list.push({name: 'Start time', value: getTimeString(data.startTime || 0)})
289        list.push({name: 'Value', value: data.value})
290        list.push({name: 'Delta', value: data.delta})
291        list.push({name: 'Duration', value: getTimeString(data.duration || 0)})
292        this.tbl!.dataSource = list
293
294    }
295
296    setThreadData(data: ThreadStruct, scrollCallback: ((d: any) => void) | undefined,scrollWakeUp:(d:any) => void | undefined) {//线程信息
297        this.initCanvas()
298        let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle");
299        let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle");
300        if (rightTitle) {
301            rightTitle.style.visibility = "hidden"
302        }
303        if (leftTitle) {
304            leftTitle.innerText = "Thread State"
305        }
306        let list: any[] = []
307        list.push({name: 'StartTime', value: getTimeString(data.startTime || 0)})
308        list.push({name: 'Duration', value: getTimeString(data.dur || 0)})
309        let state = ""
310        if (data.state) {
311            state = STATUS_MAP[data.state]
312        } else if (data.state == "" || data.state == null) {
313            state = ""
314        } else {
315            state = "Unknown State"
316        }
317        if ("Running" == state) {
318            state = state + " on CPU " + data.cpu;
319        }
320        if (data.cpu == null || data.cpu == undefined) {
321            list.push({name: 'State', value: `${state}`})
322        } else {
323            list.push({
324                name: 'State', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
325            <div style="white-space:pre-wrap">${state}</div>
326            <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="state-click" name="select" color="#7fa1e7" size="20"></lit-icon>
327            </div>`
328                    })
329        }
330        let processName = data.processName;
331        if (processName == null || processName == "" || processName.toLowerCase() == "null") {
332            processName = data.name;
333        }
334        list.push({name: 'Process', value: this.transferString(processName ?? "") + " [" + data.pid + "] "})
335        let cpu = new CpuStruct();
336        cpu.id = data.id;
337        cpu.startTime = data.startTime;
338        Promise.all([this.queryThreadWakeUpFromData(data.id!,data.startTime!,data.dur!),this.queryThreadWakeUpData(data.id!,data.startTime!,data.dur!)]).then((result)=>{
339            let fromBean = result[0]
340            let wakeUps = result[1];
341            if(fromBean != null && fromBean != undefined && fromBean.pid != 0 && fromBean.tid != 0){
342                list.push({
343                    name: 'wakeup from tid', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
344            <div style="white-space:pre-wrap">${fromBean.tid}</div>
345            <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-from"  class="wakeup-click"  name="select" color="#7fa1e7" size="20"></lit-icon>
346            </div>`
347                })
348            }
349            if(wakeUps != null){
350                for (let key in wakeUps) {
351                    list.push({
352                        name: 'wakeup tid', value: `<div style="margin-left: 5px;white-space: nowrap;display: flex;align-items: center">
353            <div style="white-space:pre-wrap">${wakeUps[key].tid}</div>
354            <lit-icon style="cursor:pointer;transform: scaleX(-1);margin-left: 5px" id="wakeup-${key}" class="wakeup-click" name="select" color="#7fa1e7" size="20"></lit-icon>
355            </div>`
356                    })
357                }
358            }
359            this.tbl!.dataSource = list
360            this.tbl?.shadowRoot?.querySelector("#state-click")?.addEventListener("click", () => {
361                //线程点击
362                if (scrollCallback) {
363                    scrollCallback(data)
364                }
365            })
366            this.tbl?.shadowRoot?.querySelector("#wakeup-from")?.addEventListener("click", (e) => {
367                //点击跳转,唤醒和被唤醒的 线程
368               if(fromBean && scrollWakeUp){
369                   scrollWakeUp({
370                       processId: fromBean.pid,
371                       tid: fromBean.tid,
372                       startTime: fromBean.ts,
373                   })
374               }
375            })
376            if(wakeUps){
377                for (let key in wakeUps) {
378                    this.tbl?.shadowRoot?.querySelector(`#wakeup-${key}`)?.addEventListener("click", (e) => {
379                        //点击跳转,唤醒和被唤醒的 线程
380                        let up = wakeUps[key];
381                        if(up && scrollWakeUp != undefined){
382                            scrollWakeUp({
383                                tid: up.tid,
384                                startTime: up.ts,
385                                processId:up.pid,
386                            })
387                        }
388                    })
389                }
390            }
391        })
392    }
393
394    /**
395     * 查询出 线程被唤醒的 线程信息
396     * @param data
397     */
398    async queryCPUWakeUpFromData(data: CpuStruct) {
399        let wb: WakeupBean | null = null
400        if (data.id == undefined || data.startTime == undefined) {
401            return null
402        }
403        let wakeupTimes = await queryWakeUpFromThread_WakeTime(data.id, data.startTime)
404        if (wakeupTimes != undefined && wakeupTimes.length > 0) {
405            let wakeupTime = wakeupTimes[0]
406            if (wakeupTime.wakeTs != undefined && wakeupTime.preRow != undefined && wakeupTime.wakeTs < wakeupTime.preRow) {
407                return null
408            }
409            if (wakeupTime.wakeTs == undefined) {
410                return null
411            }
412            let wakeupBeans = await queryWakeUpFromThread_WakeThread(wakeupTime.wakeTs)
413            if (wakeupBeans != undefined && wakeupBeans.length > 0) {
414                wb = wakeupBeans[0]
415                if (wb != null) {
416                    if (wakeupTime.wakeTs != undefined && wakeupTime.startTs != undefined) {
417                        wb.wakeupTime = wakeupTime.wakeTs - wakeupTime.startTs
418                    }
419                    wb.schedulingLatency = (data.startTime || 0) - (wb.wakeupTime || 0)
420                    if (wb.process == null) {
421                        wb.process = wb.thread;
422                    }
423                    if (wb.pid == undefined) {
424                        wb.pid = wb.tid;
425                    }
426                    wb.schedulingDesc = INPUT_WORD
427                }
428            }
429        }
430        return wb
431    }
432
433    /**
434     * 查询出 线程唤醒了哪些线程信息
435     * @param data
436     */
437    async queryThreadWakeUpFromData(itid: number, startTime: number,dur:number) : Promise<WakeupBean|undefined> {
438        let wakeUps = await queryThreadWakeUpFrom(itid, startTime,dur)
439        if (wakeUps != undefined && wakeUps.length > 0) {
440            return wakeUps[0];
441        }
442    }
443    /**
444     * 查询出 线程唤醒了哪些线程信息
445     * @param data
446     */
447    async queryThreadWakeUpData(itid: number, startTime: number,dur:number) : Promise<Array<WakeupBean>> {
448        let list :Array<WakeupBean> = [];
449        if (itid == undefined || startTime == undefined) {
450            return list
451        }
452        let wakeUps = await queryThreadWakeUp(itid, startTime,dur)//  3,4835380000
453        if (wakeUps != undefined && wakeUps.length > 0) {
454            list.push(...wakeUps)
455        }
456        return list
457    }
458
459    initCanvas(): HTMLCanvasElement | null {
460        let canvas = this.shadowRoot!.querySelector<HTMLCanvasElement>("#rightDraw")
461        let width = getComputedStyle(this.tbl!).getPropertyValue("width")
462        let height = getComputedStyle(this.tbl!).getPropertyValue("height")
463        if (canvas != null) {
464            canvas.width = Math.round(Number(width.replace("px", "")) * this.dpr)
465            canvas.height = Math.round(Number(height.replace("px", "")) * this.dpr)
466            canvas.style.width = width
467            canvas.style.height = height
468            canvas.getContext("2d")!.scale(this.dpr, this.dpr)
469        }
470        SpApplication.skinChange = (val: boolean) => {
471            this.drawRight(canvas, this.weakUpBean!)
472        }
473        return canvas
474    }
475
476    drawRight(cavs: HTMLCanvasElement | null, wakeupBean: WakeupBean | null) {
477        if (cavs == null) {
478            return
479        }
480        let context = cavs.getContext("2d");
481        if (context != null) {
482            //绘制竖线
483            if (document.querySelector<SpApplication>("sp-application")!.dark) {
484                context.strokeStyle = "#ffffff";
485                context.fillStyle = "#ffffff";
486            } else {
487                context.strokeStyle = "#000000";
488                context.fillStyle = "#000000";
489            }
490            context.lineWidth = 2;
491            context.moveTo(10, 15);
492            context.lineTo(10, 125);
493            context.stroke();
494            //绘制菱形
495            context.lineWidth = 1;
496            context.beginPath()
497            context.moveTo(10, 30);
498            context.lineTo(4, 40);
499            context.lineTo(10, 50);
500            context.lineTo(16, 40);
501            context.lineTo(10, 30);
502            context.closePath()
503            context.fill()
504            context.font = 12 + "px sans-serif";
505            //绘制wake up 文字
506            let strList = []
507            strList.push("wakeup @ " + getTimeString(wakeupBean?.wakeupTime || 0) + " on CPU " + wakeupBean?.cpu + " by")
508            strList.push("P:" + wakeupBean?.process + " [ " + wakeupBean?.pid + " ]")
509            strList.push("T:" + wakeupBean?.thread + " [ " + wakeupBean?.tid + " ]")
510            strList.forEach((str, index) => {
511                if (context != null) {
512                    context.fillText(str, 40, 40 + 16 * index)
513                }
514            })
515            //绘制左右箭头
516            context.lineWidth = 2;
517            context.lineJoin = "bevel"
518            context.moveTo(10, 95)
519            context.lineTo(20, 90)
520            context.moveTo(10, 95)
521            context.lineTo(20, 100)
522            context.moveTo(10, 95)
523            context.lineTo(80, 95)
524            context.lineTo(70, 90)
525            context.moveTo(80, 95)
526            context.lineTo(70, 100)
527            context.stroke();
528            //绘制latency
529            context.font = 12 + "px sans-serif";
530            context.fillText("Scheduling latency:" + getTimeString(wakeupBean?.schedulingLatency || 0)
531                , 90, 100)
532            //绘制最下方提示语句
533            context.font = 10 + "px sans-serif";
534            INPUT_WORD.split("\n").forEach((str, index) => {
535                context?.fillText(str, 90, 120 + 12 * index)
536            })
537
538        }
539    }
540
541    transferString(str:string): string{
542        let s = ""
543        if(str.length == 0){
544            return "";
545        }
546        s = str.replace(/&/g,"&amp;")
547        s = s.replace(/</g,"&lt;")
548        s = s.replace(/>/g,"&gt;")
549        s = s.replace(/ /g,"&nbsp;")
550        s = s.replace(/\'/g,"&#39;")
551        s = s.replace(/\"/g,"&#quat;")
552        // s = s.replace(/(/g,"&amp;")
553        // s = s.replace(/)/g,"&amp;")
554        return s
555    }
556
557    initElements(): void {
558        this.tbl = this.shadowRoot?.querySelector<LitTable>('#selectionTbl');
559        this.tbl?.addEventListener("column-click", (ev: any) => {
560        })
561        this.addTableObserver()
562    }
563
564    addTableObserver() {
565        let MutationObserver = window.MutationObserver
566        this.tableObserver = new MutationObserver((list) => {
567            if (this.tbl) {
568                let width = getComputedStyle(this.tbl).getPropertyValue("width")
569                let height = getComputedStyle(this.tbl).getPropertyValue("height")
570            }
571        })
572        let selector = this.shadowRoot?.querySelector(".left-table");
573        this.tableObserver?.observe(selector!, {attributes: true, attributeFilter: ['style'], attributeOldValue: true})
574    }
575
576    initHtml(): string {
577        return `
578        <style>
579            .current-title{
580                width: 100%;
581                display: flex;
582                top: 0;
583                background: var(--dark-background,#ffffff);
584                position: sticky;
585            }
586            .current-title h2{
587                width: 50%;
588                padding: 0 10px;
589                font-size: 16px;
590                font-weight: 400;
591                visibility: visible;
592            }
593            .bottom-scroll-area{
594                display: flex;
595                height: auto;
596                overflow-y: auto;
597            }
598            .left-table{
599                width: 50%;
600                padding: 0 10px;
601            }
602            .right-table{
603                width: 50%;
604            }
605        </style>
606        <div style="width: 100%;height: auto;position: relative">
607            <div class="current-title">
608                <h2 id="leftTitle"></h2>
609                <h2 id="rightTitle">Scheduling Latency</h2>
610            </div>
611            <div class="bottom-scroll-area">
612                <div class="left-table">
613                    <lit-table id="selectionTbl" no-head style="height: auto">
614                        <lit-table-column title="name" data-index="name" key="name" align="flex-start"  width="180px">
615                            <template><div>{{name}}</div></template>
616                        </lit-table-column>
617                        <lit-table-column title="value" data-index="value" key="value" align="flex-start" >
618                            <template><div style="display: flex;">{{value}}</div></template>
619                        </lit-table-column>
620                    </lit-table>
621                </div>
622                <div class="right-table">
623                    <canvas id="rightDraw" style="width: 100%;height: 100%;"></canvas>
624                </div>
625            </div>
626        </div>
627        `;
628    }
629}
630