summaryrefslogtreecommitdiff
path: root/browser/devtools/profiler/cleopatra/js/ProgressReporter.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/devtools/profiler/cleopatra/js/ProgressReporter.js')
-rw-r--r--browser/devtools/profiler/cleopatra/js/ProgressReporter.js185
1 files changed, 185 insertions, 0 deletions
diff --git a/browser/devtools/profiler/cleopatra/js/ProgressReporter.js b/browser/devtools/profiler/cleopatra/js/ProgressReporter.js
new file mode 100644
index 000000000..f885aecd5
--- /dev/null
+++ b/browser/devtools/profiler/cleopatra/js/ProgressReporter.js
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * ProgressReporter
+ *
+ * This class is used by long-winded tasks to report progress to observers.
+ * If a task has subtasks that want to report their own progress, these
+ * subtasks can have their own progress reporters which are hooked up to the
+ * parent progress reporter, resulting in a tree structure. A parent progress
+ * reporter will calculate its progress value as a weighted sum of its
+ * subreporters' progress values.
+ *
+ * A progress reporter has a state, an action, and a progress value.
+ *
+ * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED.
+ * - action is a string that describes the current task.
+ * - progress is the progress value as a number between 0 and 1, or NaN if
+ * indeterminate.
+ *
+ * A progress reporter starts out in the WAITING state. The DOING state is
+ * entered with the begin method which also sets the action. While the task is
+ * executing, the progress value can be updated with the setProgress method.
+ * When a task has finished, it can call the finish method which is just a
+ * shorthand for setProgress(1); this will set the state to FINISHED.
+ *
+ * Progress observers can be added with the addListener method which takes a
+ * function callback. Whenever the progress value or state change, all
+ * listener callbacks will be called with the progress reporter object. The
+ * observer can get state, progress value and action by calling the getter
+ * methods getState(), getProgress() and getAction().
+ *
+ * Creating child progress reporters for subtasks can be done with the
+ * addSubreporter(s) methods. If a progress reporter has subreporters, normal
+ * progress report functions (setProgress and finish) can no longer be called.
+ * Instead, the parent reporter will listen to progress changes on its
+ * subreporters and update its state automatically, and then notify its own
+ * listeners.
+ * When adding a subreporter, you are expected to provide an estimated
+ * duration for the subtask. This value will be used as a weight when
+ * calculating the progress of the parent reporter.
+ */
+
+const gDebugExpectedDurations = false;
+
+function ProgressReporter() {
+ this._observers = [];
+ this._subreporters = [];
+ this._subreporterExpectedDurationsSum = 0;
+ this._progress = 0;
+ this._state = ProgressReporter.STATE_WAITING;
+ this._action = "";
+}
+
+ProgressReporter.STATE_WAITING = 0;
+ProgressReporter.STATE_DOING = 1;
+ProgressReporter.STATE_FINISHED = 2;
+
+ProgressReporter.prototype = {
+ getProgress: function () {
+ return this._progress;
+ },
+ getState: function () {
+ return this._state;
+ },
+ setAction: function (action) {
+ this._action = action;
+ this._reportProgress();
+ },
+ getAction: function () {
+ switch (this._state) {
+ case ProgressReporter.STATE_WAITING:
+ return "Waiting for preceding tasks to finish...";
+ case ProgressReporter.STATE_DOING:
+ return this._action;
+ case ProgressReporter.STATE_FINISHED:
+ return "Finished.";
+ default:
+ throw "Broken state";
+ }
+ },
+ addListener: function (callback) {
+ this._observers.push(callback);
+ },
+ addSubreporter: function (expectedDuration) {
+ this._subreporterExpectedDurationsSum += expectedDuration;
+ var subreporter = new ProgressReporter();
+ var self = this;
+ subreporter.addListener(function (progress) {
+ self._recalculateProgressFromSubreporters();
+ self._recalculateStateAndActionFromSubreporters();
+ self._reportProgress();
+ });
+ this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter });
+ return subreporter;
+ },
+ addSubreporters: function (expectedDurations) {
+ var reporters = {};
+ for (var key in expectedDurations) {
+ reporters[key] = this.addSubreporter(expectedDurations[key]);
+ }
+ return reporters;
+ },
+ begin: function (action) {
+ this._startTime = Date.now();
+ this._state = ProgressReporter.STATE_DOING;
+ this._action = action;
+ this._reportProgress();
+ },
+ setProgress: function (progress) {
+ if (this._subreporters.length > 0)
+ throw "Can't call setProgress on a progress reporter with subreporters";
+ if (progress != this._progress &&
+ (progress == 1 ||
+ (isNaN(progress) != isNaN(this._progress)) ||
+ (progress - this._progress >= 0.01))) {
+ this._progress = progress;
+ if (progress == 1)
+ this._transitionToFinished();
+ this._reportProgress();
+ }
+ },
+ finish: function () {
+ this.setProgress(1);
+ },
+ _recalculateProgressFromSubreporters: function () {
+ if (this._subreporters.length == 0)
+ throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters";
+ this._progress = 0;
+ for (var i = 0; i < this._subreporters.length; i++) {
+ var expectedDuration = this._subreporters[i].expectedDuration;
+ var reporter = this._subreporters[i].reporter;
+ this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum;
+ }
+ },
+ _recalculateStateAndActionFromSubreporters: function () {
+ if (this._subreporters.length == 0)
+ throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters";
+ var actions = [];
+ var allWaiting = true;
+ var allFinished = true;
+ for (var i = 0; i < this._subreporters.length; i++) {
+ var expectedDuration = this._subreporters[i].expectedDuration;
+ var reporter = this._subreporters[i].reporter;
+ var state = reporter.getState();
+ if (state != ProgressReporter.STATE_WAITING)
+ allWaiting = false;
+ if (state != ProgressReporter.STATE_FINISHED)
+ allFinished = false;
+ if (state == ProgressReporter.STATE_DOING)
+ actions.push(reporter.getAction());
+ }
+ if (allFinished) {
+ this._transitionToFinished();
+ } else if (!allWaiting) {
+ this._state = ProgressReporter.STATE_DOING;
+ if (actions.length == 0) {
+ this._action = "About to start next task..."
+ } else {
+ this._action = actions.join("\n");
+ }
+ }
+ },
+ _transitionToFinished: function () {
+ this._state = ProgressReporter.STATE_FINISHED;
+
+ if (gDebugExpectedDurations) {
+ this._realDuration = Date.now() - this._startTime;
+ if (this._subreporters.length) {
+ for (var i = 0; i < this._subreporters.length; i++) {
+ var expectedDuration = this._subreporters[i].expectedDuration;
+ var reporter = this._subreporters[i].reporter;
+ var realDuration = reporter._realDuration;
+ dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n");
+ }
+ }
+ }
+ },
+ _reportProgress: function () {
+ for (var i = 0; i < this._observers.length; i++) {
+ this._observers[i](this);
+ }
+ },
+};