Skip to content
Snippets Groups Projects
Commit 7632ada3 authored by xiaohua guan's avatar xiaohua guan Committed by Yas Naoi
Browse files

Issue #3090446 by Xiaohua Guan, yas: Add a block to list up the status of the...

Issue #3090446 by Xiaohua Guan, yas: Add a block to list up the status of the cost balance in each namespace
parent 39db8375
No related branches found
No related tags found
No related merge requests found
......@@ -803,3 +803,33 @@ function k8s_convert_memory_to_integer($memory) {
return 0;
}
/**
* Format memory string.
*
* @param float $number
* The number of memory size.
*
* @return string
* The formatted memory string.
*/
function k8s_format_memory($number) {
$binary_units = [
'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei',
];
$exp = 1;
$memory_unit = '';
$memory_number = $number;
foreach ($binary_units as $unit) {
$base_number = pow(1024, $exp++);
if ($number < $base_number) {
break;
}
$memory_number = round($number / $base_number, 2);
$memory_unit = $unit;
}
return $memory_number . $memory_unit;
}
......@@ -671,7 +671,7 @@ class K8sNode extends CloudContentEntityBase implements K8sNodeInterface {
$fields['cpu_usage'] = BaseFieldDefinition::create('float')
->setLabel(t('CPU (Usage)'))
->setDescription(t('The requested cpu.'))
->setDescription(t('The cpu usage.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'string',
......
<?php
namespace Drupal\k8s\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Url;
use Drupal\k8s\Service\CostFieldsRendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block of the costs.
*
* @Block(
* id = "k8s_namespace_costs",
* admin_label = @Translation("K8s Namespace Costs"),
* category = @Translation("K8s")
* )
*/
class K8sNamespaceCostsBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The entity.manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The route match service.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The page cache kill switch service.
*
* @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
*/
protected $killSwitch;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The cost fields renderer.
*
* @var \Drupal\k8s\Service\CostFieldsRendererInterface
*/
protected $costFieldsRenderer;
/**
* Constructs a new K8sNodeHeatmapBlock instance.
*
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity.manager service.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match service.
* @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $kill_switch
* The page cache kill switch service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\k8s\Service\CostFieldsRendererInterface $cost_fields_renderer
* The cost fields renderer.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
EntityTypeManagerInterface $entity_type_manager,
RouteMatchInterface $route_match,
KillSwitch $kill_switch,
ModuleHandlerInterface $module_handler,
CostFieldsRendererInterface $cost_fields_renderer
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->routeMatch = $route_match;
$this->killSwitch = $kill_switch;
$this->moduleHandler = $module_handler;
$this->costFieldsRenderer = $cost_fields_renderer;
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('current_route_match'),
$container->get('page_cache_kill_switch'),
$container->get('module_handler'),
$container->get('k8s.cost_fields_renderer')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'aws_cloud_ec2_cost_type' => 'ri_one_year',
];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$options = [
'on_demand_yearly' => $this->t('On-demand Yearly ($)'),
'ri_one_year' => $this->t('RI 1 Year ($)'),
'ri_three_year' => $this->t('RI 3 Year ($)'),
];
$form['aws_cloud_ec2_cost_type'] = [
'#type' => 'select',
'#title' => $this->t('AWS Cloud EC2 Cost Type'),
'#options' => $options,
'#default_value' => $this->configuration['aws_cloud_ec2_cost_type'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['aws_cloud_ec2_cost_type']
= $form_state->getValue('aws_cloud_ec2_cost_type');
}
/**
* {@inheritdoc}
*/
public function build() {
$build = [];
$cloud_context = $this->routeMatch->getParameter('cloud_context');
if (empty($cloud_context)) {
return $build;
}
$nodes = $this->entityTypeManager
->getStorage('k8s_node')->loadByProperties([
'cloud_context' => $cloud_context,
]
);
$total_costs = $this->getTotalCosts($nodes);
// $cpu_capacity = $this->getCpuCapacity($nodes);
$cpu_capacity = array_sum(array_map(function($node) {
return $node->getCpuCapacity();
}, $nodes));
$memory_capacity = array_sum(array_map(function($node) {
return $node->getMemoryCapacity();
}, $nodes));
$pod_capacity = array_sum(array_map(function($node) {
return $node->getPodsCapacity();
}, $nodes));
$namespaces = $this->entityTypeManager
->getStorage('k8s_namespace')->loadByProperties([
'cloud_context' => $cloud_context,
]
);
// Get row data.
$rows = [];
foreach ($namespaces as $namespace) {
$pods = $this->entityTypeManager
->getStorage('k8s_pod')->loadByProperties([
'cloud_context' => $namespace->getCloudContext(),
'namespace' => $namespace->getName(),
]
);
$row['namespace'] = [
'data' => [
'#type' => 'link',
'#title' => $namespace->getName(),
'#url' => Url::fromRoute(
'entity.k8s_namespace.canonical',
[
'cloud_context' => $cloud_context,
'k8s_namespace' => $namespace->id(),
]
),
]
];
$cpu_usage = array_sum(array_map(function($pod) {
return $pod->getCpuUsage();
}, $pods));
$memory_usage = array_sum(array_map(function($pod) {
return $pod->getMemoryUsage();
}, $pods));
$pod_usage = count($pods);
$costs = ($cpu_usage / $cpu_capacity + $memory_usage / $memory_capacity + $pod_usage / $pod_capacity) / 3 * $total_costs;
$row['costs'] = $this->formatCosts($costs, $total_costs);
$row['cpu_usage'] = $this->formatCpuUsage($cpu_usage, $cpu_capacity);
$row['memory_usage'] = $this->formatMemoryUsage($memory_usage, $memory_capacity);
$row['pod_usage'] = $this->formatPodUsage($pod_usage, $pod_capacity);
$rows[] = $row;
}
$table = [
'#theme' => 'table',
'#header' => [
$this->t('Namespace'),
$this->t('Total Cost ($)'),
$this->t('CPU (Usage)'),
$this->t('Memory (Usage)'),
$this->t('Pods Allocation'),
],
'#rows' => $rows,
];
$build['k8s_namespace_costs'] = [
'#type' => 'details',
'#title' => $this->t('Namespace Costs'),
'#open' => TRUE,
];
$build['k8s_namespace_costs'][] = $table;
return $build;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
$this->killSwitch->trigger();
// BigPipe/#cache/max-age is breaking my block javascript
// https://www.drupal.org/forum/support/module-development-and-code-questions/2016-07-17/bigpipecachemax-age-is-breaking-my
// "a slight delay of a second or two before the charts are built.
// That seems to work, though it is a janky solution.".
return $this->moduleHandler->moduleExists('big_pipe') ? 1 : 0;
}
/**
* Format costs.
*
* @param int $costs
* The costs.
* @param int $total_costs
* The total costs.
*
* @return string
* The formatted costs string.
*/
private function formatCosts($costs, $total_costs) {
$costs_str = round($costs, 2);
$percentage = round($costs / $total_costs * 100, 2);
return "$costs_str ($percentage%)";
}
/**
* Format cpu usage.
*
* @param float $cpu_usage
* The cpu usage.
* @param float $cpu_capacity
* The cpu capacity.
*
* @return string
* The formatted cpu usage string.
*/
private function formatCpuUsage($cpu_usage, $cpu_capacity) {
$cpu_str = round($cpu_usage, 2);
$percentage = round($cpu_usage / $cpu_capacity * 100, 2);
return "$cpu_str ($percentage%)";
}
/**
* Format memory usage.
*
* @param int $memory_usage
* The memory usage.
* @param int $memory_capacity
* The memory capacity.
*
* @return string
* The formatted memory usage string.
*/
private function formatMemoryUsage($memory_usage, $memory_capacity) {
$memory_str = k8s_format_memory($memory_usage);
$percentage = round($memory_usage / $memory_capacity * 100, 2);
return "$memory_str ($percentage%)";
}
/**
* Format pod usage.
*
* @param int $pod_usage
* The pod usage.
* @param int $pod_capacity
* The pod capacity.
*
* @return string
* The formatted pod usage string.
*/
private function formatPodUsage($pod_usage, $pod_capacity) {
$percentage = round($pod_usage / $pod_capacity * 100, 2);
return "$pod_usage/$pod_capacity ($percentage%)";
}
/**
* Get total costs of nodes.
*
* @param array $nodes
* The k8s_node entities.
*
* @return int
* The total costs of nodes.
*/
private function getTotalCosts(array $nodes) {
$costs = 0;
if (!$this->moduleHandler->moduleExists('aws_cloud')) {
return $costs;
}
$price_date_provider = \Drupal::service('aws_cloud.instance_type_price_data_provider');
foreach ($nodes as $node) {
// Get instance type and region.
$region = NULL;
$instance_type = NULL;
foreach ($node->get('labels') as $item) {
if ($item->getItemKey() === 'beta.kubernetes.io/instance-type') {
$instance_type = $item->getItemValue();
}
elseif ($item->getItemKey() === 'failure-domain.beta.kubernetes.io/region') {
$region = $item->getItemValue();
}
}
if (empty($instance_type) || empty($region)) {
continue;
}
$price_data = $price_date_provider->getDataByRegion($region);
foreach ($price_data as $item) {
if ($item['instance_type'] == $instance_type) {
$cost_type = $this->configuration['aws_cloud_ec2_cost_type'];
if (!empty($item[$cost_type])) {
if ($cost_type == 'ri_three_year') {
$costs += $item[$cost_type] / 3;
}
else {
$costs += $item[$cost_type];
}
}
break;
}
}
}
return $costs;
}
}
......@@ -60,7 +60,7 @@ class MemoryFormatter extends FormatterBase {
$entity = $items->getEntity();
foreach ($items as $delta => $item) {
if (!$item->isEmpty()) {
$memory_str = $this->formatMemory($item->value);
$memory_str = k8s_format_memory($item->value);
if ($show_percentage) {
$capacity = $entity->getMemoryCapacity();
$percentage = round($item->value / $capacity * 100, 2);
......@@ -73,34 +73,4 @@ class MemoryFormatter extends FormatterBase {
return $elements;
}
/**
* Format memory string.
*
* @param float $number
* The number of memory size.
*
* @return string
* The formatted memory string.
*/
private function formatMemory($number) {
$binary_units = [
'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei',
];
$exp = 1;
$memory_unit = '';
$memory_number = $number;
foreach ($binary_units as $unit) {
$base_number = pow(1024, $exp++);
if ($number < $base_number) {
break;
}
$memory_number = round($number / $base_number, 2);
$memory_unit = $unit;
}
return $memory_number . $memory_unit;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment