Skip to content

Commit 85e1721

Browse files
authored
BazelProfile: precompute some fields (#195)
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 the values without fear of staleness. Signed-off-by: László Csomor <laszlo@engflow.com>
1 parent 5847f5b commit 85e1721

1 file changed

Lines changed: 60 additions & 34 deletions

File tree

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

Lines changed: 60 additions & 34 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;
@@ -42,7 +43,6 @@
4243
import java.util.Map;
4344
import java.util.Optional;
4445
import java.util.concurrent.atomic.AtomicLong;
45-
import java.util.stream.Collectors;
4646
import java.util.stream.Stream;
4747
import java.util.zip.GZIPInputStream;
4848
import java.util.zip.ZipException;
@@ -80,21 +80,40 @@ public static BazelProfile createFromPath(String path) throws IllegalArgumentExc
8080

8181
public static BazelProfile createFromInputStream(InputStream inputStream)
8282
throws IllegalArgumentException {
83-
return new BazelProfile(
83+
return BazelProfile.create(
8484
new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)));
8585
}
8686

8787
public static BazelProfile of(Reader reader) {
88-
return new BazelProfile(new JsonReader(reader));
88+
return BazelProfile.create(new JsonReader(reader));
8989
}
9090

9191
private final BazelVersion bazelVersion;
92-
private final Map<String, String> otherData = new HashMap<>();
93-
private final Map<ThreadId, ProfileThread> threads;
92+
private final ImmutableMap<String, String> otherData;
93+
private final ImmutableMap<ThreadId, ProfileThread> threads;
94+
private final ProfileThread mainThread;
95+
private final Optional<ProfileThread> criticalPath;
96+
private final Optional<ProfileThread> gcThread;
97+
98+
private BazelProfile(
99+
BazelVersion bazelVersion,
100+
ImmutableMap<String, String> otherData,
101+
ImmutableMap<ThreadId, ProfileThread> threads,
102+
ProfileThread mainThread,
103+
Optional<ProfileThread> criticalPath,
104+
Optional<ProfileThread> gcThread) {
105+
this.bazelVersion = Preconditions.checkNotNull(bazelVersion);
106+
this.otherData = Preconditions.checkNotNull(otherData);
107+
this.threads = Preconditions.checkNotNull(threads);
108+
this.mainThread = Preconditions.checkNotNull(mainThread);
109+
this.criticalPath = Preconditions.checkNotNull(criticalPath);
110+
this.gcThread = Preconditions.checkNotNull(gcThread);
111+
}
94112

95-
private BazelProfile(JsonReader profileReader) {
113+
private static BazelProfile create(JsonReader profileReader) {
114+
ImmutableMap.Builder<String, String> otherDataBuilder = ImmutableMap.builder();
115+
Map<ThreadId, ProfileThread.Builder> threadBuilders = new HashMap<>();
96116
try {
97-
Map<ThreadId, ProfileThread.Builder> threadBuilders = new HashMap<>();
98117
boolean hasOtherData = false;
99118
boolean hasTraceEvents = false;
100119
profileReader.beginObject();
@@ -104,7 +123,7 @@ private BazelProfile(JsonReader profileReader) {
104123
hasOtherData = true;
105124
profileReader.beginObject();
106125
while (profileReader.hasNext()) {
107-
otherData.put(profileReader.nextName(), profileReader.nextString());
126+
otherDataBuilder.put(profileReader.nextName(), profileReader.nextString());
108127
}
109128
profileReader.endObject();
110129
break;
@@ -123,15 +142,11 @@ private BazelProfile(JsonReader profileReader) {
123142
continue;
124143
}
125144
ThreadId threadId = new ThreadId(pid, tid);
126-
ProfileThread.Builder profileThreadBuilder =
127-
threadBuilders.compute(
128-
threadId,
129-
(key, t) -> {
130-
if (t == null) {
131-
t = new ProfileThread.Builder().setThreadId(key);
132-
}
133-
return t;
134-
});
145+
ProfileThread.Builder profileThreadBuilder = threadBuilders.get(threadId);
146+
if (profileThreadBuilder == null) {
147+
profileThreadBuilder = new ProfileThread.Builder().setThreadId(threadId);
148+
threadBuilders.put(threadId, profileThreadBuilder);
149+
}
135150
// TODO: Use success response to take action on errant events.
136151
profileThreadBuilder.addEvent(traceEvent);
137152
}
@@ -150,30 +165,41 @@ private BazelProfile(JsonReader profileReader) {
150165
TraceEventFormatConstants.SECTION_OTHER_DATA,
151166
TraceEventFormatConstants.SECTION_TRACE_EVENTS));
152167
}
153-
threads =
154-
threadBuilders.entrySet().stream()
155-
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build()));
156168
} catch (IllegalStateException | IOException e) {
157169
throw new IllegalArgumentException("Could not parse Bazel profile.", e);
158170
}
159171

160-
this.bazelVersion =
172+
ImmutableMap<String, String> otherData = otherDataBuilder.build();
173+
BazelVersion bazelVersion =
161174
BazelVersion.parse(otherData.get(BazelProfileConstants.OTHER_DATA_BAZEL_VERSION));
162-
163-
if (!containsMainThread()) {
175+
ImmutableMap.Builder<ThreadId, ProfileThread> threadsMapBuilder = ImmutableMap.builder();
176+
ProfileThread mainThread = null;
177+
ProfileThread criticalPath = null;
178+
ProfileThread gcThread = null;
179+
for (Map.Entry<ThreadId, ProfileThread.Builder> entry : threadBuilders.entrySet()) {
180+
ProfileThread t = entry.getValue().build();
181+
if (mainThread == null && isMainThread(t)) {
182+
mainThread = t;
183+
} else if (criticalPath == null && isCriticalPathThread(t)) {
184+
criticalPath = t;
185+
} else if (gcThread == null && isGarbageCollectorThread(t)) {
186+
gcThread = t;
187+
}
188+
threadsMapBuilder.put(entry.getKey(), t);
189+
}
190+
if (mainThread == null) {
164191
throw new IllegalArgumentException(
165192
String.format(
166193
"Invalid Bazel profile, JSON file missing \"%s\".",
167194
BazelProfileConstants.THREAD_MAIN));
168195
}
169-
}
170-
171-
/**
172-
* This method is called from the constructor. Either it needs to stay private or it must be
173-
* declared final, so that it cannot be overridden.
174-
*/
175-
private boolean containsMainThread() {
176-
return threads.values().stream().anyMatch(BazelProfile::isMainThread);
196+
return new BazelProfile(
197+
bazelVersion,
198+
otherData,
199+
threadsMapBuilder.build(),
200+
mainThread,
201+
Optional.ofNullable(criticalPath),
202+
Optional.ofNullable(gcThread));
177203
}
178204

179205
/**
@@ -238,15 +264,15 @@ public Stream<ProfileThread> getThreads() {
238264
}
239265

240266
public Optional<ProfileThread> getCriticalPath() {
241-
return threads.values().stream().filter(BazelProfile::isCriticalPathThread).findAny();
267+
return criticalPath;
242268
}
243269

244270
public ProfileThread getMainThread() {
245-
return threads.values().stream().filter(BazelProfile::isMainThread).findAny().get();
271+
return mainThread;
246272
}
247273

248274
public Optional<ProfileThread> getGarbageCollectorThread() {
249-
return threads.values().stream().filter(BazelProfile::isGarbageCollectorThread).findAny();
275+
return gcThread;
250276
}
251277

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

0 commit comments

Comments
 (0)