mediawiki-extensions-Visual.../modules/ve-mw/init/ve.init.mw.TargetEvents.js
Roan Kattouw 86b82b537d Make activation timings sent to EventLogging not lie
The activation timing was always a bit of a lie even pre-TargetLoader,
because the timer only started when the first RL request for VE
modules had loaded. But at least the process it covered was consistent,
which is no longer true with TargetLoader. Now that we start the
request for the HTML together with the RL request, the activation
time might include some, all or none of the HTML request depending
on how fast the RL request was.

This change makes the activation timings more useful by measuring
from the moment the user clicks "edit" to the moment the editor
is done loading, which is what actually matters.

* Moved start of activation timing to VPT init
** For mobile this falls back to when mw.Target#load is called;
   we'll have to fix that in MobileFrontend later
* Moved end of activation timing out of TargetEvents#onSurfaceReady
  into individual onSurfaceReady handlers
** This is necessary because VPT's onSurfaceReady does quite a lot,
   and we want to include the time that takes in our measurements

Change-Id: Ie44f0b839b39a2b3b22dcd86e20f0d1170cb6069
2015-04-14 21:57:11 +00:00

233 lines
7.3 KiB
JavaScript

/*!
* VisualEditor MediaWiki Initialization class.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Initialization MediaWiki Target Analytics.
*
* @class
*
* @constructor
* @param {ve.init.mw.Target} target Target class to log events for
*/
ve.init.mw.TargetEvents = function ( target ) {
this.target = target;
this.timings = { saveRetries: 0 };
// Events
this.target.connect( this, {
saveWorkflowBegin: 'onSaveWorkflowBegin',
saveWorkflowEnd: 'onSaveWorkflowEnd',
saveInitiated: 'onSaveInitated',
save: 'onSaveComplete',
saveReview: 'onSaveReview',
saveErrorEmpty: [ 'trackSaveError', 'empty' ],
saveErrorSpamBlacklist: [ 'trackSaveError', 'spamblacklist' ],
saveErrorAbuseFilter: [ 'trackSaveError', 'abusefilter' ],
saveErrorBadToken: [ 'trackSaveError', 'badtoken' ],
saveErrorNewUser: [ 'trackSaveError', 'newuser' ],
saveErrorPageDeleted: [ 'trackSaveError', 'pagedeleted' ],
saveErrorTitleBlacklist: [ 'trackSaveError', 'titleblacklist' ],
saveErrorCaptcha: [ 'trackSaveError', 'captcha' ],
saveErrorUnknown: [ 'trackSaveError', 'unknown' ],
editConflict: [ 'trackSaveError', 'editconflict' ],
surfaceReady: 'onSurfaceReady',
showChanges: 'onShowChanges',
showChangesError: 'onShowChangesError',
noChanges: 'onNoChanges',
serializeComplete: 'onSerializeComplete',
serializeError: 'onSerializeError'
} );
};
/**
* Target specific ve.track wrapper
*
* @param {string} topic Event name
* @param {Object} data Additional data describing the event, encoded as an object
*/
ve.init.mw.TargetEvents.prototype.track = function ( topic, data ) {
data.targetName = this.target.constructor.static.name;
ve.track( 'mwtiming.' + topic, data );
if ( topic.indexOf( 'performance.system.serializeforcache' ) === 0 ) {
// HACK: track serializeForCache duration here, because there's no event for that
this.timings.serializeForCache = data.duration;
}
};
/**
* Track when user begins the save workflow
*/
ve.init.mw.TargetEvents.prototype.onSaveWorkflowBegin = function () {
this.timings.saveWorkflowBegin = ve.now();
this.track( 'behavior.lastTransactionTillSaveDialogOpen', {
duration: this.timings.saveWorkflowBegin - this.timings.lastTransaction
} );
ve.track( 'mwedit.saveIntent' );
};
/**
* Track when user ends the save workflow
*/
ve.init.mw.TargetEvents.prototype.onSaveWorkflowEnd = function () {
this.track( 'behavior.saveDialogClose', { duration: ve.now() - this.timings.saveWorkflowBegin } );
this.timings.saveWorkflowBegin = null;
};
/**
* Track when document save is initiated
*/
ve.init.mw.TargetEvents.prototype.onSaveInitated = function () {
this.timings.saveInitiated = ve.now();
this.timings.saveRetries++;
this.track( 'behavior.saveDialogOpenTillSave', {
duration: this.timings.saveInitiated - this.timings.saveWorkflowBegin
} );
ve.track( 'mwedit.saveAttempt' );
};
/**
* Track when the save is complete
* @param {string} content
* @param {string} categoriesHtml
* @param {number} newRevId
*/
ve.init.mw.TargetEvents.prototype.onSaveComplete = function ( content, categoriesHtml, newRevId ) {
this.track( 'performance.user.saveComplete', { duration: ve.now() - this.timings.saveInitiated } );
this.timings.saveRetries = 0;
ve.track( 'mwedit.saveSuccess', {
timing: ve.now() - this.timings.saveInitiated + ( this.timings.serializeForCache || 0 ),
'page.revid': newRevId
} );
};
/**
* Track a save error by type
*
* @method
* @param {string} type Text for error type
*/
ve.init.mw.TargetEvents.prototype.trackSaveError = function ( type ) {
var key, data,
failureArguments = [],
// Maps mwtiming types to mwedit types
typeMap = {
badtoken: 'userBadToken',
newuser: 'userNewUser',
abusefilter: 'extensionAbuseFilter',
captcha: 'extensionCaptcha',
spamblacklist: 'extensionSpamBlacklist',
empty: 'responseEmpty',
unknown: 'responseUnknown',
pagedeleted: 'editPageDeleted',
titleblacklist: 'extensionTitleBlacklist',
editconflict: 'editConflict'
},
// Types that are logged as performance.user.saveError.{type}
// (for historical reasons; this sucks)
specialTypes = [ 'editconflict' ];
if ( arguments ) {
failureArguments = Array.prototype.slice.call( arguments, 1 );
}
key = 'performance.user.saveError';
if ( specialTypes.indexOf( type ) !== -1 ) {
key += '.' + type;
}
this.track( key, {
duration: ve.now() - this.timings.saveInitiated,
retries: this.timings.saveRetries,
type: type
} );
data = {
type: typeMap[type] || 'responseUnknown',
timing: ve.now() - this.timings.saveInitiated + ( this.timings.serializeForCache || 0 )
};
if ( type === 'unknown' && failureArguments[1].error && failureArguments[1].error.code ) {
data.message = failureArguments[1].error.code;
}
ve.track( 'mwedit.saveFailure', data );
};
/**
* Record activation having started.
* @param {number} [startTime] Timestamp activation started. Defaults to current time
*/
ve.init.mw.TargetEvents.prototype.trackActivationStart = function ( startTime ) {
this.timings.activationStart = startTime || ve.now();
};
/**
* Record activation being complete.
*/
ve.init.mw.TargetEvents.prototype.trackActivationComplete = function () {
this.track( 'performance.system.activation', { duration: ve.now() - this.timings.activationStart } );
};
/**
* Record the time of the last transaction in response to a 'transact' event on the document.
*/
ve.init.mw.TargetEvents.prototype.recordLastTransactionTime = function () {
this.timings.lastTransaction = ve.now();
};
/**
* Track time elapsed from beginning of save workflow to review
*/
ve.init.mw.TargetEvents.prototype.onSaveReview = function () {
this.timings.saveReview = ve.now();
this.track( 'behavior.saveDialogOpenTillReview', {
duration: this.timings.saveReview - this.timings.saveWorkflowBegin
} );
};
ve.init.mw.TargetEvents.prototype.onSurfaceReady = function () {
this.target.surface.getModel().getDocument().connect( this, {
transact: 'recordLastTransactionTime'
} );
};
/**
* Track when the user enters the review workflow
*/
ve.init.mw.TargetEvents.prototype.onShowChanges = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when the diff request fails in the review workflow
*/
ve.init.mw.TargetEvents.prototype.onShowChangesError = function () {
this.track( 'performance.user.reviewError', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when the diff request detects no changes
*/
ve.init.mw.TargetEvents.prototype.onNoChanges = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track whe serilization is complete in review workflow
*/
ve.init.mw.TargetEvents.prototype.onSerializeComplete = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when there is a serlization error
*/
ve.init.mw.TargetEvents.prototype.onSerializeError = function () {
if ( this.timings.saveWorkflowBegin ) {
// 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 workflow
this.track( 'performance.user.reviewError', { duration: ve.now() - this.timings.saveReview } );
}
};