All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy