Merge pull request #5327 from overleaf/bg-extend-docupdater-profiler

[docupdater] record synchronous operations in profiler

GitOrigin-RevId: 994139e7f3fb04645e435273f5143e1fa6091be2
This commit is contained in:
Brian Gough 2021-10-06 10:09:44 +01:00 committed by Copybot
parent 94c208311c
commit b4cd9b089d
3 changed files with 25 additions and 8 deletions

View file

@ -21,7 +21,8 @@ const deltaMs = function (ta, tb) {
module.exports = Profiler = (function () { module.exports = Profiler = (function () {
Profiler = class Profiler { Profiler = class Profiler {
static initClass() { static initClass() {
this.prototype.LOG_CUTOFF_TIME = 1000 this.prototype.LOG_CUTOFF_TIME = 15 * 1000
this.prototype.LOG_SYNC_CUTOFF_TIME = 1000
} }
constructor(name, args) { constructor(name, args) {
@ -30,20 +31,24 @@ module.exports = Profiler = (function () {
this.t0 = this.t = process.hrtime() this.t0 = this.t = process.hrtime()
this.start = new Date() this.start = new Date()
this.updateTimes = [] this.updateTimes = []
this.totalSyncTime = 0
} }
log(label) { log(label, options = {}) {
const t1 = process.hrtime() const t1 = process.hrtime()
const dtMilliSec = deltaMs(t1, this.t) const dtMilliSec = deltaMs(t1, this.t)
this.t = t1 this.t = t1
this.totalSyncTime += options.sync ? dtMilliSec : 0
this.updateTimes.push([label, dtMilliSec]) // timings in ms this.updateTimes.push([label, dtMilliSec]) // timings in ms
return this // make it chainable return this // make it chainable
} }
end(message) { end(message) {
const totalTime = deltaMs(this.t, this.t0) const totalTime = deltaMs(this.t, this.t0)
if (totalTime > this.LOG_CUTOFF_TIME) { const exceedsCutoff = totalTime > this.LOG_CUTOFF_TIME
// log anything greater than cutoff const exceedsSyncCutoff = this.totalSyncTime > this.LOG_SYNC_CUTOFF_TIME
if (exceedsCutoff || exceedsSyncCutoff) {
// log anything greater than cutoffs
const args = {} const args = {}
for (const k in this.args) { for (const k in this.args) {
const v = this.args[k] const v = this.args[k]
@ -52,7 +57,8 @@ module.exports = Profiler = (function () {
args.updateTimes = this.updateTimes args.updateTimes = this.updateTimes
args.start = this.start args.start = this.start
args.end = new Date() args.end = new Date()
logger.debug(args, this.name) args.status = { exceedsCutoff, exceedsSyncCutoff }
logger.warn(args, this.name)
} }
return totalTime return totalTime
} }

View file

@ -167,7 +167,7 @@ module.exports = UpdateManager = {
var profile = new Profiler('applyUpdate', { project_id, doc_id }) var profile = new Profiler('applyUpdate', { project_id, doc_id })
UpdateManager._sanitizeUpdate(update) UpdateManager._sanitizeUpdate(update)
profile.log('sanitizeUpdate') profile.log('sanitizeUpdate', { sync: true })
return DocumentManager.getDoc( return DocumentManager.getDoc(
project_id, project_id,
doc_id, doc_id,
@ -182,6 +182,7 @@ module.exports = UpdateManager = {
) )
} }
const previousVersion = version const previousVersion = version
const incomingUpdateVersion = update.v
return ShareJsUpdateManager.applyUpdate( return ShareJsUpdateManager.applyUpdate(
project_id, project_id,
doc_id, doc_id,
@ -189,7 +190,11 @@ module.exports = UpdateManager = {
lines, lines,
version, version,
function (error, updatedDocLines, version, appliedOps) { function (error, updatedDocLines, version, appliedOps) {
profile.log('sharejs.applyUpdate') profile.log('sharejs.applyUpdate', {
// only synchronous when the update applies directly to the
// doc version, otherwise getPreviousDocOps is called.
sync: incomingUpdateVersion === previousVersion,
})
if (error != null) { if (error != null) {
return callback(error) return callback(error)
} }
@ -206,7 +211,7 @@ module.exports = UpdateManager = {
projectHistoryId, projectHistoryId,
lines lines
) )
profile.log('RangesManager.applyUpdate') profile.log('RangesManager.applyUpdate', { sync: true })
if (error != null) { if (error != null) {
return callback(error) return callback(error)
} }

View file

@ -26,6 +26,8 @@ const { EventEmitter } = require('events')
const queue = require('./syncqueue') const queue = require('./syncqueue')
const types = require('../types') const types = require('../types')
const Profiler = require('../../Profiler')
const isArray = o => Object.prototype.toString.call(o) === '[object Array]' const isArray = o => Object.prototype.toString.call(o) === '[object Array]'
// This constructor creates a new Model object. There will be one model object // This constructor creates a new Model object. There will be one model object
@ -167,6 +169,7 @@ module.exports = Model = function (db, options) {
if (ops.length > 0) { if (ops.length > 0) {
try { try {
const profile = new Profiler('model.transform')
// If there's enough ops, it might be worth spinning this out into a webworker thread. // If there's enough ops, it might be worth spinning this out into a webworker thread.
for (const oldOp of Array.from(ops)) { for (const oldOp of Array.from(ops)) {
// Dup detection works by sending the id(s) the op has been submitted with previously. // Dup detection works by sending the id(s) the op has been submitted with previously.
@ -183,6 +186,7 @@ module.exports = Model = function (db, options) {
opData.op = doc.type.transform(opData.op, oldOp.op, 'left') opData.op = doc.type.transform(opData.op, oldOp.op, 'left')
opData.v++ opData.v++
} }
profile.log('transform', { sync: true }).end()
} catch (error1) { } catch (error1) {
error = error1 error = error1
return callback(error.message) return callback(error.message)
@ -190,7 +194,9 @@ module.exports = Model = function (db, options) {
} }
try { try {
const profile = new Profiler('model.apply')
snapshot = doc.type.apply(doc.snapshot, opData.op) snapshot = doc.type.apply(doc.snapshot, opData.op)
profile.log('model.apply', { sync: true }).end()
} catch (error2) { } catch (error2) {
error = error2 error = error2
return callback(error.message) return callback(error.message)