/* * Copyright (C) 2013 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.tools.perflib.vmtrace; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.primitives.UnsignedInts; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Stack; import java.util.concurrent.TimeUnit; public class Call { private final long mMethodId; /** * Note: The thread entry and exit times are stored as unsigned integers in the trace data. * In this model, they are stored as integers, but the getters for all of these time values * convert them into longs. */ private final int mEntryThreadTime; private final int mEntryGlobalTime; private final int mExitGlobalTime; private final int mExitThreadTime; private final long mInclusiveThreadTimeInCallees; private final long mInclusiveGlobalTimeInCallees; private final int mDepth; /** * Indicates whether the current call is recursive. A call is recursive if the same method * is present in its backstack. */ private final boolean mIsRecursive; private final List<Call> mCallees; private Call(@NonNull Builder builder, @NonNull Stack<Long> backStack) { mMethodId = builder.mMethodId; mEntryThreadTime = builder.mEntryThreadTime; mEntryGlobalTime = builder.mEntryGlobalTime; mExitThreadTime = builder.mExitThreadTime; mExitGlobalTime = builder.mExitGlobalTime; mDepth = backStack.size(); mIsRecursive = backStack.contains(mMethodId); if (builder.mCallees == null) { mCallees = Collections.emptyList(); } else { backStack.push(mMethodId); List<Call> callees = new ArrayList<Call>(builder.mCallees.size()); for (Builder b : builder.mCallees) { callees.add(b.build(backStack)); } backStack.pop(); mCallees = new ImmutableList.Builder<Call>().addAll(callees).build(); } mInclusiveThreadTimeInCallees = sumInclusiveTimes(mCallees, ClockType.THREAD); mInclusiveGlobalTimeInCallees = sumInclusiveTimes(mCallees, ClockType.GLOBAL); } private long sumInclusiveTimes(@NonNull List<Call> callees, ClockType clockType) { long sum = 0; for (Call c : callees) { sum += c.getInclusiveTime(clockType, TimeUnit.MICROSECONDS); } return sum; } public long getMethodId() { return mMethodId; } @NonNull public List<Call> getCallees() { return mCallees; } public int getDepth() { return mDepth; } /** * Returns true if the call is recursive (another call of the same method id is present * in its backstack) */ public boolean isRecursive() { return mIsRecursive; } public long getEntryTime(ClockType clockType, TimeUnit units) { long entryTime = clockType == ClockType.THREAD ? UnsignedInts.toLong(mEntryThreadTime) : UnsignedInts.toLong(mEntryGlobalTime); return units.convert(entryTime, VmTraceData.getDefaultTimeUnits()); } public long getExitTime(ClockType clockType, TimeUnit units) { long exitTime = clockType == ClockType.THREAD ? UnsignedInts.toLong(mExitThreadTime) : UnsignedInts.toLong(mExitGlobalTime); return units.convert(exitTime, VmTraceData.getDefaultTimeUnits()); } public long getInclusiveTime(ClockType clockType, TimeUnit units) { long inclusiveTime = clockType == ClockType.THREAD ? UnsignedInts.toLong(mExitThreadTime - mEntryThreadTime) : UnsignedInts.toLong(mExitGlobalTime - mEntryGlobalTime); return units.convert(inclusiveTime, VmTraceData.getDefaultTimeUnits()); } public long getExclusiveTime(ClockType clockType, TimeUnit units) { long inclusiveTimeInCallees = clockType == ClockType.THREAD ? mInclusiveThreadTimeInCallees : mInclusiveGlobalTimeInCallees; long exclusiveTime = getInclusiveTime(clockType, VmTraceData.getDefaultTimeUnits()) - inclusiveTimeInCallees; return units.convert(exclusiveTime, VmTraceData.getDefaultTimeUnits()); } public static class Builder { private final long mMethodId; private int mEntryThreadTime; private int mEntryGlobalTime; private int mExitGlobalTime; private int mExitThreadTime; private List<Builder> mCallees = null; public Builder(long methodId) { mMethodId = methodId; } public long getMethodId() { return mMethodId; } public void setMethodEntryTime(int threadTime, int globalTime) { mEntryThreadTime = threadTime; mEntryGlobalTime = globalTime; } public void setMethodExitTime(int threadTime, int globalTime) { mExitThreadTime = threadTime; mExitGlobalTime = globalTime; } public void addCallee(Builder c) { if (mCallees == null) { mCallees = new ArrayList<Builder>(); } mCallees.add(c); } @Nullable public List<Builder> getCallees() { return mCallees; } public int getMethodEntryThreadTime() { return mEntryThreadTime; } public int getMethodEntryGlobalTime() { return mEntryGlobalTime; } public int getMethodExitThreadTime() { return mExitThreadTime; } public int getMethodExitGlobalTime() { return mExitGlobalTime; } @NonNull public Call build(@NonNull Stack<Long> backStack) { return new Call(this, backStack); } } /** * Formats this call and all its call hierarchy using the given {@link com.android.tools.perflib.vmtrace.Call.Formatter} to * print the details for each method. */ public String format(Formatter f) { StringBuilder sb = new StringBuilder(100); printCallHierarchy(sb, f); return sb.toString(); } public interface Formatter { String format(Call c); } private static final Formatter METHOD_ID_FORMATTER = new Formatter() { @Override public String format(Call c) { return Long.toString(c.getMethodId()); } }; @Override public String toString() { return format(METHOD_ID_FORMATTER); } private void printCallHierarchy(@NonNull StringBuilder sb, Formatter formatter) { sb.append(" -> "); sb.append(formatter.format(this)); List<Call> callees = getCallees(); int lineStart = sb.lastIndexOf("\n"); int depth = sb.length() - (lineStart + 1); for (int i = 0; i < callees.size(); i++) { if (i != 0) { sb.append("\n"); sb.append(Strings.repeat(" ", depth)); } Call callee = callees.get(i); callee.printCallHierarchy(sb, formatter); } } @NonNull public Iterator<Call> getCallHierarchyIterator() { return new CallHierarchyIterator(this); } /** * An iterator for a call hierarchy. The iteration order matches the order in which the calls * were invoked. */ private static class CallHierarchyIterator implements Iterator<Call> { private final Stack<Call> mCallStack = new Stack<Call>(); public CallHierarchyIterator(@NonNull Call top) { mCallStack.push(top); } @Override public boolean hasNext() { return !mCallStack.isEmpty(); } @Override public Call next() { if (mCallStack.isEmpty()) { return null; } Call top = mCallStack.pop(); for (int i = top.getCallees().size() - 1; i >= 0; i--) { mCallStack.push(top.getCallees().get(i)); } return top; } @Override public void remove() { throw new UnsupportedOperationException(); } } }