diff options
author | Simon Rettberg | 2017-12-01 13:42:04 +0100 |
---|---|---|
committer | Simon Rettberg | 2017-12-01 13:42:04 +0100 |
commit | 5393703f6e1485ddff94a50f63bcdd216ab629f4 (patch) | |
tree | ce2db7130302bd4fb78b170e1fbd75496a1b4510 /modules-available/statistics | |
parent | Merge remote-tracking branch 'origin/permission-manager' into permission-manager (diff) | |
parent | [roomplanner] Sort already placed machines to the bottom (diff) | |
download | slx-admin-5393703f6e1485ddff94a50f63bcdd216ab629f4.tar.gz slx-admin-5393703f6e1485ddff94a50f63bcdd216ab629f4.tar.xz slx-admin-5393703f6e1485ddff94a50f63bcdd216ab629f4.zip |
Merge branch 'master' into permission-manager
Diffstat (limited to 'modules-available/statistics')
17 files changed, 431 insertions, 175 deletions
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index c395220a..a614658a 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -34,7 +34,7 @@ if ($type{0} === '~') { // External mode of operation? $mode = Request::post('mode', false, 'string'); $NOW = time(); - $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); + $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($old !== false) { settype($old['logintime'], 'integer'); settype($old['lastseen'], 'integer'); @@ -43,7 +43,7 @@ if ($type{0} === '~') { // Handle event type if ($mode === false && $type === '~poweron') { // Poweron & hw stats - $uptime = Request::post('uptime', '', 'integer'); + $uptime = Request::post('uptime', 0, 'integer'); if (strlen($macaddr) > 17) die("Invalid MAC.\n"); if ($uptime < 0 || $uptime > 4000000) die("Implausible uptime.\n"); $realcores = Request::post('realcores', 0, 'integer'); @@ -64,6 +64,65 @@ if ($type{0} === '~') { $hostname = ''; } $data = Request::post('data', '', 'string'); + // Prepare insert/update to machine table + $new = array( + 'uuid' => $uuid, + 'macaddr' => $macaddr, + 'clientip' => $ip, + 'lastseen' => $NOW, + 'lastboot' => $NOW - $uptime, + 'realcores' => $realcores, + 'mbram' => $mbram, + 'kvmstate' => $kvmstate, + 'cpumodel' => $cpumodel, + 'systemmodel'=> $systemmodel, + 'id44mb' => $id44mb, + 'badsectors' => $badsectors, + 'data' => $data, + 'state' => 'IDLE', + ); + // Create/update machine entry + if ($old === false) { + $new['firstseen'] = $NOW; + $new['hostname'] = $hostname; + $res = Database::exec('INSERT INTO machine ' + . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,' + . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname, state) VALUES ' + . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram," + . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname, :state)', $new, true); + if ($res === false) { + die("Concurrent insert, ignored. (RESULT=0)\n"); + } + } else { + // Update + $moresql = ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : ''); + if (!empty($hostname)) { + $new['hostname'] = $hostname; + $moresql .= ' hostname = :hostname,'; + } + $new['oldstate'] = $old['state']; + $new['oldlastseen'] = $old['lastseen']; + $res = Database::exec('UPDATE machine SET ' + . ' macaddr = :macaddr,' + . ' clientip = :clientip,' + . ' lastseen = :lastseen,' + . ' lastboot = :lastboot,' + . $moresql + . ' realcores = :realcores,' + . ' mbram = :mbram,' + . ' kvmstate = :kvmstate,' + . ' cpumodel = :cpumodel,' + . ' systemmodel = :systemmodel,' + . ' id44mb = :id44mb,' + . ' badsectors = :badsectors,' + . ' data = :data,' + . ' state = :state ' + . " WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", $new); + if ($res === 0) { + die("Concurrent update, ignored. (RESULT=0)\n"); + } + } + // Maybe log old crashed session if ($uptime < 120) { // See if we have a lingering session, create statistic entry if so if ($old !== false && $old['logintime'] !== 0) { @@ -93,49 +152,17 @@ if ($type{0} === '~') { } } } - // Create/update machine entry - Database::exec('INSERT INTO machine ' - . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,' - . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname) VALUES ' - . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram," - . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname)' - . ' ON DUPLICATE KEY UPDATE' - . ' macaddr = VALUES(macaddr),' - . ' clientip = VALUES(clientip),' - . ' lastseen = VALUES(lastseen),' - . ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : '') - . ' lastboot = VALUES(lastboot),' - . ' realcores = VALUES(realcores),' - . ' mbram = VALUES(mbram),' - . ' kvmstate = VALUES(kvmstate),' - . ' cpumodel = VALUES(cpumodel),' - . ' systemmodel = VALUES(systemmodel),' - . ' id44mb = VALUES(id44mb),' - . ' badsectors = VALUES(badsectors),' - . ' data = VALUES(data),' - . " hostname = If(VALUES(hostname) = '', hostname, VALUES(hostname))", array( - 'uuid' => $uuid, - 'macaddr' => $macaddr, - 'clientip' => $ip, - 'firstseen' => $NOW, - 'lastseen' => $NOW, - 'lastboot' => $NOW - $uptime, - 'realcores' => $realcores, - 'mbram' => $mbram, - 'kvmstate' => $kvmstate, - 'cpumodel' => $cpumodel, - 'systemmodel'=> $systemmodel, - 'id44mb' => $id44mb, - 'badsectors' => $badsectors, - 'data' => $data, - 'hostname' => $hostname, - )); if (($old === false || $old['clientip'] !== $ip) && Module::isAvailable('locations')) { // New, or ip changed (dynamic pool?), update subnetlicationid Location::updateMapIpToLocation($uuid, $ip); } + // Check for suspicious hardware changes + if ($old !== false) { + checkHardwareChange($old, $new); + } + // Write statistics data } else if ($type === '~runstate') { @@ -147,7 +174,7 @@ if ($type{0} === '~') { die("Address changed.\n"); } $used = Request::post('used', 0, 'integer'); - if ($old['lastboot'] === 0 && $NOW - $old['lastseen'] > 300) { + if ($old['state'] === 'OFFLINE' && $NOW - $old['lastseen'] > 600) { $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; } else { $strUpdateBoottime = ''; @@ -161,31 +188,42 @@ if ($type{0} === '~') { } $old['logintime'] = 0; } - $old['lastboot'] = 0; } // Figure out what's happening - state changes - if ($used === 0 && $old['logintime'] !== 0) { + $params = array( + 'uuid' => $uuid, + 'oldlastseen' => $old['lastseen'], + 'oldstate' => $old['state'], + ); + if ($used === 0 && $old['state'] !== 'IDLE') { // Is not in use, was in use before $sessionLength = $NOW - $old['logintime']; - Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' + $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime - . ' logintime = 0, currentuser = NULL WHERE machineuuid = :uuid', array('uuid' => $uuid)); - } elseif ($used === 1 && $old['logintime'] === 0) { + . " logintime = 0, currentuser = NULL, state = 'IDLE' " + . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate", + $params); + } elseif ($used === 1 && $old['state'] !== 'OCCUPIED') { // Machine is in use, was free before if ($sessionLength !== 0 || $old['logintime'] === 0) { // This event is a start of a new session, rather than an update - Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' + $params['user'] = Request::post('user', null, 'string'); + $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime - . ' logintime = UNIX_TIMESTAMP(), currentuser = :user WHERE machineuuid = :uuid', array( - 'uuid' => $uuid, - 'user' => Request::post('user', null, 'string'), - )); + . " logintime = UNIX_TIMESTAMP(), currentuser = :user, currentsession = NULL, state = 'OCCUPIED' " + . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate", $params); + } else { + $res = 0; } } else { // Nothing changed, simple lastseen update - Database::exec('UPDATE machine SET ' + $res = Database::exec('UPDATE machine SET ' . $strUpdateBoottime - . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid', array('uuid' => $uuid)); + . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate', $params); + } + // Did we update, or was there a concurrent update? + if ($res === 0) { + die("Concurrent update, ignored. (RESULT=0)\n"); } // 9) Log last session length if applicable if ($mode === false && $sessionLength > 0 && $sessionLength < 86400*2 && $old['logintime'] !== 0) { @@ -215,8 +253,11 @@ if ($type{0} === '~') { )); } } - Database::exec('UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), lastboot = 0 WHERE machineuuid = :uuid', array('uuid' => $uuid)); + Database::exec("UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), state = 'OFFLINE' + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); } elseif ($mode === false && $type === '~screens') { + if ($old === false) die("Unknown machine.\n"); $screens = Request::post('screen', false, 'array'); if (is_array($screens)) { // `devicetype`, `devicename`, `subid`, `machineuuid` @@ -287,6 +328,47 @@ if ($type{0} === '~') { } } + } else if ($type === '~suspend') { + // Client entering suspend + if ($old === false) die("Unknown machine.\n"); + if ($old['clientip'] !== $ip) { + EventLog::warning("[suspend] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); + die("Address changed.\n"); + } + if ($NOW - $old['lastseen'] < 610 && $old['state'] !== 'OFFLINE') { + Database::exec("UPDATE machine SET lastseen = UNIX_TIMESTAMP(), state = 'STANDBY' + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); + } else { + EventLog::info("[suspend] Client $uuid reported switch to standby when it wasn't powered on first. Was: " . $old['state']); + } + } else if ($type === '~resume') { + // Waking up from suspend + if ($old === false) die("Unknown machine.\n"); + if ($old['clientip'] !== $ip) { + EventLog::info("[resume] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip), allowed on resume."); + } + if ($old['state'] === 'STANDBY') { + $res = Database::exec("UPDATE machine SET state = 'IDLE', clientip = :ip, lastseen = UNIX_TIMESTAMP() + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'ip' => $ip, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); + // Write standby period length to statistic table + if ($mode === false && $res > 0 && $old['lastseen'] !== 0) { + $lastSeen = $old['lastseen']; + $duration = $NOW - $lastSeen; + if ($duration > 500 && $duration < 86400 * 14) { + Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' + . " VALUES (:suspend, '~suspend-length', :uuid, :clientip, '', :length)", array( + 'suspend' => $lastSeen, + 'uuid' => $uuid, + 'clientip' => $ip, + 'length' => $duration + )); + } + } + } else { + EventLog::info("[resume] Client $uuid reported wakeup from standby when it wasn't logged as being in standby. Was: " . $old['state']); + } } else { die("INVALID ACTION '$type'\n"); } @@ -315,6 +397,7 @@ function writeStatisticLog($type, $username, $data) )); } + // For backwards compat, we require the . prefix if ($type{0} === '.') { if ($type === '.vmchooser-session') { @@ -336,4 +419,22 @@ if ($type{0} === '.') { } } +/** + * @param array $old row from DB with client's old data + * @param array $new new data to be written + */ +function checkHardwareChange($old, $new) +{ + if ($new['mbram'] !== 0) { + if ($new['mbram'] + 1000 < $old['mbram']) { + $ram1 = round($old['mbram'] / 512) / 2; + $ram2 = round($new['mbram'] / 512) / 2; + EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM decreased from {$ram1}GB to {$ram2}GB"); + } + if (!empty($old['cpumodel']) && !empty($new['cpumodel']) && $new['cpumodel'] !== $old['cpumodel']) { + EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): CPU changed from '{$old['cpumodel']}' to '{$new['cpumodel']}'"); + } + } +} + echo "OK.\n"; diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index 575ab6ba..4df7b0d4 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -1,18 +1,33 @@ <?php -function logstats() { +function logstats() +{ $NOW = time(); $cutoff = $NOW - 86400 * 30; - $online = $NOW - 610; - $known = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $cutoff"); - $on = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online"); - $used = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online AND logintime <> 0"); + $join = $where = ''; + if (Module::get('runmode') !== false) { + $join = 'LEFT JOIN runmode r USING (machineuuid)'; + $where = 'AND (r.isclient IS NULL OR r.isclient <> 0)'; + } + $known = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.lastseen > $cutoff $where"); + $on = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state IN ('IDLE', 'OCCUPIED') $where"); + $used = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state = 'OCCUPIED' $where"); Database::exec("INSERT INTO statistic (dateline, typeid, clientip, username, data) VALUES (:now, '~stats', '', '', :vals)", array( 'now' => $NOW, 'vals' => $known['val'] . '#' . $on['val'] . '#' . $used['val'], )); } +function state_cleanup() +{ + // Fix online state of machines that crashed + $standby = time() - 86400 * 2; // Reset standby machines after two days + $on = time() - 610; // Reset others after ~10 minutes + Database::exec("UPDATE machine SET state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); +} + +state_cleanup(); + logstats(); if (mt_rand(1, 10) === 1) { diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php index 0afce572..6e437a71 100644 --- a/modules-available/statistics/inc/filter.inc.php +++ b/modules-available/statistics/inc/filter.inc.php @@ -29,7 +29,7 @@ class Filter $addendum = ''; /* check if we have to do some parsing*/ - if (Page_Statistics::$columns[$this->column]['type'] == 'date') { + if (Page_Statistics::$columns[$this->column]['type'] === 'date') { $args[$key] = strtotime($this->argument); } else { $args[$key] = $this->argument; @@ -180,21 +180,18 @@ class StateFilter extends Filter { public function __construct($operator, $argument) { - $this->operator = $operator; - $this->argument = $argument; + parent::__construct(null, $operator, $argument); } public function whereClause(&$args, &$joins) { + $map = [ 'on' => ['IDLE', 'OCCUPIED'], 'off' => ['OFFLINE'], 'idle' => ['IDLE'], 'occupied' => ['OCCUPIED'], 'standby' => ['STANDBY'] ]; $neg = $this->operator == '!=' ? 'NOT ' : ''; - if ($this->argument === 'on') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() ) "; - } elseif ($this->argument === 'off') { - return " $neg (lastseen + 600 < UNIX_TIMESTAMP() ) "; - } elseif ($this->argument === 'idle') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ) "; - } elseif ($this->argument === 'occupied') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ) "; + if (array_key_exists($this->argument, $map)) { + global $unique_key; + $key = $this->column . '_arg' . ($unique_key++); + $args[$key] = $map[$this->argument]; + return " machine.state $neg IN ( :$key ) "; } else { Message::addError('invalid-filter-argument', 'state', $this->argument); return ' 1'; @@ -216,8 +213,10 @@ class LocationFilter extends Filter $neg = $this->operator === '=' ? '' : 'NOT'; return "machine.locationid IS $neg NULL"; } else { - $args['lid'] = $this->argument; - return "machine.locationid {$this->operator} :lid"; + global $unique_key; + $key = $this->column . '_arg' . ($unique_key++); + $args[$key] = $this->argument; + return "machine.locationid {$this->operator} :$key"; } } } @@ -236,3 +235,20 @@ class SubnetFilter extends Filter } } +class IsClientFilter extends Filter +{ + public function __construct($argument) + { + parent::__construct(null, null, $argument); + } + + public function whereClause(&$args, &$joins) + { + if ($this->argument) { + $joins[] = ' LEFT JOIN runmode USING (machineuuid)'; + return "(runmode.isclient <> 0 OR runmode.isclient IS NULL)"; + } + $joins[] = ' INNER JOIN runmode USING (machineuuid)'; + return "runmode.isclient = 0"; + } +} diff --git a/modules-available/statistics/inc/filterset.inc.php b/modules-available/statistics/inc/filterset.inc.php index c73feeef..25c5c8fa 100644 --- a/modules-available/statistics/inc/filterset.inc.php +++ b/modules-available/statistics/inc/filterset.inc.php @@ -2,6 +2,9 @@ class FilterSet { + /** + * @var \Filter[] + */ private $filters; private $sortDirection; private $sortColumn; @@ -39,7 +42,7 @@ class FilterSet $where .= $sep . $filter->whereClause($args, $joins); } } - $join = implode('', array_unique($joins)); + $join = implode(' ', array_unique($joins)); $col = $this->sortColumn; $isMapped = array_key_exists('map_sort', Page_Statistics::$columns[$col]); @@ -72,4 +75,13 @@ class FilterSet { return $this->sortColumn; } + + public function filterNonClients() + { + if (Module::get('runmode') === false) + return; + // Runmode module exists, add filter + $this->filters[] = new IsClientFilter(true); + } + } diff --git a/modules-available/statistics/inc/machine.inc.php b/modules-available/statistics/inc/machine.inc.php index 8cb5e884..8605749b 100644 --- a/modules-available/statistics/inc/machine.inc.php +++ b/modules-available/statistics/inc/machine.inc.php @@ -51,6 +51,11 @@ class Machine public $logintime; /** + * @var string state of machine (OFFLINE, IDLE, OCCUPIED, STANDBY) + */ + public $state; + + /** * @var string json data of position inside room (if any), null/empty otherwise */ public $position; diff --git a/modules-available/statistics/inc/statistics.inc.php b/modules-available/statistics/inc/statistics.inc.php index 1c9ebf07..2500f16f 100644 --- a/modules-available/statistics/inc/statistics.inc.php +++ b/modules-available/statistics/inc/statistics.inc.php @@ -7,17 +7,12 @@ class Statistics private static $machineFields = false; - /** - * @param string $machineuuid - * @param int $returnData - * @return \Machine|false - */ - public static function getMachine($machineuuid, $returnData) + private static function initFields($returnData) { if (self::$machineFields === false) { $r = new ReflectionClass('Machine'); $props = $r->getProperties(ReflectionProperty::IS_PUBLIC); - self::$machineFields = array_flip(array_map(function($e) { return $e->getName(); }, $props)); + self::$machineFields = array_flip(array_map(function(/* @var ReflectionProperty $e */ $e) { return $e->getName(); }, $props)); } if ($returnData === Machine::NO_DATA) { unset(self::$machineFields['data']); @@ -26,8 +21,19 @@ class Statistics } else { Util::traceError('Invalid $returnData option passed'); } - $fields = implode(',', array_keys(self::$machineFields)); - $row = Database::queryFirst("SELECT * FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid')); + return implode(',', array_keys(self::$machineFields)); + } + + /** + * @param string $machineuuid + * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ... + * @return \Machine|false + */ + public static function getMachine($machineuuid, $returnData) + { + $fields = self::initFields($returnData); + + $row = Database::queryFirst("SELECT $fields FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid')); if ($row === false) return false; $m = new Machine(); @@ -37,4 +43,31 @@ class Statistics return $m; } + /** + * @param string $ip + * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ... + * @param string $sort something like 'lastseen ASC' - not sanitized, don't pass user input! + * @return \Machine[] list of matches + */ + public static function getMachinesByIp($ip, $returnData, $sort = false) + { + $fields = self::initFields($returnData); + + if ($sort === false) { + $sort = ''; + } else { + $sort = "ORDER BY $sort"; + } + $res = Database::simpleQuery("SELECT $fields FROM machine WHERE clientip = :ip $sort", compact('ip')); + $list = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $m = new Machine(); + foreach ($row as $key => $val) { + $m->{$key} = $val; + } + $list[] = $m; + } + return $list; + } + } diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php index bfa342c4..4e2dfcca 100644 --- a/modules-available/statistics/install.inc.php +++ b/modules-available/statistics/install.inc.php @@ -36,6 +36,7 @@ $res[] = $machineCreate = tableCreate('machine', " `logintime` int(10) unsigned NOT NULL, `position` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, `lastboot` int(10) unsigned NOT NULL, + `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE', `realcores` smallint(5) unsigned NOT NULL, `mbram` int(10) unsigned NOT NULL, `kvmstate` enum('UNKNOWN','UNSUPPORTED','DISABLED','ENABLED') NOT NULL, @@ -51,6 +52,7 @@ $res[] = $machineCreate = tableCreate('machine', " PRIMARY KEY (`machineuuid`), KEY `macaddr` (`macaddr`), KEY `clientip` (`clientip`), + KEY `state` (`state`), KEY `realcores` (`realcores`), KEY `mbram` (`mbram`), KEY `kvmstate` (`kvmstate`), @@ -198,6 +200,7 @@ if ($addTrigger) { finalResponse(UPDATE_RETRY, 'Locations module not installed yet, retry later'); } } + $res[] = UPDATE_DONE; } if ($machineHwCreate === UPDATE_DONE) { @@ -217,12 +220,19 @@ if ($machineHwCreate === UPDATE_DONE) { if ($ret === false) { finalResponse(UPDATE_FAILED, 'Adding constraint to statistic_hw_prop failed: ' . Database::lastError()); } + $res[] = UPDATE_DONE; } -// Create response - -if (in_array(UPDATE_DONE, $res)) { - finalResponse(UPDATE_DONE, 'Tables created successfully'); +// 2017-11-27: Add state column +if (!tableHasColumn('machine', 'state')) { + $ret = Database::exec("ALTER TABLE `machine` + ADD COLUMN `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE' AFTER `lastboot`, + ADD INDEX `state` (`state`)"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, 'Adding state column to machine table failed: ' . Database::lastError()); + } + $res[] = UPDATE_DONE; } -finalResponse(UPDATE_NOOP, 'Everything already up to date'); +// Create response +responseFromArray($res); diff --git a/modules-available/statistics/lang/de/messages.json b/modules-available/statistics/lang/de/messages.json index 8bdf9cfc..e3dff61c 100644 --- a/modules-available/statistics/lang/de/messages.json +++ b/modules-available/statistics/lang/de/messages.json @@ -1,6 +1,6 @@ { - "invalid-filter": "Ung\u00fcltiger Filter", "invalid-filter-argument": "Das Argument {{1}} ist nicht g\u00fcltig f\u00fcr den Filter {{0}}", "invalid-filter-key": "{{0}} ist kein g\u00fcltiges Filterkriterium", - "notes-saved": "Anmerkungen gespeichert" + "notes-saved": "Anmerkungen gespeichert", + "unknown-machine": "Unbekannte Rechner-ID {{0}}" }
\ No newline at end of file diff --git a/modules-available/statistics/lang/de/template-tags.json b/modules-available/statistics/lang/de/template-tags.json index 474d952a..56cf55d7 100644 --- a/modules-available/statistics/lang/de/template-tags.json +++ b/modules-available/statistics/lang/de/template-tags.json @@ -37,6 +37,7 @@ "lang_machineOccupied": "Der Rechner ist eingeschaltet und wird benutzt", "lang_machineOccupiedBy": "In Verwendung durch", "lang_machineOff": "Der Rechner ist ausgeschaltet, oder hat kein bwLehrpool gebootet", + "lang_machineStandby": "Im Standby", "lang_machineSummary": "Zusammenfassung", "lang_maximumAbbrev": "Max.", "lang_memoryStats": "Arbeitsspeicher", @@ -65,6 +66,8 @@ "lang_ramSlots": "Speicher-Slots", "lang_realCores": "Kerne", "lang_reallocatedSectors": "Defekte Sektoren", + "lang_runMode": "Betriebsmodus", + "lang_runmodeMachines": "Mit besonderem Betriebsmodus", "lang_screens": "Bildschirme", "lang_serialNo": "Serien-Nr", "lang_showList": "Liste", @@ -83,4 +86,4 @@ "lang_virtualCores": "Virtuelle Kerne", "lang_when": "Wann", "lang_withBadSectors": "Clients mit potentiell defekten Festplatten (mehr als 10 defekte Sektoren)" -} +}
\ No newline at end of file diff --git a/modules-available/statistics/lang/en/messages.json b/modules-available/statistics/lang/en/messages.json index 40eac8c9..ae6c47af 100644 --- a/modules-available/statistics/lang/en/messages.json +++ b/modules-available/statistics/lang/en/messages.json @@ -1,6 +1,6 @@ { - "invalid-filter": "Invalid filter", "invalid-filter-argument": "{{1}} is not a vald argument for filter {{0}}", "invalid-filter-key": "{{0}} is not a valid filter", - "notes-saved": "Notes have been saved" + "notes-saved": "Notes have been saved", + "unknown-machine": "Unknown machine uuid {{0}}" }
\ No newline at end of file diff --git a/modules-available/statistics/lang/en/template-tags.json b/modules-available/statistics/lang/en/template-tags.json index f514b894..ab7a7d0a 100644 --- a/modules-available/statistics/lang/en/template-tags.json +++ b/modules-available/statistics/lang/en/template-tags.json @@ -37,6 +37,7 @@ "lang_machineOccupied": "Machine is powered on and in use", "lang_machineOccupiedBy": "In use by", "lang_machineOff": "Machine is powered down, or is not running bwLehrpool", + "lang_machineStandby": "In standby mode", "lang_machineSummary": "Summary", "lang_maximumAbbrev": "max.", "lang_memoryStats": "Memory", @@ -65,6 +66,8 @@ "lang_ramSlots": "Memory slots", "lang_realCores": "Cores", "lang_reallocatedSectors": "Bad sectors", + "lang_runMode": "Mode of operation", + "lang_runmodeMachines": "With special mode of operation", "lang_screens": "Screens", "lang_serialNo": "Serial no", "lang_showList": "List", @@ -83,4 +86,4 @@ "lang_virtualCores": "Virtual cores", "lang_when": "When", "lang_withBadSectors": "Clients with potentially bad HDDs (more than 10 reallocated sectors)" -} +}
\ No newline at end of file diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index a003a303..d80d4614 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -106,6 +106,12 @@ class Page_Statistics extends Page 'op' => Page_Statistics::$op_nominal, 'type' => 'string', 'column' => true + ], + 'state' => [ + 'op' => Page_Statistics::$op_nominal, + 'type' => 'enum', + 'column' => true, + 'values' => ['occupied', 'on'] ] ]; if (Module::isAvailable('locations')) { @@ -190,8 +196,6 @@ class Page_Statistics extends Page } elseif ($action === 'addprojector' || $action === 'delprojector') { $this->handleProjector($action); } - // Fix online state of machines that crashed -- TODO: Make cronjob for this - Database::exec("UPDATE machine SET lastboot = 0 WHERE lastseen < UNIX_TIMESTAMP() - 610"); } protected function doRender() @@ -199,7 +203,12 @@ class Page_Statistics extends Page $uuid = Request::get('uuid', false, 'string'); if ($uuid !== false) { $this->showMachine($uuid); + return; + } + $show = Request::get('show', 'stat', 'string'); + if ($show === 'projectors') { + $this->showProjectors(); return; } @@ -210,23 +219,19 @@ class Page_Statistics extends Page } $sortColumn = Request::any('sortColumn'); $sortDirection = Request::any('sortDirection'); - $filters = Filter::parseQuery($this->query); + $filters = Filter::parseQuery($this->query); $filterSet = new FilterSet($filters); $filterSet->setSort($sortColumn, $sortDirection); - - $show = Request::get('show', 'stat', 'string'); if ($show == 'list') { Render::openTag('div', array('class' => 'row')); $this->showFilter('list', $filterSet); Render::closeTag('div'); $this->showMachineList($filterSet); return; - } elseif ($show === 'projectors') { - $this->showProjectors(); - return; } + $filterSet->filterNonClients(); Render::openTag('div', array('class' => 'row')); $this->showFilter('stat', $filterSet); $this->showSummary($filterSet); @@ -328,8 +333,8 @@ class Page_Statistics extends Page if ($known['val'] == 1) { $this->redirectFirst($where, $join, $args); } - $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND ($where)", $args); - $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND logintime <> 0 AND ($where)", $args); + $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state IN ('IDLE', 'OCCUPIED') AND ($where)", $args); + $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state = 'OCCUPIED' AND ($where)", $args); $hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE badsectors >= 10 AND ($where)", $args); if ($on['val'] != 0) { $usedpercent = round($used['val'] / $on['val'] * 100); @@ -367,6 +372,10 @@ class Page_Statistics extends Page } $data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2))); $data['query'] = $this->query; + if (Module::get('runmode') !== false) { + $res = Database::queryFirst('SELECT Count(*) AS cnt FROM runmode'); + $data['runmode'] = $res['cnt']; + } // Draw Render::addTemplate('summary', $data); } @@ -570,10 +579,16 @@ class Page_Statistics extends Page $xtra = ''; if ($filterSet->isNoId44Filter()) { - $xtra = ', data'; + $xtra .= ', data'; + } + if (Module::isAvailable('runmode')) { + $xtra .= ', runmode.module AS rmmodule'; + if (strpos($join, 'runmode') === false) { + $join .= ' LEFT JOIN runmode USING (machineuuid) '; + } } - $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, firstseen, lastseen,' - . ' logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,' + $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, lastseen,' + . ' logintime, state, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,' . ' badsectors ' . $xtra . ' FROM machine' . " $join WHERE $where $sort", $args); $rows = array(); @@ -585,13 +600,7 @@ class Page_Statistics extends Page } else { $singleMachine = false; } - if ($row['lastboot'] == 0) { - $row['state_off'] = true; - } elseif ($row['logintime'] == 0) { - $row['state_idle'] = true; - } else { - $row['state_occupied'] = true; - } + $row['state_' . $row['state']] = true; //$row['firstseen'] = date('d.m.Y H:i', $row['firstseen']); $row['lastseen_int'] = $row['lastseen']; $row['lastseen'] = date('d.m. H:i', $row['lastseen']); @@ -725,24 +734,40 @@ class Page_Statistics extends Page private function showMachine($uuid) { - $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot,' + $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state,' . ' mbram, kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); + if ($client === false) { + Message::addError('unknown-machine', $uuid); + return; + } // Hack: Get raw collected data if (Request::get('raw', false)) { Header('Content-Type: text/plain; charset=utf-8'); die($client['data']); } + // Runmode + if (Module::isAvailable('runmode')) { + $data = RunMode::getRunMode($uuid, RunMode::DATA_STRINGS); + if ($data !== false) { + $client += $data; + } + } + if (!isset($client['isclient'])) { + $client['isclient'] = true; + } // Mangle fields $NOW = time(); - if ($client['lastboot'] == 0) { - $client['state_off'] = true; - } elseif ($client['logintime'] == 0) { - $client['state_idle'] = true; + if (!$client['isclient']) { + if ($client['state'] === 'IDLE') { + $client['state'] = 'OCCUPIED'; + } } else { - $client['state_occupied'] = true; - $this->fillSessionInfo($client); + if ($client['state'] === 'OCCUPIED') { + $this->fillSessionInfo($client); + } } + $client['state_' . $client['state']] = true; $client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']); $client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']); if ($client['lastboot'] == 0) { @@ -750,7 +775,7 @@ class Page_Statistics extends Page } else { $uptime = $NOW - $client['lastboot']; $client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']); - if (!isset($client['state_off']) || !$client['state_off']) { + if ($client['state'] === 'IDLE' || $client['state'] === 'OCCUPIED') { $client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')'; } } @@ -833,6 +858,8 @@ class Page_Statistics extends Page $last = false; $first = true; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (!$client['isclient'] && $row['typeid'] === '~session-length') + continue; // Don't differentiate between session and idle for non-clients if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) { // Special case: offline before $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round((min($row['dateline'], $client['lastboot']) - $cutoff) * $scale, 2) . '%"> </div>'; @@ -865,12 +892,17 @@ class Page_Statistics extends Page $color = '#e77'; } $spans['graph'] .= '<div style="background:' . $color . ';left:' . round(($row['dateline'] - $cutoff) * $scale, 2) . '%;width:' . round(($row['data']) * $scale, 2) . '%"> </div>'; - $spans['rows'][] = $row; + if ($client['isclient']) { + $spans['rows'][] = $row; + } $last = $row; } if ($first && $client['lastboot'] > $cutoff) { // Special case: offline before $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round(($client['lastboot'] - $cutoff) * $scale, 2) . '%"> </div>'; + } elseif ($first) { + // Not seen in last two weeks + $spans['graph'] .= '<div style="background:#444;left:0%;width:100%"> </div>'; } if (isset($client['state_occupied'])) { $spans['graph'] .= '<div style="background:#e99;left:' . round(($client['logintime'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['logintime'] + 900) * $scale, 2) . '%"> </div>'; @@ -891,6 +923,7 @@ class Page_Statistics extends Page $spans['rows2'] = array_slice($spans['rows'], ceil(count($spans['rows']) / 2)); $spans['rows'] = array_slice($spans['rows'], 0, ceil(count($spans['rows']) / 2)); } + $spans['isclient'] = $client['isclient']; Render::addTemplate('machine-usage', $spans); // Any hdds? if (!empty($hdds['hdds'])) { @@ -922,7 +955,7 @@ class Page_Statistics extends Page } } Render::addTemplate('syslog', array( - 'clientip' => $client['clientip'], + 'machineuuid' => $client['machineuuid'], 'list' => $log, )); } diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html index ecfe622c..0b6fb37f 100644 --- a/modules-available/statistics/templates/clientlist.html +++ b/modules-available/statistics/templates/clientlist.html @@ -46,17 +46,21 @@ <tr> <td data-sort-value="{{hostname}}" class="text-nowrap"> {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}} - {{#state_off}} + {{#state_OFFLINE}} <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span> - {{/state_off}} - {{#state_idle}} + {{/state_OFFLINE}} + {{#state_IDLE}} <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span> - {{/state_idle}} - {{#state_occupied}} + {{/state_IDLE}} + {{#state_OCCUPIED}} <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span> - {{/state_occupied}} + {{/state_OCCUPIED}} + {{#state_STANDBY}} + <span class="glyphicon glyphicon-off green" title="{{lang_machineStandby}}"></span> + {{/state_STANDBY}} <a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a> <div class="small">{{machineuuid}}</div> + {{#rmmodule}}<div class="small">{{lang_runMode}}: <a class="slx-bold" href="?do=runmode&module={{rmmodule}}">{{rmmodule}}</a></div>{{/rmmodule}} </td> <td data-sort-value="{{clientip}}"><b><a href="?do=Statistics&show=list&filters=subnet={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td> <td data-sort-value="{{lastseen_int}}" class="text-right">{{lastseen}}</td> @@ -118,4 +122,4 @@ function toggleButton(v) { $queryForm.submit(); } -//--></script>
\ No newline at end of file +//--></script> diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html index 74df80c4..14d388d3 100644 --- a/modules-available/statistics/templates/machine-main.html +++ b/modules-available/statistics/templates/machine-main.html @@ -50,13 +50,13 @@ <tr> <td class="text-nowrap">{{lang_usageState}}</td> <td> - {{#state_off}} + {{#state_OFFLINE}} <span class="glyphicon glyphicon-off"></span> {{lang_machineOff}} - {{/state_off}} - {{#state_idle}} + {{/state_OFFLINE}} + {{#state_IDLE}} <span class="glyphicon glyphicon-ok green"></span> {{lang_machineIdle}} - {{/state_idle}} - {{#state_occupied}} + {{/state_IDLE}} + {{#state_OCCUPIED}} {{#username}} <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupiedBy}} <b>{{username}}</b> {{/username}} @@ -64,7 +64,10 @@ <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupied}} {{/username}} <div>{{logintime_s}}</div> - {{/state_occupied}} + {{/state_OCCUPIED}} + {{#state_STANDBY}} + <span class="glyphicon glyphicon-off green"></span> {{lang_machineStandby}} + {{/state_STANDBY}} {{#session}} <div> {{#lectureid}} @@ -77,6 +80,14 @@ {{/session}} </td> </tr> + {{#modeid}} + <tr> + <td class="text-nowrap">{{lang_runMode}}</td> + <td> + <a href="?do={{modeid}}">{{moduleName}}</a> – {{modeName}} + </td> + </tr> + {{/modeid}} </table> </div> </div> diff --git a/modules-available/statistics/templates/machine-usage.html b/modules-available/statistics/templates/machine-usage.html index ef969fc6..be435ee5 100644 --- a/modules-available/statistics/templates/machine-usage.html +++ b/modules-available/statistics/templates/machine-usage.html @@ -5,46 +5,50 @@ {{lang_usageDetails}} </div> <div class="panel-body"> - <div class="row"> - <div class="col-sm-6"> - <table class="table table-condensed"> - <tr> - <th>{{lang_eventType}}</th> - <th>{{lang_when}}</th> - <th>{{lang_duration}}</th> - </tr> - {{#rows}} - <tr> - <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> - <td>{{from}}</td> - <td>{{duration}}</td> - </tr> - {{/rows}} - </table> + {{#isclient}} + <div class="row"> + <div class="col-sm-6"> + <table class="table table-condensed"> + <tr> + <th>{{lang_eventType}}</th> + <th>{{lang_when}}</th> + <th>{{lang_duration}}</th> + </tr> + {{#rows}} + <tr> + <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> + <td>{{from}}</td> + <td>{{duration}}</td> + </tr> + {{/rows}} + </table> + </div> + <div class="col-sm-6"> + <table class="table table-condensed"> + {{#hasrows2}} + <tr> + <th>{{lang_eventType}}</th> + <th>{{lang_when}}</th> + <th>{{lang_duration}}</th> + </tr> + {{/hasrows2}} + {{#rows2}} + <tr> + <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> + <td>{{from}}</td> + <td>{{duration}}</td> + </tr> + {{/rows2}} + </table> + </div> </div> - <div class="col-sm-6"> - <table class="table table-condensed"> - {{#hasrows2}} - <tr> - <th>{{lang_eventType}}</th> - <th>{{lang_when}}</th> - <th>{{lang_duration}}</th> - </tr> - {{/hasrows2}} - {{#rows2}} - <tr> - <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> - <td>{{from}}</td> - <td>{{duration}}</td> - </tr> - {{/rows2}} - </table> - </div> - </div> + {{/isclient}} <div class="timebar"> {{{graph}}}</div> - <div> - {{lang_timebarDesc}} - </div> + {{#isclient}} + <div> + {{lang_timebarDesc}} + </div> + {{/isclient}} </div> </div> </div> diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html index 642c48fc..fe9559ed 100644 --- a/modules-available/statistics/templates/summary.html +++ b/modules-available/statistics/templates/summary.html @@ -1,6 +1,11 @@ <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-body"> + {{#runmode}} + <div class="pull-right"> + <a href="?do=runmode">{{lang_runmodeMachines}}</a>: <b>{{runmode}}</b> + </div> + {{/runmode}} <div> {{lang_knownMachines}}: <b>{{known}}</b>  <a href="?do=Statistics&show=stat&filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>  @@ -10,8 +15,9 @@ <div> <span class="glyphicon glyphicon-exclamation-sign red"></span> <a href="?do=Statistics&show=list&filters={{query}}~,~badsectors>=10"> - {{lang_withBadSectors}}: <b>{{badhdd}}</b> + {{lang_withBadSectors}}: </a> + <b>{{badhdd}}</b> </div> {{/badhdd}} </div> diff --git a/modules-available/statistics/templates/syslog.html b/modules-available/statistics/templates/syslog.html index c82cb8ac..968d32ab 100644 --- a/modules-available/statistics/templates/syslog.html +++ b/modules-available/statistics/templates/syslog.html @@ -20,7 +20,7 @@ {{/list}} </tbody> </table> -<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&ip={{clientip}}">{{lang_more}} »</a></div> +<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&machineuuid={{machineuuid}}">{{lang_more}} »</a></div> <div class="clearfix"></div> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |