From 6821a5f066b8e2a5648c0eabf1322749300d28c3 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 15 Feb 2016 18:33:37 +0100 Subject: [statistics] Smart values, show last log lines, location support --- lang/de/templates/statistics/cpumodels.json | 8 +- lang/de/templates/statistics/machine-hdds.json | 6 + lang/de/templates/statistics/syslog.json | 7 + lang/en/templates/statistics/cpumodels.json | 8 +- lang/en/templates/statistics/machine-hdds.json | 6 + lang/en/templates/statistics/syslog.json | 7 + lang/pt/templates/statistics/syslog.json | 3 + modules/statistics.inc.php | 203 ++++++++++++++++++++----- templates/statistics/cpumodels.html | 16 +- templates/statistics/machine-hdds.html | 14 +- templates/statistics/summary.html | 17 ++- templates/statistics/syslog.html | 43 ++++++ 12 files changed, 284 insertions(+), 54 deletions(-) create mode 100644 lang/de/templates/statistics/syslog.json create mode 100644 lang/en/templates/statistics/syslog.json create mode 100644 lang/pt/templates/statistics/syslog.json create mode 100644 templates/statistics/syslog.html diff --git a/lang/de/templates/statistics/cpumodels.json b/lang/de/templates/statistics/cpumodels.json index fc7cf503..85cf517f 100644 --- a/lang/de/templates/statistics/cpumodels.json +++ b/lang/de/templates/statistics/cpumodels.json @@ -1,6 +1,6 @@ { - "lang_cpuCores": "Kerne", - "lang_cpuCount": "Anzahl", - "lang_cpuName": "CPU Typ", - "lang_cpuStats": "Prozessoren" + "lang_cpuCores": "CPU-Kerne", + "lang_modelCount": "Anzahl", + "lang_modelName": "Modellname", + "lang_modelStats": "PC-Modelle" } \ No newline at end of file diff --git a/lang/de/templates/statistics/machine-hdds.json b/lang/de/templates/statistics/machine-hdds.json index 731376e3..f2f26baf 100644 --- a/lang/de/templates/statistics/machine-hdds.json +++ b/lang/de/templates/statistics/machine-hdds.json @@ -1,7 +1,13 @@ { "lang_hdds": "Festplatten", + "lang_hours": "Stunden", + "lang_modelNo": "Modell", "lang_partName": "Name", "lang_partSize": "Gr\u00f6\u00dfe", "lang_partType": "Typ", + "lang_pendingSectors": "Potentiell defekte Sektoren", + "lang_powerOnTime": "Betriebszeit", + "lang_reallocatedSectors": "Defekte Sektoren", + "lang_serialNo": "Serien-Nr", "lang_total": "Gesamt" } \ No newline at end of file diff --git a/lang/de/templates/statistics/syslog.json b/lang/de/templates/statistics/syslog.json new file mode 100644 index 00000000..960de730 --- /dev/null +++ b/lang/de/templates/statistics/syslog.json @@ -0,0 +1,7 @@ +{ + "lang_details": "Details", + "lang_event": "Ereignis", + "lang_logHeadline": "Logging", + "lang_more": "Mehr", + "lang_when": "Wann" +} \ No newline at end of file diff --git a/lang/en/templates/statistics/cpumodels.json b/lang/en/templates/statistics/cpumodels.json index c73cbb22..864933dd 100644 --- a/lang/en/templates/statistics/cpumodels.json +++ b/lang/en/templates/statistics/cpumodels.json @@ -1,6 +1,6 @@ { - "lang_cpuCores": "Cores", - "lang_cpuCount": "Count", - "lang_cpuName": "CPU type", - "lang_cpuStats": "Processors" + "lang_cpuCores": "CPU cores", + "lang_modelCount": "Count", + "lang_modelName": "Model name", + "lang_modelStats": "PC models" } \ No newline at end of file diff --git a/lang/en/templates/statistics/machine-hdds.json b/lang/en/templates/statistics/machine-hdds.json index 85afe2e8..8ce6801d 100644 --- a/lang/en/templates/statistics/machine-hdds.json +++ b/lang/en/templates/statistics/machine-hdds.json @@ -1,7 +1,13 @@ { "lang_hdds": "Hard disk drives", + "lang_hours": "hours", + "lang_modelNo": "Model", "lang_partName": "Name", "lang_partSize": "Size", "lang_partType": "Type", + "lang_pendingSectors": "Sectors pending reallocation", + "lang_powerOnTime": "Power on time", + "lang_reallocatedSectors": "Bad sectors", + "lang_serialNo": "Serial no", "lang_total": "Total" } \ No newline at end of file diff --git a/lang/en/templates/statistics/syslog.json b/lang/en/templates/statistics/syslog.json new file mode 100644 index 00000000..6737ca68 --- /dev/null +++ b/lang/en/templates/statistics/syslog.json @@ -0,0 +1,7 @@ +{ + "lang_details": "Details", + "lang_event": "Event", + "lang_logHeadline": "Logging", + "lang_more": "More", + "lang_when": "When" +} \ No newline at end of file diff --git a/lang/pt/templates/statistics/syslog.json b/lang/pt/templates/statistics/syslog.json new file mode 100644 index 00000000..c44dc44f --- /dev/null +++ b/lang/pt/templates/statistics/syslog.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/modules/statistics.inc.php b/modules/statistics.inc.php index 3104496a..dbac4b75 100644 --- a/modules/statistics.inc.php +++ b/modules/statistics.inc.php @@ -56,11 +56,11 @@ class Page_Statistics extends Page $this->showId44(); $this->showKvmState(); $this->showLatestMachines(); - $this->showCpuModels(); + $this->showSystemModels(); Render::closeTag('div'); } - private function capChart(&$json, $cutoff) + private function capChart(&$json, $cutoff, $minSlice = 0.015) { $total = 0; foreach ($json as $entry) { @@ -70,10 +70,9 @@ class Page_Statistics extends Page $accounted = 0; $id = 0; foreach ($json as $entry) { - if ($accounted < $cap || $id < 3) { - $id++; - $accounted += $entry['value']; - } + if (($accounted >= $cap || $entry['value'] / $total < $minSlice) && $id >= 3) break; + $id++; + $accounted += $entry['value']; } $json = array_slice($json, 0, $id); if ($accounted / $total < 0.99) { @@ -100,24 +99,50 @@ class Page_Statistics extends Page 'usedpercent' => round($used['val'] / $on['val'] * 100), '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']); + } else { + $x[1] = max($x[1], array_pop($points1['data'])); + $x[2] = max($x[2], array_pop($points2['data'])); + } + $points1['data'][] = $x[1]; + $points2['data'][] = $x[2]; + $sum++; + if ($sum === 12) { + $sum = 0; + } + } + $data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2))); + // Draw Render::addTemplate('statistics/summary', $data); } - private function showCpuModels() + private function showSystemModels() { global $STATS_COLORS; - $res = Database::simpleQuery("SELECT cpumodel, realcores, Count(*) AS `count` FROM machine GROUP BY cpumodel ORDER BY `count` DESC, cpumodel ASC"); + $res = Database::simpleQuery("SELECT systemmodel, Round(AVG(realcores)) AS cores, Count(*) AS `count` FROM machine" + . " GROUP BY systemmodel ORDER BY `count` DESC, systemmodel ASC"); $lines = array(); $json = array(); $id = 0; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['systemmodel'])) continue; settype($row['count'], 'integer'); - $row['id'] = 'cpuid' . $id; - $row['urlcpumodel'] = urlencode($row['cpumodel']); + $row['id'] = 'systemid' . $id; + $row['urlsystemmodel'] = urlencode($row['systemmodel']); $lines[] = $row; $json[] = array( 'color' => $STATS_COLORS[$id % count($STATS_COLORS)], - 'label' => 'cpuid' . $id, + 'label' => 'systemid' . $id, 'value' => $row['count'] ); ++$id; @@ -205,31 +230,20 @@ class Page_Statistics extends Page $data = array('rows' => array()); $json = array(); $id = 0; - $cap = ceil($total * 0.95); - $accounted = 0; foreach (array_reverse($lines, true) as $k => $v) { $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => $this->hddColorClass($k)); - if ($accounted <= $cap) { - if ($k === 0) { - $color = '#e55'; - } else { - $color = $STATS_COLORS[$id++ % count($STATS_COLORS)]; - } - $json[] = array( - 'color' => $color, - 'label' => (string)$k, - 'value' => $v - ); + if ($k === 0) { + $color = '#e55'; + } else { + $color = $STATS_COLORS[$id++ % count($STATS_COLORS)]; } - $accounted += $v; - } - if ($accounted / $total < 0.99) { $json[] = array( - 'color' => '#eee', - 'label' => 'invalid', - 'value' => ($total - $accounted) + 'color' => $color, + 'label' => (string)$k, + 'value' => $v ); } + $this->capChart($json, 0.95); $data['json'] = json_encode($json); Render::addTemplate('statistics/id44', $data); } @@ -263,7 +277,8 @@ class Page_Statistics extends Page private function showMachineList($filter, $argument) { global $SIZE_RAM, $SIZE_ID44; - $filters = array('cpumodel', 'realcores', 'kvmstate', 'clientip', 'macaddr', 'machineuuid'); + $join = ''; + $filters = array('cpumodel', 'realcores', 'kvmstate', 'clientip', 'macaddr', 'machineuuid', 'systemmodel'); if (in_array($filter, $filters)) { // Simple filters mapping into db $where = " $filter = :argument"; @@ -287,13 +302,30 @@ class Page_Statistics extends Page } elseif ($filter === 'badsectors') { $where = " badsectors >= :argument "; $args = array('argument' => $argument); + } elseif ($filter === 'state') { + if ( $argument === 'on') { + $where = " lastseen + 600 > UNIX_TIMESTAMP() "; + } elseif ($argument === 'off') { + $where = " lastseen + 600 < UNIX_TIMESTAMP() "; + } elseif ($argument === 'idle') { + $where = " lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 "; + } elseif ($argument === 'occupied') { + $where = " lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 "; + } else { + Message::addError('invalid-filter'); + return; + } + } elseif ($filter === 'location') { + $where = "subnet.locationid = :lid OR machine.locationid = :lid"; + $join = " INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) "; + $args = array('lid' => (int)$argument); } else { Message::addError('invalid-filter'); return; } $res = Database::simpleQuery("SELECT machineuuid, macaddr, clientip, firstseen, lastseen," . " logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes, badsectors FROM machine" - . " WHERE $where ORDER BY lastseen DESC, clientip ASC", $args); + . " $join WHERE $where ORDER BY lastseen DESC, clientip ASC", $args); $rows = array(); $NOW = time(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { @@ -408,7 +440,8 @@ class Page_Statistics extends Page } $client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']); $client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']); - $client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']); + $uptime = $NOW - $client['lastboot']; + $client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']) . ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')'; $client['logintime_s'] = date('d.m.Y H:i', $client['logintime']); $client['gbram'] = round(round($client['mbram'] / 500) / 2, 1); $client['gbtmp'] = round($client['id44mb'] / 1024); @@ -428,6 +461,10 @@ class Page_Statistics extends Page if ($section[1] === 'Partition tables') { $this->parseHdd($hdds, $section[2]); } + if (isset($hdds['hdds']) && $section[1] === 'smartctl') { + // This currently required that the partition table section comes first... + $this->parseSmartctl($hdds['hdds'], $section[2]); + } } } unset($client['data']); @@ -436,16 +473,23 @@ class Page_Statistics extends Page // Sessions $NOW = time(); $cutoff = $NOW - 86400 * 7; - if ($cutoff < $row['firstseen']) $cutoff = $row['firstseen']; + //if ($cutoff < $client['firstseen']) $cutoff = $client['firstseen']; $scale = 100 / ($NOW - $cutoff); $res = Database::simpleQuery("SELECT dateline, typeid, data FROM statistic" . " WHERE dateline > :cutoff AND typeid IN ('~session-length', '~offline-length') AND machineuuid = :uuid ORDER BY dateline ASC", array( - 'cutoff' => $cutoff - 86400, + 'cutoff' => $cutoff - 86400 * 14, 'uuid' => $uuid )); $spans['rows'] = array(); + $spans['graph'] = ''; $last = false; + $first = true; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) { + // Special case: offline before + $spans['graph'] .= '
 
