Merge pull request #9938 from overleaf/revert-9935-revert-9901-as-td-cm6-perf-editing-sessions

Bring back reporting of CM6 perf measurement in editing sessions

GitOrigin-RevId: 236c6e23f09a2ddaebf1c231ed77404c4b64179d
This commit is contained in:
Alasdair Smith 2022-10-12 11:17:24 +01:00 committed by Copybot
parent 39cb74286d
commit 47b3b72076
4 changed files with 114 additions and 25 deletions

View file

@ -10,7 +10,7 @@ async function updateEditingSession(req, res, next) {
} }
const userId = SessionManager.getLoggedInUserId(req.session) const userId = SessionManager.getLoggedInUserId(req.session)
const { projectId } = req.params const { projectId } = req.params
const segmentation = _getSegmentation(req) const segmentation = req.body.segmentation || {}
let countryCode = null let countryCode = null
if (userId) { if (userId) {
@ -45,20 +45,6 @@ function recordEvent(req, res, next) {
res.sendStatus(202) res.sendStatus(202)
} }
function _getSegmentation(req) {
const segmentation = req.body ? req.body.segmentation : null
const cleanedSegmentation = {}
if (
segmentation &&
segmentation.editorType &&
typeof segmentation.editorType === 'string' &&
segmentation.editorType.length < 100
) {
cleanedSegmentation.editorType = segmentation.editorType
}
return cleanedSegmentation
}
module.exports = { module.exports = {
updateEditingSession, updateEditingSession,
recordEvent, recordEvent,

View file

@ -66,6 +66,7 @@ import './features/share-project-modal/controllers/react-share-project-modal-con
import './features/source-editor/controllers/editor-switch-controller' import './features/source-editor/controllers/editor-switch-controller'
import getMeta from './utils/meta' import getMeta from './utils/meta'
import { cleanupServiceWorker } from './utils/service-worker-cleanup' import { cleanupServiceWorker } from './utils/service-worker-cleanup'
import { reportCM6Perf } from './infrastructure/cm6-performance'
App.controller( App.controller(
'IdeController', 'IdeController',
@ -256,10 +257,30 @@ If the project has been renamed please look in your project list for a new proje
}) })
ide.editingSessionHeartbeat = () => { ide.editingSessionHeartbeat = () => {
const segmentation = { eventTracking.editingSessionHeartbeat(() => {
editorType: ide.editorManager.getEditorType(), const editorType = ide.editorManager.getEditorType()
}
eventTracking.editingSessionHeartbeat(segmentation) const segmentation = {
editorType,
}
if (editorType === 'cm6') {
const cm6PerfData = reportCM6Perf()
// Ignore if no typing has happened
if (cm6PerfData.numberOfEntries > 0) {
segmentation.cm6PerfMax = cm6PerfData.max
segmentation.cm6PerfMean = cm6PerfData.mean
segmentation.cm6PerfMedian = cm6PerfData.median
segmentation.cm6PerfNinetyFifthPercentile =
cm6PerfData.ninetyFifthPercentile
segmentation.cm6PerfDocLength = cm6PerfData.docLength
segmentation.cm6PerfNumberOfEntries = cm6PerfData.numberOfEntries
}
}
return segmentation
})
} }
$scope.$on('cursor:editor:update', () => { $scope.$on('cursor:editor:update', () => {

View file

@ -0,0 +1,80 @@
import { Transaction } from '@codemirror/state'
const TIMER_START_NAME = 'CM6-BeforeUpdate'
const TIMER_END_NAME = 'CM6-AfterUpdate'
const TIMER_MEASURE_NAME = 'CM6-Update'
let latestDocLength = 0
export function timedDispatch(dispatchFn: (tr: Transaction) => void) {
return (tr: Transaction) => {
performance.mark(TIMER_START_NAME)
dispatchFn(tr)
performance.mark(TIMER_END_NAME)
if (tr.isUserEvent('input')) {
performance.measure(TIMER_MEASURE_NAME, TIMER_START_NAME, TIMER_END_NAME)
}
latestDocLength = tr.state.doc.length
}
}
function calculateMean(durations: number[]) {
if (durations.length === 0) return 0
const sum = durations.reduce((acc, entry) => acc + entry, 0)
return sum / durations.length
}
function calculateMedian(sortedDurations: number[]) {
if (sortedDurations.length === 0) return 0
const middle = Math.floor(sortedDurations.length / 2)
if (sortedDurations.length % 2 === 0) {
return (sortedDurations[middle - 1] + sortedDurations[middle]) / 2
}
return sortedDurations[middle]
}
function calculate95thPercentile(sortedDurations: number[]) {
if (sortedDurations.length === 0) return 0
const index = Math.round((sortedDurations.length - 1) * 0.95)
return sortedDurations[index]
}
export function reportCM6Perf() {
// Get entries triggered by keystrokes
const cm6Entries = performance.getEntriesByName(
TIMER_MEASURE_NAME,
'measure'
) as PerformanceMeasure[]
const inputDurations = cm6Entries
.map(({ duration }) => duration)
.sort((a, b) => a - b)
const max = inputDurations.reduce((a, b) => Math.max(a, b), 0)
const mean = calculateMean(inputDurations)
const median = calculateMedian(inputDurations)
const ninetyFifthPercentile = calculate95thPercentile(inputDurations)
performance.clearMeasures(TIMER_MEASURE_NAME)
return {
max,
mean,
median,
ninetyFifthPercentile,
docLength: latestDocLength,
numberOfEntries: inputDurations.length,
}
}
window._reportCM6Perf = () => {
console.log(reportCM6Perf())
}

View file

@ -70,11 +70,13 @@ App.factory('eventTracking', function ($http, localStorage) {
} }
}, },
editingSessionHeartbeat(segmentation) { editingSessionHeartbeat(segmentationCb = () => {}) {
sl_console.log('[Event] heartbeat trigger', segmentation) sl_console.log('[Event] heartbeat trigger')
if (!(nextHeartbeat <= new Date())) {
return // If the next heartbeat is in the future, stop
} if (nextHeartbeat > new Date()) return
const segmentation = segmentationCb()
sl_console.log('[Event] send heartbeat request', segmentation) sl_console.log('[Event] send heartbeat request', segmentation)
_sendEditingSessionHeartbeat(segmentation) _sendEditingSessionHeartbeat(segmentation)
@ -90,7 +92,7 @@ App.factory('eventTracking', function ($http, localStorage) {
? (heartbeatsSent - 2) * 60 ? (heartbeatsSent - 2) * 60
: 300 : 300
return (nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate()) nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate()
}, },
sendMB, sendMB,