Skip to content

Commit 2e63f5d

Browse files
committed
Add profiling invalidation utils (zprof)
zprof is the name I've chosen for this small profiler (or tracker if you will) of the invalidation logic. The profiled data is formalized in an internal format that is not supposed to be used by normal users, but rather by us (Zinc) and related tools (Bloop). The current profiled data exposes details of how the incremental compiler works internally and how it invalidates classes. This is the realization of an idea I registered here: sbt#550 With this idea, this data will not only be useful for debugging but for providing an automatic way of reporting bugs in Zinc. The infrastructure is far from finished but it's already in a usable state for libraries that depend on Zinc directly and have direct access to `Incremental`. By default, no profiler is used. Only people that change the profiler argument for `Incremental.compile` will be able to get the run profiles.
1 parent 396713b commit 2e63f5d

File tree

6 files changed

+394
-16
lines changed

6 files changed

+394
-16
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ lazy val zincCore = (project in internalPath / "zinc-core")
243243
name := "zinc Core",
244244
compileOrder := sbt.CompileOrder.Mixed,
245245
mimaSettings,
246+
PB.targets in Compile := List(scalapb.gen() -> (sourceManaged in Compile).value),
246247
)
247248
.configure(addSbtIO, addSbtUtilLogging, addSbtUtilRelation)
248249

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
syntax = "proto3";
2+
3+
package sbt.internal.inc;
4+
5+
///////////////////////////////////////////////////////////////////////////////////////////////
6+
///////////////////////////////////////// ZINC PROF ///////////////////////////////////////////
7+
///////////////////////////////////////////////////////////////////////////////////////////////
8+
9+
message Profile {
10+
repeated ZincRun runs = 1;
11+
repeated string string_table = 2;
12+
}
13+
14+
message ZincRun {
15+
InitialChanges initial = 1;
16+
repeated CycleInvalidation cycles = 3;
17+
}
18+
19+
message CycleInvalidation {
20+
repeated int64 invalidated = 1;
21+
repeated int64 invalidatedByPackageObjects = 2;
22+
repeated int64 initialSources = 3;
23+
repeated int64 invalidatedSources = 4;
24+
repeated int64 recompiledClasses = 5;
25+
26+
int64 startTimeNanos = 6; // Start time of compilation (UTC) as nanoseconds past the epoch
27+
int64 compilationDurationNanos = 7; // Duration of the compilation profile in nanoseconds
28+
repeated ApiChange changesAfterRecompilation = 8;
29+
30+
repeated InvalidationEvent events = 9;
31+
repeated int64 nextInvalidations = 10;
32+
bool shouldCompileIncrementally = 11;
33+
}
34+
35+
message InvalidationEvent {
36+
string kind = 1;
37+
repeated int64 inputs = 2;
38+
repeated int64 outputs = 3;
39+
string reason = 4;
40+
}
41+
42+
message Changes {
43+
repeated int64 added = 1;
44+
repeated int64 removed = 2;
45+
repeated int64 modified = 3;
46+
}
47+
48+
message ApiChange {
49+
int64 modifiedClass = 1;
50+
string reason = 2;
51+
repeated UsedName usedNames = 3; // Can be empty if the change is not related to names
52+
}
53+
54+
message InitialChanges {
55+
Changes changes = 1;
56+
repeated int64 removedProducts = 2;
57+
repeated int64 binaryDependencies = 3;
58+
repeated ApiChange externalChanges = 4;
59+
}
60+
61+
message UsedName {
62+
int64 name = 1;
63+
repeated Scope scopes = 2;
64+
}
65+
66+
message Scope {
67+
int64 kind = 1;
68+
}

internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import java.io.File
1414
import sbt.util.{ Level, Logger }
1515
import xsbti.compile.analysis.{ ReadStamps, Stamp => XStamp }
1616
import xsbti.compile.{
17-
ClassFileManager => XClassFileManager,
1817
CompileAnalysis,
1918
DependencyChanges,
20-
IncOptions
19+
IncOptions,
20+
ClassFileManager => XClassFileManager
2121
}
2222

