Skip to content

Commit ce803ee

Browse files
authored
Merge pull request #113 from retronym/asyncMulti
2 parents e08eba5 + bd40804 commit ce803ee

File tree

5 files changed

+70
-13
lines changed

5 files changed

+70
-13
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,18 @@ for merges and sets of commits that we're backtesting (UI on https://scala-ci.ty
132132

133133
### Collecting profiling data
134134

135-
```
136-
sbt> .../jmh:run Benchmark -prof jmh.extras.JFR // Java Flight Recorder
137-
```
135+
#### Async Profiler
136+
137+
- [Install](https://github.com/jvm-profiling-tools/async-profiler/releases) async-profiler 2.x.
138+
- export `ASYNC_PROFILER_DIR=/path/to/async-profiler` (the directory containing `build`)
139+
- Create a JFR profile and flamegraphs with CPU or allocation tracing: `{profAsyncCpu, profAsyncAlloc} hot -psource=scala -f1`
140+
- Create a combined JFR profile with CPU and allocation tracing: `profAsyncCpuAlloc hot -psource=scala -f1`
141+
- This can be converted to flamegraphs with the converter provided by async-profiler: `java -cp converter.jar jfr2flame`
142+
or by using the convenience sbt command `sbt jfr2flame`.
143+
144+
#### Java Flight Recorder
145+
146+
- `profJfr hot -psource=scala -f1`
138147

139148
### Using GraalVM
140149

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ addCommandAlias("cold", "compilation/jmh:run ColdScalacBenchmark -foe true")
100100
commands ++= build.Profiler.commands
101101

102102
// duplicated in project/build.sbt
103-
val jmhV = System.getProperty("jmh.version", "1.30")
103+
val jmhV = System.getProperty("jmh.version", "1.31")
104104

105105
def addJmh(project: Project): Project = {
106106
// IntelliJ SBT project import doesn't like sbt-jmh's default setup, which results the prod and test

compilation/src/main/scala/scala/tools/nsc/ScalacBenchmark.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ class ScalacBenchmark extends BenchmarkDriver {
4747
@Param(value = Array(""))
4848
var extraArgs: String = _
4949

50-
@Param(value = Array("../corpus"))
51-
var corpusPath: String = "../corpus"
50+
final val DefaultCorpusPath = ""
51+
@Param(value = Array(DefaultCorpusPath))
52+
var corpusPath: String = DefaultCorpusPath
5253

5354
// This parameter is set by ScalacBenchmarkRunner / UploadingRunner based on the Scala version.
5455
// When running the benchmark directly the "latest" symlink is used.
@@ -93,7 +94,7 @@ class ScalacBenchmark extends BenchmarkDriver {
9394
BenchmarkUtils.deleteRecursive(tempDir.toPath)
9495
}
9596

96-
def corpusSourcePath: Path = Paths.get(s"$corpusPath/$source/$corpusVersion")
97+
def corpusSourcePath: Path = Paths.get(s"${if (corpusPath == DefaultCorpusPath) "../corpus" else corpusPath}/$source/$corpusVersion")
9798

9899
@Setup(Level.Trial) def initDepsClasspath(): Unit = {
99100
val classPath = BenchmarkUtils.initDeps(corpusSourcePath)

project/Profilers.scala

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,58 @@
11
package build
22

3+
import sbt.Keys.scalaInstance
4+
35
import java.io.File
46
import sbt._
57
import sbt.complete.DefaultParsers.OptSpace
68
import sbt.complete.Parser
9+
import sbt.internal.inc.classpath.ClasspathUtilities
10+
11+
import java.util.stream.Collectors
712

813
abstract class Profiler(val name: String) {
914
def command(outDir: File): String
1015
val flameGraphOpts = s"--minwidth,1,--colors,java,--cp,--width,1800"
1116
}
1217
object Profiler {
13-
def all = List(basic, jfr, asyncAlloc, asyncCpu, asyncWall, perfNorm)
18+
def all = List(basic, jfr, asyncAlloc, asyncCpu, asyncCpuAlloc, asyncWall, perfNorm)
1419

1520
def commands = Profiler.all.map { prof => Command.arb(profParser("prof" + prof.toString.capitalize))(commandImpl(List(prof))) } :+
16-
Command.arb(profParser("prof"))(commandImpl(Profiler.all))
21+
Command.arb(profParser("prof"))(commandImpl(Profiler.all)) :+ jfr2flameCommand
1722

1823
def profParser(name: String)(s: State): Parser[String] = {
1924
import Parser._
2025
token(name ~> OptSpace) flatMap { _ => matched(s.combinedParser)} map (_.trim)
2126
}
2227

28+
def jfr2flameCommand = Command.command("jfr2flame") { state =>
29+
import java.nio.file._
30+
import scala.collection.JavaConverters._
31+
val jfrFiles: List[Path] = Files.walk(Paths.get(System.getProperty("user.dir"))).iterator.asScala.filter(x => x.toString.endsWith(".jfr")).toList
32+
for (jfrFile <- jfrFiles) {
33+
val converterJar = file(System.getenv("ASYNC_PROFILER_DIR")) / "build" / "converter.jar"
34+
val (_, si) = Project.extract(state).runTask(scalaInstance, state)
35+
val run = new Run(cp => ClasspathUtilities.makeLoader(cp, si), trapExit = true)
36+
def jfr2flame(forward: Boolean, event: String): Unit = {
37+
val jfrFileNameString = jfrFile.toAbsolutePath.toString
38+
val flameFileName = jfrFileNameString.replaceAll(""".jfr$""", s"${if (event == "cpu") "" else "-" + event}-${if (forward) "forward" else "reverse"}.html")
39+
val directionOpts = if (forward) Nil else List("--reverse")
40+
val eventOpts = if (event == "cpu") Nil else List("--" + event)
41+
val flameOpts = List("--minwidth", "0.2")
42+
run.run("jfr2flame", converterJar :: Nil, eventOpts ++ flameOpts ++ directionOpts ++ List(jfrFileNameString, flameFileName), state.log).get
43+
}
44+
for (forward <- List(true, false)) {
45+
jfr2flame(forward, "cpu")
46+
jfr2flame(forward, "alloc")
47+
}
48+
}
49+
state
50+
}
51+
def jfr2flameParser(name: String)(s: State): Parser[String] = {
52+
import Parser._
53+
token("jfr2flame" ~> OptSpace) flatMap { _ => matched(s.combinedParser)} map (_.trim)
54+
}
55+
2356
def commandImpl(profs: List[Profiler]) = (s: State, line: String) => {
2457
val commands: List[String] = profs.flatMap { (prof: Profiler) =>
2558
val outDir = file(s"target/profile-${prof.name}")
@@ -37,16 +70,30 @@ case object basic extends Profiler("basic") {
3770
case object jfr extends Profiler("jfr") {
3871
def command(outDir: File): String = s"""-jvmArgs -XX:+UnlockCommercialFeatures -prof "jfr:dir=${outDir.getAbsolutePath};stackDepth=1024;postProcessor=scala.bench.JfrToFlamegraph;verbose=true" """
3972
}
40-
sealed abstract class async(event: String) extends Profiler("async-" + event) {
41-
val framebuf = 33554432
73+
sealed abstract class async(event: String, secondaryAlloc: Boolean = false) extends Profiler("async-" + event + (if (secondaryAlloc) "-alloc" else "")) {
74+
require(event != "alloc" || !secondaryAlloc)
4275
def command(outDir: File): String = {
43-
val r = s"""-prof "async:dir=${outDir.getAbsolutePath};libPath=${System.getenv("ASYNC_PROFILER_DIR")}/build/libasyncProfiler.so;minwidth=1;width=1800;verbose=true;event=$event;filter=${event == "wall"};flat=40;trace=10;framebuf=${framebuf};output=flamegraph,jfr,text" """
76+
val opts = collection.mutable.ListBuffer[String]()
77+
val doFlame = !secondaryAlloc
78+
opts ++= List(s"dir=${outDir.getAbsolutePath}", s"libPath=${System.getenv("ASYNC_PROFILER_DIR")}/build/libasyncProfiler.so", "verbose=true")
79+
opts += s"event=${event}"
80+
if (doFlame) {
81+
opts ++= List("output=flamegraph", "minwidth=1","width=1800", s"filter=${event == "wall"}")
82+
}
83+
if (secondaryAlloc) {
84+
opts += "alloc"
85+
opts += "output=jfr"
86+
} else {
87+
opts ++= List("flat=40", "traces=10", "output=text")
88+
}
89+
val r = s"""-prof "async:${opts.mkString(";")}" """
4490
println(r)
4591
r
4692
}
4793
}
4894
case object asyncCpu extends async("cpu")
4995
case object asyncAlloc extends async("alloc")
96+
case object asyncCpuAlloc extends async("cpu", true)
5097
case object asyncWall extends async("wall")
5198
case object perfNorm extends Profiler("perfNorm") {
5299
def command(outDir: File): String = "-prof perfnorm"

project/build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
val jmhV = System.getProperty("jmh.version", "1.30") // duplicated in build.sbt
1+
val jmhV = System.getProperty("jmh.version", "1.31") // duplicated in build.sbt
22

33
libraryDependencies ++= List(
44
"org.openjdk.jmh" % "jmh-core" % jmhV,

0 commit comments

Comments
 (0)