im.yagni.driveby.tracking.Tracker.scala Maven / Gradle / Ivy
The newest version!
package im.yagni.driveby.tracking
import org.apache.commons.io.FileUtils
import collection.mutable.ListBuffer
import java.io.File
object Format {
import java.text.DecimalFormat
private val millisFormat = new DecimalFormat("#####")
def millis(duration: Long) = {
val formatted = millisFormat.format(duration)
" " * (5 - formatted.length) + formatted
}
}
object Tracker {
var verbose = false
var enabled = true
private val events = new ListBuffer[Event]()
def add(event: Event) {
if (!enabled) return
synchronized {
if (verbose) println("### " + event)
events.append(event)
}
}
//TODO: return an ExampleRun
def allEvents(exampleId: Long) = events.filter(_.exampleId == exampleId)
private def duration = (events.last.at - events.head.at) / 1000
private def actualBrowserCount = events.filter(_.isInstanceOf[BrowserOpened]).size
private def requestedBrowserCount = events.filter(_.isInstanceOf[BrowserOpenRequested]).size
private def specificationCount = events.filter(_.isInstanceOf[SpecificationStarted]).size
private def exampleCount = examples.size
private def averageExampleDuration = examples.map(_.exampleOnlyDuration).sum / exampleCount
private def examples = {
val idToEvents = events.filterNot(_.exampleId == -1).groupBy(_.exampleId)
idToEvents.keys.map(id => {ExampleRun(id, idToEvents(id))}).toList
}
private def specifications = {
val idToEvents = events.filterNot(_.specificationId == -1).groupBy(_.specificationId)
idToEvents.keys.map(id => {SpecificationRun(id, idToEvents(id))}).toList
}
private def browsers = {
val idToEvents = events.filterNot(_.browserId == -1).groupBy(_.browserId)
idToEvents.keys.map(id => {BrowserRun(id, idToEvents(id))}).toList
}
def report() {
if (!enabled) return
synchronized {
try {
doReport()
} catch {
case e: Exception => println("### Problem generating tracking report: " + e.getMessage)
}
}
}
private def doReport() {
val report = ListBuffer("Driveby Report:")
report.append("- " + exampleCount + " examples (" + specificationCount + " specifications) in " + duration + " seconds, with " + actualBrowserCount + " browsers of " + requestedBrowserCount + " requested")
report.append("- " + averageExampleDuration + " millis per example")
//TODO: report on Browser open/close failures
report.append("\nBrowsers:")
browsers.sortBy(_.browserId).foreach(b => report.append(b.report))
report.append("\nSpecifications:")
specifications.sortBy(_.duration).reverse.foreach(s => report.append(s.report))
report.append("\nExamples:")
examples.sortBy(_.exampleOnlyDuration).reverse.foreach(e => report.append(e.report))
if (verbose) println(report.mkString("\n"))
FileUtils.writeStringToFile(new File("target/specs2-reports/tracking.txt"), report.mkString("\n"))
}
}
case class BrowserRun(browserId: Long, events: ListBuffer[Event]) {
import Format._
private def requested = events.filter(_.isInstanceOf[BrowserOpenRequested]).head.asInstanceOf[BrowserOpenRequested]
private def opened = events.filter(_.isInstanceOf[BrowserOpened]).head
private def closed = events.filter(_.isInstanceOf[BrowserClosed]).head
private def lastExampleFinished = events.filter(_.isInstanceOf[BrowserWritten]).reverse.head
//TODO: better handle things not being available
private def idleDuration = {
try {
closed.at - lastExampleFinished.at
} catch { case e => println("### Browser " + browserId + " had an issue opening/closing"); -99999 }
}
private def startUpDuration = opened.at - requested.at
private def name = requested.browserType
private def exampleCount = events.filter(_.isInstanceOf[BrowserTaken]).size
def report = "- " + browserId + " (" + name + ") startup: " + millis(startUpDuration) + ", idle: " + millis(idleDuration) + ", examples: " + exampleCount
}
case class SpecificationRun(specificationId: Long, events: ListBuffer[Event]) {
import Format._
//TODO: remove asInstanceOf on start
private def start = {
val started = events.filter(_.isInstanceOf[SpecificationStarted])
if (started.isEmpty) println("###### no SpecificationStarted for " + specificationId)
started.head.asInstanceOf[SpecificationStarted]
}
//TODO: (maybe) can fail due to SpecificationFinished not occured yet ... (order of shutdown hooks)
private def finish = {
val finished = events.filter(_.isInstanceOf[SpecificationFinished])
if (finished.isEmpty) println("###### no SpecificationFinished for " + specificationId)
finished.head
}
private def name = start.name
def duration = finish.at - start.at
//TODO: show + or x instead and count examples per spec
def report = " - " + millis(duration) + " " + name
}
case class ExampleRun(exampleId: Long, events: ListBuffer[Event]) {
import Format._
//TODO: remove asInstanceOf on start
private def start = {
val started = events.filter(_.isInstanceOf[ExampleStarted])
if (started.isEmpty) println("###### no ExampleStarted for " + exampleId)
started.head.asInstanceOf[ExampleStarted]
}
//TODO: can fail due to ExampleFinished not occured yet ... (browser kippered)
private def finish = {
val finished = events.filter(_.isInstanceOf[ExampleFinished])
if (finished.isEmpty) println("###### no ExampleFinished for " + exampleId)
finished.head
}
private def browserRequested = events.filter(_.isInstanceOf[BrowserTakeRequested]).head
private def browserTaken = events.filter(_.isInstanceOf[BrowserTaken]).head
private def browserWritten = events.filter(_.isInstanceOf[BrowserWritten]).head
private def browserWaitDuration = browserTaken.at - browserRequested.at
private def name = start.name
private def totalDuration = finish.at - start.at
private def result = if (!events.filter(_.isInstanceOf[ExampleFailed]).isEmpty) "x" else "+"
def exampleOnlyDuration = browserWritten.at - browserTaken.at
//TODO: add the browserId and exampleId so we can see what's going on
//TODO: mkString this stuff up
def report = " " + result + " " + millis(exampleOnlyDuration) + " (browser: " + millis(browserWaitDuration) + ", total: " + millis(totalDuration) + ") " + name
}