Skip to content

Commit b7fcc7c

Browse files
committed
Some profiling infrastructure.
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.
1 parent 03b3f7d commit b7fcc7c

File tree

6 files changed

+165
-13
lines changed

6 files changed

+165
-13
lines changed

build.xml

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

33
<project name="sabbus" default="build">
4-
54
<description>
65
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.
76
</description>
@@ -185,16 +184,18 @@ PROPERTIES
185184
<!-- These are NOT the flags used to run SuperSabbus, but the ones written
186185
into the script runners created with scala.tools.ant.ScalaTool -->
187186
<property name="java.flags" value="-Xmx256M -Xms32M"/>
187+
<property name="jvm.opts" value=""/>
188188

189189
<!-- if ANT_OPTS is already set by the environment, it will be unaltered,
190190
but if it is unset it will take this default value. -->
191191
<property name="env.ANT_OPTS" value="-Xms1536M -Xmx1536M -Xss1M -XX:MaxPermSize=192M -XX:+UseParallelGC" />
192+
192193
<!-- to find max heap usage: -Xaprof ; currently at 980M for locker.comp -->
193-
<echo message="Using ANT_OPTS: ${env.ANT_OPTS}" />
194+
<echo message="Forking with JVM opts: ${env.ANT_OPTS} ${jvm.opts}" />
194195

195196
<property
196197
name="scalacfork.jvmargs"
197-
value="${env.ANT_OPTS}"/>
198+
value="${env.ANT_OPTS} ${jvm.opts}"/>
198199

199200
<!-- ===========================================================================
200201
INITIALISATION
@@ -255,7 +256,8 @@ INITIALISATION
255256

256257
<!-- Local libs (developer use.) -->
257258
<mkdir dir="${lib-extra.dir}"/>
258-
<path id="lib.dir.extra">
259+
260+
<path id="lib.extra">
259261
<!-- needs ant 1.7.1 -->
260262
<!-- <fileset dir="${lib-extra.dir}" erroronmissingdir="false"> -->
261263
<fileset dir="${lib-extra.dir}">
@@ -271,7 +273,7 @@ INITIALISATION
271273
<include name="forkjoin.jar"/>
272274
</fileset>
273275
<pathelement location="${ant.jar}"/>
274-
<path refid="lib.dir.extra"/>
276+
<path refid="lib.extra"/>
275277
</path>
276278

277279
<!-- Define tasks that can be run with Starr -->
@@ -284,12 +286,12 @@ INITIALISATION
284286
<path id="quick.compilation.path">
285287
<pathelement location="${build-quick.dir}/classes/library"/>
286288
<pathelement location="${lib.dir}/forkjoin.jar"/>
287-
<path refid="lib.dir.extra"/>
289+
<path refid="lib.extra"/>
288290
</path>
289291
<path id="strap.compilation.path">
290292
<pathelement location="${build-strap.dir}/classes/library"/>
291293
<pathelement location="${lib.dir}/forkjoin.jar"/>
292-
<path refid="lib.dir.extra"/>
294+
<path refid="lib.extra"/>
293295
</path>
294296
<taskdef resource="scala/tools/ant/sabbus/antlib.xml" classpathref="starr.classpath"/>
295297
</target>
@@ -872,6 +874,11 @@ PACKED QUICK BUILD (PACK)
872874
<zipfileset dirmode="755" filemode="644" src="${msil.jar}"/>
873875
</jar>
874876
<copy file="${jline.jar}" toDir="${build-pack.dir}/lib"/>
877+
<copy todir="${build-pack.dir}/lib">
878+
<fileset dir="${lib-extra.dir}">
879+
<include name="**/*.jar"/>
880+
</fileset>
881+
</copy>
875882
</target>
876883

877884
<target name="pack.pre-plugins" depends="pack.comp">
@@ -975,6 +982,7 @@ PACKED QUICK BUILD (PACK)
975982
<pathelement location="${build-pack.dir}/lib/scalap.jar"/>
976983
<pathelement location="${ant.jar}"/>
977984
<pathelement location="${jline.jar}"/>
985+
<path refid="lib.extra"/>
978986
</path>
979987
<taskdef resource="scala/tools/ant/antlib.xml" classpathref="pack.classpath"/>
980988
<taskdef resource="scala/tools/partest/antlib.xml" classpathref="pack.classpath"/>
@@ -1906,6 +1914,50 @@ POSITIONS
19061914
MISCELLANEOUS
19071915
============================================================================ -->
19081916

