20
enableTaint,
21
enableServerComponentLogs,
22
enableOwnerStacks,
23
+ enableProfilerTimer,
24
+ enableComponentPerformanceTrack,
25
} from 'shared/ReactFeatureFlags';
26
27
import {enableFlightReadableStream} from 'shared/ReactFeatureFlags';
347
keyPath: null | string, // parent server component keys
348
implicitSlot: boolean, // true if the root server component of this sequence had a null key
349
thenableState: ThenableState | null,
350
+ timed: boolean, // Profiling-only. Whether we need to track the completion time of this task.
351
environmentName: string, // DEV-only. Used to track if the environment for this task changed.
352
debugOwner: null | ReactComponentInfo, // DEV-only
353
debugStack: null | Error, // DEV-only
395
onPostpone: (reason: string) => void,
396
onAllReady: () => void,
397
onFatalError: mixed => void,
398
+ // Profiling-only
399
+ timeOrigin: number,
400
// DEV-only
401
environmentName: () => string,
402
filterStackFrame: (url: string, functionName: string) => boolean,
522
: filterStackFrame;
523
this.didWarnForKey = null;
524
}
525
+
526
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
527
+ // We start by serializing the time origin. Any future timestamps will be
528
+ // emitted relatively to this origin. Instead of using performance.timeOrigin
529
+ // as this origin, we use the timestamp at the start of the request.
530
+ // This avoids leaking unnecessary information like how long the server has
531
+ // been running and allows for more compact representation of each timestamp.
532
+ // The time origin is stored as an offset in the time space of this environment.
533
+ const timeOrigin = (this.timeOrigin = performance.now());
534
+ emitTimeOriginChunk(
535
+ this,
536
+ timeOrigin +
537
+ // $FlowFixMe[prop-missing]
538
+ performance.timeOrigin,
539
+ );
540
+ }
541
+
542
const rootTask = createTask(
543
this,
544
model,
712
},
713
);
714
715
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
716
+ // If this is async we need to time when this task finishes.
717
+ newTask.timed = true;
718
+ }
719
+
720
return newTask.id;
721
}
722
1267
// being no references to this as an owner.
1268
1269
outlineComponentInfo(request, componentDebugInfo);
1270
+
1271
+ // Track when we started rendering this component.
1272
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1273
+ task.timed = true;
1274
+ emitTimingChunk(request, componentDebugID, performance.now());
1275
+ }
1276
+
1277
emitDebugChunk(request, componentDebugID, componentDebugInfo);
1278
1279
// We've emitted the latest environment for this task so we track that.
1803
}
1804
1805
function pingTask(request: Request, task: Task): void {
1806
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1807
+ // If this was async we need to emit the time when it completes.
1808
+ task.timed = true;
1809
+ }
1810
const pingedTasks = request.pingedTasks;
1811
pingedTasks.push(task);
1812
if (pingedTasks.length === 1) {
1900
thenableState: null,
1901
}: Omit<
1902
Task,
1865
- 'environmentName' | 'debugOwner' | 'debugStack' | 'debugTask',
1903
+ 'timed' | 'environmentName' | 'debugOwner' | 'debugStack' | 'debugTask',
1904
>): any);
1905
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1906
+ task.timed = false;
1907
+ }
1908
if (__DEV__) {
1909
task.environmentName = request.environmentName();
1910
task.debugOwner = debugOwner;
3810
request.completedRegularChunks.push(processedChunk);
3811
}
3812
3813
+function emitTimeOriginChunk(request: Request, timeOrigin: number): void {
3814
+ // We emit the time origin once. All ReactTimeInfo timestamps later in the stream
3815
+ // are relative to this time origin. This allows for more compact number encoding
3816
+ // and lower precision loss.
3817
+ request.pendingChunks++;
3818
+ const row = ':N' + timeOrigin + '\n';
3819
+ const processedChunk = stringToChunk(row);
3820
+ // TODO: Move to its own priority queue.
3821
+ request.completedRegularChunks.push(processedChunk);
3822
+}
3823
+
3824
function forwardDebugInfo(
3825
request: Request,
3826
id: number,
3828
) {
3829
for (let i = 0; i < debugInfo.length; i++) {
3830
request.pendingChunks++;
3779
- if (typeof debugInfo[i].name === 'string') {
3780
- // We outline this model eagerly so that we can refer to by reference as an owner.
3781
- // If we had a smarter way to dedupe we might not have to do this if there ends up
3782
- // being no references to this as an owner.
3783
- outlineComponentInfo(request, (debugInfo[i]: any));
3831
+ if (typeof debugInfo[i].time === 'number') {
3832
+ // When forwarding time we need to ensure to convert it to the time space of the payload.
3833
+ emitTimingChunk(request, id, debugInfo[i].time);
3834
+ } else {
3835
+ if (typeof debugInfo[i].name === 'string') {
3836
+ // We outline this model eagerly so that we can refer to by reference as an owner.
3837
+ // If we had a smarter way to dedupe we might not have to do this if there ends up
3838
+ // being no references to this as an owner.
3839
+ outlineComponentInfo(request, (debugInfo[i]: any));
3840
+ }
3841
+ emitDebugChunk(request, id, debugInfo[i]);
3842
}
3785
- emitDebugChunk(request, id, debugInfo[i]);
3843
}
3844
}
3845
3846
+function emitTimingChunk(
3847
+ request: Request,
3848
+ id: number,
3849
+ timestamp: number,
3850
+): void {
3851
+ if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
3852
+ return;
3853
+ }
3854
+ request.pendingChunks++;
3855
+ const relativeTimestamp = timestamp - request.timeOrigin;
3856
+ const row =
3857
+ serializeRowHeader('D', id) + '{"time":' + relativeTimestamp + '}\n';
3858
+ const processedChunk = stringToChunk(row);
3859
+ // TODO: Move to its own priority queue.
3860
+ request.completedRegularChunks.push(processedChunk);
3861
+}
3862
+
3863
function emitChunk(
3864
request: Request,
3865
task: Task,
3951
}
3952
3953
function erroredTask(request: Request, task: Task, error: mixed): void {
3954
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
3955
+ if (task.timed) {
3956
+ emitTimingChunk(request, task.id, performance.now());
3957
+ }
3958
+ }
3959
request.abortableTasks.delete(task);
3960
task.status = ERRORED;
3961
if (
4018
task.keyPath = null;
4019
task.implicitSlot = false;
4020
4021
+ if (__DEV__) {
4022
+ const currentEnv = (0, request.environmentName)();
4023
+ if (currentEnv !== task.environmentName) {
4024
+ request.pendingChunks++;
4025
+ // The environment changed since we last emitted any debug information for this
4026
+ // task. We emit an entry that just includes the environment name change.
4027
+ emitDebugChunk(request, task.id, {env: currentEnv});
4028
+ }
4029
+ }
4030
+ // We've finished rendering. Log the end time.
4031
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
4032
+ if (task.timed) {
4033
+ emitTimingChunk(request, task.id, performance.now());
4034
+ }
4035
+ }
4036
+
4037
if (typeof resolvedModel === 'object' && resolvedModel !== null) {
4038
// We're not in a contextual place here so we can refer to this object by this ID for
4039
// any future references.
4040
request.writtenObjects.set(resolvedModel, serializeByValueID(task.id));
4041
3947
- if (__DEV__) {
3948
- const currentEnv = (0, request.environmentName)();
3949
- if (currentEnv !== task.environmentName) {
3950
- request.pendingChunks++;
3951
- // The environment changed since we last emitted any debug information for this
3952
- // task. We emit an entry that just includes the environment name change.
3953
- emitDebugChunk(request, task.id, {env: currentEnv});
3954
- }
3955
- }
3956
-
4042
// Object might contain unresolved values like additional elements.
4043
// This is simulating what the JSON loop would do if this was part of it.
4044
emitChunk(request, task, resolvedModel);
4047
// We don't need to escape it again so it's not passed the toJSON replacer.
4048
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
4049
const json: string = stringify(resolvedModel);
3965
-
3966
- if (__DEV__) {
3967
- const currentEnv = (0, request.environmentName)();
3968
- if (currentEnv !== task.environmentName) {
3969
- request.pendingChunks++;
3970
- // The environment changed since we last emitted any debug information for this
3971
- // task. We emit an entry that just includes the environment name change.
3972
- emitDebugChunk(request, task.id, {env: currentEnv});
3973
- }
3974
- }
3975
-
4050
emitModelChunk(request, task.id, json);
4051
}
4052
4156
return;
4157
}
4158
task.status = ABORTED;
4159
+ // Track when we aborted this task as its end time.
4160
+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
4161
+ if (task.timed) {
4162
+ emitTimingChunk(request, task.id, performance.now());
4163
+ }
4164
+ }
4165
// Instead of emitting an error per task.id, we emit a model that only
4166
// has a single value referencing the error.
4167
const ref = serializeByValueID(errorId);