Skip to content

Commit 088eb2d

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 088eb2d

File tree

7 files changed

+407
-137
lines changed

7 files changed

+407
-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: 123 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)
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) {
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,17 @@ 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
123158
)
124159
}
125160
private def readHeapUsage() = RealProfiler.memoryMx.getHeapMemoryUsage.getUsed
@@ -139,8 +174,19 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
139174
case gc =>
140175
}
141176
reporter.close(this)
177+
for (gcEvent <- gcEvents) {
178+
val durationNanos = TimeUnit.MILLISECONDS.toNanos(gcEvent.durationMillis)
179+
val startNanos = gcEvent.endNanos - durationNanos
180+
chromeTrace.traceDurationEvent(gcEvent.name, startNanos, durationNanos, GcThreadId)
181+
}
182+
chromeTrace.traceAsyncEventEnd("scalac", 0, "0")
183+
184+
println("Trace file: " + tracePath)
185+
chromeTrace.close()
142186
}
143187

188+
private val gcEvents = ArrayBuffer[GcEventData]()
189+
private val GcThreadId = 0
144190

145191
override def handleNotification(notification: Notification, handback: scala.Any): Unit = {
146192
import java.lang.{Long => jLong}
@@ -161,7 +207,9 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
161207
val startTime = info.get("startTime").asInstanceOf[jLong].longValue()
162208
val endTime = info.get("endTime").asInstanceOf[jLong].longValue()
163209
val threads = info.get("GcThreadCount").asInstanceOf[jInt].longValue()
164-
reporter.reportGc(GcEventData("", reportNs, startTime, endTime, name, action, cause, threads))
210+
val gcEvent = GcEventData("", reportNs, startTime, endTime, duration, name, action, cause, threads)
211+
gcEvents += gcEvent
212+
reporter.reportGc(gcEvent)
165213
}
166214
}
167215

@@ -177,22 +225,86 @@ private [profile] class RealProfiler(reporter : ProfileReporter, val settings: S
177225
doGC
178226
initialSnap.updateHeap(readHeapUsage())
179227
} else initialSnap
228+
chromeTrace.traceAsyncEventEnd(phase.name, 0, "0")
180229

181230
reporter.reportForeground(this, ProfileRange(snapBefore, finalSnap, phase, "", 0, Thread.currentThread))
182231
}
183232

233+
override def beforeUnit(phase: Phase, file: AbstractFile): ProfileSnap = {
234+
assert(mainThread eq Thread.currentThread())
235+
snapThread(0)
236+
}
237+
238+
override def afterUnit(phase: Phase, file: AbstractFile, snapBefore: ProfileSnap): Unit = {
239+
assert(mainThread eq Thread.currentThread())
240+
val initialSnap = snapThread(0)
241+
chromeTrace.traceCounterEvent("allocBytes", "allocBytes", initialSnap.allocatedBytes - this.baseline.allocatedBytes)
242+
chromeTrace.traceCounterEvent("heapBytes", "heapBytes", initialSnap.heapBytes - this.baseline.heapBytes)
243+
chromeTrace.traceCounterEvent("classesLoaded", "classesLoaded", initialSnap.totalClassesLoaded - this.baseline.totalClassesLoaded)
244+
chromeTrace.traceCounterEvent("userTime", "userTime", initialSnap.userTimeNanos - this.baseline.userTimeNanos)
245+
chromeTrace.traceCounterEvent("cpuTime", "cpuTime", initialSnap.cpuTimeNanos - this.baseline.cpuTimeNanos)
246+
chromeTrace.traceCounterEvent("idleTime", "idleTime", initialSnap.idleTimeNanos - this.baseline.idleTimeNanos)
247+
}
248+
249+
private var baseline: ProfileSnap = _
250+
184251
override def beforePhase(phase: Phase): ProfileSnap = {
185252
assert(mainThread eq Thread.currentThread())
253+
chromeTrace.traceAsyncEventStart(phase.name, 0, "0")
186254
if (settings.YprofileRunGcBetweenPhases.containsPhase(phase))
187255
doGC
188256
if (settings.YprofileExternalTool.containsPhase(phase)) {
189257
println("Profile hook start")
190258
ExternalToolHook.before()
191259
}
192260
active foreach {_.beforePhase(phase)}
193-
snapThread(0)
261+
val snap = snapThread(0)
262+
if (phase.prev eq null) baseline = snap
263+
snap
264+
}
265+
266+
private val completingStack = new mutable.ArrayStack[Global#Symbol]
267+
private val stringsOf = (0 to 128).map(_.toString).toArray
268+
def intToString(i: Int) = if (i < 128) stringsOf(i) else i.toString
269+
270+
import scala.reflect.internal.util.ChromeTrace
271+
private def traceId = if (completingStack.isEmpty) 0 else completingStack.top.id
272+
override def beforeTypedImplDef(sym: Global#Symbol): Unit = {
273+
chromeTrace.traceAsyncEventStart(sym.rawname.toString, traceId, intToString(completingStack.length))
274+
}
275+
override def afterTypedImplDef(sym: Global#Symbol): Unit = {
276+
chromeTrace.traceAsyncEventEnd(sym.rawname.toString, traceId, intToString(completingStack.length))
277+
}
278+
279+
override def beforeImplicitSearch(pt: Global#Symbol): Unit = {
280+
chromeTrace.traceAsyncEventStart("<implicit> " + pt.rawname, traceId, intToString(completingStack.length))
194281
}
195282

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

198310
object EventType extends Enumeration {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ 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+
val tellProfiler = context.openImplicits.isEmpty
85+
if (tellProfiler) currentRun.profiler.beforeImplicitSearch(ptSym)
86+
try {
87+
inferImplicit1(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, pos)
88+
} finally {
89+
if (tellProfiler) currentRun.profiler.afterImplicitSearch(ptSym)
90+
}
91+
}
92+
93+
private def inferImplicit1(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
8394
// Note that the isInvalidConversionTarget seems to make a lot more sense right here, before all the
8495
// work is performed, than at the point where it presently exists.
8596
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)