• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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