Skip to content

Commit 5a6d23c

Browse files
committed
Allow display of secondary metrics from profilers
1 parent 474c9d1 commit 5a6d23c

File tree

4 files changed

+54
-28
lines changed

4 files changed

+54
-28
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ benchdb takes a JMH result file plus some captured environment data (platform, J
8383
\----------------------------------------------------------+--------+------+-----+------------+----------+-------/
8484
```
8585

86+
Secondary metrics can be used in place of the primary metric with `-metric`, (for instance, `-metric ·gc.alloc.rate.norm` shows the the per-operation allocations recorded by JMH's `-prof gc`.
87+
8688
- Extractor patterns can be used to extract additional parameters from benchmark names. They are glob patterns with regular expression-like capture groups. Unnamed groups are discarded, named groups are extracted into parameters. For example:
8789

8890
```

core/src/main/scala/com/lightbend/benchdb/GenerateCharts.scala

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.lightbend.benchdb
33
import scala.collection.JavaConverters._
44
import com.typesafe.config.{Config, ConfigFactory, ConfigObject, ConfigRenderOptions, ConfigValueFactory}
55

6-
class GenerateCharts(go: GlobalOptions, scorePrecision: Int) extends Logging {
6+
class GenerateCharts(go: GlobalOptions, scorePrecision: Int, metric: Option[String]) extends Logging {
77

88
def generatePivoted(pivoted: Iterable[(RunResult, IndexedSeq[Option[RunResult]])],
99
pivotSets: Seq[Seq[String]], pivotParams: Seq[String], otherParams: Seq[String]): String = {
@@ -38,7 +38,7 @@ class GenerateCharts(go: GlobalOptions, scorePrecision: Int) extends Logging {
3838
}
3939
}).asJava
4040
}.asJava
41-
renderChartData(n, pn, rs.head._1.primaryMetric.scoreUnit, lineChartOptionsBase, columns, rows)
41+
renderChartData(n, pn, vAxisTitle(rs.head._1.primaryMetricOr(metric).scoreUnit), lineChartOptionsBase, columns, rows)
4242
}
4343
}
4444
}.mkString("[", ",", "]")
@@ -63,12 +63,17 @@ class GenerateCharts(go: GlobalOptions, scorePrecision: Int) extends Logging {
6363
val rows = rs.zip(paramValues.map(_.get)).sortBy(_._2).map { case (r, pv) =>
6464
(pv +: seriesRowData(s"$pn = $pv", r)).asJava
6565
}.asJava
66-
renderChartData(n, pn, rs.head.primaryMetric.scoreUnit, lineChartOptionsBase, columns, rows)
66+
renderChartData(n, pn, vAxisTitle(rs.head.primaryMetricOr(metric).scoreUnit), lineChartOptionsBase, columns, rows)
6767
}
6868
}
6969
}.mkString("[", ",", "]")
7070
}
7171

72+
private def vAxisTitle(unit: String) = metric match {
73+
case Some(x) => x + " " + unit
74+
case None => unit
75+
}
76+
7277
private def renderChartData(title: String, hAxis: String, vAxis: String, baseConfig: Config, columns: Any, rows: Any): String = {
7378
val options = optionsConfig(title, hAxis, vAxis, baseConfig)
7479
val c = ConfigValueFactory.fromMap(Map("options" -> options, "columns" -> columns, "rows" -> rows).asJava)
@@ -91,10 +96,11 @@ class GenerateCharts(go: GlobalOptions, scorePrecision: Int) extends Logging {
9196
)
9297

9398
private def seriesRowData(name: String, r: RunResult): Seq[Any] = {
94-
val score = r.primaryMetric.score
95-
val err = r.primaryMetric.scoreError
99+
val metric1 = r.primaryMetricOr(metric)
100+
val score = metric1.score
101+
val err = metric1.scoreError
96102
val scoreS = ScoreFormatter(score, scorePrecision)
97103
val errS = ScoreFormatter(err, scorePrecision)
98-
Seq(score, score-err, score+err, s"$name<br/>Score: <b>$scoreS</b> ± $errS ${r.primaryMetric.scoreUnit}")
104+
Seq(score, score-err, score+err, s"$name<br/>Score: <b>$scoreS</b> ± $errS ${metric1.scoreUnit}")
99105
}
100106
}

