Skip to content

Commit 51837ad

Browse files
committed
BazelProfile: precompute some fields
Some of the getters (getCriticalPath(), etc.) used to run in O(N) for N=threads.size(); now they are O(1) because we precompute what they'd return. Also, BazelProfile's members are no longer mutable, meaning we can safely precompute those values without fear of staleness. Signed-off-by: László Csomor <laszlo@engflow.com>
1 parent 3fa9602 commit 51837ad

1 file changed

Lines changed: 54 additions & 26 deletions

File tree

analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfile.java

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.engflow.bazel.invocation.analyzer.traceeventformat.CounterEvent;
2525
import com.engflow.bazel.invocation.analyzer.traceeventformat.TraceEventFormatConstants;
2626
import com.google.common.annotations.VisibleForTesting;
27+
import com.google.common.base.Preconditions;
2728
import com.google.common.base.Strings;
2829
import com.google.common.collect.ImmutableList;
2930
import com.google.common.collect.ImmutableMap;
@@ -78,15 +79,35 @@ public static BazelProfile createFromPath(String path) throws IllegalArgumentExc
7879

7980
public static BazelProfile createFromInputStream(InputStream inputStream)
8081
throws IllegalArgumentException {
81-
return new BazelProfile(
82+
return BazelProfile.create(
8283
new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)));
8384
}
8485

8586
private final BazelVersion bazelVersion;
86-
private final Map<String, String> otherData = new HashMap<>();
87-
private final Map<ThreadId, ProfileThread> threads = new HashMap<>();
87+
private final ImmutableMap<String, String> otherData;
88+
private final ImmutableMap<ThreadId, ProfileThread> threads;
89+
private final ProfileThread mainThread;
90+
private final Optional<ProfileThread> criticalPath;
91+
private final Optional<ProfileThread> gcThread;
92+
93+
private BazelProfile(
94+
BazelVersion bazelVersion,
95+
ImmutableMap<String, String> otherData,
96+
ImmutableMap<ThreadId, ProfileThread> threads,
97+
ProfileThread mainThread,
98+
Optional<ProfileThread> criticalPath,
99+
Optional<ProfileThread> gcThread) {
100+
this.bazelVersion = Preconditions.checkNotNull(bazelVersion);
101+
this.otherData = Preconditions.checkNotNull(otherData);
102+
this.threads = Preconditions.checkNotNull(threads);
103+
this.mainThread = Preconditions.checkNotNull(mainThread);
104+
this.criticalPath = Preconditions.checkNotNull(criticalPath);
105+
this.gcThread = Preconditions.checkNotNull(gcThread);
106+
}
88107

89-
private BazelProfile(JsonReader profileReader) {
108+
private static BazelProfile create(JsonReader profileReader) {
109+
Map<String, String> otherData = new HashMap<>();
110+
Map<ThreadId, ProfileThread> threads = new HashMap<>();
90111
try {
91112
boolean hasOtherData = false;
92113
boolean hasTraceEvents = false;
@@ -116,15 +137,11 @@ private BazelProfile(JsonReader profileReader) {
116137
continue;
117138
}
118139
ThreadId threadId = new ThreadId(pid, tid);
119-
ProfileThread profileThread =
120-
threads.compute(
121-
threadId,
122-
(key, t) -> {
123-
if (t == null) {
124-
t = new ProfileThread(threadId);
125-
}
126-
return t;
127-
});
140+
ProfileThread profileThread = threads.get(threadId);
141+
if (profileThread == null) {
142+
profileThread = new ProfileThread(threadId);
143+
threads.put(threadId, profileThread);
144+
}
128145
// TODO: Use success response to take action on errant events.
129146
profileThread.addEvent(traceEvent);
130147
}
@@ -147,23 +164,34 @@ private BazelProfile(JsonReader profileReader) {
147164
throw new IllegalArgumentException("Could not parse Bazel profile.", e);
148165
}
149166

150-
this.bazelVersion =
167+
BazelVersion bazelVersion =
151168
BazelVersion.parse(otherData.get(BazelProfileConstants.OTHER_DATA_BAZEL_VERSION));
169+
ProfileThread mainThread = null;
170+
ProfileThread criticalPath = null;
171+
ProfileThread gcThread = null;
172+
for (ProfileThread e : threads.values()) {
173+
if (mainThread == null && isMainThread(e)) {
174+
mainThread = e;
175+
} else if (criticalPath == null && isCriticalPathThread(e)) {
176+
criticalPath = e;
177+
} else if (gcThread == null && isGarbageCollectorThread(e)) {
178+
gcThread = e;
179+
}
180+
}
152181

153-
if (!containsMainThread()) {
182+
if (mainThread == null) {
154183
throw new IllegalArgumentException(
155184
String.format(
156185
"Invalid Bazel profile, JSON file missing \"%s\".",
157186
BazelProfileConstants.THREAD_MAIN));
158187
}
159-
}
160-
161-
/**
162-
* This method is called from the constructor. Either it needs to stay private or it must be
163-
* declared final, so that it cannot be overridden.
164-
*/
165-
private boolean containsMainThread() {
166-
return threads.values().stream().anyMatch(BazelProfile::isMainThread);
188+
return new BazelProfile(
189+
bazelVersion,
190+
ImmutableMap.copyOf(otherData),
191+
ImmutableMap.copyOf(threads),
192+
mainThread,
193+
Optional.ofNullable(criticalPath),
194+
Optional.ofNullable(gcThread));
167195
}
168196

169197
/**
@@ -228,15 +256,15 @@ public Stream<ProfileThread> getThreads() {
228256
}
229257

230258
public Optional<ProfileThread> getCriticalPath() {
231-
return threads.values().stream().filter(BazelProfile::isCriticalPathThread).findAny();
259+
return criticalPath;
232260
}
233261

234262
public ProfileThread getMainThread() {
235-
return threads.values().stream().filter(BazelProfile::isMainThread).findAny().get();
263+
return mainThread;
236264
}
237265

238266
public Optional<ProfileThread> getGarbageCollectorThread() {
239-
return threads.values().stream().filter(BazelProfile::isGarbageCollectorThread).findAny();
267+
return gcThread;
240268
}
241269

242270
public Optional<ImmutableList<CounterEvent>> getActionCounts() {

0 commit comments

Comments
 (0)