1 /*
2  * Copyright (C) 2016 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 
17 package com.android.monkey;
18 
19 import com.android.tradefed.log.ITestLogger;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.result.FileInputStreamSource;
22 import com.android.tradefed.result.InputStreamSource;
23 import com.android.tradefed.result.LogDataType;
24 import com.android.tradefed.util.CommandResult;
25 import com.android.tradefed.util.CommandStatus;
26 import com.android.tradefed.util.FileUtil;
27 import com.android.tradefed.util.RunUtil;
28 
29 import java.io.File;
30 import java.io.IOException;
31 
32 /**
33  * A utility class that encapsulates details of calling post-processing scripts to generate monkey
34  * ANR reports.
35  */
36 public class AnrReportGenerator {
37 
38     private static final long REPORT_GENERATION_TIMEOUT = 30 * 1000; // 30s
39 
40     private File mCachedMonkeyLog = null;
41     private File mCachedBugreport = null;
42 
43     private final String mReportScriptPath;
44     private final String mReportBasePath;
45     private final String mReportUrlPrefix;
46     private final String mReportPath;
47     private final String mDeviceSerial;
48 
49     private String mBuildId = null;
50     private String mBuildFlavor = null;
51 
52     /**
53      * Constructs the instance with details of report script and output location information. See
54      * matching options on {@link MonkeyBase} for more info.
55      */
AnrReportGenerator(String reportScriptPath, String reportBasePath, String reportUrlPrefix, String reportPath, String buildId, String buildFlavor, String deviceSerial)56     public AnrReportGenerator(String reportScriptPath, String reportBasePath,
57             String reportUrlPrefix, String reportPath,
58             String buildId, String buildFlavor, String deviceSerial) {
59         mReportScriptPath = reportScriptPath;
60         mReportBasePath = reportBasePath;
61         mReportUrlPrefix = reportUrlPrefix;
62         mReportPath = reportPath;
63         mBuildId = buildId;
64         mBuildFlavor = buildFlavor;
65         mDeviceSerial = deviceSerial;
66 
67         if (mReportBasePath == null || mReportPath == null || mReportScriptPath == null
68                 || mReportUrlPrefix == null) {
69             throw new IllegalArgumentException("ANR post-processing enabled but missing "
70                     + "required parameters!");
71         }
72     }
73 
74     /**
75      * Return the storage sub path based on build info. The path will not include trailing path
76      * separator.
77      */
getPerBuildStoragePath()78     private String getPerBuildStoragePath() {
79         if (mBuildId == null) {
80             mBuildId = "-1";
81         }
82         if (mBuildFlavor == null) {
83             mBuildFlavor = "unknown_flavor";
84         }
85         return String.format("%s/%s", mBuildId, mBuildFlavor);
86     }
87 
88     /**
89      * Sets bugreport information for ANR post-processing script
90      * @param bugreportStream
91      */
setBugReportInfo(InputStreamSource bugreportStream)92     public void setBugReportInfo(InputStreamSource bugreportStream) throws IOException {
93         if (mCachedBugreport != null) {
94             CLog.w("A bugreport for this invocation already existed at %s, overriding anyways",
95                     mCachedBugreport.getAbsolutePath());
96         }
97         mCachedBugreport = FileUtil.createTempFile("monkey-anr-report-bugreport", ".txt");
98         FileUtil.writeToFile(bugreportStream.createInputStream(), mCachedBugreport);
99     }
100 
101     /**
102      * Sets monkey log information for ANR post-processing script
103      * @param monkeyLogStream
104      */
setMonkeyLogInfo(InputStreamSource monkeyLogStream)105     public void setMonkeyLogInfo(InputStreamSource monkeyLogStream) throws IOException {
106         if (mCachedMonkeyLog != null) {
107             CLog.w("A monkey log for this invocation already existed at %s, overriding anyways",
108                     mCachedMonkeyLog.getAbsolutePath());
109         }
110         mCachedMonkeyLog = FileUtil.createTempFile("monkey-anr-report-monkey-log", ".txt");
111         FileUtil.writeToFile(monkeyLogStream.createInputStream(), mCachedMonkeyLog);
112     }
113 
genereateAnrReport(ITestLogger logger)114     public boolean genereateAnrReport(ITestLogger logger) {
115         if (mCachedMonkeyLog == null || mCachedBugreport == null) {
116             CLog.w("Cannot generate report: bugreport or monkey log not populated yet.");
117             return false;
118         }
119         // generate monkey report and log it
120         File reportPath = new File(mReportBasePath,
121                 String.format("%s/%s", mReportPath, getPerBuildStoragePath()));
122         if (reportPath.exists()) {
123             if (!reportPath.isDirectory()) {
124                 CLog.w("The expected report storage path is not a directory: %s",
125                         reportPath.getAbsolutePath());
126                 return false;
127             }
128         } else {
129             if (!reportPath.mkdirs()) {
130                 CLog.w("Failed to create report storage directory: %s",
131                         reportPath.getAbsolutePath());
132                 return false;
133             }
134         }
135         // now we should have the storage path, calculate the HTML report path
136         // HTML report file should be named as:
137         // monkey-anr-report-<device serial>-<random string>.html
138         // under the pre-constructed base report storage path
139         File htmlReport = null;
140         try {
141             htmlReport = FileUtil.createTempFile(
142                     String.format("monkey-anr-report-%s-", mDeviceSerial), ".html",
143                     reportPath);
144         } catch (IOException ioe) {
145             CLog.e("Error getting place holder file for HTML report.");
146             CLog.e(ioe);
147             return false;
148         }
149         // now ready to call the script
150         String htmlReportPath = htmlReport.getAbsolutePath();
151         String command[] = {
152                 mReportScriptPath, "--monkey", mCachedMonkeyLog.getAbsolutePath(), "--html",
153                 htmlReportPath, mCachedBugreport.getAbsolutePath()
154         };
155         CommandResult cr = RunUtil.getDefault().runTimedCmdSilently(REPORT_GENERATION_TIMEOUT,
156                 command);
157         if (cr.getStatus() == CommandStatus.SUCCESS) {
158             // Test log the generated HTML report
159             try (InputStreamSource source = new FileInputStreamSource(htmlReport)) {
160                 logger.testLog("monkey-anr-report", LogDataType.HTML, source);
161             }
162             // Clean up and declare success!
163             FileUtil.deleteFile(htmlReport);
164             return true;
165         } else {
166             CLog.w(cr.getStderr());
167             return false;
168         }
169     }
170 
cleanTempFiles()171     public void cleanTempFiles() {
172         FileUtil.deleteFile(mCachedBugreport);
173         FileUtil.deleteFile(mCachedMonkeyLog);
174     }
175 }
176