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

changeset 0
6474c204b198
     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 +};

mercurial