60
describe(preprocessData, () => {
61
let React;
62
let ReactDOM;
63
+ let Scheduler;
64
65
let act;
66
let clearedMarks;
136
});
137
}
138
139
+ function createNativeEventEntry(type, duration) {
140
+ return createUserTimingEntry({
141
+ cat: 'devtools.timeline',
142
+ name: 'EventDispatch',
143
+ args: {data: {type}},
144
+ dur: duration,
145
+ tdur: duration,
146
+ });
147
+ }
148
+
149
+ function creactCpuProfilerSample() {
150
+ return createUserTimingEntry({
151
+ args: {data: {startTime: ++startTime}},
152
+ cat: 'disabled-by-default-v8.cpu_profiler',
153
+ id: '0x1',
154
+ name: 'Profile',
155
+ ph: 'P',
156
+ });
157
+ }
158
+
159
function createBoilerplateEntries() {
160
return [
161
createProfilerVersionEntry(),
165
}
166
167
function createUserTimingData(sampleMarks) {
147
- const cpuProfilerSample = createUserTimingEntry({
148
- args: {data: {startTime: ++startTime}},
149
- cat: 'disabled-by-default-v8.cpu_profiler',
150
- id: '0x1',
151
- name: 'Profile',
152
- ph: 'P',
153
- });
168
+ const cpuProfilerSample = creactCpuProfilerSample();
169
170
const randomSample = createUserTimingEntry({
171
dur: 100,
183
pid: ++pid,
184
tid: ++tid,
185
ts: ++startTime,
171
- args: {data: {navigationId: 'fake-navigation-id'}},
186
+ args: {data: {}},
187
cat: 'blink.user_timing',
188
name: markName,
189
ph: 'R',
200
201
React = require('react');
202
ReactDOM = require('react-dom');
203
+ Scheduler = require('scheduler');
204
205
const TestUtils = require('react-dom/test-utils');
206
act = TestUtils.act;
235
});
236
237
it('should return empty data given a timeline with no React scheduling profiling marks', () => {
222
- const cpuProfilerSample = createUserTimingEntry({
223
- args: {data: {startTime: ++startTime}},
224
- cat: 'disabled-by-default-v8.cpu_profiler',
225
- id: '0x1',
226
- name: 'Profile',
227
- ph: 'P',
228
- });
238
+ const cpuProfilerSample = creactCpuProfilerSample();
239
const randomSample = createUserTimingEntry({
240
dur: 100,
241
tdur: 200,
302
});
303
304
it('should process legacy data format (before lane labels were added)', () => {
295
- const cpuProfilerSample = createUserTimingEntry({
296
- args: {data: {startTime: ++startTime}},
297
- cat: 'disabled-by-default-v8.cpu_profiler',
298
- id: '0x1',
299
- name: 'Profile',
300
- ph: 'P',
301
- });
305
+ const cpuProfilerSample = creactCpuProfilerSample();
306
307
if (gate(flags => flags.enableSchedulingProfiler)) {
308
// Data below is hard-coded based on an older profile sample.
340
}),
341
]);
342
expect(data).toMatchInlineSnapshot(`
339
- Object {
340
- "componentMeasures": Array [],
341
- "duration": 0.011,
342
- "flamechart": Array [],
343
- "laneToLabelMap": Map {
344
- 0 => "Sync",
345
- 1 => "InputContinuousHydration",
346
- 2 => "InputContinuous",
347
- 3 => "DefaultHydration",
348
- 4 => "Default",
349
- 5 => "TransitionHydration",
350
- 6 => "Transition",
351
- 7 => "Transition",
352
- 8 => "Transition",
353
- 9 => "Transition",
354
- 10 => "Transition",
355
- 11 => "Transition",
356
- 12 => "Transition",
357
- 13 => "Transition",
358
- 14 => "Transition",
359
- 15 => "Transition",
360
- 16 => "Transition",
361
- 17 => "Transition",
362
- 18 => "Transition",
363
- 19 => "Transition",
364
- 20 => "Transition",
365
- 21 => "Transition",
366
- 22 => "Retry",
367
- 23 => "Retry",
368
- 24 => "Retry",
369
- 25 => "Retry",
370
- 26 => "Retry",
371
- 27 => "SelectiveHydration",
372
- 28 => "IdleHydration",
373
- 29 => "Idle",
374
- 30 => "Offscreen",
375
- },
376
- "measures": Array [
377
- Object {
378
- "batchUID": 0,
379
- "depth": 0,
380
- "duration": 0.004999999999999999,
381
- "lanes": Array [
382
- 9,
383
- ],
384
- "timestamp": 0.006,
385
- "type": "render-idle",
386
- },
387
- Object {
388
- "batchUID": 0,
389
- "depth": 0,
390
- "duration": 0.001,
391
- "lanes": Array [
392
- 9,
393
- ],
394
- "timestamp": 0.006,
395
- "type": "render",
396
- },
397
- Object {
398
- "batchUID": 0,
399
- "depth": 0,
400
- "duration": 0.002999999999999999,
401
- "lanes": Array [
402
- 9,
403
- ],
404
- "timestamp": 0.008,
405
- "type": "commit",
406
- },
407
- Object {
408
- "batchUID": 0,
409
- "depth": 1,
410
- "duration": 0.0010000000000000009,
411
- "lanes": Array [
412
- 9,
413
- ],
414
- "timestamp": 0.009,
415
- "type": "layout-effects",
416
- },
417
- ],
418
- "nativeEvents": Array [],
419
- "otherUserTimingMarks": Array [],
420
- "reactVersion": "17.0.3",
421
- "schedulingEvents": Array [
422
- Object {
423
- "lanes": Array [
424
- 9,
425
- ],
426
- "timestamp": 0.005,
427
- "type": "schedule-render",
428
- "warning": null,
429
- },
430
- ],
431
- "startTime": 1,
432
- "suspenseEvents": Array [],
433
- }
434
- `);
343
+ Object {
344
+ "componentMeasures": Array [],
345
+ "duration": 0.011,
346
+ "flamechart": Array [],
347
+ "laneToLabelMap": Map {
348
+ 0 => "Sync",
349
+ 1 => "InputContinuousHydration",
350
+ 2 => "InputContinuous",
351
+ 3 => "DefaultHydration",
352
+ 4 => "Default",
353
+ 5 => "TransitionHydration",
354
+ 6 => "Transition",
355
+ 7 => "Transition",
356
+ 8 => "Transition",
357
+ 9 => "Transition",
358
+ 10 => "Transition",
359
+ 11 => "Transition",
360
+ 12 => "Transition",
361
+ 13 => "Transition",
362
+ 14 => "Transition",
363
+ 15 => "Transition",
364
+ 16 => "Transition",
365
+ 17 => "Transition",
366
+ 18 => "Transition",
367
+ 19 => "Transition",
368
+ 20 => "Transition",
369
+ 21 => "Transition",
370
+ 22 => "Retry",
371
+ 23 => "Retry",
372
+ 24 => "Retry",
373
+ 25 => "Retry",
374
+ 26 => "Retry",
375
+ 27 => "SelectiveHydration",
376
+ 28 => "IdleHydration",
377
+ 29 => "Idle",
378
+ 30 => "Offscreen",
379
+ },
380
+ "measures": Array [
381
+ Object {
382
+ "batchUID": 0,
383
+ "depth": 0,
384
+ "duration": 0.004999999999999999,
385
+ "lanes": Array [
386
+ 9,
387
+ ],
388
+ "timestamp": 0.006,
389
+ "type": "render-idle",
390
+ },
391
+ Object {
392
+ "batchUID": 0,
393
+ "depth": 0,
394
+ "duration": 0.001,
395
+ "lanes": Array [
396
+ 9,
397
+ ],
398
+ "timestamp": 0.006,
399
+ "type": "render",
400
+ },
401
+ Object {
402
+ "batchUID": 0,
403
+ "depth": 0,
404
+ "duration": 0.002999999999999999,
405
+ "lanes": Array [
406
+ 9,
407
+ ],
408
+ "timestamp": 0.008,
409
+ "type": "commit",
410
+ },
411
+ Object {
412
+ "batchUID": 0,
413
+ "depth": 1,
414
+ "duration": 0.0010000000000000009,
415
+ "lanes": Array [
416
+ 9,
417
+ ],
418
+ "timestamp": 0.009,
419
+ "type": "layout-effects",
420
+ },
421
+ ],
422
+ "nativeEvents": Array [],
423
+ "otherUserTimingMarks": Array [],
424
+ "reactVersion": "17.0.3",
425
+ "schedulingEvents": Array [
426
+ Object {
427
+ "lanes": Array [
428
+ 9,
429
+ ],
430
+ "timestamp": 0.005,
431
+ "type": "schedule-render",
432
+ "warning": null,
433
+ },
434
+ ],
435
+ "startTime": 1,
436
+ "suspenseEvents": Array [],
437
+ }
438
+ `);
439
}
440
});
441
448
...createUserTimingData(clearedMarks),
449
]);
450
expect(data).toMatchInlineSnapshot(`
447
- Object {
448
- "componentMeasures": Array [],
449
- "duration": 0.013,
450
- "flamechart": Array [],
451
- "laneToLabelMap": Map {
452
- 0 => "Sync",
453
- 1 => "InputContinuousHydration",
454
- 2 => "InputContinuous",
455
- 3 => "DefaultHydration",
456
- 4 => "Default",
457
- 5 => "TransitionHydration",
458
- 6 => "Transition",
459
- 7 => "Transition",
460
- 8 => "Transition",
461
- 9 => "Transition",
462
- 10 => "Transition",
463
- 11 => "Transition",
464
- 12 => "Transition",
465
- 13 => "Transition",
466
- 14 => "Transition",
467
- 15 => "Transition",
468
- 16 => "Transition",
469
- 17 => "Transition",
470
- 18 => "Transition",
471
- 19 => "Transition",
472
- 20 => "Transition",
473
- 21 => "Transition",
474
- 22 => "Retry",
475
- 23 => "Retry",
476
- 24 => "Retry",
477
- 25 => "Retry",
478
- 26 => "Retry",
479
- 27 => "SelectiveHydration",
480
- 28 => "IdleHydration",
481
- 29 => "Idle",
482
- 30 => "Offscreen",
483
- },
484
- "measures": Array [
485
- Object {
486
- "batchUID": 0,
487
- "depth": 0,
488
- "duration": 0.008,
489
- "lanes": Array [
490
- 0,
491
- ],
492
- "timestamp": 0.005,
493
- "type": "render-idle",
494
- },
495
- Object {
496
- "batchUID": 0,
497
- "depth": 0,
498
- "duration": 0.001,
499
- "lanes": Array [
500
- 0,
501
- ],
502
- "timestamp": 0.005,
503
- "type": "render",
504
- },
505
- Object {
506
- "batchUID": 0,
507
- "depth": 0,
508
- "duration": 0.005999999999999999,
509
- "lanes": Array [
510
- 0,
511
- ],
512
- "timestamp": 0.007,
513
- "type": "commit",
514
- },
515
- Object {
516
- "batchUID": 0,
517
- "depth": 1,
518
- "duration": 0.0010000000000000009,
519
- "lanes": Array [
520
- 0,
521
- ],
522
- "timestamp": 0.011,
523
- "type": "layout-effects",
524
- },
525
- ],
526
- "nativeEvents": Array [],
527
- "otherUserTimingMarks": Array [
528
- Object {
529
- "name": "__v3",
530
- "timestamp": 0.003,
531
- },
532
- ],
533
- "reactVersion": "17.0.3",
534
- "schedulingEvents": Array [
535
- Object {
536
- "lanes": Array [
537
- 0,
538
- ],
539
- "timestamp": 0.004,
540
- "type": "schedule-render",
541
- "warning": null,
542
- },
543
- ],
544
- "startTime": 4,
545
- "suspenseEvents": Array [],
546
- }
547
- `);
451
+ Object {
452
+ "componentMeasures": Array [],
453
+ "duration": 0.013,
454
+ "flamechart": Array [],
455
+ "laneToLabelMap": Map {
456
+ 0 => "Sync",
457
+ 1 => "InputContinuousHydration",
458
+ 2 => "InputContinuous",
459
+ 3 => "DefaultHydration",
460
+ 4 => "Default",
461
+ 5 => "TransitionHydration",
462
+ 6 => "Transition",
463
+ 7 => "Transition",
464
+ 8 => "Transition",
465
+ 9 => "Transition",
466
+ 10 => "Transition",
467
+ 11 => "Transition",
468
+ 12 => "Transition",
469
+ 13 => "Transition",
470
+ 14 => "Transition",
471
+ 15 => "Transition",
472
+ 16 => "Transition",
473
+ 17 => "Transition",
474
+ 18 => "Transition",
475
+ 19 => "Transition",
476
+ 20 => "Transition",
477
+ 21 => "Transition",
478
+ 22 => "Retry",
479
+ 23 => "Retry",
480
+ 24 => "Retry",
481
+ 25 => "Retry",
482
+ 26 => "Retry",
483
+ 27 => "SelectiveHydration",
484
+ 28 => "IdleHydration",
485
+ 29 => "Idle",
486
+ 30 => "Offscreen",
487
+ },
488
+ "measures": Array [
489
+ Object {
490
+ "batchUID": 0,
491
+ "depth": 0,
492
+ "duration": 0.008,
493
+ "lanes": Array [
494
+ 0,
495
+ ],
496
+ "timestamp": 0.005,
497
+ "type": "render-idle",
498
+ },
499
+ Object {
500
+ "batchUID": 0,
501
+ "depth": 0,
502
+ "duration": 0.001,
503
+ "lanes": Array [
504
+ 0,
505
+ ],
506
+ "timestamp": 0.005,
507
+ "type": "render",
508
+ },
509
+ Object {
510
+ "batchUID": 0,
511
+ "depth": 0,
512
+ "duration": 0.005999999999999999,
513
+ "lanes": Array [
514
+ 0,
515
+ ],
516
+ "timestamp": 0.007,
517
+ "type": "commit",
518
+ },
519
+ Object {
520
+ "batchUID": 0,
521
+ "depth": 1,
522
+ "duration": 0.0010000000000000009,
523
+ "lanes": Array [
524
+ 0,
525
+ ],
526
+ "timestamp": 0.011,
527
+ "type": "layout-effects",
528
+ },
529
+ ],
530
+ "nativeEvents": Array [],
531
+ "otherUserTimingMarks": Array [
532
+ Object {
533
+ "name": "__v3",
534
+ "timestamp": 0.003,
535
+ },
536
+ ],
537
+ "reactVersion": "17.0.3",
538
+ "schedulingEvents": Array [
539
+ Object {
540
+ "lanes": Array [
541
+ 0,
542
+ ],
543
+ "timestamp": 0.004,
544
+ "type": "schedule-render",
545
+ "warning": null,
546
+ },
547
+ ],
548
+ "startTime": 4,
549
+ "suspenseEvents": Array [],
550
+ }
551
+ `);
552
}
553
});
554
572
...createUserTimingData(clearedMarks),
573
]);
574
expect(data).toMatchInlineSnapshot(`
571
- Object {
572
- "componentMeasures": Array [
573
- Object {
574
- "componentName": "App",
575
- "duration": 0.001,
576
- "timestamp": 0.006,
577
- "warning": null,
578
- },
579
- Object {
580
- "componentName": "App",
581
- "duration": 0.0010000000000000009,
582
- "timestamp": 0.02,
583
- "warning": null,
584
- },
585
- ],
586
- "duration": 0.031,
587
- "flamechart": Array [],
588
- "laneToLabelMap": Map {
589
- 0 => "Sync",
590
- 1 => "InputContinuousHydration",
591
- 2 => "InputContinuous",
592
- 3 => "DefaultHydration",
593
- 4 => "Default",
594
- 5 => "TransitionHydration",
595
- 6 => "Transition",
596
- 7 => "Transition",
597
- 8 => "Transition",
598
- 9 => "Transition",
599
- 10 => "Transition",
600
- 11 => "Transition",
601
- 12 => "Transition",
602
- 13 => "Transition",
603
- 14 => "Transition",
604
- 15 => "Transition",
605
- 16 => "Transition",
606
- 17 => "Transition",
607
- 18 => "Transition",
608
- 19 => "Transition",
609
- 20 => "Transition",
610
- 21 => "Transition",
611
- 22 => "Retry",
612
- 23 => "Retry",
613
- 24 => "Retry",
614
- 25 => "Retry",
615
- 26 => "Retry",
616
- 27 => "SelectiveHydration",
617
- 28 => "IdleHydration",
618
- 29 => "Idle",
619
- 30 => "Offscreen",
620
- },
621
- "measures": Array [
622
- Object {
623
- "batchUID": 0,
624
- "depth": 0,
625
- "duration": 0.009999999999999998,
626
- "lanes": Array [
627
- 4,
628
- ],
629
- "timestamp": 0.005,
630
- "type": "render-idle",
631
- },
632
- Object {
633
- "batchUID": 0,
634
- "depth": 0,
635
- "duration": 0.003,
636
- "lanes": Array [
637
- 4,
638
- ],
639
- "timestamp": 0.005,
640
- "type": "render",
641
- },
642
- Object {
643
- "batchUID": 0,
644
- "depth": 0,
645
- "duration": 0.006,
646
- "lanes": Array [
647
- 4,
648
- ],
649
- "timestamp": 0.009,
650
- "type": "commit",
651
- },
652
- Object {
653
- "batchUID": 0,
654
- "depth": 1,
655
- "duration": 0.0010000000000000009,
656
- "lanes": Array [
657
- 4,
658
- ],
659
- "timestamp": 0.013,
660
- "type": "layout-effects",
661
- },
662
- Object {
663
- "batchUID": 0,
664
- "depth": 0,
665
- "duration": 0.0019999999999999983,
666
- "lanes": Array [
667
- 4,
668
- ],
669
- "timestamp": 0.016,
670
- "type": "passive-effects",
671
- },
672
- Object {
673
- "batchUID": 1,
674
- "depth": 0,
675
- "duration": 0.010000000000000002,
676
- "lanes": Array [
677
- 4,
678
- ],
679
- "timestamp": 0.019,
680
- "type": "render-idle",
681
- },
682
- Object {
683
- "batchUID": 1,
684
- "depth": 0,
685
- "duration": 0.002999999999999999,
686
- "lanes": Array [
687
- 4,
688
- ],
689
- "timestamp": 0.019,
690
- "type": "render",
691
- },
692
- Object {
693
- "batchUID": 1,
694
- "depth": 0,
695
- "duration": 0.006000000000000002,
696
- "lanes": Array [
697
- 4,
698
- ],
699
- "timestamp": 0.023,
700
- "type": "commit",
701
- },
702
- Object {
703
- "batchUID": 1,
704
- "depth": 1,
705
- "duration": 0.0010000000000000009,
706
- "lanes": Array [
707
- 4,
708
- ],
709
- "timestamp": 0.027,
710
- "type": "layout-effects",
711
- },
712
- Object {
713
- "batchUID": 1,
714
- "depth": 0,
715
- "duration": 0.0010000000000000009,
716
- "lanes": Array [
717
- 4,
718
- ],
719
- "timestamp": 0.03,
720
- "type": "passive-effects",
721
- },
722
- ],
723
- "nativeEvents": Array [],
724
- "otherUserTimingMarks": Array [
725
- Object {
726
- "name": "__v3",
727
- "timestamp": 0.003,
728
- },
729
- ],
730
- "reactVersion": "17.0.3",
731
- "schedulingEvents": Array [
732
- Object {
733
- "lanes": Array [
734
- 4,
735
- ],
736
- "timestamp": 0.004,
737
- "type": "schedule-render",
738
- "warning": null,
739
- },
740
- Object {
741
- "componentName": "App",
742
- "lanes": Array [
743
- 4,
744
- ],
745
- "timestamp": 0.017,
746
- "type": "schedule-state-update",
747
- "warning": null,
748
- },
749
- ],
750
- "startTime": 4,
751
- "suspenseEvents": Array [],
752
- }
753
- `);
575
+ Object {
576
+ "componentMeasures": Array [
577
+ Object {
578
+ "componentName": "App",
579
+ "duration": 0.001,
580
+ "timestamp": 0.006,
581
+ "warning": null,
582
+ },
583
+ Object {
584
+ "componentName": "App",
585
+ "duration": 0.0010000000000000009,
586
+ "timestamp": 0.02,
587
+ "warning": null,
588
+ },
589
+ ],
590
+ "duration": 0.031,
591
+ "flamechart": Array [],
592
+ "laneToLabelMap": Map {
593
+ 0 => "Sync",
594
+ 1 => "InputContinuousHydration",
595
+ 2 => "InputContinuous",
596
+ 3 => "DefaultHydration",
597
+ 4 => "Default",
598
+ 5 => "TransitionHydration",
599
+ 6 => "Transition",
600
+ 7 => "Transition",
601
+ 8 => "Transition",
602
+ 9 => "Transition",
603
+ 10 => "Transition",
604
+ 11 => "Transition",
605
+ 12 => "Transition",
606
+ 13 => "Transition",
607
+ 14 => "Transition",
608
+ 15 => "Transition",
609
+ 16 => "Transition",
610
+ 17 => "Transition",
611
+ 18 => "Transition",
612
+ 19 => "Transition",
613
+ 20 => "Transition",
614
+ 21 => "Transition",
615
+ 22 => "Retry",
616
+ 23 => "Retry",
617
+ 24 => "Retry",
618
+ 25 => "Retry",
619
+ 26 => "Retry",
620
+ 27 => "SelectiveHydration",
621
+ 28 => "IdleHydration",
622
+ 29 => "Idle",
623
+ 30 => "Offscreen",
624
+ },
625
+ "measures": Array [
626
+ Object {
627
+ "batchUID": 0,
628
+ "depth": 0,
629
+ "duration": 0.009999999999999998,
630
+ "lanes": Array [
631
+ 4,
632
+ ],
633
+ "timestamp": 0.005,
634
+ "type": "render-idle",
635
+ },
636
+ Object {
637
+ "batchUID": 0,
638
+ "depth": 0,
639
+ "duration": 0.003,
640
+ "lanes": Array [
641
+ 4,
642
+ ],
643
+ "timestamp": 0.005,
644
+ "type": "render",
645
+ },
646
+ Object {
647
+ "batchUID": 0,
648
+ "depth": 0,
649
+ "duration": 0.006,
650
+ "lanes": Array [
651
+ 4,
652
+ ],
653
+ "timestamp": 0.009,
654
+ "type": "commit",
655
+ },
656
+ Object {
657
+ "batchUID": 0,
658
+ "depth": 1,
659
+ "duration": 0.0010000000000000009,
660
+ "lanes": Array [
661
+ 4,
662
+ ],
663
+ "timestamp": 0.013,
664
+ "type": "layout-effects",
665
+ },
666
+ Object {
667
+ "batchUID": 0,
668
+ "depth": 0,
669
+ "duration": 0.0019999999999999983,
670
+ "lanes": Array [
671
+ 4,
672
+ ],
673
+ "timestamp": 0.016,
674
+ "type": "passive-effects",
675
+ },
676
+ Object {
677
+ "batchUID": 1,
678
+ "depth": 0,
679
+ "duration": 0.010000000000000002,
680
+ "lanes": Array [
681
+ 4,
682
+ ],
683
+ "timestamp": 0.019,
684
+ "type": "render-idle",
685
+ },
686
+ Object {
687
+ "batchUID": 1,
688
+ "depth": 0,
689
+ "duration": 0.002999999999999999,
690
+ "lanes": Array [
691
+ 4,
692
+ ],
693
+ "timestamp": 0.019,
694
+ "type": "render",
695
+ },
696
+ Object {
697
+ "batchUID": 1,
698
+ "depth": 0,
699
+ "duration": 0.006000000000000002,
700
+ "lanes": Array [
701
+ 4,
702
+ ],
703
+ "timestamp": 0.023,
704
+ "type": "commit",
705
+ },
706
+ Object {
707
+ "batchUID": 1,
708
+ "depth": 1,
709
+ "duration": 0.0010000000000000009,
710
+ "lanes": Array [
711
+ 4,
712
+ ],
713
+ "timestamp": 0.027,
714
+ "type": "layout-effects",
715
+ },
716
+ Object {
717
+ "batchUID": 1,
718
+ "depth": 0,
719
+ "duration": 0.0010000000000000009,
720
+ "lanes": Array [
721
+ 4,
722
+ ],
723
+ "timestamp": 0.03,
724
+ "type": "passive-effects",
725
+ },
726
+ ],
727
+ "nativeEvents": Array [],
728
+ "otherUserTimingMarks": Array [
729
+ Object {
730
+ "name": "__v3",
731
+ "timestamp": 0.003,
732
+ },
733
+ ],
734
+ "reactVersion": "17.0.3",
735
+ "schedulingEvents": Array [
736
+ Object {
737
+ "lanes": Array [
738
+ 4,
739
+ ],
740
+ "timestamp": 0.004,
741
+ "type": "schedule-render",
742
+ "warning": null,
743
+ },
744
+ Object {
745
+ "componentName": "App",
746
+ "lanes": Array [
747
+ 4,
748
+ ],
749
+ "timestamp": 0.017,
750
+ "type": "schedule-state-update",
751
+ "warning": null,
752
+ },
753
+ ],
754
+ "startTime": 4,
755
+ "suspenseEvents": Array [],
756
+ }
757
+ `);
758
}
759
});
760
837
`);
838
});
839
840
+ describe('warnings', () => {
841
+ describe('long event handlers', () => {
842
+ it('should not warn when React scedules a (sync) update inside of a short event handler', () => {
843
+ function App() {
844
+ return null;
845
+ }
846
+
847
+ if (gate(flags => flags.enableSchedulingProfiler)) {
848
+ const testMarks = [
849
+ creactCpuProfilerSample(),
850
+ ...createBoilerplateEntries(),
851
+ createNativeEventEntry('click', 5),
852
+ ];
853
+
854
+ clearedMarks.splice(0);
855
+
856
+ ReactDOM.render(<App />, document.createElement('div'));
857
+
858
+ testMarks.push(...createUserTimingData(clearedMarks));
859
+
860
+ const data = preprocessData(testMarks);
861
+ const event = data.nativeEvents.find(({type}) => type === 'click');
862
+ expect(event.warning).toBe(null);
863
+ }
864
+ });
865
+
866
+ it('should not warn about long events if the cause was non-React JavaScript', () => {
867
+ function App() {
868
+ return null;
869
+ }
870
+
871
+ if (gate(flags => flags.enableSchedulingProfiler)) {
872
+ const testMarks = [
873
+ creactCpuProfilerSample(),
874
+ ...createBoilerplateEntries(),
875
+ createNativeEventEntry('click', 25000),
876
+ ];
877
+
878
+ startTime += 2000;
879
+
880
+ clearedMarks.splice(0);
881
+
882
+ ReactDOM.render(<App />, document.createElement('div'));
883
+
884
+ testMarks.push(...createUserTimingData(clearedMarks));
885
+
886
+ const data = preprocessData(testMarks);
887
+ const event = data.nativeEvents.find(({type}) => type === 'click');
888
+ expect(event.warning).toBe(null);
889
+ }
890
+ });
891
+
892
+ it('should warn when React scedules a long (sync) update inside of an event', () => {
893
+ function App() {
894
+ return null;
895
+ }
896
+
897
+ if (gate(flags => flags.enableSchedulingProfiler)) {
898
+ const testMarks = [
899
+ creactCpuProfilerSample(),
900
+ ...createBoilerplateEntries(),
901
+ createNativeEventEntry('click', 25000),
902
+ ];
903
+
904
+ clearedMarks.splice(0);
905
+
906
+ ReactDOM.render(<App />, document.createElement('div'));
907
+
908
+ clearedMarks.forEach(markName => {
909
+ if (markName === '--render-stop') {
910
+ // Fake a long running render
911
+ startTime += 20000;
912
+ }
913
+
914
+ testMarks.push({
915
+ pid: ++pid,
916
+ tid: ++tid,
917
+ ts: ++startTime,
918
+ args: {data: {}},
919
+ cat: 'blink.user_timing',
920
+ name: markName,
921
+ ph: 'R',
922
+ });
923
+ });
924
+
925
+ const data = preprocessData(testMarks);
926
+ const event = data.nativeEvents.find(({type}) => type === 'click');
927
+ expect(event.warning).toMatchInlineSnapshot(
928
+ `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
929
+ );
930
+ }
931
+ });
932
+
933
+ it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', () => {
934
+ function Yield({id, value}) {
935
+ Scheduler.unstable_yieldValue(`${id}:${value}`);
936
+ return null;
937
+ }
938
+
939
+ if (gate(flags => flags.enableSchedulingProfiler)) {
940
+ const testMarks = [
941
+ creactCpuProfilerSample(),
942
+ ...createBoilerplateEntries(),
943
+ ];
944
+
945
+ // Advance the clock by some arbitrary amount.
946
+ startTime += 50000;
947
+
948
+ const root = ReactDOM.createRoot(document.createElement('div'));
949
+
950
+ React.startTransition(() => {
951
+ // Start rendering an async update (but don't finish).
952
+ root.render(
953
+ <>
954
+ <Yield id="A" value={1} />
955
+ <Yield id="B" value={1} />
956
+ </>,
957
+ );
958
+ expect(Scheduler).toFlushAndYieldThrough(['A:1']);
959
+
960
+ testMarks.push(...createUserTimingData(clearedMarks));
961
+ clearedMarks.splice(0);
962
+
963
+ // Advance the clock some more to make the pending React update seem long.
964
+ startTime += 20000;
965
+
966
+ // Fake a long "click" event in the middle
967
+ // and schedule a sync update that will also flush the previous work.
968
+ testMarks.push(createNativeEventEntry('click', 25000));
969
+ ReactDOM.flushSync(() => {
970
+ root.render(
971
+ <>
972
+ <Yield id="A" value={2} />
973
+ <Yield id="B" value={2} />
974
+ </>,
975
+ );
976
+ });
977
+ });
978
+
979
+ expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
980
+
981
+ testMarks.push(...createUserTimingData(clearedMarks));
982
+
983
+ const data = preprocessData(testMarks);
984
+ const event = data.nativeEvents.find(({type}) => type === 'click');
985
+ expect(event.warning).toBe(null);
986
+ }
987
+ });
988
+ });
989
+
990
+ describe('nested updates', () => {
991
+ it('should not warn about short nested (state) updates during layout effects', () => {
992
+ function Component() {
993
+ const [didMount, setDidMount] = React.useState(false);
994
+ Scheduler.unstable_yieldValue(
995
+ `Component ${didMount ? 'update' : 'mount'}`,
996
+ );
997
+ React.useLayoutEffect(() => {
998
+ setDidMount(true);
999
+ }, []);
1000
+ return didMount;
1001
+ }
1002
+
1003
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1004
+ const root = ReactDOM.createRoot(document.createElement('div'));
1005
+ act(() => {
1006
+ root.render(<Component />);
1007
+ });
1008
+
1009
+ expect(Scheduler).toHaveYielded([
1010
+ 'Component mount',
1011
+ 'Component update',
1012
+ ]);
1013
+
1014
+ const data = preprocessData([
1015
+ ...createBoilerplateEntries(),
1016
+ ...createUserTimingData(clearedMarks),
1017
+ ]);
1018
+
1019
+ const event = data.schedulingEvents.find(
1020
+ ({type}) => type === 'schedule-state-update',
1021
+ );
1022
+ expect(event.warning).toBe(null);
1023
+ }
1024
+ });
1025
+
1026
+ it('should not warn about short (forced) updates during layout effects', () => {
1027
+ class Component extends React.Component {
1028
+ _didMount: boolean = false;
1029
+ componentDidMount() {
1030
+ this._didMount = true;
1031
+ this.forceUpdate();
1032
+ }
1033
+ render() {
1034
+ Scheduler.unstable_yieldValue(
1035
+ `Component ${this._didMount ? 'update' : 'mount'}`,
1036
+ );
1037
+ return null;
1038
+ }
1039
+ }
1040
+
1041
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1042
+ const root = ReactDOM.createRoot(document.createElement('div'));
1043
+ act(() => {
1044
+ root.render(<Component />);
1045
+ });
1046
+
1047
+ expect(Scheduler).toHaveYielded([
1048
+ 'Component mount',
1049
+ 'Component update',
1050
+ ]);
1051
+
1052
+ const data = preprocessData([
1053
+ ...createBoilerplateEntries(),
1054
+ ...createUserTimingData(clearedMarks),
1055
+ ]);
1056
+
1057
+ const event = data.schedulingEvents.find(
1058
+ ({type}) => type === 'schedule-force-update',
1059
+ );
1060
+ expect(event.warning).toBe(null);
1061
+ }
1062
+ });
1063
+
1064
+ it('should warn about long nested (state) updates during layout effects', () => {
1065
+ function Component() {
1066
+ const [didMount, setDidMount] = React.useState(false);
1067
+ Scheduler.unstable_yieldValue(
1068
+ `Component ${didMount ? 'update' : 'mount'}`,
1069
+ );
1070
+ // Fake a long render
1071
+ startTime += 20000;
1072
+ React.useLayoutEffect(() => {
1073
+ setDidMount(true);
1074
+ }, []);
1075
+ return didMount;
1076
+ }
1077
+
1078
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1079
+ const cpuProfilerSample = creactCpuProfilerSample();
1080
+
1081
+ const root = ReactDOM.createRoot(document.createElement('div'));
1082
+ act(() => {
1083
+ root.render(<Component />);
1084
+ });
1085
+
1086
+ expect(Scheduler).toHaveYielded([
1087
+ 'Component mount',
1088
+ 'Component update',
1089
+ ]);
1090
+
1091
+ const testMarks = [];
1092
+ clearedMarks.forEach(markName => {
1093
+ if (markName === '--component-render-start-Component') {
1094
+ // Fake a long running render
1095
+ startTime += 20000;
1096
+ }
1097
+
1098
+ testMarks.push({
1099
+ pid: ++pid,
1100
+ tid: ++tid,
1101
+ ts: ++startTime,
1102
+ args: {data: {}},
1103
+ cat: 'blink.user_timing',
1104
+ name: markName,
1105
+ ph: 'R',
1106
+ });
1107
+ });
1108
+
1109
+ const data = preprocessData([
1110
+ cpuProfilerSample,
1111
+ ...createBoilerplateEntries(),
1112
+ ...testMarks,
1113
+ ]);
1114
+
1115
+ const event = data.schedulingEvents.find(
1116
+ ({type}) => type === 'schedule-state-update',
1117
+ );
1118
+ expect(event.warning).toMatchInlineSnapshot(
1119
+ `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
1120
+ );
1121
+ }
1122
+ });
1123
+
1124
+ it('should warn about long nested (forced) updates during layout effects', () => {
1125
+ class Component extends React.Component {
1126
+ _didMount: boolean = false;
1127
+ componentDidMount() {
1128
+ this._didMount = true;
1129
+ this.forceUpdate();
1130
+ }
1131
+ render() {
1132
+ Scheduler.unstable_yieldValue(
1133
+ `Component ${this._didMount ? 'update' : 'mount'}`,
1134
+ );
1135
+ return null;
1136
+ }
1137
+ }
1138
+
1139
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1140
+ const cpuProfilerSample = creactCpuProfilerSample();
1141
+
1142
+ const root = ReactDOM.createRoot(document.createElement('div'));
1143
+ act(() => {
1144
+ root.render(<Component />);
1145
+ });
1146
+
1147
+ expect(Scheduler).toHaveYielded([
1148
+ 'Component mount',
1149
+ 'Component update',
1150
+ ]);
1151
+
1152
+ const testMarks = [];
1153
+ clearedMarks.forEach(markName => {
1154
+ if (markName === '--component-render-start-Component') {
1155
+ // Fake a long running render
1156
+ startTime += 20000;
1157
+ }
1158
+
1159
+ testMarks.push({
1160
+ pid: ++pid,
1161
+ tid: ++tid,
1162
+ ts: ++startTime,
1163
+ args: {data: {}},
1164
+ cat: 'blink.user_timing',
1165
+ name: markName,
1166
+ ph: 'R',
1167
+ });
1168
+ });
1169
+
1170
+ const data = preprocessData([
1171
+ cpuProfilerSample,
1172
+ ...createBoilerplateEntries(),
1173
+ ...testMarks,
1174
+ ]);
1175
+
1176
+ const event = data.schedulingEvents.find(
1177
+ ({type}) => type === 'schedule-force-update',
1178
+ );
1179
+ expect(event.warning).toMatchInlineSnapshot(
1180
+ `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
1181
+ );
1182
+ }
1183
+ });
1184
+ });
1185
+
1186
+ describe('suspend during an update', () => {
1187
+ // This also tests an edge case where the a component suspends while profiling
1188
+ // before the first commit is logged (so the lane-to-labels map will not yet exist).
1189
+ it('should warn about suspending during an udpate', () => {
1190
+ let promise = null;
1191
+ let resolvedValue = null;
1192
+ function readValue(value) {
1193
+ if (resolvedValue !== null) {
1194
+ return resolvedValue;
1195
+ } else if (promise === null) {
1196
+ promise = Promise.resolve(true).then(() => {
1197
+ resolvedValue = value;
1198
+ });
1199
+ }
1200
+ throw promise;
1201
+ }
1202
+
1203
+ function Component({shouldSuspend}) {
1204
+ Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1205
+ if (shouldSuspend) {
1206
+ readValue(123);
1207
+ }
1208
+ return null;
1209
+ }
1210
+
1211
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1212
+ // Mount and commit the app
1213
+ const root = ReactDOM.createRoot(document.createElement('div'));
1214
+ act(() =>
1215
+ root.render(
1216
+ <React.Suspense fallback="Loading...">
1217
+ <Component shouldSuspend={false} />
1218
+ </React.Suspense>,
1219
+ ),
1220
+ );
1221
+
1222
+ clearedMarks.splice(0);
1223
+
1224
+ const testMarks = [creactCpuProfilerSample()];
1225
+
1226
+ // Start profiling and suspend during a render.
1227
+ act(() =>
1228
+ root.render(
1229
+ <React.Suspense fallback="Loading...">
1230
+ <Component shouldSuspend={true} />
1231
+ </React.Suspense>,
1232
+ ),
1233
+ );
1234
+
1235
+ testMarks.push(...createUserTimingData(clearedMarks));
1236
+
1237
+ const data = preprocessData(testMarks);
1238
+ expect(data.suspenseEvents).toHaveLength(1);
1239
+ expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
1240
+ `"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
1241
+ );
1242
+ }
1243
+ });
1244
+
1245
+ it('should not warn about suspending during an transition', async () => {
1246
+ let promise = null;
1247
+ let resolvedValue = null;
1248
+ function readValue(value) {
1249
+ if (resolvedValue !== null) {
1250
+ return resolvedValue;
1251
+ } else if (promise === null) {
1252
+ promise = Promise.resolve(true).then(() => {
1253
+ resolvedValue = value;
1254
+ });
1255
+ }
1256
+ throw promise;
1257
+ }
1258
+
1259
+ function Component({shouldSuspend}) {
1260
+ Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1261
+ if (shouldSuspend) {
1262
+ readValue(123);
1263
+ }
1264
+ return null;
1265
+ }
1266
+
1267
+ if (gate(flags => flags.enableSchedulingProfiler)) {
1268
+ // Mount and commit the app
1269
+ const root = ReactDOM.createRoot(document.createElement('div'));
1270
+ act(() =>
1271
+ root.render(
1272
+ <React.Suspense fallback="Loading...">
1273
+ <Component shouldSuspend={false} />
1274
+ </React.Suspense>,
1275
+ ),
1276
+ );
1277
+
1278
+ clearedMarks.splice(0);
1279
+
1280
+ const testMarks = [creactCpuProfilerSample()];
1281
+
1282
+ // Start profiling and suspend during a render.
1283
+ await act(async () =>
1284
+ React.startTransition(() =>
1285
+ root.render(
1286
+ <React.Suspense fallback="Loading...">
1287
+ <Component shouldSuspend={true} />
1288
+ </React.Suspense>,
1289
+ ),
1290
+ ),
1291
+ );
1292
+
1293
+ testMarks.push(...createUserTimingData(clearedMarks));
1294
+
1295
+ const data = preprocessData(testMarks);
1296
+ expect(data.suspenseEvents).toHaveLength(1);
1297
+ expect(data.suspenseEvents[0].warning).toBe(null);
1298
+ }
1299
+ });
1300
+ });
1301
+ });
1302
+
1303
// TODO: Add test for flamechart parsing
1304
});