Add a whole bunch of new ve.track() events for instrumenting loading and saving

Renamed events:
* performance.domLoad --> performance.system.domLoad
* performance.domSave --> performance.system.domSave

New events:
* performance.system.activation: total load time
* performance.system.domDiff: timing of paction=diff; like .domSave
* performance.system.domSerialize: timing of paction=serialize; like .domSave
* behavior.lastTransactionTillSaveDialogOpen: time from last transaction
  until user opened save dialog
* behavior.saveDialogOpenTillSave: time from save dialog opening to user
  clicking save
* behavior.saveDialogOpenTillReview: time from save dialog opening to user
  clicking review (skipped when a cached diff is shown)
* behavior.saveDialogClose: when user closes save dialog; duration is time
* performance.user.saveComplete: time from user clicking save to successful
  save completion; 'retries' indicates # of badtoken retries
* performance.user.saveError.*: time from user clicking save to failure;
  'retries' indicates # of badtoken retries
** performance.user.saveError.abusefilter
** performance.user.saveError.badtoken: token was bad and we prompted the user
** performance.user.saveError.captcha
** performance.user.saveError.editconflict
** performance.user.saveError.empty
** performance.user.saveError.spamblacklist
** performance.user.saveError.unknown
* performance.user.reviewComplete: time from user clicking review to diff showing
* performance.user.reviewError: time from user clicking review to diff failure
  since dialog was opened

Change-Id: I9815fa637d34c766c163e181d2f9527d3f32a7c3
This commit is contained in:
Roan Kattouw 2013-11-06 19:15:47 -08:00
parent ac1a386c95
commit d6a00d689e
2 changed files with 82 additions and 3 deletions

View file

