|
| 1 | +package dotty.tools |
| 2 | +package dotc |
| 3 | +package reporting |
| 4 | + |
| 5 | +import core.* |
| 6 | +import Contexts.{Context, ctx} |
| 7 | +import Symbols.{Symbol, NoSymbol} |
| 8 | +import collection.mutable |
| 9 | +import util.{EqHashMap, NoSourcePosition} |
| 10 | +import util.Spans.{Span, NoSpan} |
| 11 | +import Decorators.i |
| 12 | +import parsing.Scanners.Scanner |
| 13 | +import io.AbstractFile |
| 14 | +import annotation.internal.sharable |
| 15 | + |
| 16 | +abstract class Profile: |
| 17 | + def unitProfile(unit: CompilationUnit): Profile.Info |
| 18 | + def recordNewLine()(using Context): Unit |
| 19 | + def recordNewToken()(using Context): Unit |
| 20 | + def recordTasty(size: Int)(using Context): Unit |
| 21 | + def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit |
| 22 | + def printSummary()(using Context): Unit |
| 23 | + |
| 24 | +object Profile: |
| 25 | + def current(using Context): Profile = |
| 26 | + val run = ctx.run |
| 27 | + if run == null then NoProfile else run.profile |
| 28 | + |
| 29 | + inline val TastyChunkSize = 50 |
| 30 | + |
| 31 | + def chunks(size: Int) = (size + TastyChunkSize - 1) / TastyChunkSize |
| 32 | + |
| 33 | + case class MethodInfo(meth: Symbol, size: Int, span: Span) |
| 34 | + @sharable object NoInfo extends MethodInfo(NoSymbol, 0, NoSpan) |
| 35 | + |
| 36 | + class Info(details: Int): |
| 37 | + var lineCount: Int = 0 |
| 38 | + var tokenCount: Int = 0 |
| 39 | + var tastySize: Int = 0 |
| 40 | + def complexity: Float = chunks(tastySize).toFloat/lineCount |
| 41 | + val leading: Array[MethodInfo] = Array.fill[MethodInfo](details)(NoInfo) |
| 42 | + |
| 43 | + def recordMethodSize(meth: Symbol, size: Int, span: Span): Unit = |
| 44 | + var i = leading.length |
| 45 | + while i > 0 && leading(i - 1).size < size do |
| 46 | + if i < leading.length then leading(i) = leading(i - 1) |
| 47 | + i -= 1 |
| 48 | + if i < leading.length then |
| 49 | + leading(i) = MethodInfo(meth, size, span) |
| 50 | + end Info |
| 51 | +end Profile |
| 52 | + |
| 53 | +class ActiveProfile(details: Int) extends Profile: |
| 54 | + |
| 55 | + private val pinfo = new EqHashMap[CompilationUnit, Profile.Info] |
| 56 | + |
| 57 | + private val junkInfo = new Profile.Info(0) |
| 58 | + |
| 59 | + private def curInfo(using Context): Profile.Info = |
| 60 | + val unit: CompilationUnit | Null = ctx.compilationUnit |
| 61 | + if unit == null || unit.source.file.isVirtual then junkInfo else unitProfile(unit) |
| 62 | + |
| 63 | + def unitProfile(unit: CompilationUnit): Profile.Info = |
| 64 | + pinfo.getOrElseUpdate(unit, new Profile.Info(details)) |
| 65 | + |
| 66 | + def recordNewLine()(using Context): Unit = |
| 67 | + curInfo.lineCount += 1 |
| 68 | + def recordNewToken()(using Context): Unit = |
| 69 | + curInfo.tokenCount += 1 |
| 70 | + def recordTasty(size: Int)(using Context): Unit = |
| 71 | + curInfo.tastySize += size |
| 72 | + def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit = |
| 73 | + curInfo.recordMethodSize(meth, size, span) |
| 74 | + |
| 75 | + def printSummary()(using Context): Unit = |
| 76 | + val units = |
| 77 | + val rawUnits = pinfo.keysIterator.toArray |
| 78 | + ctx.settings.VprofileSortedBy.value match |
| 79 | + case "name" => rawUnits.sortBy(_.source.file.name) |
| 80 | + case "path" => rawUnits.sortBy(_.source.file.path) |
| 81 | + case "lines" => rawUnits.sortBy(unitProfile(_).lineCount) |
| 82 | + case "tokens" => rawUnits.sortBy(unitProfile(_).tokenCount) |
| 83 | + case "complexity" => rawUnits.sortBy(unitProfile(_).complexity) |
| 84 | + case _ => rawUnits.sortBy(unitProfile(_).tastySize) |
| 85 | + |
| 86 | + def printHeader(sourceNameWidth: Int, methNameWidth: Int = 0): String = |
| 87 | + val prefix = |
| 88 | + if methNameWidth > 0 |
| 89 | + then s"%-${sourceNameWidth}s %-${methNameWidth}s".format("Sourcefile", "Method") |
| 90 | + else s"%-${sourceNameWidth}s".format("Sourcefile") |
| 91 | + val layout = s"%-${prefix.length}s %6s %8s %7s %s %s" |
| 92 | + report.echo(layout.format(prefix, "Lines", "Tokens", "Tasty", " Complexity/Line", "Directory")) |
| 93 | + layout |
| 94 | + |
| 95 | + def printInfo(layout: String, name: String, info: Profile.Info, path: String) = |
| 96 | + val complexity = info.complexity |
| 97 | + val explanation = |
| 98 | + if complexity < 1 then "low " |
| 99 | + else if complexity < 5 then "moderate" |
| 100 | + else if complexity < 25 then "high " |
| 101 | + else "extreme " |
| 102 | + report.echo(layout.format( |
| 103 | + name, info.lineCount, info.tokenCount, Profile.chunks(info.tastySize), |
| 104 | + s"${"%6.2f".format(complexity)} $explanation", path)) |
| 105 | + |
| 106 | + def safeMax(xs: Array[Int]) = xs.max.max(10).min(50) |
| 107 | + |
| 108 | + def printAndAggregateSourceInfos(): Profile.Info = |
| 109 | + val sourceNameWidth = safeMax(units.map(_.source.file.name.length)) |
| 110 | + val layout = printHeader(sourceNameWidth) |
| 111 | + val agg = new Profile.Info(details) |
| 112 | + for unit <- units do |
| 113 | + val file = unit.source.file |
| 114 | + val info = unitProfile(unit) |
| 115 | + printInfo(layout, file.name, info, file.container.path) |
| 116 | + agg.lineCount += info.lineCount |
| 117 | + agg.tokenCount += info.tokenCount |
| 118 | + agg.tastySize += info.tastySize |
| 119 | + for Profile.MethodInfo(meth, size, span) <- info.leading do |
| 120 | + agg.recordMethodSize(meth, size, span) |
| 121 | + if units.length > 1 then |
| 122 | + report.echo(s"${"-" * sourceNameWidth}------------------------------------------") |
| 123 | + printInfo(layout, "Total", agg, "") |
| 124 | + agg |
| 125 | + |
| 126 | + def printDetails(agg: Profile.Info): Unit = |
| 127 | + val sourceNameWidth = safeMax(agg.leading.map(_.meth.source.name.length)) |
| 128 | + val methNameWidth = safeMax(agg.leading.map(_.meth.name.toString.length)) |
| 129 | + report.echo("\nMost complex methods:") |
| 130 | + val layout = printHeader(sourceNameWidth, methNameWidth) |
| 131 | + for |
| 132 | + Profile.MethodInfo(meth, size, span) <- agg.leading.reverse |
| 133 | + unit <- units.find(_.source.eq(meth.source)) |
| 134 | + do |
| 135 | + val methProfile = new ActiveProfile(0) |
| 136 | + val methCtx = ctx.fresh.setCompilationUnit(unit) |
| 137 | + val s = Scanner(meth.source, span.start, methProfile)(using methCtx) |
| 138 | + while s.offset < span.end do s.nextToken() |
| 139 | + val info = methProfile.unitProfile(unit) |
| 140 | + info.lineCount += 1 |
| 141 | + info.tastySize = size |
| 142 | + val file = meth.source.file |
| 143 | + val header = s"%-${sourceNameWidth}s %-${methNameWidth}s".format(file.name, meth.name) |
| 144 | + printInfo(layout, header, info, file.container.path) |
| 145 | + |
| 146 | + val agg = printAndAggregateSourceInfos() |
| 147 | + if details > 0 then printDetails(agg) |
| 148 | + end printSummary |
| 149 | +end ActiveProfile |
| 150 | + |
| 151 | +object NoProfile extends Profile: |
| 152 | + def unitProfile(unit: CompilationUnit) = unsupported("NoProfile.info") |
| 153 | + def recordNewLine()(using Context): Unit = () |
| 154 | + def recordNewToken()(using Context): Unit = () |
| 155 | + def recordTasty(size: Int)(using Context): Unit = () |
| 156 | + def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit = () |
| 157 | + def printSummary()(using Context): Unit = () |
0 commit comments