blob: 1381574d2c8ccf932dde29ac3f90789864f7e621 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* 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.android.tradefed.invoker.tracing;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.FileUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import perfetto.protos.PerfettoTrace.DebugAnnotation;
import perfetto.protos.PerfettoTrace.ProcessDescriptor;
import perfetto.protos.PerfettoTrace.ThreadDescriptor;
import perfetto.protos.PerfettoTrace.Trace;
import perfetto.protos.PerfettoTrace.TracePacket;
import perfetto.protos.PerfettoTrace.TrackDescriptor;
import perfetto.protos.PerfettoTrace.TrackEvent;
/** Main class helping to describe and manage an active trace. */
public class ActiveTrace {
public static final String TRACE_KEY = "invocation-trace";
private final long pid;
private final long tid;
private final long traceUuid;
private final int uid = 5555; // TODO: collect a real uid
private final boolean mainTradefedProcess;
private final Map<String, Long> mThreadToTracker;
// File where the final trace gets outputed
private File mTraceOutput;
public ActiveTrace(long pid, long tid) {
this(pid, tid, false);
}
/**
* Constructor.
*
* @param pid Current process id
* @param tid Current thread id
*/
public ActiveTrace(long pid, long tid, boolean mainProcess) {
this.pid = pid;
this.tid = tid;
this.traceUuid = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE;
mThreadToTracker = new HashMap<>();
mainTradefedProcess = mainProcess;
}
/** Start the tracing and report the metadata of the trace. */
public void startTracing(boolean isSubprocess) {
if (mTraceOutput != null) {
throw new IllegalStateException("Tracing was already started.");
}
try {
mTraceOutput = FileUtil.createTempFile(TRACE_KEY, ".perfetto-trace");
} catch (IOException e) {
CLog.e(e);
}
// Initialize all the trace metadata
createMainInvocationTracker((int) pid, (int) tid, traceUuid, isSubprocess);
}
/** Provide the trace file from a subprocess to be added to the parent. */
public void addSubprocessTrace(File subTrace) {
if (mTraceOutput == null) {
return;
}
try (FileInputStream stream = new FileInputStream(subTrace)) {
try (GZIPInputStream gzip = new GZIPInputStream(stream)) {
CLog.logAndDisplay(LogLevel.DEBUG, "merging with gzipped %s", subTrace);
FileUtil.writeToFile(gzip, mTraceOutput, true);
return;
} catch (IOException e) {
CLog.logAndDisplay(LogLevel.DEBUG, "%s isn't gzip.", subTrace);
}
} catch (IOException e) {
CLog.e(e);
}
try (FileInputStream stream = new FileInputStream(subTrace)) {
FileUtil.writeToFile(stream, mTraceOutput, true);
} catch (IOException e) {
CLog.e(e);
}
}
public void reportTraceEvent(String categories, String name, TrackEvent.Type type) {
reportTraceEvent(categories, name, (int) tid, null, type);
}
/**
* thread id of the thread that initiated the tracing.
*/
public long reportingThreadId() {
return tid;
}
public boolean isMainTradefedProcess() {
return mainTradefedProcess;
}
/**
* Very basic event reporting to do START / END of traces.
*
* @param categories Category associated with event
* @param name Event name
* @param type Type of the event being reported
*/
public void reportTraceEvent(
String categories, String name, int threadId, String threadName, TrackEvent.Type type) {
long traceIdentifier = traceUuid;
if (threadId != this.tid) {
synchronized (mThreadToTracker) {
if (mThreadToTracker.containsKey(Integer.toString(threadId))) {
Long returnedValue = mThreadToTracker.get(Integer.toString(threadId));
if (returnedValue == null) {
CLog.e("Consistency error in trace identifier.");
InvocationMetricLogger.addInvocationMetrics(
InvocationMetricKey.TRACE_INTERNAL_ERROR, 1);
return;
}
traceIdentifier = returnedValue;
} else {
traceIdentifier = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE;
createThreadTracker((int) pid, threadId, threadName, traceIdentifier);
mThreadToTracker.put(Integer.toString(threadId), Long.valueOf(traceIdentifier));
}
}
}
TracePacket.Builder tracePacket =
TracePacket.newBuilder()
.setTrustedUid(uid)
.setTrustedPid((int) pid)
.setTimestamp(System.nanoTime())
.setTrustedPacketSequenceId(1)
.setSequenceFlags(1)
.setProcessDescriptor(ProcessDescriptor.newBuilder().setPid((int) pid))
.setThreadDescriptor(ThreadDescriptor.newBuilder().setTid(threadId))
.setTrackEvent(
TrackEvent.newBuilder()
.setTrackUuid(traceIdentifier)
.setName(name)
.setType(type)
.addCategories(categories)
.addDebugAnnotations(
DebugAnnotation.newBuilder().setName(name)));
writeToTrace(tracePacket.build());
}
/** Reports the final trace files and clean up resources as needed. */
public File finalizeTracing() {
CLog.logAndDisplay(LogLevel.DEBUG, "Finalizing trace: %s", mTraceOutput);
File trace = mTraceOutput;
mTraceOutput = null;
return trace;
}
private String createProcessName(boolean isSubprocess) {
if (isSubprocess) {
return "subprocess-test-invocation";
}
if (isMainTradefedProcess()) {
return "Tradefed";
}
return "test-invocation";
}
private void createMainInvocationTracker(
int pid, int tid, long traceUuid, boolean isSubprocess) {
TrackDescriptor.Builder descriptor =
TrackDescriptor.newBuilder()
.setUuid(traceUuid)
.setName(createProcessName(isSubprocess))
.setThread(
ThreadDescriptor.newBuilder()
.setTid(tid)
.setThreadName("invocation-thread")
.setPid(pid))
.setProcess(
ProcessDescriptor.newBuilder()
.setPid(pid)
.setProcessName(createProcessName(isSubprocess)));
TracePacket.Builder traceTrackDescriptor =
TracePacket.newBuilder()
.setTrustedUid(uid)
.setTimestamp(System.nanoTime())
.setTrustedPacketSequenceId(1)
.setSequenceFlags(1)
.setTrustedPid(pid)
.setTrackDescriptor(descriptor.build());
writeToTrace(traceTrackDescriptor.build());
}
private void createThreadTracker(int pid, int tid, String threadName, long traceUuid) {
TrackDescriptor.Builder descriptor =
TrackDescriptor.newBuilder()
.setUuid(traceUuid)
.setThread(
ThreadDescriptor.newBuilder()
.setTid(tid)
.setThreadName(threadName)
.setPid(pid));
TracePacket.Builder traceTrackDescriptor =
TracePacket.newBuilder()
.setTrustedUid(uid)
.setTimestamp(System.nanoTime())
.setTrustedPacketSequenceId(1)
.setSequenceFlags(1)
.setTrustedPid(pid)
.setTrackDescriptor(descriptor.build());
writeToTrace(traceTrackDescriptor.build());
}
private synchronized void writeToTrace(TracePacket packet) {
if (mTraceOutput == null) {
return;
}
// Perfetto UI supports repeated Trace
Trace wrappingTrace = Trace.newBuilder().addPacket(packet).build();
try (FileOutputStream out = new FileOutputStream(mTraceOutput, true)) {
wrappingTrace.writeTo(out);
out.flush();
} catch (IOException e) {
CLog.e("Failed to write execution trace to file.");
CLog.e(e);
}
}
}