org.apache.hadoop.mapred.PoolFairnessCalculator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.mapred.PoolFairnessCalculator.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hadoop.mapred;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.metrics.MetricsRecord;

/**
 * Helper utilies to calculate the fairness of pools.
 */
public class PoolFairnessCalculator {
    /** Class logger */
    private static final Log LOG = LogFactory.getLog(PoolFairnessCalculator.class);

    /**
     * This is a utility class, do not construct.
     */
    private PoolFairnessCalculator() {
    }

    /** Prefix for metrics of fairness resource difference */
    private static final String FAIRNESS_DIFFERENCE_COUNT_PREFIX = "fairness_difference_count_";
    /** Prefix for metrics of average difference per pool */
    private static final String FAIRNESS_DIFFERENCE_PER_POOL_PREFIX = "fairness_difference_per_pool_";
    /** Prefix for metrics of unfairness percentage */
    private static final String FAIRNESS_PERCENT_UNFAIR_PREFIX = "fairness_percent_unfair_";
    /** Prefix of metrics for standard deviation of unfairness */
    private static final String FAIRNESS_UNFAIR_STD_DEV_PERFIX = "fairness_unfair_std_dev_";
    /** Prefix of total resources available for printing */
    private static final String TOTAL_RESOURCES_PREFIX = "total_resources_";

    /**
     * Helper object to keep track of the metadata for all resources of a
     * particular type.
     */
    private static class TotalResourceMetadata {
        /** Total resources that were allocated by the calculator */
        private int totalAllocated = 0;
        /** Total resources that were allocated by the scheduler */
        private int totalAvailable = 0;
        /**
         * Total difference between the expected and actual resources
         * allocated per pool
         */
        private int totalFairnessDifference = 0;
        /**
         * Differences between expected and actual resources squared for all pools
         */
        private int totalFairnessDifferenceSquared = 0;
        /** % of resources unfairly allocated for this resource type [0,100] */
        private float percentUnfair;
        /** Standard deviation of unfairly allocated resources */
        private float stdDevUnfair;
        /** Average resource unfairness per pool */
        private float averageUnfairPerPool;
        /**
         * Number of resources this type represents (usually one, except for
         * when this represents all the resource types)
         */
        private int resourceTypeCount = 1;
    }

    /**
     * A comparator for ResourceMetadata that sorts by the largest
     * min(min guaranteed, desired).
     */
    private static class GuaranteedDesiredComparator implements Comparator<ResourceMetadata> {
        @Override
        public int compare(ResourceMetadata left, ResourceMetadata right) {
            return right.getGuaranteedUsedAndDesired() - left.getGuaranteedUsedAndDesired();
        }
    }

    /**
     * A comparator for ResourceMetadata that sorts by the smallest expected
     * used.
     */
    private static class ExpectedUsedComparator implements Comparator<ResourceMetadata> {
        @Override
        public int compare(ResourceMetadata left, ResourceMetadata right) {
            return left.getExpectedUsed() - right.getExpectedUsed();
        }
    }

