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