001 /* 002 * LSFResourceStrategy.java 003 * 004 * Created on April 1, 2003, 3:30 PM 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.Dispatchers.lsf; 024 025 import gov.bnl.star.offline.scheduler.*; 026 import gov.bnl.star.offline.scheduler.Dispatchers.AbstractResourceStrategy; 027 import gov.bnl.star.offline.scheduler.catalog.PhysicalFile; 028 029 import java.util.Hashtable; 030 import java.util.Iterator; 031 import java.util.Set; 032 import org.apache.log4j.Logger; 033 import java.util.regex.Matcher; 034 import java.util.regex.Pattern; 035 036 037 /** Encapsulate the resource usage parameter (-R) for an LSF farm. Essentially, 038 * its a placeholder for the prepareResourceUsageSwitch() which will calculate the 039 * appriate string. The administrator should be able to create different 040 * subclasses for each farm as needed. 041 * <p> 042 * This class provides a scheleton for a typical resource usage for files residing 043 * on different NFS servers. It was designed to fit STAR needs at two sites. In case 044 * it doesn't fit the needs, the class can be subclassed to define new behaviour. 045 * <p> 046 * The resource switch is prepared in this way: 047 * Takes the list of input files and iterates over them.<br> 048 * Checks if each file needs a resource (<CODE>resourceRequired()</CODE>).<br> 049 * To determine that, checks if the file is on NFS, and looks for the typical data 050 * vault prefix (<CODE>getDataVaultPrefix</CODE>). For example, "/star/dataxx/..."; 051 * it extract the vaultNumber and it substitute it in the resourceSyntax <br> 052 * Increases the count of the resource (<CODE>increaseResourceUsage</CODE>)<br> 053 * Prepares the switch (<CODE>prepareSwitch</CODE>) 054 * <p> 055 * The resource value for each resource will the be given by the following formula: <br> 056 * <code>min (base+nCount*fileFactor, max)</code> 057 * <p> 058 * The subclass need to implement the abstract methods, which defines things that 059 * always change between different farms. 060 * <p> 061 * TODO Further work should be done to be able to configure the ResourceStrategy 062 * depending on the farm on which the job will be dispatched. This should be 063 * included in the GRID dispatchers. 064 ** 065 * @author Gabriele Carcassi, Jerome Lauret, Levente Hajdu 066 * @version July 24, 2003 067 */ 068 public class LSFResourceStrategy implements AbstractResourceStrategy, java.io.Serializable{ 069 static private Logger log = Logger.getLogger(LSFResourceStrategy.class.getName()); 070 071 /** Base value for the resource value formula. */ 072 protected int base = 0; 073 074 /** Returns the base value for the resource value formula. */ 075 public int getBase() { 076 return base; 077 } 078 079 /** Changes the base value for the resource value formula. */ 080 public void setBase(int base) { 081 this.base = base; 082 } 083 084 /** FileFactor value for the resource value formula. */ 085 protected int fileFactor = 2; 086 087 /** Returns the fileFactor value for the resource value formula. */ 088 public int getFileFactor() { 089 return fileFactor; 090 } 091 092 /** Changes the fileFactor value for the resource value formula. */ 093 public void setFileFactor(int fileFactor) { 094 this.fileFactor = fileFactor; 095 } 096 097 /** Max parameter for the standard increaseResourceUsage implementation. */ 098 protected int max = 50; 099 100 /** Returns the max value for the resource value formula. */ 101 public int getMax() { 102 return max; 103 } 104 105 /** Changes the max value for the resource value formula. */ 106 public void setMax(int max) { 107 this.max = max; 108 } 109 110 private Hashtable resources; 111 112 private String dataVaultPrefix; 113 114 /** Returns the prefix with which a file on NFS identifies on which file server or 115 * data vault is residing. For example, "/star/data07/muDST/..." represents a file 116 * on data vault "07" (that is the data vault number or id), therefore the prefix is "/star/data". 117 * @return The prefix that identifies data vaults on NFS. 118 */ 119 public String getDataVaultPrefix() { 120 return dataVaultPrefix; 121 } 122 123 /** Changes the prefix of a NFS file. The resource identifies the data vault number 124 * by looking at a prefix in the file name. For example "/star/data07/minbias/..." 125 * would have "/star/data" as a prefix. 126 */ 127 public void setDataVaultPrefix(String dataVaultPrefix) { 128 this.dataVaultPrefix = dataVaultPrefix; 129 } 130 131 private String resourceNameSyntax; 132 133 /** Returns the syntax to be used to convert a data vault number to the LSF resource. 134 */ 135 public String getResourceNameSyntax() { 136 return resourceNameSyntax; 137 } 138 139 /** Changes the syntax to be used to convert a data vault number to the LSF resource. 140 * It can be any string, and it should contain the $vaultNumber variable, which will 141 * be substituted with the particular vault number. For example, to have something 142 * like "dv7io", you should have "dv$vaultNumberio". 143 */ 144 public void setResourceNameSyntax(String resourceNameSyntax) { 145 this.resourceNameSyntax = resourceNameSyntax; 146 } 147 148 private boolean trimVaultNumberLeadingZeros; 149 150 /** Returns true if the leading zeros of a vault number will be removed when 151 * creating the corresponding LSF resource. 152 */ 153 public boolean isTrimVaultNumberLeadingZeros() { 154 return trimVaultNumberLeadingZeros; 155 } 156 157 /** Set to true if the leading zeros of a vault number will be removed when 158 * creating the corresponding LSF resource. Basically, 07 will be changed to 7. 159 */ 160 public void setTrimVaultNumberLeadingZeros(boolean trimVaultNumberLeadingZeros) { 161 this.trimVaultNumberLeadingZeros = trimVaultNumberLeadingZeros; 162 } 163 164 /** Given the vault id, returns the LSF resource name to be used in the -R 165 * parameter. 166 * @param vaultNumber the vault id, as identified in <CODE>getDataVaultPrefix</CODE> 167 * @return the LSF resource name for the vault 168 */ 169 protected String prepareResourceName(String vaultNumber) { 170 if ((removeCharactersFromVaultNumber) && (!vaultNumber.matches("[a-zA-Z]+"))) { 171 Pattern pattern = Pattern.compile("[^a-zA-Z]+"); 172 Matcher matcher = pattern.matcher(vaultNumber); 173 StringBuffer buf = new StringBuffer(vaultNumber.length()); 174 StringBuffer vaultNumberBuf = new StringBuffer(vaultNumber); 175 while (matcher.find()) { 176 buf.append(vaultNumberBuf.subSequence(matcher.start(), matcher.end())); 177 } 178 vaultNumber = buf.toString(); 179 } 180 181 if (trimVaultNumberLeadingZeros) { 182 while (vaultNumber.startsWith("0") && (vaultNumber.length() != 1)) { 183 vaultNumber = vaultNumber.substring(1); 184 } 185 } 186 187 if (trimVaultNumberDecimal) { 188 int dec = vaultNumber.indexOf('.'); 189 if (dec != -1) vaultNumber = vaultNumber.substring(0, dec); 190 } 191 192 String name = resourceNameSyntax.replaceAll("\\$vaultNumber", vaultNumber); 193 194 return name; 195 } 196 197 private String switchSyntax; 198 199 /** Holds value of property trimVaultNumberDecimal. */ 200 private boolean trimVaultNumberDecimal; 201 202 /** Holds value of property removeCharactersFromVaultNumber. */ 203 private boolean removeCharactersFromVaultNumber; 204 205 /** Returns the syntax that will be used to prepare the final switch 206 */ 207 public String getSwitchSyntax() { 208 return switchSyntax; 209 } 210 211 /** Changes the syntax that will be used to prepare the final switch. 212 * You can use the $definedNames, $nameEqualValueCommaSeparated 213 * or $nameEqualValueColumnSeparated variables in your switch. 214 * For example, the following switch: 215 * "\"select[$definedNames] rusage[$nameEqualValueCommaSeparated]\"" 216 * will produce something like: "select[defined(01)] rusage[01=50]" 217 */ 218 public void setSwitchSyntax(String switchSyntax) { 219 this.switchSyntax = switchSyntax; 220 } 221 222 /** Prepares the -R parameters to give to LSF at the end of the resource 223 * distribution. It will use <CODE>definedNames()</CODE>, 224 * <CODE>nameEqualValueCommaSeparated()</CODE> or 225 * <CODE>nameEqualValueColumnSeparated()</CODE> to prepare it more easily. 226 * @return the -R parameter for the current job 227 */ 228 protected String prepareSwitch() { 229 if (determineResourceNotUsed()) { 230 return null; 231 } 232 233 String fullSwitch = switchSyntax.replaceAll("\\$definedNames", definedNames()); 234 fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueCommaSeparated", nameEqualValueCommaSeparated()); 235 fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueColumnSeparated", nameEqualValueColumnSeparated()); 236 237 return fullSwitch; 238 } 239 240 /** Increase the resource usage as part of the standard implementation. The standard 241 * formula starts counting from <CODE>base</CODE>, adds <CODE>fileFactor</CODE> for 242 * each usage until <CODE>max</CODE> is reached. In pseudo-code: 243 * <CODE>if (nRes == 0) nRes = base; 244 * nRes += nRes; 245 * if (nRes > max) nRes = max; 246 * </CODE> 247 * <p> 248 * One can modify the three protected parameters (base, fileFactor, max) to tune 249 * the usage. 250 * @param resource the resource for which to increase the counting. 251 */ 252 protected void increaseResourceUsage(String resource) { 253 increaseResourceUptoLimit(resource, base, fileFactor, max); 254 } 255 256 /** Addes a new resource, and initializes the value to <CODE>base</CODE>. 257 * @param resName the name of the resource 258 * @param base the initial value for the resource 259 */ 260 protected void addResource(String resName, int base) { 261 if (!resources.containsKey(resName)) { 262 resources.put(resName, new Integer(base)); 263 } else { 264 throw new RuntimeException("Can't add resource " + resName + 265 " because it's already present."); 266 } 267 } 268 269 /** Increases the value associated to the given resource by a given amount. If the 270 * resource doesn't exists, first it will be created and initialized. 271 * @param resName the name of the resource 272 * @param base the initial value if the resource has to be created 273 * @param amount the amount to increase the resource by 274 * @return the new value associated to the resource 275 */ 276 protected int increaseResource(String resName, int base, int amount) { 277 if (!resources.containsKey(resName)) { 278 addResource(resName, base); 279 } 280 281 int returnValue = prepareResourceValue(resName) + amount; 282 Integer newValue = new Integer(returnValue); 283 284 resources.remove(resName); 285 resources.put(resName, newValue); 286 287 return returnValue; 288 } 289 290 /** Increases the value associated to the given resource by a given amount. If the 291 * resource doesn't exists, first it will be created and initialized. If the 292 * increased amount is over the limit, the value will be set to the limit. 293 * @return the new value associated to the resource 294 * @param limit upper limit to which the resource value will be set 295 * @param resName the name of the resource 296 * @param base the initial value if the resource has to be created 297 * @param amount the amount to increase the resource by 298 */ 299 protected int increaseResourceUptoLimit(String resName, int base, 300 int amount, int limit) { 301 if (!resources.containsKey(resName)) { 302 addResource(resName, base); 303 } 304 305 int returnValue = prepareResourceValue(resName) + amount; 306 307 if (returnValue > limit) { 308 return limit; 309 } 310 311 Integer newValue = new Integer(returnValue); 312 313 resources.remove(resName); 314 resources.put(resName, newValue); 315 316 return returnValue; 317 } 318 319 /** Returns the value currently associated to the resource. 320 * @param resName resource name 321 * @return resource value 322 */ 323 protected int prepareResourceValue(String resName) { 324 return ((Integer) resources.get(resName)).intValue(); 325 } 326 327 /** Returns the -R parameter to be used to dispatch the given job. 328 * @param job the job to be dispatched 329 * @return the -R parameter to use 330 */ 331 public String prepareResourceUsageSwitch(Job job) { 332 initResources(); 333 calculateLSFResources(job); 334 335 return prepareSwitch(); 336 } 337 338 /** Initializes the resource table. */ 339 protected void initResources() { 340 resources = new Hashtable(); 341 } 342 343 /** Returns a string already formatted containing names and values comma separated 344 * (i.e. "res1=34,res2=12"). 345 * @return resource name/value pairs comma separated (i.e. "res1=34,res2=12") 346 */ 347 protected String nameEqualValueCommaSeparated() { 348 String rusage = ""; 349 Set resNames = resources.keySet(); 350 Iterator iter = resNames.iterator(); 351 boolean first = true; 352 353 while (iter.hasNext()) { 354 if (!first) { 355 rusage += ","; 356 } 357 358 Object key = iter.next(); 359 rusage += (key + "=" + resources.get(key)); 360 first = false; 361 } 362 363 return rusage; 364 } 365 366 /** Returns a string already formatted containing names and values column 367 * separated (i.e. "res1=34:res2=12"). 368 * @return resource name/value pairs column separated (i.e. "res1=34:res2=12") 369 */ 370 371 protected String nameEqualValueColumnSeparated() { 372 String rusage = ""; 373 Set resNames = resources.keySet(); 374 Iterator iter = resNames.iterator(); 375 boolean first = true; 376 377 while (iter.hasNext()) { 378 if (!first) { 379 rusage += ":"; 380 } 381 382 Object key = iter.next(); 383 rusage += (key + "=" + resources.get(key)); 384 first = false; 385 } 386 387 return rusage; 388 } 389 390 391 392 /** A list of names, comma separated, of all the resources used. 393 * @return (i.e. "res1,res2") 394 */ 395 protected String definedNames() { 396 String defined = ""; 397 Set resNames = resources.keySet(); 398 Iterator iter = resNames.iterator(); 399 boolean first = true; 400 401 while (iter.hasNext()) { 402 if (!first) { 403 defined += " && "; 404 } 405 406 Object key = iter.next(); 407 defined += ("defined(" + key + ")"); 408 first = false; 409 } 410 411 return defined; 412 } 413 414 /** Filles the resource table with all the resources. It basically iterates over the 415 * input files and calls <CODE>increaseResourceUsage</CODE> for each file that map 416 * to a resource. 417 * @param job the job 418 */ 419 protected void calculateLSFResources(Job job) { 420 log.debug("Calculating LSF resources"); 421 for (int i = 0; i < job.getInput().size(); i++) { 422 PhysicalFile file = (PhysicalFile) job.getInput().get(i); 423 String res = resourceRequired(file); 424 425 if (res != null) { 426 increaseResourceUsage(res); 427 } 428 } 429 } 430 431 /** Determines which resource the job might need to access the file. 432 * @param file input file the job will be using 433 * @return the resource needed 434 */ 435 protected String resourceRequired(PhysicalFile file) { 436 if ("NFS".equals(file.getStorage())) { 437 log.debug("Found NFS file: " + file); 438 439 String path = file.getPath(); 440 441 if (path.startsWith(getDataVaultPrefix())) { 442 int bar = path.indexOf('/', getDataVaultPrefix().length()); 443 String serverNumber = path.substring(getDataVaultPrefix().length(), bar); 444 445 return prepareResourceName(serverNumber); 446 } 447 } 448 449 return null; 450 } 451 452 /** Determines whether any resources at all have been assigned. 453 * @return true if any resource is used. 454 */ 455 protected boolean determineResourceNotUsed() { 456 return resources.isEmpty(); 457 } 458 459 /** Getter for property trimVaultNumberDecimal. 460 * @return Value of property trimVaultNumberDecimal. 461 * 462 */ 463 public boolean isTrimVaultNumberDecimal() { 464 return this.trimVaultNumberDecimal; 465 } 466 467 /** Setter for property trimVaultNumberDecimal. 468 * @param trimVaultNumberDecimal New value of property trimVaultNumberDecimal. 469 * 470 */ 471 public void setTrimVaultNumberDecimal(boolean trimVaultNumberDecimal) { 472 this.trimVaultNumberDecimal = trimVaultNumberDecimal; 473 } 474 475 /** If true removes all the characters from the vault number. 476 * @return Value of property removeCharactersFromVaultNumber. 477 * 478 */ 479 public boolean isRemoveCharactersFromVaultNumber() { 480 return this.removeCharactersFromVaultNumber; 481 } 482 483 /** Set to true removes all the characters from the vault number 484 * @param removeCharactersFromVaultNumber New value of property removeCharactersFromVaultNumber. 485 * 486 */ 487 public void setRemoveCharactersFromVaultNumber(boolean removeCharactersFromVaultNumber) { 488 this.removeCharactersFromVaultNumber = removeCharactersFromVaultNumber; 489 } 490 491 492 private String delimiterCharacter = ","; 493 494 public String getDelimiterCharacter() { return delimiterCharacter;} 495 public void setDelimiterCharacter(String delimiterCharacter) {this.delimiterCharacter = delimiterCharacter;} 496 497 }