@player-tools/devtools-profiler-web-plugin 0.6.1--canary.125.2996
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +847 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +812 -0
- package/dist/index.mjs +812 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/src/WrapperComponent.tsx +121 -0
- package/src/__tests__/__snapshots__/index.test.tsx.snap +90 -0
- package/src/__tests__/index.test.tsx +307 -0
- package/src/constants/index.ts +28 -0
- package/src/content/common/Screen.tsx +20 -0
- package/src/content/common/index.ts +1 -0
- package/src/content/index.ts +19 -0
- package/src/content/navigation/index.ts +27 -0
- package/src/content/schema/index.ts +22 -0
- package/src/content/views/ProfilerView.tsx +75 -0
- package/src/content/views/index.ts +3 -0
- package/src/helpers/__tests__/genDataChangeTransaction.test.ts +40 -0
- package/src/helpers/__tests__/profiler.test.ts +104 -0
- package/src/helpers/genDataChangeTransaction.ts +39 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/profiler.ts +111 -0
- package/src/index.tsx +490 -0
- package/src/types.ts +26 -0
- package/types/WrapperComponent.d.ts +4 -0
- package/types/constants/index.d.ts +15 -0
- package/types/content/common/Screen.d.ts +8 -0
- package/types/content/common/index.d.ts +2 -0
- package/types/content/index.d.ts +31 -0
- package/types/content/navigation/index.d.ts +7 -0
- package/types/content/schema/index.d.ts +23 -0
- package/types/content/views/ProfilerView.d.ts +3 -0
- package/types/content/views/index.d.ts +3 -0
- package/types/helpers/genDataChangeTransaction.d.ts +16 -0
- package/types/helpers/index.d.ts +3 -0
- package/types/helpers/profiler.d.ts +18 -0
- package/types/index.d.ts +7 -0
- package/types/types.d.ts +29 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PluginData } from "@player-tools/devtools-types";
|
|
2
|
+
|
|
3
|
+
export const PLUGIN_ID = "player-ui-profiler-plugin";
|
|
4
|
+
|
|
5
|
+
export const PLUGIN_NAME = "Player UI Profiler";
|
|
6
|
+
|
|
7
|
+
export const PLUGIN_DESCRIPTION = "Standard Player UI Profiler";
|
|
8
|
+
|
|
9
|
+
export const PLUGIN_VERSION = "0.0.1";
|
|
10
|
+
|
|
11
|
+
export const VIEWS_IDS = {
|
|
12
|
+
PROFILER: "Profiler",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const INTERACTIONS = {
|
|
16
|
+
START_PROFILING: "start-profiling",
|
|
17
|
+
STOP_PROFILING: "stop-profiling",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const BASE_PLUGIN_DATA: Omit<PluginData, "flow"> = {
|
|
21
|
+
id: PLUGIN_ID,
|
|
22
|
+
name: PLUGIN_NAME,
|
|
23
|
+
description: PLUGIN_DESCRIPTION,
|
|
24
|
+
version: PLUGIN_VERSION,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const PLUGIN_INACTIVE_WARNING =
|
|
28
|
+
"The plugin has been registered, but the Player development tools are not active. If you are working in a production environment, it is recommended to remove the plugin. Either way, you can activate the Player development tools by clicking on the extension popup and refreshing the page.";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StackedView } from "@devtools-ui/plugin";
|
|
3
|
+
|
|
4
|
+
export const Screen = ({
|
|
5
|
+
main,
|
|
6
|
+
header,
|
|
7
|
+
footer,
|
|
8
|
+
id,
|
|
9
|
+
}: {
|
|
10
|
+
id: string;
|
|
11
|
+
main: React.ReactNode;
|
|
12
|
+
header?: React.ReactNode;
|
|
13
|
+
footer?: React.ReactNode;
|
|
14
|
+
}) => (
|
|
15
|
+
<StackedView id={id}>
|
|
16
|
+
{header && <StackedView.Header>{header}</StackedView.Header>}
|
|
17
|
+
<StackedView.Main>{main}</StackedView.Main>
|
|
18
|
+
{footer && <StackedView.Footer>{footer}</StackedView.Footer>}
|
|
19
|
+
</StackedView>
|
|
20
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Screen";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PLUGIN_ID } from "../constants";
|
|
2
|
+
import { navigation } from "./navigation";
|
|
3
|
+
import { schema } from "./schema";
|
|
4
|
+
import { views } from "./views";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
id: PLUGIN_ID,
|
|
8
|
+
views,
|
|
9
|
+
navigation,
|
|
10
|
+
schema,
|
|
11
|
+
data: {
|
|
12
|
+
rootNode: {
|
|
13
|
+
name: "profiler time span",
|
|
14
|
+
value: 0,
|
|
15
|
+
},
|
|
16
|
+
displayFlameGraph: false,
|
|
17
|
+
profiling: false,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VIEWS_IDS } from "../../constants";
|
|
2
|
+
|
|
3
|
+
const transitions = Object.entries(VIEWS_IDS).reduce(
|
|
4
|
+
(acc, [key, value]) => ({
|
|
5
|
+
...acc,
|
|
6
|
+
[value]: key,
|
|
7
|
+
}),
|
|
8
|
+
{} as Record<string, string>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export const navigation = {
|
|
12
|
+
BEGIN: "Plugin",
|
|
13
|
+
Plugin: {
|
|
14
|
+
startState: Object.keys(VIEWS_IDS)[0],
|
|
15
|
+
...Object.entries(VIEWS_IDS).reduce(
|
|
16
|
+
(acc, [key, value]) => ({
|
|
17
|
+
...acc,
|
|
18
|
+
[key]: {
|
|
19
|
+
state_type: "VIEW",
|
|
20
|
+
ref: value,
|
|
21
|
+
transitions,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
{} as Record<string, unknown>
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Schema } from "@player-ui/types";
|
|
2
|
+
import { dataTypes } from "@player-ui/common-types-plugin";
|
|
3
|
+
import { DSLSchema, makeBindingsForObject } from "@player-tools/dsl";
|
|
4
|
+
|
|
5
|
+
const RecordType: Schema.DataType<Record<string, unknown>> = {
|
|
6
|
+
type: "RecordType",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const schema = {
|
|
10
|
+
playerConfig: RecordType,
|
|
11
|
+
rootNode: RecordType,
|
|
12
|
+
profiling: dataTypes.BooleanType,
|
|
13
|
+
displayFlameGraph: dataTypes.BooleanType,
|
|
14
|
+
durations: [
|
|
15
|
+
{
|
|
16
|
+
name: dataTypes.StringType,
|
|
17
|
+
duration: dataTypes.StringType,
|
|
18
|
+
},
|
|
19
|
+
] as [DSLSchema],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const bindings = makeBindingsForObject(schema);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { expression as e } from "@player-tools/dsl";
|
|
4
|
+
import {
|
|
5
|
+
Action,
|
|
6
|
+
Collection,
|
|
7
|
+
FlameGraph,
|
|
8
|
+
Table,
|
|
9
|
+
Text,
|
|
10
|
+
} from "@devtools-ui/plugin";
|
|
11
|
+
import { INTERACTIONS, VIEWS_IDS } from "../../constants";
|
|
12
|
+
import { Screen } from "../common";
|
|
13
|
+
import { bindings } from "../schema";
|
|
14
|
+
|
|
15
|
+
const startProfilerExpression = e` publish('${INTERACTIONS.START_PROFILING}') `;
|
|
16
|
+
const stopProfilerExpression = e` publish('${INTERACTIONS.STOP_PROFILING}') `;
|
|
17
|
+
|
|
18
|
+
export const ProfilerView = (
|
|
19
|
+
<Screen
|
|
20
|
+
id={VIEWS_IDS.PROFILER}
|
|
21
|
+
header={
|
|
22
|
+
<Collection>
|
|
23
|
+
<Collection.Values>
|
|
24
|
+
<Action
|
|
25
|
+
applicability={e` {{profiling}} === true ` as any}
|
|
26
|
+
key="stopProfiler"
|
|
27
|
+
exp={stopProfilerExpression.toString()}
|
|
28
|
+
>
|
|
29
|
+
<Action.Label>
|
|
30
|
+
<Text>Stop</Text>
|
|
31
|
+
</Action.Label>
|
|
32
|
+
</Action>
|
|
33
|
+
<Action
|
|
34
|
+
applicability={e` {{profiling}} === false ` as any}
|
|
35
|
+
key="startProfiler"
|
|
36
|
+
exp={startProfilerExpression.toString()}
|
|
37
|
+
>
|
|
38
|
+
<Action.Label>
|
|
39
|
+
<Text>Start</Text>
|
|
40
|
+
</Action.Label>
|
|
41
|
+
</Action>
|
|
42
|
+
</Collection.Values>
|
|
43
|
+
</Collection>
|
|
44
|
+
}
|
|
45
|
+
main={
|
|
46
|
+
<Collection>
|
|
47
|
+
<Collection.Values>
|
|
48
|
+
<Table
|
|
49
|
+
applicability={e` {{displayFlameGraph}} === true ` as any}
|
|
50
|
+
binding={bindings.durations as any}
|
|
51
|
+
/>
|
|
52
|
+
<FlameGraph
|
|
53
|
+
applicability={e` {{displayFlameGraph}} === true ` as any}
|
|
54
|
+
binding={bindings.rootNode as any}
|
|
55
|
+
height={200}
|
|
56
|
+
/>
|
|
57
|
+
<Text
|
|
58
|
+
applicability={
|
|
59
|
+
e` {{displayFlameGraph}} === false && {{profiling}} === true` as any
|
|
60
|
+
}
|
|
61
|
+
>
|
|
62
|
+
Profiling...
|
|
63
|
+
</Text>
|
|
64
|
+
<Text
|
|
65
|
+
applicability={
|
|
66
|
+
e` {{displayFlameGraph}} === false && {{profiling}} === false ` as any
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
Start the profiler to generate the flame graph.
|
|
70
|
+
</Text>
|
|
71
|
+
</Collection.Values>
|
|
72
|
+
</Collection>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
DevtoolsDataChangeEvent,
|
|
4
|
+
Transaction,
|
|
5
|
+
} from "@player-tools/devtools-types";
|
|
6
|
+
import { genDataChangeTransaction } from "../genDataChangeTransaction";
|
|
7
|
+
|
|
8
|
+
describe("genDataChangeTransaction", () => {
|
|
9
|
+
it("should correctly generate a data change transaction for valid input", () => {
|
|
10
|
+
const input = {
|
|
11
|
+
playerID: "player1",
|
|
12
|
+
data: {
|
|
13
|
+
key: "value",
|
|
14
|
+
anotherKey: 123,
|
|
15
|
+
},
|
|
16
|
+
pluginID: "pluginA",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const expectedOutput: Transaction<DevtoolsDataChangeEvent> = {
|
|
20
|
+
id: -1,
|
|
21
|
+
type: "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE",
|
|
22
|
+
payload: {
|
|
23
|
+
pluginID: "pluginA",
|
|
24
|
+
data: {
|
|
25
|
+
key: "value",
|
|
26
|
+
anotherKey: 123,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
sender: "player1",
|
|
30
|
+
context: "player",
|
|
31
|
+
target: "player",
|
|
32
|
+
timestamp: expect.any(Number),
|
|
33
|
+
_messenger_: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const result = genDataChangeTransaction(input);
|
|
37
|
+
|
|
38
|
+
expect(result).toEqual(expect.objectContaining(expectedOutput));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { profiler } from "..";
|
|
3
|
+
import { ProfilerNode } from "../../types";
|
|
4
|
+
|
|
5
|
+
// mock performance.now
|
|
6
|
+
let count = 2490.0;
|
|
7
|
+
const now = vi.fn(() => {
|
|
8
|
+
count += 0.1;
|
|
9
|
+
return count;
|
|
10
|
+
});
|
|
11
|
+
global.performance = { ...global.performance, now };
|
|
12
|
+
|
|
13
|
+
describe("Profiler", () => {
|
|
14
|
+
test("starts the profiler, keep track of the events, and return the profiler tree", () => {
|
|
15
|
+
const { startTimer, endTimer, stopProfiler, start } = profiler();
|
|
16
|
+
|
|
17
|
+
start();
|
|
18
|
+
|
|
19
|
+
// process with no children
|
|
20
|
+
startTimer("process1");
|
|
21
|
+
endTimer({ hookName: "process1" });
|
|
22
|
+
|
|
23
|
+
// process with children
|
|
24
|
+
const parentNode: ProfilerNode = {
|
|
25
|
+
name: "process2",
|
|
26
|
+
children: [],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
startTimer("process2");
|
|
30
|
+
startTimer("process2.1");
|
|
31
|
+
startTimer("process2.2");
|
|
32
|
+
endTimer({ hookName: "process2.1", parentNode });
|
|
33
|
+
endTimer({ hookName: "process2.2", parentNode });
|
|
34
|
+
endTimer({ hookName: "process2", children: parentNode.children });
|
|
35
|
+
|
|
36
|
+
const rootNode = stopProfiler();
|
|
37
|
+
|
|
38
|
+
expect(rootNode).toStrictEqual({
|
|
39
|
+
durations: [
|
|
40
|
+
{ name: "process2", duration: "0.5000 ms" },
|
|
41
|
+
{ name: "process2.1", duration: "0.2000 ms" },
|
|
42
|
+
{ name: "process2.2", duration: "0.2000 ms" },
|
|
43
|
+
{ name: "process1", duration: "0.1000 ms" },
|
|
44
|
+
],
|
|
45
|
+
rootNode: {
|
|
46
|
+
children: [
|
|
47
|
+
{
|
|
48
|
+
children: [],
|
|
49
|
+
endTime: 2490.2999999999997,
|
|
50
|
+
name: "process1",
|
|
51
|
+
startTime: 2490.2,
|
|
52
|
+
tooltip: "process1, 0.1000 (ms)",
|
|
53
|
+
value: 100,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
children: [
|
|
57
|
+
{
|
|
58
|
+
children: [],
|
|
59
|
+
endTime: 2490.6999999999994,
|
|
60
|
+
name: "process2.1",
|
|
61
|
+
startTime: 2490.4999999999995,
|
|
62
|
+
tooltip: "process2.1, 0.2000 (ms)",
|
|
63
|
+
value: 200,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
children: [],
|
|
67
|
+
endTime: 2490.7999999999993,
|
|
68
|
+
name: "process2.2",
|
|
69
|
+
startTime: 2490.5999999999995,
|
|
70
|
+
tooltip: "process2.2, 0.2000 (ms)",
|
|
71
|
+
value: 200,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
endTime: 2490.899999999999,
|
|
75
|
+
name: "process2",
|
|
76
|
+
startTime: 2490.3999999999996,
|
|
77
|
+
tooltip: "process2, 0.5000 (ms)",
|
|
78
|
+
value: 500,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
endTime: 2490.999999999999,
|
|
82
|
+
name: "root",
|
|
83
|
+
startTime: 2490.1,
|
|
84
|
+
tooltip: "Profiler total time span 0.9000 (ms)",
|
|
85
|
+
value: 600,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// (re)start
|
|
90
|
+
start();
|
|
91
|
+
const { rootNode: rootNode2, durations } = stopProfiler();
|
|
92
|
+
|
|
93
|
+
expect(durations).toStrictEqual([]);
|
|
94
|
+
|
|
95
|
+
expect(rootNode2).toStrictEqual({
|
|
96
|
+
name: "root",
|
|
97
|
+
endTime: 2491.199999999999,
|
|
98
|
+
startTime: 2491.099999999999,
|
|
99
|
+
tooltip: "Profiler total time span 0.1000 (ms)",
|
|
100
|
+
value: 100,
|
|
101
|
+
children: [],
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DevtoolsDataChangeEvent,
|
|
3
|
+
Transaction,
|
|
4
|
+
} from "@player-tools/devtools-types";
|
|
5
|
+
import type { Flow } from "@player-ui/react";
|
|
6
|
+
|
|
7
|
+
const NOOP_ID = -1;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generates a data change transaction for the player devtools plugin.
|
|
11
|
+
*
|
|
12
|
+
* This function creates a transaction object that represents a change in data
|
|
13
|
+
* within a player devtools plugin. The transaction includes details such as the
|
|
14
|
+
* plugin ID, the changed data, and the player ID. It is used to communicate
|
|
15
|
+
* changes between the plugin and devtools.
|
|
16
|
+
*/
|
|
17
|
+
export const genDataChangeTransaction = ({
|
|
18
|
+
playerID,
|
|
19
|
+
data,
|
|
20
|
+
pluginID,
|
|
21
|
+
}: {
|
|
22
|
+
playerID: string;
|
|
23
|
+
data: Flow["data"];
|
|
24
|
+
pluginID: string;
|
|
25
|
+
}): Transaction<DevtoolsDataChangeEvent> => {
|
|
26
|
+
return {
|
|
27
|
+
id: NOOP_ID,
|
|
28
|
+
type: "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE",
|
|
29
|
+
payload: {
|
|
30
|
+
pluginID,
|
|
31
|
+
data,
|
|
32
|
+
},
|
|
33
|
+
sender: playerID,
|
|
34
|
+
context: "player",
|
|
35
|
+
target: "player",
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
_messenger_: true,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ProfilerNode } from "../types";
|
|
2
|
+
|
|
3
|
+
export const profiler = () => {
|
|
4
|
+
let rootNode: ProfilerNode = {
|
|
5
|
+
name: "root",
|
|
6
|
+
children: [],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let record: { [key: string]: number[] } = {};
|
|
10
|
+
let durations: { hookName: string; duration: number }[] = [];
|
|
11
|
+
|
|
12
|
+
const start = () => {
|
|
13
|
+
rootNode = {
|
|
14
|
+
name: "root",
|
|
15
|
+
startTime: performance.now(),
|
|
16
|
+
children: [],
|
|
17
|
+
};
|
|
18
|
+
record = {};
|
|
19
|
+
durations = [];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const addNodeToTree = (newNode: ProfilerNode, parentNode: ProfilerNode) => {
|
|
23
|
+
parentNode.children.push(newNode);
|
|
24
|
+
return newNode;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const startTimer = (hookName: string) => {
|
|
28
|
+
const startTime = performance.now();
|
|
29
|
+
|
|
30
|
+
if (!record[hookName] || record[hookName].length === 2) {
|
|
31
|
+
record[hookName] = [];
|
|
32
|
+
record[hookName].push(startTime);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const endTimer = ({
|
|
37
|
+
hookName,
|
|
38
|
+
parentNode = rootNode,
|
|
39
|
+
children,
|
|
40
|
+
}: {
|
|
41
|
+
hookName: string;
|
|
42
|
+
parentNode?: ProfilerNode;
|
|
43
|
+
children?: ProfilerNode[];
|
|
44
|
+
}) => {
|
|
45
|
+
let startTime: number | undefined;
|
|
46
|
+
let duration: number | undefined;
|
|
47
|
+
|
|
48
|
+
const endTime = performance.now();
|
|
49
|
+
|
|
50
|
+
for (const key in record) {
|
|
51
|
+
if (key === hookName && record[key].length === 1) {
|
|
52
|
+
[startTime] = record[key];
|
|
53
|
+
duration = endTime - startTime;
|
|
54
|
+
record[key].push(endTime);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const value = Math.ceil((duration || 0.01) * 1000);
|
|
59
|
+
|
|
60
|
+
const newNode: ProfilerNode = {
|
|
61
|
+
name: hookName,
|
|
62
|
+
startTime,
|
|
63
|
+
endTime,
|
|
64
|
+
value,
|
|
65
|
+
tooltip: `${hookName}, ${(duration || 0.01).toFixed(4)} (ms)`,
|
|
66
|
+
children: children ?? [],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
addNodeToTree(newNode, parentNode);
|
|
70
|
+
|
|
71
|
+
// Push the hookName and duration into durations array
|
|
72
|
+
durations.push({ hookName, duration: duration ? duration : 0.01 });
|
|
73
|
+
|
|
74
|
+
return newNode;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const stopProfiler = (): {
|
|
78
|
+
rootNode: ProfilerNode;
|
|
79
|
+
durations: { name: string; duration: string }[];
|
|
80
|
+
} => {
|
|
81
|
+
const endTime = performance.now();
|
|
82
|
+
const totalTime = endTime - (rootNode.startTime ?? 0);
|
|
83
|
+
|
|
84
|
+
rootNode.endTime = endTime;
|
|
85
|
+
// set the stop profiler value is the sum of its children values
|
|
86
|
+
// otherwise the difference of width of the root and the other nodes
|
|
87
|
+
// make it impossible to see them into the flame graph
|
|
88
|
+
rootNode.value =
|
|
89
|
+
rootNode.children.reduce((acc, { value }) => (acc += value ?? 0), 0) ||
|
|
90
|
+
Math.ceil((totalTime || 0.01) * 1000);
|
|
91
|
+
rootNode.tooltip = `Profiler total time span ${totalTime.toFixed(4)} (ms)`;
|
|
92
|
+
|
|
93
|
+
// Sort durations array in descending order
|
|
94
|
+
durations.sort((a, b) => b.duration - a.duration);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
rootNode,
|
|
98
|
+
durations: durations.map(({ hookName, duration }) => ({
|
|
99
|
+
name: hookName,
|
|
100
|
+
duration: `${duration.toFixed(4)} ms`,
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
start,
|
|
107
|
+
startTimer,
|
|
108
|
+
endTimer,
|
|
109
|
+
stopProfiler,
|
|
110
|
+
};
|
|
111
|
+
};
|