@samitouri / QOS-React-2 / commits / c6de014a9a

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;