diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala index 6e1739b0bd..2f9ea89dd8 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala @@ -86,6 +86,7 @@ class FileBenchResultRepository extends BenchResultRepository { } private def save(stats: Stats) { + new File(dir).mkdirs if (!dirExists) return val timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(stats.timestamp)) val name = stats.name + "--" + timestamp + "--" + stats.load + ".ser" @@ -98,8 +99,7 @@ class FileBenchResultRepository extends BenchResultRepository { case e: Exception ⇒ EventHandler.error(this, "Failed to save [%s] to [%s], due to [%s]". format(stats, f.getAbsolutePath, e.getMessage)) - } - finally { + } finally { if (out ne null) try { out.close() } catch { case ignore: Exception ⇒ } } } @@ -117,8 +117,7 @@ class FileBenchResultRepository extends BenchResultRepository { EventHandler.error(this, "Failed to load from [%s], due to [%s]". format(f.getAbsolutePath, e.getMessage)) None - } - finally { + } finally { if (in ne null) try { in.close() } catch { case ignore: Exception ⇒ } } } diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala index ee06c33b5a..69a7b4bd08 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala @@ -52,8 +52,7 @@ trait PerformanceTest extends JUnitSuite { var stat: DescriptiveStatistics = _ val resultRepository = BenchResultRepository() - - val legendTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") + lazy val report = new Report(resultRepository, compareResultWith) type TS <: TradingSystem @@ -128,95 +127,7 @@ trait PerformanceTest extends JUnitSuite { resultRepository.add(stats) - EventHandler.info(this, formatResultsTable(resultRepository.get(name))) - - percentilesChart(stats) - latencyAndThroughputChart(stats) - comparePercentilesChart(stats) - compareWithHistoricalPercentiliesChart(stats) - - } - - def percentilesChart(stats: Stats) { - val chartTitle = stats.name + " Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(resultRepository.get(stats.name), chartTitle, _.load + " clients") - EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - } - - def comparePercentilesChart(stats: Stats) { - for { - compareName ← compareResultWith - compareStats ← resultRepository.get(compareName, stats.load) - } { - val chartTitle = stats.name + " vs. " + compareName + ", " + stats.load + " clients" + ", Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(Seq(compareStats, stats), chartTitle, _.name) - EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - } - } - - def compareWithHistoricalPercentiliesChart(stats: Stats) { - val withHistorical = resultRepository.getWithHistorical(stats.name, stats.load) - if (withHistorical.size > 1) { - val chartTitle = stats.name + " vs. historical, " + stats.load + " clients" + ", Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(withHistorical, chartTitle, - stats ⇒ legendTimeFormat.format(new Date(stats.timestamp))) - EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - } - } - - def latencyAndThroughputChart(stats: Stats) { - val chartTitle = stats.name + " Latency (microseconds) and Throughput (TPS)" - val chartUrl = GoogleChartBuilder.latencyAndThroughputChartUrl(resultRepository.get(stats.name), chartTitle) - EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - } - - def formatResultsTable(statsSeq: Seq[Stats]): String = { - - val name = statsSeq.head.name - - val spaces = " " - val headerScenarioCol = ("Scenario" + spaces).take(name.length) - - val headerLine = (headerScenarioCol :: "clients" :: "TPS" :: "mean" :: "5% " :: "25% " :: "50% " :: "75% " :: "95% " :: "Durat." :: "N" :: Nil) - .mkString("\t") - val headerLine2 = (spaces.take(name.length) :: " " :: " " :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(s) " :: " " :: Nil) - .mkString("\t") - val line = List.fill(formatStats(statsSeq.head).replaceAll("\t", " ").length)("-").mkString - val formattedStats = "\n" + - line.replace('-', '=') + "\n" + - headerLine + "\n" + - headerLine2 + "\n" + - line + "\n" + - statsSeq.map(formatStats(_)).mkString("\n") + "\n" + - line + "\n" - - formattedStats - - } - - def formatStats(stats: Stats): String = { - val durationS = stats.durationNanos.toDouble / 1000000000.0 - val duration = durationS.formatted("%.0f") - - val tpsStr = stats.tps.formatted("%.0f") - val meanStr = stats.mean.formatted("%.0f") - - val summaryLine = - stats.name :: - stats.load.toString :: - tpsStr :: - meanStr :: - stats.percentiles(5).toString :: - stats.percentiles(25).toString :: - stats.percentiles(50).toString :: - stats.percentiles(75).toString :: - stats.percentiles(95).toString :: - duration :: - stats.n.toString :: - Nil - - summaryLine.mkString("\t") - + report.html(resultRepository.get(name)) } def delay(delayMs: Int) { diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala new file mode 100644 index 0000000000..9160fa631e --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala @@ -0,0 +1,179 @@ +package akka.performance.trading.common +import java.io.File +import java.text.SimpleDateFormat +import java.io.PrintWriter +import java.io.FileWriter +import akka.event.EventHandler +import java.util.Date + +class Report( + resultRepository: BenchResultRepository, + compareResultWith: Option[String] = None) { + + private val dir = System.getProperty("benchmark.resultDir", "target/benchmark") + + private def dirExists: Boolean = new File(dir).exists + private def log = System.getProperty("benchmark.logResult", "false").toBoolean + + val dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") + val legendTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") + val fileTimestampFormat = new SimpleDateFormat("yyyyMMddHHmmss") + + def html(statistics: Seq[Stats]): Unit = if (dirExists) { + + val current = statistics.last + val sb = new StringBuilder + + val title = current.name + " " + dateTimeFormat.format(new Date(current.timestamp)) + sb.append(header(title)) + sb.append("

%s

\n".format(title)) + + sb.append("
\n")
+    sb.append(formatResultsTable(statistics))
+    sb.append("\n
\n") + + sb.append(img(percentilesChart(current))) + sb.append(img(latencyAndThroughputChart(current))) + + for (stats ← statistics) { + compareWithHistoricalPercentiliesChart(stats).foreach(url ⇒ sb.append(img(url))) + } + + for (stats ← statistics) { + comparePercentilesChart(stats).foreach(url ⇒ sb.append(img(url))) + } + + if (dirExists) { + val timestamp = fileTimestampFormat.format(new Date(current.timestamp)) + val name = current.name + "--" + timestamp + ".html" + write(sb.toString, name) + } + + } + + private def img(url: String): String = { + """""".format( + url, GoogleChartBuilder.ChartWidth, GoogleChartBuilder.ChartHeight) + "\n" + } + + def percentilesChart(stats: Stats): String = { + val chartTitle = stats.name + " Percentiles (microseconds)" + val chartUrl = GoogleChartBuilder.percentilChartUrl(resultRepository.get(stats.name), chartTitle, _.load + " clients") + if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) + chartUrl + } + + def comparePercentilesChart(stats: Stats): Seq[String] = { + for { + compareName ← compareResultWith.toSeq + compareStats ← resultRepository.get(compareName, stats.load) + } yield { + val chartTitle = stats.name + " vs. " + compareName + ", " + stats.load + " clients" + ", Percentiles (microseconds)" + val chartUrl = GoogleChartBuilder.percentilChartUrl(Seq(compareStats, stats), chartTitle, _.name) + if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) + chartUrl + } + } + + def compareWithHistoricalPercentiliesChart(stats: Stats): Option[String] = { + val withHistorical = resultRepository.getWithHistorical(stats.name, stats.load) + if (withHistorical.size > 1) { + val chartTitle = stats.name + " vs. historical, " + stats.load + " clients" + ", Percentiles (microseconds)" + val chartUrl = GoogleChartBuilder.percentilChartUrl(withHistorical, chartTitle, + stats ⇒ legendTimeFormat.format(new Date(stats.timestamp))) + if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) + Some(chartUrl) + } else { + None + } + } + + def latencyAndThroughputChart(stats: Stats): String = { + val chartTitle = stats.name + " Latency (microseconds) and Throughput (TPS)" + val chartUrl = GoogleChartBuilder.latencyAndThroughputChartUrl(resultRepository.get(stats.name), chartTitle) + if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) + chartUrl + } + + def formatResultsTable(statsSeq: Seq[Stats]): String = { + + val name = statsSeq.head.name + + val spaces = " " + val headerScenarioCol = ("Scenario" + spaces).take(name.length) + + val headerLine = (headerScenarioCol :: "clients" :: "TPS" :: "mean" :: "5% " :: "25% " :: "50% " :: "75% " :: "95% " :: "Durat." :: "N" :: Nil) + .mkString("\t") + val headerLine2 = (spaces.take(name.length) :: " " :: " " :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(s) " :: " " :: Nil) + .mkString("\t") + val line = List.fill(formatStats(statsSeq.head).replaceAll("\t", " ").length)("-").mkString + val formattedStats = "\n" + + line.replace('-', '=') + "\n" + + headerLine + "\n" + + headerLine2 + "\n" + + line + "\n" + + statsSeq.map(formatStats(_)).mkString("\n") + "\n" + + line + "\n" + + if (log) EventHandler.info(this, formattedStats) + + formattedStats + + } + + def formatStats(stats: Stats): String = { + val durationS = stats.durationNanos.toDouble / 1000000000.0 + val duration = durationS.formatted("%.0f") + + val tpsStr = stats.tps.formatted("%.0f") + val meanStr = stats.mean.formatted("%.0f") + + val summaryLine = + stats.name :: + stats.load.toString :: + tpsStr :: + meanStr :: + stats.percentiles(5).toString :: + stats.percentiles(25).toString :: + stats.percentiles(50).toString :: + stats.percentiles(75).toString :: + stats.percentiles(95).toString :: + duration :: + stats.n.toString :: + Nil + + summaryLine.mkString("\t") + + } + + def write(content: String, fileName: String) { + val f = new File(dir, fileName) + var writer: PrintWriter = null + try { + writer = new PrintWriter(new FileWriter(f)) + writer.print(content) + writer.flush() + } catch { + case e: Exception ⇒ + EventHandler.error(this, "Failed to save report to [%s], due to [%s]". + format(f.getAbsolutePath, e.getMessage)) + } finally { + if (writer ne null) try { writer.close() } catch { case ignore: Exception ⇒ } + } + } + + def header(title: String) = + """| + | + | + | + |%s + | + | + |""".stripMargin.format(title) + + def footer = + """|" + |""".stripMargin + +} \ No newline at end of file