diff options
Diffstat (limited to 'modules-available/statistics/pages/summary.inc.php')
-rw-r--r-- | modules-available/statistics/pages/summary.inc.php | 300 |
1 files changed, 174 insertions, 126 deletions
diff --git a/modules-available/statistics/pages/summary.inc.php b/modules-available/statistics/pages/summary.inc.php index bf150f3e..905f5d90 100644 --- a/modules-available/statistics/pages/summary.inc.php +++ b/modules-available/statistics/pages/summary.inc.php @@ -8,16 +8,15 @@ class SubPage public static function doPreprocess() { User::assertPermission('view.summary'); + if (!Module::isAvailable('js_chart')) { + ErrorHandler::traceError('js_chart not available'); + } } public static function doRender() { - $sortColumn = Request::any('sortColumn'); - $sortDirection = Request::any('sortDirection'); - - $filters = StatisticsFilter::parseQuery(StatisticsFilter::getQuery()); + $filters = StatisticsFilter::parseQuery(); $filterSet = new StatisticsFilterSet($filters); - $filterSet->setSort($sortColumn, $sortDirection); if (!$filterSet->setAllowedLocationsFromPermission('view.summary')) { Message::addError('main.no-permission'); @@ -27,12 +26,14 @@ class SubPage // Prepare chart colors self::$STATS_COLORS = []; for ($i = 0; $i < 10; ++$i) { - self::$STATS_COLORS[] = '#55' . sprintf('%02s%02s', dechex((($i + 1) * ($i + 1)) / .3922), dechex(abs((5 - $i) * 51))); + self::$STATS_COLORS[] = '#55' . sprintf('%02s%02s', dechex( + (int)((($i + 1) * ($i + 1)) / .3922)), + dechex((int)(abs((5 - $i) * 51)))); } $filterSet->filterNonClients(); + StatisticsFilter::renderFilterBox('summary', $filterSet); Render::openTag('div', array('class' => 'row')); - StatisticsFilter::renderFilterBox('summary', $filterSet, StatisticsFilter::getQuery()); self::showSummary($filterSet); self::showMemory($filterSet); self::showId44($filterSet); @@ -42,12 +43,9 @@ class SubPage Render::closeTag('div'); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showSummary($filterSet) + private static function showSummary(StatisticsFilterSet $filterSet): void { - $filterSet->makeFragments($where, $join, $sort, $args); + $filterSet->makeFragments($where, $join, $args); $known = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE $where", $args); $on = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE state IN ('IDLE', 'OCCUPIED') AND ($where)", $args); $used = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE state = 'OCCUPIED' AND ($where)", $args); @@ -57,175 +55,204 @@ class SubPage } else { $usedpercent = 0; } - $data = array( + $data = [ 'known' => $known['val'], 'online' => $on['val'], 'used' => $used['val'], 'usedpercent' => $usedpercent, 'badhdd' => $hdd['val'], - ); + ]; // Graph - $cutoff = time() - 2 * 86400; - $res = Database::simpleQuery("SELECT dateline, data FROM statistic WHERE typeid = '~stats' AND dateline > $cutoff ORDER BY dateline ASC"); - $labels = array(); - $points1 = array('data' => array(), 'label' => 'Online', 'fillColor' => '#efe', 'strokeColor' => '#aea', 'pointColor' => '#7e7', 'pointStrokeColor' => '#fff', 'pointHighlightFill' => '#fff', 'pointHighlightStroke' => '#7e7'); - $points2 = array('data' => array(), 'label' => 'In use', 'fillColor' => '#fee', 'strokeColor' => '#eaa', 'pointColor' => '#e77', 'pointStrokeColor' => '#fff', 'pointHighlightFill' => '#fff', 'pointHighlightStroke' => '#e77'); - $sum = 0; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $x = explode('#', $row['data']); - if ($sum === 0) { - $labels[] = date('H:i', $row['dateline']); + $labels = []; + $points1 = []; + $points2 = []; + $lectures = []; + // Get locations + if ($filterSet->suitableForUsageGraph()) { + $locFilter = $filterSet->hasFilter('LocationStatisticsFilter'); + if ($locFilter === null + || ($locFilter->op === '~' && ($locFilter->argument == 0 + || (is_array($locFilter->argument) && in_array(0, $locFilter->argument))))) { + $locations = null; + $op = null; + } elseif ($locFilter->op === '~') { + $locations = array_keys(Location::getRecursiveFlat($locFilter->argument)); + $op = $locFilter->op; } else { - $x[1] = max($x[1], array_pop($points1['data'])); - $x[2] = max($x[2], array_pop($points2['data'])); + if (is_array($locFilter->argument)) { + $locations = $locFilter->argument; + } else { + $locations = [$locFilter->argument]; + } + $op = $locFilter->op; } - $points1['data'][] = $x[1]; - $points2['data'][] = $x[2]; - ++$sum; - if ($sum === 12) { - $sum = 0; + //error_log($op . ' ' . print_r($locations, true)); + $cutoff = time() - 2 * 86400; + $res = Database::simpleQuery("SELECT dateline, data FROM statistic + WHERE typeid = '~stats' AND dateline > $cutoff ORDER BY dateline DESC"); + // Get max from 4 consecutive values, which should be 4*5 = 20m + $sum = 0; + foreach ($res as $row) { + if ($row['data'][0] === '{') { + $x = json_decode($row['data'], true); + if (!is_array($x) || !isset($x['usage'])) + continue; + $x = self::mangleStatsJson($x, $locations, $op); + } else if ($locations === null) { + $x = explode('#', $row['data']); + if (count($x) < 3) + continue; + $x[] = 0; + } else { + continue; + } + if ($sum % 4 === 0) { + $labels[] = date('H:i', $row['dateline']); + } else { + $x[1] = max($x[1], array_pop($points1)); + $x[2] = max($x[2], array_pop($points2)); + $x[3] += array_pop($lectures); + } + $points1[] = $x[1]; + $points2[] = $x[2]; + $lectures[] = $x[3]; + ++$sum; } } - $data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2))); - $data['query'] = StatisticsFilter::getQuery(); + if (!empty($points1) && max($points1) > 0) { + $labels = array_reverse($labels); + $points1 = array_reverse($points1); + $points2 = array_reverse($points2); + $lectures = array_reverse($lectures); + $data['json'] = json_encode(['labels' => $labels, + 'datasets' => [ + ['data' => $points1, 'label' => 'Online', 'borderColor' => '#8f3'], + ['data' => $points2, 'label' => 'In use', 'borderColor' => '#e76'], + ]]); + $data['markings'] = json_encode($lectures); + } if (Module::get('runmode') !== false) { - $res = Database::queryFirst('SELECT Count(*) AS cnt FROM runmode'); + $res = Database::queryFirst('SELECT Count(*) AS cnt FROM machine m INNER JOIN runmode r USING (machineuuid)' + . " $join WHERE $where", $args); $data['runmode'] = $res['cnt']; } // Draw Render::addTemplate('summary', $data); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showSystemModels($filterSet) + private static function showSystemModels(StatisticsFilterSet $filterSet): void { - $filterSet->makeFragments($where, $join, $sort, $args); + $filterSet->makeFragments($where, $join, $args); $res = Database::simpleQuery('SELECT systemmodel, Round(AVG(realcores)) AS cores, Count(*) AS `count` FROM machine m' . " $join WHERE $where GROUP BY systemmodel ORDER BY `count` DESC, systemmodel ASC", $args); - $lines = array(); - $json = array(); + $lines = []; + $json = []; $id = 0; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { if (empty($row['systemmodel'])) { continue; } settype($row['count'], 'integer'); - $row['id'] = 'systemid' . $id; $row['urlsystemmodel'] = urlencode($row['systemmodel']); + $row['idx'] = count($lines); $lines[] = $row; - $json[] = array( + $json[] = [ 'color' => self::$STATS_COLORS[$id % count(self::$STATS_COLORS)], - 'label' => 'systemid' . $id, 'value' => $row['count'], - ); + ]; ++$id; } self::capChart($json, $lines, 0.92); - Render::addTemplate('cpumodels', array('rows' => $lines, 'query' => StatisticsFilter::getQuery(), 'json' => json_encode($json))); + Render::addTemplate('cpumodels', ['rows' => $lines, 'json' => json_encode($json)]); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showMemory($filterSet) + private static function alignBySteps(int $value, array $steps): int { - $filterSet->makeFragments($where, $join, $sort, $args); - $res = Database::simpleQuery("SELECT mbram, Count(*) AS `count` FROM machine m $join WHERE $where GROUP BY mbram", $args); - $lines = array(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $gb = (int)ceil($row['mbram'] / 1024); - for ($i = 1; $i < count(StatisticsFilter::SIZE_RAM); ++$i) { - if (StatisticsFilter::SIZE_RAM[$i] < $gb) { - continue; - } - if (StatisticsFilter::SIZE_RAM[$i] - $gb >= $gb - StatisticsFilter::SIZE_RAM[$i - 1]) { - --$i; - } - $gb = StatisticsFilter::SIZE_RAM[$i]; - break; + for ($i = 1; $i < count($steps); ++$i) { + if ($steps[$i] < $value) { + continue; } - if (isset($lines[$gb])) { - $lines[$gb] += $row['count']; - } else { - $lines[$gb] = $row['count']; + if ($steps[$i] - $value >= $value - $steps[$i - 1]) { + --$i; } + return $steps[$i]; + } + return $value; + } + + private static function showMemory(StatisticsFilterSet $filterSet): void + { + $filterSet->makeFragments($where, $join, $args); + $res = Database::simpleQuery("SELECT mbram, Count(*) AS `count` FROM machine m $join + WHERE $where GROUP BY mbram", $args); + $lines = []; + foreach ($res as $row) { + $gb = self::alignBySteps((int)ceil($row['mbram'] / 1024), StatisticsFilter::SIZE_RAM); + $lines[$gb] = ($lines[$gb] ?? 0) + $row['count']; } asort($lines); - $data = array('rows' => array()); - $json = array(); + $data = ['rows' => []]; + $json = []; $id = 0; foreach (array_reverse($lines, true) as $k => $v) { - $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => StatisticsStyling::ramColorClass($k * 1024)); - $json[] = array( + $data['rows'][] = [ + 'idx' => count($data['rows']), + 'gb' => $k, + 'count' => $v, + 'class' => StatisticsStyling::ramColorClass($k * 1024), + ]; + $json[] = [ 'color' => self::$STATS_COLORS[$id % count(self::$STATS_COLORS)], - 'label' => (string)$k, 'value' => $v, - ); + ]; ++$id; } self::capChart($json, $data['rows'], 0.92); $data['json'] = json_encode($json); - $data['query'] = StatisticsFilter::getQuery(); Render::addTemplate('memory', $data); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showKvmState($filterSet) + private static function showKvmState(StatisticsFilterSet $filterSet): void { - $filterSet->makeFragments($where, $join, $sort, $args); - $colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6'); - $res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine m $join WHERE $where GROUP BY kvmstate ORDER BY `count` DESC", $args); - $lines = array(); - $json = array(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $filterSet->makeFragments($where, $join, $args); + $colors = ['UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6']; + $res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine m $join + WHERE $where GROUP BY kvmstate ORDER BY `count` DESC", $args); + $lines = []; + $json = []; + foreach ($res as $row) { + $row['idx'] = count($lines); $lines[] = $row; $json[] = array( - 'color' => isset($colors[$row['kvmstate']]) ? $colors[$row['kvmstate']] : '#000', - 'label' => $row['kvmstate'], + 'color' => $colors[$row['kvmstate']] ?? '#000', 'value' => $row['count'], ); } - Render::addTemplate('kvmstate', array('rows' => $lines, 'query' => StatisticsFilter::getQuery(),'json' => json_encode($json))); + Render::addTemplate('kvmstate', array('rows' => $lines, 'json' => json_encode($json))); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showId44($filterSet) + private static function showId44(StatisticsFilterSet $filterSet): void { - $filterSet->makeFragments($where, $join, $sort, $args); + $filterSet->makeFragments($where, $join, $args); $res = Database::simpleQuery("SELECT id44mb, Count(*) AS `count` FROM machine m $join WHERE $where GROUP BY id44mb", $args); $lines = array(); $total = 0; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { $total += $row['count']; - $gb = (int)ceil($row['id44mb'] / 1024); - for ($i = 1; $i < count(StatisticsFilter::SIZE_ID44); ++$i) { - if (StatisticsFilter::SIZE_ID44[$i] < $gb) { - continue; - } - if (StatisticsFilter::SIZE_ID44[$i] - $gb >= $gb - StatisticsFilter::SIZE_ID44[$i - 1]) { - --$i; - } - $gb = StatisticsFilter::SIZE_ID44[$i]; - break; - } - if (isset($lines[$gb])) { - $lines[$gb] += $row['count']; - } else { - $lines[$gb] = $row['count']; - } + $gb = self::alignBySteps((int)ceil($row['id44mb'] / 1024), StatisticsFilter::SIZE_PARTITION); + $lines[$gb] = ($lines[$gb] ?? 0) + $row['count']; } asort($lines); $data = array('rows' => array()); $json = array(); $id = 0; foreach (array_reverse($lines, true) as $k => $v) { - $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => StatisticsStyling::hddColorClass($k)); + $data['rows'][] = [ + 'idx' => count($data['rows']), + 'gb' => $k, + 'count' => $v, + 'class' => StatisticsStyling::hddColorClass($k), + ]; if ($k === 0) { $color = '#e55'; } else { @@ -233,29 +260,26 @@ class SubPage } $json[] = array( 'color' => $color, - 'label' => (string)$k, 'value' => $v, ); } self::capChart($json, $data['rows'], 0.95); $data['json'] = json_encode($json); - $data['query'] = StatisticsFilter::getQuery(); Render::addTemplate('id44', $data); } - /** - * @param \StatisticsFilterSet $filterSet - */ - private static function showLatestMachines($filterSet) + private static function showLatestMachines(StatisticsFilterSet $filterSet): void { - $filterSet->makeFragments($where, $join, $sort, $args); + $filterSet->makeFragments($where, $join, $args); $args['cutoff'] = ceil(time() / 3600) * 3600 - 86400 * 10; - $res = Database::simpleQuery("SELECT machineuuid, clientip, hostname, firstseen, mbram, kvmstate, id44mb FROM machine m $join" - . " WHERE firstseen > :cutoff AND $where ORDER BY firstseen DESC LIMIT 32", $args); + $res = Database::simpleQuery("SELECT m.machineuuid, m.clientip, m.hostname, m.firstseen, m.mbram, m.kvmstate, m.id44mb + FROM machine m $join + WHERE firstseen > :cutoff AND $where + ORDER BY firstseen DESC LIMIT 32", $args); $rows = array(); $count = 0; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { if (empty($row['hostname'])) { $row['hostname'] = $row['clientip']; } @@ -263,9 +287,9 @@ class SubPage $row['firstseen'] = Util::prettyTime($row['firstseen']); $row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding.. $row['gbtmp'] = round($row['id44mb'] / 1024); - $row['ramclass'] = StatisticsStyling::ramColorClass($row['mbram']); + $row['ramclass'] = StatisticsStyling::ramColorClass((int)$row['mbram']); $row['kvmclass'] = StatisticsStyling::kvmColorClass($row['kvmstate']); - $row['hddclass'] = StatisticsStyling::hddColorClass($row['gbtmp']); + $row['hddclass'] = StatisticsStyling::hddColorClass((int)$row['gbtmp']); $row['kvmicon'] = $row['kvmstate'] === 'ENABLED' ? '✓' : '✗'; if (++$count > 5) { $row['collapse'] = 'collapse'; @@ -281,7 +305,7 @@ class SubPage - private static function capChart(&$json, &$rows, $cutoff, $minSlice = 0.015) + private static function capChart(array &$json, array &$rows, float $cutoff, float $minSlice = 0.015): void { $total = 0; foreach ($json as $entry) { @@ -313,4 +337,28 @@ class SubPage } } -}
\ No newline at end of file + /** + * @param array $json decoded json ~stats data + * @param ?int[] $locations + */ + private static function mangleStatsJson(array $json, ?array $locations, ?string $op): array + { + // Total, On, InUse, Lectures + $retval = [0, 0, 0, 0]; + foreach ($json['usage'] as $lid => $data) { + $lid = (int)$lid; + if ($locations === null + || ($op === '!=' && !in_array($lid, $locations)) + || ($op !== '!=' && in_array($lid, $locations))) { + $retval[0] += $data['t']; + $retval[1] += $data['o'] ?? 0; + $retval[2] += $data['u'] ?? 0; + if (isset($data['event'])) { + $retval[3] += 1; + } + } + } + return $retval; + } + +} |