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(
57             String reportScriptPath,
58             String reportBasePath,
59             String reportUrlPrefix,
60             String reportPath,
61             String buildId,
62             String buildFlavor,
63             String deviceSerial) {
64         mReportScriptPath = reportScriptPath;
65         mReportBasePath = reportBasePath;
66         mReportUrlPrefix = reportUrlPrefix;
67         mReportPath = reportPath;
68         mBuildId = buildId;
69         mBuildFlavor = buildFlavor;
70         mDeviceSerial = deviceSerial;
71 
72         if (mReportBasePath == null
73                 || mReportPath == null
74                 || mReportScriptPath == null
75                 || mReportUrlPrefix == null) {
76             throw new IllegalArgumentException(
77                     "ANR post-processing enabled but missing " + "required parameters!");
78         }
79     }
80 
81     /**
82      * Return the storage sub path based on build info. The path will not include trailing path
83      * separator.
84      */
getPerBuildStoragePath()85     private String getPerBuildStoragePath() {
86         if (mBuildId == null) {
87             mBuildId = "-1";
88         }
89         if (mBuildFlavor == null) {
90             mBuildFlavor = "unknown_flavor";
91         }
92         return String.format("%s/%s", mBuildId, mBuildFlavor);
93     }
94 
95     /**
96      * Sets bugreport information for ANR post-processing script
97      *
98      * @param bugreportStream
99      */
setBugReportInfo(InputStreamSource bugreportStream)100     public void setBugReportInfo(InputStreamSource bugreportStream) throws IOException {
101         if (mCachedBugreport != null) {
102             CLog.w(
103                     "A bugreport for this invocation already existed at %s, overriding anyways",
104                     mCachedBugreport.getAbsolutePath());
105         }
106         mCachedBugreport = FileUtil.createTempFile("monkey-anr-report-bugreport", ".txt");
107         FileUtil.writeToFile(bugreportStream.createInputStream(), mCachedBugreport);
108     }
109 
110     /**
111      * Sets monkey log information for ANR post-processing script
112      *
113      * @param monkeyLogStream
114      */
setMonkeyLogInfo(InputStreamSource monkeyLogStream)115     public void setMonkeyLogInfo(InputStreamSource monkeyLogStream) throws IOException {
116         if (mCachedMonkeyLog != null) {
117             CLog.w(
118                     "A monkey log for this invocation already existed at %s, overriding anyways",
119                     mCachedMonkeyLog.getAbsolutePath());
120         }
121         mCachedMonkeyLog = FileUtil.createTempFile("monkey-anr-report-monkey-log", ".txt");
122         FileUtil.writeToFile(monkeyLogStream.createInputStream(), mCachedMonkeyLog);
123     }
124 
genereateAnrReport(ITestLogger logger)125     public boolean genereateAnrReport(ITestLogger logger) {
126         if (mCachedMonkeyLog == null || mCachedBugreport == null) {
127             CLog.w("Cannot generate report: bugreport or monkey log not populated yet.");
128             return false;
129         }
130         // generate monkey report and log it
131         File reportPath =
132                 new File(
133                         mReportBasePath,
134                         String.format("%s/%s", mReportPath, getPerBuildStoragePath()));
135         if (reportPath.exists()) {
136             if (!reportPath.isDirectory()) {
137                 CLog.w(
138                         "The expected report storage path is not a directory: %s",
139                         reportPath.getAbsolutePath());
140                 return false;
141             }
142         } else {
143             if (!reportPath.mkdirs()) {
144                 CLog.w(
145                         "Failed to create report storage directory: %s",
146                         reportPath.getAbsolutePath());
147                 return false;
148             }
149         }
150         // now we should have the storage path, calculate the HTML report path
151         // HTML report file should be named as:
152         // monkey-anr-report-<device serial>-<random string>.html
153         // under the pre-constructed base report storage path
154         File htmlReport = null;
155         try {
156             htmlReport =
157                     FileUtil.createTempFile(
158                             String.format("monkey-anr-report-%s-", mDeviceSerial),
159                             ".html",
160                             reportPath);
161         } catch (IOException ioe) {
162             CLog.e("Error getting place holder file for HTML report.");
163             CLog.e(ioe);
164             return false;
165         }
166         // now ready to call the script
167         String htmlReportPath = htmlReport.getAbsolutePath();
168         String command[] = {
169             mReportScriptPath,
170             "--monkey",
171             mCachedMonkeyLog.getAbsolutePath(),
172             "--html",
173             htmlReportPath,
174             mCachedBugreport.getAbsolutePath()
175         };
176         CommandResult cr =
177                 RunUtil.getDefault().runTimedCmdSilently(REPORT_GENERATION_TIMEOUT, command);
178         if (cr.getStatus() == CommandStatus.SUCCESS) {
179             // Test log the generated HTML report
180             try (InputStreamSource source = new FileInputStreamSource(htmlReport)) {
181                 logger.testLog("monkey-anr-report", LogDataType.HTML, source);
182             }
183             // Clean up and declare success!
184             FileUtil.deleteFile(htmlReport);
185             return true;
186         } else {
187             CLog.w(cr.getStderr());
188             return false;
189         }
190     }
191 
cleanTempFiles()192     public void cleanTempFiles() {
193         FileUtil.deleteFile(mCachedBugreport);
194         FileUtil.deleteFile(mCachedMonkeyLog);
195     }
196 }
197