diff --git a/src/core.js b/src/core.js index 90a7eda50..e425e9ab5 100644 --- a/src/core.js +++ b/src/core.js @@ -63,13 +63,8 @@ var Page = (function PageClosure() { function Page(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; - this.stats = { - create: Date.now(), - compile: 0.0, - fonts: 0.0, - images: 0.0, - render: 0.0 - }; + this.stats = new StatTimer(); + this.stats.enabled = !!globalScope.PDFJS.enableStats; this.xref = xref; this.ref = ref; @@ -200,6 +195,8 @@ var Page = (function PageClosure() { return this.IRQueue; } + this.stats.time('Build IR Queue'); + var xref = this.xref; var content = xref.fetchIfRef(this.content); var resources = xref.fetchIfRef(this.resources); @@ -217,11 +214,14 @@ var Page = (function PageClosure() { var pe = this.pe = new PartialEvaluator( xref, handler, 'p' + this.pageNumber + '_'); var IRQueue = {}; - return (this.IRQueue = pe.getIRQueue(content, resources, IRQueue, - dependency)); + this.IRQueue = pe.getIRQueue(content, resources, IRQueue, dependency); + + this.stats.timeEnd('Build IR Queue'); + return this.IRQueue; }, ensureFonts: function pageEnsureFonts(fonts, callback) { + this.stats.time('Font Loading'); // Convert the font names to the corresponding font obj. for (var i = 0, ii = fonts.length; i < ii; i++) { fonts[i] = this.objs.objs[fonts[i]].data; @@ -231,7 +231,7 @@ var Page = (function PageClosure() { var fontObjs = FontLoader.bind( fonts, function pageEnsureFontsFontObjs(fontObjs) { - this.stats.fonts = Date.now(); + this.stats.timeEnd('Font Loading'); callback.call(this); }.bind(this), @@ -240,6 +240,8 @@ var Page = (function PageClosure() { }, display: function pageDisplay(gfx, callback) { + var stats = this.stats; + stats.time('Rendering'); var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -266,8 +268,9 @@ var Page = (function PageClosure() { function next() { startIdx = gfx.executeIRQueue(IRQueue, startIdx, next, stepper); if (startIdx == length) { - self.stats.render = Date.now(); gfx.endDrawing(); + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); if (callback) callback(); } } @@ -431,15 +434,14 @@ var Page = (function PageClosure() { return items; }, startRendering: function pageStartRendering(ctx, callback, textLayer) { - this.startRenderingTime = Date.now(); - + var stats = this.stats; + stats.time('Overall'); // If there is no displayReadyPromise yet, then the IRQueue was never // requested before. Make the request and create the promise. if (!this.displayReadyPromise) { this.pdf.startRendering(this); this.displayReadyPromise = new Promise(); } - // Once the IRQueue and fonts are loaded, perform the actual rendering. this.displayReadyPromise.then( function pageDisplayReadyPromise() { @@ -731,7 +733,7 @@ var PDFDoc = (function PDFDocClosure() { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; var depFonts = data.depFonts; - + page.stats.timeEnd('Page Request'); page.startRenderingFromIRQueue(data.IRQueue, depFonts); }, this); @@ -840,6 +842,7 @@ var PDFDoc = (function PDFDocClosure() { startRendering: function pdfDocStartRendering(page) { // The worker might not be ready to receive the page request yet. this.workerReadyPromise.then(function pdfDocStartRenderingThen() { + page.stats.time('Page Request'); this.messageHandler.send('page_request', page.pageNumber + 1); }.bind(this)); }, diff --git a/src/util.js b/src/util.js index 93bd36b55..b6869b30f 100644 --- a/src/util.js +++ b/src/util.js @@ -416,3 +416,55 @@ var Promise = (function PromiseClosure() { return Promise; })(); +var StatTimer = (function StatTimerClosure() { + function rpad(str, pad, length) { + while (str.length < length) + str += pad; + return str; + } + function StatTimer() { + this.started = {}; + this.times = []; + this.enabled = true; + } + StatTimer.prototype = { + time: function statTimerTime(name) { + if (!this.enabled) + return; + if (name in this.started) + throw 'Timer is already running for ' + name; + this.started[name] = Date.now(); + }, + timeEnd: function statTimerTimeEnd(name) { + if (!this.enabled) + return; + if (!(name in this.started)) + throw 'Timer has not been started for ' + name; + this.times.push({ + 'name': name, + 'start': this.started[name], + 'end': Date.now() + }); + // Remove timer from started so it can be called again. + delete this.started[name]; + }, + toString: function statTimerToString() { + var times = this.times; + var out = ''; + // Find the longest name for padding purposes. + var longest = 0; + for (var i = 0, ii = times.length; i < ii; ++i) { + var name = times[i]['name']; + if (name.length > longest) + longest = name.length; + } + for (var i = 0, ii = times.length; i < ii; ++i) { + var span = times[i]; + var duration = span.end - span.start; + out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; + } + return out; + } + }; + return StatTimer; +})(); diff --git a/web/debugger.js b/web/debugger.js index 43407fdaa..00f5f6fd4 100644 --- a/web/debugger.js +++ b/web/debugger.js @@ -318,6 +318,58 @@ var Stepper = (function StepperClosure() { return Stepper; })(); +var Stats = (function Stats() { + var stats = []; + function clear(node) { + while (node.hasChildNodes()) + node.removeChild(node.lastChild); + } + function getStatIndex(pageNumber) { + for (var i = 0, ii = stats.length; i < ii; ++i) + if (stats[i].pageNumber === pageNumber) + return i; + return false; + } + return { + // Poperties/functions needed by PDFBug. + id: 'Stats', + name: 'Stats', + panel: null, + manager: null, + init: function init() { + this.panel.setAttribute('style', 'padding: 5px;'); + PDFJS.enableStats = true; + }, + enabled: false, + active: false, + // Stats specific functions. + add: function(pageNumber, stat) { + if (!stat) + return; + var statsIndex = getStatIndex(pageNumber); + if (statsIndex !== false) { + var b = stats[statsIndex]; + this.panel.removeChild(b.div); + stats.splice(statsIndex, 1); + } + var wrapper = document.createElement('div'); + wrapper.className = 'stats'; + var title = document.createElement('div'); + title.className = 'title'; + title.textContent = 'Page: ' + pageNumber; + var statsDiv = document.createElement('div'); + statsDiv.textContent = stat.toString(); + wrapper.appendChild(title); + wrapper.appendChild(statsDiv); + stats.push({ pageNumber: pageNumber, div: wrapper }); + stats.sort(function(a, b) { return a.pageNumber - b.pageNumber}); + clear(this.panel); + for (var i = 0, ii = stats.length; i < ii; ++i) + this.panel.appendChild(stats[i].div); + } + }; +})(); + // Manages all the debugging tools. var PDFBug = (function PDFBugClosure() { var panelWidth = 300; @@ -327,8 +379,29 @@ var PDFBug = (function PDFBugClosure() { return { tools: [ FontInspector, - StepperManager + StepperManager, + Stats ], + enable: function(ids) { + var all = false, tools = this.tools; + if (ids.length === 1 && ids[0] === 'all') + all = true; + for (var i = 0; i < tools.length; ++i) { + var tool = tools[i]; + if (all || ids.indexOf(tool.id) !== -1) + tool.enabled = true; + } + if (!all) { + // Sort the tools by the order they are enabled. + tools.sort(function(a, b) { + var indexA = ids.indexOf(a.id); + indexA = indexA < 0 ? tools.length : indexA; + var indexB = ids.indexOf(b.id); + indexB = indexB < 0 ? tools.length : indexB; + return indexA - indexB; + }); + } + }, init: function init() { /* * Basic Layout: diff --git a/web/viewer.css b/web/viewer.css index befaf7aa6..fdce0288a 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -65,16 +65,6 @@ body { line-height: 16px; } -span#info { - display: none; -} - -@-moz-document regexp("http:.*debug=1.*") { - span#info { - display: inline-block; - } -} - /* === Sidebar === */ #sidebar { position: fixed; @@ -442,3 +432,11 @@ canvas { background: yellow; opacity: 0.3; } +#PDFBug .stats { + font-size: 10px; + white-space: pre; + font-family: courier; +} +#PDFBug .stats .title { + font-weight: bold; +} diff --git a/web/viewer.html b/web/viewer.html index 3d1b3c4b7..34b2e77cb 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -102,7 +102,6 @@ Bookmark - --