• 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 {TimeRange} from "../timer-shaft/RangeRuler.js";
18import '../../../../base-ui/icon/LitIcon.js'
19import {Rect} from "../timer-shaft/Rect.js";
20import {BaseStruct} from "../../../bean/BaseStruct.js";
21import {SpSystemTrace} from "../../SpSystemTrace.js";
22import {ns2x} from "../TimerShaftElement.js";
23import {TraceRowObject} from "./TraceRowObject.js";
24import {LitCheckBox} from "../../../../base-ui/checkbox/LitCheckBox.js";
25
26export class RangeSelectStruct {
27    startX: number | undefined
28    endX: number | undefined
29    startNS: number | undefined
30    endNS: number | undefined
31}
32
33@element('trace-row')
34export class TraceRow<T extends BaseStruct> extends BaseElement {
35    static ROW_TYPE_CPU = "cpu"
36    static ROW_TYPE_CPU_FREQ = "cpu-freq"
37    static ROW_TYPE_FPS = "fps"
38    static ROW_TYPE_PROCESS = "process"
39    static ROW_TYPE_THREAD = "thread"
40    static ROW_TYPE_MEM = "mem"
41    static ROW_TYPE_HEAP = "heap"
42    static ROW_TYPE_FUNC = "func"
43    static range: TimeRange | undefined | null;
44    static rangeSelectObject: RangeSelectStruct | undefined
45    public obj: TraceRowObject<any> | undefined | null;
46    public must: boolean = true;
47    public dataList: undefined | Array<T>;
48    public dataListCache: Array<T> = [];
49    public c: CanvasRenderingContext2D | null = null;
50    public describeEl: Element | null | undefined;
51    public canvas: HTMLCanvasElement | null | undefined;
52    public canvasContainer: HTMLDivElement | null | undefined;
53    public tipEL: HTMLDivElement | null | undefined;
54    public checkBoxEL: LitCheckBox | null | undefined;
55    public onDrawHandler: ((ctx: CanvasRenderingContext2D) => void) | undefined | null
56    public onThreadHandler: ((ctx: CanvasRenderingContext2D, useCache: boolean) => void) | undefined | null
57    public onItemDrawHandler: ((ctx: CanvasRenderingContext2D, data: T, preData?: T, nextData?: T) => void) | undefined | null
58    public supplier: (() => Promise<Array<T>>) | undefined | null
59    // @ts-ignore
60    offscreen: OffscreenCanvas | undefined;
61    canvasWidth = 0
62    canvasHeight = 0
63    isHover: boolean = false;
64    hoverX: number = 0;
65    hoverY: number = 0;
66    index: number = 0;
67    dpr = window.devicePixelRatio || 1;
68    private rootEL: HTMLDivElement | null | undefined;
69    private nameEL: HTMLLabelElement | null | undefined;
70    private isLoading: boolean = false
71
72    constructor(args: { alpha: boolean, contextId: string, isOffScreen: boolean }) {
73        super(args);
74    }
75
76    static get observedAttributes() {
77        return ["folder", "name", "expansion", "children", "height", "row-type", "row-id", "row-parent-id", "sleeping",
78            "check-type"
79        ];
80    }
81
82    public _frame: Rect | undefined;
83
84    get frame(): Rect {
85        if (this._frame) {
86            this._frame.width = (this.parentElement?.clientWidth || 0) - 248;
87            this._frame.height = this.canvas?.clientHeight || 40;
88            return this._frame;
89        } else {
90            this._frame = new Rect(0, 0, (this.parentElement?.clientWidth || 0) - 248, this.canvas?.clientHeight || 40);
91            return this._frame;
92        }
93    }
94
95    set frame(f: Rect) {
96        this._frame = f;
97    }
98
99    private _rangeSelect: boolean = false;
100
101    get rangeSelect(): boolean {
102        return this._rangeSelect;
103    }
104
105    set rangeSelect(value: boolean) {
106        this._rangeSelect = value;
107    }
108
109    get sleeping(): boolean {
110        return this.hasAttribute("sleeping");
111    }
112
113    set sleeping(value: boolean) {
114        if (value) {
115            this.setAttribute("sleeping", "")
116        } else {
117            this.removeAttribute("sleeping")
118            this.draw();
119        }
120    }
121
122    get rowType(): string | undefined | null {
123        return this.getAttribute("row-type");
124    }
125
126    set rowType(val) {
127        this.setAttribute("row-type", val || "")
128    }
129
130    get rowId(): string | undefined | null {
131        return this.getAttribute("row-id");
132    }
133
134    set rowId(val) {
135        this.setAttribute("row-id", val || "")
136    }
137
138    get rowParentId(): string | undefined | null {
139        return this.getAttribute("row-parent-id");
140    }
141
142    set rowParentId(val) {
143        this.setAttribute("row-parent-id", val || "")
144    }
145
146    set rowHidden(val: boolean) {
147        if (val) {
148            this.setAttribute("row-hidden", "")
149        } else {
150            this.removeAttribute("row-hidden")
151        }
152    }
153
154    get name(): string {
155        return this.getAttribute("name") || ""
156    }
157
158    set name(value: string) {
159        this.setAttribute("name", value)
160    }
161
162    get folder(): boolean {
163        return this.hasAttribute("folder");
164    }
165
166    set folder(value: boolean) {
167        if (value) {
168            this.setAttribute("folder", '')
169        } else {
170            this.removeAttribute('folder')
171        }
172    }
173
174    get expansion(): boolean {
175        return this.hasAttribute("expansion")
176    }
177
178    set expansion(value) {
179        if (value) {
180            this.setAttribute("expansion", '');
181        } else {
182            this.removeAttribute('expansion')
183        }
184        this.parentElement?.querySelectorAll<TraceRow<any>>(`trace-row[row-parent-id='${this.rowId}']`).forEach(it => {
185            if (this.expansion) {
186                it.rowHidden = false
187            } else {
188                it.rowHidden = true
189            }
190        })
191        this.dispatchEvent(new CustomEvent("expansion-change", {
192            detail: {
193                expansion: this.expansion,
194                rowType: this.rowType,
195                rowId: this.rowId,
196                rowParentId: this.rowParentId
197            }
198        }))
199    }
200
201    set tip(value: string) {
202        if (this.tipEL) {
203            this.tipEL.innerHTML = value;
204        }
205    }
206
207    get checkType(): string {
208        return this.getAttribute("check-type") || "";
209    }
210
211    set checkType(value: string) {
212        this.setAttribute("check-type", value);
213    }
214
215    initElements(): void {
216        this.rootEL = this.shadowRoot?.querySelector('.root')
217        this.checkBoxEL = this.shadowRoot?.querySelector<LitCheckBox>('.lit-check-box')
218        this.describeEl = this.shadowRoot?.querySelector('.describe')
219        this.nameEL = this.shadowRoot?.querySelector('.name')
220        this.canvas = this.shadowRoot?.querySelector('.panel')
221        this.canvasContainer = this.shadowRoot?.querySelector('.panel-container')
222        this.tipEL = this.shadowRoot?.querySelector('.tip')
223        this.describeEl?.addEventListener('click', () => {
224            if (this.folder) {
225                this.expansion = !this.expansion
226            }
227        })
228    }
229
230    initCanvas() {
231        if (this.canvas) {
232            if (!this.args.isOffScreen) {
233                this.c = this.canvas?.getContext(this.args.contextId, {alpha: this.args.alpha});
234            }
235
236            if (this.shadowRoot?.host.clientWidth == 0) {
237                let preCanvas = this.previousElementSibling?.shadowRoot?.querySelector<HTMLCanvasElement>("canvas");
238                if (preCanvas) {
239                    this.canvasWidth = preCanvas.width;
240                    this.canvasHeight = preCanvas.height;
241                    this.canvas.width = preCanvas.width;
242                    this.canvas.height = preCanvas.height;
243                    this.canvas.style.width = preCanvas.style.width;
244                    this.canvas.style.height = preCanvas.style.height;
245                    if (this.c instanceof CanvasRenderingContext2D && !this.args.isOffScreen) {
246                        this.c?.scale(this.dpr, this.dpr);
247                        this.c?.translate(0, 0)
248                    }
249                }
250            } else {
251                let oldWidth = (this.shadowRoot!.host.clientWidth || 0) - (this.describeEl?.clientWidth || 248) - SpSystemTrace.scrollViewWidth - 1;
252                let oldHeight = parseInt(this.getAttribute("height") || '40') //this.shadowRoot!.host.clientHeight || 0;
253                this.rootEL!.style.height = `${this.getAttribute("height") || '40'}px`
254                this.canvas.style.width = '100%';
255                this.canvas.style.height = oldHeight + 'px';
256                this.canvas.width = Math.floor(oldWidth * this.dpr);
257                this.canvas.height = Math.floor(oldHeight * this.dpr);
258                this.canvasWidth = this.canvas.width;
259                this.canvasHeight = this.canvas.height;
260                if (this.c instanceof CanvasRenderingContext2D && !this.args.isOffScreen) {
261                    this.c?.scale(this.dpr, this.dpr);
262                    this.c?.translate(0, 0)
263                }
264            }
265            if (this.args.isOffScreen) {
266                // @ts-ignore
267                this.offscreen = this.canvas.transferControlToOffscreen();
268            }
269        }
270    }
271
272    updateWidth(width: number) {
273        let dpr = window.devicePixelRatio || 1;
274        this.canvasWidth = Math.round((width - (this.describeEl?.clientWidth || 248) - SpSystemTrace.scrollViewWidth) * dpr);
275        this.canvasHeight = Math.round((this.shadowRoot!.host.clientHeight || 40) * dpr);
276        if (this.args.isOffScreen) {
277            this.draw(true);
278            return;
279        }
280        this.canvas!.width = width - (this.describeEl?.clientWidth || 248) - SpSystemTrace.scrollViewWidth;
281        this.canvas!.height = this.shadowRoot!.host.clientHeight || 40;
282        let oldWidth = this.canvas!.width;
283        let oldHeight = this.canvas!.height;
284        this.canvas!.width = Math.round((oldWidth) * dpr);
285        this.canvas!.height = Math.round(oldHeight * dpr);
286        this.canvas!.style.width = oldWidth + 'px';
287        this.canvas!.style.height = oldHeight + 'px';
288        if (this.c instanceof CanvasRenderingContext2D) {
289            this.c?.scale(dpr, dpr);
290        }
291    }
292
293    connectedCallback() {
294        this.checkBoxEL!.onchange = (ev: any) => {
295            if (!ev.target.checked) {
296                this.rangeSelect = false;
297                this.draw();
298            } else {
299                this.rangeSelect = true;
300                this.draw();
301            }
302        }
303        this.initCanvas();
304    }
305
306    onMouseHover(x: number, y: number, tip: boolean = true): T | undefined | null {
307        if (this.dataListCache && this.dataListCache.length > 0) {
308            return this.dataListCache.find(it => (it.frame && Rect.contains(it.frame, x, y)));
309        } else if (this.dataList && this.dataList.length > 0) {
310            return this.dataList.find(it => (it.frame && Rect.contains(it.frame, x, y)));
311        }
312        if (this.tipEL) {
313            this.tipEL.style.display = 'none';
314        }
315        return null;
316    }
317
318    setTipLeft(x: number, struct: any) {
319        if (struct == null && this.tipEL) {
320            this.tipEL.style.display = 'none';
321            return
322        }
323        if (this.tipEL) {
324            this.tipEL.style.display = 'flex';
325            if (x + this.tipEL.clientWidth > (this.canvas?.style.width.replace("px", "") || 0)) {
326                this.tipEL.style.transform = `translateX(${x - this.tipEL.clientWidth - 1}px)`;
327            } else {
328                this.tipEL.style.transform = `translateX(${x}px)`;
329            }
330        }
331    }
332
333    onMouseLeave(x: number, y: number) {
334        if (this.tipEL) {
335            this.tipEL.style.display = 'none';
336        }
337    }
338
339    draw(useCache: boolean = false) {
340        if (this.sleeping) {
341            return;
342        }
343        if (!this.dataList) {
344            if (this.supplier && !this.isLoading) {
345                this.isLoading = true;
346                if (this.supplier) {
347                    let promise = this.supplier();
348                    if (promise) {
349                        promise.then(res => {
350                            this.dataList = res
351                            this.isLoading = false;
352                            this.draw(false);
353                        })
354                    } else {
355                        this.dataList = [];
356                        this.isLoading = false;
357                        this.draw(false);
358                    }
359
360                }
361            }
362            if (this.c && this.c instanceof CanvasRenderingContext2D) {
363                this.c.clearRect(0, 0, this.canvas?.clientWidth || 0, this.canvas?.clientHeight || 0)
364                this.c.beginPath();
365                this.drawLines(this.c!);
366                this.drawSelection(this.c!);
367                this.c.fillStyle = window.getComputedStyle(this.nameEL!, null).getPropertyValue("color");
368                this.c.fillText("Loading...", this.frame.x + 10, Math.ceil(this.frame.y + this.frame.height / 2))
369                this.c.stroke();
370                this.c.closePath();
371            }
372        } else {
373            if (this.onDrawHandler && this.dataList && this.c instanceof CanvasRenderingContext2D) {
374                if (this.c && TraceRow.range) {
375                    this.clearCanvas(this.c!)
376                    this.c.beginPath();
377                    this.drawLines(this.c!);
378                    this.onDrawHandler!(this.c!)
379                    this.drawSelection(this.c!);
380                    this.c.closePath();
381                }
382            } else if (this.onItemDrawHandler) {
383                if (this.c && this.c instanceof CanvasRenderingContext2D && TraceRow.range) {
384                    this.clearCanvas(this.c!)
385                    this.c.beginPath();
386                    this.drawLines(this.c!);
387                    if (this.onItemDrawHandler && this.dataList) {
388                        for (let i = 0; i < this.dataList.length; i++) {
389                            let preData = this.dataList[i - 1] || undefined;
390                            let data = this.dataList[i];
391                            let nextData = this.dataList[i + 1] || undefined;
392                            this.onItemDrawHandler(this.c!, data, preData, nextData)
393                        }
394                    }
395                    this.drawSelection(this.c!);
396                    this.c.closePath();
397                }
398            } else if (this.onThreadHandler) {
399                this.onThreadHandler!(this.c!, useCache)
400            }
401        }
402    }
403
404    drawObject() {
405        if (!this.obj) return;
406        if (!this.obj.dataList) {
407            if (this.obj.supplier && !this.obj.isLoading) {
408                this.obj.isLoading = true;
409                if (this.obj.supplier) {
410                    let promise = this.obj.supplier();
411                    if (promise) {
412                        promise.then(res => {
413                            console.log(res);
414                            this.obj!.dataList = res
415                            this.obj!.isLoading = false;
416                            this.drawObject();
417                        })
418                    } else {
419                        this.obj.dataList = [];
420                        this.obj.isLoading = false;
421                        this.drawObject();
422                    }
423
424                }
425            }
426            window.requestAnimationFrame(() => {
427                if (this.c && this.c instanceof CanvasRenderingContext2D) {
428                    this.c.clearRect(0, 0, this.canvas?.clientWidth || 0, this.canvas?.clientHeight || 0)
429                    this.c.beginPath();
430                    this.c.lineWidth = 1;
431                    this.c.strokeStyle = "#dadada"
432                    if (TraceRow.range) {
433                        TraceRow.range.xs.forEach(it => {
434                            // @ts-ignore
435                            this.c!.moveTo(Math.floor(it), 0)
436                            // @ts-ignore
437                            this.c!.lineTo(Math.floor(it), this.shadowRoot?.host.clientHeight || 0)
438                        })
439                    }
440                    this.drawSelection(this.c!);
441                    this.c.fillStyle = window.getComputedStyle(this.nameEL!, null).getPropertyValue("color");
442                    this.c.fillText("Loading...", this.frame.x + 10, Math.ceil(this.frame.y + this.frame.height / 2))
443                    this.c.stroke();
444                    this.c.closePath();
445                }
446            })
447        } else {
448            if (this.obj.onDrawHandler && this.obj.dataList) {
449                window.requestAnimationFrame(() => {
450                    if (this.c && TraceRow.range) {
451                        this.clearCanvas(this.c!)
452                        // @ts-ignore
453                        this.c.beginPath();
454                        this.drawLines(<CanvasRenderingContext2D>this.c!);
455                        this.obj!.onDrawHandler!(this.c!)
456                        this.drawSelection(<CanvasRenderingContext2D>this.c!);
457                        // @ts-ignore
458                        this.c.closePath();
459                    }
460                })
461            } else if (this.obj.onThreadHandler) {
462                this.obj.onThreadHandler(this, this.c!)
463            }
464        }
465    }
466
467    clearCanvas(ctx: CanvasRenderingContext2D) {
468        if (ctx) {
469            ctx.clearRect(0, 0, this.canvas?.clientWidth || 0, this.canvas?.clientHeight || 0)
470        }
471    }
472
473    drawLines(ctx: CanvasRenderingContext2D) {
474        if (ctx) {
475            ctx.lineWidth = 1;
476            ctx.strokeStyle = this.getLineColor();
477            TraceRow.range?.xs.forEach(it => {
478                ctx.moveTo(Math.floor(it), 0)
479                ctx.lineTo(Math.floor(it), this.shadowRoot?.host.clientHeight || 0)
480            })
481            ctx.stroke();
482        }
483    }
484
485    getLineColor() {
486        return window.getComputedStyle(this.rootEL!, null).getPropertyValue("border-bottom-color")
487    }
488
489    drawSelection(ctx: CanvasRenderingContext2D) {
490        if (this.rangeSelect) {
491            TraceRow.rangeSelectObject!.startX = Math.floor(ns2x(TraceRow.rangeSelectObject!.startNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, this.frame));
492            TraceRow.rangeSelectObject!.endX = Math.floor(ns2x(TraceRow.rangeSelectObject!.endNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, this.frame));
493            if (ctx) {
494                ctx.globalAlpha = 0.5
495                ctx.fillStyle = "#666666"
496                ctx.fillRect(TraceRow.rangeSelectObject!.startX!, this.frame.y, TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!, this.frame.height)
497                ctx.globalAlpha = 1
498            }
499        }
500    }
501
502    isInTimeRange(startTime: number, duration: number): boolean {
503        return ((startTime || 0) + (duration || 0) > (TraceRow.range?.startNS || 0) && (startTime || 0) < (TraceRow.range?.endNS || 0));
504    }
505
506    attributeChangedCallback(name: string, oldValue: string, newValue: string) {
507        switch (name) {
508            case "name":
509                if (this.nameEL) {
510                    this.nameEL.textContent = newValue;
511                    this.nameEL.title = newValue;
512                }
513                break;
514            case "height":
515                if (newValue != oldValue) {
516                    if (!this.args.isOffScreen) {
517                        this.initCanvas();
518                    }
519                }
520                break;
521            case "check-type":
522                if (newValue === "check") {
523                    this.checkBoxEL?.setAttribute("checked", "");
524                } else {
525                    this.checkBoxEL?.removeAttribute("checked");
526                }
527                break;
528        }
529    }
530
531    initHtml(): string {
532        return `
533<style>
534*{
535    box-sizing: border-box;
536}
537:host(:not([row-hidden])){
538    box-sizing: border-box;
539    display: flex;
540    flex-direction: column;
541    width: 100%;
542}
543:host([row-hidden]){
544    width: 100%;
545    display: none;
546}
547.root{
548    height: 40px;
549    width: 100%;
550    display: grid;
551    grid-template-rows: 100%;
552    grid-template-columns: 248px 1fr;
553    border-bottom: 1px solid var(--dark-border1,#dadada);
554    box-sizing: border-box;
555}
556.describe{
557    box-sizing: border-box;
558    border-right: 1px solid var(--dark-border1,#c9d0da);
559    background-color: transparent;
560    align-items: center;
561    position: relative;
562}
563.panel{
564    width: 100%;
565    height: 100%;
566    overflow: visible;
567    background-color: transparent;
568}
569.panel-container{
570    width: 100%;
571    height: 100%;
572    position: relative;
573    pointer-events: none;
574}
575.tip{
576    position:absolute;
577    top: 0;
578    left: 0;
579    height: 100%;
580    background-color: white;
581    border: 1px solid #f9f9f9;
582    width: auto;
583    font-size: 8px;
584    color: #50809e;
585    flex-direction: column;
586    justify-content: center;
587    align-items: flex-start;
588    padding: 2px 10px;
589    display: none;
590    user-select: none;
591}
592.name{
593    color: var(--dark-color1,#4b5766);
594    margin-left: 10px;
595    font-size: .9rem;
596    font-weight: normal;
597    width: 100%;
598    max-height: 100%;
599    text-align: left;
600    overflow: hidden;
601    user-select: none;
602}
603.icon{
604    color: var(--dark-color1,#151515);
605    margin-left: 10px;
606}
607.describe:hover {
608    cursor: pointer;
609}
610:host([folder]) .describe:hover > .icon{
611    color:#ecb93f;
612    margin-left: 10px;
613}
614:host([folder]){
615    background-color: var(--dark-background1,#f5fafb);
616}
617:host([folder]) .icon{
618    display: flex;
619}
620:host(:not([folder])){
621    background-color: var(--dark-background,#FFFFFF);
622}
623:host(:not([folder]):not([children])) {
624}
625:host(:not([folder]):not([children])) .icon{
626    display: none;
627}
628:host(:not([folder])[children]) .icon{
629    visibility: hidden;
630    color:#fff
631}
632
633:host(:not([folder])[children]) .name{
634}
635:host([expansion]) {
636    background-color: var(--bark-expansion,#0C65D1);
637}
638:host([expansion]) .name,:host([expansion]) .icon{
639    color: #fff;
640}
641:host([expansion]) .describe{
642    border-right: 0px;
643}
644:host([expansion]:not(sleeping)) .panel-container{
645    display: none;
646}
647:host([expansion]) .children{
648    flex-direction: column;
649    width: 100%;
650}
651:host([expansion]) .icon{
652    transform: rotateZ(0deg);
653}
654:host(:not([expansion])) .children{
655    display: none;
656    flex-direction: column;
657    width: 100%;
658}
659:host(:not([expansion])) .icon{
660    transform: rotateZ(-90deg);
661}
662:host([sleeping]) .describe{
663    display: none;
664}
665:host([sleeping]) .panel-container{
666    display: none;
667}
668:host([sleeping]) .children{
669    display: none;
670}
671:host(:not([sleeping])) .describe{
672    display: flex;;
673}
674:host(:not([sleeping])) .panel-container{
675    display: flex;
676}
677:host(:not([sleeping])) .children{
678    display: flex;
679}
680
681 :host([check-type]) .lit-check-box{
682    display: none;
683}
684:host(:not([check-type])) .lit-check-box{
685    display: none;
686}
687</style>
688<div class="root">
689    <div class="describe">
690        <lit-icon class="icon" name="caret-down" size="13"></lit-icon>
691        <label class="name"></label>
692        <lit-check-box class="lit-check-box"></lit-check-box>
693    </div>
694    <div class="panel-container">
695        <canvas class="panel"></canvas>
696        <div class="tip">
697            P:process [1573]<br>
698            T:Thread [675]
699        </div>
700    </div>
701</div>
702        `;
703    }
704
705}
706