idlize

Форк
0
405 строк · 12.5 Кб
1
/*
2
 * Copyright (c) 2022-2023 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
import { float64, int32 } from "@koalaui/compat"
17

18
/**
19
 * A probe to measure performance.
20
 *
21
 * A probe can measure performance of any activity which has an entry and an exit points.
22
 * Such activity can be a method call, or a sequence of actions, possibly asynchronous.
23
 *
24
 * A probe which has been entered and exited is considered a performed probe (see {@link MainPerfProbe.probePerformed}).
25
 * A probe can be entered recursively. When all the recursive calls exits the probe becomes a performed probe.
26
 *
27
 * All performing probes form a hierarchy which is rooted at the main probe (see {@link enterMainPerfProbe}).
28
 * A last started probe (see {@link MainPerfProbe.enterProbe}) which has not yet performed becomes a parent
29
 * for the next started probe. It's the responsibility of the API caller to keep this parent-child relationship valid,
30
 * that is: a child probe should exit before its parent probe exits.
31
 *
32
 * Statistics gathered by a probe:
33
 * - call count
34
 * - recursive call count
35
 * - total time and percentage relative to the main (root) probe
36
 */
37
export interface PerfProbe {
38
    /**
39
     * The name of the probe.
40
     */
41
    readonly name: string
42

43
    /**
44
     * Whether this is a dummy probe which does not measure (a noop).
45
     *
46
     * @see MainPerfProbe.getProbe
47
     */
48
    readonly dummy: boolean
49

50
    /**
51
     * Exists the probe.
52
     *
53
     * @param log log the gathered statistics.
54
     * @see MainPerfProbe.enterProbe
55
     */
56
    exit(log: boolean|undefined): void
57

58
    /**
59
     * Cancels measuring the probe and its children probes.
60
     */
61
    cancel(): void
62

63
    /**
64
     * Whether the probe was canceled.
65
     */
66
    readonly canceled: boolean
67
}
68

69
/**
70
 * The main (root) {@link PerfProbe}.
71
 *
72
 * This probe is used to enter the main activity.
73
 *
74
 * Calling {@link PerfProbe.cancel} removes the main probe and disposes all its resources.
75
 *
76
 * Calling {@link PerfProbe.exit} exits the main probe, cancels it and when the log option is provided
77
 * logs the gathered statistics.
78
 *
79
 * @see enterMainPerfProbe
80
 * @see getMainPerfProbe
81
 */
82
export interface MainPerfProbe extends PerfProbe {
83
    /**
84
     * Enters a child probe referenced by the {@link name} and measures it.
85
     * If the probe does not exist, returns a dummy instance.
86
     *
87
     * If the probe already performs a recursive call is counted.
88
     *
89
     * @see PerfProbe.exit
90
     * @see exitProbe
91
     */
92
    enterProbe(name: string): PerfProbe
93

94
    /**
95
     * Exits a child probe referenced by the {@link name}.
96
     * If the probe does not exist, returns a dummy instance.
97
     *
98
     * This is an equivalent of calling {@link getProbe} and then {@link PerfProbe.exit}.
99
     */
100
    exitProbe(name: string): PerfProbe
101

102
    /**
103
     * Returns the child probe referenced by the {@link name} if it exists,
104
     * otherwise a dummy instance.
105
     *
106
     * @see PerfProbe.dummy
107
     */
108
    getProbe(name: string): PerfProbe
109

110
    /**
111
     * Performs the {@link func} of a child probe referenced by the {@link name} and measures it.
112
     *
113
     * This is an equivalent of calling {@link enterProbe} and then {@link exitProbe}.
114
     *
115
     * If the probe already performs a recursive call is counted.
116
     */
117
    performProbe<T>(name: string, func: () => T): T
118

119
    /**
120
     * Returns true if the probe referenced by the {@link name} has been performed
121
     * (entered and exited all the recursive calls).
122
     */
123
    probePerformed(name: string): boolean
124
}
125

126
/**
127
 * Creates a {@link MainPerfProbe} instance with the {@link name} and enters its main probe.
128
 *
129
 * If a {@link MainPerfProbe} with this {@link name} already exists then it is canceled and the new one is created.
130
 *
131
 * Exit it with {@link MainPerfProbe.exit}.
132
 */
133
export function enterMainPerfProbe(name: string): MainPerfProbe {
134
    return new MainPerfProbeImpl(name)
135
}
136

137
/**
138
 * Returns {@link MainPerfProbe} instance with the {@link name} if it exists,
139
 * otherwise a dummy instance.
140
 *
141
 * @see MainPerfProbe.dummy
142
 */
