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 EVENT_TIME_WRONG_CLOCKSOURCE_COUNT_KEY =
54             "event_time_wrong_clocksource_count";
55     public static final String EVENT_TIME_WRONG_CLOCKSOURCE_POSITIONS_KEY =
56             "event_time_wrong_clocksource_positions";
57     public static final String EVENT_COUNT_KEY = "event_count";
58     public static final String EVENT_COUNT_EXPECTED_KEY = "event_count_expected";
59     public static final String EVENT_LOG_FILENAME = "event_log_filename";
60     public static final String WRONG_SENSOR_KEY = "wrong_sensor_observed";
61     public static final String FREQUENCY_KEY = "frequency";
62     public static final String JITTER_95_PERCENTILE_PERCENT_KEY = "jitter_95_percentile_percent";
63     public static final String MEAN_KEY = "mean";
64     public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
65     public static final String MAGNITUDE_KEY = "magnitude";
66     public static final String DELAYED_BATCH_DELIVERY = "delayed_batch_delivery";
67 
68     private final Map<String, Object> mValues = new HashMap<>();
69     private final Map<String, SensorStats> mSensorStats = new HashMap<>();
70 
71     /**
72      * Add a value.
73      *
74      * @param key the key.
75      * @param value the value as an {@link Object}.
76      */
addValue(String key, Object value)77     public synchronized void addValue(String key, Object value) {
78         if (value == null) {
79             return;
80         }
81         mValues.put(key, value);
82     }
83 
84     /**
85      * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
86      * {@link SensorOperation} tree.
87      *
88      * @param key the key
89      * @param stats the sub {@link SensorStats} object.
90      */
addSensorStats(String key, SensorStats stats)91     public synchronized void addSensorStats(String key, SensorStats stats) {
92         if (stats == null) {
93             return;
94         }
95         mSensorStats.put(key, stats);
96     }
97 
98     /**
99      * Get the keys from the values table. Will not get the keys from the nested
100      * {@link SensorStats}.
101      */
getKeys()102     public synchronized Set<String> getKeys() {
103         return mValues.keySet();
104     }
105 
106     /**
107      * Get a value from the values table. Will not attempt to get values from nested
108      * {@link SensorStats}.
109      */
getValue(String key)110     public synchronized Object getValue(String key) {
111         return mValues.get(key);
112     }
113 
114     /**
115      * Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
116      * {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
117      * {@code "key1"} containing the key value pair {@code \("key2", "value"\)}, the flattened map
118      * will contain the entry {@code \("key1__key2", "value"\)}.
119      *
120      * @return a {@link Map} containing all stats from the value and sub {@link SensorStats}.
121      */
flatten()122     public synchronized Map<String, Object> flatten() {
123         final Map<String, Object> flattenedMap = new HashMap<>(mValues);
124         for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
125             for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
126                 String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
127                 flattenedMap.put(key, valueEntry.getValue());
128             }
129         }
130         return flattenedMap;
131     }
132 
133     /**
134      * Utility method to log the stats to the logcat.
135      */
log(String tag)136     public void log(String tag) {
137         final Map<String, Object> flattened = flatten();
138         for (String key : getSortedKeys(flattened)) {
139             Object value = flattened.get(key);
140             Log.v(tag, String.format("%s: %s", key, getValueString(value)));
141         }
142     }
143 
144     /**
145      * Utility method to log the stats to a file. Will overwrite the file if it already exists.
146      */
logToFile(String fileName)147     public void logToFile(String fileName) throws IOException {
148         File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
149         File logFile = new File(statsDirectory, fileName);
150         final Map<String, Object> flattened = flatten();
151         FileWriter fileWriter = new FileWriter(logFile, false /* append */);
152         try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
153             for (String key : getSortedKeys(flattened)) {
154                 Object value = flattened.get(key);
155                 writer.write(String.format("%s: %s\n", key, getValueString(value)));
156             }
157         }
158     }
159 
160     /**
161      * Provides a sanitized sensor name, that can be used in file names.
162      * See {@link #logToFile(String)}.
163      */
getSanitizedSensorName(Sensor sensor)164     public static String getSanitizedSensorName(Sensor sensor) throws SensorTestPlatformException {
165         return SensorCtsHelper.sanitizeStringForFileName(sensor.getStringType());
166     }
167 
getSortedKeys(Map<String, Object> flattenedStats)168     private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
169         List<String> keys = new ArrayList<>(flattenedStats.keySet());
170         Collections.sort(keys);
171         return keys;
172     }
173 
getValueString(Object value)174     private static String getValueString(Object value) {
175         if (value == null) {
176             return "";
177         } else if (value instanceof boolean[]) {
178             return Arrays.toString((boolean[]) value);
179         } else if (value instanceof byte[]) {
180             return Arrays.toString((byte[]) value);
181         } else if (value instanceof char[]) {
182             return Arrays.toString((char[]) value);
183         } else if (value instanceof double[]) {
184             return Arrays.toString((double[]) value);
185         } else if (value instanceof float[]) {
186             return Arrays.toString((float[]) value);
187         } else if (value instanceof int[]) {
188             return Arrays.toString((int[]) value);
189         } else if (value instanceof long[]) {
190             return Arrays.toString((long[]) value);
191         } else if (value instanceof short[]) {
192             return Arrays.toString((short[]) value);
193         } else if (value instanceof Object[]) {
194             return Arrays.toString((Object[]) value);
195         } else {
196             return value.toString();
197         }
198     }
199 }
200