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