Gilles has uploaded a new change for review. https://gerrit.wikimedia.org/r/111197
Change subject: Track detailed content loading network performance ...................................................................... Track detailed content loading network performance Leverages the W3C Navigation Timing API when available Change-Id: Ief1d327d1bd8928bf5f2bf0bd4c7141a0a608a53 Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/126 --- M MultimediaViewer.php M MultimediaViewerHooks.php M resources/mmv/mmv.js A resources/mmv/mmv.performance.js A tests/qunit/mmv.performance.test.js M tests/qunit/mmv.test.js 6 files changed, 453 insertions(+), 226 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MultimediaViewer refs/changes/97/111197/1 diff --git a/MultimediaViewer.php b/MultimediaViewer.php index 1ff2faf..bcb3316 100644 --- a/MultimediaViewer.php +++ b/MultimediaViewer.php @@ -24,7 +24,10 @@ // do not pollute global namespace call_user_func( function() { global $wgExtensionMessagesFiles, $wgResourceModules, $wgExtensionFunctions, - $wgAutoloadClasses, $wgHooks, $wgExtensionCredits; + $wgAutoloadClasses, $wgHooks, $wgExtensionCredits, $wgNetworkPerformanceSamplingFactor; + + /** @var int|bool: If set, records image load network performance once per this many requests. False if unset. **/ + $wgNetworkPerformanceSamplingFactor = false; $wgExtensionMessagesFiles['MultimediaViewer'] = __DIR__ . '/MultimediaViewer.i18n.php'; @@ -216,6 +219,16 @@ ), ), $moduleInfo( 'mmv/ui' ) ); + $wgResourceModules['mmv.performance'] = array_merge( array( + 'scripts' => array( + 'mmv.performance.js', + ), + + 'dependencies' => array( + 'oojs', + ), + ), $moduleInfo( 'mmv' ) ); + $wgResourceModules['mmv'] = array_merge( array( 'scripts' => array( 'mmv.js', @@ -243,6 +256,7 @@ 'mmv.provider', 'mediawiki.language', 'mmv.multilightbox', + 'mmv.performance', ), 'messages' => array( @@ -372,15 +386,15 @@ 'revision' => 6636420, ); - $wgResourceModules['schema.MediaViewerPerf'] = array( + $wgResourceModules['schema.MultimediaViewerNetworkPerformance'] = array( 'class' => 'ResourceLoaderSchemaModule', - 'schema' => 'MediaViewerPerf', - 'revision' => 6636500, + 'schema' => 'MultimediaViewerNetworkPerformance', + 'revision' => 7346494, ); $wgResourceModules['mmv']['dependencies'][] = 'ext.eventLogging'; $wgResourceModules['mmv']['dependencies'][] = 'schema.MediaViewer'; - $wgResourceModules['mmv']['dependencies'][] = 'schema.MediaViewerPerf'; + $wgResourceModules['mmv']['dependencies'][] = 'schema.MultimediaViewerNetworkPerformance'; } }; diff --git a/MultimediaViewerHooks.php b/MultimediaViewerHooks.php index 46b2eac..c08bbdf 100644 --- a/MultimediaViewerHooks.php +++ b/MultimediaViewerHooks.php @@ -122,6 +122,7 @@ 'tests/qunit/mmv.testhelpers.js', 'tests/qunit/mmv.test.js', 'tests/qunit/mmv.model.test.js', + 'tests/qunit/mmv.performance.test.js', 'tests/qunit/provider/mmv.provider.ImageUsage.test.js', 'tests/qunit/provider/mmv.provider.GlobalUsage.test.js', 'tests/qunit/mmv.lightboxinterface.test.js', diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index 635d80d..8015878 100755 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -52,19 +52,7 @@ 'fullscreen-link-click': 'User clicked on fullscreen button in lightbox.', 'defullscreen-link-click': 'User clicked on button to return to normal lightbox view.', 'close-link-click': 'User clicked on the lightbox close button.', - 'site-link-click': 'User clicked on the link to the file description page.', - - // Profiling events start messages, $1 replaced with profile ID - 'profile-image-load-start': 'Profiling image load with ID $1', - 'profile-image-resize-start': 'Profiling image resize with ID $1', - 'profile-metadata-fetch-start': 'Profiling image metadata fetch with ID $1', - 'profile-gender-fetch-start': 'Profiling uploader gender fetch with ID $1', - - // Profiling events end messages, $1 replaced with profile ID, $2 replaced with time it took in ms - 'profile-image-load-end': 'Finished image load with ID $1 in $2 milliseconds', - 'profile-image-resize-end': 'Finished image resize with ID $1 in $2 milliseconds', - 'profile-metadata-fetch-end': 'Finished image metadata fetch with ID $1 in $2 milliseconds', - 'profile-gender-fetch-end': 'Finished uploader gender fetch with ID $1 in $2 milliseconds' + 'site-link-click': 'User clicked on the link to the file description page.' }; /** @@ -132,6 +120,12 @@ //this.globalUsageDataProvider = new mw.mmv.provider.GlobalUsage( // new mw.Api( {ajax: { url: 'http://commons.wikimedia.org/w/api.php', dataType: 'jsonp' } } ) //); + + /** + * @property {mw.mmv.performance} + * @private + */ + this.performance = new mw.mmv.performance(); /** * imageInfo object, used for caching - promises will resolve with @@ -360,7 +354,7 @@ MMVP.loadResizedImage = function ( ui, imageData, targetWidth, requestedWidth ) { // Replace image only if data was returned. if ( imageData ) { - this.loadAndSetImage( ui, imageData, targetWidth, requestedWidth, 'image-resize' ); + this.loadAndSetImage( ui, imageData, targetWidth, requestedWidth ); } }; @@ -460,8 +454,6 @@ ui.$repoLi.removeClass( 'empty' ); if ( imageData.lastUploader ) { - gfpid = this.profileStart( 'gender-fetch' ); - // TODO: Reuse the api member, fix everywhere. // Fetch the gender from the uploader's home wiki // TODO this is ugly as hell, let's fix this in core. @@ -477,8 +469,6 @@ usprop: 'gender' } ).done( function ( data ) { var gender = 'unknown'; - - viewer.profileEnd( gfpid ); if ( data && data.query && data.query.users && data.query.users[0] && data.query.users[0].gender ) { @@ -603,43 +593,36 @@ /** * @method - * Loads and sets the image specified in the imageData. It also updates the controls - * and collects profiling information. + * Loads and sets the image specified in the imageData. It also updates the controls. * * @param {mw.LightboxInterface} ui image container * @param {mw.mmv.model.Image} imageData image information * @param {number} targetWidth * @param {number} requestedWidth - * @param {string} profileEvent profile event key */ - MMVP.loadAndSetImage = function ( ui, imageData, targetWidth, requestedWidth, profileEvent ) { - var rpid, - maybeThumb, + MMVP.loadAndSetImage = function ( ui, imageData, targetWidth, requestedWidth ) { + var maybeThumb, viewer = this, - image = new Image(); - - image.onload = function () { - viewer.profileEnd( rpid ); - }; - - rpid = this.profileStart( profileEvent, { - width: imageData.width, - height: imageData.height, - fileSize: imageData.size - }, imageData.mimeType ); + image = new Image(), + src; // Use cached image if we have it. maybeThumb = imageData.getThumbUrl( requestedWidth ); - image.src = maybeThumb || imageData.url; - if ( maybeThumb && requestedWidth > targetWidth || - !maybeThumb && imageData.width > targetWidth ) { - // Image bigger than the current area, resize before loading - image.width = targetWidth; - } + src = maybeThumb || imageData.url; - ui.replaceImageWith( image ); - this.updateControls(); + this.performance.record( 'image', src ).then( function() { + image.src = src; + + if ( maybeThumb && requestedWidth > targetWidth || + !maybeThumb && imageData.width > targetWidth ) { + // Image bigger than the current area, resize before loading + image.width = targetWidth; + } + + ui.replaceImageWith( image ); + viewer.updateControls(); + } ); }; /** @@ -649,8 +632,7 @@ * @param {string} initialSrc The string to set the src attribute to at first. */ MMVP.loadImage = function ( image, initialSrc ) { - var mdpid, - viewer = this; + var viewer = this; this.lightbox.currentIndex = image.index; @@ -673,19 +655,15 @@ $( document.body ).addClass( 'mw-mlb-lightbox-open' ); - mdpid = this.profileStart( 'metadata-fetch' ); - this.fetchImageInfoAndFileUsageInfo( image.filePageTitle ).then( function ( imageData, repoInfo, targetWidth, requestedWidth, localUsage, globalUsage ) { var repoData = mw.mmv.model.Repo.newFromRepoInfo( repoInfo[imageData.repo] ); - - viewer.profileEnd( mdpid ); viewer.stopListeningToScroll(); viewer.animateMetadataDivOnce() // We need to wait until the animation is finished before we listen to scroll .then( function() { viewer.startListeningToScroll(); } ); - viewer.loadAndSetImage( viewer.lightbox.iface, imageData, targetWidth, requestedWidth, 'image-load' ); + viewer.loadAndSetImage( viewer.lightbox.iface, imageData, targetWidth, requestedWidth ); viewer.lightbox.iface.$imageDiv.removeClass( 'empty' ); viewer.setImageInfo( image, imageData, repoData, localUsage, globalUsage ); @@ -895,79 +873,6 @@ } ); } }; - - ( function () { - var profiling = {}, - nonce = 0; - - /** - * Start profiling an event - * @param {string} type Can be image-load, image-resize, metadata-fetch, gender-fetch - * @param {Object} [imgSize] Size of image (for image related events) - * @param {number} [imgSize.width] In pixels - * @param {number} [imgSize.height] In pixels - * @param {number} [imgSize.filesize] In bytes - * @param {string} [fileType] File type (for image related events) - * @param {number} [timeout] Optional timeout for the event. - * @returns {number} The id used to finish the profiling - */ - MMVP.profileStart = function ( type, imgSize, fileType, timeout ) { - var thisid = nonce++; - - imgSize = imgSize || {}; - - profiling[thisid] = { - /* Changelog: - * 1.1 fixed the issue with zeros in every message - * 1.0 first release - */ - version: '1.1', - action: type, - imageWidth: imgSize.width, - imageHeight: imgSize.height, - fileSize: imgSize.filesize, - fileType: fileType, - userAgent: navigator.userAgent, - start: Date.now() - }; - - mw.log( mmvLogActions['profile-' + type + '-start'].replace( /\$1/g, thisid ) ); - - if ( timeout ) { - window.setTimeout( function () { - profiling[thisid] = undefined; - }, timeout ); - } - - return thisid; - }; - - /** - * Signal the end of an event being profiled and send the - * eventlogging message. - * @param {number} id Should be the value returned from profileStart - * @param {boolean} [fakeTime] For testing, whether to fake the time in the message. Time is zero if so. - */ - MMVP.profileEnd = function ( id, fakeTime ) { - var msg; - - if ( profiling[id] ) { - msg = profiling[id]; - msg.milliseconds = Date.now() - msg.start; - delete msg.start; - - mw.log( - mmvLogActions['profile-' + msg.action + '-end'] - .replace( /\$1/g, id ) - .replace( /\$2/g, fakeTime ? 0 : msg.milliseconds ) - ); - - if ( mw.eventLog ) { - mw.eventLog.logEvent( 'MediaViewerPerf', msg ); - } - } - }; - }() ); /** * Transforms a date string into localized, human-readable format. diff --git a/resources/mmv/mmv.performance.js b/resources/mmv/mmv.performance.js new file mode 100755 index 0000000..c935f0a --- /dev/null +++ b/resources/mmv/mmv.performance.js @@ -0,0 +1,229 @@ +/* + * This file is part of the MediaWiki extension MultimediaViewer. + * + * MultimediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MultimediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MultimediaViewer. If not, see <http://www.gnu.org/licenses/>. + */ + +( function ( mw, $ ) { + var P; + + /** + * @class mw.mmv.performance + * Measures the network performance + * @see https://meta.wikimedia.org/wiki/Schema:MultimediaViewerNetworkPerformance + * @constructor + */ + function Performance() {} + + P = Performance.prototype; + + /** + * Gather network performance for a given URL + * Will only run on a sample of users/requests. Avoid using this on URLs that aren't + * cached by the browser, as it will consume unnecessary bandwidth for the user. + * @param {string} type the type of request to be measured + * @param {string} url URL to be measured + * @returns {jQuery.Promise} A promise that resolves when the contents of the URL have been fetched + */ + P.record = function ( type, url ) { + var deferred = $.Deferred(), + request, + perf = this, + start; + + request = new XMLHttpRequest(); + request.onreadystatechange = function () { + var total = $.now() - start; + + if ( request.readyState === 4 ) { + deferred.resolve( request.response ); + + // The timeout is necessary because if there's an entry in window.performance, + // it hasn't been added yet at this point + setTimeout( function() { + perf.recordEntry( type, url, request, total ); + }, 1000 ); + } + }; + + start = $.now(); + request.open( 'GET', url, true ); + request.send(); + + return deferred; + }; + + /** + * Records network performance results for a given url + * Will record if enough data is present and it's not a local cache hit + * @param {string} type the type of request to be measured + * @param {string} url URL of that was measured + * @param {XMLHttpRequest} request HTTP request that just completed + * @param {number} total the total load time tracked with a basic technique + */ + P.recordEntry = function ( type, url, request, total ) { + var timingList, timingEntry, i, + matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i), + stats = { type: type, + contentHost: window.location.host, + userAgent: navigator.userAgent, + isHttps: url.indexOf( 'https' ) === 0, + total: total }, + varnishXCache, + performance = this.getWindowPerformance(); + + // If eventLog isn't present there is nowhere to record to + if ( !mw.eventLog ) { + return; + } + + if ( !this.performanceChecked ) { + this.performanceChecked = {}; + } + + // There is no need to measure the same url more than once + if (url in this.performanceChecked) { + return; + } + + this.performanceChecked[ url ] = true; + + // Don't record if we're not in the sample + if ( !this.isInSample() ) { + return; + } + + if ( !matches || matches.length !== 2 ) { + stats.urlHost = stats.contentHost; + } else { + stats.urlHost = matches[ 1 ]; + } + + stats.XCache = request.getResponseHeader( 'X-Cache' ); + stats.XVarnish = request.getResponseHeader( 'X-Varnish' ); + + if ( stats.XCache && stats.XCache.length ) { + varnishXCache = this.parseVarnishXCacheHeader( stats.XCache ); + + $.each( varnishXCache, function( key, value ) { + stats[ key ] = value; + } ); + } + + stats.contentLength = parseInt( request.getResponseHeader( 'Content-Length' ), 10 ); + stats.age = parseInt( request.getResponseHeader( 'Age' ), 10 ); + stats.timestamp = new Date( request.getResponseHeader( 'Date' ) ).getTime() / 1000; + + if ( performance && performance.getEntriesByType ) { + timingList = performance.getEntriesByType( 'resource' ); + + for ( i = 0; i < timingList.length; i++ ) { + timingEntry = timingList[ i ]; + if ( timingEntry.initiatorType === 'xmlhttprequest' + && timingEntry.name === url ) { + stats.total = Math.round( timingEntry.duration ); + stats.redirect = Math.round( timingEntry.redirectEnd - timingEntry.redirectStart ); + stats.dns = Math.round( timingEntry.domainLookupEnd - timingEntry.domainLookupStart ); + stats.tcp = Math.round( timingEntry.connectEnd - timingEntry.connectStart ); + stats.request = Math.round( timingEntry.responseStart - timingEntry.requestStart ); + stats.response = Math.round( timingEntry.responseEnd - timingEntry.responseStart ); + stats.cache = Math.round( timingEntry.domainLookupStart - timingEntry.fetchStart ); + } + } + + // Don't record entries that hit the browser cache + if ( stats.request < 1 ) { + return; + } + } + + if ( $.isPlainObject( window.Geo ) && typeof window.Geo.country === 'string' ) { + stats.country = window.Geo.country; + } + + // Don't record if we don't have country information + if ( !stats.country || !stats.country.length ) { + return; + } + + mw.eventLog.logEvent( 'MultimediaViewerNetworkPerformance', stats ); + }; + + /** + * Parses an X-Cache header from Varnish and extracts varnish information + * @param {string} header The X-Cache header from the request + * @returns {Object} The parsed X-Cache data + */ + P.parseVarnishXCacheHeader = function ( header ) { + var parts, + part, + subparts, + i, + results = {}, + matches; + + if ( !header || !header.length ) { + return results; + } + + parts = header.split( ',' ); + + for ( i = 0; i < parts.length; i++ ) { + part = parts[ i ]; + subparts = part.trim().split( ' ' ); + + // If the subparts aren't space-separated, it's an unknown format, skip + if ( !subparts.length ) { + continue; + } + + matches = part.match( /\(([0-9]+)\)/ ); + + // If there is no number between parenthesis for a given server + // it's an unknown format, skip + if ( !matches || matches.length !== 2 ) { + continue; + } + + results[ 'varnish' + ( i + 1 ) ] = subparts[ 0 ]; + results[ 'varnish' + ( i + 1 ) + 'hits' ] = parseInt( matches[ 1 ], 10 ); + } + + return results; + }; + + /** + * Returns the window's Performance object + * Allows us to override for unit tests + * @returns {Object} The window's Performance object + */ + P.getWindowPerformance = function () { + return window.performance; + }; + + /** + * Returns whether or not we should measure this request + * @returns {bool} True if this request needs to be sampled + */ + P.isInSample = function () { + var factor = mw.config.get( 'wgNetworkPerformanceSamplingFactor' ); + + if ( !$.isNumeric( factor ) || factor < 1 ) { + return false; + } + return Math.floor( Math.random() * factor ) === 0; + }; + + mw.mmv.performance = Performance; +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/mmv.performance.test.js b/tests/qunit/mmv.performance.test.js new file mode 100644 index 0000000..88cfa86 --- /dev/null +++ b/tests/qunit/mmv.performance.test.js @@ -0,0 +1,173 @@ +/* + * This file is part of the MediaWiki extension MultimediaViewer. + * + * MultimediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MultimediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MultimediaViewer. If not, see <http://www.gnu.org/licenses/>. + */ + +( function ( mw, $ ) { + QUnit.module( 'mmv.performance', QUnit.newMwEnvironment() ); + + QUnit.test( 'recordEntry', 26, function ( assert ) { + var stats, fakeRequest, + varnish1 = 'cp1061', + varnish2 = 'cp3006', + varnish3 = 'cp3005', + varnish1hits = 0, + varnish2hits = 2, + varnish3hits = 1, + xvarnish = '1754811951 1283049064, 1511828531, 1511828573 1511828528', + xcache = varnish1 + ' miss (0), ' + varnish2 + ' miss (2), ' + varnish3 + ' frontend hit (1)', + age = '12345', + contentLength = '23456', + urlHost = 'fail', + date = 'Tue, 04 Feb 2014 11:11:50 GMT', + timestamp = 1391512310, + url = 'https://' + urlHost + '/balls.jpg', + redirect = 500, + dns = 2, + tcp = 10, + request = 25, + response = 50, + cache = 1, + perfData = { + initiatorType: 'xmlhttprequest', + name: url, + duration: 12345, + redirectStart: 1000, + redirectEnd: 1500, + domainLookupStart: 2, + domainLookupEnd: 4, + connectStart: 50, + connectEnd: 60, + requestStart: 125, + responseStart: 150, + responseEnd: 200, + fetchStart: 1 + }, + country = 'FR', + oldGeo = window.Geo, + oldEventLog = mw.eventLog, + type = 'image', + performance = new mw.mmv.performance(); + + performance.getWindowPerformance = function() { + return { + getEntriesByType: function () { + return [ { + initiatorType: 'bogus', + duration: 1234, + name: url + }, perfData]; + } + }; + }; + + performance.isInSample = function() { + return true; + }; + + fakeRequest = { + getResponseHeader: function ( header ) { + switch ( header ) { + case 'X-Cache': + return xcache; + case 'X-Varnish': + return xvarnish; + case 'Age': + return age; + case 'Content-Length': + return contentLength; + case 'Date': + return date; + } + } + }; + + window.Geo = { + country: country + }; + + mw.eventLog = { + logEvent: function( type, e ) { + if ( type === 'MultimediaViewerNetworkPerformance' ) { + stats = e; + } + } + }; + + performance.recordEntry( type, url, fakeRequest ); + + assert.strictEqual( stats.type, type, 'Type of event matches' ); + assert.strictEqual( stats.varnish1, varnish1, 'First varnish server name extracted' ); + assert.strictEqual( stats.varnish2, varnish2, 'Second varnish server name extracted' ); + assert.strictEqual( stats.varnish3, varnish3, 'Third varnish server name extracted' ); + assert.strictEqual( stats.varnish4, undefined, 'Fourth varnish server is undefined' ); + assert.strictEqual( stats.varnish1hits, varnish1hits, 'First varnish hit count extracted' ); + assert.strictEqual( stats.varnish2hits, varnish2hits, 'Second varnish hit count extracted' ); + assert.strictEqual( stats.varnish3hits, varnish3hits, 'Third varnish hit count extracted' ); + assert.strictEqual( stats.varnish4hits, undefined, 'Fourth varnish hit count is undefined' ); + assert.strictEqual( stats.XVarnish, xvarnish, 'X-Varnish header passed as-is' ); + assert.strictEqual( stats.XCache, xcache, 'X-Cache header passed as-is' ); + assert.strictEqual( stats.age, parseInt( age, 10 ), 'Age header converted to integer' ); + assert.strictEqual( stats.contentLength, parseInt( contentLength, 10 ), + 'Content-Length header converted to integer' ); + assert.strictEqual( stats.contentHost, window.location.host, 'contentHost is correct' ); + assert.strictEqual( stats.urlHost, urlHost, 'urlHost is correct' ); + assert.strictEqual( stats.timestamp, timestamp, 'timestamp is correct' ); + assert.strictEqual( stats.total, perfData.duration, 'total is correct' ); + assert.strictEqual( stats.redirect, redirect, 'redirect is correct' ); + assert.strictEqual( stats.dns, dns, 'dns is correct' ); + assert.strictEqual( stats.tcp, tcp, 'tcp is correct' ); + assert.strictEqual( stats.request, request, 'request is correct' ); + assert.strictEqual( stats.response, response, 'response is correct' ); + assert.strictEqual( stats.cache, cache, 'cache is correct' ); + assert.strictEqual( stats.country, country, 'country is correct' ); + assert.strictEqual( stats.userAgent, navigator.userAgent, 'userAgent is correct' ); + assert.strictEqual( stats.isHttps, true, 'isHttps is correct' ); + + window.Geo = oldGeo; + mw.eventLog = oldEventLog; + } ); + + QUnit.test( 'parseVarnishXCacheHeader', 15, function ( assert ) { + var varnish1 = 'cp1061', + varnish2 = 'cp3006', + varnish3 = 'cp3005', + testString = varnish1 + ' miss (0), ' + varnish2 + ' miss (0), ' + varnish3 + ' frontend hit (1)', + performance = new mw.mmv.performance(), + varnishXCache = performance.parseVarnishXCacheHeader( testString ); + + assert.strictEqual( varnishXCache.varnish1, varnish1, 'First varnish server name extracted' ); + assert.strictEqual( varnishXCache.varnish2, varnish2, 'Second varnish server name extracted' ); + assert.strictEqual( varnishXCache.varnish3, varnish3, 'Third varnish server name extracted' ); + assert.strictEqual( varnishXCache.varnish4, undefined, 'Fourth varnish server is undefined' ); + assert.strictEqual( varnishXCache.varnish1hits, 0, 'First varnish hit count extracted' ); + assert.strictEqual( varnishXCache.varnish2hits, 0, 'Second varnish hit count extracted' ); + assert.strictEqual( varnishXCache.varnish3hits, 1, 'Third varnish hit count extracted' ); + assert.strictEqual( varnishXCache.varnish4hits, undefined, 'Fourth varnish hit count is undefined' ); + + testString = varnish1 + ' miss (36), ' + varnish2 + ' miss (2)'; + varnishXCache = performance.parseVarnishXCacheHeader( testString ); + + assert.strictEqual( varnishXCache.varnish1, varnish1, 'First varnish server name extracted' ); + assert.strictEqual( varnishXCache.varnish2, varnish2, 'Second varnish server name extracted' ); + assert.strictEqual( varnishXCache.varnish3, undefined, 'Third varnish server is undefined' ); + assert.strictEqual( varnishXCache.varnish1hits, 36, 'First varnish hit count extracted' ); + assert.strictEqual( varnishXCache.varnish2hits, 2, 'Second varnish hit count extracted' ); + assert.strictEqual( varnishXCache.varnish3hits, undefined, 'Third varnish hit count is undefined' ); + + varnishXCache = performance.parseVarnishXCacheHeader( 'garbage' ); + assert.ok( $.isEmptyObject( varnishXCache ), 'Varnish cache results are empty' ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/mmv.test.js b/tests/qunit/mmv.test.js index 7e73e94..e5c3ee3 100644 --- a/tests/qunit/mmv.test.js +++ b/tests/qunit/mmv.test.js @@ -7,13 +7,6 @@ [ 'close-link-click', 'User clicked on the lightbox close button.' ], [ 'site-link-click', 'User clicked on the link to the file description page.' ], [ 'Something happened', 'Something happened' ] - ], - - profileTests = [ - [ 'image-load', 'Profiling image load with ID $1', 'Finished image load with ID $1 in $2 milliseconds', 200, 200, 120348, 'jpg' ], - [ 'image-resize', 'Profiling image resize with ID $1', 'Finished image resize with ID $1 in $2 milliseconds', 400, 400, 500000, 'png' ], - [ 'metadata-fetch', 'Profiling image metadata fetch with ID $1', 'Finished image metadata fetch with ID $1 in $2 milliseconds' ], - [ 'gender-fetch', 'Profiling uploader gender fetch with ID $1', 'Finished uploader gender fetch with ID $1 in $2 milliseconds' ] ]; @@ -174,78 +167,6 @@ mw.log = backupLog; mw.eventLog = backupEventLog; - } ); - - QUnit.test( 'Profiling works as expected', ( 12 * profileTests.length ), function ( assert ) { - var i, pid, test, msgName, expectedMsg, expectedImageWidth, - expectedFileSize, expectedFileType, expectedImageHeight, - viewer = new mw.MultimediaViewer(), - backupLog = mw.log, - backupEventLog = mw.eventLog; - - function checkLogging( msg ) { - assert.strictEqual( msg, expectedMsg, 'Message ' + msgName + ' is logged correctly.' ); - } - - function checkProfileEventLog( type, msg ) { - assert.strictEqual( type, 'MediaViewerPerf', 'EventLogging gets the right event type for profile message ' + msgName + '.' ); - assert.strictEqual( msg.version, '1.1', 'EventLogging gets the right version number for profile message ' + msgName + '.' ); - assert.strictEqual( msg.action, msgName, 'EventLogging gets the right action name for message ' + msgName + '.' ); - assert.strictEqual( msg.start, undefined, 'MultimediaViewer#profileEnd deletes the event start time from ' + msgName + ' profiles sent to EventLogging.' ); - assert.strictEqual( msg.fileSize, expectedFileSize, 'EventLogging sees the correct file size for ' + msgName + ' profiles.' ); - assert.strictEqual( msg.fileType, expectedFileType, 'EventLogging sees the correct filetype for ' + msgName + ' profiles.' ); - assert.strictEqual( msg.imageHeight, expectedImageHeight, 'EventLogging sees the correct image height for ' + msgName + ' profiles.' ); - assert.strictEqual( msg.imageWidth, expectedImageWidth, 'EventLogging sees the correct image width for ' + msgName + ' profiles.' ); - assert.strictEqual( msg.userAgent, navigator.userAgent, 'EventLogging logs the browser user-agent string for ' + msgName + ' profiles.' ); - } - - mw.log = checkLogging; - mw.eventLog = mw.eventLog || {}; - mw.eventLog.logEvent = checkProfileEventLog; - - for ( i = 0; i < profileTests.length; i++ ) { - test = profileTests[i]; - msgName = test[0]; - - expectedMsg = test[1].replace( /\$1/g, i ); - expectedImageWidth = test[3]; - expectedImageHeight = test[4]; - expectedFileSize = test[5]; - expectedFileType = test[6]; - pid = viewer.profileStart( msgName, { width: expectedImageWidth, height: expectedImageHeight, filesize: expectedFileSize }, expectedFileType ); - assert.strictEqual( pid, i, 'nonce-style profile IDs come in order.' ); - - expectedMsg = test[2].replace( /\$1/g, i ).replace( /\$2/g, 0 ); - viewer.profileEnd( pid, true ); - } - - mw.log = backupLog; - mw.eventLog = backupEventLog; - } ); - - QUnit.asyncTest( 'Make sure we get sane values for the eventlogging timing', 2, function ( assert ) { - var pid, - continuing = true, - viewer = new mw.MultimediaViewer(), - backupEventLog = mw.eventLog; - - mw.eventLog = { - logEvent: function ( schema, msg ) { - // msg.filesize has whatever value we set the timeout for. - assert.ok( msg.milliseconds >= msg.fileSize, 'Right amount of time elapsed for the ' + ( continuing ? 'first' : 'second' ) + ' profile.' ); - if ( continuing ) { - continuing = false; - pid = viewer.profileStart( 'image-resize', { width: 20, height: 20, filesize: 80 }, 'png' ); - window.setTimeout( function () { viewer.profileEnd( pid ); }, 80 ); - } else { - mw.eventLog = backupEventLog; - QUnit.start(); - } - } - }; - - pid = viewer.profileStart( 'image-load', { width: 10, height: 10, filesize: 40 }, 'jpg' ); - window.setTimeout( function () { viewer.profileEnd( pid ); }, 40 ); } ); QUnit.test( 'Ensure that the click callback is getting the appropriate initial value for image loading', 1, function ( assert ) { @@ -481,11 +402,9 @@ ui.unattach(); } ); - QUnit.asyncTest( 'loadAndSetImage(): Basic load', 9, function ( assert ) { + QUnit.asyncTest( 'loadAndSetImage(): Basic load', 3, function ( assert ) { var targetWidth, requestedWidth, - profileEvent, - pid = 4321, viewer = new mw.MultimediaViewer(), ui = new mw.LightboxInterface(), size = 120, @@ -501,33 +420,19 @@ 'A person', 'Another person', 'CC-BY-SA-3.0', 0, 0 ); - // Assert funtions are called with correct data - viewer.profileStart = function ( type, imgSize, fileType ) { - assert.strictEqual( type, profileEvent, 'Correct event type for profile start.' ); - assert.strictEqual( imgSize.width, width, 'Correct width for profile start.' ); - assert.strictEqual( imgSize.height, height, 'Correct height for profile start.' ); - assert.strictEqual( imgSize.fileSize, size, 'Correct fileSize for profile start.' ); - assert.strictEqual( fileType, 'image/jpeg', 'Correct fileType for profile start.' ); - - return pid; - }; - viewer.profileEnd = function ( id ) { - assert.strictEqual( id, pid, 'Correct pid to end profiling. Image loaded correctly.' ); - QUnit.start(); - }; ui.replaceImageWith = function ( image ) { assert.strictEqual( image.src, imageUrl, 'Image to replace has correct "src" attribute.' ); assert.strictEqual( image.width, targetWidth, 'Image to replace has correct "width" attribute.' ); }; viewer.updateControls = function () { assert.ok( true, 'Controls updated.' ); + QUnit.start(); }; // Test case when image loaded is bigger than current area targetWidth = 8; // Current area < imageData.width requestedWidth = 640; - profileEvent = 'image-load'; - viewer.loadAndSetImage( ui, imageData, targetWidth, requestedWidth, profileEvent ); + viewer.loadAndSetImage( ui, imageData, targetWidth, requestedWidth ); } ); QUnit.test( 'Hash handling', 5, function ( assert ) { -- To view, visit https://gerrit.wikimedia.org/r/111197 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ief1d327d1bd8928bf5f2bf0bd4c7141a0a608a53 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/MultimediaViewer Gerrit-Branch: master Gerrit-Owner: Gilles <gdu...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits