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 * @throws IOException 271 */ doRandomReadTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)272 public static void doRandomReadTest(Context context, String dirName, ReportLog report, 273 long fileSize, int bufferSize) throws Exception { 274 File file = FileUtil.createNewFilledFile(context, 275 dirName, fileSize); 276 277 final byte[] data = FileUtil.generateRandomData(bufferSize); 278 Random random = new Random(0); 279 final int totalReadCount = (int)(fileSize / bufferSize); 280 final int[] readOffsets = new int[totalReadCount]; 281 for (int i = 0; i < totalReadCount; i++) { 282 // align in buffer size 283 readOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) & 284 ~(bufferSize - 1); 285 } 286 final int runsInOneGo = 16; 287 final int readsInOneMeasure = totalReadCount / runsInOneGo; 288 289 final RandomAccessFile randomFile = new RandomAccessFile(file, "rw"); // do not need O_SYNC 290 double[] rdAmount = new double[runsInOneGo]; 291 double[] wrAmount = new double[runsInOneGo]; 292 double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() { 293 294 @Override 295 public void run(int i) throws IOException { 296 Log.i(TAG, "starting " + i + " -th round"); 297 int start = i * readsInOneMeasure; 298 int end = (i + 1) * readsInOneMeasure; 299 for (int j = start; j < end; j++) { 300 randomFile.seek(readOffsets[j]); 301 randomFile.read(data); 302 } 303 } 304 }); 305 randomFile.close(); 306 double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024, 307 times); 308 report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 309 // This is just the amount of IO returned from kernel. So this is performance neutral. 310 report.addValues("read_amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE); 311 Stat.StatResult stat = Stat.getStat(mbps); 312 313 report.setSummary("read_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER, 314 ResultUnit.MBPS); 315 } 316 317 /** 318 * 319 * @param context 320 * @param dirName 321 * @param report 322 * @param fileSize 323 * @param bufferSize should be power of two 324 * @throws IOException 325 */ doRandomWriteTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)326 public static void doRandomWriteTest(Context context, String dirName, ReportLog report, 327 long fileSize, int bufferSize) throws Exception { 328 File file = FileUtil.createNewFilledFile(context, 329 dirName, fileSize); 330 final byte[] data = FileUtil.generateRandomData(bufferSize); 331 Random random = new Random(0); 332 final int totalWriteCount = (int)(fileSize / bufferSize); 333 final int[] writeOffsets = new int[totalWriteCount]; 334 for (int i = 0; i < totalWriteCount; i++) { 335 writeOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) & 336 ~(bufferSize - 1); 337 } 338 final int runsInOneGo = 16; 339 final int writesInOneMeasure = totalWriteCount / runsInOneGo; 340 341 final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC 342 double[] rdAmount = new double[runsInOneGo]; 343 double[] wrAmount = new double[runsInOneGo]; 344 double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() { 345 346 @Override 347 public void run(int i) throws IOException { 348 Log.i(TAG, "starting " + i + " -th round"); 349 int start = i * writesInOneMeasure; 350 int end = (i + 1) * writesInOneMeasure; 351 for (int j = start; j < end; j++) { 352 randomFile.seek(writeOffsets[j]); 353 randomFile.write(data); 354 } 355 } 356 }); 357 randomFile.close(); 358 double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024, 359 times); 360 report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 361 report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE); 362 Stat.StatResult stat = Stat.getStat(mbps); 363 364 report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER, 365 ResultUnit.MBPS); 366 } 367 368 /** 369 * 370 * @param context 371 * @param dirName 372 * @param report 373 * @param fileSize fileSize should be multiple of bufferSize. 374 * @param bufferSize 375 * @param numberRepetition 376 * @throws IOException 377 */ doSequentialUpdateTest(Context context, String dirName, long fileSize, int bufferSize, int numberRepetition, String reportName, String streamName)378 public static void doSequentialUpdateTest(Context context, String dirName, long fileSize, 379 int bufferSize, int numberRepetition, String reportName, String streamName) 380 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 DeviceReportLog report = new DeviceReportLog(reportName, streamName); 389 report.addValue("round", i, ResultType.NEUTRAL, ResultUnit.NONE); 390 final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC 391 randomFile.seek(0L); 392 double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() { 393 394 @Override 395 public void run(int i) throws IOException { 396 randomFile.write(data); 397 } 398 }); 399 randomFile.close(); 400 double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024, 401 times); 402 report.addValues("throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 403 int offset = i * numberRepeatInOneRun; 404 for (int j = 0; j < mbps.length; j++) { 405 mbpsAll[offset + j] = mbps[j]; 406 } 407 report.submit(); 408 } 409 Stat.StatResult stat = Stat.getStat(mbpsAll); 410 DeviceReportLog report = new DeviceReportLog(reportName, String.format("%s_average", 411 streamName)); 412 report.addValue("update_throughput", stat.mAverage, ResultType.HIGHER_BETTER, 413 ResultUnit.MBPS); 414 report.submit(); 415 } 416 } 417