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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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