package resa.optimize; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import resa.util.ConfigUtil; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * Created by Tom.fu on 23/4/2014. * Chain topology and not tuple split */ public class SimpleGeneralServiceModel { private static final Logger LOG = LoggerFactory.getLogger(SimpleGeneralServiceModel.class); /** * Like module A in our discussion * * @param components, the service node configuration, in this function, chain topology is assumed. * @param allocation, can be null input, in this case, directly return Infinity to indicator topology unstable * @return Double.MAX_VALUE when a) input allocation is null (i.e., system is unstable) * b) any one of the node is unstable (i.e., lambda/mu > 1, in which case, sn.estErlangT will be Double.MAX_VALUE) * else the validate estimated erlang service time. */ public static double getErlangGeneralTopCompleteTime(Map<String, ServiceNode> components, Map<String, Integer> allocation) { if (allocation == null) { return Double.MAX_VALUE; } double retVal = 0.0; for (Map.Entry<String, ServiceNode> e : components.entrySet()) { String cid = e.getKey(); ServiceNode sn = e.getValue(); Integer serverCount = allocation.get(cid); // Objects.requireNonNull(serverCount, "No allocation entry find for this component" + cid); double est = sn.estErlangT(serverCount); if (est < Double.MAX_VALUE) { retVal += (est * sn.getI2oRatio()); } else { return Double.MAX_VALUE; } } return retVal; } public static double getErlangGeneralTopCompleteTimeMilliSec(Map<String, ServiceNode> components, Map<String, Integer> allocation) { double result = getErlangGeneralTopCompleteTime(components, allocation); return result < Double.MAX_VALUE ? (result * 1000.0) : Double.MAX_VALUE; } public static Map<String, Integer> getAllocation(Map<String, ServiceNode> components, Map<String, Object> para) { Map<String, Integer> retVal = new HashMap<>(); components.forEach((cid, sn) -> { int curr = ConfigUtil.getInt(para, cid, 0); retVal.put(cid, curr); }); return retVal; } public static boolean checkStable(Map<String, ServiceNode> components, Map<String, Integer> allocation) { return components.entrySet().stream().map(e -> e.getValue().isStable(allocation.get(e.getKey()))) .allMatch(Boolean.TRUE::equals); } public static int getTotalMinRequirement(Map<String, ServiceNode> components) { return components.values().stream().mapToInt(ServiceNode::getMinReqServerCount).sum(); } public static void printAllocation(Map<String, Integer> allocation) { if (allocation == null) { LOG.warn("Null allocation input -> system is unstable."); } else { LOG.info("allocation->" + allocation); } } /** * @param components * @param totalResourceCount * @return null if a) minReq of any component is Integer.MAX_VALUE (invalid parameter mu = 0.0) * b) total minReq can not be satisfied (total minReq > totalResourceCount) * otherwise, the Map data structure. */ public static Map<String, Integer> suggestAllocationGeneralTop(Map<String, ServiceNode> components, int totalResourceCount) { Map<String, Integer> retVal = components.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getMinReqServerCount())); int topMinReq = retVal.values().stream().mapToInt(Integer::intValue).sum(); LOG.info("totalResourceCount: " + totalResourceCount + ", topMinReq: " + topMinReq); if (topMinReq <= totalResourceCount) { int remainCount = totalResourceCount - topMinReq; for (int i = 0; i < remainCount; i++) { double maxDiff = -1; String maxDiffCid = null; for (Map.Entry<String, ServiceNode> e : components.entrySet()) { String cid = e.getKey(); ServiceNode sn = e.getValue(); int currentAllocated = retVal.get(cid); double beforeAddT = sn.estErlangT(currentAllocated); double afterAddT = sn.estErlangT(currentAllocated + 1); double diff = (beforeAddT - afterAddT) * sn.getI2oRatio(); if (diff > maxDiff) { maxDiff = diff; maxDiffCid = cid; } } if (maxDiffCid != null) { int newAllocate = retVal.compute(maxDiffCid, (k, count) -> count + 1); LOG.debug((i + 1) + " of " + remainCount + ", assigned to " + maxDiffCid + ", newAllocate: " + newAllocate); } else { LOG.info("Null MaxDiffCid returned in " + (i + 1) + " of " + remainCount); for (Map.Entry<String, ServiceNode> e : components.entrySet()) { String cid = e.getKey(); ServiceNode sn = e.getValue(); int currentAllocated = retVal.get(cid); double beforeAddT = sn.estErlangT(currentAllocated); double afterAddT = sn.estErlangT(currentAllocated + 1); LOG.info(cid + ", currentAllocated: " + currentAllocated + ", beforeAddT: " + beforeAddT + ", afterAddT: " + afterAddT); } return retVal; } } } else { return null; } return retVal; } /** * Like Module A', input required QoS, output #threads required * Here we separate to two modules: first output allocation, then calculate total #threads included. * Caution all the computation involved is in second unit. * * @param components * @param maxAllowedCompleteTime, the unit here is second! consistent with function getErlangChainTopCompleteTime() * @param lowerBoundDelta, this is to set the offset of the lowerBoundServiceTime, we require delta to be positive, and 0 as default. * @param adjRatio, this is to adjust the estimated ErlangServiceTime to fit more closely to the real measured complte time * @return null if a) any service node is not in the valid state (mu = 0.0), this is not the case of rho > 1.0, just for checking mu * b) lowerBoundServiceTime > requiredQoS */ public static Map<String, Integer> getMinReqServerAllocationGeneralTop(Map<String, ServiceNode> components, double maxAllowedCompleteTime, double lowerBoundDelta, double adjRatio, int maxAvailableExec) { double lowerBoundServiceTime = 0.0; int totalMinReq = 0; for (Map.Entry<String, ServiceNode> e : components.entrySet()) { double mu = e.getValue().getMu(); ///caution, the unit should be millisecond lowerBoundServiceTime += (1.0 / mu); totalMinReq += e.getValue().getMinReqServerCount(); } Map<String, Integer> currAllocation = null; if (lowerBoundServiceTime * adjRatio + lowerBoundDelta < maxAllowedCompleteTime) { double currTime; do { currAllocation = suggestAllocationGeneralTop(components, totalMinReq); currTime = getErlangGeneralTopCompleteTime(components, currAllocation) * adjRatio; LOG.info("getMinReqServAllcQoS: " + maxAllowedCompleteTime * 1000.0 + ", currTime(ms): " + currTime * 1000.0 / adjRatio + ", currAdj(ms): " + currTime * 1000.0 + ", totalMinReqQoS: " + totalMinReq); totalMinReq++; //check: we need to check totalMinReq to avoid infinite loop! } while (currTime > maxAllowedCompleteTime && totalMinReq <= maxAvailableExec); } return totalMinReq <= maxAvailableExec ? currAllocation : null; } public static Map<String, Integer> getMinReqServerAllocationGeneralTop(Map<String, ServiceNode> components, double maxAllowedCompleteTime, int maxAvailableExec) { return getMinReqServerAllocationGeneralTop(components, maxAllowedCompleteTime, 0.0, 1.0, maxAvailableExec); } public static Map<String, Integer> getMinReqServerAllocationGeneralTop(Map<String, ServiceNode> components, double maxAllowedCompleteTime, double adjRatio, int maxAvailableExec) { return getMinReqServerAllocationGeneralTop(components, maxAllowedCompleteTime, 0.0, adjRatio, maxAvailableExec); } public static int totalServerCountInvolved(Map<String, Integer> allocation) { return Objects.requireNonNull(allocation).values().stream().mapToInt(i -> i).sum(); } /** * @param queueingNetwork * @param realLatencyMilliSec * @param targetQoSMilliSec * @param currBoltAllocation * @param maxAvailable4Bolt * @return status indicates whether the demanded QoS can be achieved or not * minReqAllocaiton, the minimum required resource (under optimized allocation) which can satisfy QoS * after: the optimized allocation under given maxAvailable4Bolt */ public static AllocResult checkOptimized(Map<String, ServiceNode> queueingNetwork, double realLatencyMilliSec, double targetQoSMilliSec, Map<String, Integer> currBoltAllocation, int maxAvailable4Bolt) { ///Caution about the time unit!, second is used in all the functions of calculation /// millisecond is used in the output display! double estimatedLatencyMilliSec = getErlangGeneralTopCompleteTimeMilliSec(queueingNetwork, currBoltAllocation); ///for better estimation, we remain (learn) this ratio, and assume that the estimated is always smaller than real. double underEstimateRatio = Math.max(1.0, realLatencyMilliSec / estimatedLatencyMilliSec); LOG.info("estLatency(ms): " + estimatedLatencyMilliSec + ", realLatency(ms)" + realLatencyMilliSec + ", underEstRatio: " + underEstimateRatio); LOG.info("Find out minReqAllocation under QoS requirement."); Map<String, Integer> minReqAllocation = getMinReqServerAllocationGeneralTop(queueingNetwork, targetQoSMilliSec / 1000.0, underEstimateRatio, maxAvailable4Bolt * 2); AllocResult.Status status = AllocResult.Status.FEASIBALE; if (minReqAllocation == null) { status = AllocResult.Status.INFEASIBLE; } LOG.info("Find out best allocation given available executors."); Map<String, Integer> after = suggestAllocationGeneralTop(queueingNetwork, maxAvailable4Bolt); Map<String, Object> context = new HashMap<>(); context.put("estLatency", estimatedLatencyMilliSec); context.put("realLatency", realLatencyMilliSec); context.put("underEstRatio", underEstimateRatio); return new AllocResult(status, minReqAllocation, after).setContext(context); } }