-
Notifications
You must be signed in to change notification settings - Fork 31
Support for benchmarking compilation under SBT #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package scala.tools.nsc | ||
|
||
import java.io.{File, IOException} | ||
import java.net.URL | ||
import java.nio.file._ | ||
import java.nio.file.attribute.BasicFileAttributes | ||
|
||
import com.typesafe.config.ConfigFactory | ||
|
||
import scala.collection.JavaConverters._ | ||
|
||
object BenchmarkUtils { | ||
def deleteRecursive(directory: Path): Unit = { | ||
Files.walkFileTree(directory, new SimpleFileVisitor[Path]() { | ||
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { | ||
Files.delete(file) | ||
FileVisitResult.CONTINUE | ||
} | ||
override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult = { | ||
Files.delete(dir) | ||
FileVisitResult.CONTINUE | ||
} | ||
}) | ||
} | ||
def initDeps(corpusSourcePath: Path): Seq[Path] = { | ||
val depsDir = Paths.get(ConfigFactory.load.getString("deps.localdir")) | ||
val depsFile = corpusSourcePath.resolve("deps.txt") | ||
val depsClasspath = Seq.newBuilder[Path] | ||
if (Files.exists(depsFile)) { | ||
val res = new StringBuilder() | ||
for (depUrlString <- Files.lines(depsFile).iterator().asScala) { | ||
val depUrl = new URL(depUrlString) | ||
val filename = Paths.get(depUrl.getPath).getFileName.toString | ||
val depFile = depsDir.resolve(filename) | ||
// TODO: check hash if file exists, or after downloading | ||
if (!Files.exists(depFile)) { | ||
if (!Files.exists(depsDir)) Files.createDirectories(depsDir) | ||
val in = depUrl.openStream | ||
Files.copy(in, depFile, StandardCopyOption.REPLACE_EXISTING) | ||
in.close() | ||
} | ||
if (res.nonEmpty) res.append(File.pathSeparator) | ||
depsClasspath += depFile | ||
} | ||
depsClasspath.result() | ||
} else Nil | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package scala.tools.nsc | ||
|
||
import java.io._ | ||
import java.net.URL | ||
import java.nio.file._ | ||
import java.nio.file.attribute.BasicFileAttributes | ||
import java.util.concurrent.TimeUnit | ||
|
||
import com.typesafe.config.ConfigFactory | ||
import org.openjdk.jmh.annotations.Mode.SampleTime | ||
import org.openjdk.jmh.annotations._ | ||
|
||
import scala.collection.JavaConverters._ | ||
|
||
@State(Scope.Benchmark) | ||
@BenchmarkMode(Array(SampleTime)) | ||
@OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
@Warmup(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS) | ||
@Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS) | ||
@Fork(value = 3) | ||
class HotSbtBenchmark { | ||
@Param(value = Array()) | ||
var source: String = _ | ||
|
||
@Param(value = Array("")) | ||
var extraArgs: String = _ | ||
|
||
// This parameter is set by ScalacBenchmarkRunner / UploadingRunner based on the Scala version. | ||
// When running the benchmark directly the "latest" symlink is used. | ||
@Param(value = Array("latest")) | ||
var corpusVersion: String = _ | ||
|
||
var sbtProcess: Process = _ | ||
var inputRedirect: ProcessBuilder.Redirect = _ | ||
var outputRedirect: ProcessBuilder.Redirect = _ | ||
var tempDir: Path = _ | ||
var scalaHome: Path = _ | ||
var processOutputReader: BufferedReader = _ | ||
var processInputReader: BufferedWriter = _ | ||
var output= new java.lang.StringBuilder() | ||
|
||
def buildDef = | ||
s""" | ||
|scalaHome := Some(file("${scalaHome.toAbsolutePath.toString}")) | ||
| | ||
|val cleanClasses = taskKey[Unit]("clean the classes directory") | ||
| | ||
|cleanClasses := IO.delete((classDirectory in Compile).value) | ||
| | ||
|scalaSource in Compile := file("${corpusSourcePath.toAbsolutePath.toString}") | ||
| | ||
|libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value | ||
|libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value | ||
| | ||
|// TODO support .java sources | ||
""".stripMargin | ||
|
||
@Setup(Level.Trial) def spawn(): Unit = { | ||
tempDir = Files.createTempDirectory("sbt-") | ||
scalaHome = Files.createTempDirectory("scalaHome-") | ||
initDepsClasspath() | ||
Files.createDirectory(tempDir.resolve("project")) | ||
Files.write(tempDir.resolve("project/build.properties"), java.util.Arrays.asList("sbt.version=0.13.15")) | ||
Files.write(tempDir.resolve("build.sbt"), buildDef.getBytes("UTF-8")) | ||
val sbtLaucherPath = System.getProperty("sbt.launcher") | ||
if (sbtLaucherPath == null) sys.error("System property -Dsbt.launcher absent") | ||
val builder = new ProcessBuilder(sys.props("java.home") + "/bin/java", "-Xms2G", "-Xmx2G", "-Dsbt.log.format=false", "-jar", sbtLaucherPath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could it be possible that some of the overhead is created by sbt's cached compilers for compiling the build / classloaders? If all that is in memory, that could slow down compilation of projecs. |
||
builder.directory(tempDir.toFile) | ||
inputRedirect = builder.redirectInput() | ||
outputRedirect = builder.redirectOutput() | ||
sbtProcess = builder.start() | ||
processOutputReader = new BufferedReader(new InputStreamReader(sbtProcess.getInputStream)) | ||
processInputReader = new BufferedWriter(new OutputStreamWriter(sbtProcess.getOutputStream)) | ||
awaitPrompt() | ||
} | ||
|
||
@Benchmark | ||
def compile(): Unit = { | ||
issue(";cleanClasses;compile") | ||
awaitPrompt() | ||
} | ||
|
||
def issue(str: String) = { | ||
processInputReader.write(str + "\n") | ||
processInputReader.flush() | ||
} | ||
|
||
def awaitPrompt(): Unit = { | ||
output.setLength(0) | ||
var line = "" | ||
val buffer = new Array[Char](128) | ||
var read : Int = -1 | ||
while (true) { | ||
read = processOutputReader.read(buffer) | ||
if (read == -1) sys.error("EOF") | ||
else { | ||
output.append(buffer, 0, read) | ||
if (output.toString.contains("\n> ")) { | ||
if (output.toString.contains("[error")) sys.error(output.toString) | ||
return | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
private def corpusSourcePath = Paths.get(s"../corpus/$source/$corpusVersion") | ||
|
||
def initDepsClasspath(): Unit = { | ||
val libDir = tempDir.resolve("lib") | ||
Files.createDirectories(libDir) | ||
for (depFile <- BenchmarkUtils.initDeps(corpusSourcePath)) { | ||
val libDirFile = libDir.resolve(depFile.getFileName) | ||
Files.copy(depFile, libDir) | ||
} | ||
|
||
val scalaHomeLibDir = scalaHome.resolve("lib") | ||
Files.createDirectories(scalaHomeLibDir) | ||
for (elem <- sys.props("java.class.path").split(File.pathSeparatorChar)) { | ||
val jarFile = Paths.get(elem) | ||
var name = jarFile.getFileName.toString | ||
if (name.startsWith("scala") && name.endsWith(".jar")) { | ||
if (name.startsWith("scala-library")) | ||
name = "scala-library.jar" | ||
else if (name.startsWith("scala-reflect")) | ||
name = "scala-reflect.jar" | ||
else if (name.startsWith("scala-compiler")) | ||
name = "scala-compiler.jar" | ||
Files.copy(jarFile, scalaHomeLibDir.resolve(name)) | ||
} | ||
} | ||
|
||
} | ||
|
||
@TearDown(Level.Trial) def terminate(): Unit = { | ||
processOutputReader.close() | ||
sbtProcess.destroyForcibly() | ||
BenchmarkUtils.deleteRecursive(tempDir) | ||
BenchmarkUtils.deleteRecursive(scalaHome) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know this is possible.