    /**
     * This method takes a list of {@link PoolMetadata} objects and calculates
     * fairness metrics of how well scheduling is doing.
     *
     * The goals of the fair scheduling are to insure that every pool is getting
     * an equal share.  The expected share of resources for each pool is
     * complicated by the pools not requiring an equal share
     * or pools that have a minimum or maximum allocation of resources.
     *
     * @param poolMetadataList List of all pool metadata
     * @param metricsRecord Where to write the metrics
     */
    public static void calculateFairness(final List<PoolMetadata> poolMetadataList,
            final MetricsRecord metricsRecord) {
        if (poolMetadataList == null || poolMetadataList.isEmpty()) {
            return;
        }

        // Find the total available usage and guaranteed resources by resource
        // type.  Add the resource metadata to the sorted set to schedule if
        // there is something to schedule (desiredAfterConstraints > 0)
        long startTime = System.currentTimeMillis();
        Map<String, TotalResourceMetadata> resourceTotalMap = new HashMap<String, TotalResourceMetadata>();
        Map<String, Set<ResourceMetadata>> resourceSchedulablePoolMap = new HashMap<String, Set<ResourceMetadata>>();
        for (PoolMetadata poolMetadata : poolMetadataList) {
            for (String resourceName : poolMetadata.getResourceMetadataKeys()) {
                ResourceMetadata resourceMetadata = poolMetadata.getResourceMetadata(resourceName);
                TotalResourceMetadata totalResourceMetadata = resourceTotalMap.get(resourceName);
                if (totalResourceMetadata == null) {
                    totalResourceMetadata = new TotalResourceMetadata();
                    resourceTotalMap.put(resourceName, totalResourceMetadata);
                }
                totalResourceMetadata.totalAvailable += resourceMetadata.getCurrentlyUsed();

                Set<ResourceMetadata> schedulablePoolSet = resourceSchedulablePoolMap.get(resourceName);
                if (schedulablePoolSet == null) {
                    schedulablePoolSet = new HashSet<ResourceMetadata>();
                    resourceSchedulablePoolMap.put(resourceName, schedulablePoolSet);
                }
                if (resourceMetadata.getDesiredAfterConstraints() > 0) {
                    if (!schedulablePoolSet.add(resourceMetadata)) {
                        throw new RuntimeException(
                                "Duplicate resource metadata " + resourceMetadata + " in " + schedulablePoolSet);
                    }
                }
            }
        }

        // First, allocate resources for all the min guaranteed resources
        // for the pools.  Ordering is done by the largest
        // min(min guaranteed, desired).
        GuaranteedDesiredComparator guarantedDesiredComparator = new GuaranteedDesiredComparator();
        List<ResourceMetadata> removePoolList = new ArrayList<ResourceMetadata>();
        for (Map.Entry<String, TotalResourceMetadata> entry : resourceTotalMap.entrySet()) {
            List<ResourceMetadata> resourceMetadataList = new ArrayList<ResourceMetadata>(
                    resourceSchedulablePoolMap.get(entry.getKey()));
            TotalResourceMetadata totalResourceMetadata = entry.getValue();
            Collections.sort(resourceMetadataList, guarantedDesiredComparator);
            while ((totalResourceMetadata.totalAllocated < totalResourceMetadata.totalAvailable)
                    && !resourceMetadataList.isEmpty()) {
                removePoolList.clear();
                for (ResourceMetadata resourceMetadata : resourceMetadataList) {
                    if (resourceMetadata.getExpectedUsed() == resourceMetadata.getGuaranteedUsedAndDesired()) {
                        removePoolList.add(resourceMetadata);
                        continue;
                    }
                    resourceMetadata.incrExpectedUsed();
                    ++totalResourceMetadata.totalAllocated;
                }
                resourceMetadataList.removeAll(removePoolList);
            }
            LOG.info("After allocating min guaranteed and desired - " + "Resource type " + entry.getKey()
                    + " totalAvailable=" + totalResourceMetadata.totalAvailable + ", totalAllocated="
                    + totalResourceMetadata.totalAllocated);
        }

        // At this point, all pools have been allocated their guaranteed used and
        // desired resources.  If there are any more resources to allocate, give
        // resources to lowest allocated pool that hasn't reached desired
        // until all the resources are gone
        ExpectedUsedComparator expectedUsedComparator = new ExpectedUsedComparator();
        PriorityQueue<ResourceMetadata> minHeap = new PriorityQueue<ResourceMetadata>(100, expectedUsedComparator);
        for (Map.Entry<String, TotalResourceMetadata> entry : resourceTotalMap.entrySet()) {
            minHeap.addAll(resourceSchedulablePoolMap.get(entry.getKey()));
            TotalResourceMetadata totalResourceMetadata = entry.getValue();
            while ((totalResourceMetadata.totalAllocated < totalResourceMetadata.totalAvailable)
                    && !minHeap.isEmpty()) {
                ResourceMetadata resourceMetadata = minHeap.remove();
                if (resourceMetadata.getExpectedUsed() == resourceMetadata.getDesiredAfterConstraints()) {
                    continue;
                }
                resourceMetadata.incrExpectedUsed();
                ++totalResourceMetadata.totalAllocated;
                minHeap.add(resourceMetadata);
            }
            minHeap.clear();
        }

        // Now calculate the difference of the expected allocation and the
        // actual allocation to get the following metrics.  When calculating
        // the percent bad allocated divide by 2 because the difference double
        // counts a bad allocation
        // 1) total tasks difference between expected and actual allocation
        //    0 is totally fair, higher is less fair
        // 2) % of tasks incorrectly allocated
        //    0 is totally fair, higher is less fair
        // 3) average difference per pool
        //    0 is totally fair, higher is less fair
        // 4) standard deviation per pool
        //    0 is totally fair, higher is less fair
        for (PoolMetadata poolMetadata : poolMetadataList) {
            for (String resourceName : poolMetadata.getResourceMetadataKeys()) {
                ResourceMetadata resourceMetadata = poolMetadata.getResourceMetadata(resourceName);
                int diff = Math.abs(resourceMetadata.getExpectedUsed() - resourceMetadata.getCurrentlyUsed());
                LOG.info("Pool " + poolMetadata.getPoolName() + ", resourceName=" + resourceName + ", expectedUsed="
                        + resourceMetadata.getExpectedUsed() + ", currentUsed="
                        + resourceMetadata.getCurrentlyUsed() + ", maxAllowed=" + resourceMetadata.getMaxAllowed()
                        + ", desiredAfterConstraints=" + resourceMetadata.getDesiredAfterConstraints()
                        + ", guaranteedUsedAndDesired=" + resourceMetadata.getGuaranteedUsedAndDesired() + ", diff="
                        + diff);
                resourceTotalMap.get(resourceName).totalFairnessDifference += diff;
                resourceTotalMap.get(resourceName).totalFairnessDifferenceSquared += diff * diff;
            }
        }
        TotalResourceMetadata allResourceMetadata = new TotalResourceMetadata();
        allResourceMetadata.resourceTypeCount = resourceTotalMap.size();
        for (TotalResourceMetadata totalResourceMetadata : resourceTotalMap.values()) {
            allResourceMetadata.totalAvailable += totalResourceMetadata.totalAvailable;
            allResourceMetadata.totalFairnessDifference += totalResourceMetadata.totalFairnessDifference;
            allResourceMetadata.totalFairnessDifferenceSquared += totalResourceMetadata.totalFairnessDifferenceSquared;
        }
        resourceTotalMap.put("all", allResourceMetadata);
        StringBuilder metricsBuilder = new StringBuilder();
        for (Map.Entry<String, TotalResourceMetadata> entry : resourceTotalMap.entrySet()) {
            TotalResourceMetadata totalResourceMetadata = entry.getValue();
            totalResourceMetadata.percentUnfair = (totalResourceMetadata.totalAvailable == 0) ? 0
                    : totalResourceMetadata.totalFairnessDifference * 100f / 2
                            / totalResourceMetadata.totalAvailable;
            totalResourceMetadata.stdDevUnfair = (float) Math
                    .sqrt((double) totalResourceMetadata.totalFairnessDifferenceSquared / poolMetadataList.size()
                            / totalResourceMetadata.resourceTypeCount);
            totalResourceMetadata.averageUnfairPerPool = (float) totalResourceMetadata.totalFairnessDifference
                    / poolMetadataList.size() / totalResourceMetadata.resourceTypeCount;

            metricsRecord.setMetric(FAIRNESS_DIFFERENCE_COUNT_PREFIX + entry.getKey(),
                    totalResourceMetadata.totalFairnessDifference);
            metricsBuilder.append(FAIRNESS_DIFFERENCE_COUNT_PREFIX + entry.getKey() + "="
                    + totalResourceMetadata.totalFairnessDifference + "\n");
            metricsRecord.setMetric(FAIRNESS_PERCENT_UNFAIR_PREFIX + entry.getKey(),
                    totalResourceMetadata.percentUnfair);
            metricsBuilder.append(FAIRNESS_PERCENT_UNFAIR_PREFIX + entry.getKey() + "="
                    + totalResourceMetadata.percentUnfair + "\n");
            metricsRecord.setMetric(FAIRNESS_DIFFERENCE_PER_POOL_PREFIX + entry.getKey(),
                    totalResourceMetadata.averageUnfairPerPool);
            metricsBuilder.append(FAIRNESS_DIFFERENCE_PER_POOL_PREFIX + entry.getKey() + "="
                    + totalResourceMetadata.averageUnfairPerPool + "\n");
            metricsRecord.setMetric(FAIRNESS_UNFAIR_STD_DEV_PERFIX + entry.getKey(),
                    totalResourceMetadata.stdDevUnfair);
            metricsBuilder.append(FAIRNESS_UNFAIR_STD_DEV_PERFIX + entry.getKey() + "="
                    + totalResourceMetadata.stdDevUnfair + "\n");
            metricsBuilder.append(
                    TOTAL_RESOURCES_PREFIX + entry.getKey() + "=" + totalResourceMetadata.totalAvailable + "\n");
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("calculateFairness took " + (System.currentTimeMillis() - startTime) + " millisecond(s).");
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("\n" + metricsBuilder.toString());
        }
    }
}