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