Massively overhauled profiling data architecture
Brian Vaughn committed
May 22, 2019 at 06:05 UTC
c6de014a9a70d3bff970a3a58df7ac6af28fa51b
33 files changed
+3253
-3266
src/__tests__/__snapshots__/profiling-test.js.snap
deleted
-1106
@@ -1,1106 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`profiling CommitDetails should be collected for each commit: CommitDetails commitIndex: 0 1`] = `
4
-Object {
5
- "actualDurations": Map {
6
- 1 => 12,
7
- 2 => 12,
8
- 3 => 0,
9
- 4 => 1,
10
- 5 => 1,
11
- },
12
- "commitIndex": 0,
13
- "interactions": Array [],
14
- "priorityLevel": "Immediate",
15
- "rootID": 1,
16
- "selfDurations": Map {
17
- 1 => 0,
18
- 2 => 10,
19
- 3 => 0,
20
- 4 => 1,
21
- 5 => 1,
22
- },
23
-}
24
-`;
25
-
26
-exports[`profiling CommitDetails should be collected for each commit: CommitDetails commitIndex: 1 1`] = `
27
-Object {
28
- "actualDurations": Map {
29
- 3 => 0,
30
- 4 => 1,
31
- 6 => 2,
32
- 2 => 13,
33
- 1 => 13,
34
- },
35
- "commitIndex": 1,
36
- "interactions": Array [],
37
- "priorityLevel": "Immediate",
38
- "rootID": 1,
39
- "selfDurations": Map {
40
- 3 => 0,
41
- 4 => 1,
42
- 6 => 2,
43
- 2 => 10,
44
- 1 => 0,
45
- },
46
-}
47
-`;
48
-
49
-exports[`profiling CommitDetails should be collected for each commit: CommitDetails commitIndex: 2 1`] = `
50
-Object {
51
- "actualDurations": Map {
52
- 3 => 0,
53
- 2 => 10,
54
- 1 => 10,
55
- },
56
- "commitIndex": 2,
57
- "interactions": Array [],
58
- "priorityLevel": "Immediate",
59
- "rootID": 1,
60
- "selfDurations": Map {
61
- 3 => 0,
62
- 2 => 10,
63
- 1 => 0,
64
- },
65
-}
66
-`;
67
-
68
-exports[`profiling CommitDetails should be collected for each commit: CommitDetails commitIndex: 3 1`] = `
69
-Object {
70
- "actualDurations": Map {
71
- 2 => 10,
72
- 1 => 10,
73
- },
74
- "commitIndex": 3,
75
- "interactions": Array [],
76
- "priorityLevel": "Immediate",
77
- "rootID": 1,
78
- "selfDurations": Map {
79
- 2 => 10,
80
- 1 => 0,
81
- },
82
-}
83
-`;
84
-
85
-exports[`profiling CommitDetails should be collected for each commit: imported data 1`] = `
86
-Object {
87
- "commitDetails": Array [
88
- Object {
89
- "actualDurations": Map {
90
- 1 => 12,
91
- 2 => 12,
92
- 3 => 0,
93
- 4 => 1,
94
- 5 => 1,
95
- },
96
- "commitIndex": 0,
97
- "interactions": Array [],
98
- "priorityLevel": "Immediate",
99
- "rootID": 1,
100
- "selfDurations": Map {
101
- 1 => 0,
102
- 2 => 10,
103
- 3 => 0,
104
- 4 => 1,
105
- 5 => 1,
106
- },
107
- },
108
- Object {
109
- "actualDurations": Map {
110
- 3 => 0,
111
- 4 => 1,
112
- 6 => 2,
113
- 2 => 13,
114
- 1 => 13,
115
- },
116
- "commitIndex": 1,
117
- "interactions": Array [],
118
- "priorityLevel": "Immediate",
119
- "rootID": 1,
120
- "selfDurations": Map {
121
- 3 => 0,
122
- 4 => 1,
123
- 6 => 2,
124
- 2 => 10,
125
- 1 => 0,
126
- },
127
- },
128
- Object {
129
- "actualDurations": Map {
130
- 3 => 0,
131
- 2 => 10,
132
- 1 => 10,
133
- },
134
- "commitIndex": 2,
135
- "interactions": Array [],
136
- "priorityLevel": "Immediate",
137
- "rootID": 1,
138
- "selfDurations": Map {
139
- 3 => 0,
140
- 2 => 10,
141
- 1 => 0,
142
- },
143
- },
144
- Object {
145
- "actualDurations": Map {
146
- 2 => 10,
147
- 1 => 10,
148
- },
149
- "commitIndex": 3,
150
- "interactions": Array [],
151
- "priorityLevel": "Immediate",
152
- "rootID": 1,
153
- "selfDurations": Map {
154
- 2 => 10,
155
- 1 => 0,
156
- },
157
- },
158
- ],
159
- "interactions": Object {
160
- "interactions": Array [],
161
- "rootID": 1,
162
- },
163
- "profilingOperations": Map {
164
- 1 => Array [
165
- Uint32Array [
166
- 1,
167
- 1,
168
- 17,
169
- 6,
170
- 80,
171
- 97,
172
- 114,
173
- 101,
174
- 110,
175
- 116,
176
- 5,
177
- 67,
178
- 104,
179
- 105,
180
- 108,
181
- 100,
182
- 1,
183
- 48,
184
- 1,
185
- 49,
186
- 1,
187
- 1,
188
- 11,
189
- 1,
190
- 1,
191
- 4,
192
- 1,
193
- 12000,
194
- 1,
195
- 2,
196
- 5,
197
- 1,
198
- 0,
199
- 1,
200
- 0,
201
- 4,
202
- 2,
203
- 12000,
204
- 1,
205
- 3,
206
- 5,
207
- 2,
208
- 2,
209
- 2,
210
- 3,
211
- 4,
212
- 3,
213
- 0,
214
- 1,
215
- 4,
216
- 5,
217
- 2,
218
- 2,
219
- 2,
220
- 4,
221
- 4,
222
- 4,
223
- 1000,
224
- 1,
225
- 5,
226
- 8,
227
- 2,
228
- 2,
229
- 2,
230
- 0,
231
- 4,
232
- 5,
233
- 1000,
234
- ],
235
- Uint32Array [
236
- 1,
237
- 1,
238
- 8,
239
- 5,
240
- 67,
241
- 104,
242
- 105,
243
- 108,
244
- 100,
245
- 1,
246
- 50,
247
- 1,
248
- 6,
249
- 5,
250
- 2,
251
- 2,
252
- 1,
253
- 2,
254
- 4,
255
- 6,
256
- 2000,
257
- 4,
258
- 2,
259
- 14000,
260
- 3,
261
- 2,
262
- 4,
263
- 3,
264
- 4,
265
- 6,
266
- 5,
267
- 4,
268
- 1,
269
- 14000,
270
- ],
271
- Uint32Array [
272
- 1,
273
- 1,
274
- 0,
275
- 2,
276
- 2,
277
- 6,
278
- 4,
279
- 4,
280
- 2,
281
- 11000,
282
- 3,
283
- 2,
284
- 2,
285
- 3,
286
- 5,
287
- 4,
288
- 1,
289
- 11000,
290
- ],
291
- Uint32Array [
292
- 1,
293
- 1,
294
- 0,
295
- 2,
296
- 1,
297
- 3,
298
- ],
299
- ],
300
- },
301
- "profilingSnapshots": Map {
302
- 1 => Map {},
303
- },
304
- "profilingSummary": Object {
305
- "commitDurations": Array [
306
- 12,
307
- 13,
308
- 10,
309
- 10,
310
- ],
311
- "commitTimes": Array [
312
- 12,
313
- 25,
314
- 35,
315
- 45,
316
- ],
317
- "initialTreeBaseDurations": Map {},
318
- "interactionCount": 0,
319
- "rootID": 1,
320
- },
321
- "version": 3,
322
-}
323
-`;
324
-
325
-exports[`profiling CommitDetails should calculate a self duration based on actual children (not filtered children): CommitDetails with filtered self durations 1`] = `
326
-Object {
327
- "actualDurations": Map {
328
- 1 => 16,
329
- 2 => 16,
330
- 3 => 1,
331
- 5 => 1,
332
- },
333
- "commitIndex": 0,
334
- "interactions": Array [],
335
- "priorityLevel": "Immediate",
336
- "rootID": 1,
337
- "selfDurations": Map {
338
- 1 => 0,
339
- 2 => 10,
340
- 3 => 1,
341
- 5 => 1,
342
- },
343
-}
344
-`;
345
-
346
-exports[`profiling CommitDetails should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 1`] = `
347
-Object {
348
- "actualDurations": Map {
349
- 1 => 15,
350
- 2 => 15,
351
- 3 => 5,
352
- 4 => 2,
353
- },
354
- "commitIndex": 0,
355
- "interactions": Array [],
356
- "priorityLevel": "Immediate",
357
- "rootID": 1,
358
- "selfDurations": Map {
359
- 1 => 0,
360
- 2 => 10,
361
- 3 => 3,
362
- 4 => 2,
363
- },
364
-}
365
-`;
366
-
367
-exports[`profiling CommitDetails should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 2`] = `
368
-Object {
369
- "actualDurations": Map {
370
- 5 => 3,
371
- 3 => 3,
372
- },
373
- "commitIndex": 1,
374
- "interactions": Array [],
375
- "priorityLevel": "Immediate",
376
- "rootID": 1,
377
- "selfDurations": Map {
378
- 5 => 3,
379
- 3 => 0,
380
- },
381
-}
382
-`;
383
-
384
-exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 2 1`] = `
385
-Object {
386
- "commitDurations": Array [
387
- 0,
388
- 10,
389
- 1,
390
- 10,
391
- 2,
392
- 10,
393
- ],
394
- "fiberID": 2,
395
- "rootID": 1,
396
-}
397
-`;
398
-
399
-exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 3 1`] = `
400
-Object {
401
- "commitDurations": Array [
402
- 0,
403
- 0,
404
- 1,
405
- 0,
406
- 2,
407
- 0,
408
- ],
409
- "fiberID": 3,
410
- "rootID": 1,
411
-}
412
-`;
413
-
414
-exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 4 1`] = `
415
-Object {
416
- "commitDurations": Array [
417
- 0,
418
- 1,
419
- ],
420
- "fiberID": 4,
421
- "rootID": 1,
422
-}
423
-`;
424
-
425
-exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 5 1`] = `
426
-Object {
427
- "commitDurations": Array [
428
- 1,
429
- 1,
430
- 2,
431
- 1,
432
- ],
433
- "fiberID": 5,
434
- "rootID": 1,
435
-}
436
-`;
437
-
438
-exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 6 1`] = `
439
-Object {
440
- "commitDurations": Array [
441
- 2,
442
- 2,
443
- ],
444
- "fiberID": 6,
445
- "rootID": 1,
446
-}
447
-`;
448
-
449
-exports[`profiling FiberCommits should be collected for each rendered fiber: imported data 1`] = `
450
-Object {
451
- "commitDetails": Array [
452
- Object {
453
- "actualDurations": Map {
454
- 1 => 11,
455
- 2 => 11,
456
- 3 => 0,
457
- 4 => 1,
458
- },
459
- "commitIndex": 0,
460
- "interactions": Array [],
461
- "priorityLevel": "Immediate",
462
- "rootID": 1,
463
- "selfDurations": Map {
464
- 1 => 0,
465
- 2 => 10,
466
- 3 => 0,
467
- 4 => 1,
468
- },
469
- },
470
- Object {
471
- "actualDurations": Map {
472
- 3 => 0,
473
- 5 => 1,
474
- 2 => 11,
475
- 1 => 11,
476
- },
477
- "commitIndex": 1,
478
- "interactions": Array [],
479
- "priorityLevel": "Immediate",
480
- "rootID": 1,
481
- "selfDurations": Map {
482
- 3 => 0,
483
- 5 => 1,
484
- 2 => 10,
485
- 1 => 0,
486
- },
487
- },
488
- Object {
489
- "actualDurations": Map {
490
- 3 => 0,
491
- 5 => 1,
492
- 6 => 2,
493
- 2 => 13,
494
- 1 => 13,
495
- },
496
- "commitIndex": 2,
497
- "interactions": Array [],
498
- "priorityLevel": "Immediate",
499
- "rootID": 1,
500
- "selfDurations": Map {
501
- 3 => 0,
502
- 5 => 1,
503
- 6 => 2,
504
- 2 => 10,
505
- 1 => 0,
506
- },
507
- },
508
- ],
509
- "interactions": Object {
510
- "interactions": Array [],
511
- "rootID": 1,
512
- },
513
- "profilingOperations": Map {
514
- 1 => Array [
515
- Uint32Array [
516
- 1,
517
- 1,
518
- 15,
519
- 6,
520
- 80,
521
- 97,
522
- 114,
523
- 101,
524
- 110,
525
- 116,
526
- 5,
527
- 67,
528
- 104,
529
- 105,
530
- 108,
531
- 100,
532
- 1,
533
- 48,
534
- 1,
535
- 1,
536
- 11,
537
- 1,
538
- 1,
539
- 4,
540
- 1,
541
- 11000,
542
- 1,
543
- 2,
544
- 5,
545
- 1,
546
- 0,
547
- 1,
548
- 0,
549
- 4,
550
- 2,
551
- 11000,
552
- 1,
553
- 3,
554
- 5,
555
- 2,
556
- 2,
557
- 2,
558
- 3,
559
- 4,
560
- 3,
561
- 0,
562
- 1,
563
- 4,
564
- 8,
565
- 2,
566
- 2,
567
- 2,
568
- 0,
569
- 4,
570
- 4,
571
- 1000,
572
- ],
573
- Uint32Array [
574
- 1,
575
- 1,
576
- 8,
577
- 5,
578
- 67,
579
- 104,
580
- 105,
581
- 108,
582
- 100,
583
- 1,
584
- 49,
585
- 1,
586
- 5,
587
- 5,
588
- 2,
589
- 2,
590
- 1,
591
- 2,
592
- 4,
593
- 5,
594
- 1000,
595
- 4,
596
- 2,
597
- 12000,
598
- 3,
599
- 2,
600
- 3,
601
- 3,
602
- 5,
603
- 4,
604
- 4,
605
- 1,
606
- 12000,
607
- ],
608
- Uint32Array [
609
- 1,
610
- 1,
611
- 8,
612
- 5,
613
- 67,
614
- 104,
615
- 105,
616
- 108,
617
- 100,
618
- 1,
619
- 50,
620
- 1,
621
- 6,
622
- 5,
623
- 2,
624
- 2,
625
- 1,
626
- 2,
627
- 4,
628
- 6,
629
- 2000,
630
- 4,
631
- 2,
632
- 14000,
633
- 3,
634
- 2,
635
- 4,
636
- 3,
637
- 5,
638
- 6,
639
- 4,
640
- 4,
641
- 1,
642
- 14000,
643
- ],
644
- ],
645
- },
646
- "profilingSnapshots": Map {
647
- 1 => Map {},
648
- },
649
- "profilingSummary": Object {
650
- "commitDurations": Array [
651
- 11,
652
- 11,
653
- 13,
654
- ],
655
- "commitTimes": Array [
656
- 11,
657
- 22,
658
- 35,
659
- ],
660
- "initialTreeBaseDurations": Map {},
661
- "interactionCount": 0,
662
- "rootID": 1,
663
- },
664
- "version": 3,
665
-}
666
-`;
667
-
668
-exports[`profiling Interactions should be collected for every traced interaction: Interactions 1`] = `
669
-Object {
670
- "interactions": Array [
671
- Object {
672
- "__count": 1,
673
- "commits": Array [
674
- 0,
675
- ],
676
- "id": 0,
677
- "name": "mount: one child",
678
- "timestamp": 0,
679
- },
680
- Object {
681
- "__count": 0,
682
- "commits": Array [
683
- 1,
684
- ],
685
- "id": 1,
686
- "name": "update: two children",
687
- "timestamp": 11,
688
- },
689
- ],
690
- "rootID": 1,
691
-}
692
-`;
693
-
694
-exports[`profiling Interactions should be collected for every traced interaction: imported data 1`] = `
695
-Object {
696
- "commitDetails": Array [
697
- Object {
698
- "actualDurations": Map {
699
- 1 => 11,
700
- 2 => 11,
701
- 3 => 0,
702
- 4 => 1,
703
- },
704
- "commitIndex": 0,
705
- "interactions": Array [
706
- Object {
707
- "__count": 1,
708
- "id": 0,
709
- "name": "mount: one child",
710
- "timestamp": 0,
711
- },
712
- ],
713
- "priorityLevel": "Immediate",
714
- "rootID": 1,
715
- "selfDurations": Map {
716
- 1 => 0,
717
- 2 => 10,
718
- 3 => 0,
719
- 4 => 1,
720
- },
721
- },
722
- Object {
723
- "actualDurations": Map {
724
- 3 => 0,
725
- 5 => 1,
726
- 2 => 11,
727
- 1 => 11,
728
- },
729
- "commitIndex": 1,
730
- "interactions": Array [
731
- Object {
732
- "__count": 0,
733
- "id": 1,
734
- "name": "update: two children",
735
- "timestamp": 11,
736
- },
737
- ],
738
- "priorityLevel": "Immediate",
739
- "rootID": 1,
740
- "selfDurations": Map {
741
- 3 => 0,
742
- 5 => 1,
743
- 2 => 10,
744
- 1 => 0,
745
- },
746
- },
747
- ],
748
- "interactions": Object {
749
- "interactions": Array [
750
- Object {
751
- "__count": 1,
752
- "commits": Array [
753
- 0,
754
- ],
755
- "id": 0,
756
- "name": "mount: one child",
757
- "timestamp": 0,
758
- },
759
- Object {
760
- "__count": 0,
761
- "commits": Array [
762
- 1,
763
- ],
764
- "id": 1,
765
- "name": "update: two children",
766
- "timestamp": 11,
767
- },
768
- ],
769
- "rootID": 1,
770
- },
771
- "profilingOperations": Map {
772
- 1 => Array [
773
- Uint32Array [
774
- 1,
775
- 1,
776
- 15,
777
- 6,
778
- 80,
779
- 97,
780
- 114,
781
- 101,
782
- 110,
783
- 116,
784
- 5,
785
- 67,
786
- 104,
787
- 105,
788
- 108,
789
- 100,
790
- 1,
791
- 48,
792
- 1,
793
- 1,
794
- 11,
795
- 1,
796
- 1,
797
- 4,
798
- 1,
799
- 11000,
800
- 1,
801
- 2,
802
- 5,
803
- 1,
804
- 0,
805
- 1,
806
- 0,
807
- 4,
808
- 2,
809
- 11000,
810
- 1,
811
- 3,
812
- 5,
813
- 2,
814
- 2,
815
- 2,
816
- 3,
817
- 4,
818
- 3,
819
- 0,
820
- 1,
821
- 4,
822
- 8,
823
- 2,
824
- 2,
825
- 2,
826
- 0,
827
- 4,
828
- 4,
829
- 1000,
830
- ],
831
- Uint32Array [
832
- 1,
833
- 1,
834
- 8,
835
- 5,
836
- 67,
837
- 104,
838
- 105,
839
- 108,
840
- 100,
841
- 1,
842
- 49,
843
- 1,
844
- 5,
845
- 5,
846
- 2,
847
- 2,
848
- 1,
849
- 2,
850
- 4,
851
- 5,
852
- 1000,
853
- 4,
854
- 2,
855
- 12000,
856
- 3,
857
- 2,
858
- 3,
859
- 3,
860
- 5,
861
- 4,
862
- 4,
863
- 1,
864
- 12000,
865
- ],
866
- ],
867
- },
868
- "profilingSnapshots": Map {
869
- 1 => Map {},
870
- },
871
- "profilingSummary": Object {
872
- "commitDurations": Array [
873
- 11,
874
- 11,
875
- ],
876
- "commitTimes": Array [
877
- 11,
878
- 22,
879
- ],
880
- "initialTreeBaseDurations": Map {},
881
- "interactionCount": 2,
882
- "rootID": 1,
883
- },
884
- "version": 3,
885
-}
886
-`;
887
-
888
-exports[`profiling ProfilingSummary should be collected for each commit: ProfilingSummary 1`] = `
889
-Object {
890
- "commitDurations": Array [
891
- 13,
892
- 10,
893
- 10,
894
- ],
895
- "commitTimes": Array [
896
- 13,
897
- 23,
898
- 33,
899
- ],
900
- "initialTreeBaseDurations": Map {
901
- 1 => 12,
902
- 2 => 12,
903
- 3 => 0,
904
- 4 => 1,
905
- 5 => 1,
906
- },
907
- "interactionCount": 0,
908
- "rootID": 1,
909
-}
910
-`;
911
-
912
-exports[`profiling ProfilingSummary should be collected for each commit: imported data 1`] = `
913
-Object {
914
- "commitDetails": Array [
915
- Object {
916
- "actualDurations": Map {
917
- 3 => 0,
918
- 4 => 1,
919
- 6 => 2,
920
- 2 => 13,
921
- 1 => 13,
922
- },
923
- "commitIndex": 0,
924
- "interactions": Array [],
925
- "priorityLevel": "Immediate",
926
- "rootID": 1,
927
- "selfDurations": Map {
928
- 3 => 0,
929
- 4 => 1,
930
- 6 => 2,
931
- 2 => 10,
932
- 1 => 0,
933
- },
934
- },
935
- Object {
936
- "actualDurations": Map {
937
- 3 => 0,
938
- 2 => 10,
939
- 1 => 10,
940
- },
941
- "commitIndex": 1,
942
- "interactions": Array [],
943
- "priorityLevel": "Immediate",
944
- "rootID": 1,
945
- "selfDurations": Map {
946
- 3 => 0,
947
- 2 => 10,
948
- 1 => 0,
949
- },
950
- },
951
- Object {
952
- "actualDurations": Map {
953
- 2 => 10,
954
- 1 => 10,
955
- },
956
- "commitIndex": 2,
957
- "interactions": Array [],
958
- "priorityLevel": "Immediate",
959
- "rootID": 1,
960
- "selfDurations": Map {
961
- 2 => 10,
962
- 1 => 0,
963
- },
964
- },
965
- ],
966
- "interactions": Object {
967
- "interactions": Array [],
968
- "rootID": 1,
969
- },
970
- "profilingOperations": Map {
971
- 1 => Array [
972
- Uint32Array [
973
- 1,
974
- 1,
975
- 8,
976
- 5,
977
- 67,
978
- 104,
979
- 105,
980
- 108,
981
- 100,
982
- 1,
983
- 50,
984
- 1,
985
- 6,
986
- 5,
987
- 2,
988
- 2,
989
- 1,
990
- 2,
991
- 4,
992
- 6,
993
- 2000,
994
- 4,
995
- 2,
996
- 14000,
997
- 3,
998
- 2,
999
- 4,
1000
- 3,
1001
- 4,
1002
- 6,
1003
- 5,
1004
- 4,
1005
- 1,
1006
- 14000,
1007
- ],
1008
- Uint32Array [
1009
- 1,
1010
- 1,
1011
- 0,
1012
- 2,
1013
- 2,
1014
- 6,
1015
- 4,
1016
- 4,
1017
- 2,
1018
- 11000,
1019
- 3,
1020
- 2,
1021
- 2,
1022
- 3,
1023
- 5,
1024
- 4,
1025
- 1,
1026
- 11000,
1027
- ],
1028
- Uint32Array [
1029
- 1,
1030
- 1,
1031
- 0,
1032
- 2,
1033
- 1,
1034
- 3,
1035
- ],
1036
- ],
1037
- },
1038
- "profilingSnapshots": Map {
1039
- 1 => Map {
1040
- 1 => Object {
1041
- "children": Array [
1042
- 2,
1043
- ],
1044
- "displayName": null,
1045
- "id": 1,
1046
- "key": null,
1047
- "type": 11,
1048
- },
1049
- 2 => Object {
1050
- "children": Array [
1051
- 3,
1052
- 4,
1053
- 5,
1054
- ],
1055
- "displayName": "Parent",
1056
- "id": 2,
1057
- "key": null,
1058
- "type": 5,
1059
- },
1060
- 3 => Object {
1061
- "children": Array [],
1062
- "displayName": "Child",
1063
- "id": 3,
1064
- "key": "0",
1065
- "type": 5,
1066
- },
1067
- 4 => Object {
1068
- "children": Array [],
1069
- "displayName": "Child",
1070
- "id": 4,
1071
- "key": "1",
1072
- "type": 5,
1073
- },
1074
- 5 => Object {
1075
- "children": Array [],
1076
- "displayName": "Child",
1077
- "id": 5,
1078
- "key": null,
1079
- "type": 8,
1080
- },
1081
- },
1082
- },
1083
- "profilingSummary": Object {
1084
- "commitDurations": Array [
1085
- 13,
1086
- 10,
1087
- 10,
1088
- ],
1089
- "commitTimes": Array [
1090
- 13,
1091
- 23,
1092
- 33,
1093
- ],
1094
- "initialTreeBaseDurations": Map {
1095
- 1 => 12,
1096
- 2 => 12,
1097
- 3 => 0,
1098
- 4 => 1,
1099
- 5 => 1,
1100
- },
1101
- "interactionCount": 0,
1102
- "rootID": 1,
1103
- },
1104
- "version": 3,
1105
-}
1106
-`;
src/__tests__/__snapshots__/profilingCache-test.js.snap
new
+1495
@@ -0,0 +1,1495 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`ProfilingCache should calculate a self duration based on actual children (not filtered children): CommitDetails with filtered self durations 1`] = `
4
+Object {
5
+ "duration": 16,
6
+ "fiberActualDurations": Map {
7
+ 1 => 16,
8
+ 2 => 16,
9
+ 3 => 1,
10
+ 5 => 1,
11
+ },
12
+ "fiberSelfDurations": Map {
13
+ 1 => 0,
14
+ 2 => 10,
15
+ 3 => 1,
16
+ 5 => 1,
17
+ },
18
+ "interactionIDs": Array [],
19
+ "priorityLevel": "Immediate",
20
+ "screenshot": null,
21
+ "timestamp": 16,
22
+}
23
+`;
24
+
25
+exports[`ProfilingCache should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 1`] = `
26
+Object {
27
+ "duration": 15,
28
+ "fiberActualDurations": Map {
29
+ 1 => 15,
30
+ 2 => 15,
31
+ 3 => 5,
32
+ 4 => 2,
33
+ },
34
+ "fiberSelfDurations": Map {
35
+ 1 => 0,
36
+ 2 => 10,
37
+ 3 => 3,
38
+ 4 => 2,
39
+ },
40
+ "interactionIDs": Array [],
41
+ "priorityLevel": "Immediate",
42
+ "screenshot": null,
43
+ "timestamp": 15,
44
+}
45
+`;
46
+
47
+exports[`ProfilingCache should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 2`] = `
48
+Object {
49
+ "duration": 3,
50
+ "fiberActualDurations": Map {
51
+ 5 => 3,
52
+ 3 => 3,
53
+ },
54
+ "fiberSelfDurations": Map {
55
+ 5 => 3,
56
+ 3 => 0,
57
+ },
58
+ "interactionIDs": Array [],
59
+ "priorityLevel": "Immediate",
60
+ "screenshot": null,
61
+ "timestamp": 18,
62
+}
63
+`;
64
+
65
+exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 0 1`] = `
66
+Object {
67
+ "duration": 12,
68
+ "fiberActualDurations": Map {
69
+ 1 => 12,
70
+ 2 => 12,
71
+ 3 => 0,
72
+ 4 => 1,
73
+ 5 => 1,
74
+ },
75
+ "fiberSelfDurations": Map {
76
+ 1 => 0,
77
+ 2 => 10,
78
+ 3 => 0,
79
+ 4 => 1,
80
+ 5 => 1,
81
+ },
82
+ "interactionIDs": Array [],
83
+ "priorityLevel": "Immediate",
84
+ "screenshot": null,
85
+ "timestamp": 12,
86
+}
87
+`;
88
+
89
+exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 1 1`] = `
90
+Object {
91
+ "duration": 13,
92
+ "fiberActualDurations": Map {
93
+ 3 => 0,
94
+ 4 => 1,
95
+ 6 => 2,
96
+ 2 => 13,
97
+ 1 => 13,
98
+ },
99
+ "fiberSelfDurations": Map {
100
+ 3 => 0,
101
+ 4 => 1,
102
+ 6 => 2,
103
+ 2 => 10,
104
+ 1 => 0,
105
+ },
106
+ "interactionIDs": Array [],
107
+ "priorityLevel": "Immediate",
108
+ "screenshot": null,
109
+ "timestamp": 25,
110
+}
111
+`;
112
+
113
+exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 2 1`] = `
114
+Object {
115
+ "duration": 10,
116
+ "fiberActualDurations": Map {
117
+ 3 => 0,
118
+ 2 => 10,
119
+ 1 => 10,
120
+ },
121
+ "fiberSelfDurations": Map {
122
+ 3 => 0,
123
+ 2 => 10,
124
+ 1 => 0,
125
+ },
126
+ "interactionIDs": Array [],
127
+ "priorityLevel": "Immediate",
128
+ "screenshot": null,
129
+ "timestamp": 35,
130
+}
131
+`;
132
+
133
+exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 3 1`] = `
134
+Object {
135
+ "duration": 10,
136
+ "fiberActualDurations": Map {
137
+ 2 => 10,
138
+ 1 => 10,
139
+ },
140
+ "fiberSelfDurations": Map {
141
+ 2 => 10,
142
+ 1 => 0,
143
+ },
144
+ "interactionIDs": Array [],
145
+ "priorityLevel": "Immediate",
146
+ "screenshot": null,
147
+ "timestamp": 45,
148
+}
149
+`;
150
+
151
+exports[`ProfilingCache should collect data for each commit: imported data 1`] = `
152
+"{
153
+ \\"version\\": 5,
154
+ \\"dataForRoots\\": [
155
+ {
156
+ \\"commitData\\": [
157
+ {
158
+ \\"duration\\": 12,
159
+ \\"fiberActualDurations\\": [
160
+ [
161
+ 1,
162
+ 12
163
+ ],
164
+ [
165
+ 2,
166
+ 12
167
+ ],
168
+ [
169
+ 3,
170
+ 0
171
+ ],
172
+ [
173
+ 4,
174
+ 1
175
+ ],
176
+ [
177
+ 5,
178
+ 1
179
+ ]
180
+ ],
181
+ \\"fiberSelfDurations\\": [
182
+ [
183
+ 1,
184
+ 0
185
+ ],
186
+ [
187
+ 2,
188
+ 10
189
+ ],
190
+ [
191
+ 3,
192
+ 0
193
+ ],
194
+ [
195
+ 4,
196
+ 1
197
+ ],
198
+ [
199
+ 5,
200
+ 1
201
+ ]
202
+ ],
203
+ \\"interactionIDs\\": [],
204
+ \\"priorityLevel\\": \\"Immediate\\",
205
+ \\"screenshot\\": null,
206
+ \\"timestamp\\": 12
207
+ },
208
+ {
209
+ \\"duration\\": 13,
210
+ \\"fiberActualDurations\\": [
211
+ [
212
+ 3,
213
+ 0
214
+ ],
215
+ [
216
+ 4,
217
+ 1
218
+ ],
219
+ [
220
+ 6,
221
+ 2
222
+ ],
223
+ [
224
+ 2,
225
+ 13
226
+ ],
227
+ [
228
+ 1,
229
+ 13
230
+ ]
231
+ ],
232
+ \\"fiberSelfDurations\\": [
233
+ [
234
+ 3,
235
+ 0
236
+ ],
237
+ [
238
+ 4,
239
+ 1
240
+ ],
241
+ [
242
+ 6,
243
+ 2
244
+ ],
245
+ [
246
+ 2,
247
+ 10
248
+ ],
249
+ [
250
+ 1,
251
+ 0
252
+ ]
253
+ ],
254
+ \\"interactionIDs\\": [],
255
+ \\"priorityLevel\\": \\"Immediate\\",
256
+ \\"screenshot\\": null,
257
+ \\"timestamp\\": 25
258
+ },
259
+ {
260
+ \\"duration\\": 10,
261
+ \\"fiberActualDurations\\": [
262
+ [
263
+ 3,
264
+ 0
265
+ ],
266
+ [
267
+ 2,
268
+ 10
269
+ ],
270
+ [
271
+ 1,
272
+ 10
273
+ ]
274
+ ],
275
+ \\"fiberSelfDurations\\": [
276
+ [
277
+ 3,
278
+ 0
279
+ ],
280
+ [
281
+ 2,
282
+ 10
283
+ ],
284
+ [
285
+ 1,
286
+ 0
287
+ ]
288
+ ],
289
+ \\"interactionIDs\\": [],
290
+ \\"priorityLevel\\": \\"Immediate\\",
291
+ \\"screenshot\\": null,
292
+ \\"timestamp\\": 35
293
+ },
294
+ {
295
+ \\"duration\\": 10,
296
+ \\"fiberActualDurations\\": [
297
+ [
298
+ 2,
299
+ 10
300
+ ],
301
+ [
302
+ 1,
303
+ 10
304
+ ]
305
+ ],
306
+ \\"fiberSelfDurations\\": [
307
+ [
308
+ 2,
309
+ 10
310
+ ],
311
+ [
312
+ 1,
313
+ 0
314
+ ]
315
+ ],
316
+ \\"interactionIDs\\": [],
317
+ \\"priorityLevel\\": \\"Immediate\\",
318
+ \\"screenshot\\": null,
319
+ \\"timestamp\\": 45
320
+ }
321
+ ],
322
+ \\"displayName\\": \\"Parent\\",
323
+ \\"initialTreeBaseDurations\\": [],
324
+ \\"interactionCommits\\": [],
325
+ \\"interactions\\": [],
326
+ \\"operations\\": [
327
+ [
328
+ 1,
329
+ 1,
330
+ 17,
331
+ 6,
332
+ 80,
333
+ 97,
334
+ 114,
335
+ 101,
336
+ 110,
337
+ 116,
338
+ 5,
339
+ 67,
340
+ 104,
341
+ 105,
342
+ 108,
343
+ 100,
344
+ 1,
345
+ 48,
346
+ 1,
347
+ 49,
348
+ 1,
349
+ 1,
350
+ 11,
351
+ 1,
352
+ 1,
353
+ 4,
354
+ 1,
355
+ 12000,
356
+ 1,
357
+ 2,
358
+ 5,
359
+ 1,
360
+ 0,
361
+ 1,
362
+ 0,
363
+ 4,
364
+ 2,
365
+ 12000,
366
+ 1,
367
+ 3,
368
+ 5,
369
+ 2,
370
+ 2,
371
+ 2,
372
+ 3,
373
+ 4,
374
+ 3,
375
+ 0,
376
+ 1,
377
+ 4,
378
+ 5,
379
+ 2,
380
+ 2,
381
+ 2,
382
+ 4,
383
+ 4,
384
+ 4,
385
+ 1000,
386
+ 1,
387
+ 5,
388
+ 8,
389
+ 2,
390
+ 2,
391
+ 2,
392
+ 0,
393
+ 4,
394
+ 5,
395
+ 1000
396
+ ],
397
+ [
398
+ 1,
399
+ 1,
400
+ 8,
401
+ 5,
402
+ 67,
403
+ 104,
404
+ 105,
405
+ 108,
406
+ 100,
407
+ 1,
408
+ 50,
409
+ 1,
410
+ 6,
411
+ 5,
412
+ 2,
413
+ 2,
414
+ 1,
415
+ 2,
416
+ 4,
417
+ 6,
418
+ 2000,
419
+ 4,
420
+ 2,
421
+ 14000,
422
+ 3,
423
+ 2,
424
+ 4,
425
+ 3,
426
+ 4,
427
+ 6,
428
+ 5,
429
+ 4,
430
+ 1,
431
+ 14000
432
+ ],
433
+ [
434
+ 1,
435
+ 1,
436
+ 0,
437
+ 2,
438
+ 2,
439
+ 6,
440
+ 4,
441
+ 4,
442
+ 2,
443
+ 11000,
444
+ 3,
445
+ 2,
446
+ 2,
447
+ 3,
448
+ 5,
449
+ 4,
450
+ 1,
451
+ 11000
452
+ ],
453
+ [
454
+ 1,
455
+ 1,
456
+ 0,
457
+ 2,
458
+ 1,
459
+ 3
460
+ ]
461
+ ],
462
+ \\"rootID\\": 1,
463
+ \\"snapshots\\": []
464
+ }
465
+ ]
466
+}"
467
+`;
468
+
469
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 2 1`] = `
470
+Array [
471
+ 0,
472
+ 1,
473
+ 2,
474
+]
475
+`;
476
+
477
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 3 1`] = `
478
+Array [
479
+ 0,
480
+ 1,
481
+ 2,
482
+]
483
+`;
484
+
485
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
486
+Array [
487
+ 0,
488
+]
489
+`;
490
+
491
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
492
+Array [
493
+ 1,
494
+ 2,
495
+]
496
+`;
497
+
498
+exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
499
+Array [
500
+ 2,
501
+]
502
+`;
503
+
504
+exports[`ProfilingCache should collect data for each rendered fiber: imported data 1`] = `
505
+"{
506
+ \\"version\\": 5,
507
+ \\"dataForRoots\\": [
508
+ {
509
+ \\"commitData\\": [
510
+ {
511
+ \\"duration\\": 11,
512
+ \\"fiberActualDurations\\": [
513
+ [
514
+ 1,
515
+ 11
516
+ ],
517
+ [
518
+ 2,
519
+ 11
520
+ ],
521
+ [
522
+ 3,
523
+ 0
524
+ ],
525
+ [
526
+ 4,
527
+ 1
528
+ ]
529
+ ],
530
+ \\"fiberSelfDurations\\": [
531
+ [
532
+ 1,
533
+ 0
534
+ ],
535
+ [
536
+ 2,
537
+ 10
538
+ ],
539
+ [
540
+ 3,
541
+ 0
542
+ ],
543
+ [
544
+ 4,
545
+ 1
546
+ ]
547
+ ],
548
+ \\"interactionIDs\\": [],
549
+ \\"priorityLevel\\": \\"Immediate\\",
550
+ \\"screenshot\\": null,
551
+ \\"timestamp\\": 11
552
+ },
553
+ {
554
+ \\"duration\\": 11,
555
+ \\"fiberActualDurations\\": [
556
+ [
557
+ 3,
558
+ 0
559
+ ],
560
+ [
561
+ 5,
562
+ 1
563
+ ],
564
+ [
565
+ 2,
566
+ 11
567
+ ],
568
+ [
569
+ 1,
570
+ 11
571
+ ]
572
+ ],
573
+ \\"fiberSelfDurations\\": [
574
+ [
575
+ 3,
576
+ 0
577
+ ],
578
+ [
579
+ 5,
580
+ 1
581
+ ],
582
+ [
583
+ 2,
584
+ 10
585
+ ],
586
+ [
587
+ 1,
588
+ 0
589
+ ]
590
+ ],
591
+ \\"interactionIDs\\": [],
592
+ \\"priorityLevel\\": \\"Immediate\\",
593
+ \\"screenshot\\": null,
594
+ \\"timestamp\\": 22
595
+ },
596
+ {
597
+ \\"duration\\": 13,
598
+ \\"fiberActualDurations\\": [
599
+ [
600
+ 3,
601
+ 0
602
+ ],
603
+ [
604
+ 5,
605
+ 1
606
+ ],
607
+ [
608
+ 6,
609
+ 2
610
+ ],
611
+ [
612
+ 2,
613
+ 13
614
+ ],
615
+ [
616
+ 1,
617
+ 13
618
+ ]
619
+ ],
620
+ \\"fiberSelfDurations\\": [
621
+ [
622
+ 3,
623
+ 0
624
+ ],
625
+ [
626
+ 5,
627
+ 1
628
+ ],
629
+ [
630
+ 6,
631
+ 2
632
+ ],
633
+ [
634
+ 2,
635
+ 10
636
+ ],
637
+ [
638
+ 1,
639
+ 0
640
+ ]
641
+ ],
642
+ \\"interactionIDs\\": [],
643
+ \\"priorityLevel\\": \\"Immediate\\",
644
+ \\"screenshot\\": null,
645
+ \\"timestamp\\": 35
646
+ }
647
+ ],
648
+ \\"displayName\\": \\"Parent\\",
649
+ \\"initialTreeBaseDurations\\": [],
650
+ \\"interactionCommits\\": [],
651
+ \\"interactions\\": [],
652
+ \\"operations\\": [
653
+ [
654
+ 1,
655
+ 1,
656
+ 15,
657
+ 6,
658
+ 80,
659
+ 97,
660
+ 114,
661
+ 101,
662
+ 110,
663
+ 116,
664
+ 5,
665
+ 67,
666
+ 104,
667
+ 105,
668
+ 108,
669
+ 100,
670
+ 1,
671
+ 48,
672
+ 1,
673
+ 1,
674
+ 11,
675
+ 1,
676
+ 1,
677
+ 4,
678
+ 1,
679
+ 11000,
680
+ 1,
681
+ 2,
682
+ 5,
683
+ 1,
684
+ 0,
685
+ 1,
686
+ 0,
687
+ 4,
688
+ 2,
689
+ 11000,
690
+ 1,
691
+ 3,
692
+ 5,
693
+ 2,
694
+ 2,
695
+ 2,
696
+ 3,
697
+ 4,
698
+ 3,
699
+ 0,
700
+ 1,
701
+ 4,
702
+ 8,
703
+ 2,
704
+ 2,
705
+ 2,
706
+ 0,
707
+ 4,
708
+ 4,
709
+ 1000
710
+ ],
711
+ [
712
+ 1,
713
+ 1,
714
+ 8,
715
+ 5,
716
+ 67,
717
+ 104,
718
+ 105,
719
+ 108,
720
+ 100,
721
+ 1,
722
+ 49,
723
+ 1,
724
+ 5,
725
+ 5,
726
+ 2,
727
+ 2,
728
+ 1,
729
+ 2,
730
+ 4,
731
+ 5,
732
+ 1000,
733
+ 4,
734
+ 2,
735
+ 12000,
736
+ 3,
737
+ 2,
738
+ 3,
739
+ 3,
740
+ 5,
741
+ 4,
742
+ 4,
743
+ 1,
744
+ 12000
745
+ ],
746
+ [
747
+ 1,
748
+ 1,
749
+ 8,
750
+ 5,
751
+ 67,
752
+ 104,
753
+ 105,
754
+ 108,
755
+ 100,
756
+ 1,
757
+ 50,
758
+ 1,
759
+ 6,
760
+ 5,
761
+ 2,
762
+ 2,
763
+ 1,
764
+ 2,
765
+ 4,
766
+ 6,
767
+ 2000,
768
+ 4,
769
+ 2,
770
+ 14000,
771
+ 3,
772
+ 2,
773
+ 4,
774
+ 3,
775
+ 5,
776
+ 6,
777
+ 4,
778
+ 4,
779
+ 1,
780
+ 14000
781
+ ]
782
+ ],
783
+ \\"rootID\\": 1,
784
+ \\"snapshots\\": []
785
+ }
786
+ ]
787
+}"
788
+`;
789
+
790
+exports[`ProfilingCache should collect data for each root: ProfilingSummary 1`] = `
791
+Object {
792
+ "commitData": Array [
793
+ Object {
794
+ "duration": 13,
795
+ "fiberActualDurations": Map {
796
+ 3 => 0,
797
+ 4 => 1,
798
+ 6 => 2,
799
+ 2 => 13,
800
+ 1 => 13,
801
+ },
802
+ "fiberSelfDurations": Map {
803
+ 3 => 0,
804
+ 4 => 1,
805
+ 6 => 2,
806
+ 2 => 10,
807
+ 1 => 0,
808
+ },
809
+ "interactionIDs": Array [],
810
+ "priorityLevel": "Immediate",
811
+ "screenshot": null,
812
+ "timestamp": 13,
813
+ },
814
+ Object {
815
+ "duration": 10,
816
+ "fiberActualDurations": Map {
817
+ 3 => 0,
818
+ 2 => 10,
819
+ 1 => 10,
820
+ },
821
+ "fiberSelfDurations": Map {
822
+ 3 => 0,
823
+ 2 => 10,
824
+ 1 => 0,
825
+ },
826
+ "interactionIDs": Array [],
827
+ "priorityLevel": "Immediate",
828
+ "screenshot": null,
829
+ "timestamp": 23,
830
+ },
831
+ Object {
832
+ "duration": 10,
833
+ "fiberActualDurations": Map {
834
+ 2 => 10,
835
+ 1 => 10,
836
+ },
837
+ "fiberSelfDurations": Map {
838
+ 2 => 10,
839
+ 1 => 0,
840
+ },
841
+ "interactionIDs": Array [],
842
+ "priorityLevel": "Immediate",
843
+ "screenshot": null,
844
+ "timestamp": 33,
845
+ },
846
+ ],
847
+ "displayName": "Parent",
848
+ "initialTreeBaseDurations": Map {
849
+ 1 => 12,
850
+ 2 => 12,
851
+ 3 => 0,
852
+ 4 => 1,
853
+ 5 => 1,
854
+ },
855
+ "interactionCommits": Map {},
856
+ "interactions": Map {},
857
+ "operations": Array [
858
+ Uint32Array [
859
+ 1,
860
+ 1,
861
+ 8,
862
+ 5,
863
+ 67,
864
+ 104,
865
+ 105,
866
+ 108,
867
+ 100,
868
+ 1,
869
+ 50,
870
+ 1,
871
+ 6,
872
+ 5,
873
+ 2,
874
+ 2,
875
+ 1,
876
+ 2,
877
+ 4,
878
+ 6,
879
+ 2000,
880
+ 4,
881
+ 2,
882
+ 14000,
883
+ 3,
884
+ 2,
885
+ 4,
886
+ 3,
887
+ 4,
888
+ 6,
889
+ 5,
890
+ 4,
891
+ 1,
892
+ 14000,
893
+ ],
894
+ Uint32Array [
895
+ 1,
896
+ 1,
897
+ 0,
898
+ 2,
899
+ 2,
900
+ 6,
901
+ 4,
902
+ 4,
903
+ 2,
904
+ 11000,
905
+ 3,
906
+ 2,
907
+ 2,
908
+ 3,
909
+ 5,
910
+ 4,
911
+ 1,
912
+ 11000,
913
+ ],
914
+ Uint32Array [
915
+ 1,
916
+ 1,
917
+ 0,
918
+ 2,
919
+ 1,
920
+ 3,
921
+ ],
922
+ ],
923
+ "rootID": 1,
924
+ "snapshots": Map {
925
+ 1 => Object {
926
+ "children": Array [
927
+ 2,
928
+ ],
929
+ "displayName": null,
930
+ "id": 1,
931
+ "key": null,
932
+ "type": 11,
933
+ },
934
+ 2 => Object {
935
+ "children": Array [
936
+ 3,
937
+ 4,
938
+ 5,
939
+ ],
940
+ "displayName": "Parent",
941
+ "id": 2,
942
+ "key": null,
943
+ "type": 5,
944
+ },
945
+ 3 => Object {
946
+ "children": Array [],
947
+ "displayName": "Child",
948
+ "id": 3,
949
+ "key": "0",
950
+ "type": 5,
951
+ },
952
+ 4 => Object {
953
+ "children": Array [],
954
+ "displayName": "Child",
955
+ "id": 4,
956
+ "key": "1",
957
+ "type": 5,
958
+ },
959
+ 5 => Object {
960
+ "children": Array [],
961
+ "displayName": "Child",
962
+ "id": 5,
963
+ "key": null,
964
+ "type": 8,
965
+ },
966
+ },
967
+}
968
+`;
969
+
970
+exports[`ProfilingCache should collect data for each root: imported data 1`] = `
971
+"{
972
+ \\"version\\": 5,
973
+ \\"dataForRoots\\": [
974
+ {
975
+ \\"commitData\\": [
976
+ {
977
+ \\"duration\\": 13,
978
+ \\"fiberActualDurations\\": [
979
+ [
980
+ 3,
981
+ 0
982
+ ],
983
+ [
984
+ 4,
985
+ 1
986
+ ],
987
+ [
988
+ 6,
989
+ 2
990
+ ],
991
+ [
992
+ 2,
993
+ 13
994
+ ],
995
+ [
996
+ 1,
997
+ 13
998
+ ]
999
+ ],
1000
+ \\"fiberSelfDurations\\": [
1001
+ [
1002
+ 3,
1003
+ 0
1004
+ ],
1005
+ [
1006
+ 4,
1007
+ 1
1008
+ ],
1009
+ [
1010
+ 6,
1011
+ 2
1012
+ ],
1013
+ [
1014
+ 2,
1015
+ 10
1016
+ ],
1017
+ [
1018
+ 1,
1019
+ 0
1020
+ ]
1021
+ ],
1022
+ \\"interactionIDs\\": [],
1023
+ \\"priorityLevel\\": \\"Immediate\\",
1024
+ \\"screenshot\\": null,
1025
+ \\"timestamp\\": 13
1026
+ },
1027
+ {
1028
+ \\"duration\\": 10,
1029
+ \\"fiberActualDurations\\": [
1030
+ [
1031
+ 3,
1032
+ 0
1033
+ ],
1034
+ [
1035
+ 2,
1036
+ 10
1037
+ ],
1038
+ [
1039
+ 1,
1040
+ 10
1041
+ ]
1042
+ ],
1043
+ \\"fiberSelfDurations\\": [
1044
+ [
1045
+ 3,
1046
+ 0
1047
+ ],
1048
+ [
1049
+ 2,
1050
+ 10
1051
+ ],
1052
+ [
1053
+ 1,
1054
+ 0
1055
+ ]
1056
+ ],
1057
+ \\"interactionIDs\\": [],
1058
+ \\"priorityLevel\\": \\"Immediate\\",
1059
+ \\"screenshot\\": null,
1060
+ \\"timestamp\\": 23
1061
+ },
1062
+ {
1063
+ \\"duration\\": 10,
1064
+ \\"fiberActualDurations\\": [
1065
+ [
1066
+ 2,
1067
+ 10
1068
+ ],
1069
+ [
1070
+ 1,
1071
+ 10
1072
+ ]
1073
+ ],
1074
+ \\"fiberSelfDurations\\": [
1075
+ [
1076
+ 2,
1077
+ 10
1078
+ ],
1079
+ [
1080
+ 1,
1081
+ 0
1082
+ ]
1083
+ ],
1084
+ \\"interactionIDs\\": [],
1085
+ \\"priorityLevel\\": \\"Immediate\\",
1086
+ \\"screenshot\\": null,
1087
+ \\"timestamp\\": 33
1088
+ }
1089
+ ],
1090
+ \\"displayName\\": \\"Parent\\",
1091
+ \\"initialTreeBaseDurations\\": [
1092
+ [
1093
+ 1,
1094
+ 12
1095
+ ],
1096
+ [
1097
+ 2,
1098
+ 12
1099
+ ],
1100
+ [
1101
+ 3,
1102
+ 0
1103
+ ],
1104
+ [
1105
+ 4,
1106
+ 1
1107
+ ],
1108
+ [
1109
+ 5,
1110
+ 1
1111
+ ]
1112
+ ],
1113
+ \\"interactionCommits\\": [],
1114
+ \\"interactions\\": [],
1115
+ \\"operations\\": [
1116
+ [
1117
+ 1,
1118
+ 1,
1119
+ 8,
1120
+ 5,
1121
+ 67,
1122
+ 104,
1123
+ 105,
1124
+ 108,
1125
+ 100,
1126
+ 1,
1127
+ 50,
1128
+ 1,
1129
+ 6,
1130
+ 5,
1131
+ 2,
1132
+ 2,
1133
+ 1,
1134
+ 2,
1135
+ 4,
1136
+ 6,
1137
+ 2000,
1138
+ 4,
1139
+ 2,
1140
+ 14000,
1141
+ 3,
1142
+ 2,
1143
+ 4,
1144
+ 3,
1145
+ 4,
1146
+ 6,
1147
+ 5,
1148
+ 4,
1149
+ 1,
1150
+ 14000
1151
+ ],
1152
+ [
1153
+ 1,
1154
+ 1,
1155
+ 0,
1156
+ 2,
1157
+ 2,
1158
+ 6,
1159
+ 4,
1160
+ 4,
1161
+ 2,
1162
+ 11000,
1163
+ 3,
1164
+ 2,
1165
+ 2,
1166
+ 3,
1167
+ 5,
1168
+ 4,
1169
+ 1,
1170
+ 11000
1171
+ ],
1172
+ [
1173
+ 1,
1174
+ 1,
1175
+ 0,
1176
+ 2,
1177
+ 1,
1178
+ 3
1179
+ ]
1180
+ ],
1181
+ \\"rootID\\": 1,
1182
+ \\"snapshots\\": [
1183
+ [
1184
+ 1,
1185
+ {
1186
+ \\"id\\": 1,
1187
+ \\"children\\": [
1188
+ 2
1189
+ ],
1190
+ \\"displayName\\": null,
1191
+ \\"key\\": null,
1192
+ \\"type\\": 11
1193
+ }
1194
+ ],
1195
+ [
1196
+ 2,
1197
+ {
1198
+ \\"id\\": 2,
1199
+ \\"children\\": [
1200
+ 3,
1201
+ 4,
1202
+ 5
1203
+ ],
1204
+ \\"displayName\\": \\"Parent\\",
1205
+ \\"key\\": null,
1206
+ \\"type\\": 5
1207
+ }
1208
+ ],
1209
+ [
1210
+ 3,
1211
+ {
1212
+ \\"id\\": 3,
1213
+ \\"children\\": [],
1214
+ \\"displayName\\": \\"Child\\",
1215
+ \\"key\\": \\"0\\",
1216
+ \\"type\\": 5
1217
+ }
1218
+ ],
1219
+ [
1220
+ 4,
1221
+ {
1222
+ \\"id\\": 4,
1223
+ \\"children\\": [],
1224
+ \\"displayName\\": \\"Child\\",
1225
+ \\"key\\": \\"1\\",
1226
+ \\"type\\": 5
1227
+ }
1228
+ ],
1229
+ [
1230
+ 5,
1231
+ {
1232
+ \\"id\\": 5,
1233
+ \\"children\\": [],
1234
+ \\"displayName\\": \\"Child\\",
1235
+ \\"key\\": null,
1236
+ \\"type\\": 8
1237
+ }
1238
+ ]
1239
+ ]
1240
+ }
1241
+ ]
1242
+}"
1243
+`;
1244
+
1245
+exports[`ProfilingCache should report every traced interaction: Interactions 1`] = `
1246
+Array [
1247
+ Object {
1248
+ "__count": 1,
1249
+ "id": 0,
1250
+ "name": "mount: one child",
1251
+ "timestamp": 0,
1252
+ },
1253
+ Object {
1254
+ "__count": 0,
1255
+ "id": 1,
1256
+ "name": "update: two children",
1257
+ "timestamp": 11,
1258
+ },
1259
+]
1260
+`;
1261
+
1262
+exports[`ProfilingCache should report every traced interaction: imported data 1`] = `
1263
+"{
1264
+ \\"version\\": 5,
1265
+ \\"dataForRoots\\": [
1266
+ {
1267
+ \\"commitData\\": [
1268
+ {
1269
+ \\"duration\\": 11,
1270
+ \\"fiberActualDurations\\": [
1271
+ [
1272
+ 1,
1273
+ 11
1274
+ ],
1275
+ [
1276
+ 2,
1277
+ 11
1278
+ ],
1279
+ [
1280
+ 3,
1281
+ 0
1282
+ ],
1283
+ [
1284
+ 4,
1285
+ 1
1286
+ ]
1287
+ ],
1288
+ \\"fiberSelfDurations\\": [
1289
+ [
1290
+ 1,
1291
+ 0
1292
+ ],
1293
+ [
1294
+ 2,
1295
+ 10
1296
+ ],
1297
+ [
1298
+ 3,
1299
+ 0
1300
+ ],
1301
+ [
1302
+ 4,
1303
+ 1
1304
+ ]
1305
+ ],
1306
+ \\"interactionIDs\\": [
1307
+ 0
1308
+ ],
1309
+ \\"priorityLevel\\": \\"Immediate\\",
1310
+ \\"screenshot\\": null,
1311
+ \\"timestamp\\": 11
1312
+ },
1313
+ {
1314
+ \\"duration\\": 11,
1315
+ \\"fiberActualDurations\\": [
1316
+ [
1317
+ 3,
1318
+ 0
1319
+ ],
1320
+ [
1321
+ 5,
1322
+ 1
1323
+ ],
1324
+ [
1325
+ 2,
1326
+ 11
1327
+ ],
1328
+ [
1329
+ 1,
1330
+ 11
1331
+ ]
1332
+ ],
1333
+ \\"fiberSelfDurations\\": [
1334
+ [
1335
+ 3,
1336
+ 0
1337
+ ],
1338
+ [
1339
+ 5,
1340
+ 1
1341
+ ],
1342
+ [
1343
+ 2,
1344
+ 10
1345
+ ],
1346
+ [
1347
+ 1,
1348
+ 0
1349
+ ]
1350
+ ],
1351
+ \\"interactionIDs\\": [
1352
+ 1
1353
+ ],
1354
+ \\"priorityLevel\\": \\"Immediate\\",
1355
+ \\"screenshot\\": null,
1356
+ \\"timestamp\\": 22
1357
+ }
1358
+ ],
1359
+ \\"displayName\\": \\"Parent\\",
1360
+ \\"initialTreeBaseDurations\\": [],
1361
+ \\"interactionCommits\\": [
1362
+ [
1363
+ 0,
1364
+ [
1365
+ 0
1366
+ ]
1367
+ ],
1368
+ [
1369
+ 1,
1370
+ [
1371
+ 1
1372
+ ]
1373
+ ]
1374
+ ],
1375
+ \\"interactions\\": [
1376
+ [
1377
+ 0,
1378
+ {
1379
+ \\"__count\\": 1,
1380
+ \\"id\\": 0,
1381
+ \\"name\\": \\"mount: one child\\",
1382
+ \\"timestamp\\": 0
1383
+ }
1384
+ ],
1385
+ [
1386
+ 1,
1387
+ {
1388
+ \\"__count\\": 0,
1389
+ \\"id\\": 1,
1390
+ \\"name\\": \\"update: two children\\",
1391
+ \\"timestamp\\": 11
1392
+ }
1393
+ ]
1394
+ ],
1395
+ \\"operations\\": [
1396
+ [
1397
+ 1,
1398
+ 1,
1399
+ 15,
1400
+ 6,
1401
+ 80,
1402
+ 97,
1403
+ 114,
1404
+ 101,
1405
+ 110,
1406
+ 116,
1407
+ 5,
1408
+ 67,
1409
+ 104,
1410
+ 105,
1411
+ 108,
1412
+ 100,
1413
+ 1,
1414
+ 48,
1415
+ 1,
1416
+ 1,
1417
+ 11,
1418
+ 1,
1419
+ 1,
1420
+ 4,
1421
+ 1,
1422
+ 11000,
1423
+ 1,
1424
+ 2,
1425
+ 5,
1426
+ 1,
1427
+ 0,
1428
+ 1,
1429
+ 0,
1430
+ 4,
1431
+ 2,
1432
+ 11000,
1433
+ 1,
1434
+ 3,
1435
+ 5,
1436
+ 2,
1437
+ 2,
1438
+ 2,
1439
+ 3,
1440
+ 4,
1441
+ 3,
1442
+ 0,
1443
+ 1,
1444
+ 4,
1445
+ 8,
1446
+ 2,
1447
+ 2,
1448
+ 2,
1449
+ 0,
1450
+ 4,
1451
+ 4,
1452
+ 1000
1453
+ ],
1454
+ [
1455
+ 1,
1456
+ 1,
1457
+ 8,
1458
+ 5,
1459
+ 67,
1460
+ 104,
1461
+ 105,
1462
+ 108,
1463
+ 100,
1464
+ 1,
1465
+ 49,
1466
+ 1,
1467
+ 5,
1468
+ 5,
1469
+ 2,
1470
+ 2,
1471
+ 1,
1472
+ 2,
1473
+ 4,
1474
+ 5,
1475
+ 1000,
1476
+ 4,
1477
+ 2,
1478
+ 12000,
1479
+ 3,
1480
+ 2,
1481
+ 3,
1482
+ 3,
1483
+ 5,
1484
+ 4,
1485
+ 4,
1486
+ 1,
1487
+ 12000
1488
+ ]
1489
+ ],
1490
+ \\"rootID\\": 1,
1491
+ \\"snapshots\\": []
1492
+ }
1493
+ ]
1494
+}"
1495
+`;
src/__tests__/__snapshots__/profilingCharts-test.js.snap
+28
@@ -247,6 +247,20 @@ Object {
247
248
exports[`profiling charts interactions should contain valid data: Interactions 1`] = `
249
Object {
250
+ "interactions": Array [
251
+ Object {
252
+ "__count": 1,
253
+ "id": 0,
254
+ "name": "mount",
255
+ "timestamp": 0,
256
+ },
257
+ Object {
258
+ "__count": 0,
259
+ "id": 1,
260
+ "name": "update",
261
+ "timestamp": 15,
262
+ },
263
+ ],
264
"lastInteractionTime": 25,
265
"maxCommitDuration": 15,
266
}
@@ -254,6 +268,20 @@ Object {
268
269
exports[`profiling charts interactions should contain valid data: Interactions 2`] = `
270
Object {
271
+ "interactions": Array [
272
+ Object {
273
+ "__count": 1,
274
+ "id": 0,
275
+ "name": "mount",
276
+ "timestamp": 0,
277
+ },
278
+ Object {
279
+ "__count": 0,
280
+ "id": 1,
281
+ "name": "update",
282
+ "timestamp": 15,
283
+ },
284
+ ],
285
"lastInteractionTime": 25,
286
"maxCommitDuration": 15,
287
}
src/__tests__/profilerStore-test.js
new
+57
@@ -0,0 +1,57 @@
1
+// @flow
2
+
3
+import type Store from 'src/devtools/store';
4
+
5
+describe('ProfilerStore', () => {
6
+ let React;
7
+ let ReactDOM;
8
+ let store: Store;
9
+ let utils;
10
+
11
+ beforeEach(() => {
12
+ utils = require('./utils');
13
+ utils.beforeEachProfiling();
14
+
15
+ store = global.store;
16
+ store.collapseNodesByDefault = false;
17
+
18
+ React = require('react');
19
+ ReactDOM = require('react-dom');
20
+ });
21
+
22
+ it('should not remove profiling data when roots are unmounted', async () => {
23
+ const Parent = ({ count }) =>
24
+ new Array(count)
25
+ .fill(true)
26
+ .map((_, index) => <Child key={index} duration={index} />);
27
+ const Child = () => <div>Hi!</div>;
28
+
29
+ const containerA = document.createElement('div');
30
+ const containerB = document.createElement('div');
31
+
32
+ utils.act(() => {
33
+ ReactDOM.render(<Parent key="A" count={3} />, containerA);
34
+ ReactDOM.render(<Parent key="B" count={2} />, containerB);
35
+ });
36
+
37
+ utils.act(() => store.startProfiling());
38
+
39
+ utils.act(() => {
40
+ ReactDOM.render(<Parent key="A" count={4} />, containerA);
41
+ ReactDOM.render(<Parent key="B" count={1} />, containerB);
42
+ });
43
+
44
+ utils.act(() => store.stopProfiling());
45
+
46
+ const rootA = store.roots[0];
47
+ const rootB = store.roots[1];
48
+
49
+ utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
50
+
51
+ expect(store.profilerStore.getDataForRoot(rootA)).not.toBeNull();
52
+
53
+ utils.act(() => ReactDOM.unmountComponentAtNode(containerA));
54
+
55
+ expect(store.profilerStore.getDataForRoot(rootB)).not.toBeNull();
56
+ });
57
+});
src/__tests__/profiling-test.js
deleted
-574
@@ -1,574 +0,0 @@
1
-// @flow
2
-
3
-import typeof ReactTestRenderer from 'react-test-renderer';
4
-import type Bridge from 'src/bridge';
5
-import type Store from 'src/devtools/store';
6
-
7
-describe('profiling', () => {
8
- let React;
9
- let ReactDOM;
10
- let Scheduler;
11
- let SchedulerTracing;
12
- let TestRenderer: ReactTestRenderer;
13
- let bridge: Bridge;
14
- let store: Store;
15
- let utils;
16
-
17
- beforeEach(() => {
18
- utils = require('./utils');
19
- utils.beforeEachProfiling();
20
-
21
- bridge = global.bridge;
22
- store = global.store;
23
- store.collapseNodesByDefault = false;
24
-
25
- React = require('react');
26
- ReactDOM = require('react-dom');
27
- Scheduler = require('scheduler');
28
- SchedulerTracing = require('scheduler/tracing');
29
- TestRenderer = utils.requireTestRenderer();
30
- });
31
-
32
- it('should throw if importing older/unsupported data', () => {
33
- const {
34
- prepareImportedProfilingData,
35
- } = require('src/devtools/views/Profiler/utils');
36
- expect(() =>
37
- prepareImportedProfilingData(
38
- JSON.stringify({
39
- version: 0,
40
- })
41
- )
42
- ).toThrow('Unsupported profiler export version "0"');
43
- });
44
-
45
- describe('ProfilingSummary', () => {
46
- it('should be collected for each commit', async done => {
47
- const Parent = ({ count }) => {
48
- Scheduler.advanceTime(10);
49
- const children = new Array(count)
50
- .fill(true)
51
- .map((_, index) => <Child key={index} duration={index} />);
52
- return (
53
- <React.Fragment>
54
- {children}
55
- <MemoizedChild duration={1} />
56
- </React.Fragment>
57
- );
58
- };
59
- const Child = ({ duration }) => {
60
- Scheduler.advanceTime(duration);
61
- return null;
62
- };
63
- const MemoizedChild = React.memo(Child);
64
-
65
- const container = document.createElement('div');
66
-
67
- utils.act(() => ReactDOM.render(<Parent count={2} />, container));
68
- utils.act(() => store.startProfiling());
69
- utils.act(() => ReactDOM.render(<Parent count={3} />, container));
70
- utils.act(() => ReactDOM.render(<Parent count={1} />, container));
71
- utils.act(() => ReactDOM.render(<Parent count={0} />, container));
72
- utils.act(() => store.stopProfiling());
73
-
74
- let profilingSummary = null;
75
-
76
- function Suspender({ previousPofilingSummary, rendererID, rootID }) {
77
- profilingSummary = store.profilingCache.ProfilingSummary.read({
78
- rendererID,
79
- rootID,
80
- });
81
- if (previousPofilingSummary != null) {
82
- expect(profilingSummary).toEqual(previousPofilingSummary);
83
- } else {
84
- expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
85
- }
86
- return null;
87
- }
88
-
89
- const rendererID = utils.getRendererID();
90
- const rootID = store.roots[0];
91
-
92
- await utils.actAsync(() =>
93
- TestRenderer.create(
94
- <React.Suspense fallback={null}>
95
- <Suspender
96
- previousPofilingSummary={null}
97
- rendererID={rendererID}
98
- rootID={rootID}
99
- />
100
- </React.Suspense>
101
- )
102
- );
103
-
104
- expect(profilingSummary).not.toBeNull();
105
-
106
- utils.exportImportHelper(bridge, store, rendererID, rootID);
107
-
108
- await utils.actAsync(() =>
109
- TestRenderer.create(
110
- <React.Suspense fallback={null}>
111
- <Suspender
112
- previousPofilingSummary={profilingSummary}
113
- rendererID={rendererID}
114
- rootID={rootID}
115
- />
116
- </React.Suspense>
117
- )
118
- );
119
-
120
- done();
121
- });
122
- });
123
-
124
- describe('CommitDetails', () => {
125
- it('should be collected for each commit', async done => {
126
- const Parent = ({ count }) => {
127
- Scheduler.advanceTime(10);
128
- const children = new Array(count)
129
- .fill(true)
130
- .map((_, index) => <Child key={index} duration={index} />);
131
- return (
132
- <React.Fragment>
133
- {children}
134
- <MemoizedChild duration={1} />
135
- </React.Fragment>
136
- );
137
- };
138
- const Child = ({ duration }) => {
139
- Scheduler.advanceTime(duration);
140
- return null;
141
- };
142
- const MemoizedChild = React.memo(Child);
143
-
144
- const container = document.createElement('div');
145
-
146
- utils.act(() => store.startProfiling());
147
- utils.act(() => ReactDOM.render(<Parent count={2} />, container));
148
- utils.act(() => ReactDOM.render(<Parent count={3} />, container));
149
- utils.act(() => ReactDOM.render(<Parent count={1} />, container));
150
- utils.act(() => ReactDOM.render(<Parent count={0} />, container));
151
- utils.act(() => store.stopProfiling());
152
-
153
- const allCommitDetails = [];
154
-
155
- function Suspender({
156
- commitIndex,
157
- previousCommitDetails,
158
- rendererID,
159
- rootID,
160
- }) {
161
- const commitDetails = store.profilingCache.CommitDetails.read({
162
- commitIndex,
163
- rendererID,
164
- rootID,
165
- });
166
- if (previousCommitDetails != null) {
167
- expect(commitDetails).toEqual(previousCommitDetails);
168
- } else {
169
- allCommitDetails.push(commitDetails);
170
- expect(commitDetails).toMatchSnapshot(
171
- `CommitDetails commitIndex: ${commitIndex}`
172
- );
173
- }
174
- return null;
175
- }
176
-
177
- const rendererID = utils.getRendererID();
178
- const rootID = store.roots[0];
179
-
180
- for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
181
- await utils.actAsync(() => {
182
- TestRenderer.create(
183
- <React.Suspense fallback={null}>
184
- <Suspender
185
- commitIndex={commitIndex}
186
- previousCommitDetails={null}
187
- rendererID={rendererID}
188
- rootID={rootID}
189
- />
190
- </React.Suspense>
191
- );
192
- });
193
- }
194
-
195
- expect(allCommitDetails).toHaveLength(4);
196
-
197
- utils.exportImportHelper(bridge, store, rendererID, rootID);
198
-
199
- for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
200
- await utils.actAsync(() => {
201
- TestRenderer.create(
202
- <React.Suspense fallback={null}>
203
- <Suspender
204
- commitIndex={commitIndex}
205
- previousCommitDetails={allCommitDetails[commitIndex]}
206
- rendererID={rendererID}
207
- rootID={rootID}
208
- />
209
- </React.Suspense>
210
- );
211
- });
212
- }
213
-
214
- done();
215
- });
216
-
217
- it('should calculate a self duration based on actual children (not filtered children)', async done => {
218
- store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
219
-
220
- const Grandparent = () => {
221
- Scheduler.advanceTime(10);
222
- return (
223
- <React.Fragment>
224
- <Parent key="one" />
225
- <Parent key="two" />
226
- </React.Fragment>
227
- );
228
- };
229
- const Parent = () => {
230
- Scheduler.advanceTime(2);
231
- return <Child />;
232
- };
233
- const Child = () => {
234
- Scheduler.advanceTime(1);
235
- return null;
236
- };
237
-
238
- utils.act(() => store.startProfiling());
239
- utils.act(() =>
240
- ReactDOM.render(<Grandparent />, document.createElement('div'))
241
- );
242
- utils.act(() => store.stopProfiling());
243
-
244
- let commitDetails = null;
245
-
246
- function Suspender({ commitIndex, rendererID, rootID }) {
247
- commitDetails = store.profilingCache.CommitDetails.read({
248
- commitIndex,
249
- rendererID,
250
- rootID,
251
- });
252
- expect(commitDetails).toMatchSnapshot(
253
- `CommitDetails with filtered self durations`
254
- );
255
- return null;
256
- }
257
-
258
- const rendererID = utils.getRendererID();
259
- const rootID = store.roots[0];
260
-
261
- await utils.actAsync(() => {
262
- TestRenderer.create(
263
- <React.Suspense fallback={null}>
264
- <Suspender
265
- commitIndex={0}
266
- rendererID={rendererID}
267
- rootID={rootID}
268
- />
269
- </React.Suspense>
270
- );
271
- });
272
-
273
- expect(commitDetails).not.toBeNull();
274
-
275
- done();
276
- });
277
-
278
- it('should calculate self duration correctly for suspended views', async done => {
279
- let data;
280
- const getData = () => {
281
- if (data) {
282
- return data;
283
- } else {
284
- throw new Promise(resolve => {
285
- data = 'abc';
286
- resolve(data);
287
- });
288
- }
289
- };
290
-
291
- const Parent = () => {
292
- Scheduler.advanceTime(10);
293
- return (
294
- <React.Suspense fallback={<Fallback />}>
295
- <Async />
296
- </React.Suspense>
297
- );
298
- };
299
- const Fallback = () => {
300
- Scheduler.advanceTime(2);
301
- return 'Fallback...';
302
- };
303
- const Async = () => {
304
- Scheduler.advanceTime(3);
305
- const data = getData();
306
- return data;
307
- };
308
-
309
- utils.act(() => store.startProfiling());
310
- await utils.actAsync(() =>
311
- ReactDOM.render(<Parent />, document.createElement('div'))
312
- );
313
- utils.act(() => store.stopProfiling());
314
-
315
- const allCommitDetails = [];
316
-
317
- function Suspender({ commitIndex, rendererID, rootID }) {
318
- const commitDetails = store.profilingCache.CommitDetails.read({
319
- commitIndex,
320
- rendererID,
321
- rootID,
322
- });
323
- allCommitDetails.push(commitDetails);
324
- expect(commitDetails).toMatchSnapshot(
325
- `CommitDetails with filtered self durations`
326
- );
327
- return null;
328
- }
329
-
330
- const rendererID = utils.getRendererID();
331
- const rootID = store.roots[0];
332
-
333
- for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
334
- await utils.actAsync(() => {
335
- TestRenderer.create(
336
- <React.Suspense fallback={null}>
337
- <Suspender
338
- commitIndex={commitIndex}
339
- rendererID={rendererID}
340
- rootID={rootID}
341
- />
342
- </React.Suspense>
343
- );
344
- });
345
- }
346
-
347
- expect(allCommitDetails).toHaveLength(2);
348
-
349
- done();
350
- });
351
- });
352
-
353
- describe('FiberCommits', () => {
354
- it('should be collected for each rendered fiber', async done => {
355
- const Parent = ({ count }) => {
356
- Scheduler.advanceTime(10);
357
- const children = new Array(count)
358
- .fill(true)
359
- .map((_, index) => <Child key={index} duration={index} />);
360
- return (
361
- <React.Fragment>
362
- {children}
363
- <MemoizedChild duration={1} />
364
- </React.Fragment>
365
- );
366
- };
367
- const Child = ({ duration }) => {
368
- Scheduler.advanceTime(duration);
369
- return null;
370
- };
371
- const MemoizedChild = React.memo(Child);
372
-
373
- const container = document.createElement('div');
374
-
375
- utils.act(() => store.startProfiling());
376
- utils.act(() => ReactDOM.render(<Parent count={1} />, container));
377
- utils.act(() => ReactDOM.render(<Parent count={2} />, container));
378
- utils.act(() => ReactDOM.render(<Parent count={3} />, container));
379
- utils.act(() => store.stopProfiling());
380
-
381
- const allFiberCommits = [];
382
-
383
- function Suspender({
384
- fiberID,
385
- previousFiberCommits,
386
- rendererID,
387
- rootID,
388
- }) {
389
- const fiberCommits = store.profilingCache.FiberCommits.read({
390
- fiberID,
391
- rendererID,
392
- rootID,
393
- });
394
- if (previousFiberCommits != null) {
395
- expect(fiberCommits).toEqual(previousFiberCommits);
396
- } else {
397
- allFiberCommits.push(fiberCommits);
398
- expect(fiberCommits).toMatchSnapshot(
399
- `FiberCommits: element ${fiberID}`
400
- );
401
- }
402
- return null;
403
- }
404
-
405
- const rendererID = utils.getRendererID();
406
- const rootID = store.roots[0];
407
-
408
- for (let index = 0; index < store.numElements; index++) {
409
- await utils.actAsync(() => {
410
- const fiberID = store.getElementIDAtIndex(index);
411
- if (fiberID == null) {
412
- throw Error(`Unexpected null ID for element at index ${index}`);
413
- }
414
- TestRenderer.create(
415
- <React.Suspense fallback={null}>
416
- <Suspender
417
- fiberID={fiberID}
418
- previousFiberCommits={null}
419
- rendererID={rendererID}
420
- rootID={rootID}
421
- />
422
- </React.Suspense>
423
- );
424
- });
425
- }
426
-
427
- expect(allFiberCommits).toHaveLength(store.numElements);
428
-
429
- utils.exportImportHelper(bridge, store, rendererID, rootID);
430
-
431
- for (let index = 0; index < store.numElements; index++) {
432
- await utils.actAsync(() => {
433
- const fiberID = store.getElementIDAtIndex(index);
434
- if (fiberID == null) {
435
- throw Error(`Unexpected null ID for element at index ${index}`);
436
- }
437
- TestRenderer.create(
438
- <React.Suspense fallback={null}>
439
- <Suspender
440
- fiberID={fiberID}
441
- previousFiberCommits={allFiberCommits[index]}
442
- rendererID={rendererID}
443
- rootID={rootID}
444
- />
445
- </React.Suspense>
446
- );
447
- });
448
- }
449
-
450
- done();
451
- });
452
- });
453
-
454
- describe('Interactions', () => {
455
- it('should be collected for every traced interaction', async done => {
456
- const Parent = ({ count }) => {
457
- Scheduler.advanceTime(10);
458
- const children = new Array(count)
459
- .fill(true)
460
- .map((_, index) => <Child key={index} duration={index} />);
461
- return (
462
- <React.Fragment>
463
- {children}
464
- <MemoizedChild duration={1} />
465
- </React.Fragment>
466
- );
467
- };
468
- const Child = ({ duration }) => {
469
- Scheduler.advanceTime(duration);
470
- return null;
471
- };
472
- const MemoizedChild = React.memo(Child);
473
-
474
- const container = document.createElement('div');
475
-
476
- utils.act(() => store.startProfiling());
477
- utils.act(() =>
478
- SchedulerTracing.unstable_trace(
479
- 'mount: one child',
480
- Scheduler.unstable_now(),
481
- () => ReactDOM.render(<Parent count={1} />, container)
482
- )
483
- );
484
- utils.act(() =>
485
- SchedulerTracing.unstable_trace(
486
- 'update: two children',
487
- Scheduler.unstable_now(),
488
- () => ReactDOM.render(<Parent count={2} />, container)
489
- )
490
- );
491
- utils.act(() => store.stopProfiling());
492
-
493
- let interactions = null;
494
-
495
- function Suspender({ previousInteractions, rendererID, rootID }) {
496
- interactions = store.profilingCache.Interactions.read({
497
- rendererID,
498
- rootID,
499
- });
500
- if (previousInteractions != null) {
501
- expect(interactions).toEqual(previousInteractions);
502
- } else {
503
- expect(interactions).toMatchSnapshot('Interactions');
504
- }
505
- return null;
506
- }
507
-
508
- const rendererID = utils.getRendererID();
509
- const rootID = store.roots[0];
510
-
511
- await utils.actAsync(() =>
512
- TestRenderer.create(
513
- <React.Suspense fallback={null}>
514
- <Suspender
515
- previousInteractions={null}
516
- rendererID={rendererID}
517
- rootID={rootID}
518
- />
519
- </React.Suspense>
520
- )
521
- );
522
-
523
- expect(interactions).not.toBeNull();
524
-
525
- utils.exportImportHelper(bridge, store, rendererID, rootID);
526
-
527
- await utils.actAsync(() =>
528
- TestRenderer.create(
529
- <React.Suspense fallback={null}>
530
- <Suspender
531
- previousInteractions={interactions}
532
- rendererID={rendererID}
533
- rootID={rootID}
534
- />
535
- </React.Suspense>
536
- )
537
- );
538
-
539
- done();
540
- });
541
- });
542
-
543
- it('should remove profiling data when roots are unmounted', async () => {
544
- const Parent = ({ count }) =>
545
- new Array(count)
546
- .fill(true)
547
- .map((_, index) => <Child key={index} duration={index} />);
548
- const Child = () => <div>Hi!</div>;
549
-
550
- const containerA = document.createElement('div');
551
- const containerB = document.createElement('div');
552
-
553
- utils.act(() => {
554
- ReactDOM.render(<Parent key="A" count={3} />, containerA);
555
- ReactDOM.render(<Parent key="B" count={2} />, containerB);
556
- });
557
-
558
- utils.act(() => store.startProfiling());
559
-
560
- utils.act(() => {
561
- ReactDOM.render(<Parent key="A" count={4} />, containerA);
562
- ReactDOM.render(<Parent key="B" count={1} />, containerB);
563
- });
564
-
565
- utils.act(() => ReactDOM.unmountComponentAtNode(containerB));
566
-
567
- utils.act(() => ReactDOM.unmountComponentAtNode(containerA));
568
-
569
- utils.act(() => store.stopProfiling());
570
-
571
- // Assert all maps are empty
572
- store.assertExpectedRootMapSizes();
573
- });
574
-});
src/__tests__/profilingCache-test.js
new
+465
@@ -0,0 +1,465 @@
1
+// @flow
2
+
3
+import typeof ReactTestRenderer from 'react-test-renderer';
4
+import type Bridge from 'src/bridge';
5
+import type Store from 'src/devtools/store';
6
+
7
+describe('ProfilingCache', () => {
8
+ let React;
9
+ let ReactDOM;
10
+ let Scheduler;
11
+ let SchedulerTracing;
12
+ let TestRenderer: ReactTestRenderer;
13
+ let bridge: Bridge;
14
+ let store: Store;
15
+ let utils;
16
+
17
+ beforeEach(() => {
18
+ utils = require('./utils');
19
+ utils.beforeEachProfiling();
20
+
21
+ bridge = global.bridge;
22
+ store = global.store;
23
+ store.collapseNodesByDefault = false;
24
+
25
+ React = require('react');
26
+ ReactDOM = require('react-dom');
27
+ Scheduler = require('scheduler');
28
+ SchedulerTracing = require('scheduler/tracing');
29
+ TestRenderer = utils.requireTestRenderer();
30
+ });
31
+
32
+ it('should collect data for each root', async done => {
33
+ const Parent = ({ count }) => {
34
+ Scheduler.advanceTime(10);
35
+ const children = new Array(count)
36
+ .fill(true)
37
+ .map((_, index) => <Child key={index} duration={index} />);
38
+ return (
39
+ <React.Fragment>
40
+ {children}
41
+ <MemoizedChild duration={1} />
42
+ </React.Fragment>
43
+ );
44
+ };
45
+ const Child = ({ duration }) => {
46
+ Scheduler.advanceTime(duration);
47
+ return null;
48
+ };
49
+ const MemoizedChild = React.memo(Child);
50
+
51
+ const container = document.createElement('div');
52
+
53
+ utils.act(() => ReactDOM.render(<Parent count={2} />, container));
54
+ utils.act(() => store.startProfiling());
55
+ utils.act(() => ReactDOM.render(<Parent count={3} />, container));
56
+ utils.act(() => ReactDOM.render(<Parent count={1} />, container));
57
+ utils.act(() => ReactDOM.render(<Parent count={0} />, container));
58
+ utils.act(() => store.stopProfiling());
59
+
60
+ let profilingDataForRoot = null;
61
+
62
+ // TODO (profarc) Add multi roots
63
+
64
+ function Suspender({ previousProfilingDataForRoot, rootID }) {
65
+ profilingDataForRoot = store.profilerStore.getDataForRoot(rootID);
66
+ if (previousProfilingDataForRoot != null) {
67
+ expect(profilingDataForRoot).toEqual(previousProfilingDataForRoot);
68
+ } else {
69
+ expect(profilingDataForRoot).toMatchSnapshot('ProfilingSummary');
70
+ }
71
+ return null;
72
+ }
73
+
74
+ const rootID = store.roots[0];
75
+
76
+ await utils.actAsync(() =>
77
+ TestRenderer.create(
78
+ <React.Suspense fallback={null}>
79
+ <Suspender previousProfilingDataForRoot={null} rootID={rootID} />
80
+ </React.Suspense>
81
+ )
82
+ );
83
+
84
+ expect(profilingDataForRoot).not.toBeNull();
85
+
86
+ utils.exportImportHelper(bridge, store, rootID);
87
+
88
+ await utils.actAsync(() =>
89
+ TestRenderer.create(
90
+ <React.Suspense fallback={null}>
91
+ <Suspender
92
+ previousProfilingDataForRoot={profilingDataForRoot}
93
+ rootID={rootID}
94
+ />
95
+ </React.Suspense>
96
+ )
97
+ );
98
+
99
+ done();
100
+ });
101
+
102
+ it('should collect data for each commit', async done => {
103
+ const Parent = ({ count }) => {
104
+ Scheduler.advanceTime(10);
105
+ const children = new Array(count)
106
+ .fill(true)
107
+ .map((_, index) => <Child key={index} duration={index} />);
108
+ return (
109
+ <React.Fragment>
110
+ {children}
111
+ <MemoizedChild duration={1} />
112
+ </React.Fragment>
113
+ );
114
+ };
115
+ const Child = ({ duration }) => {
116
+ Scheduler.advanceTime(duration);
117
+ return null;
118
+ };
119
+ const MemoizedChild = React.memo(Child);
120
+
121
+ const container = document.createElement('div');
122
+
123
+ utils.act(() => store.startProfiling());
124
+ utils.act(() => ReactDOM.render(<Parent count={2} />, container));
125
+ utils.act(() => ReactDOM.render(<Parent count={3} />, container));
126
+ utils.act(() => ReactDOM.render(<Parent count={1} />, container));
127
+ utils.act(() => ReactDOM.render(<Parent count={0} />, container));
128
+ utils.act(() => store.stopProfiling());
129
+
130
+ const allCommitData = [];
131
+
132
+ function Suspender({ commitIndex, previousCommitDetails, rootID }) {
133
+ const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
134
+ if (previousCommitDetails != null) {
135
+ expect(commitData).toEqual(previousCommitDetails);
136
+ } else {
137
+ allCommitData.push(commitData);
138
+ expect(commitData).toMatchSnapshot(
139
+ `CommitDetails commitIndex: ${commitIndex}`
140
+ );
141
+ }
142
+ return null;
143
+ }
144
+
145
+ const rootID = store.roots[0];
146
+
147
+ for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
148
+ await utils.actAsync(() => {
149
+ TestRenderer.create(
150
+ <React.Suspense fallback={null}>
151
+ <Suspender
152
+ commitIndex={commitIndex}
153
+ previousCommitDetails={null}
154
+ rootID={rootID}
155
+ />
156
+ </React.Suspense>
157
+ );
158
+ });
159
+ }
160
+
161
+ expect(allCommitData).toHaveLength(4);
162
+
163
+ utils.exportImportHelper(bridge, store, rootID);
164
+
165
+ for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
166
+ await utils.actAsync(() => {
167
+ TestRenderer.create(
168
+ <React.Suspense fallback={null}>
169
+ <Suspender
170
+ commitIndex={commitIndex}
171
+ previousCommitDetails={allCommitData[commitIndex]}
172
+ rootID={rootID}
173
+ />
174
+ </React.Suspense>
175
+ );
176
+ });
177
+ }
178
+
179
+ done();
180
+ });
181
+
182
+ it('should calculate a self duration based on actual children (not filtered children)', async done => {
183
+ store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
184
+
185
+ const Grandparent = () => {
186
+ Scheduler.advanceTime(10);
187
+ return (
188
+ <React.Fragment>
189
+ <Parent key="one" />
190
+ <Parent key="two" />
191
+ </React.Fragment>
192
+ );
193
+ };
194
+ const Parent = () => {
195
+ Scheduler.advanceTime(2);
196
+ return <Child />;
197
+ };
198
+ const Child = () => {
199
+ Scheduler.advanceTime(1);
200
+ return null;
201
+ };
202
+
203
+ utils.act(() => store.startProfiling());
204
+ utils.act(() =>
205
+ ReactDOM.render(<Grandparent />, document.createElement('div'))
206
+ );
207
+ utils.act(() => store.stopProfiling());
208
+
209
+ let commitData = null;
210
+
211
+ function Suspender({ commitIndex, rootID }) {
212
+ commitData = store.profilerStore.getCommitData(rootID, commitIndex);
213
+ expect(commitData).toMatchSnapshot(
214
+ `CommitDetails with filtered self durations`
215
+ );
216
+ return null;
217
+ }
218
+
219
+ const rootID = store.roots[0];
220
+
221
+ await utils.actAsync(() => {
222
+ TestRenderer.create(
223
+ <React.Suspense fallback={null}>
224
+ <Suspender commitIndex={0} rootID={rootID} />
225
+ </React.Suspense>
226
+ );
227
+ });
228
+
229
+ expect(commitData).not.toBeNull();
230
+
231
+ done();
232
+ });
233
+
234
+ it('should calculate self duration correctly for suspended views', async done => {
235
+ let data;
236
+ const getData = () => {
237
+ if (data) {
238
+ return data;
239
+ } else {
240
+ throw new Promise(resolve => {
241
+ data = 'abc';
242
+ resolve(data);
243
+ });
244
+ }
245
+ };
246
+
247
+ const Parent = () => {
248
+ Scheduler.advanceTime(10);
249
+ return (
250
+ <React.Suspense fallback={<Fallback />}>
251
+ <Async />
252
+ </React.Suspense>
253
+ );
254
+ };
255
+ const Fallback = () => {
256
+ Scheduler.advanceTime(2);
257
+ return 'Fallback...';
258
+ };
259
+ const Async = () => {
260
+ Scheduler.advanceTime(3);
261
+ const data = getData();
262
+ return data;
263
+ };
264
+
265
+ utils.act(() => store.startProfiling());
266
+ await utils.actAsync(() =>
267
+ ReactDOM.render(<Parent />, document.createElement('div'))
268
+ );
269
+ utils.act(() => store.stopProfiling());
270
+
271
+ const allCommitData = [];
272
+
273
+ function Suspender({ commitIndex, rootID }) {
274
+ const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
275
+ allCommitData.push(commitData);
276
+ expect(commitData).toMatchSnapshot(
277
+ `CommitDetails with filtered self durations`
278
+ );
279
+ return null;
280
+ }
281
+
282
+ const rootID = store.roots[0];
283
+
284
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
285
+ await utils.actAsync(() => {
286
+ TestRenderer.create(
287
+ <React.Suspense fallback={null}>
288
+ <Suspender commitIndex={commitIndex} rootID={rootID} />
289
+ </React.Suspense>
290
+ );
291
+ });
292
+ }
293
+
294
+ expect(allCommitData).toHaveLength(2);
295
+
296
+ done();
297
+ });
298
+
299
+ it('should collect data for each rendered fiber', async done => {
300
+ const Parent = ({ count }) => {
301
+ Scheduler.advanceTime(10);
302
+ const children = new Array(count)
303
+ .fill(true)
304
+ .map((_, index) => <Child key={index} duration={index} />);
305
+ return (
306
+ <React.Fragment>
307
+ {children}
308
+ <MemoizedChild duration={1} />
309
+ </React.Fragment>
310
+ );
311
+ };
312
+ const Child = ({ duration }) => {
313
+ Scheduler.advanceTime(duration);
314
+ return null;
315
+ };
316
+ const MemoizedChild = React.memo(Child);
317
+
318
+ const container = document.createElement('div');
319
+
320
+ utils.act(() => store.startProfiling());
321
+ utils.act(() => ReactDOM.render(<Parent count={1} />, container));
322
+ utils.act(() => ReactDOM.render(<Parent count={2} />, container));
323
+ utils.act(() => ReactDOM.render(<Parent count={3} />, container));
324
+ utils.act(() => store.stopProfiling());
325
+
326
+ const allFiberCommits = [];
327
+
328
+ function Suspender({ fiberID, previousFiberCommits, rootID }) {
329
+ const fiberCommits = store.profilingCache.getFiberCommits({
330
+ fiberID,
331
+ rootID,
332
+ });
333
+ if (previousFiberCommits != null) {
334
+ expect(fiberCommits).toEqual(previousFiberCommits);
335
+ } else {
336
+ allFiberCommits.push(fiberCommits);
337
+ expect(fiberCommits).toMatchSnapshot(
338
+ `FiberCommits: element ${fiberID}`
339
+ );
340
+ }
341
+ return null;
342
+ }
343
+
344
+ const rootID = store.roots[0];
345
+
346
+ for (let index = 0; index < store.numElements; index++) {
347
+ await utils.actAsync(() => {
348
+ const fiberID = store.getElementIDAtIndex(index);
349
+ if (fiberID == null) {
350
+ throw Error(`Unexpected null ID for element at index ${index}`);
351
+ }
352
+ TestRenderer.create(
353
+ <React.Suspense fallback={null}>
354
+ <Suspender
355
+ fiberID={fiberID}
356
+ previousFiberCommits={null}
357
+ rootID={rootID}
358
+ />
359
+ </React.Suspense>
360
+ );
361
+ });
362
+ }
363
+
364
+ expect(allFiberCommits).toHaveLength(store.numElements);
365
+
366
+ utils.exportImportHelper(bridge, store, rootID);
367
+
368
+ for (let index = 0; index < store.numElements; index++) {
369
+ await utils.actAsync(() => {
370
+ const fiberID = store.getElementIDAtIndex(index);
371
+ if (fiberID == null) {
372
+ throw Error(`Unexpected null ID for element at index ${index}`);
373
+ }
374
+ TestRenderer.create(
375
+ <React.Suspense fallback={null}>
376
+ <Suspender
377
+ fiberID={fiberID}
378
+ previousFiberCommits={allFiberCommits[index]}
379
+ rootID={rootID}
380
+ />
381
+ </React.Suspense>
382
+ );
383
+ });
384
+ }
385
+
386
+ done();
387
+ });
388
+
389
+ it('should report every traced interaction', async done => {
390
+ const Parent = ({ count }) => {
391
+ Scheduler.advanceTime(10);
392
+ const children = new Array(count)
393
+ .fill(true)
394
+ .map((_, index) => <Child key={index} duration={index} />);
395
+ return (
396
+ <React.Fragment>
397
+ {children}
398
+ <MemoizedChild duration={1} />
399
+ </React.Fragment>
400
+ );
401
+ };
402
+ const Child = ({ duration }) => {
403
+ Scheduler.advanceTime(duration);
404
+ return null;
405
+ };
406
+ const MemoizedChild = React.memo(Child);
407
+
408
+ const container = document.createElement('div');
409
+
410
+ utils.act(() => store.startProfiling());
411
+ utils.act(() =>
412
+ SchedulerTracing.unstable_trace(
413
+ 'mount: one child',
414
+ Scheduler.unstable_now(),
415
+ () => ReactDOM.render(<Parent count={1} />, container)
416
+ )
417
+ );
418
+ utils.act(() =>
419
+ SchedulerTracing.unstable_trace(
420
+ 'update: two children',
421
+ Scheduler.unstable_now(),
422
+ () => ReactDOM.render(<Parent count={2} />, container)
423
+ )
424
+ );
425
+ utils.act(() => store.stopProfiling());
426
+
427
+ let interactions = null;
428
+
429
+ function Suspender({ previousInteractions, rootID }) {
430
+ interactions = store.profilingCache.getInteractionsChartData({
431
+ rootID,
432
+ }).interactions;
433
+ if (previousInteractions != null) {
434
+ expect(interactions).toEqual(previousInteractions);
435
+ } else {
436
+ expect(interactions).toMatchSnapshot('Interactions');
437
+ }
438
+ return null;
439
+ }
440
+
441
+ const rootID = store.roots[0];
442
+
443
+ await utils.actAsync(() =>
444
+ TestRenderer.create(
445
+ <React.Suspense fallback={null}>
446
+ <Suspender previousInteractions={null} rootID={rootID} />
447
+ </React.Suspense>
448
+ )
449
+ );
450
+
451
+ expect(interactions).not.toBeNull();
452
+
453
+ utils.exportImportHelper(bridge, store, rootID);
454
+
455
+ await utils.actAsync(() =>
456
+ TestRenderer.create(
457
+ <React.Suspense fallback={null}>
458
+ <Suspender previousInteractions={interactions} rootID={rootID} />
459
+ </React.Suspense>
460
+ )
461
+ );
462
+
463
+ done();
464
+ });
465
+});
src/__tests__/profilingCharts-test.js
+24
-66
@@ -62,59 +62,45 @@ describe('profiling charts', () => {
62
);
63
utils.act(() => store.stopProfiling());
64
65
- let suspenseResolved = false;
65
+ let renderFinished = false;
66
67
- function Suspender({ commitIndex, rendererID, rootID }) {
68
- const profilingSummary = store.profilingCache.ProfilingSummary.read({
69
- rendererID,
70
- rootID,
71
- });
72
- const commitDetails = store.profilingCache.CommitDetails.read({
73
- commitIndex,
74
- rendererID,
75
- rootID,
76
- });
77
- suspenseResolved = true;
67
+ function Suspender({ commitIndex, rootID }) {
68
const commitTree = store.profilingCache.getCommitTree({
69
commitIndex,
80
- profilingSummary,
70
+ rootID,
71
});
72
const chartData = store.profilingCache.getFlamegraphChartData({
83
- commitDetails,
73
commitIndex,
74
commitTree,
75
+ rootID,
76
});
77
expect(commitTree).toMatchSnapshot(`${commitIndex}: CommitTree`);
78
expect(chartData).toMatchSnapshot(
79
`${commitIndex}: FlamegraphChartData`
80
);
81
+ renderFinished = true;
82
return null;
83
}
84
94
- const rendererID = utils.getRendererID();
85
const rootID = store.roots[0];
86
87
for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
98
- suspenseResolved = false;
88
+ renderFinished = false;
89
90
await utils.actAsync(
91
() =>
92
TestRenderer.create(
93
<React.Suspense fallback={null}>
104
- <Suspender
105
- commitIndex={commitIndex}
106
- rendererID={rendererID}
107
- rootID={rootID}
108
- />
94
+ <Suspender commitIndex={commitIndex} rootID={rootID} />
95
</React.Suspense>
96
),
97
3
98
);
99
114
- expect(suspenseResolved).toBe(true);
100
+ expect(renderFinished).toBe(true);
101
}
102
117
- expect(suspenseResolved).toBe(true);
103
+ expect(renderFinished).toBe(true);
104
105
done();
106
});
@@ -156,54 +142,40 @@ describe('profiling charts', () => {
142
);
143
utils.act(() => store.stopProfiling());
144
159
- let suspenseResolved = false;
145
+ let renderFinished = false;
146
161
- function Suspender({ commitIndex, rendererID, rootID }) {
162
- const profilingSummary = store.profilingCache.ProfilingSummary.read({
163
- rendererID,
164
- rootID,
165
- });
166
- const commitDetails = store.profilingCache.CommitDetails.read({
167
- commitIndex,
168
- rendererID,
169
- rootID,
170
- });
171
- suspenseResolved = true;
147
+ function Suspender({ commitIndex, rootID }) {
148
const commitTree = store.profilingCache.getCommitTree({
149
commitIndex,
174
- profilingSummary,
150
+ rootID,
151
});
152
const chartData = store.profilingCache.getRankedChartData({
177
- commitDetails,
153
commitIndex,
154
commitTree,
155
+ rootID,
156
});
157
expect(commitTree).toMatchSnapshot(`${commitIndex}: CommitTree`);
158
expect(chartData).toMatchSnapshot(`${commitIndex}: RankedChartData`);
159
+ renderFinished = true;
160
return null;
161
}
162
186
- const rendererID = utils.getRendererID();
163
const rootID = store.roots[0];
164
165
for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
190
- suspenseResolved = false;
166
+ renderFinished = false;
167
168
await utils.actAsync(
169
() =>
170
TestRenderer.create(
171
<React.Suspense fallback={null}>
196
- <Suspender
197
- commitIndex={commitIndex}
198
- rendererID={rendererID}
199
- rootID={rootID}
200
- />
172
+ <Suspender commitIndex={commitIndex} rootID={rootID} />
173
</React.Suspense>
174
),
175
3
176
);
177
206
- expect(suspenseResolved).toBe(true);
178
+ expect(renderFinished).toBe(true);
179
}
180
181
done();
@@ -246,47 +218,33 @@ describe('profiling charts', () => {
218
);
219
utils.act(() => store.stopProfiling());
220
249
- let suspenseResolved = false;
221
+ let renderFinished = false;
222
251
- function Suspender({ commitIndex, rendererID, rootID }) {
252
- const profilingSummary = store.profilingCache.ProfilingSummary.read({
253
- rendererID,
254
- rootID,
255
- });
256
- const { interactions } = store.profilingCache.Interactions.read({
257
- rendererID,
258
- rootID,
259
- });
260
- suspenseResolved = true;
223
+ function Suspender({ commitIndex, rootID }) {
224
const chartData = store.profilingCache.getInteractionsChartData({
262
- interactions,
263
- profilingSummary,
225
+ rootID,
226
});
227
expect(chartData).toMatchSnapshot('Interactions');
228
+ renderFinished = true;
229
return null;
230
}
231
269
- const rendererID = utils.getRendererID();
232
const rootID = store.roots[0];
233
234
for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
273
- suspenseResolved = false;
235
+ renderFinished = false;
236
237
await utils.actAsync(
238
() =>
239
TestRenderer.create(
240
<React.Suspense fallback={null}>
279
- <Suspender
280
- commitIndex={commitIndex}
281
- rendererID={rendererID}
282
- rootID={rootID}
283
- />
241
+ <Suspender commitIndex={commitIndex} rootID={rootID} />
242
</React.Suspense>
243
),
244
3
245
);
246
289
- expect(suspenseResolved).toBe(true);
247
+ expect(renderFinished).toBe(true);
248
}
249
250
done();
src/__tests__/profilingCommitTreeBuilder-test.js
+7
-16
@@ -45,43 +45,34 @@ describe('commit tree', () => {
45
utils.act(() => ReactDOM.render(<Parent count={0} />, container));
46
utils.act(() => store.stopProfiling());
47
48
- let suspenseResolved = false;
48
+ let renderFinished = false;
49
50
- function Suspender({ commitIndex, rendererID, rootID }) {
51
- const profilingSummary = store.profilingCache.ProfilingSummary.read({
52
- rendererID,
53
- rootID,
54
- });
55
- suspenseResolved = true;
50
+ function Suspender({ commitIndex, rootID }) {
51
const commitTree = store.profilingCache.getCommitTree({
52
commitIndex,
58
- profilingSummary,
53
+ rootID,
54
});
55
expect(commitTree).toMatchSnapshot(`${commitIndex}: CommitTree`);
56
+ renderFinished = true;
57
return null;
58
}
59
64
- const rendererID = utils.getRendererID();
60
const rootID = store.roots[0];
61
62
for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
68
- suspenseResolved = false;
63
+ renderFinished = false;
64
65
await utils.actAsync(
66
() =>
67
TestRenderer.create(
68
<React.Suspense fallback={null}>
74
- <Suspender
75
- commitIndex={commitIndex}
76
- rendererID={rendererID}
77
- rootID={rootID}
78
- />
69
+ <Suspender commitIndex={commitIndex} rootID={rootID} />
70
</React.Suspense>
71
),
72
3
73
);
74
84
- expect(suspenseResolved).toBe(true);
75
+ expect(renderFinished).toBe(true);
76
}
77
78
done();
src/__tests__/profilingUtils-test.js
new
+20
@@ -0,0 +1,20 @@
1
+// @flow
2
+
3
+describe('profiling utils', () => {
4
+ let utils;
5
+
6
+ beforeEach(() => {
7
+ utils = require('src/devtools/views/Profiler/utils');
8
+ });
9
+
10
+ it('should throw if importing older/unsupported data', () => {
11
+ expect(() =>
12
+ utils.prepareProfilingDataFrontendFromExport(
13
+ ({
14
+ version: 0,
15
+ dataForRoots: [],
16
+ }: any)
17
+ )
18
+ ).toThrow('Unsupported profiler export version "0"');
19
+ });
20
+});
src/__tests__/utils.js
+25
-35
@@ -2,10 +2,10 @@
2
3
import typeof ReactTestRenderer from 'react-test-renderer';
4
5
-import type { ElementType } from 'src/types';
6
-
5
import type Bridge from 'src/bridge';
6
import type Store from 'src/devtools/store';
7
+import type { ProfilingDataFrontend } from 'src/devtools/views/Profiler/types';
8
+import type { ElementType } from 'src/types';
9
10
export function act(callback: Function): void {
11
const TestUtils = require('react-dom/test-utils');
@@ -136,54 +136,44 @@ export function requireTestRenderer(): ReactTestRenderer {
136
export function exportImportHelper(
137
bridge: Bridge,
138
store: Store,
139
- rendererID: number,
139
rootID: number
140
): void {
141
const { act } = require('./utils');
142
const {
144
- prepareExportedProfilingSummary,
145
- prepareImportedProfilingData,
143
+ prepareProfilingDataExport,
144
+ prepareProfilingDataFrontendFromExport,
145
} = require('src/devtools/views/Profiler/utils');
146
148
- let exportedProfilingDataJsonString = '';
149
- const onExportFile = ({ contents }) => {
150
- if (typeof contents === 'string') {
151
- exportedProfilingDataJsonString = (contents: string);
152
- }
153
- };
154
- bridge.addListener('exportFile', onExportFile);
155
-
156
- act(() => {
157
- const exportProfilingSummary = prepareExportedProfilingSummary(
158
- store.profilingOperations,
159
- store.profilingSnapshots,
160
- rendererID,
161
- rootID
162
- );
163
- bridge.send('exportProfilingSummary', exportProfilingSummary);
164
- });
147
+ const { profilerStore } = store;
148
166
- // Cleanup to be able to call this again on the same bridge without memory leaks.
167
- bridge.removeListener('exportFile', onExportFile);
149
+ expect(profilerStore.profilingData).not.toBeNull();
150
169
- expect(typeof exportedProfilingDataJsonString).toBe('string');
170
- expect(exportedProfilingDataJsonString).not.toBe('');
151
+ const profilingDataFrontendInitial = ((profilerStore.profilingData: any): ProfilingDataFrontend);
152
172
- const profilingData = prepareImportedProfilingData(
173
- exportedProfilingDataJsonString
153
+ const profilingDataExport = prepareProfilingDataExport(
154
+ profilingDataFrontendInitial
155
);
175
- // Sanity check that profiling snapshots are serialized correctly.
176
- expect(store.profilingSnapshots.get(rootID)).toEqual(
177
- profilingData.profilingSnapshots.get(rootID)
156
+
157
+ // Simulate writing/reading to disk.
158
+ const serializedProfilingDataExport = JSON.stringify(
159
+ profilingDataExport,
160
+ null,
161
+ 2
162
);
179
- expect(store.profilingOperations.get(rootID)).toEqual(
180
- profilingData.profilingOperations.get(rootID)
163
+ const parsedProfilingDataExport = JSON.parse(serializedProfilingDataExport);
164
+
165
+ const profilingDataFrontend = prepareProfilingDataFrontendFromExport(
166
+ (parsedProfilingDataExport: any)
167
);
168
169
+ // Sanity check that profiling snapshots are serialized correctly.
170
+ expect(profilingDataFrontendInitial).toEqual(profilingDataFrontend);
171
+
172
// Snapshot the JSON-parsed object, rather than the raw string, because Jest formats the diff nicer.
184
- expect(profilingData).toMatchSnapshot('imported data');
173
+ expect(serializedProfilingDataExport).toMatchSnapshot('imported data');
174
175
act(() => {
187
- store.profilingData = profilingData;
176
+ // Apply the new exported-then-reimported data so tests can re-run assertions.
177
+ profilerStore.profilingData = profilingDataFrontend;
178
});
179
}
src/backend/agent.js
+3
-100
@@ -10,7 +10,6 @@ import {
10
} from '../constants';
11
import { hideOverlay, showOverlay } from './views/Highlighter';
12
13
-import type { ExportedProfilingSummaryFromFrontend } from 'src/devtools/views/Profiler/types';
13
import type {
14
PathFrame,
15
PathMatch,
@@ -20,8 +19,6 @@ import type {
19
import type { OwnersList } from 'src/devtools/views/Components/types';
20
import type { Bridge, ComponentFilter } from '../types';
21
23
-import { prepareExportedProfilingData } from 'src/devtools/views/Profiler/utils';
24
-
22
const debug = (methodName, ...args) => {
23
if (__DEBUG__) {
24
console.log(
@@ -96,12 +93,8 @@ export default class Agent extends EventEmitter {
93
'clearHighlightedElementInDOM',
94
this.clearHighlightedElementInDOM
95
);
99
- bridge.addListener('exportProfilingSummary', this.exportProfilingSummary);
100
- bridge.addListener('getCommitDetails', this.getCommitDetails);
101
- bridge.addListener('getFiberCommits', this.getFiberCommits);
102
- bridge.addListener('getInteractions', this.getInteractions);
96
+ bridge.addListener('getProfilingData', this.getProfilingData);
97
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
104
- bridge.addListener('getProfilingSummary', this.getProfilingSummary);
98
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
99
bridge.addListener('getOwnersList', this.getOwnersList);
100
bridge.addListener('inspectElement', this.inspectElement);
@@ -154,109 +147,19 @@ export default class Agent extends EventEmitter {
147
return null;
148
}
149
157
- exportProfilingSummary = (
158
- exportedProfilingSummary: ExportedProfilingSummaryFromFrontend
159
- ): void => {
160
- const { rendererID, rootID } = exportedProfilingSummary;
150
+ getProfilingData = ({ rendererID }: {| rendererID: RendererID |}) => {
151
const renderer = this._rendererInterfaces[rendererID];
152
if (renderer == null) {
153
console.warn(`Invalid renderer id "${rendererID}"`);
164
- return;
165
- }
166
- try {
167
- const exportedProfilingDataFromRenderer = renderer.getExportedProfilingData(
168
- rootID
169
- );
170
- const exportedProfilingData = prepareExportedProfilingData(
171
- exportedProfilingDataFromRenderer,
172
- exportedProfilingSummary
173
- );
174
- this._bridge.send('exportFile', {
175
- contents: JSON.stringify(exportedProfilingData, null, 2),
176
- filename: 'profile-data.json',
177
- });
178
- } catch (error) {
179
- console.warn(`Unable to export file: ${error.stack}`);
154
}
181
- };
155
183
- getCommitDetails = ({
184
- commitIndex,
185
- rendererID,
186
- rootID,
187
- }: {
188
- commitIndex: number,
189
- rendererID: number,
190
- rootID: number,
191
- }) => {
192
- const renderer = this._rendererInterfaces[rendererID];
193
- if (renderer == null) {
194
- console.warn(`Invalid renderer id "${rendererID}"`);
195
- } else {
196
- this._bridge.send(
197
- 'commitDetails',
198
- renderer.getCommitDetails(rootID, commitIndex)
199
- );
200
- }
201
- };
202
-
203
- getFiberCommits = ({
204
- fiberID,
205
- rendererID,
206
- rootID,
207
- }: {
208
- fiberID: number,
209
- rendererID: number,
210
- rootID: number,
211
- }) => {
212
- const renderer = this._rendererInterfaces[rendererID];
213
- if (renderer == null) {
214
- console.warn(`Invalid renderer id "${rendererID}"`);
215
- } else {
216
- this._bridge.send(
217
- 'fiberCommits',
218
- renderer.getFiberCommits(rootID, fiberID)
219
- );
220
- }
221
- };
222
-
223
- getInteractions = ({
224
- rendererID,
225
- rootID,
226
- }: {
227
- rendererID: number,
228
- rootID: number,
229
- }) => {
230
- const renderer = this._rendererInterfaces[rendererID];
231
- if (renderer == null) {
232
- console.warn(`Invalid renderer id "${rendererID}"`);
233
- } else {
234
- this._bridge.send('interactions', renderer.getInteractions(rootID));
235
- }
156
+ this._bridge.send('profilingData', renderer.getProfilingData());
157
};
158
159
getProfilingStatus = () => {
160
this._bridge.send('profilingStatus', this._isProfiling);
161
};
162
242
- getProfilingSummary = ({
243
- rendererID,
244
- rootID,
245
- }: {
246
- rendererID: number,
247
- rootID: number,
248
- }) => {
249
- const renderer = this._rendererInterfaces[rendererID];
250
- if (renderer == null) {
251
- console.warn(`Invalid renderer id "${rendererID}"`);
252
- } else {
253
- this._bridge.send(
254
- 'profilingSummary',
255
- renderer.getProfilingSummary(rootID)
256
- );
257
- }
258
- };
259
-
163
clearHighlightedElementInDOM = () => {
164
hideOverlay();
165
};
src/backend/renderer.js
+127
-191
@@ -18,7 +18,6 @@ import {
18
ElementTypeRoot,
19
ElementTypeSuspense,
20
} from 'src/types';
21
-import { PROFILER_EXPORT_VERSION } from 'src/constants';
21
import {
22
getDisplayName,
23
getDefaultComponentFilters,
@@ -37,17 +36,13 @@ import {
36
import { inspectHooksOfFiber } from './ReactDebugHooks';
37
38
import type {
40
- CommitDetailsBackend,
39
+ CommitDataBackend,
40
DevToolsHook,
42
- ExportedProfilingDataFromRenderer,
41
Fiber,
44
- FiberCommitsBackend,
45
- InteractionBackend,
46
- InteractionsBackend,
47
- InteractionWithCommitsBackend,
42
PathFrame,
43
PathMatch,
50
- ProfilingSummaryBackend,
44
+ ProfilingDataBackend,
45
+ ProfilingDataForRootBackend,
46
ReactRenderer,
47
RendererInterface,
48
} from './types';
@@ -55,6 +50,7 @@ import type {
50
InspectedElement,
51
Owner,
52
} from 'src/devtools/views/Components/types';
53
+import type { Interaction } from 'src/devtools/views/Profiler/types';
54
import type { ComponentFilter, ElementType } from 'src/types';
55
56
function getInternalReactConstants(version) {
@@ -820,6 +816,12 @@ export function attach(
816
pushOperation(ElementTypeRoot);
817
pushOperation(isProfilingSupported ? 1 : 0);
818
pushOperation(hasOwnerMetadata ? 1 : 0);
819
+
820
+ if (isProfiling) {
821
+ if (displayNamesByRootID !== null) {
822
+ displayNamesByRootID.set(id, getDisplayNameForRoot(fiber));
823
+ }
824
+ }
825
} else {
826
const { key } = fiber;
827
const displayName = getDisplayNameForFiber(fiber);
@@ -1286,7 +1288,7 @@ export function attach(
1288
durations: [],
1289
commitTime: performance.now() - profilingStartTime,
1290
interactions: Array.from(root.memoizedInteractions).map(
1289
- (interaction: InteractionBackend) => ({
1291
+ (interaction: Interaction) => ({
1292
...interaction,
1293
timestamp: interaction.timestamp - profilingStartTime,
1294
})
@@ -1329,7 +1331,7 @@ export function attach(
1331
durations: [],
1332
commitTime: performance.now() - profilingStartTime,
1333
interactions: Array.from(root.memoizedInteractions).map(
1332
- (interaction: InteractionBackend) => ({
1334
+ (interaction: Interaction) => ({
1335
...interaction,
1336
timestamp: interaction.timestamp - profilingStartTime,
1337
})
@@ -1981,188 +1983,113 @@ export function attach(
1983
type CommitProfilingData = {|
1984
commitTime: number,
1985
durations: Array<number>,
1984
- interactions: Array<InteractionBackend>,
1986
+ interactions: Array<Interaction>,
1987
maxActualDuration: number,
1988
priorityLevel: string | null,
1989
|};
1990
1991
type CommitProfilingMetadataMap = Map<number, Array<CommitProfilingData>>;
1992
+ type DisplayNamesByRootID = Map<number, string>;
1993
1994
let currentCommitProfilingMetadata: CommitProfilingData | null = null;
1995
+ let displayNamesByRootID: DisplayNamesByRootID | null = null;
1996
let initialTreeBaseDurationsMap: Map<number, number> | null = null;
1997
let initialIDToRootMap: Map<number, number> | null = null;
1998
let isProfiling: boolean = false;
1999
let profilingStartTime: number = 0;
2000
let rootToCommitProfilingMetadataMap: CommitProfilingMetadataMap | null = null;
2001
1998
- function getCommitDetails(
1999
- rootID: number,
2000
- commitIndex: number
2001
- ): CommitDetailsBackend {
2002
- const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
2003
- rootID
2004
- );
2005
- if (commitProfilingMetadata != null) {
2006
- const commitProfilingData = commitProfilingMetadata[commitIndex];
2007
- if (commitProfilingData != null) {
2008
- return {
2009
- commitIndex,
2010
- durations: commitProfilingData.durations,
2011
- interactions: commitProfilingData.interactions,
2012
- priorityLevel: commitProfilingData.priorityLevel,
2013
- rootID,
2014
- };
2015
- }
2002
+ function getProfilingData(): ProfilingDataBackend {
2003
+ const dataForRoots: Array<ProfilingDataForRootBackend> = [];
2004
+
2005
+ if (rootToCommitProfilingMetadataMap === null) {
2006
+ throw Error(
2007
+ 'getProfilingData() called before any profiling data was recorded'
2008
+ );
2009
}
2010
2018
- console.warn(
2019
- `getCommitDetails(): No profiling info recorded for root "${rootID}" and commit ${commitIndex}`
2020
- );
2011
+ rootToCommitProfilingMetadataMap.forEach(
2012
+ (commitProfilingMetadata, rootID) => {
2013
+ const commitData: Array<CommitDataBackend> = [];
2014
+ const initialTreeBaseDurations: Array<[number, number]> = [];
2015
+ const allInteractions: Map<number, Interaction> = new Map();
2016
+ const interactionCommits: Map<number, Array<number>> = new Map();
2017
2022
- return {
2023
- commitIndex,
2024
- durations: [],
2025
- interactions: [],
2026
- priorityLevel: null,
2027
- rootID,
2028
- };
2029
- }
2018
+ const displayName =
2019
+ (displayNamesByRootID !== null && displayNamesByRootID.get(rootID)) ||
2020
+ 'Unknown';
2021
2031
- function getFiberCommits(
2032
- rootID: number,
2033
- fiberID: number
2034
- ): FiberCommitsBackend {
2035
- const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
2036
- rootID
2037
- );
2038
- if (commitProfilingMetadata != null) {
2039
- const commitDurations = [];
2040
- commitProfilingMetadata.forEach(({ durations }, commitIndex) => {
2041
- for (let i = 0; i < durations.length; i += 3) {
2042
- if (durations[i] === fiberID) {
2043
- commitDurations.push(commitIndex, durations[i + 2]);
2044
- break;
2045
- }
2022
+ if (initialTreeBaseDurationsMap != null) {
2023
+ initialTreeBaseDurationsMap.forEach((treeBaseDuration, id) => {
2024
+ if (
2025
+ initialIDToRootMap != null &&
2026
+ initialIDToRootMap.get(id) === rootID
2027
+ ) {
2028
+ // We don't need to convert milliseconds to microseconds in this case,
2029
+ // because the profiling summary is JSON serialized.
2030
+ initialTreeBaseDurations.push([id, treeBaseDuration]);
2031
+ }
2032
+ });
2033
}
2047
- });
2048
-
2049
- return {
2050
- commitDurations,
2051
- fiberID,
2052
- rootID,
2053
- };
2054
- }
2034
2056
- console.warn(
2057
- `getFiberCommits(): No profiling info recorded for root "${rootID}"`
2058
- );
2035
+ commitProfilingMetadata.forEach((commitProfilingData, commitIndex) => {
2036
+ const {
2037
+ durations,
2038
+ interactions,
2039
+ maxActualDuration,
2040
+ priorityLevel,
2041
+ commitTime,
2042
+ } = commitProfilingData;
2043
2060
- return {
2061
- commitDurations: [],
2062
- fiberID,
2063
- rootID,
2064
- };
2065
- }
2044
+ const interactionIDs: Array<number> = [];
2045
2067
- function getInteractions(rootID: number): InteractionsBackend {
2068
- const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
2069
- rootID
2070
- );
2071
- if (commitProfilingMetadata != null) {
2072
- const interactionsMap: Map<
2073
- number,
2074
- InteractionWithCommitsBackend
2075
- > = new Map();
2076
-
2077
- commitProfilingMetadata.forEach((commitProfilingData, commitIndex) => {
2078
- commitProfilingData.interactions.forEach(interaction => {
2079
- const interactionWithCommits = interactionsMap.get(interaction.id);
2080
- if (interactionWithCommits != null) {
2081
- interactionWithCommits.commits.push(commitIndex);
2082
- } else {
2083
- interactionsMap.set(interaction.id, {
2084
- ...interaction,
2085
- commits: [commitIndex],
2086
- });
2087
- }
2088
- });
2089
- });
2046
+ interactions.forEach(interaction => {
2047
+ if (!allInteractions.has(interaction.id)) {
2048
+ allInteractions.set(interaction.id, interaction);
2049
+ }
2050
2091
- return {
2092
- interactions: Array.from(interactionsMap.values()),
2093
- rootID,
2094
- };
2095
- }
2051
+ interactionIDs.push(interaction.id);
2052
2097
- console.warn(
2098
- `getInteractions(): No interactions recorded for root "${rootID}"`
2099
- );
2053
+ const commitIndices = interactionCommits.get(interaction.id);
2054
+ if (commitIndices != null) {
2055
+ commitIndices.push(commitIndex);
2056
+ } else {
2057
+ interactionCommits.set(interaction.id, [commitIndex]);
2058
+ }
2059
+ });
2060
+
2061
+ const fiberActualDurations: Array<[number, number]> = [];
2062
+ const fiberSelfDurations: Array<[number, number]> = [];
2063
+ for (let i = 0; i < durations.length; i += 3) {
2064
+ const fiberID = durations[i];
2065
+ fiberActualDurations.push([fiberID, durations[i + 1]]);
2066
+ fiberSelfDurations.push([fiberID, durations[i + 2]]);
2067
+ }
2068
2101
- return {
2102
- interactions: [],
2103
- rootID,
2104
- };
2105
- }
2069
+ commitData.push({
2070
+ duration: maxActualDuration,
2071
+ fiberActualDurations,
2072
+ fiberSelfDurations,
2073
+ interactionIDs,
2074
+ priorityLevel,
2075
+ timestamp: commitTime,
2076
+ });
2077
+ });
2078
2107
- function getExportedProfilingData(
2108
- rootID: number
2109
- ): ExportedProfilingDataFromRenderer {
2110
- const commitDetailsForEachCommit = [];
2111
- const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
2112
- rootID
2113
- );
2114
- if (commitProfilingMetadata != null) {
2115
- for (let index = 0; index < commitProfilingMetadata.length; index++) {
2116
- commitDetailsForEachCommit.push(getCommitDetails(rootID, index));
2079
+ dataForRoots.push({
2080
+ commitData,
2081
+ displayName,
2082
+ initialTreeBaseDurations,
2083
+ interactionCommits: Array.from(interactionCommits.entries()),
2084
+ interactions: Array.from(allInteractions.entries()),
2085
+ rootID,
2086
+ });
2087
}
2118
- }
2119
-
2120
- return {
2121
- version: PROFILER_EXPORT_VERSION,
2122
- profilingSummary: getProfilingSummary(rootID),
2123
- commitDetails: commitDetailsForEachCommit,
2124
- interactions: getInteractions(rootID),
2125
- };
2126
- }
2127
-
2128
- function getProfilingSummary(rootID: number): ProfilingSummaryBackend {
2129
- const interactions = new Set();
2130
- const commitDurations = [];
2131
- const commitTimes = [];
2132
-
2133
- const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
2134
- rootID
2088
);
2136
- if (commitProfilingMetadata != null) {
2137
- commitProfilingMetadata.forEach(metadata => {
2138
- commitDurations.push(metadata.maxActualDuration);
2139
- commitTimes.push(metadata.commitTime);
2140
- metadata.interactions.forEach(({ name, timestamp }) => {
2141
- interactions.add(`${timestamp}:${name}`);
2142
- });
2143
- });
2144
- }
2145
-
2146
- const initialTreeBaseDurations = [];
2147
- if (initialTreeBaseDurationsMap != null) {
2148
- initialTreeBaseDurationsMap.forEach((treeBaseDuration, id) => {
2149
- if (
2150
- initialIDToRootMap != null &&
2151
- initialIDToRootMap.get(id) === rootID
2152
- ) {
2153
- // We don't need to convert milliseconds to microseconds in this case,
2154
- // because the profiling summary is JSON serialized.
2155
- initialTreeBaseDurations.push(id, treeBaseDuration);
2156
- }
2157
- });
2158
- }
2089
2090
return {
2161
- commitDurations,
2162
- commitTimes,
2163
- initialTreeBaseDurations,
2164
- interactionCount: interactions.size,
2165
- rootID,
2091
+ dataForRoots,
2092
+ rendererID,
2093
};
2094
}
2095
@@ -2175,9 +2102,18 @@ export function attach(
2102
// It's important we snapshot both the durations and the id-to-root map,
2103
// since either of these may change during the profiling session
2104
// (e.g. when a fiber is re-rendered or when a fiber gets removed).
2105
+ displayNamesByRootID = new Map();
2106
initialTreeBaseDurationsMap = new Map(idToTreeBaseDurationMap);
2107
initialIDToRootMap = new Map(idToRootMap);
2108
2109
+ hook.getFiberRoots(rendererID).forEach(root => {
2110
+ const rootID = getFiberID(getPrimaryFiber(root.current));
2111
+ ((displayNamesByRootID: any): DisplayNamesByRootID).set(
2112
+ rootID,
2113
+ getDisplayNameForRoot(root.current)
2114
+ );
2115
+ });
2116
+
2117
isProfiling = true;
2118
profilingStartTime = performance.now();
2119
rootToCommitProfilingMetadataMap = new Map();
@@ -2313,6 +2249,32 @@ export function attach(
2249
const rootDisplayNameCounter: Map<string, number> = new Map();
2250
2251
function setRootPseudoKey(id: number, fiber: Fiber) {
2252
+ const name = getDisplayNameForRoot(fiber);
2253
+ const counter = rootDisplayNameCounter.get(name) || 0;
2254
+ rootDisplayNameCounter.set(name, counter + 1);
2255
+ const pseudoKey = `${name}:${counter}`;
2256
+ rootPseudoKeys.set(id, pseudoKey);
2257
+ }
2258
+
2259
+ function removeRootPseudoKey(id: number) {
2260
+ const pseudoKey = rootPseudoKeys.get(id);
2261
+ if (pseudoKey === undefined) {
2262
+ throw new Error('Expected root pseudo key to be known.');
2263
+ }
2264
+ const name = pseudoKey.substring(0, pseudoKey.lastIndexOf(':'));
2265
+ const counter = rootDisplayNameCounter.get(name);
2266
+ if (counter === undefined) {
2267
+ throw new Error('Expected counter to be known.');
2268
+ }
2269
+ if (counter > 1) {
2270
+ rootDisplayNameCounter.set(name, counter - 1);
2271
+ } else {
2272
+ rootDisplayNameCounter.delete(name);
2273
+ }
2274
+ rootPseudoKeys.delete(id);
2275
+ }
2276
+
2277
+ function getDisplayNameForRoot(fiber: Fiber): string {
2278
let preferredDisplayName = null;
2279
let fallbackDisplayName = null;
2280
let child = fiber.child;
@@ -2339,29 +2301,7 @@ export function attach(
2301
}
2302
child = child.child;
2303
}
2342
- const name = preferredDisplayName || fallbackDisplayName || 'Anonymous';
2343
- const counter = rootDisplayNameCounter.get(name) || 0;
2344
- rootDisplayNameCounter.set(name, counter + 1);
2345
- const pseudoKey = `${name}:${counter}`;
2346
- rootPseudoKeys.set(id, pseudoKey);
2347
- }
2348
-
2349
- function removeRootPseudoKey(id: number) {
2350
- const pseudoKey = rootPseudoKeys.get(id);
2351
- if (pseudoKey === undefined) {
2352
- throw new Error('Expected root pseudo key to be known.');
2353
- }
2354
- const name = pseudoKey.substring(0, pseudoKey.lastIndexOf(':'));
2355
- const counter = rootDisplayNameCounter.get(name);
2356
- if (counter === undefined) {
2357
- throw new Error('Expected counter to be known.');
2358
- }
2359
- if (counter > 1) {
2360
- rootDisplayNameCounter.set(name, counter - 1);
2361
- } else {
2362
- rootDisplayNameCounter.delete(name);
2363
- }
2364
- rootPseudoKeys.delete(id);
2304
+ return preferredDisplayName || fallbackDisplayName || 'Anonymous';
2305
}
2306
2307
function getPathFrame(fiber: Fiber): PathFrame {
@@ -2459,15 +2399,11 @@ export function attach(
2399
cleanup,
2400
flushInitialOperations,
2401
getBestMatchForTrackedPath,
2462
- getCommitDetails,
2402
getFiberIDFromNative,
2464
- getFiberCommits,
2465
- getInteractions,
2403
findNativeByFiberID,
2404
getOwnersList,
2405
getPathForElement,
2469
- getExportedProfilingData,
2470
- getProfilingSummary,
2406
+ getProfilingData,
2407
handleCommitFiberRoot,
2408
handleCommitFiberUnmount,
2409
inspectElement,
src/backend/types.js
+23
-49
@@ -5,6 +5,7 @@ import type {
5
InspectedElement,
6
Owner,
7
} from 'src/devtools/views/Components/types';
8
+import type { Interaction } from 'src/devtools/views/Profiler/types';
9
10
type BundleType =
11
| 0 // PROD
@@ -115,51 +116,33 @@ export type ReactRenderer = {
116
currentDispatcherRef?: {| current: null | Dispatcher |},
117
};
118
118
-export type InteractionBackend = {|
119
- id: number,
120
- name: string,
121
- timestamp: number,
122
-|};
123
-
124
-export type CommitDetailsBackend = {|
125
- commitIndex: number,
126
- // An interleaved array: fiberID at [i], actualDuration at [i + 1], computed selfDuration at [i + 2].
127
- durations: Array<number>,
128
- interactions: Array<InteractionBackend>,
119
+export type CommitDataBackend = {|
120
+ duration: number,
121
+ // Tuple of fiber ID and actual duration
122
+ fiberActualDurations: Array<[number, number]>,
123
+ // Tuple of fiber ID and computed "self" duration
124
+ fiberSelfDurations: Array<[number, number]>,
125
+ interactionIDs: Array<number>,
126
priorityLevel: string | null,
130
- rootID: number,
131
-|};
132
-
133
-export type FiberCommitsBackend = {|
134
- commitDurations: Array<number>,
135
- fiberID: number,
136
- rootID: number,
137
-|};
138
-
139
-export type InteractionWithCommitsBackend = {|
140
- ...InteractionBackend,
141
- commits: Array<number>,
142
-|};
143
-
144
-export type InteractionsBackend = {|
145
- interactions: Array<InteractionWithCommitsBackend>,
146
- rootID: number,
127
+ timestamp: number,
128
|};
129
149
-export type ProfilingSummaryBackend = {|
150
- commitDurations: Array<number>,
151
- commitTimes: Array<number>,
152
- // An interleaved array: fiberID at [i], initialTreeBaseDuration at [i + 1].
153
- initialTreeBaseDurations: Array<number>,
154
- interactionCount: number,
130
+export type ProfilingDataForRootBackend = {|
131
+ commitData: Array<CommitDataBackend>,
132
+ displayName: string,
133
+ // Tuple of Fiber ID and base duration
134
+ initialTreeBaseDurations: Array<[number, number]>,
135
+ // Tuple of Interaction ID and commit indices
136
+ interactionCommits: Array<[number, Array<number>]>,
137
+ interactions: Array<[number, Interaction]>,
138
rootID: number,
139
|};
140
158
-export type ExportedProfilingDataFromRenderer = {|
159
- version: 3,
160
- profilingSummary: ProfilingSummaryBackend,
161
- commitDetails: Array<CommitDetailsBackend>,
162
- interactions: InteractionsBackend,
141
+// Profiling data collected by the renderer interface.
142
+// This information will be passed to the frontend and combined with info it collects.
143
+export type ProfilingDataBackend = {|
144
+ dataForRoots: Array<ProfilingDataForRootBackend>,
145
+ rendererID: number,
146
|};
147
148
export type PathFrame = {|
@@ -178,21 +161,12 @@ export type RendererInterface = {
161
findNativeByFiberID: (id: number) => ?Array<NativeType>,
162
flushInitialOperations: () => void,
163
getBestMatchForTrackedPath: () => PathMatch | null,
181
- getCommitDetails: (
182
- rootID: number,
183
- commitIndex: number
184
- ) => CommitDetailsBackend,
164
getFiberIDFromNative: (
165
component: NativeType,
166
findNearestUnfilteredAncestor?: boolean
167
) => number | null,
189
- getFiberCommits: (rootID: number, fiberID: number) => FiberCommitsBackend,
190
- getInteractions: (rootID: number) => InteractionsBackend,
168
+ getProfilingData(): ProfilingDataBackend,
169
getOwnersList: (id: number) => Array<Owner> | null,
192
- getExportedProfilingData: (
193
- rootID: number
194
- ) => ExportedProfilingDataFromRenderer,
195
- getProfilingSummary: (rootID: number) => ProfilingSummaryBackend,
170
getPathForElement: (id: number) => Array<PathFrame> | null,
171
handleCommitFiberRoot: (fiber: Object, commitPriority?: number) => void,
172
handleCommitFiberUnmount: (fiber: Object) => void,
src/constants.js
+1
-1
@@ -16,4 +16,4 @@ export const SESSION_STORAGE_LAST_SELECTION_KEY =
16
17
export const __DEBUG__ = false;
18
19
-export const PROFILER_EXPORT_VERSION = 3;
19
+export const PROFILER_EXPORT_VERSION = 5;
src/devtools/ProfilerStore.js
new
+370
@@ -0,0 +1,370 @@
1
+// @flow
2
+
3
+import EventEmitter from 'events';
4
+import memoize from 'memoize-one';
5
+import throttle from 'lodash.throttle';
6
+import { prepareProfilingDataFrontendFromBackendAndStore } from './views/Profiler/utils';
7
+import ProfilingCache from './ProfilingCache';
8
+import Store from './store';
9
+
10
+import type { ProfilingDataBackend } from 'src/backend/types';
11
+import type {
12
+ CommitDataFrontend,
13
+ ProfilingDataForRootFrontend,
14
+ ProfilingDataFrontend,
15
+ SnapshotNode,
16
+} from './views/Profiler/types';
17
+import type { Bridge } from '../types';
18
+
19
+const THROTTLE_CAPTURE_SCREENSHOT_DURATION = 500;
20
+
21
+export default class ProfilerStore extends EventEmitter {
22
+ _bridge: Bridge;
23
+
24
+ // Suspense cache for lazily calculating derived profiling data.
25
+ _cache: ProfilingCache;
26
+
27
+ // Temporary store of profiling data from the backend renderer(s).
28
+ // This data will be converted to the ProfilingDataFrontend format after being collected from all renderers.
29
+ _dataBackends: Array<ProfilingDataBackend> = [];
30
+
31
+ // Data from the most recently completed profiling session,
32
+ // or data that has been imported from a previously exported session.
33
+ // This object contains all necessary data to drive the Profiler UI interface,
34
+ // even though some of it is lazily parsed/derived via the ProfilingCache.
35
+ _dataFrontend: ProfilingDataFrontend | null = null;
36
+
37
+ // Snapshot of the state of the main Store (including all roots) when profiling started.
38
+ // Once profiling is finished, this snapshot can be used along with "operations" messages emitted during profiling,
39
+ // to reconstruct the state of each root for each commit.
40
+ // It's okay to use a single root to store this information because node IDs are unique across all roots.
41
+ //
42
+ // This map is only updated while profiling is in progress;
43
+ // Upon completion, it is converted into the exportable ProfilingDataFrontend format.
44
+ _initialSnapshotsByRootID: Map<number, Map<number, SnapshotNode>> = new Map();
45
+
46
+ // Map of root (id) to a list of tree mutation that occur during profiling.
47
+ // Once profiling is finished, these mutations can be used, along with the initial tree snapshots,
48
+ // to reconstruct the state of each root for each commit.
49
+ //
50
+ // This map is only updated while profiling is in progress;
51
+ // Upon completion, it is converted into the exportable ProfilingDataFrontend format.
52
+ _inProgressOperationsByRootID: Map<number, Array<Uint32Array>> = new Map();
53
+
54
+ // Map of root (id) to a Map of screenshots by commit ID.
55
+ // Stores screenshots for each commit (when profiling).
56
+ //
57
+ // This map is only updated while profiling is in progress;
58
+ // Upon completion, it is converted into the exportable ProfilingDataFrontend format.
59
+ _inProgressScreenshotsByRootID: Map<number, Map<number, string>> = new Map();
60
+
61
+ // The backend is currently profiling.
62
+ // When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
63
+ _isProfiling: boolean = false;
64
+
65
+ // After profiling, data is requested from each attached renderer using this queue.
66
+ // So long as this queue is not empty, the store is retrieving and processing profiling data from the backend.
67
+ _rendererQueue: Set<number> = new Set();
68
+
69
+ _store: Store;
70
+
71
+ constructor(bridge: Bridge, store: Store, defaultIsProfiling: boolean) {
72
+ super();
73
+
74
+ this._bridge = bridge;
75
+ this._isProfiling = defaultIsProfiling;
76
+ this._store = store;
77
+
78
+ bridge.addListener('operations', this.onBridgeOperations);
79
+ bridge.addListener('profilingData', this.onBridgeProfilingData);
80
+ bridge.addListener('profilingStatus', this.onProfilingStatus);
81
+ bridge.addListener('shutdown', this.onBridgeShutdown);
82
+
83
+ // It's possible that profiling has already started (e.g. "reload and start profiling")
84
+ // so the frontend needs to ask the backend for its status after mounting.
85
+ bridge.send('getProfilingStatus');
86
+
87
+ this._cache = new ProfilingCache(this);
88
+ }
89
+
90
+ getCommitData(rootID: number, commitIndex: number): CommitDataFrontend {
91
+ if (this._dataFrontend !== null) {
92
+ const dataForRoot = this._dataFrontend.dataForRoots.get(rootID);
93
+ if (dataForRoot != null) {
94
+ const commitDatum = dataForRoot.commitData[commitIndex];
95
+ if (commitDatum != null) {
96
+ return commitDatum;
97
+ }
98
+ }
99
+ }
100
+
101
+ throw Error(
102
+ `Could not find commit data for root "${rootID}" and commit ${commitIndex}`
103
+ );
104
+ }
105
+
106
+ getDataForRoot(rootID: number): ProfilingDataForRootFrontend {
107
+ if (this._dataFrontend !== null) {
108
+ const dataForRoot = this._dataFrontend.dataForRoots.get(rootID);
109
+ if (dataForRoot != null) {
110
+ return dataForRoot;
111
+ }
112
+ }
113
+
114
+ throw Error(`Could not find commit data for root "${rootID}"`);
115
+ }
116
+
117
+ get cache(): ProfilingCache {
118
+ return this._cache;
119
+ }
120
+
121
+ // Profiling data has been recorded for at least one root.
122
+ get hasProfilingData(): boolean {
123
+ return (
124
+ this._dataFrontend !== null && this._dataFrontend.dataForRoots.size > 0
125
+ );
126
+ }
127
+
128
+ // TODO (profarc) Remove this getter
129
+ get initialSnapshotsByRootID(): Map<number, Map<number, SnapshotNode>> {
130
+ return this._initialSnapshotsByRootID;
131
+ }
132
+
133
+ // TODO (profarc) Remove this getter
134
+ get inProgressOperationsByRootID(): Map<number, Array<Uint32Array>> {
135
+ return this._inProgressOperationsByRootID;
136
+ }
137
+
138
+ // TODO (profarc) Remove this getter
139
+ get inProgressScreenshotsByRootID(): Map<number, Map<number, string>> {
140
+ return this._inProgressScreenshotsByRootID;
141
+ }
142
+
143
+ get isProcessingData(): boolean {
144
+ return this._rendererQueue.size > 0 || this._dataBackends.length > 0;
145
+ }
146
+
147
+ get isProfiling(): boolean {
148
+ return this._isProfiling;
149
+ }
150
+
151
+ get profilingData(): ProfilingDataFrontend | null {
152
+ return this._dataFrontend;
153
+ }
154
+ set profilingData(value: ProfilingDataFrontend | null): void {
155
+ this._dataBackends.splice(0);
156
+ this._dataFrontend = value;
157
+ this._initialSnapshotsByRootID.clear();
158
+ this._inProgressOperationsByRootID.clear();
159
+ this._inProgressScreenshotsByRootID.clear();
160
+ this._cache.invalidate();
161
+
162
+ // TODO (profarc) Remove subscriptions to Store for this
163
+ this._store.emit('profilingData');
164
+ this.emit('profilingData');
165
+ }
166
+
167
+ clear(): void {
168
+ this._dataBackends.splice(0);
169
+ this._dataFrontend = null;
170
+ this._initialSnapshotsByRootID.clear();
171
+ this._inProgressOperationsByRootID.clear();
172
+ this._inProgressScreenshotsByRootID.clear();
173
+
174
+ // Invalidate suspense cache if profiling data is being (re-)recorded.
175
+ // Note that we clear now because any existing data is "stale".
176
+ this._cache.invalidate();
177
+
178
+ // TODO (profarc) Remove subscriptions to Store for this
179
+ this._store.emit('isProfiling');
180
+ this.emit('isProfiling');
181
+ }
182
+
183
+ startProfiling(): void {
184
+ this._bridge.send('startProfiling');
185
+
186
+ // Don't actually update the local profiling boolean yet!
187
+ // Wait for onProfilingStatus() to confirm the status has changed.
188
+ // This ensures the frontend and backend are in sync wrt which commits were profiled.
189
+ // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
190
+ }
191
+
192
+ stopProfiling(): void {
193
+ this._bridge.send('stopProfiling');
194
+
195
+ // Don't actually update the local profiling boolean yet!
196
+ // Wait for onProfilingStatus() to confirm the status has changed.
197
+ // This ensures the frontend and backend are in sync wrt which commits were profiled.
198
+ // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
199
+ }
200
+
201
+ _captureScreenshot = throttle(
202
+ memoize((rootID: number, commitIndex: number) => {
203
+ this._bridge.send('captureScreenshot', { commitIndex, rootID });
204
+ }),
205
+ THROTTLE_CAPTURE_SCREENSHOT_DURATION
206
+ );
207
+
208
+ _takeProfilingSnapshotRecursive = (
209
+ elementID: number,
210
+ profilingSnapshots: Map<number, SnapshotNode>
211
+ ) => {
212
+ const element = this._store.getElementByID(elementID);
213
+ if (element !== null) {
214
+ const snapshotNode: SnapshotNode = {
215
+ id: elementID,
216
+ children: element.children.slice(0),
217
+ displayName: element.displayName,
218
+ key: element.key,
219
+ type: element.type,
220
+ };
221
+ profilingSnapshots.set(elementID, snapshotNode);
222
+
223
+ element.children.forEach(childID =>
224
+ this._takeProfilingSnapshotRecursive(childID, profilingSnapshots)
225
+ );
226
+ }
227
+ };
228
+
229
+ onBridgeOperations = (operations: Uint32Array) => {
230
+ if (!(operations instanceof Uint32Array)) {
231
+ // $FlowFixMe TODO HACK Temporary workaround for the fact that Chrome is not transferring the typed array.
232
+ operations = Uint32Array.from(Object.values(operations));
233
+ }
234
+
235
+ // The first two values are always rendererID and rootID
236
+ const rootID = operations[1];
237
+
238
+ if (this._isProfiling) {
239
+ let profilingOperations = this._inProgressOperationsByRootID.get(rootID);
240
+ if (profilingOperations == null) {
241
+ profilingOperations = [operations];
242
+ this._inProgressOperationsByRootID.set(rootID, profilingOperations);
243
+ } else {
244
+ profilingOperations.push(operations);
245
+ }
246
+
247
+ if (!this._initialSnapshotsByRootID.has(rootID)) {
248
+ this._initialSnapshotsByRootID.set(rootID, new Map());
249
+ }
250
+
251
+ if (this._store.captureScreenshots) {
252
+ const commitIndex = profilingOperations.length - 1;
253
+ this._captureScreenshot(rootID, commitIndex);
254
+ }
255
+ }
256
+ };
257
+
258
+ onBridgeProfilingData = (dataBackend: ProfilingDataBackend) => {
259
+ if (this._isProfiling) {
260
+ // This should never happen, but if it does- ignore previous profiling data.
261
+ return;
262
+ }
263
+
264
+ const { rendererID } = dataBackend;
265
+
266
+ if (!this._rendererQueue.has(rendererID)) {
267
+ throw Error(
268
+ `Unexpected profiling data update from renderer "${rendererID}"`
269
+ );
270
+ }
271
+
272
+ this._dataBackends.push(dataBackend);
273
+ this._rendererQueue.delete(rendererID);
274
+
275
+ if (this._rendererQueue.size === 0) {
276
+ this._dataFrontend = prepareProfilingDataFrontendFromBackendAndStore(
277
+ this._dataBackends,
278
+ this._inProgressOperationsByRootID,
279
+ this._inProgressScreenshotsByRootID,
280
+ this._initialSnapshotsByRootID
281
+ );
282
+
283
+ this._dataBackends.splice(0);
284
+
285
+ // TODO (profarc) Remove subscriptions to Store for this
286
+ this._store.emit('isProcessingData');
287
+ this.emit('isProcessingData');
288
+ }
289
+ };
290
+
291
+ onBridgeShutdown = () => {
292
+ this._bridge.removeListener('operations', this.onBridgeOperations);
293
+ this._bridge.removeListener('profilingStatus', this.onProfilingStatus);
294
+ this._bridge.removeListener('shutdown', this.onBridgeShutdown);
295
+ };
296
+
297
+ onProfilingStatus = (isProfiling: boolean) => {
298
+ if (isProfiling) {
299
+ this._dataBackends.splice(0);
300
+ this._dataFrontend = null;
301
+ this._initialSnapshotsByRootID.clear();
302
+ this._inProgressOperationsByRootID.clear();
303
+ this._inProgressScreenshotsByRootID.clear();
304
+ this._rendererQueue.clear();
305
+
306
+ // Record snapshot of tree at the time profiling is started.
307
+ // This info is required to handle cases of e.g. nodes being removed during profiling.
308
+ this._store.roots.forEach(rootID => {
309
+ const profilingSnapshots = new Map();
310
+ this._initialSnapshotsByRootID.set(rootID, profilingSnapshots);
311
+ this._takeProfilingSnapshotRecursive(rootID, profilingSnapshots);
312
+ });
313
+ }
314
+
315
+ if (this._isProfiling !== isProfiling) {
316
+ this._isProfiling = isProfiling;
317
+
318
+ // Invalidate suspense cache if profiling data is being (re-)recorded.
319
+ // Note that we clear again, in case any views read from the cache while profiling.
320
+ // (That would have resolved a now-stale value without any profiling data.)
321
+ this._cache.invalidate();
322
+
323
+ // TODO (profarc) Remove subscriptions to Store for this
324
+ this._store.emit('isProfiling');
325
+ this.emit('isProfiling');
326
+
327
+ // If we've just finished a profiling session, we need to fetch data stored in each renderer interface
328
+ // and re-assemble it on the front-end into a format (ProfilingDataFrontend) that can power the Profiler UI.
329
+ // During this time, DevTools UI should probably not be interactive.
330
+ if (!isProfiling) {
331
+ this._dataBackends.splice(0);
332
+ this._rendererQueue.clear();
333
+
334
+ for (let rendererID of this._store.rootIDToRendererID.values()) {
335
+ if (!this._rendererQueue.has(rendererID)) {
336
+ this._rendererQueue.add(rendererID);
337
+
338
+ this._bridge.send('getProfilingData', { rendererID });
339
+ }
340
+ }
341
+
342
+ // TODO (profarc) Remove subscriptions to Store for this
343
+ this._store.emit('isProcessingData');
344
+ this.emit('isProcessingData');
345
+ }
346
+ }
347
+ };
348
+
349
+ onScreenshotCaptured = ({
350
+ commitIndex,
351
+ dataURL,
352
+ rootID,
353
+ }: {|
354
+ commitIndex: number,
355
+ dataURL: string,
356
+ rootID: number,
357
+ |}) => {
358
+ let screenshotsForRootByCommitIndex = this._inProgressScreenshotsByRootID.get(
359
+ rootID
360
+ );
361
+ if (!screenshotsForRootByCommitIndex) {
362
+ screenshotsForRootByCommitIndex = new Map();
363
+ this._inProgressScreenshotsByRootID.set(
364
+ rootID,
365
+ screenshotsForRootByCommitIndex
366
+ );
367
+ }
368
+ screenshotsForRootByCommitIndex.set(commitIndex, dataURL);
369
+ };
370
+}
src/devtools/ProfilingCache.js
+55
-366
@@ -1,7 +1,6 @@
1
// @flow
2
3
-import { createResource } from './cache';
4
-import Store from './store';
3
+import ProfilerStore from './ProfilerStore';
4
import {
5
getCommitTree,
6
invalidateCommitTrees,
@@ -19,415 +18,105 @@ import {
18
invalidateChartData as invalidateRankedChartData,
19
} from 'src/devtools/views/Profiler/RankedChartBuilder';
20
22
-import type { Resource } from './cache';
23
-import type {
24
- CommitDetailsBackend,
25
- FiberCommitsBackend,
26
- InteractionsBackend,
27
- ProfilingSummaryBackend,
28
-} from 'src/backend/types';
29
-import type {
30
- CommitDetailsFrontend,
31
- FiberCommitsFrontend,
32
- InteractionsFrontend,
33
- InteractionWithCommitsFrontend,
34
- CommitTreeFrontend,
35
- ProfilingSummaryFrontend,
36
-} from 'src/devtools/views/Profiler/types';
21
+import type { CommitTree } from 'src/devtools/views/Profiler/types';
22
import type { ChartData as FlamegraphChartData } from 'src/devtools/views/Profiler/FlamegraphChartBuilder';
23
import type { ChartData as InteractionsChartData } from 'src/devtools/views/Profiler/InteractionsChartBuilder';
24
import type { ChartData as RankedChartData } from 'src/devtools/views/Profiler/RankedChartBuilder';
40
-import type { Bridge } from 'src/types';
41
-
42
-type CommitDetailsParams = {|
43
- commitIndex: number,
44
- rendererID: number,
45
- rootID: number,
46
-|};
47
-
48
-type FiberCommitsParams = {|
49
- fiberID: number,
50
- rendererID: number,
51
- rootID: number,
52
-|};
53
-
54
-type InteractionsParams = {|
55
- rendererID: number,
56
- rootID: number,
57
-|};
58
-
59
-type GetCommitTreeParams = {|
60
- commitIndex: number,
61
- profilingSummary: ProfilingSummaryFrontend,
62
-|};
63
-
64
-type ProfilingSummaryParams = {|
65
- rendererID: number,
66
- rootID: number,
67
-|};
25
26
export default class ProfilingCache {
70
- _bridge: Bridge;
71
- _store: Store;
72
-
73
- _pendingCommitDetailsMap: Map<
74
- string,
75
- (commitDetails: CommitDetailsFrontend) => void
76
- > = new Map();
77
-
78
- _pendingFiberCommitsMap: Map<
79
- string,
80
- (fiberCommits: FiberCommitsFrontend) => void
81
- > = new Map();
82
-
83
- _pendingInteractionsMap: Map<
84
- number,
85
- (interactions: InteractionsFrontend) => void
86
- > = new Map();
87
-
88
- _pendingProfileSummaryMap: Map<
89
- number,
90
- (profilingSummary: ProfilingSummaryFrontend) => void
91
- > = new Map();
92
-
93
- CommitDetails: Resource<
94
- CommitDetailsParams,
95
- string,
96
- CommitDetailsFrontend
97
- > = createResource(
98
- ({ commitIndex, rendererID, rootID }: CommitDetailsParams) => {
99
- return new Promise(resolve => {
100
- const pendingKey = `${rootID}-${commitIndex}`;
101
- const profilingData = this._store.profilingData;
102
- if (profilingData !== null) {
103
- const commitDetailsByCommitIndex = profilingData.commitDetails;
104
- if (
105
- commitDetailsByCommitIndex != null &&
106
- commitIndex < commitDetailsByCommitIndex.length
107
- ) {
108
- const commitDetails = commitDetailsByCommitIndex[commitIndex];
109
- if (commitDetails != null) {
110
- this._pendingCommitDetailsMap.delete(pendingKey);
111
- resolve(commitDetails);
112
- return;
113
- }
114
- }
115
- } else if (this._store.profilingOperations.has(rootID)) {
116
- this._pendingCommitDetailsMap.set(pendingKey, resolve);
117
- this._bridge.send('getCommitDetails', {
118
- commitIndex,
119
- rendererID,
120
- rootID,
121
- });
122
- return;
123
- }
124
-
125
- this._pendingCommitDetailsMap.delete(pendingKey);
126
-
127
- // If no profiling data was recorded for this root, skip the round trip.
128
- resolve({
129
- rootID,
130
- commitIndex,
131
- actualDurations: new Map(),
132
- priorityLevel: null,
133
- interactions: [],
134
- selfDurations: new Map(),
135
- });
136
- });
137
- },
138
- ({ commitIndex, rendererID, rootID }: CommitDetailsParams) =>
139
- `${rootID}-${commitIndex}`
140
- );
141
-
142
- FiberCommits: Resource<
143
- FiberCommitsParams,
144
- string,
145
- FiberCommitsFrontend
146
- > = createResource(
147
- ({ fiberID, rendererID, rootID }: FiberCommitsParams) => {
148
- return new Promise(resolve => {
149
- const pendingKey = `${rootID}-${fiberID}`;
150
- const profilingData = this._store.profilingData;
151
- if (profilingData !== null) {
152
- const { commitDetails } = profilingData;
153
- const commitDurations = [];
154
- commitDetails.forEach(({ selfDurations }, commitIndex) => {
155
- const selfDuration = selfDurations.get(fiberID);
156
- if (selfDuration != null) {
157
- commitDurations.push(commitIndex, selfDuration);
158
- }
159
- });
160
- this._pendingFiberCommitsMap.delete(pendingKey);
161
- resolve({
162
- commitDurations,
163
- fiberID,
164
- rootID,
165
- });
166
- return;
167
- } else if (this._store.profilingOperations.has(rootID)) {
168
- this._pendingFiberCommitsMap.set(pendingKey, resolve);
169
- this._bridge.send('getFiberCommits', {
170
- fiberID,
171
- rendererID,
172
- rootID,
173
- });
174
- return;
175
- }
176
-
177
- this._pendingFiberCommitsMap.delete(pendingKey);
178
-
179
- // If no profiling data was recorded for this root, skip the round trip.
180
- resolve({
181
- commitDurations: [],
182
- fiberID,
183
- rootID,
184
- });
185
- });
186
- },
187
- ({ fiberID, rendererID, rootID }: FiberCommitsParams) =>
188
- `${rootID}-${fiberID}`
189
- );
190
-
191
- Interactions: Resource<
192
- InteractionsParams,
193
- number,
194
- InteractionsFrontend
195
- > = createResource(
196
- ({ rendererID, rootID }: InteractionsParams) => {
197
- return new Promise(resolve => {
198
- const pendingKey = rootID;
199
- const profilingData = this._store.profilingData;
200
- if (profilingData !== null) {
201
- const interactionsFrontend: InteractionsFrontend =
202
- profilingData.interactions;
203
- if (interactionsFrontend != null) {
204
- this._pendingInteractionsMap.delete(pendingKey);
205
- resolve(interactionsFrontend);
206
- return;
207
- }
208
- } else if (this._store.profilingOperations.has(rootID)) {
209
- this._pendingInteractionsMap.set(pendingKey, resolve);
210
- this._bridge.send('getInteractions', {
211
- rendererID,
212
- rootID,
213
- });
214
- return;
215
- }
216
-
217
- this._pendingInteractionsMap.delete(pendingKey);
27
+ _fiberCommits: Map<number, Array<number>> = new Map();
28
+ _profilerStore: ProfilerStore;
29
219
- // If no profiling data was recorded for this root, skip the round trip.
220
- resolve({
221
- interactions: [],
222
- rootID,
223
- });
224
- });
225
- },
226
- ({ rendererID, rootID }: ProfilingSummaryParams) => rootID
227
- );
228
-
229
- ProfilingSummary: Resource<
230
- ProfilingSummaryParams,
231
- number,
232
- ProfilingSummaryFrontend
233
- > = createResource(
234
- ({ rendererID, rootID }: ProfilingSummaryParams) => {
235
- return new Promise(resolve => {
236
- const pendingKey = rootID;
237
- const profilingData = this._store.profilingData;
238
- if (profilingData !== null) {
239
- const profilingSummaryFrontend: ProfilingSummaryFrontend =
240
- profilingData.profilingSummary;
241
- if (profilingSummaryFrontend != null) {
242
- this._pendingProfileSummaryMap.delete(pendingKey);
243
- resolve(profilingSummaryFrontend);
244
- return;
245
- }
246
- } else if (this._store.profilingOperations.has(rootID)) {
247
- this._pendingProfileSummaryMap.set(pendingKey, resolve);
248
- this._bridge.send('getProfilingSummary', { rendererID, rootID });
249
- return;
250
- }
251
-
252
- this._pendingProfileSummaryMap.delete(pendingKey);
253
-
254
- // If no profiling data was recorded for this root, skip the round trip.
255
- resolve({
256
- rootID,
257
- commitDurations: [],
258
- commitTimes: [],
259
- initialTreeBaseDurations: new Map(),
260
- interactionCount: 0,
261
- });
262
- });
263
- },
264
- ({ rendererID, rootID }: ProfilingSummaryParams) => rootID
265
- );
266
-
267
- constructor(bridge: Bridge, store: Store) {
268
- this._bridge = bridge;
269
- this._store = store;
270
-
271
- bridge.addListener('commitDetails', this.onCommitDetails);
272
- bridge.addListener('fiberCommits', this.onFiberCommits);
273
- bridge.addListener('interactions', this.onInteractions);
274
- bridge.addListener('profilingSummary', this.onProfileSummary);
30
+ constructor(profilerStore: ProfilerStore) {
31
+ this._profilerStore = profilerStore;
32
}
33
277
- getCommitTree = ({ commitIndex, profilingSummary }: GetCommitTreeParams) =>
34
+ getCommitTree = ({
35
+ commitIndex,
36
+ rootID,
37
+ }: {|
38
+ commitIndex: number,
39
+ rootID: number,
40
+ |}) =>
41
getCommitTree({
42
commitIndex,
280
- profilingSummary,
281
- store: this._store,
43
+ profilerStore: this._profilerStore,
44
+ rootID,
45
+ });
46
+
47
+ getFiberCommits = ({
48
+ fiberID,
49
+ rootID,
50
+ }: {|
51
+ fiberID: number,
52
+ rootID: number,
53
+ |}): Array<number> => {
54
+ const cachedFiberCommits = this._fiberCommits.get(fiberID);
55
+ if (cachedFiberCommits != null) {
56
+ return cachedFiberCommits;
57
+ }
58
+
59
+ const fiberCommits = [];
60
+ const dataForRoot = this._profilerStore.getDataForRoot(rootID);
61
+ dataForRoot.commitData.forEach((commitDatum, commitIndex) => {
62
+ if (commitDatum.fiberActualDurations.has(fiberID)) {
63
+ fiberCommits.push(commitIndex);
64
+ }
65
});
66
67
+ this._fiberCommits.set(fiberID, fiberCommits);
68
+
69
+ return fiberCommits;
70
+ };
71
+
72
getFlamegraphChartData = ({
285
- commitDetails,
73
commitIndex,
74
commitTree,
75
+ rootID,
76
}: {|
289
- commitDetails: CommitDetailsFrontend,
77
commitIndex: number,
291
- commitTree: CommitTreeFrontend,
78
+ commitTree: CommitTree,
79
+ rootID: number,
80
|}): FlamegraphChartData =>
81
getFlamegraphChartData({
294
- commitDetails,
82
commitIndex,
83
commitTree,
84
+ profilerStore: this._profilerStore,
85
+ rootID,
86
});
87
88
getInteractionsChartData = ({
300
- interactions,
301
- profilingSummary,
89
+ rootID,
90
}: {|
303
- interactions: Array<InteractionWithCommitsFrontend>,
304
- profilingSummary: ProfilingSummaryFrontend,
91
+ rootID: number,
92
|}): InteractionsChartData =>
93
getInteractionsChartData({
307
- interactions,
308
- profilingSummary,
94
+ profilerStore: this._profilerStore,
95
+ rootID,
96
});
97
98
getRankedChartData = ({
312
- commitDetails,
99
commitIndex,
100
commitTree,
101
+ rootID,
102
}: {|
316
- commitDetails: CommitDetailsFrontend,
103
commitIndex: number,
318
- commitTree: CommitTreeFrontend,
104
+ commitTree: CommitTree,
105
+ rootID: number,
106
|}): RankedChartData =>
107
getRankedChartData({
321
- commitDetails,
108
commitIndex,
109
commitTree,
110
+ profilerStore: this._profilerStore,
111
+ rootID,
112
});
113
114
invalidate() {
327
- // Invalidate Suspense caches.
328
- this.CommitDetails.clear();
329
- this.FiberCommits.clear();
330
- this.Interactions.clear();
331
- this.ProfilingSummary.clear();
115
+ this._fiberCommits.clear();
116
333
- // Invalidate non-Suspense caches too.
117
invalidateCommitTrees();
118
invalidateFlamegraphChartData();
119
invalidateInteractionsChartData();
120
invalidateRankedChartData();
338
-
339
- this._pendingCommitDetailsMap.clear();
340
- this._pendingFiberCommitsMap.clear();
341
- this._pendingInteractionsMap.clear();
342
- this._pendingProfileSummaryMap.clear();
121
}
344
-
345
- onCommitDetails = ({
346
- commitIndex,
347
- durations,
348
- interactions,
349
- priorityLevel,
350
- rootID,
351
- }: CommitDetailsBackend) => {
352
- const key = `${rootID}-${commitIndex}`;
353
- const resolve = this._pendingCommitDetailsMap.get(key);
354
- if (resolve != null) {
355
- this._pendingCommitDetailsMap.delete(key);
356
-
357
- const actualDurationsMap = new Map<number, number>();
358
- const selfDurationsMap = new Map<number, number>();
359
- for (let i = 0; i < durations.length; i += 3) {
360
- const fiberID = durations[i];
361
- actualDurationsMap.set(fiberID, durations[i + 1]);
362
- selfDurationsMap.set(fiberID, durations[i + 2]);
363
- }
364
-
365
- resolve({
366
- actualDurations: actualDurationsMap,
367
- commitIndex,
368
- interactions,
369
- priorityLevel,
370
- rootID,
371
- selfDurations: selfDurationsMap,
372
- });
373
- }
374
- };
375
-
376
- onFiberCommits = ({
377
- commitDurations,
378
- fiberID,
379
- rootID,
380
- }: FiberCommitsBackend) => {
381
- const key = `${rootID}-${fiberID}`;
382
- const resolve = this._pendingFiberCommitsMap.get(key);
383
- if (resolve != null) {
384
- this._pendingFiberCommitsMap.delete(key);
385
-
386
- resolve({
387
- commitDurations,
388
- fiberID,
389
- rootID,
390
- });
391
- }
392
- };
393
-
394
- onInteractions = ({ interactions, rootID }: InteractionsBackend) => {
395
- const resolve = this._pendingInteractionsMap.get(rootID);
396
- if (resolve != null) {
397
- this._pendingInteractionsMap.delete(rootID);
398
-
399
- resolve({
400
- interactions,
401
- rootID,
402
- });
403
- }
404
- };
405
-
406
- onProfileSummary = ({
407
- commitDurations,
408
- commitTimes,
409
- initialTreeBaseDurations,
410
- interactionCount,
411
- rootID,
412
- }: ProfilingSummaryBackend) => {
413
- const resolve = this._pendingProfileSummaryMap.get(rootID);
414
- if (resolve != null) {
415
- this._pendingProfileSummaryMap.delete(rootID);
416
-
417
- const initialTreeBaseDurationsMap = new Map();
418
- for (let i = 0; i < initialTreeBaseDurations.length; i += 2) {
419
- const fiberID = initialTreeBaseDurations[i];
420
- const initialTreeBaseDuration = initialTreeBaseDurations[i + 1];
421
- initialTreeBaseDurationsMap.set(fiberID, initialTreeBaseDuration);
422
- }
423
-
424
- resolve({
425
- commitDurations,
426
- commitTimes,
427
- initialTreeBaseDurations: initialTreeBaseDurationsMap,
428
- interactionCount,
429
- rootID,
430
- });
431
- }
432
- };
122
}
src/devtools/store.js
+49
-206
@@ -1,8 +1,6 @@
1
// @flow
2
3
import EventEmitter from 'events';
4
-import memoize from 'memoize-one';
5
-import throttle from 'lodash.throttle';
4
import { inspect } from 'util';
5
import {
6
TREE_OPERATION_ADD,
@@ -19,11 +17,12 @@ import {
17
import { __DEBUG__ } from '../constants';
18
import ProfilingCache from './ProfilingCache';
19
import { printStore } from 'src/__tests__/storeSerializer';
20
+import ProfilerStore from './ProfilerStore';
21
22
import type { Element } from './views/Components/types';
23
import type {
25
- ImportedProfilingData,
26
- ProfilingSnapshotNode,
24
+ ProfilingDataFrontend,
25
+ SnapshotNode,
26
} from './views/Profiler/types';
27
import type { Bridge, ComponentFilter, ElementType } from '../types';
28
@@ -43,8 +42,6 @@ const LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY =
42
const LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY =
43
'React::DevTools::collapseNodesByDefault';
44
46
-const THROTTLE_CAPTURE_SCREENSHOT_DURATION = 500;
47
-
45
type Config = {|
46
isProfiling?: boolean,
47
supportsCaptureScreenshots?: boolean,
@@ -80,37 +77,11 @@ export default class Store extends EventEmitter {
77
// The InspectedElementContext also relies on this mutability for its WeakMap usage.
78
_idToElement: Map<number, Element> = new Map();
79
83
- // The backend is currently profiling.
84
- // When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
85
- _isProfiling: boolean = false;
86
-
80
// Map of element (id) to the set of elements (ids) it owns.
81
// This map enables getOwnersListForElement() to avoid traversing the entire tree.
82
_ownersMap: Map<number, Set<number>> = new Map();
83
91
- // Suspense cache for reading profiling data.
92
- _profilingCache: ProfilingCache;
93
-
94
- // The user has imported a previously exported profiling session.
95
- _profilingData: ImportedProfilingData | null = null;
96
-
97
- // Map of root (id) to a list of tree mutation that occur during profiling.
98
- // Once profiling is finished, these mutations can be used, along with the initial tree snapshots,
99
- // to reconstruct the state of each root for each commit.
100
- _profilingOperationsByRootID: Map<number, Array<Uint32Array>> = new Map();
101
-
102
- // Map of root (id) to a Map of screenshots by commit ID.
103
- // Stores screenshots for each commit (when profiling).
104
- _profilingScreenshotsByRootID: Map<number, Map<number, string>> = new Map();
105
-
106
- // Snapshot of the state of the main Store (including all roots) when profiling started.
107
- // Once profiling is finished, this snapshot can be used along with "operations" messages emitted during profiling,
108
- // to reconstruct the state of each root for each commit.
109
- // It's okay to use a single root to store this information because node IDs are unique across all roots.
110
- _profilingSnapshotsByRootID: Map<
111
- number,
112
- Map<number, ProfilingSnapshotNode>
113
- > = new Map();
84
+ _profilerStore: ProfilerStore;
85
86
// Incremented each time the store is mutated.
87
// This enables a passive effect to detect a mutation between render and commit phase.
@@ -150,17 +121,16 @@ export default class Store extends EventEmitter {
121
122
this._componentFilters = getSavedComponentFilters();
123
124
+ let isProfiling = false;
125
if (config != null) {
126
+ isProfiling = config.isProfiling === true;
127
+
128
const {
155
- isProfiling,
129
supportsCaptureScreenshots,
130
supportsFileDownloads,
131
supportsProfiling,
132
supportsReloadAndProfile,
133
} = config;
161
- if (isProfiling) {
162
- this._isProfiling = true;
163
- }
134
if (supportsCaptureScreenshots) {
135
this._supportsCaptureScreenshots = true;
136
this._captureScreenshots =
@@ -180,15 +150,9 @@ export default class Store extends EventEmitter {
150
151
this._bridge = bridge;
152
bridge.addListener('operations', this.onBridgeOperations);
183
- bridge.addListener('profilingStatus', this.onProfilingStatus);
184
- bridge.addListener('screenshotCaptured', this.onScreenshotCaptured);
153
bridge.addListener('shutdown', this.onBridgeShutdown);
154
187
- // It's possible that profiling has already started (e.g. "reload and start profiling")
188
- // so the frontend needs to ask the backend for its status after mounting.
189
- bridge.send('getProfilingStatus');
190
-
191
- this._profilingCache = new ProfilingCache(bridge, this);
155
+ this._profilerStore = new ProfilerStore(bridge, this, isProfiling);
156
}
157
158
// This is only used in tests to avoid memory leaks.
@@ -197,23 +161,6 @@ export default class Store extends EventEmitter {
161
// The only safe time to assert these maps are empty is when the store is empty.
162
this.assertMapSizeMatchesRootCount(this._idToElement, '_idToElement');
163
this.assertMapSizeMatchesRootCount(this._ownersMap, '_ownersMap');
200
-
201
- // These maps will be empty unless profiling mode has been started.
202
- // After this, their size should always match the number of roots,
203
- // but unless we want to track additional metadata about profiling history,
204
- // the only safe time to assert this is when the store is empty.
205
- this.assertMapSizeMatchesRootCount(
206
- this._profilingOperationsByRootID,
207
- '_profilingOperationsByRootID'
208
- );
209
- this.assertMapSizeMatchesRootCount(
210
- this._profilingScreenshotsByRootID,
211
- '_profilingScreenshotsByRootID'
212
- );
213
- this.assertMapSizeMatchesRootCount(
214
- this._profilingSnapshotsByRootID,
215
- '_profilingSnapshotsByRootID'
216
- );
164
}
165
166
// These maps should always be the same size as the number of roots
@@ -273,7 +220,7 @@ export default class Store extends EventEmitter {
220
return this._componentFilters;
221
}
222
set componentFilters(value: Array<ComponentFilter>): void {
276
- if (this._isProfiling) {
223
+ if (this._profilerStore.isProfiling) {
224
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
225
// If necessary, we could support this- but it doesn't seem like a necessary use case.
226
throw Error('Cannot modify filter preferences while profiling');
@@ -295,54 +242,65 @@ export default class Store extends EventEmitter {
242
return this._hasOwnerMetadata;
243
}
244
298
- // Profiling data has been recorded for at least one root.
245
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
246
get hasProfilingData(): boolean {
300
- return (
301
- this._profilingData !== null || this._profilingOperationsByRootID.size > 0
302
- );
247
+ return this._profilerStore.hasProfilingData;
248
}
249
250
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
251
+ get isProcessingProfilingData(): boolean {
252
+ return this._profilerStore.isProcessingData;
253
+ }
254
+
255
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
256
get isProfiling(): boolean {
306
- return this._isProfiling;
257
+ return this._profilerStore.isProfiling;
258
}
259
260
get numElements(): number {
261
return this._weightAcrossRoots;
262
}
263
264
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
265
get profilingCache(): ProfilingCache {
314
- return this._profilingCache;
266
+ return this._profilerStore.cache;
267
}
268
317
- get profilingData(): ImportedProfilingData | null {
318
- return this._profilingData;
269
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
270
+ get profilingData(): ProfilingDataFrontend | null {
271
+ return this._profilerStore.profilingData;
272
}
320
- set profilingData(value: ImportedProfilingData | null): void {
321
- this._profilingData = value;
322
- this._profilingOperationsByRootID = new Map();
323
- this._profilingScreenshotsByRootID = new Map();
324
- this._profilingSnapshotsByRootID = new Map();
325
- this._profilingCache.invalidate();
326
-
327
- this.emit('profilingData');
273
+ set profilingData(value: ProfilingDataFrontend | null): void {
274
+ this._profilerStore.profilingData = value;
275
+ }
276
+
277
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
278
+ get profilingOperationsByRootID(): Map<number, Array<Uint32Array>> {
279
+ return this._profilerStore.inProgressOperationsByRootID;
280
}
281
330
- get profilingOperations(): Map<number, Array<Uint32Array>> {
331
- return this._profilingOperationsByRootID;
282
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
283
+ get profilingScreenshotsByRootID(): Map<number, Map<number, string>> {
284
+ return this._profilerStore.inProgressScreenshotsByRootID;
285
}
286
334
- get profilingScreenshots(): Map<number, Map<number, string>> {
335
- return this._profilingScreenshotsByRootID;
287
+ // TODO (profarc) Update views to use ProfilerStore directly to access this value.
288
+ get profilingSnapshotsByRootID(): Map<number, Map<number, SnapshotNode>> {
289
+ return this._profilerStore.initialSnapshotsByRootID;
290
}
291
338
- get profilingSnapshots(): Map<number, Map<number, ProfilingSnapshotNode>> {
339
- return this._profilingSnapshotsByRootID;
292
+ get profilerStore(): ProfilerStore {
293
+ return this._profilerStore;
294
}
295
296
get revision(): number {
297
return this._revision;
298
}
299
300
+ get rootIDToRendererID(): Map<number, number> {
301
+ return this._rootIDToRendererID;
302
+ }
303
+
304
get roots(): $ReadOnlyArray<number> {
305
return this._roots;
306
}
@@ -363,17 +321,9 @@ export default class Store extends EventEmitter {
321
return this._supportsReloadAndProfile;
322
}
323
324
+ // TODO (profarc) Update views to use ProfilerStore directly to access this method.
325
clearProfilingData(): void {
367
- this._profilingData = null;
368
- this._profilingOperationsByRootID = new Map();
369
- this._profilingScreenshotsByRootID = new Map();
370
- this._profilingSnapshotsByRootID = new Map();
371
-
372
- // Invalidate suspense cache if profiling data is being (re-)recorded.
373
- // Note that we clear now because any existing data is "stale".
374
- this._profilingCache.invalidate();
375
-
376
- this.emit('isProfiling');
326
+ this._profilerStore.clear();
327
}
328
329
containsElement(id: number): boolean {
@@ -601,22 +551,14 @@ export default class Store extends EventEmitter {
551
return false;
552
}
553
554
+ // TODO (profarc) Update views to use ProfilerStore directly to access this method.
555
startProfiling(): void {
605
- this._bridge.send('startProfiling');
606
-
607
- // Don't actually update the local profiling boolean yet!
608
- // Wait for onProfilingStatus() to confirm the status has changed.
609
- // This ensures the frontend and backend are in sync wrt which commits were profiled.
610
- // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
556
+ this._profilerStore.startProfiling();
557
}
558
559
+ // TODO (profarc) Update views to use ProfilerStore directly to access this method.
560
stopProfiling(): void {
614
- this._bridge.send('stopProfiling');
615
-
616
- // Don't actually update the local profiling boolean yet!
617
- // Wait for onProfilingStatus() to confirm the status has changed.
618
- // This ensures the frontend and backend are in sync wrt which commits were profiled.
619
- // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
561
+ this._profilerStore.stopProfiling();
562
}
563
564
// TODO Maybe split this into two methods: expand() and collapse()
@@ -701,34 +643,6 @@ export default class Store extends EventEmitter {
643
}
644
}
645
704
- _captureScreenshot = throttle(
705
- memoize((rootID: number, commitIndex: number) => {
706
- this._bridge.send('captureScreenshot', { commitIndex, rootID });
707
- }),
708
- THROTTLE_CAPTURE_SCREENSHOT_DURATION
709
- );
710
-
711
- _takeProfilingSnapshotRecursive = (
712
- elementID: number,
713
- profilingSnapshots: Map<number, ProfilingSnapshotNode>
714
- ) => {
715
- const element = this.getElementByID(elementID);
716
- if (element !== null) {
717
- const snapshotNode: ProfilingSnapshotNode = {
718
- id: elementID,
719
- children: element.children.slice(0),
720
- displayName: element.displayName,
721
- key: element.key,
722
- type: element.type,
723
- };
724
- profilingSnapshots.set(elementID, snapshotNode);
725
-
726
- element.children.forEach(childID =>
727
- this._takeProfilingSnapshotRecursive(childID, profilingSnapshots)
728
- );
729
- }
730
- };
731
-
646
_adjustParentTreeWeight = (
647
parentElement: Element | null,
648
weightDelta: number
@@ -769,23 +683,8 @@ export default class Store extends EventEmitter {
683
684
let haveRootsChanged = false;
685
686
+ // The first two values are always rendererID and rootID
687
const rendererID = operations[0];
773
- const rootID = operations[1];
774
-
775
- if (this._isProfiling) {
776
- let profilingOperations = this._profilingOperationsByRootID.get(rootID);
777
- if (profilingOperations == null) {
778
- profilingOperations = [operations];
779
- this._profilingOperationsByRootID.set(rootID, profilingOperations);
780
- } else {
781
- profilingOperations.push(operations);
782
- }
783
-
784
- if (this._captureScreenshots) {
785
- const commitIndex = profilingOperations.length - 1;
786
- this._captureScreenshot(rootID, commitIndex);
787
- }
788
- }
688
689
const addedElementIDs: Array<number> = [];
690
// This is a mapping of removed ID -> parent ID:
@@ -857,10 +756,6 @@ export default class Store extends EventEmitter {
756
weight: 0,
757
});
758
860
- if (this._isProfiling) {
861
- this._profilingSnapshotsByRootID.set(id, new Map());
862
- }
863
-
759
haveRootsChanged = true;
760
} else {
761
parentID = ((operations[i]: any): number);
@@ -956,10 +851,6 @@ export default class Store extends EventEmitter {
851
this._rootIDToRendererID.delete(id);
852
this._rootIDToCapabilities.delete(id);
853
959
- this._profilingOperationsByRootID.delete(id);
960
- this._profilingScreenshotsByRootID.delete(id);
961
- this._profilingSnapshotsByRootID.delete(id);
962
-
854
haveRootsChanged = true;
855
} else {
856
if (__DEBUG__) {
@@ -1065,60 +956,12 @@ export default class Store extends EventEmitter {
956
this.emit('mutated', [addedElementIDs, removedElementIDs]);
957
};
958
1068
- onProfilingStatus = (isProfiling: boolean) => {
1069
- if (isProfiling) {
1070
- this._profilingData = null;
1071
- this._profilingOperationsByRootID = new Map();
1072
- this._profilingScreenshotsByRootID = new Map();
1073
- this._profilingSnapshotsByRootID = new Map();
1074
- this.roots.forEach(rootID => {
1075
- const profilingSnapshots = new Map();
1076
- this._profilingSnapshotsByRootID.set(rootID, profilingSnapshots);
1077
- this._takeProfilingSnapshotRecursive(rootID, profilingSnapshots);
1078
- });
1079
- }
1080
-
1081
- if (this._isProfiling !== isProfiling) {
1082
- this._isProfiling = isProfiling;
1083
-
1084
- // Invalidate suspense cache if profiling data is being (re-)recorded.
1085
- // Note that we clear again, in case any views read from the cache while profiling.
1086
- // (That would have resolved a now-stale value without any profiling data.)
1087
- this._profilingCache.invalidate();
1088
-
1089
- this.emit('isProfiling');
1090
- }
1091
- };
1092
-
1093
- onScreenshotCaptured = ({
1094
- commitIndex,
1095
- dataURL,
1096
- rootID,
1097
- }: {|
1098
- commitIndex: number,
1099
- dataURL: string,
1100
- rootID: number,
1101
- |}) => {
1102
- let profilingScreenshotsForRootByCommitIndex = this._profilingScreenshotsByRootID.get(
1103
- rootID
1104
- );
1105
- if (!profilingScreenshotsForRootByCommitIndex) {
1106
- profilingScreenshotsForRootByCommitIndex = new Map();
1107
- this._profilingScreenshotsByRootID.set(
1108
- rootID,
1109
- profilingScreenshotsForRootByCommitIndex
1110
- );
1111
- }
1112
- profilingScreenshotsForRootByCommitIndex.set(commitIndex, dataURL);
1113
- };
1114
-
959
onBridgeShutdown = () => {
960
if (__DEBUG__) {
961
debug('onBridgeShutdown', 'unsubscribing from Bridge');
962
}
963
964
this._bridge.removeListener('operations', this.onBridgeOperations);
1121
- this._bridge.removeListener('profilingStatus', this.onProfilingStatus);
965
this._bridge.removeListener('shutdown', this.onBridgeShutdown);
966
};
967
}
src/devtools/views/Profiler/CommitFlamegraph.js
+9
-34
@@ -13,7 +13,7 @@ import { StoreContext } from '../context';
13
import styles from './CommitFlamegraph.css';
14
15
import type { ChartData, ChartNode } from './FlamegraphChartBuilder';
16
-import type { CommitDetailsFrontend, CommitTreeFrontend } from './types';
16
+import type { CommitTree } from './types';
17
18
export type ItemData = {|
19
chartData: ChartData,
@@ -26,7 +26,7 @@ export type ItemData = {|
26
27
export default function CommitFlamegraphAutoSizer(_: {||}) {
28
const { profilingCache } = useContext(StoreContext);
29
- const { rendererID, rootID, selectedCommitIndex, selectFiber } = useContext(
29
+ const { rootID, selectedCommitIndex, selectFiber } = useContext(
30
ProfilerContext
31
);
32
@@ -38,39 +38,22 @@ export default function CommitFlamegraphAutoSizer(_: {||}) {
38
[selectFiber]
39
);
40
41
- const profilingSummary = profilingCache.ProfilingSummary.read({
42
- rendererID: ((rendererID: any): number),
43
- rootID: ((rootID: any): number),
44
- });
45
-
46
- let commitDetails: CommitDetailsFrontend | null = null;
47
- let commitTree: CommitTreeFrontend | null = null;
41
+ let commitTree: CommitTree | null = null;
42
let chartData: ChartData | null = null;
43
if (selectedCommitIndex !== null) {
50
- commitDetails = profilingCache.CommitDetails.read({
51
- commitIndex: selectedCommitIndex,
52
- rendererID: ((rendererID: any): number),
53
- rootID: ((rootID: any): number),
54
- });
55
-
44
commitTree = profilingCache.getCommitTree({
45
commitIndex: selectedCommitIndex,
58
- profilingSummary,
46
+ rootID: ((rootID: any): number),
47
});
48
49
chartData = profilingCache.getFlamegraphChartData({
62
- commitDetails,
50
commitIndex: selectedCommitIndex,
51
commitTree,
52
+ rootID: ((rootID: any): number),
53
});
54
}
55
68
- if (
69
- commitDetails != null &&
70
- commitTree != null &&
71
- chartData != null &&
72
- chartData.depth > 0
73
- ) {
56
+ if (commitTree != null && chartData != null && chartData.depth > 0) {
57
return (
58
<div className={styles.Container} onClick={deselectCurrentFiber}>
59
<AutoSizer>
@@ -79,8 +62,7 @@ export default function CommitFlamegraphAutoSizer(_: {||}) {
62
// by the time this render prop function is called, the values of the `let` variables have not changed.
63
<CommitFlamegraph
64
chartData={((chartData: any): ChartData)}
82
- commitDetails={((commitDetails: any): CommitDetailsFrontend)}
83
- commitTree={((commitTree: any): CommitTreeFrontend)}
65
+ commitTree={((commitTree: any): CommitTree)}
66
height={height}
67
width={width}
68
/>
@@ -95,19 +77,12 @@ export default function CommitFlamegraphAutoSizer(_: {||}) {
77
78
type Props = {|
79
chartData: ChartData,
98
- commitDetails: CommitDetailsFrontend,
99
- commitTree: CommitTreeFrontend,
80
+ commitTree: CommitTree,
81
height: number,
82
width: number,
83
|};
84
104
-function CommitFlamegraph({
105
- chartData,
106
- commitDetails,
107
- commitTree,
108
- height,
109
- width,
110
-}: Props) {
85
+function CommitFlamegraph({ chartData, commitTree, height, width }: Props) {
86
const { selectFiber, selectedFiberID } = useContext(ProfilerContext);
87
88
const selectedChartNodeIndex = useMemo<number>(() => {
src/devtools/views/Profiler/CommitRanked.js
+9
-34
@@ -13,7 +13,7 @@ import { StoreContext } from '../context';
13
import styles from './CommitRanked.css';
14
15
import type { ChartData } from './RankedChartBuilder';
16
-import type { CommitDetailsFrontend, CommitTreeFrontend } from './types';
16
+import type { CommitTree } from './types';
17
18
export type ItemData = {|
19
chartData: ChartData,
@@ -26,7 +26,7 @@ export type ItemData = {|
26
27
export default function CommitRankedAutoSizer(_: {||}) {
28
const { profilingCache } = useContext(StoreContext);
29
- const { rendererID, rootID, selectedCommitIndex, selectFiber } = useContext(
29
+ const { rootID, selectedCommitIndex, selectFiber } = useContext(
30
ProfilerContext
31
);
32
@@ -38,47 +38,29 @@ export default function CommitRankedAutoSizer(_: {||}) {
38
[selectFiber]
39
);
40
41
- const profilingSummary = profilingCache.ProfilingSummary.read({
42
- rendererID: ((rendererID: any): number),
43
- rootID: ((rootID: any): number),
44
- });
45
-
46
- let commitDetails: CommitDetailsFrontend | null = null;
47
- let commitTree: CommitTreeFrontend | null = null;
41
+ let commitTree: CommitTree | null = null;
42
let chartData: ChartData | null = null;
43
if (selectedCommitIndex !== null) {
50
- commitDetails = profilingCache.CommitDetails.read({
51
- commitIndex: selectedCommitIndex,
52
- rendererID: ((rendererID: any): number),
53
- rootID: ((rootID: any): number),
54
- });
55
-
44
commitTree = profilingCache.getCommitTree({
45
commitIndex: selectedCommitIndex,
58
- profilingSummary,
46
+ rootID: ((rootID: any): number),
47
});
48
49
chartData = profilingCache.getRankedChartData({
62
- commitDetails,
50
commitIndex: selectedCommitIndex,
51
commitTree,
52
+ rootID: ((rootID: any): number),
53
});
54
}
55
68
- if (
69
- commitDetails != null &&
70
- commitTree != null &&
71
- chartData != null &&
72
- chartData.nodes.length > 0
73
- ) {
56
+ if (commitTree != null && chartData != null && chartData.nodes.length > 0) {
57
return (
58
<div className={styles.Container} onClick={deselectCurrentFiber}>
59
<AutoSizer>
60
{({ height, width }) => (
61
<CommitRanked
62
chartData={((chartData: any): ChartData)}
80
- commitDetails={((commitDetails: any): CommitDetailsFrontend)}
81
- commitTree={((commitTree: any): CommitTreeFrontend)}
63
+ commitTree={((commitTree: any): CommitTree)}
64
height={height}
65
width={width}
66
/>
@@ -93,19 +75,12 @@ export default function CommitRankedAutoSizer(_: {||}) {
75
76
type Props = {|
77
chartData: ChartData,
96
- commitDetails: CommitDetailsFrontend,
97
- commitTree: CommitTreeFrontend,
78
+ commitTree: CommitTree,
79
height: number,
80
width: number,
81
|};
82
102
-function CommitRanked({
103
- chartData,
104
- commitDetails,
105
- commitTree,
106
- height,
107
- width,
108
-}: Props) {
83
+function CommitRanked({ chartData, commitTree, height, width }: Props) {
84
const { selectedFiberID, selectFiber } = useContext(ProfilerContext);
85
86
const selectedFiberIndex = useMemo(
src/devtools/views/Profiler/CommitTreeBuilder.js
+44
-66
@@ -9,14 +9,13 @@ import {
9
} from 'src/constants';
10
import { utfDecodeString } from 'src/utils';
11
import { ElementTypeRoot } from 'src/types';
12
-import Store from 'src/devtools/store';
12
+import ProfilerStore from 'src/devtools/ProfilerStore';
13
14
import type { ElementType } from 'src/types';
15
import type {
16
- CommitTreeFrontend,
17
- CommitTreeNodeFrontend,
18
- ProfilingSnapshotNode,
19
- ProfilingSummaryFrontend,
16
+ CommitTree,
17
+ CommitTreeNode,
18
+ ProfilingDataForRootFrontend,
19
} from 'src/devtools/views/Profiler/types';
20
21
const debug = (methodName, ...args) => {
@@ -30,36 +29,40 @@ const debug = (methodName, ...args) => {
29
}
30
};
31
33
-const rootToCommitTreeMap: Map<number, Array<CommitTreeFrontend>> = new Map();
32
+const rootToCommitTreeMap: Map<number, Array<CommitTree>> = new Map();
33
34
export function getCommitTree({
35
commitIndex,
37
- profilingSummary,
38
- store,
36
+ profilerStore,
37
+ rootID,
38
}: {|
39
commitIndex: number,
41
- profilingSummary: ProfilingSummaryFrontend,
42
- store: Store,
43
-|}): CommitTreeFrontend {
44
- const { rootID } = profilingSummary;
45
-
40
+ profilerStore: ProfilerStore,
41
+ rootID: number,
42
+|}): CommitTree {
43
if (!rootToCommitTreeMap.has(rootID)) {
44
rootToCommitTreeMap.set(rootID, []);
45
}
46
47
const commitTrees = ((rootToCommitTreeMap.get(
48
rootID
52
- ): any): Array<CommitTreeFrontend>);
49
+ ): any): Array<CommitTree>);
50
51
if (commitIndex < commitTrees.length) {
52
return commitTrees[commitIndex];
53
}
54
58
- const { profilingData } = store;
59
- const profilingOperations =
60
- profilingData != null
61
- ? profilingData.profilingOperations
62
- : store.profilingOperations;
55
+ const { profilingData } = profilerStore;
56
+ if (profilingData === null) {
57
+ throw Error(`No profiling data available`);
58
+ }
59
+
60
+ const dataForRoot = profilingData.dataForRoots.get(rootID);
61
+ if (dataForRoot == null) {
62
+ throw Error(`Could not find profiling data for root "${rootID}"`);
63
+ }
64
+
65
+ const { operations } = dataForRoot;
66
67
// Commits are generated sequentially and cached.
68
// If this is the very first commit, start with the cached snapshot and apply the first mutation.
@@ -67,32 +70,12 @@ export function getCommitTree({
70
if (commitIndex === 0) {
71
const nodes = new Map();
72
70
- const { profilingData } = store;
71
- const profilingSnapshots =
72
- profilingData != null
73
- ? profilingData.profilingSnapshots.get(rootID)
74
- : store.profilingSnapshots.get(rootID);
75
-
76
- if (profilingSnapshots == null) {
77
- throw Error(`Could not find profiling snapshot for root "${rootID}"`);
78
- }
79
-
73
// Construct the initial tree.
81
- recursivelyInitializeTree(
82
- rootID,
83
- 0,
84
- nodes,
85
- profilingSummary.initialTreeBaseDurations,
86
- profilingSnapshots
87
- );
74
+ recursivelyInitializeTree(rootID, 0, nodes, dataForRoot);
75
76
// Mutate the tree
90
- const commitOperations = profilingOperations.get(rootID);
91
- if (commitOperations != null && commitIndex < commitOperations.length) {
92
- const commitTree = updateTree(
93
- { nodes, rootID },
94
- commitOperations[commitIndex]
95
- );
77
+ if (operations != null && commitIndex < operations.length) {
78
+ const commitTree = updateTree({ nodes, rootID }, operations[commitIndex]);
79
80
if (__DEBUG__) {
81
__printTree(commitTree);
@@ -104,14 +87,14 @@ export function getCommitTree({
87
} else {
88
const previousCommitTree = getCommitTree({
89
commitIndex: commitIndex - 1,
107
- profilingSummary,
108
- store,
90
+ profilerStore,
91
+ rootID,
92
});
110
- const commitOperations = profilingOperations.get(rootID);
111
- if (commitOperations != null && commitIndex < commitOperations.length) {
93
+
94
+ if (operations != null && commitIndex < operations.length) {
95
const commitTree = updateTree(
96
previousCommitTree,
114
- commitOperations[commitIndex]
97
+ operations[commitIndex]
98
);
99
100
if (__DEBUG__) {
@@ -131,11 +114,10 @@ export function getCommitTree({
114
function recursivelyInitializeTree(
115
id: number,
116
parentID: number,
134
- nodes: Map<number, CommitTreeNodeFrontend>,
135
- initialTreeBaseDurations: Map<number, number>,
136
- profilingSnapshots: Map<number, ProfilingSnapshotNode>
117
+ nodes: Map<number, CommitTreeNode>,
118
+ dataForRoot: ProfilingDataForRootFrontend
119
): void {
138
- const node = profilingSnapshots.get(id);
120
+ const node = dataForRoot.snapshots.get(id);
121
if (node != null) {
122
nodes.set(id, {
123
id,
@@ -143,35 +125,31 @@ function recursivelyInitializeTree(
125
displayName: node.displayName,
126
key: node.key,
127
parentID,
146
- treeBaseDuration: ((initialTreeBaseDurations.get(id): any): number),
128
+ treeBaseDuration: ((dataForRoot.initialTreeBaseDurations.get(
129
+ id
130
+ ): any): number),
131
type: node.type,
132
});
133
134
node.children.forEach(childID =>
151
- recursivelyInitializeTree(
152
- childID,
153
- id,
154
- nodes,
155
- initialTreeBaseDurations,
156
- profilingSnapshots
157
- )
135
+ recursivelyInitializeTree(childID, id, nodes, dataForRoot)
136
);
137
}
138
}
139
140
function updateTree(
163
- commitTree: CommitTreeFrontend,
141
+ commitTree: CommitTree,
142
operations: Uint32Array
165
-): CommitTreeFrontend {
143
+): CommitTree {
144
// Clone the original tree so edits don't affect it.
145
const nodes = new Map(commitTree.nodes);
146
147
// Clone nodes before mutating them so edits don't affect them.
170
- const getClonedNode = (id: number): CommitTreeNodeFrontend => {
148
+ const getClonedNode = (id: number): CommitTreeNode => {
149
const clonedNode = ((Object.assign(
150
{},
151
nodes.get(id)
174
- ): any): CommitTreeNodeFrontend);
152
+ ): any): CommitTreeNode);
153
nodes.set(id, clonedNode);
154
return clonedNode;
155
};
@@ -219,7 +197,7 @@ function updateTree(
197
debug('Add', `new root fiber ${id}`);
198
}
199
222
- const node: CommitTreeNodeFrontend = {
200
+ const node: CommitTreeNode = {
201
children: [],
202
displayName: null,
203
id,
@@ -254,7 +232,7 @@ function updateTree(
232
const parentNode = getClonedNode(parentID);
233
parentNode.children = parentNode.children.concat(id);
234
257
- const node: CommitTreeNodeFrontend = {
235
+ const node: CommitTreeNode = {
236
children: [],
237
displayName,
238
id,
@@ -355,7 +333,7 @@ export function invalidateCommitTrees(): void {
333
}
334
335
// DEBUG
358
-const __printTree = (commitTree: CommitTreeFrontend) => {
336
+const __printTree = (commitTree: CommitTree) => {
337
if (__DEBUG__) {
338
const { nodes, rootID } = commitTree;
339
console.group('__printTree()');
src/devtools/views/Profiler/FlamegraphChartBuilder.js
+14
-9
@@ -2,8 +2,9 @@
2
3
import { ElementTypeForwardRef, ElementTypeMemo } from 'src/types';
4
import { formatDuration } from './utils';
5
+import ProfilerStore from 'src/devtools/ProfilerStore';
6
6
-import type { CommitDetailsFrontend, CommitTreeFrontend } from './types';
7
+import type { CommitTree } from './types';
8
9
export type ChartNode = {|
10
actualDuration: number,
@@ -28,15 +29,19 @@ export type ChartData = {|
29
const cachedChartData: Map<string, ChartData> = new Map();
30
31
export function getChartData({
31
- commitDetails,
32
commitIndex,
33
commitTree,
34
+ profilerStore,
35
+ rootID,
36
}: {|
35
- commitDetails: CommitDetailsFrontend,
37
commitIndex: number,
37
- commitTree: CommitTreeFrontend,
38
+ commitTree: CommitTree,
39
+ profilerStore: ProfilerStore,
40
+ rootID: number,
41
|}): ChartData {
39
- const { actualDurations, rootID, selfDurations } = commitDetails;
42
+ const commitDatum = profilerStore.getCommitData(rootID, commitIndex);
43
+
44
+ const { fiberActualDurations, fiberSelfDurations } = commitDatum;
45
const { nodes } = commitTree;
46
47
const key = `${rootID}-${commitIndex}`;
@@ -62,9 +67,9 @@ export function getChartData({
67
68
const { children, displayName, key, treeBaseDuration, type } = node;
69
65
- const actualDuration = actualDurations.get(id) || 0;
66
- const selfDuration = selfDurations.get(id) || 0;
67
- const didRender = actualDurations.has(id);
70
+ const actualDuration = fiberActualDurations.get(id) || 0;
71
+ const selfDuration = fiberSelfDurations.get(id) || 0;
72
+ const didRender = fiberActualDurations.has(id);
73
74
const name = displayName || 'Anonymous';
75
const maybeKey = key !== null ? ` key="${key}"` : '';
@@ -131,7 +136,7 @@ export function getChartData({
136
walkTree(id, baseDuration, 1);
137
}
138
134
- actualDurations.forEach((duration, id) => {
139
+ fiberActualDurations.forEach((duration, id) => {
140
const node = nodes.get(id);
141
if (node != null) {
142
let currentID = node.parentID;
src/devtools/views/Profiler/InteractionListItem.js
+15
-11
@@ -17,9 +17,8 @@ type Props = {
17
function InteractionListItem({ data: itemData, index, style }: Props) {
18
const {
19
chartData,
20
- interactions,
20
+ dataForRoot,
21
labelWidth,
22
- profilingSummary,
22
scaleX,
23
selectedInteractionID,
24
selectCommitIndex,
@@ -27,20 +26,22 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
26
selectTab,
27
} = itemData;
28
30
- const { maxCommitDuration } = chartData;
31
- const { commitDurations, commitTimes } = profilingSummary;
29
+ const { commitData, interactionCommits } = dataForRoot;
30
+ const { interactions, lastInteractionTime, maxCommitDuration } = chartData;
31
32
const interaction = interactions[index];
33
+ if (interaction == null) {
34
+ throw Error(`Could not find interaction #${index}`);
35
+ }
36
37
const handleClick = useCallback(() => {
38
selectInteraction(interaction.id);
39
}, [interaction, selectInteraction]);
40
41
+ const commits = interactionCommits.get(interaction.id) || [];
42
+
43
const startTime = interaction.timestamp;
40
- const stopTime =
41
- interaction.commits.length > 0
42
- ? commitTimes[interaction.commits[interaction.commits.length - 1]]
43
- : interaction.timestamp;
44
+ const stopTime = lastInteractionTime;
45
46
const viewCommit = (commitIndex: number) => {
47
selectTab('flame-chart');
@@ -71,7 +72,7 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
72
width: scaleX(stopTime - startTime, 0),
73
}}
74
/>
74
- {interaction.commits.map(commitIndex => (
75
+ {commits.map(commitIndex => (
76
<div
77
className={styles.CommitBox}
78
key={commitIndex}
@@ -80,10 +81,13 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
81
backgroundColor: getGradientColor(
82
Math.min(
83
1,
83
- Math.max(0, commitDurations[commitIndex] / maxCommitDuration)
84
+ Math.max(
85
+ 0,
86
+ commitData[commitIndex].duration / maxCommitDuration
87
+ )
88
) || 0
89
),
86
- left: labelWidth + scaleX(commitTimes[commitIndex], 0),
90
+ left: labelWidth + scaleX(commitData[commitIndex].timestamp, 0),
91
}}
92
/>
93
))}
src/devtools/views/Profiler/Interactions.js
+8
-22
@@ -11,18 +11,14 @@ import { scale } from './utils';
11
12
import styles from './Interactions.css';
13
14
+import type { ProfilingDataForRootFrontend } from './types';
15
import type { ChartData } from './InteractionsChartBuilder';
16
import type { TabID } from './ProfilerContext';
16
-import type {
17
- InteractionWithCommitsFrontend,
18
- ProfilingSummaryFrontend,
19
-} from './types';
17
18
export type ItemData = {|
19
chartData: ChartData,
23
- interactions: Array<InteractionWithCommitsFrontend>,
20
+ dataForRoot: ProfilingDataForRootFrontend,
21
labelWidth: number,
25
- profilingSummary: ProfilingSummaryFrontend,
22
scaleX: (value: number, fallbackValue: number) => number,
23
selectedInteractionID: number | null,
24
selectCommitIndex: (id: number | null) => void,
@@ -42,29 +38,21 @@ export default function InteractionsAutoSizer(_: {||}) {
38
39
function Interactions({ height, width }: {| height: number, width: number |}) {
40
const {
45
- rendererID,
41
rootID,
42
selectedInteractionID,
43
selectInteraction,
44
selectCommitIndex,
45
selectTab,
46
} = useContext(ProfilerContext);
52
- const { profilingCache } = useContext(StoreContext);
47
+ const { profilerStore } = useContext(StoreContext);
48
54
- const { interactions } = profilingCache.Interactions.read({
55
- rendererID: ((rendererID: any): number),
56
- rootID: ((rootID: any): number),
57
- });
49
+ const dataForRoot = profilerStore.getDataForRoot(((rootID: any): number));
50
59
- const profilingSummary = profilingCache.ProfilingSummary.read({
60
- rendererID: ((rendererID: any): number),
51
+ const chartData = profilerStore.cache.getInteractionsChartData({
52
rootID: ((rootID: any): number),
53
});
54
64
- const chartData = profilingCache.getInteractionsChartData({
65
- interactions,
66
- profilingSummary,
67
- });
55
+ const { interactions } = chartData;
56
57
const handleKeyDown = useCallback(
58
event => {
@@ -110,9 +98,8 @@ function Interactions({ height, width }: {| height: number, width: number |}) {
98
99
return {
100
chartData,
113
- interactions,
101
+ dataForRoot,
102
labelWidth,
115
- profilingSummary,
103
scaleX: scale(0, chartData.lastInteractionTime, 0, timelineWidth),
104
selectedInteractionID,
105
selectCommitIndex,
@@ -121,8 +108,7 @@ function Interactions({ height, width }: {| height: number, width: number |}) {
108
};
109
}, [
110
chartData,
124
- interactions,
125
- profilingSummary,
111
+ dataForRoot,
112
selectedInteractionID,
113
selectCommitIndex,
114
selectInteraction,
src/devtools/views/Profiler/InteractionsChartBuilder.js
+22
-15
@@ -1,11 +1,11 @@
1
// @flow
2
3
-import type {
4
- InteractionWithCommitsFrontend,
5
- ProfilingSummaryFrontend,
6
-} from './types';
3
+import ProfilerStore from 'src/devtools/ProfilerStore';
4
+
5
+import type { Interaction } from './types';
6
7
export type ChartData = {|
8
+ interactions: Array<Interaction>,
9
lastInteractionTime: number,
10
maxCommitDuration: number,
11
|};
@@ -13,30 +13,37 @@ export type ChartData = {|
13
const cachedChartData: Map<number, ChartData> = new Map();
14
15
export function getChartData({
16
- interactions,
17
- profilingSummary,
16
+ profilerStore,
17
+ rootID,
18
}: {|
19
- interactions: Array<InteractionWithCommitsFrontend>,
20
- profilingSummary: ProfilingSummaryFrontend,
19
+ profilerStore: ProfilerStore,
20
+ rootID: number,
21
|}): ChartData {
22
- const { rootID } = profilingSummary;
23
-
22
if (cachedChartData.has(rootID)) {
23
return ((cachedChartData.get(rootID): any): ChartData);
24
}
25
28
- const { commitDurations, commitTimes } = profilingSummary;
26
+ const dataForRoot = profilerStore.getDataForRoot(rootID);
27
+ if (dataForRoot == null) {
28
+ throw Error(`Could not find profiling data for root "${rootID}"`);
29
+ }
30
+
31
+ const { commitData, interactions } = dataForRoot;
32
33
const lastInteractionTime =
31
- commitTimes.length > 0 ? commitTimes[commitTimes.length - 1] : 0;
34
+ commitData.length > 0 ? commitData[commitData.length - 1].timestamp : 0;
35
36
let maxCommitDuration = 0;
37
35
- commitDurations.forEach(commitDuration => {
36
- maxCommitDuration = Math.max(maxCommitDuration, commitDuration);
38
+ commitData.forEach(commitDatum => {
39
+ maxCommitDuration = Math.max(maxCommitDuration, commitDatum.duration);
40
});
41
39
- const chartData = { lastInteractionTime, maxCommitDuration };
42
+ const chartData = {
43
+ interactions: Array.from(interactions.values()),
44
+ lastInteractionTime,
45
+ maxCommitDuration,
46
+ };
47
48
cachedChartData.set(rootID, chartData);
49
src/devtools/views/Profiler/ProfilerContext.js
+4
-4
@@ -16,7 +16,7 @@ import {
16
import { StoreContext } from '../context';
17
import Store from '../../store';
18
19
-import type { ImportedProfilingData } from './types';
19
+import type { ProfilingDataFrontend } from './types';
20
21
export type TabID = 'flame-chart' | 'ranked-chart' | 'interactions';
22
@@ -72,7 +72,7 @@ ProfilerContext.displayName = 'ProfilerContext';
72
73
type StoreProfilingState = {|
74
hasProfilingData: boolean,
75
- profilingData: ImportedProfilingData | null,
75
+ profilingData: ProfilingDataFrontend | null,
76
isProfiling: boolean,
77
|};
78
@@ -117,11 +117,11 @@ function ProfilerContextController({ children }: Props) {
117
rendererID = store.getRendererIDForElement(selectedElementID);
118
rootID = store.getRootIDForElement(selectedElementID);
119
rootHasProfilingData =
120
- rootID === null ? false : store.profilingOperations.has(rootID);
120
+ rootID === null ? false : store.profilingOperationsByRootID.has(rootID);
121
} else if (store.roots.length > 0) {
122
// If no root is selected, assume the first root; many React apps are single root anyway.
123
rootID = store.roots[0];
124
- rootHasProfilingData = store.profilingOperations.has(rootID);
124
+ rootHasProfilingData = store.profilingOperationsByRootID.has(rootID);
125
rendererID = store.getRendererIDForElement(rootID);
126
}
127
src/devtools/views/Profiler/ProfilingImportExportButtons.js
+23
-23
@@ -5,20 +5,20 @@ import { ProfilerContext } from './ProfilerContext';
5
import { ModalDialogContext } from '../ModalDialog';
6
import Button from '../Button';
7
import ButtonIcon from '../ButtonIcon';
8
-import { BridgeContext, StoreContext } from '../context';
8
+import { StoreContext } from '../context';
9
import {
10
- prepareExportedProfilingSummary,
11
- prepareImportedProfilingData,
10
+ prepareProfilingDataExport,
11
+ prepareProfilingDataFrontendFromExport,
12
} from './utils';
13
14
import styles from './ProfilingImportExportButtons.css';
15
16
+import type { ProfilingDataExport } from './types';
17
+
18
export default function ProfilingImportExportButtons() {
17
- const bridge = useContext(BridgeContext);
18
- const { isProfiling, rendererID, rootHasProfilingData, rootID } = useContext(
19
- ProfilerContext
20
- );
19
+ const { isProfiling, rendererID, rootID } = useContext(ProfilerContext);
20
const store = useContext(StoreContext);
21
+ const { profilerStore } = store;
22
23
const inputRef = useRef<HTMLInputElement | null>(null);
24
@@ -29,20 +29,15 @@ export default function ProfilingImportExportButtons() {
29
return;
30
}
31
32
- const exportedProfilingSummary = prepareExportedProfilingSummary(
33
- store.profilingOperations,
34
- store.profilingSnapshots,
35
- rootID,
36
- rendererID
37
- );
38
- bridge.send('exportProfilingSummary', exportedProfilingSummary);
39
- }, [
40
- bridge,
41
- rendererID,
42
- rootID,
43
- store.profilingOperations,
44
- store.profilingSnapshots,
45
- ]);
32
+ if (profilerStore.profilingData !== null) {
33
+ const profilingDataExport = prepareProfilingDataExport(
34
+ profilerStore.profilingData
35
+ );
36
+
37
+ // TODO (profarc) Generate anchor "download" tag and click it
38
+ console.log('profilingDataExport:', profilingDataExport);
39
+ }
40
+ }, [rendererID, rootID, profilerStore.profilingData]);
41
42
const uploadData = useCallback(() => {
43
if (inputRef.current !== null) {
@@ -57,7 +52,12 @@ export default function ProfilingImportExportButtons() {
52
fileReader.addEventListener('load', () => {
53
try {
54
const raw = ((fileReader.result: any): string);
60
- store.profilingData = prepareImportedProfilingData(raw);
55
+ const profilingDataExport = ((JSON.parse(
56
+ raw
57
+ ): any): ProfilingDataExport);
58
+ store.profilingData = prepareProfilingDataFrontendFromExport(
59
+ profilingDataExport
60
+ );
61
} catch (error) {
62
modalDialogDispatch({
63
type: 'SHOW',
@@ -97,7 +97,7 @@ export default function ProfilingImportExportButtons() {
97
</Button>
98
{store.supportsFileDownloads && (
99
<Button
100
- disabled={isProfiling || !rootHasProfilingData}
100
+ disabled={isProfiling || !profilerStore.hasProfilingData}
101
onClick={downloadData}
102
title="Save profile..."
103
>
src/devtools/views/Profiler/RankedChartBuilder.js
+12
-7
@@ -2,8 +2,9 @@
2
3
import { ElementTypeForwardRef, ElementTypeMemo } from 'src/types';
4
import { formatDuration } from './utils';
5
+import ProfilerStore from 'src/devtools/ProfilerStore';
6
6
-import type { CommitDetailsFrontend, CommitTreeFrontend } from './types';
7
+import type { CommitTree } from './types';
8
9
export type ChartNode = {|
10
id: number,
@@ -20,15 +21,19 @@ export type ChartData = {|
21
const cachedChartData: Map<string, ChartData> = new Map();
22
23
export function getChartData({
23
- commitDetails,
24
commitIndex,
25
commitTree,
26
+ profilerStore,
27
+ rootID,
28
}: {|
27
- commitDetails: CommitDetailsFrontend,
29
commitIndex: number,
29
- commitTree: CommitTreeFrontend,
30
+ commitTree: CommitTree,
31
+ profilerStore: ProfilerStore,
32
+ rootID: number,
33
|}): ChartData {
31
- const { actualDurations, rootID, selfDurations } = commitDetails;
34
+ const commitDatum = profilerStore.getCommitData(rootID, commitIndex);
35
+
36
+ const { fiberActualDurations, fiberSelfDurations } = commitDatum;
37
const { nodes } = commitTree;
38
39
const key = `${rootID}-${commitIndex}`;
@@ -39,7 +44,7 @@ export function getChartData({
44
let maxSelfDuration = 0;
45
46
const chartNodes: Array<ChartNode> = [];
42
- actualDurations.forEach((actualDuration, id) => {
47
+ fiberActualDurations.forEach((actualDuration, id) => {
48
const node = nodes.get(id);
49
50
if (node == null) {
@@ -52,7 +57,7 @@ export function getChartData({
57
if (parentID === 0) {
58
return;
59
}
55
- const selfDuration = selfDurations.get(id) || 0;
60
+ const selfDuration = fiberSelfDurations.get(id) || 0;
61
maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
62
63
const name = displayName || 'Anonymous';
src/devtools/views/Profiler/SidebarCommitInfo.js
+30
-47
@@ -12,24 +12,12 @@ export type Props = {||};
12
export default function SidebarCommitInfo(_: Props) {
13
const {
14
selectedCommitIndex,
15
- rendererID,
15
rootID,
16
selectInteraction,
17
selectTab,
18
} = useContext(ProfilerContext);
19
21
- const {
22
- captureScreenshots,
23
- profilingCache,
24
- profilingScreenshots,
25
- } = useContext(StoreContext);
26
-
27
- const screenshotsByCommitIndex =
28
- rootID !== null ? profilingScreenshots.get(rootID) : null;
29
- const screenshot =
30
- screenshotsByCommitIndex != null && selectedCommitIndex !== null
31
- ? screenshotsByCommitIndex.get(selectedCommitIndex)
32
- : null;
20
+ const { captureScreenshots, profilerStore } = useContext(StoreContext);
21
22
const [
23
isScreenshotModalVisible,
@@ -45,26 +33,22 @@ export default function SidebarCommitInfo(_: Props) {
33
[]
34
);
35
48
- if (selectedCommitIndex === null) {
36
+ if (rootID === null || selectedCommitIndex === null) {
37
return <div className={styles.NothingSelected}>Nothing selected</div>;
38
}
39
52
- const { commitDurations, commitTimes } = profilingCache.ProfilingSummary.read(
53
- {
54
- rendererID: ((rendererID: any): number),
55
- rootID: ((rootID: any): number),
56
- }
57
- );
58
-
59
- const { interactions, priorityLevel } = profilingCache.CommitDetails.read({
60
- commitIndex: selectedCommitIndex,
61
- rendererID: ((rendererID: any): number),
62
- rootID: ((rootID: any): number),
63
- });
40
+ const { interactions } = profilerStore.getDataForRoot(rootID);
41
+ const {
42
+ duration,
43
+ interactionIDs,
44
+ priorityLevel,
45
+ screenshot,
46
+ timestamp,
47
+ } = profilerStore.getCommitData(rootID, selectedCommitIndex);
48
65
- const viewInteraction = interaction => {
49
+ const viewInteraction = interactionID => {
50
selectTab('interactions');
67
- selectInteraction(interaction.id);
51
+ selectInteraction(interactionID);
52
};
53
54
return (
@@ -80,34 +64,33 @@ export default function SidebarCommitInfo(_: Props) {
64
)}
65
<li className={styles.ListItem}>
66
<label className={styles.Label}>Committed at</label>:{' '}
83
- <span className={styles.Value}>
84
- {formatTime(commitTimes[((selectedCommitIndex: any): number)])}s
85
- </span>
67
+ <span className={styles.Value}>{formatTime(timestamp)}s</span>
68
</li>
69
<li className={styles.ListItem}>
70
<label className={styles.Label}>Render duration</label>:{' '}
89
- <span className={styles.Value}>
90
- {formatDuration(
91
- commitDurations[((selectedCommitIndex: any): number)]
92
- )}
93
- ms
94
- </span>
71
+ <span className={styles.Value}>{formatDuration(duration)}ms</span>
72
</li>
73
<li className={styles.Interactions}>
74
<label className={styles.Label}>Interactions</label>:
75
<div className={styles.InteractionList}>
99
- {interactions.length === 0 ? (
76
+ {interactionIDs.length === 0 ? (
77
<div className={styles.NoInteractions}>None</div>
78
) : null}
102
- {interactions.map((interaction, index) => (
103
- <button
104
- key={index}
105
- className={styles.Interaction}
106
- onClick={() => viewInteraction(interaction)}
107
- >
108
- {interaction.name}
109
- </button>
110
- ))}
79
+ {interactionIDs.map(interactionID => {
80
+ const interaction = interactions.get(interactionID);
81
+ if (interaction == null) {
82
+ throw Error(`Invalid interaction "${interactionID}"`);
83
+ }
84
+ return (
85
+ <button
86
+ key={interactionID}
87
+ className={styles.Interaction}
88
+ onClick={() => viewInteraction(interactionID)}
89
+ >
90
+ {interaction.name}
91
+ </button>
92
+ );
93
+ })}
94
</div>
95
</li>
96
{captureScreenshots && (
src/devtools/views/Profiler/SidebarInteractions.js
+39
-46
@@ -13,48 +13,69 @@ export type Props = {||};
13
export default function SidebarInteractions(_: Props) {
14
const {
15
selectedInteractionID,
16
- rendererID,
16
rootID,
17
selectCommitIndex,
18
selectTab,
19
} = useContext(ProfilerContext);
20
22
- const { profilingCache } = useContext(StoreContext);
21
+ const { profilingCache, profilerStore } = useContext(StoreContext);
22
23
if (selectedInteractionID === null) {
24
return <div className={styles.NothingSelected}>Nothing selected</div>;
25
}
26
28
- const { interactions } = profilingCache.Interactions.read({
29
- rendererID: ((rendererID: any): number),
30
- rootID: ((rootID: any): number),
31
- });
32
- const interaction = interactions.find(
33
- interaction => interaction.id === selectedInteractionID
27
+ const { interactionCommits, interactions } = profilerStore.getDataForRoot(
28
+ ((rootID: any): number)
29
);
30
+ const interaction = interactions.get(selectedInteractionID);
31
if (interaction == null) {
32
throw Error(
33
`Could not find interaction by selected interaction id "${selectedInteractionID}"`
34
);
35
}
36
41
- const profilingSummary = profilingCache.ProfilingSummary.read({
42
- rendererID: ((rendererID: any): number),
43
- rootID: ((rootID: any): number),
44
- });
45
-
37
const { maxCommitDuration } = profilingCache.getInteractionsChartData({
47
- interactions,
48
- profilingSummary,
38
+ rootID: ((rootID: any): number),
39
});
40
51
- const { commitDurations, commitTimes } = profilingSummary;
52
-
41
const viewCommit = (commitIndex: number) => {
42
selectTab('flame-chart');
43
selectCommitIndex(commitIndex);
44
};
45
46
+ const listItems: Array<React$Node> = [];
47
+ const commitIndices = interactionCommits.get(selectedInteractionID);
48
+ if (commitIndices != null) {
49
+ commitIndices.forEach(commitIndex => {
50
+ const { duration, timestamp } = profilerStore.getCommitData(
51
+ ((rootID: any): number),
52
+ commitIndex
53
+ );
54
+
55
+ listItems.push(
56
+ <li
57
+ key={commitIndex}
58
+ className={styles.ListItem}
59
+ onClick={() => viewCommit(commitIndex)}
60
+ >
61
+ <div
62
+ className={styles.CommitBox}
63
+ style={{
64
+ backgroundColor: getGradientColor(
65
+ Math.min(1, Math.max(0, duration / maxCommitDuration)) || 0
66
+ ),
67
+ }}
68
+ />
69
+ <div>
70
+ timestamp: {formatTime(timestamp)}s
71
+ <br />
72
+ duration: {formatDuration(duration)}ms
73
+ </div>
74
+ </li>
75
+ );
76
+ });
77
+ }
78
+
79
return (
80
<Fragment>
81
<div className={styles.Toolbar}>
@@ -62,35 +83,7 @@ export default function SidebarInteractions(_: Props) {
83
</div>
84
<div className={styles.Content}>
85
<div className={styles.Commits}>Commits:</div>
65
- <ul className={styles.List}>
66
- {interaction.commits.map(commitIndex => (
67
- <li
68
- key={commitIndex}
69
- className={styles.ListItem}
70
- onClick={() => viewCommit(commitIndex)}
71
- >
72
- <div
73
- className={styles.CommitBox}
74
- style={{
75
- backgroundColor: getGradientColor(
76
- Math.min(
77
- 1,
78
- Math.max(
79
- 0,
80
- commitDurations[commitIndex] / maxCommitDuration
81
- )
82
- ) || 0
83
- ),
84
- }}
85
- />
86
- <div>
87
- timestamp: {formatTime(commitTimes[commitIndex])}s
88
- <br />
89
- duration: {formatDuration(commitDurations[commitIndex])}ms
90
- </div>
91
- </li>
92
- ))}
93
- </ul>
86
+ <ul className={styles.List}>{listItems}</ul>
87
</div>
88
</Fragment>
89
);
src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
+10
-14
@@ -12,9 +12,8 @@ import styles from './SidebarSelectedFiberInfo.css';
12
export type Props = {||};
13
14
export default function SidebarSelectedFiberInfo(_: Props) {
15
- const { profilingCache } = useContext(StoreContext);
15
+ const { profilingCache, profilerStore } = useContext(StoreContext);
16
const {
17
- rendererID,
17
rootID,
18
selectCommitIndex,
19
selectedCommitIndex,
@@ -23,22 +22,19 @@ export default function SidebarSelectedFiberInfo(_: Props) {
22
selectFiber,
23
} = useContext(ProfilerContext);
24
26
- const { commitTimes } = profilingCache.ProfilingSummary.read({
27
- rendererID: ((rendererID: any): number),
28
- rootID: ((rootID: any): number),
29
- });
30
-
31
- const { commitDurations } = profilingCache.FiberCommits.read({
25
+ const commitIndices = profilingCache.getFiberCommits({
26
fiberID: ((selectedFiberID: any): number),
33
- rendererID: ((rendererID: any): number),
27
rootID: ((rootID: any): number),
28
});
29
30
const listItems = [];
38
- for (let i = 0; i < commitDurations.length; i += 2) {
39
- const commitIndex = commitDurations[i];
40
- const duration = commitDurations[i + 1];
41
- const time = commitTimes[commitIndex];
31
+ for (let i = 0; i < commitIndices.length; i += 2) {
32
+ const commitIndex = commitIndices[i];
33
+
34
+ const { duration, timestamp } = profilerStore.getCommitData(
35
+ ((rootID: any): number),
36
+ commitIndex
37
+ );
38
39
listItems.push(
40
<button
@@ -50,7 +46,7 @@ export default function SidebarSelectedFiberInfo(_: Props) {
46
}
47
onClick={() => selectCommitIndex(commitIndex)}
48
>
53
- {formatTime(time)}s for {formatDuration(duration)}ms
49
+ {formatTime(timestamp)}s for {formatDuration(duration)}ms
50
</button>
51
);
52
}
src/devtools/views/Profiler/SnapshotSelector.js
+16
-12
@@ -16,29 +16,33 @@ export default function SnapshotSelector(_: Props) {
16
const {
17
isCommitFilterEnabled,
18
minCommitDuration,
19
- rendererID,
19
rootID,
20
selectedCommitIndex,
21
selectCommitIndex,
22
} = useContext(ProfilerContext);
23
25
- const { profilingCache } = useContext(StoreContext);
26
- const { commitDurations, commitTimes } = profilingCache.ProfilingSummary.read(
27
- {
28
- rendererID: ((rendererID: any): number),
29
- rootID: ((rootID: any): number),
30
- }
31
- );
24
+ const { profilerStore } = useContext(StoreContext);
25
+ const { commitData } = profilerStore.getDataForRoot(((rootID: any): number));
26
+
27
+ const commitDurations: Array<number> = [];
28
+ const commitTimes: Array<number> = [];
29
+ commitData.forEach(commitDatum => {
30
+ commitDurations.push(commitDatum.duration);
31
+ commitTimes.push(commitDatum.timestamp);
32
+ });
33
34
const filteredCommitIndices = useMemo(
35
() =>
35
- commitDurations.reduce((reduced, commitDuration, index) => {
36
- if (!isCommitFilterEnabled || commitDuration >= minCommitDuration) {
36
+ commitData.reduce((reduced, commitDatum, index) => {
37
+ if (
38
+ !isCommitFilterEnabled ||
39
+ commitDatum.duration >= minCommitDuration
40
+ ) {
41
reduced.push(index);
42
}
43
return reduced;
44
}, []),
41
- [commitDurations, isCommitFilterEnabled, minCommitDuration]
45
+ [commitData, isCommitFilterEnabled, minCommitDuration]
46
);
47
48
const numFilteredCommits = filteredCommitIndices.length;
@@ -112,7 +116,7 @@ export default function SnapshotSelector(_: Props) {
116
[viewNextCommit, viewPrevCommit]
117
);
118
115
- if (commitDurations.length === 0) {
119
+ if (commitData.length === 0) {
120
return null;
121
}
122
src/devtools/views/Profiler/types.js
+82
-68
@@ -1,13 +1,8 @@
1
// @flow
2
3
import type { ElementType } from 'src/types';
4
-import type {
5
- CommitDetailsBackend,
6
- InteractionsBackend,
7
- ProfilingSummaryBackend,
8
-} from 'src/backend/types';
4
10
-export type CommitTreeNodeFrontend = {|
5
+export type CommitTreeNode = {|
6
id: number,
7
children: Array<number>,
8
displayName: string | null,
@@ -17,94 +12,113 @@ export type CommitTreeNodeFrontend = {|
12
type: ElementType,
13
|};
14
20
-export type CommitTreeFrontend = {|
21
- nodes: Map<number, CommitTreeNodeFrontend>,
15
+export type CommitTree = {|
16
+ nodes: Map<number, CommitTreeNode>,
17
rootID: number,
18
|};
19
25
-export type InteractionFrontend = {|
20
+export type Interaction = {|
21
id: number,
22
name: string,
23
timestamp: number,
24
|};
25
31
-export type InteractionWithCommitsFrontend = {|
32
- ...InteractionFrontend,
33
- commits: Array<number>,
26
+export type SnapshotNode = {|
27
+ id: number,
28
+ children: Array<number>,
29
+ displayName: string | null,
30
+ key: number | string | null,
31
+ type: ElementType,
32
|};
33
36
-export type InteractionsFrontend = {|
37
- interactions: Array<InteractionWithCommitsFrontend>,
38
- rootID: number,
39
-|};
34
+export type CommitDataFrontend = {|
35
+ // How long was this commit?
36
+ duration: number,
37
+
38
+ // Map of Fiber (ID) to actual duration for this commit;
39
+ // Fibers that did not render will not have entries in this Map.
40
+ fiberActualDurations: Map<number, number>,
41
41
-export type CommitDetailsFrontend = {|
42
- actualDurations: Map<number, number>,
43
- commitIndex: number,
44
- interactions: Array<InteractionFrontend>,
42
+ // Map of Fiber (ID) to "self duration" for this commit;
43
+ // Fibers that did not render will not have entries in this Map.
44
+ fiberSelfDurations: Map<number, number>,
45
+
46
+ // Which interactions (IDs) were associated with this commit.
47
+ interactionIDs: Array<number>,
48
+
49
+ // Priority level of the commit (if React provided this info)
50
priorityLevel: string | null,
46
- rootID: number,
47
- selfDurations: Map<number, number>,
48
-|};
51
50
-export type FiberCommitsFrontend = {|
51
- commitDurations: Array<number>,
52
- fiberID: number,
53
- rootID: number,
54
-|};
52
+ // Screenshot data for this commit (if available).
53
+ screenshot: string | null,
54
56
-export type ProfilingSummaryFrontend = {|
57
- rootID: number,
55
+ // When did this commit occur (relative to the start of profiling)
56
+ timestamp: number,
57
+|};
58
59
- // Commit durations
60
- commitDurations: Array<number>,
59
+export type ProfilingDataForRootFrontend = {|
60
+ // Timing, duration, and other metadata about each commit.
61
+ commitData: Array<CommitDataFrontend>,
62
62
- // Commit times (relative to when profiling started)
63
- commitTimes: Array<number>,
63
+ // Display name of the nearest descendant component (ideally a function or class component).
64
+ // This value is used by the root selector UI.
65
+ displayName: string,
66
65
- // Map of fiber id to (initial) tree base duration
67
+ // Map of fiber id to (initial) tree base duration when Profiling session was started.
68
+ // This info can be used along with commitOperations to reconstruct the tree for any commit.
69
initialTreeBaseDurations: Map<number, number>,
70
68
- interactionCount: number,
71
+ // All interactions recorded (for this root) during the current session.
72
+ interactionCommits: Map<number, Array<number>>,
73
+
74
+ // All interactions recorded (for this root) during the current session.
75
+ interactions: Map<number, Interaction>,
76
+
77
+ // List of tree mutation that occur during profiling.
78
+ // These mutations can be used along with initial snapshots to reconstruct the tree for any commit.
79
+ operations: Array<Uint32Array>,
80
+
81
+ // Identifies the root this profiler data corresponds to.
82
+ rootID: number,
83
+
84
+ // Map of fiber id to node when the Profiling session was started.
85
+ // This info can be used along with commitOperations to reconstruct the tree for any commit.
86
+ snapshots: Map<number, SnapshotNode>,
87
|};
88
71
-export type ProfilingSnapshotNode = {|
72
- id: number,
73
- children: Array<number>,
74
- displayName: string | null,
75
- key: number | string | null,
76
- type: ElementType,
89
+// Combination of profiling data collected by the renderer interface (backend) and Store (frontend).
90
+export type ProfilingDataFrontend = {|
91
+ // Profiling data per root.
92
+ dataForRoots: Map<number, ProfilingDataForRootFrontend>,
93
|};
94
79
-export type ImportedProfilingData = {|
80
- version: 3,
81
- profilingOperations: Map<number, Array<Uint32Array>>,
82
- profilingSnapshots: Map<number, Map<number, ProfilingSnapshotNode>>,
83
- commitDetails: Array<CommitDetailsFrontend>,
84
- interactions: InteractionsFrontend,
85
- profilingSummary: ProfilingSummaryFrontend,
95
+export type CommitDataExport = {|
96
+ duration: number,
97
+ // Tuple of fiber ID and actual duration
98
+ fiberActualDurations: Array<[number, number]>,
99
+ // Tuple of fiber ID and computed "self" duration
100
+ fiberSelfDurations: Array<[number, number]>,
101
+ interactionIDs: Array<number>,
102
+ priorityLevel: string | null,
103
+ screenshot: string | null,
104
+ timestamp: number,
105
|};
106
88
-export type SerializableProfilingDataOperationsByRootID = Array<
89
- [number, Array<Array<number>>]
90
->;
91
-export type SerializableProfilingDataSnapshotsByRootID = Array<
92
- [number, Array<[number, ProfilingSnapshotNode]>]
93
->;
94
-
95
-export type ExportedProfilingSummaryFromFrontend = {|
96
- version: 3,
97
- profilingOperationsByRootID: SerializableProfilingDataOperationsByRootID,
98
- profilingSnapshotsByRootID: SerializableProfilingDataSnapshotsByRootID,
99
- rendererID: number,
107
+export type ProfilingDataForRootExport = {|
108
+ commitData: Array<CommitDataExport>,
109
+ displayName: string,
110
+ // Tuple of Fiber ID and base duration
111
+ initialTreeBaseDurations: Array<[number, number]>,
112
+ // Tuple of Interaction ID and commit indices
113
+ interactionCommits: Array<[number, Array<number>]>,
114
+ interactions: Array<[number, Interaction]>,
115
+ operations: Array<Array<number>>,
116
rootID: number,
117
+ snapshots: Array<[number, SnapshotNode]>,
118
|};
119
103
-export type ExportedProfilingData = {|
104
- version: 3,
105
- profilingOperationsByRootID: SerializableProfilingDataOperationsByRootID,
106
- profilingSnapshotsByRootID: SerializableProfilingDataSnapshotsByRootID,
107
- commitDetails: Array<CommitDetailsBackend>,
108
- interactions: InteractionsBackend,
109
- profilingSummary: ProfilingSummaryBackend,
120
+// Serializable vefrsion of ProfilingDataFrontend data.
121
+export type ProfilingDataExport = {|
122
+ version: 5,
123
+ dataForRoots: Array<ProfilingDataForRootExport>,
124
|};
src/devtools/views/Profiler/utils.js
+167
-144
@@ -2,15 +2,15 @@
2
3
import { PROFILER_EXPORT_VERSION } from 'src/constants';
4
5
+import type { ProfilingDataBackend } from 'src/backend/types';
6
import type {
6
- ExportedProfilingSummaryFromFrontend,
7
- ExportedProfilingData,
8
- ImportedProfilingData,
9
- ProfilingSnapshotNode,
7
+ ProfilingDataExport,
8
+ ProfilingDataForRootExport,
9
+ ProfilingDataForRootFrontend,
10
+ ProfilingDataFrontend,
11
+ SnapshotNode,
12
} from './types';
13
12
-import type { ExportedProfilingDataFromRenderer } from 'src/backend/types';
13
-
14
const commitGradient = [
15
'var(--color-commit-gradient-0)',
16
'var(--color-commit-gradient-1)',
@@ -24,153 +24,176 @@ const commitGradient = [
24
'var(--color-commit-gradient-9)',
25
];
26
27
-export const prepareExportedProfilingSummary = (
28
- profilingOperations: Map<number, Array<Uint32Array>>,
29
- profilingSnapshots: Map<number, Map<number, ProfilingSnapshotNode>>,
30
- rendererID: number,
31
- rootID: number
32
-) => {
33
- const profilingOperationsForRoot = [];
34
- const operations = profilingOperations.get(rootID);
35
- if (operations != null) {
36
- operations.forEach(operationsTypedArray => {
37
- // Convert typed array to plain array before JSON serialization, or it will be converted to an Object.
38
- const operationsPlainArray = Array.from(operationsTypedArray);
39
- profilingOperationsForRoot.push(operationsPlainArray);
40
- });
41
- }
42
-
43
- // Convert Map to Array of key-value pairs or JSON.stringify will clobber the contents.
44
- const profilingSnapshotsForRoot = [];
45
- const profilingSnapshotsMap = profilingSnapshots.get(rootID);
46
- if (profilingSnapshotsMap != null) {
47
- for (const [elementID, snapshotNode] of profilingSnapshotsMap.entries()) {
48
- profilingSnapshotsForRoot.push([elementID, snapshotNode]);
49
- }
50
- }
27
+// Combines info from the Store (frontend) and renderer interfaces (backend) into the format required by the Profiler UI.
28
+// This format can then be quickly exported (and re-imported).
29
+export function prepareProfilingDataFrontendFromBackendAndStore(
30
+ dataBackends: Array<ProfilingDataBackend>,
31
+ operationsByRootID: Map<number, Array<Uint32Array>>,
32
+ screenshotsByRootID: Map<number, Map<number, string>>,
33
+ snapshotsByRootID: Map<number, Map<number, SnapshotNode>>
34
+): ProfilingDataFrontend {
35
+ const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
36
+
37
+ dataBackends.forEach(dataBackend => {
38
+ dataBackend.dataForRoots.forEach(
39
+ ({
40
+ commitData,
41
+ displayName,
42
+ initialTreeBaseDurations,
43
+ interactionCommits,
44
+ interactions,
45
+ rootID,
46
+ }) => {
47
+ const screenshots = screenshotsByRootID.get(rootID) || null;
48
+
49
+ const operations = operationsByRootID.get(rootID);
50
+ if (operations == null) {
51
+ throw Error(`Could not find profiling operations for root ${rootID}`);
52
+ }
53
52
- const exportedProfilingSummary: ExportedProfilingSummaryFromFrontend = {
53
- version: PROFILER_EXPORT_VERSION,
54
- profilingOperationsByRootID: [[rootID, profilingOperationsForRoot]],
55
- profilingSnapshotsByRootID: [[rootID, profilingSnapshotsForRoot]],
56
- rendererID,
57
- rootID,
58
- };
59
- return exportedProfilingSummary;
60
-};
54
+ const snapshots = snapshotsByRootID.get(rootID);
55
+ if (snapshots == null) {
56
+ throw Error(`Could not find profiling snapshots for root ${rootID}`);
57
+ }
58
62
-export const prepareExportedProfilingData = (
63
- exportedProfilingDataFromRenderer: ExportedProfilingDataFromRenderer,
64
- exportedProfilingSummary: ExportedProfilingSummaryFromFrontend
65
-): ExportedProfilingData => {
66
- if (exportedProfilingDataFromRenderer.version !== PROFILER_EXPORT_VERSION) {
67
- throw new Error(
68
- `Unsupported profiling data version ${
69
- exportedProfilingDataFromRenderer.version
70
- } from renderer with id "${exportedProfilingSummary.rendererID}"`
71
- );
72
- }
73
- if (exportedProfilingSummary.version !== PROFILER_EXPORT_VERSION) {
74
- throw new Error(
75
- `Unsupported profiling summary version ${
76
- exportedProfilingSummary.version
77
- } from renderer with id "${exportedProfilingSummary.rendererID}"`
59
+ dataForRoots.set(rootID, {
60
+ commitData: commitData.map((commitDataBackend, commitIndex) => ({
61
+ duration: commitDataBackend.duration,
62
+ fiberActualDurations: new Map(
63
+ commitDataBackend.fiberActualDurations
64
+ ),
65
+ fiberSelfDurations: new Map(commitDataBackend.fiberSelfDurations),
66
+ interactionIDs: commitDataBackend.interactionIDs,
67
+ priorityLevel: commitDataBackend.priorityLevel,
68
+ screenshot:
69
+ (screenshots !== null && screenshots.get(commitIndex)) || null,
70
+ timestamp: commitDataBackend.timestamp,
71
+ })),
72
+ displayName,
73
+ initialTreeBaseDurations: new Map(initialTreeBaseDurations),
74
+ interactionCommits: new Map(interactionCommits),
75
+ interactions: new Map(interactions),
76
+ operations,
77
+ rootID,
78
+ snapshots,
79
+ });
80
+ }
81
);
79
- }
80
- const exportedProfilingData: ExportedProfilingData = {
81
- version: PROFILER_EXPORT_VERSION,
82
- profilingSummary: exportedProfilingDataFromRenderer.profilingSummary,
83
- commitDetails: exportedProfilingDataFromRenderer.commitDetails,
84
- interactions: exportedProfilingDataFromRenderer.interactions,
85
- profilingOperationsByRootID:
86
- exportedProfilingSummary.profilingOperationsByRootID,
87
- profilingSnapshotsByRootID:
88
- exportedProfilingSummary.profilingSnapshotsByRootID,
89
- };
90
- return exportedProfilingData;
91
-};
82
+ });
83
93
-/**
94
- * This function should mirror `prepareExportedProfilingData` and `prepareExportedProfilingSummary`.
95
- */
96
-export const prepareImportedProfilingData = (
97
- exportedProfilingDataJsonString: string
98
-) => {
99
- const parsed = JSON.parse(exportedProfilingDataJsonString);
84
+ return { dataForRoots };
85
+}
86
101
- if (parsed.version !== PROFILER_EXPORT_VERSION) {
102
- throw Error(`Unsupported profiler export version "${parsed.version}".`);
103
- }
87
+// Converts a Profiling data export into the format required by the Store.
88
+export function prepareProfilingDataFrontendFromExport(
89
+ profilingDataExport: ProfilingDataExport
90
+): ProfilingDataFrontend {
91
+ const { version } = profilingDataExport;
92
105
- // Some "exported" types in `parsed` are `...Backend`, see `prepareExportedProfilingData`,
106
- // they come to `ExportedProfilingData` from `ExportedProfilingDataFromRenderer`.
107
- // But the "imported" types in `ImportedProfilingData` are `...Frontend`,
108
- // and some of them aren't exactly the same as `...Backend` (i.e. an interleaved array versus a map).
109
- // The type annotations here help us to spot the incompatibilities and properly convert.
110
-
111
- const exportedProfilingData: ExportedProfilingData = parsed;
112
-
113
- const profilingSummaryExported = exportedProfilingData.profilingSummary;
114
- const initialTreeBaseDurations =
115
- profilingSummaryExported.initialTreeBaseDurations;
116
- const initialTreeBaseDurationsMap = new Map();
117
- for (let i = 0; i < initialTreeBaseDurations.length; i += 2) {
118
- const fiberID = initialTreeBaseDurations[i];
119
- const initialTreeBaseDuration = initialTreeBaseDurations[i + 1];
120
- initialTreeBaseDurationsMap.set(fiberID, initialTreeBaseDuration);
93
+ if (version !== PROFILER_EXPORT_VERSION) {
94
+ throw Error(`Unsupported profiler export version "${version}"`);
95
}
96
123
- const profilingData: ImportedProfilingData = {
124
- version: parsed.version,
125
- profilingOperations: new Map(
126
- exportedProfilingData.profilingOperationsByRootID.map(
127
- ([rootID, profilingOperationsForRoot]) => [
128
- rootID,
129
- profilingOperationsForRoot.map(operations =>
130
- Uint32Array.from(operations)
131
- ),
132
- ]
133
- )
134
- ),
135
- profilingSnapshots: new Map(
136
- exportedProfilingData.profilingSnapshotsByRootID.map(
137
- ([rootID, profilingSnapshotsForRoot]) => [
138
- rootID,
139
- new Map(profilingSnapshotsForRoot),
140
- ]
141
- )
142
- ),
143
- commitDetails: exportedProfilingData.commitDetails.map(
144
- commitDetailsBackendItem => {
145
- const durations = commitDetailsBackendItem.durations;
146
- const actualDurationsMap = new Map<number, number>();
147
- const selfDurationsMap = new Map<number, number>();
148
- for (let i = 0; i < durations.length; i += 3) {
149
- const fiberID = durations[i];
150
- actualDurationsMap.set(fiberID, durations[i + 1]);
151
- selfDurationsMap.set(fiberID, durations[i + 2]);
152
- }
153
- return {
154
- actualDurations: actualDurationsMap,
155
- commitIndex: commitDetailsBackendItem.commitIndex,
156
- interactions: commitDetailsBackendItem.interactions,
157
- priorityLevel: commitDetailsBackendItem.priorityLevel,
158
- rootID: commitDetailsBackendItem.rootID,
159
- selfDurations: selfDurationsMap,
160
- };
161
- }
162
- ),
163
- interactions: exportedProfilingData.interactions,
164
- profilingSummary: {
165
- rootID: profilingSummaryExported.rootID,
166
- commitDurations: profilingSummaryExported.commitDurations,
167
- commitTimes: profilingSummaryExported.commitTimes,
168
- initialTreeBaseDurations: initialTreeBaseDurationsMap,
169
- interactionCount: profilingSummaryExported.interactionCount,
170
- },
97
+ const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
98
+ profilingDataExport.dataForRoots.forEach(
99
+ ({
100
+ commitData,
101
+ displayName,
102
+ initialTreeBaseDurations,
103
+ interactionCommits,
104
+ interactions,
105
+ operations,
106
+ rootID,
107
+ snapshots,
108
+ }) => {
109
+ dataForRoots.set(rootID, {
110
+ commitData: commitData.map(
111
+ ({
112
+ duration,
113
+ fiberActualDurations,
114
+ fiberSelfDurations,
115
+ interactionIDs,
116
+ priorityLevel,
117
+ screenshot,
118
+ timestamp,
119
+ }) => ({
120
+ duration,
121
+ fiberActualDurations: new Map(fiberActualDurations),
122
+ fiberSelfDurations: new Map(fiberSelfDurations),
123
+ interactionIDs,
124
+ priorityLevel,
125
+ screenshot,
126
+ timestamp,
127
+ })
128
+ ),
129
+ displayName,
130
+ initialTreeBaseDurations: new Map(initialTreeBaseDurations),
131
+ interactionCommits: new Map(interactionCommits),
132
+ interactions: new Map(interactions),
133
+ operations: operations.map(array => Uint32Array.from(array)), // Convert Array back to Uint32Array
134
+ rootID,
135
+ snapshots: new Map(snapshots),
136
+ });
137
+ }
138
+ );
139
+
140
+ return { dataForRoots };
141
+}
142
+
143
+// Converts a Store Profiling data into a format that can be safely (JSON) serialized for export.
144
+export function prepareProfilingDataExport(
145
+ profilingDataFrontend: ProfilingDataFrontend
146
+): ProfilingDataExport {
147
+ const dataForRoots: Array<ProfilingDataForRootExport> = [];
148
+ profilingDataFrontend.dataForRoots.forEach(
149
+ ({
150
+ commitData,
151
+ displayName,
152
+ initialTreeBaseDurations,
153
+ interactionCommits,
154
+ interactions,
155
+ operations,
156
+ rootID,
157
+ snapshots,
158
+ }) => {
159
+ dataForRoots.push({
160
+ commitData: commitData.map(
161
+ ({
162
+ duration,
163
+ fiberActualDurations,
164
+ fiberSelfDurations,
165
+ interactionIDs,
166
+ priorityLevel,
167
+ screenshot,
168
+ timestamp,
169
+ }) => ({
170
+ duration,
171
+ fiberActualDurations: Array.from(fiberActualDurations.entries()),
172
+ fiberSelfDurations: Array.from(fiberSelfDurations.entries()),
173
+ interactionIDs,
174
+ priorityLevel,
175
+ screenshot,
176
+ timestamp,
177
+ })
178
+ ),
179
+ displayName,
180
+ initialTreeBaseDurations: Array.from(
181
+ initialTreeBaseDurations.entries()
182
+ ),
183
+ interactionCommits: Array.from(interactionCommits.entries()),
184
+ interactions: Array.from(interactions.entries()),
185
+ operations: operations.map(array => Array.from(array)), // Convert Uint32Array to Array for serialization
186
+ rootID,
187
+ snapshots: Array.from(snapshots.entries()),
188
+ });
189
+ }
190
+ );
191
+
192
+ return {
193
+ version: PROFILER_EXPORT_VERSION,
194
+ dataForRoots,
195
};
172
- return profilingData;
173
-};
196
+}
197
198
export const getGradientColor = (value: number) => {
199
const maxIndex = commitGradient.length - 1;