/*
* 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.Nullable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/** Statistics per method. */
public class MethodProfileData {
/** {@link TimeUnit} for all time values stored in this model. */
private static final TimeUnit DATA_TIME_UNITS = TimeUnit.NANOSECONDS;
/** Stats maintained per thread. Key is thread id. */
private final Map<Integer, MethodStats> mPerThreadCumulativeStats;
/** Stats maintained per thread and per callee. */
private final Table<Integer, Long, MethodStats> mPerThreadStatsByCallee;
/** Stats maintained per thread and per caller. */
private final Table<Integer, Long, MethodStats> mPerThreadStatsByCaller;
/** Indicates whether this method was ever used recursively. */
private final boolean mIsRecursive;
private MethodProfileData(Builder b) {
mPerThreadCumulativeStats = ImmutableMap.copyOf(b.mPerThreadCumulativeStats);
mPerThreadStatsByCallee = ImmutableTable.copyOf(b.mPerThreadStatsByCallee);
mPerThreadStatsByCaller = ImmutableTable.copyOf(b.mPerThreadStatsByCaller);
mIsRecursive = b.mRecursive;
}
/** Returns the number of invocations of this method in a given thread. */
public long getInvocationCount(ThreadInfo thread) {
MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
return getInvocationCount(stats);
}
public long getInvocationCountFromCaller(ThreadInfo thread, Long callerId) {
MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
return getInvocationCount(stats);
}
/** Returns whether this method was ever called recursively. */
public boolean isRecursive() {
return mIsRecursive;
}
/** Returns the exclusive time of this method in a particular thread in the given time units. */
public long getExclusiveTime(ThreadInfo thread, ClockType clockType, TimeUnit unit) {
MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
return getExclusiveTime(stats, clockType, unit);
}
/** Returns the inclusive time of this method in a particular thread in the given time units. */
public long getInclusiveTime(ThreadInfo thread, ClockType clockType, TimeUnit unit) {
MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
return getInclusiveTime(stats, clockType, unit);
}
/** Returns the callers for this method in a given thread. (across all its invocations). */
public Set<Long> getCallers(ThreadInfo thread) {
Map<Long, MethodStats> perCallerStats = mPerThreadStatsByCaller.row(thread.getId());
return perCallerStats.keySet();
}
/** Returns the callees from this method in a given thread. (across all its invocations). */
public Set<Long> getCallees(ThreadInfo thread) {
Map<Long, MethodStats> perCalleeStats = mPerThreadStatsByCallee.row(thread.getId());
return perCalleeStats.keySet();
}
/** Returns the exclusive time of this method when called from the given caller method. */
public long getExclusiveTimeByCaller(ThreadInfo thread, Long callerId,
ClockType clockType, TimeUnit unit) {
MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
return getExclusiveTime(stats, clockType, unit);
}
/** Returns the inclusive time of this method when called from the given caller method. */
public long getInclusiveTimeByCaller(ThreadInfo thread, Long callerId,
ClockType clockType, TimeUnit unit) {
MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
return getInclusiveTime(stats, clockType, unit);
}
/** Returns the inclusive time of the callee when called from this method. */
public long getInclusiveTimeByCallee(ThreadInfo thread, Long calleeId,
ClockType clockType, TimeUnit unit) {
MethodStats stats = mPerThreadStatsByCallee.get(thread.getId(), calleeId);
return getInclusiveTime(stats, clockType, unit);
}
private long getExclusiveTime(@Nullable MethodStats stats, ClockType clockType, TimeUnit unit) {
return stats != null ? stats.getExclusiveTime(clockType, unit) : 0;
}
private long getInclusiveTime(@Nullable MethodStats stats, ClockType clockType,
TimeUnit unit) {
return stats != null ? stats.getInclusiveTime(clockType, unit) : 0;
}
private long getInvocationCount(MethodStats stats) {
return stats != null ? stats.getInvocationCount() : 0;
}
private static class MethodStats {
private long mInclusiveThreadTime;
private long mExclusiveThreadTime;
private long mInclusiveGlobalTime;
private long mExclusiveGlobalTime;
private long mInvocationCount;
public long getInclusiveTime(ClockType clockType, TimeUnit unit) {
long time = clockType == ClockType.THREAD ? mInclusiveThreadTime : mInclusiveGlobalTime;
return unit.convert(time, DATA_TIME_UNITS);
}
public long getExclusiveTime(ClockType clockType, TimeUnit unit) {
long time = clockType == ClockType.THREAD ? mExclusiveThreadTime : mExclusiveGlobalTime;
return unit.convert(time, DATA_TIME_UNITS);
}
private long getInvocationCount() {
return mInvocationCount;
}
}
public static class Builder {
private final Map<Integer, MethodStats> mPerThreadCumulativeStats = Maps.newHashMap();
private final Table<Integer, Long, MethodStats> mPerThreadStatsByCaller =
HashBasedTable.create();
private final Table<Integer, Long, MethodStats> mPerThreadStatsByCallee =
HashBasedTable.create();
private boolean mRecursive;
public void addCallTime(Call call, Call parent, ThreadInfo thread) {
for (ClockType type: ClockType.values()) {
addExclusiveTime(call, parent, thread, type);
if (!call.isRecursive()) {
addInclusiveTime(call, parent, thread, type);
}
}
}
private void addExclusiveTime(Call call, Call parent, ThreadInfo thread, ClockType type) {
long time = call.getExclusiveTime(type, DATA_TIME_UNITS);
addExclusiveTime(getPerThreadStats(thread), time, type);
if (parent != null) {
addExclusiveTime(getPerCallerStats(thread, parent), time, type);
}
}
private void addInclusiveTime(Call call, Call parent, ThreadInfo thread, ClockType type) {
long time = call.getInclusiveTime(type, DATA_TIME_UNITS);
addInclusiveTime(getPerThreadStats(thread), time, type);
if (parent != null) {
addInclusiveTime(getPerCallerStats(thread, parent), time, type);
}
for (Call callee: call.getCallees()) {
addInclusiveTime(getPerCalleeStats(thread, callee),
callee.getInclusiveTime(type, DATA_TIME_UNITS), type);
}
}
private void addInclusiveTime(MethodStats stats, long time, ClockType type) {
if (type == ClockType.THREAD) {
stats.mInclusiveThreadTime += time;
} else {
stats.mInclusiveGlobalTime += time;
}
}
private void addExclusiveTime(MethodStats stats, long time, ClockType type) {
if (type == ClockType.THREAD) {
stats.mExclusiveThreadTime += time;
} else {
stats.mExclusiveGlobalTime += time;
}
}
private MethodStats getPerThreadStats(ThreadInfo thread) {
MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
if (stats == null) {
stats = new MethodStats();
mPerThreadCumulativeStats.put(thread.getId(), stats);
}
return stats;
}
private MethodStats getPerCallerStats(ThreadInfo thread, Call parent) {
return getMethodStatsFromTable(thread.getId(), parent.getMethodId(),
mPerThreadStatsByCaller);
}
private MethodStats getPerCalleeStats(ThreadInfo thread, Call callee) {
return getMethodStatsFromTable(thread.getId(), callee.getMethodId(),
mPerThreadStatsByCallee);
}
private MethodStats getMethodStatsFromTable(Integer threadId, Long methodId,
Table<Integer, Long, MethodStats> statsTable) {
MethodStats stats = statsTable.get(threadId, methodId);
if (stats == null) {
stats = new MethodStats();
statsTable.put(threadId, methodId, stats);
}
return stats;
}
public void incrementInvocationCount(Call c, Call parent, ThreadInfo thread) {
getPerThreadStats(thread).mInvocationCount++;
if (parent != null) {
getPerCallerStats(thread, parent).mInvocationCount++;
}
for (Call callee: c.getCallees()) {
getPerCalleeStats(thread, callee).mInvocationCount++;
}
}
public MethodProfileData build() {
return new MethodProfileData(this);
}
public void setRecursive() {
mRecursive = true;
}
}
}