diff options
Diffstat (limited to 'modules-available/vmstore')
17 files changed, 607 insertions, 8 deletions
diff --git a/modules-available/vmstore/baseconfig/getconfig.inc.php b/modules-available/vmstore/baseconfig/getconfig.inc.php index 3bad16e1..d239a3d7 100644 --- a/modules-available/vmstore/baseconfig/getconfig.inc.php +++ b/modules-available/vmstore/baseconfig/getconfig.inc.php @@ -1,5 +1,8 @@ <?php +/** @var ?string $uuid */ +/** @var ?string $ip */ + // VMStore path and type $vmstore = Property::getVmStoreConfig(); if (is_array($vmstore) && isset($vmstore['storetype'])) { @@ -21,6 +24,7 @@ if (is_array($vmstore) && isset($vmstore['storetype'])) { ConfigHolder::add("SLX_VM_NFS_OPTS", $vmstore['cifsopts']); } break; + default: } } diff --git a/modules-available/vmstore/hooks/main-warning.inc.php b/modules-available/vmstore/hooks/main-warning.inc.php index ca2d1382..50d81ac8 100644 --- a/modules-available/vmstore/hooks/main-warning.inc.php +++ b/modules-available/vmstore/hooks/main-warning.inc.php @@ -4,7 +4,7 @@ * Hook for main page: Show warning if vmstore not configured yet; set "warning" flag if so */ -if (!is_array(Property::getVmStoreConfig())) { +if (empty(Property::getVmStoreConfig())) { Message::addError('vmstore.vmstore-not-configured', true); // Always specify module prefix since this is running in main $needSetup = true; // Set $needSetup to true if you want a warning badge to appear in the menu } diff --git a/modules-available/vmstore/inc/vmstorebenchmark.inc.php b/modules-available/vmstore/inc/vmstorebenchmark.inc.php new file mode 100644 index 00000000..b819ef8a --- /dev/null +++ b/modules-available/vmstore/inc/vmstorebenchmark.inc.php @@ -0,0 +1,84 @@ +<?php + +class VmStoreBenchmark +{ + + const PROP_LIST_KEY = 'vmstore.benchmark'; + + /** + * @param string[] $machineUuids List of UUIDs + * @return void + */ + public static function prepareSelectDialog(array $uuids) + { + Module::isAvailable('rebootcontrol'); + User::assertPermission('.vmstore.benchmark'); + $uuids = array_values(Request::post('uuid', Request::REQUIRED, 'array')); + $machines = RebootUtils::getFilteredMachineList($uuids, '.vmstore.benchmark'); + if ($machines === false) + return; + $machines = array_column($machines, 'machineuuid'); + $id = Property::addToList(self::PROP_LIST_KEY, + json_encode(['machines' => $machines]), 60); + Util::redirect('?do=vmstore&show=benchmark&action=select&id=' . $id); + } + + /** + * @param string $image relative path/name of image + * @param string $serverOrMode IP address of DNBD3 server, OR 'auto' for all servers known to client, or 'nfs' for NFS + * @param int $start timestamp when the clients should start + * @return ?string taskId, or null on error + */ + public static function start(string $id, array $machineUuids, string $image, string $serverOrMode, int &$start): ?string + { + Module::isAvailable('rebootcontrol'); + $clients = Database::queryAll('SELECT machineuuid, clientip FROM machine WHERE machineuuid IN (:uuids)', + ['uuids' => $machineUuids]); + if (empty($clients)) { + ErrorHandler::traceError('Cannot start benchmark: No matching clients'); + } + // The more clients we have, the longer it takes to SSH into all of them. + // As of 2022, RemoteExec processes 4 clients in parallel + $start = ceil(count($clients) / 4 + 5 + time()); + if ($serverOrMode === 'nfs') { + $modeOption = '--nfs'; + } elseif ($serverOrMode === 'auto') { + $modeOption = ''; + } else { + $modeOption = "--servers '$serverOrMode'"; + } + // We fork off the benchmark into the background, and collect the results with another RemoteExec job + // when we're done. This is because RemoteExec only does four concurrent SSH connections, so if we wanted to + // do this the easy, synchronous way, we never could run more than four tests at the same time. + $command = <<<COMMAND +( + exec &> /dev/null < /dev/null + setsid + while true; do + echo 3 > /proc/sys/vm/drop_caches + sleep 1 + done & + flush=\$! + image_speedcheck --start $start --console $modeOption --file "$image" > "/tmp/speedcheck-$id" + kill \$flush +) & +COMMAND; + $task = RebootControl::runScript($clients, $command); + return $task['id'] ?? null; + } + + /** + * @return array{cpu: array, net: array} + */ + public static function parseBenchLine(string $line): array + { + $out = ['cpu' => [], 'net' => []]; + foreach (explode(',', $line) as $elem) { + $elem = explode('+', $elem); + $out['net'][] = ['x' => (int)$elem[0], 'y' => (int)$elem[1]]; + //$out['cpu'][] = ['x' => $elem[0], 'y' => $elem[2]]; + } + return $out; + } + +}
\ No newline at end of file diff --git a/modules-available/vmstore/lang/de/messages.json b/modules-available/vmstore/lang/de/messages.json index 993d355d..86aa780e 100644 --- a/modules-available/vmstore/lang/de/messages.json +++ b/modules-available/vmstore/lang/de/messages.json @@ -1,3 +1,9 @@ { + "benchmark-already-started": "Benchmark bereits gestartet", + "benchmark-failed": "Benchmark fehlgeschlagen", + "dnbd3-failed": "DNBD3-Verbindung fehlgeschlagen", + "invalid-benchmark-job": "Ung\u00fcltige Benchmark-ID: {{0}}", + "invalid-dnbd3-server-id": "Ung\u00fcltige DNBD3 Server-ID: {{0}}", + "select-image-first": "Bitte zuerst ein Image ausw\u00e4hlen", "vmstore-not-configured": "Es ist noch kein Speicherort f\u00fcr die Virtuellen Maschinen festgelegt." }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/de/module.json b/modules-available/vmstore/lang/de/module.json index 87be6cae..80241434 100644 --- a/modules-available/vmstore/lang/de/module.json +++ b/modules-available/vmstore/lang/de/module.json @@ -1,4 +1,8 @@ { + "dnbd3-all-loadbalance": "Alle DNBD3-Server nutzen (load balancing)", + "menu_benchmark": "Benchmark", + "menu_edit": "Bearbeiten", "module_name": "VM Speicherort", + "page-title-benchmark": "Netzwerk Benchmark", "page_title": "Speicherort f\u00fcr VMs festlegen" }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/de/permissions.json b/modules-available/vmstore/lang/de/permissions.json index 1f8d18d7..ffc3be39 100644 --- a/modules-available/vmstore/lang/de/permissions.json +++ b/modules-available/vmstore/lang/de/permissions.json @@ -1,3 +1,4 @@ { + "benchmark": "Darf Benchmarks starten.", "edit": "Den verwendeten VM-Speicher konfigurieren." }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/de/template-tags.json b/modules-available/vmstore/lang/de/template-tags.json index 8b6661c2..4b848e79 100644 --- a/modules-available/vmstore/lang/de/template-tags.json +++ b/modules-available/vmstore/lang/de/template-tags.json @@ -1,8 +1,13 @@ { + "lang_benchmark": "Benchmark", + "lang_benchmarkMainPageText": "Um ein Benchmark mit einem oder mehreren Rechnern zu starten, w\u00e4hlen Sie die entsprechenden Ger\u00e4te in der Listenansicht der Client-Statistiken aus.", + "lang_benchmarkResult": "Ergebnis", + "lang_benchmarkSecondsReminaing": "Sekunden, die f\u00fcr den Test verbleiben", "lang_cifsHelp1": "Ben\u00f6tigt wird ein CIFS-Share, z.B. von einem Windows Server, der f\u00fcr\r\nden Satellitenserver schreibbar, und f\u00fcr die Arbeitsstationen lesbar\r\nist.", "lang_cifsHelp2": "Geben Sie f\u00fcr den Satellitenserver einen User mit Lese- und\r\nSchreibberechtigungen an. F\u00fcr die Clients sollte ein User angegeben\r\nwerden, der nur Leseberechtigungen auf dem Share besitzt. Am einfachsten\r\nerreichen Sie dies, indem Sie passwortlosen Gastzugriff mit Leserechten\r\nauf die Freigabe erlauben.", "lang_cifsHelp3": "Wenn exklusiv DNBD3 verwendet wird, k\u00f6nnen Sie den passwortlosen\r\nGastzugriff deaktivieren und die Zeile \"Nur-Lese-Zugangsdaten\" leer\r\nlassen. Dies erh\u00f6ht die Sicherheit.", "lang_configure": "Konfigurieren", + "lang_image": "Image", "lang_internal": "Intern", "lang_nfsHelp1": "Ben\u00f6tigt wird ein NFSv4\/3-Share, der f\u00fcr den Satellitenserver schreibbar, und f\u00fcr die Arbeitsstationen lesbar ist. Beispielkonfiguration auf dem NFS-Server, wenn der Satellitenserver die Adresse 1.2.3.4 hat:", "lang_nfsHelp2": "Alternative Konfiguration mittels all_squash. In diesem Fall muss das Verzeichnis auf dem Server dem Benutzer mit der uid 1234 geh\u00f6ren:", @@ -12,6 +17,11 @@ "lang_optionalMountOptions": "Zu verwendende Mount-Optionen (optional):", "lang_readOnly": "Nur-Lese-Zugangsdaten", "lang_readWrite": "Lese\/Schreib-Zugangsdaten", + "lang_selectImage": "Image f\u00fcr den Test ausw\u00e4hlen", + "lang_selectServerOrNfs": "Quelle f\u00fcr Lesetest ausw\u00e4hlen", + "lang_size": "Gr\u00f6\u00dfe", + "lang_start": "Start", + "lang_users": "Aktuelle Verbindungen", "lang_vmLocation": "VM Speicherort", "lang_vmLocationChoose": "Bitte w\u00e4hlen Sie, wo die Images der Virtuellen Maschinen gespeichert werden sollen.", "lang_vmLocationConfiguration": "VM Speicherort wird konfiguriert", diff --git a/modules-available/vmstore/lang/en/messages.json b/modules-available/vmstore/lang/en/messages.json index 9ac360eb..0b935c94 100644 --- a/modules-available/vmstore/lang/en/messages.json +++ b/modules-available/vmstore/lang/en/messages.json @@ -1,3 +1,9 @@ { + "benchmark-already-started": "Benchmark already started", + "benchmark-failed": "Benchmark failed", + "dnbd3-failed": "DNBD3 connection failed", + "invalid-benchmark-job": "Invalid benchmark ID: {{0}}", + "invalid-dnbd3-server-id": "Invalid DNBD3 server ID: {{0}}", + "select-image-first": "Please select an image first", "vmstore-not-configured": "A location for the virtual machine is not set yet." }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/en/module.json b/modules-available/vmstore/lang/en/module.json index a424640e..12e5167b 100644 --- a/modules-available/vmstore/lang/en/module.json +++ b/modules-available/vmstore/lang/en/module.json @@ -1,4 +1,8 @@ { + "dnbd3-all-loadbalance": "Use all DNBD3 servers (load balancing)", + "menu_benchmark": "Benchmark", + "menu_edit": "Edit", "module_name": "VM Storage Location", + "page-title-benchmark": "Network benchmark", "page_title": "Setting VM Storage Location" }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/en/permissions.json b/modules-available/vmstore/lang/en/permissions.json index 6d34014a..fb5d56a5 100644 --- a/modules-available/vmstore/lang/en/permissions.json +++ b/modules-available/vmstore/lang/en/permissions.json @@ -1,3 +1,4 @@ { + "benchmark": "May start benchmarks.", "edit": "Configure VM storage to use." }
\ No newline at end of file diff --git a/modules-available/vmstore/lang/en/template-tags.json b/modules-available/vmstore/lang/en/template-tags.json index b1d53db1..348c59fc 100644 --- a/modules-available/vmstore/lang/en/template-tags.json +++ b/modules-available/vmstore/lang/en/template-tags.json @@ -1,8 +1,13 @@ { + "lang_benchmark": "Benchmark", + "lang_benchmarkMainPageText": "To start a benchmark, select one or more clients in the list view of the Client Statistics.", + "lang_benchmarkResult": "Results", + "lang_benchmarkSecondsReminaing": "Seconds remaining", "lang_cifsHelp1": "Requires a CIFS\/SMB share that's writable for the satellite server and read-only for the clients (if not using DNBD3).", "lang_cifsHelp2": "Please provide user credentials with read\/write permissions which will be used by the server. For the clients, user credentials that allow read-only access is required. You could also enable passwordless guest login for read-only access.", "lang_cifsHelp3": "If you want to use DNBD3 in exclusive mode, you can leave the read only credentials empty, to prevent people from browsing the share.", "lang_configure": "Configure", + "lang_image": "Image", "lang_internal": "Internal", "lang_nfsHelp1": "An NFSv4\/3-Share is required. It should be readable by all the workstations, and writable for the satellite server. An example, assuming the satellite server has IP address 1.2.3.4:", "lang_nfsHelp2": "Alternate configuration using all_squash. The exported directory should be owned (and be writable) by the user with uid 1234.", @@ -12,6 +17,11 @@ "lang_optionalMountOptions": "Mount options to use (optional):", "lang_readOnly": "Read-only Access", "lang_readWrite": "Read\/Write Access", + "lang_selectImage": "Select image for testing", + "lang_selectServerOrNfs": "Select source for reading", + "lang_size": "Size", + "lang_start": "Start", + "lang_users": "Current connections", "lang_vmLocation": "VM Storage Location", "lang_vmLocationChoose": "Please choose where the images of virtual machines will be stored.", "lang_vmLocationConfiguration": "VM location is configured", diff --git a/modules-available/vmstore/page.inc.php b/modules-available/vmstore/page.inc.php index 1e0cc619..9d7f16c2 100644 --- a/modules-available/vmstore/page.inc.php +++ b/modules-available/vmstore/page.inc.php @@ -2,12 +2,28 @@ class Page_VmStore extends Page { - private $mountTask = false; + /** + * @var ?string + */ + private $mountTask = null; protected function doPreprocess() { User::load(); + if (User::hasPermission('edit')) { + Dashboard::addSubmenu('?do=vmstore', Dictionary::translate('menu_edit')); + } + if (User::hasPermission('benchmark')) { + Dashboard::addSubmenu('?do=vmstore&show=benchmark', Dictionary::translate('menu_benchmark')); + } + + if (Request::any('show') === 'benchmark') { + User::assertPermission('benchmark'); + $this->benchmarkDoPreprocess(); + return; + } + User::assertPermission('edit'); $action = Request::post('action'); @@ -19,10 +35,15 @@ class Page_VmStore extends Page protected function doRender() { + if (Request::any('show') === 'benchmark') { + $this->benchmarkDoRender(); + return; + } + $action = Request::post('action'); - if ($action === 'setstore' && !Taskmanager::isFailed($this->mountTask)) { + if ($action === 'setstore' && !Taskmanager::isFailed(Taskmanager::status($this->mountTask))) { Render::addTemplate('mount', array( - 'task' => $this->mountTask['id'] + 'task' => $this->mountTask )); return; } @@ -50,19 +71,256 @@ class Page_VmStore extends Page Util::redirect('?do=VmStore'); } // Validate syntax of nfs/cifs - if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#is', $vmstore['nfsaddr'])) { + if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#i', $vmstore['nfsaddr'])) { Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']); Util::redirect('?do=VmStore'); } $vmstore['cifsaddr'] = str_replace('\\', '/', $vmstore['cifsaddr']); - if ($storetype === 'cifs' && !preg_match('#^//\S+/.+$#is', $vmstore['cifsaddr'])) { + if ($storetype === 'cifs' && !preg_match('#^//\S+/.+$#i', $vmstore['cifsaddr'])) { Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']); Util::redirect('?do=VmStore'); } $this->mountTask = Trigger::mount($vmstore); - if ($this->mountTask !== false) { + if ($this->mountTask !== null) { TaskmanagerCallback::addCallback($this->mountTask, 'manualMount', $vmstore); } } + private function benchmarkDoPreprocess() + { + if (!Module::isAvailable('rebootcontrol')) { + ErrorHandler::traceError('rebootcontrol module not enabled'); + } + Render::setTitle(Dictionary::translate('page-title-benchmark')); + if (Request::post('action') === 'start') { + $this->benchmarkActionStart(); + } + } + + private function benchmarkDoRender() + { + switch (Request::get('action')) { + case 'select': + $this->benchmarkShowImageSelect(); + break; + case 'result': + $this->benchmarkShowResult(); + break; + default: + Render::addTemplate('benchmark-nothing'); + } + } + + private function getJobFromId(int $id): ?array + { + $data = Property::getListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id); + if ($data !== null) { + $data = json_decode($data, true); + } + if (!is_array($data) || !isset($data['machines'])) { + Message::addError('invalid-benchmark-job', $id); + return null; + } + return $data; + } + + private function benchmarkActionStart() + { + Module::isAvailable('dnbd3'); + $id = Request::post('id', Request::REQUIRED, 'int'); + $data = $this->getJobFromId($id); + if ($data === null) + return; + if (isset($data['task'])) { + if ($data['task'] === 'inprogress') { + // Let's hope the proper ID gets written in a short while + sleep(1); + } + Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id); + } + $selectedServer = Request::post('server', 'auto', 'string'); + if ($selectedServer === 'nfs' || !Dnbd3::isEnabled()) { + $selectedServer = 'nfs'; + } elseif ($selectedServer !== 'auto') { + $ip = Dnbd3::getServer($selectedServer); + if ($ip === false) { + Message::addError('invalid-dnbd3-server-id', $selectedServer); + return; + } + $selectedServer = $ip['clientip']; + } + $data['image'] = Request::post('image', Request::REQUIRED, 'string'); + // Save once first to minimize race window + $data['task'] = 'inprogress'; + Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30); + $start = 0; + $data['task'] = VmStoreBenchmark::start($id, $data['machines'], $data['image'], $selectedServer, $start); + if ($data['task'] === null) { + $data['task'] = 'failed'; + } else { + // Test is 2x 30 seconds + $data['expected'] = $start + 64; + } + error_log('Saving: ' . json_encode($data)); + Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30); + Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id); + } + + private function benchmarkShowImageSelect() + { + $id = Request::get('id', Request::REQUIRED, 'int'); + $data = $this->getJobFromId($id); + if ($data === null) + return; + if (isset($data['task'])) { + Message::addWarning('benchmark-already-started'); + Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id); + } + Module::isAvailable('dnbd3'); + $lookup = Dnbd3::getActiveServers(); + $list = Dnbd3Rpc::getStatsMulti(array_keys($lookup), [Dnbd3Rpc::QUERY_IMAGES]); + if (empty($list)) { + Message::addError('dnbd3-failed'); + Util::redirect('?do=vmstore'); + } + $images = []; + foreach ($list as $json) { + foreach ($json['images'] as $img) { + $name = $img['name'] . ':' . $img['rid']; + if (!isset($images[$name])) { + $images[$name] = [ + 'users' => 0, + 'size' => $img['size'], + 'size_s' => Util::readableFileSize($img['size'], 1), + 'name' => $name, + 'id' => count($images) + ]; + } + $images[$name]['users'] += $img['users']; + } + } + $servers = []; + if (Dnbd3::isEnabled()) { + $servers[] = ['idx' => 'auto', + 'server' => Dictionary::translate('dnbd3-all-loadbalance')]; + foreach ($lookup as $ip => $idx) { + $servers[] = ['idx' => $idx, 'server' => $ip]; + } + } + if (!Dnbd3::isEnabled() || Dnbd3::hasNfsFallback()) { + $servers[] = ['idx' => 'nfs', 'server' => 'NFS']; + } + $servers[0]['checked'] = 'checked'; + ArrayUtil::sortByColumn($images, 'users', SORT_DESC, SORT_NUMERIC); + Module::isAvailable('js_stupidtable'); + Render::addTemplate('benchmark-imgselect', [ + 'id' => $id, + 'list' => array_values($images), + 'servers' => $servers, + ]); + } + + private function benchmarkShowResult() + { + $id = Request::get('id', Request::REQUIRED, 'int'); + $data = $this->getJobFromId($id); + if ($data === null) + return; + if (!isset($data['task'])) { + Message::addWarning('select-image-first'); + Util::redirect('?do=vmstore&show=benchmark&action=select&id=' . $id); + } + if ($data['task'] === 'failed') { + Message::addError('benchmark-failed'); + return; + } + $remaining = 0; + if ($data['task'] !== 'done') { + $remaining = ($data['expected'] ?? 0) - time(); + if ($remaining < 0) { + $remaining = 0; + } + $this->processRunningBenchmark($id, $data, $remaining === 0); + $refresh = $remaining; + Util::clamp($refresh, 2, 64); + } + $args = [ + 'id' => $id, + 'result' => json_encode($data['result'] ?? []), + 'wanted' => json_encode($data['machines']), + ]; + if ($remaining > 0) { + $args['remaining'] = $remaining; + $args['refresh'] = $refresh ?? 60; + } + Module::isAvailable('js_chart'); + Render::addTemplate('benchmark-result', $args); + } + + private function processRunningBenchmark(int $id, array &$data, bool $timeout) + { + Module::isAvailable('rebootcontrol'); + $changed = false; + + $active = array_filter($data['machines'], function ($e) use ($data) { return !isset($data['result'][$e]); }); + if (empty($active)) { + $timeout = true; + } else { + if ($timeout) { + // cat everything for easier troubleshooting + $command = <<<EOF +cat "/tmp/speedcheck-$id" +EOF; + } else { + $command = <<<EOF +grep -q '^Seq:' "/tmp/speedcheck-$id" && cat "/tmp/speedcheck-$id" +EOF; + } + $task = RebootControl::runScript($active, $command); + $task = Taskmanager::waitComplete($task, 4000); + if ($task === false) { + $data['task'] = 'failed'; + return; + } + if (!isset($data['result'])) { + $data['result'] = []; + } + $res =& $task['data']; + foreach ($res['result'] as $uuid => $out) { + if (isset($data['result'][$uuid])) + continue; + error_log(json_encode($out)); + // Not finished, ignore + if (($out['state'] !== 'DONE' || $out['exitCode'] !== 0) && !$timeout) + continue; + $changed = true; + unset($client); + $client = ['machineuuid' => $uuid]; + $data['result'][$uuid] =& $client; + if (preg_match_all("/^\+(\w{3}):(\d+),(.*)$/m", $out['stdout'], $modes, PREG_SET_ORDER)) { + foreach ($modes as $mode) { + $client[$mode[1]] = [ + 'start' => $mode[2], + 'values' => VmStoreBenchmark::parseBenchLine($mode[3]), + ]; + } + } else { + $client['stderr'] = substr($out['stderr'], 0, 4000) + . "\nStatus: {$out['state']}, ExitCode: {$out['exitCode']}"; + $client['stdout'] = substr($out['stdout'], 0, 4000); + } + $m = Database::queryFirst('SELECT clientip, hostname FROM machine WHERE machineuuid = :uuid', + ['uuid' => $uuid]); + $client['name'] = empty($m['hostname']) ? $m['clientip'] : $m['hostname']; + } + } + if (count($data['result']) === count($data['machines']) || $timeout) { + $data['task'] = 'done'; + $changed = true; + } + if ($changed) { + Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30); + } + } + }
\ No newline at end of file diff --git a/modules-available/vmstore/permissions/permissions.json b/modules-available/vmstore/permissions/permissions.json index 8303fd02..0617c673 100644 --- a/modules-available/vmstore/permissions/permissions.json +++ b/modules-available/vmstore/permissions/permissions.json @@ -1,5 +1,8 @@ { "edit": { "location-aware": false + }, + "benchmark": { + "location-aware": true } }
\ No newline at end of file diff --git a/modules-available/vmstore/templates/benchmark-imgselect.html b/modules-available/vmstore/templates/benchmark-imgselect.html new file mode 100644 index 00000000..be81aa3e --- /dev/null +++ b/modules-available/vmstore/templates/benchmark-imgselect.html @@ -0,0 +1,59 @@ +<div class="page-header"> + <h1>{{lang_benchmark}}</h1> +</div> + +<form role="form" method="post" action="?do=vmstore"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="show" value="benchmark"> + <input type="hidden" name="id" value="{{id}}"> + + <h4>{{lang_selectServerOrNfs}}</h4> + {{#servers}} + <div class="radio"> + <input type="radio" id="s-{{idx}}" name="server" value="{{idx}}" {{checked}}> + <label for="s-{{idx}}">{{server}}</label> + </div> + {{/servers}} + + <div class="slx-space"></div> + + <h4>{{lang_selectImage}}</h4> + <div> + <table class="table table-condensed stupidtable"> + <thead> + <tr> + <th data-sort="string">{{lang_image}}</th> + <th class="slx-smallcol" data-sort="int" data-sort-default="desc">{{lang_users}}</th> + <th class="slx-smallcol" data-sort="int" data-sort-default="desc">{{lang_size}}</th> + </tr> + </thead> + <tbody> + {{#list}} + <tr> + <td> + <div class="radio radio-inline"> + <input type="radio" id="r-{{id}}" name="image" value="{{name}}"> + <label for="r-{{id}}">{{name}}</label> + </div> + </td> + <td class="text-right">{{users}}</td> + <td class="text-right" data-sort-value="{{size}}">{{size_s}}</td> + </tr> + {{/list}} + </tbody> + </table> + </div> + + <div class="slx-space"></div> + + <div style="position:fixed;bottom:0;right:0;padding:8px;background:#fff;width:100%;border-top:1px solid #ddd"> + <div class="buttonbar text-right"> + <button type="submit" name="action" value="start" class="btn btn-primary"> + <span class="glyphicon glyphicon-play"></span> + {{lang_start}} + </button> + </div> + </div> + + +</form>
\ No newline at end of file diff --git a/modules-available/vmstore/templates/benchmark-nothing.html b/modules-available/vmstore/templates/benchmark-nothing.html new file mode 100644 index 00000000..aeef9187 --- /dev/null +++ b/modules-available/vmstore/templates/benchmark-nothing.html @@ -0,0 +1,7 @@ +<div class="page-header"> + <h1>{{lang_benchmark}}</h1> +</div> + +<div class="alert alert-info"> + {{lang_benchmarkMainPageText}} +</div>
\ No newline at end of file diff --git a/modules-available/vmstore/templates/benchmark-result.html b/modules-available/vmstore/templates/benchmark-result.html new file mode 100644 index 00000000..28f31f12 --- /dev/null +++ b/modules-available/vmstore/templates/benchmark-result.html @@ -0,0 +1,141 @@ +<h1>{{lang_benchmark}}</h1> + +<h2>{{lang_benchmarkResult}}</h2> + +{{#remaining}} +<div class="alert alert-info"> + {{lang_benchmarkSecondsReminaing}}: <span id="remaining-seconds">{{remaining}}</span> +</div> +{{/remaining}} + +<div id="graphs"></div> + +<div id="errors"></div> + +<script> + document.addEventListener('DOMContentLoaded', function() { + var result = {{{result}}}; + var clients = {{{wanted}}}; + var graphs = {}; + function formatBytes(val) { + return Math.floor(val / 1024 / 1024) + "\u2009MiB/s"; + } + function renderX(val, index) { + return Math.floor(val / 1000) + '\u2009s'; + } + function makeGraph(typeKey, resourceKey, caption) { + var uuid; + var ds = []; + var gmin = 0, rmax = 0; + var colors = []; + var cnt = 0; + for (uuid in result) { + if (!result[uuid][typeKey]) { + delete result[uuid]; + continue; + } + if (gmin === 0 || result[uuid][typeKey].start < gmin) { + gmin = result[uuid][typeKey].start; + } + cnt++; + } + if (cnt === 1) { + colors.push('rgb(0, 128, 0)'); + } else { + for (i = 0; i < cnt; ++i) { + colors.push('rgb(0, 128, ' + (i / (cnt - 1)) * 255 + ')'); + } + } + var v, i, o, idx; + var sums = []; + for (uuid in result) { + o = result[uuid][typeKey].start - gmin; // Adjust according to earliest client + v = result[uuid][typeKey].values[resourceKey]; + for (i = 0; i < v.length; ++i) { + v[i].x += o; + if (cnt > 1) { + idx = Math.round(v[i].x / 250); + if (sums[idx]) { + sums[idx] += v[i].y | 0; + } else { + sums[idx] = v[i].y | 0; + } + } + } + if (v[v.length-1].x > rmax) rmax = v[v.length-1].x; // Get max value + ds.push({data: v, label: result[uuid].name, borderColor: colors[ds.length], fill: false}); + } + if (cnt > 1) { + ds.push({data: sums, label: 'Sum', borderColor: '#c00'}); + } + if (!graphs[typeKey]) { + var $e = $('#graphs'); + var $c = $('<canvas style="width:100%;height:250px">'); + $e.append($('<h3>').text(caption)); + $e.append($c); + var ls = []; + for (i = 0; i <= rmax; i += 250) ls.push(i); // Generate steps for graph + graphs[typeKey] = new Chart($c[0].getContext('2d'), {data: {datasets: ds, labels: ls}, type: 'scatter', options: { + animation: false, + responsive: true, + borderWidth: 2, + pointBorderWidth: 0, + showLine: true, + scales: { y: { ticks: { callback: formatBytes }}, x: { ticks: { callback: renderX }, max: rmax } }, + plugins: { + tooltip: { callbacks: { label: function(context) { + if (context.parsed.y !== null) { + return context.dataset.label + ": " + formatBytes(context.parsed.y); + } + return context.dataset.label; + } + }}, + legend: { position: 'left'} + } + }}); + } else { + graphs[typeKey].data.datasets = ds; + graphs[typeKey].update(); + } + } + + var $err = $('#errors'); + for (var uuid in result) { + if (result[uuid].stdout || result[uuid].stderr) { + var $frame = $('<div class="panel panel-body">'); + $frame.append($('<h5>').text(result[uuid].name)); + if (result[uuid].stdout) { + $frame.append($('<label>').text('stdout')); + $frame.append($('<pre>').text(result[uuid].stdout)); + } + if (result[uuid].stderr) { + $frame.append($('<label>').text('stderr')); + $frame.append($('<pre>').text(result[uuid].stderr)); + } + $err.append($frame); + } + } + + makeGraph('SEQ', 'net', 'Sequential Reads'); + makeGraph('RND', 'net', 'Random 1M'); + + {{#refresh}} + setTimeout(function() { + window.location.reload(); + }, {{refresh}} * 1000); + {{#remaining}} + var remaining = {{remaining}}; + function updateRemainingCounter() { + if (remaining > 0) { + setTimeout(updateRemainingCounter, 1000); + } else { + window.location.reload(); + } + $('#remaining-seconds').text(remaining--); + } + updateRemainingCounter(); + {{/remaining}} + {{/refresh}} + + }); +</script>
\ No newline at end of file diff --git a/modules-available/vmstore/templates/page-vmstore.html b/modules-available/vmstore/templates/page-vmstore.html index 0e1ad601..fa222631 100644 --- a/modules-available/vmstore/templates/page-vmstore.html +++ b/modules-available/vmstore/templates/page-vmstore.html @@ -1,10 +1,11 @@ +<h1>{{lang_vmLocation}}</h1> + <form role="form" method="post" action="?do=VmStore"> <input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1"> <input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1"> <input type="hidden" name="token" value="{{token}}"> <input type="hidden" name="action" value="setstore"> - <h1>{{lang_vmLocation}}</h1> <p>{{lang_vmLocationChoose}} <a class="btn btn-default" data-toggle="modal" data-target="#help-store"><span class="glyphicon glyphicon-question-sign"></span></a></p> |