1917+
<target name="yourkit.init">
1918+
<property name="yourkit.home" value="/Applications/YourKit.app"/>
1919+
<property name="yourkit.api.jar" value="${yourkit.home}/lib/yjp-controller-api-redist.jar"/>
1920+
<property name="yourkit.agent" value="${yourkit.home}/bin/mac/libyjpagent.jnilib"/>
1921+
<property name="yourkit.jvm.opts" value="-agentpath:${yourkit.agent}"/>
1922+
<property name="yourkit.scalac.opts" value="-Yprofile:all"/>
1923+
</target>
1924+
1925+
<!-- Builds yourkit wrapper/jar and copies into lib/extra. -->
1926+
<target name="yourkit.build" depends="locker.done,yourkit.init">
1927+
<copy file="${yourkit.api.jar}" todir="${lib-extra.dir}"/>
1928+
<property name="yourkit.build.dir" value="${build-quick.dir}/classes/yourkit"/>
1929+
<mkdir dir="${yourkit.build.dir}"/>
1930+
1931+
<scalacfork
1932+
destdir="${yourkit.build.dir}"
1933+
compilerpathref="locker.classpath"
1934+
params="${scalac.args.all}"
1935+
srcdir="${src.dir}/yourkit"
1936+
jvmargs="${scalacfork.jvmargs}">
1937+
<include name="**/*.scala"/>
1938+
<compilationpath>
1939+
<path refid="locker.classpath"/>
1940+
</compilationpath>
1941+
</scalacfork>
1942+
<jar destfile="${lib-extra.dir}/scalac-yourkit.jar">
1943+
<fileset dir="${yourkit.build.dir}"/>
1944+
</jar>
1945+
</target>
1946+
1947+
<!-- Builds quick.lib/comp with profiling enabled. -->
1948+
<target name="yourkit.run" depends="yourkit.build">
1949+
<antcall target="clean"/>
1950+
<ant target="quick.lib" inheritall="false" inheritrefs="false">
1951+
<property name="jvm.opts" value="${yourkit.jvm.opts}"/>
1952+
<property name="scalac.args" value="${yourkit.scalac.opts}"/>
1953+
</ant>
1954+
<ant target="quick.comp" inheritall="false" inheritrefs="false">
1955+
<property name="jvm.opts" value="${yourkit.jvm.opts}"/>
1956+
<property name="scalac.args" value="${yourkit.scalac.opts}"/>
1957+
</ant>
1958+
<antcall target="build"/>
1959+
</target>
1960+
19091961
<target name="graph.init">
19101962
<taskdef name="vizant" classname="vizant.Vizant" classpath="${lib-ant.dir}/vizant.jar"/>
19111963
</target>

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundE
99
import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException }
1010
import compat.Platform.currentTime
1111

12+
import scala.tools.util.Profiling
1213
import scala.collection.{ mutable, immutable }
1314
import io.{ SourceReader, AbstractFile, Path }
1415
import reporters.{ Reporter, ConsoleReporter }
@@ -232,6 +233,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
232233
def logClasspath = settings.Ylogcp.value
233234
def printLate = settings.printLate.value
234235
def printStats = settings.Ystatistics.value
236+
def profileClass = settings.YprofileClass.value
235237
def richExes = settings.YrichExes.value
236238
def showTrees = settings.Xshowtrees.value
237239
def target = settings.target.value
@@ -242,14 +244,16 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
242244
def declsOnly = false
243245

244246
/** Flags as applied to the current or previous phase */
245-
def browsePhase = isActive(settings.browse)
246-
def checkPhase = wasActive(settings.check)
247-
def logPhase = isActive(settings.log)
248-
def printPhase = isActive(settings.Xprint)
249-
def showPhase = isActive(settings.Yshow)
247+
def browsePhase = isActive(settings.browse)
248+
def checkPhase = wasActive(settings.check)
249+
def logPhase = isActive(settings.log)
250+
def printPhase = isActive(settings.Xprint)
251+
def showPhase = isActive(settings.Yshow)
252+
def profilePhase = isActive(settings.Yprofile) && !profileAll
250253

251254
/** Derived values */
252255
def showNames = List(showClass, showObject).flatten
256+
def profileAll = settings.Yprofile.doAllPhases
253257
def jvm = target startsWith "jvm"
254258
def msil = target == "msil"
255259
def verboseDebug = debug && verbose
@@ -818,6 +822,9 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
818822
private def showMembers() =
819823
opt.showNames foreach (x => showDef(x, opt.declsOnly, globalPhase))
820824

