Commit 211a5bf0 authored by I Patini's avatar I Patini
Browse files

EMS: Web Admin, Baguette Client, Control Service: Made functional the client &...

EMS: Web Admin, Baguette Client, Control Service: Made functional the client & cluster statistics in Web Admin topology section. Moved 'SystemResourceMonitor' class from control service (info service) to 'common' package to become available to baguette-client. Improved 'sysmon.sh' script to report current date/time and uptime, and it is also bundled with EMS clients. Extended EMS client to include 'sysmon.sh' data in client statistics sent to EMS server (through SystemResourceMonitor)
parent 42544db1
Pipeline #20816 passed with stages
in 14 minutes and 5 seconds
......@@ -77,6 +77,15 @@
<lineEnding>unix</lineEnding>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<outputDirectory>bin</outputDirectory>
<directory>${project.parent.basedir}/bin</directory>
<includes>
<include>sysmon.*</include>
</includes>
<lineEnding>unix</lineEnding>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
<!-- use this section if you want to package dependencies -->
<dependencySets>
......
......@@ -18,6 +18,7 @@ import eu.melodic.event.brokercep.cep.CepService;
import eu.melodic.event.brokercep.event.EventMap;
import eu.melodic.event.brokerclient.event.EventGenerator;
import eu.melodic.event.brokerclient.properties.BrokerClientProperties;
import eu.melodic.event.common.misc.SystemResourceMonitor;
import eu.melodic.event.util.*;
import io.atomix.cluster.ClusterMembershipEvent;
import io.atomix.cluster.Member;
......@@ -117,6 +118,8 @@ public class CommandExecutor {
@Autowired
private TaskScheduler taskScheduler;
private ScheduledFuture<?> statsSendTask;
@Autowired
private SystemResourceMonitor systemResourceMonitor;
public CommandExecutor() {
......@@ -1257,8 +1260,14 @@ public class CommandExecutor {
statsSendTask = taskScheduler.scheduleWithFixedDelay(() -> {
try {
Map<String, Object> statsMap = brokerCepService.getBrokerCepStatistics();
log.debug("Statistics: {}", statsMap);
if (out != null) out.println("-STATS:" + serializeToString(statsMap));
log.debug("BCEP Statistics: {}", statsMap);
Map<String, Object> sysMap = systemResourceMonitor.getLatestMeasurements();
log.debug("System Statistics: {}", sysMap);
Map<String, Object> clientStats = new HashMap<>();
if (statsMap!=null) clientStats.putAll(statsMap);
if (sysMap!=null) clientStats.putAll(sysMap);
if (out != null) out.println("-STATS:" + serializeToString(clientStats));
} catch (Exception ex) {
log.error("Exception while sending Statistics to server: ", ex);
}
......
......@@ -8,6 +8,16 @@
# https://www.mozilla.org/en-US/MPL/2.0/
#
# Current Time / Start Time / Uptime
curr_dt=`date '+%Y-%m-%d %H:%M:%S'`
up_dt=`uptime -s`
curr_dt_sec=`date -d "$curr_dt" +%s`
up_dt_sec=`date -d "$up_dt" +%s`
uptime_sec=$(( curr_dt_sec - up_dt_sec ))
echo CurrDateTime: $curr_dt_sec
echo UpDateTime: $up_dt_sec
echo Uptime: $uptime_sec
# Report CPU usage (%)
echo CPU: `top -b -n1 | grep "Cpu(s)" | awk '{print $2 + $4}'`
......
......@@ -7,7 +7,7 @@
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.melodic.event.control.info;
package eu.melodic.event.common.misc;
import eu.melodic.event.brokercep.BrokerCepService;
import eu.melodic.event.brokercep.event.EventMap;
......@@ -21,8 +21,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
......@@ -37,12 +35,12 @@ public class SystemResourceMonitor implements Runnable, InitializingBean {
@Getter @Setter
private boolean enabled = Boolean.parseBoolean(
System.getenv().getOrDefault("EMS_SYSMON_ENABLED", "true"));
@Getter @Setter @Min(1000)
@Getter @Setter
private long period = Math.max(1000L,Long.parseLong(
System.getenv().getOrDefault("EMS_SYSMON_PERIOD", "30000")));
@Getter @Setter @NotBlank
private String commandStr = System.getenv("EMS_SYSMON_COMMAND");
@Getter @Setter @NotBlank
@Getter @Setter
private String commandStr = System.getenv().getOrDefault("EMS_SYSMON_COMMAND", "./bin/sysmon.sh");
@Getter @Setter
private String systemResourceMetricsTopic = System.getenv("EMS_SYSMON_TOPIC");
private final BrokerCepService brokerCepService;
......
......@@ -12,6 +12,7 @@ package eu.melodic.event.control.info;
import eu.melodic.event.baguette.server.BaguetteServer;
import eu.melodic.event.baguette.server.ClientShellCommand;
import eu.melodic.event.brokercep.BrokerCepService;
import eu.melodic.event.common.misc.SystemResourceMonitor;
import eu.melodic.event.control.ControlServiceCoordinator;
import eu.melodic.event.control.properties.ControlServiceProperties;
import eu.melodic.event.translate.TranslationContext;
......
......@@ -49,6 +49,15 @@ class Utils {
return new Date(data * mult).toISOString().substr(start, len);
}
toDuration(data) {
let s = data % 60;
data = (data-s) / 60;
let m = data % 60;
data = (data-m) / 60;
let h = data;
return h+':'+new String(100+m).substr(1)+':'+new String(100+s).substr(1);
}
toKB(data) {
return (data) ? (Math.round(precision * data / 1024) / precision).toString() : data;
}
......@@ -70,6 +79,17 @@ class Utils {
}).format(num);
}
orderOfMagnitude(data) {
if (!data || typeof data !== 'number') return false;
let d = Math.abs(data);
let r = 0;
while (d>=1) {
r++;
d = d / 10;
}
return r;
}
/*updateSelect(newVal, targetMap, valueField, textField) {
// add new or update targetMap entries
for (let c of newVal) {
......
......@@ -57,20 +57,29 @@
<td class="align-middle text-center">
<div class="row p-0 m-0">
<div class="col-md-3 m-0 border-left border-right">
<small>CPU:</small>
<Sparkline type="bullet" width="100%" height="10px" :values="[.7,c.stats.cpu,1]"></Sparkline>
<small>CPU: {{currentClientUsage(c.id, 'cpu', 0)+'%'}}</small>
<Sparkline type="line" width="100%" height="20px"
:values="clientUsageData(c.id, 'cpu')"
:options="{ type: 'line', lineWidth: 1, chartRangeMin: 0, chartRangeMax: sparkLineRangeMax(clientUsageData(c.id, 'cpu')) }"
></Sparkline>
</div>
<div class="col-md-3 m-0 border-left border-right">
<small>Mem:</small>
<Sparkline type="bullet" width="100%" height="10px" :values="[.8,c.stats.mem,1]"></Sparkline>
<small>Mem: {{currentClientUsage(c.id, 'ram', 0)+'%'}}</small>
<Sparkline type="line" width="100%" height="20px"
:values="clientUsageData(c.id, 'ram')"
:options="{ type: 'line', lineWidth: 1, chartRangeMin: 0, chartRangeMax: sparkLineRangeMax(clientUsageData(c.id, 'ram')) }"
></Sparkline>
</div>
<div class="col-md-3 m-0 border-left border-right">
<small style="white-space: pre;">#Events:</small><br/>
<Sparkline type="line" width="100%" height="20px" :values="[0,0,10,8,0]"></Sparkline>
<small style="white-space: pre;">#Events: {{currentClientUsage(c.id, 'count-total-events', 0)}}</small><br/>
<Sparkline type="line" width="100%" height="20px"
:values="clientUsageData(c.id, 'count-total-events')"
:options="{ type: 'line', lineWidth: 1, chartRangeMin: 0, chartRangeMax: sparkLineRangeMax(clientUsageData(c.id, 'count-total-events')) }"
></Sparkline>
</div>
<div class="col-md-3 m-0 border-left border-right">
<small style="white-space: nowrap;">Uptime:</small><br/>
<small style="white-space: nowrap;">{{c.stats.uptime ? "toIsoFormat(c.stats.uptime,'sec','time')" : '--:--:--'}}</small>
<small style="white-space: nowrap;">{{clientUptime(c.id) ?? '--:--:--'}}</small>
</div>
</div>
</td>
......@@ -153,12 +162,15 @@
<td class="align-middle text-center">
<div class="row p-0 m-0">
<div class="col-md-6 m-0 border-left border-right">
<small style="white-space: pre;">#Events:</small><br/>
<Sparkline type="line" width="100%" height="20px" :values="[0,0,10,8,0]"></Sparkline>
<small style="white-space: pre;">#Events: {{currentClientUsage(c.id, 'count-total-events', 0)}}</small><br/>
<Sparkline type="line" width="100%" height="20px"
:values="clientUsageData(c.id, 'count-total-events')"
:options="{ type: 'line', lineWidth: 1, chartRangeMin: 0, chartRangeMax: sparkLineRangeMax(clientUsageData(c.id, 'count-total-events')) }"
></Sparkline>
</div>
<div class="col-md-6 m-0 border-left border-right">
<small style="white-space: nowrap;">Uptime:</small><br/>
<small style="white-space: nowrap;">{{c.stats.uptime ? 'xx:xx:xx' : '--:--:--'}}</small>
<small style="white-space: nowrap;">{{clientUptime(c.id) ?? '--:--:--'}}</small>
</div>
</div>
</td>
......@@ -357,17 +369,22 @@ import 'vue3-blocks-tree/dist/vue3-blocks-tree.css';
import ActionsList from './widgets/node-actions-list';
import JVectorMap from '@/components/jvectormap/jvectormap.vue';
//import utils from '@/utils.js';
import LeafletMap from '@/components/leaflet-map/leaflet-map.vue';
import countryCoords from './country-coordinates.js';
import utils from '@/utils.js';
import { TimeWindow } from '@/components/ems/ts/ts.js';
const TIME_WINDOW_LENGTH = 5*60; // seconds
export default {
name: 'Admin Dashboard',
components: { Card, Sparkline, VueBlocksTree, JVectorMap, LeafletMap, ActionsList },
props: {
modelValue: Object,
clientStats: Object,
sseRef: String
},
data() {
//const winLocation = window.location.hostname;
......@@ -386,12 +403,21 @@ export default {
},
clientMarkers: [],
clientConnections: [],
clientStatsTimeseries: { },
dataWindow: 60,
defaultChartGridValues: {
l0: [0, 0]
}
};
},
watch: {
modelValue: function() {
this.initData();
},
clientStats: function() {
this.updateClientStats();
this.updateClusterStats();
},
treeData: function() {
//console.log("TREE DATA UPDATED: ", this.treeData);
}
......@@ -406,6 +432,8 @@ export default {
let _getWebsshServiceUrl = this.getWebsshServiceUrl;
this.clientStatsTimeseries = { };
$('#ssh-console-dialog').dialog({
autoOpen : false,
modal : true,
......@@ -682,6 +710,93 @@ export default {
// -------------------------------------------------------------------------------------------------------------
updateClientStats() {
for (const [id, data] of Object.entries(this.clientStats)) {
if (!this.clientStatsTimeseries[id]) {
let sseInterval = this.$root.$refs[this.sseRef].getCurrentInterval();
this.clientStatsTimeseries[id] = new TimeWindow(TIME_WINDOW_LENGTH, sseInterval);
}
if (data) this.clientStatsTimeseries[id].add(data);
}
},
updateClusterStats() {
if (!this.treeData) return;
for (const zoneObj of this.treeData.children) {
let zoneId = zoneObj.id;
if (!this.clientStatsTimeseries[zoneId]) {
let sseInterval = this.$root.$refs[this.sseRef].getCurrentInterval();
this.clientStatsTimeseries[zoneId] = new TimeWindow(TIME_WINDOW_LENGTH, sseInterval);
}
let maxUptime = -1;
let totalEvents = 0;
let clientCount = 0;
for (const clientObj of zoneObj.children) {
let clientId = clientObj.id;
let clientUptime = this.currentClientUsageData(clientId, 'uptime');
let clientTotalEvents = this.currentClientUsageData(clientId, 'count-total-events');
if (maxUptime < clientUptime) maxUptime = clientUptime;
totalEvents += clientTotalEvents;
clientCount++;
}
this.clientStatsTimeseries[zoneId].add({ 'uptime': maxUptime, 'count-total-events': totalEvents, 'count-clients': clientCount });
}
},
clientUptime(id) {
return utils.toDuration( this.currentClientUsageData(id, 'uptime') );
},
currentClientUsageData(id, metric, prefix) {
if (!id || !this.clientStatsTimeseries || !this.clientStatsTimeseries[id]) return null;
let v = this.clientStatsTimeseries[id].getLast();
if (!v || !(metric in v)) return null;
v = v[metric];
if (prefix==='KB') v = parseFloat(utils.toKB(v));
return v;
},
currentClientUsage(id, metric, precision, prefix) {
let v = this.currentClientUsageData(id, metric, prefix);
if (v==null) return '--';
if (v>=10) precision = 0;
//return v.toFixed(precision);
return utils.toNum(v, precision);
},
clientUsageData(id, metric, prefix) {
if (!id || !this.clientStatsTimeseries || !this.clientStatsTimeseries[id]) return [ 0 ];
var values = this.clientStatsTimeseries[id].getWindowData(this.dataWindow).map(data => (data && (metric in data)) ? data[metric] : 0);
if (prefix==='KB') values = values.map(x => parseFloat(utils.toKB(x)));
return values;
},
clientUsageDataAndLines(id, metric, gridValues) {
var result = {};
if (Array.isArray(metric)) {
for (let m in metric)
result[m] = this.clientUsageData(id, m);
} else
result[metric] = this.clientUsageData(id, metric);
result = Object.assign(result, gridValues ?? this.defaultChartGridValues);
return result;
},
/*clusterUsageData(id, metric, prefix) {
},*/
sparkLineRangeMax(numArr) {
let numMax = Math.max( ...numArr );
let order = utils.orderOfMagnitude(numMax);
let order2 = Math.pow(10, order);
let normalized = numMax / order2;
let factor = Math.min(Math.ceil(10 * normalized), 10);
return factor * order2 / 10;
},
// -------------------------------------------------------------------------------------------------------------
updateClientMarkers(clients) {
// Update markers of EMS server and clients
let markers = [ ];
......
......@@ -21,7 +21,7 @@
</Section>
<Section title="Topology" :collapsed="false" background="rgba(0,255,100,.3)">
<TopologySection v-model="ems" />
<TopologySection v-model="ems" :clientStats="clients['client-metrics']" :sseRef="sseRef" />
</Section>
<!--<Section title="Geography" background="rgba(250,170,0,.25)">
......@@ -81,7 +81,7 @@ export default {
console.log('EMS-SSE data updated');
if (newVal && newVal.data) {
if (newVal.data.ems) this.ems = newVal.data.ems;
//if (newVal.data.clients) this.clients = newVal.data.clients;
if (newVal.data.clients) this.clients = newVal.data.clients;
if (this.sysmonTimeseries!=null && this.ems && utils.valueExists(this.ems, 'system-info.system-resource-metrics')) {
let sysmonData = utils.getValue(this.ems, 'system-info.system-resource-metrics');
......@@ -102,6 +102,7 @@ export default {
return {
showOverviewHeader: true,
ems: { },
clients: { },
sysmonTimeseries: null,
};
},
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment