/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger.engine.e1.scenario;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.griddynamics.jagger.coordinator.NodeId;
import com.griddynamics.jagger.util.SystemClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.*;
import static com.griddynamics.jagger.util.DecimalUtil.areEqual;
import static com.griddynamics.jagger.util.DecimalUtil.compare;
public class DefaultTpsRouter implements TpsRouter {
private static final Logger log = LoggerFactory.getLogger(DefaultTpsRouter.class);
private final DesiredTps desiredTps;
private final MaxTpsCalculator maxTpsCalculator;
private final SystemClock clock;
private final Map<NodeId, BigDecimal> desiredTpsPerNode;
public DefaultTpsRouter(DesiredTps desiredTps, MaxTpsCalculator maxTpsCalculator, SystemClock clock) {
this.desiredTps = desiredTps;
this.maxTpsCalculator = maxTpsCalculator;
this.clock = clock;
this.desiredTpsPerNode = Maps.newHashMap();
}
public Map<NodeId, BigDecimal> getDesiredTpsPerNode(Map<NodeId, ? extends NodeTpsStatistics> tpsStat) {
// todo maybe pass interval?
BigDecimal tps = desiredTps.get(clock.currentTimeMillis());
initialize(tpsStat.keySet());
final Map<NodeId, BigDecimal> maxTpsPerNode = Maps.newHashMap();
Set<NodeId> nodes = desiredTpsPerNode.keySet();
for (NodeId node : nodes) {
log.debug("Going to detect max tps for node {}", node);
BigDecimal maxTps = maxTpsCalculator.getMaxTps(tpsStat.get(node));
if (maxTps == null) {
log.debug("Max tps for node {} cannot be detected. Using desired tps as max", node);
maxTps = tps;
}
log.debug("Max tps for node {} is {}", node, maxTps);
maxTpsPerNode.put(node, maxTps);
}
List<NodeId> nodeList = Lists.newLinkedList(nodes);
Collections.sort(nodeList, new Comparator<NodeId>() {
@Override
public int compare(NodeId first, NodeId second) {
return maxTpsPerNode.get(first).compareTo(maxTpsPerNode.get(second));
}
});
log.debug("Recalculating desired tps per node...");
BigDecimal missingTps = BigDecimal.ZERO;
for (int i = 0; i < nodeList.size(); i++) {
NodeId node = nodeList.get(i);
BigDecimal desiredTpsForNode = desiredTpsPerNode.get(node);
BigDecimal maxTpsForNode = maxTpsPerNode.get(node);
log.debug("For node {} desired tps {} max tps {}", new Object[]{node, desiredTpsForNode, maxTpsForNode});
if (compare(desiredTpsForNode, maxTpsForNode) > 0) {
log.debug("Cannot increase tps at node {} to {}", node, desiredTpsForNode);
BigDecimal delta = desiredTpsForNode.subtract(maxTpsForNode);
log.debug("{} tps should be produced by other nodes", delta);
missingTps = missingTps.add(delta);
log.debug("Total missing tps is {}", missingTps);
desiredTpsPerNode.put(node, maxTpsForNode);
continue;
}
BigDecimal newTpsForNode = desiredTpsForNode.add(missingTps.divide(new BigDecimal(nodes.size() - i)));
if (areEqual(desiredTpsForNode, newTpsForNode)) {
log.debug("Node {} tps should not be changed", node);
continue;
}
log.debug("Node {} is capable to produce more tps", node);
log.debug("New desired tps for node is {}", newTpsForNode);
if (compare(newTpsForNode, maxTpsForNode) > 0) {
log.debug("Cannot increase tps at node {} to {}", node, newTpsForNode);
log.debug("Increasing tps on node to it's max {}", maxTpsForNode);
desiredTpsPerNode.put(node, maxTpsForNode);
missingTps = missingTps.subtract(maxTpsForNode);
log.debug("Total missing tps is {}", missingTps);
continue;
}
desiredTpsPerNode.put(node, newTpsForNode);
log.debug("Tps on node {} will be changed to {}", node, newTpsForNode);
missingTps = missingTps.subtract(newTpsForNode);
log.debug("Total missing tps {}", tps);
}
return desiredTpsPerNode;
}
@Override
public BigDecimal getDesiredTps() {
return desiredTps.getDesiredTps();
}
private void initialize(Set<NodeId> nodes) {
BigDecimal avgTps = desiredTps.get(clock.currentTimeMillis()).divide(new BigDecimal(nodes.size()), 3, BigDecimal.ROUND_HALF_UP);
for (NodeId node : nodes) {
desiredTpsPerNode.put(node, avgTps);
}
}
}