Skip to content

Commit da73760

Browse files
committed
Emit detailed compiler trace under -Yprofile
Suitable for viewing directly in chrome://tracing, or manipulation with the underlying tooling at: https://github.com/catapult-project/catapult/tree/master/tracing - Time spent completing lazy types is excluded from the callsite, and instead attributed to the symbol being completed itself. - The top-most implicit search is tracked. - macro expansions are tracked the trace
1 parent 36a614b commit da73760

File tree

8 files changed

+413
-137
lines changed

8 files changed

+413
-137
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,10 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
438438
currentRun.informUnitStarting(this, unit)
439439
val unit0 = currentUnit
440440
currentRun.currentUnit = unit
441+
val beforeSnap = currentRun.profiler.beforeUnit(phase, unit.source.file)
441442
try apply(unit)
442443
finally {
444+
currentRun.profiler.afterUnit(phase, unit.source.file, beforeSnap)
443445
currentRun.currentUnit = unit0
444446
currentRun.advanceUnit()
445447
}
@@ -1100,6 +1102,9 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
11001102

11011103
def newJavaUnitParser(unit: CompilationUnit): JavaUnitParser = new JavaUnitParser(unit)
11021104

1105+
override protected[scala] def currentRunProfilerBeforeCompletion(root: Symbol): Unit = currentRun.profiler.beforeCompletion(root)
1106+
override protected[scala] def currentRunProfilerAfterCompletion(root: Symbol): Unit = currentRun.profiler.afterCompletion(root)
1107+
11031108
/** A Run is a single execution of the compiler on a set of units.
11041109
*/
11051110
class Run extends RunContextApi with RunReporting with RunParsing {
@@ -1448,7 +1453,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
14481453
private final val GlobalPhaseName = "global (synthetic)"
14491454
protected final val totalCompileTime = statistics.newTimer("#total compile time", GlobalPhaseName)
14501455

1451-
def compileUnits(units: List[CompilationUnit], fromPhase: Phase): Unit = compileUnitsInternal(units,fromPhase)
1456+
def compileUnits(units: List[CompilationUnit], fromPhase: Phase): Unit = compileUnitsInternal(units,fromPhase)
14521457
private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) {
14531458
units foreach addUnit
14541459
reporter.reset()

src/compiler/scala/tools/nsc/profile/Profiler.scala

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ package scala.tools.nsc.profile
22

33
import java.io.{FileWriter, PrintWriter}
44
import java.lang.management.ManagementFactory
5+
import java.nio.file.Files
56
import java.util.ServiceLoader
67
import java.util.concurrent.TimeUnit
78
import java.util.concurrent.atomic.AtomicInteger
9+
810
import javax.management.openmbean.CompositeData
911
import javax.management.{Notification, NotificationEmitter, NotificationListener}
1012

11-
import scala.tools.nsc.{Phase, Settings}
13+
import scala.collection.mutable
14+
import scala.collection.mutable.ArrayBuffer
15+
import scala.reflect.internal.util.ChromeTrace
16+
import scala.reflect.io.{AbstractFile, File}
17+
import scala.tools.nsc.{Global, Phase, Settings}
1218

1319
object Profiler {
1420
def apply(settings: Settings):Profiler =
@@ -20,13 +26,15 @@ object Profiler {
2026
new RealProfiler(reporter, settings)
2127
}
2228

23-
private[profile] val emptySnap = ProfileSnap(0, "", 0, 0, 0, 0, 0, 0)
29+
private[profile] val emptySnap = ProfileSnap(0, "", 0, 0, 0, 0, 0, 0, 0, 0)
30+
}
31+
case class GcEventData(pool:String, reportTimeNs: Long, gcStartMillis:Long, gcEndMillis:Long, durationMillis: Long, name:String, action:String, cause:String, threads:Long) {
32+
val endNanos = System.nanoTime()
2433
}
25-
case class GcEventData(pool:String, reportTimeNs: Long, gcStartMillis:Long, gcEndMillis:Long, name:String, action:String, cause:String, threads:Long)
2634

2735
case class ProfileSnap(threadId: Long, threadName: String, snapTimeNanos : Long,
28-
idleTimeNanos:Long, cpuTimeNanos: Long, userTimeNanos: Long,
29-
allocatedBytes:Long, heapBytes:Long) {
36+
idleTimeNanos:Long, cpuTimeNanos: Long, userTimeNanos: Long,
37+
allocatedBytes:Long, heapBytes:Long, totalClassesLoaded: Long, totalJITCompilationTime: Long) {
3038
def updateHeap(heapBytes:Long) = {
3139
copy(heapBytes = heapBytes)
3240
}
@@ -61,20 +69,38 @@ case class ProfileRange(start: ProfileSnap, end:ProfileSnap, phase:Phase, purpos
6169
def retainedHeapMB = toMegaBytes(end.heapBytes - start.heapBytes)
6270
}
6371

64-
sealed trait Profiler {
72+
sealed abstract class Profiler {
6573

6674
def finished(): Unit
6775

6876
def beforePhase(phase: Phase): ProfileSnap
6977

7078
def afterPhase(phase: Phase, profileBefore: ProfileSnap): Unit
79+
80+
def beforeUnit(phase: Phase, file: AbstractFile): ProfileSnap
81+
82+
def afterUnit(phase: Phase, file: AbstractFile, profileBefore: ProfileSnap): Unit
83+
84+
def beforeTypedImplDef(sym: Global#Symbol): Unit = ()
85+
def afterTypedImplDef(sym: Global#Symbol): Unit = ()
86+
87+
def beforeImplicitSearch(pt: Global#Symbol): Unit = ()
88+
def afterImplicitSearch(pt: Global#Symbol): Unit = ()
89+
90+
def beforeMacroExpansion(macroSym: Global#Symbol): Unit = ()
91+
def afterMacroExpansion(macroSym: Global#Symbol): Unit = ()
92+
93+
def beforeCompletion(root: Global#Symbol): Unit = ()
94+
def afterCompletion(root: Global#Symbol): Unit = ()
7195
}
7296
private [profile] object NoOpProfiler extends Profiler {
7397

7498
override def beforePhase(phase: Phase): ProfileSnap = Profiler.emptySnap
7599

76100
override def afterPhase(phase: Phase, profileBefore: ProfileSnap): Unit = ()
77101

102+
override def beforeUnit(phase: Phase, file: AbstractFile): ProfileSnap = Profiler.emptySnap
103+
override def afterUnit(phase: Phase, file: AbstractFile, profileBefore: ProfileSnap): Unit = ()
78104
override def finished(): Unit = ()
79105
}
80106
private [profile] object RealProfiler {
@@ -91,6 +117,14 @@ private [profile] object RealProfiler {
91117
}
92118

93119
private [profile] class RealProfiler(reporter : ProfileReporter, val settings: Settings) extends Profiler with NotificationListener {
120+
121+
val tracePath = {
122+
if (settings.YprofileDestination.isSetByUser) new File(new java.io.File(settings.YprofileDestination.value)).changeExtension("trace").jfile.toPath
123+
else Files.createTempFile("scalac-", ".trace")
124+
}
125+
private val chromeTrace = new ChromeTrace(tracePath)
126+
chromeTrace.traceAsyncEventStart("scalac", 0, "0")
127+
94128
def completeBackground(threadRange: ProfileRange): Unit = {
95129
reporter.reportBackground(this, threadRange)
96130
}
@@ -110,16 +144,18 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
110144
private[profile] def snapThread( idleTimeNanos:Long): ProfileSnap = {
111145
import RealProfiler._
112146
val current = Thread.currentThread()
113-
147+
val allocatedBytes = threadMx.getThreadAllocatedBytes(Thread.currentThread().getId)
114148
ProfileSnap(
115149
threadId = current.getId,
116150
threadName = current.getName,
117151
snapTimeNanos = System.nanoTime(),
118152
idleTimeNanos = idleTimeNanos,
119153
cpuTimeNanos = threadMx.getCurrentThreadCpuTime,
120154
userTimeNanos = threadMx.getCurrentThreadUserTime,
121-
allocatedBytes = threadMx.getThreadAllocatedBytes(Thread.currentThread().getId),
122-
heapBytes = readHeapUsage()
155+
allocatedBytes = allocatedBytes,
156+
heapBytes = readHeapUsage(),
157+
totalClassesLoaded = classLoaderMx.getTotalLoadedClassCount,
158+
totalJITCompilationTime = compileMx.getTotalCompilationTime
123159
)
124160
}
125161
private def readHeapUsage() = RealProfiler.memoryMx.getHeapMemoryUsage.getUsed
@@ -139,8 +175,19 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
139175
case gc =>
140176
}
141177
reporter.close(this)
178+
for (gcEvent <- gcEvents) {
179+
val durationNanos = TimeUnit.MILLISECONDS.toNanos(gcEvent.durationMillis)
180+
val startNanos = gcEvent.endNanos - durationNanos
181+
chromeTrace.traceDurationEvent(gcEvent.name, startNanos, durationNanos, GcThreadId)
182+
}
183+
chromeTrace.traceAsyncEventEnd("scalac", 0, "0")
184+
185+
println("Trace file: " + tracePath)
186+
chromeTrace.close()
142187
}
143188

189+
private val gcEvents = ArrayBuffer[GcEventData]()
190+
private val GcThreadId = 0
144191

145192
override def handleNotification(notification: Notification, handback: scala.Any): Unit = {
146193
import java.lang.{Long => jLong}
@@ -161,7 +208,9 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
161208
val startTime = info.get("startTime").asInstanceOf[jLong].longValue()
162209
val endTime = info.get("endTime").asInstanceOf[jLong].longValue()
163210
val threads = info.get("GcThreadCount").asInstanceOf[jInt].longValue()
164-
reporter.reportGc(GcEventData("", reportNs, startTime, endTime, name, action, cause, threads))
211+
val gcEvent = GcEventData("", reportNs, startTime, endTime, duration, name, action, cause, threads)
212+
gcEvents += gcEvent
213+
reporter.reportGc(gcEvent)
165214
}
166215
}
167216

@@ -177,22 +226,89 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
177226
doGC
178227
initialSnap.updateHeap(readHeapUsage())
179228
} else initialSnap
229+
chromeTrace.traceAsyncEventEnd(phase.name, 0, "0")
180230

181231
reporter.reportForeground(this, ProfileRange(snapBefore, finalSnap, phase, "", 0, Thread.currentThread))
182232
}
183233

234+
override def beforeUnit(phase: Phase, file: AbstractFile): ProfileSnap = {
235+
assert(mainThread eq Thread.currentThread())
236+
chromeTrace.traceAsyncEventStart(file.name, 0, "0")
237+
snapThread(0)
238+
}
239+
240+
override def afterUnit(phase: Phase, file: AbstractFile, snapBefore: ProfileSnap): Unit = {
241+
assert(mainThread eq Thread.currentThread())
242+
val initialSnap = snapThread(0)
243+
chromeTrace.traceAsyncEventEnd(file.name, 0, "0")
244+
chromeTrace.traceCounterEvent("allocBytes", "allocBytes", initialSnap.allocatedBytes - this.baseline.allocatedBytes)
245+
chromeTrace.traceCounterEvent("heapBytes", "heapBytes", initialSnap.heapBytes - this.baseline.heapBytes)
246+
chromeTrace.traceCounterEvent("classesLoaded", "classesLoaded", initialSnap.totalClassesLoaded - this.baseline.totalClassesLoaded)
247+
chromeTrace.traceCounterEvent("jitCompilationTime", "jitCompilationTime", initialSnap.totalJITCompilationTime - this.baseline.totalJITCompilationTime)
248+
chromeTrace.traceCounterEvent("userTime", "userTime", initialSnap.userTimeNanos - this.baseline.userTimeNanos)
249+
chromeTrace.traceCounterEvent("cpuTime", "cpuTime", initialSnap.cpuTimeNanos - this.baseline.cpuTimeNanos)
250+
chromeTrace.traceCounterEvent("idleTime", "idleTime", initialSnap.idleTimeNanos - this.baseline.idleTimeNanos)
251+
}
252+
253+
private var baseline: ProfileSnap = _
254+
184255
override def beforePhase(phase: Phase): ProfileSnap = {
185256
assert(mainThread eq Thread.currentThread())
257+
chromeTrace.traceAsyncEventStart(phase.name, 0, "0")
186258
if (settings.YprofileRunGcBetweenPhases.containsPhase(phase))
187259
doGC
188260
if (settings.YprofileExternalTool.containsPhase(phase)) {
189261
println("Profile hook start")
190262
ExternalToolHook.before()
191263
}
192264
active foreach {_.beforePhase(phase)}
193-
snapThread(0)
265+
val snap = snapThread(0)
266+
if (phase.prev eq null) baseline = snap
267+
snap
268+
}
269+
270+
private val completingStack = new mutable.ArrayStack[Global#Symbol]
271+
private val stringsOf = (0 to 128).map(_.toString).toArray
272+
def intToString(i: Int) = if (i < 128) stringsOf(i) else i.toString
273+
274+
import scala.reflect.internal.util.ChromeTrace
275+
private def traceId = if (completingStack.isEmpty) 0 else completingStack.top.id
276+
override def beforeTypedImplDef(sym: Global#Symbol): Unit = {
277+
chromeTrace.traceAsyncEventStart(sym.rawname.toString, traceId, intToString(completingStack.length))
278+
}
279+
override def afterTypedImplDef(sym: Global#Symbol): Unit = {
280+
chromeTrace.traceAsyncEventEnd(sym.rawname.toString, traceId, intToString(completingStack.length))
281+
}
282+
283+
override def beforeImplicitSearch(pt: Global#Symbol): Unit = {
284+
chromeTrace.traceAsyncEventStart("<implicit> " + pt.rawname, traceId, intToString(completingStack.length))
194285
}
195286

287+
override def afterImplicitSearch(pt: Global#Symbol): Unit = {
288+
chromeTrace.traceAsyncEventEnd("<implicit> " + pt.rawname, traceId, intToString(completingStack.length))
289+
}
290+
291+
override def beforeMacroExpansion(macroSym: Global#Symbol): Unit = {
292+
chromeTrace.traceAsyncEventStart("<macro> " + macroSym.rawname, traceId, intToString(completingStack.length))
293+
}
294+
295+
override def afterMacroExpansion(macroSym: Global#Symbol): Unit = {
296+
chromeTrace.traceAsyncEventEnd("<macro> " + macroSym.rawname, traceId, intToString(completingStack.length))
297+
}
298+
299+
override def beforeCompletion(root: Global#Symbol): Unit = {
300+
chromeTrace.traceAsyncEventStart("<wait>", traceId, intToString(completingStack.length))
301+
completingStack.push(root)
302+
chromeTrace.traceAsyncEventStart("<completion>", traceId, intToString(completingStack.length))
303+
chromeTrace.traceAsyncEventStart(root.rawname.toString, traceId, intToString(completingStack.length))
304+
}
305+
306+
override def afterCompletion(root: Global#Symbol): Unit = {
307+
chromeTrace.traceAsyncEventEnd(root.rawname.toString, traceId, intToString(completingStack.length))
308+
chromeTrace.traceAsyncEventEnd("<completion>", traceId, intToString(completingStack.length))
309+
completingStack.pop()
310+
chromeTrace.traceAsyncEventEnd("<wait>", traceId, intToString(completingStack.length))
311+
}
196312
}
197313

198314
object EventType extends Enumeration {

src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ abstract class SymbolLoaders {
214214
}
215215

216216
override def complete(root: Symbol) {
217+
currentRunProfilerBeforeCompletion(root)
217218
try {
218219
val start = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
219220
val currentphase = phase
@@ -228,6 +229,8 @@ abstract class SymbolLoaders {
228229
case ex @ (_: IOException | _: MissingRequirementError) =>
229230
ok = false
230231
signalError(root, ex)
232+
} finally {
233+
currentRunProfilerAfterCompletion(root)
231234
}
232235
initRoot(root)
233236
if (!root.isPackageClass) initRoot(root.companionSymbol)

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ trait Implicits {
8080
* @return A search result
8181
*/
8282
def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
83+
val ptSym = pt.typeSymbol
84+
currentRun.profiler.beforeImplicitSearch(ptSym)
85+
try {
86+
inferImplicit1(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, pos)
87+
} finally {
88+
currentRun.profiler.afterImplicitSearch(ptSym)
89+
}
90+
}
91+
92+
private def inferImplicit1(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
8393
// Note that the isInvalidConversionTarget seems to make a lot more sense right here, before all the
8494
// work is performed, than at the point where it presently exists.
8595
val shouldPrint = printTypings && !context.undetparams.isEmpty

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,15 @@ trait Macros extends MacroRuntimes with Traces with Helpers {
754754
/** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`.
755755
* @see DefMacroExpander
756756
*/
757-
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = pluginsMacroExpand(typer, expandee, mode, pt)
757+
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = {
758+
val macroSym = expandee.symbol
759+
currentRun.profiler.beforeMacroExpansion(macroSym)
760+
try {
761+
pluginsMacroExpand(typer, expandee, mode, pt)
762+
} finally {
763+
currentRun.profiler.afterMacroExpansion(macroSym)
764+
}
765+
}
758766

759767
/** Default implementation of `macroExpand`.
760768
* Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroExpand for more details)

0 commit comments

Comments
 (0)