summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/resources/chromeos/provided_file_systems.css69
-rw-r--r--chrome/browser/resources/chromeos/provided_file_systems.html39
-rw-r--r--chrome/browser/resources/chromeos/provided_file_systems.js199
3 files changed, 301 insertions, 6 deletions
diff --git a/chrome/browser/resources/chromeos/provided_file_systems.css b/chrome/browser/resources/chromeos/provided_file_systems.css
index b049d4a1..63b99f1 100644
--- a/chrome/browser/resources/chromeos/provided_file_systems.css
+++ b/chrome/browser/resources/chromeos/provided_file_systems.css
@@ -11,7 +11,8 @@ body {
}
#fileSystems,
-#requestEvents {
+#requestEvents,
+#requestTimeline {
background-color: white;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
@@ -67,3 +68,69 @@ body {
#requestEvents td {
border-top: 1px solid #eee;
}
+
+#requestEvents td span {
+ border-radius: 5px;
+ padding: 3px;
+}
+
+#requestTimeline {
+ overflow: auto;
+ width: 100%;
+}
+
+#requestTimeline .request-timeline-padding {
+ margin: 10px 5px;
+ position: relative;
+}
+
+#requestTimeline .request-timeline-request {
+ border: none;
+ border-radius: 4px;
+ height: 8px;
+ margin: 3px 0;
+ min-width: 8px;
+ opacity: 0.75;
+ position: absolute;
+ transition: opacity 100ms;
+}
+
+#requestTimeline [data-request-type='REQUEST_UNMOUNT'],
+#requestEvents [data-request-type='REQUEST_UNMOUNT'] span {
+ background-color: cadetblue;
+ color: white;
+}
+
+#requestTimeline [data-request-type='GET_METADATA'],
+#requestEvents [data-request-type='GET_METADATA'] span {
+ background-color: gold;
+}
+
+#requestTimeline [data-request-type='READ_DIRECTORY'],
+#requestEvents [data-request-type='READ_DIRECTORY'] span {
+ background-color: hotpink;
+ color: white;
+}
+
+#requestTimeline [data-request-type='OPEN_FILE'],
+#requestEvents [data-request-type='OPEN_FILE'] span {
+ background-color: darkturquoise;
+ color: white;
+}
+
+#requestTimeline [data-request-type='CLOSE_FILE'],
+#requestEvents [data-request-type='CLOSE_FILE'] span {
+ background-color: mediumspringgreen;
+}
+
+#requestTimeline [data-request-type='READ_FILE'],
+#requestEvents [data-request-type='READ_FILE'] span {
+ background-color: royalblue;
+ color: white;
+}
+
+#requestTimeline [data-state='rejected'],
+#requestEvents [data-error]:not([data-error='']) span {
+ background-color: tomato;
+ color: white;
+}
diff --git a/chrome/browser/resources/chromeos/provided_file_systems.html b/chrome/browser/resources/chromeos/provided_file_systems.html
index d7add37..7e2dd47 100644
--- a/chrome/browser/resources/chromeos/provided_file_systems.html
+++ b/chrome/browser/resources/chromeos/provided_file_systems.html
@@ -46,6 +46,29 @@
</template>
</polymer-element>
+ <polymer-element name="request-timeline">
+ <template>
+ <link rel="stylesheet"
+ href="chrome://provided-file-systems/provided_file_systems.css">
+ <div id="requestTimeline">
+ <div class="request-timeline-padding"
+ style="min-height: {{rows.length * rowHeight}}px">
+ <template id="request-timeline-request"
+ repeat="{{request in chart}}">
+ <div class="request-timeline-request"
+ title="{{request.id}}"
+ data-state="{{request.state}}"
+ data-request-type="{{request.requestType}}"
+ style="left: {{request.left * scale}}px;
+ top: {{request.row * rowHeight}}px;
+ width: {{request.length * scale}}px">
+ </div>
+ </template>
+ </div>
+ </div>
+ </template>
+ </polymer-element>
+
<polymer-element name="request-events">
<template>
<link rel="stylesheet"
@@ -65,11 +88,18 @@
<tbody>
<template id="request-event" repeat="{{item in model}}">
<tr>
- <td>{{item.time | formatTime}}</td>
+ <td>
+ <a id="request-{{item.id}}"></a>
+ {{item.time | formatTime}}
+ </td>
<td>{{item.id}}</td>
- <td>{{item.requestType}}</td>
+ <td data-request-type="{{item.requestType}}">
+ <span>{{item.requestType}}</span>
+ </td>
<td>{{item.eventType}}</td>
- <td>{{item.error}}</td>
+ <td data-error="{{item.error}}">
+ <span>{{item.error}}</span>
+ </td>
<td>{{item.hasMore | formatHasMore}}</td>
</tr>
</template>
@@ -82,6 +112,9 @@
<file-systems id="file-systems">
</file-systems>
+ <request-timeline hidden id="request-timeline">
+ </request-timeline>
+
<request-events hidden id="request-events">
</request-events>
</body>
diff --git a/chrome/browser/resources/chromeos/provided_file_systems.js b/chrome/browser/resources/chromeos/provided_file_systems.js
index 712f859..5e81155 100644
--- a/chrome/browser/resources/chromeos/provided_file_systems.js
+++ b/chrome/browser/resources/chromeos/provided_file_systems.js
@@ -24,7 +24,10 @@ Polymer('file-systems', {
requestEventsNode.hidden = false;
requestEventsNode.model = [];
- console.log(sender.dataset.extensionId, sender.dataset.id);
+ var requestTimelineNode = document.querySelector('#request-timeline');
+ requestTimelineNode.hidden = false;
+ requestTimelineNode.model = [];
+
chrome.send('selectFileSystem', [sender.dataset.extensionId,
sender.dataset.id]);
},
@@ -75,6 +78,194 @@ Polymer('request-events', {
model: []
});
+// Defines the request-timeline element.
+Polymer('request-timeline', {
+ /**
+ * Observes changes in the model.
+ * @type {Object.<string, string>}
+ */
+ observe: {
+ 'model.length': 'chartUpdate'
+ },
+
+ /**
+ * Called when the element is created.
+ */
+ ready: function() {
+ // Update active requests in the background for nice animation.
+ var activeUpdateAnimation = function() {
+ this.activeUpdate();
+ requestAnimationFrame(activeUpdateAnimation);
+ }.bind(this);
+ activeUpdateAnimation();
+ },
+
+ /**
+ * Updates chart elements of active requests, so they grow with time.
+ */
+ activeUpdate: function() {
+ if (Object.keys(this.active).length == 0)
+ return;
+
+ for (var id in this.active) {
+ var index = this.active[id];
+ this.chart[index].length = Date.now() - this.chart[index].time;
+ }
+ },
+
+ /**
+ * Generates <code>chart</code> from the new <code>model</code> value.
+ */
+ chartUpdate: function(oldLength, newLength) {
+ // If the new value is empty, then clear the model.
+ if (!newLength) {
+ this.active = {};
+ this.rows = [];
+ this.chart = [];
+ this.timeStart = null;
+ this.idleStart = null;
+ this.idleTotal = 0;
+ return;
+ }
+
+ // Only adding new entries to the model is supported (or clearing).
+ console.assert(newLength >= oldLength);
+
+ for (var i = oldLength; i < newLength; i++) {
+ var event = this.model[i];
+ switch (event.eventType) {
+ case 'created':
+ // If this is the first creation event in the chart, then store its
+ // time as beginning time of the chart.
+ if (!this.timeStart)
+ this.timeStart = event.time;
+
+ // If this event terminates idling, then add the idling time to total
+ // idling time. This is used to avoid gaps in the chart while idling.
+ if (Object.keys(this.active).length == 0 && this.idleStart)
+ this.idleTotal += event.time.getTime() - this.idleStart.getTime();
+
+ // Find the appropriate row for this chart element.
+ var rowIndex = 0;
+ while (true) {
+ // Add to this row only if there is enough space, and if the row
+ // is of the same type.
+ var addToRow = (rowIndex >= this.rows.length) ||
+ (this.rows[rowIndex].time.getTime() <= event.time.getTime() &&
+ !this.rows[rowIndex].active &&
+ (this.rows[rowIndex].requestType == event.requestType));
+
+ if (addToRow) {
+ this.chart.push({
+ index: this.chart.length,
+ id: event.id,
+ time: event.time,
+ requestType: event.requestType,
+ left: event.time - this.timeStart - this.idleTotal,
+ row: rowIndex,
+ modelIndexes: [i]
+ });
+
+ this.rows[rowIndex] = {
+ requestType: event.requestType,
+ time: event.time,
+ active: true
+ };
+
+ this.active[event.id] = this.chart.length - 1;
+ break;
+ }
+
+ rowIndex++;
+ }
+ break;
+
+ case 'fulfilled':
+ case 'rejected':
+ if (!(event.id in this.active))
+ return;
+ var chartIndex = this.active[event.id];
+ this.chart[chartIndex].state = event.eventType;
+ this.chart[chartIndex].modelIndexes.push(i);
+ break;
+
+ case 'destroyed':
+ if (!(event.id in this.active))
+ return;
+
+ var chartIndex = this.active[event.id];
+ this.chart[chartIndex].length =
+ event.time - this.chart[chartIndex].time;
+ this.chart[chartIndex].modelIndexes.push(i);
+ this.rows[this.chart[chartIndex].row].time = event.time;
+ this.rows[this.chart[chartIndex].row].active = false;
+ delete this.active[event.id];
+
+ // If this was the last active request, then idling starts.
+ if (Object.keys(this.active).length == 0)
+ this.idleStart = event.time;
+ break;
+ }
+ }
+ },
+
+ /**
+ * Map of requests which has started, but are not completed yet, from
+ * a request id to the chart element index.
+ * @type {Object.<number, number>}}
+ */
+ active: {},
+
+ /**
+ * List of chart elements, calculated from the model.
+ * @type {Array.<Object>}
+ */
+ chart: [],
+
+ /**
+ * List of rows in the chart, with the last endTime value on it.
+ * @type {Array.<Object>}
+ */
+ rows: [],
+
+ /**
+ * Scale of the chart.
+ * @type {number}
+ */
+ scale: 0.1,
+
+ /**
+ * Height of each row in the chart in pixels.
+ * @type {number}
+ */
+ rowHeight: 14,
+
+ /**
+ * Time of the first created request.
+ * @type {Date}
+ */
+ timeStart: null,
+
+ /**
+ * Time of the last idling started.
+ * @type {Date}
+ */
+ idleStart: null,
+
+ /**
+ * Total idling time since chart generation started. Used to avoid
+ * generating gaps in the chart when there is no activity. In milliseconds.
+ * @type {number}
+ */
+ idleTotal: 0,
+
+ /**
+ * List of requests information maps.
+ * @type {Array.<Object>}
+ */
+ model: []
+});
+
/*
* Updates the mounted file system list.
* @param {Array.<Object>} fileSystems Array containing provided file system
@@ -90,8 +281,12 @@ function updateFileSystems(fileSystems) {
* @param {Object} event Event.
*/
function onRequestEvent(event) {
- var requestEventsNode = document.querySelector('#request-events');
event.time = new Date(event.time); // Convert to a real Date object.
+
+ var requestTimelineNode = document.querySelector('#request-timeline');
+ requestTimelineNode.model.push(event);
+
+ var requestEventsNode = document.querySelector('#request-events');
requestEventsNode.model.push(event);
}