diff --git a/modules/cloud_service_providers/k8s/js/k8s_node_allocated_resources.js b/modules/cloud_service_providers/k8s/js/k8s_node_allocated_resources.js index 8ae3d3f..130677a 100644 --- a/modules/cloud_service_providers/k8s/js/k8s_node_allocated_resources.js +++ b/modules/cloud_service_providers/k8s/js/k8s_node_allocated_resources.js @@ -3,18 +3,21 @@ 'use strict'; if (!drupalSettings.k8s || !drupalSettings.k8s.metrics_enable) { - $("#k8s_node_allocated_resources").parent().parent().hide(); + $('#k8s_node_allocated_resources').parent().parent().hide(); return; } const FONT_FAMILY = 'Lucida Grande, -apple-system, BlinkMacSystemFont', FONT_SIZE_RSC_ALLOCATION_RATIO = 19, FONT_SIZE_RSC_USAGE = 15, + FONT_SIZE_TITLE = 19, WIDTH = 250, HEIGHT = 250, + TITLE_HEIGHT = 20, RING_WIDTH = 50, RADIUS = Math.min(WIDTH, HEIGHT) / 2, - FOREGROUND_RING_WIDTH = 30; + FOREGROUND_RING_WIDTH = 30, + RECT_PX = 85; // Create a dummy ... to measure the length (pixel) of a string. $('', {id: 'rsc_allocation_ratio_width_check', appendTo: 'body'}) @@ -29,6 +32,12 @@ .css('position', 'absolute') .css('white-space', 'nowrap'); + $('', {id: 'title_width_check', appendTo: 'body'}) + .css('font-size', FONT_SIZE_TITLE) + .css('visibility', 'hidden') + .css('position', 'absolute') + .css('white-space', 'nowrap'); + let pie = d3.pie() .sort(null).value(function (d) { return d.value; @@ -44,16 +53,102 @@ function createSvg(data) { return d3.select(data.selector) - .attr("align","center") + .attr('align', 'center') .append('svg') .attr('class', data.class) .attr('width', WIDTH) - .attr('height', HEIGHT) + .attr('height', HEIGHT + TITLE_HEIGHT) .style('display', 'inline'); } - function render(data) { + // Create a tooltip. + let tooltip = d3.select('body') + .append('div') + .style('opacity', 1) + .attr('class', 'tooltip') + .style('background-color', 'white') + .style('border', 'solid') + .style('border-width', '2px') + .style('border-radius', '5px') + .style('padding', '5px') + .style('display', 'none'); + + // Three function that change the tooltip when user hover / move / leave a cell. + let mouseover = function (pie) { + tooltip.style('display', 'block'); + + d3.select(this) + .style('stroke', pie.data.color) + .style('stroke-width', 5) + .style('opacity', 1); + }; + + let mousemove = function (pie) { + tooltip.html('' + pie.value + ' ' + pie.data.suffix + '') + .style('left', (d3.event.pageX + 10) + 'px') + .style('top', (d3.event.pageY - 120) + 'px'); + }; + let mouseleave = function (pie) { + tooltip.style('display', 'none'); + d3.select(this) + .style('stroke', 'none') + .style('opacity', 0.8); + }; + + let drawLegend = function() { + let width = 600; + let height = 50; + let padding = 20; + let xpadding = 90; + let number = 20; + let svg = d3.select('#k8s_node_allocated_resources') + .append('svg') + .attr('width', width) + .attr('height', height) + .attr('viewBox', 0 + ' ' + 0 + ' ' + width + ' ' + height); + + svg.append('text') + .attr('x', 10) + .attr('y', 25) + .style('font-family', FONT_FAMILY) + .style('font-size', 16) + .style('font-weight', 900) + .text('Legend'); + + let color = d3.interpolateSpectral; + let defs = svg.append('defs') + .append('linearGradient') + .attr('id', 'legendGradient'); + + for (let i = 0; i <= number; i++) { + defs.append('stop') + .attr('offset', (i * 100 / number) + '%') + .attr('stop-color', color((number - i) / number)); + } + + svg.append('rect') + .attr('x', xpadding) + .attr('y', 0) + .attr('width', width - 2 * xpadding) + .attr('height', height - padding) + .attr('fill', 'url(#legendGradient)'); + + let xScale = d3.scaleLinear() + .range([0, width - 2 * xpadding]) + .domain([0, 1.0]); + + let axis = svg.append('g') + .attr('transform', 'translate(' + xpadding + ',' + (height - padding) + ')') + .call( + d3.axisBottom(xScale) + .tickFormat(d3.format('.0%')) + ); + axis.select('path') + .attr('opacity', 0.0) + }; + + function render(data) { let svg = d3.select('svg.' + data.class); // The normal pie chart. @@ -83,6 +178,9 @@ .attr('fill', function(d, i) { return d.data.color; }) + .on('mouseover', mouseover) + .on('mousemove', mousemove) + .on('mouseleave', mouseleave) .attr('d', arc) // Start transition. .transition() @@ -112,6 +210,11 @@ let rsc_allocation_ratio_height_check_px = $('#rsc_allocation_ratio_width_check') .text(data.resourceAllocationRatio).get(0).offsetHeight; + let title_width_check_px = $('#title_width_check') + .text(data.title).get(0).offsetWidth; + let title_height_check_px = $('#title_width_check') + .text(data.title).get(0).offsetHeight; + // Remove the element. $('#rsc_usage_width_check').empty(); $('#rsc_allocation_ratio_width_check').empty(); @@ -132,7 +235,7 @@ .text(data.resourceAllocationRatio); svg.selectAll('.resource_usage') - .remove(); + .remove(); svg.append('text') .attr('class', 'resource_usage') @@ -142,6 +245,19 @@ .style('font-size', FONT_SIZE_RSC_USAGE) .style('fill', d3.schemeSet3[8] + data.pieChart[0].color) .text(data.resourceUsage); + + // Add title. + svg.selectAll('.chart_title') + .remove(); + svg.append('text') + .attr('class', '.chart_title') + .attr('x', WIDTH / 2 + title_width_check_px / -2) + .attr('y', HEIGHT - title_height_check_px + TITLE_HEIGHT) + .style('font-family', FONT_FAMILY) + .style('font-size', FONT_SIZE_TITLE) + .style('font-weight', 900) + .style('fill', d3.rgb(data.pieChart[0].color).darker(1)) + .text(data.title); } const selectors = [{ @@ -178,57 +294,50 @@ pods_allocation += parseInt(node.podsAllocation); }); - // Color scheme combination example for doughnut chart. - // Pastel dark green. - // color: d3.schemePastel2[0], - // Pastel Light green. - // color: d3.schemePastel2[4], - - // Paired dark blue. - // color: d3.schemePaired[1], - // Paired light blue. - // color: d3.schemePaired[0], - - // Paired dark green. - // color: d3.schemePaired[3], - // Paired light green. - // color: d3.schemePaired[2], - // CPU usage doughnut chart. let cpu_request_ratio = cpu_request / cpu_capacity; + let cpu_request_rounded = cpu_request.toFixed(2); render({ svg: svgs.pop(), selector: '#k8s_node_allocated_resources', class: 'k8s_node_cpu_usage', pieChart: [{ color: d3.interpolateSpectral(1 - cpu_request_ratio), - value: cpu_request + value: cpu_request_rounded, + suffix: 'Cores' }, { color: d3.interpolateSpectral(1 - cpu_request_ratio - 0.15), - value: cpu_capacity - cpu_request + value: (cpu_capacity - cpu_request_rounded).toFixed(2), + suffix: 'Cores' }], - resourceUsage: cpu_request.toFixed(2) + ' / ' + + resourceUsage: cpu_request_rounded + ' / ' + cpu_capacity + ' Cores', - resourceAllocationRatio: (cpu_request_ratio * 100).toFixed(1) + '%' + resourceAllocationRatio: (cpu_request_ratio * 100).toFixed(1) + '%', + title: 'CPU Core Usage' }); // Memory usage doughnut chart. let memory_request_ratio = memory_request / memory_capacity; + let memory_request_rounded = (memory_request / 1024 / 1024 / 1024).toFixed(2); + let memory_capacity_rounded = (memory_capacity / 1024 / 1024 / 1024).toFixed(2); render({ svg: svgs.pop(), selector: '#k8s_node_allocated_resources', class: 'k8s_node_memory_usage', pieChart: [{ color: d3.interpolateSpectral(1 - memory_request_ratio), - value: memory_request + value: memory_request_rounded, + suffix: 'GiB' }, { color: d3.interpolateSpectral(1 - memory_request_ratio - 0.15), - value: memory_capacity - memory_request + value: (memory_capacity_rounded - memory_request_rounded).toFixed(2), + suffix: 'GiB' }], resourceUsage: - (memory_request / 1024 / 1024 / 1024).toFixed(2) + ' / ' + - (memory_capacity / 1024 / 1024 / 1024).toFixed(2) + ' GiB', - resourceAllocationRatio: (memory_request_ratio * 100).toFixed(1) + '%' + memory_request_rounded + ' / ' + + memory_capacity_rounded + ' GiB', + resourceAllocationRatio: (memory_request_ratio * 100).toFixed(1) + '%', + title: 'Memory Usage' }); // Pods allocation doughnut chart. @@ -239,18 +348,22 @@ class: 'k8s_node_pods_allocation', pieChart: [{ color: d3.interpolateSpectral(1 - pods_allocation_ratio), - value: pods_allocation + value: pods_allocation, + suffix: 'Pods' }, { color: d3.interpolateSpectral(1 - pods_allocation_ratio - 0.15), - value: pods_capacity - pods_allocation + value: pods_capacity - pods_allocation, + suffix: 'Pods' }], - resourceUsage: pods_allocation + ' / ' + pods_capacity + " Pods", - resourceAllocationRatio: (pods_allocation_ratio * 100).toFixed(1) + '%' + resourceUsage: pods_allocation + ' / ' + pods_capacity + ' Pods', + resourceAllocationRatio: (pods_allocation_ratio * 100).toFixed(1) + '%', + title: 'Pods Allocation' }); }); }; updateNodeAllocatedResources(); + drawLegend(); let interval = drupalSettings.k8s.k8s_js_refresh_interval || 10; setInterval(function() { diff --git a/modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js b/modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js index d1c279c..6b934f2 100644 --- a/modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js +++ b/modules/cloud_service_providers/k8s/js/k8s_node_heatmap.js @@ -157,7 +157,6 @@ let mouseover = function (pod) { tooltip.style('opacity', 1); d3.select(this) - .style('stroke', 'black') .style('stroke', '#cc6600') .style('opacity', 1); }; @@ -167,6 +166,7 @@ .html('' + pod.name + '
' + pod.cpuUsage + '
' + 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' @@ -175,7 +175,7 @@ }; let mouseleave = function (pod) { - tooltip.style('opacity', 0); + tooltip.style('display', 'none'); d3.select(this) .style('stroke', 'none') .style('opacity', 0.8); diff --git a/modules/cloud_service_providers/k8s/src/Controller/ApiController.php b/modules/cloud_service_providers/k8s/src/Controller/ApiController.php index ba3e0ca..b7e4405 100644 --- a/modules/cloud_service_providers/k8s/src/Controller/ApiController.php +++ b/modules/cloud_service_providers/k8s/src/Controller/ApiController.php @@ -281,6 +281,7 @@ class ApiController extends ControllerBase implements ApiControllerInterface { $pods = $this->entityTypeManager->getStorage('k8s_pod') ->loadByProperties([ 'cloud_context' => $cloud_context, + 'node_name' => $node_name, ]); foreach ($pods ?: [] as $pod) {