'; + } + $first = false; if ($row['dateline'] + $row['data'] < $cutoff || $row['data'] > 864000) continue; if ($last !== false && abs($last['dateline'] - $row['dateline']) < 30 && abs($last['data'] - $row['data']) < 30) continue; @@ -471,6 +515,10 @@ class Page_Statistics extends Page $spans['rows'][] = $row; $last = $row; } + if ($first && $client['lastboot'] > $cutoff) { + // Special case: offline before + $spans['graph'] .= '
 
'; + } if (isset($client['state_occupied'])) { $spans['graph'] .= '
 
'; } elseif (isset($client['state_off'])) { @@ -496,8 +544,50 @@ class Page_Statistics extends Page Render::addScriptBottom('chart.min'); Render::addTemplate('statistics/machine-hdds', $hdds); } + // Client log + $lres = Database::simpleQuery("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog" + . " WHERE clientip = :clientip ORDER BY logid DESC LIMIT 25", array('clientip' => $client['clientip'])); + $today = date('d.m.Y'); + $yesterday = date('d.m.Y', time() - 86400); + $count = 0; + $log = array(); + while ($row = $lres->fetch(PDO::FETCH_ASSOC)) { + if (substr($row['description'], -5) === 'on :0' && strpos($row['description'], 'root logged') === false) continue; + $day = date('d.m.Y', $row['dateline']); + if ($day === $today) { + $day = Dictionary::translate('today'); + } elseif ($day === $yesterday) { + $day = Dictionary::translate('yesterday'); + } + $row['date'] = $day . date(' H:i', $row['dateline']); + $row['icon'] = $this->eventToIconName($row['logtypeid']); + $log[] = $row; + if (++$count === 10) break; + } + Render::addTemplate('statistics/syslog', array( + 'clientip' => $client['clientip'], + 'list' => $log + )); + // Notes Render::addTemplate('statistics/machine-notes', $client); } + + private function eventToIconName($event) + { + switch ($event) { + case 'session-open': + return 'glyphicon-log-in'; + case 'session-close': + return 'glyphicon-log-out'; + case 'partition-swap': + return 'glyphicon-info-sign'; + case 'partition-temp': + case 'smartctl-realloc': + return 'glyphicon-exclamation-sign'; + default: + return 'glyphicon-minus'; + } + } private function parseCpu(&$row, $data) { @@ -586,7 +676,7 @@ class Page_Statistics extends Page private function parseHdd(&$row, $data) { $hdds = array(); - // Could have more than one partition - linear scan + // Could have more than one disk - linear scan $lines = preg_split("/[\r\n]+/", $data); $dev = false; $i = 0; @@ -650,5 +740,46 @@ class Page_Statistics extends Page unset($hdd); $row['hdds'] = &$hdds; } + + private function parseSmartctl(&$hdds, $data) + { + $lines = preg_split("/[\r\n]+/", $data); + $i = 0; + foreach ($lines as $line) { + if (preg_match('/^NEXTHDD=(.+)$/', $line, $out)) { + unset($dev); + foreach ($hdds as &$hdd) { + if ($hdd['dev'] === $out[1]) $dev =& $hdd; + } + continue; + } + if (!isset($dev)) continue; + if (preg_match('/^([A-Z][^:]+):\s*(.*)$/', $line, $out)) { + $dev['s_' . preg_replace('/\s|-|_/', '', $out[1])] = $out[2]; + } elseif (preg_match('/^\s*\d+\s+(\S+)\s+\S+\s+\d+\s+\d+\s+\d+\s+\S+\s+(\d+)(\s|$)/', $line, $out)) { + $dev['s_' . preg_replace('/\s|-|_/', '', $out[1])] = $out[2]; + } + } + // Format strings + foreach ($hdds as &$hdd) { + if (isset($hdd['s_PowerOnHours'])) { + $hdd['PowerOnTime'] = ''; + $val = (int)$hdd['s_PowerOnHours']; + if ($val > 8760) { + $hdd['PowerOnTime'] .= floor($val / 8760) . 'Y, '; + $val %= 8760; + } + if ($val > 720) { + $hdd['PowerOnTime'] .= floor($val / 720) . 'M, '; + $val %= 720; + } + if ($val > 24) { + $hdd['PowerOnTime'] .= floor($val / 24) . 'd, '; + $val %= 24; + } + $hdd['PowerOnTime'] .= $val . 'h'; + } + } + } } diff --git a/templates/statistics/cpumodels.html b/templates/statistics/cpumodels.html index f98b89db..2f24cd92 100644 --- a/templates/statistics/cpumodels.html +++ b/templates/statistics/cpumodels.html @@ -1,21 +1,23 @@
- {{lang_cpuStats}} + {{lang_modelStats}}
- + - + {{#rows}} - - + + {{/rows}} @@ -27,7 +29,7 @@ document.addEventListener("DOMContentLoaded", function() { var data = {{{json}}}; var sel = false; - new Chart(document.getElementById('cpumodelchart').getContext('2d')).Pie(data, { + new Chart(document.getElementById('cpumodelchart').getContext('2d')).Pie(data, { animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { @@ -46,4 +48,4 @@ - \ No newline at end of file + diff --git a/templates/statistics/machine-hdds.html b/templates/statistics/machine-hdds.html index cb3cbffc..fd6cf1be 100644 --- a/templates/statistics/machine-hdds.html +++ b/templates/statistics/machine-hdds.html @@ -4,9 +4,21 @@
- {{dev}} + {{s_ModelFamily}} {{dev}}
+ {{#s_DeviceModel}} +
{{lang_modelNo}}: {{s_DeviceModel}}, {{lang_serialNo}}: {{s_SerialNumber}}
+ {{/s_DeviceModel}} + {{#s_ReallocatedSectorCt}} +
{{lang_reallocatedSectors}}: {{s_ReallocatedSectorCt}}
+ {{/s_ReallocatedSectorCt}} + {{#s_CurrentPendingSector}} +
{{lang_pendingSectors}}: {{s_CurrentPendingSector}}
+ {{/s_CurrentPendingSector}} + {{#s_PowerOnHours}} +
{{lang_powerOnTime}}: {{s_PowerOnHours}} {{lang_hours}} ({{PowerOnTime}})
+ {{/s_PowerOnHours}}
{{lang_cpuName}}{{lang_modelName}} {{lang_cpuCores}}{{lang_cpuCount}}{{lang_modelCount}}
{{cpumodel}}{{realcores}} + {{systemmodel}} + {{cores}} {{count}}
diff --git a/templates/statistics/summary.html b/templates/statistics/summary.html index fb6466fc..5f16fd89 100644 --- a/templates/statistics/summary.html +++ b/templates/statistics/summary.html @@ -3,8 +3,8 @@
{{lang_knownMachines}}: {{known}}  - {{lang_onlineMachines}}: {{online}}  - {{lang_inUseMachines}}: {{used}} ({{usedpercent}}%) + {{lang_onlineMachines}}: {{online}}  + {{lang_inUseMachines}}: {{used}} ({{usedpercent}}%)
{{#badhdd}}
@@ -16,5 +16,18 @@ {{/badhdd}}
+
+ + +
diff --git a/templates/statistics/syslog.html b/templates/statistics/syslog.html new file mode 100644 index 00000000..c82cb8ac --- /dev/null +++ b/templates/statistics/syslog.html @@ -0,0 +1,43 @@ +

{{lang_logHeadline}}

+
+ + + + + + + + {{#list}} + + + + + + + {{/list}} + +
{{lang_when}}{{lang_event}}{{lang_details}}
{{date}}{{description}}{{#extra}} + » + + {{/extra}}
+ +
+ + + + -- cgit v1.2.3-55-g7522