Put AMPL generation code in separate file
The amount of code was getting a bit much to leave this inside the `NebulousApp` class. Change-Id: I31228c904a9b458d4cf90829d73684399d9cc655
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
package eu.nebulouscloud.optimiser.controller;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate AMPL from an app message. This class could live as a couple of
|
||||||
|
* methods in {@link NebulousApp} but we group all things AMPL in this file
|
||||||
|
* for better readability.
|
||||||
|
*/
|
||||||
|
public class AMPLGenerator {
|
||||||
|
/**
|
||||||
|
* Generate AMPL code for the app, based on the parameter definition(s).
|
||||||
|
* Public for testability, not because we'll be calling it outside of its
|
||||||
|
* class.
|
||||||
|
*/
|
||||||
|
public static String generateAMPL(NebulousApp app) {
|
||||||
|
final StringWriter result = new StringWriter();
|
||||||
|
final PrintWriter out = new PrintWriter(result);
|
||||||
|
out.format("# AMPL file for application '%s' with id %s%n", app.getName(), app.getUUID());
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Variables");
|
||||||
|
for (final JsonNode p : app.getKubevelaVariables()) {
|
||||||
|
ObjectNode param = (ObjectNode) p;
|
||||||
|
String param_name = param.get("key").textValue();
|
||||||
|
String param_path = param.get("path").textValue();
|
||||||
|
String param_type = param.get("type").textValue();
|
||||||
|
ObjectNode value = (ObjectNode)param.get("value");
|
||||||
|
if (param_type.equals("float")) {
|
||||||
|
out.format("var %s", param_name);
|
||||||
|
if (value != null) {
|
||||||
|
String separator = "";
|
||||||
|
JsonNode lower = value.get("lower_bound");
|
||||||
|
JsonNode upper = value.get("upper_bound");
|
||||||
|
// `isNumber` because the constraint might be given as integer
|
||||||
|
if (lower.isNumber()) {
|
||||||
|
out.format(" >= %s", lower.doubleValue());
|
||||||
|
separator = ", ";
|
||||||
|
}
|
||||||
|
if (upper.isNumber()) {
|
||||||
|
out.format("%s<= %s", separator, upper.doubleValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.format("; # %s%n", param_path);
|
||||||
|
} else if (param_type.equals("int")) {
|
||||||
|
out.format("var %s integer", param_name);
|
||||||
|
if (value != null) {
|
||||||
|
String separator = "";
|
||||||
|
JsonNode lower = value.get("lower_bound");
|
||||||
|
JsonNode upper = value.get("upper_bound");
|
||||||
|
if (lower.isIntegralNumber()) {
|
||||||
|
out.format(" >= %s", lower.longValue());
|
||||||
|
separator = ", ";
|
||||||
|
}
|
||||||
|
if (upper.isIntegralNumber()) {
|
||||||
|
out.format("%s<= %s", separator, upper.longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.format("; # %s%n", param_path);
|
||||||
|
} else if (param_type.equals("string")) {
|
||||||
|
out.println("# TODO not sure how to specify a string variable");
|
||||||
|
out.format("var %s symbolic; # %s%n", param_name, param_path);
|
||||||
|
} else if (param_type.equals("array")) {
|
||||||
|
out.format("# TODO generate entries for map '%s' at %s%n", param_name, param_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Raw metrics");
|
||||||
|
out.println("# TODO: here we should also have initial values!");
|
||||||
|
for (final JsonNode m : app.getRawMetrics().values()) {
|
||||||
|
out.format("param %s; # %s%n", m.get("key").asText(), m.get("name").asText());
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Composite metrics");
|
||||||
|
out.println("# TODO: here we should also have initial values!");
|
||||||
|
for (final JsonNode m : app.getCompositeMetrics().values()) {
|
||||||
|
out.format("param %s; # %s%n", m.get("key").asText(), m.get("name").asText());
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Performance indicators = composite metrics that have at least one variable in their formula");
|
||||||
|
for (final JsonNode m : app.getPerformanceIndicators().values()) {
|
||||||
|
String formula = replaceVariables(m.get("formula").asText(), m.withObject("mapping"));
|
||||||
|
out.format("# %s : %s%n", m.get("name").asText(), m.get("formula").asText());
|
||||||
|
out.format("param %s = %s;%n", m.get("key").asText(), formula);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# TBD: cost parameters - for all components! and use of node-candidates tensor");
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Utility functions");
|
||||||
|
for (JsonNode f : app.getOriginalAppMessage().withArray(NebulousApp.utility_function_path)) {
|
||||||
|
String formula = replaceVariables(f.get("formula").asText(), f.withObject("mapping"));
|
||||||
|
out.format("# %s : %s%n", f.get("name").asText(), f.get("formula").asText());
|
||||||
|
out.format("%s %s :%n %s;%n",
|
||||||
|
f.get("type").asText(), f.get("key").asText(),
|
||||||
|
formula);
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
out.println("# Default utility function: tbd");
|
||||||
|
out.println();
|
||||||
|
out.println("# Constraints. For constraints we don't have name from GUI, must be created");
|
||||||
|
out.println("# TODO: generate from 'slo' hierarchical entry");
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace variables in formulas.
|
||||||
|
*
|
||||||
|
* @param formula a string like "A + B".
|
||||||
|
* @param mappings an object with mapping from variables to their
|
||||||
|
* replacements.
|
||||||
|
* @return the formula, with all variables replaced.
|
||||||
|
*/
|
||||||
|
private static String replaceVariables(String formula, ObjectNode mappings) {
|
||||||
|
// If AMPL needs more rewriting of the formula than just variable name
|
||||||
|
// replacement, we should parse the formula here. For now, since
|
||||||
|
// variables are word-shaped, we can hopefully get by with regular
|
||||||
|
// expressions on the string representation of the formula.
|
||||||
|
StringBuilder result = new StringBuilder(formula);
|
||||||
|
Pattern id = Pattern.compile("\\b(\\w+)\\b");
|
||||||
|
Matcher matcher = id.matcher(formula);
|
||||||
|
int lengthDiff = 0;
|
||||||
|
while (matcher.find()) {
|
||||||
|
String var = matcher.group(1);
|
||||||
|
JsonNode re = mappings.get(var);
|
||||||
|
if (re != null) {
|
||||||
|
int start = matcher.start(1) + lengthDiff;
|
||||||
|
int end = matcher.end(1) + lengthDiff;
|
||||||
|
result.replace(start, end, re.asText());
|
||||||
|
lengthDiff += re.asText().length() - var.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ public class LocalExecution implements Callable<Integer> {
|
|||||||
log.info("Sending AMPL to channel {}", publisher);
|
log.info("Sending AMPL to channel {}", publisher);
|
||||||
app.sendAMPL();
|
app.sendAMPL();
|
||||||
}
|
}
|
||||||
System.out.println(app.generateAMPL());
|
System.out.println(AMPLGenerator.generateAMPL(app));
|
||||||
// TODO: wait for solver reply here?
|
// TODO: wait for solver reply here?
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -22,8 +20,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.Spliterators;
|
import java.util.Spliterators;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@@ -54,6 +50,7 @@ public class NebulousApp {
|
|||||||
/** Locations of the UUID and name in the app creation message (String) */
|
/** Locations of the UUID and name in the app creation message (String) */
|
||||||
private static final JsonPointer uuid_path = JsonPointer.compile("/application/uuid");
|
private static final JsonPointer uuid_path = JsonPointer.compile("/application/uuid");
|
||||||
private static final JsonPointer name_path = JsonPointer.compile("/application/name");
|
private static final JsonPointer name_path = JsonPointer.compile("/application/name");
|
||||||
|
public static final JsonPointer utility_function_path = JsonPointer.compile("/utility_functions");
|
||||||
|
|
||||||
/** The YAML converter */
|
/** The YAML converter */
|
||||||
// Note that instantiating this is apparently expensive, so we do it only once
|
// Note that instantiating this is apparently expensive, so we do it only once
|
||||||
@@ -87,18 +84,23 @@ public class NebulousApp {
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private String UUID;
|
private String UUID;
|
||||||
/** The app name; used as SAL job name as well */
|
/** The app name, a user-defined string. Not safe to assume that this is
|
||||||
private String app_name;
|
* a unique value. */
|
||||||
private JsonNode original_app_message;
|
@Getter private String name;
|
||||||
|
/** The original app message. */
|
||||||
|
@Getter private JsonNode originalAppMessage;
|
||||||
private ObjectNode original_kubevela;
|
private ObjectNode original_kubevela;
|
||||||
private ArrayNode kubevela_variables;
|
/** The array of KubeVela variables in the app message. */
|
||||||
|
@Getter private ArrayNode kubevelaVariables;
|
||||||
|
|
||||||
/** Map from AMPL variable name to location in KubeVela. */
|
/** Map from AMPL variable name to location in KubeVela. */
|
||||||
private Map<String, JsonPointer> kubevela_variable_paths = new HashMap<>();
|
private Map<String, JsonPointer> kubevela_variable_paths = new HashMap<>();
|
||||||
/** Raw metrics. */
|
/** The app's raw metrics, a map from key to the defining JSON node. */
|
||||||
private Map<String, JsonNode> raw_metrics = new HashMap<>();
|
@Getter private Map<String, JsonNode> rawMetrics = new HashMap<>();
|
||||||
private Map<String, JsonNode> composite_metrics = new HashMap<>();
|
/** The app's composite metrics, a map from key to the defining JSON node. */
|
||||||
private Map<String, JsonNode> performance_indicators = new HashMap<>();
|
@Getter private Map<String, JsonNode> compositeMetrics = new HashMap<>();
|
||||||
|
/** The app's performance indicators, a map from key to the defining JSON node. */
|
||||||
|
@Getter private Map<String, JsonNode> performanceIndicators = new HashMap<>();
|
||||||
|
|
||||||
/** When an app gets deployed or redeployed, this is where we send the AMPL file */
|
/** When an app gets deployed or redeployed, this is where we send the AMPL file */
|
||||||
private Publisher ampl_message_channel;
|
private Publisher ampl_message_channel;
|
||||||
@@ -111,16 +113,24 @@ public class NebulousApp {
|
|||||||
*
|
*
|
||||||
* @param app_message The whole app creation message (JSON)
|
* @param app_message The whole app creation message (JSON)
|
||||||
* @param kubevela A parsed representation of the deployable KubeVela App model (YAML)
|
* @param kubevela A parsed representation of the deployable KubeVela App model (YAML)
|
||||||
* @param parameters A parameter mapping as a sequence of JSON objects (JSON)
|
* @param ampl_message_channel A publisher for sending the generated AMPL file, or null
|
||||||
*/
|
*/
|
||||||
// Note that example KubeVela and parameter files can be found at
|
// Note that example KubeVela and parameter files can be found at
|
||||||
// optimiser-controller/src/test/resources/
|
// optimiser-controller/src/test/resources/
|
||||||
public NebulousApp(JsonNode app_message, ObjectNode kubevela, ArrayNode parameters, Publisher ampl_message_channel) {
|
public NebulousApp(JsonNode app_message, ObjectNode kubevela, Publisher ampl_message_channel) {
|
||||||
this.original_app_message = app_message;
|
this.UUID = app_message.at(uuid_path).textValue();
|
||||||
|
this.name = app_message.at(name_path).textValue();
|
||||||
|
this.originalAppMessage = app_message;
|
||||||
this.original_kubevela = kubevela;
|
this.original_kubevela = kubevela;
|
||||||
this.kubevela_variables = parameters;
|
JsonNode parameters = app_message.at(variables_path);
|
||||||
|
if (parameters.isArray()) {
|
||||||
|
this.kubevelaVariables = (ArrayNode)app_message.at(variables_path);
|
||||||
|
} else {
|
||||||
|
log.error("Cannot read parameters from app message '{}', continuing without parameters", UUID);
|
||||||
|
this.kubevelaVariables = mapper.createArrayNode();
|
||||||
|
}
|
||||||
this.ampl_message_channel = ampl_message_channel;
|
this.ampl_message_channel = ampl_message_channel;
|
||||||
for (final JsonNode p : parameters) {
|
for (final JsonNode p : kubevelaVariables) {
|
||||||
kubevela_variable_paths.put(p.get("key").asText(),
|
kubevela_variable_paths.put(p.get("key").asText(),
|
||||||
yqPathToJsonPointer(p.get("path").asText()));
|
yqPathToJsonPointer(p.get("path").asText()));
|
||||||
}
|
}
|
||||||
@@ -140,16 +150,16 @@ public class NebulousApp {
|
|||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
JsonNode m = it.next();
|
JsonNode m = it.next();
|
||||||
if (m.get("type").asText().equals("raw")) {
|
if (m.get("type").asText().equals("raw")) {
|
||||||
raw_metrics.put(m.get("key").asText(), m);
|
rawMetrics.put(m.get("key").asText(), m);
|
||||||
it.remove();
|
it.remove();
|
||||||
done = false;
|
done = false;
|
||||||
} else {
|
} else {
|
||||||
ObjectNode mappings = m.withObject("mapping");
|
ObjectNode mappings = m.withObject("mapping");
|
||||||
boolean is_composite_metric = StreamSupport.stream(
|
boolean is_composite_metric = StreamSupport.stream(
|
||||||
Spliterators.spliteratorUnknownSize(mappings.elements(), Spliterator.ORDERED), false)
|
Spliterators.spliteratorUnknownSize(mappings.elements(), Spliterator.ORDERED), false)
|
||||||
.allMatch(o -> raw_metrics.containsKey(o.asText()) || composite_metrics.containsKey(o.asText()));
|
.allMatch(o -> rawMetrics.containsKey(o.asText()) || compositeMetrics.containsKey(o.asText()));
|
||||||
if (is_composite_metric) {
|
if (is_composite_metric) {
|
||||||
composite_metrics.put(m.get("key").asText(), m);
|
compositeMetrics.put(m.get("key").asText(), m);
|
||||||
it.remove();
|
it.remove();
|
||||||
done = false;
|
done = false;
|
||||||
}
|
}
|
||||||
@@ -158,11 +168,9 @@ public class NebulousApp {
|
|||||||
}
|
}
|
||||||
for (JsonNode m : metrics) {
|
for (JsonNode m : metrics) {
|
||||||
// What's left is neither a raw nor composite metric.
|
// What's left is neither a raw nor composite metric.
|
||||||
performance_indicators.put(m.get("key").asText(), m);
|
performanceIndicators.put(m.get("key").asText(), m);
|
||||||
}
|
}
|
||||||
this.UUID = app_message.at(uuid_path).textValue();
|
log.info("New App instantiated: Name='{}', UUID='{}'", name, UUID);
|
||||||
this.app_name = app_message.at(name_path).textValue();
|
|
||||||
log.info("New App instantiated: Name='{}', UUID='{}'", app_name, UUID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,7 +190,6 @@ public class NebulousApp {
|
|||||||
} else {
|
} else {
|
||||||
return new NebulousApp(app_message,
|
return new NebulousApp(app_message,
|
||||||
(ObjectNode)yaml_mapper.readTree(kubevela_string),
|
(ObjectNode)yaml_mapper.readTree(kubevela_string),
|
||||||
(ArrayNode)parameters,
|
|
||||||
ampl_message_channel);
|
ampl_message_channel);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -217,7 +224,7 @@ public class NebulousApp {
|
|||||||
* @return true if all requirements hold, false otherwise
|
* @return true if all requirements hold, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean validatePaths() {
|
public boolean validatePaths() {
|
||||||
for (final Object p : kubevela_variables) {
|
for (final Object p : kubevelaVariables) {
|
||||||
ObjectNode param = (ObjectNode) p;
|
ObjectNode param = (ObjectNode) p;
|
||||||
String param_name = param.get("key").textValue();
|
String param_name = param.get("key").textValue();
|
||||||
if (param_name == null || param_name.equals("")) return false;
|
if (param_name == null || param_name.equals("")) return false;
|
||||||
@@ -311,141 +318,13 @@ public class NebulousApp {
|
|||||||
log.error("AMPL publisher not set, cannot send AMPL file");
|
log.error("AMPL publisher not set, cannot send AMPL file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ampl = generateAMPL();
|
String ampl = AMPLGenerator.generateAMPL(this);
|
||||||
ObjectNode msg = mapper.createObjectNode();
|
ObjectNode msg = mapper.createObjectNode();
|
||||||
msg.put(getUUID() + ".ampl", ampl);
|
msg.put(getUUID() + ".ampl", ampl);
|
||||||
ampl_message_channel.send(mapper.convertValue(msg, Map.class), getUUID());
|
ampl_message_channel.send(mapper.convertValue(msg, Map.class), getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate AMPL code for the app, based on the parameter definition(s).
|
|
||||||
* Public for testability, not because we'll be calling it outside of its
|
|
||||||
* class.
|
|
||||||
*/
|
|
||||||
public String generateAMPL() {
|
|
||||||
final StringWriter result = new StringWriter();
|
|
||||||
final PrintWriter out = new PrintWriter(result);
|
|
||||||
out.println("# AMPL file for application with id " + getUUID());
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Variables");
|
|
||||||
for (final JsonNode p : kubevela_variables) {
|
|
||||||
ObjectNode param = (ObjectNode) p;
|
|
||||||
String param_name = param.get("key").textValue();
|
|
||||||
String param_path = param.get("path").textValue();
|
|
||||||
String param_type = param.get("type").textValue();
|
|
||||||
ObjectNode value = (ObjectNode)param.get("value");
|
|
||||||
if (param_type.equals("float")) {
|
|
||||||
out.format("var %s", param_name);
|
|
||||||
if (value != null) {
|
|
||||||
String separator = "";
|
|
||||||
JsonNode lower = value.get("lower_bound");
|
|
||||||
JsonNode upper = value.get("upper_bound");
|
|
||||||
// `isNumber` because the constraint might be given as integer
|
|
||||||
if (lower.isNumber()) {
|
|
||||||
out.format(" >= %s", lower.doubleValue());
|
|
||||||
separator = ", ";
|
|
||||||
}
|
|
||||||
if (upper.isNumber()) {
|
|
||||||
out.format("%s<= %s", separator, upper.doubleValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.format("; # %s%n", param_path);
|
|
||||||
} else if (param_type.equals("int")) {
|
|
||||||
out.format("var %s integer", param_name);
|
|
||||||
if (value != null) {
|
|
||||||
String separator = "";
|
|
||||||
JsonNode lower = value.get("lower_bound");
|
|
||||||
JsonNode upper = value.get("upper_bound");
|
|
||||||
if (lower.isIntegralNumber()) {
|
|
||||||
out.format(" >= %s", lower.longValue());
|
|
||||||
separator = ", ";
|
|
||||||
}
|
|
||||||
if (upper.isIntegralNumber()) {
|
|
||||||
out.format("%s<= %s", separator, upper.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.format("; # %s%n", param_path);
|
|
||||||
} else if (param_type.equals("string")) {
|
|
||||||
out.println("# TODO not sure how to specify a string variable");
|
|
||||||
out.format("var %s symbolic; # %s%n", param_name, param_path);
|
|
||||||
} else if (param_type.equals("array")) {
|
|
||||||
out.format("# TODO generate entries for map '%s' at %s%n", param_name, param_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Raw metrics");
|
|
||||||
out.println("# TODO: here we should also have initial values!");
|
|
||||||
for (final JsonNode m : raw_metrics.values()) {
|
|
||||||
out.format("param %s; # %s%n", m.get("key").asText(), m.get("name").asText());
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Composite metrics");
|
|
||||||
out.println("# TODO: here we should also have initial values!");
|
|
||||||
for (final JsonNode m : composite_metrics.values()) {
|
|
||||||
out.format("param %s; # %s%n", m.get("key").asText(), m.get("name").asText());
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Performance indicators = composite metrics that have at least one variable in their formula");
|
|
||||||
for (final JsonNode m : performance_indicators.values()) {
|
|
||||||
String formula = replaceVariables(m.get("formula").asText(), m.withObject("mapping"));
|
|
||||||
out.format("# %s : %s%n", m.get("name").asText(), m.get("formula").asText());
|
|
||||||
out.format("param %s = %s;%n", m.get("key").asText(), formula);
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# TBD: cost parameters - for all components! and use of node-candidates tensor");
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Utility functions");
|
|
||||||
for (JsonNode f : original_app_message.withArray("/utility_functions")) {
|
|
||||||
String formula = replaceVariables(f.get("formula").asText(), f.withObject("mapping"));
|
|
||||||
out.format("# %s : %s%n", f.get("name").asText(), f.get("formula").asText());
|
|
||||||
out.format("%s %s :%n %s;%n",
|
|
||||||
f.get("type").asText(), f.get("key").asText(),
|
|
||||||
formula);
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
out.println("# Default utility function: tbd");
|
|
||||||
out.println();
|
|
||||||
out.println("# Constraints. For constraints we don't have name from GUI, must be created");
|
|
||||||
out.println("# TODO: generate from 'slo' hierarchical entry");
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace variables in formulas.
|
|
||||||
*
|
|
||||||
* @param formula a string like "A + B".
|
|
||||||
* @param mappings an object with mapping from variables to their
|
|
||||||
* replacements.
|
|
||||||
* @return the formula, with all variables replaced.
|
|
||||||
*/
|
|
||||||
private String replaceVariables(String formula, ObjectNode mappings) {
|
|
||||||
// If AMPL needs more rewriting of the formula than just variable name
|
|
||||||
// replacement, we should parse the formula here. For now, since
|
|
||||||
// variables are word-shaped, we can hopefully get by with regular
|
|
||||||
// expressions on the string representation of the formula.
|
|
||||||
StringBuilder result = new StringBuilder(formula);
|
|
||||||
Pattern id = Pattern.compile("\\b(\\w+)\\b");
|
|
||||||
Matcher matcher = id.matcher(formula);
|
|
||||||
int lengthDiff = 0;
|
|
||||||
while (matcher.find()) {
|
|
||||||
String var = matcher.group(1);
|
|
||||||
JsonNode re = mappings.get(var);
|
|
||||||
if (re != null) {
|
|
||||||
int start = matcher.start(1) + lengthDiff;
|
|
||||||
int end = matcher.end(1) + lengthDiff;
|
|
||||||
result.replace(start, end, re.asText());
|
|
||||||
lengthDiff += re.asText().length() - var.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle incoming solver message.
|
* Handle incoming solver message.
|
||||||
@@ -563,7 +442,7 @@ public class NebulousApp {
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// 1. Create SAL job
|
// 1. Create SAL job
|
||||||
log.info("Creating job info");
|
log.info("Creating job info");
|
||||||
JobInformation jobinfo = new JobInformation(UUID, app_name);
|
JobInformation jobinfo = new JobInformation(UUID, name);
|
||||||
// TODO: figure out what ports to specify here
|
// TODO: figure out what ports to specify here
|
||||||
List<Communication> communications = List.of();
|
List<Communication> communications = List.of();
|
||||||
// This task is deployed on the controller node (the one not specified
|
// This task is deployed on the controller node (the one not specified
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
# AMPL file for application with id f81ee-b42a8-a13d56-e28ec9-2f5578
|
var face_detection_edge_worker_cpu >= 1.2, <= 3.0;
|
||||||
|
var face_detection_edge_worker_memory >= 250.0, <= 1000.0;
|
||||||
|
var face_detection_edge_worker_count integer >= 0, <= 5;
|
||||||
|
# TODO generate entries for map 'face_detection_edge_workers' -> I think it's not needed
|
||||||
|
var face_detection_cloud_worker_cpu >= 3.0, <= 6.0;
|
||||||
|
var face_detection_cloud_worker_memory >= 1000.0, <= 4000.0;
|
||||||
|
var face_detection_cloud_worker_count integer >= 2, <= 10;
|
||||||
|
# TODO generate entries for map 'face_detection_cloud_workers'
|
||||||
|
|
||||||
# Variables
|
#Raw and composite metrics that are independent from running configuration. The values will be provided by the solver.
|
||||||
var face_detection_cloud_worker_cpu >= 3.0, <= 6.0; # .spec.components[3].properties.cpu
|
param TotalCoresUsed;
|
||||||
var face_detection_cloud_worker_memory >= 1000.0, <= 4000.0; # .spec.components[3].properties.memory
|
param AvgResponseTime;
|
||||||
var face_detection_cloud_worker_count integer; # .spec.components[3].traits[1].properties.replicas
|
|
||||||
|
|
||||||
# Raw metrics
|
|
||||||
# TODO: here we should also have initial values!
|
#Performance indicators = composite metrics that have at least one variable in their formula
|
||||||
param CoresUsed; # CoresUsed
|
param TotalCoresUsedFraction = TotalCoresUsed/(face_detection_edge_worker_cpu*face_detection_edge_worker_count+face_detection_cloud_worker_cpu*face_detection_cloud_worker_count);
|
||||||
param ResponseTime; # ResponseTime
|
param AvgResponseTimePerComponent = AvgResponseTime/(face_detection_edge_worker_count+face_detection_cloud_worker_count);
|
||||||
|
|
||||||
# Composite metrics
|
# Composite metrics
|
||||||
# TODO: here we should also have initial values!
|
# TODO: here we should also have initial values!
|
||||||
@@ -24,15 +30,16 @@ param AvgResponseTimePerComponent = AvgResponseTime/face_detection_cloud_worker_
|
|||||||
# TBD: cost parameters - for all components! and use of node-candidates tensor
|
# TBD: cost parameters - for all components! and use of node-candidates tensor
|
||||||
|
|
||||||
#Utility functions
|
#Utility functions
|
||||||
# Utility Function 1 : A
|
maximize utility_function_1:
|
||||||
minimize utility_function_1 :
|
|
||||||
AvgResponseTimePerComponent;
|
|
||||||
# Utility Function 2 : A
|
|
||||||
maximize utility_function_2 :
|
|
||||||
TotalCoresUsedFraction;
|
TotalCoresUsedFraction;
|
||||||
|
|
||||||
# Default utility function: tbd
|
minimize utility_function_2:
|
||||||
|
AvgResponseTimePerComponent;
|
||||||
|
|
||||||
# Constraints. For constraints we don't have name from GUI, must be created
|
# Constraints. For constraints we don't have name from GUI, must be created
|
||||||
# TODO: generate from 'slo' hierarchical entry
|
# TODO: generate from 'slo' hierarchical entry
|
||||||
|
|
||||||
|
|
||||||
|
#Constraints. For constraints we don't have name from GUI, must be created
|
||||||
|
subject to constraint_1: AvgResponseTimePerComponent < 3600;
|
||||||
|
subject to constraint_2: TotalCoresUsedFraction > 0.2 and TotalCoresUsedFraction < 0.9;
|
||||||
|
|||||||
Reference in New Issue
Block a user