core/src/main/scala/com/lightbend/benchdb/Main.scala

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ object Main extends Logging {
6868
"Query the database for test results and print them.") {
6969
val pivot = Opts.options[String]("pivot", help = "Parameter names to pivot in table output.").map(_.toList).orElse(Opts(Nil))
7070
val raw = Opts.flag("raw", "Print raw JSON data instead of a table.").orFalse
71-
(runs, benchs, extract, regex, scorePrecision, pivot, raw).mapN { case (runs, benchs, extract, regex, sp, pivot, raw) =>
71+
val metric = Opts.option[String]("metric", "Secondary metric to report rather than the primary, e.g. ·gc.alloc.rate.norm").orNone
72+
(runs, benchs, extract, regex, scorePrecision, pivot, raw, metric).mapN { case (runs, benchs, extract, regex, sp, pivot, raw, metric) =>
7273
{ go =>
7374
if(raw && pivot.nonEmpty) logger.error("Cannot pivot in raw output mode.")
74-
else queryResults(go, runs, benchs, extract, regex, sp, pivot, raw)
75+
else queryResults(go, runs, benchs, extract, regex, sp, pivot, raw, metric)
7576
}
7677
}
7778
}
@@ -80,8 +81,9 @@ object Main extends Logging {
8081
val template = Opts.option[Path]("template", "HTML template containing file.").orNone
8182
val out = Opts.option[Path]("out", short = "o", help = "Output file to generate, or '-' for stdout.").orNone
8283
val pivot = Opts.options[String]("pivot", help = "Parameter names to combine in a chart.").map(_.toList).orElse(Opts(Nil))
83-
(runs, benchs, extract, regex, scorePrecision, pivot, template, out).mapN { case (runs, benchs, extract, regex, sp, pivot, template, out) =>
84-
createChart(_, runs, benchs, extract, regex, sp, pivot, template, out, args)
84+
val metric = Opts.option[String]("metric", "Secondary metric to report rather than the primary, e.g. ·gc.alloc.rate.norm").orNone
85+
(runs, benchs, extract, regex, scorePrecision, pivot, metric, template, out).mapN { case (runs, benchs, extract, regex, sp, pivot, metric, template, out) =>
86+
createChart(_, runs, benchs, extract, regex, sp, pivot, metric, template, out, args)
8587
}
8688
}
8789

@@ -169,12 +171,16 @@ object Main extends Logging {
169171
}
170172
}
171173

172-
def queryResults(go: GlobalOptions, runs: Seq[String], benchs: Seq[String], extract: Seq[String], regex: Boolean, scorePrecision: Int, pivot: Seq[String], raw: Boolean): Unit = try {
174+
def queryResults(go: GlobalOptions, runs: Seq[String], benchs: Seq[String], extract: Seq[String], regex: Boolean, scorePrecision: Int, pivot: Seq[String], raw: Boolean, metric: Option[String]): Unit = try {
173175
new Global(go).use { g =>
174176
val multi = runs.size > 1
175177
val allRs = g.dao.run(g.dao.checkVersion andThen g.dao.queryResults(runs))
176178
.map { case (rr, runId) => RunResult.fromDb(rr, runId, multi) }
177179
val rs = RunResult.extract(extract, regex, RunResult.filterByName(benchs, allRs)).toSeq
180+
val benchmarkLabel = metric match {
181+
case Some(id) => s"Benchmark [$id]"
182+
case None => "Benchmark"
183+
}
178184
if(raw) {
179185
print("[")
180186
rs.zipWithIndex.foreach { case (r, idx) =>
@@ -200,22 +206,23 @@ object Main extends Logging {
200206
paramNames.map(s => Format(Align.Right)),
201207
Seq(Format(), Format(Align.Right), Format(Align.Right), Format(Align.Right), Format())
202208
).flatten
203-
val header = (("Benchmark" +: paramNames.map(s => s"($s)")) ++ Vector("Mode", "Cnt", "Score", "Error", "Units")).map(formatHeader)
209+
val header = ((benchmarkLabel +: paramNames.map(s => s"($s)")) ++ Vector("Mode", "Cnt", "Score", "Error", "Units")).map(formatHeader)
204210
val data = rs.iterator.map { r =>
211+
val score = r.primaryMetricOr(metric)
205212
Vector(
206213
Seq(r.name),
207214
paramNames.map(k => r.params.getOrElse(k, "")),
208215
Seq(r.db.mode, r.cnt,
209-
ScoreFormatter(r.primaryMetric.score, scorePrecision), ScoreFormatter(r.primaryMetric.scoreError, scorePrecision),
210-
r.primaryMetric.scoreUnit)
216+
ScoreFormatter(score.score, scorePrecision), ScoreFormatter(score.scoreError, scorePrecision),
217+
score.scoreUnit)
211218
).flatten
212219
}.toVector
213220
val table = new TableFormatter(go).apply(columns, Vector(header, null) ++ data)
214221
table.foreach(println)
215222
} else if(paramNames.length + pivotSet.size != allParamNames.length) {
216223
logger.error(s"Illegal pivot parameters.")
217224
} else {
218-
val (pivoted, pivotSets) = RunResult.pivot(rs, pivot, paramNames)
225+
val (pivoted, pivotSets) = RunResult.pivot(rs, pivot, paramNames, metric)
219226
val columns = Vector(
220227
Seq(Format()),
221228
paramNames.map(s => Format(Align.Right)),
@@ -230,23 +237,25 @@ object Main extends Logging {
230237
Seq(""),
231238
).flatten
232239
val header2 = Vector(
233-
Seq("Benchmark"),
240+
Seq(benchmarkLabel),
234241
paramNames.map(s => s"($s)"),
235242
Seq("Mode", "Cnt"),
236243
pivotSets.flatMap(_ => Seq("Score", "Error")),
237244
Seq("Units"),
238245
).flatten.map(formatHeader)
239246
val data = pivoted.iterator.map { case (r, pivotData) =>
240247
val scoreData = pivotData.flatMap {
241-
case Some(rr) => Seq(ScoreFormatter(rr.primaryMetric.score, scorePrecision), ScoreFormatter(rr.primaryMetric.scoreError, scorePrecision))
248+
case Some(rr) =>
249+
val score = rr.primaryMetricOr(metric)
250+
Seq(ScoreFormatter(score.score, scorePrecision), ScoreFormatter(score.scoreError, scorePrecision))
242251
case None => Seq(null, null)
243252
}
244253
Vector(
245254
Seq(r.name),
246255
paramNames.map(k => r.params.getOrElse(k, "")),
247256
Seq(r.db.mode, r.cnt),
248257
scoreData,
249-
Seq(r.primaryMetric.scoreUnit)
258+
Seq(r.primaryMetricOr(metric).scoreUnit)
250259
).flatten
251260
}.toVector
252261
val table = new TableFormatter(go).apply(columns, Vector(header1, header2, null) ++ data)
@@ -259,7 +268,7 @@ object Main extends Logging {
259268
case ex: PatternSyntaxException => logger.error(ex.toString)
260269
}
261270

262-
def createChart(go: GlobalOptions, runs: Seq[String], benchs: Seq[String], extract: Seq[String], regex: Boolean, scorePrecision: Int, pivot: Seq[String], template: Option[Path], out: Option[Path], cmdLine: Array[String]): Unit = {
271+
def createChart(go: GlobalOptions, runs: Seq[String], benchs: Seq[String], extract: Seq[String], regex: Boolean, scorePrecision: Int, pivot: Seq[String], metric: Option[String], template: Option[Path], out: Option[Path], cmdLine: Array[String]): Unit = {
263272
new Global(go).use { g =>
264273
val multi = runs.size > 1
265274
val allRs = g.dao.run(g.dao.checkVersion andThen g.dao.queryResults(runs))
@@ -268,14 +277,14 @@ object Main extends Logging {
268277
val allParamNames = rs.flatMap(_.params.keys).distinct.toVector
269278
val pivotSet = pivot.toSet
270279
val paramNames = allParamNames.filterNot(pivotSet.contains)
271-
val gen = new GenerateCharts(go, scorePrecision)
280+
val gen = new GenerateCharts(go, scorePrecision, metric)
272281
val data =
273282
if(pivot.isEmpty) gen.generate(rs)
274283
else if(paramNames.length + pivotSet.size != allParamNames.length) {
275284
logger.error(s"Illegal pivot parameters.")
276285
""
277286
} else {
278-
val (pivoted, pivotSets) = RunResult.pivot(rs, pivot, paramNames)
287+
val (pivoted, pivotSets) = RunResult.pivot(rs, pivot, paramNames, metric)
279288
gen.generatePivoted(pivoted, pivotSets, pivot, paramNames)
280289
}
281290
val templateHtml = template match {

core/src/main/scala/com/lightbend/benchdb/RunResult.scala

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,22 @@ final class RunResult(val db: DbRunResult, val rc: Config, val runId: Long, val
2626
lazy val dbJvmArgs: Seq[DbJvmArg] = jvmArgs.zipWithIndex.map { case (s, idx) => new DbJvmArg(db.uuid, idx, s) }
2727
lazy val dbParams: Seq[DbRunResultParam] = params.iterator.map { case (k, v) => new DbRunResultParam(None, db.uuid, k, v) }.toSeq
2828

29-
lazy val primaryMetric: RunResult.Metric =
29+
final def primaryMetricOr(secondary: Option[String]): RunResult.Metric = {
30+
secondary match {
31+
case Some(m) => secondaryMetrics(m)
32+
case None => primaryMetric
33+
}
34+
}
35+
36+
private lazy val primaryMetric: RunResult.Metric =
3037
parseMetric(rc.getConfig("primaryMetric"))
3138

32-
lazy val secondaryMetrics: Map[String, RunResult.Metric] = {
33-
val sc = rc.getConfig("secondaryMetrics")
34-
sc.entrySet().asScala.iterator.map { me =>
35-
(me.getKey, parseMetric(sc.getConfig(me.getKey)))
39+
private lazy val secondaryMetrics: Map[String, RunResult.Metric] = {
40+
val path = "secondaryMetrics"
41+
val sc = rc.getConfig(path)
42+
val keys = rc.getAnyRef(path).asInstanceOf[java.util.Map[String, _]].keySet()
43+
keys.asScala.iterator.map { k =>
44+
(k, parseMetric(sc.getConfig("\"" + k + "\"")))
3645
}.toMap
3746
}
3847

@@ -172,7 +181,7 @@ object RunResult extends Logging {
172181
}
173182
}
174183

175-
def pivot(rs: Iterable[RunResult], pivot: Seq[String], other: Seq[String]): (Iterable[(RunResult, IndexedSeq[Option[RunResult]])], Seq[Seq[String]]) = {
184+
def pivot(rs: Iterable[RunResult], pivot: Seq[String], other: Seq[String], metric: Option[String]): (Iterable[(RunResult, IndexedSeq[Option[RunResult]])], Seq[Seq[String]]) = {
176185
val pivotValues = pivot.map { p => rs.iterator.map(r => r.params.getOrElse(p, null)).toVector.distinct }
177186
val otherValues = other.map { p => rs.iterator.map(r => r.params.getOrElse(p, null)).toVector.distinct }
178187
def types(values: Seq[Seq[String]]) = values.map { ss =>
@@ -200,7 +209,7 @@ object RunResult extends Logging {
200209
val pivotColumns = valueCombinations(0, pivot.length).sorted(new ParamOrdering(pivotTypes))
201210

202211
val fixedTypes = Seq((false, false), (false, false), (true, true), (false, false))
203-
val groupsMap = rs.groupBy(r => Seq(r.name, r.db.mode, r.cnt.toString, r.primaryMetric.scoreUnit) ++ other.map(p => r.params.getOrElse(p, null)))
212+
val groupsMap = rs.groupBy(r => Seq(r.name, r.db.mode, r.cnt.toString, r.primaryMetricOr(metric).scoreUnit) ++ other.map(p => r.params.getOrElse(p, null)))
204213
val groups = groupsMap.toSeq.sortBy(_._1)(new ParamOrdering(fixedTypes ++ otherTypes))
205214
//groups.foreach { case (groupParams, groupData) => println(s"group: $groupParams -> ${groupData.size}") }
206215
val grouped = groups.map { case (gr, data) =>

0 commit comments

Comments
 (0)