diff options
Diffstat (limited to 'resources/src/mediawiki/mediawiki.debug.profile.js')
-rw-r--r-- | resources/src/mediawiki/mediawiki.debug.profile.js | 556 |
1 files changed, 0 insertions, 556 deletions
diff --git a/resources/src/mediawiki/mediawiki.debug.profile.js b/resources/src/mediawiki/mediawiki.debug.profile.js deleted file mode 100644 index 04f7acd0..00000000 --- a/resources/src/mediawiki/mediawiki.debug.profile.js +++ /dev/null @@ -1,556 +0,0 @@ -/*! - * JavaScript for the debug toolbar profiler, enabled through $wgDebugToolbar - * and StartProfiler.php. - * - * @author Erik Bernhardson - * @since 1.23 - */ - -( function ( mw, $ ) { - 'use strict'; - - /** - * @singleton - * @class mw.Debug.profile - */ - var profile = mw.Debug.profile = { - /** - * Object containing data for the debug toolbar - * - * @property ProfileData - */ - data: null, - - /** - * @property DOMElement - */ - container: null, - - /** - * Initializes the profiling pane. - */ - init: function ( data, width, mergeThresholdPx, dropThresholdPx ) { - data = data || mw.config.get( 'debugInfo' ).profile; - profile.width = width || $(window).width() - 20; - // merge events from same pixel(some events are very granular) - mergeThresholdPx = mergeThresholdPx || 2; - // only drop events if requested - dropThresholdPx = dropThresholdPx || 0; - - if ( - !Array.prototype.map || - !Array.prototype.reduce || - !Array.prototype.filter || - !document.createElementNS || - !document.createElementNS.bind - ) { - profile.container = profile.buildRequiresBrowserFeatures(); - } else if ( data.length === 0 ) { - profile.container = profile.buildNoData(); - } else { - // Initialize createSvgElement (now that we know we have - // document.createElementNS and bind) - this.createSvgElement = document.createElementNS.bind( document, 'http://www.w3.org/2000/svg' ); - - // generate a flyout - profile.data = new ProfileData( data, profile.width, mergeThresholdPx, dropThresholdPx ); - // draw it - profile.container = profile.buildSvg( profile.container ); - profile.attachFlyout(); - } - - return profile.container; - }, - - buildRequiresBrowserFeatures: function () { - return $( '<div>' ) - .text( 'Certain browser features, including parts of ECMAScript 5 and document.createElementNS, are required for the profile visualization.' ) - .get( 0 ); - }, - - buildNoData: function () { - return $( '<div>' ).addClass( 'mw-debug-profile-no-data' ) - .text( 'No events recorded, ensure profiling is enabled in StartProfiler.php.' ) - .get( 0 ); - }, - - /** - * Creates DOM nodes appropriately namespaced for SVG. - * Initialized in init after checking support - * - * @param string tag to create - * @return DOMElement - */ - createSvgElement: null, - - /** - * @param DOMElement|undefined - */ - buildSvg: function ( node ) { - var container, group, i, g, - timespan = profile.data.timespan, - gapPerEvent = 38, - space = 10.5, - currentHeight = space, - totalHeight = 0; - - profile.ratio = ( profile.width - space * 2 ) / ( timespan.end - timespan.start ); - totalHeight += gapPerEvent * profile.data.groups.length; - - if ( node ) { - $( node ).empty(); - } else { - node = profile.createSvgElement( 'svg' ); - node.setAttribute( 'version', '1.2' ); - node.setAttribute( 'baseProfile', 'tiny' ); - } - node.style.height = totalHeight; - node.style.width = profile.width; - - // use a container that can be transformed - container = profile.createSvgElement( 'g' ); - node.appendChild( container ); - - for ( i = 0; i < profile.data.groups.length; i++ ) { - group = profile.data.groups[i]; - g = profile.buildTimeline( group ); - - g.setAttribute( 'transform', 'translate( 0 ' + currentHeight + ' )' ); - container.appendChild( g ); - - currentHeight += gapPerEvent; - } - - return node; - }, - - /** - * @param Object group of periods to transform into graphics - */ - buildTimeline: function ( group ) { - var text, tspan, line, i, - sum = group.timespan.sum, - ms = ' ~ ' + ( sum < 1 ? sum.toFixed( 2 ) : sum.toFixed( 0 ) ) + ' ms', - timeline = profile.createSvgElement( 'g' ); - - timeline.setAttribute( 'class', 'mw-debug-profile-timeline' ); - - // draw label - text = profile.createSvgElement( 'text' ); - text.setAttribute( 'x', profile.xCoord( group.timespan.start ) ); - text.setAttribute( 'y', 0 ); - text.textContent = group.name; - timeline.appendChild( text ); - - // draw metadata - tspan = profile.createSvgElement( 'tspan' ); - tspan.textContent = ms; - text.appendChild( tspan ); - - // draw timeline periods - for ( i = 0; i < group.periods.length; i++ ) { - timeline.appendChild( profile.buildPeriod( group.periods[i] ) ); - } - - // full-width line under each timeline - line = profile.createSvgElement( 'line' ); - line.setAttribute( 'class', 'mw-debug-profile-underline' ); - line.setAttribute( 'x1', 0 ); - line.setAttribute( 'y1', 28 ); - line.setAttribute( 'x2', profile.width ); - line.setAttribute( 'y2', 28 ); - timeline.appendChild( line ); - - return timeline; - }, - - /** - * @param Object period to transform into graphics - */ - buildPeriod: function ( period ) { - var node, - head = profile.xCoord( period.start ), - tail = profile.xCoord( period.end ), - g = profile.createSvgElement( 'g' ); - - g.setAttribute( 'class', 'mw-debug-profile-period' ); - $( g ).data( 'period', period ); - - if ( head + 16 > tail ) { - node = profile.createSvgElement( 'rect' ); - node.setAttribute( 'x', head ); - node.setAttribute( 'y', 8 ); - node.setAttribute( 'width', 2 ); - node.setAttribute( 'height', 9 ); - g.appendChild( node ); - - node = profile.createSvgElement( 'rect' ); - node.setAttribute( 'x', head ); - node.setAttribute( 'y', 8 ); - node.setAttribute( 'width', ( period.end - period.start ) * profile.ratio || 2 ); - node.setAttribute( 'height', 6 ); - g.appendChild( node ); - } else { - node = profile.createSvgElement( 'polygon' ); - node.setAttribute( 'points', pointList( [ - [ head, 8 ], - [ head, 19 ], - [ head + 8, 8 ], - [ head, 8] - ] ) ); - g.appendChild( node ); - - node = profile.createSvgElement( 'polygon' ); - node.setAttribute( 'points', pointList( [ - [ tail, 8 ], - [ tail, 19 ], - [ tail - 8, 8 ], - [ tail, 8 ] - ] ) ); - g.appendChild( node ); - - node = profile.createSvgElement( 'line' ); - node.setAttribute( 'x1', head ); - node.setAttribute( 'y1', 9 ); - node.setAttribute( 'x2', tail ); - node.setAttribute( 'y2', 9 ); - g.appendChild( node ); - } - - return g; - }, - - /** - * @param Object - */ - buildFlyout: function ( period ) { - var contained, sum, ms, mem, i, - node = $( '<div>' ); - - for ( i = 0; i < period.contained.length; i++ ) { - contained = period.contained[i]; - sum = contained.end - contained.start; - ms = '' + ( sum < 1 ? sum.toFixed( 2 ) : sum.toFixed( 0 ) ) + ' ms'; - mem = formatBytes( contained.memory ); - - $( '<div>' ).text( contained.source.name ) - .append( $( '<span>' ).text( ' ~ ' + ms + ' / ' + mem ).addClass( 'mw-debug-profile-meta' ) ) - .appendTo( node ); - } - - return node; - }, - - /** - * Attach a hover flyout to all .mw-debug-profile-period groups. - */ - attachFlyout: function () { - // for some reason addClass and removeClass from jQuery - // arn't working on svg elements in chrome <= 33.0 (possibly more) - var $container = $( profile.container ), - addClass = function ( node, value ) { - var current = node.getAttribute( 'class' ), - list = current ? current.split( ' ' ) : false, - idx = list ? list.indexOf( value ) : -1; - - if ( idx === -1 ) { - node.setAttribute( 'class', current ? ( current + ' ' + value ) : value ); - } - }, - removeClass = function ( node, value ) { - var current = node.getAttribute( 'class' ), - list = current ? current.split( ' ' ) : false, - idx = list ? list.indexOf( value ) : -1; - - if ( idx !== -1 ) { - list.splice( idx, 1 ); - node.setAttribute( 'class', list.join( ' ' ) ); - } - }, - // hide all tipsy flyouts - hide = function () { - $container.find( '.mw-debug-profile-period.tipsy-visible' ) - .each( function () { - removeClass( this, 'tipsy-visible' ); - $( this ).tipsy( 'hide' ); - } ); - }; - - $container.find( '.mw-debug-profile-period' ).tipsy( { - fade: true, - gravity: function () { - return $.fn.tipsy.autoNS.call( this ) + $.fn.tipsy.autoWE.call( this ); - }, - className: 'mw-debug-profile-tipsy', - center: false, - html: true, - trigger: 'manual', - title: function () { - return profile.buildFlyout( $( this ).data( 'period' ) ).html(); - } - } ).on( 'mouseenter', function () { - hide(); - addClass( this, 'tipsy-visible' ); - $( this ).tipsy( 'show' ); - } ); - - $container.on( 'mouseleave', function ( event ) { - var $from = $( event.relatedTarget ), - $to = $( event.target ); - // only close the tipsy if we are not - if ( $from.closest( '.tipsy' ).length === 0 && - $to.closest( '.tipsy' ).length === 0 && - $to.get( 0 ).namespaceURI !== 'http://www.w4.org/2000/svg' - ) { - hide(); - } - } ).on( 'click', function () { - // convenience method for closing - hide(); - } ); - }, - - /** - * @return number the x co-ordinate for the specified timestamp - */ - xCoord: function ( msTimestamp ) { - return ( msTimestamp - profile.data.timespan.start ) * profile.ratio; - } - }; - - function ProfileData( data, width, mergeThresholdPx, dropThresholdPx ) { - // validate input data - this.data = data.map( function ( event ) { - event.periods = event.periods.filter( function ( period ) { - return period.start && period.end - && period.start < period.end - // period start must be a reasonable ms timestamp - && period.start > 1000000; - } ); - return event; - } ).filter( function ( event ) { - return event.name && event.periods.length > 0; - } ); - - // start and end time of the data - this.timespan = this.data.reduce( function ( result, event ) { - return event.periods.reduce( periodMinMax, result ); - }, periodMinMax.initial() ); - - // transform input data - this.groups = this.collate( width, mergeThresholdPx, dropThresholdPx ); - - return this; - } - - /** - * There are too many unique events to display a line for each, - * so this does a basic grouping. - */ - ProfileData.groupOf = function ( label ) { - var pos, prefix = 'Profile section ended by close(): '; - if ( label.indexOf( prefix ) === 0 ) { - label = label.slice( prefix.length ); - } - - pos = [ '::', ':', '-' ].reduce( function ( result, separator ) { - var pos = label.indexOf( separator ); - if ( pos === -1 ) { - return result; - } else if ( result === -1 ) { - return pos; - } else { - return Math.min( result, pos ); - } - }, -1 ); - - if ( pos === -1 ) { - return label; - } else { - return label.slice( 0, pos ); - } - }; - - /** - * @return Array list of objects with `name` and `events` keys - */ - ProfileData.groupEvents = function ( events ) { - var group, i, - groups = {}; - - // Group events together - for ( i = events.length - 1; i >= 0; i-- ) { - group = ProfileData.groupOf( events[i].name ); - if ( groups[group] ) { - groups[group].push( events[i] ); - } else { - groups[group] = [events[i]]; - } - } - - // Return an array of groups - return Object.keys( groups ).map( function ( group ) { - return { - name: group, - events: groups[group] - }; - } ); - }; - - ProfileData.periodSorter = function ( a, b ) { - if ( a.start === b.start ) { - return a.end - b.end; - } - return a.start - b.start; - }; - - ProfileData.genMergePeriodReducer = function ( mergeThresholdMs ) { - return function ( result, period ) { - if ( result.length === 0 ) { - // period is first result - return [{ - start: period.start, - end: period.end, - contained: [period] - }]; - } - var last = result[result.length - 1]; - if ( period.end < last.end ) { - // end is contained within previous - result[result.length - 1].contained.push( period ); - } else if ( period.start - mergeThresholdMs < last.end ) { - // neighbors within merging distance - result[result.length - 1].end = period.end; - result[result.length - 1].contained.push( period ); - } else { - // period is next result - result.push( { - start: period.start, - end: period.end, - contained: [period] - } ); - } - return result; - }; - }; - - /** - * Collect all periods from the grouped events and apply merge and - * drop transformations - */ - ProfileData.extractPeriods = function ( events, mergeThresholdMs, dropThresholdMs ) { - // collect the periods from all events - return events.reduce( function ( result, event ) { - if ( !event.periods.length ) { - return result; - } - result.push.apply( result, event.periods.map( function ( period ) { - // maintain link from period to event - period.source = event; - return period; - } ) ); - return result; - }, [] ) - // sort combined periods - .sort( ProfileData.periodSorter ) - // Apply merge threshold. Original periods - // are maintained in the `contained` property - .reduce( ProfileData.genMergePeriodReducer( mergeThresholdMs ), [] ) - // Apply drop threshold - .filter( function ( period ) { - return period.end - period.start > dropThresholdMs; - } ); - }; - - /** - * runs a callback on all periods in the group. Only valid after - * groups.periods[0..n].contained are populated. This runs against - * un-transformed data and is better suited to summing or other - * stat collection - */ - ProfileData.reducePeriods = function ( group, callback, result ) { - return group.periods.reduce( function ( result, period ) { - return period.contained.reduce( callback, result ); - }, result ); - }; - - /** - * Transforms this.data grouping by labels, merging neighboring - * events in the groups, and drops events and groups below the - * display threshold. Groups are returned sorted by starting time. - */ - ProfileData.prototype.collate = function ( width, mergeThresholdPx, dropThresholdPx ) { - // ms to pixel ratio - var ratio = ( this.timespan.end - this.timespan.start ) / width, - // transform thresholds to ms - mergeThresholdMs = mergeThresholdPx * ratio, - dropThresholdMs = dropThresholdPx * ratio; - - return ProfileData.groupEvents( this.data ) - // generate data about the grouped events - .map( function ( group ) { - // Cleaned periods from all events - group.periods = ProfileData.extractPeriods( group.events, mergeThresholdMs, dropThresholdMs ); - // min and max timestamp per group - group.timespan = ProfileData.reducePeriods( group, periodMinMax, periodMinMax.initial() ); - // ms from first call to end of last call - group.timespan.length = group.timespan.end - group.timespan.start; - // collect the un-transformed periods - group.timespan.sum = ProfileData.reducePeriods( group, function ( result, period ) { - result.push( period ); - return result; - }, [] ) - // sort by start time - .sort( ProfileData.periodSorter ) - // merge overlapping - .reduce( ProfileData.genMergePeriodReducer( 0 ), [] ) - // sum - .reduce( function ( result, period ) { - return result + period.end - period.start; - }, 0 ); - - return group; - }, this ) - // remove groups that have had all their periods filtered - .filter( function ( group ) { - return group.periods.length > 0; - } ) - // sort events by first start - .sort( function ( a, b ) { - return ProfileData.periodSorter( a.timespan, b.timespan ); - } ); - }; - - // reducer to find edges of period array - function periodMinMax( result, period ) { - if ( period.start < result.start ) { - result.start = period.start; - } - if ( period.end > result.end ) { - result.end = period.end; - } - return result; - } - - periodMinMax.initial = function () { - return { start: Number.POSITIVE_INFINITY, end: Number.NEGATIVE_INFINITY }; - }; - - function formatBytes( bytes ) { - var i, sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if ( bytes === 0 ) { - return '0 Bytes'; - } - i = parseInt( Math.floor( Math.log( bytes ) / Math.log( 1024 ) ), 10 ); - return Math.round( bytes / Math.pow( 1024, i ), 2 ) + ' ' + sizes[i]; - } - - // turns a 2d array into a point list for svg - // polygon points attribute - // ex: [[1,2],[3,4],[4,2]] = '1,2 3,4 4,2' - function pointList( pairs ) { - return pairs.map( function ( pair ) { - return pair.join( ',' ); - } ).join( ' ' ); - } -}( mediaWiki, jQuery ) ); |