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

japgolly.microlibs.utils.Debug.scala Maven / Gradle / Ivy

The newest version!
package japgolly.microlibs.utils

import japgolly.microlibs.stdlib_ext.MutableArray
import japgolly.microlibs.stdlib_ext.StdlibExt._
import japgolly.microlibs.utils.AsciiTable
import java.time.Duration
import scala.annotation.tailrec

object Debug {

  def printStackTrace(filter: String): Unit = {
    val t = new RuntimeException()
      .stackTraceAsString
      .linesIterator
      .drop(2)
      .filter(_ contains filter)
      .map(t => s"\u001b[96m$t${Console.RESET}")
      .mkString("\n")
    printInIsolation(t, sep = true)
  }

  def printInIsolation(str: String, sep: Boolean = false): Unit =
    synchronized {
      System.out.flush()
      System.err.flush()
      Thread.sleep(10)

      if (sep) {
        val s = "=" * 120
        println(s)
        println(str)
        println(s)
      } else
        println(str)

      System.out.flush()
      System.err.flush()
      Thread.sleep(60)
    }

  trait Implicits {
    final implicit def debugAnyExt[A](a: A): DebugAnyExt[A] =
      new DebugAnyExt(a)
  }

  object Implicits extends Implicits

  final class DebugAnyExt[A](private val self: A) extends AnyVal {
    def tapPrint()                                          : A = { println(self); self }
    def tapPrint(title: Any)                                : A = { println(s"$title: $self"); self }
    def tapPrintF(f: A => Any)                              : A = { println(f(self)); self }
    def printStackTraceWhen(f: A => Boolean, filter: String): A = { if (f(self)) Debug.printStackTrace(filter); self }
  }

  private val blank = Console.INVISIBLE + " " + Console.RESET

  // ===================================================================================================================

  def logDuration[A](run: => A): A =
    logDuration(None, run)

  def logDuration[A](name: String, run: => A): A =
    logDuration(Some(name), run)

  def logDurationN[A](name: String)(run: => A): A =
    logDuration(Some(name), run)

  def logDuration[A](name: Option[String], run: => A): A = {
    val start = System.nanoTime()
    try
      run
    finally {
      val end = System.nanoTime()
      val dur = Duration.ofNanos(end - start)
      name match {
        case Some(n) => println(s"$n completed in ${dur.conciseDesc}")
        case None    => println(s"Completed in ${dur.conciseDesc}")
      }
    }
  }

  def durationLogger(prefix: String = ""): DurationLogger =
    new DurationLogger(prefix)

  final class DurationLogger(prefix: String) {
    private val start = System.nanoTime()
    private var last = start

    def log(name: Any): Unit = {
      val now = System.nanoTime()
      val dur = Duration.ofNanos(now - last)
      val durT = Duration.ofNanos(now - start)
      println(s"$prefix$name completed in ${dur.conciseDesc} (${durT.conciseDesc} since start)")
      last = now
    }
  }

  // ===================================================================================================================

  private final class MutInt(init: Int) {
    var value = init
  }

  def CallCounter(): CallCounter =
    new CallCounter

  final class CallCounter {
    private[this] val lock = new AnyRef
    private var stats = Map.empty[String, MutInt]

    def clear(): Unit =
      lock.synchronized {
        stats = Map.empty[String, MutInt]
      }

    def inc(name: String): Unit = {
      val n = name.trim
      lock.synchronized {
        stats.get(n) match {
          case Some(m) => m.value += 1
          case None    => stats = stats.updated(n, new MutInt(1))
        }
      }
    }

    def apply[A](name: String)(a: => A): A = {
      inc(name)
      a
    }

    def report(): String =
      lock.synchronized {
        val rows = MutableArray(stats).sortBy(_._1).iterator().map { case (n, m) => Seq(n, "%,10d".format(m.value))}.toList
        val content = Seq("NAME", "COUNT") :: rows
        AsciiTable(content)
      }

    def printReport(): Unit = {
      printInIsolation(report())
    }

    def results(): Map[String, Int] =
      lock.synchronized(stats.mapValuesNow(_.value))
  }

  // ===================================================================================================================

  def StackTraceCounter(): StackTraceCounter =
    new StackTraceCounter

  final class StackTraceCounter {
    private[this] val cc = CallCounter()

    def clear(): Unit =
      cc.clear()

    def here(): Unit = {
      val stackTrace = new RuntimeException().stackTraceAsString
      cc.inc(stackTrace)
    }

    def report(): String = {
      val results = cc.results()
      val n = 3
      val top = MutableArray(results).sortBy(-_._2).iterator().take(n).map { case (s, c) => s"$c hits -- $s" }.toList
      val c = results.size
      s"""$c stack traces
         |$blank
         |Top ${n.min(c)}:
         |$blank
         |${top.mkString(s"\n$blank\n")}
         |$blank
         |""".stripMargin
    }

    def printReport(): Unit = {
      printInIsolation(report())
    }
  }

  // ===================================================================================================================

  trait ABTest {
    def clear(): Unit
    val addA: String => Unit
    val addB: String => Unit
    def complete(): Unit
  }

  object ABTest {
    object Off extends ABTest {
      override def clear(): Unit = ()
      override val addA = _ => ()
      override val addB = addA
      override def complete(): Unit = ()
    }

    def apply(): ABTest =
      new ABTest {
        private[this] val lock = new AnyRef
        private[this] val a = collection.mutable.ArrayBuffer.empty[String]
        private[this] val b = collection.mutable.ArrayBuffer.empty[String]
        private[this] var lastOk = -1

        override def clear(): Unit =
          lock.synchronized {
            a.clear()
            b.clear()
          }

        override val addA: String => Unit = add(_, true)
        override val addB: String => Unit = add(_, false)

        private def add(s: String, addToA: Boolean): Unit =
          lock.synchronized {
            (if (addToA) a else b) += s
            val limit = a.length.min(b.length)

            // min
            @tailrec def check(i: Int): Unit =
              if (i < limit) {
                val x = a(i)
                val y = b(i)
                if (x != y) {
                  val n = 8
                  val err =
                    new AssertionError(
                      s"""
                         |Discrepancy found at [$i]:
                         |$blank
                         |A: $x
                         |B: $y
                         |$blank
                         |As:
                         |${a.toVector.take(i + n).zipWithIndex.drop(i - n).map {case (s, i) => s"  a[$i] $s"}.mkString("\n")}
                         |$blank
                         |Bs:
                         |${b.toVector.take(i + n).zipWithIndex.drop(i - n).map {case (s, i) => s"  b[$i] $s"}.mkString("\n")}
                         |$blank
                         |""".stripMargin)
                  err.printStackTrace()
                  Thread.sleep(100)
                  throw err
                }
                lastOk = i
                check(i + 1)
              }

            check(lastOk + 1)
          }

        override def complete(): Unit =
          lock.synchronized {
            val x = a.length
            val y = b.length
            if (x != y) {
              val min = x.min(y)
              throw new AssertionError(
                s"""
                   |Discrepancy found! A had $x steps but B had $y steps.
                   |$blank
                   |Extra steps:
                   |${(a.drop(min) ++ b.drop(min)).mkString("\n")}
                   |$blank
                   |""".stripMargin)
            }
          }
      }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy