blob: b1569c1dd66955e617b3ffaafcc581eb04b561d9 [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.
*/
/**
* Render trendline for stats below the containers and instances view
*/
function StatTrendlines(baseUrl, cluster, environ, toponame, physicalPlan, logicalPlan) {
var result = {};
var target = d3.select('#stat-trendlines').style('text-align', 'center');
// When a user clicks an instance, render area charts below the containers and instances graphic
result.showInstance = function (name, instance) {
var margin = {
top: 15,
right: 60,
bottom: 20,
left: 80
};
var rowHeight = 30;
var endTime = moment().startOf('minute');
var startTime = moment().startOf('minute').subtract(60, 'minutes');
var height = rowHeight * window.pollingMetrics.length;
var outerWidth = 500, outerHeight = height + margin.top + margin.bottom;
var width = outerWidth - margin.left - margin.right;
var outerSvg = target.text('').append('svg')
.attr('class', 'text-center')
.attr('width', outerWidth)
.attr('height', outerHeight)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var svg = outerSvg.append('g');
var svgTop = outerSvg.append('g');
var tip = d3.tip()
.attr('class', 'd3-tip instance text-center')
.offset([-8, 0])
.html(function(d) {
return moment(d.data[0]).format('h:mma') + ': <strong>' + d.metric.format(d.data[1]) + '</strong> ' + d.metric.legendDescription;
});
svg.call(tip);
svg.append('rect')
.attr('fill', 'white')
.attr('stroke', 'white')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
var scale = d3.time.scale()
.domain([startTime.valueOf(), endTime.valueOf()])
.range([0, width]);
var axis = d3.svg.axis()
.scale(scale)
.orient('bottom')
.ticks(d3.time.minute, 15)
.tickFormat(function (d) {
return moment(d).format('h:mma');
})
.tickSize(-height);
var rows = svg
.selectAll('.row')
.data(window.pollingMetrics)
.enter()
.append('g')
.attr('class', 'row')
.attr('transform', function (d, i) { return 'translate(0,' + i * rowHeight + ')'; });
svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call(axis);
svg.append('defs')
.append('clipPath')
.attr('id', 'row-clip')
.append('rect')
.attr('x', 0)
.attr('y', 3)
.attr('width', width)
.attr('height', rowHeight - 3);
rows.append('text')
.attr('y', (rowHeight / 2) + 3)
.attr('x', -5)
.attr('text-anchor', 'end')
.text(function (d) {
var name = instance === '*' ? 'Max ' + d.name : d.name;
return extractWrappedWord(name, true);
})
.append('tspan')
.attr('y', (rowHeight / 2) + 14)
.attr('x', -5)
.attr('text-anchor', 'end')
.text(function (d) {
var name = instance === '*' ? 'Max ' + d.name : d.name;
return extractWrappedWord(name, false);
});
rows.append('rect')
.style('fill', 'none')
.attr('width', width)
.attr('height', rowHeight);
var metricValues = {};
// Request metrics for each row and render it
rows.each(function (metric, i) {
// make color gradient
var extent = d3.extent(metric.scaleTicks);
var outerRow = d3.select(this);
var row = outerRow.append('g')
.attr('clip-path', 'url(#row-clip)');
outerRow.append('g')
.attr('class', 'x axis')
.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', rowHeight)
.attr('y2', rowHeight);
var yScale = d3.scale.linear()
.domain(extent)
.range([rowHeight, 0]);
// request data and render the line chart
(metric.queryTrendline || makeTrendlineQuery)(metric, name, instance, startTime, endTime, function (data) {
metricValues[metric.name] = data;
var rectScale = d3.scale.ordinal()
.domain(data.map(function (d) { return d[0].getTime(); }))
.rangeRoundBands([0, width], 0.1, 0);
var bars = row.selectAll('.data-bar')
.data(data)
.enter()
.append('g')
.attr('class', 'data-bar')
.attr('transform', function (d) {
return 'translate(' + rectScale(d[0].getTime()) + ',0)';
});
var clipY = d3.scale.linear()
.domain([0, rowHeight * 2 / 3])
.range([0, rowHeight * 2 / 3])
.clamp(true);
var clipHeight = d3.scale.linear()
.domain([0, rowHeight / 3])
.range([0, rowHeight / 3])
.clamp(true);
bars.append('rect')
.each(function (d) { d.target = this; })
.attr('x', 0)
.attr('y', function (d) { return clipY(yScale(d[1])); })
.attr('height', function (d) { return clipHeight(height - yScale(d[1])); })
.style('fill', 'white')
.style('stroke', 'none')
.attr('width', rectScale.rangeBand());
bars.append('rect')
.attr('x', 0)
.attr('width', rectScale.rangeBand())
.attr('y', function (d) { return yScale(d[1]); })
.attr('height', function (d) { return height - yScale(d[1]); })
.style('stroke', 'none')
.style('opacity', 1)
.style('fill', function (d) { return metric.colorScale(d[1]); });
var timeout;
bars.on('mouseover', function (d) {
tip.show({data: d, metric: metric}, d.target);
rows.selectAll('rect').style('opacity', function (other) {
return other === d ? 1 : 0.3;
});
clearTimeout(timeout);
}).on('mouseout', function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
tip.hide();
rows.selectAll('rect').style('opacity', 1);
}, 100);
});
});
});
var valueIndicators = rows.append('text')
.attr('y', rowHeight / 2 + 5)
.attr('dx', 5)
.style('opacity', 0);
// hide the reset button when the entire topology view selected
d3.select('html').classed('all-topo-view', name === '*');
if (name === '*') {
d3.select('#trendline-title').text('Topology Metrics');
} else if (instance === '*') {
d3.select('#trendline-title').text(name + ' Metrics');
} else {
d3.select('#trendline-title').text(instance + ' Metrics');
container = instance.split("_")[1];
target
.append('div')
.attr('class', 'text-center')
.html([
'<a class="btn btn-primary btn-xs" target="_blank" href="/topologies/' + cluster + '/' + environ + '/' + toponame + '/' + container + '/file?path=./log-files/' + instance + '.log.0">logs</a>',
'<a class="btn btn-primary btn-xs" target="_blank" href="/topologies/filestats/' + cluster + '/' + environ + '/' + toponame + '/' + container + '">job</a>',
'<a class="btn btn-primary btn-xs" target="_blank" href="/topologies/' + cluster + '/' + environ + '/' + toponame + '/' + name + '/' + instance + '/exceptions">exceptions</a>',
'<br>',
].join(' '));
}
};
// In case input name contains more than 2 words, then
// only first 2 words are printed in one line and all
// the other words in next line. Used to solve text-wrap issue.
function extractWrappedWord(name, isFirstLine) {
if (name == null || name.trim().length == 0){
return name;
}
var words = name.split(' ');
if(words.length >= 3 && isFirstLine){
return words[0] + ' ' + words[1];
}
if(words.length >= 3){
var remainingWords = words.slice(2, words.length);
return remainingWords.join(' ');
}
if(isFirstLine){
return name;
}
return '';
}
function makeTrendlineQuery(metric, name, instance, start, end, callback) {
var minuteToIndex = {};
start = start.valueOf() / 1000;
end = end.valueOf() / 1000;
for (var i = start, j = 0; i < end; i+=60, j++) {
minuteToIndex[i] = j;
}
function round(n) {
return Math.floor(n / 60) * 60;
}
executeMetricsQuery();
function executeMetricsQuery() {
var u = baseUrl + '/topologies/metrics/timeline?'
var request = [
u + 'cluster=' + cluster,
'environ=' + environ,
'topology=' + toponame,
'metric=' + metric.metricName,
'component=' + name,
'instance=' + instance,
'starttime=' + start,
'endtime=' + end,
'max=' + true
].join('&');
d3.json(request, function (error, data) {
var result = [];
if (data && data.hasOwnProperty("status") && data["status"] === "success" && data.hasOwnProperty("result")) {
var result = [];
d3.entries(minuteToIndex).forEach(function (d) {
result[d.value] = [new Date((+d.key) * 1000), 0];
});
var timeline = data.result.timeline[0];
for (var timestamp in timeline.data) {
if (timeline.data.hasOwnProperty(timestamp)) {
if (result[minuteToIndex[round(timestamp)]]) {
result[minuteToIndex[round(timestamp)]][1] = timeline.data[timestamp];
}
}
}
resolve(result);
}
});
}
function resolve(data) {
callback(data);
callback = function () {};
}
}
return result;
}