Skip to content

Commit c0e30e6

Browse files
authored
Merge pull request #89 from kidroca/kidroca/better-print-format
Update Onyx.printMetrics format
2 parents 86be759 + 541279a commit c0e30e6

File tree

2 files changed

+74
-29
lines changed

2 files changed

+74
-29
lines changed

lib/decorateWithMetrics.js

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import _ from 'underscore';
66
*/
77
let stats = {};
88

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+
914
/**
1015
* Wraps a function with metrics capturing logic
1116
* @param {function} func
@@ -20,7 +25,7 @@ function decorateWithMetrics(func, alias = func.name) {
2025
stats[alias] = [];
2126

2227
function decorated(...args) {
23-
const startTime = performance.now();
28+
const startTime = performance.now() - APP_LAUNCH_TIME;
2429

2530
const originalPromise = func.apply(this, args);
2631

@@ -30,7 +35,7 @@ function decorateWithMetrics(func, alias = func.name) {
3035
* */
3136
originalPromise
3237
.finally(() => {
33-
const endTime = performance.now();
38+
const endTime = performance.now() - APP_LAUNCH_TIME;
3439

3540
if (!_.has(stats, alias)) {
3641
stats[alias] = [];
@@ -62,9 +67,12 @@ function sum(list, prop) {
6267
}
6368

6469
/**
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
6876
*/
6977
function getMetrics() {
7078
const summaries = _.chain(stats)
@@ -74,30 +82,42 @@ function getMetrics() {
7482
const avg = (total / calls.length) || 0;
7583
const max = _.max(calls, 'duration').duration || 0;
7684
const min = _.min(calls, 'duration').duration || 0;
85+
const lastCall = _.max(calls, 'endTime');
7786

7887
return [methodName, {
7988
methodName,
8089
total,
8190
max,
8291
min,
8392
avg,
93+
lastCall,
8494
calls,
8595
}];
8696
})
8797
.object() // Create a map like methodName -> StatSummary
8898
.value();
8999

90100
const totalTime = sum(_.values(summaries), 'total');
91-
const averageTime = (totalTime / _.size(summaries)) || 0;
101+
const lastCompleteCall = _.max(_.values(summaries), ['lastCall', 'endTime']).lastCall;
92102

93103
return {
94104
totalTime,
95-
averageTime,
96105
summaries,
106+
lastCompleteCall,
97107
};
98108
}
99109

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+
101121
const minute = 60 * 1000;
102122
if (millis > minute) {
103123
return `${(millis / minute).toFixed(1)}min`;
@@ -115,30 +135,55 @@ function toHumanReadableDuration(millis) {
115135
* Print extensive information on the dev console
116136
* max, min, average, total time for each method
117137
* 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
118141
*/
119-
function printMetrics() {
120-
const {totalTime, averageTime, summaries} = getMetrics();
142+
function printMetrics(raw = false) {
143+
const {totalTime, summaries, lastCompleteCall = {endTime: -1}} = getMetrics();
121144

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)
128147
.sortBy('avg')
129148
.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),
138161
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+
142187
console.groupEnd();
143188
/* eslint-enable */
144189
}

tests/unit/decorateWithMetricsTest.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('decorateWithMetrics', () => {
2424
const metrics = getMetrics();
2525
expect(metrics).toEqual(expect.objectContaining({
2626
totalTime: expect.any(Number),
27-
averageTime: expect.any(Number),
27+
lastCompleteCall: expect.any(Object),
2828
summaries: expect.objectContaining({mockFn: expect.any(Object)}),
2929
}));
3030

@@ -264,7 +264,7 @@ describe('decorateWithMetrics', () => {
264264
// THEN stats should be empty and the total time 0
265265
expect(result.summaries).toEqual({});
266266
expect(result.totalTime).toEqual(0);
267-
expect(result.averageTime).toEqual(0);
267+
expect(result.lastCompleteCall).not.toBeDefined();
268268
});
269269

270270
it('Should calculate total and average correctly', () => {

0 commit comments

Comments
 (0)