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