Home | History | Annotate | Download | only in chromeos
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 <include src="../../../../third_party/polymer_legacy/platform/platform.js">
      6 <include src="../../../../third_party/polymer_legacy/polymer/polymer.js">
      7 
      8 /**
      9  * Formats size to a human readable form.
     10  * @param {number} size Size in bytes.
     11  * @return {string} Output string in a human-readable format.
     12  */
     13 function formatSizeCommon(size) {
     14   if (size < 1024)
     15     return size + ' B';
     16   if (size < 1024 * 1024)
     17     return Math.round(size / 1024) + ' KB';
     18   return Math.round(size / 1024 / 1024) + ' MB';
     19 }
     20 
     21 // Defines the file-systems element.
     22 Polymer('file-systems', {
     23   /**
     24    * Called when the element is created.
     25    */
     26   ready: function() {
     27   },
     28 
     29   /**
     30    * Selects an active file system from the list.
     31    * @param {Event} event Event.
     32    * @param {number} detail Detail.
     33    * @param {HTMLElement} sender Sender.
     34    */
     35   rowClicked: function(event, detail, sender) {
     36     var requestEventsNode = document.querySelector('#request-events');
     37     requestEventsNode.hidden = false;
     38     requestEventsNode.model = [];
     39 
     40     var requestTimelineNode = document.querySelector('#request-timeline');
     41     requestTimelineNode.hidden = false;
     42     requestTimelineNode.model = [];
     43 
     44     chrome.send('selectFileSystem', [sender.dataset.extensionId,
     45       sender.dataset.id]);
     46   },
     47 
     48   /**
     49    * List of provided file system information maps.
     50    * @type {Array.<Object>}
     51    */
     52   model: []
     53 });
     54 
     55 // Defines the request-log element.
     56 Polymer('request-events', {
     57   /**
     58    * Called when the element is created.
     59    */
     60   ready: function() {
     61   },
     62 
     63   /**
     64    * Formats time to a hh:mm:ss.xxxx format.
     65    * @param {Date} time Input time.
     66    * @return {string} Output string in a human-readable format.
     67    */
     68   formatTime: function(time) {
     69     return ('0' + time.getHours()).slice(-2) + ':' +
     70            ('0' + time.getMinutes()).slice(-2) + ':' +
     71            ('0' + time.getSeconds()).slice(-2) + '.' +
     72            ('000' + time.getMilliseconds()).slice(-3);
     73   },
     74 
     75   /**
     76    * Formats size to a human readable form.
     77    * @param {number} size Size in bytes.
     78    * @return {string} Output string in a human-readable format.
     79    */
     80   formatSize: function(size) {
     81     return formatSizeCommon(size);
     82   },
     83 
     84   /**
     85    * Formats a boolean value to human-readable form.
     86    * @param {boolean=} opt_hasMore Input value.
     87    * @return {string} Output string in a human-readable format.
     88    */
     89   formatHasMore: function(opt_hasMore) {
     90     if (opt_hasMore == undefined)
     91       return '';
     92 
     93     return opt_hasMore ? 'HAS_MORE' : 'LAST';
     94   },
     95 
     96   /**
     97    * Formats execution time to human-readable form.
     98    * @param {boolean=} opt_executionTime Input value.
     99    * @return {string} Output string in a human-readable format.
    100    */
    101   formatExecutionTime: function(opt_executionTime) {
    102     if (opt_executionTime == undefined)
    103       return '';
    104 
    105     return opt_executionTime + ' ms';
    106   },
    107 
    108   /**
    109    * List of events.
    110    * @type {Array.<Object>}
    111    */
    112   model: []
    113 });
    114 
    115 // Defines the request-timeline element.
    116 Polymer('request-timeline', {
    117   /**
    118    * Step for zoomin in and out.
    119    * @type {number}
    120    * @const
    121    */
    122   SCALE_STEP: 1.5,
    123 
    124   /**
    125    * Height of each row in the chart in pixels.
    126    * @type {number}
    127    * @const
    128    */
    129   ROW_HEIGHT: 14,
    130 
    131   /**
    132    * Observes changes in the model.
    133    * @type {Object.<string, string>}
    134    */
    135   observe: {
    136     'model.length': 'chartUpdate'
    137   },
    138 
    139   /**
    140    * Called when the element is created.
    141    */
    142   ready: function() {
    143     // Update active requests in the background for nice animation.
    144     var activeUpdateAnimation = function() {
    145       this.activeUpdate();
    146       requestAnimationFrame(activeUpdateAnimation);
    147     }.bind(this);
    148     activeUpdateAnimation();
    149   },
    150 
    151   /**
    152    * Formats size to a human readable form.
    153    * @param {number} size Size in bytes.
    154    * @return {string} Output string in a human-readable format.
    155    */
    156   formatSize: function(size) {
    157     return formatSizeCommon(size);
    158   },
    159 
    160   /**
    161    * Zooms in the timeline.
    162    * @param {Event} event Event.
    163    * @param {number} detail Detail.
    164    * @param {HTMLElement} sender Sender.
    165    */
    166   zoomInClicked: function(event, detail, sender) {
    167     this.scale *= this.SCALE_STEP;
    168   },
    169 
    170   /**
    171    * Zooms out the timeline.
    172    * @param {Event} event Event.
    173    * @param {number} detail Detail.
    174    * @param {HTMLElement} sender Sender.
    175    */
    176   zoomOutClicked: function(event, detail, sender) {
    177     this.scale /= this.SCALE_STEP;
    178   },
    179 
    180   /**
    181    * Selects or deselects an element on the timeline.
    182    * @param {Event} event Event.
    183    * @param {number} detail Detail.
    184    * @param {HTMLElement} sender Sender.
    185    */
    186   elementClicked: function(event, detail, sender) {
    187     if (sender.dataset.id in this.selected) {
    188       delete this.selected[sender.dataset.id];
    189       sender.classList.remove('selected');
    190     } else {
    191       this.selected[sender.dataset.id] = true;
    192       sender.classList.add('selected');
    193     }
    194 
    195     var requestEventsNode = document.querySelector('#request-events');
    196     requestEventsNode.hidden = false;
    197 
    198     requestEventsNode.model = [];
    199     for (var i = 0; i < this.model.length; i++) {
    200       if (this.model[i].id in this.selected)
    201         requestEventsNode.model.push(this.model[i]);
    202     }
    203   },
    204 
    205   /**
    206    * Updates chart elements of active requests, so they grow with time.
    207    */
    208   activeUpdate: function() {
    209     if (Object.keys(this.active).length == 0)
    210       return;
    211 
    212     for (var id in this.active) {
    213       var index = this.active[id];
    214       this.chart[index].length = Date.now() - this.chart[index].time;
    215     }
    216   },
    217 
    218   /**
    219    * Generates <code>chart</code> from the new <code>model</code> value.
    220    */
    221   chartUpdate: function(oldLength, newLength) {
    222     // If the new value is empty, then clear the model.
    223     if (!newLength) {
    224       this.active = {};
    225       this.rows = [];
    226       this.chart = [];
    227       this.timeStart = null;
    228       this.idleStart = null;
    229       this.idleTotal = 0;
    230       this.selected = [];
    231       return;
    232     }
    233 
    234     // Only adding new entries to the model is supported (or clearing).
    235     console.assert(newLength >= oldLength);
    236 
    237     for (var i = oldLength; i < newLength; i++) {
    238       var event = this.model[i];
    239       switch (event.eventType) {
    240         case 'created':
    241           // If this is the first creation event in the chart, then store its
    242           // time as beginning time of the chart.
    243           if (!this.timeStart)
    244             this.timeStart = event.time;
    245 
    246           // If this event terminates idling, then add the idling time to total
    247           // idling time. This is used to avoid gaps in the chart while idling.
    248           if (Object.keys(this.active).length == 0 && this.idleStart)
    249             this.idleTotal += event.time.getTime() - this.idleStart.getTime();
    250 
    251           // Find the appropriate row for this chart element.
    252           var rowIndex = 0;
    253           while (true) {
    254             // Add to this row only if there is enough space, and if the row
    255             // is of the same type.
    256             var addToRow = (rowIndex >= this.rows.length) ||
    257                 (this.rows[rowIndex].time.getTime() <= event.time.getTime() &&
    258                  !this.rows[rowIndex].active &&
    259                  (this.rows[rowIndex].requestType == event.requestType));
    260 
    261             if (addToRow) {
    262               this.chart.push({
    263                 index: this.chart.length,
    264                 id: event.id,
    265                 time: event.time,
    266                 executionTime: 0,
    267                 length: 0,
    268                 requestType: event.requestType,
    269                 left: event.time - this.timeStart - this.idleTotal,
    270                 row: rowIndex,
    271                 modelIndexes: [i]
    272               });
    273 
    274               this.rows[rowIndex] = {
    275                 requestType: event.requestType,
    276                 time: event.time,
    277                 active: true
    278               };
    279 
    280               this.active[event.id] = this.chart.length - 1;
    281               break;
    282             }
    283 
    284             rowIndex++;
    285           }
    286           break;
    287 
    288         case 'fulfilled':
    289         case 'rejected':
    290           if (!(event.id in this.active))
    291             return;
    292           var chartIndex = this.active[event.id];
    293           this.chart[chartIndex].state = event.eventType;
    294           this.chart[chartIndex].executionTime = event.executionTime;
    295           this.chart[chartIndex].valueSize = event.valueSize;
    296           this.chart[chartIndex].modelIndexes.push(i);
    297           break;
    298 
    299         case 'destroyed':
    300           if (!(event.id in this.active))
    301             return;
    302 
    303           var chartIndex = this.active[event.id];
    304           this.chart[chartIndex].length =
    305               event.time - this.chart[chartIndex].time;
    306           this.chart[chartIndex].modelIndexes.push(i);
    307           this.rows[this.chart[chartIndex].row].time = event.time;
    308           this.rows[this.chart[chartIndex].row].active = false;
    309           delete this.active[event.id];
    310 
    311           // If this was the last active request, then idling starts.
    312           if (Object.keys(this.active).length == 0)
    313             this.idleStart = event.time;
    314           break;
    315       }
    316     }
    317   },
    318 
    319   /**
    320    * Map of selected requests.
    321    * @type {Object.<number, boolean>}
    322    */
    323   selected: {},
    324 
    325   /**
    326    * Map of requests which has started, but are not completed yet, from
    327    * a request id to the chart element index.
    328    * @type {Object.<number, number>}}
    329    */
    330   active: {},
    331 
    332   /**
    333    * List of chart elements, calculated from the model.
    334    * @type {Array.<Object>}
    335    */
    336   chart: [],
    337 
    338   /**
    339    * List of rows in the chart, with the last endTime value on it.
    340    * @type {Array.<Object>}
    341    */
    342   rows: [],
    343 
    344   /**
    345    * Scale of the chart.
    346    * @type {number}
    347    */
    348   scale: 1,
    349 
    350   /**
    351    * Time of the first created request.
    352    * @type {Date}
    353    */
    354   timeStart: null,
    355 
    356   /**
    357    * Time of the last idling started.
    358    * @type {Date}
    359    */
    360   idleStart: null,
    361 
    362   /**
    363    * Total idling time since chart generation started. Used to avoid
    364    * generating gaps in the chart when there is no activity. In milliseconds.
    365    * @type {number}
    366    */
    367   idleTotal: 0,
    368 
    369   /**
    370    * List of requests information maps.
    371    * @type {Array.<Object>}
    372    */
    373   model: []
    374 });
    375 
    376 /*
    377  * Updates the mounted file system list.
    378  * @param {Array.<Object>} fileSystems Array containing provided file system
    379  *     information.
    380  */
    381 function updateFileSystems(fileSystems) {
    382   var fileSystemsNode = document.querySelector('#file-systems');
    383   fileSystemsNode.model = fileSystems;
    384 }
    385 
    386 /**
    387  * Called when a request is created.
    388  * @param {Object} event Event.
    389  */
    390 function onRequestEvent(event) {
    391   event.time = new Date(event.time);  // Convert to a real Date object.
    392   var requestTimelineNode = document.querySelector('#request-timeline');
    393   requestTimelineNode.model.push(event);
    394 }
    395 
    396 document.addEventListener('DOMContentLoaded', function() {
    397   var context = document.getCSSCanvasContext('2d', 'dashedPattern', 4, 4);
    398   context.beginPath();
    399   context.strokeStyle = '#ffffff';
    400   context.moveTo(0, 0);
    401   context.lineTo(4, 4);
    402   context.stroke();
    403 
    404   chrome.send('updateFileSystems');
    405 
    406   // Refresh periodically.
    407   setInterval(function() {
    408     chrome.send('updateFileSystems');
    409   }, 1000);
    410 });
    411