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