blob: c76be34b443ae4ac8e56063802c144407eb081ca [file] [log] [blame]
// 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.driver.CounterTrack;
import androidx.tracing.driver.ProcessTrack;
import androidx.tracing.driver.ThreadTrack;
import androidx.tracing.driver.TraceContext;
import androidx.tracing.driver.TraceDriver;
import androidx.tracing.driver.wire.TraceSink;
import androidx.tracing.driver.wire.TraceSinkUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class PerfettoTiming extends TimingImplBase {
private final TraceSink sink;
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) {
File directory = new File(options.perfettoTraceDumpDirectory);
int sequenceId = 1;
sink = TraceSinkUtils.TraceSink(directory, sequenceId);
traceDriver = new TraceDriver(sink, true);
TraceContext traceContext = traceDriver.getContext();
processTrack = traceContext.getOrCreateProcessTrack(1, title);
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();
threadTrack.beginSection(title);
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.getContext().flush();
sink.close();
}
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;
}
}
}