summaryrefslogtreecommitdiff
path: root/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
blob: 027d550695858ae4fb4234ca21993d9f1b6dfa13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*
 * 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 java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/** Reconstructs call stacks from a sequence of trace events (method entry/exit events) */
public class CallStackReconstructor {
    private List<Call> mTopLevelCallees = new ArrayList<Call>();
    private Stack<Call> mCallStack = new Stack<Call>();

    private boolean mFixupComplete;

    public void addTraceAction(long methodId, TraceAction action, int threadTime, int globalTime) {
        if (action == TraceAction.METHOD_ENTER) {
            enterMethod(methodId, threadTime, globalTime);
        } else {
            exitMethod(methodId, threadTime, globalTime);
        }
    }

    private void enterMethod(long methodId, int threadTime, int globalTime) {
        Call c = new Call(methodId);
        c.setMethodEntryTime(threadTime, globalTime);

        if (mCallStack.isEmpty()) {
            mTopLevelCallees.add(c);
        } else {
            Call caller = mCallStack.peek();
            caller.addCallee(c);
        }

        mCallStack.push(c);
    }

    private void exitMethod(long methodId, int threadTime, int globalTime) {
        if (!mCallStack.isEmpty()) {
            Call c = mCallStack.pop();
            if (c.getMethodId() != methodId) {
                String msg = String
                        .format("Error during call stack reconstruction. Attempt to exit from method 0x%1$x while in method 0x%2$x",
                                c.getMethodId(), methodId);
                throw new RuntimeException(msg);
            }

            c.setMethodExitTime(threadTime, globalTime);
        } else {
            // We are exiting out of a method that was entered into before tracing was started.
            // In such a case, create this method
            Call c = new Call(methodId);
            c.setMethodExitTime(threadTime, globalTime);

            // All the previous calls at the top level are now assumed to have been called from
            // this method. So mark this method as having called all of those methods, and reset
            // the top level to only include this method
            for (Call topLevelCallee : mTopLevelCallees) {
                c.addCallee(topLevelCallee);
            }
            mTopLevelCallees.clear();
            mTopLevelCallees.add(c);
        }
    }

    private void fixupCallStacks() {
        if (mFixupComplete) {
            return;
        }

        // fixup whatever needs fixing up
        // TODO: use global / thread times to infer context switches

        // Fix call stack depths
        for (Call c : mTopLevelCallees) {
            setStackDepthRecursive(c, 0);
        }

        mFixupComplete = true;
    }

    private void setStackDepthRecursive(@NonNull Call call, int i) {
        call.setDepth(i);

        for (Call c : call.getCallees()) {
            setStackDepthRecursive(c, i+1);
        }
    }

    public List<Call> getTopLevelCallees() {
        fixupCallStacks();
        return mTopLevelCallees;
    }
}