1 /* 2 * Copyright (C) 2018 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.storagemonitoring; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.car.builtin.util.Slogf; 21 import android.car.storagemonitoring.LifetimeWriteInfo; 22 23 import com.android.car.CarLog; 24 import com.android.internal.annotations.VisibleForTesting; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.nio.file.Files; 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * <p>Loads lifetime write data for mounted filesystems via sysfs.</p> 34 * 35 * <p>ext4 and f2fs currently offer this information via 36 * /sys/fs/<i>type</i>/<i>partition</i>/lifetime_write_kbytes VFS entry points.</p> 37 */ 38 public class SysfsLifetimeWriteInfoProvider implements LifetimeWriteInfoProvider { 39 private static final String TAG = CarLog.tagFor(SysfsLifetimeWriteInfoProvider.class); 40 41 private static final String DEFAULT_PATH = "/sys/fs/"; 42 private static final String[] KNOWN_FILESYSTEMS = new String[] { 43 "ext4", 44 "f2fs" 45 }; 46 private static final String FILENAME = "lifetime_write_kbytes"; 47 48 private final File mWriteInfosPath; 49 SysfsLifetimeWriteInfoProvider()50 public SysfsLifetimeWriteInfoProvider() { 51 this(new File(DEFAULT_PATH)); 52 } 53 54 @VisibleForTesting SysfsLifetimeWriteInfoProvider(File writeInfosPath)55 SysfsLifetimeWriteInfoProvider(File writeInfosPath) { 56 mWriteInfosPath = writeInfosPath; 57 } 58 59 @Nullable tryParse(File dir)60 private LifetimeWriteInfo tryParse(File dir) { 61 File writefile = new File(dir, FILENAME); 62 if (!writefile.exists() || !writefile.isFile()) { 63 Slogf.d(TAG, writefile + " not a valid source of lifetime writes"); 64 return null; 65 } 66 List<String> datalines; 67 try { 68 datalines = Files.readAllLines(writefile.toPath()); 69 } catch (IOException e) { 70 Slogf.e(TAG, "unable to read write info from " + writefile, e); 71 return null; 72 } 73 if (datalines == null || datalines.size() != 1) { 74 Slogf.e(TAG, "unable to read valid write info from " + writefile); 75 return null; 76 } 77 String data = datalines.get(0); 78 try { 79 long writtenBytes = 1024L * Long.parseLong(data); 80 if (writtenBytes < 0) { 81 Slogf.e(TAG, "file at location " + writefile + " contained a negative data amount " 82 + data + ". Ignoring."); 83 return null; 84 } else { 85 return new LifetimeWriteInfo( 86 dir.getName(), 87 dir.getParentFile().getName(), 88 writtenBytes); 89 } 90 } catch (NumberFormatException e) { 91 Slogf.e(TAG, "unable to read valid write info from " + writefile, e); 92 return null; 93 } 94 } 95 96 @Override 97 @NonNull load()98 public LifetimeWriteInfo[] load() { 99 List<LifetimeWriteInfo> writeInfos = new ArrayList<>(); 100 101 for (String fstype : KNOWN_FILESYSTEMS) { 102 File fspath = new File(mWriteInfosPath, fstype); 103 if (!fspath.exists() || !fspath.isDirectory()) continue; 104 File[] files = fspath.listFiles(File::isDirectory); 105 if (files == null) { 106 Slogf.e(TAG, "there are no directories at location " + fspath.getAbsolutePath()); 107 continue; 108 } 109 for (int index = 0; index < files.length; index++) { 110 LifetimeWriteInfo writeInfo = tryParse(files[index]); 111 if (writeInfo != null) { 112 writeInfos.add(writeInfo); 113 } 114 } 115 } 116 117 return writeInfos.toArray(new LifetimeWriteInfo[0]); 118 } 119 } 120