2323
/**
@@ -46,6 +46,7 @@ object Incremental {
4646
* @param callbackBuilder The builder that builds callback where we report dependency issues.
4747
* @param log The log where we write debugging information
4848
* @param options Incremental compilation options
49+
* @param profiler An implementation of an invalidation profiler, empty by default.
4950
* @param equivS The means of testing whether two "Stamps" are the same.
5051
* @return
5152
* A flag of whether or not compilation completed successfully, and the resulting dependency analysis object.
@@ -58,11 +59,12 @@ object Incremental {
5859
compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback, XClassFileManager) => Unit,
5960
callbackBuilder: AnalysisCallback.Builder,
6061
log: sbt.util.Logger,
61-
options: IncOptions
62+
options: IncOptions,
63+
profiler: InvalidationProfiler = InvalidationProfiler.empty
6264
)(implicit equivS: Equiv[XStamp]): (Boolean, Analysis) = {
6365
val previous = previous0 match { case a: Analysis => a }
64-
val incremental: IncrementalCommon =
65-
new IncrementalNameHashing(log, options)
66+
val runProfiler = profiler.profileRun
67+
val incremental: IncrementalCommon = new IncrementalNameHashing(log, options, runProfiler)
6668
val initialChanges = incremental.detectInitialChanges(sources, previous, current, lookup)
6769
val binaryChanges = new DependencyChanges {
6870
val modifiedBinaries = initialChanges.binaryDeps.toArray

internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ import scala.annotation.tailrec
3131
* @param log An instance of a logger.
3232
* @param options An instance of incremental compiler options.
3333
*/
34-
private[inc] abstract class IncrementalCommon(val log: Logger, options: IncOptions) {
34+
private[inc] abstract class IncrementalCommon(
35+
val log: Logger,
36+
options: IncOptions,
37+
profiler: RunProfiler
38+
) extends InvalidationProfilerUtils {
3539
// Work around bugs in classpath handling such as the "currently" problematic -javabootclasspath
3640
private[this] def enableShallowLookup: Boolean =
3741
java.lang.Boolean.getBoolean("xsbt.skip.cp.lookup")
@@ -81,11 +85,11 @@ private[inc] abstract class IncrementalCommon(val log: Logger, options: IncOptio
8185
// Computes which source files are mapped to the invalidated classes and recompile them
8286
val invalidatedSources =
8387
mapInvalidationsToSources(classesToRecompile, initialChangedSources, allSources, previous)
84-
val (current, recompiledRecently) =
88+
val current =
8589
recompileClasses(invalidatedSources, binaryChanges, previous, doCompile, classfileManager)
8690

8791
// Return immediate analysis as all sources have been recompiled
88-
if (recompiledRecently == allSources) current
92+
if (invalidatedSources == allSources) current
8993
else {
9094
val recompiledClasses: Set[String] = {
9195
// Represents classes detected as changed externally and internally (by a previous cycle)
@@ -107,6 +111,18 @@ private[inc] abstract class IncrementalCommon(val log: Logger, options: IncOptio
107111
)
108112

109113
val continue = lookup.shouldDoIncrementalCompilation(nextInvalidations, current)
114+
115+
profiler.registerCycle(
116+
invalidatedClasses,
117+
invalidatedByPackageObjects,
118+
initialChangedSources,
119+
invalidatedSources,
120+
recompiledClasses,
121+
newApiChanges,
122+
nextInvalidations,
123+
continue
124+
)
125+
110126
cycle(
111127
if (continue) nextInvalidations else Set.empty,
112128
Set.empty,
@@ -147,20 +163,20 @@ private[inc] abstract class IncrementalCommon(val log: Logger, options: IncOptio
147163
previous: Analysis,
148164
doCompile: (Set[File], DependencyChanges) => Analysis,
149165
classfileManager: XClassFileManager
150-
): (Analysis, Set[File]) = {
166+
): Analysis = {
151167
val pruned =
152168
IncrementalCommon.pruneClassFilesOfInvalidations(sources, previous, classfileManager)
153169
debug("********* Pruned: \n" + pruned.relations + "\n*********")
154170
val fresh = doCompile(sources, binaryChanges)
155171
debug("********* Fresh: \n" + fresh.relations + "\n*********")
156-
val merged = pruned ++ fresh
157172

158173
/* This is required for both scala compilation and forked java compilation, despite
159174
* being redundant for the most common Java compilation (using the local compiler). */
160175
classfileManager.generated(fresh.relations.allProducts.toArray)
161176

177+
val merged = pruned ++ fresh
162178
debug("********* Merged: \n" + merged.relations + "\n*********")
163-
(merged, sources)
179+
merged
164180
}
165181

166182
/**
@@ -258,7 +274,9 @@ private[inc] abstract class IncrementalCommon(val log: Logger, options: IncOptio
258274
else incrementalExternalChanges
259275
}
260276

261-
InitialChanges(sourceChanges, removedProducts, changedBinaries, externalApiChanges)
277+
val init = InitialChanges(sourceChanges, removedProducts, changedBinaries, externalApiChanges)
278+
profiler.registerInitial(init)
279+
init
262280
}
263281

264282
/**

internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalNameHashing.scala

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ import xsbt.api.SameAPI
2424
* See [[MemberRefInvalidator]] for more information on how the name heuristics work to invalidate
2525
* member references.
2626
*/
27-
private[inc] class IncrementalNameHashingCommon(log: Logger, options: IncOptions)
28-
extends IncrementalCommon(log, options) {
27+
private[inc] class IncrementalNameHashingCommon(
28+
log: Logger,
29+
options: IncOptions,
30+
profiler: RunProfiler
31+
) extends IncrementalCommon(log, options, profiler) {
2932
import IncrementalCommon.transitiveDeps
3033

3134
private val memberRefInvalidator = new MemberRefInvalidator(log, options.logRecompileOnMacro())
@@ -142,13 +145,32 @@ private[inc] class IncrementalNameHashingCommon(log: Logger, options: IncOptions
142145

143146
val modifiedClass = change.modifiedClass
144147
val transitiveInheritance = invalidateByInheritance(relations, modifiedClass)
148+
profiler.registerEvent(
149+
zprof.InvalidationEvent.InheritanceKind,
150+
List(modifiedClass),
151+
transitiveInheritance,
152+
s"The invalidated class names inherit directly or transitively on ${modifiedClass}."
153+
)
154+
145155
val localInheritance =
146156
transitiveInheritance.flatMap(invalidateByLocalInheritance(relations, _))
157+
profiler.registerEvent(
158+
zprof.InvalidationEvent.LocalInheritanceKind,
159+
transitiveInheritance,
160+
localInheritance,
161+
s"The invalidated class names inherit (via local inheritance) directly or transitively on ${modifiedClass}."
162+
)
147163

148164
val memberRefSrcDeps = relations.memberRef.internal
149165
val memberRefInvalidation =
150166
memberRefInvalidator.get(memberRefSrcDeps, relations.names, change, isScalaClass)
151167
val memberRef = transitiveInheritance flatMap memberRefInvalidation
168+
profiler.registerEvent(
169+
zprof.InvalidationEvent.MemberReferenceKind,
170+
transitiveInheritance,
171+
memberRef,
172+
s"The invalidated class names refer directly or transitively to ${modifiedClass}."
173+
)
152174
val all = transitiveInheritance ++ localInheritance ++ memberRef
153175

154176
def debugMessage: String = {
@@ -182,5 +204,5 @@ private[inc] class IncrementalNameHashingCommon(log: Logger, options: IncOptions
182204
): Set[String] = relations.memberRef.internal.reverse(className)
183205
}
184206

185-
private final class IncrementalNameHashing(log: Logger, options: IncOptions)
186-
extends IncrementalNameHashingCommon(log, options)
207+
private final class IncrementalNameHashing(log: Logger, options: IncOptions, profiler: RunProfiler)
208+
extends IncrementalNameHashingCommon(log, options, profiler)

0 commit comments

Comments
 (0)