• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2025 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 { int32 } from "@koalaui/compat"
17
18/**
19 * Adds statistics for constructing/disposing of the TreeNode instances.
20 * It is disabled by default because collecting such data affects performance.
21 */
22const DEBUG_WITH_NODE_STATS = false
23
24export class KoalaProfiler {
25    private static readonly map = DEBUG_WITH_NODE_STATS
26        ? new Map<int32, Set<Object>>()
27        : undefined
28
29    static nodeCreated(nodeType: int32, node: Object) {
30        if (KoalaProfiler.map === undefined) return
31        let set = KoalaProfiler.map!.get(nodeType)
32        if (set === undefined) {
33            set = new Set<Object>()
34            KoalaProfiler.map!.set(nodeType, set)
35        }
36        set.add(node)
37    }
38
39    static nodeDisposed(nodeType: int32, node: Object) {
40        if (KoalaProfiler.map === undefined) return
41        let set = KoalaProfiler.map!.get(nodeType)
42        if (set === undefined) throw new Error("node never existed")
43        if (!set.delete(node)) console.log("node is already disposed")
44    }
45
46    public static counters: KoalaProfiler | undefined = undefined
47
48    private invalidations = 0
49    private computes = 0
50    private builds = 0
51    private nodes = 0
52    private realDraws = 0
53    private cachedDraws = 0
54    private measures = 0
55    private layouts = 0
56    private frames = 0
57    private lastTime = 0.0
58    private lastFPS = 0
59    private updateEnterTime = 0.0
60    private updateExitTime = 0.0
61    private updateTime = 0.0
62    private buildEnterTime = 0.0
63    private buildExitTime = 0.0
64    private buildTime = 0.0
65    private layoutEnterTime = 0.0
66    private layoutExitTime = 0.0
67    private layoutTime = 0.0
68    private drawEnterTime = 0.0
69    private drawExitTime = 0.0
70    private drawTime = 0.0
71    private updatableStates = 0
72    private mutableStates = 0
73    private computableValues = 0
74
75    static enable() {
76        KoalaProfiler.counters = new KoalaProfiler()
77    }
78
79    static disable() {
80        KoalaProfiler.counters = undefined
81    }
82
83    static enabled(): boolean {
84        return KoalaProfiler.counters != undefined
85    }
86
87    reset() {
88        this.invalidations = 0
89        this.computes = 0
90        this.builds = 0
91        this.nodes = 0
92        this.realDraws = 0
93        this.cachedDraws = 0
94        this.layouts = 0
95        this.measures = 0
96        this.updateEnterTime = 0
97        this.updateExitTime = 0
98        this.updatableStates = 0
99        this.mutableStates = 0
100        this.computableValues = 0
101    }
102
103    report() {
104        console.log(this.getReport())
105    }
106
107    getReport(): string {
108        const updateTime = Math.round(1000 * (this.updateExitTime - this.updateEnterTime))
109        const buildTime = Math.round(1000 * (this.buildExitTime - this.buildEnterTime))
110        const layoutTime = Math.round(1000 * (this.layoutExitTime - this.layoutEnterTime))
111        const drawTime = Math.round(1000 * (this.drawExitTime - this.drawEnterTime))
112        if (this.updateTime < updateTime) this.updateTime = updateTime
113        if (this.buildTime < buildTime) this.buildTime = buildTime
114        if (this.layoutTime < layoutTime) this.layoutTime = layoutTime
115        if (this.drawTime < drawTime) this.drawTime = drawTime
116
117        // TODO: OHOS does not properly handle \n in template literals
118        const array = Array.of<string>(
119            `invalidations: ${this.invalidations}`,
120            `modified states: ${this.mutableStates}/${this.updatableStates} + ${this.computableValues}`,
121            `update states (mks): ${this.updateTime} / ${updateTime}`,
122            `build root node (mks): ${this.buildTime} / ${buildTime}`,
123            `layout view (mks): ${this.layoutTime} / ${layoutTime}`,
124            `draw view (mks): ${this.drawTime} / ${drawTime}`,
125            `computes: ${this.computes}`,
126            `builds: ${this.builds}`,
127            `nodes: ${this.nodes}`,
128            `realDraws: ${this.realDraws}`,
129            `cachedDraws: ${this.cachedDraws}`,
130            `measures: ${this.measures}`,
131            `layouts: ${this.layouts}`,
132            `FPS: ${this.lastFPS}`,
133        )
134        KoalaProfiler.map?.forEach((set:Set<Object>, kind:int32) => {
135            if (set.size > 0) array.push(kind + ":" + set.size)
136        })
137        return array.join("\n")
138    }
139
140    invalidation() { this.invalidations++ }
141    compute() { this.computes++ }
142    build() { this.builds++ }
143    node() { this.nodes++ }
144    realDraw() { this.realDraws++ }
145    cachedDraw() { this.cachedDraws++ }
146    layout() {  this.layouts++ }
147    measure() { this.measures++ }
148    frame(ms: number) {
149        if (ms - this.lastTime <= 1000) {
150            this.frames++
151        } else {
152            this.lastFPS = Math.round(this.frames * 1000 / (ms - this.lastTime)) as int32
153            this.frames = 1
154            this.lastTime = ms
155        }
156    }
157    buildRootEnter() {
158        this.buildEnterTime = Date.now()
159    }
160    buildRootExit() {
161        this.buildExitTime = Date.now()
162    }
163    layoutEnter() {
164        this.layoutEnterTime = Date.now()
165    }
166    layoutExit() {
167        this.layoutExitTime = Date.now()
168    }
169    drawEnter() {
170        this.drawEnterTime = Date.now()
171    }
172    drawExit() {
173        this.drawExitTime = Date.now()
174    }
175    updateSnapshotEnter() {
176        this.updateEnterTime = Date.now()
177    }
178    updateSnapshotExit() {
179        this.updateExitTime = Date.now()
180    }
181    updateSnapshot(modified: int32, all?: int32) {
182        if (all === undefined) {
183            this.computableValues = modified - this.mutableStates
184
185        } else {
186            this.mutableStates = modified
187            this.updatableStates = all
188        }
189    }
190}
191