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