143
export function getMainPerfProbe(name: string): MainPerfProbe {
144
    const instance = MainPerfProbeImpl.mainProbes.get(name)
145
    return instance ? instance : MainPerfProbeImpl.DUMMY
146
}
147

148
class DummyPerfProbe implements MainPerfProbe {
149
    get name(): string { return "dummy" }
150
    get dummy(): boolean { return true }
151
    exit(log: boolean|undefined): void {}
152
    cancel () {}
153
    get canceled(): boolean { return false }
154
    enterProbe(name: string): PerfProbe { return PerfProbeImpl.DUMMY }
155
    exitProbe (name: string): PerfProbe { return PerfProbeImpl.DUMMY }
156
    getProbe(name: string): PerfProbe { return PerfProbeImpl.DUMMY }
157
    //performProbe: <T>(_: string, func: () => T) => func(),
158
    performProbe<T>(name: string, func: () => T): T { return func() }
159
    probePerformed(_: string): boolean { return false }
160
}
161

162
class PerfProbeImpl implements PerfProbe {
163
    constructor(
164
        name: string,
165
        main?: MainPerfProbeImpl,
166
        parent?: PerfProbeImpl,
167
        remainder: boolean = false
168
    ) {
169
        this._name = name
170
        this._main = main
171
        this.parent = parent
172
        this.remainder = remainder
173
    }
174

175
    private readonly _name: string
176
    private readonly _main: MainPerfProbeImpl|undefined
177
    public parent: PerfProbeImpl|undefined
178
    public readonly remainder: boolean
179

180
    children: Array<PerfProbeImpl> = new Array<PerfProbeImpl>()
181
    childrenSorted: boolean = false
182
    index: int32 = 0
183
    startTime: float64 = 0.0
184
    totalTime: float64 = 0.0
185
    callCount: int32 = 0
186
    currentRecursionDepth: int32 = 0
187
    recursiveCallCount: int32 = 0
188
    _canceled: boolean = false
189

190
    get name(): string {
191
        return this._name
192
    }
193

194
    get dummy(): boolean {
195
        return false
196
    }
197

198
    get main(): MainPerfProbeImpl {
199
        return this._main!
200
    }
201

202
    get performing(): boolean {
203
        return this.startTime > 0
204
    }
205

206
    exit(log?: boolean): void {
207
        if (this.canceled) return
208

209
        if (this.currentRecursionDepth == 0) {
210
            this.totalTime += Date.now() - this.startTime
211
            this.startTime = 0
212
        }
213
        else {
214
            this.currentRecursionDepth--
215
        }
216
        if (!this.performing) {
217
            this.main.pop(this)
218
        }
219
        if (log) this.log()
220
    }
221

222
    cancel(cancelChildren: boolean = true) {
223
        this._canceled = true
224
        if (cancelChildren) this.maybeCancelChildren()
225
    }
226

227
    private maybeCancelChildren() {
228
        MainPerfProbeImpl.visit(this, false, (probe: PerfProbeImpl, depth: int32) => {
229
            if (probe.performing) probe.cancel(false)
230
        })
231
    }
232

233
    get canceled(): boolean {
234
        return this._canceled
235
    }
236

237
    toString(): string {
238
        if (this.canceled) {
239
            return `[${this.name}] canceled`
240
        }
241
        if (this.performing) {
242
            return `[${this.name}] still performing...`
243
        }
244
        const mainProbe = this.main.probes.get(this.main.name)!
245
        const percentage = mainProbe.totalTime > 0 ? Math.round((100 / mainProbe.totalTime) * this.totalTime) : 0
246
        return `[${this.name}] call count: ${this.callCount}` +
247
            ` | recursive call count: ${this.recursiveCallCount}` +
248
            ` | time: ${this.totalTime}ms ${percentage}%`
249
    }
250

251
    protected log(prefix?: string) {
252
        console.log(prefix ? `${prefix}${this.toString()}` : this.toString())
253
    }
254

255
    static readonly DUMMY: PerfProbe = new DummyPerfProbe()
256
}
257

