From d736e75ade7a4472aefb72af9036f86016adcb42 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 30 Sep 2021 17:44:30 +0200 Subject: [statistics] Adapt hw-data parsing to new json format for display --- modules-available/statistics/pages/hints.inc.php | 50 +++- modules-available/statistics/pages/machine.inc.php | 296 ++++++++++++++++++--- 2 files changed, 303 insertions(+), 43 deletions(-) (limited to 'modules-available/statistics/pages') diff --git a/modules-available/statistics/pages/hints.inc.php b/modules-available/statistics/pages/hints.inc.php index 5c6acfbb..278c0e26 100644 --- a/modules-available/statistics/pages/hints.inc.php +++ b/modules-available/statistics/pages/hints.inc.php @@ -5,19 +5,26 @@ class SubPage public static function doPreprocess() { - + User::assertPermission('hints'); } public static function doRender() { - self::showMemoryUpgrade(); - self::showMemorySlow(); - self::showUnusedSpace(); + $locs = User::getAllowedLocations('hints'); + if (in_array(0, $locs)) { + $locs = []; + } + self::showMemoryUpgrade($locs); + self::showMemorySlow($locs); + self::showUnusedSpace($locs); } - private static function showMemoryUpgrade() + private static function showMemoryUpgrade(array $locs) { $q = new HardwareQuery(HardwareInfo::MAINBOARD); + if (!empty($locs)) { + $q->addMachineWhere('locationid', 'IN', $locs); + } $q->addLocalColumn('Memory Slot Occupied'); $q->addGlobalColumn('Memory Slot Count'); $q->addGlobalColumn('Memory Maximum Capacity'); @@ -40,9 +47,12 @@ class SubPage Render::addTemplate('hints-ram-upgrade', ['list' => $list]); } - private static function showMemorySlow() + private static function showMemorySlow(array $locs) { $q = new HardwareQuery(HardwareInfo::RAM_MODULE); + if (!empty($locs)) { + $q->addMachineWhere('locationid', 'IN', $locs); + } $q->addLocalColumn('Locator'); $q->addLocalColumn('Bank Locator'); $q->addGlobalColumn('Form Factor'); @@ -57,17 +67,39 @@ class SubPage Render::addTemplate('hints-ram-underclocked', ['list' => $list]); } - private static function showUnusedSpace() + private static function showUnusedSpace(array $locs) { + $id44 = $id45 = []; + // ID44 $q = new HardwareQuery(HardwareInfo::HDD); + if (!empty($locs)) { + $q->addMachineWhere('locationid', 'IN', $locs); + } + $q->addMachineColumn('clientip'); + $q->addMachineColumn('hostname'); $q->addWhere(false, 'unused', '>', 2000000000); // 2 GB $q->addMachineWhere('id44mb', '<', 20000); // 20 GB - $id44 = $q->query()->fetchAll(); + foreach ($q->query()->fetchAll() as $row) { + $row['unused_s'] = Util::readableFileSize($row['unused']); + $row['id44mb_s'] = Util::readableFileSize($row['id44mb'], -1, 2); + $id44[] = $row; + } + // ID45 $q = new HardwareQuery(HardwareInfo::HDD); + if (!empty($locs)) { + $q->addMachineWhere('locationid', 'IN', $locs); + } + $q->addMachineColumn('clientip'); + $q->addMachineColumn('hostname'); $q->addWhere(false, 'unused', '>', 25000000000); // 25 GB $q->addMachineWhere('id44mb', '>', 20000); // 20 GB $q->addMachineWhere('id45mb', '<', 20000); // 20 GB - $id45 = $q->query()->fetchAll(); + foreach ($q->query()->fetchAll() as $row) { + $row['unused_s'] = Util::readableFileSize($row['unused']); + $row['id44mb_s'] = Util::readableFileSize($row['id44mb'], -1, 2); + $row['id45mb_s'] = Util::readableFileSize($row['id45mb'], -1, 2); + $id45[] = $row; + } Render::addTemplate('hints-hdd-grow', [ 'id44' => $id44, 'id45' => $id45, diff --git a/modules-available/statistics/pages/machine.inc.php b/modules-available/statistics/pages/machine.inc.php index f3af4f47..b23db721 100644 --- a/modules-available/statistics/pages/machine.inc.php +++ b/modules-available/statistics/pages/machine.inc.php @@ -54,7 +54,7 @@ class SubPage $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state, mbram, live_tmpsize, live_tmpfree, live_id45size, live_id45free, live_swapsize, live_swapfree, live_memsize, live_memfree, live_cpuload, live_cputemp, - Length(position) AS hasroomplan, kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes + Length(position) AS hasroomplan, kvmstate, cpumodel, id44mb, id45mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($client === false) { @@ -70,6 +70,55 @@ class SubPage Header('Content-Type: text/plain; charset=utf-8'); die($client['data']); } + // Parse data + $hdds = array(); + if ($client['data'][0] === '{') { + $json = json_decode($client['data'], true); + if (is_array($json)) { + $client += self::parseJson($uuid, $json); + $hdds['hdds'] = self::queryHddData($uuid); + } + } else { + self::parseLegacy($client, $hdds); + } + unset($client['data']); + // Get rid of configured speed, if equal to maximum speed + foreach ($client['ram'] as &$item) { + if (isset($item['Configured Memory Speed']) && $item['Configured Memory Speed'] === $item['Speed']) { + unset($item['Configured Memory Speed']); + } + } + unset($item); + $client['lspci1'] = $client['lspci2'] = []; + foreach ($client['lspci'] as $item) { + $item['vendor_s'] = PciId::getPciId(PciId::VENDOR, $item['vendor']); + $item['device_s'] = PciId::getPciId(PciId::DEVICE, $item['vendor'] . $item['device']); + if ($item['vendor_s'] === false) { + $pciLookup[$item['vendor']] = true; + } + if ($item['device_s'] === false) { + $pciLookup[$item['vendor'] . ':' . $item['device']] = true; + } + $class = $item['class']; + if ($class === '0300' || $class === '0200' || $class === '0403') { + $dst =& $client['lspci1']; + } else { + $dst =& $client['lspci2']; + } + if (!isset($dst[$class])) { + $dst[$class] = [ + 'class' => $class, + 'class_s' => PciId::getPciId(PciId::DEVCLASS, $class, true), + 'entries' => [], + ]; + } + $dst[$class]['entries'][] = $item; + } + unset($dst, $client['lspci']); + ksort($client['lspci1']); + ksort($client['lspci2']); + $client['lspci1'] = array_values($client['lspci1']); + $client['lspci2'] = array_values($client['lspci2']); // Runmode if (Module::isAvailable('runmode')) { $data = RunMode::getRunMode($uuid, RunMode::DATA_STRINGS); @@ -121,6 +170,7 @@ class SubPage } $client['gbram'] = round(ceil($client['mbram'] / 512) / 2, 1); $client['gbtmp'] = round($client['id44mb'] / 1024); + $client['gbid45'] = round($client['id45mb'] / 1024); foreach (['tmp', 'id45', 'swap', 'mem'] as $item) { if ($client['live_' . $item . 'size'] == 0) continue; @@ -135,44 +185,25 @@ class SubPage $client['ramclass'] = StatisticsStyling::ramColorClass($client['mbram']); $client['kvmclass'] = StatisticsStyling::kvmColorClass($client['kvmstate']); $client['hddclass'] = StatisticsStyling::hddColorClass($client['gbtmp']); - // Parse the giant blob of data - if (strpos($client['data'], "\r") !== false) { - $client['data'] = str_replace("\r", "\n", $client['data']); - } - $hdds = array(); - if (preg_match_all('/##### ([^#]+) #+$(.*?)^#####/ims', $client['data'] . '########', $out, PREG_SET_ORDER)) { - foreach ($out as $section) { - if ($section[1] === 'CPU') { - HardwareParser::parseCpu($client, $section[2]); - } - if ($section[1] === 'dmidecode') { - HardwareParser::parseDmiDecode($client, $section[2]); - } - if ($section[1] === 'Partition tables') { - HardwareParser::parseHdd($hdds, $section[2]); - } - if ($section[1] === 'PCI ID') { - $client['lspci1'] = $client['lspci2'] = array(); - HardwareParser::parsePci($client['lspci1'], $client['lspci2'], $section[2]); - } - if (isset($hdds['hdds']) && $section[1] === 'smartctl') { - // This currently requires that the partition table section comes first... - HardwareParser::parseSmartctl($hdds['hdds'], $section[2]); - } - } + // Format HDD data to strings + foreach ($hdds['hdds'] as &$hdd) { + $hdd['smart_status_failed'] = !($client['smart_status//passed'] ?? 1); + self::mangleHdd($hdd); } - unset($client['data']); // BIOS update check - if (!empty($client['biosrevision'])) { - $mainboard = $client['mobomanufacturer'] . '##' . $client['mobomodel']; - $system = $client['pcmanufacturer'] . '##' . $client['pcmodel']; - $ret = self::checkBios($mainboard, $system, $client['biosdate'], $client['biosrevision']); + if (!empty($client['bios']['BIOS Revision']) || !empty($client['bios']['Release Date'])) { + if (preg_match('#^(\d{1,2})/(\d{1,2})/(\d{4})#', $client['bios']['Release Date'], $out)) { + $client['bios']['Release Date'] = $out[2] . '.' . $out[1] . '.' . $out[3]; + } + $mainboard = $client['mainboard']['Manufacturer'] . '##' . $client['mainboard']['Product Name']; + $system = $client['system']['Manufacturer'] . '##' . $client['system']['Product Name']; + $ret = self::checkBios($mainboard, $system, $client['bios']['Release Date'], $client['bios']['BIOS Revision']); if ($ret === false) { // Not loaded, use AJAX $params = [ 'mainboard' => $mainboard, 'system' => $system, - 'date' => $client['biosdate'], - 'revision' => $client['biosrevision'], + 'date' => $client['bios']['Release Date'], + 'revision' => $client['bios']['BIOS Revision'], ]; $client['biosurl'] = '?do=statistics&action=bios&' . http_build_query($params); } elseif (!isset($ret['status']) || $ret['status'] !== 0) { @@ -211,6 +242,10 @@ class SubPage Permission::addGlobalTags($client['perms'], null, ['hardware.projectors.edit', 'hardware.projectors.view']); // Throw output at user Render::addTemplate('machine-main', $client); + if (!empty($pciLookup)) { + Render::addTemplate('js-pciquery', + ['missing_ids' => json_encode(array_keys($pciLookup))]); + } // Sessions $NOW = time(); $cutoff = $NOW - 86400 * 7; @@ -348,7 +383,182 @@ class SubPage } } - private static function eventToIconName($event) + private static function parseLegacy(array &$client, array &$hdds) + { + // Parse the giant blob of data + if (strpos($client['data'], "\r") !== false) { + $client['data'] = str_replace("\r", "\n", $client['data']); + } + if (preg_match_all('/##### ([^#]+) #+$(.*?)^#####/ims', $client['data'] . '########', $out, PREG_SET_ORDER)) { + foreach ($out as $section) { + if ($section[1] === 'CPU') { + HardwareParserLegacy::parseCpu($client, $section[2]); + } + if ($section[1] === 'dmidecode') { + HardwareParserLegacy::parseDmiDecode($client, $section[2]); + } + if ($section[1] === 'Partition tables') { + HardwareParserLegacy::parseHdd($hdds, $section[2]); + } + if ($section[1] === 'PCI ID') { + $client['lspci'] = HardwareParserLegacy::parsePci($section[2]); + } + if (isset($hdds['hdds']) && $section[1] === 'smartctl') { + // This currently requires that the partition table section comes first... + HardwareParserLegacy::parseSmartctl($hdds['hdds'], $section[2]); + } + } + } + } + + private static function parseJson(string $uuid, array $json): array + { + $return = [ + 'cpu' => $json['cpu'] ?? [], + 'lspci' => $json['lspci'] ?? [], + 'ram' => array_map(function($item) { + return HardwareParser::prepareDmiProperties($item); + }, HardwareParser::getDmiHandles($json, 17)), + ]; + foreach ($return['ram'] as $ram) { + if (!empty($ram['Form Factor']) && !empty($ram['Type'])) { + $return['ramtype'] = $ram['Type'] . '-' . $ram['Form Factor']; + break; + } + } + $need = [ + 'bios' => 0, + 'system' => 1, + 'mainboard' => 2, + ]; + foreach ($need as $name => $id) { + $return[$name] = HardwareParser::prepareDmiProperties( + HardwareParser::getDmiHandles($json, $id)[0] ?? []); + } + $q = new HardwareQuery(HardwareInfo::MAINBOARD, $uuid); + $q->addGlobalColumn('Memory Maximum Capacity'); + $q->addGlobalColumn('Memory Slot Count'); + $res = $q->query()->fetch(); + if (is_array($res)) { + $return += $res; + } + return $return; + } + + private static function queryHddData(string $uuid): array + { + $hdds = []; + $ret = Database::simpleQuery("SELECT mp.`machinehwid`, mp.`prop`, mp.`value`, mp.`numeric` + FROM machine_x_hw_prop mp + INNER JOIN machine_x_hw mxhw ON (mp.machinehwid = mxhw.machinehwid AND mxhw.machineuuid = :uuid AND mxhw.disconnecttime = 0) + INNER JOIN statistic_hw sh ON (mxhw.hwid = sh.hwid AND sh.hwtype = :type) + UNION SELECT mxhw.`machinehwid`, hwp.`prop`, hwp.`value`, hwp.`numeric` + FROM statistic_hw_prop hwp + INNER JOIN machine_x_hw mxhw ON (hwp.hwid = mxhw.hwid AND mxhw.machineuuid = :uuid AND mxhw.disconnecttime = 0) + INNER JOIN statistic_hw sh ON (mxhw.hwid = sh.hwid AND sh.hwtype = :type) + ", + ['type' => HardwareInfo::HDD, 'uuid' => $uuid]); + foreach ($ret as $row) { + if (!isset($hdds[$row['machinehwid']])) { + $hdds[$row['machinehwid']] = ['partitions' => []]; + } + $hdd =& $hdds[$row['machinehwid']]; + if (preg_match('/^(attr_[0-9]+)_(.*)$/', $row['prop'], $out)) { + // SMART attributes + if (!isset($hdd[$out[1]])) { + $hdd[$out[1]] = []; + } + $hdd[$out[1]][$out[2]] = $row['numeric'] ?? $row['value']; + } elseif (preg_match('/^part_([0-9]+)_(.*)$/', $row['prop'], $out)) { + // Partitions + if (!isset($hdd['partitions'][$out[1]])) { + $hdd['partitions'][$out[1]] = ['id' => 'dev-' . count($hdds) . '-' . $out[1], 'index' => $out[1] + 1]; + } + $hdd['partitions'][$out[1]][$out[2]] = $row['numeric'] ?? $row['value']; + } else { + $hdd[$row['prop']] = $row['numeric'] ?? $row['value']; + } + } + foreach ($hdds as $k => &$hdd) { + $hdd['devid'] = 'k' . $k; + $hdd['partitions'] = array_values($hdd['partitions']); + } + return array_values($hdds); + } + + private static function mangleHdd(array &$hdd) + { + $hours = $hdd['power_on_time//hours'] ?? $hdd['attr_9']['raw'] ?? $hdd['power_on_hours'] + ?? $hdd['power_on_time']['hours'] ?? null; + if ($hours !== null) { + $hdd['PowerOnTime'] = ''; + $val = (int)str_replace('.', '', $hours); + if ($val > 8760) { + $hdd['PowerOnTime'] .= floor($val / 8760) . 'Y, '; + $val %= 8760; + } + if ($val > 720) { + $hdd['PowerOnTime'] .= floor($val / 720) . 'M, '; + $val %= 720; + } + if ($val > 24) { + $hdd['PowerOnTime'] .= floor($val / 24) . 'd, '; + $val %= 24; + } + $hdd['PowerOnTime'] .= $val . 'h'; + } + // Sort by start for building pie-chart + $xx = array_column($hdd['partitions'], 'start'); + array_multisort($xx, SORT_ASC, SORT_NUMERIC, + $hdd['partitions']); + $used = 0; + $json = []; + $lastEnd = 0; + $minDisplaySize = $hdd['size'] / 150; + foreach ($hdd['partitions'] as &$part) { + $dist = $part['start'] - $lastEnd; + if ($dist > $minDisplaySize) { + error_log('Dist: ' . Util::readableFileSize($dist)); + $json[] = ['value' => $dist, 'color' => '#aaa']; + } + if ($part['size'] > $minDisplaySize) { + $json[] = ['value' => $part['size'], 'color' => self::typeToColor($part), 'label' => $part['id']]; + } + $part['size_s'] = Util::readableFileSize($part['size']); + $used += $part['size']; + $lastEnd = $part['start'] + $part['size']; + if (!isset($part['name'])) { + $part['name'] = self::mbrType($part['slxtype'] ?? $part['type']); + } + } + $dist = $hdd['size'] - $lastEnd; + if ($dist > $minDisplaySize) { + $json[] = ['value' => $dist, 'color' => '#aaa']; + } + $hdd['json'] = json_encode($json); + $hdd['size_s'] = Util::readableFileSize($hdd['size']); + if ($hdd['size'] - $used > 1000000000) { + $hdd['unused_s'] = Util::readableFileSize($hdd['size'] - $used); + } + // Finally sort by index for table display + array_multisort(array_column($hdd['partitions'], 'index'), SORT_ASC, + $hdd['partitions']); + } + + private static function typeToColor(array $part): string + { + switch ($part['slxtype'] ?? $part['type']) { + case 44: + return '#5c1'; + case 45: + return '#0d7'; + case 82: + return '#48f'; + } + return '#e55'; + } + + private static function eventToIconName($event): string { switch ($event) { case 'session-open': @@ -441,4 +651,22 @@ class SubPage return $retval; } + private static function mbrType(string $type): string + { + switch ($type) { + case '44': + case '45': + return 'OpenSLX-ID' . $type; + case '82': + return 'Linux Swap'; + case '83': + return 'Linux'; + case '7': + return 'NTFS/Windows'; + case 'ef': + return 'EFI'; + } + return $type; + } + } \ No newline at end of file -- cgit v1.2.3-55-g7522