001    /*
002     * $RCSfile: FileAssignment.java,v $
003     *
004     * Created on April 2, 2006, 11:48 AM
005     *
006     * This file is part of the STAR Scheduler.
007     * Copyright (c) 2002-2006 STAR Collaboration - Brookhaven National Laboratory
008     *
009     * STAR Scheduler is free software; you can redistribute it and/or modify
010     * it under the terms of the GNU General Public License as published by
011     * the Free Software Foundation; either version 2 of the License, or
012     * (at your option) any later version.
013     *
014     * STAR Scheduler is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017     * GNU General Public License for more details.
018     *
019     * You should have received a copy of the GNU General Public License
020     * along with STAR Scheduler; if not, write to the Free Software
021     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
022     */
023    package gov.bnl.star.offline.scheduler.policy;
024    
025    import gov.bnl.star.offline.scheduler.catalog.PhysicalFile;
026    
027    import java.util.*;
028    
029    public class FileAssignment {
030        private ListMap filemap = new ListMap();
031        private int minFiles;
032        private int maxFiles;
033        private boolean noMin;
034        private boolean noMax;
035    
036        /** Create a new empty distribution, specifying the limits on how many files
037         * to assign to a single process
038         * @param minFiles minimum number of files to assign to a process
039         * @param maxFiles maximum number of files to assign to a process
040         */
041        public FileAssignment(int minFiles, int maxFiles) {
042            this.minFiles = minFiles;
043            this.maxFiles = maxFiles;
044    
045            if (minFiles == -1) {
046                noMin = true;
047            }
048    
049            if (maxFiles == -1) {
050                noMax = true;
051            }
052        }
053    
054        /** True if the correct assignment can be divided into processes and
055         * dispatched correctly.
056         * @return false if the filse on any location can't be divided correctly
057         */
058        public boolean isValid() {
059            if (noMin) {
060                return true;
061            }
062    
063            Iterator iter = getLocations().iterator();
064    
065            while (iter.hasNext()) {
066                Location location = (Location) iter.next();
067    
068                if (isValid(location) == false) {
069                    return false;
070                }
071            }
072    
073            return true;
074        }
075    
076        /** True if the files assigned to the location can be divided in to a valid
077         * process.
078         * @param location location to check
079         * @return false if the filse on the location can't be divided correctly
080         */
081        public boolean isValid(Location location) {
082            return needsMoreFiles(location) == 0;
083        }
084    
085        /** Returns the list of locations on which the files can't be divided in
086         * valid processes.
087         * @return a list of Location objects
088         */
089        public List getInvalidLocations() {
090            List invalids = new ArrayList();
091            Iterator iter = getLocations().iterator();
092    
093            while (iter.hasNext()) {
094                Location location = (Location) iter.next();
095    
096                if (isValid(location) == false) {
097                    invalids.add(location);
098                }
099            }
100    
101            return invalids;
102        }
103    
104        /** Returns a printable summary of the current assignment, stating the
105         * total number of locations, and the number of invalid locations.
106         * @return a short summary of the locations
107         */
108        public String getSummary() {
109            StringBuffer summary = new StringBuffer();
110            summary.append(getLocations().size()).append(" location, of which ");
111            summary.append(getInvalidLocations().size()).append(" invalid: ")
112                   .append(getInvalidLocations());
113    
114            return summary.toString();
115        }
116    
117        /** A full description of the current assignment, in a human readable
118         * form, contaning the number of files and processes for all the locations.
119         * @return a full report of the file distribution
120         */
121        public String getReport() {
122            StringBuffer report = new StringBuffer();
123    
124            List invalids = new ArrayList();
125            Iterator iter = getLocations().iterator();
126    
127            while (iter.hasNext()) {
128                Location location = (Location) iter.next();
129    
130                if (isValid(location) == false) {
131                    invalids.add(location);
132                }
133    
134                report.append(location);
135                report.append(": nFiles ").append(getFiles(location).size());
136                report.append(" - nProc ").append(minProcs(location));
137    
138                if (isValid(location)) {
139                    report.append(" - valid");
140                } else {
141                    report.append(" - INVALID");
142                }
143    
144                report.append("\n");
145            }
146    
147            return report.toString();
148        }
149    
150        /** Returns the minimum number of process to dispatch on the location to
151         * cover all the files. It is the number of files divided by maxFiles, rounded
152         * up to the next integer. This number might not satisfy the minFiles
153         * requirements.
154         * @param location the location in the assignment
155         * @return the number of processes on which the list will be split
156         */
157        public int minProcs(Location location) {
158            
159            
160            if (noMax) return 1;
161            /*
162            if (noMax || (maxFiles == 0)) return 1;
163            */
164            
165            int nFiles = getFiles(location).size();
166            int nProc = nFiles / maxFiles;
167            int mod = nFiles % maxFiles;
168    
169            if (mod > 0) {
170                nProc++;
171            }
172            
173            return nProc;
174        }
175    
176        /** Returns how many are to be assigned to the location to make the assignment
177         * valid.
178         * @param location the location to check
179         * @return the number of files needed to meet the min requirement; 0 if the
180         * requirements are met
181         */
182        public int needsMoreFiles(Location location) {
183            if (noMin) {
184                return 0;
185            }
186    
187            int minTotalFiles = minProcs(location) * minFiles;
188            int nFiles = getFiles(location).size();
189            int needed = minTotalFiles - nFiles;
190    
191            if (needed > 0) {
192                return needed;
193            }
194    
195            return 0;
196        }
197    
198        /** Returns how many files are to be deassigned from the location to
199         * make the assignment valid.
200         * @param location the location to check
201         * @return the number of files to remove to meet the max files requirements;
202         * 0 if the requirements are met
203         */
204        public int needsLessFiles(Location location) {
205            
206            //if (noMax) { //this is could be a bug. More testing is needed
207            //    return 0;
208            //}
209            
210            if (isValid()) {
211                return 0;
212            }
213    
214            int nFiles = getFiles(location).size();
215            int maxTotalFiles = (minProcs(location) - 1) * maxFiles;
216            int needed = nFiles - maxTotalFiles;
217            
218            System.out.println("maxTotalFiles = " + maxTotalFiles + "  nFiles = " + nFiles);
219    
220            return needed;
221        }
222    
223        /** Returns how many files can be assigned to the location without changing
224         * the number of processes.
225         * @param location location to check
226         * @return max number of files that can be added
227         */
228        public int canAdd(Location location) {
229            if (noMax) {
230                // No maximum: can add everything
231                return Integer.MAX_VALUE;
232            }
233    
234            int maxTotalFiles = (minProcs(location)) * maxFiles;
235    
236            return maxTotalFiles - getFiles(location).size();
237        }
238    
239        /** Returns how many files can be deassigned to the location without changing
240         * the number of processes.
241         * @param location location to check
242         * @return max number of files that be removed
243         */
244        public int canRemove(Location location) {
245            // TODO This is not quite right
246            if (noMin) {
247                return getFiles(location).size();
248            }
249    
250            int minTotalFiles = (minProcs(location)) * minFiles;
251            int remove = minTotalFiles - getFiles(location).size();
252    
253            if (remove > 0) {
254                return remove;
255            }
256    
257            return 0;
258        }
259    
260        /** Returns the locations to which something was assigned.
261         * @return a Set of Location objects
262         */
263        public Set getLocations() {
264            return filemap.getKeys();
265        }
266    
267        /** Returns a list of URL of the files assigned to that location (a location
268         * can be a set of nodes).
269         * @param location the location to check
270         * @return a List of URL objects
271         */
272        public List getFiles(Location location) {
273            return filemap.getList(location);
274        }
275    
276        /** Returns the list of files assigned to a particular process of one
277         * location.
278         * @param location the location to check
279         * @param processIndex the process number
280         * @return a List of URL objects
281         */
282        public List getFiles(Location location, int processIndex) {
283            int nProc = minProcs(location);
284            List total = getFiles(location);
285    
286            if (nProc <= processIndex) {
287                throw new ArrayIndexOutOfBoundsException(
288                    "Process index requested is too high");
289            }
290    
291            // Calculating how many files. The modulus tells us how many processes
292            // are going to have one process more than the others
293            int filesPerProcess = total.size() / nProc;
294            int mod = total.size() % nProc;
295            int nFiles = filesPerProcess;
296    
297            if (processIndex < mod) {
298                nFiles++;
299            }
300    
301            // Calculating the offset on the list. Again, the modulus is used to
302            // know how many preeceding processes had one file more
303            int index = (filesPerProcess * processIndex) +
304                Math.min(processIndex, mod);
305    
306            return total.subList(index, index + nFiles);
307        }
308    
309        /** Adds the file to the assignment, the host is used as location
310         * @param file the URL of the file
311         */
312        public void add(PhysicalFile file) {
313            filemap.add(Location.getLocation(file), file);
314        }
315    
316        /** Assigns the file to the location.
317         * @param location the location to which to assign the file
318         * @param file the URL of the file
319         */
320        public void add(Location location, PhysicalFile file) {
321            filemap.add(location, file);
322        }
323    
324        /** Adds a collection of files to the assignment. It will use the
325         * add(URL) method iteratively.
326         * @param coll a Collection of URL objects
327         */
328        /*public void add(Collection coll) {
329            Iterator iter = coll.iterator();
330    
331            while (iter.hasNext()) {
332                PhysicalFile file = (PhysicalFile) iter.next();
333                add(file);
334            }
335        }*/
336    
337        /** Adds an entire FileAssignment to the present.
338         * @param assignment the assignment to add to this
339         */
340        public void addAll(FileAssignment assignment) {
341            filemap.addAll(assignment.filemap);
342        }
343    
344        /** Adds a list of files to the assignment.
345         * @param col a Collection of PhysicalFile objects
346         */
347        public void addAllPhysical(Collection col) {
348            Iterator iter = col.iterator();
349    
350            while (iter.hasNext()) {
351                add(((PhysicalFile) iter.next()));
352            }
353        }
354    
355        /** Adds a list of files to the specified location.
356         * @param location the location to which to add to
357         * @param col a Collection of PhysicalFile objects
358         */
359        public void addAllPhysical(Location location, Collection col) {
360            Iterator iter = col.iterator();
361    
362            while (iter.hasNext()) {
363                add(location, ((PhysicalFile) iter.next()));
364            }
365        }
366    
367        /** Removes the file from the location.
368         * @param location the location from which to remove the file
369         * @param file the file to remove
370         */
371        public void remove(Location location, PhysicalFile file) {
372            filemap.remove(location, file);
373        }
374    
375        /** Removes a set of file a location.
376         */
377        public void removeAllPhysical(Location location, Collection col) {
378            Iterator iter = col.iterator();
379    
380            while (iter.hasNext()) {
381                remove(location, ((PhysicalFile) iter.next()));
382            }
383        }
384        
385        public void orderFiles(Comparator comp) {
386            Set location = getLocations();
387            Iterator iter = location.iterator();
388            
389            while (iter.hasNext()) {
390                List files = getFiles((Location)iter.next());
391                Collections.sort(files, comp);
392            }
393        }
394    
395    }