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

Reply via email to