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