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