com.griddynamics.jagger.engine.e1.scenario.DefaultWorkloadSuggestionMaker.java Source code

Java tutorial

Introduction

Here is the source code for com.griddynamics.jagger.engine.e1.scenario.DefaultWorkloadSuggestionMaker.java

Source

/*
 * 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 GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 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.google.common.collect.Table;
import com.griddynamics.jagger.util.DecimalUtil;
import com.griddynamics.jagger.util.Pair;
import com.griddynamics.jagger.util.TimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.*;

import static com.griddynamics.jagger.util.DecimalUtil.areEqual;

public class DefaultWorkloadSuggestionMaker implements WorkloadSuggestionMaker {
    private static final Logger log = LoggerFactory.getLogger(DefaultWorkloadSuggestionMaker.class);

    private static final WorkloadConfiguration CALIBRATION_CONFIGURATION = WorkloadConfiguration.with(1, 0);
    private static final int MIN_DELAY = 10;

    private final int maxDiff;

    public DefaultWorkloadSuggestionMaker(int maxDiff) {
        this.maxDiff = maxDiff;
    }

    @Override
    public WorkloadConfiguration suggest(BigDecimal desiredTps, NodeTpsStatistics statistics, int maxThreads) {
        log.debug("Going to suggest workload configuration. desired tps {}. statistics {}", desiredTps, statistics);

        Table<Integer, Integer, Pair<Long, BigDecimal>> threadDelayStats = statistics.getThreadDelayStats();

        if (areEqual(desiredTps, BigDecimal.ZERO)) {
            return WorkloadConfiguration.with(0, 0);
        }

        if (threadDelayStats.isEmpty()) {
            throw new IllegalArgumentException("Cannot suggest workload configuration");
        }

        if (!threadDelayStats.contains(CALIBRATION_CONFIGURATION.getThreads(),
                CALIBRATION_CONFIGURATION.getDelay())) {
            log.debug("Statistics is empty. Going to return calibration info.");
            return CALIBRATION_CONFIGURATION;
        }
        if (threadDelayStats.size() == 2 && areEqual(threadDelayStats.get(1, 0).getSecond(), BigDecimal.ZERO)) {
            log.warn("No calibration info. Going to retry.");
            return CALIBRATION_CONFIGURATION;
        }

        Map<Integer, Pair<Long, BigDecimal>> noDelays = threadDelayStats.column(0);

        Integer threadCount = findClosestPoint(desiredTps, noDelays);

        if (threadCount == 0) {
            threadCount = 1;
        }

        if (threadCount > maxThreads) {
            log.warn("{} calculated max {} allowed", threadCount, maxThreads);
            threadCount = maxThreads;
        }

        int currentThreads = statistics.getCurrentWorkloadConfiguration().getThreads();
        int diff = threadCount - currentThreads;
        if (diff > maxDiff) {
            log.debug("Increasing to {} is required current thread count is {} max allowed diff is {}",
                    new Object[] { threadCount, currentThreads, maxDiff });
            return WorkloadConfiguration.with(currentThreads + maxDiff, 0);
        }

        if (noDelays.containsKey(threadCount) && noDelays.get(threadCount).getSecond().compareTo(desiredTps) < 0) {
            if (log.isDebugEnabled()) {
                log.debug("Statistics for current point has been already calculated and it is less then desired one"
                        + "\nLook like we have achieved maximum for this node."
                        + "\nGoing to help max tps detector.");
            }
            int threads = currentThreads;
            if (threads < maxThreads) {
                threads++;
            }
            return WorkloadConfiguration.with(threads, 0);
        }

        if (!threadDelayStats.contains(threadCount, 0)) {
            return WorkloadConfiguration.with(threadCount, 0);
        }

        Map<Integer, Pair<Long, BigDecimal>> delays = threadDelayStats.row(threadCount);

        if (delays.size() == 1) {
            int delay = suggestDelay(delays.get(0).getSecond(), threadCount, desiredTps);

            return WorkloadConfiguration.with(threadCount, delay);
        }

        Integer delay = findClosestPoint(desiredTps, threadDelayStats.row(threadCount));

        return WorkloadConfiguration.with(threadCount, delay);

    }

    private static Integer findClosestPoint(BigDecimal desiredTps, Map<Integer, Pair<Long, BigDecimal>> stats) {
        SortedMap<Long, Integer> map = Maps.newTreeMap(new Comparator<Long>() {
            @Override
            public int compare(Long first, Long second) {
                return second.compareTo(first);
            }
        });
        for (Map.Entry<Integer, Pair<Long, BigDecimal>> entry : stats.entrySet()) {
            map.put(entry.getValue().getFirst(), entry.getKey());
        }

        if (map.size() < 2) {
            throw new IllegalArgumentException("Not enough stats to calculate point");
        }

        Iterator<Map.Entry<Long, Integer>> iterator = map.entrySet().iterator();
        Integer firstPoint = iterator.next().getValue();
        Integer secondPoint = iterator.next().getValue();

        if (firstPoint > secondPoint) {
            Integer temp = secondPoint;
            secondPoint = firstPoint;
            firstPoint = temp;
        }

        BigDecimal x1 = new BigDecimal(firstPoint);
        BigDecimal z1 = stats.get(firstPoint).getSecond();

        BigDecimal x2 = new BigDecimal(secondPoint);
        BigDecimal z2 = stats.get(secondPoint).getSecond();

        BigDecimal a = x2.subtract(x1);
        BigDecimal c = z2.subtract(z1);

        if (areEqual(c, BigDecimal.ZERO)) {
            return firstPoint;
        }

        // Line equation
        // y - y1 = ((y2 - y1)/(x2 - x1))*(x-x1)
        BigDecimal approxPoint = desiredTps.subtract(z1).multiply(a).divide(c, 3, BigDecimal.ROUND_HALF_UP).add(x1);

        Integer result = 0;
        if (DecimalUtil.compare(approxPoint, BigDecimal.ZERO) > 0) {
            approxPoint = approxPoint.divide(BigDecimal.ONE, 0, BigDecimal.ROUND_UP);
            result = approxPoint.intValue();
        }
        return result;
    }

    private static int suggestDelay(BigDecimal tpsFromStat, Integer threadCount, BigDecimal desiredTps) {
        BigDecimal oneSecond = new BigDecimal(TimeUtils.secondsToMillis(1));
        BigDecimal result = oneSecond.multiply(new BigDecimal(threadCount)).divide(desiredTps, 3,
                BigDecimal.ROUND_HALF_UP);
        result = result.subtract(
                oneSecond.multiply(new BigDecimal(threadCount)).divide(tpsFromStat, 3, BigDecimal.ROUND_HALF_UP));

        int i = result.intValue();
        if (i == 0) {
            i = MIN_DELAY;
        }
        return i;
    }

    private static Integer findClosestPoint(Set<Integer> points, Integer point) {
        List<Integer> list = Lists.newArrayList(points);
        Collections.sort(list);

        int index = list.indexOf(point);
        if (index == -1) {
            throw new IllegalStateException("Point is not found");
        }

        Integer left = null;
        if (index != 0) {
            left = list.get(index - 1);
        }

        Integer right = null;
        if (index != (list.size() - 1)) {
            right = list.get(index + 1);
        }

        if (left == null) {
            return right;
        }

        if (right == null) {
            return left;
        }

        if ((right - index) < (index - left)) {
            return right;
        }

        return left;
    }

}