From 4028f83c6df65e4c2a5e6aef4edc4bbef42a7959 Mon Sep 17 00:00:00 2001 From: extempore Date: Tue, 16 Nov 2010 23:08:45 +0000 Subject: [PATCH] Some profiling infrastructure. With no small amount of fiddling I avoided creating any dependency on yourkit. In addition, there was no way to give arguments to the JVM without losing the ones defined in ANT_OPTS, which has been a massive pain for a while. So there is now "jvm.opts" which is simply appended to ANT_OPTS, e.g. % ant -Djvm.opts=-verbose [echo] Forking with JVM opts: -Xms1536M -Xmx2g -Xss1M -XX:MaxPermSize=192M -XX:+UseParallelGC -verbose There is a minimal stub defining a profiler interface: scala.tools.util.Profiling Then the yourkit wrapper implements that interface. Once your locker has been rebuilt once, you can do this: ant yourkit.run And it will build quick.lib/comp with profiling enabled, assuming it can find the necessary files. See the yourkit.init target for values to change: or ant -Dyourkit.home=/path/to/it might be enough. Review by dragos. git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@23528 5e8d7ff9-d8ef-0310-90f0-a4852d11357a --- build.xml | 66 +++++++++++++++++-- src/compiler/scala/tools/nsc/Global.scala | 39 ++++++++--- src/compiler/scala/tools/nsc/io/package.scala | 11 +++- .../tools/nsc/settings/ScalaSettings.scala | 2 + src/compiler/scala/tools/util/Profiling.scala | 32 +++++++++ .../scala/tools/util/YourkitProfiling.scala | 36 ++++++++++ 6 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 src/compiler/scala/tools/util/Profiling.scala create mode 100644 src/yourkit/scala/tools/util/YourkitProfiling.scala diff --git a/build.xml b/build.xml index 3c010748d..d9c08bb1c 100644 --- a/build.xml +++ b/build.xml @@ -1,7 +1,6 @@ - SuperSabbus for Scala core, builds the scala library and compiler. It can also package it as a simple distribution, tests it for stable bootstrapping and against the Scala test suite. @@ -185,16 +184,18 @@ PROPERTIES + + - + + value="${env.ANT_OPTS} ${jvm.opts}"/> - + + @@ -271,7 +273,7 @@ INITIALISATION - + @@ -284,12 +286,12 @@ INITIALISATION - + - + @@ -872,6 +874,11 @@ PACKED QUICK BUILD (PACK) + + + + + @@ -975,6 +982,7 @@ PACKED QUICK BUILD (PACK) + @@ -1907,6 +1915,50 @@ POSITIONS MISCELLANEOUS ============================================================================ --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 671559fc4..90e282554 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -9,6 +9,7 @@ import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundE import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } import compat.Platform.currentTime +import scala.tools.util.Profiling import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } import reporters.{ Reporter, ConsoleReporter } @@ -232,6 +233,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable def logClasspath = settings.Ylogcp.value def printLate = settings.printLate.value def printStats = settings.Ystatistics.value + def profileClass = settings.YprofileClass.value def richExes = settings.YrichExes.value def showTrees = settings.Xshowtrees.value def target = settings.target.value @@ -242,14 +244,16 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable def declsOnly = false /** Flags as applied to the current or previous phase */ - def browsePhase = isActive(settings.browse) - def checkPhase = wasActive(settings.check) - def logPhase = isActive(settings.log) - def printPhase = isActive(settings.Xprint) - def showPhase = isActive(settings.Yshow) + def browsePhase = isActive(settings.browse) + def checkPhase = wasActive(settings.check) + def logPhase = isActive(settings.log) + def printPhase = isActive(settings.Xprint) + def showPhase = isActive(settings.Yshow) + def profilePhase = isActive(settings.Yprofile) && !profileAll /** Derived values */ def showNames = List(showClass, showObject).flatten + def profileAll = settings.Yprofile.doAllPhases def jvm = target startsWith "jvm" def msil = target == "msil" def verboseDebug = debug && verbose @@ -818,6 +822,9 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable private def showMembers() = opt.showNames foreach (x => showDef(x, opt.declsOnly, globalPhase)) + // If -Yprofile isn't given this will never be triggered. + lazy val profiler = Class.forName(opt.profileClass).newInstance().asInstanceOf[Profiling] + /** Compile list of source files */ def compileSources(_sources: List[SourceFile]) { val depSources = dependencyAnalysis.calculateFiles(_sources.distinct) // bug #1268, scalac confused by duplicated filenames @@ -830,11 +837,21 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable for (source <- sources) addUnit(new CompilationUnit(source)) globalPhase = firstPhase + + if (opt.profileAll) { + inform("starting CPU profiling on compilation run") + profiler.startProfiling() + } while (globalPhase != terminalPhase && !reporter.hasErrors) { val startTime = currentTime phase = globalPhase - globalPhase.run - + + if (opt.profilePhase) { + inform("starting CPU profiling on phase " + globalPhase.name) + profiler profile globalPhase.run + } + else globalPhase.run + // write icode to *.icode files if (opt.writeICode && (runIsAt(icodePhase) || opt.printPhase && runIsPast(icodePhase))) writeICode() @@ -847,11 +864,11 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable // print members if (opt.showPhase) showMembers() - + // browse trees with swing tree viewer if (opt.browsePhase) treeBrowser browse units - + // progress update informTime(globalPhase.description, startTime) globalPhase = globalPhase.next @@ -866,6 +883,10 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable advancePhase } + if (opt.profileAll) { + profiler.stopProfiling() + profiler.captureSnapshot() + } // If no phase was specified for -Xshow-class/object, show it now. if (settings.Yshow.isDefault) diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala index 0c0f76123..48a8f6d39 100644 --- a/src/compiler/scala/tools/nsc/io/package.scala +++ b/src/compiler/scala/tools/nsc/io/package.scala @@ -14,5 +14,14 @@ package object io { def submit(runnable: Runnable) = Executors.newSingleThreadExecutor() submit runnable def runnableFn(f: () => Unit): Runnable = runnable(f()) def callableFn[T](f: () => T): Callable[T] = callable(f()) - def spawnFn[T](f: () => T): Future[T] = spawn(f()) + def spawnFn[T](f: () => T): Future[T] = spawn(f()) + + // Create, start, and return a background thread + // If isDaemon is true, it is marked as daemon (and will not interfere with JVM shutdown) + def daemonize(isDaemon: Boolean)(body: => Unit): Thread = { + val thread = new Thread(runnable(body)) + thread setDaemon isDaemon + thread.start + thread + } } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 38cb497f3..47b46c002 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -122,6 +122,8 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java") val noimports = BooleanSetting ("-Yno-imports", "Compile without any implicit imports") val nopredefs = BooleanSetting ("-Yno-predefs", "Compile without any implicit predefined values") + val Yprofile = PhasesSetting ("-Yprofile", "Profile the given phase. Needs yjpagent to run.") + val YprofileClass = StringSetting ("-Yprofile-class", "class", "Name of profiler class", "scala.tools.util.YourkitProfiling") val Yrecursion = IntSetting ("-Yrecursion", "Recursion depth used when locking symbols", 0, Some(0, Int.MaxValue), (_: String) => None) val selfInAnnots = BooleanSetting ("-Yself-in-annots", "Include a \"self\" identifier inside of annotations") val Xshowtrees = BooleanSetting ("-Yshow-trees", "Show detailed trees when used in connection with -Xprint:") diff --git a/src/compiler/scala/tools/util/Profiling.scala b/src/compiler/scala/tools/util/Profiling.scala new file mode 100644 index 000000000..5a661bbd6 --- /dev/null +++ b/src/compiler/scala/tools/util/Profiling.scala @@ -0,0 +1,32 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package util + +/** This is a (very) minimal stub for profiling, the purpose + * of which is making it possible to integrate profiling hooks in + * the compiler without creating a dependency on any particular + * profiler. You can specify a profiler class (which must be an + * instance of this class) like so: + * + * // or -Yprofile:phase to profile individual phases + * scalac -Yprofile-class your.profiler.Class -Yprofile:all + * + */ +abstract class Profiling { + def isActive: Boolean + def startProfiling(): Unit + def stopProfiling(): Unit + def captureSnapshot(): Unit + + def profile[T](body: => T): T = { + startProfiling() + val result = body + stopProfiling() + captureSnapshot() + result + } +} diff --git a/src/yourkit/scala/tools/util/YourkitProfiling.scala b/src/yourkit/scala/tools/util/YourkitProfiling.scala new file mode 100644 index 000000000..f7745730d --- /dev/null +++ b/src/yourkit/scala/tools/util/YourkitProfiling.scala @@ -0,0 +1,36 @@ +package scala.tools +package util + +import com.yourkit.api._ +import nsc.io._ + +class YourkitProfiling extends Profiling { + @volatile private var active = false + private var recordAllocation = false + lazy val controller = new Controller + + def startProfiling(): Unit = { + if (isActive) + return + + active = true + daemonize(true) { + controller.startCPUProfiling(ProfilingModes.CPU_SAMPLING, Controller.DEFAULT_FILTERS) + if (recordAllocation) + controller.startAllocationRecording(true, 100, false, 0) + } + } + + def captureSnapshot() = + daemonize(false)(controller.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP)) + + def stopProfiling() = { + if (recordAllocation) + controller.stopAllocationRecording() + + controller.stopCPUProfiling() + active = false + } + + def isActive = active +} \ No newline at end of file