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