Skip to content

Commit 317b0e7

Browse files
committed
Add -Yprofile option
Add -Yprofile and -Yprofile-sorted-by option. This prints for each source file - the number of code lines - the number of tokens - the size of serialized tasty trees (just the trees, not the name table, nor the positions) - the complexity per line, computed by the formula (tasty-size/lines/50). That is, we assume a 50 tasy bytes/line as standard. Using -Yprofile-sorted-by one can sort by a column name.
1 parent cec9aa3 commit 317b0e7

File tree

7 files changed

+150
-6
lines changed

7 files changed

+150
-6
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import io.AbstractFile
1515
import Phases.unfusedPhases
1616

1717
import util._
18-
import reporting.{Suppression, Action}
18+
import reporting.{Suppression, Action, Profile, ActiveProfile, NoProfile}
1919
import reporting.Diagnostic
2020
import reporting.Diagnostic.Warning
2121
import rewrites.Rewrites
@@ -197,12 +197,19 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
197197
compileUnits()(using ctx)
198198
}
199199

200+
var profile: Profile = NoProfile
201+
200202
private def compileUnits()(using Context) = Stats.maybeMonitored {
201203
if (!ctx.mode.is(Mode.Interactive)) // IDEs might have multi-threaded access, accesses are synchronized
202204
ctx.base.checkSingleThreaded()
203205

204206
compiling = true
205207

208+
profile =
209+
if ctx.settings.Yprofile.value || !ctx.settings.YprofileSortedBy.value.isEmpty
210+
then ActiveProfile()
211+
else NoProfile
212+
206213
// If testing pickler, make sure to stop after pickling phase:
207214
val stopAfter =
208215
if (ctx.settings.YtestPickler.value) List("pickler")
@@ -321,8 +328,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
321328
def printSummary(): Unit = {
322329
printMaxConstraint()
323330
val r = runContext.reporter
324-
r.summarizeUnreportedWarnings
325-
r.printSummary
331+
if !r.errorsReported then
332+
profile.printSummary()
333+
r.summarizeUnreportedWarnings()
334+
r.printSummary()
326335
}
327336

328337
override def reset(): Unit = {

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,8 @@ private sealed trait YSettings:
333333
val YinstrumentDefs: Setting[Boolean] = BooleanSetting("-Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
334334

335335
val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.")
336+
337+
val Yprofile: Setting[Boolean] = BooleanSetting("-Yprofile", "Show information about sizes and compiletime complexity.")
338+
val YprofileSortedBy = ChoiceSetting("-Yprofile-sorted-by", "key", "Show information about sizes and compiletime complexity sorted by given column name", List("name", "path", "lines", "tokens", "tasty", "complexity"), "")
336339
end YSettings
340+

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import rewrites.Rewrites.patch
1919
import config.Feature
2020
import config.Feature.migrateTo3
2121
import config.SourceVersion.`3.0`
22+
import reporting.{NoProfile, Profile}
2223

2324
object Scanners {
2425

@@ -189,6 +190,8 @@ object Scanners {
189190
case _ => true
190191
}
191192

193+
private val profile = if this.isInstanceOf[LookaheadScanner] then NoProfile else Profile.current
194+
192195
if (rewrite) {
193196
val s = ctx.settings
194197
val rewriteTargets = List(s.newSyntax, s.oldSyntax, s.indent, s.noindent)
@@ -399,6 +402,7 @@ object Scanners {
399402
getNextToken(lastToken)
400403
if isAfterLineEnd then handleNewLine(lastToken)
401404
postProcessToken(lastToken, lastName)
405+
profile.recordNewToken()
402406
printState()
403407

404408
final def printState() =
@@ -639,6 +643,8 @@ object Scanners {
639643
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
640644
if token != OUTDENT then
641645
handleNewIndentWidth(currentRegion, _.otherIndentWidths += nextWidth)
646+
if next.token == EMPTY then
647+
profile.recordNewLine()
642648
end handleNewLine
643649

644650
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth) =
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package dotty.tools
2+
package dotc
3+
package reporting
4+
5+
import core.*
6+
import Contexts.{Context, ctx}
7+
import collection.mutable
8+
import util.{EqHashMap, NoSourcePosition}
9+
10+
abstract class Profile:
11+
def unitProfile(unit: CompilationUnit): Profile.Info
12+
def recordNewLine()(using Context): Unit
13+
def recordNewToken()(using Context): Unit
14+
def recordTasty(size: Int)(using Context): Unit
15+
def printSummary()(using Context): Unit
16+
17+
object Profile:
18+
def current(using Context): Profile =
19+
if ctx.run == null then NoProfile else ctx.run.profile
20+
21+
private val TastyFactor = 40
22+
23+
class Info:
24+
var lineCount: Int = 0
25+
var tokenCount: Int = 0
26+
var tastySize: Int = 0
27+
def complexity: Float = tastySize.toFloat/lineCount/TastyFactor
28+
end Profile
29+
30+
class ActiveProfile extends Profile:
31+
32+
private val pinfo = new EqHashMap[CompilationUnit, Profile.Info]
33+
34+
private val junkInfo = new Profile.Info
35+
36+
private def curInfo(using Context): Profile.Info =
37+
val unit = ctx.compilationUnit
38+
if unit == null then junkInfo else unitProfile(unit)
39+
40+
def unitProfile(unit: CompilationUnit): Profile.Info =
41+
pinfo.getOrElseUpdate(unit, new Profile.Info)
42+
43+
def recordNewLine()(using Context): Unit =
44+
curInfo.lineCount += 1
45+
def recordNewToken()(using Context): Unit =
46+
curInfo.tokenCount += 1
47+
def recordTasty(size: Int)(using Context): Unit =
48+
curInfo.tastySize += size
49+
50+
def printSummary()(using Context): Unit =
51+
val units =
52+
val rawUnits = pinfo.keysIterator.toArray
53+
ctx.settings.YprofileSortedBy.value match
54+
case "name" => rawUnits.sortBy(_.source.file.name)
55+
case "path" => rawUnits.sortBy(_.source.file.path)
56+
case "lines" => rawUnits.sortBy(unitProfile(_).lineCount)
57+
case "tokens" => rawUnits.sortBy(unitProfile(_).tokenCount)
58+
case "complexity" => rawUnits.sortBy(unitProfile(_).complexity)
59+
case _ => rawUnits.sortBy(unitProfile(_).tastySize)
60+
61+
val nameWidth = units.map(_.source.file.name.length).max.max(10).min(50)
62+
val layout = s"%-${nameWidth}s %6s %8s %8s %s %s"
63+
report.echo(layout.format("Source file", "Lines", "Tokens", "Tasty", " Complexity/Line", "Directory"))
64+
65+
def printInfo(name: String, info: Profile.Info, path: String) =
66+
val complexity = info.complexity
67+
val explanation =
68+
if complexity < 1 then "low "
69+
else if complexity < 5 then "moderate"
70+
else if complexity < 25 then "high "
71+
else "extreme "
72+
val complexityStr = s"${"%6.2f".format(info.complexity)} $explanation"
73+
report.echo(layout.format(
74+
name, info.lineCount, info.tokenCount, info.tastySize, complexityStr, path))
75+
76+
val agg = new Profile.Info
77+
for unit <- units do
78+
val info = unitProfile(unit)
79+
val file = unit.source.file
80+
printInfo(file.name, info, file.container.path)
81+
agg.lineCount += info.lineCount
82+
agg.tokenCount += info.tokenCount
83+
agg.tastySize += info.tastySize
84+
if units.length > 1 then
85+
report.echo(s"${"-"*nameWidth}------------------------------------------")
86+
printInfo("Total", agg, "")
87+
end printSummary
88+
end ActiveProfile
89+
90+
object NoProfile extends Profile:
91+
def unitProfile(unit: CompilationUnit) = unsupported("NoProfile.info")
92+
def recordNewLine()(using Context): Unit = ()
93+
def recordNewToken()(using Context): Unit = ()
94+
def recordTasty(size: Int)(using Context): Unit = ()
95+
def printSummary()(using Context): Unit = ()

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,14 @@ abstract class Reporter extends interfaces.ReporterResult {
214214
b.mkString("\n")
215215
}
216216

217-
def summarizeUnreportedWarnings(using Context): Unit =
217+
def summarizeUnreportedWarnings()(using Context): Unit =
218218
for (settingName, count) <- unreportedWarnings do
219219
val were = if count == 1 then "was" else "were"
220220
val msg = s"there $were ${countString(count, settingName.tail + " warning")}; re-run with $settingName for details"
221221
report(Warning(msg, NoSourcePosition))
222222

223223
/** Print the summary of warnings and errors */
224-
def printSummary(using Context): Unit = {
224+
def printSummary()(using Context): Unit = {
225225
val s = summary
226226
if (s != "") report(new Info(s, NoSourcePosition))
227227
}

compiler/src/dotty/tools/dotc/transform/Pickler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Periods._
1111
import Phases._
1212
import Symbols._
1313
import Flags.Module
14-
import reporting.ThrowingReporter
14+
import reporting.{ThrowingReporter, Profile}
1515
import collection.mutable
1616
import scala.concurrent.{Future, Await, ExecutionContext}
1717
import scala.concurrent.duration.Duration
@@ -70,6 +70,7 @@ class Pickler extends Phase {
7070
picklers(cls) = pickler
7171
val treePkl = new TreePickler(pickler)
7272
treePkl.pickle(tree :: Nil)
73+
Profile.current.recordTasty(treePkl.buf.length)
7374
val positionWarnings = new mutable.ListBuffer[String]()
7475
val pickledF = inContext(ctx.fresh) {
7576
Future {

tests/pos/profile-test.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// When compiling this with -Yprofile, this should output
2+
//
3+
// Source file Lines Tokens Tasty Complexity/Line Directory
4+
// profile-test.scala 16 50 316 0.49 low tests/pos
5+
object ProfileTest:
6+
7+
def test = ???
8+
9+
def bar: Boolean = ??? ;
10+
def baz = ???
11+
12+
/** doc comment
13+
*/
14+
def bam = ???
15+
16+
if bar then
17+
// comment
18+
baz
19+
else
20+
bam
21+
22+
if bar then {
23+
baz
24+
}
25+
else {
26+
// comment
27+
bam
28+
}
29+

0 commit comments

Comments
 (0)