Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2012 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 android.filesystem.cts;
     18 
     19 import android.content.Context;
     20 import android.util.Log;
     21 
     22 import com.android.compatibility.common.util.DeviceReportLog;
     23 import com.android.compatibility.common.util.MeasureRun;
     24 import com.android.compatibility.common.util.MeasureTime;
     25 import com.android.compatibility.common.util.ReportLog;
     26 import com.android.compatibility.common.util.ResultType;
     27 import com.android.compatibility.common.util.ResultUnit;
     28 import com.android.compatibility.common.util.Stat;
     29 import com.android.compatibility.common.util.SystemUtil;
     30 
     31 import java.io.BufferedReader;
     32 import java.io.File;
     33 import java.io.FileInputStream;
     34 import java.io.FileOutputStream;
     35 import java.io.IOException;
     36 import java.io.InputStreamReader;
     37 import java.io.RandomAccessFile;
     38 import java.util.Random;
     39 
     40 public class FileUtil {
     41     private static final String TAG = "FileUtil";
     42     private static final Random mRandom = new Random(0);
     43     private static long mFileId = 0;
     44 
     45     public static final int BUFFER_SIZE = 10 * 1024 * 1024;
     46     /**
     47      * create array with different data per each call
     48      *
     49      * @param length
     50      * @param randomSeed
     51      * @return
     52      */
     53     public static byte[] generateRandomData(int length) {
     54         byte[] buffer = new byte[length];
     55         int val = mRandom.nextInt();
     56         for (int i = 0; i < length / 4; i++) {
     57             // in little-endian
     58             buffer[i * 4] = (byte)(val & 0x000000ff);
     59             buffer[i * 4 + 1] = (byte)((val & 0x0000ff00) >> 8);
     60             buffer[i * 4 + 2] = (byte)((val & 0x00ff0000) >> 16);
     61             buffer[i * 4 + 3] = (byte)((val & 0xff000000) >> 24);
     62             val++;
     63         }
     64         for (int i = (length / 4) * 4; i < length; i++) {
     65             buffer[i] = 0;
     66         }
     67         return buffer;
     68     }
     69 
     70     /**
     71      * create a new file under the given dirName.
     72      * Existing files will not be affected.
     73      * @param context
     74      * @param dirName
     75      * @return
     76      */
     77     public static File createNewFile(Context context, String dirName) {
     78         File topDir = new File(context.getFilesDir(), dirName);
     79         topDir.mkdir();
     80         String[] list = topDir.list();
     81 
     82         String newFileName;
     83         while (true) {
     84             newFileName = Long.toString(mFileId);
     85             boolean fileExist = false;
     86             for (String child : list) {
     87                 if (child.equals(newFileName)) {
     88                     fileExist = true;
     89                     break;
     90                 }
     91             }
     92             if (!fileExist) {
     93                 break;
     94             }
     95             mFileId++;
     96         }
     97         mFileId++;
     98         //Log.i(TAG, "filename" + Long.toString(mFileId));
     99         return new File(topDir, newFileName);
    100     }
    101 
    102     /**
    103      * create multiple new files
    104      * @param context
    105      * @param dirName
    106      * @param count number of files to create
    107      * @return
    108      */
    109     public static File[] createNewFiles(Context context, String dirName, int count) {
    110         File[] files = new File[count];
    111         for (int i = 0; i < count; i++) {
    112             files[i] = createNewFile(context, dirName);
    113         }
    114         return files;
    115     }
    116 
    117     /**
    118      * write file with given byte array
    119      * @param file
    120      * @param data
    121      * @param append will append if set true. Otherwise, write from beginning
    122      * @throws IOException
    123      */
    124     public static void writeFile(File file, byte[] data, boolean append) throws IOException {
    125         final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
    126         if (append) {
    127             randomFile.seek(randomFile.length());
    128         } else {
    129             randomFile.seek(0L);
    130         }
    131         randomFile.write(data);
    132         randomFile.close();
    133     }
    134 
    135     /**
    136      * create a new file with given length.
    137      * @param context
    138      * @param dirName
    139      * @param length
    140      * @return
    141      * @throws IOException
    142      */
    143     public static File createNewFilledFile(Context context, String dirName, long length)
    144             throws IOException {
    145         File file = createNewFile(context, dirName);
    146         FileOutputStream out = new FileOutputStream(file);
    147         byte[] data = generateRandomData(BUFFER_SIZE);
    148         long written = 0;
    149         while (written < length) {
    150             int toWrite = (int) Math.min(BUFFER_SIZE, length - written);
    151             out.write(data, 0, toWrite);
    152             written += toWrite;
    153         }
    154         out.flush();
    155         out.close();
    156         return file;
    157     }
    158 
    159     /**
    160      * remove given file or directory under the current app's files dir.
    161      * @param context
    162      * @param name
    163      */
    164     public static void removeFileOrDir(Context context, String name) {
    165         File entry = new File(context.getFilesDir(), name);
    166         if (entry.exists()) {
    167             removeEntry(entry);
    168         }
    169     }
    170 
    171     private static void removeEntry(File entry) {
    172         if (entry.isDirectory()) {
    173             String[] children = entry.list();
    174             for (String child : children) {
    175                 removeEntry(new File(entry, child));
    176             }
    177         }
    178         Log.i(TAG, "delete file " + entry.getAbsolutePath());
    179         entry.delete();
    180     }
    181 
    182     /**
    183      * measure time taken for each IO run with amount R/W
    184      * @param count
    185      * @param run
    186      * @param readAmount returns amount of read in bytes for each interval.
    187      *        Value will not be written if /proc/self/io does not exist.
    188      * @param writeAmount returns amount of write in bytes for each interval.
    189      * @return time per each interval
    190      * @throws IOException
    191      */
    192     public static double[] measureIO(int count, double[] readAmount, double[] writeAmount,
    193             MeasureRun run)  throws Exception {
    194         double[] result = new double[count];
    195         File procIo = new File("/proc/self/io");
    196         boolean measureIo = procIo.exists() && procIo.canRead();
    197         long prev = System.currentTimeMillis();
    198         RWAmount prevAmount = new RWAmount();
    199         if (measureIo) {
    200             prevAmount = getRWAmount(procIo);
    201         }
    202         for (int i = 0; i < count; i++) {
    203             run.run(i);
    204             long current =  System.currentTimeMillis();
    205             result[i] = current - prev;
    206             prev = current;
    207             if (measureIo) {
    208                 RWAmount currentAmount = getRWAmount(procIo);
    209                 readAmount[i] = currentAmount.mRd - prevAmount.mRd;
    210                 writeAmount[i] = currentAmount.mWr - prevAmount.mWr;
    211                 prevAmount = currentAmount;
    212             }
    213         }
    214         return result;
    215     }
    216 
    217     private static class RWAmount {
    218         public double mRd = 0.0;
    219         public double mWr = 0.0;
    220     };
    221 
    222     private static RWAmount getRWAmount(File file) throws IOException {
    223         RWAmount amount = new RWAmount();
    224 
    225         BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
    226         String line;
    227         while((line = br.readLine())!= null) {
    228             if (line.startsWith("read_bytes")) {
    229                 amount.mRd = Double.parseDouble(line.split(" ")[1]);
    230             } else if (line.startsWith("write_bytes")) {
    231                 amount.mWr = Double.parseDouble(line.split(" ")[1]);
    232             }
    233         }
    234         br.close();
    235         return amount;
    236     }
    237 
    238     /**
    239      * get file size exceeding total memory size ( 2x total memory).
    240      * The size is rounded in bufferSize. And the size will be bigger than 400MB.
    241      * @param context
    242      * @param bufferSize
    243      * @return file size or 0 if there is not enough space.
    244      */
    245     public static long getFileSizeExceedingMemory(Context context, int bufferSize) {
    246         long freeDisk = SystemUtil.getFreeDiskSize(context);
    247         long memSize = SystemUtil.getTotalMemory(context);
    248         long diskSizeTarget = (2 * memSize / bufferSize) * bufferSize;
    249         final long minimumDiskSize = (512L * 1024L * 1024L / bufferSize) * bufferSize;
    250         final long reservedDiskSize = (50L * 1024L * 1024L / bufferSize) * bufferSize;
    251         if ( diskSizeTarget < minimumDiskSize ) {
    252             diskSizeTarget = minimumDiskSize;
    253         }
    254         if (diskSizeTarget > freeDisk) {
    255             Log.i(TAG, "Free disk size " + freeDisk + " too small");
    256             return 0;
    257         }
    258         if ((freeDisk - diskSizeTarget) < reservedDiskSize) {
    259             diskSizeTarget -= reservedDiskSize;
    260         }
    261         return diskSizeTarget;
    262     }
    263 
    264     /**
    265      *
    266      * @param context
    267      * @param dirName
    268      * @param report
    269      * @param fileSize
    270      * @param bufferSize should be power of two
    271      * @throws IOException
    272      */
    273     public static void doRandomReadTest(Context context, String dirName, ReportLog report,
    274             long fileSize, int bufferSize) throws Exception {
    275         File file = FileUtil.createNewFilledFile(context,
    276                 dirName, fileSize);
    277 
    278         final byte[] data = FileUtil.generateRandomData(bufferSize);
    279         Random random = new Random(0);
    280         final int totalReadCount = (int)(fileSize / bufferSize);
    281         final int[] readOffsets = new int[totalReadCount];
    282         for (int i = 0; i < totalReadCount; i++) {
    283             // align in buffer size
    284             readOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
    285                     ~(bufferSize - 1);
    286         }
    287         final int runsInOneGo = 16;
    288         final int readsInOneMeasure = totalReadCount / runsInOneGo;
    289 
    290         final RandomAccessFile randomFile = new RandomAccessFile(file, "rw"); // do not need O_SYNC
    291         double[] rdAmount = new double[runsInOneGo];
    292         double[] wrAmount = new double[runsInOneGo];
    293         double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
    294 
    295             @Override
    296             public void run(int i) throws IOException {
    297                 Log.i(TAG, "starting " + i + " -th round");
    298                 int start = i * readsInOneMeasure;
    299                 int end = (i + 1) * readsInOneMeasure;
    300                 for (int j = start; j < end; j++) {
    301                     randomFile.seek(readOffsets[j]);
    302                     randomFile.read(data);
    303                 }
    304             }
    305         });
    306         randomFile.close();
    307         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
    308                 times);
    309         report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
    310         // This is just the amount of IO returned from kernel. So this is performance neutral.
    311         report.addValues("read_amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
    312         Stat.StatResult stat = Stat.getStat(mbps);
    313 
    314         report.setSummary("read_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
    315                 ResultUnit.MBPS);
    316     }
    317 
    318     /**
    319      *
    320      * @param context
    321      * @param dirName
    322      * @param report
    323      * @param fileSize
    324      * @param bufferSize should be power of two
    325      * @throws IOException
    326      */
    327     public static void doRandomWriteTest(Context context, String dirName, ReportLog report,
    328             long fileSize, int bufferSize) throws Exception {
    329         File file = FileUtil.createNewFilledFile(context,
    330                 dirName, fileSize);
    331         final byte[] data = FileUtil.generateRandomData(bufferSize);
    332         Random random = new Random(0);
    333         final int totalWriteCount = (int)(fileSize / bufferSize);
    334         final int[] writeOffsets = new int[totalWriteCount];
    335         for (int i = 0; i < totalWriteCount; i++) {
    336             writeOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
    337                     ~(bufferSize - 1);
    338         }
    339         final int runsInOneGo = 16;
    340         final int writesInOneMeasure = totalWriteCount / runsInOneGo;
    341 
    342         final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
    343         double[] rdAmount = new double[runsInOneGo];
    344         double[] wrAmount = new double[runsInOneGo];
    345         double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
    346 
    347             @Override
    348             public void run(int i) throws IOException {
    349                 Log.i(TAG, "starting " + i + " -th round");
    350                 int start = i * writesInOneMeasure;
    351                 int end = (i + 1) * writesInOneMeasure;
    352                 for (int j = start; j < end; j++) {
    353                     randomFile.seek(writeOffsets[j]);
    354                     randomFile.write(data);
    355                 }
    356             }
    357         });
    358         randomFile.close();
    359         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
    360                 times);
    361         report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
    362         report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
    363         Stat.StatResult stat = Stat.getStat(mbps);
    364 
    365         report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
    366                 ResultUnit.MBPS);
    367     }
    368 
    369     /**
    370      *
    371      * @param context
    372      * @param dirName
    373      * @param report
    374      * @param fileSize fileSize should be multiple of bufferSize.
    375      * @param bufferSize
    376      * @param numberRepetition
    377      * @throws IOException
    378      */
    379     public static void doSequentialUpdateTest(Context context, String dirName, long fileSize,
    380             int bufferSize, int numberRepetition, String reportName, String streamName)
    381             throws Exception {
    382         File file = FileUtil.createNewFilledFile(context,
    383                 dirName, fileSize);
    384         final byte[] data = FileUtil.generateRandomData(bufferSize);
    385         int numberRepeatInOneRun = (int)(fileSize / bufferSize);
    386         double[] mbpsAll = new double[numberRepetition * numberRepeatInOneRun];
    387         for (int i = 0; i < numberRepetition; i++) {
    388             Log.i(TAG, "starting " + i + " -th round");
    389             DeviceReportLog report = new DeviceReportLog(reportName, streamName);
    390             report.addValue("round", i,  ResultType.NEUTRAL, ResultUnit.NONE);
    391             final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd");  // force O_SYNC
    392             randomFile.seek(0L);
    393             double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() {
    394 
    395                 @Override
    396                 public void run(int i) throws IOException {
    397                     randomFile.write(data);
    398                 }
    399             });
    400             randomFile.close();
    401             double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
    402                     times);
    403             report.addValues("throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
    404             int offset = i * numberRepeatInOneRun;
    405             for (int j = 0; j < mbps.length; j++) {
    406                 mbpsAll[offset + j] = mbps[j];
    407             }
    408             report.submit();
    409         }
    410         Stat.StatResult stat = Stat.getStat(mbpsAll);
    411         DeviceReportLog report = new DeviceReportLog(reportName, String.format("%s_average",
    412                 streamName));
    413         report.addValue("update_throughput", stat.mAverage, ResultType.HIGHER_BETTER,
    414                 ResultUnit.MBPS);
    415         report.submit();
    416     }
    417 }
    418