• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
generateRandomData(int length)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      */
createNewFile(Context context, String dirName)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      */
createNewFiles(Context context, String dirName, int count)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      */
writeFile(File file, byte[] data, boolean append)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      */
createNewFilledFile(Context context, String dirName, long length)143     public static File createNewFilledFile(Context context, String dirName, long length)
144             throws IOException {
145         File file = createNewFile(context, dirName);
146         final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
147         byte[] data = generateRandomData(BUFFER_SIZE);
148 
149         while (file.length() < length) {
150             int toWrite = (int) Math.min(BUFFER_SIZE, length - file.length());
151             randomFile.write(data, 0, toWrite);
152         }
153 
154         randomFile.close();
155         return file;
156     }
157 
158     /**
159      * remove given file or directory under the current app's files dir.
160      * @param context
161      * @param name
162      */
removeFileOrDir(Context context, String name)163     public static void removeFileOrDir(Context context, String name) {
164         File entry = new File(context.getFilesDir(), name);
165         if (entry.exists()) {
166             removeEntry(entry);
167         }
168     }
169 
removeEntry(File entry)170     private static void removeEntry(File entry) {
171         if (entry.isDirectory()) {
172             String[] children = entry.list();
173             for (String child : children) {
174                 removeEntry(new File(entry, child));
175             }
176         }
177         Log.i(TAG, "delete file " + entry.getAbsolutePath());
178         entry.delete();
179     }
180 
181     /**
182      * measure time taken for each IO run with amount R/W
183      * @param count
184      * @param run
185      * @param readAmount returns amount of read in bytes for each interval.
186      *        Value will not be written if /proc/self/io does not exist.
187      * @param writeAmount returns amount of write in bytes for each interval.
188      * @return time per each interval
189      * @throws IOException
190      */
measureIO(int count, double[] readAmount, double[] writeAmount, MeasureRun run)191     public static double[] measureIO(int count, double[] readAmount, double[] writeAmount,
192             MeasureRun run)  throws Exception {
193         double[] result = new double[count];
194         File procIo = new File("/proc/self/io");
195         boolean measureIo = procIo.exists() && procIo.canRead();
196         long prev = System.currentTimeMillis();
197         RWAmount prevAmount = new RWAmount();
198         if (measureIo) {
199             prevAmount = getRWAmount(procIo);
200         }
201         for (int i = 0; i < count; i++) {
202             run.run(i);
203             long current =  System.currentTimeMillis();
204             result[i] = current - prev;
205             prev = current;
206             if (measureIo) {
207                 RWAmount currentAmount = getRWAmount(procIo);
208                 readAmount[i] = currentAmount.mRd - prevAmount.mRd;
209                 writeAmount[i] = currentAmount.mWr - prevAmount.mWr;
210                 prevAmount = currentAmount;
211             }
212         }
213         return result;
214     }
215 
216     private static class RWAmount {
217         public double mRd = 0.0;
218         public double mWr = 0.0;
219     };
220 
getRWAmount(File file)221     private static RWAmount getRWAmount(File file) throws IOException {
222         RWAmount amount = new RWAmount();
223 
224         BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
225         String line;
226         while((line = br.readLine())!= null) {
227             if (line.startsWith("read_bytes")) {
228                 amount.mRd = Double.parseDouble(line.split(" ")[1]);
229             } else if (line.startsWith("write_bytes")) {
230                 amount.mWr = Double.parseDouble(line.split(" ")[1]);
231             }
232         }
233         br.close();
234         return amount;
235     }
236 
237     /**
238      * get file size exceeding total memory size ( 2x total memory).
239      * The size is rounded in bufferSize. And the size will be bigger than 400MB.
240      * @param context
241      * @param bufferSize
242      * @return file size or 0 if there is not enough space.
243      */
getFileSizeExceedingMemory(Context context, int bufferSize)244     public static long getFileSizeExceedingMemory(Context context, int bufferSize) {
245         long freeDisk = SystemUtil.getFreeDiskSize(context);
246         long memSize = SystemUtil.getTotalMemory(context);
247         long diskSizeTarget = (2 * memSize / bufferSize) * bufferSize;
248         final long minimumDiskSize = (512L * 1024L * 1024L / bufferSize) * bufferSize;
249         final long reservedDiskSize = (50L * 1024L * 1024L / bufferSize) * bufferSize;
250         if ( diskSizeTarget < minimumDiskSize ) {
251             diskSizeTarget = minimumDiskSize;
252         }
253         if (diskSizeTarget > freeDisk) {
254             Log.i(TAG, "Free disk size " + freeDisk + " too small");
255             return 0;
256         }
257         if ((freeDisk - diskSizeTarget) < reservedDiskSize) {
258             diskSizeTarget -= reservedDiskSize;
259         }
260         return diskSizeTarget;
261     }
262 
263     /**
264      *
265      * @param context
266      * @param dirName
267      * @param report
268      * @param fileSize
269      * @param bufferSize should be power of two
270      * @throws IOException
271      */
doRandomReadTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)272     public static void doRandomReadTest(Context context, String dirName, ReportLog report,
273             long fileSize, int bufferSize) throws Exception {
274         File file = FileUtil.createNewFilledFile(context,
275                 dirName, fileSize);
276 
277         final byte[] data = FileUtil.generateRandomData(bufferSize);
278         Random random = new Random(0);
279         final int totalReadCount = (int)(fileSize / bufferSize);
280         final int[] readOffsets = new int[totalReadCount];
281         for (int i = 0; i < totalReadCount; i++) {
282             // align in buffer size
283             readOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
284                     ~(bufferSize - 1);
285         }
286         final int runsInOneGo = 16;
287         final int readsInOneMeasure = totalReadCount / runsInOneGo;
288 
289         final RandomAccessFile randomFile = new RandomAccessFile(file, "rw"); // do not need O_SYNC
290         double[] rdAmount = new double[runsInOneGo];
291         double[] wrAmount = new double[runsInOneGo];
292         double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
293 
294             @Override
295             public void run(int i) throws IOException {
296                 Log.i(TAG, "starting " + i + " -th round");
297                 int start = i * readsInOneMeasure;
298                 int end = (i + 1) * readsInOneMeasure;
299                 for (int j = start; j < end; j++) {
300                     randomFile.seek(readOffsets[j]);
301                     randomFile.read(data);
302                 }
303             }
304         });
305         randomFile.close();
306         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
307                 times);
308         report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
309         // This is just the amount of IO returned from kernel. So this is performance neutral.
310         report.addValues("read_amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
311         Stat.StatResult stat = Stat.getStat(mbps);
312 
313         report.setSummary("read_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
314                 ResultUnit.MBPS);
315     }
316 
317     /**
318      *
319      * @param context
320      * @param dirName
321      * @param report
322      * @param fileSize
323      * @param bufferSize should be power of two
324      * @throws IOException
325      */
doRandomWriteTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)326     public static void doRandomWriteTest(Context context, String dirName, ReportLog report,
327             long fileSize, int bufferSize) throws Exception {
328         File file = FileUtil.createNewFilledFile(context,
329                 dirName, fileSize);
330         final byte[] data = FileUtil.generateRandomData(bufferSize);
331         Random random = new Random(0);
332         final int totalWriteCount = (int)(fileSize / bufferSize);
333         final int[] writeOffsets = new int[totalWriteCount];
334         for (int i = 0; i < totalWriteCount; i++) {
335             writeOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) &
336                     ~(bufferSize - 1);
337         }
338         final int runsInOneGo = 16;
339         final int writesInOneMeasure = totalWriteCount / runsInOneGo;
340 
341         final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC
342         double[] rdAmount = new double[runsInOneGo];
343         double[] wrAmount = new double[runsInOneGo];
344         double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() {
345 
346             @Override
347             public void run(int i) throws IOException {
348                 Log.i(TAG, "starting " + i + " -th round");
349                 int start = i * writesInOneMeasure;
350                 int end = (i + 1) * writesInOneMeasure;
351                 for (int j = start; j < end; j++) {
352                     randomFile.seek(writeOffsets[j]);
353                     randomFile.write(data);
354                 }
355             }
356         });
357         randomFile.close();
358         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
359                 times);
360         report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
361         report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
362         Stat.StatResult stat = Stat.getStat(mbps);
363 
364         report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
365                 ResultUnit.MBPS);
366     }
367 
368     /**
369      *
370      * @param context
371      * @param dirName
372      * @param report
373      * @param fileSize fileSize should be multiple of bufferSize.
374      * @param bufferSize
375      * @param numberRepetition
376      * @throws IOException
377      */
doSequentialUpdateTest(Context context, String dirName, long fileSize, int bufferSize, int numberRepetition, String reportName, String streamName)378     public static void doSequentialUpdateTest(Context context, String dirName, long fileSize,
379             int bufferSize, int numberRepetition, String reportName, String streamName)
380             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             DeviceReportLog report = new DeviceReportLog(reportName, streamName);
389             report.addValue("round", i,  ResultType.NEUTRAL, ResultUnit.NONE);
390             final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd");  // force O_SYNC
391             randomFile.seek(0L);
392             double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() {
393 
394                 @Override
395                 public void run(int i) throws IOException {
396                     randomFile.write(data);
397                 }
398             });
399             randomFile.close();
400             double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
401                     times);
402             report.addValues("throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
403             int offset = i * numberRepeatInOneRun;
404             for (int j = 0; j < mbps.length; j++) {
405                 mbpsAll[offset + j] = mbps[j];
406             }
407             report.submit();
408         }
409         Stat.StatResult stat = Stat.getStat(mbpsAll);
410         DeviceReportLog report = new DeviceReportLog(reportName, String.format("%s_average",
411                 streamName));
412         report.addValue("update_throughput", stat.mAverage, ResultType.HIGHER_BETTER,
413                 ResultUnit.MBPS);
414         report.submit();
415     }
416 }
417