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