summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorSimon Rettberg2018-01-04 18:34:28 +0100
committerSimon Rettberg2018-01-04 18:34:28 +0100
commit406cd79ff35ab1dd3ba796207f2ea471e87f17d5 (patch)
treee3efb7c338aee52126ccd16061872645c5e6cc43 /modules-available
parent[inc/Database] Add profiling info (timing, query count) (diff)
downloadslx-admin-406cd79ff35ab1dd3ba796207f2ea471e87f17d5.tar.gz
slx-admin-406cd79ff35ab1dd3ba796207f2ea471e87f17d5.tar.xz
slx-admin-406cd79ff35ab1dd3ba796207f2ea471e87f17d5.zip
[statistics_reporting] Query rewrite: Use iterative approach
Several simple test cases yielded incorrect results, also sessions partially overlapping with the selected hours were not handled properly. Iterative processing of logged events instead of single humongous unmaintainable SQL query.
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/statistics_reporting/inc/getdata.inc.php142
-rw-r--r--modules-available/statistics_reporting/inc/queries.inc.php512
-rw-r--r--modules-available/statistics_reporting/page.inc.php30
-rw-r--r--modules-available/statistics_reporting/style.css9
-rw-r--r--modules-available/statistics_reporting/templates/table-client.html50
-rw-r--r--modules-available/statistics_reporting/templates/table-location.html40
-rw-r--r--modules-available/statistics_reporting/templates/table-total.html40
7 files changed, 499 insertions, 324 deletions
diff --git a/modules-available/statistics_reporting/inc/getdata.inc.php b/modules-available/statistics_reporting/inc/getdata.inc.php
index ae4d5aa4..13d39502 100644
--- a/modules-available/statistics_reporting/inc/getdata.inc.php
+++ b/modules-available/statistics_reporting/inc/getdata.inc.php
@@ -10,24 +10,59 @@ class GetData
public static $lowerTimeBound = 0;
public static $upperTimeBound = 24;
public static $salt;
+ private static $TS_LIST = false;
+ private static $SECS_LIST = false;
+
+ private static function fillLocation(&$entry, $anonymize)
+ {
+ $locations = Location::getLocationsAssoc();
+ if ($anonymize) {
+ $entry['locationname'] = md5($entry['locationid'] . self::$salt);
+ } elseif (isset($locations[$entry['locationid']])) {
+ $entry['locationname'] = $locations[$entry['locationid']]['locationname'];
+ $entry['parentlocations'] = array_reduce($locations[$entry['locationid']]['parents'], function ($carry, $item) {
+ return $carry . sprintf("%04d", $item);
+ }) . sprintf("%04d", $entry['locationid']);
+ } else {
+ $entry['locationname'] = Dictionary::translate('notAssigned', true);
+ }
+ if ($anonymize) {
+ unset($entry['locationid']);
+ }
+ }
+
+ private static function addPrintables(&$entry)
+ {
+ if (self::$SECS_LIST === false) {
+ self::$SECS_LIST = ['totalTime', 'totalOffTime', 'totalIdleTime', 'totalSessionTime', 'totalStandbyTime', 'medianSessionLength'];
+ }
+ if (self::$TS_LIST === false) {
+ self::$TS_LIST = ['lastStart', 'lastLogout'];
+ }
+ $perc = isset($entry['totalTime']) && $entry['totalTime'] > 0;
+ foreach (self::$SECS_LIST as $k) {
+ if (isset($entry[$k])) {
+ $entry[$k . '_s'] = self::formatSeconds($entry[$k]);
+ if ($perc && $k !== 'totalTime') {
+ $entry[$k . '_p'] = round($entry[$k] / $entry['totalTime'] * 100);
+ }
+ }
+ }
+ foreach (self::$TS_LIST as $k) {
+ if (isset($entry[$k])) {
+ $entry[$k . '_s'] = Util::prettyTime($entry[$k]);
+ }
+ }
+ }
// total
public static function total($flags = 0) {
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
// total time online, average time online, total number of logins
- $res = Queries::getOverallStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $row = $res->fetch(PDO::FETCH_ASSOC);
- $data = array('totalTime' => $row['sum'], 'medianSessionLength' => self::calcMedian($row['median']), 'longSessions' => $row['longSessions'], 'shortSessions' => $row['shortSessions']);
-
- //total time offline
- $res = Queries::getTotalOfflineStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $row = $res->fetch(PDO::FETCH_ASSOC);
- $data['totalOffTime'] = $row['timeOff'];
+ $data = Queries::getOverallStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
if ($printable) {
- $data["totalTime_s"] = self::formatSeconds($data["totalTime"]);
- $data["medianSessionLength_s"] = self::formatSeconds($data["medianSessionLength"]);
- $data["totalOffTime_s"] = self::formatSeconds($data["totalOffTime"]);
+ self::addPrintables($data);
}
$data['uniqueUsers'] = Queries::getUniqueUserCount(self::$from, self::$to);
@@ -38,63 +73,29 @@ class GetData
public static function perLocation($flags = 0) {
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
- $res = Queries::getLocationStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $data = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- self::nullToZero($row);
- $median = self::calcMedian(self::calcMedian($row['medianSessionLength']));
- $entry = array(
- 'location' => ($anonymize ? $row['locHash'] : $row['locName']),
- 'totalTime' => $row['timeSum'],
- 'medianSessionLength' => $median,
- 'totalOffTime' => $row['offlineSum'],
- 'longSessions' => $row['longSessions'],
- 'shortSessions' => $row['shortSessions']
- );
- if (!$anonymize) {
- $entry['locationId'] = $row['locId'];
- }
+ $data = Queries::getLocationStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
+ foreach ($data as &$entry) {
+ //self::nullToZero($entry);
+ self::fillLocation($entry, $anonymize);
if ($printable) {
- $entry['totalTime_s'] = self::formatSeconds($row['timeSum']);
- $entry['medianSessionLength_s'] = self::formatSeconds($median);
- $entry['totalOffTime_s'] = self::formatSeconds($row['offlineSum']);
+ self::addPrintables($entry);
}
- $data[] = $entry;
}
return $data;
}
// per client
- public static function perClient($flags = 0) {
+ public static function perClient($flags = 0, $new = false) {
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
- $res = Queries::getClientStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $data = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- self::nullToZero($row);
- $median = self::calcMedian(self::calcMedian($row['medianSessionLength']));
- $entry = array(
- 'hostname' => ($anonymize ? $row['clientHash'] : $row['clientName']),
- 'totalTime' => $row['timeSum'],
- 'medianSessionLength' => $median,
- 'totalOffTime' => $row['offlineSum'],
- 'lastStart' => $row['lastStart'],
- 'lastLogout' => $row['lastLogout'],
- 'longSessions' => $row['longSessions'],
- 'shortSessions' => $row['shortSessions'],
- 'location' => ($anonymize ? $row['locHash'] : $row['locName']),
- );
- if (!$anonymize) {
- $entry['locationId'] = $row['locId'];
- }
+ $data = Queries::getClientStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
+ foreach ($data as &$entry) {
+ //self::nullToZero($entry);
+ $entry['hostname'] = ($anonymize ? md5($entry['clientName'] . self::$salt) : $entry['clientName']);
+ self::fillLocation($entry, $anonymize);
if ($printable) {
- $entry['totalTime_s'] = self::formatSeconds($row['timeSum']);
- $entry['medianSessionLength_s'] = self::formatSeconds($median);
- $entry['totalOffTime_s'] = self::formatSeconds($row['offlineSum']);
- $entry['lastStart_s'] = $row['lastStart'] == 0 ? "" : date(DATE_ISO8601, $row['lastStart']);
- $entry['lastLogout_s'] = $row['lastLogout'] == 0 ? "" : date(DATE_ISO8601, $row['lastLogout']);
+ self::addPrintables($entry);
}
- $data[] = $entry;
}
return $data;
}
@@ -104,9 +105,11 @@ class GetData
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$res = Queries::getUserStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
$data = array();
- $user = $anonymize ? 'userHash' : 'name';
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $data[] = array('user' => $row[$user], 'sessions' => $row['count']);
+ if ($anonymize && $row['name'] !== 'anonymous') {
+ $row['name'] = md5($row['name'] . self::$salt);
+ }
+ $data[] = array('user' => $row['name'], 'sessions' => $row['count']);
}
return $data;
}
@@ -117,10 +120,12 @@ class GetData
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$res = Queries::getVMStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
$data = array();
- $vm = $anonymize ? 'vmHash' : 'name';
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
self::nullToZero($row);
- $data[] = array('vm' => $row[$vm], 'sessions' => $row['count']);
+ if ($anonymize) {
+ $row['name'] = md5($row['name'] . self::$salt);
+ }
+ $data[] = array('vm' => $row['name'], 'sessions' => $row['count']);
}
return $data;
}
@@ -140,19 +145,4 @@ class GetData
return sprintf('%dd, %02d:%02d:%02d', $seconds / (3600*24), ($seconds % (3600*24)) / 3600, ($seconds%3600) / 60, $seconds%60);
}
- // Calculate Median
- private static function calcMedian($string) {
- $arr = explode(",", $string);
- sort($arr, SORT_NUMERIC);
- $count = count($arr); //total numbers in array
- $middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
- if($count % 2) { // odd number, middle is the median
- $median = $arr[(int) $middleval];
- } else { // even number, calculate avg of 2 medians
- $low = $arr[(int) $middleval];
- $high = $arr[(int) $middleval+1];
- $median = (($low+$high)/2);
- }
- return round($median);
- }
} \ No newline at end of file
diff --git a/modules-available/statistics_reporting/inc/queries.inc.php b/modules-available/statistics_reporting/inc/queries.inc.php
index bd8eb72e..395bb548 100644
--- a/modules-available/statistics_reporting/inc/queries.inc.php
+++ b/modules-available/statistics_reporting/inc/queries.inc.php
@@ -4,214 +4,342 @@
class Queries
{
- // Client Data: Name, Time Online, Median Time Online, Time Offline, last start, last logout, Last Time Booted, Number of Sessions > 60Sec, Number of Sessions < 60Sec, name of location, id of location (anonymized), machine uuid (anonymized)
- public static function getClientStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $notassigned = Dictionary::translate('notAssigned', true);
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT t2.name AS clientName, timeSum, medianSessionLength, offlineSum, IFNULL(lastStart, 0) as lastStart, IFNULL(lastLogout, 0) as lastLogout, longSessions, shortSessions, t2.locId, t2.locName, MD5(CONCAT(t2.locId, :salt)) AS locHash, MD5(CONCAT(t2.uuid, :salt)) AS clientHash FROM (
- SELECT machine.machineuuid AS 'uuid', SUM(CAST(sessionTable.length AS UNSIGNED)) AS 'timeSum', GROUP_CONCAT(sessionTable.length) AS 'medianSessionLength', SUM(sessionTable.length >= 60) AS 'longSessions', SUM(sessionTable.length < 60) AS 'shortSessions', MAX(sessionTable.endInBound) AS 'lastLogout'
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable
- RIGHT JOIN machine ON sessionTable.machineuuid = machine.machineuuid
- GROUP BY machine.machineuuid
- ) t1
- RIGHT JOIN (
- SELECT IF(machine.hostname = '', machine.clientip, machine.hostname) AS 'name', machine.machineuuid AS 'uuid', SUM(CAST(offlineTable.length AS UNSIGNED)) AS 'offlineSum', MAX(offlineTable.endInBound) AS 'lastStart', IFNULL(location.locationname, '$notassigned') AS 'locName', location.locationid AS 'locId'
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable
- RIGHT JOIN machine ON offlineTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.machineuuid
- ) t2
- ON t1.uuid = t2.uuid", array("salt" => GetData::$salt));
+ private static function keepKeys(&$array, $list)
+ {
+ foreach (array_keys($array) as $key) {
+ if (!in_array($key, $list)) {
+ unset($array[$key]);
+ }
+ }
+ }
- return $res;
+ public static function getClientStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ foreach ($machines as &$machine) {
+ $machine['medianSessionLength'] = self::calcMedian($machine['sessions']);
+ unset($machine['sessions']);
+ $machine['clientName'] = $machine['hostname'] ? $machine['hostname'] : $machine['clientip'];
+ }
+ return $machines;
}
- // Location Data: Name, ID (anonymized), Time Online, Median Time Online, Time Offline, Number of Sessions > 60Sec, Number of Sessions < 60Sec
- public static function getLocationStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $notassigned = Dictionary::translate('notAssigned', true);
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT t2.locId, t2.locName, MD5(CONCAT(t2.locId, :salt)) AS locHash, timeSum, medianSessionLength, offlineSum, longSessions, shortSessions FROM (
- SELECT location.locationid AS 'locId', SUM(CAST(sessionTable.length AS UNSIGNED)) AS 'timeSum', GROUP_CONCAT(sessionTable.length) AS 'medianSessionLength', SUM(sessionTable.length >= 60) AS 'longSessions', SUM(sessionTable.length < 60) AS 'shortSessions'
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable
- RIGHT JOIN machine ON sessionTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.locationid
- ) t1
- RIGHT JOIN (
- SELECT IFNULL(location.locationname, '$notassigned') AS 'locName', location.locationid AS 'locId', SUM(CAST(offlineTable.length AS UNSIGNED)) AS 'offlineSum'
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable
- RIGHT JOIN machine ON offlineTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.locationid
- ) t2
- ON t1.locId = t2.locId", array("salt" => GetData::$salt));
- return $res;
+ public static function getLocationStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ $locations = [];
+ $keys = ['locationid', 'totalTime', 'totalOffTime', 'totalSessionTime', 'totalStandbyTime', 'totalIdleTime', 'totalIdleTime', 'longSessions', 'shortSessions', 'sessions'];
+ while ($machine = array_pop($machines)) {
+ if (!isset($locations[$machine['locationid']])) {
+ self::keepKeys($machine, $keys);
+ $locations[$machine['locationid']] = $machine;
+ } else {
+ $l =& $locations[$machine['locationid']];
+ $l['totalTime'] += $machine['totalTime'];
+ $l['totalOffTime'] += $machine['totalOffTime'];
+ $l['totalSessionTime'] += $machine['totalSessionTime'];
+ $l['totalStandbyTime'] += $machine['totalStandbyTime'];
+ $l['totalIdleTime'] += $machine['totalIdleTime'];
+ $l['longSessions'] += $machine['longSessions'];
+ $l['shortSessions'] += $machine['shortSessions'];
+ $l['sessions'] = array_merge($l['sessions'], $machine['sessions']);
+ }
+ }
+ foreach ($locations as &$location) {
+ $location['medianSessionLength'] = self::calcMedian($location['sessions']);
+ unset($location['sessions']);
+ }
+ return $locations;
}
- // User Data: Name, Name(anonymized), Number of Logins
- public static function getUserStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT username AS name, IF(username = 'anonymous', 'anonymous', md5(CONCAT(username, :salt))) AS userHash, COUNT(*) AS 'count'
- FROM statistic
- WHERE typeid='.vmchooser-session-name' AND dateline >= $from and dateline <= $to
- AND FROM_UNIXTIME(dateline, '%H') >= $lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < $upperTimeBound
- GROUP BY username
- ORDER BY 2 DESC", array("salt" => GetData::$salt));
- return $res;
+ public static function getOverallStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ $total = false;
+ $keys = ['totalTime', 'totalOffTime', 'totalSessionTime', 'totalStandbyTime', 'totalIdleTime', 'totalIdleTime', 'longSessions', 'shortSessions', 'sessions'];
+ while ($machine = array_pop($machines)) {
+ if ($total === false) {
+ self::keepKeys($machine, $keys);
+ $total = $machine;
+ } else {
+ $total['totalTime'] += $machine['totalTime'];
+ $total['totalOffTime'] += $machine['totalOffTime'];
+ $total['totalSessionTime'] += $machine['totalSessionTime'];
+ $total['totalStandbyTime'] += $machine['totalStandbyTime'];
+ $total['totalIdleTime'] += $machine['totalIdleTime'];
+ $total['longSessions'] += $machine['longSessions'];
+ $total['shortSessions'] += $machine['shortSessions'];
+ $total['sessions'] = array_merge($total['sessions'], $machine['sessions']);
+ }
+ }
+ $total['medianSessionLength'] = self::calcMedian($total['sessions']);
+ unset($total['sessions']);
+ return $total;
}
- // Virtual Machine Data: Name, Number of Usages
- public static function getVMStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT data AS name, MD5(CONCAT(data, :salt)) AS vmHash, COUNT(*) AS 'count'
- FROM statistic
- WHERE typeid='.vmchooser-session-name' AND dateline >= $from and dateline <= $to
- AND FROM_UNIXTIME(dateline, '%H') >= $lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < $upperTimeBound
- GROUP BY data
- ORDER BY 2 DESC", array("salt" => GetData::$salt));
- return $res;
+ /**
+ * @param \PDOStatement $res
+ * @param int $from
+ * @param int $to
+ * @param int $lowerTimeBound
+ * @param int $upperTimeBound
+ * @return array
+ */
+ private static function getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound)
+ {
+ //$debug = false;
+ if ($lowerTimeBound === 0 && $upperTimeBound === 24 || $upperTimeBound <= $lowerTimeBound) {
+ $bounds = false;
+ } else {
+ $bounds = [$lowerTimeBound, $upperTimeBound];
+ }
+ $machines = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['firstseen'] = max($row['firstseen'], $from);
+ $row += array(
+ 'totalTime' => self::timeDiff($row['firstseen'], $to, $bounds),
+ 'totalOffTime' => 0,
+ 'totalSessionTime' => 0,
+ 'totalStandbyTime' => 0,
+ 'sessions' => [],
+ 'lastStart' => 0,
+ 'lastLogout' => 0,
+ 'longSessions' => 0,
+ 'shortSessions' => 0,
+ 'active' => false,
+ );
+ $machines[$row['machineuuid']] = $row;
+ }
+ // Don't filter by typeid in the query, still faster by being able to use the machineuuid/dateline index and filtering later
+ $last = $from - 86400; // Start 24h early to catch sessions already in progress
+ $dups = [];
+ // Fetch in batches of 1000 rows (for current 50 machines)
+ do {
+ $res = Database::simpleQuery("SELECT logid, dateline, typeid, machineuuid, data
+ FROM statistic WHERE dateline >= :last AND dateline <= :to AND machineuuid IS NOT NULL
+ ORDER BY dateline ASC LIMIT 1000", compact('last', 'to'));
+ $last = false;
+ $count = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $count += 1; // Update count first, as we use it as a condition in outer loop. No continue before this
+ settype($row['logid'], 'int');
+ // Update for next query
+ $last = $row['dateline'];
+ // Ignore dups, we query >= last dateline as we can have multiple events at the same second, but
+ // only some of them got returned because of LIMIT. Skip here because excluding in query directly
+ // would make the query itself rather inefficient. We also cannot use logid > X because the logid
+ // is not strictly ascending with time, as dateline gets backdated to event start on insert
+ if ($count === 150) {
+ $dups = [];
+ } elseif ($count > 900) {
+ $dups[] = $row['logid'];
+ } elseif ($count < 150 && array_key_exists($row['logid'], $dups)) {
+ continue;
+ }
+ if (!isset($machines[$row['machineuuid']]))
+ continue;
+ if ($row['typeid'] !== '~offline-length' && $row['typeid'] !== '~suspend-length' && $row['typeid'] !== '~session-length')
+ continue;
+ settype($row['dateline'], 'int');
+ settype($row['data'], 'int');
+ if ($row['data'] <= 0)
+ continue;
+ // Clamp to $from and $to
+ if ($row['dateline'] < $from) {
+ $diff = $row['dateline'] + $row['data'] - $from;
+ if ($diff <= 0)
+ continue;
+ $row['dateline'] += $diff;
+ $row['data'] -= $diff;
+ }
+ if ($row['dateline'] + $row['data'] > $to) {
+ $row['data'] = $to - $row['dateline'];
+ if ($row['data'] < 0)
+ continue;
+ }
+ $machine =& $machines[$row['machineuuid']];
+ // Process event if applicable
+ if ($row['typeid'] === '~session-length') { // SESSION timespan
+ $row['typeid'] = 'totalSessionTime';
+ $machine['lastLogout'] = $row['dateline'] + $row['data'];
+ } elseif ($row['typeid'] === '~offline-length') { // OFFLINE timespan
+ $row['typeid'] = 'totalOffTime';
+ $machine['lastStart'] = $row['dateline'] + $row['data'];
+ } else { // STANDBY timespan
+ $row['typeid'] = 'totalStandbyTime';
+ }
+ self::addTime($machine, $row, $bounds);
+ }
+ $dups = array_flip($dups);
+ } while ($last !== false && $count === 1000); // Check if we need to fetch more rows for current batch
+ foreach ($machines as &$machine) {
+ if (!$machine['active']) {
+ $machine['totalOffTime'] = $machine['totalTime'];
+ }
+ $machine['totalIdleTime'] = $machine['totalTime'] - ($machine['totalOffTime'] + $machine['totalStandbyTime'] + $machine['totalSessionTime']);
+ }
+ return $machines;
}
- //Total Data: Time Online, Median Time Online, Number of Sessions > 60Sec, Number of Sessions < 60Sec
- public static function getOverallStatistics ($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT SUM(CAST(sessionTable.length AS UNSIGNED)) AS sum, GROUP_CONCAT(sessionTable.length) AS median, SUM(sessionTable.length >= 60) AS longSessions, SUM(sessionTable.length < 60) AS shortSessions
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable");
- return $res;
+ private static function addTime(&$machine, $row, $bounds)
+ {
+ // First event, handle difference
+ if (!$machine['active'] && $row['dateline'] + $row['data'] >= $machine['firstseen']) {
+ if ($row['dateline'] > $machine['firstseen']) {
+ $s = $machine['firstseen'];
+ $e = $row['dateline'];
+ /*
+ if ($debug) {
+ error_log('Initial offline time += ' . self::timeDiff($s, $e, $bounds, true));
+ }
+ */
+ $machine['totalOffTime'] += self::timeDiff($s, $e, $bounds);
+ $machine['active'] = true;
+ if ($machine['lastStart'] < $row['dateline']) {
+ $machine['lastStart'] = $row['dateline'];
+ }
+ } else {
+ // Not offline at beginning of period, do nothing
+ $machine['active'] = true;
+ }
+ }
+ // Current row
+ if ($bounds === false) {
+ // Simple case: No bounds
+ $machine[$row['typeid']] += $row['data'];
+ } else {
+ $start = $row['dateline'];
+ $end = $row['dateline'] + $row['data'];
+ /*
+ if ($debug) {
+ error_log('Adding ' . $row['typeid'] . ' += ' . self::timeDiff($start, $end, $bounds, true));
+ }
+ */
+ $machine[$row['typeid']] += self::timeDiff($start, $end, $bounds);
+ $sh = date('G', $start);
+ }
+ if ($row['typeid'] === 'totalSessionTime' && ($bounds === false || ($sh >= $bounds[0] && $sh < $bounds[1]))) {
+ if ($row['data'] >= 60) {
+ $machine['longSessions'] += 1;
+ $machine['sessions'][] = $row['data'];
+ } else {
+ $machine['shortSessions'] += 1;
+ }
+ }
}
- // Total Data(2): Time Offline
- public static function getTotalOfflineStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT SUM(CAST(offlineTable.length AS UNSIGNED)) AS timeOff
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable");
- return $res;
+ private static function timeDiff($start, $end, $bounds)
+ {
+ // Put given timespan into bounds
+ /*
+ if ($debug) {
+ $os = $start;
+ $oe = $end;
+ }
+ */
+ if ($bounds !== false) {
+ // Put start time into bounds
+ if ($start !== null) {
+ $sh = date('G', $start);
+ if ($sh < $bounds[0]) {
+ $start = strtotime($bounds[0] . ':00:00', $start);
+ } elseif ($sh >= $bounds[1]) {
+ $start = strtotime($bounds[0] . ':00:00 +1day', $start);
+ }
+ }
+ // Put end time into bounds
+ if ($end !== null && $end > $start) {
+ $eh = date('G', $end);
+ if ($eh < $bounds[0]) {
+ $end = strtotime($bounds[1] . ':00:00 -1day', $end);
+ } elseif ($eh >= $bounds[1]) {
+ $end = strtotime($bounds[1] . ':00:00', $end);
+ }
+ }
+ }
+ if ($end !== null && $start !== null && $end < $start) {
+ $end = $start;
+ }
+ /*
+ if ($debug) {
+ if ($start >= $end) {
+ error_log('END < START: ' . date('d.m.Y H:i:s', $start) . ' - ' . date('d.m.Y H:i:s', $end));
+ } else {
+ if ($os != $start) {
+ error_log('Corrected start: ' . date('d.m.Y H:i:s', $os) . ' to ' . date('d.m.Y H:i:s', $start));
+ }
+ if ($oe != $end) {
+ error_log('Corrected end : ' . date('d.m.Y H:i:s', $oe) . ' to ' . date('d.m.Y H:i:s', $end));
+ }
+ }
+ }
+ */
+ // Calc time excluding out of range hours
+ return ($end - $start) - self::getIgnoredTime($start, $end, $bounds);
}
- // query string which provides table with time-cutoff and time-bounds
- private static function getBoundedTableQueryString($typeid, $from, $to, $lowerTimeBound, $upperTimeBound)
+ private static function getIgnoredTime($start, $end, $bounds)
{
- // get Clients that are currently oflfine (the offline time is not yet recorded in the statistic table)
- $union = $typeid == '~offline-length' ?
- "union
- select CAST(IF(lastseen < $from, $from, lastseen) as UNSIGNED) as start, $to as end,
- '~offline-length' as typeid, machineuuid, 'machine'
- from machine where lastseen <= $to and UNIX_TIMESTAMP() - lastseen >= 600" : "";
+ if ($bounds === false || $start >= $end)
+ return 0;
+ $end = strtotime('00:00:00', $end);
+ if ($start >= $end)
+ return 0;
+ /*
+ if ($debug) {
+ error_log('From ' . date('d.m.Y H:i:s', $start) . ' to ' . date('d.m.Y H:i:s', $end) . ' = ' . ceil(($end - $start) / 86400) * (24 - ($bounds[1] - $bounds[0])));
+ }
+ */
+ return (int)ceil(($end - $start) / 86400) * (24 - ($bounds[1] - $bounds[0])) * 3600;
+ }
+
+ /**
+ * Get median of array.
+ * @param int[] list of values
+ * @return int The median
+ */
+ private static function calcMedian($array) {
+ if (empty($array))
+ return 0;
+ sort($array, SORT_NUMERIC);
+ $count = count($array); //total numbers in array
+ $middleval = (int)floor(($count-1) / 2); // find the middle value, or the lowest middle value
+ if($count % 2 === 1) { // odd number, middle is the median
+ return (int)$array[$middleval];
+ }
+ // even number, calculate avg of 2 medians
+ $low = $array[$middleval];
+ $high = $array[$middleval+1];
+ return (int)round(($low+$high) / 2);
+ }
+ // User Data: Name, Name(anonymized), Number of Logins
+ public static function getUserStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
+ $res = Database::simpleQuery("SELECT username AS name, COUNT(*) AS 'count'
+ FROM statistic
+ WHERE typeid='.vmchooser-session-name' AND dateline >= :from and dateline <= :to
+ AND FROM_UNIXTIME(dateline, '%H') >= :lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < :upperTimeBound
+ GROUP BY username", compact('from', 'to', 'lowerTimeBound', 'upperTimeBound'));
+ return $res;
+ }
- $lowerFormat = "'%y-%m-%d $lowerTimeBound:00:00'";
- $upperFormat = "'%y-%m-%d ".($upperTimeBound-1).":59:59'";
- $queryString = "
- select
-
- # The whole length of the session/offline time.
- (end-start
-
- # Now the time that is not within the daily time bounds is subtracted.
- # This includes the time before the first daily bound, the time after the last daily bound
- # and the time between the daily bounds (if a session/offline time spans multiple days)
-
- # Time before the first daily bound is subtracted.
- - IF(
- start > startUpper,
- UNIX_TIMESTAMP(FROM_UNIXTIME(start, $lowerFormat) + INTERVAL 1 DAY) - start,
- IF(
- start < startLower,
- startLower - start,
- 0
- )
- )
-
- # Time after the last daily bound is subtracted.
- - IF(
- end > endUpper,
- end - (endUpper + 1),
- IF(
- end < endLower,
- end - (UNIX_TIMESTAMP(FROM_UNIXTIME(end, $upperFormat) - INTERVAL 1 DAY) + 1),
- 0
- )
- )
-
- # Time between the daily bounds is subtracted.
- - ( daysDiff - 2
- + IF(start <= startUpper, 1, 0)
- + IF(end >= endLower, 1, 0)
- ) * ((24 - ($upperTimeBound - $lowerTimeBound)) * 3600)
-
- # If the session crossed a clock change (to/from daylight saving time), the last subtraction may have subtracted
- # one hour too much or too little. This IF will correct this.
- - IF(
- innerStart < innerEnd,
- IF(
- timeDiff = 1 AND ($lowerTimeBound >= 2 OR $upperTimeBound <= 2),
- 3600,
- IF(timeDiff = -1 AND ($lowerTimeBound >= 3 OR $upperTimeBound <= 3), -3600, 0)
- ),
- 0
- )
-
- ) as 'length',
-
- IF(end < endUpper AND end > endLower AND end < $to, end, 0) as endInBound,
-
- machineuuid
-
-
- # These nested selects are necessary because some things need to be calculated before others.
- # (e.g. start is needed to calculate startLower)
- from (
- select
- *,
-
- # timeDiff is the clock change between innerStart and innerEnd. ( 0 = no clock change)
- ((CAST(date_format(from_unixtime(innerStart), '%H') as SIGNED) -
- CAST(date_format(convert_tz(from_unixtime(innerStart), @@session.time_zone, '+00:00'), '%H') as SIGNED) + 24) % 24
- -
- (CAST(date_format(from_unixtime(innerEnd), '%H') as SIGNED) -
- CAST(date_format(convert_tz(from_unixtime(innerEnd), @@session.time_zone, '+00:00'), '%H') as SIGNED) + 24) % 24) as timeDiff
- from (
- select
- *,
-
- # innerStart and innerEnd are start and end but excluding the time before the first daily upper bound and after the last daily lower bound.
- CAST(IF(start <= startUpper, startUpper, UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat) + INTERVAL 1 DAY)) as UNSIGNED) as innerStart,
- CAST(IF(end >= endLower, endLower, UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat) - INTERVAL 1 DAY)) as UNSIGNED) as innerEnd
- from (
- select
- *,
-
- # daysDiff = how many different days the start and end are apart (0 = start and end on the same day)
- (TO_DAYS(FROM_UNIXTIME(end, '%y-%m-%d')) - TO_DAYS(FROM_UNIXTIME(start, '%y-%m-%d'))) as daysDiff,
-
- # startLower = lower daily time bound on the starting day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(start, $lowerFormat)) as UNSIGNED) as startLower,
- # startUpper = upper daily time bound on the starting day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) as UNSIGNED) as startUpper,
- # endLower = lower daily time bound on the ending day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat)) as UNSIGNED) as endLower,
- # endUpper = upper daily time bound on the ending day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(end, $upperFormat)) as UNSIGNED) as endUpper
- from (
- # Statistic logs (combined with currently offline machines if offline times are requested) .
- select CAST(IF(dateline < $from, $from, dateline) as UNSIGNED) as start,
- CAST(IF(dateline+data > $to, $to, dateline+data) as UNSIGNED) as end,
- typeid, machineuuid, 'statistic'
- from statistic where dateline+data >= $from and dateline <= $to and typeid = '$typeid'
- $union
- ) t
- ) t
- ) t
- ) t
-
-
- # Filter out the session that are at least overlapping with the time bounds.
- where (
- (daysDiff = 0 and (start <= UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) and end >= UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat))))
- or
- (daysDiff = 1 and (start <= UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) or end >= UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat))))
- or
- daysDiff >= 2
- )
- ";
- return "(".$queryString.")";
+ // Virtual Machine Data: Name, Number of Usages
+ public static function getVMStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
+ $res = Database::simpleQuery("SELECT data AS name, COUNT(*) AS 'count'
+ FROM statistic
+ WHERE typeid='.vmchooser-session-name' AND dateline >= :from and dateline <= :to
+ AND FROM_UNIXTIME(dateline, '%H') >= :lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < :upperTimeBound
+ GROUP BY data", compact('from', 'to', 'lowerTimeBound', 'upperTimeBound'));
+ return $res;
}
public static function getDozmodStats($from, $to)
diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php
index 863715d4..b30b5cab 100644
--- a/modules-available/statistics_reporting/page.inc.php
+++ b/modules-available/statistics_reporting/page.inc.php
@@ -24,8 +24,8 @@ class Page_Statistics_Reporting extends Page
/**
* @var array Names of columns that are being used by the various tables
*/
- private $COLUMNS = array('location', 'totalTime', 'medianSessionLength', 'sessions', 'longSessions', 'shortSessions',
- 'totalOffTime', 'lastLogout', 'lastStart');
+ private $COLUMNS = array('locationname', 'totalTime', 'medianSessionLength', 'sessions', 'longSessions', 'shortSessions',
+ 'totalOffTime', 'totalStandbyTime', 'totalSessionTime', 'totalIdleTime', 'lastLogout', 'lastStart');
/**
* @var array Names of the tables we can display
@@ -56,10 +56,16 @@ class Page_Statistics_Reporting extends Page
}
// timespan you want to see. default = last 7 days
- GetData::$from = strtotime("- " . ($this->days - 1) . " days 00:00:00");
+ GetData::$from = strtotime("-" . ($this->days - 1) . " days 00:00:00");
GetData::$to = time();
GetData::$lowerTimeBound = $this->lower;
GetData::$upperTimeBound = $this->upper;
+ /*
+ GetData::$from = strtotime("2017-12-01 00:00:00");
+ GetData::$to = strtotime("2017-12-15 00:00:00");
+ GetData::$lowerTimeBound = 1;
+ GetData::$upperTimeBound = 2;
+ */
// Export - handle in doPreprocess so we don't render the menu etc.
if ($this->action === 'export') {
@@ -193,20 +199,20 @@ class Page_Statistics_Reporting extends Page
if (Request::get('col_' . $column, 'delete', 'string') === 'delete') {
foreach ($res as &$row) {
unset($row[$column], $row[$column . '_s']);
- if ($column === 'location') {
- unset($row['locationId']);
+ if ($column === 'locationname') {
+ unset($row['locationid']);
}
}
} elseif ($printable && isset($row[0][$column . '_s'])) {
foreach ($res as &$row) {
unset($row[$column]);
}
- } elseif ($column === 'location' && (isset($res[0]['location']) || isset($res[0]['locationId']))) {
+ } elseif ($column === 'locationname' && (isset($res[0]['locationname']) || isset($res[0]['locationid']))) {
foreach ($res as &$row) {
if ($printable) {
- unset($row['locationId']);
+ unset($row['locationid']);
} else {
- unset($row['location']);
+ unset($row['locationname']);
}
}
}
@@ -285,7 +291,7 @@ class Page_Statistics_Reporting extends Page
$highlight = Request::get('location', false, 'int');
if ($highlight !== false) {
foreach ($data as &$row) {
- if ($row['locationId'] == $highlight) {
+ if ($row['locationid'] == $highlight) {
$row['highlight'] = true;
}
}
@@ -293,7 +299,7 @@ class Page_Statistics_Reporting extends Page
// only show locations which you have permission for
$filterLocs = User::getAllowedLocations("table.view.location");
foreach ($data as $key => $row) {
- if (!in_array($row['locationId'], $filterLocs)) {
+ if (!in_array($row['locationid'], $filterLocs)) {
unset($data[$key]);
}
}
@@ -301,11 +307,11 @@ class Page_Statistics_Reporting extends Page
$data = array_values($data);
return $data;
case 'client':
- $data = GetData::perClient($flags);
+ $data = GetData::perClient($flags, Request::any('new', false, 'string'));
// only show clients from locations which you have permission for
$filterLocs = User::getAllowedLocations("table.view.location");
foreach ($data as $key => $row) {
- if (!in_array($row['locationId'], $filterLocs)) {
+ if (!in_array($row['locationid'], $filterLocs)) {
unset($data[$key]);
}
}
diff --git a/modules-available/statistics_reporting/style.css b/modules-available/statistics_reporting/style.css
index 3cd6653f..c732099d 100644
--- a/modules-available/statistics_reporting/style.css
+++ b/modules-available/statistics_reporting/style.css
@@ -35,4 +35,13 @@
margin-left: -1.5em;
text-align: center;
line-height: 1.6em;
+}
+
+.table-nowrap td {
+ white-space: nowrap;
+}
+
+td.p-addon {
+ padding: 5px 5px 5px 0 !important;
+ text-align: right;
} \ No newline at end of file
diff --git a/modules-available/statistics_reporting/templates/table-client.html b/modules-available/statistics_reporting/templates/table-client.html
index 59153e01..a23c2f89 100644
--- a/modules-available/statistics_reporting/templates/table-client.html
+++ b/modules-available/statistics_reporting/templates/table-client.html
@@ -1,29 +1,43 @@
-<table id="table-perclient" class="table table-condensed table-striped stupidtable">
+<table id="table-perclient" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th>
- <th data-sort="string" class="text-left col_location">{{lang_location}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_lastLogout">{{lang_lastLogout}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_lastStart">{{lang_lastStart}}</th>
+ <th data-sort="string">{{lang_hostname}}</th>
+ <th data-sort="string" class="col_locationname">{{lang_location}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalTime">{{lang_totalTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_longSessions">{{lang_longSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_lastLogout">{{lang_lastLogout}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_lastStart">{{lang_lastStart}}</th>
</tr>
</thead>
<tbody>
{{#data}}
<tr>
<td class="text-left">{{hostname}}</td>
- <td class="text-left col_location"><a class="locationLink" href="#" data-lid="{{locationId}}">{{location}}</a></td>
- <td data-sort-value="{{totalTime}}" class="text-left col_totalTime">{{totalTime_s}}</td>
- <td data-sort-value="{{medianSessionLength}}" class="text-left col_medianSessionLength">{{medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{longSessions}}</td>
- <td class="text-left col_shortSessions">{{shortSessions}}</td>
- <td data-sort-value="{{totalOffTime}}" class="text-left col_totalOffTime">{{totalOffTime_s}}</td>
- <td data-sort-value="{{lastLogout}}" class="text-left col_lastLogout">{{lastLogout_s}}</td>
- <td data-sort-value="{{lastStart}}" class="text-left col_lastStart">{{lastStart_s}}</td>
+ <td class="col_locationname"><a class="locationLink" href="#" data-lid="{{locationid}}">{{locationname}}</a></td>
+ <td data-sort-value="{{totalTime}}" class="col_totalTime">{{totalTime_s}}</td>
+ <td data-sort-value="{{medianSessionLength}}" class="col_medianSessionLength">{{medianSessionLength_s}}</td>
+ <td class="col_longSessions">{{longSessions}}</td>
+ <td class="col_shortSessions">{{shortSessions}}</td>
+ <td data-sort-value="{{totalOffTime}}" class="text-right col_totalOffTime">{{totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{totalOffTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalStandbyTime}}" class="text-right col_totalStandbyTime">{{totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{totalStandbyTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalSessionTime}}" class="text-right col_totalSessionTime">{{totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{totalSessionTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalIdleTime}}" class="text-right col_totalIdleTime">{{totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{totalIdleTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{lastLogout}}" class="col_lastLogout">{{lastLogout_s}}</td>
+ <td data-sort-value="{{lastStart}}" class="col_lastStart">{{lastStart_s}}</td>
</tr>
{{/data}}
</tbody>
diff --git a/modules-available/statistics_reporting/templates/table-location.html b/modules-available/statistics_reporting/templates/table-location.html
index a0867208..4e470d1e 100644
--- a/modules-available/statistics_reporting/templates/table-location.html
+++ b/modules-available/statistics_reporting/templates/table-location.html
@@ -1,23 +1,37 @@
-<table id="table-perlocation" class="table table-condensed table-striped stupidtable">
+<table id="table-perlocation" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th data-sort="string" class="text-left col-md-2">{{lang_location}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="string">{{lang_location}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalTime">{{lang_totalTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_longSessions">{{lang_longSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">(%)</th>
</tr>
</thead>
<tbody>
{{#data}}
<tr{{#highlight}} class="info"{{/highlight}}>
- <td class="locationName text-left">{{location}}</td>
- <td data-sort-value="{{totalTime}}" class="text-left col_totalTime">{{totalTime_s}}</td>
- <td data-sort-value="{{medianSessionLength}}" class="text-left col_medianSessionLength">{{medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{longSessions}}</td>
- <td class="text-left col_shortSessions">{{shortSessions}}</td>
- <td data-sort-value="{{totalOffTime}}" class="text-left col_totalOffTime">{{totalOffTime_s}}</td>
+ <td data-sort-value="{{parentlocations}}" class="locationName text-left">{{locationname}}</td>
+ <td data-sort-value="{{totalTime}}" class="text-right col_totalTime">{{totalTime_s}}</td>
+ <td data-sort-value="{{medianSessionLength}}" class="col_medianSessionLength">{{medianSessionLength_s}}</td>
+ <td class="text-right col_longSessions">{{longSessions}}</td>
+ <td class="text-right col_shortSessions">{{shortSessions}}</td>
+ <td data-sort-value="{{totalOffTime}}" class="text-right col_totalOffTime">{{totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{totalOffTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalStandbyTime}}" class="text-right col_totalStandbyTime">{{totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{totalStandbyTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalSessionTime}}" class="text-right col_totalSessionTime">{{totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{totalSessionTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalIdleTime}}" class="text-right col_totalIdleTime">{{totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{totalIdleTime_p}}&thinsp;%</td>
</tr>
{{/data}}
</tbody>
diff --git a/modules-available/statistics_reporting/templates/table-total.html b/modules-available/statistics_reporting/templates/table-total.html
index 8d5d7571..83078714 100644
--- a/modules-available/statistics_reporting/templates/table-total.html
+++ b/modules-available/statistics_reporting/templates/table-total.html
@@ -1,22 +1,36 @@
-<table id="table-total" class="table table-condensed table-striped stupidtable">
+<table id="table-total" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th class="text-left col-md-2"></th>
- <th class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th></th>
+ <th class="col_totalTime">{{lang_totalTime}}</th>
+ <th class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th class="col_longSessions">{{lang_longSessions}}</th>
+ <th class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th class="col_totalOffTime">(%)</th>
+ <th class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th class="col_totalStandbyTime">(%)</th>
+ <th class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th class="col_totalSessionTime">(%)</th>
+ <th class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th class="col_totalIdleTime">(%)</th>
</tr>
</thead>
<tbody>
<tr>
- <th class="text-left">{{lang_total}}</th>
- <td class="text-left col_totalTime">{{data.totalTime_s}}</td>
- <td class="text-left col_medianSessionLength">{{data.medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{data.longSessions}}</td>
- <td class="text-left col_shortSessions">{{data.shortSessions}}</td>
- <td class="text-left col_totalOffTime">{{data.totalOffTime_s}}</td>
+ <td>{{lang_total}}</td>
+ <td class="col_totalTime">{{data.totalTime_s}}</td>
+ <td class="col_medianSessionLength">{{data.medianSessionLength_s}}</td>
+ <td class="col_longSessions">{{data.longSessions}}</td>
+ <td class="col_shortSessions">{{data.shortSessions}}</td>
+ <td class="text-right col_totalOffTime">{{data.totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{data.totalOffTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalStandbyTime">{{data.totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{data.totalStandbyTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalSessionTime">{{data.totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{data.totalSessionTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalIdleTime">{{data.totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{data.totalIdleTime_p}}&thinsp;%</td>
</tr>
</tbody>
</table>