mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
39cb74286d
commit
47b3b72076
4 changed files with 114 additions and 25 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
eventTracking.editingSessionHeartbeat(() => {
|
||||||
|
const editorType = ide.editorManager.getEditorType()
|
||||||
|
|
||||||
const segmentation = {
|
const segmentation = {
|
||||||
editorType: ide.editorManager.getEditorType(),
|
editorType,
|
||||||
}
|
}
|
||||||
eventTracking.editingSessionHeartbeat(segmentation)
|
|
||||||
|
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', () => {
|
||||||
|
|
80
services/web/frontend/js/infrastructure/cm6-performance.ts
Normal file
80
services/web/frontend/js/infrastructure/cm6-performance.ts
Normal 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())
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue