Added export+import profiling data test coverage Also fixed an incomplete import feature as well as a regression in export that had gone unnoticed due to previous lack of test coverage.
Added export+import profiling data test coverage Also fixed an incomplete import feature as well as a regression in export that had gone unnoticed due to previous lack of test coverage.
Brian Vaughn committed
May 7, 2019 at 13:08 UTC
acdc9bae674d27fb107a8ed99680148f07cfd55d
10 files changed
+1043
-77
package.json
+2
-1
@@ -14,7 +14,8 @@
14
"<rootDir>"
15
],
16
"moduleNameMapper": {
17
- "^src/(.*)$": "<rootDir>/src/$1"
17
+ "^src/(.*)$": "<rootDir>/src/$1",
18
+ "\\.css$": "<rootDir>/src/__tests__/__mocks__/cssMock.js"
19
},
20
"setupFiles": [
21
"<rootDir>/src/__tests__/setupEnv"
src/__tests__/__mocks__/cssMock.js
new
+1
@@ -0,0 +1 @@
1
+module.exports = {};
src/__tests__/__snapshots__/profiling-test.js.snap
+765
@@ -54,6 +54,222 @@ Object {
54
}
55
`;
56
57
+exports[`profiling CommitDetails should be collected for each commit: exported data 1`] = `
58
+Object {
59
+ "commitDetails": Array [
60
+ Object {
61
+ "actualDurations": Array [
62
+ 1,
63
+ 14,
64
+ 2,
65
+ 14,
66
+ 3,
67
+ 2,
68
+ 4,
69
+ 2,
70
+ ],
71
+ "commitIndex": 0,
72
+ "interactions": Array [],
73
+ "rootID": 1,
74
+ },
75
+ Object {
76
+ "actualDurations": Array [
77
+ 3,
78
+ 2,
79
+ 4,
80
+ 2,
81
+ 5,
82
+ 2,
83
+ 2,
84
+ 16,
85
+ 1,
86
+ 16,
87
+ ],
88
+ "commitIndex": 1,
89
+ "interactions": Array [],
90
+ "rootID": 1,
91
+ },
92
+ Object {
93
+ "actualDurations": Array [
94
+ 3,
95
+ 2,
96
+ 2,
97
+ 12,
98
+ 1,
99
+ 12,
100
+ ],
101
+ "commitIndex": 2,
102
+ "interactions": Array [],
103
+ "rootID": 1,
104
+ },
105
+ Object {
106
+ "actualDurations": Array [
107
+ 2,
108
+ 10,
109
+ 1,
110
+ 10,
111
+ ],
112
+ "commitIndex": 3,
113
+ "interactions": Array [],
114
+ "rootID": 1,
115
+ },
116
+ ],
117
+ "interactions": Object {
118
+ "interactions": Array [],
119
+ "rootID": 1,
120
+ },
121
+ "profilingOperations": Map {
122
+ 1 => Array [
123
+ Array [
124
+ 1,
125
+ 1,
126
+ 17,
127
+ 6,
128
+ 80,
129
+ 97,
130
+ 114,
131
+ 101,
132
+ 110,
133
+ 116,
134
+ 5,
135
+ 67,
136
+ 104,
137
+ 105,
138
+ 108,
139
+ 100,
140
+ 1,
141
+ 48,
142
+ 1,
143
+ 49,
144
+ 1,
145
+ 1,
146
+ 11,
147
+ 1,
148
+ 1,
149
+ 4,
150
+ 1,
151
+ 14000,
152
+ 1,
153
+ 2,
154
+ 5,
155
+ 1,
156
+ 0,
157
+ 1,
158
+ 0,
159
+ 4,
160
+ 2,
161
+ 14000,
162
+ 1,
163
+ 3,
164
+ 5,
165
+ 2,
166
+ 2,
167
+ 2,
168
+ 3,
169
+ 4,
170
+ 3,
171
+ 2000,
172
+ 1,
173
+ 4,
174
+ 5,
175
+ 2,
176
+ 2,
177
+ 2,
178
+ 4,
179
+ 4,
180
+ 4,
181
+ 2000,
182
+ ],
183
+ Array [
184
+ 1,
185
+ 1,
186
+ 8,
187
+ 5,
188
+ 67,
189
+ 104,
190
+ 105,
191
+ 108,
192
+ 100,
193
+ 1,
194
+ 50,
195
+ 1,
196
+ 5,
197
+ 5,
198
+ 2,
199
+ 2,
200
+ 1,
201
+ 2,
202
+ 4,
203
+ 5,
204
+ 2000,
205
+ 4,
206
+ 2,
207
+ 16000,
208
+ 3,
209
+ 2,
210
+ 3,
211
+ 3,
212
+ 4,
213
+ 5,
214
+ 4,
215
+ 1,
216
+ 16000,
217
+ ],
218
+ Array [
219
+ 1,
220
+ 1,
221
+ 0,
222
+ 2,
223
+ 2,
224
+ 5,
225
+ 4,
226
+ 4,
227
+ 2,
228
+ 12000,
229
+ 4,
230
+ 1,
231
+ 12000,
232
+ ],
233
+ Array [
234
+ 1,
235
+ 1,
236
+ 0,
237
+ 2,
238
+ 1,
239
+ 3,
240
+ 4,
241
+ 2,
242
+ 10000,
243
+ 4,
244
+ 1,
245
+ 10000,
246
+ ],
247
+ ],
248
+ },
249
+ "profilingSnapshots": Map {
250
+ 1 => Map {},
251
+ },
252
+ "profilingSummary": Object {
253
+ "commitDurations": Array [
254
+ 14,
255
+ 16,
256
+ 12,
257
+ 10,
258
+ ],
259
+ "commitTimes": Array [
260
+ 14,
261
+ 30,
262
+ 42,
263
+ 52,
264
+ ],
265
+ "initialTreeBaseDurations": Array [],
266
+ "interactionCount": 0,
267
+ "rootID": 1,
268
+ },
269
+ "version": 1,
270
+}
271
+`;
272
+
273
exports[`profiling FiberCommits should be collected for each rendered fiber: FiberCommits: element 2 1`] = `
274
Object {
275
"commitDurations": Array [
@@ -108,6 +324,202 @@ Object {
324
}
325
`;
326
327
+exports[`profiling FiberCommits should be collected for each rendered fiber: exported data 1`] = `
328
+Object {
329
+ "commitDetails": Array [
330
+ Object {
331
+ "actualDurations": Array [
332
+ 1,
333
+ 12,
334
+ 2,
335
+ 12,
336
+ 3,
337
+ 2,
338
+ ],
339
+ "commitIndex": 0,
340
+ "interactions": Array [],
341
+ "rootID": 1,
342
+ },
343
+ Object {
344
+ "actualDurations": Array [
345
+ 3,
346
+ 2,
347
+ 4,
348
+ 2,
349
+ 2,
350
+ 14,
351
+ 1,
352
+ 14,
353
+ ],
354
+ "commitIndex": 1,
355
+ "interactions": Array [],
356
+ "rootID": 1,
357
+ },
358
+ Object {
359
+ "actualDurations": Array [
360
+ 3,
361
+ 2,
362
+ 4,
363
+ 2,
364
+ 5,
365
+ 2,
366
+ 2,
367
+ 16,
368
+ 1,
369
+ 16,
370
+ ],
371
+ "commitIndex": 2,
372
+ "interactions": Array [],
373
+ "rootID": 1,
374
+ },
375
+ ],
376
+ "interactions": Object {
377
+ "interactions": Array [],
378
+ "rootID": 1,
379
+ },
380
+ "profilingOperations": Map {
381
+ 1 => Array [
382
+ Array [
383
+ 1,
384
+ 1,
385
+ 15,
386
+ 6,
387
+ 80,
388
+ 97,
389
+ 114,
390
+ 101,
391
+ 110,
392
+ 116,
393
+ 5,
394
+ 67,
395
+ 104,
396
+ 105,
397
+ 108,
398
+ 100,
399
+ 1,
400
+ 48,
401
+ 1,
402
+ 1,
403
+ 11,
404
+ 1,
405
+ 1,
406
+ 4,
407
+ 1,
408
+ 12000,
409
+ 1,
410
+ 2,
411
+ 5,
412
+ 1,
413
+ 0,
414
+ 1,
415
+ 0,
416
+ 4,
417
+ 2,
418
+ 12000,
419
+ 1,
420
+ 3,
421
+ 5,
422
+ 2,
423
+ 2,
424
+ 2,
425
+ 3,
426
+ 4,
427
+ 3,
428
+ 2000,
429
+ ],
430
+ Array [
431
+ 1,
432
+ 1,
433
+ 8,
434
+ 5,
435
+ 67,
436
+ 104,
437
+ 105,
438
+ 108,
439
+ 100,
440
+ 1,
441
+ 49,
442
+ 1,
443
+ 4,
444
+ 5,
445
+ 2,
446
+ 2,
447
+ 1,
448
+ 2,
449
+ 4,
450
+ 4,
451
+ 2000,
452
+ 4,
453
+ 2,
454
+ 14000,
455
+ 3,
456
+ 2,
457
+ 2,
458
+ 3,
459
+ 4,
460
+ 4,
461
+ 1,
462
+ 14000,
463
+ ],
464
+ Array [
465
+ 1,
466
+ 1,
467
+ 8,
468
+ 5,
469
+ 67,
470
+ 104,
471
+ 105,
472
+ 108,
473
+ 100,
474
+ 1,
475
+ 50,
476
+ 1,
477
+ 5,
478
+ 5,
479
+ 2,
480
+ 2,
481
+ 1,
482
+ 2,
483
+ 4,
484
+ 5,
485
+ 2000,
486
+ 4,
487
+ 2,
488
+ 16000,
489
+ 3,
490
+ 2,
491
+ 3,
492
+ 3,
493
+ 4,
494
+ 5,
495
+ 4,
496
+ 1,
497
+ 16000,
498
+ ],
499
+ ],
500
+ },
501
+ "profilingSnapshots": Map {
502
+ 1 => Map {},
503
+ },
504
+ "profilingSummary": Object {
505
+ "commitDurations": Array [
506
+ 12,
507
+ 14,
508
+ 16,
509
+ ],
510
+ "commitTimes": Array [
511
+ 12,
512
+ 26,
513
+ 42,
514
+ ],
515
+ "initialTreeBaseDurations": Array [],
516
+ "interactionCount": 0,
517
+ "rootID": 1,
518
+ },
519
+ "version": 1,
520
+}
521
+`;
522
+
523
exports[`profiling Interactions should be collected for every traced interaction: Interactions 1`] = `
524
Array [
525
Object {
@@ -131,6 +543,181 @@ Array [
543
]
544
`;
545
546
+exports[`profiling Interactions should be collected for every traced interaction: exported data 1`] = `
547
+Object {
548
+ "commitDetails": Array [
549
+ Object {
550
+ "actualDurations": Array [
551
+ 1,
552
+ 12,
553
+ 2,
554
+ 12,
555
+ 3,
556
+ 2,
557
+ ],
558
+ "commitIndex": 0,
559
+ "interactions": Array [
560
+ Object {
561
+ "__count": 1,
562
+ "id": 0,
563
+ "name": "mount: one child",
564
+ "timestamp": 0,
565
+ },
566
+ ],
567
+ "rootID": 1,
568
+ },
569
+ Object {
570
+ "actualDurations": Array [
571
+ 3,
572
+ 2,
573
+ 4,
574
+ 2,
575
+ 2,
576
+ 14,
577
+ 1,
578
+ 14,
579
+ ],
580
+ "commitIndex": 1,
581
+ "interactions": Array [
582
+ Object {
583
+ "__count": 0,
584
+ "id": 1,
585
+ "name": "update: two children",
586
+ "timestamp": 12,
587
+ },
588
+ ],
589
+ "rootID": 1,
590
+ },
591
+ ],
592
+ "interactions": Object {
593
+ "interactions": Array [
594
+ Object {
595
+ "__count": 1,
596
+ "commits": Array [
597
+ 0,
598
+ ],
599
+ "id": 0,
600
+ "name": "mount: one child",
601
+ "timestamp": 0,
602
+ },
603
+ Object {
604
+ "__count": 0,
605
+ "commits": Array [
606
+ 1,
607
+ ],
608
+ "id": 1,
609
+ "name": "update: two children",
610
+ "timestamp": 12,
611
+ },
612
+ ],
613
+ "rootID": 1,
614
+ },
615
+ "profilingOperations": Map {
616
+ 1 => Array [
617
+ Array [
618
+ 1,
619
+ 1,
620
+ 15,
621
+ 6,
622
+ 80,
623
+ 97,
624
+ 114,
625
+ 101,
626
+ 110,
627
+ 116,
628
+ 5,
629
+ 67,
630
+ 104,
631
+ 105,
632
+ 108,
633
+ 100,
634
+ 1,
635
+ 48,
636
+ 1,
637
+ 1,
638
+ 11,
639
+ 1,
640
+ 1,
641
+ 4,
642
+ 1,
643
+ 12000,
644
+ 1,
645
+ 2,
646
+ 5,
647
+ 1,
648
+ 0,
649
+ 1,
650
+ 0,
651
+ 4,
652
+ 2,
653
+ 12000,
654
+ 1,
655
+ 3,
656
+ 5,
657
+ 2,
658
+ 2,
659
+ 2,
660
+ 3,
661
+ 4,
662
+ 3,
663
+ 2000,
664
+ ],
665
+ Array [
666
+ 1,
667
+ 1,
668
+ 8,
669
+ 5,
670
+ 67,
671
+ 104,
672
+ 105,
673
+ 108,
674
+ 100,
675
+ 1,
676
+ 49,
677
+ 1,
678
+ 4,
679
+ 5,
680
+ 2,
681
+ 2,
682
+ 1,
683
+ 2,
684
+ 4,
685
+ 4,
686
+ 2000,
687
+ 4,
688
+ 2,
689
+ 14000,
690
+ 3,
691
+ 2,
692
+ 2,
693
+ 3,
694
+ 4,
695
+ 4,
696
+ 1,
697
+ 14000,
698
+ ],
699
+ ],
700
+ },
701
+ "profilingSnapshots": Map {
702
+ 1 => Map {},
703
+ },
704
+ "profilingSummary": Object {
705
+ "commitDurations": Array [
706
+ 12,
707
+ 14,
708
+ ],
709
+ "commitTimes": Array [
710
+ 12,
711
+ 26,
712
+ ],
713
+ "initialTreeBaseDurations": Array [],
714
+ "interactionCount": 2,
715
+ "rootID": 1,
716
+ },
717
+ "version": 1,
718
+}
719
+`;
720
+
721
exports[`profiling ProfilingSummary should be collected for each commit: ProfilingSummary 1`] = `
722
Object {
723
"commitDurations": Array [
@@ -153,3 +740,181 @@ Object {
740
"rootID": 1,
741
}
742
`;
743
+
744
+exports[`profiling ProfilingSummary should be collected for each commit: exported data 1`] = `
745
+Object {
746
+ "commitDetails": Array [
747
+ Object {
748
+ "actualDurations": Array [
749
+ 3,
750
+ 2,
751
+ 4,
752
+ 2,
753
+ 5,
754
+ 2,
755
+ 2,
756
+ 16,
757
+ 1,
758
+ 16,
759
+ ],
760
+ "commitIndex": 0,
761
+ "interactions": Array [],
762
+ "rootID": 1,
763
+ },
764
+ Object {
765
+ "actualDurations": Array [
766
+ 3,
767
+ 2,
768
+ 2,
769
+ 12,
770
+ 1,
771
+ 12,
772
+ ],
773
+ "commitIndex": 1,
774
+ "interactions": Array [],
775
+ "rootID": 1,
776
+ },
777
+ Object {
778
+ "actualDurations": Array [
779
+ 2,
780
+ 10,
781
+ 1,
782
+ 10,
783
+ ],
784
+ "commitIndex": 2,
785
+ "interactions": Array [],
786
+ "rootID": 1,
787
+ },
788
+ ],
789
+ "interactions": Object {
790
+ "interactions": Array [],
791
+ "rootID": 1,
792
+ },
793
+ "profilingOperations": Map {
794
+ 1 => Array [
795
+ Array [
796
+ 1,
797
+ 1,
798
+ 8,
799
+ 5,
800
+ 67,
801
+ 104,
802
+ 105,
803
+ 108,
804
+ 100,
805
+ 1,
806
+ 50,
807
+ 1,
808
+ 5,
809
+ 5,
810
+ 2,
811
+ 2,
812
+ 1,
813
+ 2,
814
+ 4,
815
+ 5,
816
+ 2000,
817
+ 4,
818
+ 2,
819
+ 16000,
820
+ 3,
821
+ 2,
822
+ 3,
823
+ 3,
824
+ 4,
825
+ 5,
826
+ 4,
827
+ 1,
828
+ 16000,
829
+ ],
830
+ Array [
831
+ 1,
832
+ 1,
833
+ 0,
834
+ 2,
835
+ 2,
836
+ 5,
837
+ 4,
838
+ 4,
839
+ 2,
840
+ 12000,
841
+ 4,
842
+ 1,
843
+ 12000,
844
+ ],
845
+ Array [
846
+ 1,
847
+ 1,
848
+ 0,
849
+ 2,
850
+ 1,
851
+ 3,
852
+ 4,
853
+ 2,
854
+ 10000,
855
+ 4,
856
+ 1,
857
+ 10000,
858
+ ],
859
+ ],
860
+ },
861
+ "profilingSnapshots": Map {
862
+ 1 => Map {
863
+ 1 => Object {
864
+ "children": Array [
865
+ 2,
866
+ ],
867
+ "displayName": null,
868
+ "id": 1,
869
+ "key": null,
870
+ },
871
+ 2 => Object {
872
+ "children": Array [
873
+ 3,
874
+ 4,
875
+ ],
876
+ "displayName": "Parent",
877
+ "id": 2,
878
+ "key": null,
879
+ },
880
+ 3 => Object {
881
+ "children": Array [],
882
+ "displayName": "Child",
883
+ "id": 3,
884
+ "key": "0",
885
+ },
886
+ 4 => Object {
887
+ "children": Array [],
888
+ "displayName": "Child",
889
+ "id": 4,
890
+ "key": "1",
891
+ },
892
+ },
893
+ },
894
+ "profilingSummary": Object {
895
+ "commitDurations": Array [
896
+ 16,
897
+ 12,
898
+ 10,
899
+ ],
900
+ "commitTimes": Array [
901
+ 16,
902
+ 28,
903
+ 38,
904
+ ],
905
+ "initialTreeBaseDurations": Array [
906
+ 1,
907
+ 14,
908
+ 2,
909
+ 14,
910
+ 3,
911
+ 2,
912
+ 4,
913
+ 2,
914
+ ],
915
+ "interactionCount": 0,
916
+ "rootID": 1,
917
+ },
918
+ "version": 1,
919
+}
920
+`;
src/__tests__/profiling-test.js
+174
-31
@@ -1,6 +1,7 @@
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', () => {
@@ -9,13 +10,55 @@ describe('profiling', () => {
10
let Scheduler;
11
let SchedulerTracing;
12
let TestRenderer: ReactTestRenderer;
13
+ let bridge: Bridge;
14
let store: Store;
15
let utils;
16
17
+ const exportImportHelper = (rendererID: number, rootID: number) => {
18
+ const {
19
+ prepareProfilingExport,
20
+ prepareProfilingImport,
21
+ } = require('src/devtools/views/Profiler/utils');
22
+
23
+ let exportedProfilingSummary;
24
+ bridge.addListener('exportFile', ({ contents }) => {
25
+ exportedProfilingSummary = contents;
26
+ });
27
+
28
+ utils.act(() => {
29
+ const exportProfilingSummary = prepareProfilingExport(
30
+ store.profilingOperations,
31
+ store.profilingSnapshots,
32
+ rootID,
33
+ rendererID
34
+ );
35
+ bridge.send('exportProfilingSummary', exportProfilingSummary);
36
+ });
37
+
38
+ expect(exportedProfilingSummary).toBeDefined();
39
+
40
+ const importedProfilingSummary = prepareProfilingImport(
41
+ ((exportedProfilingSummary: any): string)
42
+ );
43
+
44
+ // Sanity check that profiling snapshots are serialized correctly.
45
+ expect(store.profilingSnapshots.get(rootID)).toEqual(
46
+ importedProfilingSummary.profilingSnapshots.get(rootID)
47
+ );
48
+
49
+ // Snapshot the JSON-parsed object, rather than the raw string, because Jest formats the diff nicer.
50
+ expect(importedProfilingSummary).toMatchSnapshot('exported data');
51
+
52
+ utils.act(() => {
53
+ store.importedProfilingData = importedProfilingSummary;
54
+ });
55
+ };
56
+
57
beforeEach(() => {
58
utils = require('./utils');
59
utils.beforeEachProfiling();
60
61
+ bridge = global.bridge;
62
store = global.store;
63
store.collapseNodesByDefault = false;
64
@@ -48,15 +91,18 @@ describe('profiling', () => {
91
utils.act(() => ReactDOM.render(<Parent count={0} />, container));
92
utils.act(() => store.stopProfiling());
93
51
- let suspenseResolved = false;
94
+ let profilingSummary = null;
95
53
- function Suspender({ rendererID, rootID }) {
54
- const profilingSummary = store.profilingCache.ProfilingSummary.read({
96
+ function Suspender({ previousPofilingSummary, rendererID, rootID }) {
97
+ profilingSummary = store.profilingCache.ProfilingSummary.read({
98
rendererID,
99
rootID,
100
});
58
- suspenseResolved = true;
59
- expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
101
+ if (previousPofilingSummary != null) {
102
+ expect(profilingSummary).toEqual(previousPofilingSummary);
103
+ } else {
104
+ expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
105
+ }
106
return null;
107
}
108
@@ -66,12 +112,30 @@ describe('profiling', () => {
112
await utils.actSuspense(() =>
113
TestRenderer.create(
114
<React.Suspense fallback={null}>
69
- <Suspender rendererID={rendererID} rootID={rootID} />
115
+ <Suspender
116
+ previousPofilingSummary={null}
117
+ rendererID={rendererID}
118
+ rootID={rootID}
119
+ />
120
</React.Suspense>
121
)
122
);
123
74
- expect(suspenseResolved).toBe(true);
124
+ expect(profilingSummary).not.toBeNull();
125
+
126
+ exportImportHelper(rendererID, rootID);
127
+
128
+ await utils.actSuspense(() =>
129
+ TestRenderer.create(
130
+ <React.Suspense fallback={null}>
131
+ <Suspender
132
+ previousPofilingSummary={profilingSummary}
133
+ rendererID={rendererID}
134
+ rootID={rootID}
135
+ />
136
+ </React.Suspense>
137
+ )
138
+ );
139
140
done();
141
});
@@ -99,38 +163,65 @@ describe('profiling', () => {
163
utils.act(() => ReactDOM.render(<Parent count={0} />, container));
164
utils.act(() => store.stopProfiling());
165
102
- let suspenseResolved = false;
166
+ const allCommitDetails = [];
167
104
- function Suspender({ commitIndex, rendererID, rootID }) {
168
+ function Suspender({
169
+ commitIndex,
170
+ previousCommitDetails,
171
+ rendererID,
172
+ rootID,
173
+ }) {
174
const commitDetails = store.profilingCache.CommitDetails.read({
175
commitIndex,
176
rendererID,
177
rootID,
178
});
110
- suspenseResolved = true;
111
- expect(commitDetails).toMatchSnapshot(
112
- `CommitDetails commitIndex: ${commitIndex}`
113
- );
179
+ if (previousCommitDetails != null) {
180
+ expect(commitDetails).toEqual(previousCommitDetails);
181
+ } else {
182
+ allCommitDetails.push(commitDetails);
183
+ expect(commitDetails).toMatchSnapshot(
184
+ `CommitDetails commitIndex: ${commitIndex}`
185
+ );
186
+ }
187
return null;
188
}
189
190
const rendererID = utils.getRendererID();
191
const rootID = store.roots[0];
192
120
- for (let commitIndex = 0; commitIndex <= 3; commitIndex++) {
121
- suspenseResolved = false;
193
+ for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
194
await utils.actSuspense(() => {
195
TestRenderer.create(
196
<React.Suspense fallback={null}>
197
<Suspender
198
commitIndex={commitIndex}
199
+ previousCommitDetails={null}
200
+ rendererID={rendererID}
201
+ rootID={rootID}
202
+ />
203
+ </React.Suspense>
204
+ );
205
+ });
206
+ }
207
+
208
+ expect(allCommitDetails).toHaveLength(4);
209
+
210
+ exportImportHelper(rendererID, rootID);
211
+
212
+ for (let commitIndex = 0; commitIndex < 4; commitIndex++) {
213
+ await utils.actSuspense(() => {
214
+ TestRenderer.create(
215
+ <React.Suspense fallback={null}>
216
+ <Suspender
217
+ commitIndex={commitIndex}
218
+ previousCommitDetails={allCommitDetails[commitIndex]}
219
rendererID={rendererID}
220
rootID={rootID}
221
/>
222
</React.Suspense>
223
);
224
});
133
- expect(suspenseResolved).toBe(true);
225
}
226
227
done();
@@ -158,18 +249,27 @@ describe('profiling', () => {
249
utils.act(() => ReactDOM.render(<Parent count={3} />, container));
250
utils.act(() => store.stopProfiling());
251
161
- let suspenseResolved = false;
252
+ const allFiberCommits = [];
253
163
- function Suspender({ fiberID, rendererID, rootID }) {
254
+ function Suspender({
255
+ fiberID,
256
+ previousFiberCommits,
257
+ rendererID,
258
+ rootID,
259
+ }) {
260
const fiberCommits = store.profilingCache.FiberCommits.read({
261
fiberID,
262
rendererID,
263
rootID,
264
});
169
- suspenseResolved = true;
170
- expect(fiberCommits).toMatchSnapshot(
171
- `FiberCommits: element ${fiberID}`
172
- );
265
+ if (previousFiberCommits != null) {
266
+ expect(fiberCommits).toEqual(previousFiberCommits);
267
+ } else {
268
+ allFiberCommits.push(fiberCommits);
269
+ expect(fiberCommits).toMatchSnapshot(
270
+ `FiberCommits: element ${fiberID}`
271
+ );
272
+ }
273
return null;
274
}
275
@@ -177,7 +277,6 @@ describe('profiling', () => {
277
const rootID = store.roots[0];
278
279
for (let index = 0; index < store.numElements; index++) {
180
- suspenseResolved = false;
280
await utils.actSuspense(() => {
281
const fiberID = store.getElementIDAtIndex(index);
282
if (fiberID == null) {
@@ -187,13 +286,36 @@ describe('profiling', () => {
286
<React.Suspense fallback={null}>
287
<Suspender
288
fiberID={fiberID}
289
+ previousFiberCommits={null}
290
+ rendererID={rendererID}
291
+ rootID={rootID}
292
+ />
293
+ </React.Suspense>
294
+ );
295
+ });
296
+ }
297
+
298
+ expect(allFiberCommits).toHaveLength(store.numElements);
299
+
300
+ exportImportHelper(rendererID, rootID);
301
+
302
+ for (let index = 0; index < store.numElements; index++) {
303
+ await utils.actSuspense(() => {
304
+ const fiberID = store.getElementIDAtIndex(index);
305
+ if (fiberID == null) {
306
+ throw Error(`Unexpected null ID for element at index ${index}`);
307
+ }
308
+ TestRenderer.create(
309
+ <React.Suspense fallback={null}>
310
+ <Suspender
311
+ fiberID={fiberID}
312
+ previousFiberCommits={allFiberCommits[index]}
313
rendererID={rendererID}
314
rootID={rootID}
315
/>
316
</React.Suspense>
317
);
318
});
196
- expect(suspenseResolved).toBe(true);
319
}
320
321
done();
@@ -232,15 +354,18 @@ describe('profiling', () => {
354
);
355
utils.act(() => store.stopProfiling());
356
235
- let suspenseResolved = false;
357
+ let interactions = null;
358
237
- function Suspender({ rendererID, rootID }) {
238
- const interactions = store.profilingCache.Interactions.read({
359
+ function Suspender({ previousInteractions, rendererID, rootID }) {
360
+ interactions = store.profilingCache.Interactions.read({
361
rendererID,
362
rootID,
363
});
242
- suspenseResolved = true;
243
- expect(interactions).toMatchSnapshot('Interactions');
364
+ if (previousInteractions != null) {
365
+ expect(interactions).toEqual(previousInteractions);
366
+ } else {
367
+ expect(interactions).toMatchSnapshot('Interactions');
368
+ }
369
return null;
370
}
371
@@ -250,12 +375,30 @@ describe('profiling', () => {
375
await utils.actSuspense(() =>
376
TestRenderer.create(
377
<React.Suspense fallback={null}>
253
- <Suspender rendererID={rendererID} rootID={rootID} />
378
+ <Suspender
379
+ previousInteractions={null}
380
+ rendererID={rendererID}
381
+ rootID={rootID}
382
+ />
383
</React.Suspense>
384
)
385
);
386
258
- expect(suspenseResolved).toBe(true);
387
+ expect(interactions).not.toBeNull();
388
+
389
+ exportImportHelper(rendererID, rootID);
390
+
391
+ await utils.actSuspense(() =>
392
+ TestRenderer.create(
393
+ <React.Suspense fallback={null}>
394
+ <Suspender
395
+ previousInteractions={interactions}
396
+ rendererID={rendererID}
397
+ rootID={rootID}
398
+ />
399
+ </React.Suspense>
400
+ )
401
+ );
402
403
done();
404
});
src/backend/agent.js
+3
-3
@@ -151,12 +151,12 @@ export default class Agent extends EventEmitter {
151
152
exportProfilingSummary = ({
153
profilingOperations,
154
- profilingSnapshot,
154
+ profilingSnapshots,
155
rendererID,
156
rootID,
157
}: {
158
profilingOperations: Array<any>,
159
- profilingSnapshot: Array<any>,
159
+ profilingSnapshots: Array<any>,
160
rendererID: number,
161
rootID: number,
162
}) => {
@@ -170,7 +170,7 @@ export default class Agent extends EventEmitter {
170
{
171
...rendererData,
172
profilingOperations,
173
- profilingSnapshot,
173
+ profilingSnapshots,
174
},
175
null,
176
2
src/devtools/ProfilingCache.js
+19
-2
@@ -144,8 +144,25 @@ export default class ProfilingCache {
144
return new Promise(resolve => {
145
const importedProfilingData = this._store.importedProfilingData;
146
if (importedProfilingData !== null) {
147
- // TODO (profiling) commit details
148
- // Copy from renderer getFiberCommits()
147
+ const { commitDetails } = (importedProfilingData: any);
148
+ if (commitDetails != null) {
149
+ const commitDurations = [];
150
+ commitDetails.forEach(({ actualDurations }, commitIndex) => {
151
+ for (let i = 0; i < actualDurations.length; i += 2) {
152
+ if (actualDurations[i] === fiberID) {
153
+ commitDurations.push(commitIndex, actualDurations[i + 1]);
154
+ break;
155
+ }
156
+ }
157
+ });
158
+ this._pendingFiberCommitsMap.set(`${rootID}-${fiberID}`, resolve);
159
+ this.onFiberCommits({
160
+ commitDurations,
161
+ fiberID,
162
+ rootID,
163
+ });
164
+ return;
165
+ }
166
} else if (this._store.profilingOperations.has(rootID)) {
167
this._pendingFiberCommitsMap.set(`${rootID}-${fiberID}`, resolve);
168
this._bridge.send('getFiberCommits', {
src/devtools/store.js
+6
-6
@@ -707,11 +707,11 @@ export default class Store extends EventEmitter {
707
708
_takeProfilingSnapshotRecursive = (
709
elementID: number,
710
- profilingSnapshot: Map<number, ProfilingSnapshotNode>
710
+ profilingSnapshots: Map<number, ProfilingSnapshotNode>
711
) => {
712
const element = this.getElementByID(elementID);
713
if (element !== null) {
714
- profilingSnapshot.set(elementID, {
714
+ profilingSnapshots.set(elementID, {
715
id: elementID,
716
children: element.children.slice(0),
717
displayName: element.displayName,
@@ -719,7 +719,7 @@ export default class Store extends EventEmitter {
719
});
720
721
element.children.forEach(childID =>
722
- this._takeProfilingSnapshotRecursive(childID, profilingSnapshot)
722
+ this._takeProfilingSnapshotRecursive(childID, profilingSnapshots)
723
);
724
}
725
};
@@ -1067,9 +1067,9 @@ export default class Store extends EventEmitter {
1067
this._profilingScreenshotsByRootID = new Map();
1068
this._profilingSnapshotsByRootID = new Map();
1069
this.roots.forEach(rootID => {
1070
- const profilingSnapshot = new Map();
1071
- this._profilingSnapshotsByRootID.set(rootID, profilingSnapshot);
1072
- this._takeProfilingSnapshotRecursive(rootID, profilingSnapshot);
1070
+ const profilingSnapshots = new Map();
1071
+ this._profilingSnapshotsByRootID.set(rootID, profilingSnapshots);
1072
+ this._takeProfilingSnapshotRecursive(rootID, profilingSnapshots);
1073
});
1074
}
1075
src/devtools/views/Profiler/CommitTreeBuilder.js
+6
-6
@@ -68,12 +68,12 @@ export function getCommitTree({
68
const nodes = new Map();
69
70
const { importedProfilingData } = store;
71
- const profilingSnapshot =
71
+ const profilingSnapshots =
72
importedProfilingData != null
73
? importedProfilingData.profilingSnapshots.get(rootID)
74
: store.profilingSnapshots.get(rootID);
75
76
- if (profilingSnapshot == null) {
76
+ if (profilingSnapshots == null) {
77
throw Error(`Could not find profiling snapshot for root "${rootID}"`);
78
}
79
@@ -83,7 +83,7 @@ export function getCommitTree({
83
0,
84
nodes,
85
profilingSummary.initialTreeBaseDurations,
86
- profilingSnapshot
86
+ profilingSnapshots
87
);
88
89
// Mutate the tree
@@ -133,9 +133,9 @@ function recursivelyInitializeTree(
133
parentID: number,
134
nodes: Map<number, CommitTreeNodeFrontend>,
135
initialTreeBaseDurations: Map<number, number>,
136
- profilingSnapshot: Map<number, ProfilingSnapshotNode>
136
+ profilingSnapshots: Map<number, ProfilingSnapshotNode>
137
): void {
138
- const node = profilingSnapshot.get(id);
138
+ const node = profilingSnapshots.get(id);
139
if (node != null) {
140
nodes.set(id, {
141
id,
@@ -152,7 +152,7 @@ function recursivelyInitializeTree(
152
id,
153
nodes,
154
initialTreeBaseDurations,
155
- profilingSnapshot
155
+ profilingSnapshots
156
)
157
);
158
}
src/devtools/views/Profiler/ProfilingImportExportButtons.js
+14
-27
@@ -5,6 +5,7 @@ import { ProfilerContext } from './ProfilerContext';
5
import Button from '../Button';
6
import ButtonIcon from '../ButtonIcon';
7
import { BridgeContext, StoreContext } from '../context';
8
+import { prepareProfilingExport, prepareProfilingImport } from './utils';
9
10
import styles from './ProfilingImportExportButtons.css';
11
@@ -20,32 +21,19 @@ export default function ProfilingImportExportButtons() {
21
const inputRef = useRef<HTMLInputElement | null>(null);
22
23
const downloadData = useCallback(() => {
23
- if (rootID === null) {
24
+ if (rendererID === null || rootID === null) {
25
return;
26
}
27
27
- const profilingOperationsForRoot = [];
28
- const operations = store.profilingOperations.get(rootID);
29
- if (operations != null) {
30
- operations.forEach(operations => {
31
- // Convert typed Array before JSON serialization, or it will be converted to an Object.
32
- profilingOperationsForRoot.push(Array.from(operations));
33
- });
34
- }
35
-
36
- const profilingSnapshotForRoot = [];
37
- const queue = [rootID];
38
- while (queue.length) {
39
- const id = queue.pop();
40
- profilingSnapshotForRoot.push([id, store.profilingSnapshots.get(id)]);
41
- }
42
-
43
- bridge.send('exportProfilingSummary', {
44
- profilingOperations: [[rootID, profilingOperationsForRoot]],
45
- profilingSnapshot: profilingSnapshotForRoot,
46
- rendererID,
47
- rootID,
48
- });
28
+ bridge.send(
29
+ 'exportProfilingSummary',
30
+ prepareProfilingExport(
31
+ store.profilingOperations,
32
+ store.profilingSnapshots,
33
+ rootID,
34
+ rendererID
35
+ )
36
+ );
37
}, [
38
bridge,
39
rendererID,
@@ -65,11 +53,10 @@ export default function ProfilingImportExportButtons() {
53
if (input !== null && input.files.length > 0) {
54
const fileReader = new FileReader();
55
fileReader.addEventListener('load', () => {
68
- const data = JSON.parse((fileReader.result: any));
69
- data.profilingOperations = new Map(data.profilingOperations);
70
- data.profilingSnapshot = new Map(data.profilingSnapshot);
56
+ const raw = ((fileReader.result: any): string);
57
+ const data = prepareProfilingImport(raw);
58
72
- // TODO (profiling) Version check; warn if older version.
59
+ // TODO (profiling) Catch possible version check error and show dialog.
60
61
store.importedProfilingData = ((data: any): ImportedProfilingData);
62
});
src/devtools/views/Profiler/utils.js
+53
-1
@@ -1,6 +1,10 @@
1
// @flow
2
3
-import type { CommitDetailsFrontend, CommitTreeFrontend } from './types';
3
+import type {
4
+ CommitDetailsFrontend,
5
+ CommitTreeFrontend,
6
+ ProfilingSnapshotNode,
7
+} from './types';
8
9
const commitGradient = [
10
'var(--color-commit-gradient-0)',
@@ -43,6 +47,54 @@ export const calculateSelfDuration = (
47
return selfDuration;
48
};
49
50
+export const prepareProfilingExport = (
51
+ profilingOperations: Map<number, Array<Uint32Array>>,
52
+ profilingSnapshots: Map<number, Map<number, ProfilingSnapshotNode>>,
53
+ rendererID: number,
54
+ rootID: number
55
+) => {
56
+ const profilingOperationsForRoot = [];
57
+ const operations = profilingOperations.get(rootID);
58
+ if (operations != null) {
59
+ operations.forEach(operations => {
60
+ // Convert typed Array before JSON serialization, or it will be converted to an Object.
61
+ profilingOperationsForRoot.push(Array.from<number>(operations));
62
+ });
63
+ }
64
+
65
+ // Convert Map to Object or JSON.stringify will clobber the contents.
66
+ const profilingSnapshotsForRoot = {};
67
+ const profilingSnapshotsMap = profilingSnapshots.get(rootID);
68
+ if (profilingSnapshotsMap != null) {
69
+ for (let [id, snapshot] of profilingSnapshotsMap.entries()) {
70
+ profilingSnapshotsForRoot[id] = snapshot;
71
+ }
72
+ }
73
+
74
+ return {
75
+ profilingOperations: profilingOperationsForRoot,
76
+ profilingSnapshots: profilingSnapshotsForRoot,
77
+ rendererID,
78
+ rootID,
79
+ };
80
+};
81
+
82
+export const prepareProfilingImport = (raw: string) => {
83
+ const parsed = JSON.parse(raw);
84
+
85
+ // TODO (profiling) Version check; throw if older version.
86
+
87
+ const entries = [];
88
+ Object.values(parsed.profilingSnapshots).forEach(snapshot => {
89
+ entries.push([(snapshot: any).id, snapshot]);
90
+ });
91
+
92
+ const rootID = parsed.profilingSummary.rootID;
93
+ parsed.profilingOperations = new Map([[rootID, parsed.profilingOperations]]);
94
+ parsed.profilingSnapshots = new Map([[rootID, new Map(entries)]]);
95
+ return parsed;
96
+};
97
+
98
export const getGradientColor = (value: number) => {
99
const maxIndex = commitGradient.length - 1;
100
let index;