summaryrefslogtreecommitdiffstats
path: root/modules-available/statistics/inc/hardwareinfo.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/statistics/inc/hardwareinfo.inc.php')
-rw-r--r--modules-available/statistics/inc/hardwareinfo.inc.php463
1 files changed, 0 insertions, 463 deletions
diff --git a/modules-available/statistics/inc/hardwareinfo.inc.php b/modules-available/statistics/inc/hardwareinfo.inc.php
index 1259391f..5ef94365 100644
--- a/modules-available/statistics/inc/hardwareinfo.inc.php
+++ b/modules-available/statistics/inc/hardwareinfo.inc.php
@@ -13,467 +13,4 @@ class HardwareInfo
const HDD = 'HDD';
const CPU = 'CPU';
-
- public static function parseMachine(string $uuid, string $data)
- {
- $data = json_decode($data, true);
- $version = $data['version'] ?? 0;
- if ($version != 2) {
- error_log("Received unsupported hw json v$version");
- return;
- }
- // determine misc stuff first
- $globalMainboardExtra = [];
- $localMainboardExtra = [];
- // physical memory array
- $memArrays = self::getDmiHandles($data, 16);
- $globalMainboardExtra['Memory Slot Count'] = 0;
- $globalMainboardExtra['Memory Maximum Capacity'] = 0;
- foreach ($memArrays as $mem) {
- $mem = self::prepareDmiProperties($mem);
- if (isset($mem['Number Of Devices']) && ($mem['Use'] ?? 0) === 'System Memory') {
- $globalMainboardExtra['Memory Slot Count'] += $mem['Number Of Devices'];
- }
- if (isset($mem['Maximum Capacity'])) {
- $globalMainboardExtra['Memory Maximum Capacity'] += Parser::convertSize($mem['Maximum Capacity'], 'M', false);
- }
- }
- // 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]);
- foreach (['Version', 'Release Date', 'Firmware Revision'] as $k) {
- if (isset($bios[$k])) {
- $localMainboardExtra['BIOS ' . $k] = $bios[$k];
- }
- }
- if (isset($bios['BIOS Revision'])) {
- $localMainboardExtra['BIOS Revision'] = $bios['BIOS Revision'];
- }
- foreach (['Vendor', 'ROM Size'] as $k) {
- if (isset($bios[$k])) {
- $globalMainboardExtra['BIOS ' . $k] = $bios[$k];
- }
- }
- // Using the general helper function
- $ramModCount = self::updateHwTypeFromDmi($uuid, $data, 17, self::RAM_MODULE, function (array $flat): bool {
- return ($flat['Size'] ?? 0) > 65 * 1024 * 1024;
- },
- ['Locator'],
- ['Data Width', 'Size', 'Form Factor', 'Type', 'Type Detail', 'Speed', 'Manufacturer', 'Part Number',
- 'Minimum Voltage', 'Maximum Voltage'],
- ['Locator', 'Bank Locator', 'Serial Number', 'Asset Tag', 'Configured Memory Speed', 'Configured Voltage']
- );
- // Fake RAM slots used/total into this
- $localMainboardExtra['Memory Slot Occupied'] = $ramModCount;
- self::updateHwTypeFromDmi($uuid, $data, 2, self::MAINBOARD, ['Manufacturer', 'Product Name'],
- [],
- ['Manufacturer', 'Product Name', 'Type', 'Version'],
- ['Serial Number', 'Asset Tag', 'Location In Chassis'],
- $globalMainboardExtra, $localMainboardExtra
- );
- self::updateHwTypeFromDmi($uuid, $data, 1, self::DMI_SYSTEM, ['Manufacturer', 'Product Name'],
- [],
- ['Manufacturer', 'Product Name', 'Version', 'Wake-up Type'],
- ['Serial Number', 'UUID', 'SKU Number']
- );
- self::updateHwTypeFromDmi($uuid, $data, 39, self::POWER_SUPPLY, ['Manufacturer'],
- ['Location', 'Power Unit Group', 'Name'], // Location might be empty/"Unknown", but Name can be something like "PSU 2"
- ['Manufacturer', 'Product Name', 'Model Part Number', 'Revision', 'Max Power Capacity'],
- ['Serial Number', 'Asset Tag', 'Status', 'Plugged', 'Hot Replaceable']
- );
- self::updateHwTypeFromDmi($uuid, $data, 4, self::CPU, ['Version'],
- ['Socket Designation'],
- ['Type', 'Family', 'Manufacturer', 'Signature', 'Version', 'Core Count', 'Thread Count'],
- ['Voltage', 'Current Speed', 'Upgrade', 'Core Enabled']);
- self::updateHwTypeFromDmi($uuid, $data, 9, self::SYSTEM_SLOT, function(array &$entry): bool {
- if (!isset($entry['Type']))
- return false;
- // Split up PCIe info
- if (preg_match('/^x(?<b>\d+) PCI Express( (?<g>\d+)( x(?<s>\d+))?)?$/', $entry['Type'], $out)) {
- $entry['Type'] = 'PCI Express';
- $entry['PCIe Bus Width'] = $out['b'];
- if (!empty($out['g'])) {
- $entry['PCIe Gen'] = $out['g'];
- }
- if (!empty($out['s'])) {
- $entry['PCIe Slot Width'] = $out['s'];
- }
- }
- return true;
- },
- ['Designation', 'ID', 'Bus Address'],
- ['Type', 'PCIe Bus Width', 'PCIe Gen', 'PCIe Slot Width'],
- ['Current Usage', 'Designation']
- );
- // ---- lspci ------------------------------------
- $pciHwIds = [];
- foreach (($data['lspci'] ?? []) as $dev) {
- $hwid = self::writeGlobalHardwareData(self::PCI_DEVICE,
- self::propsFromArray($dev, 'vendor', 'device', 'rev', 'class'));
- $mappingId = self::writeLocalHardwareData($uuid, $hwid, $dev['slot'] ?? 'unknown',
- self::propsFromArray($dev, 'slot', 'subsystem', 'subsystem_vendor'));
- $pciHwIds[] = $mappingId;
- }
- self::markDisconnected($uuid, self::PCI_DEVICE, $pciHwIds);
- // ---- Disks ------------------------------------0Y3R3K
- $hddHwIds = [];
- foreach (($data['drives'] ?? []) as $dev) {
- if (empty($dev['readlink']))
- continue;
- if (!isset($dev['smartctl'])) {
- $smart = [];
- } else {
- $smart =& $dev['smartctl'];
- }
- if (!isset($dev['lsblk']['blockdevices'][0])) {
- $lsblk = [];
- } else {
- $lsblk =& $dev['lsblk']['blockdevices'][0];
- }
- if (!isset($smart['rotation_rate']) && isset($lsblk['rota']) && !$lsblk['rota']) {
- $smart['rotation_rate'] = 0;
- }
- $hwid = self::writeGlobalHardwareData(self::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',
- '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,
- 'rotation_rate', 'sata_version//string', 'interface_speed//max//string'));
- // Mangle smart attribute table
- // TODO: This is (S)ATA, do NVMe too, has different structure
- // TODO: Handle used endurance indicator for SSDs
- $table = [];
- foreach (($smart['ata_smart_attributes']['table'] ?? []) as $attr) {
- if (!isset($attr['id']))
- continue;
- $id = 'attr_' . $attr['id'] . '_';
- foreach (['value', 'worst', 'thresh', 'when_failed'] as $item) {
- if (isset($attr[$item])) {
- $table[$id . $item] = $attr[$item];
- }
- }
- if (isset($attr['raw']['value'])) {
- if ($attr['id'] === 194) {
- if (!isset($smart['temperature'])) {
- $smart['temperature'] = [];
- }
- if (!isset($smart['temperature']['current'])) {
- $smart['temperature']['current'] = $attr['raw']['value'] & 0xffff;
- }
- $smart['temperature']['min'] = ($attr['raw']['value'] >> 16) & 0xffff;
- $smart['temperature']['max'] = ($attr['raw']['value'] >> 32) & 0xffff;
- }
- $table[$id . 'raw'] = $attr['raw']['value'];
- }
- }
- // Partitions - find special ones
- if (isset($dev['sfdisk']['partitiontable'])) {
- $table['partition_table'] = $dev['sfdisk']['partitiontable']['label'] ?? 'none';
- switch ($dev['sfdisk']['partitiontable']['unit'] ?? 'sectors') {
- case 'sectors':
- $fac = 512;
- break;
- case 'bytes':
- $fac = 1;
- break;
- default:
- $fac = 0;
- }
- $i = 0;
- foreach (($dev['sfdisk']['partitiontable']['partitions'] ?? []) as $part) {
- $id = 'part_' . $i . '_';
- foreach (['start', 'size', 'type', 'uuid', 'name'] as $item) {
- if (!isset($part[$item]))
- continue;
- if ($item === 'size' || $item === 'start') {
- // Turn size and start into byte offsets
- $table[$id . $item] = $part[$item] * $fac;
- } else {
- $table[$id . $item] = $part[$item];
- }
- }
- $type = $table[$id . 'type'] ?? 0;
- $name = $table[$id . 'name'] ?? '';
- if ($type == '44' || strtolower($type) === '87f86132-ff94-4987-b250-444444444444'
- || $name === 'OpenSLX-ID44') {
- $table[$id . 'slxtype'] = '44';
- } elseif ($type == '45' || strtolower($type) === '87f86132-ff94-4987-b250-454545454545'
- || $name === 'OpenSLX-ID45') {
- $table[$id . 'slxtype'] = '45';
- }
- //
- ++$i;
- }
- }
- $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);
- // 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 '@%'", [
- 'id' => $mappingId,
- 'keep' => array_keys($table),
- ]);
- $hddHwIds[] = $mappingId;
- unset($smart, $lsblk);
- } // End loop over disks
- self::markDisconnected($uuid, self::HDD, $hddHwIds);
- }
-
- private static function updateHwTypeFromDmi(string $uuid, array $data, int $type, string $dbType,
- $requiredPropsOrCallback, array $pathFields, array $globalProps, array $localProps,
- array $globalExtra = [], array $localExtra = []): int
- {
- $sections = self::getDmiHandles($data, $type);
- $thisMachineHwIds = [];
- foreach ($sections as $section) {
- $flat = self::prepareDmiProperties($section);
- if (is_array($requiredPropsOrCallback)) {
- foreach ($requiredPropsOrCallback as $prop) {
- if (!isset($flat[$prop]))
- continue 2;
- }
- }
- if (is_callable($requiredPropsOrCallback)) {
- if (!$requiredPropsOrCallback($flat))
- continue;
- }
- // Global
- $props = self::propsFromArray($flat, ...$globalProps);
- $hwid = self::writeGlobalHardwareData($dbType, $props + $globalExtra);
- // Local
- $pathId = md5(self::idFromArray($flat, ...$pathFields));
- $props = self::propsFromArray($flat, ...$localProps);
- $mappingId = self::writeLocalHardwareData($uuid, $hwid, $pathId, $props + $localExtra);
- $thisMachineHwIds[] = $mappingId;
- }
- // Any hw <-> client mappings not in that list get marked as disconnected
- self::markDisconnected($uuid, $dbType, $thisMachineHwIds);
- return count($thisMachineHwIds);
- }
-
- private static function writeGlobalHardwareData(string $dbType, array $global): int
- {
- static $cache = [];
- // Since the global properties are supposed to be unique for a specific piece of hardware, use them all
- // to generate a combined ID for this hardware entity, as opposed to $localProps, which should differ
- // between instances of the same hardware entity, e.g. one specific HDD model has different serial numbers.
- $id = md5(implode(' ', $global));
- if (!isset($cache[$id])) {
- // Cache lookup, make sure we insert this only once for every run, as this is supposed to be general
- // information about the hardware, e.g. model number, max. resultion, capacity, ...
- $hwid = Database::insertIgnore('statistic_hw', 'hwid', ['hwtype' => $dbType, 'hwname' => $id]);
- $vals = [];
- foreach ($global as $k => $v) {
- $vals[] = [$hwid, $k, $v];
- }
- if (!empty($vals)) {
- Database::exec("INSERT IGNORE INTO statistic_hw_prop (hwid, prop, `value`) VALUES :vals",
- ['vals' => $vals]);
- }
- $cache[$id] = $hwid;
- }
- return $cache[$id];
- }
-
- /**
- * Mark all devices of a given type disconnected from the given machine, with an optional
- * exclude list of machine-client-mapping IDs
- * @param string $uuid client
- * @param string $dbType type, eg HDD
- * @param array $excludedHwIds mappingIDs to exclude, ie. devices that are still connected
- */
- private static function markDisconnected(string $uuid, string $dbType, array $excludedHwIds)
- {
- error_log("Marking disconnected for $dbType from " . implode(', ', $excludedHwIds));
- if (empty($excludedHwIds)) {
- Database::exec("UPDATE machine_x_hw mxh, statistic_hw h
- SET mxh.disconnecttime = UNIX_TIMESTAMP()
- WHERE h.hwtype = :type AND h.hwid = mxh.hwid AND mxh.machineuuid = :uuid
- AND mxh.disconnecttime = 0",
- ['type' => $dbType, 'uuid' => $uuid]);
- } else {
- Database::exec("UPDATE machine_x_hw mxh, statistic_hw h
- SET mxh.disconnecttime = UNIX_TIMESTAMP()
- WHERE h.hwtype = :type AND h.hwid = mxh.hwid AND mxh.machineuuid = :uuid
- AND mxh.disconnecttime = 0 AND mxh.machinehwid NOT IN (:hwids)",
- ['type' => $dbType, 'uuid' => $uuid, 'hwids' => $excludedHwIds]);
- }
- }
-
- /**
- * Takes key-value-array, returns a new array with only the keys listed in $fields.
- */
- private static function propsFromArray(array $array, string ...$fields): array
- {
- $ret = [];
- foreach ($fields as $field) {
- if (strpos($field, '//') === false) {
- if (isset($array[$field]) && !is_array($array[$field])) {
- $ret[$field] = $array[$field];
- }
- } else {
- $parts = explode('//', $field);
- $elem = $array;
- foreach ($parts as $part) {
- if (isset($elem[$part])) {
- $elem = $elem[$part];
- } else {
- $elem = false;
- break;
- }
- }
- if ($elem !== false && !is_array($elem)) {
- $ret[preg_replace('~//(value|string)$~', '', $field)] = $elem;
- }
- }
- }
- return $ret;
- }
-
- /**
- * Takes key-value-array, returns a concatenated string of all the values with the keys given in $fields.
- * The items are separated by spaces, and returned in the order they were given in $fields. Missing keys
- * are silently omitted.
- */
- private static function idFromArray(array $array, string ...$fields): string
- {
- $out = '';
- foreach ($fields as $field) {
- if (!isset($array[$field]))
- continue;
- if (empty($out)) {
- $out = $array[$field];
- } else {
- $out .= ' ' . $array[$field];
- }
- }
- return $out;
- }
-
- /**
- * Takes hwinfo json, then looks up and returns all sections from the
- * dmidecode subtree that represent the given dmi table entry type,
- * e.g. 17 for memory. It will then return an array of 'props' subtrees.
- * @param array $data hwinfo tree
- * @param int $type dmi type
- * @return array [ <props>, <props>, ... ]
- */
- private static function getDmiHandles(array $data, int $type): array
- {
- if (empty($data['dmidecode']))
- return [];
- $ret = [];
- foreach ($data['dmidecode'] as $section) {
- if ($section['handle']['type'] == $type) {
- $ret[] = $section['props'];
- }
- }
- return $ret;
- }
-
- const SIZE_LOOKUP = ['T' => 1099511627776, 'G' => 1073741824, 'M' => 1048576, 'K' => 1024];
-
- /**
- * Takes an array of type [ key1 => [ 'values' => [ <val1.1>, <val1.2>, ... ] ], key2 => ... ]
- * and turns it into [ key1 => <val1.1>, key2 => <val2.1>, ... ]
- * Along the way, any fields with bogus values, or values analogous to empty will get removed
- */
- private static function prepareDmiProperties(array $data): array
- {
- $ret = [];
- foreach ($data as $key => $vals) {
- $val = trim($vals['values'][0]);
- if ($val === '[Empty]' || $val === 'NULL')
- continue;
- $val = preg_replace('/[^a-z0-9]/', '', strtolower($val));
- if ($val === '' || $val === 'notspecified' || $val === 'tobefilledbyoem' || $val === 'unknown'
- || $val === 'chassismanufacture' || $val === 'chassismanufacturer' || $val === 'chassisversion'
- || $val === 'chassisserialnumber' || $val === 'defaultstring' || $val === 'productname'
- || $val === 'manufacturer' || $val === 'systemmodel' || $val === 'fillbyoem') {
- continue;
- }
- $val = trim($vals['values'][0] ?? '');
- if ($key === 'Manufacturer') {
- $val = self::fixManufacturer($val);
- } elseif (preg_match('/^(\d+)\s+([TGMK])B$/i', $val, $out)) {
- $val = $out[1] * self::SIZE_LOOKUP[strtoupper($out[2])];
- }
- $ret[$key] = $val;
- }
- return $ret;
- }
-
- /**
- * Unify different variants of manufacturer names
- */
- private static function fixManufacturer(string $in): string
- {
- $in = Parser::decodeJedec($in);
- switch (strtolower($in)) {
- case 'advanced micro devices, inc.':
- case 'advanced micro devices':
- case 'authenticamd':
- return 'AMD';
- case 'apple inc.':
- return 'Apple';
- case 'asustek computer inc.':
- return 'ASUS';
- case 'dell inc.':
- return 'Dell';
- case 'fujitsu':
- case 'fujitsu client computing limited':
- return 'Fujitsu';
- case 'hewlett packard':
- case 'hewlett-packard':
- return 'HP';
- case 'hynix semiconduc':
- case 'hynix/hyundai':
- case 'hyundai electronics hynix semiconductor inc':
- case 'hynix semiconductor inc sk hynix':
- return 'SK Hynix';
- case 'genuineintel':
- case 'intel corporation':
- case 'intel(r) corp.':
- case 'intel(r) corporation':
- return 'Intel';
- case 'samsung sdi':
- return 'Samsung';
- }
- return $in;
- }
-
- /**
- * Establish a mapping between a client and some hardware device.
- * Optionally writes hardware properties specific to a hardware instance of a client
- * @param string $uuid client
- * @param int $hwid hw global hw id
- * @param string $pathId unique identifier for the local instance of this hw, e.q. PCI slot, /dev path, something that handles the case that there are multiple instances of the same hardware in one machine
- * @param array $props KV-pairs of properties to write for this instance; can be empty
- * @return int
- */
- private static function writeLocalHardwareData(string $uuid, int $hwid, string $pathId, array $props): int
- {
- // Add mapping between hw entity and machine
- $mappingId = Database::insertIgnore('machine_x_hw', 'machinehwid',
- ['hwid' => $hwid, 'machineuuid' => $uuid, 'devpath' => $pathId],
- ['disconnecttime' => 0]);
- // And all the properties specific to this entity instance (e.g. serial number)
- if (!empty($props)) {
- $vals = [];
- foreach ($props as $k => $v) {
- $vals[] = [$mappingId, $k, $v];
- }
- Database::exec("INSERT INTO machine_x_hw_prop (machinehwid, prop, value)
- VALUES :vals
- ON DUPLICATE KEY UPDATE value = VALUES(value)", ['vals' => $vals]);
- }
- return $mappingId;
- }
-
}