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