258
class MainPerfProbeImpl extends PerfProbeImpl implements MainPerfProbe {
259
    constructor(
260
        name: string
261
    ) {
262
        super(name)
263
        const prev = MainPerfProbeImpl.mainProbes.get(name)
264
        if (prev) prev.cancel()
265
        MainPerfProbeImpl.mainProbes.set(name, this)
266
        this.currentProbe = this.enterProbe(name)
267
    }
268

269
    static readonly mainProbes: Map<string, MainPerfProbeImpl> = new Map<string, MainPerfProbeImpl>()
270

271
    currentProbe: PerfProbeImpl
272
    probes: Map<string, PerfProbeImpl> = new Map<string, PerfProbeImpl>()
273

274
    private createProbe(name: string): PerfProbeImpl {
275
        const probes = name == this.name ? this : new PerfProbeImpl(name, this)
276
        this.push(probes)
277
        return probes
278
    }
279

280
    get main(): MainPerfProbeImpl {
281
        return this
282
    }
283

284
    push(probe: PerfProbeImpl) {
285
        probe.parent = this.currentProbe
286
        probe.index = probe.parent ? probe.parent!.children.length as int32 : 0
287
        if (probe.parent) probe.parent!.children.push(probe)
288
        this.currentProbe = probe
289
    }
290

291
    pop(probe: PerfProbeImpl) {
292
        if (probe.parent) {
293
            this.currentProbe = probe.parent!
294
        }
295
    }
296

297
    performProbe<T>(name: string, func: () => T): T {
298
        const probe = this.enterProbe(name)
299
        try {
300
            return func()
301
        }
302
        finally {
303
            probe.exit()
304
        }
305
    }
306

307
    enterProbe(name: string): PerfProbeImpl {
308
        let probe = this.probes.get(name)
309
        if (!probe) {
310
            probe = this.createProbe(name)
311
            this.probes.set(name, probe)
312
        }
313
        probe._canceled = false
314

315
        if (probe.performing) {
316
            probe.recursiveCallCount++
317
            probe.currentRecursionDepth++
318
        } else {
319
            probe.startTime = Date.now()
320
            probe.callCount++
321
        }
322
        return probe
323
    }
324

325
    exitProbe(name: string): PerfProbe {
326
        const probe = this.getProbe(name)
327
        probe.exit(undefined)
328
        return probe
329
    }
330

331
    getProbe(name: string): PerfProbe {
332
        const probe = this.probes.get(name)
333
        return probe !== undefined ? probe : PerfProbeImpl.DUMMY
334
    }
335

336
    probePerformed(name: string): boolean {
337
        const probe = this.probes.get(name)
338
        return probe != undefined && !probe!.performing && !probe!.canceled
339
    }
340

341
    exit(log?: boolean) {
342
        super.exit()
343
        if (log) this.log()
344
        this.cancel()
345
    }
346

347
    cancel() {
348
        MainPerfProbeImpl.mainProbes.delete(this.name)
349
    }
350

351
    static visit(root: PerfProbeImpl, logging: boolean, apply: (probe: PerfProbeImpl, depth: int32) => void) {
352
        let current: PerfProbeImpl = root
353
        let index = 0
354
        let depth = 0
355
        let visiting = true
356
        while (true) {
357
            if (visiting) apply(current, depth)
358
            if (index >= current.children.length) {
359
                if (!current.parent) {
360
                    break
361
                }
362
                current = current.parent!
363
                index = ++current.index
364
                depth--
365
                visiting = false
366
                continue
367
            }
368
            visiting = true
369
            if (logging && !current.childrenSorted) {
370
                current.childrenSorted = true
371
                current.children = current.children.sort((p1: PerfProbeImpl, p2: PerfProbeImpl):number => p2.totalTime - p1.totalTime)
372
                if (depth == 0) {
373
                    // a special probe to log the time remained
374
                    current.children.push(new PerfProbeImpl("<remainder>", root.main, current, true))
375
                }
376
            }
377
            current = current.children[index]
378
            index = 0
379
            depth++
380
        }
381
    }
382

383
    protected log(prefix?: string) {
384
        prefix = prefix !== undefined ? `${prefix}: ` : ""
385
        console.log(`${prefix}perf probes:`)
386

387
        MainPerfProbeImpl.visit(this.main, true, (probe, depth):void => {
388
            let indent = "\t"
389
            for (let i = 0; i < depth; i++) indent += "\t"
390
            if (probe.remainder) {
391
                const parentTime = probe.parent!.totalTime
392
                let childrenTime = 0
393
                probe.parent!.children.forEach((a: PerfProbeImpl):void => { childrenTime += a.totalTime })
394
                probe.totalTime = Math.max(0, parentTime - childrenTime) as int32
395
                const percentage = parentTime > 0 ? Math.round((100 / parentTime) * probe.totalTime) : 0
396
                console.log(`${prefix}${indent}[${probe.name}] time: ${probe.totalTime}ms ${percentage}%`)
397
            }
398
            else {
399
                console.log(`${prefix}${indent}${probe.toString()}`)
400
            }
401
        })
402
    }
403

404
    static readonly DUMMY: MainPerfProbe = new DummyPerfProbe()
405
}
406

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.