• 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.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      */
generateRandomData(int length)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      */
createNewFile(Context context, String dirName)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      */
createNewFiles(Context context, String dirName, int count)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      */
writeFile(File file, byte[] data, boolean append)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      */
createNewFilledFile(Context context, String dirName, long length)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      */
removeFileOrDir(Context context, String name)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 
removeEntry(File entry)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      */
measureIO(int count, double[] readAmount, double[] writeAmount, MeasureRun run)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 
getRWAmount(File file)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      */
getFileSizeExceedingMemory(Context context, int bufferSize)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      */
doRandomReadTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)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      */
doRandomWriteTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)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      */
doSequentialUpdateTest(Context context, String dirName, long fileSize, int bufferSize, int numberRepetition, String reportName, String streamName)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