Merge pull request #10235 from overleaf/td-cm6-lag-metrics

Add CM6 typing lag metrics

GitOrigin-RevId: 7068647a28980d888ecb5a0203f0a3563ce4c713
This commit is contained in:
Tim Down 2022-11-01 10:37:00 +00:00 committed by Copybot
parent 2bb8bd6f14
commit a320982f79
2 changed files with 83 additions and 8 deletions

View file

@ -280,6 +280,11 @@ If the project has been renamed please look in your project list for a new proje
'Grammarly', 'Grammarly',
'SessionLength', 'SessionLength',
'Memory', 'Memory',
'Lags',
'NonLags',
'LongestLag',
'MeanLagsPerMeasure',
'MeanKeypressesPerMeasure',
] ]
for (const prop of perfProps) { for (const prop of perfProps) {

View file

@ -5,19 +5,31 @@ import grammarlyExtensionPresent from '../shared/utils/grammarly'
const TIMER_START_NAME = 'CM6-BeforeUpdate' const TIMER_START_NAME = 'CM6-BeforeUpdate'
const TIMER_END_NAME = 'CM6-AfterUpdate' const TIMER_END_NAME = 'CM6-AfterUpdate'
const TIMER_DOM_UPDATE_NAME = 'CM6-DomUpdate'
const TIMER_MEASURE_NAME = 'CM6-Update' const TIMER_MEASURE_NAME = 'CM6-Update'
let latestDocLength = 0 let latestDocLength = 0
const sessionStart = Date.now() const sessionStart = Date.now()
let performanceMeasureOptionsSupport = false let performanceOptionsSupport = false
// Check that performance.measure accepts an options object // Check that performance.mark and performance.measure accept an options object
try { try {
const testMeasureName = 'featureTest' const testMarkName = 'featureTestMark'
performance.measure(testMeasureName, { start: performance.now() }) performance.mark(testMarkName, {
startTime: performance.now(),
detail: { test: 1 },
})
performance.clearMarks(testMarkName)
const testMeasureName = 'featureTestMeasure'
performance.measure(testMeasureName, {
start: performance.now(),
detail: { test: 1 },
})
performance.clearMeasures(testMeasureName) performance.clearMeasures(testMeasureName)
performanceMeasureOptionsSupport = true
performanceOptionsSupport = true
} catch (e) {} } catch (e) {}
let performanceMemorySupport = false let performanceMemorySupport = false
@ -40,15 +52,26 @@ function isInputOrDelete(userEventType: string | undefined) {
) )
} }
// "keypress" is not strictly accurate; what we really mean is a user-initiated
// event that either inserts or deletes exactly one character. This corresponds
// to CM6 user event types input.type, delete.forward or delete.backward
function isKeypress(userEventType: string | undefined) {
return (
!!userEventType &&
['input.type', 'delete.forward', 'delete.backward'].includes(userEventType)
)
}
export function timedDispatch() { export function timedDispatch() {
let userEventsSinceDomUpdateCount = 0 let userEventsSinceDomUpdateCount = 0
let keypressesSinceDomUpdateCount = 0
return ( return (
view: EditorView, view: EditorView,
tr: Transaction, tr: Transaction,
dispatchFn: (tr: Transaction) => void dispatchFn: (tr: Transaction) => void
) => { ) => {
if (!performanceMeasureOptionsSupport) { if (!performanceOptionsSupport) {
dispatchFn(tr) dispatchFn(tr)
return return
} }
@ -64,6 +87,10 @@ export function timedDispatch() {
if (isInputOrDelete(userEventType)) { if (isInputOrDelete(userEventType)) {
++userEventsSinceDomUpdateCount ++userEventsSinceDomUpdateCount
if (isKeypress(userEventType)) {
++keypressesSinceDomUpdateCount
}
performance.measure(TIMER_MEASURE_NAME, { performance.measure(TIMER_MEASURE_NAME, {
start: TIMER_START_NAME, start: TIMER_START_NAME,
end: TIMER_END_NAME, end: TIMER_END_NAME,
@ -75,7 +102,11 @@ export function timedDispatch() {
view.requestMeasure({ view.requestMeasure({
key: 'inputEventCounter', key: 'inputEventCounter',
read() { read() {
performance.mark(TIMER_DOM_UPDATE_NAME, {
detail: { keypressesSinceDomUpdateCount },
})
userEventsSinceDomUpdateCount = 0 userEventsSinceDomUpdateCount = 0
keypressesSinceDomUpdateCount = 0
}, },
}) })
} }
@ -120,6 +151,10 @@ export function reportCM6Perf() {
'measure' 'measure'
) as PerformanceMeasure[] ) as PerformanceMeasure[]
performance.clearMeasures(TIMER_MEASURE_NAME)
performance.clearMarks(TIMER_START_NAME)
performance.clearMarks(TIMER_END_NAME)
const inputEvents = cm6Entries.filter(({ detail }) => const inputEvents = cm6Entries.filter(({ detail }) =>
isInputOrDelete(detail.userEventType) isInputOrDelete(detail.userEventType)
) )
@ -141,10 +176,40 @@ export function reportCM6Perf() {
const grammarly = grammarlyExtensionPresent() const grammarly = grammarlyExtensionPresent()
const sessionLength = Math.floor((Date.now() - sessionStart) / 1000) // In seconds const sessionLength = Math.floor((Date.now() - sessionStart) / 1000) // In seconds
performance.clearMeasures(TIMER_MEASURE_NAME)
const memory = performanceMemorySupport ? measureMemoryUsage() : null const memory = performanceMemorySupport ? measureMemoryUsage() : null
// Get entries for keypress counts between DOM updates
const domUpdateEntries = performance.getEntriesByName(
TIMER_DOM_UPDATE_NAME,
'mark'
) as PerformanceMeasure[]
performance.clearMarks(TIMER_DOM_UPDATE_NAME)
let lags = 0
let nonLags = 0
let longestLag = 0
let totalKeypressCount = 0
for (const entry of domUpdateEntries) {
const keypressCount = entry.detail.keypressesSinceDomUpdateCount
if (keypressCount === 1) {
++nonLags
} else if (keypressCount > 1) {
++lags
}
if (keypressCount > longestLag) {
longestLag = keypressCount
}
totalKeypressCount += keypressCount
}
const meanLagsPerMeasure = round(lags / (lags + nonLags), 4)
const meanKeypressesPerMeasure = round(
totalKeypressCount / (lags + nonLags),
4
)
return { return {
max, max,
mean, mean,
@ -156,6 +221,11 @@ export function reportCM6Perf() {
grammarly, grammarly,
sessionLength, sessionLength,
memory, memory,
lags,
nonLags,
longestLag,
meanLagsPerMeasure,
meanKeypressesPerMeasure,
} }
} }