blob: 413b3210cff7bcdabd530492d1da9d0a040b1388 [file]
// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.timing;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
import androidx.tracing.CounterTrack;
import androidx.tracing.EventMetadataCloseable;
import androidx.tracing.ProcessTrack;
import androidx.tracing.PropagationUnsupportedToken;
import androidx.tracing.ThreadTrack;
import androidx.tracing.TraceContext;
import androidx.tracing.TraceDriver;
import androidx.tracing.wire.TraceDriverUtils;
import androidx.tracing.wire.TraceSink;
import androidx.tracing.wire.TraceSinkUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import kotlinx.coroutines.Dispatchers;
import okio.BufferedSink;
import okio.Okio;
public class PerfettoTiming extends TimingImplBase {
// The API now allows you to specify the category and in the future let you conditionally turn on
// categories.
private static final String TRACE_CATEGORY = "R8";
private final TraceDriver traceDriver;
private final ProcessTrack processTrack;
private final ThreadTrack threadTrack;
private int depth = 0;
private CounterTrack memoryTrack;
private Future<Void> memoryTracker;
private volatile boolean memoryTrackerActive = true;
public PerfettoTiming(String title, InternalOptions options, ExecutorService executorService) {
int sequenceId = 1;
TraceSink sink;
if (options.perfettoTraceDumpFile != null) {
BufferedSink bufferedSink;
try {
File file = new File(options.perfettoTraceDumpFile);
bufferedSink = Okio.buffer(Okio.appendingSink(file));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
sink = new TraceSink(sequenceId, bufferedSink, Dispatchers.getIO());
} else {
File directory = new File(options.perfettoTraceDumpDirectory);
sink = TraceSinkUtils.TraceSink(directory, sequenceId);
}
// Populates the process track correctly.
traceDriver = TraceDriverUtils.TraceDriver(sink);
TraceContext traceContext = traceDriver.getContext();
processTrack = traceContext.getProcess();
int mainThreadId = (int) Thread.currentThread().getId();
threadTrack = processTrack.getOrCreateThreadTrack(mainThreadId, "Main thread");
begin(title);
// Memory tracking requires an executor service.
if (executorService != null) {
memoryTrack = processTrack.getOrCreateCounterTrack("Memory");
memoryTracker =
executorService.submit(
() -> {
while (true) {
// Check the memoryCounterActive flag every 250ms.
for (int i = 0; i < 4; i++) {
Thread.sleep(250);
if (!memoryTrackerActive) {
return null;
}
}
// Update the memory counter every 1s.
memoryTrack.setCounter(
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
}
});
}
}
@Override
public Timing createThreadTiming(String title, InternalOptions options) {
int threadId = (int) Thread.currentThread().getId();
ThreadTrack threadTrack = processTrack.getOrCreateThreadTrack(threadId, "Worker");
return new PerfettoThreadTiming(threadTrack).begin(title);
}
@Override
public Timing begin(String title) {
assert threadTrack.getId() == Thread.currentThread().getId();
// Actually dispatch events to trace sink after adding any additional event metadata if needed.
// You can do so by using: eventMetadataCloseable.metadata.add* APIs.
// R8 is using the low level track APIs (and Java) which is why the API looks the way it does.
EventMetadataCloseable eventMetadataCloseable =
threadTrack.beginSection$tracing(
TRACE_CATEGORY, title, PropagationUnsupportedToken.INSTANCE);
eventMetadataCloseable.metadata.dispatchToTraceSink();
depth++;
return this;
}
@Override
public Timing end() {
assert threadTrack.getId() == Thread.currentThread().getId();
threadTrack.endSection();
depth--;
return this;
}
@Override
public TimingMerger beginMerger(String title, int numberOfThreads) {
return EmptyTimingMerger.get();
}
@Override
public void report() {
end();
assert depth == 0 : depth;
awaitMemoryTracker();
traceDriver.close();
}
@Override
public Timing endAll() {
for (int i = 1; i < depth; i++) {
end();
}
return this;
}
private void awaitMemoryTracker() {
if (memoryTracker != null) {
// Signal to the memory tracker to stop.
memoryTrackerActive = false;
// Await the memory tracker.
try {
memoryTracker.get();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for future.", e);
} catch (ExecutionException e) {
throw unwrapExecutionException(e);
}
memoryTrack = null;
memoryTracker = null;
}
}
}