cloud-8.x-2.0-beta1/modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js
modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js
(function ($, drupalSettings) {
'use strict';
if (!drupalSettings.k8s || !drupalSettings.k8s.metrics_enable) {
$("#k8s_node_allocated_resources").parent().parent().hide();
return;
}
const FONT_FAMILY = 'Lucida Grande, -apple-system, BlinkMacSystemFont';
const FONT_SIZE_AXIS_LABEL = 13;
const RECT_PX = 85;
const MAX_Y_AXIS_LABEL_STR_LENGTH = 30;
// Create a dummy <span>...</span> to measure the length (pixel) of a string.
// This <span/> element should be removed later on.
$('<span/>', {id: 'width_check', appendTo: 'body'})
.css('font-size', FONT_SIZE_AXIS_LABEL)
.css('visibility', 'hidden')
.css('position', 'absolute')
.css('white-space', 'nowrap');
//Read the data.
let updateNodeHeatmap = function () {
d3.json(window.location.pathname + '/allocated_resources').then(function (nodes) {
// List up the node names.
let get_node_name = function (node_name) {
return node_name.length > MAX_Y_AXIS_LABEL_STR_LENGTH
? node_name.substring(0, MAX_Y_AXIS_LABEL_STR_LENGTH) + '...'
: node_name;
};
let node_names = nodes.map(function (node) {
return get_node_name(node.name);
});
let y_axis_label_px = 0;
// 1. Get the max pods capacity across the K8s cluster.
let max_pods_capacity = Math.max(...(nodes.map(function (node) {
return node.podsCapacity;
})));
// 2. Set the dimensions and margins of the graph.
let pods_allocation_scale
= [...Array(max_pods_capacity).keys()].map(i => ++i);
let pods = Array();
nodes.forEach(function (node) {
// Measure the length (pixel) of a string.
// let width_check_px = $('#width_check').text(node.name).get(0).offsetWidth;
let node_name = get_node_name(node.name);
let width_check_px = $('#width_check').text(node_name).get(0).offsetWidth;
y_axis_label_px = width_check_px > y_axis_label_px
? width_check_px
: y_axis_label_px;
for (let i = 0; i < node.podsCapacity; i++) {
let pod_name = i < node.podsAllocation && node.pods[i]
? node.pods[i].name
: '';
let cpu_usage = i < node.podsAllocation && node.pods[i]
// @TODO: "* 100 * 3" is an adjustment. Change the unit for the normal use.
? node.pods[i].cpuUsage * 100 * 3 + 20
: 0;
let memory_usage = i < node.podsAllocation && node.pods[i]
? Math.floor(node.pods[i].memoryUsage / 1024 / 1024)
: '0';
pods.push({
'index': i + 1,
'nodeName': node_name,
'name': pod_name,
'cpuUsage': cpu_usage,
'memoryUsage': memory_usage
});
}
});
// Remove the <span/> element.
$('#width_check').empty();
let margin = {top: 20, right: 15, bottom: 20, left: y_axis_label_px + 15};
let width = max_pods_capacity * RECT_PX - margin.left - margin.right;
let height = node_names.length * RECT_PX - margin.top - margin.bottom;
height = height < RECT_PX
? RECT_PX - margin.top / 4 - margin.bottom / 4
: height;
// * Important * Initialization the SVG object. If you remove this code,
// the SVG object will be duplicated repeatedly.
$('#k8s_node_heatmap').empty();
// Append the svg object to the body of the page.
let svg = d3.select('#k8s_node_heatmap')
.append('svg')
.attr('class' ,'node_heatmap')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.style('font-family', FONT_FAMILY)
.attr('transform',
'translate(' + margin.left + ', ' + margin.top + ')');
// Build X scales and axis:
let x = d3.scaleBand()
.range([0, width])
.domain(pods_allocation_scale)
.padding(0.1);
svg.append('g')
.style('font-size', FONT_SIZE_AXIS_LABEL)
.style('font-family', FONT_FAMILY)
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x).tickSize(0))
.select('.domain').remove();
// Build Y scales and axis:
let y = d3.scaleBand()
.range([height, 0])
.domain(node_names)
.padding(0.1);
svg.append('g')
.style('font-size', FONT_SIZE_AXIS_LABEL)
.style('font-family', FONT_FAMILY)
.call(d3.axisLeft(y).tickSize(0))
.select('.domain').remove();
// Build color scale.
let my_color = d3.scaleLinear()
// .range(['white', '#69b3a2']) // Green.
// .range(['white', '#f89830']) // Light Orange.
// .range(['white', '#f79820']) // Dark Orange.
// Dark orange.
.range(['white', '#ff9900'])
.domain([1, 100]);
// Create a tooltip.
let tooltip = d3.select('#k8s_node_heatmap')
.append('div')
.style('opacity', 0)
.attr('class', 'tooltip')
.style('background-color', 'white')
.style('border', 'solid')
.style('border-width', '2px')
.style('border-radius', '5px')
.style('padding', '5px');
// Three function that change the tooltip when user hover / move / leave a cell.
let mouseover = function (pod) {
tooltip.style('opacity', 1);
d3.select(this)
.style('stroke', '#cc6600')
.style('opacity', 1);
};
let mousemove = function (pod) {
tooltip
.html('<strong>' + pod.name + '</strong><br />'
+ pod.cpuUsage + '<br />'
+ pod.memoryUsage + ' MiB')
.style('display', 'block')
.style('left', (d3.mouse(this)[0] + y_axis_label_px + RECT_PX / 2.5) + 'px')
.style('top', node_names.length > 1
? (d3.mouse(this)[1] + RECT_PX / 2.5 + margin.top) + 'px'
: ($('#k8s_node_heatmap').get(0).offset().top) + 'px'
);
};
let mouseleave = function (pod) {
tooltip.style('display', 'none');
d3.select(this)
.style('stroke', 'none')
.style('opacity', 0.8);
};
// Add the squares.
svg.selectAll('svg.node_heatmap')
.exit()
.remove()
.data(pods, function (pod) {
return pod.nodeName + ':' + pod.index;
})
.enter()
.append('rect')
.attr('x', function (pod) {
return x(pod.index);
})
.attr('y', function (pod) {
return y(pod.nodeName);
})
.attr('rx', 5)
.attr('ry', 5)
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
// 20: Adjust color.
.style('fill', function (pod) {
return my_color(pod.cpuUsage + 20);
})
.style('stroke-width', 4)
.style('stroke', 'none')
.style('opacity', 0.8)
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseleave', mouseleave);
});
};
updateNodeHeatmap();
let interval = drupalSettings.k8s.k8s_js_refresh_interval || 10;
setInterval(function() {
updateNodeHeatmap();
}, interval * 1000);
} (jQuery, drupalSettings));
