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