1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
import java.io.PrintWriter
import java.io.FileOutputStream
import java.util.Calendar
import scala.reflect.internal.util.BatchSourceFile
import scala.tools.nsc.interactive
import scala.tools.nsc.interactive.tests._
import scala.tools.nsc.io._
import scala.tools.nsc.doc
/** 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-<date>.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
import interactive.Global
trait InteractiveScaladocAnalyzer extends interactive.InteractiveAnalyzer with doc.ScaladocAnalyzer {
val global : Global
override def newTyper(context: Context) = new Typer(context) with InteractiveTyper with ScaladocTyper {
override def canAdaptConstantTypeToLiteral = false
}
}
private class ScaladocEnabledGlobal extends Global(settings, compilerReporter) {
override lazy val analyzer = new {
val global: ScaladocEnabledGlobal.this.type = ScaladocEnabledGlobal.this
} with InteractiveScaladocAnalyzer
}
override def createGlobal: Global = new ScaladocEnabledGlobal
override def execute(): Unit = memoryConsumptionTest()
def batchSource(name: String) =
new BatchSourceFile(AbstractFile.getFile(name))
def memoryConsumptionTest() {
val N = 50
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/reflect/scala/reflect/internal/Types.scala")
val treesUnit = AbstractFile.getFile(baseDir.parent.parent.parent.parent / "src/reflect/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)
}
usedMem / mega // report size in MB
}
//dumpDataToFile(filename, usedMem)
// 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
}
private def dumpDataToFile(filename: String, usedMem: Seq[Long]) {
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()
}
/** 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()
}
}
|