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