1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.util;
17 
18 import com.android.tradefed.device.DeviceNotAvailableException;
19 import com.android.tradefed.device.ITestDevice;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.result.FileInputStreamSource;
22 
23 import java.io.File;
24 import java.util.HashMap;
25 import java.util.List;
26 
27 /**
28  * An atrace utility developed primarily for identifying the root causes of ANRs during Monkey
29  * testing. Invoking the start command will start asynchronously monitoring the tagged traces in a
30  * circular buffer. Invoking stop will dump the contents of the buffer into an InputStreamSource
31  * that it returns.
32  *
33  * To use this for the case mentioned above (identifying ANRs), one has to first implement the start
34  * method at the beginning of the test and the end method immediately at the end of the test. From
35  * here one can choose how to store and handle the data. Most should probably use the systrace
36  * with the --from-file option to generate an HTML viewer.
37  */
38 public class CircularAtraceUtil {
39 
40     private static final String ATRACE_START_CMD = "atrace --async_start -b %d -c %s -z";
41     private static final String ATRACE_STOP_CMD = "atrace --async_stop -z -b %d > %s";
42     private static final String DEFAULT_TAGS_STRING = "am gfx sched view";
43     private static final String DEVICE_FILE = "${EXTERNAL_STORAGE}/atrace.txt";
44     private static final int KB_IN_MB = 1000;
45 
46     private static HashMap<ITestDevice, Integer> deviceBufferMap = new HashMap<ITestDevice, Integer>();
47 
48     /**
49      * Starts atrace asynchronously with the tags specified.
50      * @param device the device whose actions will be monitored
51      * @param tags tags that atrace should monitor; defaults to 'am gfx sched view'
52      * @param bufferSizeMB the circular buffers size in MB
53      */
startTrace(ITestDevice device, List<String> tags, int bufferSizeMB)54     public static void startTrace(ITestDevice device, List<String> tags, int bufferSizeMB) throws
55             DeviceNotAvailableException, IllegalStateException {
56         // Errors and warnings
57         if (device == null) {
58             throw new IllegalStateException("Cannot start circular atrace without a device");
59         } else if (deviceBufferMap.containsKey(device)) {
60             throw new IllegalStateException("Must end current atrace before starting another");
61         }
62 
63         // Supply tags if empty or null
64         String tagsString = DEFAULT_TAGS_STRING;
65         if (tags != null && !tags.isEmpty()) {
66             tagsString = ArrayUtil.join(" ", tags);
67         }
68 
69         // Execute shell command to start atrace
70         String command = String.format(ATRACE_START_CMD, bufferSizeMB * KB_IN_MB, tagsString);
71         CLog.d("Starting circular atrace utility with command: %s", command);
72         device.executeShellCommand(command);
73         // Put buffer size in map for end and to ensure correct use-flow
74         deviceBufferMap.put(device, bufferSizeMB);
75     }
76 
77     /**
78      * Stops and dumps atrace asynchronously into a File, which it returns in an InputStreamSource.
79      * @return a FileInputStreamSource with the results from the atrace command
80      */
endTrace(ITestDevice device)81     public static FileInputStreamSource endTrace(ITestDevice device) throws DeviceNotAvailableException,
82             IllegalStateException {
83         // Errors and warnings
84         if (!deviceBufferMap.containsKey(device)) {
85             throw new IllegalStateException("Must start circular atrace before ending");
86         }
87 
88         // Get buffer size in MB and ensure correct use-flow by removing
89         int bufferSizeKB = deviceBufferMap.remove(device) * KB_IN_MB;
90         // Execute shell command to stop atrace with results
91         String command = String.format(ATRACE_STOP_CMD, bufferSizeKB, DEVICE_FILE);
92         CLog.d("Ending atrace utility with command: %s", command);
93         device.executeShellCommand(command);
94 
95         File temp = device.pullFile(DEVICE_FILE);
96         if (temp != null) {
97             FileInputStreamSource stream = new FileInputStreamSource(temp);
98             return stream;
99         } else {
100             CLog.w("Could not pull file: %s", DEVICE_FILE);
101             return null;
102         }
103     }
104 }
105