From bd6d89e1a861db721d06c2dc1cf39775cc938545 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 11 Jan 2012 16:48:51 -0800 Subject: [PATCH 1/4] Start of the benchmark recording framework. --- src/core.js | 33 +++++++++++++++++--------------- src/util.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ web/viewer.css | 6 ++++++ web/viewer.js | 22 ++++++++++++++------- 4 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/core.js b/src/core.js index 765a239b7..90fcb09d5 100644 --- a/src/core.js +++ b/src/core.js @@ -60,13 +60,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.bench = new Bench(); + this.bench.enabled = !!globalScope.PDFJS.enableBench; this.xref = xref; this.ref = ref; @@ -187,6 +182,8 @@ var Page = (function PageClosure() { return this.IRQueue; } + this.bench.time('Build IR Queue'); + var xref = this.xref; var content = xref.fetchIfRef(this.content); var resources = xref.fetchIfRef(this.resources); @@ -201,11 +198,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.bench.timeEnd('Build IR Queue'); + return this.IRQueue; }, ensureFonts: function pageEnsureFonts(fonts, callback) { + this.bench.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; @@ -215,7 +215,7 @@ var Page = (function PageClosure() { var fontObjs = FontLoader.bind( fonts, function pageEnsureFontsFontObjs(fontObjs) { - this.stats.fonts = Date.now(); + this.bench.timeEnd('Font Loading'); callback.call(this); }.bind(this), @@ -224,6 +224,8 @@ var Page = (function PageClosure() { }, display: function pageDisplay(gfx, callback) { + var bench = this.bench; + bench.time('Rendering'); var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -244,8 +246,9 @@ var Page = (function PageClosure() { function next() { startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); if (startIdx == length) { - self.stats.render = Date.now(); gfx.endDrawing(); + bench.timeEnd('Rendering'); + bench.timeEnd('Overall'); if (callback) callback(); } } @@ -388,15 +391,14 @@ var Page = (function PageClosure() { return items; }, startRendering: function pageStartRendering(ctx, callback, textLayer) { - this.startRenderingTime = Date.now(); - + var bench = this.bench; + bench.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() { @@ -677,7 +679,7 @@ var PDFDoc = (function PDFDocClosure() { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; var depFonts = data.depFonts; - + page.bench.timeEnd('Page Request'); page.startRenderingFromIRQueue(data.IRQueue, depFonts); }, this); @@ -786,6 +788,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.bench.time('Page Request'); this.messageHandler.send('page_request', page.pageNumber + 1); }.bind(this)); }, diff --git a/src/util.js b/src/util.js index 99b422296..80cf937c4 100644 --- a/src/util.js +++ b/src/util.js @@ -338,3 +338,55 @@ var Promise = (function PromiseClosure() { return Promise; })(); +var Bench = (function BenchClosure() { + function rpad(str, pad, length) { + while (str.length < length) + str += pad; + return str; + } + function Bench() { + this.started = {}; + this.times = []; + this.enabled = true; + } + Bench.prototype = { + time: function benchTime(name) { + if (!this.enabled) + return; + if (name in this.started) + throw 'Timer is already running for ' + name; + this.started[name] = Date.now(); + }, + timeEnd: function benchTimeEnd(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 benchToString() { + 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 Bench; +})(); diff --git a/web/viewer.css b/web/viewer.css index e355f7fc2..d5c18504c 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -67,6 +67,12 @@ body { span#info { display: none; + position: fixed; + top: 32px; + right: 0px; + font-size: 10px; + white-space: pre; + font-family: courier; } @-moz-document regexp("http:.*debug=1.*") { diff --git a/web/viewer.js b/web/viewer.js index ac3fbff0c..fc7d99cdd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -222,6 +222,7 @@ var PDFView = { return; } + pages[val - 1].updateStats(); currentPageNumber = val; var event = document.createEvent('UIEvents'); event.initUIEvent('pagechange', false, false, window, 0); @@ -421,7 +422,7 @@ var PDFView = { for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); var pageView = new PageView(container, page, i, page.width, page.height, - page.stats, this.navigateTo.bind(this)); + page.bench, this.navigateTo.bind(this)); var thumbnailView = new ThumbnailView(sidebar, page, i, page.width / page.height); bindOnAfterDraw(pageView, thumbnailView); @@ -581,7 +582,7 @@ var PDFView = { }; var PageView = function pageView(container, content, id, pageWidth, pageHeight, - stats, navigateTo) { + bench, navigateTo) { this.id = id; this.content = content; @@ -800,11 +801,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, ctx.restore(); ctx.translate(-this.x * scale, -this.y * scale); - stats.begin = Date.now(); this.content.startRendering(ctx, (function pageViewDrawCallback(error) { if (error) PDFView.error('An error occurred while rendering the page.', error); + this.stats = content.bench; this.updateStats(); if (this.onAfterDraw) this.onAfterDraw(); @@ -819,10 +820,12 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, }; this.updateStats = function pageViewUpdateStats() { - var t1 = stats.compile, t2 = stats.fonts, t3 = stats.render; - var str = 'Time to compile/fonts/render: ' + - (t1 - stats.begin) + '/' + (t2 - t1) + '/' + (t3 - t2) + ' ms'; - document.getElementById('info').innerHTML = str; + if (!PDFJS.enableBench || !this.stats || PDFView.page != this.id) + return; + var stats = this.stats; + var statsHtml = 'Page ' + this.id + '\n'; + statsHtml += stats.toString().replace(/\n/g, '
'); + document.getElementById('info').innerHTML = statsHtml; }; }; @@ -1016,6 +1019,11 @@ window.addEventListener('load', function webViewerLoad(evt) { if ('disableTextLayer' in params) PDFJS.disableTextLayer = (params['disableTextLayer'] === 'true'); + if ('enableBench' in params) + PDFJS.enableBench = (params['enableBench'] === 'true'); + if (PDFJS.enableBench) + document.getElementById('info').style.display = 'block'; + var sidebarScrollView = document.getElementById('sidebarScrollView'); sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true); }, true); From 66052f2dd29aa62f72f91823cdc2275b27e71005 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 8 Feb 2012 16:38:43 -0800 Subject: [PATCH 2/4] Switch to textContent. Fix lint. --- src/util.js | 2 +- web/viewer.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util.js b/src/util.js index 4ef87c245..5c48c198e 100644 --- a/src/util.js +++ b/src/util.js @@ -409,6 +409,6 @@ var Bench = (function BenchClosure() { } return out; } - } + }; return Bench; })(); diff --git a/web/viewer.js b/web/viewer.js index 2e4c1a767..de32aca89 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -887,9 +887,9 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (!PDFJS.enableBench || !this.stats || PDFView.page != this.id) return; var stats = this.stats; - var statsHtml = 'Page ' + this.id + '\n'; - statsHtml += stats.toString().replace(/\n/g, '
'); - document.getElementById('info').innerHTML = statsHtml; + var statsText = 'Page ' + this.id + '\n'; + statsText += stats.toString(); + document.getElementById('info').textContent = statsText; }; }; From e07505ff8da4b307d20a1faebe787c246f0cd36f Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 21 Feb 2012 09:52:09 -0800 Subject: [PATCH 3/4] Update stats to use the new pdfBug panel. --- src/core.js | 28 +++++++++--------- src/util.js | 14 ++++----- web/debugger.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++++- web/viewer.css | 24 ++++++---------- web/viewer.js | 34 ++++++---------------- 5 files changed, 112 insertions(+), 63 deletions(-) diff --git a/src/core.js b/src/core.js index f09ed927c..c816e7284 100644 --- a/src/core.js +++ b/src/core.js @@ -63,8 +63,8 @@ var Page = (function PageClosure() { function Page(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; - this.bench = new Bench(); - this.bench.enabled = !!globalScope.PDFJS.enableBench; + this.stats = new StatTimer(); + this.stats.enabled = !!globalScope.PDFJS.enableStats; this.xref = xref; this.ref = ref; @@ -195,7 +195,7 @@ var Page = (function PageClosure() { return this.IRQueue; } - this.bench.time('Build IR Queue'); + this.stats.time('Build IR Queue'); var xref = this.xref; var content = xref.fetchIfRef(this.content); @@ -216,12 +216,12 @@ var Page = (function PageClosure() { var IRQueue = {}; this.IRQueue = pe.getIRQueue(content, resources, IRQueue, dependency); - this.bench.timeEnd('Build IR Queue'); + this.stats.timeEnd('Build IR Queue'); return this.IRQueue; }, ensureFonts: function pageEnsureFonts(fonts, callback) { - this.bench.time('Font Loading'); + 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.bench.timeEnd('Font Loading'); + this.stats.timeEnd('Font Loading'); callback.call(this); }.bind(this), @@ -240,8 +240,8 @@ var Page = (function PageClosure() { }, display: function pageDisplay(gfx, callback) { - var bench = this.bench; - bench.time('Rendering'); + var stats = this.stats; + stats.time('Rendering'); var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -269,8 +269,8 @@ var Page = (function PageClosure() { startIdx = gfx.executeIRQueue(IRQueue, startIdx, next, stepper); if (startIdx == length) { gfx.endDrawing(); - bench.timeEnd('Rendering'); - bench.timeEnd('Overall'); + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); if (callback) callback(); } } @@ -413,8 +413,8 @@ var Page = (function PageClosure() { return items; }, startRendering: function pageStartRendering(ctx, callback, textLayer) { - var bench = this.bench; - bench.time('Overall'); + 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) { @@ -712,7 +712,7 @@ var PDFDoc = (function PDFDocClosure() { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; var depFonts = data.depFonts; - page.bench.timeEnd('Page Request'); + page.stats.timeEnd('Page Request'); page.startRenderingFromIRQueue(data.IRQueue, depFonts); }, this); @@ -821,7 +821,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.bench.time('Page Request'); + 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 17ef81983..b6869b30f 100644 --- a/src/util.js +++ b/src/util.js @@ -416,26 +416,26 @@ var Promise = (function PromiseClosure() { return Promise; })(); -var Bench = (function BenchClosure() { +var StatTimer = (function StatTimerClosure() { function rpad(str, pad, length) { while (str.length < length) str += pad; return str; } - function Bench() { + function StatTimer() { this.started = {}; this.times = []; this.enabled = true; } - Bench.prototype = { - time: function benchTime(name) { + 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 benchTimeEnd(name) { + timeEnd: function statTimerTimeEnd(name) { if (!this.enabled) return; if (!(name in this.started)) @@ -448,7 +448,7 @@ var Bench = (function BenchClosure() { // Remove timer from started so it can be called again. delete this.started[name]; }, - toString: function benchToString() { + toString: function statTimerToString() { var times = this.times; var out = ''; // Find the longest name for padding purposes. @@ -466,5 +466,5 @@ var Bench = (function BenchClosure() { return out; } }; - return Bench; + return StatTimer; })(); diff --git a/web/debugger.js b/web/debugger.js index c759a5ee8..53fd332ea 100644 --- a/web/debugger.js +++ b/web/debugger.js @@ -312,6 +312,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; @@ -321,8 +373,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 dc091daff..fdce0288a 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -65,22 +65,6 @@ body { line-height: 16px; } -span#info { - display: none; - position: fixed; - top: 32px; - right: 0px; - font-size: 10px; - white-space: pre; - font-family: courier; -} - -@-moz-document regexp("http:.*debug=1.*") { - span#info { - display: inline-block; - } -} - /* === Sidebar === */ #sidebar { position: fixed; @@ -448,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.js b/web/viewer.js index 816f14ffe..d66a8b5fd 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -458,7 +458,7 @@ var PDFView = { for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); var pageView = new PageView(container, page, i, page.width, page.height, - page.bench, this.navigateTo.bind(this)); + page.stats, this.navigateTo.bind(this)); var thumbnailView = new ThumbnailView(sidebar, page, i, page.width / page.height); bindOnAfterDraw(pageView, thumbnailView); @@ -635,7 +635,7 @@ var PDFView = { }; var PageView = function pageView(container, content, id, pageWidth, pageHeight, - bench, navigateTo) { + stats, navigateTo) { this.id = id; this.content = content; @@ -880,7 +880,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (error) PDFView.error('An error occurred while rendering the page.', error); - self.stats = content.bench; + self.stats = content.stats; self.updateStats(); if (self.onAfterDraw) self.onAfterDraw(); @@ -894,12 +894,10 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, }; this.updateStats = function pageViewUpdateStats() { - if (!PDFJS.enableBench || !this.stats || PDFView.page != this.id) - return; - var stats = this.stats; - var statsText = 'Page ' + this.id + '\n'; - statsText += stats.toString(); - document.getElementById('info').textContent = statsText; + if (PDFJS.pdfBug && Stats.enabled) { + var stats = this.stats; + Stats.add(this.id, stats); + } }; }; @@ -1134,25 +1132,11 @@ window.addEventListener('load', function webViewerLoad(evt) { if ('pdfBug' in hashParams) { PDFJS.pdfBug = true; var pdfBug = hashParams['pdfBug']; - var all = false, enabled = []; - if (pdfBug === 'all') - all = true; - else - enabled = pdfBug.split(','); - var debugTools = PDFBug.tools; - for (var i = 0; i < debugTools.length; ++i) { - var tool = debugTools[i]; - if (all || enabled.indexOf(tool.id) !== -1) - tool.enabled = true; - } + var enabled = pdfBug.split(','); + PDFBug.enable(enabled); PDFBug.init(); } - if ('enableBench' in params) - PDFJS.enableBench = (params['enableBench'] === 'true'); - if (PDFJS.enableBench) - document.getElementById('info').style.display = 'block'; - var sidebarScrollView = document.getElementById('sidebarScrollView'); sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true); }, true); From b24d647db292b9a7de507a4c72b2bb7dc8b23b46 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 12 Mar 2012 14:15:09 -0700 Subject: [PATCH 4/4] Remove unused span. --- web/viewer.html | 1 - 1 file changed, 1 deletion(-) 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 - --