Added CPU isolation

Both CPU pinning and CPU isolation is possible thru the GUI
This commit is contained in:
bergware
2018-09-04 12:18:01 +02:00
parent e55ea90f59
commit 62e05ca5ed
5 changed files with 170 additions and 16 deletions

View File

@@ -0,0 +1,29 @@
Menu="CPUset"
Title="CPU Isolation"
Tag="leaf"
---
<?PHP
/* Copyright 2005-2018, Lime Technology
* Copyright 2012-2018, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<form name="is">
<input type="hidden" name="names" value="isolcpus">
<table class='tablesorter shift' style='width:auto'>
<thead><tr><th><i class="fa fa-list"></i> Isolation</th><?create()?></tr></thead>
<tbody id="table-is"><?=$spinner?></tbody>
</table>
<input type="button" value="Apply" onclick="apply(this.form)" disabled><input type="button" value="Done" onclick="done()"><span id="wait-is" class="red-text" style="display:none"><i class="fa fa-spinner fa-spin"></i> Please wait...</span>
</form>
> CPU isolation allows the user to specify CPU cores that are to be explicitly reserved for assignment (to VMs or Containers).
>
> This is incredibly important for gaming VMs to run smoothly because even if you manually pin your docker containers to not overlap with your gaming VM,
> the host OS can still utilize those same cores as the guest VM needs for things like returning responses for the webGui, running a parity check, btrfs operations, etc.

View File

@@ -1,8 +1,9 @@
Menu="OtherSettings"
Title="CPU Pinning"
Type="xmenu"
Icon="grid.png"
Tag="map-marker"
Cond="pgrep('libvirtd')!==false || pgrep('dockerd')!==false"
Tabs="true"
---
<?PHP
/* Copyright 2005-2018, Lime Technology
@@ -44,6 +45,7 @@ function create() {
}
?>
<style>
form{margin-bottom:20px}
table.tablesorter tr>th+th{text-align:right;vertical-align:top}
table.tablesorter tr>td+td+td{vertical-align:top}
table.tablesorter tr>th+th+th,table.tablesorter tr>td+td{text-align:center}
@@ -96,7 +98,11 @@ function apply(form) {
$('#'+id+'-'+reply.success.stripper()).hide('slow');
// cleanup when all is done
if (!--wait) {
setTimeout(function(){$('#wait-'+id).hide();},500);
if (id == 'is') {
$('#wait-is').html('<strong>Reboot system to make new settings active!</strong>');
} else {
setTimeout(function(){$('#wait-'+id).hide();},500);
}
$('input[value="Done"]').prop('disabled',false);
}
}
@@ -126,6 +132,13 @@ function ct() {
buttons(document.ct);
});
}
function is() {
// fetch the current isolcpu assignments
$.post('/webGui/include/CPUset.php',{id:'is',cpus:'<?=$cpuset?>'},function(d){
$('#table-is').html(d);
buttons(document.is);
});
}
function reset(form) {
// undo changes without a complete refresh of the page
$(form).find('input[value="Apply"]').prop('disabled',true);
@@ -133,14 +146,18 @@ function reset(form) {
switch ($(form).prop('name')) {
case 'vm': $('#table-vm').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); vm(); break;
case 'ct': $('#table-ct').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); ct(); break;
case 'is': $('#table-is').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); is(); break;
}
}
function buttons(form) {
$(form).find('input[type=checkbox]').each(function(){$(this).on('change',function(){
var total = $(form).find('input[type=checkbox]').length;
var checked = 'input[name^="'+$(this).prop('name').split(':')[0]+':'+'"]:checked';
var cores = $(form).find(checked).length;
// vms must have at least one core selected
if ($(form).prop('name')=='vm') $(form).find(checked).prop('disabled',cores<2);
// isolation may not have all cores selected
if ($(form).prop('name')=='is' && $(this).prop('checked')) $(this).prop('checked',cores<total);
// we need the Apply and Done buttons react on checkbox changes
$(form).find('input[value="Apply"]').prop('disabled',false);
$(form).find('input[value="Done"]').val('Reset').prop('onclick',null).off('click').click(function(){reset(form);});
@@ -153,21 +170,13 @@ $(function(){
<?if ($dockerd):?>
ct();
<?endif;?>
is();
});
</script>
> This page gives a total view of the current CPU pinning assignments for both VMs and Docker containers.<br>
> It also allows to modify these assignments.
>
> Running VMs or containers are **stopped first** and restarted after the modification.<br>
> Stopped VMs or containers are instantly modified and new assignments become active when the user manually starts the VM or container.
>
> When ***Apply*** is pressed a scan is performed to find the changes, subsequently only VMs or containers which have changes are modified in parallel.
>
> *Please wait until all updates are finished*.
>
> By default NO cores are selected for a Docker container, which means it uses all available cores.<br>
> **Do not select ALL cores, just select NONE**
<?if (!$libvirtd && !$dockerd):?>
<div class="notice">No CPU pinning available. VM service or Docker service must be started</div>
<?endif;?>
<?if ($libvirtd):?>
<form name="vm">
@@ -178,8 +187,8 @@ $(function(){
</table>
<input type="button" value="Apply" onclick="apply(this.form)" disabled><input type="button" value="Done" onclick="done()"><span id="wait-vm" class="red-text" style="display:none"><i class="fa fa-spinner fa-spin"></i> Please wait...</span>
</form>
<br>
<?endif;?>
<?if ($dockerd):?>
<form name="ct">
<input type="hidden" name="names" id="names-ct" value="">
@@ -190,3 +199,16 @@ $(function(){
<input type="button" value="Apply" onclick="apply(this.form)" disabled><input type="button" value="Done" onclick="done()"><span id="wait-ct" class="red-text" style="display:none"><i class="fa fa-spinner fa-spin"></i> Please wait...</span>
</form>
<?endif;?>
> This page gives a total view of the current CPU pinning assignments for both VMs and Docker containers.<br>
> It also allows to modify these assignments.
>
> Running VMs or containers are **stopped first** and restarted after the modification.<br>
> Stopped VMs or containers are instantly modified and new assignments become active when the user manually starts the VM or container.
>
> When ***Apply*** is pressed a scan is performed to find the changes, subsequently only VMs or containers which have changes are modified in parallel.
>
> *Important: Please wait until all updates are finished before leaving this page*.
>
> By default NO cores are selected for a Docker container, which means it uses all available cores.<br>
> Do not select **ALL** cores for containers, just select **NO** cores if you want unrestricted core use.

View File

@@ -14,6 +14,10 @@
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$cpus = explode(';',$_POST['cpus']);
function scan($area, $text) {
return strpos($area,$text)!==false;
}
function create($id, $name, $vcpu) {
// create the list of checkboxes. Make multiple rows when CPU cores are many ;)
global $cpus;
@@ -83,5 +87,39 @@ case 'ct':
// return the cpu assignments and available container names
echo "\0".implode(';',array_map('urlencode',$cts));
break;
case 'is':
$sys = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
$size = count($sys);
$menu = $i = 0;
$isolcpus = $isol = [];
// find the default section
while ($i < $size) {
if (scan($sys[$i],'label ')) {
$n = $i + 1;
// find the current isolcpus setting
while (!scan($sys[$n],'label ') && $n < $size) {
if (scan($sys[$n],'menu default')) $menu = 1;
if (scan($sys[$n],'append')) foreach (explode(' ',$sys[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isol = explode('=',$cmd)[1]; break;}
$n++;
}
if ($menu) break; else $i = $n - 1;
}
$i++;
}
if ($isol != '') {
// convert to individual numbers
foreach (explode(',',$isol) as $cpu) {
unset($first,$last);
list($first,$last) = explode('-',$cpu);
$last = $last ?: $first;
for ($x = $first; $x <= $last; $x++) $isolcpus[] = $x;
}
sort($isolcpus,SORT_NUMERIC);
$isolcpus = array_unique($isolcpus,SORT_NUMERIC);
}
echo "<tr><td>Isolated CPUs</td>";
create('is', 'isolcpus', $isolcpus);
echo "</tr>";
break;
}
?>

View File

@@ -20,7 +20,7 @@ foreach($_POST as $key => $val) {
list($name,$cpu) = explode(':',$key);
$map[urldecode($name)] .= "$cpu,";
}
// map holds the list of each vm or container and its newly proposed cpu assignments
// map holds the list of each vm, container or isolcpus and its newly proposed cpu assignments
$map = array_map(function($d){return substr($d,0,-1);},$map);
switch ($_POST['id']) {
@@ -72,6 +72,14 @@ case 'ct':
}
$reply = ['success' => (count($changes) ? implode(';',$changes) : '')];
break;
case 'is':
// report changed isolcpus in temporary file
foreach ($map as $name => $isolcpu) {
file_put_contents("/var/tmp/$name.tmp",$isolcpu);
$changes[] = $name;
}
$reply = ['success' => (count($changes) ? implode(';',$changes) : '')];
break;
}
// signal changes
header('Content-Type: application/json');

View File

@@ -13,6 +13,10 @@
<?
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
function scan($area, $text) {
return strpos($area,$text)!==false;
}
$name = urldecode($_POST['name']);
switch ($_POST['id']) {
case 'vm':
@@ -114,6 +118,59 @@ case 'ct':
}
$reply = ['success' => $name];
break;
case 'is':
$cfg = '/boot/syslinux/syslinux.cfg';
$sys = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
$size = count($sys);
$menu = $i = 0;
$cmd = [];
// find the default section
while ($i < $size) {
if (scan($sys[$i],'label ')) {
$n = $i + 1;
while (!scan($sys[$n],'label ') && $n < $size) {
if (scan($sys[$n],'menu default')) $menu = 1;
// find the current command
if (scan($sys[$n],'append')) {$cmd = preg_split('/\s+/',trim($sys[$n])); break;}
$n++;
}
if ($menu) break; else $i = $n - 1;
}
$i++;
}
if ($cmd) {
// modify the current command
$file = "/var/tmp/$name.tmp";
// read new isolcpus assignments
$isolcpus = file_get_contents($file); unlink($file);
if ($isolcpus != '') {
$numbers = explode(',',$isolcpus);
sort($numbers,SORT_NUMERIC);
$previous = array_shift($numbers);
$isolcpus = $previous;
$range = false;
// convert sequential numbers to a range
foreach ($numbers as $number) {
if ($number == $previous+1) {
$range = true;
} else {
if ($range) {$isolcpus .= '-'.$previous; $range = false;}
$isolcpus .= ','.$number;
}
$previous = $number;
}
if ($range) $isolcpus .= '-'.$previous;
$isolcpus = "isolcpus=$isolcpus";
}
// replace an existing setting
for ($c = 0; $c < count($cmd); $c++) if (scan($cmd[$c],'isolcpus')) {$cmd[$c] = $isolcpus; break;}
// or insert a new setting
if ($c == count($cmd) && $isolcpus) array_splice($cmd,-1,0,$isolcpus);
$sys[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
file_put_contents($cfg, implode("\n",$sys)."\n");
}
$reply = ['success' => $name];
break;
}
header('Content-Type: application/json');
die(json_encode($reply));