Skip to content

Commit

Permalink
Backport -Yprofile-trace from Scala 2
Browse files Browse the repository at this point in the history
  • Loading branch information
WojciechMazur committed Mar 7, 2024
1 parent 7dc4d3d commit 3eeaa3d
Show file tree
Hide file tree
Showing 7 changed files with 511 additions and 15 deletions.
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ private sealed trait YSettings:
//.withPostSetHook( _ => YprofileEnabled.value = true )
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 *", "_")
//.withPostSetHook( _ => YprofileEnabled.value = true )
val YprofileTrace: Setting[String] = StringSetting("-Yprofile-trace", "file", "Capture trace of compilation in Chrome Trace format", "profile.trace")
//.withPostSetHook( _ => YprofileEnabled.value = true )

// Experimental language features
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Disable kind polymorphism.")
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ object Phases {
val doSkipJava = ctx.settings.YjavaTasty.value && this <= picklerPhase && skipIfJava
for unit <- units do
given unitCtx: Context = runCtx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
runCtx.profiler.beforeUnit(this, unit)
if ctx.run.enterUnit(unit) then
try
if doSkipJava && unit.typedAsJava then
Expand All @@ -355,7 +356,10 @@ object Phases {
catch case ex: Throwable if !ctx.run.enrichedErrorMessage =>
println(ctx.run.enrichErrorMessage(s"unhandled exception while running $phaseName on $unit"))
throw ex
finally ctx.run.advanceUnit()
finally
runCtx.profiler.afterUnit(this, unit)
ctx.run.advanceUnit()

buf += unitCtx.compilationUnit
end if
end for
Expand Down
190 changes: 190 additions & 0 deletions compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Scala 2 compiler backport of https://github.com/scala/scala/pull/7364
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package dotty.tools.dotc.profile

import scala.language.unsafeNulls

import java.io.Closeable
import java.lang.management.ManagementFactory
import java.nio.file.{Files, Path}
import java.util
import java.util.concurrent.TimeUnit

import scala.collection.mutable

object ChromeTrace {
private object EventType {
final val Start = "B"
final val Instant = "I"
final val End = "E"
final val Complete = "X"

final val Counter = "C"

final val AsyncStart = "b"
final val AsyncInstant = "n"
final val AsyncEnd = "e"
}
}

/** Allows writing a subset of https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#
* for use in Chrome's about://tracing 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 */
final class ChromeTrace(f: Path) extends Closeable {
import ChromeTrace.EventType
private val traceWriter = FileUtils.newAsyncBufferedWriter(f)
private val context = mutable.Stack[JsonContext](TopContext)
private val tidCache = new ThreadLocal[String]() {
override def initialValue(): String = "%05d".format(Thread.currentThread().getId())
}
objStart()
fld("traceEvents")
context.push(ValueContext)
arrStart()
traceWriter.newLine()

private val pid = ManagementFactory.getRuntimeMXBean().getName().replaceAll("@.*", "")

override def close(): Unit = {
arrEnd()
objEnd()
context.pop()
tidCache.remove()
traceWriter.close()
}

def traceDurationEvent(name: String, startNanos: Long, durationNanos: Long, tid: String = this.tid(), pidSuffix: String = ""): Unit = {
val durationMicros = nanosToMicros(durationNanos)
val startMicros = nanosToMicros(startNanos)
objStart()
str("cat", "scalac")
str("name", name)
str("ph", EventType.Complete)
str("tid", tid)
writePid(pidSuffix)
lng("ts", startMicros)
lng("dur", durationMicros)
objEnd()
traceWriter.newLine()
}

private def writePid(pidSuffix: String) = {
if (pidSuffix == "")
str("pid", pid)
else
str2("pid", pid, "-", pidSuffix)
}

def traceCounterEvent(name: String, counterName: String, count: Long, processWide: Boolean): Unit = {
objStart()
str("cat", "scalac")
str("name", name)
str("ph", EventType.Counter)
str("tid", tid())
writePid(pidSuffix = if (processWide) "" else tid())
lng("ts", microTime())
fld("args")
objStart()
lng(counterName, count)
objEnd()
objEnd()
traceWriter.newLine()
}

def traceDurationEventStart(cat: String, name: String, colour: String = "", pidSuffix: String = tid()): Unit = traceDurationEventStartEnd(EventType.Start, cat, name, colour, pidSuffix)
def traceDurationEventEnd(cat: String, name: String, colour: String = "", pidSuffix: String = tid()): Unit = traceDurationEventStartEnd(EventType.End, cat, name, colour, pidSuffix)

private def traceDurationEventStartEnd(eventType: String, cat: String, name: String, colour: String, pidSuffix: String = ""): Unit = {
objStart()
str("cat", cat)
str("name", name)
str("ph", eventType)
writePid(pidSuffix)
str("tid", tid())
lng("ts", microTime())
if (colour != "") {
str("cname", colour)
}
objEnd()
traceWriter.newLine()
}

private def tid(): String = tidCache.get()

private def nanosToMicros(t: Long): Long = TimeUnit.NANOSECONDS.toMicros(t)

private def microTime(): Long = nanosToMicros(System.nanoTime())

sealed abstract class JsonContext
case class ArrayContext(var first: Boolean) extends JsonContext
case class ObjectContext(var first: Boolean) extends JsonContext
case object ValueContext extends JsonContext
case object TopContext extends JsonContext

private def str(name: String, value: String): Unit = {
fld(name)
traceWriter.write("\"")
traceWriter.write(value) // This assumes no escaping is needed
traceWriter.write("\"")
}
private def str2(name: String, value: String, valueContinued1: String, valueContinued2: String): Unit = {
fld(name)
traceWriter.write("\"")
traceWriter.write(value) // This assumes no escaping is needed
traceWriter.write(valueContinued1) // This assumes no escaping is needed
traceWriter.write(valueContinued2) // This assumes no escaping is needed
traceWriter.write("\"")
}
private def lng(name: String, value: Long): Unit = {
fld(name)
traceWriter.write(String.valueOf(value))
traceWriter.write("")
}
private def objStart(): Unit = {
context.top match {
case ac @ ArrayContext(first) =>
if (first) ac.first = false
else traceWriter.write(",")
case _ =>
}
context.push(ObjectContext(true))
traceWriter.write("{")
}
private def objEnd(): Unit = {
traceWriter.write("}")
context.pop()
}
private def arrStart(): Unit = {
traceWriter.write("[")
context.push(ArrayContext(true))
}
private def arrEnd(): Unit = {
traceWriter.write("]")
context.pop()
}

private def fld(name: String) = {
val topContext = context.top
topContext match {
case oc @ ObjectContext(first) =>
if (first) oc.first = false
else traceWriter.write(",")
case context =>
throw new IllegalStateException("Wrong context: " + context)
}
traceWriter.write("\"")
traceWriter.write(name)
traceWriter.write("\"")
traceWriter.write(":")
}
}
Loading

0 comments on commit 3eeaa3d

Please sign in to comment.