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.cts.util.SystemUtil; 21 import android.util.Log; 22 23 import com.android.compatibility.common.util.DeviceReportLog; 24 import com.android.compatibility.common.util.MeasureRun; 25 import com.android.compatibility.common.util.MeasureTime; 26 import com.android.compatibility.common.util.ReportLog; 27 import com.android.compatibility.common.util.ResultType; 28 import com.android.compatibility.common.util.ResultUnit; 29 import com.android.compatibility.common.util.Stat; 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 * create array with different data per each call 46 * 47 * @param length 48 * @param randomSeed 49 * @return 50 */ generateRandomData(int length)51 public static byte[] generateRandomData(int length) { 52 byte[] buffer = new byte[length]; 53 int val = mRandom.nextInt(); 54 for (int i = 0; i < length / 4; i++) { 55 // in little-endian 56 buffer[i * 4] = (byte)(val & 0x000000ff); 57 buffer[i * 4 + 1] = (byte)((val & 0x0000ff00) >> 8); 58 buffer[i * 4 + 2] = (byte)((val & 0x00ff0000) >> 16); 59 buffer[i * 4 + 3] = (byte)((val & 0xff000000) >> 24); 60 val++; 61 } 62 for (int i = (length / 4) * 4; i < length; i++) { 63 buffer[i] = 0; 64 } 65 return buffer; 66 } 67 68 /** 69 * create a new file under the given dirName. 70 * Existing files will not be affected. 71 * @param context 72 * @param dirName 73 * @return 74 */ createNewFile(Context context, String dirName)75 public static File createNewFile(Context context, String dirName) { 76 File topDir = new File(context.getFilesDir(), dirName); 77 topDir.mkdir(); 78 String[] list = topDir.list(); 79 80 String newFileName; 81 while (true) { 82 newFileName = Long.toString(mFileId); 83 boolean fileExist = false; 84 for (String child : list) { 85 if (child.equals(newFileName)) { 86 fileExist = true; 87 break; 88 } 89 } 90 if (!fileExist) { 91 break; 92 } 93 mFileId++; 94 } 95 mFileId++; 96 //Log.i(TAG, "filename" + Long.toString(mFileId)); 97 return new File(topDir, newFileName); 98 } 99 100 /** 101 * create multiple new files 102 * @param context 103 * @param dirName 104 * @param count number of files to create 105 * @return 106 */ createNewFiles(Context context, String dirName, int count)107 public static File[] createNewFiles(Context context, String dirName, int count) { 108 File[] files = new File[count]; 109 for (int i = 0; i < count; i++) { 110 files[i] = createNewFile(context, dirName); 111 } 112 return files; 113 } 114 115 /** 116 * write file with given byte array 117 * @param file 118 * @param data 119 * @param append will append if set true. Otherwise, write from beginning 120 * @throws IOException 121 */ writeFile(File file, byte[] data, boolean append)122 public static void writeFile(File file, byte[] data, boolean append) throws IOException { 123 final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC 124 if (append) { 125 randomFile.seek(randomFile.length()); 126 } else { 127 randomFile.seek(0L); 128 } 129 randomFile.write(data); 130 randomFile.close(); 131 } 132 133 /** 134 * create a new file with given length. 135 * @param context 136 * @param dirName 137 * @param length 138 * @return 139 * @throws IOException 140 */ createNewFilledFile(Context context, String dirName, long length)141 public static File createNewFilledFile(Context context, String dirName, long length) 142 throws IOException { 143 final int BUFFER_SIZE = 10 * 1024 * 1024; 144 File file = createNewFile(context, dirName); 145 FileOutputStream out = new FileOutputStream(file); 146 byte[] data = generateRandomData(BUFFER_SIZE); 147 long written = 0; 148 while (written < length) { 149 out.write(data); 150 written += BUFFER_SIZE; 151 } 152 out.flush(); 153 out.close(); 154 return file; 155 } 156 157 /** 158 * remove given file or directory under the current app's files dir. 159 * @param context 160 * @param name 161 */ removeFileOrDir(Context context, String name)162 public static void removeFileOrDir(Context context, String name) { 163 File entry = new File(context.getFilesDir(), name); 164 if (entry.exists()) { 165 removeEntry(entry); 166 } 167 } 168 removeEntry(File entry)169 private static void removeEntry(File entry) { 170 if (entry.isDirectory()) { 171 String[] children = entry.list(); 172 for (String child : children) { 173 removeEntry(new File(entry, child)); 174 } 175 } 176 Log.i(TAG, "delete file " + entry.getAbsolutePath()); 177 entry.delete(); 178 } 179 180 /** 181 * measure time taken for each IO run with amount R/W 182 * @param count 183 * @param run 184 * @param readAmount returns amount of read in bytes for each interval. 185 * Value will not be written if /proc/self/io does not exist. 186 * @param writeAmount returns amount of write in bytes for each interval. 187 * @return time per each interval 188 * @throws IOException 189 */ measureIO(int count, double[] readAmount, double[] writeAmount, MeasureRun run)190 public static double[] measureIO(int count, double[] readAmount, double[] writeAmount, 191 MeasureRun run) throws Exception { 192 double[] result = new double[count]; 193 File procIo = new File("/proc/self/io"); 194 boolean measureIo = procIo.exists() && procIo.canRead(); 195 long prev = System.currentTimeMillis(); 196 RWAmount prevAmount = new RWAmount(); 197 if (measureIo) { 198 prevAmount = getRWAmount(procIo); 199 } 200 for (int i = 0; i < count; i++) { 201 run.run(i); 202 long current = System.currentTimeMillis(); 203 result[i] = current - prev; 204 prev = current; 205 if (measureIo) { 206 RWAmount currentAmount = getRWAmount(procIo); 207 readAmount[i] = currentAmount.mRd - prevAmount.mRd; 208 writeAmount[i] = currentAmount.mWr - prevAmount.mWr; 209 prevAmount = currentAmount; 210 } 211 } 212 return result; 213 } 214 215 private static class RWAmount { 216 public double mRd = 0.0; 217 public double mWr = 0.0; 218 }; 219 getRWAmount(File file)220 private static RWAmount getRWAmount(File file) throws IOException { 221 RWAmount amount = new RWAmount(); 222 223 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); 224 String line; 225 while((line = br.readLine())!= null) { 226 if (line.startsWith("read_bytes")) { 227 amount.mRd = Double.parseDouble(line.split(" ")[1]); 228 } else if (line.startsWith("write_bytes")) { 229 amount.mWr = Double.parseDouble(line.split(" ")[1]); 230 } 231 } 232 br.close(); 233 return amount; 234 } 235 236 /** 237 * get file size exceeding total memory size ( 2x total memory). 238 * The size is rounded in bufferSize. And the size will be bigger than 400MB. 239 * @param context 240 * @param bufferSize 241 * @return file size or 0 if there is not enough space. 242 */ getFileSizeExceedingMemory(Context context, int bufferSize)243 public static long getFileSizeExceedingMemory(Context context, int bufferSize) { 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 final long reservedDiskSize = (50L * 1024L * 1024L / bufferSize) * bufferSize; 249 if ( diskSizeTarget < minimumDiskSize ) { 250 diskSizeTarget = minimumDiskSize; 251 } 252 if (diskSizeTarget > freeDisk) { 253 Log.i(TAG, "Free disk size " + freeDisk + " too small"); 254 return 0; 255 } 256 if ((freeDisk - diskSizeTarget) < reservedDiskSize) { 257 diskSizeTarget -= reservedDiskSize; 258 } 259 return diskSizeTarget; 260 } 261 262 /** 263 * 264 * @param context 265 * @param dirName 266 * @param report 267 * @param fileSize 268 * @param bufferSize should be power of two 269 * @throws IOException 270 */ doRandomReadTest(Context context, String dirName, ReportLog report, long fileSize, int bufferSize)271 public static void doRandomReadTest(Context context, String dirName, ReportLog report, 272 long fileSize, int bufferSize) throws Exception { 273 File file = FileUtil.createNewFilledFile(context, 274 dirName, fileSize); 275 276 final byte[] data = FileUtil.generateRandomData(bufferSize); 277 Random random = new Random(0); 278 final int totalReadCount = (int)(fileSize / bufferSize); 279 final int[] readOffsets = new int[totalReadCount]; 280 for (int i = 0; i < totalReadCount; i++) { 281 // align in buffer size 282 readOffsets[i] = (int)(random.nextFloat() * (fileSize - bufferSize)) & 283 ~(bufferSize - 1); 284 } 285 final int runsInOneGo = 16; 286 final int readsInOneMeasure = totalReadCount / runsInOneGo; 287 288 final RandomAccessFile randomFile = new RandomAccessFile(file, "rw"); // do not need O_SYNC 289 double[] rdAmount = new double[runsInOneGo]; 290 double[] wrAmount = new double[runsInOneGo]; 291 double[] times = FileUtil.measureIO(runsInOneGo, rdAmount, wrAmount, new MeasureRun() { 292 293 @Override 294 public void run(int i) throws IOException { 295 Log.i(TAG, "starting " + i + " -th round"); 296 int start = i * readsInOneMeasure; 297 int end = (i + 1) * readsInOneMeasure; 298 for (int j = start; j < end; j++) { 299 randomFile.seek(readOffsets[j]); 300 randomFile.read(data); 301 } 302 } 303 }); 304 randomFile.close(); 305 double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024, 306 times); 307 report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 308 // This is just the amount of IO returned from kernel. So this is performance neutral. 309 report.addValues("read_amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE); 310 Stat.StatResult stat = Stat.getStat(mbps); 311 312 report.setSummary("read_throughput_average", 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 = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024, 358 times); 359 report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 360 report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE); 361 Stat.StatResult stat = Stat.getStat(mbps); 362 363 report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER, 364 ResultUnit.MBPS); 365 } 366 367 /** 368 * 369 * @param context 370 * @param dirName 371 * @param report 372 * @param fileSize fileSize should be multiple of bufferSize. 373 * @param bufferSize 374 * @param numberRepetition 375 * @throws IOException 376 */ doSequentialUpdateTest(Context context, String dirName, long fileSize, int bufferSize, int numberRepetition, String reportName, String streamName)377 public static void doSequentialUpdateTest(Context context, String dirName, long fileSize, 378 int bufferSize, int numberRepetition, String reportName, String streamName) 379 throws Exception { 380 File file = FileUtil.createNewFilledFile(context, 381 dirName, fileSize); 382 final byte[] data = FileUtil.generateRandomData(bufferSize); 383 int numberRepeatInOneRun = (int)(fileSize / bufferSize); 384 double[] mbpsAll = new double[numberRepetition * numberRepeatInOneRun]; 385 for (int i = 0; i < numberRepetition; i++) { 386 Log.i(TAG, "starting " + i + " -th round"); 387 DeviceReportLog report = new DeviceReportLog(reportName, streamName); 388 report.addValue("round", i, ResultType.NEUTRAL, ResultUnit.NONE); 389 final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd"); // force O_SYNC 390 randomFile.seek(0L); 391 double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() { 392 393 @Override 394 public void run(int i) throws IOException { 395 randomFile.write(data); 396 } 397 }); 398 randomFile.close(); 399 double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024, 400 times); 401 report.addValues("throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS); 402 int offset = i * numberRepeatInOneRun; 403 for (int j = 0; j < mbps.length; j++) { 404 mbpsAll[offset + j] = mbps[j]; 405 } 406 report.submit(); 407 } 408 Stat.StatResult stat = Stat.getStat(mbpsAll); 409 DeviceReportLog report = new DeviceReportLog(reportName, String.format("%s_average", 410 streamName)); 411 report.addValue("update_throughput", stat.mAverage, ResultType.HIGHER_BETTER, 412 ResultUnit.MBPS); 413 report.submit(); 414 } 415 } 416