825+
// If -Yprofile isn't given this will never be triggered.
826+
lazy val profiler = Class.forName(opt.profileClass).newInstance().asInstanceOf[Profiling]
827+
821828
/** Compile list of source files */
822829
def compileSources(_sources: List[SourceFile]) {
823830
val depSources = dependencyAnalysis.calculateFiles(_sources.distinct) // bug #1268, scalac confused by duplicated filenames
@@ -830,10 +837,20 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
830837
for (source <- sources) addUnit(new CompilationUnit(source))
831838

832839
globalPhase = firstPhase
840+
841+
if (opt.profileAll) {
842+
inform("starting CPU profiling on compilation run")
843+
profiler.startProfiling()
844+
}
833845
while (globalPhase != terminalPhase && !reporter.hasErrors) {
834846
val startTime = currentTime
835847
phase = globalPhase
836-
globalPhase.run
848+
849+
if (opt.profilePhase) {
850+
inform("starting CPU profiling on phase " + globalPhase.name)
851+
profiler profile globalPhase.run
852+
}
853+
else globalPhase.run
837854

838855
// write icode to *.icode files
839856
if (opt.writeICode && (runIsAt(icodePhase) || opt.printPhase && runIsPast(icodePhase)))
@@ -866,6 +883,10 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
866883

867884
advancePhase
868885
}
886+
if (opt.profileAll) {
887+
profiler.stopProfiling()
888+
profiler.captureSnapshot()
889+
}
869890

870891
// If no phase was specified for -Xshow-class/object, show it now.
871892
if (settings.Yshow.isDefault)

src/compiler/scala/tools/nsc/io/package.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,13 @@ package object io {
1515
def runnableFn(f: () => Unit): Runnable = runnable(f())
1616
def callableFn[T](f: () => T): Callable[T] = callable(f())
1717
def spawnFn[T](f: () => T): Future[T] = spawn(f())
18+
19+
// Create, start, and return a background thread
20+
// If isDaemon is true, it is marked as daemon (and will not interfere with JVM shutdown)
21+
def daemonize(isDaemon: Boolean)(body: => Unit): Thread = {
22+
val thread = new Thread(runnable(body))
23+
thread setDaemon isDaemon
24+
thread.start
25+
thread
26+
}
1827
}

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings {
122122
val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java")
123123
val noimports = BooleanSetting ("-Yno-imports", "Compile without any implicit imports")
124124
val nopredefs = BooleanSetting ("-Yno-predefs", "Compile without any implicit predefined values")
125+
val Yprofile = PhasesSetting ("-Yprofile", "Profile the given phase. Needs yjpagent to run.")
126+
val YprofileClass = StringSetting ("-Yprofile-class", "class", "Name of profiler class", "scala.tools.util.YourkitProfiling")
125127
val Yrecursion = IntSetting ("-Yrecursion", "Recursion depth used when locking symbols", 0, Some(0, Int.MaxValue), (_: String) => None)
126128
val selfInAnnots = BooleanSetting ("-Yself-in-annots", "Include a \"self\" identifier inside of annotations")
127129
val Xshowtrees = BooleanSetting ("-Yshow-trees", "Show detailed trees when used in connection with -Xprint:<phase>")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* NSC -- new Scala compiler
2+
* Copyright 2005-2010 LAMP/EPFL
3+
* @author Paul Phillips
4+
*/
5+
6+
package scala.tools
7+
package util
8+
9+
/** This is a (very) minimal stub for profiling, the purpose
10+
* of which is making it possible to integrate profiling hooks in
11+
* the compiler without creating a dependency on any particular
12+
* profiler. You can specify a profiler class (which must be an
13+
* instance of this class) like so:
14+
*
15+
* // or -Yprofile:phase to profile individual phases
16+
* scalac -Yprofile-class your.profiler.Class -Yprofile:all <files>
17+
*
18+
*/
19+
abstract class Profiling {
20+
def isActive: Boolean
21+
def startProfiling(): Unit
22+
def stopProfiling(): Unit
23+
def captureSnapshot(): Unit
24+
25+
def profile[T](body: => T): T = {
26+
startProfiling()
27+
val result = body
28+
stopProfiling()
29+
captureSnapshot()
30+
result
31+
}
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package scala.tools
2+
package util
3+
4+
import com.yourkit.api._
5+
import nsc.io._
6+
7+
class YourkitProfiling extends Profiling {
8+
@volatile private var active = false
9+
private var recordAllocation = false
10+
lazy val controller = new Controller
11+
12+
def startProfiling(): Unit = {
13+
if (isActive)
14+
return
15+
16+
active = true
17+
daemonize(true) {
18+
controller.startCPUProfiling(ProfilingModes.CPU_SAMPLING, Controller.DEFAULT_FILTERS)
19+
if (recordAllocation)
20+
controller.startAllocationRecording(true, 100, false, 0)
21+
}
22+
}
23+
24+
def captureSnapshot() =
25+
daemonize(false)(controller.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP))
26+
27+
def stopProfiling() = {
28+
if (recordAllocation)
29+
controller.stopAllocationRecording()
30+
31+
controller.stopCPUProfiling()
32+
active = false
33+
}
34+
35+
def isActive = active
36+
}

0 commit comments

Comments
 (0)