summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2021-09-21 16:52:06 +0200
committerSimon Rettberg2021-09-21 16:52:06 +0200
commit0156024a414ea1503e539cb2a30d05a422d4cd16 (patch)
treea92eb114deb3a0e44252a2c3d90928b5c6a673d1
parent[statistics] Query builder WIP (diff)
downloadslx-admin-0156024a414ea1503e539cb2a30d05a422d4cd16.tar.gz
slx-admin-0156024a414ea1503e539cb2a30d05a422d4cd16.tar.xz
slx-admin-0156024a414ea1503e539cb2a30d05a422d4cd16.zip
Passthrough WIP
-rw-r--r--modules-available/minilinux/inc/linuxbootentryhook.inc.php20
-rw-r--r--modules-available/passthrough/config.json7
-rw-r--r--modules-available/passthrough/inc/passthrough.inc.php42
-rw-r--r--modules-available/passthrough/install.inc.php9
-rw-r--r--modules-available/passthrough/page.inc.php97
-rw-r--r--modules-available/passthrough/permissions/permissions.json3
-rw-r--r--modules-available/passthrough/templates/hardware-list.html96
-rw-r--r--modules-available/statistics/api.inc.php4
-rw-r--r--modules-available/statistics/inc/hardwareinfo.inc.php54
-rw-r--r--modules-available/statistics/inc/hardwareparser.inc.php20
-rw-r--r--modules-available/statistics/inc/hardwarequery.inc.php40
-rw-r--r--modules-available/statistics/inc/pciid.inc.php63
-rw-r--r--modules-available/statistics/page.inc.php50
13 files changed, 432 insertions, 73 deletions
diff --git a/modules-available/minilinux/inc/linuxbootentryhook.inc.php b/modules-available/minilinux/inc/linuxbootentryhook.inc.php
index e57336f0..9e10bd6e 100644
--- a/modules-available/minilinux/inc/linuxbootentryhook.inc.php
+++ b/modules-available/minilinux/inc/linuxbootentryhook.inc.php
@@ -15,7 +15,7 @@ class LinuxBootEntryHook extends BootEntryHook
return Dictionary::translateFileModule('minilinux', 'module', 'module_name', true);
}
- public function extraFields()
+ public function extraFields(): array
{
/* For translate module:
* Dictionary::translate('ipxe-kcl-extra');
@@ -32,7 +32,7 @@ class LinuxBootEntryHook extends BootEntryHook
/**
* @return HookEntryGroup[]
*/
- protected function groupsInternal()
+ protected function groupsInternal(): array
{
/*
* Dictionary::translate('default_boot_entry');
@@ -76,7 +76,7 @@ class LinuxBootEntryHook extends BootEntryHook
* @param $localData
* @return BootEntry the actual boot entry instance for given entry, false if invalid id
*/
- public function getBootEntryInternal($localData)
+ public function getBootEntryInternal($localData): BootEntry
{
$id = $localData['id'];
if ($id === 'default') { // Special case
@@ -125,7 +125,7 @@ class LinuxBootEntryHook extends BootEntryHook
return BootEntry::newStandardBootEntry($bios, $efi, $arch);
}
- private function generateExecData($effectiveId, $remoteData, $localData)
+ private function generateExecData($effectiveId, $remoteData, $localData): ExecData
{
$exec = new ExecData();
// Defaults
@@ -146,9 +146,7 @@ class LinuxBootEntryHook extends BootEntryHook
if (!empty($localData['debug'])) {
// Debug boot enabled
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
- isset($remoteData['debugCommandLineModifier'])
- ? $remoteData['debugCommandLineModifier']
- : '-vga -quiet -splash -loglevel loglevel=7'
+ $remoteData['debugCommandLineModifier'] ?? '-vga -quiet -splash -loglevel loglevel=7'
);
}
// disable all CPU sidechannel attack mitigations etc.
@@ -156,6 +154,14 @@ class LinuxBootEntryHook extends BootEntryHook
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
'noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off');
}
+ // GVT, PCI Pass-thru etc.
+ if (Module::isAvailable('statistics')) {
+ $hwextra = HardwareInfo::getKclModifications();
+ if (!empty($hwextra)) {
+ $exec->commandLine = IPxe::modifyCommandLine($exec->commandLine, $hwextra);
+ }
+ }
+ // User-supplied modifications
if (!empty($localData['kcl-extra'])) {
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine, $localData['kcl-extra']);
}
diff --git a/modules-available/passthrough/config.json b/modules-available/passthrough/config.json
new file mode 100644
index 00000000..3f65995f
--- /dev/null
+++ b/modules-available/passthrough/config.json
@@ -0,0 +1,7 @@
+{
+ "category": "main.settings-client",
+ "dependencies": [
+ "statistics",
+ "locations"
+ ]
+} \ No newline at end of file
diff --git a/modules-available/passthrough/inc/passthrough.inc.php b/modules-available/passthrough/inc/passthrough.inc.php
new file mode 100644
index 00000000..51fe7214
--- /dev/null
+++ b/modules-available/passthrough/inc/passthrough.inc.php
@@ -0,0 +1,42 @@
+<?php
+
+class Passthrough
+{
+
+ public static function getGroupDropdown(array $row): array
+ {
+ static $list = false;
+ if ($list === false) {
+ $list = Database::queryKeyValueList("SELECT groupid, title FROM passthrough_group ORDER BY groupid");
+ self::ensurePrepopulated($list);
+ }
+ $out = [];
+ foreach ($list as $id => $title) {
+ if ($row['class'] !== '0300' && ($id === 'GPU' || $id === 'GVT'))
+ continue;
+ $item = ['ptid' => $id, 'ptname' => $id . ' (' . $title . ')'];
+ if ($row['@PASSTHROUGH'] === $id) {
+ $item['selected'] = 'selected';
+ }
+ $out[] = $item;
+ }
+ return $out;
+ }
+
+ private static function ensurePrepopulated(&$list)
+ {
+ $want = [
+ 'GPU' => '[Special] GPU passthrough default group',
+ 'GVT' => '[Special] Intel GVT-g default group',
+ ];
+ foreach ($want as $id => $title) {
+ if (!isset($list[$id])) {
+ Database::exec("INSERT INTO passthrough_group (groupid, title) VALUES (:id, :title)
+ ON DUPLICATE KEY UPDATE title = VALUES(title)",
+ ['id' => $id, 'title' => $title]);
+ $list[$id] = $title;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/passthrough/install.inc.php b/modules-available/passthrough/install.inc.php
new file mode 100644
index 00000000..e08be38b
--- /dev/null
+++ b/modules-available/passthrough/install.inc.php
@@ -0,0 +1,9 @@
+<?php
+
+$result[] = tableCreate('passthrough_group', "
+ `groupid` varchar(32) CHARACTER SET ascii DEFAULT NULL,
+ `title` varchar(200) NOT NULL,
+ PRIMARY KEY (`groupid`)
+");
+
+responseFromArray($result); \ No newline at end of file
diff --git a/modules-available/passthrough/page.inc.php b/modules-available/passthrough/page.inc.php
new file mode 100644
index 00000000..3913bad0
--- /dev/null
+++ b/modules-available/passthrough/page.inc.php
@@ -0,0 +1,97 @@
+<?php
+
+class Page_Passthrough extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+ User::assertPermission('view');
+ $action = Request::post('action');
+ if ($action === 'save-hwlist') {
+ $this->saveHwList();
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=passthrough');
+ }
+ }
+
+ private function saveHwList()
+ {
+ $newgroups = Request::post('newgroup', [], 'array');
+ foreach ($newgroups as $id => $title) {
+ $id = strtoupper(preg_replace('/[^a-z0-9_\-]/ig', '', $id));
+ if (empty($id))
+ continue;
+ Database::exec("INSERT IGNORE INTO passthrough_group (groupid, title)
+ VALUES (:group, :title)",
+ ['group' => $id, 'title' => $title]);
+ }
+ $groups = Request::post('ptgroup', Request::REQUIRED, 'array');
+ $insert = [];
+ $delete = [];
+ foreach ($groups as $hwid => $group) {
+ if (empty($group)) {
+ $delete[] = $hwid;
+ } else {
+ $insert[] = [$hwid, '@PASSTHROUGH', $group];
+ }
+ }
+ if (!empty($insert)) {
+ Database::exec("INSERT INTO statistic_hw_prop (hwid, prop, `value`) VALUES :list
+ ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)",
+ ['list' => $insert]);
+ }
+ if (!empty($delete)) {
+ Database::exec("DELETE FROM statistic_hw_prop WHERE hwid IN (:list) AND prop = '@PASSTHROUGH'", ['list' => $delete]);
+ }
+ Message::addSuccess('list-updated');
+ Util::redirect('?do=passthrough&show=hwlist');
+ }
+
+ /*
+ *
+ */
+
+ protected function doRender()
+ {
+ $show = Request::get('show');
+ if ($show === 'hwlist') {
+ $this->showHardwareList();
+ } else {
+ Util::redirect('?do=passthrough&show=hwlist');
+ }
+ }
+
+ private function showHardwareList()
+ {
+ $q = new HardwareQuery(HardwareInfo::PCI_DEVICE, null, false);
+ $q->addColumn(true, 'vendor');
+ $q->addColumn(true, 'device');
+ $q->addColumn(true, 'class');
+ $q->addColumn(true, '@PASSTHROUGH');
+ $rows = [];
+ foreach ($q->query('shw.hwid') as $row) {
+ $row['ptlist'] = Passthrough::getGroupDropdown($row);
+ $rows[] = $row;
+ }
+ // Sort Graphics Cards first, rest by class, vendor, device
+ usort($rows, function ($row1, $row2) {
+ $a = $row1['class'];
+ $b = $row2['class'];
+ if ($a === $b)
+ return hexdec($row1['vendor'].$row1['device']) - hexdec($row2['vendor'] . $row2['device']);
+ if ($a === '0300')
+ return -1;
+ if ($b === '0300')
+ return 1;
+ return hexdec($a) - hexdec($b);
+ });
+ foreach ($rows as &$row) {
+ $row['vendor_name'] = PciId::getPciId(PciId::VENDOR, $row['vendor'] ?? '');
+ $row['device_name'] = PciId::getPciId(PciId::DEVICE, $row['vendor'] . ':' . $row['device']);
+ }
+ Render::addTemplate('hardware-list', ['list' => $rows]);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/passthrough/permissions/permissions.json b/modules-available/passthrough/permissions/permissions.json
new file mode 100644
index 00000000..d0932f1e
--- /dev/null
+++ b/modules-available/passthrough/permissions/permissions.json
@@ -0,0 +1,3 @@
+{
+ "view": false
+} \ No newline at end of file
diff --git a/modules-available/passthrough/templates/hardware-list.html b/modules-available/passthrough/templates/hardware-list.html
new file mode 100644
index 00000000..2450e457
--- /dev/null
+++ b/modules-available/passthrough/templates/hardware-list.html
@@ -0,0 +1,96 @@
+<form method="post" action="?do=passthrough&amp;show=hwlist">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="slx-smallcol">{{lang_class}}</th>
+ <th class="slx-smallcol">{{lang_deviceIdNumeric}}</th>
+ <th>{{lang_deviceName}}</th>
+ <th class="slx-smallcol">{{lang_useCount}}</th>
+ <th>{{lang_passthroughGroup}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#list}}
+ <tr>
+ <td>{{class}}</td>
+ <td>{{vendor}}:{{device}}</td>
+ <td>
+ <table class="slx-ellipsis">
+ <tr>
+ <td>{{device_name}}</td>
+ </tr>
+ </table>
+ <div class="small">{{vendor_name}}</div>
+ </td>
+ <td class="text-right">{{connected_count}}</td>
+ <td>
+ <select name="ptgroup[{{hwid}}]" class="form-control ptgroup-select">
+ <option value="">{{lang_noPassthroughGroup}}</option>
+ {{#ptlist}}
+ <option value="{{ptid}}" {{selected}}>{{ptname}}</option>
+ {{/ptlist}}
+ </select>
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+ <div id="new-groups"></div>
+ <div class="buttonbar text-right">
+ <button type="button" data-target="#add-group-form" data-toggle="modal" class="btn btn-default">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_add}}
+ </button>
+ <button type="submit" name="action" value="save-hwlist" class="btn btn-success">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+</form>
+
+<div class="modal fade" id="add-group-form" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_addPassthroughGroup}}</b>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="group-id">{{lang_groupId}}</label>
+ <input type="text" name="group-id" id="group-id" class="form-control">
+ </div>
+ <div class="form-group">
+ <label for="group-title">{{lang_groupTitle}}</label>
+ <input type="text" name="group-title" id="group-title" class="form-control">
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default"
+ data-dismiss="modal">{{lang_cancel}}</button>
+ <button id="add-group-button" type="button" class="btn btn-success" data-dismiss="modal">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_addGroup}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ document.addEventListener('DOMContentLoaded', function() {
+ $('#add-group-button').click(function () {
+ var gid = $('#group-id').val().replace(/[^a-zA-Z0-9_\-]/g, '').toUpperCase();
+ var title = $('#group-title').val().trim();
+ if (gid.length === 0)
+ return;
+ $('#new-groups').append($('<input type="hidden">')
+ .attr('name', 'newgroup[' + gid + ']')
+ .attr('value', title));
+ $('.ptgroup-select').each(function() {
+ $(this).append($('<option>').attr('value', gid).text(gid + ' (' + title + ')'));
+ });
+ });
+ });
+</script> \ No newline at end of file
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php
index 0a194f2e..e20ae696 100644
--- a/modules-available/statistics/api.inc.php
+++ b/modules-available/statistics/api.inc.php
@@ -1,6 +1,8 @@
<?php
if (Request::any('action') === 'test' && isLocalExecution()) {
+ error_log(HardwareInfo::getKclModifications('0A5D9E23-80F4-9C43-912C-96D80AE7E80B'));
+ exit;
$x = new HardwareQuery(HardwareInfo::PCI_DEVICE);
//$x->addCompare(false, 'Memory Slot Occupied', '>=', true, 'Memory Slot Count');
$x->addWhere(true, 'vendor', '=', '8086');
@@ -79,7 +81,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'));
}
diff --git a/modules-available/statistics/inc/hardwareinfo.inc.php b/modules-available/statistics/inc/hardwareinfo.inc.php
index 5ef94365..90b8975b 100644
--- a/modules-available/statistics/inc/hardwareinfo.inc.php
+++ b/modules-available/statistics/inc/hardwareinfo.inc.php
@@ -13,4 +13,58 @@ 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->addColumn(true, 'vendor');
+ $hw->addColumn(true, 'device');
+ $res = $hw->query();
+ $passthrough = [];
+ $gvt = false;
+ foreach ($res as $row) {
+ if ($row['@PASSTHROUGH'] === 'GVT') {
+ $gvt = true;
+ } else {
+ $passthrough[] = $row['vendor'] . ':' . $row['device'];
+ }
+ }
+ $kcl = '';
+ if ($gvt || !empty($passthrough)) {
+ $kcl = '-iommu -intel_iommu iommu=pt intel_iommu=on'; // TODO AMD
+ }
+ if (!empty($passthrough)) {
+ $kcl .= ' vfio-pci.ids=' . implode(',', $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..87dcc4cf 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;
}
@@ -717,7 +717,7 @@ class HardwareParser
$hwid = self::writeGlobalHardwareData(HardwareInfo::PCI_DEVICE,
self::propsFromArray($dev, 'vendor', 'device', 'rev', 'class'));
$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);
diff --git a/modules-available/statistics/inc/hardwarequery.inc.php b/modules-available/statistics/inc/hardwarequery.inc.php
index 7ccde2f6..b0b7d6ee 100644
--- a/modules-available/statistics/inc/hardwarequery.inc.php
+++ b/modules-available/statistics/inc/hardwarequery.inc.php
@@ -16,13 +16,15 @@ class HardwareQuery
public function __construct($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,7 +62,7 @@ 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`";
@@ -85,7 +93,7 @@ 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`";
}
@@ -93,13 +101,25 @@ class HardwareQuery
/**
* @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..6bf852e6
--- /dev/null
+++ b/modules-available/statistics/inc/pciid.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+class PciId
+{
+
+ const DEVICE = 'DEVICE';
+ const VENDOR = 'VENDOR';
+ const DEVCLASS = 'CLASS';
+
+
+ /**
+ * @param string $cat type of query - self::DEVICE, self::VENDOR or self::DEVCLASS
+ * @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;
+ }
+ $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/page.inc.php b/modules-available/statistics/page.inc.php
index 3e4aa9ce..d26649b6 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -264,60 +264,20 @@ 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;
+ $cached = PciId::getPciId($cat, $param, true);
+ if ($cached === false) {
+ $cached = 'Unknown';
}
- $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;
- }
- 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;
}
}