From b1acf8c1977aafaaed3117a1ea89cf06b08b716b Mon Sep 17 00:00:00 2001 From: gmungoc Date: Tue, 12 Sep 2017 13:47:40 +0100 Subject: [PATCH] JAL-2727 first pass of test of memory recovery on close all --- test/jalview/analysis/AlignmentGenerator.java | 95 ++++++++++------ test/jalview/gui/FreeUpMemoryTest.java | 146 +++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 test/jalview/gui/FreeUpMemoryTest.java diff --git a/test/jalview/analysis/AlignmentGenerator.java b/test/jalview/analysis/AlignmentGenerator.java index 3187fd9..9d3877c 100644 --- a/test/jalview/analysis/AlignmentGenerator.java +++ b/test/jalview/analysis/AlignmentGenerator.java @@ -27,39 +27,25 @@ import jalview.datamodel.SequenceI; import jalview.gui.JvOptionPane; import jalview.io.FastaFile; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.Arrays; import java.util.Random; import org.testng.annotations.BeforeClass; /** - * Generates, and outputs in Fasta format, a random DNA alignment for given + * Generates, and outputs in Fasta format, a random peptide or nucleotide alignment for given * sequence length and count. Will regenerate the same alignment each time if * the same random seed is used (so may be used for reproducible unit tests). * Not guaranteed to reproduce the same results between versions, as the rules * may get tweaked to produce more 'realistic' results. * - * Arguments: - * - * * @author gmcarstairs - * */ public class AlignmentGenerator { - @BeforeClass(alwaysRun = true) - public void setUpJvOptionPane() - { - JvOptionPane.setInteractiveMode(false); - JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); - } - private static final char GAP = '-'; private static final char ZERO = '0'; @@ -72,51 +58,76 @@ public class AlignmentGenerator private Random random; + private PrintStream ps; /** - * Outputs a DNA 'alignment' where each position is a random choice from - * 'GTCA-'. + * Outputs a pseudo-randomly generated nucleotide or peptide alignment + * Arguments: + * + * * * @param args + * @throws FileNotFoundException */ - public static void main(String[] args) + public static void main(String[] args) throws FileNotFoundException { - if (args.length != 6) + if (args.length != 6 && args.length != 7) { usage(); return; } + + PrintStream ps = System.out; + if (args.length == 7) + { + ps = new PrintStream(new File(args[6])); + } + boolean nucleotide = args[0].toLowerCase().startsWith("n"); int width = Integer.parseInt(args[1]); int height = Integer.parseInt(args[2]); long randomSeed = Long.valueOf(args[3]); int gapPercentage = Integer.valueOf(args[4]); int changePercentage = Integer.valueOf(args[5]); - AlignmentI al = new AlignmentGenerator(nucleotide).generate(width, - height, - randomSeed, gapPercentage, changePercentage); - System.out.println("; " + height + " sequences of " + width + ps.println("; " + height + " sequences of " + width + " bases with " + gapPercentage + "% gaps and " + changePercentage + "% mutations (random seed = " + randomSeed + ")"); - System.out.println(new FastaFile().print(al.getSequencesArray(), true)); + + new AlignmentGenerator(nucleotide, ps).generate(width, height, + randomSeed, gapPercentage, changePercentage); + + if (ps != System.out) + { + ps.close(); + } } /** - * Print parameter help. + * Prints parameter help */ private static void usage() { System.out.println("Usage:"); System.out.println("arg0: n (for nucleotide) or p (for peptide)"); System.out.println("arg1: number of (non-gap) bases per sequence"); - System.out.println("arg2: number sequences"); + System.out.println("arg2: number of sequences"); System.out .println("arg3: an integer as random seed (same seed = same results)"); System.out.println("arg4: percentage of gaps to (randomly) generate"); System.out .println("arg5: percentage of 'mutations' to (randomly) generate"); + System.out + .println("arg6: (optional) path to output file (default is sysout)"); System.out.println("Example: AlignmentGenerator n 12 15 387 10 5"); System.out .println("- 15 nucleotide sequences of 12 bases each, approx 10% gaps and 5% mutations, random seed = 387"); @@ -124,16 +135,28 @@ public class AlignmentGenerator } /** - * Constructor that sets nucleotide or peptide symbol set + * Constructor that sets nucleotide or peptide symbol set, and also writes the + * generated alignment to sysout */ public AlignmentGenerator(boolean nuc) { - BASES = nuc ? NUCS : PEPS; + this(nuc, System.out); + } + + /** + * Constructor that sets nucleotide or peptide symbol set, and also writes the + * generated alignment to the specified output stream (if not null). This can + * be used to write the alignment to a file or sysout. + */ + public AlignmentGenerator(boolean nucleotide, PrintStream printStream) + { + BASES = nucleotide ? NUCS : PEPS; + ps = printStream; } /** - * Outputs a DNA 'alignment' of given width and height, where each position is - * a random choice from 'GTCA-'. + * Outputs an 'alignment' of given width and height, where each position is a + * random choice from the symbol alphabet, or - for gap * * @param width * @param height @@ -153,6 +176,12 @@ public class AlignmentGenerator seqno + 1, width, changePercentage); } AlignmentI al = new Alignment(seqs); + + if (ps != null) + { + ps.println(new FastaFile().print(al.getSequencesArray(), true)); + } + return al; } diff --git a/test/jalview/gui/FreeUpMemoryTest.java b/test/jalview/gui/FreeUpMemoryTest.java new file mode 100644 index 0000000..2a9a8c9 --- /dev/null +++ b/test/jalview/gui/FreeUpMemoryTest.java @@ -0,0 +1,146 @@ +package jalview.gui; + +import static org.testng.Assert.assertTrue; + +import jalview.analysis.AlignmentGenerator; +import jalview.bin.Cache; +import jalview.bin.Jalview; +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 = 1024 * 1024; + + /** + * 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. + * + * If the test fails, this suggests that a reference to some large object + * (perhaps the alignment data, or consensus profile) 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(); + + /* + * load alignment, wait for consensus and other threads to complete + */ + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(), + DataSourceType.FILE); + waitForThreads(af.getViewport()); + + af.closeMenuItem_actionPerformed(true); + + /* + * request garbage collection and allow 1 second for it to complete; + * NB there is no guarantee when, or whether, it will run! + */ + System.gc(); + synchronized (this) + { + try + { + wait(1000); + } catch (InterruptedException e) + { + } + } + + /* + * check used memory is 'reasonably low' + */ + long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB; + long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB; + long usedMemory = availableMemory - freeMemory; + System.out.println("Memory in use after close all windows: " + + usedMemory + "MB"); + + /* + * 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 + */ + long expectedMax = 100L; + assertTrue(usedMemory < expectedMax, + String.format("Used memory %d > %d", usedMemory, expectedMax)); + } + + /** + * wait for consensus etc thread to complete + * + * @param av + */ + protected void waitForThreads(AlignViewport av) + { + while (av.isCalcInProgress()) + { + try + { + Thread.sleep(200); + } catch (Exception x) + { + } + } + } + + /** + * Generates an alignment (large enough for this test but not so large it is + * too slow or runs out of memory) and saves it in a temporary file. + * + * @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); + ag.generate(1000, 20000, 0, 10, 15); + return f; + } +} -- 1.7.10.2