1 /* 2 * Copyright (C) 2021 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 17 package com.android.car.telemetry; 18 19 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_ALREADY_EXISTS; 20 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_NONE; 21 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_UNKNOWN; 22 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_VERSION_TOO_OLD; 23 24 import android.car.telemetry.MetricsConfigKey; 25 import android.util.ArrayMap; 26 import android.util.AtomicFile; 27 28 import com.android.car.CarLog; 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.utils.Slogf; 31 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.nio.file.Files; 36 import java.nio.file.Paths; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Map; 40 41 /** 42 * This class is responsible for storing, retrieving, and deleting {@link 43 * TelemetryProto.MetricsConfig}. All of the methods are blocking so the class should only be 44 * accessed on the telemetry thread. 45 */ 46 public class MetricsConfigStore { 47 @VisibleForTesting 48 static final String METRICS_CONFIG_DIR = "metrics_configs"; 49 50 private final File mConfigDirectory; 51 private Map<String, TelemetryProto.MetricsConfig> mActiveConfigs; 52 MetricsConfigStore(File rootDirectory)53 public MetricsConfigStore(File rootDirectory) { 54 mConfigDirectory = new File(rootDirectory, METRICS_CONFIG_DIR); 55 mConfigDirectory.mkdirs(); 56 mActiveConfigs = new ArrayMap<>(); 57 // TODO(b/197336485): Add expiration date check for MetricsConfig 58 for (File file : mConfigDirectory.listFiles()) { 59 AtomicFile atomicFile = new AtomicFile(file); 60 try { 61 TelemetryProto.MetricsConfig config = 62 TelemetryProto.MetricsConfig.parseFrom(atomicFile.readFully()); 63 mActiveConfigs.put(config.getName(), config); 64 } catch (IOException e) { 65 // TODO(b/197336655): record failure 66 atomicFile.delete(); 67 } 68 } 69 } 70 71 /** 72 * Returns all active {@link TelemetryProto.MetricsConfig} from disk. 73 */ getActiveMetricsConfigs()74 public List<TelemetryProto.MetricsConfig> getActiveMetricsConfigs() { 75 return new ArrayList<>(mActiveConfigs.values()); 76 } 77 78 /** 79 * Stores the MetricsConfig to disk if it is valid. It checks both config name and version for 80 * validity. 81 * 82 * @param metricsConfig the config to be persisted to disk. 83 * @return {@link android.car.telemetry.CarTelemetryManager.MetricsConfigError} status code. 84 */ addMetricsConfig(TelemetryProto.MetricsConfig metricsConfig)85 public int addMetricsConfig(TelemetryProto.MetricsConfig metricsConfig) { 86 // TODO(b/197336485): Check expiration date for MetricsConfig 87 if (metricsConfig.getVersion() <= 0) { 88 return ERROR_METRICS_CONFIG_VERSION_TOO_OLD; 89 } 90 if (mActiveConfigs.containsKey(metricsConfig.getName())) { 91 int currentVersion = mActiveConfigs.get(metricsConfig.getName()).getVersion(); 92 if (currentVersion > metricsConfig.getVersion()) { 93 return ERROR_METRICS_CONFIG_VERSION_TOO_OLD; 94 } else if (currentVersion == metricsConfig.getVersion()) { 95 return ERROR_METRICS_CONFIG_ALREADY_EXISTS; 96 } 97 } 98 mActiveConfigs.put(metricsConfig.getName(), metricsConfig); 99 AtomicFile atomicFile = new AtomicFile(new File(mConfigDirectory, metricsConfig.getName())); 100 FileOutputStream fos = null; 101 try { 102 fos = atomicFile.startWrite(); 103 fos.write(metricsConfig.toByteArray()); 104 atomicFile.finishWrite(fos); 105 } catch (IOException e) { 106 // TODO(b/197336655): record failure 107 atomicFile.failWrite(fos); 108 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write metrics config to disk", e); 109 return ERROR_METRICS_CONFIG_UNKNOWN; 110 } 111 return ERROR_METRICS_CONFIG_NONE; 112 } 113 114 /** 115 * Deletes the MetricsConfig from disk. 116 * 117 * @param key the unique identifier of the metrics config that should be deleted. 118 * @return true for successful removal, false otherwise. 119 */ removeMetricsConfig(MetricsConfigKey key)120 public boolean removeMetricsConfig(MetricsConfigKey key) { 121 String metricsConfigName = key.getName(); 122 if (!mActiveConfigs.containsKey(key.getName()) 123 || mActiveConfigs.get(key.getName()).getVersion() != key.getVersion()) { 124 return false; // no match found, nothing to remove 125 } 126 mActiveConfigs.remove(metricsConfigName); 127 try { 128 return Files.deleteIfExists(Paths.get( 129 mConfigDirectory.getAbsolutePath(), metricsConfigName)); 130 } catch (IOException e) { 131 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove MetricsConfig: " + key.getName(), e); 132 // TODO(b/197336655): record failure 133 } 134 return false; 135 } 136 137 /** Deletes all MetricsConfigs from disk. */ removeAllMetricsConfigs()138 public void removeAllMetricsConfigs() { 139 mActiveConfigs.clear(); 140 for (File file : mConfigDirectory.listFiles()) { 141 if (!file.delete()) { 142 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove MetricsConfig: " + file.getName()); 143 } 144 } 145 } 146 } 147