Home | History | Annotate | Download | only in result
      1 /*
      2  * Copyright (C) 2010 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.result;
     17 
     18 import com.android.tradefed.build.IBuildInfo;
     19 import com.android.tradefed.command.FatalHostError;
     20 import com.android.tradefed.log.LogUtil.CLog;
     21 import com.android.tradefed.util.FileUtil;
     22 import com.android.tradefed.util.StreamUtil;
     23 
     24 import java.io.BufferedInputStream;
     25 import java.io.BufferedOutputStream;
     26 import java.io.File;
     27 import java.io.FileInputStream;
     28 import java.io.FileOutputStream;
     29 import java.io.IOException;
     30 import java.io.InputStream;
     31 import java.io.OutputStream;
     32 import java.util.ArrayList;
     33 import java.util.List;
     34 import java.util.zip.GZIPOutputStream;
     35 import java.util.zip.ZipEntry;
     36 import java.util.zip.ZipOutputStream;
     37 
     38 
     39 /**
     40  * A helper for {@link ITestInvocationListener}'s that will save log data to a file
     41  */
     42 public class LogFileSaver {
     43 
     44     private static final int BUFFER_SIZE = 64 * 1024;
     45     private File mInvLogDir;
     46     private List<String> mInvLogPathSegments;
     47 
     48     /**
     49      * Creates a {@link LogFileSaver}.
     50      * <p/>
     51      * Construct a unique file system directory in rootDir/branch/build_id/testTag/uniqueDir
     52      * <p/>
     53      * If directory creation fails, will use a temp directory.
     54      *
     55      * @param buildInfo the {@link IBuildInfo}
     56      * @param rootDir the root file system path
     57      * @param logRetentionDays If provided a '.retention' file will be written to log directory
     58      *            containing a timestamp equal to current time + logRetentionDays. External cleanup
     59      *            scripts can use this file to determine when to delete log directories.
     60      */
     61     public LogFileSaver(IBuildInfo buildInfo, File rootDir, Integer logRetentionDays) {
     62         List<String> testArtifactPathSegments = generateTestArtifactPath(buildInfo);
     63         File buildDir = createBuildDir(testArtifactPathSegments, rootDir);
     64         mInvLogDir = createInvLogDir(buildDir, logRetentionDays);
     65         String invLogDirName = mInvLogDir.getName();
     66         mInvLogPathSegments = new ArrayList<>(testArtifactPathSegments);
     67         mInvLogPathSegments.add(invLogDirName);
     68     }
     69 
     70     private File createTempDir() {
     71         try {
     72             return FileUtil.createTempDir("inv_");
     73         } catch (IOException e) {
     74             // uh oh, this can't be good, abort tradefed
     75             throw new FatalHostError("Cannot create tmp directory.", e);
     76         }
     77     }
     78 
     79     /**
     80      * Creates a {@link LogFileSaver}.
     81      * <p/>
     82      * Construct a unique file system directory in rootDir/branch/build_id/uniqueDir
     83      *
     84      * @param buildInfo the {@link IBuildInfo}
     85      * @param rootDir the root file system path
     86      */
     87     public LogFileSaver(IBuildInfo buildInfo, File rootDir) {
     88         this(buildInfo, rootDir, null);
     89     }
     90 
     91     /**
     92      * An alternate {@link LogFileSaver} constructor that will just use given directory as the
     93      * log storage directory.
     94      *
     95      * @param rootDir
     96      */
     97     public LogFileSaver(File rootDir) {
     98         this(null, rootDir, null);
     99     }
    100 
    101     /**
    102      * Get the directory used to store files.
    103      *
    104      * @return the {@link File} directory
    105      */
    106     public File getFileDir() {
    107         return mInvLogDir;
    108     }
    109 
    110     /**
    111      * Create unique invocation log directory.
    112      * @param buildDir the build directory
    113      * @param logRetentionDays
    114      * @return the create invocation directory
    115      */
    116     File createInvLogDir(File buildDir, Integer logRetentionDays) {
    117         // now create unique directory within the buildDir
    118         File invocationDir = null;
    119         try {
    120             invocationDir = FileUtil.createTempDir("inv_", buildDir);
    121             if (logRetentionDays != null && logRetentionDays > 0) {
    122                 new RetentionFileSaver().writeRetentionFile(invocationDir, logRetentionDays);
    123             }
    124         } catch (IOException e) {
    125             CLog.e("Unable to create unique directory in %s. Attempting to use tmp dir instead",
    126                     buildDir.getAbsolutePath());
    127             CLog.e(e);
    128             // try to create one in a tmp location instead
    129             invocationDir = createTempDir();
    130         }
    131         CLog.i("Using log file directory %s", invocationDir.getAbsolutePath());
    132         return invocationDir;
    133     }
    134 
    135     /**
    136      * Attempt to create a folder to store log's for given build info.
    137      *
    138      * @param buildPathSegments build path segments
    139      * @param rootDir the root file system path to create directory from
    140      * @return a {@link File} pointing to the directory to store log files in
    141      */
    142     File createBuildDir(List<String> buildPathSegments, File rootDir) {
    143         File buildReportDir;
    144         buildReportDir = FileUtil.getFileForPath(rootDir,
    145                 buildPathSegments.toArray(new String[] {}));
    146 
    147         // if buildReportDir already exists and is a directory - use it.
    148         if (buildReportDir.exists()) {
    149             if (buildReportDir.isDirectory()) {
    150                 return buildReportDir;
    151             } else {
    152                 CLog.w("Cannot create build-specific output dir %s. File already exists.",
    153                         buildReportDir.getAbsolutePath());
    154             }
    155         } else {
    156             if (FileUtil.mkdirsRWX(buildReportDir)) {
    157                 return buildReportDir;
    158             } else {
    159                 CLog.w("Cannot create build-specific output dir %s. Failed to create directory.",
    160                         buildReportDir.getAbsolutePath());
    161             }
    162         }
    163         return buildReportDir;
    164     }
    165 
    166     /**
    167      * A helper to create test artifact path segments based on the build info.
    168      * <p />
    169      * {@code [branch/]build-id/test-tag}
    170      */
    171     List<String> generateTestArtifactPath(IBuildInfo buildInfo) {
    172         final List<String> pathSegments = new ArrayList<String>();
    173         if (buildInfo == null) {
    174             return pathSegments;
    175         }
    176         if (buildInfo.getBuildBranch() != null) {
    177             pathSegments.add(buildInfo.getBuildBranch());
    178         }
    179         pathSegments.add(buildInfo.getBuildId());
    180         pathSegments.add(buildInfo.getTestTag());
    181         return pathSegments;
    182     }
    183 
    184     /**
    185      * A helper function that translates a string into something that can be used as a filename
    186      */
    187     private static String sanitizeFilename(String name) {
    188         return name.replace(File.separatorChar, '_');
    189     }
    190 
    191     /**
    192      * Save the log data to a file
    193      *
    194      * @param dataName a {@link String} descriptive name of the data. e.g. "dev
    195      * @param dataType the {@link LogDataType} of the file.
    196      * @param dataStream the {@link InputStream} of the data.
    197      * @return the file of the generated data
    198      * @throws IOException if log file could not be generated
    199      */
    200     public File saveLogData(String dataName, LogDataType dataType, InputStream dataStream)
    201             throws IOException {
    202         return saveLogDataRaw(dataName, dataType.getFileExt(), dataStream);
    203     }
    204 
    205     /**
    206      * Save raw data to a file
    207      * @param dataName a {@link String} descriptive name of the data. e.g. "dev
    208      * @param ext the extension of the date
    209      * @param dataStream the {@link InputStream} of the data.
    210      * @return the file of the generated data
    211      * @throws IOException if log file could not be generated
    212      */
    213     public File saveLogDataRaw(String dataName, String ext, InputStream dataStream)
    214             throws IOException {
    215         final String saneDataName = sanitizeFilename(dataName);
    216         // add underscore to end of data name to make generated name more readable
    217         File logFile = FileUtil.createTempFile(saneDataName + "_", "." + ext,
    218                 mInvLogDir);
    219         FileUtil.writeToFile(dataStream, logFile);
    220         CLog.i("Saved log file %s", logFile.getAbsolutePath());
    221         return logFile;
    222     }
    223 
    224     /**
    225      * Save and compress, if necessary, the log data to a gzip file
    226      *
    227      * @param dataName a {@link String} descriptive name of the data. e.g. "dev
    228      * @param dataType the {@link LogDataType} of the file. Log data which is a (ie
    229      *            {@link LogDataType#isCompressed()} is <code>true</code>)
    230      * @param dataStream the {@link InputStream} of the data.
    231      * @return the file of the generated data
    232      * @throws IOException if log file could not be generated
    233      */
    234     public File saveAndGZipLogData(String dataName, LogDataType dataType, InputStream dataStream)
    235             throws IOException {
    236         if (dataType.isCompressed()) {
    237             CLog.d("Log data for %s is already compressed, skipping compression", dataName);
    238             return saveLogData(dataName, dataType, dataStream);
    239         }
    240         BufferedInputStream bufInput = null;
    241         OutputStream outStream = null;
    242         try {
    243             final String saneDataName = sanitizeFilename(dataName);
    244             File logFile = createCompressedLogFile(saneDataName, dataType);
    245             bufInput = new BufferedInputStream(dataStream);
    246             outStream = createGZipLogStream(logFile);
    247             StreamUtil.copyStreams(bufInput, outStream);
    248             CLog.i("Saved log file %s", logFile.getAbsolutePath());
    249             return logFile;
    250         } finally {
    251             StreamUtil.close(bufInput);
    252             StreamUtil.close(outStream);
    253         }
    254     }
    255 
    256     /**
    257      * Save and compress, if necessary, the log data to a zip file
    258      *
    259      * @param dataName a {@link String} descriptive name of the data. e.g. "dev
    260      * @param dataType the {@link LogDataType} of the file. Log data which is a (ie
    261      *            {@link LogDataType#isCompressed()} is <code>true</code>)
    262      * @param dataStream the {@link InputStream} of the data.
    263      * @return the file of the generated data
    264      * @throws IOException if log file could not be generated
    265      */
    266     public File saveAndZipLogData(String dataName, LogDataType dataType, InputStream dataStream)
    267             throws IOException {
    268         if (dataType.isCompressed()) {
    269             CLog.d("Log data for %s is already compressed, skipping compression", dataName);
    270             return saveLogData(dataName, dataType, dataStream);
    271         }
    272         BufferedInputStream bufInput = null;
    273         ZipOutputStream outStream = null;
    274         try {
    275             final String saneDataName = sanitizeFilename(dataName);
    276             // add underscore to end of data name to make generated name more readable
    277             File logFile = FileUtil.createTempFile(saneDataName + "_", "."
    278                     + LogDataType.ZIP.getFileExt(), mInvLogDir);
    279             bufInput = new BufferedInputStream(dataStream);
    280             outStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(
    281                     logFile), BUFFER_SIZE));
    282             outStream.putNextEntry(new ZipEntry(saneDataName + "." + dataType.getFileExt()));
    283             StreamUtil.copyStreams(bufInput, outStream);
    284             CLog.i("Saved log file %s", logFile.getAbsolutePath());
    285             return logFile;
    286         } finally {
    287             StreamUtil.close(bufInput);
    288             StreamUtil.closeZipStream(outStream);
    289         }
    290     }
    291 
    292     /**
    293      * Creates an empty file for storing compressed log data.
    294      *
    295      * @param dataName a {@link String} descriptive name of the data to be stor
    296      *            "device_logcat"
    297      * @param origDataType the type of {@link LogDataType} to be stored
    298      * @return a {@link File}
    299      * @throws IOException if log file could not be created
    300      */
    301     public File createCompressedLogFile(String dataName, LogDataType origDataType)
    302             throws IOException {
    303         // add underscore to end of data name to make generated name more readable
    304         return FileUtil.createTempFile(dataName + "_",
    305                 String.format(".%s.%s", origDataType.getFileExt(), LogDataType.GZIP.getFileExt()),
    306                 mInvLogDir);
    307     }
    308 
    309     /**
    310      * Creates a output stream to write GZIP-compressed data to a file
    311      *
    312      * @param logFile the {@link File} to write to
    313      * @return the {@link OutputStream} to compress and write data to the file.
    314      *         this stream when complete
    315      * @throws IOException if stream could not be generated
    316      */
    317     public OutputStream createGZipLogStream(File logFile) throws IOException {
    318         return new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(
    319                 logFile)), BUFFER_SIZE);
    320     }
    321 
    322     /**
    323      * Helper method to create an input stream to read contents of given log fi
    324      * <p/>
    325      * TODO: consider moving this method elsewhere. Placed here for now so it e
    326      * users of this class to mock.
    327      *
    328      * @param logFile the {@link File} to read from
    329      * @return a buffered {@link InputStream} to read file data. Callers must c
    330      *         this stream when complete
    331      * @throws IOException if stream could not be generated
    332      */
    333     public InputStream createInputStreamFromFile(File logFile) throws IOException {
    334         return new BufferedInputStream(new FileInputStream(logFile), BUFFER_SIZE);
    335     }
    336 
    337     /**
    338      *
    339      * @return the unique invocation log path segments.
    340      */
    341     public List<String> getInvocationLogPathSegments() {
    342         return new ArrayList<>(mInvLogPathSegments);
    343     }
    344 }
    345