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

dotty.tools.dotc.reporting.Profile.scala Maven / Gradle / Ivy

package dotty.tools
package dotc
package reporting

import core.*
import Contexts.{Context, ctx}
import Symbols.{Symbol, NoSymbol}
import collection.mutable
import util.{EqHashMap, NoSourcePosition}
import util.Spans.{Span, NoSpan}
import Decorators.i
import parsing.Scanners.Scanner
import io.AbstractFile
import annotation.internal.sharable

abstract class Profile:
  def unitProfile(unit: CompilationUnit): Profile.Info
  def recordNewLine()(using Context): Unit
  def recordNewToken()(using Context): Unit
  def recordTasty(size: Int)(using Context): Unit
  def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit
  def printSummary()(using Context): Unit

object Profile:
  def current(using Context): Profile =
    val run = ctx.run
    if run == null then NoProfile else run.profile

  inline val TastyChunkSize = 50

  def chunks(size: Int) = (size + TastyChunkSize - 1) / TastyChunkSize

  case class MethodInfo(meth: Symbol, size: Int, span: Span)
  @sharable object NoInfo extends MethodInfo(NoSymbol, 0, NoSpan)

  class Info(details: Int):
    var lineCount: Int = 0
    var tokenCount: Int = 0
    var tastySize: Int = 0
    def complexity: Float = chunks(tastySize).toFloat/lineCount
    val leading: Array[MethodInfo] = Array.fill[MethodInfo](details)(NoInfo)

    def recordMethodSize(meth: Symbol, size: Int, span: Span): Unit =
      var i = leading.length
      while i > 0 && leading(i - 1).size < size do
        if i < leading.length then leading(i) = leading(i - 1)
        i -= 1
      if i < leading.length then
        leading(i) = MethodInfo(meth, size, span)
  end Info
end Profile

class ActiveProfile(details: Int) extends Profile:

  private val pinfo = new EqHashMap[CompilationUnit, Profile.Info]

  private val junkInfo = new Profile.Info(0)

  private def curInfo(using Context): Profile.Info =
    val unit: CompilationUnit | Null = ctx.compilationUnit
    if unit == null || unit.source.file.isVirtual then junkInfo else unitProfile(unit)

  def unitProfile(unit: CompilationUnit): Profile.Info =
    pinfo.getOrElseUpdate(unit, new Profile.Info(details))

  def recordNewLine()(using Context): Unit =
    curInfo.lineCount += 1
  def recordNewToken()(using Context): Unit =
    curInfo.tokenCount += 1
  def recordTasty(size: Int)(using Context): Unit =
    curInfo.tastySize += size
  def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit =
    curInfo.recordMethodSize(meth, size, span)

  def printSummary()(using Context): Unit =
    val units =
      val rawUnits = pinfo.keysIterator.toArray
      ctx.settings.VprofileSortedBy.value match
        case "name"       => rawUnits.sortBy(_.source.file.name)
        case "path"       => rawUnits.sortBy(_.source.file.path)
        case "lines"      => rawUnits.sortBy(unitProfile(_).lineCount)
        case "tokens"     => rawUnits.sortBy(unitProfile(_).tokenCount)
        case "complexity" => rawUnits.sortBy(unitProfile(_).complexity)
        case _            => rawUnits.sortBy(unitProfile(_).tastySize)

    def printHeader(sourceNameWidth: Int, methNameWidth: Int = 0): String =
      val prefix =
        if methNameWidth > 0
        then s"%-${sourceNameWidth}s %-${methNameWidth}s".format("Sourcefile", "Method")
        else s"%-${sourceNameWidth}s".format("Sourcefile")
      val layout = s"%-${prefix.length}s %6s %8s %7s %s    %s"
      report.echo(layout.format(prefix, "Lines", "Tokens", "Tasty", " Complexity/Line", "Directory"))
      layout

    def printInfo(layout: String, name: String, info: Profile.Info, path: String) =
      val complexity = info.complexity
      val explanation =
        if complexity < 1       then "low     "
        else if complexity < 5  then "moderate"
        else if complexity < 25 then "high    "
        else                         "extreme "
      report.echo(layout.format(
        name, info.lineCount, info.tokenCount, Profile.chunks(info.tastySize),
        s"${"%6.2f".format(complexity)}  $explanation", path))

    def safeMax(xs: Array[Int]) = if xs.isEmpty then 10 else xs.max.max(10).min(50)

    def printAndAggregateSourceInfos(): Profile.Info =
      val sourceNameWidth = safeMax(units.map(_.source.file.name.length))
      val layout = printHeader(sourceNameWidth)
      val agg = new Profile.Info(details)
      for unit <- units do
        val file = unit.source.file
        val info = unitProfile(unit)
        printInfo(layout, file.name, info, file.container.path)
        agg.lineCount += info.lineCount
        agg.tokenCount += info.tokenCount
        agg.tastySize += info.tastySize
        for Profile.MethodInfo(meth, size, span) <- info.leading do
          agg.recordMethodSize(meth, size, span)
      if units.length > 1 then
        report.echo(s"${"-" * sourceNameWidth}------------------------------------------")
        printInfo(layout, "Total", agg, "")
      agg

    def printDetails(agg: Profile.Info): Unit =
      val sourceNameWidth = safeMax(agg.leading.map(_.meth.source.name.length))
      val methNameWidth = safeMax(agg.leading.map(_.meth.name.toString.length))
      report.echo("\nMost complex methods:")
      val layout = printHeader(sourceNameWidth, methNameWidth)
      for
        Profile.MethodInfo(meth, size, span) <- agg.leading.reverse
        unit <- units.find(_.source.eq(meth.source))
      do
        val methProfile = new ActiveProfile(0)
        val methCtx = ctx.fresh.setCompilationUnit(unit)
        val s = Scanner(meth.source, span.start, methProfile)(using methCtx)
        while s.offset < span.end do s.nextToken()
        val info = methProfile.unitProfile(unit)
        info.lineCount += 1
        info.tastySize = size
        val file = meth.source.file
        val header = s"%-${sourceNameWidth}s %-${methNameWidth}s".format(file.name, meth.name)
        printInfo(layout, header, info, file.container.path)

    val agg = printAndAggregateSourceInfos()
    if details > 0 then printDetails(agg)
  end printSummary
end ActiveProfile

object NoProfile extends Profile:
  def unitProfile(unit: CompilationUnit) = unsupported("NoProfile.info")
  def recordNewLine()(using Context): Unit = ()
  def recordNewToken()(using Context): Unit = ()
  def recordTasty(size: Int)(using Context): Unit = ()
  def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit = ()
  def printSummary()(using Context): Unit = ()




© 2015 - 2025 Weber Informatics LLC | Privacy Policy