@@ -6,6 +6,11 @@ import _ from 'underscore';
6
6
*/
7
7
let stats = { } ;
8
8
9
+ /* For some reason `performance.now()` does not start from `0` but a very large offset
10
+ * like `508,080,000` see: https://github.com/facebook/react-native/issues/30069
11
+ * Capturing an offset allows us to record start/ends times relative to app launch time */
12
+ const APP_LAUNCH_TIME = performance . now ( ) ;
13
+
9
14
/**
10
15
* Wraps a function with metrics capturing logic
11
16
* @param {function } func
@@ -20,7 +25,7 @@ function decorateWithMetrics(func, alias = func.name) {
20
25
stats [ alias ] = [ ] ;
21
26
22
27
function decorated ( ...args ) {
23
- const startTime = performance . now ( ) ;
28
+ const startTime = performance . now ( ) - APP_LAUNCH_TIME ;
24
29
25
30
const originalPromise = func . apply ( this , args ) ;
26
31
@@ -30,7 +35,7 @@ function decorateWithMetrics(func, alias = func.name) {
30
35
* */
31
36
originalPromise
32
37
. finally ( ( ) => {
33
- const endTime = performance . now ( ) ;
38
+ const endTime = performance . now ( ) - APP_LAUNCH_TIME ;
34
39
35
40
if ( ! _ . has ( stats , alias ) ) {
36
41
stats [ alias ] = [ ] ;
@@ -62,9 +67,12 @@ function sum(list, prop) {
62
67
}
63
68
64
69
/**
65
- * Returns total, average time and all captured stats mapped under
66
- * summaries.methodName -> method stats
67
- * @returns {{averageTime: number, summaries: Record<string, Object>, totalTime: number} }
70
+ * Aggregates and returns benchmark information
71
+ * @returns {{summaries: Record<string, Object>, totalTime: number, lastCompleteCall: *} }
72
+ * An object with
73
+ * - `totalTime` - total time spent by decorated methods
74
+ * - `lastCompleteCall` - millisecond since launch the last call completed at
75
+ * - `summaries` - mapping of all captured stats: summaries.methodName -> method stats
68
76
*/
69
77
function getMetrics ( ) {
70
78
const summaries = _ . chain ( stats )
@@ -74,30 +82,42 @@ function getMetrics() {
74
82
const avg = ( total / calls . length ) || 0 ;
75
83
const max = _ . max ( calls , 'duration' ) . duration || 0 ;
76
84
const min = _ . min ( calls , 'duration' ) . duration || 0 ;
85
+ const lastCall = _ . max ( calls , 'endTime' ) ;
77
86
78
87
return [ methodName , {
79
88
methodName,
80
89
total,
81
90
max,
82
91
min,
83
92
avg,
93
+ lastCall,
84
94
calls,
85
95
} ] ;
86
96
} )
87
97
. object ( ) // Create a map like methodName -> StatSummary
88
98
. value ( ) ;
89
99
90
100
const totalTime = sum ( _ . values ( summaries ) , 'total' ) ;
91
- const averageTime = ( totalTime / _ . size ( summaries ) ) || 0 ;
101
+ const lastCompleteCall = _ . max ( _ . values ( summaries ) , [ 'lastCall' , 'endTime' ] ) . lastCall ;
92
102
93
103
return {
94
104
totalTime,
95
- averageTime,
96
105
summaries,
106
+ lastCompleteCall,
97
107
} ;
98
108
}
99
109
100
- function toHumanReadableDuration ( millis ) {
110
+ /**
111
+ * Convert milliseconds to human readable time
112
+ * @param {number } millis
113
+ * @param {boolean } [raw=false]
114
+ * @returns {string|number }
115
+ */
116
+ function toDuration ( millis , raw = false ) {
117
+ if ( raw ) {
118
+ return millis ;
119
+ }
120
+
101
121
const minute = 60 * 1000 ;
102
122
if ( millis > minute ) {
103
123
return `${ ( millis / minute ) . toFixed ( 1 ) } min` ;
@@ -115,30 +135,55 @@ function toHumanReadableDuration(millis) {
115
135
* Print extensive information on the dev console
116
136
* max, min, average, total time for each method
117
137
* and a table of individual calls
138
+ *
139
+ * @param {boolean } [raw=false] setting this to true will print raw instead of human friendly times
140
+ * Useful when you copy the printed table to excel and let excel do the number formatting
118
141
*/
119
- function printMetrics ( ) {
120
- const { totalTime, averageTime , summaries } = getMetrics ( ) ;
142
+ function printMetrics ( raw = false ) {
143
+ const { totalTime, summaries , lastCompleteCall = { endTime : - 1 } } = getMetrics ( ) ;
121
144
122
- /* eslint-disable no-console */
123
- console . group ( 'Onyx Benchmark' ) ;
124
- console . info ( ' Total: ' , toHumanReadableDuration ( totalTime ) ) ;
125
- console . info ( ' Average: ' , toHumanReadableDuration ( averageTime ) ) ;
126
-
127
- _ . chain ( summaries )
145
+ const prettyData = _ . chain ( summaries )
146
+ . filter ( method => method . avg > 0 )
128
147
. sortBy ( 'avg' )
129
148
. reverse ( )
130
- . forEach ( ( { calls, methodName, ...summary } ) => {
131
- const times = _ . map ( summary , ( value , key ) => `${ key } : ${ toHumanReadableDuration ( value ) } ` ) ;
132
-
133
- console . groupCollapsed ( `${ methodName } \n ${ times . join ( '\n ' ) } \n calls: ${ calls . length } ` ) ;
134
- console . table ( calls . map ( call => ( {
135
- startTime : toHumanReadableDuration ( call . startTime ) ,
136
- endTime : toHumanReadableDuration ( call . endTime ) ,
137
- duration : toHumanReadableDuration ( call . duration ) ,
149
+ . map ( ( {
150
+ calls, methodName, lastCall, ...summary
151
+ } ) => {
152
+ const prettyTimes = _ . chain ( summary )
153
+ . map ( ( value , key ) => ( [ key , toDuration ( value , raw ) ] ) )
154
+ . object ( )
155
+ . value ( ) ;
156
+
157
+ const prettyCalls = calls . map ( call => ( {
158
+ startTime : toDuration ( call . startTime , raw ) ,
159
+ endTime : toDuration ( call . endTime , raw ) ,
160
+ duration : toDuration ( call . duration , raw ) ,
138
161
args : JSON . stringify ( call . args )
139
- } ) ) ) ;
140
- console . groupEnd ( ) ;
141
- } ) ;
162
+ } ) ) ;
163
+
164
+ return {
165
+ methodName,
166
+ ...prettyTimes ,
167
+ 'time last call completed' : toDuration ( lastCall . endTime , raw ) ,
168
+ calls : calls . length ,
169
+ prettyCalls,
170
+ } ;
171
+ } )
172
+ . value ( ) ;
173
+
174
+ /* eslint-disable no-console */
175
+ console . group ( 'Onyx Benchmark' ) ;
176
+ console . info ( ' Total: ' , toDuration ( totalTime , raw ) ) ;
177
+ console . info ( ' Last call finished at: ' , toDuration ( lastCompleteCall . endTime , raw ) ) ;
178
+
179
+ console . table ( prettyData . map ( ( { prettyCalls, ...summary } ) => summary ) ) ;
180
+
181
+ prettyData . forEach ( ( method ) => {
182
+ console . groupCollapsed ( `[${ method . methodName } ] individual calls: ` ) ;
183
+ console . table ( method . prettyCalls ) ;
184
+ console . groupEnd ( ) ;
185
+ } ) ;
186
+
142
187
console . groupEnd ( ) ;
143
188
/* eslint-enable */
144
189
}
0 commit comments