From 234ee6d56bdbf4234f8f6f0f2ebc0560f25701ee Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 4 Jan 2011 01:17:32 +0000 Subject: Some profiler changes. after each phase, so all the allocations are partitionable by phase in the profiler. I also changed the name of -Yprofile-resident to -Yprofile-memory and had it snapshot after the run instead of between them, so it is usable for regular scalac usage as well. Added tools/profile_scalac which can be used as a drop-in replacement which handles some tedious setup. Review by dragos. --- src/compiler/scala/tools/nsc/Global.scala | 69 ++++++++++++---------- .../scala/tools/nsc/settings/ScalaSettings.scala | 4 +- src/compiler/scala/tools/util/Profiling.scala | 15 ++++- .../scala/tools/util/YourkitProfiling.scala | 45 ++++++++++---- tools/locker_scala | 8 +++ tools/locker_scalac | 8 +++ tools/lockercp | 10 ++++ tools/profile_scala | 17 ++++++ tools/profile_scalac | 25 ++++++++ tools/quickcp | 6 +- 10 files changed, 160 insertions(+), 47 deletions(-) create mode 100755 tools/locker_scala create mode 100755 tools/locker_scalac create mode 100755 tools/lockercp create mode 100755 tools/profile_scala create mode 100755 tools/profile_scalac diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 244319a5ff..684490b84d 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -238,22 +238,23 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable // scalac -Dscala.timings will make this true. def timings = system.props contains "scala.timings" - def debug = settings.debug.value - def deprecation = settings.deprecation.value - def experimental = settings.Xexperimental.value - def fatalWarnings = settings.Xwarnfatal.value - 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 - def typerDebug = settings.Ytyperdebug.value - def unchecked = settings.unchecked.value - def verbose = settings.verbose.value - def writeICode = settings.writeICode.value - def declsOnly = false + def debug = settings.debug.value + def deprecation = settings.deprecation.value + def experimental = settings.Xexperimental.value + def fatalWarnings = settings.Xwarnfatal.value + def logClasspath = settings.Ylogcp.value + def printLate = settings.printLate.value + def printStats = settings.Ystatistics.value + def profileClass = settings.YprofileClass.value + def profileMem = settings.YprofileMem.value + def richExes = settings.YrichExes.value + def showTrees = settings.Xshowtrees.value + def target = settings.target.value + def typerDebug = settings.Ytyperdebug.value + def unchecked = settings.unchecked.value + def verbose = settings.verbose.value + def writeICode = settings.writeICode.value + def declsOnly = false /** Flags as applied to the current or previous phase */ def browsePhase = isActive(settings.browse) @@ -261,11 +262,13 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable def logPhase = isActive(settings.log) def printPhase = isActive(settings.Xprint) def showPhase = isActive(settings.Yshow) - def profilePhase = isActive(settings.Yprofile) && !profileAll + def profCPUPhase = isActive(settings.Yprofile) && !profileAll /** Derived values */ + def noShow = settings.Yshow.isDefault def showNames = List(showClass, showObject).flatten def profileAll = settings.Yprofile.doAllPhases + def profileAny = !settings.Yprofile.isDefault || !settings.YprofileMem.isDefault def jvm = target startsWith "jvm" def msil = target == "msil" def verboseDebug = debug && verbose @@ -663,14 +666,6 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable /** To be initialized from firstPhase. */ private var terminalPhase: Phase = NoPhase - if (settings.YprofileRes.value) { - System.gc(); - print("Saving snapshot..") - profiler.captureSnapshot() - println("[saved]") - specializeTypes.printSpecStats() - } - /** Whether compilation should stop at or skip the phase with given name. */ protected def stopPhase(name: String) = settings.stop contains name protected def skipPhase(name: String) = settings.skip contains name @@ -872,12 +867,16 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable val startTime = currentTime phase = globalPhase - if (opt.profilePhase) { + if (opt.profCPUPhase) { inform("starting CPU profiling on phase " + globalPhase.name) profiler profile globalPhase.run } else globalPhase.run + // Create a profiling generation for each phase's allocations + if (opt.profileAny) + profiler.advanceGeneration(globalPhase.name) + // progress update informTime(globalPhase.description, startTime) phaseTimings(globalPhase) = currentTime - startTime @@ -912,15 +911,14 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable advancePhase } - if (opt.profileAll) { + if (opt.profileAll) profiler.stopProfiling() - profiler.captureSnapshot() - } + if (opt.timings) inform(phaseTimings.formatted) - // If no phase was specified for -Xshow-class/object, show it now. - if (settings.Yshow.isDefault) + // In case no phase was specified for -Xshow-class/object, show it now for sure. + if (opt.noShow) showMembers() if (reporter.hasErrors) { @@ -943,6 +941,15 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable symSource.keys foreach (x => resetPackageClass(x.owner)) informTime("total", startTime) + // save heap snapshot if requested + if (opt.profileMem) { + inform("Saving heap snapshot, this could take a while...") + System.gc() + profiler.captureSnapshot() + inform("...done saving heap snapshot.") + specializeTypes.printSpecStats() + } + // record dependency data if (!dependencyAnalysis.off) dependencyAnalysis.saveDependencyAnalysis() diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 20a93edb5f..627fdac2d0 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -112,8 +112,8 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val noimports = BooleanSetting ("-Yno-imports", "Compile without any implicit imports.") // Not actually doing anything, so disabled. // val nopredefs = BooleanSetting ("-Yno-predefs", "Compile without any implicit predefined values.") - val Yprofile = PhasesSetting ("-Yprofile", "(Requires jvm -agentpath to contain yjgpagent) Profile") - val YprofileRes = BooleanSetting ("-Yprofile-resident", "Profile memory, get heap snapshot after each compiler run (requires yjpagent, see above).") + val Yprofile = PhasesSetting ("-Yprofile", "(Requires jvm -agentpath to contain yjgpagent) Profile CPU usage of given phases.") + val YprofileMem = BooleanSetting ("-Yprofile-memory", "Profile memory, get heap snapshot after each compiler run (requires yjpagent, see above).") val YprofileClass = StringSetting ("-Yprofile-class", "class", "Name of profiler class.", "scala.tools.util.YourkitProfiling") val Yrecursion = IntSetting ("-Yrecursion", "Set 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.") diff --git a/src/compiler/scala/tools/util/Profiling.scala b/src/compiler/scala/tools/util/Profiling.scala index e0a1a0e2a0..aada795ee2 100644 --- a/src/compiler/scala/tools/util/Profiling.scala +++ b/src/compiler/scala/tools/util/Profiling.scala @@ -22,7 +22,13 @@ abstract class Profiling { def stopProfiling(): Unit def captureSnapshot(): Unit - def profile[T](body: => T): T = { + def allocationFreq: Option[Int] // record every Nth allocation + def startRecordingAllocations(): Unit + def stopRecordingAllocations(): Unit + + def profile[T](body: => T): T = profileCPU(body) + + def profileCPU[T](body: => T): T = { startProfiling() val result = body stopProfiling() @@ -30,6 +36,13 @@ abstract class Profiling { result } + def profileMem[T](body: => T): T = { + startRecordingAllocations() + val result = body + stopRecordingAllocations() + result + } + /** Advance the current object generation. * * Each object on the heap is associated to a generation number. Generations diff --git a/src/yourkit/scala/tools/util/YourkitProfiling.scala b/src/yourkit/scala/tools/util/YourkitProfiling.scala index 9d166d9a03..fb07eb1101 100644 --- a/src/yourkit/scala/tools/util/YourkitProfiling.scala +++ b/src/yourkit/scala/tools/util/YourkitProfiling.scala @@ -2,34 +2,57 @@ package scala.tools package util import com.yourkit.api._ +import com.yourkit.runtime._ import nsc.io._ class YourkitProfiling extends Profiling { @volatile private var active = false - private var recordAllocation = false + @volatile private var freq: Option[Int] = None lazy val controller = new Controller + def defaultFreq = 100 + def allocationFreq = freq + def setAllocationFreq(x: Int) = freq = if (x <= 0) None else Some(x) + + def startRecordingAllocations() = { + controller.startAllocationRecording(true, freq getOrElse defaultFreq, false, 0) + } + def stopRecordingAllocations() = { + controller.stopAllocationRecording() + } + 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) + try { + controller.startCPUProfiling(ProfilingModes.CPU_SAMPLING, Controller.DEFAULT_FILTERS) + if (freq.isDefined) + startRecordingAllocations() + } + catch { + case _: PresentableException => () // if it's already running, no big deal + } } } - def captureSnapshot() = - daemonize(false)(controller.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP)) + def captureSnapshot() = { + daemonize(true)(controller.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP)) + } def stopProfiling() = { - if (recordAllocation) - controller.stopAllocationRecording() + try { + if (freq.isDefined) + stopRecordingAllocations() - controller.stopCPUProfiling() - active = false + controller.stopCPUProfiling() + } + catch { + case _: PresentableException => () // if it's already running, no big deal + } + finally active = false } def advanceGeneration(desc: String) { @@ -37,4 +60,4 @@ class YourkitProfiling extends Profiling { } def isActive = active -} \ No newline at end of file +} diff --git a/tools/locker_scala b/tools/locker_scala new file mode 100755 index 0000000000..4434c94bf3 --- /dev/null +++ b/tools/locker_scala @@ -0,0 +1,8 @@ +#!/bin/bash +# + +THISDIR=`dirname $0` +CP=`$THISDIR/lockercp` +CLASS="scala.tools.nsc.MainGenericRunner" + +java -classpath "$CP" $CLASS -usejavacp "$@" diff --git a/tools/locker_scalac b/tools/locker_scalac new file mode 100755 index 0000000000..2ad153e929 --- /dev/null +++ b/tools/locker_scalac @@ -0,0 +1,8 @@ +#!/bin/bash +# + +THISDIR=`dirname $0` +CP=`$THISDIR/lockercp` +CLASS="scala.tools.nsc.Main" + +java -classpath "$CP" $CLASS -usejavacp "$@" diff --git a/tools/lockercp b/tools/lockercp new file mode 100755 index 0000000000..3e8799596f --- /dev/null +++ b/tools/lockercp @@ -0,0 +1,10 @@ +#!/bin/sh +# + +THISDIR=`dirname $0` +ABS=${THISDIR}/abspath +LIBDIR=`$ABS $THISDIR/../lib` + +cp=`${THISDIR}/cpof ${THISDIR}/../build/locker/classes` + +echo ${cp}:$LIBDIR/fjbg.jar:$LIBDIR/msil.jar:$LIBDIR/forkjoin.jar:$LIBDIR/jline.jar:$LIBDIR/extra/'*' diff --git a/tools/profile_scala b/tools/profile_scala new file mode 100755 index 0000000000..037fc327bd --- /dev/null +++ b/tools/profile_scala @@ -0,0 +1,17 @@ +#!/bin/bash +# + +# Uses quick by default +CLASSPATH=`tools/quickcp` + +AGENT=${YOURKIT_PATH:-/Applications/YourKit.app/bin/mac/libyjpagent.jnilib} + +java $JAVA_OPTS \ + -classpath $CLASSPATH \ + -agentpath:$AGENT=$YNP_STARTUP_OPTIONS \ + scala.tools.nsc.MainGenericRunner -usejavacp \ + -i <(cat <