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