blob: 54adf6a067576ad68190939de22b81d1bcfeaa8d [file] [log] [blame]
<!--
This in an HTML Import-able file that contains the definition
of the following elements:
<job-timeline-sk>
Status information about the task scheduler.
To use this file import it:
<link href="/res/imp/job-timeline-sk.html" rel="import" />
Usage:
<job-timeline-sk job="[[job]]" tasks="[[tasks]]" epochs="[[epochs]]"></job-timeline-sk>
Properties:
job: Job instance, as provided by the Task Scheduler server.
tasks: Array of Task instances, as provided by the Task Scheduler server.
epochs: Array of Strings; timestamps indicating events, eg. scheduler tick times.
Methods:
None.
Events:
None.
-->
<link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
<dom-module id="job-timeline-sk">
<template>
<style>
:host{
flex-grow: 1;
display: flex;
flex-direction: column;
}
#svg {
flex-grow: 1;
min-width: 600px;
min-height: 300px;
max-width: 1800px;
max-height: 800px;
}
</style>
<h2>Job <span>[[job.id]]</span></h2>
<div class="layout horizontal"><a href$="/job/[[job.id]]">[back to job page]</a></div>
<svg id="svg"></svg>
</template>
<script src="/res/imp/bower_components/d3/d3.min.js"></script>
<script src="/res/js/gantt.js"></script>
<script>
(function() {
function ts(tsStr) {
// If the timestamp is zero-ish, return the current datetime.
if (Date.parse(tsStr) <= 0) {
return new Date();
}
return new Date(tsStr);
}
Polymer({
is: "job-timeline-sk",
properties: {
job: {
type: Object,
},
tasks: {
type: Array,
},
epochs: {
type: Array,
},
_chart: {
type: Object,
},
},
observers: [
"_draw(job.*, tasks.*, epochs.*)",
],
attached: function() {
this._chart = gantt(this.$.svg);
window.addEventListener("resize", this.draw.bind(this));
this.draw();
},
draw: function() {
this.debounce("draw", function() {
this._draw();
}.bind(this));
},
_draw: function() {
if (!this.job || !this.tasks || this.epochs === undefined || !this._chart) {
return;
}
const tasks = [{
category: this.job.name + " (job)",
start: ts(this.job.created),
end: ts(this.job.finished),
color: "#0072b2",
}];
for (const t of this.tasks) {
// Creation timestamp may be after start and finish timestamps in the
// case of deduplicated tasks. Since we care more about the
// contribution to the job than the task itself, set the start and
// finish timestamps equal to the creation timestamp in this case.
const createTs = ts(t.created);
if (t.started && ts(t.started).getTime() < createTs.getTime()) {
t.started = t.created;
}
if (t.finished && ts(t.finished).getTime() < createTs.getTime()) {
t.finished = t.created;
}
const segments = [];
let lastSegmentEnd = createTs;
const seg = function(label, end, color) {
const s = {
label: label,
start: lastSegmentEnd,
end: ts(end),
color: color,
};
lastSegmentEnd = s.end;
segments.push(s);
};
if (t.started) {
seg("pending", t.started, "#e69f00");
}
if (t.swarming && t.swarming.performance_stats && t.swarming.performance_stats.isolated_upload) {
const overheadTotal = t.swarming.performance_stats.bot_overhead * 1000;
const overheadUpload = t.swarming.performance_stats.isolated_upload.duration * 1000;
const overheadDownload = overheadTotal - overheadUpload;
const startTs = ts(t.started).getTime();
const finishTs = ts(t.finished).getTime();
seg("overhead", startTs + overheadDownload, "#d55e00");
seg("running", finishTs - overheadUpload, "#0072b2");
seg("overhead", t.finished, "#d55e00");
} else {
seg("running", t.finished, "#0072b2");
}
tasks.push({
category: t.name,
start: ts(t.created),
end: ts(t.finished),
segments: segments,
});
}
const epochs = [];
for (const epoch of this.epochs) {
epochs.push(ts(epoch));
}
let g = this._chart.tasks(tasks).epochs(epochs);
if (this.job.requested) {
g.start(ts(this.job.requested));
}
g.draw();
}
});
})();
</script>
</dom-module>