@playfast/reform-profiler 1.0.2 → 1.2.0
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/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/noop.d.ts +23 -0
- package/dist/noop.d.ts.map +1 -0
- package/dist/noop.js +41 -0
- package/dist/noop.js.map +1 -0
- package/dist/profileScene.d.ts +12 -0
- package/dist/profileScene.d.ts.map +1 -0
- package/dist/profileScene.js +32 -0
- package/dist/profileScene.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/noop.d.ts +5 -0
- package/dist/react/noop.d.ts.map +1 -0
- package/dist/react/noop.js +3 -0
- package/dist/react/noop.js.map +1 -0
- package/dist/react/overlay.d.ts +5 -0
- package/dist/react/overlay.d.ts.map +1 -0
- package/dist/react/overlay.js +70 -0
- package/dist/react/overlay.js.map +1 -0
- package/dist/react/styles.d.ts +23 -0
- package/dist/react/styles.d.ts.map +1 -0
- package/dist/react/styles.js +91 -0
- package/dist/react/styles.js.map +1 -0
- package/dist/recorder.d.ts +38 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +82 -0
- package/dist/recorder.js.map +1 -0
- package/dist/registry.d.ts +12 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +57 -0
- package/dist/registry.js.map +1 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +48 -0
- package/dist/report.js.map +1 -0
- package/dist/trace.d.ts +36 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +58 -0
- package/dist/trace.js.map +1 -0
- package/package.json +42 -24
- package/src/index.ts +1 -1
- package/src/noop.ts +48 -12
- package/src/profileScene.ts +85 -19
- package/src/profiler.test.ts +38 -10
- package/src/react/overlay.tsx +3 -1
- package/src/recorder.ts +2 -3
- package/src/registry.ts +4 -1
- package/src/trace.ts +19 -2
package/src/profiler.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Schema as S } from 'effect'
|
|
1
|
+
import { Effect, Option, Schema as S } from 'effect'
|
|
2
2
|
import { expect, test } from 'vitest'
|
|
3
3
|
import {
|
|
4
4
|
Composition,
|
|
@@ -16,13 +16,19 @@ import {
|
|
|
16
16
|
ui,
|
|
17
17
|
} from '@playfast/reform'
|
|
18
18
|
import { Layer } from 'effect'
|
|
19
|
+
import { until } from '@playfast/reform/testkit/flight.testkit'
|
|
19
20
|
import { makeProfiler } from './recorder'
|
|
20
21
|
import { profileScene } from './profileScene'
|
|
21
22
|
import { listProfilers } from './registry'
|
|
22
23
|
import { toChromeTrace, traceJson } from './trace'
|
|
23
24
|
import { formatReport } from './report'
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// The recorder coalesces on the reform scheduler's queueMicrotask; a microtask
|
|
27
|
+
// queued after it therefore runs once every pending flush has drained.
|
|
28
|
+
const flushed = (): Promise<void> => new Promise((resolve) => queueMicrotask(() => resolve()))
|
|
29
|
+
|
|
30
|
+
const reaches = <A>(read: () => A, predicate: (value: A) => boolean): Promise<A> =>
|
|
31
|
+
Effect.runPromise(until(read, predicate))
|
|
26
32
|
|
|
27
33
|
test('aggregates count and time per name, per kind', () => {
|
|
28
34
|
const profiler = makeProfiler({ label: 'agg' })
|
|
@@ -76,11 +82,11 @@ test('subscribers are notified once per burst (coalesced)', async () => {
|
|
|
76
82
|
profiler.eventDispatched('a', 'High')
|
|
77
83
|
profiler.eventDispatched('b', 'High')
|
|
78
84
|
profiler.eventDispatched('c', 'High')
|
|
79
|
-
await
|
|
85
|
+
await flushed()
|
|
80
86
|
expect(calls.count).toBe(1)
|
|
81
87
|
unsubscribe()
|
|
82
88
|
profiler.eventDispatched('d', 'High')
|
|
83
|
-
await
|
|
89
|
+
await flushed()
|
|
84
90
|
expect(calls.count).toBe(1)
|
|
85
91
|
})
|
|
86
92
|
|
|
@@ -114,19 +120,25 @@ class Bump extends Reducer.make('ProfilerBump', { states: [Count], events: [Bump
|
|
|
114
120
|
const BumpLive = Reducer.live(Bump, (n) => n + 1)
|
|
115
121
|
|
|
116
122
|
class RootUi extends ui('ProfilerRoot')<{ props: { count: number } }>() {}
|
|
123
|
+
|
|
117
124
|
class Root extends Composition.make('ProfilerRoot', {
|
|
118
125
|
title: 'ProfilerRoot',
|
|
119
126
|
states: [States],
|
|
120
127
|
events: [Bumped],
|
|
121
128
|
ui: RootUi,
|
|
122
|
-
}) {}
|
|
129
|
+
})<Root>() {}
|
|
123
130
|
const RootLive = Composition.live(Root, function* () {
|
|
124
131
|
const count = yield* StateGroup.select(States, 'profiler-count')
|
|
125
132
|
return mount({ props: { count }, slots: {} })
|
|
126
133
|
})
|
|
127
134
|
|
|
128
135
|
const app = RootLive.pipe(
|
|
129
|
-
Layer.provideMerge(
|
|
136
|
+
Layer.provideMerge(
|
|
137
|
+
provide(
|
|
138
|
+
RootUi,
|
|
139
|
+
Ui.make(RootUi, () => null),
|
|
140
|
+
),
|
|
141
|
+
),
|
|
130
142
|
Layer.provideMerge(
|
|
131
143
|
Layer.mergeAll(BumpLive).pipe(
|
|
132
144
|
Layer.provideMerge(Layer.mergeAll(Engine, StateGroup.live(States, { 'profiler-count': 0 }))),
|
|
@@ -145,9 +157,15 @@ test('profileScene records engine activity through makeAppRuntime and registers
|
|
|
145
157
|
|
|
146
158
|
runtime.boot()
|
|
147
159
|
runtime.dispatch('High', Event.construct(Bumped, {}))
|
|
148
|
-
await settle()
|
|
149
160
|
|
|
150
|
-
const snapshot =
|
|
161
|
+
const snapshot = await reaches(
|
|
162
|
+
() => profiler.snapshot(),
|
|
163
|
+
(taken) =>
|
|
164
|
+
(taken.events.get('ProfilerBumped')?.count ?? 0) >= 2 &&
|
|
165
|
+
(taken.reducers.get('ProfilerBump')?.count ?? 0) >= 2 &&
|
|
166
|
+
(taken.states.get('profiler-count')?.count ?? 0) >= 2 &&
|
|
167
|
+
taken.frames.count >= 1,
|
|
168
|
+
)
|
|
151
169
|
expect(snapshot.events.get('ProfilerBumped')?.count).toBe(2)
|
|
152
170
|
expect(snapshot.reducers.get('ProfilerBump')?.count).toBe(2)
|
|
153
171
|
expect(snapshot.states.get('profiler-count')?.count).toBe(2)
|
|
@@ -158,13 +176,23 @@ test('profileScene records engine activity through makeAppRuntime and registers
|
|
|
158
176
|
expect(report).toContain('ProfilerBump')
|
|
159
177
|
|
|
160
178
|
runtime.dispose()
|
|
161
|
-
await
|
|
179
|
+
await reaches(
|
|
180
|
+
() => listProfilers().map((entry) => entry.handle),
|
|
181
|
+
(handles) => !handles.includes(profiler),
|
|
182
|
+
)
|
|
162
183
|
expect(listProfilers().map((entry) => entry.handle)).not.toContain(profiler)
|
|
163
184
|
|
|
164
185
|
const before = profiler.snapshot().events.get('ProfilerBumped')?.count
|
|
165
186
|
const plain = makeAppRuntime(baseScene)
|
|
187
|
+
// The plain runtime's own count landing proves its frame ran; the profiler must not have seen it.
|
|
188
|
+
const plainCount = Option.getOrThrow(
|
|
189
|
+
plain.readOption(StateGroup.select(States, 'profiler-count').store),
|
|
190
|
+
)
|
|
166
191
|
plain.dispatch('High', Event.construct(Bumped, {}))
|
|
167
|
-
await
|
|
192
|
+
await reaches(
|
|
193
|
+
() => plainCount.get(),
|
|
194
|
+
(count) => count === 1,
|
|
195
|
+
)
|
|
168
196
|
expect(profiler.snapshot().events.get('ProfilerBumped')?.count).toBe(before)
|
|
169
197
|
plain.dispose()
|
|
170
198
|
})
|
package/src/react/overlay.tsx
CHANGED
|
@@ -97,7 +97,9 @@ const DroppedNote = ({ count }: DroppedNoteProps): ReactNode => {
|
|
|
97
97
|
if (count === 0) {
|
|
98
98
|
return null
|
|
99
99
|
}
|
|
100
|
-
return
|
|
100
|
+
return (
|
|
101
|
+
<div style={{ color: palette.dim }}>({count} early records evicted from the ring buffer)</div>
|
|
102
|
+
)
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
interface ProfilerBodyProps {
|
package/src/recorder.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Instrumentation, makeScheduler, type SpanEnd } from '@playfast/reform'
|
|
1
|
+
import { type Instrumentation, makeScheduler, type SpanEnd } from '@playfast/reform/internal'
|
|
2
2
|
|
|
3
3
|
export type ProfileKind =
|
|
4
4
|
| 'event'
|
|
@@ -93,8 +93,7 @@ export const makeProfiler = (options: ProfilerOptionsExternalApi = {}): Profiler
|
|
|
93
93
|
readonly detail: string
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const instant = (site: Site): void =>
|
|
97
|
-
record({ ...site, start: performance.now(), dur: 0 })
|
|
96
|
+
const instant = (site: Site): void => record({ ...site, start: performance.now(), dur: 0 })
|
|
98
97
|
|
|
99
98
|
const span = (site: Site): SpanEnd => {
|
|
100
99
|
const start = performance.now()
|
package/src/registry.ts
CHANGED
|
@@ -10,7 +10,10 @@ export interface ProfilerRegistration {
|
|
|
10
10
|
|
|
11
11
|
interface RegistryState {
|
|
12
12
|
// Refcount: StrictMode double-build shares one entry.
|
|
13
|
-
readonly entries: Map<
|
|
13
|
+
readonly entries: Map<
|
|
14
|
+
ProfilerHandle,
|
|
15
|
+
{ readonly registration: ProfilerRegistration; readonly count: number }
|
|
16
|
+
>
|
|
14
17
|
readonly listeners: Set<() => void>
|
|
15
18
|
readonly version: { current: number }
|
|
16
19
|
readonly overlay: { owner: Option.Option<object> }
|
package/src/trace.ts
CHANGED
|
@@ -54,7 +54,14 @@ const US_PER_MS = 1000
|
|
|
54
54
|
export const toChromeTrace = (profiler: ProfilerHandle): ChromeTrace => {
|
|
55
55
|
const { records } = profiler.snapshot()
|
|
56
56
|
const meta: ReadonlyArray<ChromeTraceEvent> = [
|
|
57
|
-
{
|
|
57
|
+
{
|
|
58
|
+
ph: 'M',
|
|
59
|
+
name: 'process_name',
|
|
60
|
+
ts: 0,
|
|
61
|
+
pid,
|
|
62
|
+
tid: 0,
|
|
63
|
+
args: { name: `reform: ${profiler.label}` },
|
|
64
|
+
},
|
|
58
65
|
...Object.values(lanes).map(
|
|
59
66
|
(lane): ChromeTraceEvent => ({
|
|
60
67
|
ph: 'M',
|
|
@@ -71,9 +78,19 @@ export const toChromeTrace = (profiler: ProfilerHandle): ChromeTrace => {
|
|
|
71
78
|
const cat = record.kind
|
|
72
79
|
const tid = lanes[record.kind].tid
|
|
73
80
|
const args = record.detail === '' ? {} : { detail: record.detail }
|
|
81
|
+
const complete: ChromeTraceEvent = {
|
|
82
|
+
ph: 'X',
|
|
83
|
+
name: record.name,
|
|
84
|
+
cat,
|
|
85
|
+
ts: startUs,
|
|
86
|
+
dur: record.dur * US_PER_MS,
|
|
87
|
+
pid,
|
|
88
|
+
tid,
|
|
89
|
+
args,
|
|
90
|
+
}
|
|
74
91
|
return instantKinds.has(record.kind)
|
|
75
92
|
? { ph: 'i', name: record.name, cat, ts: startUs, pid, tid, s: 't', args }
|
|
76
|
-
:
|
|
93
|
+
: complete
|
|
77
94
|
})
|
|
78
95
|
return { traceEvents: [...meta, ...body], displayTimeUnit: 'ms' }
|
|
79
96
|
}
|