WifiAwareMetrics.java revision dca9963c7292e17318f5ba0dee6c3683c7c9f941
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.net.wifi.aware.WifiAwareNetworkSpecifier;
21import android.text.TextUtils;
22import android.util.Log;
23import android.util.SparseArray;
24import android.util.SparseIntArray;
25
26import com.android.internal.annotations.VisibleForTesting;
27import com.android.server.wifi.Clock;
28import com.android.server.wifi.nano.WifiMetricsProto;
29
30import java.io.FileDescriptor;
31import java.io.PrintWriter;
32import java.util.Collections;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.Map;
36import java.util.Set;
37
38/**
39 * Wi-Fi Aware metric container/processor.
40 */
41public class WifiAwareMetrics {
42    private static final String TAG = "WifiAwareMetrics";
43    private static final boolean DBG = false;
44
45    // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
46    // Buckets:
47    //    1 -> 10: 9 @ 1
48    //    10 -> 100: 9 @ 10
49    //    100 -> 1000: 9 @ 10^2
50    //    10^3 -> 10^4: 9 @ 10^3
51    //    10^4 -> 10^5: 9 @ 10^4
52    //    10^5 -> 10^6: 9 @ 10^5
53    //    10^6 -> 10^7: 9 @ 10^6
54    //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
55    private static final HistParms DURATION_LOG_HISTOGRAM = new HistParms(0, 1, 10, 9, 8);
56
57    private final Object mLock = new Object();
58    private final Clock mClock;
59
60    // enableUsage/disableUsage data
61    private long mLastEnableUsageMs = 0;
62    private long mLastEnableUsageInThisSampleWindowMs = 0;
63    private long mAvailableTimeMs = 0;
64    private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();
65
66    // enabled data
67    private long mLastEnableAwareMs = 0;
68    private long mLastEnableAwareInThisSampleWindowMs = 0;
69    private long mEnabledTimeMs = 0;
70    private SparseIntArray mHistogramAwareEnabledDurationMs = new SparseIntArray();
71
72    // attach data
73    private static class AttachData {
74        boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
75        int mMaxConcurrentAttaches;
76    }
77    private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
78    private SparseIntArray mAttachStatusData = new SparseIntArray();
79    private SparseIntArray mHistogramAttachDuration = new SparseIntArray();
80
81    // discovery data
82    private int mMaxPublishInApp = 0;
83    private int mMaxSubscribeInApp = 0;
84    private int mMaxDiscoveryInApp = 0;
85    private int mMaxPublishInSystem = 0;
86    private int mMaxSubscribeInSystem = 0;
87    private int mMaxDiscoveryInSystem = 0;
88    private SparseIntArray mPublishStatusData = new SparseIntArray();
89    private SparseIntArray mSubscribeStatusData = new SparseIntArray();
90    private SparseIntArray mHistogramPublishDuration = new SparseIntArray();
91    private SparseIntArray mHistogramSubscribeDuration = new SparseIntArray();
92    private Set<Integer> mAppsWithDiscoverySessionResourceFailure = new HashSet<>();
93
94    // data-path (NDI/NDP) data
95    private int mMaxNdiInApp = 0;
96    private int mMaxNdpInApp = 0;
97    private int mMaxSecureNdpInApp = 0;
98    private int mMaxNdiInSystem = 0;
99    private int mMaxNdpInSystem = 0;
100    private int mMaxSecureNdpInSystem = 0;
101    private int mMaxNdpPerNdi = 0;
102    private SparseIntArray mInBandNdpStatusData = new SparseIntArray();
103    private SparseIntArray mOutOfBandNdpStatusData = new SparseIntArray();
104
105    private SparseIntArray mNdpCreationTimeDuration = new SparseIntArray();
106    private long mNdpCreationTimeMin = -1;
107    private long mNdpCreationTimeMax = 0;
108    private long mNdpCreationTimeSum = 0;
109    private long mNdpCreationTimeSumSq = 0;
110    private long mNdpCreationTimeNumSamples = 0;
111
112    public WifiAwareMetrics(Clock clock) {
113        mClock = clock;
114    }
115
116    /**
117     * Push usage stats for WifiAwareStateMachine.enableUsage() to
118     * histogram_aware_available_duration_ms.
119     */
120    public void recordEnableUsage() {
121        synchronized (mLock) {
122            if (mLastEnableUsageMs != 0) {
123                Log.w(TAG, "enableUsage: mLastEnableUsage*Ms initialized!?");
124            }
125            mLastEnableUsageMs = mClock.getElapsedSinceBootMillis();
126            mLastEnableUsageInThisSampleWindowMs = mLastEnableUsageMs;
127        }
128    }
129
130    /**
131     * Push usage stats for WifiAwareStateMachine.disableUsage() to
132     * histogram_aware_available_duration_ms.
133     */
134
135    public void recordDisableUsage() {
136        synchronized (mLock) {
137            if (mLastEnableUsageMs == 0) {
138                Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
139                return;
140            }
141
142            long now = mClock.getElapsedSinceBootMillis();
143            addLogValueToHistogram(now - mLastEnableUsageMs, mHistogramAwareAvailableDurationMs,
144                    DURATION_LOG_HISTOGRAM);
145            mAvailableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
146            mLastEnableUsageMs = 0;
147            mLastEnableUsageInThisSampleWindowMs = 0;
148        }
149    }
150
151    /**
152     * Push usage stats of Aware actually being enabled on-the-air: start
153     */
154    public void recordEnableAware() {
155        synchronized (mLock) {
156            if (mLastEnableAwareMs != 0) {
157                return; // already enabled
158            }
159            mLastEnableAwareMs = mClock.getElapsedSinceBootMillis();
160            mLastEnableAwareInThisSampleWindowMs = mLastEnableAwareMs;
161        }
162    }
163
164    /**
165     * Push usage stats of Aware actually being enabled on-the-air: stop (disable)
166     */
167    public void recordDisableAware() {
168        synchronized (mLock) {
169            if (mLastEnableAwareMs == 0) {
170                return; // already disabled
171            }
172
173            long now = mClock.getElapsedSinceBootMillis();
174            addLogValueToHistogram(now - mLastEnableAwareMs, mHistogramAwareEnabledDurationMs,
175                    DURATION_LOG_HISTOGRAM);
176            mEnabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
177            mLastEnableAwareMs = 0;
178            mLastEnableAwareInThisSampleWindowMs = 0;
179        }
180    }
181
182    /**
183     * Push information about a new attach session.
184     */
185    public void recordAttachSession(int uid, boolean usesIdentityCallback,
186            SparseArray<WifiAwareClientState> clients) {
187        // count the number of clients with the specific uid
188        int currentConcurrentCount = 0;
189        for (int i = 0; i < clients.size(); ++i) {
190            if (clients.valueAt(i).getUid() == uid) {
191                ++currentConcurrentCount;
192            }
193        }
194
195        synchronized (mLock) {
196            AttachData data = mAttachDataByUid.get(uid);
197            if (data == null) {
198                data = new AttachData();
199                mAttachDataByUid.put(uid, data);
200            }
201            data.mUsesIdentityCallback |= usesIdentityCallback;
202            data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
203                    currentConcurrentCount);
204            recordAttachStatus(NanStatusType.SUCCESS);
205        }
206    }
207
208    /**
209     * Push information about a new attach session status (recorded when attach session is created).
210     */
211    public void recordAttachStatus(int status) {
212        synchronized (mLock) {
213            mAttachStatusData.put(status, mAttachStatusData.get(status) + 1);
214        }
215    }
216
217    /**
218     * Push duration information of an attach session.
219     */
220    public void recordAttachSessionDuration(long creationTime) {
221        synchronized (mLock) {
222            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
223                    mHistogramAttachDuration,
224                    DURATION_LOG_HISTOGRAM);
225        }
226    }
227
228    /**
229     * Push information about the new discovery session.
230     */
231    public void recordDiscoverySession(int uid, boolean isPublish,
232            SparseArray<WifiAwareClientState> clients) {
233        // count the number of sessions per uid and overall
234        int numPublishesInSystem = 0;
235        int numSubscribesInSystem = 0;
236        int numPublishesOnUid = 0;
237        int numSubscribesOnUid = 0;
238
239        for (int i = 0; i < clients.size(); ++i) {
240            WifiAwareClientState client = clients.valueAt(i);
241            boolean sameUid = client.getUid() == uid;
242
243            SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
244            for (int j = 0; j < sessions.size(); ++j) {
245                WifiAwareDiscoverySessionState session = sessions.valueAt(j);
246
247                if (session.isPublishSession()) {
248                    numPublishesInSystem += 1;
249                    if (sameUid) {
250                        numPublishesOnUid += 1;
251                    }
252                } else {
253                    numSubscribesInSystem += 1;
254                    if (sameUid) {
255                        numSubscribesOnUid += 1;
256                    }
257                }
258            }
259        }
260
261        synchronized (mLock) {
262            mMaxPublishInApp = Math.max(mMaxPublishInApp, numPublishesOnUid);
263            mMaxSubscribeInApp = Math.max(mMaxSubscribeInApp, numSubscribesOnUid);
264            mMaxDiscoveryInApp = Math.max(mMaxDiscoveryInApp,
265                    numPublishesOnUid + numSubscribesOnUid);
266            mMaxPublishInSystem = Math.max(mMaxPublishInSystem, numPublishesInSystem);
267            mMaxSubscribeInSystem = Math.max(mMaxSubscribeInSystem, numSubscribesInSystem);
268            mMaxDiscoveryInSystem = Math.max(mMaxDiscoveryInSystem,
269                    numPublishesInSystem + numSubscribesInSystem);
270        }
271    }
272
273    /**
274     * Push information about a new discovery session status (recorded when the discovery session is
275     * created).
276     */
277    public void recordDiscoveryStatus(int uid, int status, boolean isPublish) {
278        synchronized (mLock) {
279            if (isPublish) {
280                mPublishStatusData.put(status, mPublishStatusData.get(status) + 1);
281            } else {
282                mSubscribeStatusData.put(status, mSubscribeStatusData.get(status) + 1);
283            }
284
285            if (status == NanStatusType.NO_RESOURCES_AVAILABLE) {
286                mAppsWithDiscoverySessionResourceFailure.add(uid);
287            }
288        }
289    }
290
291    /**
292     * Push duration information of a discovery session.
293     */
294    public void recordDiscoverySessionDuration(long creationTime, boolean isPublish) {
295        synchronized (mLock) {
296            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
297                    isPublish ? mHistogramPublishDuration : mHistogramSubscribeDuration,
298                    DURATION_LOG_HISTOGRAM);
299        }
300    }
301
302    /**
303     * Record NDP (and by extension NDI) usage - on successful creation of an NDP.
304     */
305    public void recordNdpCreation(int uid,
306            Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
307                    .AwareNetworkRequestInformation> networkRequestCache) {
308        int numNdpInApp = 0;
309        int numSecureNdpInApp = 0;
310        int numNdpInSystem = 0;
311        int numSecureNdpInSystem = 0;
312
313        Map<String, Integer> ndpPerNdiMap = new HashMap<>();
314        Set<String> ndiInApp = new HashSet<>();
315        Set<String> ndiInSystem = new HashSet<>();
316
317        for (WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri :
318                networkRequestCache.values()) {
319            if (anri.state
320                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
321                    .STATE_INITIATOR_CONFIRMED
322                    && anri.state
323                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
324                    .STATE_RESPONDER_CONFIRMED) {
325                continue; // only count completed (up-and-running) NDPs
326            }
327
328            boolean sameUid = anri.uid == uid;
329            boolean isSecure = !TextUtils.isEmpty(anri.networkSpecifier.passphrase) || (
330                    anri.networkSpecifier.pmk != null && anri.networkSpecifier.pmk.length != 0);
331
332            // in-app stats
333            if (sameUid) {
334                numNdpInApp += 1;
335                if (isSecure) {
336                    numSecureNdpInApp += 1;
337                }
338
339                ndiInApp.add(anri.interfaceName);
340            }
341
342            // system stats
343            numNdpInSystem += 1;
344            if (isSecure) {
345                numSecureNdpInSystem += 1;
346            }
347
348            // ndp/ndi stats
349            Integer ndpCount = ndpPerNdiMap.get(anri.interfaceName);
350            if (ndpCount == null) {
351                ndpPerNdiMap.put(anri.interfaceName, 1);
352            } else {
353                ndpPerNdiMap.put(anri.interfaceName, ndpCount + 1);
354            }
355
356            // ndi stats
357            ndiInSystem.add(anri.interfaceName);
358        }
359
360        synchronized (mLock) {
361            mMaxNdiInApp = Math.max(mMaxNdiInApp, ndiInApp.size());
362            mMaxNdpInApp = Math.max(mMaxNdpInApp, numNdpInApp);
363            mMaxSecureNdpInApp = Math.max(mMaxSecureNdpInApp, numSecureNdpInApp);
364            mMaxNdiInSystem = Math.max(mMaxNdiInSystem, ndiInSystem.size());
365            mMaxNdpInSystem = Math.max(mMaxNdpInSystem, numNdpInSystem);
366            mMaxSecureNdpInSystem = Math.max(mMaxSecureNdpInSystem, numSecureNdpInSystem);
367            mMaxNdpPerNdi = Math.max(mMaxNdpPerNdi, Collections.max(ndpPerNdiMap.values()));
368        }
369    }
370
371    /**
372     * Record the completion status of NDP negotiation. There are multiple steps in NDP negotiation
373     * a failure on any aborts the process and is recorded. A success on intermediate stages is
374     * not recorded - only the final success.
375     */
376    public void recordNdpStatus(int status, boolean isOutOfBand, long startTimestamp) {
377        synchronized (mLock) {
378            if (isOutOfBand) {
379                mOutOfBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
380            } else {
381                mInBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
382            }
383
384            if (status == NanStatusType.SUCCESS) {
385                long creationTime = mClock.getElapsedSinceBootMillis() - startTimestamp;
386                addLogValueToHistogram(creationTime, mNdpCreationTimeDuration,
387                        DURATION_LOG_HISTOGRAM);
388                mNdpCreationTimeMin = (mNdpCreationTimeMin == -1) ? creationTime : Math.min(
389                        mNdpCreationTimeMin, creationTime);
390                mNdpCreationTimeMax = Math.max(mNdpCreationTimeMax, creationTime);
391                mNdpCreationTimeSum += creationTime;
392                mNdpCreationTimeSumSq += creationTime * creationTime;
393                mNdpCreationTimeNumSamples += 1;
394            }
395        }
396    }
397
398    /**
399     * Consolidate all metrics into the proto.
400     */
401    public WifiMetricsProto.WifiAwareLog consolidateProto() {
402        WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
403        long now = mClock.getElapsedSinceBootMillis();
404        synchronized (mLock) {
405            log.histogramAwareAvailableDurationMs = histogramToProtoArray(
406                    mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
407            log.availableTimeMs = mAvailableTimeMs;
408            if (mLastEnableUsageInThisSampleWindowMs != 0) {
409                log.availableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
410            }
411
412            log.histogramAwareEnabledDurationMs = histogramToProtoArray(
413                    mHistogramAwareEnabledDurationMs, DURATION_LOG_HISTOGRAM);
414            log.enabledTimeMs = mEnabledTimeMs;
415            if (mLastEnableAwareInThisSampleWindowMs != 0) {
416                log.enabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
417            }
418
419            log.numApps = mAttachDataByUid.size();
420            log.numAppsUsingIdentityCallback = 0;
421            log.maxConcurrentAttachSessionsInApp = 0;
422            for (AttachData ad: mAttachDataByUid.values()) {
423                if (ad.mUsesIdentityCallback) {
424                    ++log.numAppsUsingIdentityCallback;
425                }
426                log.maxConcurrentAttachSessionsInApp = Math.max(
427                        log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
428            }
429            log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
430            log.histogramAttachDurationMs = histogramToProtoArray(mHistogramAttachDuration,
431                    DURATION_LOG_HISTOGRAM);
432
433            log.maxConcurrentPublishInApp = mMaxPublishInApp;
434            log.maxConcurrentSubscribeInApp = mMaxSubscribeInApp;
435            log.maxConcurrentDiscoverySessionsInApp = mMaxDiscoveryInApp;
436            log.maxConcurrentPublishInSystem = mMaxPublishInSystem;
437            log.maxConcurrentSubscribeInSystem = mMaxSubscribeInSystem;
438            log.maxConcurrentDiscoverySessionsInSystem = mMaxDiscoveryInSystem;
439            log.histogramPublishStatus = histogramToProtoArray(mPublishStatusData);
440            log.histogramSubscribeStatus = histogramToProtoArray(mSubscribeStatusData);
441            log.numAppsWithDiscoverySessionFailureOutOfResources =
442                    mAppsWithDiscoverySessionResourceFailure.size();
443            log.histogramPublishSessionDurationMs = histogramToProtoArray(mHistogramPublishDuration,
444                    DURATION_LOG_HISTOGRAM);
445            log.histogramSubscribeSessionDurationMs = histogramToProtoArray(
446                    mHistogramSubscribeDuration, DURATION_LOG_HISTOGRAM);
447
448            log.maxConcurrentNdiInApp = mMaxNdiInApp;
449            log.maxConcurrentNdiInSystem = mMaxNdiInSystem;
450            log.maxConcurrentNdpInApp = mMaxNdpInApp;
451            log.maxConcurrentNdpInSystem = mMaxNdpInSystem;
452            log.maxConcurrentSecureNdpInApp = mMaxSecureNdpInApp;
453            log.maxConcurrentSecureNdpInSystem = mMaxSecureNdpInSystem;
454            log.maxConcurrentNdpPerNdi = mMaxNdpPerNdi;
455            log.histogramRequestNdpStatus = histogramToProtoArray(mInBandNdpStatusData);
456            log.histogramRequestNdpOobStatus = histogramToProtoArray(mOutOfBandNdpStatusData);
457
458            log.histogramNdpCreationTimeMs = histogramToProtoArray(mNdpCreationTimeDuration,
459                    DURATION_LOG_HISTOGRAM);
460            log.ndpCreationTimeMsMin = mNdpCreationTimeMin;
461            log.ndpCreationTimeMsMax = mNdpCreationTimeMax;
462            log.ndpCreationTimeMsSum = mNdpCreationTimeSum;
463            log.ndpCreationTimeMsSumOfSq = mNdpCreationTimeSumSq;
464            log.ndpCreationTimeMsNumSamples = mNdpCreationTimeNumSamples;
465        }
466        return log;
467    }
468
469    /**
470     * clear Wi-Fi Aware metrics
471     */
472    public void clear() {
473        long now = mClock.getElapsedSinceBootMillis();
474        synchronized (mLock) {
475            // don't clear mLastEnableUsage since could be valid for next measurement period
476            mHistogramAwareAvailableDurationMs.clear();
477            mAvailableTimeMs = 0;
478            if (mLastEnableUsageInThisSampleWindowMs != 0) {
479                mLastEnableUsageInThisSampleWindowMs = now;
480            }
481
482            // don't clear mLastEnableAware since could be valid for next measurement period
483            mHistogramAwareEnabledDurationMs.clear();
484            mEnabledTimeMs = 0;
485            if (mLastEnableAwareInThisSampleWindowMs != 0) {
486                mLastEnableAwareInThisSampleWindowMs = now;
487            }
488
489            mAttachDataByUid.clear();
490            mAttachStatusData.clear();
491            mHistogramAttachDuration.clear();
492
493            mMaxPublishInApp = 0;
494            mMaxSubscribeInApp = 0;
495            mMaxDiscoveryInApp = 0;
496            mMaxPublishInSystem = 0;
497            mMaxSubscribeInSystem = 0;
498            mMaxDiscoveryInSystem = 0;
499            mPublishStatusData.clear();
500            mSubscribeStatusData.clear();
501            mHistogramPublishDuration.clear();
502            mHistogramSubscribeDuration.clear();
503            mAppsWithDiscoverySessionResourceFailure.clear();
504
505            mMaxNdiInApp = 0;
506            mMaxNdpInApp = 0;
507            mMaxSecureNdpInApp = 0;
508            mMaxNdiInSystem = 0;
509            mMaxNdpInSystem = 0;
510            mMaxSecureNdpInSystem = 0;
511            mMaxNdpPerNdi = 0;
512            mInBandNdpStatusData.clear();
513            mOutOfBandNdpStatusData.clear();
514
515            mNdpCreationTimeDuration.clear();
516            mNdpCreationTimeMin = -1;
517            mNdpCreationTimeMax = 0;
518            mNdpCreationTimeSum = 0;
519            mNdpCreationTimeSumSq = 0;
520            mNdpCreationTimeNumSamples = 0;
521        }
522    }
523
524    /**
525     * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
526     * serialized metrics (handled by parent WifiMetrics).
527     *
528     * @param fd   unused
529     * @param pw   PrintWriter for writing dump to
530     * @param args unused
531     */
532    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
533        synchronized (mLock) {
534            pw.println("mLastEnableUsage:" + mLastEnableUsageMs);
535            pw.println(
536                    "mLastEnableUsageInThisSampleWindow:" + mLastEnableUsageInThisSampleWindowMs);
537            pw.println("mAvailableTime:" + mAvailableTimeMs);
538            pw.println("mHistogramAwareAvailableDurationMs:");
539            for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
540                pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
541                        + mHistogramAwareAvailableDurationMs.valueAt(i));
542            }
543            pw.println("mAttachDataByUid:");
544            for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
545                pw.println("  " + "uid=" + ade.getKey() + ": identity="
546                        + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
547                        + ade.getValue().mMaxConcurrentAttaches);
548            }
549            pw.println("mAttachStatusData:");
550            for (int i = 0; i < mAttachStatusData.size(); ++i) {
551                pw.println("  " + mAttachStatusData.keyAt(i) + ": "
552                        + mAttachStatusData.valueAt(i));
553            }
554            pw.println("mHistogramAttachDuration:");
555            for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
556                pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
557                        + mHistogramAttachDuration.valueAt(i));
558            }
559        }
560    }
561
562    // histogram utilities
563
564    /**
565     * Specifies a ~log histogram consisting of two levels of buckets - a set of N big buckets:
566     *
567     * Buckets starts at: B + P * M^i, where i=0, ... , N-1 (N big buckets)
568     * Each big bucket is divided into S sub-buckets
569     *
570     * Each (big) bucket is M times bigger than the previous one.
571     *
572     * The buckets are then:
573     * #0: B + P * M^0 with S buckets each of width (P*M^1-P*M^0)/S
574     * #1: B + P * M^1 with S buckets each of width (P*M^2-P*M^1)/S
575     * ...
576     * #N-1: B + P * M^(N-1) with S buckets each of width (P*M^N-P*M^(N-1))/S
577     */
578    @VisibleForTesting
579    public static class HistParms {
580        public HistParms(int b, int p, int m, int s, int n) {
581            this.b = b;
582            this.p = p;
583            this.m = m;
584            this.s = s;
585            this.n = n;
586
587            // derived values
588            mLog = Math.log(m);
589            bb = new double[n];
590            sbw = new double[n];
591            bb[0] = b + p;
592            sbw[0] = p * (m - 1.0) / (double) s;
593            for (int i = 1; i < n; ++i) {
594                bb[i] = m * (bb[i - 1] - b) + b;
595                sbw[i] = m * sbw[i - 1];
596            }
597        }
598
599        // spec
600        public int b;
601        public int p;
602        public int m;
603        public int s;
604        public int n;
605
606        // derived
607        public double mLog;
608        public double[] bb; // bucket base
609        public double[] sbw; // sub-bucket width
610    }
611
612    /**
613     * Adds the input value to the histogram based on the histogram parameters.
614     */
615    @VisibleForTesting
616    public static int addLogValueToHistogram(long x, SparseIntArray histogram, HistParms hp) {
617        double logArg = (double) (x - hp.b) / (double) hp.p;
618        int bigBucketIndex = -1;
619        if (logArg > 0) {
620            bigBucketIndex = (int) (Math.log(logArg) / hp.mLog);
621        }
622        int subBucketIndex;
623        if (bigBucketIndex < 0) {
624            bigBucketIndex = 0;
625            subBucketIndex = 0;
626        } else if (bigBucketIndex >= hp.n) {
627            bigBucketIndex = hp.n - 1;
628            subBucketIndex = hp.s - 1;
629        } else {
630            subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
631            if (subBucketIndex >= hp.s) { // probably a rounding error so move to next big bucket
632                bigBucketIndex++;
633                if (bigBucketIndex >= hp.n) {
634                    bigBucketIndex = hp.n - 1;
635                    subBucketIndex = hp.s - 1;
636                } else {
637                    subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
638                }
639            }
640        }
641        int key = bigBucketIndex * hp.s + subBucketIndex;
642
643        // note that get() returns 0 if index not there already
644        int newValue = histogram.get(key) + 1;
645        histogram.put(key, newValue);
646
647        return newValue;
648    }
649
650    /**
651     * Converts the histogram (with the specified histogram parameters) to an array of proto
652     * histogram buckets.
653     */
654    @VisibleForTesting
655    public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
656            SparseIntArray histogram, HistParms hp) {
657        WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
658                new WifiMetricsProto.WifiAwareLog.HistogramBucket[histogram.size()];
659        for (int i = 0; i < histogram.size(); ++i) {
660            int key = histogram.keyAt(i);
661
662            protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
663            protoArray[i].start = (long) (hp.bb[key / hp.s] + hp.sbw[key / hp.s] * (key % hp.s));
664            protoArray[i].end = (long) (protoArray[i].start + hp.sbw[key / hp.s]);
665            protoArray[i].count = histogram.valueAt(i);
666        }
667
668        return protoArray;
669    }
670
671    /**
672     * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
673     */
674    public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
675        int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
676        int newValue = histogram.get(protoStatus) + 1;
677        histogram.put(protoStatus, newValue);
678    }
679
680    /**
681     * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
682     */
683    @VisibleForTesting
684    public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
685            SparseIntArray histogram) {
686        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
687                new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];
688
689        for (int i = 0; i < histogram.size(); ++i) {
690            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
691            protoArray[i].nanStatusType = histogram.keyAt(i);
692            protoArray[i].count = histogram.valueAt(i);
693        }
694
695        return protoArray;
696    }
697
698    /**
699     * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
700     */
701    public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
702        switch (nanStatusType) {
703            case NanStatusType.SUCCESS:
704                return WifiMetricsProto.WifiAwareLog.SUCCESS;
705            case NanStatusType.INTERNAL_FAILURE:
706                return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
707            case NanStatusType.PROTOCOL_FAILURE:
708                return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
709            case NanStatusType.INVALID_SESSION_ID:
710                return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
711            case NanStatusType.NO_RESOURCES_AVAILABLE:
712                return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
713            case NanStatusType.INVALID_ARGS:
714                return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
715            case NanStatusType.INVALID_PEER_ID:
716                return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
717            case NanStatusType.INVALID_NDP_ID:
718                return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
719            case NanStatusType.NAN_NOT_ALLOWED:
720                return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
721            case NanStatusType.NO_OTA_ACK:
722                return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
723            case NanStatusType.ALREADY_ENABLED:
724                return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
725            case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
726                return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
727            case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
728                return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
729            default:
730                Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
731                return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
732        }
733    }
734}
735