mirror of
https://github.com/unraid/webgui.git
synced 2026-03-12 22:09:53 -05:00
Added CPU isolation
Both CPU pinning and CPU isolation is possible thru the GUI
This commit is contained in:
29
plugins/dynamix/CPUisol.page
Normal file
29
plugins/dynamix/CPUisol.page
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user