package jalview.gui;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import jalview.analysis.AlignmentGenerator;
import jalview.bin.Cache;
import jalview.bin.Jalview;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceGroup;
import jalview.io.DataSourceType;
import jalview.io.FileLoader;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class FreeUpMemoryTest
{
private static final int ONE_MB = 1000 * 1000;
/**
* Configure (read-only) Jalview property settings for test
*/
@BeforeClass(alwaysRun = true)
public void setUp()
{
Jalview.main(new String[] { "-nonews", "-props",
"test/jalview/testProps.jvprops" });
Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
Boolean.TRUE.toString());
Cache.applicationProperties.setProperty("SHOW_QUALITY",
Boolean.TRUE.toString());
Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
Boolean.TRUE.toString());
Cache.applicationProperties.setProperty("SHOW_OCCUPANCY",
Boolean.TRUE.toString());
Cache.applicationProperties.setProperty("SHOW_IDENTITY",
Boolean.TRUE.toString());
}
/**
* A simple test that memory is released when all windows are closed.
*
* - generates a reasonably large alignment and loads it
* - performs various operations on the alignment
* - closes all windows
* - requests garbage collection
* - asserts that the remaining memory footprint (heap usage) is 'not large'
*
*
* If the test fails, this suggests that a reference to some large object
* (perhaps the alignment data, or some annotation / Tree / PCA data) has
* failed to be garbage collected. If this is the case, the heap will need to
* be inspected manually (suggest using jvisualvm) in order to track down
* where large objects are still referenced. The code (for example
* AlignmentViewport.dispose()) should then be updated to ensure references to
* large objects are set to null when they are no longer required.
*
* @throws IOException
*/
@Test(groups = "Memory")
public void testFreeMemoryOnClose() throws IOException
{
File f = generateAlignment();
f.deleteOnExit();
doStuffInJalview(f);
Desktop.instance.closeAll_actionPerformed(null);
checkUsedMemory(35L);
}
/**
* Requests garbage collection and then checks whether remaining memory in use
* is less than the expected value (in Megabytes)
*
* @param expectedMax
*/
protected void checkUsedMemory(long expectedMax)
{
/*
* request garbage collection and wait briefly for it to run;
* NB there is no guarantee when, or whether, it will do so
*/
System.gc();
waitFor(100);
/*
* a second gc() call should not be necessary - but it is!
* the test passes with it, and fails without it
*/
System.gc();
waitFor(100);
/*
* check used memory is 'reasonably low'
*/
long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
long usedMemory = availableMemory - freeMemory;
/*
* sanity check - fails if any frame was added after
* closeAll_actionPerformed
*/
assertEquals(Desktop.instance.getAllFrames().length, 0);
/*
* if this assertion fails
* - set a breakpoint here
* - run jvisualvm to inspect a heap dump of Jalview
* - identify large objects in the heap and their referers
* - fix code as necessary to null the references on close
*/
System.out.println("Used memory after gc = " + usedMemory + "MB");
assertTrue(usedMemory < expectedMax, String.format(
"Used memory %d should be less than %d (Recommend running test manually to verify)",
usedMemory,
expectedMax));
}
/**
* Loads an alignment from file and exercises various operations in Jalview
*
* @param f
*/
protected void doStuffInJalview(File f)
{
/*
* load alignment, wait for consensus and other threads to complete
*/
AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
DataSourceType.FILE);
while (af.getViewport().isCalcInProgress())
{
waitFor(200);
}
/*
* set a selection group - potential memory leak if it retains
* a reference to the alignment
*/
SequenceGroup sg = new SequenceGroup();
sg.setStartRes(0);
sg.setEndRes(100);
AlignmentI al = af.viewport.getAlignment();
for (int i = 0; i < al.getHeight(); i++)
{
sg.addSequence(al.getSequenceAt(i), false);
}
af.viewport.setSelectionGroup(sg);
/*
* compute Tree and PCA (on all sequences, 100 columns)
*/
af.openTreePcaDialog();
CalculationChooser dialog = af.alignPanel.getCalculationDialog();
dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
/*
* wait until Tree and PCA have been computed
*/
while (af.viewport.getCurrentTree() == null
&& dialog.getPcaPanel().isWorking())
{
waitFor(10);
}
/*
* give Swing time to add the PCA panel (?!?)
*/
waitFor(100);
}
/**
* Wait for waitMs miliseconds
*
* @param waitMs
*/
protected void waitFor(int waitMs)
{
try
{
Thread.sleep(waitMs);
} catch (InterruptedException e)
{
}
}
/**
* Generates an alignment and saves it in a temporary file, to be loaded by
* Jalview. We use a peptide alignment (so Conservation and Quality are
* calculated), which is wide enough to ensure Consensus, Conservation and
* Occupancy have a significant memory footprint (if not removed from the
* heap).
*
* @return
* @throws IOException
*/
private File generateAlignment() throws IOException
{
File f = File.createTempFile("MemoryTest", "fa");
PrintStream ps = new PrintStream(f);
AlignmentGenerator ag = new AlignmentGenerator(false, ps);
int width = 100000;
int height = 100;
ag.generate(width, height, 0, 10, 15);
return f;
}
}