summaryrefslogtreecommitdiffstats
path: root/modules-available/statistics
diff options
context:
space:
mode:
authorSimon Rettberg2021-09-21 16:52:06 +0200
committerSimon Rettberg2022-03-09 15:06:54 +0100
commit67e6b6c9981207d7d658f2ad2bf1c39b75c099c7 (patch)
treed2f54c424dd983a1500acf0d3d0bc3bbfee6e9cd /modules-available/statistics
parent[statistics] Support new json-format of hardware info from client (diff)
downloadslx-admin-67e6b6c9981207d7d658f2ad2bf1c39b75c099c7.tar.gz
slx-admin-67e6b6c9981207d7d658f2ad2bf1c39b75c099c7.tar.xz
slx-admin-67e6b6c9981207d7d658f2ad2bf1c39b75c099c7.zip
[passthrough] New module for managing hardware passthrough for QEMU
Diffstat (limited to 'modules-available/statistics')
-rw-r--r--modules-available/statistics/api.inc.php11
-rw-r--r--modules-available/statistics/inc/hardwareinfo.inc.php69
-rw-r--r--modules-available/statistics/inc/hardwareparser.inc.php83
-rw-r--r--modules-available/statistics/inc/hardwarequery.inc.php73
-rw-r--r--modules-available/statistics/inc/pciid.inc.php76
-rw-r--r--modules-available/statistics/install.inc.php11
-rw-r--r--modules-available/statistics/page.inc.php78
-rw-r--r--modules-available/statistics/pages/hints.inc.php77
-rw-r--r--modules-available/statistics/templates/hints-ram-underclocked.html44
-rw-r--r--modules-available/statistics/templates/hints-ram-upgrade.html31
10 files changed, 455 insertions, 98 deletions
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php
index 0a194f2e..52dbe284 100644
--- a/modules-available/statistics/api.inc.php
+++ b/modules-available/statistics/api.inc.php
@@ -1,15 +1,19 @@
<?php
if (Request::any('action') === 'test' && isLocalExecution()) {
+ /*
+ error_log(HardwareInfo::getKclModifications());
+ exit;
$x = new HardwareQuery(HardwareInfo::PCI_DEVICE);
//$x->addCompare(false, 'Memory Slot Occupied', '>=', true, 'Memory Slot Count');
$x->addWhere(true, 'vendor', '=', '8086');
- $x->addColumn(true, 'device');
+ $x->addGlobalColumn('device');
$res = $x->query();
foreach ($res as $row) {
error_log(json_encode($row));
}
exit;
+ */
HardwareParser::parseMachine('0A5D9E23-80F4-9C43-912C-96D80AE7E80B',
file_get_contents('/tmp/bla.json'));
echo 'Kweries: ' . Database::getQueryCount();
@@ -79,7 +83,7 @@ if ($type[0] === '~') {
$hostname = '';
}
$data = Util::cleanUtf8(Request::post('json', '', 'string'));
- $hasJson = !empty($data);
+ $hasJson = !empty($data) && $data[0] === '{';
if (!$hasJson) {
$data = Util::cleanUtf8(Request::post('data', '', 'string'));
}
@@ -340,7 +344,6 @@ if ($type[0] === '~') {
'hwid' => $hwid,
'machineuuid' => $uuid,
'devpath' => $port,
- 'serial' => '',
), array('disconnecttime' => 0));
$validProps = array();
if (count($screen) > 1) {
@@ -383,7 +386,7 @@ if ($type[0] === '~') {
// Some screens connected, make sure old entries get removed
Database::exec("UPDATE machine_x_hw x, statistic_hw h
SET x.disconnecttime = UNIX_TIMESTAMP()
- WHERE (x.hwid, x.devpath) NOT IN (:pairs) AND x.disconnecttime = 0 AND h.hwtype = :type
+ WHERE (x.hwid, x.devpath) NOT IN (:pairs) AND x.hwid = h.hwid AND x.disconnecttime = 0 AND h.hwtype = :type
AND x.machineuuid = :uuid", array(
'pairs' => $keepPair,
'uuid' => $uuid,
diff --git a/modules-available/statistics/inc/hardwareinfo.inc.php b/modules-available/statistics/inc/hardwareinfo.inc.php
index 5ef94365..6a6c74cd 100644
--- a/modules-available/statistics/inc/hardwareinfo.inc.php
+++ b/modules-available/statistics/inc/hardwareinfo.inc.php
@@ -13,4 +13,73 @@ class HardwareInfo
const HDD = 'HDD';
const CPU = 'CPU';
+ /**
+ * Get a KCL modification string for the given machine, enabling GVT, PCI passthrough etc.
+ * You can provide a UUID and/or MAC, or nothing. If nothing is provided,
+ * the "uuid" and "mac" GET parameters will be used. If both are provided,
+ * the resulting machine that has the greatest "lastseen" value will be used.
+ * @param ?string $uuid UUID of machine
+ * @param ?string $mac MAC of machine
+ * @return string
+ */
+ public static function getKclModifications($uuid = null, $mac = null): string
+ {
+ if ($uuid === null && $mac === null) {
+ $uuid = Request::get('uuid', '', 'string');
+ $mac = Request::get('mac', '', 'string');
+ $mac = str_replace(':', '-', $mac);
+ }
+ $res = Database::simpleQuery("SELECT machineuuid, lastseen FROM machine
+ WHERE machineuuid = :uuid OR macaddr = :mac", ['uuid' => $uuid, 'mac' => $mac]);
+ $best = null;
+ foreach ($res as $row) {
+ if ($best === null || $best['lastseen'] < $row['lastseen']) {
+ $best = $row;
+ }
+ }
+ if ($best === null)
+ return '';
+ $hw = new HardwareQuery(self::PCI_DEVICE, $best['machineuuid'], true);
+ // TODO: Get list of enabled pass through groups for this client's location
+ $hw->addWhere(true, '@PASSTHROUGH', 'IN', ['GPU', 'GVT']);
+ $hw->addGlobalColumn('vendor');
+ $hw->addGlobalColumn('device');
+ $hw->addLocalColumn('slot');
+ $res = $hw->query();
+ $passthrough = [];
+ $slots = [];
+ $gvt = false;
+ foreach ($res as $row) {
+ if ($row['@PASSTHROUGH'] === 'GVT') {
+ $gvt = true;
+ } else {
+ $passthrough[$row['vendor'] . ':' . $row['device']] = 1;
+ error_log('Passthouorgh: ' . $row['vendor'] . ':' . $row['device']);
+ $slots[preg_replace('/\.[0-9]+$/', '', $row['slot'])] = 1;
+ }
+ }
+ $kcl = '';
+ if ($gvt || !empty($passthrough)) {
+ $kcl = '-iommu -intel_iommu iommu=pt intel_iommu=on'; // TODO AMD
+ }
+ if (!empty($passthrough)) {
+ foreach (array_keys($slots) as $slot) {
+ error_log('Querying slot ' . $slot);
+ $hw = new HardwareQuery(self::PCI_DEVICE, $best['machineuuid'], true);
+ $hw->addWhere(false, 'slot', 'LIKE', $slot . '.%');
+ $hw->addGlobalColumn('vendor');
+ $hw->addGlobalColumn('device');
+ foreach ($hw->query() as $row) {
+ $passthrough[$row['vendor'] . ':' . $row['device']] = 1;
+ error_log('Extra PT: ' . $row['vendor'] . ':' . $row['device']);
+ }
+ }
+ $kcl .= ' vfio-pci.ids=' . implode(',', array_keys($passthrough));
+ }
+ if ($gvt) {
+ $kcl .= ' i915.enable_gvt=1';
+ }
+ return $kcl;
+ }
+
}
diff --git a/modules-available/statistics/inc/hardwareparser.inc.php b/modules-available/statistics/inc/hardwareparser.inc.php
index d356e226..15534749 100644
--- a/modules-available/statistics/inc/hardwareparser.inc.php
+++ b/modules-available/statistics/inc/hardwareparser.inc.php
@@ -308,29 +308,29 @@ class HardwareParser
foreach ($out as $entry) {
if (!isset($pci[$entry['class']])) {
$class = 'c.' . $entry['class'];
- $res = Page_Statistics::getPciId('CLASS', $class);
- if ($res === false || $res['dateline'] < $NOW) {
+ $res = PciId::getPciId('CLASS', $class);
+ if ($res === false) {
$pci[$entry['class']]['lookupClass'] = 'do-lookup';
$pci[$entry['class']]['class'] = $class;
} else {
- $pci[$entry['class']]['class'] = $res['value'];
+ $pci[$entry['class']]['class'] = $res;
}
}
$new = array(
'ven' => $entry['ven'],
'dev' => $entry['ven'] . ':' . $entry['dev'],
);
- $res = Page_Statistics::getPciId('VENDOR', $new['ven']);
- if ($res === false || $res['dateline'] < $NOW) {
+ $res = PciId::getPciId('VENDOR', $new['ven']);
+ if ($res === false) {
$new['lookupVen'] = 'do-lookup';
} else {
- $new['ven'] = $res['value'];
+ $new['ven'] = $res;
}
- $res = Page_Statistics::getPciId('DEVICE', $new['dev']);
- if ($res === false || $res['dateline'] < $NOW) {
+ $res = PciId::getPciId('DEVICE', $new['dev']);
+ if ($res === false) {
$new['lookupDev'] = 'do-lookup';
} else {
- $new['dev'] = $res['value'] . ' (' . $new['dev'] . ')';
+ $new['dev'] = $res . ' (' . $new['dev'] . ')';
}
$pci[$entry['class']]['entries'][] = $new;
}
@@ -444,8 +444,8 @@ class HardwareParser
return (int)($out[1] * self::SI_LOOKUP[strtoupper($out[2])]);
}
// Count, size (unitless)
- if (is_numeric($val) && preg_match('/^[0-9]+$/', $val)
- && preg_match('/used|occupied|count|number|size/', $key)) {
+ if (is_numeric($val) && preg_match('/^-?[0-9]+$/', $val)
+ && preg_match('/used|occupied|count|number|size|temperature/', $key)) {
return (int)$val;
}
// Date
@@ -538,7 +538,7 @@ class HardwareParser
{
$ret = [];
foreach ($data as $key => $vals) {
- $val = trim($vals['values'][0]);
+ $val = trim($vals['values'][0] ?? 'NULL');
if ($val === '[Empty]' || $val === 'NULL')
continue;
$val = preg_replace('/[^a-z0-9]/', '', strtolower($val));
@@ -567,7 +567,7 @@ class HardwareParser
*/
private static function markDisconnected(string $uuid, string $dbType, array $excludedHwIds)
{
- error_log("Marking disconnected for $dbType from " . implode(', ', $excludedHwIds));
+ error_log("Marking disconnected for $dbType except " . implode(', ', $excludedHwIds));
if (empty($excludedHwIds)) {
Database::exec("UPDATE machine_x_hw mxh, statistic_hw h
SET mxh.disconnecttime = UNIX_TIMESTAMP()
@@ -630,10 +630,12 @@ class HardwareParser
$globalMainboardExtra['Memory Slot Count'] += $mem['Number Of Devices'];
}
if (isset($mem['Maximum Capacity'])) {
- $globalMainboardExtra['Memory Maximum Capacity'] += self::convertSize($mem['Maximum Capacity'], 'M', false);
+ $globalMainboardExtra['Memory Maximum Capacity']
+ += self::convertSize($mem['Maximum Capacity'], 'M', false);
}
}
- $globalMainboardExtra['Memory Maximum Capacity'] = self::convertSize($globalMainboardExtra['Memory Maximum Capacity'] . ' MB');
+ $globalMainboardExtra['Memory Maximum Capacity']
+ = self::convertSize($globalMainboardExtra['Memory Maximum Capacity'] . ' MB', 'G');
// BIOS section - need to cross-match this with mainboard or system model, as it doesn't have a meaningful
// identifier on its own
$bios = self::prepareDmiProperties(self::getDmiHandles($data, 0)[0]);
@@ -651,8 +653,14 @@ class HardwareParser
}
}
// Using the general helper function
- $ramModCount = self::updateHwTypeFromDmi($uuid, $data, 17, HardwareInfo::RAM_MODULE, function (array $flat): bool {
- return self::convertSize(($flat['Size'] ?? 0), '', false) > 65 * 1024 * 1024;
+ $capa = 0;
+ $ramModCount = self::updateHwTypeFromDmi($uuid, $data, 17, HardwareInfo::RAM_MODULE, function (array $flat) use (&$capa): bool {
+ $size = self::convertSize(($flat['Size'] ?? 0), '', false);
+ if ($size > 65 * 1024 * 1024) {
+ $capa += $size;
+ return true;
+ }
+ return false;
},
['Locator'],
['Data Width',
@@ -669,6 +677,7 @@ class HardwareParser
);
// Fake RAM slots used/total into this
$localMainboardExtra['Memory Slot Occupied'] = $ramModCount;
+ $localMainboardExtra['Memory Installed Capacity'] = self::convertSize($capa, 'G', true);
self::updateHwTypeFromDmi($uuid, $data, 2, HardwareInfo::MAINBOARD, ['Manufacturer', 'Product Name'],
[],
['Manufacturer', 'Product Name', 'Type', 'Version'],
@@ -714,15 +723,18 @@ class HardwareParser
// ---- lspci ------------------------------------
$pciHwIds = [];
foreach (($data['lspci'] ?? []) as $dev) {
- $hwid = self::writeGlobalHardwareData(HardwareInfo::PCI_DEVICE,
- self::propsFromArray($dev, 'vendor', 'device', 'rev', 'class'));
+ $props = self::propsFromArray($dev, 'vendor', 'device', 'rev', 'class');
+ if (!isset($props['vendor']) || !isset($props['device']))
+ continue;
+ $hwid = self::writeGlobalHardwareData(HardwareInfo::PCI_DEVICE, $props);
$mappingId = self::writeLocalHardwareData($uuid, $hwid, $dev['slot'] ?? 'unknown',
- self::propsFromArray($dev, 'slot', 'subsystem', 'subsystem_vendor'));
+ self::propsFromArray($dev, 'slot', 'subsystem', 'subsystem_vendor', 'iommu_group'));
$pciHwIds[] = $mappingId;
}
self::markDisconnected($uuid, HardwareInfo::PCI_DEVICE, $pciHwIds);
// ---- Disks ------------------------------------0Y3R3K
$hddHwIds = [];
+ $id44 = $id45 = 0;
foreach (($data['drives'] ?? []) as $dev) {
if (empty($dev['readlink']))
continue;
@@ -739,11 +751,12 @@ class HardwareParser
if (!isset($smart['rotation_rate']) && isset($lsblk['rota']) && !$lsblk['rota']) {
$smart['rotation_rate'] = 0;
}
+ $size = $lsblk['size'] ?? $smart['user_capacity']['bytes'] ?? 'unknown';
$hwid = self::writeGlobalHardwareData(HardwareInfo::HDD, [
// Try to use the model name as the unique identifier
'model' => $smart['model_name'] ?? $lsblk['model'] ?? 'unknown',
// Append device size as some kind of fallback, in case model is unknown
- 'size' => $lsblk['size'] ?? $smart['user_capacity']['bytes'] ?? 'unknown',
+ 'size' => $size,
'physical_block_size' => $smart['physical_block_size'] ?? $lsblk['phy-sec'] ?? 0,
'logical_block_size' => $smart['logical_block_size'] ?? $lsblk['log-sec'] ?? 0,
] + self::propsFromArray($smart,
@@ -781,6 +794,7 @@ class HardwareParser
}, ARRAY_FILTER_USE_BOTH);
}
// Partitions - find special ones
+ $used = 0;
if (isset($dev['sfdisk']['partitiontable'])) {
$table['partition_table'] = $dev['sfdisk']['partitiontable']['label'] ?? 'none';
switch ($dev['sfdisk']['partitiontable']['unit'] ?? 'sectors') {
@@ -795,6 +809,13 @@ class HardwareParser
}
$i = 0;
foreach (($dev['sfdisk']['partitiontable']['partitions'] ?? []) as $part) {
+ if (!isset($part['size']))
+ continue;
+ $type = hexdec($part['type'] ?? '0');
+ if ($type === 0x0 || $type === 0x5 || $type === 0xf || $type === 0x15 || $type === 0x1f
+ || $type === 0x85 || $type === 0xc5 || $type == 0xcf)
+ continue; // Extended partition, ignore
+ $used += $part['size'] * $fac;
$id = 'part_' . $i . '_';
foreach (['start', 'size', 'type', 'uuid', 'name'] as $item) {
if (!isset($part[$item]))
@@ -811,19 +832,23 @@ class HardwareParser
if ($type == '44' || strtolower($type) === '87f86132-ff94-4987-b250-444444444444'
|| $name === 'OpenSLX-ID44') {
$table[$id . 'slxtype'] = '44';
+ $id44 += $part['size'] * $fac;
} elseif ($type == '45' || strtolower($type) === '87f86132-ff94-4987-b250-454545454545'
|| $name === 'OpenSLX-ID45') {
$table[$id . 'slxtype'] = '45';
+ $id45 += $part['size'] * $fac;
}
//
++$i;
}
}
+ $table['unused'] = $size - $used;
+ $table += self::propsFromArray($smart + ($lsblk ?? []),
+ 'serial_number', 'firmware_version',
+ 'interface_speed//current//string',
+ 'smart_status//passed', 'temperature//current', 'temperature//min', 'temperature//max');
$mappingId = self::writeLocalHardwareData($uuid, $hwid, $dev['readlink'],
- self::propsFromArray($smart + ($lsblk ?? []),
- 'serial_number', 'firmware_version',
- 'interface_speed//current//string',
- 'smart_status//passed', 'temperature//current', 'temperature//min', 'temperature//max') + $table);
+ $table);
// Delete old partition and smart attribute entries
Database::exec("DELETE FROM machine_x_hw_prop WHERE machinehwid = :id AND prop NOT IN (:keep)
AND prop NOT LIKE '@%'", [
@@ -836,8 +861,12 @@ class HardwareParser
self::markDisconnected($uuid, HardwareInfo::HDD, $hddHwIds);
//
// Mark parse date
- Database::exec("UPDATE machine SET dataparsetime = UNIX_TIMESTAMP() WHERE machineuuid = :uuid",
- ['uuid' => $uuid]);
+ Database::exec("UPDATE machine SET dataparsetime = UNIX_TIMESTAMP(), id44mb = :id44, id45mb = :id45
+ WHERE machineuuid = :uuid", [
+ 'uuid' => $uuid,
+ 'id44' => round($id44 / (1024 * 1024)),
+ 'id45' => round($id45 / (1024 * 1024)),
+ ]);
}
/**
diff --git a/modules-available/statistics/inc/hardwarequery.inc.php b/modules-available/statistics/inc/hardwarequery.inc.php
index 7ccde2f6..6b5662d4 100644
--- a/modules-available/statistics/inc/hardwarequery.inc.php
+++ b/modules-available/statistics/inc/hardwarequery.inc.php
@@ -13,16 +13,18 @@ class HardwareQuery
* @param string $type type form HardwareInfo
* @param ?string $uuid
*/
- public function __construct($type, $uuid = null, $connectedOnly = true)
+ public function __construct(string $type, $uuid = null, $connectedOnly = true)
{
if ($connectedOnly) {
- $this->where[] = 'mxhw.disconnecttime = 0';
+ $this->joins['mxhw_join'] = "INNER JOIN machine_x_hw mxhw ON (mxhw.hwid = shw.hwid AND mxhw.disconnecttime = 0)";
+ } else {
+ $this->joins['mxhw_join'] = "INNER JOIN machine_x_hw mxhw ON (mxhw.hwid = shw.hwid)";
}
if ($uuid !== null) {
$this->where[] = 'mxhw.machineuuid = :uuid';
$this->args['uuid'] = $uuid;
}
- $this->joins[] = "INNER JOIN statistic_hw shw ON (mxhw.hwid = shw.hwid AND shw.hwtype = :hwtype)";
+ $this->where[] = 'shw.hwtype = :hwtype';
$this->args['hwtype'] = $type;
}
@@ -44,7 +46,13 @@ class HardwareQuery
}
}
- public function addWhere(bool $global, string $prop, string $op, string $value)
+ /**
+ * @param bool $global
+ * @param string $prop
+ * @param string $op
+ * @param string|string[] $value
+ */
+ public function addWhere(bool $global, string $prop, string $op, $value)
{
if (isset($this->columns[$prop]))
return;
@@ -54,12 +62,24 @@ class HardwareQuery
$vid = $this->id();
$valueCol = ($op === '<' || $op === '>' || $op === '<=' || $op === '>=') ? 'numeric' : 'value';
$this->joins[$prop] = "INNER JOIN $table $tid ON ($srcTable.$column = $tid.$column AND
- $tid.prop = :$pid AND $tid.`$valueCol` $op :$vid)";
+ $tid.prop = :$pid AND $tid.`$valueCol` $op (:$vid))";
$this->args[$pid] = $prop;
$this->args[$vid] = $value;
$this->columns[$prop] = "$tid.`value` AS `$prop`";
}
+ public function addMachineWhere(string $column, string $op, $value)
+ {
+ if (isset($this->columns[$column]))
+ return;
+ $valueCol = ($op === '<' || $op === '>' || $op === '<=' || $op === '>=') ? 'numeric' : 'value';
+ $vid = $this->id();
+ $this->joins['machine'] = 'INNER JOIN machine m USING (machineuuid)';
+ $this->where[] = "m.$column $op :$vid";
+ $this->args[$vid] = $value;
+ $this->columns[$column] = "m.$column";
+ }
+
public function addCompare(bool $global1, string $prop1, string $op, string $global2, string $prop2)
{
$this->fillTableVars($global1, $srcTable1, $table1, $column1);
@@ -76,6 +96,17 @@ class HardwareQuery
$this->args[$pid1] = $prop1;
$this->args[$pid2] = $prop2;
$this->columns[$prop1] = "$tid1.`value` AS `$prop1`";
+ $this->columns[$prop2] = "$tid2.`value` AS `$prop2`";
+ }
+
+ public function addGlobalColumn(string $prop)
+ {
+ $this->addColumn(true, $prop);
+ }
+
+ public function addLocalColumn(string $prop)
+ {
+ $this->addColumn(false, $prop);
}
public function addColumn(bool $global, string $prop)
@@ -85,21 +116,41 @@ class HardwareQuery
$this->fillTableVars($global, $srcTable, $table, $column);
$tid = $this->id();
$pid = $this->id();
- $this->joins[$prop] = "INNER JOIN $table $tid ON ($srcTable.$column = $tid.$column AND $tid.prop = :$pid)";
+ $this->joins[$prop] = "LEFT JOIN $table $tid ON ($srcTable.$column = $tid.$column AND $tid.prop = :$pid)";
$this->args[$pid] = $prop;
$this->columns[$prop] = "$tid.`value` AS `$prop`";
}
+ public function addMachineColumn(string $column)
+ {
+ if (isset($this->columns[$column]))
+ return;
+ $this->joins['machine'] = 'INNER JOIN machine m USING (machineuuid)';
+ $this->columns[$column] = "m.$column";
+ }
+
/**
* @return false|PDOStatement
*/
- public function query()
+ public function query(string $groupBy = '')
{
- $this->columns[] = 'mxhw.machineuuid';
- $query = 'SELECT ' . implode(', ', $this->columns)
- . ' FROM machine_x_hw mxhw '
+ $columns = $this->columns;
+ $columns[] = 'mxhw.machineuuid';
+ $columns[] = 'shw.hwid';
+ if (empty($groupBy) || $groupBy === 'mxhw.machinehwid') {
+ $columns[] = 'mxhw.disconnecttime';
+ } else {
+ $columns[] = 'Sum(If(mxhw.disconnecttime = 0, 1, 0)) AS connected_count';
+ }
+ if (!empty($groupBy)) {
+ $columns[] = 'Count(*) AS group_count';
+ $groupBy = " GROUP BY $groupBy";
+ }
+ $query = 'SELECT ' . implode(', ', $columns)
+ . ' FROM statistic_hw shw '
. implode(' ', $this->joins)
- . ' WHERE ' . implode(' AND ', $this->where);
+ . ' WHERE ' . implode(' AND ', $this->where)
+ . $groupBy;
return Database::simpleQuery($query, $this->args);
}
diff --git a/modules-available/statistics/inc/pciid.inc.php b/modules-available/statistics/inc/pciid.inc.php
new file mode 100644
index 00000000..65edf570
--- /dev/null
+++ b/modules-available/statistics/inc/pciid.inc.php
@@ -0,0 +1,76 @@
+<?php
+
+class PciId
+{
+
+ const DEVICE = 'DEVICE';
+ const VENDOR = 'VENDOR';
+ const DEVCLASS = 'CLASS';
+ const AUTO = 'AUTO';
+
+
+ /**
+ * @param string $cat type of query - self::DEVICE, self::VENDOR, self::DEVCLASS or self::AUTO for auto detection
+ * @param string $id the id to query - depends on $cat
+ * @return string|false Name of Class/Vendor/Device, false if not found
+ */
+ public static function getPciId(string $cat, string $id, bool $dnsQuery = false)
+ {
+ static $cache = [];
+ if ($cat === self::DEVCLASS && $id[1] !== '.') {
+ $id = 'c.' . $id;
+ }
+ if ($cat === self::AUTO) {
+ if (preg_match('/^([a-f0-9]{4}):([a-f0-9]{4})$/', $id, $out)) {
+ $cat = 'DEVICE';
+ } elseif (preg_match('/^([a-f0-9]{4})$/', $id, $out)) {
+ $cat = 'VENDOR';
+ } elseif (preg_match('/^c\.([a-f0-9]{2})([a-f0-9]{2})$/', $id, $out)) {
+ $cat = 'CLASS';
+ } else {
+ error_log('Invalid PCIID lookup format: ' . $id);
+ return false;
+ }
+ }
+ $key = $cat . '-' . $id;
+ if (isset($cache[$key]))
+ return $cache[$key];
+ $row = Database::queryFirst('SELECT value, dateline FROM pciid WHERE category = :cat AND id = :id LIMIT 1',
+ array('cat' => $cat, 'id' => $id));
+ if ($row !== false && $row['dateline'] >= time()) {
+ return $cache[$key] = $row['value'];
+ }
+ if (!$dnsQuery)
+ return false;
+ // Unknown, query
+ if ($cat === self::DEVICE && preg_match('/^([a-f0-9]{4}):([a-f0-9]{4})$/', $id, $out)) {
+ $host = $out[2] . '.' . $out[1];
+ } elseif ($cat === self::VENDOR && preg_match('/^([a-f0-9]{4})$/', $id, $out)) {
+ $host = $out[1];
+ } elseif ($cat === self::DEVCLASS && preg_match('/^c\.([a-f0-9]{2})([a-f0-9]{2})$/', $id, $out)) {
+ $host = $out[2] . '.' . $out[1] . '.c';
+ } else {
+ error_log("getPciId called with unknown format: ($cat) ($id)");
+ return false;
+ }
+ $res = dns_get_record($host . '.pci.id.ucw.cz', DNS_TXT);
+ if (!is_array($res))
+ return false;
+ foreach ($res as $entry) {
+ if (isset($entry['txt']) && substr($entry['txt'], 0, 2) === 'i=') {
+ $string = substr($entry['txt'], 2);
+ Database::exec('INSERT INTO pciid (category, id, value, dateline) VALUES (:cat, :id, :value, :timeout)'
+ . ' ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)',
+ array(
+ 'cat' => $cat,
+ 'id' => $id,
+ 'value' => $string,
+ 'timeout' => time() + mt_rand(10, 30) * 86400,
+ ), true);
+ return $cache[$key] = $string;
+ }
+ }
+ return $cache[$key] = ($row['value'] ?? false);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php
index 5856c81e..1b664d04 100644
--- a/modules-available/statistics/install.inc.php
+++ b/modules-available/statistics/install.inc.php
@@ -40,6 +40,7 @@ $res[] = tableCreate('machine', "
`cpumodel` varchar(120) NOT NULL,
`systemmodel` varchar(120) NOT NULL DEFAULT '',
`id44mb` int(10) unsigned NOT NULL,
+ `id45mb` int(10) unsigned NOT NULL,
`badsectors` int(10) unsigned NOT NULL,
`data` mediumblob NOT NULL,
`dataparsetime` int(10) unsigned NOT NULL DEFAULT 0,
@@ -339,7 +340,15 @@ if (!tableHasColumn('machine', 'dataparsetime')) {
$ret = Database::exec("ALTER TABLE `machine`
ADD COLUMN `dataparsetime` int(10) unsigned NOT NULL DEFAULT '0' AFTER `data`");
if ($ret === false) {
- finalResponse(UPDATE_FAILED, 'Adding mem-stat columns to machine table failed: ' . Database::lastError());
+ finalResponse(UPDATE_FAILED, 'Adding dateparsetime column to machine table failed: ' . Database::lastError());
+ }
+ $res[] = UPDATE_DONE;
+}
+if (!tableHasColumn('machine', 'id45mb')) {
+ $ret = Database::exec("ALTER TABLE `machine`
+ ADD COLUMN `id45mb` int(10) unsigned NOT NULL AFTER `id44mb`");
+ if ($ret === false) {
+ finalResponse(UPDATE_FAILED, 'Adding id45mb column to machine table failed: ' . Database::lastError());
}
$res[] = UPDATE_DONE;
}
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index 3e4aa9ce..04d9a515 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -24,10 +24,12 @@ class Page_Statistics extends Page
/*
Dictionary::translate('submenu_projectors');
Dictionary::translate('submenu_replace');
+ Dictionary::translate('submenu_hints');
*/
- foreach (['projectors', 'replace'] as $section) {
- Dashboard::addSubmenu('?do=statistics&show=' . $section, Dictionary::translate('submenu_' . $section, true));
+ foreach (['projectors', 'replace', 'hints'] as $section) {
+ Dashboard::addSubmenu('?do=statistics&show=' . $section,
+ Dictionary::translate('submenu_' . $section, true));
}
$this->show = Request::any('show', false, 'string');
@@ -251,11 +253,24 @@ class Page_Statistics extends Page
{
if (!User::load())
return;
- if (Request::any('action') === 'bios') {
+ $action = Request::any('action');
+ if ($action === 'bios') {
require_once 'modules/statistics/pages/machine.inc.php';
SubPage::ajaxCheckBios();
return;
}
+ if ($action === 'json-lookup') {
+ $reply = [];
+ foreach (Request::post('list', [], 'array') as $item) {
+ $name = PciId::getPciId(PciId::AUTO, $item, true);
+ if ($name === false) {
+ $name = '?????';
+ }
+ $reply[$item] = $name;
+ }
+ header('Content-Type: application/json');
+ die(json_encode($reply));
+ }
$param = Request::any('lookup', false, 'string');
if ($param === false) {
@@ -263,61 +278,14 @@ class Page_Statistics extends Page
}
$add = '';
if (preg_match('/^([a-f0-9]{4}):([a-f0-9]{4})$/', $param, $out)) {
- $cat = 'DEVICE';
- $host = $out[2] . '.' . $out[1];
$add = ' (' . $param . ')';
- } elseif (preg_match('/^([a-f0-9]{4})$/', $param, $out)) {
- $cat = 'VENDOR';
- $host = $out[1];
- } elseif (preg_match('/^c\.([a-f0-9]{2})([a-f0-9]{2})$/', $param, $out)) {
- $cat = 'CLASS';
- $host = $out[2] . '.' . $out[1] . '.c';
- } else {
- die('Invalid format requested');
- }
- $cached = Page_Statistics::getPciId($cat, $param);
- if ($cached !== false && $cached['dateline'] > time()) {
- echo $cached['value'], $add;
- exit;
- }
- $res = dns_get_record($host . '.pci.id.ucw.cz', DNS_TXT);
- if (is_array($res)) {
- foreach ($res as $entry) {
- if (isset($entry['txt']) && substr($entry['txt'], 0, 2) === 'i=') {
- $string = substr($entry['txt'], 2);
- Page_Statistics::setPciId($cat, $param, $string);
- echo $string, $add;
- exit;
- }
- }
}
- if ($cached !== false) {
- echo $cached['value'], $add;
- exit;
+ $cached = PciId::getPciId(PciId::AUTO, $param, true);
+ if ($cached === false) {
+ $cached = 'Unknown';
}
- die('Not found');
- }
-
- public static function getPciId($cat, $id)
- {
- static $cache = [];
- $key = $cat . '-' . $id;
- if (isset($cache[$key]))
- return $cache[$key];
- return $cache[$key] = Database::queryFirst('SELECT value, dateline FROM pciid WHERE category = :cat AND id = :id LIMIT 1',
- array('cat' => $cat, 'id' => $id));
- }
-
- private static function setPciId($cat, $id, $value)
- {
- Database::exec('INSERT INTO pciid (category, id, value, dateline) VALUES (:cat, :id, :value, :timeout)'
- . ' ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)',
- array(
- 'cat' => $cat,
- 'id' => $id,
- 'value' => $value,
- 'timeout' => time() + mt_rand(10, 30) * 86400,
- ), true);
+ echo $cached, $add;
+ exit;
}
}
diff --git a/modules-available/statistics/pages/hints.inc.php b/modules-available/statistics/pages/hints.inc.php
new file mode 100644
index 00000000..5c6acfbb
--- /dev/null
+++ b/modules-available/statistics/pages/hints.inc.php
@@ -0,0 +1,77 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+
+ }
+
+ public static function doRender()
+ {
+ self::showMemoryUpgrade();
+ self::showMemorySlow();
+ self::showUnusedSpace();
+ }
+
+ private static function showMemoryUpgrade()
+ {
+ $q = new HardwareQuery(HardwareInfo::MAINBOARD);
+ $q->addLocalColumn('Memory Slot Occupied');
+ $q->addGlobalColumn('Memory Slot Count');
+ $q->addGlobalColumn('Memory Maximum Capacity');
+ $q->addMachineColumn('clientip');
+ $q->addMachineColumn('hostname');
+ $q->addWhere(false, 'Memory Installed Capacity', '<', 8 * 1024 * 1024 * 1024);
+ $list = [];
+ foreach ($q->query() as $row) {
+ if (HardwareParser::convertSize($row['Memory Installed Capacity'], 'M', false)
+ >= HardwareParser::convertSize($row['Memory Maximum Capacity'], 'M', false)) {
+ $row['size_class'] = 'danger';
+ $list[] = $row;
+ } elseif ($row['Memory Slot Occupied'] < $row['Memory Slot Count']) {
+ $row['count_class'] = 'success';
+ array_unshift($list, $row);
+ } else {
+ $list[] = $row;
+ }
+ }
+ Render::addTemplate('hints-ram-upgrade', ['list' => $list]);
+ }
+
+ private static function showMemorySlow()
+ {
+ $q = new HardwareQuery(HardwareInfo::RAM_MODULE);
+ $q->addLocalColumn('Locator');
+ $q->addLocalColumn('Bank Locator');
+ $q->addGlobalColumn('Form Factor');
+ $q->addGlobalColumn('Type');
+ $q->addGlobalColumn('Size');
+ $q->addGlobalColumn('Manufacturer');
+ $q->addLocalColumn('Serial Number');
+ $q->addMachineColumn('clientip');
+ $q->addMachineColumn('hostname');
+ $q->addCompare(true, 'Speed', '>', false, 'Configured Memory Speed');
+ $list = $q->query()->fetchAll();
+ Render::addTemplate('hints-ram-underclocked', ['list' => $list]);
+ }
+
+ private static function showUnusedSpace()
+ {
+ $q = new HardwareQuery(HardwareInfo::HDD);
+ $q->addWhere(false, 'unused', '>', 2000000000); // 2 GB
+ $q->addMachineWhere('id44mb', '<', 20000); // 20 GB
+ $id44 = $q->query()->fetchAll();
+ $q = new HardwareQuery(HardwareInfo::HDD);
+ $q->addWhere(false, 'unused', '>', 25000000000); // 25 GB
+ $q->addMachineWhere('id44mb', '>', 20000); // 20 GB
+ $q->addMachineWhere('id45mb', '<', 20000); // 20 GB
+ $id45 = $q->query()->fetchAll();
+ Render::addTemplate('hints-hdd-grow', [
+ 'id44' => $id44,
+ 'id45' => $id45,
+ ]);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/statistics/templates/hints-ram-underclocked.html b/modules-available/statistics/templates/hints-ram-underclocked.html
new file mode 100644
index 00000000..e1f19c4f
--- /dev/null
+++ b/modules-available/statistics/templates/hints-ram-underclocked.html
@@ -0,0 +1,44 @@
+<h2>{{lang_ramUnderclocked}}</h2>
+
+<p>{{lang_ramUnderclockedText}}</p>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_machine}}</th>
+ <th>{{lang_type}}</th>
+ <th>{lang_speedCurrent}}</th>
+ <th>{{lang_speedDesign}}</th>
+ <th>{{lang_manufacturer}}</th>
+ <th>{{lang_serialNumber}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td>
+ <a class="slx-bold" href="?do=statistics&amp;uuid={{machineuuid}}">
+ {{hostname}}{{^hostname}}{{clientip}}{{/hostname}}
+ </a>
+ <div class="small">{{machineuuid}}</div>
+ </td>
+ <td>
+ {{Type}} {{Form Factor}}
+ <div>{{Size}}</div>
+ </td>
+ <td>
+ {{Configured Memory Speed}}
+ </td>
+ <td>
+ {{Speed}}
+ </td>
+ <td>
+ {{Manufacturer}}
+ </td>
+ <td>
+ {{Serial Number}}
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table> \ No newline at end of file
diff --git a/modules-available/statistics/templates/hints-ram-upgrade.html b/modules-available/statistics/templates/hints-ram-upgrade.html
new file mode 100644
index 00000000..be0d2532
--- /dev/null
+++ b/modules-available/statistics/templates/hints-ram-upgrade.html
@@ -0,0 +1,31 @@
+<h2>{{lang_ramUpgrade}}</h2>
+
+<p>{{lang_ramUpgradeText}}</p>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_machine}}</th>
+ <th>{{lang_installedCountMax}}</th>
+ <th>{{lang_ramSizeCurrentMax}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td>
+ <a class="slx-bold" href="?do=statistics&amp;uuid={{machineuuid}}">
+ {{hostname}}{{^hostname}}{{clientip}}{{/hostname}}
+ </a>
+ <div class="small">{{machineuuid}}</div>
+ </td>
+ <td class="{{count_class}}">
+ {{Memory Slot Occupied}} / {{Memory Slot Count}}
+ </td>
+ <td class="{{size_class}}">
+ {{Memory Installed Capacity}} / {{Memory Maximum Capacity}}
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+</table> \ No newline at end of file