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