m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/client/AttributeInput.java (renamed from src/main/java/pl/edu/mimuw/cloudatlas/client/Attribute.java)2
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/client/Client.java2
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/client/ClientController.java174
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/client/DataStringInput.java (renamed from src/main/java/pl/edu/mimuw/cloudatlas/client/ContactsString.java)2
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java3
-rw-r--r--src/main/resources/pl/edu/mimuw/cloudatlas/client/templates/attribChart.html67
6 files changed, 220 insertions, 30 deletions
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/client/Attribute.java b/src/main/java/pl/edu/mimuw/cloudatlas/client/AttributeInput.java
index abaa02a..58e1a30 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/client/Attribute.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/client/AttributeInput.java
@@ -2,7 +2,7 @@ package pl.edu.mimuw.cloudatlas.client;
import pl.edu.mimuw.cloudatlas.model.Value;
-public class Attribute {
+public class AttributeInput {
private String zoneName;
private String attributeName;
private String valueString;
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/client/Client.java b/src/main/java/pl/edu/mimuw/cloudatlas/client/Client.java
index b63e4fd..ca9e7e0 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/client/Client.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/client/Client.java
@@ -2,8 +2,10 @@ package pl.edu.mimuw.cloudatlas.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
+@EnableScheduling
public class Client {
public static void main(String[] args) {
SpringApplication.run(Client.class, args);
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/client/ClientController.java b/src/main/java/pl/edu/mimuw/cloudatlas/client/ClientController.java
index 022c665..ae649c0 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/client/ClientController.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/client/ClientController.java
@@ -1,6 +1,7 @@
package pl.edu.mimuw.cloudatlas.client;
import com.google.gson.Gson;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
@@ -8,13 +9,9 @@ import pl.edu.mimuw.cloudatlas.api.Api;
import pl.edu.mimuw.cloudatlas.model.*;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/*
should enable reading attribute values stored by the agent
@@ -31,6 +28,10 @@ plotting the attributes with numeric values as real-time graphs.
public class ClientController {
private Api api;
+ private Map<ValueTime, AttributesMap> attributes;
+ private String currentZoneName;
+ private static final int MAX_ENTRIES = 10;
+
ClientController() {
try {
Registry registry = LocateRegistry.getRegistry("localhost");
@@ -39,6 +40,13 @@ public class ClientController {
System.err.println("Client exception:");
e.printStackTrace();
}
+
+ this.attributes = new LinkedHashMap<ValueTime, AttributesMap>() {
+ protected boolean removeEldestEntry(Map.Entry<ValueTime, AttributesMap> eldest) {
+ return size() > MAX_ENTRIES;
+ }
+ };
+ this.currentZoneName = "/uw/violet07";
}
@GetMapping("/")
@@ -80,11 +88,11 @@ public class ClientController {
@GetMapping("/contacts")
public String contactPage(Model model) {
- model.addAttribute("contactsObject" , new ContactsString());
+ model.addAttribute("contactsObject" , new DataStringInput());
return "contactsForm";
}
- private Set<ValueContact> parseContactsString(ContactsString contactsInput) throws Exception {
+ private Set<ValueContact> parseContactsString(DataStringInput contactsInput) throws Exception {
Gson gson = new Gson();
Map<String, ArrayList> contactStrings = gson.fromJson(contactsInput.getString(), Map.class);
Set<ValueContact> contactObjects = new HashSet<ValueContact>();
@@ -106,7 +114,7 @@ public class ClientController {
}
@PostMapping("/contacts")
- public String contactPage(@ModelAttribute ContactsString contactsObject, Model model) {
+ public String contactPage(@ModelAttribute DataStringInput contactsObject, Model model) {
boolean success = true;
Set<ValueContact> contactObjects;
@@ -130,11 +138,11 @@ public class ClientController {
@GetMapping("/attribs")
public String attribPage(Model model) {
- model.addAttribute("attributeObject", new Attribute());
+ model.addAttribute("attributeObject", new AttributeInput());
return "attribForm";
}
- private Value parseAttributeValue(Attribute attributeObject) throws Exception {
+ private Value parseAttributeValue(AttributeInput attributeObject) throws Exception {
Value attributeValue = null;
switch (attributeObject.getAttributeType()) {
@@ -159,7 +167,7 @@ public class ClientController {
attributeValue = new ValueDuration(attributeObject.getValueString());
break;
case "Contact":
- ContactsString contactsString = new ContactsString();
+ DataStringInput contactsString = new DataStringInput();
contactsString.setString(attributeObject.getValueString());
attributeValue = parseContactsString(contactsString).iterator().next();
break;
@@ -175,7 +183,7 @@ public class ClientController {
}
@PostMapping("/attribs")
- public String attribPage(@ModelAttribute Attribute attributeObject, Model model) {
+ public String attribPage(@ModelAttribute AttributeInput attributeObject, Model model) {
boolean success = true;
Value attributeValue;
@@ -200,8 +208,150 @@ public class ClientController {
return "home";
}
+ private String getAvailableZonesString() {
+ boolean success = true;
+ Set<String> availableZones;
+ String availableZonesString = "";
+
+ try {
+ availableZones = api.getZoneSet();
+ availableZonesString = availableZones.toString().substring(1, availableZones.toString().length() - 1);
+ } catch (Exception e) {
+ success = false;
+ System.err.println("Client exception:");
+ e.printStackTrace();
+ }
+
+ if (success) {
+ return "Available zones are: " + availableZonesString;
+ } else {
+ return "No zones available, error occured during fetch";
+ }
+ }
+
@GetMapping("/values")
public String valuesPage(Model model) {
+ model.addAttribute("availableZones", getAvailableZonesString());
+ model.addAttribute("currentZone", "Current zone: " + this.currentZoneName);
+ model.addAttribute("zoneName", new DataStringInput());
+ return "attribChart";
+ }
+
+ @Scheduled(fixedRate = 5000)
+ private void fetchAttributeData() {
+ AttributesMap attribData;
+ ValueTime currentTime;
+
+ try {
+ if (!this.currentZoneName.isEmpty()) {
+ attribData = api.getZoneAttributeValues(this.currentZoneName);
+ currentTime = new ValueTime(System.currentTimeMillis());
+ this.attributes.put(currentTime, attribData);
+ }
+ } catch (Exception e) {
+ System.err.println("Client exception:");
+ e.printStackTrace();
+ }
+ }
+
+ private ArrayList getAllAttributeValues(AttributesMap attribs, Boolean justNumerical) {
+ ArrayList valuesList = new ArrayList<>();
+ Value val;
+
+ for (Map.Entry<Attribute, Value> entry : attribs) {
+ val = entry.getValue();
+ // casting to ValueDouble and ValueInt caused some errors
+ // and gson turns all numerical values into doubles anyway
+ if (justNumerical && isValueNumerical(val)) {
+ valuesList.add(Double.parseDouble(val.toString()));
+ } else if (!justNumerical) {
+ valuesList.add(val.toString());
+ }
+ }
+
+ return valuesList;
+ }
+
+ private boolean isValueNumerical(Value val) {
+ Type valType = val.getType();
+
+ if (TypePrimitive.DOUBLE.isCompatible(valType) || TypePrimitive.INTEGER.isCompatible(valType)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private AttributesMap getLastAttributesMap() {
+ ArrayList<Map.Entry<ValueTime, AttributesMap>> attribsMap = new ArrayList<>(this.attributes.entrySet());
+ return attribsMap.get(attribsMap.size() - 1).getValue();
+ }
+
+ private ArrayList<String> getAttributesColumnNames(Boolean justNumerical) {
+ ArrayList<String> chartValueNames = new ArrayList<>();
+ AttributesMap lastAttribMap = getLastAttributesMap();
+
+ for (Map.Entry<Attribute, Value> e : lastAttribMap) {
+ if (!justNumerical || isValueNumerical(e.getValue())) {
+ chartValueNames.add(e.getKey().getName());
+ }
+ }
+ chartValueNames.add(0, "Timestamp");
+ return chartValueNames;
+ }
+
+ // data format compatible with Google Charts Table and Google Line Chart input
+ // but it's a generic 2d array table representation
+ // https://developers.google.com/chart/interactive/docs/gallery/table
+ private ArrayList<ArrayList> getValuesTable(Boolean justNumerical) {
+ ArrayList valueRow;
+ ArrayList<ArrayList> allValues = new ArrayList<>();
+ ArrayList<String> valueNames = getAttributesColumnNames(justNumerical);
+
+ for (Map.Entry<ValueTime, AttributesMap> attribMapEntry : this.attributes.entrySet()) {
+ valueRow = getAllAttributeValues(attribMapEntry.getValue(), justNumerical);
+ while (valueRow.size() < valueNames.size() - 1) {
+ valueRow.add(null);
+ }
+ valueRow.add(0, attribMapEntry.getKey().toString().substring(11, 19));
+ allValues.add(valueRow);
+ }
+
+ // optional trimming of table length
+ // if (allValues.size() > 10) {
+ // allValues = new ArrayList<ArrayList>(allValues.subList(allValues.size() - 11, allValues.size() - 1));
+ // }
+ allValues.add(0, valueNames);
+ return allValues;
+ }
+
+ private String processAttribValues(ArrayList<ArrayList> valuesTable) {
+ String jsonAttributes = "";
+ Gson gson = new Gson();
+ jsonAttributes = gson.toJson(valuesTable);
+ System.out.println(jsonAttributes);
+ return jsonAttributes;
+ }
+
+ @GetMapping("/attribNumValues")
+ @ResponseBody
+ public String attribNumValuesApi() {
+ return processAttribValues(getValuesTable(true));
+ }
+
+ @GetMapping("/attribAllValues")
+ @ResponseBody
+ public String attribAllValuesApi() {
+ return processAttribValues(getValuesTable(false));
+ }
+
+ @PostMapping("/values")
+ public String valuesPage(@ModelAttribute DataStringInput zoneName, Model model) {
+ this.currentZoneName = zoneName.getString();
+ this.attributes.clear();
+ model.addAttribute("currentZone", "Current zone: " + this.currentZoneName);
+ model.addAttribute("availableZones", getAvailableZonesString());
+ model.addAttribute("zoneName", new DataStringInput());
return "attribChart";
}
}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/client/ContactsString.java b/src/main/java/pl/edu/mimuw/cloudatlas/client/DataStringInput.java
index 7cdd82e..e1465c7 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/client/ContactsString.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/client/DataStringInput.java
@@ -1,6 +1,6 @@
package pl.edu.mimuw.cloudatlas.client;
-public class ContactsString {
+public class DataStringInput {
private String string;
public String getString() {
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
index c74c1df..3391417 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
@@ -27,6 +27,7 @@ package pl.edu.mimuw.cloudatlas.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -34,7 +35,7 @@ import java.util.Map.Entry;
* Represents a map from <code>Attribute</code> to <code>Value</code>. It cannot contain duplicate keys.
*/
public class AttributesMap implements Iterable<Entry<Attribute, Value>>, Cloneable, Serializable {
- private Map<Attribute, Value> map = new HashMap<Attribute, Value>();
+ private Map<Attribute, Value> map = new LinkedHashMap<Attribute, Value>();
private void checkNulls(Attribute attribute, Value value) {
if(attribute == null)
diff --git a/src/main/resources/pl/edu/mimuw/cloudatlas/client/templates/attribChart.html b/src/main/resources/pl/edu/mimuw/cloudatlas/client/templates/attribChart.html
index 7e0b37f..1a49261 100644
--- a/src/main/resources/pl/edu/mimuw/cloudatlas/client/templates/attribChart.html
+++ b/src/main/resources/pl/edu/mimuw/cloudatlas/client/templates/attribChart.html
@@ -6,38 +6,75 @@
<title>Attributes chart</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript">
+ google.charts.load('current', {'packages':['table']});
google.charts.load('current', {'packages':['corechart']});
- google.charts.setOnLoadCallback(drawChart);
+ google.charts.setOnLoadCallback(refreshChart());
- function drawChart() {
- var data = google.visualization.arrayToDataTable([
- ['Year', 'Sales', 'Expenses'],
- ['2013', 1000, 400],
- ['2014', 1170, 460],
- ['2015', 660, 1120],
- ['2016', 1030, 540]
- ]);
+ function refreshChart() {
+ setInterval(getData, 5000);
+ }
+
+ function getData(){
+ $.getJSON("/attribAllValues", function(data, status){
+ drawTable(data)
+ });
+ $.getJSON("/attribNumValues", function(data, status){
+ drawChart(data)
+ });
+ }
+
+ function drawChart(jsonData) {
+ var data = google.visualization.arrayToDataTable(jsonData);
var options = {
- title: 'Company Performance',
- hAxis: {title: 'Year', titleTextStyle: {color: '#333'}},
- vAxis: {minValue: 0}
+ title: 'Attribute values',
+ hAxis: {title: 'Timestamp', titleTextStyle: {color: '#333'}},
+ vAxis: {scaleType: 'log'},
+ interpolateNulls: true,
+ crosshair: {trigger: 'both', orientation: 'vertical'}
};
- var chart = new google.visualization.AreaChart(document.getElementById('chart_div'));
+ var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
+
+ function drawTable(jsonData) {
+ var data = new google.visualization.DataTable();
+ for (let col of jsonData[0]) {
+ data.addColumn('string', col);
+ }
+ jsonData.shift();
+ data.addRows(jsonData);
+
+ var table = new google.visualization.Table(document.getElementById('table_div'));
+
+ table.draw(data, {width: '100%', height: '100%'});
+ }
+
</script>
</head>
<body>
<div th:replace="fragments/navbar :: navbar"></div>
+<h4 th:text="${currentZone}"></h4>
+<div id="zoneAttribForm">
+ <form action="#" th:action="@{/values}" th:object="${zoneName}" method="post">
+ <div class="form-group">
+ <label for="ZoneName1">Enter zone name to get attribute values</label>
+ <input type="text" class="form-control" id="ZoneName1" rows="3" th:field="*{string}"/>
+ <small id="passwordHelpBlock" class="form-text text-muted" th:text="${availableZones}">
+ </small>
+ </div>
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </form>
+</div>
+<div id="table_div"></div>
<div id="chart_div" style="width: 100%; height: 500px;"></div>
-<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>