@ -37,6 +37,7 @@ ve.init.mw.ViewPageTarget = function VeInitMwViewPageTarget() {
this.saveDialog = null;
this.onBeforeUnloadFallback = null;
this.onBeforeUnloadHandler = null;
this.timings = {};
this.active = false;
this.edited = false;
this.sanityCheckFinished = false;
@ -149,6 +150,7 @@ ve.init.mw.ViewPageTarget.compatibility = {
ve.init.mw.ViewPageTarget.prototype.activate = function () {
if ( !this.active && !this.activating ) {
this.activating = true;
this.timings.activationStart = ve.now();
// User interface changes
this.transformPage();
@ -238,6 +240,7 @@ ve.init.mw.ViewPageTarget.prototype.onLoad = function ( doc ) {
if ( mw.config.get( 'wgVisualEditorConfig' ).showBetaWelcome ) {
this.showBetaWelcome();
}
ve.track( 'performance.system.activation', { 'duration': ve.now() - this.timings.activationStart } );
mw.hook( 've.activationComplete' ).fire();
}, this ) );
}
@ -288,6 +291,7 @@ ve.init.mw.ViewPageTarget.prototype.onTokenError = function ( response, status )
* @param {number} [newid] New revision id, undefined if unchanged
*/
ve.init.mw.ViewPageTarget.prototype.onSave = function ( html, newid ) {
ve.track( 'performance.user.saveComplete', { 'duration': ve.now() - this.timings.saveDialogSave } );
if ( !this.pageExists || this.restoring ) {
// This is a page creation or restoration, refresh the page
this.tearDownBeforeUnloadHandler();
@ -345,6 +349,10 @@ ve.init.mw.ViewPageTarget.prototype.onSave = function ( html, newid ) {
*/
ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data ) {
var api, editApi,
trackData = {
'duration': ve.now() - this.timings.saveDialogSave,
'retries': this.timings.saveRetries
},
viewPage = this;
this.saveDialog.saveButton.setDisabled( false );
@ -354,6 +362,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// Handle empty response
if ( !data ) {
ve.track( 'performance.user.saveError.empty', trackData );
this.saveDialog.showMessage(
'api-save-error',
ve.msg( 'visualeditor-saveerror', 'Empty server response' ),
@ -369,6 +378,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// Handle spam blacklist error (either from core or from Extension:SpamBlacklist)
if ( editApi && editApi.spamblacklist ) {
ve.track( 'performance.user.saveError.spamblacklist', trackData );
this.saveDialog.showMessage(
'api-save-error',
// TODO: Use mediawiki.language equivalant of Language.php::listToText once it exists
@ -384,6 +394,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// Handle warnings/errors from Extension:AbuseFilter
// TODO: Move this to a plugin
if ( editApi && editApi.info && editApi.info.indexOf( 'Hit AbuseFilter:' ) === 0 && editApi.warning ) {
ve.track( 'performance.user.saveError.abusefilter', trackData );
this.saveDialog.showMessage(
'api-save-error',
$.parseHTML( editApi.warning ),
@ -433,9 +444,11 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
mw.config.get( 'wgUserId' ) === userInfo.id
) {
// New session is the same user still
this.timings.saveRetries++;
viewPage.saveDocument();
} else {
// The now current session is a different user
ve.track( 'performance.user.saveError.badtoken', trackData );
viewPage.saveDialog.saveButton.setDisabled( false );
// Trailing space is to separate from the other message.
@ -494,6 +507,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// API for different things in the UI. At this point we only support the FancyCaptha which we
// very intuitively detect by the presence of a "url" property.
if ( editApi && editApi.captcha && editApi.captcha.url ) {
ve.track( 'performance.user.saveError.captcha', trackData );
this.captcha = {
input: new OO.ui.TextInputWidget(),
id: editApi.captcha.id
@ -519,6 +533,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
}
// Handle (other) unknown and/or unrecoverable errors
ve.track( 'performance.user.saveError.unknown', trackData );
this.saveDialog.showMessage(
'api-save-error',
document.createTextNode(
@ -542,6 +557,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
* @param {string} diffHtml
*/
ve.init.mw.ViewPageTarget.prototype.onShowChanges = function ( diffHtml ) {
ve.track( 'performance.user.reviewComplete', { 'duration': ve.now() - this.timings.saveDialogReview } );
// Invalidate the viewer diff on next change
this.surface.getModel().getDocument().connect( this, { 'transact': 'clearSaveDialogDiff' } );
this.saveDialog.setDiffAndReview( diffHtml );
@ -554,6 +570,7 @@ ve.init.mw.ViewPageTarget.prototype.onShowChanges = function ( diffHtml ) {
* @param {string} wikitext
*/
ve.init.mw.ViewPageTarget.prototype.onSerialize = function ( wikitext ) {
ve.track( 'performance.user.reviewComplete', { 'duration': ve.now() - this.timings.saveDialogReview } );
// Invalidate the viewer wikitext on next change
this.surface.getModel().getDocument().connect( this, { 'transact': 'clearSaveDialogDiff' } );
this.saveDialog.setDiffAndReview( $( '<pre>' ).text( wikitext ) );
@ -567,6 +584,7 @@ ve.init.mw.ViewPageTarget.prototype.onSerialize = function ( wikitext ) {
* @param {string} status Text status message
*/
ve.init.mw.ViewPageTarget.prototype.onShowChangesError = function ( jqXHR, status ) {
ve.track( 'performance.user.reviewError', { 'duration': ve.now() - this.timings.saveDialogReview } );
alert( ve.msg( 'visualeditor-differror', status ) );
this.saveDialog.$loadingIcon.hide();
};
@ -579,6 +597,11 @@ ve.init.mw.ViewPageTarget.prototype.onShowChangesError = function ( jqXHR, statu
* @param {string} status Text status message
*/
ve.init.mw.ViewPageTarget.prototype.onSerializeError = function ( jqXHR, status ) {
if ( this.timings.saveDialogOpen ) {
// This function can be called by the switch to wikitext button as well, so only log
// reviewError if we actually got here from the save dialog
ve.track( 'performance.user.reviewError', { 'duration': ve.now() - this.timings.saveDialogReview } );
}
alert( ve.msg( 'visualeditor-serializeerror', status ) );
this.saveDialog.$loadingIcon.hide();
};
@ -589,6 +612,10 @@ ve.init.mw.ViewPageTarget.prototype.onSerializeError = function ( jqXHR, status
* @method
*/
ve.init.mw.ViewPageTarget.prototype.onEditConflict = function () {
ve.track( 'performance.user.saveError.editconflict', {
'duration': ve.now() - this.timings.saveDialogSave,
'retries': this.timings.saveRetries
} );
this.saveDialog.$loadingIcon.hide();
this.saveDialog.swapPanel( 'conflict' );
};
@ -599,6 +626,7 @@ ve.init.mw.ViewPageTarget.prototype.onEditConflict = function () {
* @method
*/
ve.init.mw.ViewPageTarget.prototype.onNoChanges = function () {
ve.track( 'performance.user.reviewComplete', { 'duration': ve.now() - this.timings.saveDialogReview } );
this.saveDialog.$loadingIcon.hide();
this.saveDialog.swapPanel( 'nochanges' );
this.saveDialog.reviewGoodButton.setDisabled( false );
@ -673,6 +701,13 @@ ve.init.mw.ViewPageTarget.prototype.clearSaveDialogDiff = function () {
this.surface.getModel().getDocument().disconnect( this, { 'transact': 'clearSaveDialogDiff' } );
};
/**
* Record the time of the last transaction in response to a 'transact' event on the document.
*/
ve.init.mw.ViewPageTarget.prototype.recordLastTransactionTime = function () {
this.timings.lastTransaction = ve.now();
};
/**
* Check if the user is entering wikitext, and show a notification if they are.
*
@ -731,6 +766,11 @@ ve.init.mw.ViewPageTarget.prototype.onSaveDialogReview = function () {
this.saveDialog.setSanityCheck( this.sanityCheckVerified );
if ( !this.saveDialog.$reviewViewer.find( 'table, pre' ).length ) {
this.timings.saveDialogReview = ve.now();
ve.track( 'behavior.saveDialogOpenTillReview', {
'duration': this.timings.saveDialogReview - this.timings.saveDialogOpen
} );
this.saveDialog.reviewGoodButton.setDisabled( true );
this.saveDialog.$loadingIcon.show();
if ( this.pageExists ) {
@ -755,6 +795,11 @@ ve.init.mw.ViewPageTarget.prototype.onSaveDialogReview = function () {
* @method
*/
ve.init.mw.ViewPageTarget.prototype.onSaveDialogSave = function () {
this.timings.saveDialogSave = ve.now();
this.timings.saveRetries = 0;
ve.track( 'behavior.saveDialogOpenTillSave', {
'duration': this.timings.saveDialogSave - this.timings.saveDialogOpen
} );
this.saveDocument();
};
@ -912,6 +957,9 @@ ve.init.mw.ViewPageTarget.prototype.setUpSurface = function ( doc, callback ) {
target.surface.getModel().getDocument().connect( target, {
'transact': 'clearSaveDialogDiff'
} );
target.surface.getModel().getDocument().connect( target, {
'transact': 'recordLastTransactionTime'
} );
target.surface.getModel().connect( target, {
'documentUpdate': 'checkForWikitextWarning',
'history': 'updateToolbarSaveButtonState'
@ -1188,7 +1236,8 @@ ve.init.mw.ViewPageTarget.prototype.setupSaveDialog = function () {
viewPage.saveDialog.connect( this, {
'save': 'onSaveDialogSave',
'review': 'onSaveDialogReview',
'resolve': 'onSaveDialogResolveConflict'
'resolve': 'onSaveDialogResolveConflict',
'close': 'onSaveDialogClose'
} );
// Setup checkboxes
viewPage.saveDialog.setupCheckboxes( ve.getObjectValues( viewPage.checkboxes ).join( '\n' ) );
@ -1203,6 +1252,18 @@ ve.init.mw.ViewPageTarget.prototype.showSaveDialog = function () {
this.saveDialog.setSanityCheck( this.sanityCheckVerified );
this.saveDialog.swapPanel( 'save' );
this.surface.getDialogs().open( 'mwSave' );
this.timings.saveDialogOpen = ve.now();
ve.track( 'behavior.lastTransactionTillSaveDialogOpen', {
'duration': this.timings.saveDialogOpen - this.timings.lastTransaction
} );
};
/**
* Respond to the save dialog being closed.
*/
ve.init.mw.ViewPageTarget.prototype.onSaveDialogClose = function () {
ve.track( 'behavior.saveDialogClose', { 'duration': ve.now() - this.timings.saveDialogOpen } );
this.timings.saveDialogOpen = null;
};
/**

View file

@ -539,7 +539,7 @@ ve.init.mw.Target.prototype.load = function () {
'cache': 'false'
} )
.then( function ( data, status, jqxhr ) {
ve.track( 'performance.domLoad', {
ve.track( 'performance.system.domLoad', {
'bytes': $.byteLength( jqxhr.responseText ),
'duration': ve.now() - start,
'cacheHit': /hit/i.test( jqxhr.getResponseHeader( 'X-Cache' ) ),
@ -598,7 +598,7 @@ ve.init.mw.Target.prototype.save = function ( doc, options ) {
'timeout': 100000
} )
.then( function ( data, status, jqxhr ) {
ve.track( 'performance.domSave', {
ve.track( 'performance.system.domSave', {
'bytes': $.byteLength( jqxhr.responseText ),
'duration': ve.now() - start,
'parsoid': jqxhr.getResponseHeader( 'X-Parsoid-Performance' )
@ -618,6 +618,7 @@ ve.init.mw.Target.prototype.save = function ( doc, options ) {
* @param {HTMLDocument} doc Document to compare against (via wikitext)
*/
ve.init.mw.Target.prototype.showChanges = function ( doc ) {
var start = ve.now();
$.ajax( {
'url': this.apiUrl,
'data': {
@ -632,6 +633,14 @@ ve.init.mw.Target.prototype.showChanges = function ( doc ) {
'type': 'POST',
// Wait up to 100 seconds before giving up
'timeout': 100000
} )
.then( function ( data, status, jqxhr ) {
ve.track( 'performance.system.domDiff', {
'bytes': $.byteLength( jqxhr.responseText ),
'duration': ve.now() - start,
'parsoid': jqxhr.getResponseHeader( 'X-Parsoid-Performance' )
} );
return jqxhr;
} )
.done( ve.bind( ve.init.mw.Target.onShowChanges, this ) )
.fail( ve.bind( ve.init.mw.Target.onShowChangesError, this ) );
@ -700,6 +709,7 @@ ve.init.mw.Target.prototype.submit = function ( wikitext, options ) {
* @returns {boolean} Serializing has beeen started
*/
ve.init.mw.Target.prototype.serialize = function ( doc, callback ) {
var start = ve.now();
// Prevent duplicate requests
if ( this.serializing ) {
return false;
@ -722,6 +732,14 @@ ve.init.mw.Target.prototype.serialize = function ( doc, callback ) {
// Wait up to 100 seconds before giving up
'timeout': 100000,
'cache': 'false'
} )
.then( function ( data, status, jqxhr ) {
ve.track( 'performance.system.domSerialize', {
'bytes': $.byteLength( jqxhr.responseText ),
'duration': ve.now() - start,
'parsoid': jqxhr.getResponseHeader( 'X-Parsoid-Performance' )
} );
return jqxhr;
} )
.done( ve.bind( ve.init.mw.Target.onSerialize, this ) )
.fail( ve.bind( ve.init.mw.Target.onSerializeError, this ) );