@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.
Files changed (53) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/noop.d.ts +23 -0
  6. package/dist/noop.d.ts.map +1 -0
  7. package/dist/noop.js +41 -0
  8. package/dist/noop.js.map +1 -0
  9. package/dist/profileScene.d.ts +12 -0
  10. package/dist/profileScene.d.ts.map +1 -0
  11. package/dist/profileScene.js +32 -0
  12. package/dist/profileScene.js.map +1 -0
  13. package/dist/react/index.d.ts +2 -0
  14. package/dist/react/index.d.ts.map +1 -0
  15. package/dist/react/index.js +2 -0
  16. package/dist/react/index.js.map +1 -0
  17. package/dist/react/noop.d.ts +5 -0
  18. package/dist/react/noop.d.ts.map +1 -0
  19. package/dist/react/noop.js +3 -0
  20. package/dist/react/noop.js.map +1 -0
  21. package/dist/react/overlay.d.ts +5 -0
  22. package/dist/react/overlay.d.ts.map +1 -0
  23. package/dist/react/overlay.js +70 -0
  24. package/dist/react/overlay.js.map +1 -0
  25. package/dist/react/styles.d.ts +23 -0
  26. package/dist/react/styles.d.ts.map +1 -0
  27. package/dist/react/styles.js +91 -0
  28. package/dist/react/styles.js.map +1 -0
  29. package/dist/recorder.d.ts +38 -0
  30. package/dist/recorder.d.ts.map +1 -0
  31. package/dist/recorder.js +82 -0
  32. package/dist/recorder.js.map +1 -0
  33. package/dist/registry.d.ts +12 -0
  34. package/dist/registry.d.ts.map +1 -0
  35. package/dist/registry.js +57 -0
  36. package/dist/registry.js.map +1 -0
  37. package/dist/report.d.ts +3 -0
  38. package/dist/report.d.ts.map +1 -0
  39. package/dist/report.js +48 -0
  40. package/dist/report.js.map +1 -0
  41. package/dist/trace.d.ts +36 -0
  42. package/dist/trace.d.ts.map +1 -0
  43. package/dist/trace.js +58 -0
  44. package/dist/trace.js.map +1 -0
  45. package/package.json +42 -24
  46. package/src/index.ts +1 -1
  47. package/src/noop.ts +48 -12
  48. package/src/profileScene.ts +85 -19
  49. package/src/profiler.test.ts +38 -10
  50. package/src/react/overlay.tsx +3 -1
  51. package/src/recorder.ts +2 -3
  52. package/src/registry.ts +4 -1
  53. package/src/trace.ts +19 -2
@@ -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
- const settle = (ms = 20) => new Promise((resolve) => setTimeout(resolve, ms))
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 settle(0)
85
+ await flushed()
80
86
  expect(calls.count).toBe(1)
81
87
  unsubscribe()
82
88
  profiler.eventDispatched('d', 'High')
83
- await settle(0)
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(provide(RootUi, Ui.make(RootUi, () => null))),
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 = profiler.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 settle()
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 settle()
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
  })
@@ -97,7 +97,9 @@ const DroppedNote = ({ count }: DroppedNoteProps): ReactNode => {
97
97
  if (count === 0) {
98
98
  return null
99
99
  }
100
- return <div style={{ color: palette.dim }}>({count} early records evicted from the ring buffer)</div>
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<ProfilerHandle, { readonly registration: ProfilerRegistration; readonly count: number }>
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
- { ph: 'M', name: 'process_name', ts: 0, pid, tid: 0, args: { name: `reform: ${profiler.label}` } },
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
- : { ph: 'X', name: record.name, cat, ts: startUs, dur: record.dur * US_PER_MS, pid, tid, args }
93
+ : complete
77
94
  })
78
95
  return { traceEvents: [...meta, ...body], displayTimeUnit: 'ms' }
79
96
  }