1 /*
2  * Copyright (C) 2014 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 android.hardware.cts.helpers;
18 
19 import android.hardware.Sensor;
20 import android.hardware.cts.helpers.sensoroperations.SensorOperation;
21 import android.util.Log;
22 
23 import java.io.BufferedWriter;
24 import java.io.File;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Set;
35 
36 /**
37  * Class used to store stats related to {@link SensorOperation}s. Sensor stats may be linked
38  * together so that they form a tree.
39  */
40 public class SensorStats {
41     public static final String DELIMITER = "__";
42 
43     public static final String ERROR = "error";
44     public static final String EVENT_FIFO_LENGTH = "event_fifo_length_observed";
45     public static final String EVENT_GAP_COUNT_KEY = "event_gap_count";
46     public static final String EVENT_GAP_POSITIONS_KEY = "event_gap_positions";
47     public static final String EVENT_OUT_OF_ORDER_COUNT_KEY = "event_out_of_order_count";
48     public static final String EVENT_OUT_OF_ORDER_POSITIONS_KEY = "event_out_of_order_positions";
49     public static final String EVENT_TIME_SYNCHRONIZATION_COUNT_KEY =
50             "event_time_synchronization_count";
51     public static final String EVENT_TIME_SYNCHRONIZATION_POSITIONS_KEY =
52             "event_time_synchronization_positions";
53     public static final String FREQUENCY_KEY = "frequency";
54     public static final String JITTER_95_PERCENTILE_PERCENT_KEY = "jitter_95_percentile_percent";
55     public static final String MEAN_KEY = "mean";
56     public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
57     public static final String MAGNITUDE_KEY = "magnitude";
58     public static final String DELAYED_BATCH_DELIVERY = "delayed_batch_delivery";
59 
60     private final Map<String, Object> mValues = new HashMap<>();
61     private final Map<String, SensorStats> mSensorStats = new HashMap<>();
62 
63     /**
64      * Add a value.
65      *
66      * @param key the key.
67      * @param value the value as an {@link Object}.
68      */
addValue(String key, Object value)69     public synchronized void addValue(String key, Object value) {
70         if (value == null) {
71             return;
72         }
73         mValues.put(key, value);
74     }
75 
76     /**
77      * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
78      * {@link SensorOperation} tree.
79      *
80      * @param key the key
81      * @param stats the sub {@link SensorStats} object.
82      */
addSensorStats(String key, SensorStats stats)83     public synchronized void addSensorStats(String key, SensorStats stats) {
84         if (stats == null) {
85             return;
86         }
87         mSensorStats.put(key, stats);
88     }
89 
90     /**
91      * Get the keys from the values table. Will not get the keys from the nested
92      * {@link SensorStats}.
93      */
getKeys()94     public synchronized Set<String> getKeys() {
95         return mValues.keySet();
96     }
97 
98     /**
99      * Get a value from the values table. Will not attempt to get values from nested
100      * {@link SensorStats}.
101      */
getValue(String key)102     public synchronized Object getValue(String key) {
103         return mValues.get(key);
104     }
105 
106     /**
107      * Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
108      * {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
109      * {@code "key1"} containing the key value pair {@code \("key2", "value"\)}, the flattened map
110      * will contain the entry {@code \("key1__key2", "value"\)}.
111      *
112      * @return a {@link Map} containing all stats from the value and sub {@link SensorStats}.
113      */
flatten()114     public synchronized Map<String, Object> flatten() {
115         final Map<String, Object> flattenedMap = new HashMap<>(mValues);
116         for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
117             for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
118                 String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
119                 flattenedMap.put(key, valueEntry.getValue());
120             }
121         }
122         return flattenedMap;
123     }
124 
125     /**
126      * Utility method to log the stats to the logcat.
127      */
log(String tag)128     public void log(String tag) {
129         final Map<String, Object> flattened = flatten();
130         for (String key : getSortedKeys(flattened)) {
131             Object value = flattened.get(key);
132             Log.v(tag, String.format("%s: %s", key, getValueString(value)));
133         }
134     }
135 
136     /**
137      * Utility method to log the stats to a file. Will overwrite the file if it already exists.
138      */
logToFile(String fileName)139     public void logToFile(String fileName) throws IOException {
140         File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
141         File logFile = new File(statsDirectory, fileName);
142         final Map<String, Object> flattened = flatten();
143         FileWriter fileWriter = new FileWriter(logFile, false /* append */);
144         try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
145             for (String key : getSortedKeys(flattened)) {
146                 Object value = flattened.get(key);
147                 writer.write(String.format("%s: %s\n", key, getValueString(value)));
148             }
149         }
150     }
151 
152     /**
153      * Provides a sanitized sensor name, that can be used in file names.
154      * See {@link #logToFile(String)}.
155      */
getSanitizedSensorName(Sensor sensor)156     public static String getSanitizedSensorName(Sensor sensor) throws SensorTestPlatformException {
157         return SensorCtsHelper.sanitizeStringForFileName(sensor.getStringType());
158     }
159 
getSortedKeys(Map<String, Object> flattenedStats)160     private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
161         List<String> keys = new ArrayList<>(flattenedStats.keySet());
162         Collections.sort(keys);
163         return keys;
164     }
165 
getValueString(Object value)166     private static String getValueString(Object value) {
167         if (value == null) {
168             return "";
169         } else if (value instanceof boolean[]) {
170             return Arrays.toString((boolean[]) value);
171         } else if (value instanceof byte[]) {
172             return Arrays.toString((byte[]) value);
173         } else if (value instanceof char[]) {
174             return Arrays.toString((char[]) value);
175         } else if (value instanceof double[]) {
176             return Arrays.toString((double[]) value);
177         } else if (value instanceof float[]) {
178             return Arrays.toString((float[]) value);
179         } else if (value instanceof int[]) {
180             return Arrays.toString((int[]) value);
181         } else if (value instanceof long[]) {
182             return Arrays.toString((long[]) value);
183         } else if (value instanceof short[]) {
184             return Arrays.toString((short[]) value);
185         } else if (value instanceof Object[]) {
186             return Arrays.toString((Object[]) value);
187         } else {
188             return value.toString();
189         }
190     }
191 }
192