eu.stratosphere.nephele.streaming.taskmanager.chaining.TaskChainer.java Source code

Java tutorial

Introduction

Here is the source code for eu.stratosphere.nephele.streaming.taskmanager.chaining.TaskChainer.java

Source

/***********************************************************************************************************************
 *
 * Copyright (C) 2010 by the Stratosphere project (http://stratosphere.eu)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 **********************************************************************************************************************/
package eu.stratosphere.nephele.streaming.taskmanager.chaining;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;

import org.apache.commons.lang3.tuple.Pair;

import eu.stratosphere.nephele.streaming.taskmanager.qosreporter.QosReporterConfigCenter;

/**
 * @author Bjoern Lohrmann
 */
public class TaskChainer {

    /**
     * Holds a list adjacent chains. The order in this list corresponds to their
     * actual order during execution. Each chain is one thread, plus an
     * undefined number of user threads created by the user code in the tasks.
     */
    private final ArrayList<TaskChain> chains = new ArrayList<TaskChain>();

    private final ExecutorService backgroundWorkers;

    private final QosReporterConfigCenter configCenter;

    public TaskChainer(List<TaskInfo> chainingCandidates, ExecutorService backgroundWorkers,
            QosReporterConfigCenter configCenter) {

        this.backgroundWorkers = backgroundWorkers;
        this.configCenter = configCenter;

        for (TaskInfo task : chainingCandidates) {
            this.chains.add(new TaskChain(task));
        }
    }

    public void collectCpuUtilizations() {
        for (TaskChain chain : this.chains) {
            chain.measureCPUUtilizationIfPossible();
        }
    }

    public void attemptDynamicChaining() {
        if (!areChainsReadyForDynamicChaining()) {
            return;
        }

        mergeChainsIfPossible();
        splitChainsIfNecessary();
    }

    /**
     * Searches for chains that continually use up more than one CPU core and
     * splits all of them up into two chains in the background.
     */
    private void splitChainsIfNecessary() {

        int currChainIndex = 0;
        while (currChainIndex < this.chains.size()) {
            TaskChain chain = this.chains.get(currChainIndex);

            if (chain.hasCPUUtilizationMeasurements() && chain.getCPUUtilization() > 99
                    && chain.getNumberOfChainedTasks() > 1) {

                Pair<TaskChain, TaskChain> splitResult = splitChain(chain);
                this.chains.set(currChainIndex, splitResult.getLeft());
                this.chains.add(currChainIndex + 1, splitResult.getRight());
                currChainIndex = currChainIndex + 2;
            } else {
                currChainIndex++;
            }
        }
    }

    /**
     * Searches for the highest number of chains that can be merged into a
     * single chain without using up more than one CPU core (actually 91% of a
     * core to allow for fluctuations) merges them in the background.
     */
    private boolean mergeChainsIfPossible() {
        // index of chain where to start the merge (inclusive)
        int bestMergeStart = -1;

        // index of chain where to end the merge (exclusive)
        int bestMergeEnd = 0;

        double bestMergeCpuUtil = 0;

        for (int currMergeStart = 0; currMergeStart < this.chains.size() - 1; currMergeStart++) {

            double currMergeCpuUtil = 0;

            int currMergeEnd = currMergeStart;

            while (currMergeEnd < this.chains.size()) {
                double newCpuUtil = this.chains.get(currMergeEnd).getCPUUtilization();
                if (currMergeCpuUtil + newCpuUtil <= 91.0) {
                    currMergeCpuUtil += newCpuUtil;
                } else {
                    break;
                }
                currMergeEnd++;
            }

            if (currMergeEnd - currMergeStart > 1 && currMergeCpuUtil > bestMergeCpuUtil) {
                bestMergeStart = currMergeStart;
                bestMergeEnd = currMergeEnd;
                bestMergeCpuUtil = currMergeCpuUtil;
            }
        }

        if (bestMergeStart != -1) {
            mergeChains(bestMergeStart, bestMergeEnd);
            return true;
        }

        return false;
    }

    /**
     * Splits up the chain at the given index into two chains with the goal of
     * having one remainder chain that never uses more than one CPU core and
     * another remainder chain that uses as little CPU as possible. The idea
     * behind this is for the latter remainder chain to be split up later on (if
     * it is uses a lot of CPU) or be merged with another chain (if it uses
     * little CPU) at a later point.
     */
    private Pair<TaskChain, TaskChain> splitChain(TaskChain flow) {

        // pair contains: <noOfTasksToUnchain, estimatedRemainingCPUUtilization>
        Pair<Integer, Double> beginUnchainProposal = computeUnchainingProposal(flow, true);
        Pair<Integer, Double> endUnchainProposal = computeUnchainingProposal(flow, false);

        // The remainder CPU util x is 0%<x<99% in both unchaining proposals. We
        // will choose the proposal with the higher x.
        int splitIndex;
        if (beginUnchainProposal.getRight() >= endUnchainProposal.getRight()) {
            splitIndex = beginUnchainProposal.getLeft();
        } else {
            splitIndex = flow.getNumberOfChainedTasks() - endUnchainProposal.getLeft();
        }

        return TaskChain.splitAndAnnounceChain(flow, splitIndex, this.backgroundWorkers, this.configCenter);
    }

    private Pair<Integer, Double> computeUnchainingProposal(TaskChain chain, boolean unchainAtBeginning) {

        int noOfChainedTasks = chain.getNumberOfChainedTasks();
        double estimatedRemainingCpuUtil = chain.getCPUUtilization();

        int noOfTasksToUnchain = 0;
        while (estimatedRemainingCpuUtil >= 99 && noOfTasksToUnchain < noOfChainedTasks - 1) {

            noOfTasksToUnchain++;

            TaskInfo task;
            if (unchainAtBeginning) {
                task = chain.getTask(noOfTasksToUnchain - 1);
            } else {
                task = chain.getTask(noOfChainedTasks - noOfTasksToUnchain);
            }
            estimatedRemainingCpuUtil -= task.getUnchainedCpuUtilization();
        }

        return Pair.of(noOfTasksToUnchain, estimatedRemainingCpuUtil);
    }

    private void mergeChains(int mergeStart, int mergeEnd) {
        List<TaskChain> chainsToMerge = this.chains.subList(mergeStart, mergeEnd);

        TaskChain mergedChain = TaskChain.mergeAndAnnounceChains(chainsToMerge, this.backgroundWorkers,
                this.configCenter);

        this.chains.removeAll(chainsToMerge);
        this.chains.add(mergeStart, mergedChain);
    }

    private boolean areChainsReadyForDynamicChaining() {
        for (TaskChain flow : this.chains) {
            if (!flow.hasCPUUtilizationMeasurements()) {
                return false;
            }
        }
        return true;
    }

    public void measureCPUUtilizations() {
        for (TaskChain chain : this.chains) {
            chain.measureCPUUtilizationIfPossible();
        }
    }
}