/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.nbio.structure.align.client; import org.biojava.nbio.structure.Atom; import org.biojava.nbio.structure.StructureException; import org.biojava.nbio.structure.align.StructureAlignment; import org.biojava.nbio.structure.align.StructureAlignmentFactory; import org.biojava.nbio.structure.align.ce.CeCPMain; import org.biojava.nbio.structure.align.ce.CeMain; import org.biojava.nbio.structure.align.events.AlignmentProgressListener; import org.biojava.nbio.structure.align.fatcat.FatCatFlexible; import org.biojava.nbio.structure.align.fatcat.FatCatRigid; import org.biojava.nbio.structure.align.model.AFPChain; import org.biojava.nbio.structure.align.util.AFPChainScorer; import org.biojava.nbio.structure.align.util.AlignmentTools; import org.biojava.nbio.structure.align.util.AtomCache; import org.biojava.nbio.structure.align.util.ResourceManager; import org.biojava.nbio.structure.align.xml.AFPChainXMLConverter; import org.biojava.nbio.structure.align.xml.PdbPairsMessage; import org.biojava.nbio.structure.domain.RemotePDPProvider; import org.biojava.nbio.structure.io.LocalPDBDirectory.FetchBehavior; import org.biojava.nbio.structure.scop.RemoteScopInstallation; import org.biojava.nbio.structure.scop.ScopFactory; import org.biojava.nbio.core.util.FlatFileCache; import org.biojava.nbio.core.util.PrettyXMLWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; /** Contains the single thread for a job that can run multiple alignments. * * @author Andreas Prlic * */ public class FarmJobRunnable implements Runnable { private static final Logger logger = LoggerFactory.getLogger(FarmJobRunnable.class); //private static final int DEFAULT_PAIR_FETCH_DELAY = 30000; //private static final String CONNECTION_PAIR_DELAY = "connection.pair.delay"; private static final String JFATCAT_NAME = "jfatcat.name"; private static final String JFATCAT_VERSION = "jfatcat.version"; private static ResourceManager resourceManager = ResourceManager.getResourceManager("jfatcat"); //private static DateFormat dateFormat = new SimpleDateFormat("MMMM dd, yyyy h:mm a",Locale.US); FarmJobParameters params; String prevName1; Atom[] ca1 ; long startTime; long maxTime; int maxNrAlignments; int alignmentsCalculated; boolean waitForAlignments; private static final String randomUsername = getRandomUsername(); boolean terminated ; List<AlignmentProgressListener> progressListeners; CountProgressListener counter ; String userName = null; protected AtomCache cache; boolean verbose = false; // TODO dmyersturnbull: we should probably remove this in favor of SLF4J String version = null; private static final String alignURL = "/align/"; public FarmJobRunnable(FarmJobParameters params){ terminated = false; this.params = params; verbose = false; // multiple farm jobs share the same SoftHashMap for caching coordinates cache = new AtomCache( params.getPdbFilePath(), params.getCacheFilePath()); if ( params.getServer()!= null && (!params.getServer().equals("") ) ) { RemotePDPProvider pdpprovider = new RemotePDPProvider(); String serverURL = params.getServer(); if ( ! serverURL.endsWith("/")) serverURL += "/"; if ( serverURL.endsWith(alignURL)) { serverURL = serverURL.substring(0,serverURL.length()-alignURL.length()); } pdpprovider.setServer(serverURL+"/domains/"); cache.setPdpprovider(pdpprovider); RemoteScopInstallation scop = new RemoteScopInstallation(); scop.setServer(serverURL+"/domains/"); ScopFactory.setScopDatabase(scop); } cache.setFetchBehavior(FetchBehavior.FETCH_FILES); maxNrAlignments = params.getNrAlignments(); progressListeners = null; if (params.getUsername() == null) { userName = randomUsername; } else { userName = params.getUsername(); } counter = new CountProgressListener(); addAlignmentProgressListener(counter); waitForAlignments = true; if ( params.isVerbose()){ verbose = true; } } public void addAlignmentProgressListener(AlignmentProgressListener listener){ if (progressListeners == null) progressListeners = new ArrayList<AlignmentProgressListener>(); progressListeners.add(listener); } public void clearListeners(){ if ( progressListeners == null) return; progressListeners.clear(); progressListeners = null; } protected static String getRandomUsername(){ String name = ""; try { InetAddress i = InetAddress.getLocalHost(); name += i.getHostAddress(); name += "_"; } catch (UnknownHostException e){ throw new RuntimeException(e); } name += UUID.randomUUID(); return name; } @Override public void run() { // Retrieve resource String appVersion = resourceManager.getString(JFATCAT_VERSION); String appName = resourceManager.getString(JFATCAT_NAME); logger.info("{} version: {}", appName, appVersion); startTime = System.currentTimeMillis(); // -t ime is in seconds. long maxSec = params.getTime(); if ( maxSec < 5 ) maxTime = Long.MAX_VALUE; else maxTime = startTime + params.getTime() * 1000; terminated = false; alignmentsCalculated = 0; maxNrAlignments = params.getNrAlignments(); if ( maxNrAlignments < 0 ){ maxNrAlignments = Integer.MAX_VALUE; } logger.info("running job for max {} alignments", maxNrAlignments); while (! terminated){ // talk to server // get list of alignments to run // if maxNrAlignments > 100 we split up the calculations in chunks of 100. // otherwise we request all of them at once. // we request PdbPairsMessage msg = getAlignmentPairsFromServer(); if ( msg == null) { logger.error("Got null instead of alignment pairs from server."); randomSleep(); continue; } SortedSet<PdbPair> alignmentPairs = msg.getPairs(); logger.debug("{}: Server responded with {} pairs.", userName, alignmentPairs.size()); List<String> results = new ArrayList<String>(); String algorithmName = msg.getMethod(); if ( version == null) { setVersion(algorithmName); } for(PdbPair pair : alignmentPairs){ if ( terminated) break; long now = System.currentTimeMillis(); if ( now >= maxTime) { terminated = true; break; } if ( alignmentsCalculated >= maxNrAlignments) { terminated = true; break; } String name1 = pair.getName1(); String name2 = pair.getName2(); if ( progressListeners != null) notifyStartAlignment(name1,name2); try { String resultXML = alignPair(name1, name2,algorithmName); if ( progressListeners != null) notifyEndAlignment(); results.add(resultXML); } catch (Exception e){ logger.error("Problem aligning {} with {}", name1, name2, e); StringWriter sw = new StringWriter(); PrettyXMLWriter xml = new PrettyXMLWriter(new PrintWriter(sw)); try { xml.openTag("AFPChain"); xml.attribute("name1", name1); xml.attribute("name2", name2); xml.attribute("error", e.getMessage()); xml.closeTag("AFPChain"); } catch(IOException ex){ logger.error("Error occured converting alignment for {} and {} to XML", name1, name2, ex); } if ( progressListeners != null) notifyEndAlignment(); results.add(sw.toString()); } alignmentsCalculated++; } // send results back to server sendResultsToServer(results); long end = System.currentTimeMillis(); if ( end >= maxTime) { logger.info("OK end of job: reached maxTime {}", maxTime); terminated = true; } if ( alignmentsCalculated >= maxNrAlignments) { logger.info("OK end of job: reached maxNrAlignments", maxNrAlignments); terminated = true; } long tdiff = (end - startTime); if ( tdiff != 0) { logger.info(userName + String.format(": job has run for : %.2f", (tdiff) / 1000.0 / 60) + " min."); logger.info("{}: total nr of alignments calculated: {}", userName, alignmentsCalculated); if ( alignmentsCalculated > 0) logger.info(userName + String.format(": average time / alignment: %.2f", (tdiff / alignmentsCalculated / 1000.0)) + " sec."); } } logger.info(userName + ": jFATCAT job result: " + counter); // clean up in the end... clearListeners(); cache.notifyShutdown(); } private void setVersion(String algorithmName) { StructureAlignment algorithm; try { algorithm = StructureAlignmentFactory.getAlgorithm(algorithmName); version = algorithm.getVersion(); } catch (StructureException e) { throw new RuntimeException("Couldn't set version for algorithm \"" + algorithmName + "\"", e); // version = resourceManager.getString(JFATCAT_VERSION); // dmyersturnbull: was this } } private void notifyStartAlignment(String name1, String name2) { if ( progressListeners != null){ for (AlignmentProgressListener li : progressListeners){ li.alignmentStarted(name1, name2); } } } private void notifyEndAlignment(){ if ( progressListeners != null){ for (AlignmentProgressListener li : progressListeners){ li.alignmentEnded(); } } } private void notifyRequestingAlignments(int nrAlignments){ if ( progressListeners != null){ for (AlignmentProgressListener li : progressListeners){ li.requestingAlignmentsFromServer(nrAlignments); } } } private void notifySubmittingAlignments(int nrAlignments, String message){ if ( progressListeners != null){ for (AlignmentProgressListener li : progressListeners){ li.sentResultsToServer(nrAlignments,message); } } } public String alignPair(String name1, String name2) throws StructureException, IOException { return alignPair(name1, name2, FatCatRigid.algorithmName); } public String alignPair(String name1, String name2, String algorithmName) throws StructureException, IOException { // make sure each thread has an independent instance of the algorithm object ... StructureAlignment algorithm = getAlgorithm(algorithmName); // we are running with default parameters if ( verbose ) { logger.debug("aligning {} against {}", name1, name2); } long startTime = System.currentTimeMillis(); if ( prevName1 == null) initMaster(name1); if ( ! prevName1.equals(name1) ) { // we need to reload the master initMaster(name1); } // get a copy of the atoms, but clone them, since they will be rotated... Atom[] ca2 = cache.getAtoms(name2); AFPChain afpChain = algorithm.align(ca1, ca2); afpChain.setName1(name1); afpChain.setName2(name2); try { // add tmScore double tmScore = AFPChainScorer.getTMScore(afpChain, ca1, ca2); afpChain.setTMScore(tmScore); } catch (RuntimeException e){ logger.error("ca1 size: {} ca2 length: {} {} {}", ca1.length, ca2.length, afpChain.getName1(), afpChain.getName2(), e); } long endTime = System.currentTimeMillis(); long calcTime = (endTime-startTime); if ( verbose ){ boolean isCP = !AlignmentTools.isSequentialAlignment(afpChain, false); String msg = "finished alignment: " + name1 + " vs. " + name2 + " in " + (calcTime) / 1000.0 + " sec."; msg += " algo: " + algorithmName + " v:" + version + " " + afpChain; if ( isCP ) msg += "HAS A CIRCULAR PERMUTATION!!!"; logger.debug(msg); } if (verbose){ printMemory(); } afpChain.setCalculationTime(calcTime); return AFPChainXMLConverter.toXML(afpChain, ca1, ca2); } private void printMemory() { int size = 1048576; long heapSize = Runtime.getRuntime().totalMemory() / size; // Get maximum size of heap in bytes. The heap cannot grow beyond this size. // Any attempt will result in an OutOfMemoryException. long heapMaxSize = Runtime.getRuntime().maxMemory() / size; // Get amount of free memory within the heap in bytes. This size will increase // after garbage collection and decrease as new objects are created. long heapFreeSize = Runtime.getRuntime().freeMemory() / size; StringBuilder msg = new StringBuilder(); msg.append(" total: ").append(heapSize).append(" M"); msg.append(" max: "). append(heapMaxSize).append(" M"); msg.append(" free: ").append(heapFreeSize).append(" M"); logger.debug(msg.toString()); } private StructureAlignment getAlgorithm(String algorithmName) throws StructureException { StructureAlignment algorithm = null; if ( algorithmName == null){ algorithm = new FatCatRigid(); } else if ( algorithmName.equalsIgnoreCase(FatCatRigid.algorithmName)){ algorithm = new FatCatRigid(); } else if ( algorithmName.equalsIgnoreCase(CeMain.algorithmName)){ algorithm = new CeMain(); } else if ( algorithmName.equalsIgnoreCase(CeCPMain.algorithmName)){ algorithm = new CeCPMain(); } else if ( algorithmName.equalsIgnoreCase(FatCatFlexible.algorithmName)){ algorithm = new FatCatFlexible(); } else { algorithm = StructureAlignmentFactory.getAlgorithm(algorithmName); } if ( algorithm == null) { algorithm = new FatCatRigid(); } return algorithm; } private void initMaster(String name1) throws IOException, StructureException{ ca1 = cache.getAtoms(name1); prevName1 = name1; } /** talk to centralized server and fetch all alignments to run. * * @return a list of pairs to align. */ protected PdbPairsMessage getAlignmentPairsFromServer() { String url = params.getServer(); int nrPairs = params.getStepSize(); if ( maxNrAlignments < nrPairs ) nrPairs = maxNrAlignments; SortedSet<PdbPair> allPairs = new TreeSet<PdbPair>(); PdbPairsMessage msg = null; try { if ( progressListeners != null) notifyRequestingAlignments(nrPairs); if ( ! waitForAlignments) { msg = JFatCatClient.getPdbPairs(url, nrPairs, userName); allPairs = msg.getPairs(); } else { while (allPairs.isEmpty()) { msg = JFatCatClient.getPdbPairs(url, nrPairs, userName); allPairs = msg.getPairs(); if (allPairs.isEmpty()) { randomSleep(); } } } } catch ( JobKillException k ){ logger.debug("Terminating job", k); terminate(); } catch (Exception e) { logger.error("Error while requesting alignment pairs", e); // an error has occured sleep 30 sec. randomSleep(); } return msg; } private void randomSleep() { try { int delay = JFatCatClient.getRandomSleepTime(); logger.debug("sleeping {} sec.", delay/1000); Thread.sleep(delay); } catch (InterruptedException ex){ logger.trace("InterruptedException occurred while sleeping", ex); } } protected void sendResultsToServer(List<String> results) { String serverLocation = params.getServer(); if ( results.size() < 1) return; String fullXml = "<alignments>"; for (String xml: results){ fullXml +=xml; } fullXml += "</alignments>"; String msg = ""; try { msg = JFatCatClient.sendMultiAFPChainToServer(serverLocation,fullXml, userName, version ); } catch (JobKillException e){ logger.info("{} Got Job Kill message from server, terminating...", userName, e); terminate(); } if ( progressListeners != null) notifySubmittingAlignments(results.size(), msg); logger.info("{}: Sent {} results to server. job status: {}", userName, results.size(), counter); logger.info("{}: fileCache size: {}", userName, FlatFileCache.getInstance().size()); } /** Send signal to terminate calculations * */ public synchronized void terminate(){ terminated = true; } public boolean isWaitForAlignments() { return waitForAlignments; } public void setWaitForAlignments(boolean waitForAlignments) { this.waitForAlignments = waitForAlignments; } }