Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.1.0]Include Bazel version information in profile metadata. #17576

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/google/devtools/build/lib/profiler/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ java_library(
],
deps = [
":network_metrics_collector",
"//src/main/java/com/google/devtools/build/lib/actions:resource_estimator",
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_version_info",
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/clock",
"//src/main/java/com/google/devtools/build/lib/collect:extrema",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.profiler;


import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
import com.google.devtools.build.lib.profiler.Profiler.TaskData;
import com.google.gson.stream.JsonWriter;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.annotation.Nullable;

/** Writes the profile in Json Trace file format. */
class JsonTraceFileWriter implements Runnable {
protected final BlockingQueue<TraceData> queue;
protected final Thread thread;
protected IOException savedException;

private final OutputStream outStream;
private final long profileStartTimeNanos;
private final ThreadLocal<Boolean> metadataPosted = ThreadLocal.withInitial(() -> Boolean.FALSE);
private final boolean slimProfile;
private final UUID buildID;
private final String outputBase;

private static final long SLIM_PROFILE_EVENT_THRESHOLD = 10_000;
private static final long SLIM_PROFILE_MAXIMAL_PAUSE_NS = Duration.ofMillis(100).toNanos();
private static final long SLIM_PROFILE_MAXIMAL_DURATION_NS = Duration.ofMillis(250).toNanos();

private static final TaskData POISON_PILL =
new TaskData(/* startTimeNanos= */ 0, /* eventType= */ null, "poison pill");

JsonTraceFileWriter(
OutputStream outStream,
long profileStartTimeNanos,
boolean slimProfile,
String outputBase,
UUID buildID) {
this.queue = new LinkedBlockingQueue<>();
this.thread = new Thread(this, "profile-writer-thread");
this.outStream = outStream;
this.profileStartTimeNanos = profileStartTimeNanos;
this.slimProfile = slimProfile;
this.buildID = buildID;
this.outputBase = outputBase;
}

public void shutdown() throws IOException {
// Add poison pill to queue and then wait for writer thread to shut down.
queue.add(POISON_PILL);
try {
thread.join();
} catch (InterruptedException e) {
thread.interrupt();
Thread.currentThread().interrupt();
}
if (savedException != null) {
throw savedException;
}
}

public void start() {
thread.start();
}

public void enqueue(TraceData data) {
if (!metadataPosted.get()) {
metadataPosted.set(Boolean.TRUE);
queue.add(new ThreadMetadata());
}
queue.add(data);
}

private static final class MergedEvent {
int count = 0;
long startTimeNanos;
long endTimeNanos;
TaskData data;

/*
* Tries to merge an additional event, i.e. if the event is close enough to the already merged
* event.
*
* Returns null, if merging was possible.
* If not mergeable, returns the TaskData of the previously merged events and clears the
* internal data structures.
*/
@Nullable
TaskData maybeMerge(TaskData data) {
long startTimeNanos = data.startTimeNanos;
long endTimeNanos = startTimeNanos + data.durationNanos;
if (count > 0 && startTimeNanos >= this.startTimeNanos && endTimeNanos <= this.endTimeNanos) {
// Skips child tasks.
return null;
}
if (count == 0) {
this.data = data;
this.startTimeNanos = startTimeNanos;
this.endTimeNanos = endTimeNanos;
count++;
return null;
} else if (startTimeNanos <= this.endTimeNanos + SLIM_PROFILE_MAXIMAL_PAUSE_NS) {
this.endTimeNanos = endTimeNanos;
count++;
return null;
} else {
TaskData ret = getAndReset();
this.startTimeNanos = startTimeNanos;
this.endTimeNanos = endTimeNanos;
this.data = data;
count = 1;
return ret;
}
}

// Returns a TaskData object representing the merged data and clears internal data structures.
TaskData getAndReset() {
TaskData ret;
if (data == null || count <= 1) {
ret = data;
} else {
ret =
new TaskData(
data.threadId,
this.startTimeNanos,
this.endTimeNanos - this.startTimeNanos,
"merged " + count + " events");
}
count = 0;
data = null;
return ret;
}
}

private static boolean isCandidateForMerging(TaskData data) {
return data.durationNanos > 0
&& data.durationNanos < SLIM_PROFILE_MAXIMAL_DURATION_NS
&& data.type != ProfilerTask.CRITICAL_PATH_COMPONENT;
}

/**
* Saves all gathered information from taskQueue queue to the file. Method is invoked internally
* by the Timer-based thread and at the end of profiling session.
*/
@Override
public void run() {
try {
boolean receivedPoisonPill = false;
try (JsonWriter writer =
new JsonWriter(
// The buffer size of 262144 is chosen at random.
new OutputStreamWriter(
new BufferedOutputStream(outStream, 262144), StandardCharsets.UTF_8))) {
writer.beginObject();
writer.name("otherData");
writer.beginObject();
writer.name("bazel_version").value(BlazeVersionInfo.instance().getReleaseName());
writer.name("build_id").value(buildID.toString());
writer.name("output_base").value(outputBase);
writer.name("date").value(new Date().toString());
writer.endObject();
writer.name("traceEvents");
writer.beginArray();

// Generate metadata event for the critical path as thread 0 in disguise.
ThreadMetadata criticalPathMetadata =
ThreadMetadata.createFakeThreadMetadataForCriticalPath();
criticalPathMetadata.writeTraceData(writer, profileStartTimeNanos);

HashMap<Long, MergedEvent> eventsPerThread = new HashMap<>();
int eventCount = 0;
TraceData data;
while ((data = queue.take()) != POISON_PILL) {
Preconditions.checkNotNull(data);
eventCount++;

if (slimProfile
&& eventCount > SLIM_PROFILE_EVENT_THRESHOLD
&& data instanceof TaskData
&& isCandidateForMerging((TaskData) data)) {
TaskData taskData = (TaskData) data;
eventsPerThread.putIfAbsent(taskData.threadId, new MergedEvent());
TaskData mergedTaskData = eventsPerThread.get(taskData.threadId).maybeMerge(taskData);
if (mergedTaskData != null) {
mergedTaskData.writeTraceData(writer, profileStartTimeNanos);
}
} else {
data.writeTraceData(writer, profileStartTimeNanos);
}
}
for (JsonTraceFileWriter.MergedEvent value : eventsPerThread.values()) {
TaskData taskData = value.getAndReset();
if (taskData != null) {
taskData.writeTraceData(writer, profileStartTimeNanos);
}
}
receivedPoisonPill = true;
writer.setIndent(" ");
writer.endArray();
writer.endObject();
} catch (IOException e) {
this.savedException = e;
if (!receivedPoisonPill) {
while (queue.take() != POISON_PILL) {
// We keep emptying the queue, but we can't write anything.
}
}
}
} catch (InterruptedException e) {
// Exit silently.
}
}
}