Home | History | Annotate | Download | only in storagelifetime
      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.google.android.car.kitchensink.storagelifetime;
     17 
     18 import static android.system.OsConstants.O_APPEND;
     19 import static android.system.OsConstants.O_RDWR;
     20 
     21 import android.annotation.Nullable;
     22 import android.car.Car;
     23 import android.car.CarNotConnectedException;
     24 import android.car.storagemonitoring.CarStorageMonitoringManager;
     25 import android.car.storagemonitoring.CarStorageMonitoringManager.IoStatsListener;
     26 import android.car.storagemonitoring.IoStats;
     27 import android.car.storagemonitoring.IoStatsEntry;
     28 import android.os.Bundle;
     29 import android.os.StatFs;
     30 import android.support.v4.app.Fragment;
     31 import android.system.ErrnoException;
     32 import android.util.Log;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.widget.ArrayAdapter;
     37 import android.widget.ListView;
     38 import android.widget.TextView;
     39 
     40 import com.google.android.car.kitchensink.KitchenSinkActivity;
     41 import com.google.android.car.kitchensink.R;
     42 
     43 import libcore.io.Libcore;
     44 
     45 import java.io.File;
     46 import java.io.FileDescriptor;
     47 import java.io.IOException;
     48 import java.nio.ByteBuffer;
     49 import java.nio.file.Files;
     50 import java.nio.file.Path;
     51 import java.nio.file.StandardOpenOption;
     52 import java.security.NoSuchAlgorithmException;
     53 import java.security.SecureRandom;
     54 import java.util.List;
     55 
     56 public class StorageLifetimeFragment extends Fragment {
     57     private static final String FILE_NAME = "storage.bin";
     58     private static final String TAG = "CAR.STORAGELIFETIME.KS";
     59 
     60     private static final int KILOBYTE = 1024;
     61     private static final int MEGABYTE = 1024 * 1024;
     62 
     63     private StatFs mStatFs;
     64     private KitchenSinkActivity mActivity;
     65     private TextView mStorageWearInfo;
     66     private ListView mStorageChangesHistory;
     67     private TextView mFreeSpaceInfo;
     68     private TextView mIoActivity;
     69     private CarStorageMonitoringManager mStorageManager;
     70 
     71     private final IoStatsListener mIoListener = new IoStatsListener() {
     72         @Override
     73         public void onSnapshot(IoStats snapshot) {
     74             if (mIoActivity != null) {
     75                 mIoActivity.setText("");
     76                 snapshot.getStats().forEach(uidIoStats -> {
     77                     final long bytesWrittenToStorage = uidIoStats.foreground.bytesWrittenToStorage +
     78                             uidIoStats.background.bytesWrittenToStorage;
     79                     final long fsyncCalls = uidIoStats.foreground.fsyncCalls +
     80                             uidIoStats.background.fsyncCalls;
     81                     if (bytesWrittenToStorage > 0 || fsyncCalls > 0) {
     82                         mIoActivity.append(String.format(
     83                             "uid = %d, runtime = %d, bytes writen to disk = %d, fsync calls = %d\n",
     84                             uidIoStats.uid,
     85                             uidIoStats.runtimeMillis,
     86                             bytesWrittenToStorage,
     87                             fsyncCalls));
     88                     }
     89                 });
     90                 final List<IoStatsEntry> totals;
     91                 try {
     92                     totals = mStorageManager.getAggregateIoStats();
     93                 } catch (CarNotConnectedException e) {
     94                     Log.e(TAG, "Car not connected or not supported", e);
     95                     return;
     96                 }
     97 
     98                 final long totalBytesWrittenToStorage = totals.stream()
     99                         .mapToLong(stats -> stats.foreground.bytesWrittenToStorage +
    100                                 stats.background.bytesWrittenToStorage)
    101                         .reduce(0L, (x,y)->x+y);
    102                 final long totalFsyncCalls = totals.stream()
    103                         .mapToLong(stats -> stats.foreground.fsyncCalls +
    104                             stats.background.fsyncCalls)
    105                         .reduce(0L, (x,y)->x+y);
    106 
    107                 mIoActivity.append(String.format(
    108                         "total bytes written to disk = %d, total fsync calls = %d",
    109                         totalBytesWrittenToStorage,
    110                         totalFsyncCalls));
    111             }
    112         }
    113     };
    114 
    115     // TODO(egranata): put this somewhere more useful than KitchenSink
    116     private static String preEolToString(int preEol) {
    117         switch (preEol) {
    118             case 1: return "normal";
    119             case 2: return "warning";
    120             case 3: return "urgent";
    121             default:
    122                 return "unknown";
    123         }
    124     }
    125 
    126     private Path getFilePath() throws IOException {
    127         Path filePath = new File(mActivity.getFilesDir(), FILE_NAME).toPath();
    128         if (Files.notExists(filePath)) {
    129             Files.createFile(filePath);
    130         }
    131         return filePath;
    132     }
    133 
    134     private void writeBytesToFile(int size) {
    135         try {
    136             final Path filePath = getFilePath();
    137             byte[] data = new byte[size];
    138             SecureRandom.getInstanceStrong().nextBytes(data);
    139             Files.write(filePath,
    140                 data,
    141                 StandardOpenOption.APPEND);
    142         } catch (NoSuchAlgorithmException | IOException e) {
    143             Log.w(TAG, "could not append data", e);
    144         }
    145     }
    146 
    147     private void fsyncFile() {
    148         try {
    149             final Path filePath = getFilePath();
    150             FileDescriptor fd = Libcore.os.open(filePath.toString(), O_APPEND | O_RDWR, 0);
    151             if (!fd.valid()) {
    152                 Log.w(TAG, "file descriptor is invalid");
    153                 return;
    154             }
    155             // fill byteBuffer with arbitrary data in order to make an fsync() meaningful
    156             ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[] {101, 110, 114, 105, 99, 111});
    157             Libcore.os.write(fd, byteBuffer);
    158             Libcore.os.fsync(fd);
    159             Libcore.os.close(fd);
    160         } catch (ErrnoException | IOException e) {
    161             Log.w(TAG, "could not fsync data", e);
    162         }
    163     }
    164 
    165     @Nullable
    166     @Override
    167     public View onCreateView(
    168             LayoutInflater inflater,
    169             @Nullable ViewGroup container,
    170             @Nullable Bundle savedInstanceState) {
    171         View view = inflater.inflate(R.layout.storagewear, container, false);
    172         mActivity = (KitchenSinkActivity) getHost();
    173         mStorageWearInfo = view.findViewById(R.id.storage_wear_info);
    174         mStorageChangesHistory = view.findViewById(R.id.storage_events_list);
    175         mFreeSpaceInfo = view.findViewById(R.id.free_disk_space);
    176         mIoActivity = view.findViewById(R.id.last_io_snapshot);
    177 
    178         view.findViewById(R.id.write_one_kilobyte).setOnClickListener(
    179             v -> writeBytesToFile(KILOBYTE));
    180 
    181         view.findViewById(R.id.write_one_megabyte).setOnClickListener(
    182             v -> writeBytesToFile(MEGABYTE));
    183 
    184         view.findViewById(R.id.perform_fsync).setOnClickListener(
    185             v -> fsyncFile());
    186 
    187         return view;
    188     }
    189 
    190     private void reloadInfo() {
    191         try {
    192             mStatFs = new StatFs(mActivity.getFilesDir().getAbsolutePath());
    193 
    194             mStorageManager =
    195                 (CarStorageMonitoringManager) mActivity.getCar().getCarManager(
    196                         Car.STORAGE_MONITORING_SERVICE);
    197 
    198             mStorageWearInfo.setText("Wear estimate: " +
    199                 mStorageManager.getWearEstimate() + "\nPre EOL indicator: " +
    200                 preEolToString(mStorageManager.getPreEolIndicatorStatus()));
    201 
    202             mStorageChangesHistory.setAdapter(new ArrayAdapter(mActivity,
    203                     R.layout.wear_estimate_change_textview,
    204                     mStorageManager.getWearEstimateHistory().toArray()));
    205 
    206             mFreeSpaceInfo.setText("Available blocks: " + mStatFs.getAvailableBlocksLong() +
    207                 "\nBlock size: " + mStatFs.getBlockSizeLong() + " bytes" +
    208                 "\nfor a total free space of: " +
    209                 (mStatFs.getBlockSizeLong() * mStatFs.getAvailableBlocksLong() / MEGABYTE) + "MB");
    210         } catch (android.car.CarNotConnectedException|
    211                  android.support.car.CarNotConnectedException e) {
    212             Log.e(TAG, "Car not connected or not supported", e);
    213         }
    214     }
    215 
    216     private void registerListener() {
    217         try {
    218             mStorageManager.registerListener(mIoListener);
    219         } catch (CarNotConnectedException e) {
    220             Log.e(TAG, "Car not connected or not supported", e);
    221         }
    222     }
    223 
    224     private void unregisterListener() {
    225         try {
    226             mStorageManager.unregisterListener(mIoListener);
    227         } catch (CarNotConnectedException e) {
    228             Log.e(TAG, "Car not connected or not supported", e);
    229         }
    230     }
    231 
    232     @Override
    233     public void onResume() {
    234         super.onResume();
    235         reloadInfo();
    236         registerListener();
    237     }
    238 
    239     @Override
    240     public void onPause() {
    241         unregisterListener();
    242         super.onPause();
    243     }
    244 }
    245