browser/devtools/profiler/cleopatra/js/ProgressReporter.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:67318096481f
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 /**
6 * ProgressReporter
7 *
8 * This class is used by long-winded tasks to report progress to observers.
9 * If a task has subtasks that want to report their own progress, these
10 * subtasks can have their own progress reporters which are hooked up to the
11 * parent progress reporter, resulting in a tree structure. A parent progress
12 * reporter will calculate its progress value as a weighted sum of its
13 * subreporters' progress values.
14 *
15 * A progress reporter has a state, an action, and a progress value.
16 *
17 * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED.
18 * - action is a string that describes the current task.
19 * - progress is the progress value as a number between 0 and 1, or NaN if
20 * indeterminate.
21 *
22 * A progress reporter starts out in the WAITING state. The DOING state is
23 * entered with the begin method which also sets the action. While the task is
24 * executing, the progress value can be updated with the setProgress method.
25 * When a task has finished, it can call the finish method which is just a
26 * shorthand for setProgress(1); this will set the state to FINISHED.
27 *
28 * Progress observers can be added with the addListener method which takes a
29 * function callback. Whenever the progress value or state change, all
30 * listener callbacks will be called with the progress reporter object. The
31 * observer can get state, progress value and action by calling the getter
32 * methods getState(), getProgress() and getAction().
33 *
34 * Creating child progress reporters for subtasks can be done with the
35 * addSubreporter(s) methods. If a progress reporter has subreporters, normal
36 * progress report functions (setProgress and finish) can no longer be called.
37 * Instead, the parent reporter will listen to progress changes on its
38 * subreporters and update its state automatically, and then notify its own
39 * listeners.
40 * When adding a subreporter, you are expected to provide an estimated
41 * duration for the subtask. This value will be used as a weight when
42 * calculating the progress of the parent reporter.
43 */
44
45 "use strict";
46
47 const gDebugExpectedDurations = false;
48
49 function ProgressReporter() {
50 this._observers = [];
51 this._subreporters = [];
52 this._subreporterExpectedDurationsSum = 0;
53 this._progress = 0;
54 this._state = ProgressReporter.STATE_WAITING;
55 this._action = "";
56 }
57
58 ProgressReporter.STATE_WAITING = 0;
59 ProgressReporter.STATE_DOING = 1;
60 ProgressReporter.STATE_FINISHED = 2;
61
62 ProgressReporter.prototype = {
63 getProgress: function () {
64 return this._progress;
65 },
66 getState: function () {
67 return this._state;
68 },
69 setAction: function (action) {
70 this._action = action;
71 this._reportProgress();
72 },
73 getAction: function () {
74 switch (this._state) {
75 case ProgressReporter.STATE_WAITING:
76 return "Waiting for preceding tasks to finish...";
77 case ProgressReporter.STATE_DOING:
78 return this._action;
79 case ProgressReporter.STATE_FINISHED:
80 return "Finished.";
81 default:
82 throw "Broken state";
83 }
84 },
85 addListener: function (callback) {
86 this._observers.push(callback);
87 },
88 addSubreporter: function (expectedDuration) {
89 this._subreporterExpectedDurationsSum += expectedDuration;
90 var subreporter = new ProgressReporter();
91 var self = this;
92 subreporter.addListener(function (progress) {
93 self._recalculateProgressFromSubreporters();
94 self._recalculateStateAndActionFromSubreporters();
95 self._reportProgress();
96 });
97 this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter });
98 return subreporter;
99 },
100 addSubreporters: function (expectedDurations) {
101 var reporters = {};
102 for (var key in expectedDurations) {
103 reporters[key] = this.addSubreporter(expectedDurations[key]);
104 }
105 return reporters;
106 },
107 begin: function (action) {
108 this._startTime = Date.now();
109 this._state = ProgressReporter.STATE_DOING;
110 this._action = action;
111 this._reportProgress();
112 },
113 setProgress: function (progress) {
114 if (this._subreporters.length > 0)
115 throw "Can't call setProgress on a progress reporter with subreporters";
116 if (progress != this._progress &&
117 (progress == 1 ||
118 (isNaN(progress) != isNaN(this._progress)) ||
119 (progress - this._progress >= 0.01))) {
120 this._progress = progress;
121 if (progress == 1)
122 this._transitionToFinished();
123 this._reportProgress();
124 }
125 },
126 finish: function () {
127 this.setProgress(1);
128 },
129 _recalculateProgressFromSubreporters: function () {
130 if (this._subreporters.length == 0)
131 throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters";
132 this._progress = 0;
133 for (var i = 0; i < this._subreporters.length; i++) {
134 var expectedDuration = this._subreporters[i].expectedDuration;
135 var reporter = this._subreporters[i].reporter;
136 this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum;
137 }
138 },
139 _recalculateStateAndActionFromSubreporters: function () {
140 if (this._subreporters.length == 0)
141 throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters";
142 var actions = [];
143 var allWaiting = true;
144 var allFinished = true;
145 for (var i = 0; i < this._subreporters.length; i++) {
146 var expectedDuration = this._subreporters[i].expectedDuration;
147 var reporter = this._subreporters[i].reporter;
148 var state = reporter.getState();
149 if (state != ProgressReporter.STATE_WAITING)
150 allWaiting = false;
151 if (state != ProgressReporter.STATE_FINISHED)
152 allFinished = false;
153 if (state == ProgressReporter.STATE_DOING)
154 actions.push(reporter.getAction());
155 }
156 if (allFinished) {
157 this._transitionToFinished();
158 } else if (!allWaiting) {
159 this._state = ProgressReporter.STATE_DOING;
160 if (actions.length == 0) {
161 this._action = "About to start next task..."
162 } else {
163 this._action = actions.join("\n");
164 }
165 }
166 },
167 _transitionToFinished: function () {
168 this._state = ProgressReporter.STATE_FINISHED;
169
170 if (gDebugExpectedDurations) {
171 this._realDuration = Date.now() - this._startTime;
172 if (this._subreporters.length) {
173 for (var i = 0; i < this._subreporters.length; i++) {
174 var expectedDuration = this._subreporters[i].expectedDuration;
175 var reporter = this._subreporters[i].reporter;
176 var realDuration = reporter._realDuration;
177 dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n");
178 }
179 }
180 }
181 },
182 _reportProgress: function () {
183 for (var i = 0; i < this._observers.length; i++) {
184 this._observers[i](this);
185 }
186 },
187 };

mercurial