From a81c907bd3b6d5e57572a652ee7865a90da9a76e Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Mon, 2 Apr 2012 23:24:58 +0200 Subject: Added presentation memory leak test. --- .../nsc/interactive/tests/InteractiveTest.scala | 1 + .../nsc/interactive/tests/core/AskCommand.scala | 4 +- test/files/presentation/memory-leaks.check | 14 +++ .../memory-leaks/MemoryLeaksTest.scala | 125 +++++++++++++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 test/files/presentation/memory-leaks.check create mode 100644 test/files/presentation/memory-leaks/MemoryLeaksTest.scala diff --git a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala index 7be115e777..9dc2a8de10 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala @@ -55,6 +55,7 @@ abstract class InteractiveTest with AskShutdown with AskReload with AskLoadedTyped + with AskType with PresentationCompilerInstance with CoreTestDefs with InteractiveTestSettings { self => diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommand.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommand.scala index 35d6723818..657ef23eed 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommand.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/AskCommand.scala @@ -101,13 +101,13 @@ trait AskTypeAt extends AskCommand { trait AskType extends AskCommand { import compiler.Tree - private[tests] def askType(source: SourceFile, forceReload: Boolean)(implicit reporter: Reporter): Response[Tree] = { + protected def askType(source: SourceFile, forceReload: Boolean)(implicit reporter: Reporter): Response[Tree] = { ask { compiler.askType(source, forceReload, _) } } - private[tests] def askType(sources: Seq[SourceFile], forceReload: Boolean)(implicit reporter: Reporter): Seq[Response[Tree]] = { + protected def askType(sources: Seq[SourceFile], forceReload: Boolean)(implicit reporter: Reporter): Seq[Response[Tree]] = { for(source <- sources) yield askType(source, forceReload) } diff --git a/test/files/presentation/memory-leaks.check b/test/files/presentation/memory-leaks.check new file mode 100644 index 0000000000..9d8cbb4da4 --- /dev/null +++ b/test/files/presentation/memory-leaks.check @@ -0,0 +1,14 @@ +reload: Trees.scala, Typers.scala, Types.scala +reload: Trees.scala +reload: Types.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +reload: Typers.scala +No leaks detected. diff --git a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala new file mode 100644 index 0000000000..e24f36d7b6 --- /dev/null +++ b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala @@ -0,0 +1,125 @@ +import java.io.PrintWriter +import java.io.FileOutputStream +import java.util.Calendar + +import scala.tools.nsc.interactive.tests._ +import scala.tools.nsc.util._ +import scala.tools.nsc.io._ + +/** This test runs the presentation compiler on the Scala compiler project itself and records memory consumption. + * + * The test scenario is to open Typers, Trees and Types, then repeatedly add and remove one character + * in Typers.scala. Each step causes the parser, namer, and type checker to run. + * + * At each step we record the memory usage after the GC has run. At the end of the test, + * simple linear regression is used to compute the straight line that best fits the + * curve, and if the slope is higher than 1 (meaning a leak of 1MB/run), we fail the test. + * + * The Scala compiler sources are assumed to be under 'basedir/src/compiler'. + * + * The individual data points are saved under 'usedMem-.txt', under the test project + * directory. Use the cool graph-it.R (https://github.com/scala-ide/scala-ide/blob/master/org.scala-ide.sdt.core.tests/graph-it.R) + * script to see the memory curve for the given test run. + */ +object Test extends InteractiveTest { + final val mega = 1024 * 1024 + + override def main(args: Array[String]) { + memoryConsumptionTest() + compiler.askShutdown() + } + + def batchSource(name: String) = + new BatchSourceFile(AbstractFile.getFile(name)) + + def memoryConsumptionTest() { + val N = 10 + val filename = "usedmem-%tF.txt".format(Calendar.getInstance.getTime) + + val typerUnit = AbstractFile.getFile(baseDir.parent.parent.parent.parent / "src/compiler/scala/tools/nsc/typechecker/Typers.scala") + val typesUnit = AbstractFile.getFile(baseDir.parent.parent.parent.parent / "src/compiler/scala/reflect/internal/Types.scala") + val treesUnit = AbstractFile.getFile(baseDir.parent.parent.parent.parent / "src/compiler/scala/reflect/internal/Trees.scala") + + askReload(Seq(new BatchSourceFile(typerUnit), new BatchSourceFile(typesUnit), new BatchSourceFile(treesUnit))) + typeCheckWith(treesUnit, new String(treesUnit.toCharArray)) + typeCheckWith(typesUnit, new String(typesUnit.toCharArray)) + + val originalTyper = new String(typerUnit.toCharArray) + + val (prefix, postfix) = originalTyper.splitAt(originalTyper.indexOf("import global._")) + val changedTyper = prefix + " a\n " + postfix + + val usedMem = for (i <- 1 to N) yield { + val src = if (i % 2 == 0) originalTyper else changedTyper + + val usedMem = withGC { + typeCheckWith(typerUnit, src) + } + +// println("UsedMem:\t%d\t%d".format(i, usedMem / mega)) + usedMem / mega // report size in MB + } + + // println("=" * 80) + + val outputFile = new PrintWriter(new FileOutputStream(filename)) + outputFile.println("\tusedMem") + for ((dataPoint, i) <- usedMem.zipWithIndex) { + outputFile.println("%d\t%d".format(i, dataPoint)) + } + outputFile.close() + // drop the first two measurements, since the compiler needs some memory when initializing + val (a, b) = linearModel((3L to N).toSeq, usedMem.drop(2)) + //println("LinearModel: constant: %.4f\tslope:%.4f".format(a, b)) + + if (b > 1.0) + println("Rate of memory consumption is alarming! %.4f MB/run".format(b)) + else + println("No leaks detected.") + } + + private def typeCheckWith(file: AbstractFile, src: String) = { + val sourceFile = new BatchSourceFile(file, src.toCharArray) + askReload(Seq(sourceFile)) + askLoadedTyped(sourceFile).get // block until it's here + } + + + /** Return the linear model of these values, (a, b). First value is the constant factor, + * second value is the slope, i.e. `y = a + bx` + * + * The linear model of a set of points is a straight line that minimizes the square distance + * between the each point and the line. + * + * See: http://en.wikipedia.org/wiki/Simple_linear_regression + */ + def linearModel(xs: Seq[Long], ys: Seq[Long]): (Double, Double) = { + require(xs.length == ys.length) + + def mean(v: Seq[Long]): Double = v.sum.toDouble / v.length + + val meanXs = mean(xs) + val meanYs = mean(ys) + + val beta = (mean((xs, ys).zipped.map(_ * _)) - meanXs * meanYs) / (mean(xs.map(x => x * x)) - meanXs * meanXs) + val alfa = meanYs - beta * meanXs + + (alfa, beta) + } + + /** Run the given closure and return the amount of used memory at the end of its execution. + * + * Runs the GC before and after the execution of `f'. + */ + def withGC(f: => Unit): Long = { + val r = Runtime.getRuntime + System.gc() + + f; + + System.gc() + + r.totalMemory() - r.freeMemory() + } + +} \ No newline at end of file -- cgit v1.2.3 From 19b6ad5ee41d8a68612a58b925397280f08bc931 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 3 Apr 2012 12:18:46 +0200 Subject: avoid memory leak by avoiding default arg --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2aff00f6a5..09fb39125e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2199,7 +2199,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { vparams map {p => if(p.tpt.tpe == null) typedType(p.tpt).tpe else p.tpt.tpe} } - def mkParams(methodSym: Symbol, formals: List[Type] = deriveFormals) = { + def mkParams(methodSym: Symbol, formals: List[Type]/* = deriveFormals*/) = { selOverride match { case None if targs.isEmpty => MissingParameterTypeAnonMatchError(tree, pt); (Nil, EmptyTree) case None => @@ -2225,7 +2225,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { // rig the show so we can get started typing the method body -- later we'll correct the infos... anonClass setInfo ClassInfoType(List(ObjectClass.tpe, pt, SerializableClass.tpe), newScope, anonClass) val methodSym = anonClass.newMethod(nme.apply, tree.pos, FINAL) - val (paramSyms, selector) = mkParams(methodSym) + val (paramSyms, selector) = mkParams(methodSym, deriveFormals) if (selector eq EmptyTree) EmptyTree else { @@ -2289,7 +2289,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def isDefinedAtMethod = { val methodSym = anonClass.newMethod(nme.isDefinedAt, tree.pos, FINAL) - val (paramSyms, selector) = mkParams(methodSym) + val (paramSyms, selector) = mkParams(methodSym, deriveFormals) if (selector eq EmptyTree) EmptyTree else { val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym)) // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it) -- cgit v1.2.3