1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/profiler/cleopatra/js/ProgressReporter.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,187 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * ProgressReporter 1.10 + * 1.11 + * This class is used by long-winded tasks to report progress to observers. 1.12 + * If a task has subtasks that want to report their own progress, these 1.13 + * subtasks can have their own progress reporters which are hooked up to the 1.14 + * parent progress reporter, resulting in a tree structure. A parent progress 1.15 + * reporter will calculate its progress value as a weighted sum of its 1.16 + * subreporters' progress values. 1.17 + * 1.18 + * A progress reporter has a state, an action, and a progress value. 1.19 + * 1.20 + * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED. 1.21 + * - action is a string that describes the current task. 1.22 + * - progress is the progress value as a number between 0 and 1, or NaN if 1.23 + * indeterminate. 1.24 + * 1.25 + * A progress reporter starts out in the WAITING state. The DOING state is 1.26 + * entered with the begin method which also sets the action. While the task is 1.27 + * executing, the progress value can be updated with the setProgress method. 1.28 + * When a task has finished, it can call the finish method which is just a 1.29 + * shorthand for setProgress(1); this will set the state to FINISHED. 1.30 + * 1.31 + * Progress observers can be added with the addListener method which takes a 1.32 + * function callback. Whenever the progress value or state change, all 1.33 + * listener callbacks will be called with the progress reporter object. The 1.34 + * observer can get state, progress value and action by calling the getter 1.35 + * methods getState(), getProgress() and getAction(). 1.36 + * 1.37 + * Creating child progress reporters for subtasks can be done with the 1.38 + * addSubreporter(s) methods. If a progress reporter has subreporters, normal 1.39 + * progress report functions (setProgress and finish) can no longer be called. 1.40 + * Instead, the parent reporter will listen to progress changes on its 1.41 + * subreporters and update its state automatically, and then notify its own 1.42 + * listeners. 1.43 + * When adding a subreporter, you are expected to provide an estimated 1.44 + * duration for the subtask. This value will be used as a weight when 1.45 + * calculating the progress of the parent reporter. 1.46 + */ 1.47 + 1.48 +"use strict"; 1.49 + 1.50 +const gDebugExpectedDurations = false; 1.51 + 1.52 +function ProgressReporter() { 1.53 + this._observers = []; 1.54 + this._subreporters = []; 1.55 + this._subreporterExpectedDurationsSum = 0; 1.56 + this._progress = 0; 1.57 + this._state = ProgressReporter.STATE_WAITING; 1.58 + this._action = ""; 1.59 +} 1.60 + 1.61 +ProgressReporter.STATE_WAITING = 0; 1.62 +ProgressReporter.STATE_DOING = 1; 1.63 +ProgressReporter.STATE_FINISHED = 2; 1.64 + 1.65 +ProgressReporter.prototype = { 1.66 + getProgress: function () { 1.67 + return this._progress; 1.68 + }, 1.69 + getState: function () { 1.70 + return this._state; 1.71 + }, 1.72 + setAction: function (action) { 1.73 + this._action = action; 1.74 + this._reportProgress(); 1.75 + }, 1.76 + getAction: function () { 1.77 + switch (this._state) { 1.78 + case ProgressReporter.STATE_WAITING: 1.79 + return "Waiting for preceding tasks to finish..."; 1.80 + case ProgressReporter.STATE_DOING: 1.81 + return this._action; 1.82 + case ProgressReporter.STATE_FINISHED: 1.83 + return "Finished."; 1.84 + default: 1.85 + throw "Broken state"; 1.86 + } 1.87 + }, 1.88 + addListener: function (callback) { 1.89 + this._observers.push(callback); 1.90 + }, 1.91 + addSubreporter: function (expectedDuration) { 1.92 + this._subreporterExpectedDurationsSum += expectedDuration; 1.93 + var subreporter = new ProgressReporter(); 1.94 + var self = this; 1.95 + subreporter.addListener(function (progress) { 1.96 + self._recalculateProgressFromSubreporters(); 1.97 + self._recalculateStateAndActionFromSubreporters(); 1.98 + self._reportProgress(); 1.99 + }); 1.100 + this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter }); 1.101 + return subreporter; 1.102 + }, 1.103 + addSubreporters: function (expectedDurations) { 1.104 + var reporters = {}; 1.105 + for (var key in expectedDurations) { 1.106 + reporters[key] = this.addSubreporter(expectedDurations[key]); 1.107 + } 1.108 + return reporters; 1.109 + }, 1.110 + begin: function (action) { 1.111 + this._startTime = Date.now(); 1.112 + this._state = ProgressReporter.STATE_DOING; 1.113 + this._action = action; 1.114 + this._reportProgress(); 1.115 + }, 1.116 + setProgress: function (progress) { 1.117 + if (this._subreporters.length > 0) 1.118 + throw "Can't call setProgress on a progress reporter with subreporters"; 1.119 + if (progress != this._progress && 1.120 + (progress == 1 || 1.121 + (isNaN(progress) != isNaN(this._progress)) || 1.122 + (progress - this._progress >= 0.01))) { 1.123 + this._progress = progress; 1.124 + if (progress == 1) 1.125 + this._transitionToFinished(); 1.126 + this._reportProgress(); 1.127 + } 1.128 + }, 1.129 + finish: function () { 1.130 + this.setProgress(1); 1.131 + }, 1.132 + _recalculateProgressFromSubreporters: function () { 1.133 + if (this._subreporters.length == 0) 1.134 + throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters"; 1.135 + this._progress = 0; 1.136 + for (var i = 0; i < this._subreporters.length; i++) { 1.137 + var expectedDuration = this._subreporters[i].expectedDuration; 1.138 + var reporter = this._subreporters[i].reporter; 1.139 + this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum; 1.140 + } 1.141 + }, 1.142 + _recalculateStateAndActionFromSubreporters: function () { 1.143 + if (this._subreporters.length == 0) 1.144 + throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters"; 1.145 + var actions = []; 1.146 + var allWaiting = true; 1.147 + var allFinished = true; 1.148 + for (var i = 0; i < this._subreporters.length; i++) { 1.149 + var expectedDuration = this._subreporters[i].expectedDuration; 1.150 + var reporter = this._subreporters[i].reporter; 1.151 + var state = reporter.getState(); 1.152 + if (state != ProgressReporter.STATE_WAITING) 1.153 + allWaiting = false; 1.154 + if (state != ProgressReporter.STATE_FINISHED) 1.155 + allFinished = false; 1.156 + if (state == ProgressReporter.STATE_DOING) 1.157 + actions.push(reporter.getAction()); 1.158 + } 1.159 + if (allFinished) { 1.160 + this._transitionToFinished(); 1.161 + } else if (!allWaiting) { 1.162 + this._state = ProgressReporter.STATE_DOING; 1.163 + if (actions.length == 0) { 1.164 + this._action = "About to start next task..." 1.165 + } else { 1.166 + this._action = actions.join("\n"); 1.167 + } 1.168 + } 1.169 + }, 1.170 + _transitionToFinished: function () { 1.171 + this._state = ProgressReporter.STATE_FINISHED; 1.172 + 1.173 + if (gDebugExpectedDurations) { 1.174 + this._realDuration = Date.now() - this._startTime; 1.175 + if (this._subreporters.length) { 1.176 + for (var i = 0; i < this._subreporters.length; i++) { 1.177 + var expectedDuration = this._subreporters[i].expectedDuration; 1.178 + var reporter = this._subreporters[i].reporter; 1.179 + var realDuration = reporter._realDuration; 1.180 + dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n"); 1.181 + } 1.182 + } 1.183 + } 1.184 + }, 1.185 + _reportProgress: function () { 1.186 + for (var i = 0; i < this._observers.length; i++) { 1.187 + this._observers[i](this); 1.188 + } 1.189 + }, 1.190 +};