blob: 865fe521d9abe499f9ce18180ffc478874ab5b0d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Ember from 'ember';
import Converter from 'yarn-ui/utils/converter';
import ColumnDef from 'em-table/utils/column-definition';
export default Ember.Component.extend({
canvas: {
svg: undefined,
h: 0,
w: 0,
tooltip: undefined
},
clusterMetrics: undefined,
modelArr: [],
colors: d3.scale.category10().range(),
_selected: undefined,
gridColumns: [],
gridRows: [],
serviceName: undefined,
selected: function() {
return this._selected;
}.property(),
tableComponentName: function() {
return "app-attempt-table";
}.property(),
setSelected: function(d) {
var dom;
if (this._selected === d) {
return;
}
// restore color
if (this._selected) {
dom = d3.select("#timeline-bar-" + this._selected.get("id"));
dom.attr("fill", this.colors[0]);
}
this._selected = d;
this.set("selected", d);
dom = d3.select("#timeline-bar-" + d.get("id"));
dom.attr("fill", this.colors[1]);
},
getPerItemHeight: function() {
var arrSize = this.modelArr.length;
if (arrSize < 20) {
return 30;
} else if (arrSize < 100) {
return 10;
} else {
return 2;
}
},
getPerItemGap: function() {
var arrSize = this.modelArr.length;
if (arrSize < 20) {
return 5;
} else if (arrSize < 100) {
return 1;
} else {
return 1;
}
},
getCanvasHeight: function() {
return (this.getPerItemHeight() + this.getPerItemGap()) * this.modelArr.length + 200;
},
draw: function(start, end) {
// get w/h of the svg
var bbox = d3.select("#" + this.get("parent-id"))
.node()
.getBoundingClientRect();
this.canvas.w = bbox.width;
this.canvas.h = this.getCanvasHeight();
this.canvas.svg = d3.select("#" + this.get("parent-id"))
.append("svg")
.attr("width", this.canvas.w)
.attr("height", this.canvas.h)
.attr("id", this.get("my-id"));
this.renderTimeline(start, end);
},
renderTimeline: function(start, end) {
var border = 30;
var singleBarHeight = this.getPerItemHeight();
var gap = this.getPerItemGap();
var textWidth = 200;
/*
start-time end-time
|--------------------------------------|
==============
==============
==============
===============
*/
var xScaler = d3.scale.linear()
.domain([start, end])
.range([0, this.canvas.w - 2 * border - textWidth]);
/*
* Render frame of timeline view
*/
this.canvas.svg.append("line")
.attr("x1", border + textWidth)
.attr("y1", border - 5)
.attr("x2", this.canvas.w - border)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("line")
.attr("x1", border + textWidth)
.attr("y1", border - 10)
.attr("x2", border + textWidth)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("line")
.attr("x1", this.canvas.w - border)
.attr("y1", border - 10)
.attr("x2", this.canvas.w - border)
.attr("y2", border - 5)
.attr("class", "chart");
this.canvas.svg.append("text")
.text(Converter.timeStampToDate(start))
.attr("y", border - 15)
.attr("x", border + textWidth)
.attr("class", "bar-chart-text")
.attr("text-anchor", "left");
this.canvas.svg.append("text")
.text(Converter.timeStampToDate(end))
.attr("y", border - 15)
.attr("x", this.canvas.w - border)
.attr("class", "bar-chart-text")
.attr("text-anchor", "end");
// show bar
var bar = this.canvas.svg.selectAll("bars")
.data(this.modelArr)
.enter()
.append("rect")
.attr("y", function(d, i) {
return border + (gap + singleBarHeight) * i;
})
.attr("x", function(d) {
return border + textWidth + xScaler(d.get("startTs"));
})
.attr("height", singleBarHeight)
.attr("fill", function() {
return this.colors[0];
}.bind(this))
.attr("width", function(d) {
var finishedTs = xScaler(d.get("finishedTs"));
finishedTs = finishedTs > 0 ? finishedTs : xScaler(end);
return finishedTs - xScaler(d.get("startTs"));
})
.attr("id", function(d) {
return "timeline-bar-" + d.get("id");
});
bar.on("click", function(d) {
this.setSelected(d);
}.bind(this));
this.bindTooltip(bar);
if (this.modelArr.length <= 20) {
// show bar texts
for (var i = 0; i < this.modelArr.length; i++) {
this.canvas.svg.append("text")
.text(this.modelArr[i].get(this.get("label")))
.attr("y", border + (gap + singleBarHeight) * i + singleBarHeight / 2)
.attr("x", border)
.attr("class", "bar-chart-text");
}
}
},
bindTooltip: function(d) {
d.on("mouseover", function() {
this.tooltip
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mousemove", function(d) {
this.tooltip.style("opacity", 0.9);
this.tooltip.html(d.get("tooltipLabel"))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}.bind(this))
.on("mouseout", function() {
this.tooltip.style("opacity", 0);
}.bind(this));
},
initTooltip: function() {
this.tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.attr("id", "chart-tooltip")
.style("opacity", 0);
},
didInsertElement: function() {
// init tooltip
this.initTooltip();
this.modelArr = [];
// init model
if (this.get("rmModel")) {
this.get("rmModel").forEach(function(o) {
if(!this.modelArr.contains(o)) {
this.modelArr.push(o);
}
}.bind(this));
}
if (this.get("tsModel")) {
this.get("tsModel").forEach(function(o) {
if(!this.modelArr.contains(o)) {
this.modelArr.push(o);
}
}.bind(this));
}
if(this.modelArr.length === 0) {
return;
}
this.modelArr.sort(function(a, b) {
var tsA = a.get("startTs");
var tsB = b.get("startTs");
return tsA - tsB;
});
var begin = 0;
if (this.modelArr.length > 0) {
begin = this.modelArr[0].get("startTs");
}
var end = 0;
for (var i = 0; i < this.modelArr.length; i++) {
var ts = this.modelArr[i].get("finishedTs");
if (ts > end) {
end = ts;
}
}
if (end < begin) {
end = Date.now();
}
this.draw(begin, end);
if (this.modelArr.length > 0) {
this.setSelected(this.modelArr[0]);
}
if (this.get('attemptModel')) {
this.setAttemptsGridColumnsAndRows();
} else {
this.setContainersGridColumnsAndRows();
}
},
setAttemptsGridColumnsAndRows: function() {
var self = this;
var columns = [];
var serviceName = this.get('serviceName');
columns.push({
id: 'id',
headerTitle: 'Attempt ID',
contentPath: 'id',
cellComponentName: 'em-table-linked-cell',
minWidth: '300px',
getCellContent: function(row) {
var attemptId = row.get('id');
var query = serviceName? '?service='+serviceName : '';
return {
displayText: attemptId,
href: `#/yarn-app-attempt/${attemptId}${query}`
};
}
}, {
id: 'attemptStartedTime',
headerTitle: 'Started Time',
contentPath: 'attemptStartedTime'
}, {
id: 'finishedTime',
headerTitle: 'Finished Time',
contentPath: 'finishedTime',
getCellContent: function(row) {
if (row.get('finishedTs')) {
return row.get('finishedTime');
}
return 'N/A';
}
}, {
id: 'elapsedTime',
headerTitle: 'Elapsed Time',
contentPath: 'elapsedTime'
}, {
id: 'appMasterContainerId',
headerTitle: 'AM Container ID',
contentPath: 'appMasterContainerId',
minWidth: '350px'
}, {
id: 'amNodeId',
headerTitle: 'AM Node ID',
contentPath: 'amNodeId'
}, {
id: 'attemptState',
headerTitle: 'State',
contentPath: 'attemptState',
getCellContent: function(row) {
var state = row.get('attemptState');
if (state) {
return state;
} else {
return 'N/A';
}
}
}, {
id: 'nodeHttpAddress',
headerTitle: 'NodeManager Web UI',
contentPath: 'nodeHttpAddress',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
if (address) {
return `<a href="${address}" target="_blank">${address}</a>`;
} else {
return 'N/A';
}
}
}, {
id: 'logsLink',
headerTitle: 'Logs',
contentPath: 'logsLink',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var logUrl = self.checkHttpProtocol(row.get('logsLink'));
if (logUrl) {
return `<a href="${logUrl}" target="_blank">Link</a>`;
} else {
return 'N/A';
}
}
});
var gridCols = ColumnDef.make(columns);
this.set('gridColumns', gridCols);
this.set('gridRows', this.modelArr);
},
setContainersGridColumnsAndRows: function() {
var self = this;
var columns = [];
columns.push({
id: 'id',
headerTitle: 'Container ID',
contentPath: 'id',
minWidth: '350px'
}, {
id: 'startedTime',
headerTitle: 'Started Time',
contentPath: 'startedTime'
}, {
id: 'finishedTime',
headerTitle: 'Finished Time',
contentPath: 'finishedTime',
getCellContent: function(row) {
if (row.get('finishedTs')) {
return row.get('finishedTime');
}
return 'N/A';
}
}, {
id: 'elapsedTime',
headerTitle: 'Elapsed Time',
contentPath: 'elapsedTime'
}, {
id: 'priority',
headerTitle: 'Priority',
contentPath: 'priority'
}, {
id: 'containerExitStatus',
headerTitle: 'Exit Status',
contentPath: 'containerExitStatus',
getCellContent: function(row) {
var status = row.get('containerExitStatus');
if (status) {
return status;
} else {
return 'N/A';
}
}
}, {
id: 'containerState',
headerTitle: 'State',
contentPath: 'containerState',
getCellContent: function(row) {
var state = row.get('containerState');
if (state) {
return state;
} else {
return 'N/A';
}
}
}, {
id: 'logUrl',
headerTitle: 'Logs',
contentPath: 'logUrl',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var url = self.checkHttpProtocol(row.get('logUrl'));
if (url) {
return `<a href="${url}" target="_blank">${url}</a>`;
} else {
return 'N/A';
}
}
}, {
id: 'nodeHttpAddress',
headerTitle: 'Node Manager UI',
contentPath: 'nodeHttpAddress',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
if (address) {
return `<a href="${address}" target="_blank">${address}</a>`;
} else {
return 'N/A';
}
}
});
var gridCols = ColumnDef.make(columns);
this.set('gridColumns', gridCols);
this.set('gridRows', this.modelArr);
},
checkHttpProtocol: function(prop) {
if (prop && prop.indexOf('://') < 0) {
prop = 'http://' + prop;
}
return prop;
},
isDataEmpty: Ember.computed(function() {
return this.modelArr.length === 0;
})
});