1 /* 2 * Copyright (C) 2016 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 com.android.internal.location.gnssmetrics; 18 19 import android.location.GnssStatus; 20 import android.os.SystemClock; 21 import android.os.SystemProperties; 22 import android.os.connectivity.GpsBatteryStats; 23 import android.server.location.ServerLocationProtoEnums; 24 import android.text.format.DateUtils; 25 import android.util.Base64; 26 import android.util.Log; 27 import android.util.StatsLog; 28 import android.util.TimeUtils; 29 30 import com.android.internal.app.IBatteryStats; 31 import com.android.internal.location.nano.GnssLogsProto.GnssLog; 32 import com.android.internal.location.nano.GnssLogsProto.PowerMetrics; 33 34 import java.util.Arrays; 35 36 /** 37 * GnssMetrics: Is used for logging GNSS metrics 38 * 39 * @hide 40 */ 41 public class GnssMetrics { 42 43 private static final String TAG = GnssMetrics.class.getSimpleName(); 44 45 /* Constant which indicates GPS signal quality is as yet unknown */ 46 public static final int GPS_SIGNAL_QUALITY_UNKNOWN = 47 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_UNKNOWN; // -1 48 49 /* Constant which indicates GPS signal quality is poor */ 50 public static final int GPS_SIGNAL_QUALITY_POOR = 51 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_POOR; // 0 52 53 /* Constant which indicates GPS signal quality is good */ 54 public static final int GPS_SIGNAL_QUALITY_GOOD = 55 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_GOOD; // 1 56 57 /* Number of GPS signal quality levels */ 58 public static final int NUM_GPS_SIGNAL_QUALITY_LEVELS = GPS_SIGNAL_QUALITY_GOOD + 1; 59 60 /** Default time between location fixes (in millisecs) */ 61 private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000; 62 63 /* The time since boot when logging started */ 64 private String logStartInElapsedRealTime; 65 66 /* GNSS power metrics */ 67 private GnssPowerMetrics mGnssPowerMetrics; 68 69 /** 70 * A boolean array indicating whether the constellation types have been used in fix. 71 */ 72 private boolean[] mConstellationTypes; 73 74 /** Constructor */ GnssMetrics(IBatteryStats stats)75 public GnssMetrics(IBatteryStats stats) { 76 mGnssPowerMetrics = new GnssPowerMetrics(stats); 77 locationFailureStatistics = new Statistics(); 78 timeToFirstFixSecStatistics = new Statistics(); 79 positionAccuracyMeterStatistics = new Statistics(); 80 topFourAverageCn0Statistics = new Statistics(); 81 reset(); 82 } 83 84 /** 85 * Logs the status of a location report received from the HAL 86 * 87 * @param isSuccessful 88 */ logReceivedLocationStatus(boolean isSuccessful)89 public void logReceivedLocationStatus(boolean isSuccessful) { 90 if (!isSuccessful) { 91 locationFailureStatistics.addItem(1.0); 92 return; 93 } 94 locationFailureStatistics.addItem(0.0); 95 return; 96 } 97 98 /** 99 * Logs missed reports 100 * 101 * @param desiredTimeBetweenFixesMilliSeconds 102 * @param actualTimeBetweenFixesMilliSeconds 103 */ logMissedReports(int desiredTimeBetweenFixesMilliSeconds, int actualTimeBetweenFixesMilliSeconds)104 public void logMissedReports(int desiredTimeBetweenFixesMilliSeconds, 105 int actualTimeBetweenFixesMilliSeconds) { 106 int numReportMissed = (actualTimeBetweenFixesMilliSeconds / 107 Math.max(DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1; 108 if (numReportMissed > 0) { 109 for (int i = 0; i < numReportMissed; i++) { 110 locationFailureStatistics.addItem(1.0); 111 } 112 } 113 return; 114 } 115 116 /** 117 * Logs time to first fix 118 * 119 * @param timeToFirstFixMilliSeconds 120 */ logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds)121 public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) { 122 timeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds/1000)); 123 return; 124 } 125 126 /** 127 * Logs position accuracy 128 * 129 * @param positionAccuracyMeters 130 */ logPositionAccuracyMeters(float positionAccuracyMeters)131 public void logPositionAccuracyMeters(float positionAccuracyMeters) { 132 positionAccuracyMeterStatistics.addItem((double) positionAccuracyMeters); 133 return; 134 } 135 136 /* 137 * Logs CN0 when at least 4 SVs are available 138 * 139 */ logCn0(float[] cn0s, int numSv)140 public void logCn0(float[] cn0s, int numSv) { 141 if (numSv == 0 || cn0s == null || cn0s.length == 0 || cn0s.length < numSv) { 142 if (numSv == 0) { 143 mGnssPowerMetrics.reportSignalQuality(null, 0); 144 } 145 return; 146 } 147 float[] cn0Array = Arrays.copyOf(cn0s, numSv); 148 Arrays.sort(cn0Array); 149 mGnssPowerMetrics.reportSignalQuality(cn0Array, numSv); 150 if (numSv < 4) { 151 return; 152 } 153 if (cn0Array[numSv - 4] > 0.0) { 154 double top4AvgCn0 = 0.0; 155 for (int i = numSv - 4; i < numSv; i++) { 156 top4AvgCn0 += (double) cn0Array[i]; 157 } 158 top4AvgCn0 /= 4; 159 topFourAverageCn0Statistics.addItem(top4AvgCn0); 160 } 161 return; 162 } 163 164 165 /** 166 * Logs that a constellation type has been observed. 167 */ logConstellationType(int constellationType)168 public void logConstellationType(int constellationType) { 169 if (constellationType >= mConstellationTypes.length) { 170 Log.e(TAG, "Constellation type " + constellationType + " is not valid."); 171 return; 172 } 173 mConstellationTypes[constellationType] = true; 174 } 175 176 /** 177 * Dumps GNSS metrics as a proto string 178 * @return 179 */ dumpGnssMetricsAsProtoString()180 public String dumpGnssMetricsAsProtoString() { 181 GnssLog msg = new GnssLog(); 182 if (locationFailureStatistics.getCount() > 0) { 183 msg.numLocationReportProcessed = locationFailureStatistics.getCount(); 184 msg.percentageLocationFailure = (int) (100.0 * locationFailureStatistics.getMean()); 185 } 186 if (timeToFirstFixSecStatistics.getCount() > 0) { 187 msg.numTimeToFirstFixProcessed = timeToFirstFixSecStatistics.getCount(); 188 msg.meanTimeToFirstFixSecs = (int) timeToFirstFixSecStatistics.getMean(); 189 msg.standardDeviationTimeToFirstFixSecs 190 = (int) timeToFirstFixSecStatistics.getStandardDeviation(); 191 } 192 if (positionAccuracyMeterStatistics.getCount() > 0) { 193 msg.numPositionAccuracyProcessed = positionAccuracyMeterStatistics.getCount(); 194 msg.meanPositionAccuracyMeters = (int) positionAccuracyMeterStatistics.getMean(); 195 msg.standardDeviationPositionAccuracyMeters 196 = (int) positionAccuracyMeterStatistics.getStandardDeviation(); 197 } 198 if (topFourAverageCn0Statistics.getCount() > 0) { 199 msg.numTopFourAverageCn0Processed = topFourAverageCn0Statistics.getCount(); 200 msg.meanTopFourAverageCn0DbHz = topFourAverageCn0Statistics.getMean(); 201 msg.standardDeviationTopFourAverageCn0DbHz 202 = topFourAverageCn0Statistics.getStandardDeviation(); 203 } 204 msg.powerMetrics = mGnssPowerMetrics.buildProto(); 205 msg.hardwareRevision = SystemProperties.get("ro.boot.revision", ""); 206 String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT); 207 reset(); 208 return s; 209 } 210 211 /** 212 * Dumps GNSS Metrics as text 213 * 214 * @return GNSS Metrics 215 */ dumpGnssMetricsAsText()216 public String dumpGnssMetricsAsText() { 217 StringBuilder s = new StringBuilder(); 218 s.append("GNSS_KPI_START").append('\n'); 219 s.append(" KPI logging start time: ").append(logStartInElapsedRealTime).append("\n"); 220 s.append(" KPI logging end time: "); 221 TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s); 222 s.append("\n"); 223 s.append(" Number of location reports: ").append( 224 locationFailureStatistics.getCount()).append("\n"); 225 if (locationFailureStatistics.getCount() > 0) { 226 s.append(" Percentage location failure: ").append( 227 100.0 * locationFailureStatistics.getMean()).append("\n"); 228 } 229 s.append(" Number of TTFF reports: ").append( 230 timeToFirstFixSecStatistics.getCount()).append("\n"); 231 if (timeToFirstFixSecStatistics.getCount() > 0) { 232 s.append(" TTFF mean (sec): ").append(timeToFirstFixSecStatistics.getMean()).append("\n"); 233 s.append(" TTFF standard deviation (sec): ").append( 234 timeToFirstFixSecStatistics.getStandardDeviation()).append("\n"); 235 } 236 s.append(" Number of position accuracy reports: ").append( 237 positionAccuracyMeterStatistics.getCount()).append("\n"); 238 if (positionAccuracyMeterStatistics.getCount() > 0) { 239 s.append(" Position accuracy mean (m): ").append( 240 positionAccuracyMeterStatistics.getMean()).append("\n"); 241 s.append(" Position accuracy standard deviation (m): ").append( 242 positionAccuracyMeterStatistics.getStandardDeviation()).append("\n"); 243 } 244 s.append(" Number of CN0 reports: ").append( 245 topFourAverageCn0Statistics.getCount()).append("\n"); 246 if (topFourAverageCn0Statistics.getCount() > 0) { 247 s.append(" Top 4 Avg CN0 mean (dB-Hz): ").append( 248 topFourAverageCn0Statistics.getMean()).append("\n"); 249 s.append(" Top 4 Avg CN0 standard deviation (dB-Hz): ").append( 250 topFourAverageCn0Statistics.getStandardDeviation()).append("\n"); 251 } 252 s.append(" Used-in-fix constellation types: "); 253 for (int i = 0; i < mConstellationTypes.length; i++) { 254 if (mConstellationTypes[i]) { 255 s.append(GnssStatus.constellationTypeToString(i)).append(" "); 256 } 257 } 258 s.append("\n"); 259 s.append("GNSS_KPI_END").append("\n"); 260 GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); 261 if (stats != null) { 262 s.append("Power Metrics").append("\n"); 263 s.append(" Time on battery (min): " 264 + stats.getLoggingDurationMs() / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); 265 long[] t = stats.getTimeInGpsSignalQualityLevel(); 266 if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { 267 s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " + 268 Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + 269 " dB-Hz (min): ").append(t[1] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); 270 s.append(" Amount of time (while on battery) Top 4 Avg CN0 <= " + 271 Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + 272 " dB-Hz (min): ").append(t[0] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); 273 } 274 s.append(" Energy consumed while on battery (mAh): ").append( 275 stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS)).append("\n"); 276 } 277 s.append("Hardware Version: " + SystemProperties.get("ro.boot.revision", "")).append("\n"); 278 return s.toString(); 279 } 280 281 /** Class for storing statistics */ 282 private class Statistics { 283 284 /** Resets statistics */ reset()285 public void reset() { 286 count = 0; 287 sum = 0.0; 288 sumSquare = 0.0; 289 } 290 291 /** Adds an item */ addItem(double item)292 public void addItem(double item) { 293 count++; 294 sum += item; 295 sumSquare += item * item; 296 } 297 298 /** Returns number of items added */ getCount()299 public int getCount() { 300 return count; 301 } 302 303 /** Returns mean */ getMean()304 public double getMean() { 305 return sum/count; 306 } 307 308 /** Returns standard deviation */ getStandardDeviation()309 public double getStandardDeviation() { 310 double m = sum/count; 311 m = m * m; 312 double v = sumSquare/count; 313 if (v > m) { 314 return Math.sqrt(v - m); 315 } 316 return 0; 317 } 318 319 private int count; 320 private double sum; 321 private double sumSquare; 322 } 323 324 /** Location failure statistics */ 325 private Statistics locationFailureStatistics; 326 327 /** Time to first fix statistics */ 328 private Statistics timeToFirstFixSecStatistics; 329 330 /** Position accuracy statistics */ 331 private Statistics positionAccuracyMeterStatistics; 332 333 /** Top 4 average CN0 statistics */ 334 private Statistics topFourAverageCn0Statistics; 335 336 /** 337 * Resets GNSS metrics 338 */ reset()339 private void reset() { 340 StringBuilder s = new StringBuilder(); 341 TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s); 342 logStartInElapsedRealTime = s.toString(); 343 locationFailureStatistics.reset(); 344 timeToFirstFixSecStatistics.reset(); 345 positionAccuracyMeterStatistics.reset(); 346 topFourAverageCn0Statistics.reset(); 347 resetConstellationTypes(); 348 return; 349 } 350 351 /** Resets {@link #mConstellationTypes} as an all-false boolean array. */ resetConstellationTypes()352 public void resetConstellationTypes() { 353 mConstellationTypes = new boolean[GnssStatus.CONSTELLATION_COUNT]; 354 } 355 356 /* Class for handling GNSS power related metrics */ 357 private class GnssPowerMetrics { 358 359 /* Threshold for Top Four Average CN0 below which GNSS signal quality is declared poor */ 360 public static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; 361 362 /* Minimum change in Top Four Average CN0 needed to trigger a report */ 363 private static final double REPORTING_THRESHOLD_DB_HZ = 1.0; 364 365 /* BatteryStats API */ 366 private final IBatteryStats mBatteryStats; 367 368 /* Last reported Top Four Average CN0 */ 369 private double mLastAverageCn0; 370 371 /* Last reported signal quality bin (based on Top Four Average CN0) */ 372 private int mLastSignalLevel; 373 GnssPowerMetrics(IBatteryStats stats)374 public GnssPowerMetrics(IBatteryStats stats) { 375 mBatteryStats = stats; 376 // Used to initialize the variable to a very small value (unachievable in practice) so that 377 // the first CNO report will trigger an update to BatteryStats 378 mLastAverageCn0 = -100.0; 379 mLastSignalLevel = GPS_SIGNAL_QUALITY_UNKNOWN; 380 } 381 382 /** 383 * Builds power metrics proto buf. This is included in the gnss proto buf. 384 * @return PowerMetrics 385 */ buildProto()386 public PowerMetrics buildProto() { 387 PowerMetrics p = new PowerMetrics(); 388 GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); 389 if (stats != null) { 390 p.loggingDurationMs = stats.getLoggingDurationMs(); 391 p.energyConsumedMah = stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS); 392 long[] t = stats.getTimeInGpsSignalQualityLevel(); 393 p.timeInSignalQualityLevelMs = new long[t.length]; 394 for (int i = 0; i < t.length; i++) { 395 p.timeInSignalQualityLevelMs[i] = t[i]; 396 } 397 } 398 return p; 399 } 400 401 /** 402 * Returns the GPS power stats 403 * @return GpsBatteryStats 404 */ getGpsBatteryStats()405 public GpsBatteryStats getGpsBatteryStats() { 406 try { 407 return mBatteryStats.getGpsBatteryStats(); 408 } catch (Exception e) { 409 Log.w(TAG, "Exception", e); 410 return null; 411 } 412 } 413 414 /** 415 * Reports signal quality to BatteryStats. Signal quality is based on Top four average CN0. If 416 * the number of SVs seen is less than 4, then signal quality is the average CN0. 417 * Changes are reported only if the average CN0 changes by more than REPORTING_THRESHOLD_DB_HZ. 418 */ reportSignalQuality(float[] ascendingCN0Array, int numSv)419 public void reportSignalQuality(float[] ascendingCN0Array, int numSv) { 420 double avgCn0 = 0.0; 421 if (numSv > 0) { 422 for (int i = Math.max(0, numSv - 4); i < numSv; i++) { 423 avgCn0 += (double) ascendingCN0Array[i]; 424 } 425 avgCn0 /= Math.min(numSv, 4); 426 } 427 if (Math.abs(avgCn0 - mLastAverageCn0) < REPORTING_THRESHOLD_DB_HZ) { 428 return; 429 } 430 int signalLevel = getSignalLevel(avgCn0); 431 if (signalLevel != mLastSignalLevel) { 432 StatsLog.write(StatsLog.GPS_SIGNAL_QUALITY_CHANGED, signalLevel); 433 mLastSignalLevel = signalLevel; 434 } 435 try { 436 mBatteryStats.noteGpsSignalQuality(signalLevel); 437 mLastAverageCn0 = avgCn0; 438 } catch (Exception e) { 439 Log.w(TAG, "Exception", e); 440 } 441 return; 442 } 443 444 /** 445 * Obtains signal level based on CN0 446 */ getSignalLevel(double cn0)447 private int getSignalLevel(double cn0) { 448 if (cn0 > POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) { 449 return GnssMetrics.GPS_SIGNAL_QUALITY_GOOD; 450 } 451 return GnssMetrics.GPS_SIGNAL_QUALITY_POOR; 452 } 453 } 454 } 455