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