Home | History | Annotate | Download | only in stressfs
      1 /*
      2  * Copyright (C) 2017 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 package com.android.car.test.stressfs;
     17 
     18 import android.app.Service;
     19 import android.app.Notification;
     20 import android.app.NotificationChannel;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.net.Uri;
     24 import android.os.Binder;
     25 import android.os.Bundle;
     26 import android.os.Environment;
     27 import android.os.IBinder;
     28 import android.util.Log;
     29 import android.R.drawable;
     30 
     31 import java.io.*;
     32 import java.util.*;
     33 
     34 /**
     35  * Used to stress the file writing before and during shutdown to help ensure
     36  * that the filesystem shuts down at the right time, in a consistent manner,
     37  * and does not get corrupted.
     38  *
     39  * Writes to two files - one on the data partition, one on the external storage
     40  * partition - simultaneous in two separate threads; starting over after a
     41  * certain amount of data is written.
     42  *
     43  * This class is intended to be invoked from the shell.  For a 64KB file
     44  * written in 1KB chunks, invoke from the host workstation:
     45  *   adb install -g StressFS.apk
     46  *   adb shell am start \
     47  *     -n com.android.car.test.stressfs/.WritingActivity \
     48  *     -a com.android.car.test.stressfs.START
     49  *     -d "stressfs://start?block=1024\&file=65536"
     50  *
     51  * After reboot:
     52  *   adb uninstall com.android.car.test.stressfs
     53  *   adb shell "rm -rf /storage/emulated/0/stressfs_data*"
     54  *
     55  * On boot after running this while shutting down, fsck should flag any
     56  * corruption that it sees resulting from this running.  The goal is to set the
     57  * shutdown sequence in a manner that does not corrupt so that this check can
     58  * be avoided.
     59  */
     60 public class WritingService extends Service {
     61 
     62     private static final String TAG = "StressFS";
     63 
     64     private static final String ACTION_START = "com.android.car.test.stressfs.START";
     65 
     66     private static final int DEFAULT_BLOCK_SIZE = 4096;
     67     private static final int DEFAULT_FILE_SIZE = 16 * 1024 * 1024;
     68 
     69     private static final String FILENAME_PREFIX = "stressfs_data_";
     70 
     71     private static final int NOTIFICATION_ID = 100;
     72 
     73     /** Continuously writes test data to a specified file. */
     74     private static class WriteToDisk extends Thread {
     75         private final String mLogTag;
     76         private final File mFile;
     77         private final int mFileSize;
     78         private final byte[] mTestData;
     79 
     80         public WriteToDisk(
     81                 String logTag,
     82                 File file,
     83                 int fileSize,
     84                 byte[] testData) {
     85             mLogTag = logTag;
     86             mFile = file;
     87             mFileSize = fileSize;
     88             mTestData = testData;
     89         }
     90 
     91         /** Writes data to a file, restarting once the maximum amount of data is reached.*/
     92         @Override
     93         public void run() {
     94             Log.d(TAG, mLogTag + " thread started");
     95             while (true) {
     96                 try {
     97                     FileOutputStream fos = new FileOutputStream(mFile);
     98                     // Write in chunks until the amount of data requested
     99                     // is written.
    100                     for (int j = 0; j < mFileSize; j += mTestData.length) {
    101                         fos.write(mTestData);
    102                     }
    103                     fos.close();
    104                 } catch (FileNotFoundException e) {
    105                     Log.e(TAG, "File not found: ", e);
    106                 } catch (IOException e) {
    107                     Log.e(TAG, "IO error: ", e);
    108                 }
    109             }
    110         }
    111     }
    112 
    113     /** Raises service priority and starts the writing threads. */
    114     @Override
    115     public IBinder onBind(Intent intent) {
    116         Notification notification =
    117                 new Notification.Builder(this, NotificationChannel.DEFAULT_CHANNEL_ID)
    118                         .setContentTitle("Stress Filesystem service running.")
    119                         .setSmallIcon(drawable.ic_menu_save)
    120                         .build();
    121 
    122         // Raise priority of this service.
    123         startForeground(NOTIFICATION_ID, notification);
    124 
    125         File dataPartitionFile = getFileStreamPath(FILENAME_PREFIX + UUID.randomUUID());
    126         File externalPartitionFile = new File(
    127                 Environment.getExternalStorageDirectory(), FILENAME_PREFIX + UUID.randomUUID());
    128 
    129         Log.i(TAG, "External storage state: " +
    130                 Environment.getExternalStorageState(externalPartitionFile));
    131 
    132         Uri data = intent.getData();
    133         if (data != null) {
    134             Log.i(TAG, "Data: " + data.toString());
    135         }
    136         int blockSize = getQueryParam(data, "block", DEFAULT_BLOCK_SIZE);
    137         int fileSize = getQueryParam(data, "file", DEFAULT_FILE_SIZE);
    138         Log.i(TAG, "Block Size: " + blockSize);
    139         Log.i(TAG, "File Size: " + fileSize);
    140 
    141         if (fileSize % blockSize != 0) {
    142             Log.w(TAG, "File size should be a multiple of block size.");
    143         }
    144 
    145         // Populate some test data.
    146         StringBuilder builder = new StringBuilder(blockSize);
    147         for (int i = 0; i < builder.capacity(); i++) {
    148             builder.append((char)(i % 26 + 'A'));
    149         }
    150         byte[] testData = new String(builder).getBytes();
    151 
    152         // Spawn two threads - one to write to the /data partition, one to
    153         // write to the SD card.
    154         new WriteToDisk("data", dataPartitionFile, fileSize, testData).start();
    155         new WriteToDisk("external", externalPartitionFile, fileSize, testData).start();
    156 
    157         // No need to return a binder interface, since there is no more
    158         // interaction needed from the activity starting the service.
    159         return null;
    160     }
    161 
    162     /** Keeps service alive once started. */
    163     @Override
    164     public int onStartCommand(Intent intent, int flags, int startId) {
    165         return START_STICKY;
    166     }
    167 
    168     /** Parses an integer query parameter from the input Uri. */
    169     private int getQueryParam(Uri data, String key, int defaultValue) {
    170         if (data == null) {
    171             return defaultValue;
    172         }
    173         String inValString = data.getQueryParameter(key);
    174         if (inValString != null) {
    175             try {
    176                 int inVal = Integer.parseInt(inValString);
    177                 if (inVal != 0) {
    178                     return inVal;
    179                 }
    180             } catch (NumberFormatException e) {
    181                 return defaultValue;
    182             }
    183         }
    184         return defaultValue;
    185     }
    186 }
    187