WifiAwareMetrics.java revision a83967f56939392072253162731bfe090c2e7709
1/*
2 * Copyright (C) 2017 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
17package com.android.server.wifi.aware;
18
19import android.hardware.wifi.V1_0.NanStatusType;
20import android.util.Log;
21import android.util.SparseArray;
22import android.util.SparseIntArray;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.server.wifi.Clock;
26import com.android.server.wifi.nano.WifiMetricsProto;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.HashMap;
31import java.util.Map;
32
33/**
34 * Wi-Fi Aware metric container/processor.
35 */
36public class WifiAwareMetrics {
37    private static final String TAG = "WifiAwareMetrics";
38    private static final boolean DBG = false;
39
40    // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
41    // Buckets:
42    //    1 -> 10: 9 @ 1
43    //    10 -> 100: 9 @ 10
44    //    100 -> 1000: 9 @ 10^2
45    //    10^3 -> 10^4: 9 @ 10^3
46    //    10^4 -> 10^5: 9 @ 10^4
47    //    10^5 -> 10^6: 9 @ 10^5
48    //    10^6 -> 10^7: 9 @ 10^6
49    //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
50    private static final HistParms DURATION_LOG_HISTOGRAM = new HistParms(0, 1, 10, 9, 8);
51
52    private final Object mLock = new Object();
53    private final Clock mClock;
54
55    // enableUsage/disableUsage data
56    private long mLastEnableUsage = 0;
57    private long mLastEnableUsageInThisLogWindow = 0;
58    private long mAvailableTime = 0;
59    private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();
60
61    // app data (attach)
62    private static class AttachData {
63        boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
64        int mMaxConcurrentAttaches;
65    }
66    private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
67    private SparseIntArray mAttachStatusData = new SparseIntArray();
68    private SparseIntArray mHistogramAttachDuration = new SparseIntArray();
69
70    public WifiAwareMetrics(Clock clock) {
71        mClock = clock;
72    }
73
74    /**
75     * Push usage stats for WifiAwareStateMachine.enableUsage() to
76     * histogram_aware_available_duration_ms.
77     */
78    public void recordEnableUsage() {
79        synchronized (mLock) {
80            if (mLastEnableUsage != 0) {
81                Log.w(TAG, "enableUsage: mLastEnableUsage* initialized!?");
82            }
83            mLastEnableUsage = mClock.getElapsedSinceBootMillis();
84            mLastEnableUsageInThisLogWindow = mLastEnableUsage;
85        }
86    }
87
88    /**
89     * Push usage stats for WifiAwareStateMachine.disableUsage() to
90     * histogram_aware_available_duration_ms.
91     */
92
93    public void recordDisableUsage() {
94        synchronized (mLock) {
95            if (mLastEnableUsage == 0) {
96                Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
97                return;
98            }
99
100            long now = mClock.getElapsedSinceBootMillis();
101            addLogValueToHistogram(now - mLastEnableUsage, mHistogramAwareAvailableDurationMs,
102                    DURATION_LOG_HISTOGRAM);
103            mAvailableTime += now - mLastEnableUsageInThisLogWindow;
104            mLastEnableUsage = 0;
105            mLastEnableUsageInThisLogWindow = 0;
106        }
107    }
108
109    /**
110     * Push information about a new attach session.
111     */
112    public void recordAttachSession(int uid, boolean usesIdentityCallback,
113            SparseArray<WifiAwareClientState> clients) {
114        // count the number of clients with the specific uid
115        int currentConcurrentCount = 0;
116        for (int i = 0; i < clients.size(); ++i) {
117            if (clients.valueAt(i).getUid() == uid) {
118                ++currentConcurrentCount;
119            }
120        }
121
122        synchronized (mLock) {
123            AttachData data = mAttachDataByUid.get(uid);
124            if (data == null) {
125                data = new AttachData();
126                mAttachDataByUid.put(uid, data);
127            }
128            data.mUsesIdentityCallback |= usesIdentityCallback;
129            data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
130                    currentConcurrentCount);
131            recordAttachStatus(NanStatusType.SUCCESS);
132        }
133    }
134
135    /**
136     * Push information about a new attach session status (recorded when attach session is created).
137     */
138    public void recordAttachStatus(int status) {
139        synchronized (mLock) {
140            mAttachStatusData.put(status, mAttachStatusData.get(status) + 1);
141        }
142    }
143
144    /**
145     * Push duration information of an attach session.
146     */
147    public void recordAttachSessionDuration(long creationTime) {
148        synchronized (mLock) {
149            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
150                    mHistogramAttachDuration,
151                    DURATION_LOG_HISTOGRAM);
152        }
153    }
154
155    /**
156     * Consolidate all metrics into the proto.
157     */
158    public WifiMetricsProto.WifiAwareLog consolidateProto() {
159        WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
160        long now = mClock.getElapsedSinceBootMillis();
161        synchronized (mLock) {
162            log.histogramAwareAvailableDurationMs = histogramToProtoArray(
163                    mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
164            log.availableTimeMs = mAvailableTime;
165            if (mLastEnableUsageInThisLogWindow != 0) {
166                log.availableTimeMs += now - mLastEnableUsageInThisLogWindow;
167            }
168            log.numApps = mAttachDataByUid.size();
169            log.numAppsUsingIdentityCallback = 0;
170            log.maxConcurrentAttachSessionsInApp = 0;
171            for (AttachData ad: mAttachDataByUid.values()) {
172                if (ad.mUsesIdentityCallback) {
173                    ++log.numAppsUsingIdentityCallback;
174                }
175                log.maxConcurrentAttachSessionsInApp = Math.max(
176                        log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
177            }
178            log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
179            log.histogramAttachDurationMs = histogramToProtoArray(mHistogramAttachDuration,
180                    DURATION_LOG_HISTOGRAM);
181        }
182        return log;
183    }
184
185    /**
186     * clear Wi-Fi Aware metrics
187     */
188    public void clear() {
189        long now = mClock.getElapsedSinceBootMillis();
190        synchronized (mLock) {
191            // don't clear mLastEnableUsage since could be valid for next measurement period
192            mHistogramAwareAvailableDurationMs.clear();
193            mAvailableTime = 0;
194            if (mLastEnableUsageInThisLogWindow != 0) {
195                mLastEnableUsageInThisLogWindow = now;
196            }
197            mAttachDataByUid.clear();
198            mAttachStatusData.clear();
199            mHistogramAttachDuration.clear();
200        }
201    }
202
203    /**
204     * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
205     * serialized metrics (handled by parent WifiMetrics).
206     *
207     * @param fd   unused
208     * @param pw   PrintWriter for writing dump to
209     * @param args unused
210     */
211    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
212        synchronized (mLock) {
213            pw.println("mLastEnableUsage:" + mLastEnableUsage);
214            pw.println("mLastEnableUsageInThisLogWindow:" + mLastEnableUsageInThisLogWindow);
215            pw.println("mAvailableTime:" + mAvailableTime);
216            pw.println("mHistogramAwareAvailableDurationMs:");
217            for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
218                pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
219                        + mHistogramAwareAvailableDurationMs.valueAt(i));
220            }
221            pw.println("mAttachDataByUid:");
222            for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
223                pw.println("  " + "uid=" + ade.getKey() + ": identity="
224                        + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
225                        + ade.getValue().mMaxConcurrentAttaches);
226            }
227            pw.println("mAttachStatusData:");
228            for (int i = 0; i < mAttachStatusData.size(); ++i) {
229                pw.println("  " + mAttachStatusData.keyAt(i) + ": "
230                        + mAttachStatusData.valueAt(i));
231            }
232            pw.println("mHistogramAttachDuration:");
233            for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
234                pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
235                        + mHistogramAttachDuration.valueAt(i));
236            }
237        }
238    }
239
240    // histogram utilities
241
242    /**
243     * Specifies a ~log histogram consisting of two levels of buckets - a set of N big buckets:
244     *
245     * Buckets starts at: B + P * M^i, where i=0, ... , N-1 (N big buckets)
246     * Each big bucket is divided into S sub-buckets
247     *
248     * Each (big) bucket is M times bigger than the previous one.
249     *
250     * The buckets are then:
251     * #0: B + P * M^0 with S buckets each of width (P*M^1-P*M^0)/S
252     * #1: B + P * M^1 with S buckets each of width (P*M^2-P*M^1)/S
253     * ...
254     * #N-1: B + P * M^(N-1) with S buckets each of width (P*M^N-P*M^(N-1))/S
255     */
256    @VisibleForTesting
257    public static class HistParms {
258        public HistParms(int b, int p, int m, int s, int n) {
259            this.b = b;
260            this.p = p;
261            this.m = m;
262            this.s = s;
263            this.n = n;
264
265            // derived values
266            mLog = Math.log(m);
267            bb = new double[n];
268            sbw = new double[n];
269            bb[0] = b + p;
270            sbw[0] = p * (m - 1.0) / (double) s;
271            for (int i = 1; i < n; ++i) {
272                bb[i] = m * (bb[i - 1] - b) + b;
273                sbw[i] = m * sbw[i - 1];
274            }
275        }
276
277        // spec
278        public int b;
279        public int p;
280        public int m;
281        public int s;
282        public int n;
283
284        // derived
285        public double mLog;
286        public double[] bb; // bucket base
287        public double[] sbw; // sub-bucket width
288    }
289
290    /**
291     * Adds the input value to the histogram based on the histogram parameters.
292     */
293    @VisibleForTesting
294    public static int addLogValueToHistogram(long x, SparseIntArray histogram, HistParms hp) {
295        double logArg = (double) (x - hp.b) / (double) hp.p;
296        int bigBucketIndex = -1;
297        if (logArg > 0) {
298            bigBucketIndex = (int) (Math.log(logArg) / hp.mLog);
299        }
300        int subBucketIndex;
301        if (bigBucketIndex < 0) {
302            bigBucketIndex = 0;
303            subBucketIndex = 0;
304        } else if (bigBucketIndex >= hp.n) {
305            bigBucketIndex = hp.n - 1;
306            subBucketIndex = hp.s - 1;
307        } else {
308            subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
309            if (subBucketIndex >= hp.s) { // probably a rounding error so move to next big bucket
310                bigBucketIndex++;
311                if (bigBucketIndex >= hp.n) {
312                    bigBucketIndex = hp.n - 1;
313                    subBucketIndex = hp.s - 1;
314                } else {
315                    subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
316                }
317            }
318        }
319        int key = bigBucketIndex * hp.s + subBucketIndex;
320
321        // note that get() returns 0 if index not there already
322        int newValue = histogram.get(key) + 1;
323        histogram.put(key, newValue);
324
325        return newValue;
326    }
327
328    /**
329     * Converts the histogram (with the specified histogram parameters) to an array of proto
330     * histogram buckets.
331     */
332    @VisibleForTesting
333    public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
334            SparseIntArray histogram, HistParms hp) {
335        WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
336                new WifiMetricsProto.WifiAwareLog.HistogramBucket[histogram.size()];
337        for (int i = 0; i < histogram.size(); ++i) {
338            int key = histogram.keyAt(i);
339
340            protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
341            protoArray[i].start = (long) (hp.bb[key / hp.s] + hp.sbw[key / hp.s] * (key % hp.s));
342            protoArray[i].end = (long) (protoArray[i].start + hp.sbw[key / hp.s]);
343            protoArray[i].count = histogram.valueAt(i);
344        }
345
346        return protoArray;
347    }
348
349    /**
350     * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
351     */
352    public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
353        int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
354        int newValue = histogram.get(protoStatus) + 1;
355        histogram.put(protoStatus, newValue);
356    }
357
358    /**
359     * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
360     */
361    @VisibleForTesting
362    public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
363            SparseIntArray histogram) {
364        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
365                new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];
366
367        for (int i = 0; i < histogram.size(); ++i) {
368            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
369            protoArray[i].nanStatusType = histogram.keyAt(i);
370            protoArray[i].count = histogram.valueAt(i);
371        }
372
373        return protoArray;
374    }
375
376    /**
377     * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
378     */
379    public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
380        switch (nanStatusType) {
381            case NanStatusType.SUCCESS:
382                return WifiMetricsProto.WifiAwareLog.SUCCESS;
383            case NanStatusType.INTERNAL_FAILURE:
384                return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
385            case NanStatusType.PROTOCOL_FAILURE:
386                return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
387            case NanStatusType.INVALID_SESSION_ID:
388                return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
389            case NanStatusType.NO_RESOURCES_AVAILABLE:
390                return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
391            case NanStatusType.INVALID_ARGS:
392                return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
393            case NanStatusType.INVALID_PEER_ID:
394                return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
395            case NanStatusType.INVALID_NDP_ID:
396                return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
397            case NanStatusType.NAN_NOT_ALLOWED:
398                return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
399            case NanStatusType.NO_OTA_ACK:
400                return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
401            case NanStatusType.ALREADY_ENABLED:
402                return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
403            case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
404                return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
405            case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
406                return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
407            default:
408                Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
409                return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
410        }
411    }
412}
413