Skip to content

Commit 2fb4db6

Browse files
WojciechMazurtgodzik
authored andcommitted
Scala 2 forwardport: -Yprofile-trace (scala#19897)
* Scala 2 tracing profiler backport from scala/scala#7364 extended with more Scala 3 idiomatic syntax based on inlined methods * Fixes the `context.profiler` which could have been `null`, now it's initially a NoOp Profiler * Check dependencies of `-Yprofile-enabled` dependent tasks, now we get an error if `-Yprofile-trace` is set without `-Yprofile-enabled`
1 parent c1c2fbf commit 2fb4db6

File tree

18 files changed

+863
-81
lines changed

18 files changed

+863
-81
lines changed

Diff for: compiler/src/dotty/tools/dotc/Compiler.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@ class Compiler {
152152
List(new GenBCode) :: // Generate JVM bytecode
153153
Nil
154154

155-
var runId: Int = 1
155+
// TODO: Initially 0, so that the first nextRunId call would return InitialRunId == 1
156+
// Changing the initial runId from 1 to 0 makes the scala2-library-bootstrap fail to compile,
157+
// when the underlying issue is fixed, please update dotc.profiler.RealProfiler.chromeTrace logic
158+
private var runId: Int = 1
156159
def nextRunId: Int = {
157160
runId += 1; runId
158161
}

Diff for: compiler/src/dotty/tools/dotc/Run.scala

+3-4
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,9 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
311311
if phaseWillRun then
312312
Stats.trackTime(s"phase time ms/$phase") {
313313
val start = System.currentTimeMillis
314-
val profileBefore = profiler.beforePhase(phase)
315-
try units = phase.runOn(units)
316-
catch case _: InterruptedException => cancelInterrupted()
317-
profiler.afterPhase(phase, profileBefore)
314+
profiler.onPhase(phase):
315+
try units = phase.runOn(units)
316+
catch case _: InterruptedException => cancelInterrupted()
318317
if (ctx.settings.Xprint.value.containsPhase(phase))
319318
for (unit <- units)
320319
lastPrintedTree =

Diff for: compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+5-8
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private sealed trait WarningSettings:
174174
name = "-Wunused",
175175
helpArg = "warning",
176176
descr = "Enable or disable specific `unused` warnings",
177-
choices = List(
177+
choices = List(
178178
ChoiceWithHelp("nowarn", ""),
179179
ChoiceWithHelp("all", ""),
180180
ChoiceWithHelp(
@@ -422,14 +422,11 @@ private sealed trait YSettings:
422422
val YfromTastyIgnoreList: Setting[List[String]] = MultiStringSetting("-Yfrom-tasty-ignore-list", "file", "List of `tasty` files in jar files that will not be loaded when using -from-tasty.")
423423
val YnoExperimental: Setting[Boolean] = BooleanSetting("-Yno-experimental", "Disable experimental language features.")
424424
val YlegacyLazyVals: Setting[Boolean] = BooleanSetting("-Ylegacy-lazy-vals", "Use legacy (pre 3.3.0) implementation of lazy vals.")
425-
426425
val YprofileEnabled: Setting[Boolean] = BooleanSetting("-Yprofile-enabled", "Enable profiling.")
427-
val YprofileDestination: Setting[String] = StringSetting("-Yprofile-destination", "file", "Where to send profiling output - specify a file, default is to the console.", "")
428-
//.withPostSetHook( _ => YprofileEnabled.value = true )
429-
val YprofileExternalTool: Setting[List[String]] = PhasesSetting("-Yprofile-external-tool", "Enable profiling for a phase using an external tool hook. Generally only useful for a single phase.", "typer")
430-
//.withPostSetHook( _ => YprofileEnabled.value = true )
431-
val YprofileRunGcBetweenPhases: Setting[List[String]] = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_")
432-
//.withPostSetHook( _ => YprofileEnabled.value = true )
426+
val YprofileDestination: Setting[String] = StringSetting("-Yprofile-destination", "file", "Where to send profiling output - specify a file, default is to the console.", "", depends = List(YprofileEnabled -> true))
427+
val YprofileExternalTool: Setting[List[String]] = PhasesSetting("-Yprofile-external-tool", "Enable profiling for a phase using an external tool hook. Generally only useful for a single phase.", "typer", depends = List(YprofileEnabled -> true))
428+
val YprofileRunGcBetweenPhases: Setting[List[String]] = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_", depends = List(YprofileEnabled -> true))
429+
val YprofileTrace: Setting[String] = StringSetting("-Yprofile-trace", "file", s"Capture trace of compilation in JSON Chrome Trace format to the specified file. This option requires ${YprofileEnabled.name}. The output file can be visualized using https://ui.perfetto.dev/.", "", depends = List(YprofileEnabled -> true))
433430

434431
// Experimental language features
435432
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Disable kind polymorphism.")

Diff for: compiler/src/dotty/tools/dotc/config/Settings.scala

+19-6
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,20 @@ object Settings:
5151

5252
def warn(msg: String): Settings.ArgsSummary =
5353
ArgsSummary(sstate, arguments.tail, errors, warnings :+ msg)
54-
}
54+
55+
def deprecated(msg: String, extraArgs: List[String] = Nil): Settings.ArgsSummary =
56+
ArgsSummary(sstate, extraArgs ++ arguments.tail, errors, warnings :+ msg)
57+
58+
@unshared
59+
val settingCharacters = "[a-zA-Z0-9_\\-]*".r
60+
def validateSettingString(name: String): Unit =
61+
assert(settingCharacters.matches(name), s"Setting string $name contains invalid characters")
62+
63+
/** List of setting-value pairs that are required for another setting to be valid.
64+
* For example, `s = Setting(..., depends = List(YprofileEnabled -> true))`
65+
* means that `s` requires `YprofileEnabled` to be set to `true`.
66+
*/
67+
type SettingDependencies = List[(Setting[?], Any)]
5568

5669
case class Setting[T: ClassTag] private[Settings] (
5770
name: String,
@@ -61,7 +74,7 @@ object Settings:
6174
choices: Option[Seq[?]] = None,
6275
prefix: String = "",
6376
aliases: List[String] = Nil,
64-
depends: List[(Setting[?], Any)] = Nil,
77+
depends: SettingDependencies = Nil,
6578
ignoreInvalidArgs: Boolean = false,
6679
propertyClass: Option[Class[?]] = None)(private[Settings] val idx: Int) {
6780

@@ -284,8 +297,8 @@ object Settings:
284297
def BooleanSetting(name: String, descr: String, initialValue: Boolean = false, aliases: List[String] = Nil): Setting[Boolean] =
285298
publish(Setting(name, descr, initialValue, aliases = aliases))
286299

287-
def StringSetting(name: String, helpArg: String, descr: String, default: String, aliases: List[String] = Nil): Setting[String] =
288-
publish(Setting(name, descr, default, helpArg, aliases = aliases))
300+
def StringSetting(name: String, helpArg: String, descr: String, default: String, aliases: List[String] = Nil, depends: SettingDependencies = Nil): Setting[String] =
301+
publish(Setting(name, descr, default, helpArg, aliases = aliases, depends = depends))
289302

290303
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, aliases: List[String] = Nil): Setting[String] =
291304
publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases))
@@ -314,8 +327,8 @@ object Settings:
314327
def PathSetting(name: String, descr: String, default: String, aliases: List[String] = Nil): Setting[String] =
315328
publish(Setting(name, descr, default, aliases = aliases))
316329

317-
def PhasesSetting(name: String, descr: String, default: String = "", aliases: List[String] = Nil): Setting[List[String]] =
318-
publish(Setting(name, descr, if (default.isEmpty) Nil else List(default), aliases = aliases))
330+
def PhasesSetting(name: String, descr: String, default: String = "", aliases: List[String] = Nil, depends: SettingDependencies = Nil): Setting[List[String]] =
331+
publish(Setting(name, descr, if (default.isEmpty) Nil else List(default), aliases = aliases, depends = depends))
319332

320333
def PrefixSetting(name: String, pre: String, descr: String): Setting[List[String]] =
321334
publish(Setting(name, descr, Nil, prefix = pre))

Diff for: compiler/src/dotty/tools/dotc/core/Contexts.scala

+1
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ object Contexts {
747747
.updated(settingsStateLoc, settingsGroup.defaultState)
748748
.updated(notNullInfosLoc, Nil)
749749
.updated(compilationUnitLoc, NoCompilationUnit)
750+
.updated(profilerLoc, Profiler.NoOp)
750751
c._searchHistory = new SearchRoot
751752
c._gadtState = GadtState(GadtConstraint.empty)
752753
c

Diff for: compiler/src/dotty/tools/dotc/core/Phases.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,11 @@ object Phases {
330330
/** @pre `isRunnable` returns true */
331331
def runOn(units: List[CompilationUnit])(using runCtx: Context): List[CompilationUnit] =
332332
val buf = List.newBuilder[CompilationUnit]
333-
for unit <- units do
333+
334+
// Test that we are in a state where we need to check if the phase should be skipped for a java file,
335+
// this prevents checking the expensive `unit.typedAsJava` unnecessarily.
336+
val doCheckJava = skipIfJava && !isAfterLastJavaPhase
337+
for unit <- units do ctx.profiler.onUnit(this, unit):
334338
given unitCtx: Context = runCtx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
335339
if ctx.run.enterUnit(unit) then
336340
try

Diff for: compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import java.nio.channels.ClosedByInterruptException
77

88
import scala.util.control.NonFatal
99

10-
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
10+
import dotty.tools.dotc.classpath.FileUtils.{hasTastyExtension, hasBetastyExtension}
11+
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile, NoAbstractFile }
1112
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
1213

1314
import Contexts.*, Symbols.*, Flags.*, SymDenotations.*, Types.*, Scopes.*, Names.*
@@ -333,7 +334,15 @@ abstract class SymbolLoader extends LazyType { self =>
333334
def description(using Context): String = s"proxy to ${self.description}"
334335
}
335336

336-
override def complete(root: SymDenotation)(using Context): Unit = {
337+
private inline def profileCompletion[T](root: SymDenotation)(inline body: T)(using Context): T = {
338+
val sym = root.symbol
339+
def associatedFile = root.symbol.associatedFile match
340+
case file: AbstractFile => file
341+
case _ => NoAbstractFile
342+
ctx.profiler.onCompletion(sym, associatedFile)(body)
343+
}
344+
345+
override def complete(root: SymDenotation)(using Context): Unit = profileCompletion(root) {
337346
def signalError(ex: Exception): Unit = {
338347
if (ctx.debug) ex.printStackTrace()
339348
val msg = ex.getMessage()
+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Scala 2 compiler backport of https://github.com/scala/scala/pull/7364
2+
/*
3+
* Scala (https://www.scala-lang.org)
4+
*
5+
* Copyright EPFL and Lightbend, Inc.
6+
*
7+
* Licensed under Apache License 2.0
8+
* (http://www.apache.org/licenses/LICENSE-2.0).
9+
*
10+
* See the NOTICE file distributed with this work for
11+
* additional information regarding copyright ownership.
12+
*/
13+
14+
package dotty.tools.dotc.profile
15+
16+
import scala.language.unsafeNulls
17+
18+
import java.io.Closeable
19+
import java.lang.management.ManagementFactory
20+
import java.nio.file.{Files, Path}
21+
import java.util
22+
import java.util.concurrent.TimeUnit
23+
24+
import scala.collection.mutable
25+
26+
object ChromeTrace {
27+
private object EventType {
28+
final val Start = "B"
29+
final val Instant = "I"
30+
final val End = "E"
31+
final val Complete = "X"
32+
33+
final val Counter = "C"
34+
35+
final val AsyncStart = "b"
36+
final val AsyncInstant = "n"
37+
final val AsyncEnd = "e"
38+
}
39+
}
40+
41+
/** Allows writing a subset of captrue traces based on https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#
42+
* Can be visualized using https://ui.perfetto.dev/, Chrome's about://tracing (outdated) or the tooling in https://www.google.com.au/search?q=catapult+tracing&oq=catapult+tracing+&aqs=chrome..69i57.3974j0j4&sourceid=chrome&ie=UTF-8 */
43+
final class ChromeTrace(f: Path) extends Closeable {
44+
import ChromeTrace.EventType
45+
private val traceWriter = FileUtils.newAsyncBufferedWriter(f)
46+
private val context = mutable.Stack[JsonContext](TopContext)
47+
private val tidCache = new ThreadLocal[String]() {
48+
override def initialValue(): String = "%05d".format(Thread.currentThread().getId())
49+
}
50+
objStart()
51+
fld("traceEvents")
52+
context.push(ValueContext)
53+
arrStart()
54+
traceWriter.newLine()
55+
56+
private val pid = ManagementFactory.getRuntimeMXBean().getName().replaceAll("@.*", "")
57+
58+
override def close(): Unit = {
59+
arrEnd()
60+
objEnd()
61+
context.pop()
62+
tidCache.remove()
63+
traceWriter.close()
64+
}
65+
66+
def traceDurationEvent(name: String, startNanos: Long, durationNanos: Long, tid: String = this.tid(), pidSuffix: String = ""): Unit = {
67+
val durationMicros = nanosToMicros(durationNanos)
68+
val startMicros = nanosToMicros(startNanos)
69+
objStart()
70+
str("cat", "scalac")
71+
str("name", name)
72+
str("ph", EventType.Complete)
73+
str("tid", tid)
74+
writePid(pidSuffix)
75+
lng("ts", startMicros)
76+
lng("dur", durationMicros)
77+
objEnd()
78+
traceWriter.newLine()
79+
}
80+
81+
private def writePid(pidSuffix: String) = {
82+
if (pidSuffix == "")
83+
str("pid", pid)
84+
else
85+
str2("pid", pid, "-", pidSuffix)
86+
}
87+
88+
def traceCounterEvent(name: String, counterName: String, count: Long, processWide: Boolean): Unit = {
89+
objStart()
90+
str("cat", "scalac")
91+
str("name", name)
92+
str("ph", EventType.Counter)
93+
str("tid", tid())
94+
writePid(pidSuffix = if (processWide) "" else tid())
95+
lng("ts", microTime())
96+
fld("args")
97+
objStart()
98+
lng(counterName, count)
99+
objEnd()
100+
objEnd()
101+
traceWriter.newLine()
102+
}
103+
104+
def traceDurationEventStart(cat: String, name: String, colour: String = "", pidSuffix: String = tid()): Unit = traceDurationEventStartEnd(EventType.Start, cat, name, colour, pidSuffix)
105+
def traceDurationEventEnd(cat: String, name: String, colour: String = "", pidSuffix: String = tid()): Unit = traceDurationEventStartEnd(EventType.End, cat, name, colour, pidSuffix)
106+
107+
private def traceDurationEventStartEnd(eventType: String, cat: String, name: String, colour: String, pidSuffix: String = ""): Unit = {
108+
objStart()
109+
str("cat", cat)
110+
str("name", name)
111+
str("ph", eventType)
112+
writePid(pidSuffix)
113+
str("tid", tid())
114+
lng("ts", microTime())
115+
if (colour != "") {
116+
str("cname", colour)
117+
}
118+
objEnd()
119+
traceWriter.newLine()
120+
}
121+
122+
private def tid(): String = tidCache.get()
123+
124+
private def nanosToMicros(t: Long): Long = TimeUnit.NANOSECONDS.toMicros(t)
125+
126+
private def microTime(): Long = nanosToMicros(System.nanoTime())
127+
128+
private sealed abstract class JsonContext
129+
private case class ArrayContext(var first: Boolean) extends JsonContext
130+
private case class ObjectContext(var first: Boolean) extends JsonContext
131+
private case object ValueContext extends JsonContext
132+
private case object TopContext extends JsonContext
133+
134+
private def str(name: String, value: String): Unit = {
135+
fld(name)
136+
traceWriter.write("\"")
137+
traceWriter.write(value) // This assumes no escaping is needed
138+
traceWriter.write("\"")
139+
}
140+
private def str2(name: String, value: String, valueContinued1: String, valueContinued2: String): Unit = {
141+
fld(name)
142+
traceWriter.write("\"")
143+
traceWriter.write(value) // This assumes no escaping is needed
144+
traceWriter.write(valueContinued1) // This assumes no escaping is needed
145+
traceWriter.write(valueContinued2) // This assumes no escaping is needed
146+
traceWriter.write("\"")
147+
}
148+
private def lng(name: String, value: Long): Unit = {
149+
fld(name)
150+
traceWriter.write(String.valueOf(value))
151+
traceWriter.write("")
152+
}
153+
private def objStart(): Unit = {
154+
context.top match {
155+
case ac @ ArrayContext(first) =>
156+
if (first) ac.first = false
157+
else traceWriter.write(",")
158+
case _ =>
159+
}
160+
context.push(ObjectContext(true))
161+
traceWriter.write("{")
162+
}
163+
private def objEnd(): Unit = {
164+
traceWriter.write("}")
165+
context.pop()
166+
}
167+
private def arrStart(): Unit = {
168+
traceWriter.write("[")
169+
context.push(ArrayContext(true))
170+
}
171+
private def arrEnd(): Unit = {
172+
traceWriter.write("]")
173+
context.pop()
174+
}
175+
176+
private def fld(name: String) = {
177+
val topContext = context.top
178+
topContext match {
179+
case oc @ ObjectContext(first) =>
180+
if (first) oc.first = false
181+
else traceWriter.write(",")
182+
case context =>
183+
throw new IllegalStateException("Wrong context: " + context)
184+
}
185+
traceWriter.write("\"")
186+
traceWriter.write(name)
187+
traceWriter.write("\"")
188+
traceWriter.write(":")
189+
}
190+
}

0 commit comments

Comments
 (0)