All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.uima.ducc.rm.scheduler.NodepoolScheduler Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*
 * 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.uima.ducc.rm.scheduler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.uima.ducc.common.Node;
import org.apache.uima.ducc.common.utils.DuccLogger;
import org.apache.uima.ducc.common.utils.SystemPropertyResolver;


/**
 * This implementation of IScheduler is the initial implementation of a scheduler using classes.
 */
public class NodepoolScheduler
    implements IScheduler,
               SchedConstants
{
    DuccLogger logger = DuccLogger.getLogger(NodepoolScheduler.class, COMPONENT_NAME);

    Map resourceClasses;
    Map needyJobs = new TreeMap(new JobByTimeSorter());
    NodePool globalNodepool;

    Object[] classes;

    SchedulingUpdate schedulingUpdate;

    EvictionPolicy evictionPolicy = EvictionPolicy.SHRINK_BY_MACHINE;

    int fragmentationThreshold = 2;
    boolean do_defragmentation = true;
    boolean use_global_allotment = true;
    int global_allotment = Integer.MAX_VALUE;
    int scheduling_quantum;

    NodepoolScheduler()   
    {
    }

    public void setClasses(Map prclasses)
    {
        this.resourceClasses = prclasses;

        // organize all the priority classes into lists by priority because we process all classes of the
        // same priority in a group
        //
        // This version of the scheduler requires that classes at the same priority MUST be of the same type.
        // i.e. we don't support mixed-share at this point.
        HashMap> sorter = new HashMap>();
        for ( ResourceClass pcl : prclasses.values() ) {
 
            ArrayList cl = sorter.get(pcl.getPriority());
            if ( cl == null ) {
                cl = new ArrayList();
                sorter.put(pcl.getPriority(), cl);
            }

            // make sure policies match
            if (cl.size() > 0 ) {
                ResourceClass prev = cl.get(0);
                if ( prev.getPolicy() != pcl.getPolicy() ) {
                    throw new SchedulingException(null, "Scheduling policy must match for same-priority classes: class " +
                                                  prev.getName() + " : " + pcl.getName());
                }
            }

            cl.add(pcl);
        }

        // now sort the keys
        ArrayList keys = new ArrayList();
        keys.addAll(sorter.keySet());
        Collections.sort(keys);  // "best" (lowest) priority first

        // and finally create the "classes" array, ordered by decreasing priority, each element is the
        // collection of priority classes at the same priority.
        classes = new Object[sorter.size()];
        int ndx = 0;
        for ( Integer k : keys ) {
            classes[ndx++] = sorter.get(k);
        }

        fragmentationThreshold = SystemPropertyResolver.getIntProperty("ducc.rm.fragmentation.threshold", fragmentationThreshold);
        do_defragmentation = SystemPropertyResolver.getBooleanProperty("ducc.rm.defragmentation", do_defragmentation);
        use_global_allotment = SystemPropertyResolver.getBooleanProperty("ducc.rm.use_global_allotment",  use_global_allotment);
        global_allotment = SystemPropertyResolver.getIntProperty("ducc.rm.global_allotment", global_allotment);
    }

    /**
     * Sets the top-level np for this scheduler
     */
    public void setNodePool(NodePool np)
    {
        this.globalNodepool = np;
        this.scheduling_quantum = np.getShareQuantum() >> 20;   // to GB from KB
    }

    public void setEvictionPolicy(EvictionPolicy ep)
    {
        this.evictionPolicy = ep;
    }

    /**
     * Check the allotment for the user, given that we want to allocate
     *    - nprocs new processes for
     *    - job j
     */
    boolean validSingleAllotment(IRmJob j)
    {
        String methodName = "validAllotment";
        //
        // Original design and implementation for UIMA-4275: class based.  Subsequent discussion resulted in
        // changing to a single global allotment.  I'll leave the class-based allotment in for now because
        // I suspect it will re-raise its ugly head. (jrc)
        //

        if ( use_global_allotment ) {
            User u = j.getUser();
            int lim = u.getOverrideLimit();
            if ( lim < 0 ) {
                lim = global_allotment;
            }

            int shares = u.countNPShares();
            long sharesInGB = ((shares + j.getShareOrder()) * scheduling_quantum);
            if ( sharesInGB > lim ) {
                schedulingUpdate.defer(j, "Deferred because allotment of " + lim + "GB is exceeded by user " + j.getUserName());
                logger.info(methodName, j.getId(), "Deferred because allotment of " + lim + "GB is exceeded by user " + j.getUserName());
                return false;
            }
        } else {
            ResourceClass rc = j.getResourceClass();
            if ( rc.allotmentExceeded(j) ) {
                schedulingUpdate.defer(j, "Deferred because allotment of " + rc.getAllotment(j) + "GB is exceeded by user " + j.getUserName());
                logger.info(methodName, j.getId(), "Deferred because allotment of " + rc.getAllotment(j) + "GB is exceeded by user " + j.getUserName());
                return false;
            }
        } 
        return true; 
    }

    /**
     * This returns the total number of PROCESSES we get to allocate for this job, including those
     * already given.
     */
    int getAllotmentForJob(IRmJob j)
    {
    	String methodName = "getAllotmentForJob";
        User u = j.getUser();
        
        // Let the job ask for the world.  This accounts for init cap, prediction, number usable, etc
        int order = j.getShareOrder();
        int wanted = j.getJobCap();                // in nshares
        logger.info(methodName, j.getId(), "Job cap", nSharesToString(wanted, order));
        
        // Find out how many qshares we're allowed
        int allotment_in_gb = u.getOverrideLimit();
        if ( allotment_in_gb < 0 ) {
            allotment_in_gb = global_allotment;
        }
        int user_allotment = allotment_in_gb / scheduling_quantum;     // qshares

        // How many qshares we, the user, have used
        int allocated = u.countNPShares();
        logger.info(methodName, j.getId(), "Current NP allocation for user", allocated, "qshares", (allocated * scheduling_quantum), "GB",
                    "user_allotment", user_allotment, "user_allotment in GB", allotment_in_gb );

        // This is how many QShares we get to allocate for the job
        int additional = Math.max(0, user_allotment - allocated);
        int additional_processes = additional / order;
        logger.info(methodName, j.getId(), "Additional Nshares allowed for request:", nSharesToString(additional_processes, order));

        // No shares, so we show deferred
        if ( (additional_processes == 0) ) {
            if (j.countNShares() == 0)  {
                // over allotment, never had anything, can get anything so is deferred
                schedulingUpdate.defer(j, "Deferred because allotment of " + allotment_in_gb + "GB is exceeded.");
                logger.info(methodName, j.getId(), "Deferred because allotment of",  allotment_in_gb, "GB is exceeded.");
            } else {
                logger.info(methodName, j.getId(), "Allotment of", allotment_in_gb, "GB caps request. Return with", allocated, "qshares allocated.");
            }
            return j.countNShares();
        }

        int allowed = j.countNShares() + additional_processes;

        if ( allowed < wanted ) {
            logger.info(methodName, j.getId(), "Capping job on allotment: ", allotment_in_gb + " GB. Remaining allowed nshares [",
                        allowed, "] wanted [", wanted, "]");
        }

        logger.info(methodName, j.getId(), "Allowed", nSharesToString(allowed, order), "wanted", nSharesToString(wanted, order));
        return Math.min(wanted, allowed);
    }

    private void reworknShares(int[] vshares, int[] nshares)
    {
        // now redo nshares
        int len = vshares.length;
        System.arraycopy(vshares, 0, nshares, 0, len);
        for ( int o = 1; o < len; o++ ) {                     // counting by share order
            for ( int p = o+1; p < len; p++ ) {
                if ( nshares[p] != 0 ) {
                    nshares[o] += (p / o) * nshares[p];
                }
            }
        }
        //System.out.println("vshares : " + fmtArray(vmachines));
        //System.out.println("nshares : " + fmtArray(nshares));
    }

    /**
     * @param nshares is a table showing the number of virtual shares for each order.
     * @param count is number of N shares to remove
     * @param order is the order that is affected
     */
    void doShareSplits(int[] vmach, long count, int order)
    {

        rsbo : {
            for ( int o = order; o < vmach.length; o++ ) {
                while ( vmach[o] > 0 ) {
                    
                    int given    = o / order;
                    int residual = o % order;
                    
                    if ( count >= given ) {       // we give it all away
                        count -= given;
                        if ( residual > 0 ) {
                            vmach[residual]++;   // and maybe a leftover
                        }                                        
                    } else {                     // can't give it all away
                        int leftover = o - ((int)count * order); 
                        vmach[leftover]++;
                            count = 0;	
                    }
                    
                    vmach[o] --;
                    
                    if ( count == 0 ) {
                        break rsbo;
                    }
                }
            }
        }

    }


    /**
     * @param nshares is a table showing the number of virtual shares for each order.
     * @param count is number of N shares to remove
     * @param order is the order that is affected
     */
    void removeSharesByOrder(int[] vmach, int[] nshares, long count, int order)
    {
        if ( count == 0 ) return;                 // shortcut so we don't have to keep checking in caller

        //
        // First do the ones that may be able to fill with at most one machine split.
        //
        for ( int f = 1; (f * order) < vmach.length; f++ ) {
            int fo = f * order;
            if ( vmach[fo] > 0 ) {
                long available   = vmach[fo] * f;
                long given       = Math.min(count, available);
                int remaining   = (int) (available - given);
                vmach[fo]       = (remaining * order) / fo;
                int residual    = (remaining * order) % fo;
                if ( residual > 0 ) {
                    vmach[residual] ++;
                }
                count -= given;
            }
            if ( count == 0 ) {
            	break;
            }
        }

        //
        // Now we must do splits if we still need some.
        //
        if ( count > 0 ) {
            doShareSplits(vmach, count, order);
        }

        reworknShares(vmach, nshares);
    }

    /**
     * Create a string showing virtual and quantum shares, given virtual shares and the order.
     * For use in debugging messages.
     */
    String nSharesToString(int shares, int order)
    {
        return String.format("%dQ%d", shares, (shares * order));
    }

    /**
     * Create a string showing virtual and quantum shares, given quantum shares and the order.
     * For use in debugging messages.
     */
    String qSharesToString(int shares, int order)
    {
    	String o = null;
    	if ( order == 0 ) {
    		o = "-";
    	} else {
    		o = (Integer.toString(shares/order));
    	}

    	return String.format("%sQ%d", o, shares);
    }

    // /**
    //  * Return the nodepool for a class, or the global nodepool if none is explicitly associated with the class.
    //  * @deprecated Remove as soon as it is verified corrcct.
    //  */
    // NodePool getNodepool(ResourceClass rc)
    // {
    //     String id = rc.getNodepoolName();
    //     if ( id == null ) {
    //         return globalNodepool;
    //     }
    //     return globalNodepool.getSubpool(id);
    // }

    // ==========================================================================================
    // ==========================================================================================
    // =========================== REWORKED CODE FOR FAIR SHARE ==================================
    // ==========================================================================================
    // ==========================================================================================

    // /**
    //  * @deprecated - see rc.geMaxOrder();
    //  */
    // int calculateMaxJobOrder(ArrayList rcs)
    // {
    //     int max = 0;
    //     for ( ResourceClass rc: rcs ) {
    //         HashMap> jobs = rc.getAllJobsByOrder();
    //         for ( int i : jobs.keySet() ) {
    //             max= Math.max(max, i);
    //         }
    //     }
    //     return max;
    // }

    private String fmtArray(int[] array)
    {
        Object[] vals = new Object[array.length];
        StringBuffer sb = new StringBuffer();
        
        for ( int i = 0; i < array.length; i++ ) {
            sb.append("%3s ");
            vals[i] = Integer.toString(array[i]);
        }
        return String.format(sb.toString(), vals);
    }

    // UIMA-4275 Don't pass in total shares any more, we used only for class caps, which work differently now.
    protected void apportion_qshares(List entities, int[] vshares, String descr)
    {
        String methodName = "apportion_qshares";
        boolean shares_given = false;
        int maxorder = globalNodepool.getMaxOrder();
        int[] nshares = globalNodepool.makeArray();           // nshares


        if ( entities.size() == 0 ) return;
        Collections.sort(entities, entities.get(0).getApportionmentSorter());

        reworknShares(vshares, nshares);

        ArrayList working = new ArrayList();
        working.addAll(entities);

        HashMap given_by_order = new HashMap();
        HashMap   deserved   = new HashMap();      // qshares

        
        // This section dealing with RmCounter writes the counting parameters to the log in a form
        // that can be cut/pasted into a java properties file.  This file can then be used in the
        // RmCounter test/deveopment application.  It also turns out to be very useful in the log
        // in general so it is promoted to 'info' level.
        StringBuffer   enames = null;
        StringBuffer eweights = null;
        logger.info(methodName, null, descr, "RmCounter Start");
        logger.info(methodName, null, descr, "maxorder = ", globalNodepool.getMaxOrder());
        enames = new StringBuffer();            
        eweights = new StringBuffer();  

        for ( IEntity e : working ) {              
            int[] gbo = globalNodepool.makeArray();
            e.setGivenByOrder(gbo);

            given_by_order.put(e, gbo);
            deserved.put(e, 0);

            // jrc e.initWantedByOrder();

            enames.append(e.getName());
            enames.append(" ");
            eweights.append(Integer.toString(e.getShareWeight()));
            eweights.append(" ");      
        }

        logger.info(methodName, null, descr, "entity_names = ", enames.toString());
        logger.info(methodName, null, descr, "weights      = ", eweights.toString());
        for ( IEntity e : working ) {
            logger.info(methodName, null, descr, "wantedby." + e.getName() + " = ", fmtArray(e.getWantedByOrder()));
        }
        logger.info(methodName, null, descr, "vmachines =", fmtArray(vshares));
        logger.info(methodName, null, descr, "RmCounter End");


        int pass = 0;
        do {
            // Starting at highest order, give full fair share to any entity that wants it, minus the
            // shares already given.  Remove the newly given shares and trickle down the fragments.

            logger.trace(methodName, null, descr, "----------------------- Pass", (pass++), "------------------------------");
            logger.trace(methodName, null, descr, "vshares", fmtArray(vshares));
            logger.trace(methodName, null, descr, "nshares", fmtArray(nshares));
            shares_given = false;
            HashMap given_per_round = new HashMap();        // qshares
            int allweights = 0;
            for ( IEntity e : working ) {
                allweights += e.getShareWeight();
                given_per_round.put(e, 0);
            }

            //
            // work out deserved for everybody based on what's still around.
            //
            int all_qshares = nshares[1];
            for ( IEntity e : working ) {
                int base_fs = (int) Math.floor(nshares[1] * ( (double) e.getShareWeight() / allweights ));
                double d_base_fs = nshares[1] * ( (double) e.getShareWeight() / allweights );
                deserved.put(e, base_fs);
                all_qshares -= base_fs;

                logger.trace(methodName, null, descr, e.getName(), "Wanted  :", fmtArray(e.getWantedByOrder()));
                logger.trace(methodName, null, descr, e.getName(), "deserved:", base_fs, d_base_fs);
            }

            logger.trace(methodName, null, descr,  "Leftover after giving deserved:" + all_qshares);
            if ( all_qshares > 0 ) {
                for ( IEntity e: working ) {
                    deserved.put(e, deserved.get(e) + 1);
                    all_qshares--;
                    if ( all_qshares == 0 ) break;
                }
            }
            for ( IEntity e : working ) {
                logger.trace(methodName, null, descr, String.format("Final deserved by %15s: int[%3d] (after bonus)", e.getName(), deserved.get(e)));
            }

            for ( int o = maxorder; o > 0; o--) {  
                int total_taken = 0;                                 // nshares
                if ( nshares[o] == 0 ) {
                    logger.trace(methodName, null, descr, "O " + o + " no shares to give, moving on.");
                    continue;
                }
                for ( IEntity e : working ) {
                    int[] wbo = e.getWantedByOrder();                 // processes - NShares
                    int[] gbo = given_by_order.get(e);                //             NShares

                    if ( wbo[o] == 0 ) {
                        logger.trace(methodName, null, descr, "O", o, "Entity", e.getName(), "nothing wanted at this order, moving on.");
                        continue;
                    }

                    double dgiven = nshares[o] * ((double) e.getShareWeight() / allweights) * o;     // QShares for base calcs
                    int    des = deserved.get(e);                                                    // total deserved this round QShares
                    int    gpr = given_per_round.get(e);                                             // total given this round
                    int    mpr = Math.max(0, des-gpr);                                               // max this round, deserved less what I aleady was given
                    //int    tgiven = Math.min(mpr, (int) Math.floor(dgiven));                         // what is calculated, capped by what I alreay have
                    // UIMA-4275, floor to ciel.  Below with tgiven and rgiven we deal with ther emainder also.  The floor plus the residual below
                    //            seemed really agressive and some small allocation were working out to 0 when they shouldn't
                    int    tgiven = Math.min(mpr, (int) Math.ceil(dgiven));                          // what is calculated, capped by what I alreay have
                    int    cap = e.calculateCap();                                                   // get caps, if any, in qshares (simplified in UIMA-4275)
                    logger.trace(methodName, null, descr, "O", o, ":", e.getName(), "Before caps, given", tgiven, "cap", cap);

                    if ( gbo[0] >= cap ) {           // UIMA-4275
                        logger.trace(methodName, null, descr, "O", o, "Entity", e.getName(), "cap prevents further allocation.");
                        continue;
                    }

                    int    given = tgiven / o;                                                       // tentatively given, back to NShares
                    int    rgiven = tgiven % o;                                                      // residual - remainder
                    //int    twanted = wbo[0] + gbo[0];                                              // actual wanted: still wanted plus alredy given
                    // if ( twanted <= fragmentationThreshold ) {                                    // if under the defrag limit, round up
                    if ( (rgiven > 0) && ( given == 0) ) {
                        given = Math.min( ++given, nshares[o] );                                     // UIMA-3664
                    }
                    // }                                                                                // if not under the defrag limit, round down

                    if ( given + gbo[0] > cap ) {                                                    // adjust for caps
                        given = Math.max(0, cap - gbo[0]);
                    }

                    int    taken = Math.min(given, wbo[o]);                                          // NShares
                    taken = Math.min(taken, nshares[o] - total_taken);                               // cappend on physical (in case rounding overcommitted)

                    logger.trace(methodName, null, descr,
                                 "O", o, ":", e.getName(), "After  caps,",
                                 " dgiven Q[", dgiven,
                                 "] given N[", given ,
                                 "] taken N[", taken ,
                                 "]");

                    gbo[o] += taken;
                    gbo[0] += taken;
                    wbo[o] -= taken;
                    wbo[0] -= taken;
                    total_taken += taken;
                    given_per_round.put(e, given_per_round.get(e) + (taken*o));
                }
                if ( total_taken > 0 ) shares_given = true;
                removeSharesByOrder(vshares, nshares, total_taken, o);

                // If you were given all you deserve this round, then pull the weight so you don't
                // dilute the giving for shares you aren't owed.
                Iterator iter = working.iterator();
                while ( iter.hasNext() ) {
                    IEntity e = iter.next();
                    int des = deserved.get(e);
                    int gpr = given_per_round.get(e);
                    int got_all = Math.max(0, des - gpr);
                    if ( got_all == 0 ) {
                        allweights -= e.getShareWeight();
                    }
                }                
                if ( allweights <=0 ) break;   // JRC JRC
            }

            // Remove entities that have everything they want or could otherwise get
            Iterator iter = working.iterator();
            while ( iter.hasNext() ) {
                IEntity e = iter.next();
                if ( (e.getWantedByOrder()[0] == 0) || (e.getGivenByOrder()[0] >= e.calculateCap()) ) {      // UIMA-4275, checking fair-share cap
                    // logger.info(methodName, null, descr, e.getName(), "reaped, nothing more wanted:", fmtArray(e.getWantedByOrder()));
                    iter.remove();
                }
            }

            // Remove entities that can't get anythng more.  This is important to get better convergence - otherwise
            // the "spectator" jobs will pollute the fair-share and convergence will be much harder to achieve.
            
            iter = working.iterator();
            while ( iter.hasNext() ) {
                IEntity e = iter.next();
                int[] wbo = e.getWantedByOrder();
                boolean purge = true;
                for ( int o = maxorder; o > 0; o-- ) {
                    if ( (wbo[o] > 0) && (nshares[o] > 0) ) {   // if wants something, and resources still exist for it ...
                        purge = false;                          // then no purge
                        break;
                    }
                }
                if ( purge ) {
                    //logger.info(methodName, null, descr, e.getName(), "reaped, nothing more usablee:", fmtArray(e.getWantedByOrder()), "usable:",
                    //            fmtArray(nshares));
                    iter.remove();
                }
            }

            if ( logger.isTrace() ) {
                logger.trace(methodName, null, descr, "Survivors at end of pass:");
                for ( IEntity e : working ) {
                    logger.trace(methodName, null, descr, e.toString());
                }
            }
        } while ( shares_given );

        if ( logger.isTrace() ) {
            logger.info(methodName, null, descr, "Final before bonus:");
            for ( IEntity e : entities ) {
                int[] gbo = e.getGivenByOrder();
                logger.info(methodName, null, descr, String.format("%12s %s", e.getName(), fmtArray(gbo)));
            }
        }

        //
        // A final pass, in case something was left behind due to rounding.
        // These are all bonus shares.  We'll give preference to the "oldest" 
        // entities.  But only one extra per pass, in order to evenly distribute.
        //
        // Technically, we might want to distribute the according to the
        // entity weights, but we're not doing that (assuming all weights are 1).
        //
        boolean given = true;
        //int     bonus = 0;
        while ( (nshares[1] > 0) && (given)) {
            given = false;
            for ( IEntity e : entities ) {
                //int[] wbo = e.getWantedByOrder();         // nshares
                int[] gbo = e.getGivenByOrder();          // nshares

                for ( int o = maxorder; o > 0; o-- ) {                
                    // the entity access its wbo, gbo, and entity-specific knowledge to decide whether
                    // the bonus is usable.  if so, we give out exactly one in an attempt to spread the wealth.
                    //
                    // An example of where you can't use, is a class over a nodepool whose resources
                    // are exhausted, in which case we'd loop and see if anybody else was game.
                    // UIMA-4065
                    while ( (e.canUseBonus(o) ) && (vshares[o] > 0) ) {
                        gbo[o]++;
                        gbo[0]++;
                        removeSharesByOrder(vshares, nshares, 1, o);
                        given = true;
                        break;
                    }
                }

                // UIMA-4605 - old 'bonus' code -- keep for a while as quick reference
                // for ( int o = maxorder; o > 0; o-- ) {                
                //     int canuse = wbo[o] - gbo[o];
                //     while ( (canuse > 0 ) && (vshares[o] > 0) ) {
                //         gbo[o]++;
                //         //bonus++;
                //         canuse = wbo[o] - gbo[o];
                //         removeSharesByOrder(vshares, nshares, 1, o);
                //         given = true;
                //         break;
                //     }
                // }
            }
        } 

        logger.debug(methodName, null, descr, "Final apportionment:");
        for ( IEntity e : entities ) {
            int[] gbo = e.getGivenByOrder();          // nshares
            logger.debug(methodName, null, descr, String.format("%12s gbo %s", e.getName(), fmtArray(gbo)));                
        }
        logger.debug(methodName, null, descr, "vshares", fmtArray(vshares));
        logger.debug(methodName, null, descr, "nshares", fmtArray(nshares));
          
        // if ( bonus > 0 ) {
        //     logger.debug(methodName, null, descr, "Final after bonus:");
        //     for ( IEntity e : entities ) {
        //         int[] gbo = e.getGivenByOrder();          // nshares
        //         logger.debug(methodName, null, descr, String.format("%12s %s", e.getName(), fmtArray(gbo)));                
        //     }
        //     logger.debug(methodName, null, descr, "vshares", fmtArray(vshares));
        //     logger.debug(methodName, null, descr, "nshares", fmtArray(nshares));
        // } else {
        //     logger.debug(methodName, null, descr, "No bonus to give.");
        // }
    }


    /**
     * Count out shares for only the jobs in the ResouceClasses here, and only from the given
     * nodepool.
     */
	protected void countClassShares(NodePool np, List rcs)
    { 		
		String methodName = "countClassShares";

        if ( logger.isDebug() ) {
            StringBuffer sb = new StringBuffer("Counting for nodepool ");
            sb.append(np.getId());
            sb.append(" - classes -");
            for ( ResourceClass rc : rcs ) {
                sb.append(" ");
                sb.append(rc.getName());
            }

            logger.debug(methodName, null, sb.toString());
        }
        // if ( true ) return;

        // pull the counts.  these don't get updated by the counting routines per-se.  after doing counting the np's are
        // expected to do the 'what-of' calculations that do acutall allocation and which update the counts
        int[] vshares = np.cloneVMachinesByOrder();

        ArrayList l = new ArrayList();
        l.addAll(rcs); 

        for ( IEntity e : l ) {
            e.initWantedByOrder((ResourceClass) e);
        }

        apportion_qshares((List) l, vshares, methodName);               // UIMA-4275, remove 'total'

        int sum_of_weights = 0;
        for ( ResourceClass rc : rcs ) {
            sum_of_weights += rc.getShareWeight(); // see next loop
        }

        //
        // If we have to do a "final eviction" because of fragmentation (evict stuff even though
        // it's below it's calcualted counts), we want to know the pure, unmodified fair share
        // for each rc.  Here is a great place to do that calculation.
        //
        for ( ResourceClass rc : rcs ) {
            int fair_share = (int) Math.floor(np.countTotalShares() * ( (double)  rc.getShareWeight() / sum_of_weights ));
            rc.setPureFairShare(fair_share);
        }
    }


    /**
     * Count out shares for only the jobs in the ResouceClasses here, and only from the given
     * nodepool.
     */
	protected boolean countUserShares(ResourceClass rc)
    {
		String methodName = "countUserShares";

        HashMap allJobs = rc.getAllJobs();
        if ( allJobs.size() == 0 ) {
            return false;                                                          // nothing to do ...
        }

        // if ( rc.getName().equals("urgent") ) {
        //     int stop_here = 1;
        //     stop_here++;
        // }

        int[] vshares = rc.getGivenByOrder();                                      // assigned in countClassShares

        HashMap users = new HashMap();                     // get a map of all users in this class by examining the curent jobs
        for ( IRmJob j : allJobs.values() ) {
        	User u = j.getUser();
            if ( users.containsKey(u) ) continue;
            u.initWantedByOrder(rc);
            users.put(u, u);
        }

        ArrayList l = new ArrayList();
        l.addAll(users.values()); 
        apportion_qshares((List) l, vshares, methodName);                // UIMA-4275 remove 'total'

        //
        // For final eviction if needed, calculate the pure uncapped un-bonused count for
        // each user.
        //
        int pure_share = rc.getPureFairShare();
        int fs = (int) Math.floor((double) pure_share / users.size());
        for ( User u : users.values() ) {
            u.setPureFairShare(fs);
        }
               
        return true;
    }

    private void countJobShares(ResourceClass rc)
    {
    	String methodName = "countJobShares";

        HashMap> userJobs = rc.getAllJobsByUser();

        for ( User u : userJobs.keySet() ) {
            HashMap jobs = userJobs.get(u);
            if ( jobs.size() == 0 ) {
                continue;
            }
            
            int[] vshares = u.getGivenByOrder();
            ArrayList l = new ArrayList();
            l.addAll(jobs.values()); 

            for ( IEntity e : l ) {
                e.initWantedByOrder(rc);
            }

            apportion_qshares((List) l, vshares, methodName);     // UIMA-4275, remove "total"

            //
            // For final eviction if needed, calculate the pure uncapped un-bonused count for
            // each user.
            //
            int pure_share = u.getPureFairShare();
            int fs = (int) Math.floor((double) pure_share / jobs.size());
            for ( IRmJob j : jobs.values() ) {
                j.setPureFairShare(fs);
            }
        }
    }

    /**
     * Find the set of classes from the presented set of elibible classes that have jobs in
     * the given nodepool.  UIMA-4065
     *
     * @param np        Relevent nodepool
     * @param eligible  (Possibly restricted) set of classes that **might** have jobs in the nodepool
     * @return List of classes with jobs in the nodepool
     */
    private List gatherRcs(NodePool np, List eligible)
    {
        ArrayList ret = new ArrayList();
        String npn = np.getId();
        for ( ResourceClass rc : eligible ) {
            String rcnpn = rc.getNodepoolName();
            if ( rcnpn == null       ) continue;
            if ( rc.countJobs() == 0 ) continue;
            if ( rcnpn.equals(npn)   ) ret.add(rc);
        }
        return ret;        
    }

    /**
     * Do a depth-first traversal of the nodepool calculating counts for all the jobs in the nodepool and its children.
     *
     * Starting at each leaf NP, gather all the classes that have jobs in the NP, and if there are any, get class counts
     * for the classes.  Pass the classes up to the caller who can do a more global recount if necessary.  If there are
     * no jobs over the nodepool then bypass the count for it, of course.  This is a rewrite of the original which did
     * not properly handle the recursion past 1 level (root + 1).  It uses gatherRcs() as a helper to find relevent classes.
     * jrc 2014-11-05. UIMA-4605
     *
     * Note that this is tricky - please make sure you understand all the logic in countClassShares before changing
     * anything.
     * 
     * @param np
     * @param eligible
     * @return List of classes with potential counts.
     */
    protected List traverseNodepoolsForCounts(NodePool np, List eligible)
    {
        //String methodName = "traverseNodepoolsForCounts";

        List myRcs = gatherRcs(np, eligible);       // the resource classes for NodePool np
        boolean hasJobs = (myRcs.size() > 0);                      // do I have jobs for this np?

        List subpools = np.getChildrenAscending();       // now recurse down to leaves from here
        for ( NodePool subpool : subpools ) {
            List subrc = traverseNodepoolsForCounts(subpool, eligible);
            myRcs.addAll(subrc);
        }        

        // now do our fs, if there are jobs resident in this np
        if ( hasJobs ) countClassShares(np, myRcs);
        return myRcs;                                             // return aggregated classes to caller
    }

//     /**
//      * Depth-first traversal of the nodepool.  Once you get to a leaf, count the shares.  This sets an
//      * upper-bound on the number of shares a class can have.  As you wind back up the tree the counts may
//      * be reduced because of competition from jobs in the parent node.  By the time we're done we should have
//      * accounted for all jobs and all usable resources.
//      *
//      * Note how this works:
//      * Consider a configuration with two nodepools plus global, A, B, and G.  Suppose nodepools A and B have
//      * 30 shares each and G only has 10 shares.  G can apportion over it's 10, plus the 60 from A and B. So
//      * after apporioning over A and B we need to do fair-share over G+A+B to insure that jobs submitted
//      * to G are not "cheated" - recall that jobs in this set of classes have the same weight and priority,
//      * and thus the same "right" to all the shares.  However, allocating a job from class A over the
//      * full set of 10+30 shares could over-allocate it.  So the cap calculations must be sure never to
//      * increase the already-given shares for subpools.
//      *
//      * Therefore we traverse the FULL SET of classes on every recursion.  When calculating caps from
//      * apportion_shares the resource classes will have to account for multiple traversals and not over-allocate
//      * if a class has already been apportioned from a subpool.
//      *
//      * Keep for a while for reference.  It is wrong but if there are still bugs in the rewrite we
//      * want easy reference to the original. jrc 2014-11-05 UIMA-4065
//      */
//     protected void traverseNodepoolsForCounts(NodePool np, List rcs)
//     {
//         //HashMap subpools = np.getChildren();
//         List subpools = np.getChildrenAscending();
//         for ( NodePool subpool : subpools ) {
//             ArrayList cls = new ArrayList();
//             String npn = subpool.getId();
//             int njobs = 0;
//             for ( ResourceClass rc : rcs ) {
//                 String rcnpn = rc.getNodepoolName();
//                 if ( rcnpn == null ) continue;

//                 if ( rc.getNodepoolName().equals(npn) ) {
//                     cls.add(rc);
//                     njobs += rc.countJobs();
//                 }
//             }
//             if ( njobs > 0 ) {
//                 traverseNodepoolsForCounts(subpool, cls);
//             }
//         }

//         countClassShares(np, rcs);
//     }


    protected void updateNodepools(NodePool np, ArrayList rcs)
    {
        //HashMap subpools = np.getChildren();
        List subpools = np.getChildrenAscending();
        for ( NodePool subpool : subpools ) {
            ArrayList cls = new ArrayList();
            String npn = subpool.getId();
            int njobs = 0;
            for ( ResourceClass rc : rcs ) {
                String rcnpn = rc.getNodepoolName();
                if ( rcnpn == null ) continue;

                if ( rc.getNodepoolName().equals(npn) ) {
                    cls.add(rc);
                    njobs += rc.countJobs();
                }
            }
            if ( njobs > 0 ) {
                updateNodepools(subpool, cls);
            }
        }

        // All calls have rcs EXACTLY the same as classes assigned to the np except
        // for the last one, to global.  At the end of the recursion everything except
        // those have been processed.  We grab just the ones in --global-- so we don't
        // end up counting them more than once.
        if ( np == globalNodepool ) {
            ArrayList tmp = new ArrayList();
            for ( ResourceClass rc : rcs ) {
                if ( rc.getNodepoolName().equals(globalNodepool.getId()) ) {
                    tmp.add(rc);
                }
            }
            rcs = tmp;
        }

        for (ResourceClass rc : rcs ) {
            if ( rc.countJobs() == 0 ) continue;
            rc.updateNodepool(np);
        }
    }

    private void howMuchFairShare(ArrayList rcs)
    {
        String methodName = "howMuchFairShare";
        if ( logger.isTrace() ) {
            logger.info(methodName, null, "Scheduling FAIR SHARE for these classes:");
            logger.info(methodName, null, "   ", ResourceClass.getHeader());
            logger.info(methodName, null, "   ", ResourceClass.getDashes());
            for ( ResourceClass pc : rcs ) {
                logger.info(methodName, null, "   ", pc.toString());
            }
        }

        ArrayList eligible = new ArrayList();
        Collections.sort(rcs, new ClassByWeightSorter());

        for ( ResourceClass rc : rcs ) {
            HashMap jobs = rc.getAllJobs();
            logger.info(methodName, null, "Schedule class", rc.getName());
            rc.clearShares();

            if ( jobs.size() == 0 ) {
                logger.info(methodName, null, "No jobs to schedule in class ", rc.getName());
            } else {
                eligible.add(rc);
                for ( IRmJob j : jobs.values() ) {                    
                	logger.info(methodName, j.getId(), "Scheduling job in class ", rc.getName(), ":", j.toString());
                }
            }
        }
        if ( eligible.size() == 0 ) {
            return;
        }

        //
        // First step, figure out how many shares per class.
        //
        traverseNodepoolsForCounts(globalNodepool, eligible);   // (only these classes, not the global set)

        //
        // Everything should be stable - now reduce the counts in the nodepools
        //
        /**
        logger.info(methodName, null, "Fair-share class counts:");
        for ( ResourceClass rc : rcs ) {
            logger.info(methodName, null, rc.tablesToString());
        }
        */
        // 
        // Update the class-level counts
        //
        updateNodepools(globalNodepool, eligible);

        //
        // Now the easy part, user and job allocations
        //

        //
        // We now know how many shares per class.  Let's do something similar for all the users in each class.
        // This will leave us with the number of shares per user for each resource class.
        //
        // The fair-share is easier to calculate because all priorities are the same within a class - we
        // dealt with class weights in the loop above.  But we always have to deal with jobs that don't
        // use their full allocation.
        //
        for ( ResourceClass rc : rcs ) {
        
            if ( !rc.hasSharesGiven() ) {
                for (IRmJob j : rc.getAllJobs().values()) {
                    j.clearShares();
                    j.undefer();         // can get message set during defrag
                }
            	continue;
            }

            // 
            // Common rc-based share assignment for counting both user and job shares
            //
            /**
            int[] tmpSharesByOrder = rc.getSharesByOrder(globalNodepool.getArraySize());                       // get the shares given by countClassShares            
            int len = tmpSharesByOrder.length;
            for ( int i = 1; i < len; i++ ) {                                // now get by-share totals, same as nSharesByOrder in the node pool,
                // only for the class, not globally
                for ( int j = i+1; j < len; j++ ) {
                    if ( tmpSharesByOrder[j] != 0 ) {
                        tmpSharesByOrder[i] += (j / i) * tmpSharesByOrder[j];
                    }
                }
            }
            */
            
            if ( ! countUserShares(rc) ) {
                continue;                                // nothing to do, on to the next class
            }

            countJobShares(rc);
        }
        
    }


    /**
     * Is the job submitted to one of these classes?
     */
    protected boolean jobInClass(ArrayList rcs, IRmJob j)
    {
        for ( ResourceClass rc : rcs ) {
            if ( j.getResourceClass() == rc ) return true;
        }
        return false;
    }

    /**
     * Expand jobs from the needy list ahead of other jobs, because we likely stole the
     * shares from older jobs which would otherwise get priority.
     */
    protected void expandNeedyJobs(NodePool np, ArrayList rcs) 
    {
        String methodName = "expandNeedyJobs";

        if ( needyJobs.size() == 0 ) return;

        logger.trace(methodName, null, "Enter: needyJobs.size =", needyJobs.size());

        List subpools = np.getChildrenAscending();
        for ( NodePool subpool : subpools ) {
            expandNeedyJobs(subpool, rcs);
        }

        List fair_share_jobs = new ArrayList();
        List fixed_share_jobs = new ArrayList();
        List reservations = new ArrayList();

        List removeList = new ArrayList();
        for ( IRmJob j : needyJobs.values() ) {
            if ( j.isCompleted() ) {
                removeList.add(j);
                continue;
            }

            ResourceClass rc = j.getResourceClass();
            if ( rc == null ) {
                removeList.add(j);
                continue;           // job completed or was canceled or some other wierdness
            }

            if ( !jobInClass(rcs, j) ) continue;

            if ( rc.getNodepool() == np ) {
                switch ( rc.getPolicy()) {
                    case FAIR_SHARE:
                        fair_share_jobs.add(j);
                        break;
                    case FIXED_SHARE:
                        fixed_share_jobs.add(j);
                        break;
                    case RESERVE:
                        reservations.add(j);
                        break;
                }
                removeList.add(j);
            } 
        }
        
        for ( IRmJob j : removeList ) {
            needyJobs.remove(j);
        }


        // try to connect shares now
        Collections.sort(reservations, new JobByTimeSorter());
        logger.debug(methodName, null, "NP[", np.getId(), "Expand needy reservations.", listJobSet(reservations));
        for ( IRmJob j : reservations ) {
            ResourceClass rc = j.getResourceClass();
            np.findMachines(j, rc);
        }

        Collections.sort(fixed_share_jobs, new JobByTimeSorter());
        logger.debug(methodName, null, "NP[", np.getId(), "Expand needy fixed.", listJobSet(fixed_share_jobs));
        for ( IRmJob j : fixed_share_jobs ) {
            if ( np.findShares(j, false) > 0 ) {
                //
                // Need to fix the shares here, if any, because the findShares() code is same for fixed and fair share so it
                // won't have done that yet.
                //
                for ( Share s : j.getPendingShares().values() ) {
                    s.setFixed();
                    logger.info(methodName, j.getId(), "Assign:", s);
                }
            }
        }
 
        Collections.sort(fair_share_jobs, new JobByTimeSorter());
        logger.debug(methodName, null, "NP[", np.getId(), "Expand needy jobs.", listJobSet(fair_share_jobs));
        np.doExpansion(fair_share_jobs);

        logger.trace(methodName, null, "Exit : needyJobs.size =", needyJobs.size());
    }

    // private static int stop_here_dx = 0;
    protected void traverseNodepoolsForExpansion(NodePool np, ArrayList rcs)
    {
		String methodName = "traverseNodepoolsForExpansion";
        // HashMap subpools = np.getChildren();
        List subpools = np.getChildrenAscending();

        StringBuffer sb = new StringBuffer();
        for ( NodePool sp : subpools ) {
            sb.append(sp.getId());
            sb.append(" ");
        }
        logger.info(methodName, null, np.getId(), "Doing expansions in this order:", sb.toString());

        for ( NodePool subpool : subpools ) {
            traverseNodepoolsForExpansion(subpool, rcs);
        }

        // logger.info(methodName, null, "--- stop_here_dx", stop_here_dx);
        // if ( stop_here_dx == 13 ) {
        //     @SuppressWarnings("unused")
		// 	int stophere;
        //     stophere=1;
        // }
        // stop_here_dx++;

        // fall through at leaves
        
        // gather all the classes that are assigned to this pool

        ArrayList cls = new ArrayList();
        String npn = np.getId();
        int njobs = 0;
        for ( ResourceClass rc : rcs ) {
            String rcnpn = rc.getNodepoolName();
            if ( rcnpn == null ) continue;
            
            if ( rc.getNodepoolName().equals(npn) ) {
                cls.add(rc);
                njobs += rc.countJobs();
            }
        }

        if ( njobs == 0 ) return;
        
        ArrayList jobs = new ArrayList();    // collect all the jobs for the current np
        for ( ResourceClass rc : cls ) {
            jobs.addAll(rc.getAllJobs().values());
        }
        Collections.sort(jobs, new JobByTimeSorter());
        
        np.doExpansion(jobs);
    }

    /**
     * Counts are established.  We strictly honor them inside each job and evict where the count is 
     * less than the currently allocated shares, and add where it is greater.
     */
    //static int stophere = 0;
    protected void whatOfFairShare(ArrayList rcs)
    {
    	String methodName = "whatOfFairShare";

        ArrayList eligible = new ArrayList();
        Collections.sort(rcs, new ClassByWeightSorter());

        for ( ResourceClass rc : rcs ) {
            HashMap jobs = rc.getAllJobs();
            logger.info(methodName, null, "Schedule class", rc.getName());

            if ( jobs.size() == 0 ) {
                logger.info(methodName, null, "No jobs to schedule in class ", rc.getName());
            } else { 
                eligible.add(rc);
                for ( IRmJob j : jobs.values() ) {  // just logging for debug for now
                	logger.info(methodName, j.getId(), "Scheduling job in class ", rc.getName(), ":", j.countNSharesGiven(), "shares given, order", j.getShareOrder());
                }
            }
        }

        if ( eligible.size() == 0 ) {
            return;
        }

        //traverseNodepoolsForEviction(globalNodepool, eligible);
        // logger.trace(methodName, null, "Machine occupancy before expansion", stophere++);
        // if ( stophere == 7 ) {
        //     @SuppressWarnings("unused")
		// 	int stophere;
        //     stophere = 1 ;
        // }
        traverseNodepoolsForExpansion(globalNodepool, eligible);
    }

    // ==========================================================================================
    // ==========================================================================================
    // =========================== REWORKED CODE FOR FIXED_SHARE =================================
    // ==========================================================================================
    // ==========================================================================================

    /**
     * Make sure there are enough shares to allocate either directly, or through preemption,
     * and count them out.
     */
    void howMuchFixed(ArrayList rcs)
    {
    	String methodName = "howMuchFixed";

        if ( logger.isTrace() ) {
            logger.info(methodName, null, "Scheduling FIXED SHARE for these classes:");
            logger.info(methodName, null, "   ", ResourceClass.getHeader());
            logger.info(methodName, null, "   ", ResourceClass.getDashes());
            for ( ResourceClass pc : rcs ) {
                logger.info(methodName, null, "   ", pc.toString());
            }
        }

        int total_jobs = 0;
        for ( ResourceClass rc : rcs ) {
            HashMap jobs = rc.getAllJobs();
            total_jobs += jobs.size();
        }
        if ( total_jobs == 0 ) {
            return;
        }

        for ( ResourceClass rc : rcs ) {
            ArrayList jobs = rc.getAllJobsSorted(new JobByTimeSorter());            

            for ( IRmJob j : jobs ) {
                logger.info(methodName, j.getId(), "Scheduling job to class:", rc.getName());

                j.clearShares();                               // reset virtual shares at start of each schedling cycle
                j.undefer();                                   // in case it works this time!

                switch ( j.getDuccType() ) {
                    case Job:
                        countFixedForJob(j, rc);
                        break;
                    case Service:
                    case Pop:
                    case Reservation:
                    default:
                        countSingleFixedProcess(j, rc);
                        break;
                }
            }            
        }
    }

    
    void countFixedForJob(IRmJob j, ResourceClass rc)
    {
        String methodName = "countFixedForJob";

        logger.info(methodName, j.getId(), "Counting shares for", j.getShortType() + "." + j.getId());

        // Allowed something if we get here.  Must see if we have something to give.
        NodePool np = rc.getNodepool();

        int order = j.getShareOrder();
        int available = np.countLocalNSharesByOrder(order);
        logger.info(methodName, j.getId(), "available shares of order", order, "in np:", available);

        if ( available == 0 ) {
            if (j.countNShares() == 0)  {
                if ( np.countFixable(j) > 0 ) {
                    schedulingUpdate.defer(j, "Deferred because insufficient resources are availble.");
                    logger.info(methodName, j.getId(), "Deferring, insufficient shares available. NP", np.getId(), 
                                "available[", np.countNSharesByOrder(order), "]");
                } else {
                    schedulingUpdate.defer(j, "Deferred because no hosts in class " + rc.getName() + " have sufficient memory to accomodate the request.");
                    logger.info(methodName, j.getId(), "Deferring, no machines big enough for the request. NP", np.getId(), 
                                "available[", np.countNSharesByOrder(order), "]");
                }
                return;
            } else {
                logger.info(methodName, j.getId(), "Nodepool is out of shares: NP", np.getId(), 
                            "available[", np.countNSharesByOrder(order), "]");
            }
        }

        int granted = getAllotmentForJob(j); // in nshares, processes

        //
        // The job passes; give the job a count
        //
        logger.info(methodName, j.getId(), "+++++ nodepool", np.getId(), "class", rc.getName(), "order", order, "shares", nSharesToString(granted, order));
        int[] gbo = globalNodepool.makeArray();
        gbo[order] = granted;                      // what we get
        j.setGivenByOrder(gbo);
        
        // The difference between what we pass to 'what of', and what we already have.  The shares we already have are accounted
        // for in a special step at the start of the scheduling round.
        np.countOutNSharesByOrder(order, granted - j.countNShares());
    }

    void countSingleFixedProcess(IRmJob j, ResourceClass rc)
    {
        String methodName = "countSingleFixedProcess";


        logger.info(methodName, j.getId(), "Counting shares for", j.getShortType() + "." + j.getId(), "in class", rc.getName());
        NodePool np = rc.getNodepool();

        if ( j.isCompleted() ) {
            return;
        }

        if ( j.countNShares() > 0 ) {                  // only 1 allowed, UIMA-4275
            // already accounted for as well, since it is a non-preemptable share
            logger.info(methodName, j.getId(), "[stable]", "assigned", j.countNShares(), "processes, ", 
                        (j.countNShares() * j.getShareOrder()), "QS");
            int[] gbo = globalNodepool.makeArray();
            
            gbo[j.getShareOrder()] = 1;                // must set the allocation so eviction works right
            j.setGivenByOrder(gbo);
            return;
        }
        
        int order = j.getShareOrder();
        
        //
        // Now see if we have sufficient shares in the nodepool for this allocation.
        //
        if ( np.countLocalNSharesByOrder(order) == 0 ) {            
            if (np.countFixable(j) > 0 ) {
                schedulingUpdate.defer(j, "Deferred  because insufficient resources are availble.");
                logger.info(methodName, j.getId(), "Deferring, insufficient shares available. NP", np.getId(), "available[", np.countNSharesByOrder(order), "]");
                
            } else {
                schedulingUpdate.defer(j, "Deferred because no hosts in class " + rc.getName() + " have sufficient memory to accomodate the request.");
                logger.info(methodName, j.getId(), "Deferring, no machines big enough for the request. NP", np.getId(), 
                            "available[", np.countNSharesByOrder(order), "]");
            }
            return;
        }
        
        //
        // Make sure this allocation does not blow the allotment cap.
        //
        if ( ! validSingleAllotment(j) ) return;        // this method will defer the job and log it
        
        //
        // The job passes.  Assign it a count and get on with life ...
        //
        logger.info(methodName, j.getId(), "+++++ nodepool", np.getId(), "class", rc.getName(), "order", order, "shares", nSharesToString(1, order));
        int[] gbo = globalNodepool.makeArray();
        gbo[order] = 1;
        j.setGivenByOrder(gbo);
        
        np.countOutNSharesByOrder(order, 1);
    }

    /**
     * If there are free shares of the right order just assign them.  Otherwise
     * the counts will cause evictions in lower-priority code so we just wait.
     */
    protected void whatOfFixedShare(ArrayList rcs)
    {
    	String methodName = "whatOfFixedShare";
        for ( ResourceClass rc : rcs ) {
            ArrayList jobs = rc.getAllJobsSorted(new JobByTimeSorter());

            NodePool np = rc.getNodepool();
            for ( IRmJob j : jobs ) {
                if ( j.countNShares() == j.countNSharesGiven() ) {  // got what we need, we're done
                    continue;
                }

                if ( j.isRefused() ) {                      // bypass jobs that we know can't be allocated. unlikely after UIMA-4275.
                    continue;
                }

                if ( j.isDeferred() ) {                     // UIMA-4275 - still waiting for an allocation
                    continue;
                }

                if ( j.isCompleted() ) {                    // UIMA-4327 - reinstated, if this gets set we aren't allowed to expand any more
                    continue;
                }

                int order = j.getShareOrder();
                int count = j.countNSharesGiven();

                if ( np.findSharesHorizontal(j) > 0 ) {       // UIMA-4275, no longer require full allocation, we'll take what we can
                                                              // UIMA-4712 first try horizontal stacking
                    //
                    // Need to fix the shares here, if any, because the findShares() code is same for fixed and fair share so it
                    // won't have done that yet.
                    //
                    for ( Share s : j.getPendingShares().values() ) {
                        s.setFixed();
                    }
                    logger.info(methodName, j.getId(), "Assign(H):", nSharesToString(count, order));
                }

                if ( j.countNShares() == 0 ) {                // UIMA-4712 now try horizontal stacking
                    if ( np.findSharesVertical(j) > 0 ) {
                        //
                        // Need to fix the shares here, if any, because the findShares() code is same for fixed and fair share so it
                        // won't have done that yet.
                        //
                        for ( Share s : j.getPendingShares().values() ) {
                            s.setFixed();
                        }
                        logger.info(methodName, j.getId(), "Assign(V):", nSharesToString(count, order));
                    }
                }

                // 
                // If nothing assigned we're waiting on preemptions which will occur naturally, or by forcible eviction of squatters,
                // or defrag.
                //
                if ( j.countNShares() == 0 ) {
                    j.setReason("Waiting for preemptions.");
                }                
            }
        }
    }

    // ==========================================================================================
    // ==========================================================================================
    // =========================== REWORKED CODE FOR RESERVATIONS ================================
    // ==========================================================================================
    // ==========================================================================================
	private void howMuchReserve(ArrayList rcs)
    {
        String methodName = "howMuchreserve";

        if ( logger.isTrace() ) {
            logger.info(methodName, null, "Calculating counts for RESERVATION for these classes:");
            logger.info(methodName, null, "   ", ResourceClass.getHeader());
            logger.info(methodName, null, "   ", ResourceClass.getDashes());
            for ( ResourceClass pc : rcs ) {
                logger.info(methodName, null, "   ", pc.toString());
            }
        }

        int total_jobs = 0;
        for ( ResourceClass rc : rcs ) {
            HashMap jobs = rc.getAllJobs();
            total_jobs += jobs.size();
        }
        if ( total_jobs == 0 ) {
            return;
        }

        for ( ResourceClass rc : rcs ) {
            ArrayList jobs = rc.getAllJobsSorted(new JobByTimeSorter());

            // Now pick up the work that can  be scheduled, if any
            for ( IRmJob j : jobs) {
                j.clearShares();                               // reset shares assigned at start of each schedling cycle
                j.undefer();                                   // in case it works this time!

                switch ( j.getDuccType() ) {
                    case Job:
                        countReservationForJob(j, rc);
                        break;
                    case Service:
                    case Pop:
                    case Reservation:
                    default:
                        countSingleReservation(j, rc);
                        break;
                }
            }            
        }
    }

    void countReservationForJob(IRmJob j, ResourceClass rc)
    {
        String methodName = "countReservationForJob";

        logger.info(methodName, j.getId(), "Counting full machines for", j.getShortType() + "." + j.getId());

        // Allowed something if we get here.  Must see if we have something to give.
        NodePool np = rc.getNodepool();

        // Are there any machines of the right size in the NP that can be reserved for this job?
        int available = np.countReservables(j);
        if ( available == 0 ) {
            if (j.countNShares() == 0)  {
                schedulingUpdate.defer(j, "Deferred because there are no hosts of the correct size in class " + rc.getName());
                logger.info(methodName, j.getId(), "Deferred because no hosts of correct size. NP", np.getId());
                           
            } else {
                logger.info(methodName, j.getId(), "Nodepool is out of shares: NP", np.getId());
            }
            return;
        }

        int granted = getAllotmentForJob(j);           // total allowed, including those already scheduled
   
        int needed = granted - j.countNShares();                     // additional shares
        int freeable = 0;
        if ( needed > 0 ) {
            freeable = np.countFreeableMachines(j, needed);         // might schedule evictions
            if ( (freeable + j.countNShares()) == 0 ) {
                schedulingUpdate.defer(j, "Deferred because resources are exhausted."); 
                logger.warn(methodName, j.getId(), "Deferred because resources are exhausted in nodepool " + np.getId());
                return;
            }
        }

        //
        // The job passes; give the job a count
        //
        logger.info(methodName, j.getId(), "Request is granted a machine for reservation.");
        int[] gbo =globalNodepool.makeArray();
        int order = j.getShareOrder();     // memory, coverted to order, so we can find stuff        
        gbo[order] = freeable + j.countNShares(); // account for new stuff plus what it already has
        j.setGivenByOrder(gbo);

    }

    void countSingleReservation(IRmJob j, ResourceClass rc)
    {
        String methodName = "countSingleReservation";

        logger.info(methodName, j.getId(), "Counting shares for", j.getShortType() + "." + j.getId(), "in class", rc.getName());
        NodePool np = rc.getNodepool();

        if ( j.countNShares() > 0 ) {
            logger.info(methodName, j.getId(), "[stable]", "assigned", j.countNShares(), "processes, ", 
                        (j.countNShares() * j.getShareOrder()), "QS");

            int[] gbo =globalNodepool.makeArray();

            gbo[j.getShareOrder()] = 1;         // UIMA4275 - only one
            j.setGivenByOrder(gbo);            
            return;
        }

        //
        // Make sure this allocation does not blow the allotment cap.
        //
        if ( ! validSingleAllotment(j) ) return;   // defers and logs 

        if ( np.countReservables(j) == 0 ) {
            schedulingUpdate.defer(j, "Deferred because there are no hosts of the correct size in class " + rc.getName());
            logger.warn(methodName, j.getId(), "Deferred because requested memory " + j.getMemory() + " does not match any machine.");
            return;
        }

        if ( np.countFreeableMachines(j, 1) == 0 ) {         // might also schedule preemptions
            schedulingUpdate.defer(j, "Deferred because resources are exhausted."); 
            logger.warn(methodName, j.getId(), "Deferred because resources are exhausted in nodepool " + np.getId());
            return;
        }
                
        logger.info(methodName, j.getId(), "Request is granted a machine for reservation.");
        int[] gbo =globalNodepool.makeArray();
        int order = j.getShareOrder();     // memory, coverted to order, so we can find stuff        
        gbo[order] = 1;
        j.setGivenByOrder(gbo);
        
    }

    /**
     */
	private void whatOfReserve(ArrayList rcs)
    {
        String methodName = "whatOfToReserve";
        for ( ResourceClass rc : rcs ) {
            NodePool np = rc.getNodepool();

            ArrayList jobs = rc.getAllJobsSorted(new JobByTimeSorter());
            for ( IRmJob j: jobs ) {

                if ( j.isRefused() ) {                   // bypass jobs that we know can't be allocated
                    continue;
                }

                if ( j.isDeferred() ) {                  // counts don't work, we can't do this yet
                    continue;
                }

                if ( j.isCompleted() ) {                 // UIMA-4327 - reinstated, if this gets set we aren't allowed to expand any more
                    continue;
                }

                try {
                    np.findMachines(j, rc);
                } catch (Exception e) {
                    logger.error(methodName, j.getId(), "Reservation issues:", e);
                    continue;
                }

                // 
                // Either shares were assigned or not.  If not we wait for evictions, otherwise it is
                // fully allocated. Nothing more to do here.
                //
                if ( j.countNShares() == 0 ) {
                    j.setReason("Waiting for preemptions.");
                }
            }
        }
    }

    /**
     * Remove non-preemptable resources from the counts.
     */
    protected void accountForNonPreemptable()
    {
        for (ResourceClass rc : resourceClasses.values() ) {
            switch ( rc.getPolicy() ) {
               case FAIR_SHARE:
                   break;

               case FIXED_SHARE: 
               case RESERVE:               
                   {
                       NodePool np = rc.getNodepool();
                       HashMap jobs = rc.getAllJobs();
                       for ( IRmJob j : jobs.values() ) {
                           HashMap shares = j.getAssignedShares();
                           np.accountForShares(shares);
                       }
                   }
                   break;                
            }
        }
    }

    /**
     * Remove active shares from the nodepool counts, leaving us with just the free shares in the tables.
     */
    protected void accountForFairShare()
    {
        for (ResourceClass rc : resourceClasses.values() ) {
            if ( rc.getPolicy() == Policy.FAIR_SHARE ) {
                NodePool np = rc.getNodepool();
                HashMap jobs = rc.getAllJobs();
                for ( IRmJob j : jobs.values() ) {
                    HashMap shares = j.getAssignedShares();
                    np.accountForShares(shares);
                }
            }
        }
    }
        
    /**
     * The second stage - work up all counts globally
     */
    protected void findHowMuch(ArrayList rcs)
    {
        switch ( rcs.get(0).getPolicy() ) {
             case FAIR_SHARE:
                  howMuchFairShare(rcs);
                  break;
             case FIXED_SHARE:
                 howMuchFixed(rcs);
                 break;
             case RESERVE:
                 howMuchReserve(rcs);
                 break;
        }
    }

    /**
     * Collect all the fair-share classes and pass them to the nodepools for eviction
     * This interacts recursively with NodePool.doEvictions*.
     *
     * In NodepoolScheduler we recurse doing depth-first traversal to have every nodepool do evictions for jobs
     * that are submitted to that pool.  The NodePool then recurses again in depth-first mode to evict shares that
     * have spilled into lower-level pools.  We have a double-recursion:
     * 1.  Evict shares for jobs that originate in each node pool
     * 2.  Evict shares that have spilled into lower-level pools.
     */
    // private static int stop_here_de = 0;
    protected void doEvictions(NodePool nodepool)
    {
    	String methodName = "doEvictions";
    	
        // logger.info(methodName, null, "--- stop_here_de", stop_here_de);
        // if ( stop_here_de == 7 ) {
        //     @SuppressWarnings("unused")
		// 	int stophere;
        //     stophere=1;
        // }
        // stop_here_de++;

        for ( NodePool np : nodepool.getChildrenDescending() ) {   // recurse down the tree
            logger.info(methodName, null, "Recurse to", np.getId(), "from", nodepool.getId());
            doEvictions(np);                                       // depth-first traversal
            logger.info(methodName, null, "Return from", np.getId(), "proceeding with logic for", nodepool.getId());
        }

        int neededByOrder[] =globalNodepool.makeArray();         // for each order, how many N-shares do I want to add?
        // int total_needed = 0;

        Map overages = new HashMap();        // UIMA-4275
        for ( ResourceClass cl : resourceClasses.values() ) {
            if ( cl.getNodepoolName().equals(nodepool.getId()) && (cl.getAllJobs().size() > 0) ) {
                HashMap jobs = cl.getAllJobs();
                String npn = cl.getNodepoolName();
                logger.info(methodName, null, String.format("%12s %7s %7s %6s %5s", npn, "Counted", "Current", "Needed", "Order"));

                for ( IRmJob j : jobs.values() ) {
                    int counted = j.countNSharesGiven();      // allotment from the counter
                    int current = j.countNShares();           // currently allocated, plus pending, less those removed by earlier preemption
                    int needed = (counted - current);
                    int order = j.getShareOrder();
         
                    // Why abs and not max?  Because if needed > 0, that's shares we need to make space for.
                    //                               if needed < 0, that's shares we need to dump because the
                    //                                              counts say so.
                    //                               if needed == 0 then clearly nothing

                    if ( needed < 0 ) {
                        // UIMA-4275 these guys must be forced to shrink
                        overages.put(j, -needed);
                    } else {
                        // needed = Math.abs(needed); 
                        // needed = Math.max(0, needed);
                        
                        logger.info(methodName, j.getId(), String.format("%12s %7d %7d %6d %5d", npn, counted, current, needed, order));
                        neededByOrder[order] += needed;
                        // total_needed += needed;
                    }
                }
            }
    
        }

        // Every job in overages is required to lose the indicated number of share.  If this is done optimally it
        // will leave suficcient space for the counted shares of all the expansions.  Therein lies the rub.
        //
        // The older code below does its best to make space for the 'needed' array but it fails to fully evict
        // an over-deployed job in a number of situations.  The loop here is going to rely on defragmentation, which
        // we did not have originally to do final cleanup.  The job will be asked to dump its extra processes according
        // to the mechanism in its shrinkBy() method.  See that method for details.
        // UIMA-4275
        for (IRmJob j : overages.keySet()) {
            j.shrinkBy(overages.get(j));
        }

        // First we try to make enough space in the right places for under-allocation jobs
        // logger.debug(methodName, null, nodepool.getId(),  "NeededByOrder before any eviction:", Arrays.toString(neededByOrder));        
        // if ( (nodepool.countOccupiedShares() > 0) && (total_needed > 0) ) {
        //     nodepool.doEvictionsByMachine(neededByOrder, false);


    }

    /**
     * Determine if a candidate share can or cannot be transferred (eventually) to a needy job based on nodepool constraints.
     */
    boolean compatibleNodepools(Share candidate, IRmJob needy)
    {
        Machine m = candidate.getMachine();
        ResourceClass nrc = needy.getResourceClass();
        NodePool np = nrc.getNodepool();

        return np.containsMachine(m);           // can we get to the candidate share from 'needy's np?
    }

    // /**
    //  * Discover whether the potential job is able or unable to supply shares to a needy job because of nodepool restrictions.
    //  */
    // boolean compatibleNodepools(IRmJob potential, IRmJob needy)
    // {
    //     ResourceClass prc = potential.getResourceClass();
    //     ResourceClass nrc = needy.getResourceClass();

    //     NodePool pp = prc.getNodepool();
    //     NodePool np = nrc.getNodepool();

    //     return np.containsSubpool(pp) || pp.containsSubpool(np);
    // }

    /**
     * Discover whether the potential resource class is able or unable to supply shares to a jobs in a needy class because of nodepool restrictions.
     */
    boolean compatibleNodepools(ResourceClass potential, IRmJob needy)
    {
        ResourceClass nrc = needy.getResourceClass();

        NodePool pp = potential.getNodepool();
        NodePool np = nrc.getNodepool();

        return np.containsSubpool(pp) || pp.containsSubpool(np);
    }

    /**
     * For debugging, get job ids onto a single line.
     */
    String listJobSet(Map jobs)
    {
        if ( jobs.size() == 0 ) return "NONE";
        StringBuffer sb = new StringBuffer("[");
        for ( IRmJob j : jobs.keySet() ) {
            sb.append(j.getId());
            sb.append(" ");
        }
        sb.append("]");
        return sb.toString();
    }

    String listJobSet(List jobs)
    {
        if ( jobs.size() == 0 ) return "NONE";
        StringBuffer sb = new StringBuffer("[");
        for ( IRmJob j : jobs ) {
            sb.append(j.getId());
            sb.append(" ");
        }
        sb.append("]");
        return sb.toString();
    }

    /**
     * Make a share available to job 'nj'.  If possible, just reassign  If not, cancel it's expansion or evict it, as appropriate.
     */
    boolean clearShare(Share s, IRmJob nj)
    {
    	String methodName = "clearShare";
        IRmJob rich_j = s.getJob();
        if ( s.isPending() ) {
            if (s.getShareOrder() == nj.getShareOrder() ) {                            // same size, reassign it directly
                logger.debug(methodName, nj.getId(), "Reassign expanded share", s.toString(), "from", rich_j.getId());
                Machine m = s.getMachine();
                m.reassignShare(s, nj);
                rich_j.cancelPending(s);
                nj.assignShare(s);                
                return false;
            } else {                                                                   // different size, just discard it
                logger.debug(methodName, nj.getId(), "Canceling expansion share", s.toString(), "from", rich_j.getId());
                rich_j.cancelPending(s);
                Machine m = s.getMachine();
                m.removeShare(s);
                return true;
            }
        } else {                                                                       // not pending, must evict it
            logger.debug(methodName, nj.getId(), "Donate", s.toString(), "from", rich_j.getId());
            rich_j.shrinkByOne(s);
            return false;
        }
    }

    /**
     * This routine tries to find enough process that can be coopted from "rich" users for jobs that deserved
     * shares but couldn't get them because of fragmentation.
     *
     * @param j               This is the job we are trying to find shrares for. 
     * @param needed          This is the number of processes we need for job j.
     * @param users_by_wealth This is all the users who can donate, ordered by wealth.
     * @param jobs_by_user    This is all the candidate jobs owned by the user.  Note that this is not necessarily
     *                        ALL the user's jobs, we will have culled everything that makes no sense to
     *                        take from in the caller.
     *
     * @return The number of processes we found space for.  Note this could be different from the number
     *         of processes evicted, if it took more than one eviction to make spece.  Also We may have
     *         evicted a process smaller than is needed, because there was already some free space on
     *         the machine.
     */
    int takeFromTheRich(IRmJob nj, 
                        int needed,
                        TreeMap users_by_wealth,
                        HashMap> jobs_by_user)
    {
    	String methodName = "takeFromTheRich";
        // 1. Collect all machines that have shares, which if evicted, would make enough space
        //    - in compatible NP
        //    - g + sum(shares belonging to rich users on the machine);
        // 2. Order the machiens by 
        //    a) richest user
        //    b) largest machine
        // 3. Pick next machine,
        //    - clear enough shares
        //    - remove machine from list
        //    - update wealth
        // 4. Repeat at 2 until
        //    a) have given what is needed
        //    b) nothing left to give

        Map  candidateJobs    = new HashMap();
        Map eligibleMachines = new TreeMap(new EligibleMachineSorter());

        for ( TreeMap jobs : jobs_by_user.values() ) {
            candidateJobs.putAll(jobs);
        }

        int given = 0;
        int orderNeeded = nj.getShareOrder();
        
        ResourceClass cl     = nj.getResourceClass();               // needy job's resource class
        String        npname = cl.getNodepoolName();                // name of the class
        NodePool      np     = globalNodepool.getSubpool(npname);   // job's nodepool
        Map machines = np.getAllMachines();          // everything here is a candidate, nothing else is
                                                                    //   this is the machines in the pool, and all the
                                                                    //   subpools

        // Here we filter all the machines looking for machines that *might* be able to satisfy the defrag.  At the 
        // end this set of machines is eligbleMachines.
        machine_loop : 
            for ( Machine m : machines.values() ) {

                if ( m.getShareOrder() < orderNeeded ) {                // nope, too small
                    logger.debug(methodName, nj.getId(), "Bypass ", m.getId(), ": too small for request of order", orderNeeded); 
                    continue;
                }

                // if the job is a reservation the machine size has to matchm and machine must be clearable
                if ( nj.getSchedulingPolicy() == Policy.RESERVE ) {
                    if ( m.getShareOrder() != orderNeeded ) {
                        logger.debug(methodName, nj.getId(), "Bypass ", m.getId(), ": RESERVE policy requires exact match for order", orderNeeded);
                        continue;
                    }
                    // machine must be clearable as well
                    Collection shares = m.getActiveShares().values();
                    for ( Share s : shares ) {
                        if ( ! candidateJobs.containsKey(s.getJob()) ) {
                            logger.debug(methodName, nj.getId(), "Bypass ", m.getId(), ": for reservation, machine contains non-candidate job", s.getJob().getId());
                            continue machine_loop;
                        }
                    }                
                }

                Map as = m.getActiveShares();            // everything alloacated here
                int g = m.getVirtualShareOrder();                      // g is space that we might be able to make after defrag:
                //    free space + freeable-from-candidates
                for ( Share s : as.values() ) {
                    IRmJob j = s.getJob();
                    if ( s.isForceable() && candidateJobs.containsKey(j) ) {  // evictable, and a candidate for reclamation by defrag
                        g += j.getShareOrder();
                    }
                }

                if ( g >= orderNeeded ) {                              // if it's usable by the job, it's a candidate
                    logger.info(methodName, nj.getId(), "Candidate machine:", m.getId());
                    eligibleMachines.put(m, m);
                } else {
                    logger.info(methodName, nj.getId(), "Not a candidate, insufficient free space + candidate shares:", m.getId());
                }
            }
        
        // Now eligibleMachines is the set of candidate machines for defrag

        logger.info(methodName, nj.getId(), "Found", eligibleMachines.size(), "machines to be searched in this order:");
        StringBuffer buf = new StringBuffer();
        for ( Machine m : eligibleMachines.keySet() ) {
            buf.append(m.getId());
            buf.append(" ");
            }
        logger.info(methodName, nj.getId(), "Eligible machines:", buf.toString());

        // first part done, we know where to look.

        // Now just bop through the machines to see if we can get anything for this specific job (nj)
        int given_per_round = 0;
        do {
            int g = 0;
            given_per_round = 0;
            for ( Machine m : eligibleMachines.keySet() ) {

                //
                // How best to order candidate shares?  You can choose the "wealthiest" first, but if it's not a good
                // match by size, end up evicting too many shares which could include a not-so-wealthy share, or
                // increase frag by breaking it up and leaving a useless bit.
                //
                // So we're going to try ordering shares by "wealthiest", but then if we find an exact match by size,
                // order that to the front of the candidates.  We may not end up evicting the "wealthiest", but we
                // should end up evicting tne least disruptive share.
                //
                List sh = new ArrayList();
                sh.addAll(m.getActiveShares().values());
                Collections.sort(sh, new ShareByWealthSorter());

                g = m.getVirtualShareOrder();         // ( free space at this point )
                List potentialShares     = new ArrayList();
                for ( Share s : sh ) {
                    IRmJob j = s.getJob();
                    // User u = j.getUser();
                    
                    if ( s.isForceable() ) {
                        if ( candidateJobs.containsKey(j) ) {
                            g += s.getShareOrder();
                            if ( s.getShareOrder() == orderNeeded ) {
                                potentialShares.add(0, s);    // exact matches first
                            } else {
                                potentialShares.add(s);
                            }
                        }
                    }
                    if ( g >= orderNeeded ) break;
                }
                
                // potentialShares should be properly ordered as discussed above at this point
                if ( g >= orderNeeded ) {
                    // found enough on this machine for 1 share!
                    logger.debug(methodName, nj.getId(), "Clearing shares: g[", g, "], orderNeeded[", orderNeeded, "]");
                    g = m.getVirtualShareOrder();             // reset
                    for ( Share s : potentialShares ) {
                        IRmJob j = s.getJob();
                        User u = j.getUser();

                        g += s.getShareOrder();
                        given_per_round++;
                        clearShare(s, nj);
                        u.subtractWealth(s.getShareOrder());
                        logger.debug(methodName, nj.getId(), "Clearing share", s, "order[", s.getShareOrder(),
                                     "]: g[", g, "], orderNeeded[", orderNeeded, "]");
                        if ( g >= orderNeeded) break; // inner loop, could break on exact match without giving everything away
                    }
                    break;                            // outer loop, if anything was found
                }       
            }

            if ( given_per_round > 0 ) {
                // Must reorder the eligible list to get the "next" best candidate.  We could try to remove 
                // machines that were exhausted above ...
                Map tmp = new HashMap();
                tmp.putAll(eligibleMachines);
                eligibleMachines.clear();
                for ( Machine m : tmp.keySet() ) {
                    eligibleMachines.put(m, m);
                }

                // and also must track how many processes we made space for
                given = given + (g / orderNeeded);    // at least one,or else we have a bug 
                logger.debug(methodName, nj.getId(), "LOOPEND: given[", given, "] g[", g, "] orderNeeded[", orderNeeded, "]");
            }
            logger.debug(methodName, nj.getId(), "Given_per_round", given_per_round, "given", given, "needed", needed);
        } while ( (given_per_round > 0) && ( given < needed ));

        // Sometimes we can directly reassign a share, in which case the job isn't waiting any more.
        // We only care about setting a message if the poor thing is still totally starved of resources.
        if ( nj.countNShares() == 0 ) {
            nj.setReason("Waiting for defragmentation.");
        }

        return given;
    }

    void doFinalEvictions(HashMap needy)
    {
    	String methodName = "doFinalEvictions";

        for ( IRmJob j : needy.keySet() ) {
            logger.debug(methodName, j.getId(), "Will attempt to have space made for", needy.get(j), "processes");
        }

        //
        // Search for candidate donors and order by "most able to be generous".  Nodepools must be compatible.
        //
        // If prioritiy of needy is same or better, the cCandidates must not be needy, must be initialized already, 
        //     and have sufficient shares to give.
        // 
        // If priority of needy is better we keep track of the rich vs the poor jobs and possibly perform a second
        //     pass that includes poor jobs, if we can't get enougg from the rich.
        //
        for ( IRmJob nj : needy.keySet() ) {
            int priority_needy = nj.getSchedulingPriority();
            TreeMap rich_candidates = new TreeMap(new FragmentationSorter());  // first class candidates, they're rich and available
            TreeMap poor_candidates = new TreeMap(new FragmentationSorter());  // clearing for better priority job, we only use this if it's
                                                                                                               // impossible to clear from the rich candidates

            for ( ResourceClass rc : resourceClasses.values() ) {
                
                if ( rc.getPolicy() == Policy.RESERVE )     continue;          // exempt from preemption
                if ( rc.getPolicy() == Policy.FIXED_SHARE ) continue;          // exempt from preemption

                if ( ! compatibleNodepools(rc, nj) ) {
                    logger.debug(methodName, nj.getId(), "Skipping class", rc.getName(), "vs job class", nj.getResourceClass().getName(), "because of incompatible nodepools.");
                    continue;
                }

                int priority_candidate = rc.getPriority();
                boolean use_expanded_pool = false;      // better priority job is allowed to look at poor jobs if can't be satisfied from the rich

                if ( priority_needy > priority_candidate ) {  // Greater means worse 
                    logger.debug(methodName, nj.getId(), "Jobs in class", rc.getName(), "are not candidates because better priority: [", 
                                 priority_candidate, "vs", priority_needy, "]");
                    continue;
                }

                if ( priority_needy < priority_candidate ) {   // less means better
                    logger.debug(methodName, nj.getId(), "Needy job has better priority than jobs in class", rc.getName(), "[", 
                                 priority_candidate, "vs", priority_needy, "]. Using expanded pool.");
                    use_expanded_pool = true;
                }

                HashMap jobs = rc.getAllJobs();
                for ( IRmJob j : jobs.values() ) {
                    int nshares = j.countNShares();
                    int qshares = nshares * j.getShareOrder();

                    if ( nshares == 0 ) {
                        logger.debug(methodName, nj.getId(), "Job", j.getId(), "is not a candidate because it has no share.");
                        continue;
                    } 

                    if ( needy.containsKey(j) ) {
                        if ( use_expanded_pool ) {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is a backup candidate because it's needy.");
                            poor_candidates.put(j, j);
                        } else {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is a not a candidate because it's needy.");
                        }
                        continue;
                    }
                    
                    if ( ! j.isInitialized() ) {
                        if ( use_expanded_pool ) {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is a backup candidate because it's not initialized yet.");
                            poor_candidates.put(j, j);
                        } else {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is not a candidate because it's not initialized yet.");
                        }
                        continue;
                    }
                    
                    if ( nshares < fragmentationThreshold ) {
                        if ( use_expanded_pool ) {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is a backup candidate because below frag threshold. nshares[", nshares, "] qshares[", qshares, "] threshold[", fragmentationThreshold, "]");
                            poor_candidates.put(j, j);
                        } else {
                            logger.debug(methodName, nj.getId(), "Job", j.getId(), "is not a candidate because below frag threshold. nshares[", nshares, "] qshares[", qshares, "] threshold[", fragmentationThreshold, "]");
                        }
                        continue;
                    }

                    logger.debug(methodName, nj.getId(), "Job", j.getId(), "is a candidate with processes[", nshares, "] qshares[", qshares, "]");
                    rich_candidates.put(j, j);
                }
            } // End search for candidate donors

            //
            // Here start looking at 'needy' and trying to match them agains the candidates
            //
            HashMap> jobs_by_user = new HashMap>();  // use this to track where the wealth originatses
            TreeMap users_by_wealth = new TreeMap(new UserByWealthSorter());             // orders users by wealth

            collectWealth(rich_candidates, users_by_wealth, jobs_by_user);

            int needed = needy.get(nj);      // this was adjusted to a reasonable level in the caller
            logger.debug(methodName, nj.getId(), "Needy job looking for", needed, "more processes of O[", nj.getShareOrder(), "]");

            //
            // Try stealing shares from the "rich" candidates first.
            //
            needed -= takeFromTheRich(nj, needed, users_by_wealth, jobs_by_user);
            if ( needed <= 0 ) {
                // This can go <0 if total space freed + unused space on a node adds up to >1 share.
                // It's slimplest to just not sweat it and call it satisfied.
                logger.info(methodName, nj.getId(), "Satisfied needs of job by taking from the rich.");
                continue;
            }

            //
            // The needy job had sufficient priority that be built up a list of emergency-backup jobs to evict.
            //
            if ( poor_candidates.size() > 0) {
                logger.info(methodName, nj.getId(), "Could not clear sufficient space from rich candidates.  Retrying with all candidates.");
                jobs_by_user.clear();
                users_by_wealth.clear();
                rich_candidates.putAll(poor_candidates);
                collectWealth(rich_candidates, users_by_wealth, jobs_by_user);

                needed -= takeFromTheRich(nj, needed, users_by_wealth, jobs_by_user);
                if ( needed <= 0 ) {
                    // This can go <0 if total space freed + unused space on a node adds up to >1 share.
                    // It's slimplest to just not sweat it and call it satisfied.
                    logger.info(methodName, nj.getId(), "Satisfied needs of job by taking from all candidates.");
                    continue;
                }
            }
            logger.info(methodName, nj.getId(), "Could not get enough from the rich. Asked for", needy.get(nj), "still needing", needed);
            nj.setReason("Waiting for defragmentation.");
        }
    }

    
    void collectWealth(TreeMap candidates, TreeMap users_by_wealth, HashMap> jobs_by_user)
    {
        // Candidates are ordered by the FragmentationSorter
        //   - most over pure fair share
        //   - hten most currently allocated

        // user_by_wealth is ordered by the UserByWealthSorter
        //   - ordered by most wealth - actual qshares over all jobs

        //
        // Collect total wealth and order the wealthy by spondulix
        //
        HashMap shares_by_user = new HashMap();                                // use this to track user's wealth
        
        for ( IRmJob j : candidates.values() ) {
            User u = j.getUser();
            
            if ( shares_by_user.get(u) == null ) {
                shares_by_user.put(u, 0);
            }
            shares_by_user.put(u, shares_by_user.get(u) + (j.countNShares() * j.getShareOrder()));
            
            TreeMap ujobs = jobs_by_user.get(u);
            if ( ujobs == null ) {
                ujobs = new TreeMap(new JobByShareSorter()); // orders by largest number of assigned shares
                jobs_by_user.put(u, ujobs);
            }
            ujobs.put(j, j);
        }
        
        // and tracks their fat jobs
        for ( User u : shares_by_user.keySet() ) {
            u.setShareWealth(shares_by_user.get(u));       // qshares
            users_by_wealth.put(u, u);
        }
    }


    void getNodepools(NodePool top, List nodepools)
    {        
        for ( NodePool np : top.getChildren().values()) {
            getNodepools(np, nodepools);
        }
        nodepools.add(top);
    }

    /**
     * Check to see if there are jobs whose counts indicate they should be getting shares, but which
     * can't be satisfied either from free shares or from pending evictions because of
     * fragmentation.
     *
     * If there's frag, we return a map that shows how much each under-allocated job needs, and
     * a map of the pending evictions.
     */
    void detectFragmentation(HashMap needed_by_job)
    {
    	String methodName = "detectFragmentation";

        if ( logger.isDebug() ) {
            logger.debug(methodName, null, "vMachines:", fmtArray(globalNodepool.cloneVMachinesByOrder()));
        }

        List poollist = new ArrayList();
        getNodepools(globalNodepool, poollist);
        NodePool[] allPools = poollist.toArray(new NodePool[poollist.size()]);

        if ( logger.isDebug() ) {
            // make sure the node pool list is built correctly
            StringBuffer sb = new StringBuffer("Nodepools:");
            for ( NodePool np : allPools ) {
                sb.append(np.getId());
                sb.append(" ");
            }
            logger.debug(methodName, null, sb.toString());
        }

        // These next two maps are built lazily, once per call to this routine
        Map vshares = new HashMap();     // Virtual shares of each order in each nodepool.
                                               // This gets enhanced with pending evictions so we
                                               // can tell whether enough space is accounted for
                                               // for each job.
        Map nshares = new HashMap();     // For each order, the number of shares available,
                                               // either directly, or through splits from higher-order
                                               // space, enhanced with pending evictions and purges.
        Map> jobs = new HashMap>();

        for ( int npi = 0; npi < allPools.length; npi++ ) {                       // Turns out to be depth-first traversal !
            // First pass, init the structures, including any free space that may have been unusable
            NodePool np = allPools[npi];
            String id = np.getId();

            int[] vmach =globalNodepool.makeArray();
            int[] nmach =globalNodepool.makeArray();
            Map jobmap = new HashMap();
            vshares.put(id, vmach);
            nshares.put(id, nmach);
            jobs.put(id, jobmap);
        }

        boolean must_defrag = false;
        String headerfmt = "%14s %20s %6s %4s %7s %6s %2s";
        String datafmt   = "%14s %20s %6d %4d %7d %6d %2d";

        for ( ResourceClass rc : resourceClasses.values() ) {
            // Next: Look at every job and work out its "need".  Collect jobs by nodepool into the jobmaps.
            Map allJobs = rc.getAllJobs();
            String npn = rc.getNodepoolName();
            Map jobmap = jobs.get(npn);

            if ( allJobs.size() == 0 ) continue;

            logger.info(methodName, null, String.format(headerfmt, "Nodepool", "User", "PureFS", "NSh", "Counted", "Needed", "O"), "Class:", rc.getName());
            for ( IRmJob j : allJobs.values() ) {

                if ( j.isRefused() ) {
                    continue;
                }

                if ( j.isDeferred() ) {
                    continue;
                }

                int counted = j.countNSharesGiven();          // accounting for ramp-up, various caps, etc. 

                int current = j.countNShares();                // currently allocated, plus pending, less those removed by earlier preemption
                int needed = counted - current;                // could go negative if its evicting
                int order = j.getShareOrder();
                
                if ( j.getSchedulingPolicy() == Policy.FAIR_SHARE ) {   // cap on frag threshold
                    if ( current >= fragmentationThreshold ) { 
                        needed = 0;
                    } else if ( current >= j.getPureFairShare() ) {     // more than our pure share, we're not needy
                        needed = 0;
                    } else if ( needed < 0 ) {                          // more than out count, likely are evicting
                        needed = 0;
                    } else if ( needed > 0) {
                        needed = Math.min(needed, fragmentationThreshold);
                        jobmap.put(j, needed);                 // we'll log this in a minute, not here
                        must_defrag = true;
                    }                    
                } else {                                       // if not fair-share, must always try to defrag if needed
                                                               // its full allocation, and therefore cannot be needy
                    // UIMA-4275 We rely on quotas to keep 'needed' under control
                    if ( needed > 0 ) {
                        jobmap.put(j, needed);   
                        must_defrag = true;
                    }
                }


                logger.info(methodName, j.getId(), 
                            String.format(datafmt, 
                                          npn,
                                          j.getUser().getName(),
                                          j.getPureFairShare(),
                                          current,
                                          counted,
                                          needed,
                                          order),
                            (needed > 0) ? "POTENTIALLY NEEDY" : ""
                            );

//                             String.format("NP: %10s User %8s Pure fs[%3d] Nshares[%3d] AsgnNshares[%3d] needed[%3d] O[%d] %s",
//                                           npn,
//                                           j.getUser().getName(),
//                                           j.getPureFairShare(),
//                                           current,
//                                           counted,
//                                           needed,
//                                           order,
//                                           (needed > 0) ? "POTENTIALLY NEEDY" : ""
//                                           ));

                
            }
        }
        if ( ! must_defrag ) return;                          // the rest of this is expensive, let's bypass if we can

        for ( int npi = 0; npi < allPools.length; npi++ ) {                       // Turns out to be depth-first traversal !
            // Next pass, find all the open and potentially open spots in each nodepool.  We need to coalesce
            // evicted shares with each other and with open space on each machine in order to know whether the
            // space is potentially usable by jobs.
            NodePool np = allPools[npi];

            Map machs = np.getAllMachinesForPool();
            for ( Machine m : machs.values() ) {

                int free = m.countFreedUpShares();                           // free space plus evicted shares - eventual space
                if ( free != 0 ) {
                    logger.trace(methodName, null, "Freed shares", free, "on machine", m.getId());
                    for ( NodePool npj = np; npj != null; npj = npj.getParent() ) {        // must propogate up because of how these tables work
                        String id_j = npj.getId();
                        int[] vmach_j = vshares.get(id_j);
                        logger.trace(methodName, null, "Update v before: NP[", id_j, "] v:", fmtArray(vmach_j));
                        vmach_j[free]++;                                         // This is the largest potential share that can be made on this machine,
                        // after evictions starting 'here' and propogating up
                        logger.trace(methodName, null, "Update v after : NP[", id_j, "] v:", fmtArray(vmach_j));
                    }
                }                
            }
        }
        
        for ( int npi = 0; npi < allPools.length; npi++ ) {
            // Next pass, create the cumulative "n" style table from the "v" table of holes
            String id = allPools[npi].getId();

            int[] vmach = vshares.get(id);
            int[] nmach = nshares.get(id);
            reworknShares(vmach, nmach);                                 // Populate nmach from vmach for this np, with free or potentially free shares

            if ( logger.isInfo() ) {
                logger.info(methodName, null, "NP", id, "After check: virtual    free Space", fmtArray(vmach));
                logger.info(methodName, null, "NP", id, "After check: cumulative free Space", fmtArray(nmach));
            }
        }

        for ( int npi = 0; npi < allPools.length; npi++ ) {
            // Last step, traverse jobs by nodepool and determine their need.
            String id = allPools[npi].getId();

            Map jobmap = jobs.get(id);
            if ( jobmap.size() == 0 ) continue;                          // only looking at potentially needy jobs

            int[] nmach = nshares.get(id);

            for ( IRmJob j : jobmap.keySet() ) {

                int needed = jobmap.get(j);
                int order = j.getShareOrder();
                int available = nmach[order];
                int to_remove = 0;

                needyJobs.put(j, j);
                
                if ( available >= needed ) {
                    needed = 0;
                    to_remove = needed;
                } else {
                    to_remove = available;
                    needed -= to_remove;
                }
                
                if ( to_remove > 0 ) {
                    NodePool np = allPools[npi];
                    for ( NodePool npj = np; npj != null; npj = npj.getParent() ) {        // must propogate up because of how these tables work
                        String id_j = npj.getId();
                        int[] vmach_j = vshares.get(id_j);
                        int[] nmach_j = nshares.get(id_j);
                        removeSharesByOrder(vmach_j, nmach_j, to_remove, order);
                    }
                }
                
                if ( needed > 0 ) {
                    needed_by_job.put(j, needed);
                    logger.info(methodName, j.getId(), 
                                String.format("NP: %10s User: %10s Pure fs: %3d needed %3d O[%d] %s",
                                              id,
                                              j.getUser().getName(),
                                              j.getPureFairShare(),
                                              needed,
                                              order,
                                              "ACTUALLY NEEDY"
                                              ));
                }
            }
        }            
    }

    void insureFullEviction()
    {
    	String methodName = "insureFullEviction";

        int jobcount = 0;
        for ( ResourceClass rc : resourceClasses.values() ) {
            if ( rc.getPolicy() != Policy.RESERVE ) {
                jobcount += rc.countJobs();
            }
        }
        if ( jobcount == 0 ) return;

        HashMap needy = new HashMap();
        detectFragmentation(needy);
        if ( needy.size() == 0 ) {
            logger.info(methodName, null, "No needy jobs, defragmentation bypassed.");
            return;
        }

        logger.info(methodName, null, "NEEDY JOBS DETECTED");
        doFinalEvictions(needy);
    }

    /**
     * The third stage - work up all counts globally
     */
    protected void findWhatOf(ArrayList rcs)
    {
        switch ( rcs.get(0).getPolicy() ) {
            case FAIR_SHARE:
                whatOfFairShare(rcs);
                break;
            case FIXED_SHARE:
                whatOfFixedShare(rcs);
                break;
            case RESERVE:
                whatOfReserve(rcs);
                break;
        }
    }

    void setSchedulingUpdate(ArrayList rcs)
    {
        for ( ResourceClass rc : rcs ) {
            HashMap jobs = rc.getAllJobs();
            for ( IRmJob j : jobs.values() ) {
                if ( j.isRefused() ) {
                    continue;
                }
                if ( j.isExpanded() ) {
                    schedulingUpdate.addExpandedJob(j);
                }
                if ( j.isShrunken() ) {
                    schedulingUpdate.addShrunkenJob(j);
                }
                if ( j.isStable() && ( ! j.isReservation() ) ) {
                    schedulingUpdate.addStableJob(j);
                }
                if ( j.isDormant() ) {
                    schedulingUpdate.addDormantJob(j);
                }
                if ( j.isReservation() ) {
                    schedulingUpdate.addReservation(j);
                }
            }
        }
    }

    /**
     * Figure out what we have to give away.  Called on entry, and after any kind of
     * action that takes machines out of the schedulable pool ( e.g. reservation ).
     */
	private void resetNodepools()
    {
    	//String methodName = "countResources";
        int maxorder = 0;
        for ( ResourceClass rc: resourceClasses.values() ) {
            maxorder = Math.max(maxorder, rc.getMaxJobOrder());
        }
        globalNodepool.reset(maxorder);
        // logger.info(methodName, null, "Scheduling Tables at Start of Epoch:\n", globalNodepool.toString());
    }

    /**
     * IScheduler entry point for the fairShare calculation.
     *
     * This implements the easy three step process described at the top of the file.
     */
    public void schedule(SchedulingUpdate upd)
    {
        String methodName = "schedule";
        
        int jobcount = 0;
        for ( ResourceClass rc : resourceClasses.values() ) {

            HashMap allJobs = rc.getAllJobs();
            jobcount += allJobs.size();
            for ( IRmJob j : allJobs.values() ) {
                j.initJobCap();
            }
        }

        if ( jobcount == 0 ) {
            logger.info(methodName, null, "No jobs to schedule under nodepool", globalNodepool.getId());
            return;
        }

        logger.info(methodName, null, "Machine occupancy before schedule");
        globalNodepool.queryMachines();

        this.schedulingUpdate = upd;

        //
        // Reset all counts, and adjust for the non-preemptable work.  Nodepool counts end up reflecting all
        // *potential* shares in the system.
        //
        resetNodepools();
        accountForNonPreemptable();

        //
        // Walk through the classes starting with highest priority and just pass out the resources.
        // On each iteration we pass the list of all classes at the same priority, to get scheduled
        // together.  We assume that we do NOT mix policy among priorities, enforced by the caller.
        //        
        for ( int i = 0; i < classes.length; i++ ) {
            @SuppressWarnings("unchecked")
                ArrayList rcs = (ArrayList) classes[i];
            findHowMuch(rcs);
        }
        
        //
        // Counts are now placed into all the class, user, and job structures.  We need to account for
        // all *actual* resources, so we can find free things more gracefully.
        //
        resetNodepools();        
        accountForNonPreemptable();
        accountForFairShare();
        
        // ////////////////////////////////////////////////////////////////////////////////////////////////////
        // And now, find real, physical resources.
        //
        // First pre-expansion of needy jobs, prioritized
        // Second, normal expandion of the scheduled work.
        //
        if ( logger.isTrace() ) {
            globalNodepool.queryMachines();
        }

        for ( int i = 0; i < classes.length; i++ ) {
            @SuppressWarnings("unchecked")
                ArrayList rcs = (ArrayList) classes[i];
            expandNeedyJobs(globalNodepool, rcs);  
        }

        for ( int i = 0; i < classes.length; i++ ) {
            @SuppressWarnings("unchecked")
                ArrayList rcs = (ArrayList) classes[i];
            findWhatOf(rcs);
        }
        //
        //
        // ////////////////////////////////////////////////////////////////////////////////////////////////////

//         //
//         // Ethnic cleansing of shares. We look at each subpool and force eviction of non-member
//         // shares, strictly according to counts, to prevent squatters from locking out legitimate
//         // members of the pool.
//         //
//         cleanNodepools(globalNodepool);
        doEvictions(globalNodepool);
        // doForcedEvictions(globalNodepool);

        //
        // If we've fragmented we may not have been able to make enough space to get everybody's fair
        // share startd.  Here we check to make sure everybody who is capped got at least what
        // the deserved, and if not, we go in more agressively against jobs that have more than
        // their "absolute" fair share.
        //
        if ( do_defragmentation ) {
            insureFullEviction();
        }

        //
        // At last, walk through all the jobs and place them into the scheduling update appropripriately
        //
        for ( int i = 0; i < classes.length; i++ ) {
            @SuppressWarnings("unchecked")
                ArrayList rcs = (ArrayList) classes[i];
            setSchedulingUpdate(rcs);
        }

        globalNodepool.resetPreemptables();                            // Reservations: preemptables are machines that are going to get cleared
                                                                       // for pendingreservations. preemptable machines do not get reset 
                                                                       // normally so we can save state across the scheduling phases.
    }

//     //
//     // Order entities by age,  oldest first
//     //
//     static private class EntityByAgeSorter
//         implements Comparator
//     {
//         public int compare(IEntity e1, IEntity e2)
//         {
//             if ( e1 == e2 ) return 0;

//             // remember older has lower time
//             return (int) (e1.getTimestamp() - e2.getTimestamp());
//         }
//     }


    static private class JobByTimeSorter
        implements Comparator
    {
        public int compare(IRmJob j1, IRmJob j2)
        {
            if ( j1.equals(j2) ) return 0;

            if ( j1.getTimestamp() == j2.getTimestamp() ) {           // if tied on time (unlikely)
                return (j2.getShareOrder() - j1.getShareOrder());       // break tie on share order, decreasing
            }
            return (int) (j1.getTimestamp() - j2.getTimestamp());     // increasing time order
        }
    }


    //
    // Order classes by share weight, descending
    //
    static private class ClassByWeightSorter
        implements Comparator
    {
        public int compare(ResourceClass r1, ResourceClass r2)
        {
            if ( r1 == r2 ) return 0;

            return (r2.getShareWeight() - r1.getShareWeight());
        }
    }

    //
    // Treemap sorter, must never return 0
    //
    // Order classes by Q share wealth, descending
    //
    static private class UserByWealthSorter
        implements Comparator
    {
        public int compare(User u1, User u2)
        {
            if ( u1.equals(u2) ) return 0;

            int w1 = u1.getShareWealth();
            int w2 = u2.getShareWealth();

            if ( w1 == w2 ) return -1;       // we don't ever want these guys to compare equal
                                             // unless the instances are the same, checked above
            return w2 - w1;
        }
    }

    //
    // Treemap sorter, must never return 0
    //
    // Order jobs by largest assigned q shares first
    //
    static private class JobByShareSorter
        implements Comparator
    {
        public int compare(IRmJob j1, IRmJob j2)
        {
            if ( j1.equals(j2) ) return 0;       // same instances MUST return equal, 

            int s1 = j1.countNShares() * j1.getShareOrder();
            int s2 = j2.countNShares() * j2.getShareOrder();

            if ( s1 == s2 ) return -1;           // similar share count but never equal
            return ( s2 - s1 );
        }
    }

    //
    // Order jobs by "most able to be generous".
    //
    static private class FragmentationSorter
        implements Comparator
    {
        //
        // Order by
        // a) most over pure fair share
        // b) most currently allocated
        //
        public int compare(IRmJob j1, IRmJob j2)
        {

            if ( j1.equals(j2) ) return 0;

            // pure fair-share
            int p1 = j1.getPureFairShare();    // qshares
            int p2 = j2.getPureFairShare(); 

            // actual current allocation
            int c1 = j1.countNShares() * j1.getShareOrder();  // to qshares
            int c2 = j2.countNShares() * j2.getShareOrder();

            // overage ...
            int o1 = Math.max(0, c1 - p1);
            int o2 = Math.max(0, c2 - p2);

            if ( o1 < o2 ) return  1;         // largest overage first
            if ( o1 > o2 ) return -1;

            if ( c1 < c2 ) return  1;         // largest allocation first
            if ( c1 > c2 ) return -1;

            // and the tie breaker, these guys are never allowed to compare equal unless the
            // instances are the same, and we don't care about the order if we get this far
            return -1;
        }
    }

    //
    // Order shares by
    // - pending (can be reassigned or dropped)
    // - investment
    //
    // Pending always sorts before not-pending.
    //
    // This is a sorter for a tree map so we have to be sure not to return equality unless the objects
    // are the same objects.
    //
//    static private class FinalEvictionSorter
//        implements Comparator
//    {
//        
//        public int compare(Share s1, Share s2)
//        {
//            if ( s1 == s2 ) return 0;
//
//            // pending shares first, no point expanding them if we don't have to
//            if ( s1.isPending() && s2.isPending() ) return -1;            
//            if ( s1.isPending() ) return -1;
//            if  (s2.isPending() ) return 1;
//
//            // Shares on machines with more space first, deal with defrag, which is why we're here
//            int vso1 = s1.getMachine().countFreedUpShares();
//            int vso2 = s2.getMachine().countFreedUpShares();
//
//            if ( vso1 != vso2 ) {
//                return vso2 - vso1;      // (more space first)
//            }
//
//            // All else being equal, use investment
//            int inv =  (int) (s1.getInvestment() - s2.getInvestment());  
//            if ( inv == 0 ) return -1;                  // careful not to return 0
//            return inv;
//        }
//    }

    //
    // Sort machines for defrag.

    // 1 any machine with free space F, and a candidate job j of order O(j) such
    //    that F + O(j) == O(nj)
    //    Tiebraker on j is wealth W: W(j1) > W(j2)

    // 2 choice, any machine with a candidate job of the same order as the needy job
    //    Secondary sort, candidate job A is richer than candidate job B
    //
    // Tiebreak 1 ~ 2: W(j1) > W(j2) - choose host whose job is richest
    //
    // a) machines with richest users first
    // b) largest machine second
    //
    static private class EligibleMachineSorter
        implements Comparator
    {
        
        public int compare(Machine m1, Machine m2)
        {
            if ( m1.equals(m2) ) return 0;       

            int m1wealth = 0;
            int m2wealth = 0;
            Map sh1 = m1.getActiveShares();
            for ( Share s : sh1.values() ) {
                IRmJob j = s.getJob();
                User u = j.getUser();
                m1wealth = Math.max(m1wealth, u.getShareWealth());
            }
 
            Map sh2 = m2.getActiveShares();
            for ( Share s : sh2.values() ) {
                IRmJob j = s.getJob();
                User u = j.getUser();
                m2wealth = Math.max(m2wealth, u.getShareWealth());
            }

            if ( m1wealth != m2wealth ) return m2wealth - m1wealth;       // richest uesr first

            long m1mem = m1.getMemory();
            long m2mem = m2.getMemory();

            if ( m1mem == m2mem ) return -1;       // for tree map, must not return 0 unless same object
            return (int) (m2mem - m1mem);          // largest machine first.
        }
    }

    //
    // Order shares by most wealthy owner - defrag
    // Orders by wealthiest owner first.
    //
    static private class ShareByWealthSorter
        implements Comparator
    {
        
        public int compare(Share s1, Share s2)
        {
            if ( s1.equals(s2) ) return 0;       

            int s1wealth = 0;
            int s2wealth = 0;

            IRmJob j1 = s1.getJob();
            User u1 = j1.getUser();
            s1wealth = u1.getShareWealth();

            IRmJob j2 = s2.getJob();
            User u2 = j2.getUser();
            s2wealth = u2.getShareWealth();

            if ( s2wealth == s1wealth ) {                  
                return RmJob.compareInvestment(s1, s2);    // UIMA-4275
            }

            return s2wealth - s1wealth;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy