From 9ea3f60a3c2d947b5fc76af8aed98b025954a237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Csomor?= Date: Thu, 9 Oct 2025 17:00:35 +0200 Subject: [PATCH] BazelProfile: precompute some fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../analyzer/bazelprofile/BazelProfile.java | 94 ++++++++++++------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfile.java b/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfile.java index 1120161..3f6c7f5 100644 --- a/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfile.java +++ b/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfile.java @@ -24,6 +24,7 @@ import com.engflow.bazel.invocation.analyzer.traceeventformat.CounterEvent; import com.engflow.bazel.invocation.analyzer.traceeventformat.TraceEventFormatConstants; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -42,7 +43,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; @@ -80,21 +80,40 @@ public static BazelProfile createFromPath(String path) throws IllegalArgumentExc public static BazelProfile createFromInputStream(InputStream inputStream) throws IllegalArgumentException { - return new BazelProfile( + return BazelProfile.create( new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))); } public static BazelProfile of(Reader reader) { - return new BazelProfile(new JsonReader(reader)); + return BazelProfile.create(new JsonReader(reader)); } private final BazelVersion bazelVersion; - private final Map otherData = new HashMap<>(); - private final Map threads; + private final ImmutableMap otherData; + private final ImmutableMap threads; + private final ProfileThread mainThread; + private final Optional criticalPath; + private final Optional gcThread; + + private BazelProfile( + BazelVersion bazelVersion, + ImmutableMap otherData, + ImmutableMap threads, + ProfileThread mainThread, + Optional criticalPath, + Optional gcThread) { + this.bazelVersion = Preconditions.checkNotNull(bazelVersion); + this.otherData = Preconditions.checkNotNull(otherData); + this.threads = Preconditions.checkNotNull(threads); + this.mainThread = Preconditions.checkNotNull(mainThread); + this.criticalPath = Preconditions.checkNotNull(criticalPath); + this.gcThread = Preconditions.checkNotNull(gcThread); + } - private BazelProfile(JsonReader profileReader) { + private static BazelProfile create(JsonReader profileReader) { + ImmutableMap.Builder otherDataBuilder = ImmutableMap.builder(); + Map threadBuilders = new HashMap<>(); try { - Map threadBuilders = new HashMap<>(); boolean hasOtherData = false; boolean hasTraceEvents = false; profileReader.beginObject(); @@ -104,7 +123,7 @@ private BazelProfile(JsonReader profileReader) { hasOtherData = true; profileReader.beginObject(); while (profileReader.hasNext()) { - otherData.put(profileReader.nextName(), profileReader.nextString()); + otherDataBuilder.put(profileReader.nextName(), profileReader.nextString()); } profileReader.endObject(); break; @@ -123,15 +142,11 @@ private BazelProfile(JsonReader profileReader) { continue; } ThreadId threadId = new ThreadId(pid, tid); - ProfileThread.Builder profileThreadBuilder = - threadBuilders.compute( - threadId, - (key, t) -> { - if (t == null) { - t = new ProfileThread.Builder().setThreadId(key); - } - return t; - }); + ProfileThread.Builder profileThreadBuilder = threadBuilders.get(threadId); + if (profileThreadBuilder == null) { + profileThreadBuilder = new ProfileThread.Builder().setThreadId(threadId); + threadBuilders.put(threadId, profileThreadBuilder); + } // TODO: Use success response to take action on errant events. profileThreadBuilder.addEvent(traceEvent); } @@ -150,30 +165,41 @@ private BazelProfile(JsonReader profileReader) { TraceEventFormatConstants.SECTION_OTHER_DATA, TraceEventFormatConstants.SECTION_TRACE_EVENTS)); } - threads = - threadBuilders.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build())); } catch (IllegalStateException | IOException e) { throw new IllegalArgumentException("Could not parse Bazel profile.", e); } - this.bazelVersion = + ImmutableMap otherData = otherDataBuilder.build(); + BazelVersion bazelVersion = BazelVersion.parse(otherData.get(BazelProfileConstants.OTHER_DATA_BAZEL_VERSION)); - - if (!containsMainThread()) { + ImmutableMap.Builder threadsMapBuilder = ImmutableMap.builder(); + ProfileThread mainThread = null; + ProfileThread criticalPath = null; + ProfileThread gcThread = null; + for (Map.Entry entry : threadBuilders.entrySet()) { + ProfileThread t = entry.getValue().build(); + if (mainThread == null && isMainThread(t)) { + mainThread = t; + } else if (criticalPath == null && isCriticalPathThread(t)) { + criticalPath = t; + } else if (gcThread == null && isGarbageCollectorThread(t)) { + gcThread = t; + } + threadsMapBuilder.put(entry.getKey(), t); + } + if (mainThread == null) { throw new IllegalArgumentException( String.format( "Invalid Bazel profile, JSON file missing \"%s\".", BazelProfileConstants.THREAD_MAIN)); } - } - - /** - * This method is called from the constructor. Either it needs to stay private or it must be - * declared final, so that it cannot be overridden. - */ - private boolean containsMainThread() { - return threads.values().stream().anyMatch(BazelProfile::isMainThread); + return new BazelProfile( + bazelVersion, + otherData, + threadsMapBuilder.build(), + mainThread, + Optional.ofNullable(criticalPath), + Optional.ofNullable(gcThread)); } /** @@ -238,15 +264,15 @@ public Stream getThreads() { } public Optional getCriticalPath() { - return threads.values().stream().filter(BazelProfile::isCriticalPathThread).findAny(); + return criticalPath; } public ProfileThread getMainThread() { - return threads.values().stream().filter(BazelProfile::isMainThread).findAny().get(); + return mainThread; } public Optional getGarbageCollectorThread() { - return threads.values().stream().filter(BazelProfile::isGarbageCollectorThread).findAny(); + return gcThread; } public Optional> getActionCounts() {