-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Onyx.printMetrics format #89
Changes from all commits
a06856e
e8413b0
5fae00c
9a1e475
d15c407
897ba8b
541279a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,11 @@ import _ from 'underscore'; | |
*/ | ||
let stats = {}; | ||
|
||
/* For some reason `performance.now()` does not start from `0` but a very large offset | ||
* like `508,080,000` see: https://github.com/facebook/react-native/issues/30069 | ||
* Capturing an offset allows us to record start/ends times relative to app launch time */ | ||
const APP_LAUNCH_TIME = performance.now(); | ||
Comment on lines
+9
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've found this issue after I switched to the "new" debugging flow today: https://expensify.slack.com/archives/C01GTK53T8Q/p1626422647390900?thread_ts=1626297548.276300&cid=C01GTK53T8Q There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's related to the "old" chrome debugger not being used |
||
|
||
/** | ||
* Wraps a function with metrics capturing logic | ||
* @param {function} func | ||
|
@@ -20,7 +25,7 @@ function decorateWithMetrics(func, alias = func.name) { | |
stats[alias] = []; | ||
|
||
function decorated(...args) { | ||
const startTime = performance.now(); | ||
const startTime = performance.now() - APP_LAUNCH_TIME; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to understand the reliability of this next usage of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't make anything more or less reliable, just keeps our start/end times relative to app launch Differences between On the old flow, when this was provided by Chrome, the value will be very close to By capturing our own reference point APP_LAUNCH_TIME we keep start/end times relative to that point There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I remove this, I'll get this ATM: "Last call finished at: 8907.4min" so it's not midnight There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haha, ok awesome, thanks for the explanation. |
||
|
||
const originalPromise = func.apply(this, args); | ||
|
||
|
@@ -30,7 +35,7 @@ function decorateWithMetrics(func, alias = func.name) { | |
* */ | ||
originalPromise | ||
.finally(() => { | ||
const endTime = performance.now(); | ||
const endTime = performance.now() - APP_LAUNCH_TIME; | ||
|
||
if (!_.has(stats, alias)) { | ||
stats[alias] = []; | ||
|
@@ -62,9 +67,12 @@ function sum(list, prop) { | |
} | ||
|
||
/** | ||
* Returns total, average time and all captured stats mapped under | ||
* summaries.methodName -> method stats | ||
* @returns {{averageTime: number, summaries: Record<string, Object>, totalTime: number}} | ||
* Aggregates and returns benchmark information | ||
* @returns {{summaries: Record<string, Object>, totalTime: number, lastCompleteCall: *}} | ||
* An object with | ||
* - `totalTime` - total time spent by decorated methods | ||
* - `lastCompleteCall` - millisecond since launch the last call completed at | ||
* - `summaries` - mapping of all captured stats: summaries.methodName -> method stats | ||
*/ | ||
function getMetrics() { | ||
const summaries = _.chain(stats) | ||
|
@@ -74,30 +82,42 @@ function getMetrics() { | |
const avg = (total / calls.length) || 0; | ||
const max = _.max(calls, 'duration').duration || 0; | ||
const min = _.min(calls, 'duration').duration || 0; | ||
const lastCall = _.max(calls, 'endTime'); | ||
|
||
return [methodName, { | ||
methodName, | ||
total, | ||
max, | ||
min, | ||
avg, | ||
lastCall, | ||
calls, | ||
}]; | ||
}) | ||
.object() // Create a map like methodName -> StatSummary | ||
.value(); | ||
|
||
const totalTime = sum(_.values(summaries), 'total'); | ||
const averageTime = (totalTime / _.size(summaries)) || 0; | ||
kidroca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const lastCompleteCall = _.max(_.values(summaries), ['lastCall', 'endTime']).lastCall; | ||
|
||
return { | ||
totalTime, | ||
averageTime, | ||
summaries, | ||
lastCompleteCall, | ||
}; | ||
} | ||
|
||
function toHumanReadableDuration(millis) { | ||
/** | ||
* Convert milliseconds to human readable time | ||
* @param {number} millis | ||
* @param {boolean} [raw=false] | ||
* @returns {string|number} | ||
*/ | ||
function toDuration(millis, raw = false) { | ||
if (raw) { | ||
return millis; | ||
} | ||
|
||
const minute = 60 * 1000; | ||
if (millis > minute) { | ||
return `${(millis / minute).toFixed(1)}min`; | ||
|
@@ -115,30 +135,55 @@ function toHumanReadableDuration(millis) { | |
* Print extensive information on the dev console | ||
* max, min, average, total time for each method | ||
* and a table of individual calls | ||
* | ||
* @param {boolean} [raw=false] setting this to true will print raw instead of human friendly times | ||
* Useful when you copy the printed table to excel and let excel do the number formatting | ||
kidroca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
function printMetrics() { | ||
const {totalTime, averageTime, summaries} = getMetrics(); | ||
function printMetrics(raw = false) { | ||
const {totalTime, summaries, lastCompleteCall = {endTime: -1}} = getMetrics(); | ||
kidroca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/* eslint-disable no-console */ | ||
console.group('Onyx Benchmark'); | ||
console.info(' Total: ', toHumanReadableDuration(totalTime)); | ||
console.info(' Average: ', toHumanReadableDuration(averageTime)); | ||
|
||
_.chain(summaries) | ||
const prettyData = _.chain(summaries) | ||
.filter(method => method.avg > 0) | ||
.sortBy('avg') | ||
.reverse() | ||
.forEach(({calls, methodName, ...summary}) => { | ||
const times = _.map(summary, (value, key) => `${key}: ${toHumanReadableDuration(value)}`); | ||
|
||
console.groupCollapsed(`${methodName}\n ${times.join('\n ')} \n calls: ${calls.length}`); | ||
console.table(calls.map(call => ({ | ||
startTime: toHumanReadableDuration(call.startTime), | ||
endTime: toHumanReadableDuration(call.endTime), | ||
duration: toHumanReadableDuration(call.duration), | ||
.map(({ | ||
calls, methodName, lastCall, ...summary | ||
}) => { | ||
const prettyTimes = _.chain(summary) | ||
.map((value, key) => ([key, toDuration(value, raw)])) | ||
.object() | ||
.value(); | ||
|
||
const prettyCalls = calls.map(call => ({ | ||
startTime: toDuration(call.startTime, raw), | ||
endTime: toDuration(call.endTime, raw), | ||
duration: toDuration(call.duration, raw), | ||
args: JSON.stringify(call.args) | ||
}))); | ||
console.groupEnd(); | ||
}); | ||
})); | ||
|
||
return { | ||
methodName, | ||
...prettyTimes, | ||
'time last call completed': toDuration(lastCall.endTime, raw), | ||
calls: calls.length, | ||
prettyCalls, | ||
}; | ||
}) | ||
.value(); | ||
|
||
/* eslint-disable no-console */ | ||
console.group('Onyx Benchmark'); | ||
console.info(' Total: ', toDuration(totalTime, raw)); | ||
console.info(' Last call finished at: ', toDuration(lastCompleteCall.endTime, raw)); | ||
|
||
console.table(prettyData.map(({prettyCalls, ...summary}) => summary)); | ||
|
||
prettyData.forEach((method) => { | ||
console.groupCollapsed(`[${method.methodName}] individual calls: `); | ||
console.table(method.prettyCalls); | ||
console.groupEnd(); | ||
}); | ||
|
||
console.groupEnd(); | ||
/* eslint-enable */ | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, since it is supposed to be more reliable than
Date
, but still has this issue? 🤔Not a blocker, but I'm not sure I'd expect it this to be
0
.performance.now()
returns elapsed since the time origin (or is supposed to at least).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it will never be
0
but the origin is definitely not app launch the benchmark would say something like "Last call finished at: 3940min" which is uselessThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it that makes more sense to me now. Thanks for the explanation.