content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wifi.aware;
  
  import android.hardware.wifi.V1_0.NanStatusType;
  import android.net.wifi.aware.WifiAwareNetworkSpecifier;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.SparseArray;
  import android.util.SparseIntArray;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.wifi.Clock;
  import com.android.server.wifi.nano.WifiMetricsProto;
  import com.android.server.wifi.util.MetricsUtils;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * Wi-Fi Aware metric container/processor.
   */
  public class WifiAwareMetrics {
      private static final String TAG = "WifiAwareMetrics";
      private static final boolean VDBG = false;
      /* package */ boolean mDbg = false;
  
      // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
      // Buckets:
      //    1 -> 10: 9 @ 1
      //    10 -> 100: 9 @ 10
      //    100 -> 1000: 9 @ 10^2
      //    10^3 -> 10^4: 9 @ 10^3
      //    10^4 -> 10^5: 9 @ 10^4
      //    10^5 -> 10^6: 9 @ 10^5
      //    10^6 -> 10^7: 9 @ 10^6
      //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
      private static final MetricsUtils.LogHistParms DURATION_LOG_HISTOGRAM =
              new MetricsUtils.LogHistParms(0, 1, 10, 9, 8);
  
      // Histogram for ranging limits in discovery. Indicates the following 5 buckets (in meters):
      //   < 10
      //   [10, 30)
      //   [30, 60)
      //   [60, 100)
      //   >= 100
      private static final int[] RANGING_LIMIT_METERS = { 10, 30, 60, 100 };
  
      private final Object mLock = new Object();
      private final Clock mClock;
  
      // enableUsage/disableUsage data
      private long mLastEnableUsageMs = 0;
      private long mLastEnableUsageInThisSampleWindowMs = 0;
      private long mAvailableTimeMs = 0;
      private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();
  
      // enabled data
      private long mLastEnableAwareMs = 0;
      private long mLastEnableAwareInThisSampleWindowMs = 0;
      private long mEnabledTimeMs = 0;
      private SparseIntArray mHistogramAwareEnabledDurationMs = new SparseIntArray();
  
      // attach data
      private static class AttachData {
          boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
          int mMaxConcurrentAttaches;
      }
      private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
      private SparseIntArray mAttachStatusData = new SparseIntArray();
      private SparseIntArray mHistogramAttachDuration = new SparseIntArray();
  
      // discovery data
      private int mMaxPublishInApp = 0;
      private int mMaxSubscribeInApp = 0;
      private int mMaxDiscoveryInApp = 0;
      private int mMaxPublishInSystem = 0;
      private int mMaxSubscribeInSystem = 0;
      private int mMaxDiscoveryInSystem = 0;
      private SparseIntArray mPublishStatusData = new SparseIntArray();
      private SparseIntArray mSubscribeStatusData = new SparseIntArray();
      private SparseIntArray mHistogramPublishDuration = new SparseIntArray();
      private SparseIntArray mHistogramSubscribeDuration = new SparseIntArray();
      private Set<Integer> mAppsWithDiscoverySessionResourceFailure = new HashSet<>();
  
      // discovery with ranging data
      private int mMaxPublishWithRangingInApp = 0;
      private int mMaxSubscribeWithRangingInApp = 0;
      private int mMaxPublishWithRangingInSystem = 0;
      private int mMaxSubscribeWithRangingInSystem = 0;
      private SparseIntArray mHistogramSubscribeGeofenceMin = new SparseIntArray();
      private SparseIntArray mHistogramSubscribeGeofenceMax = new SparseIntArray();
      private int mNumSubscribesWithRanging = 0;
      private int mNumMatchesWithRanging = 0;
      private int mNumMatchesWithoutRangingForRangingEnabledSubscribes = 0;
  
      // data-path (NDI/NDP) data
      private int mMaxNdiInApp = 0;
      private int mMaxNdpInApp = 0;
      private int mMaxSecureNdpInApp = 0;
      private int mMaxNdiInSystem = 0;
      private int mMaxNdpInSystem = 0;
      private int mMaxSecureNdpInSystem = 0;
      private int mMaxNdpPerNdi = 0;
      private SparseIntArray mInBandNdpStatusData = new SparseIntArray();
      private SparseIntArray mOutOfBandNdpStatusData = new SparseIntArray();
  
      private SparseIntArray mNdpCreationTimeDuration = new SparseIntArray();
      private long mNdpCreationTimeMin = -1;
      private long mNdpCreationTimeMax = 0;
      private long mNdpCreationTimeSum = 0;
      private long mNdpCreationTimeSumSq = 0;
      private long mNdpCreationTimeNumSamples = 0;
  
      private SparseIntArray mHistogramNdpDuration = new SparseIntArray();
  
      public WifiAwareMetrics(Clock clock) {
          mClock = clock;
      }
  
      /**
       * Push usage stats for WifiAwareStateMachine.enableUsage() to
       * histogram_aware_available_duration_ms.
       */
      public void recordEnableUsage() {
          synchronized (mLock) {
              if (mLastEnableUsageMs != 0) {
                  Log.w(TAG, "enableUsage: mLastEnableUsage*Ms initialized!?");
              }
              mLastEnableUsageMs = mClock.getElapsedSinceBootMillis();
              mLastEnableUsageInThisSampleWindowMs = mLastEnableUsageMs;
          }
      }
  
      /**
       * Push usage stats for WifiAwareStateMachine.disableUsage() to
       * histogram_aware_available_duration_ms.
       */
  
      public void recordDisableUsage() {
          synchronized (mLock) {
              if (mLastEnableUsageMs == 0) {
                  Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
                  return;
              }
  
              long now = mClock.getElapsedSinceBootMillis();
              MetricsUtils.addValueToLogHistogram(now - mLastEnableUsageMs,
                      mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
              mAvailableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
              mLastEnableUsageMs = 0;
              mLastEnableUsageInThisSampleWindowMs = 0;
          }
      }
  
      /**
       * Push usage stats of Aware actually being enabled on-the-air: start
       */
      public void recordEnableAware() {
          synchronized (mLock) {
              if (mLastEnableAwareMs != 0) {
                  return; // already enabled
              }
              mLastEnableAwareMs = mClock.getElapsedSinceBootMillis();
              mLastEnableAwareInThisSampleWindowMs = mLastEnableAwareMs;
          }
      }
  
      /**
       * Push usage stats of Aware actually being enabled on-the-air: stop (disable)
       */
      public void recordDisableAware() {
          synchronized (mLock) {
              if (mLastEnableAwareMs == 0) {
                  return; // already disabled
              }
  
              long now = mClock.getElapsedSinceBootMillis();
              MetricsUtils.addValueToLogHistogram(now - mLastEnableAwareMs,
                      mHistogramAwareEnabledDurationMs, DURATION_LOG_HISTOGRAM);
              mEnabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
              mLastEnableAwareMs = 0;
              mLastEnableAwareInThisSampleWindowMs = 0;
          }
      }
  
      /**
       * Push information about a new attach session.
       */
      public void recordAttachSession(int uid, boolean usesIdentityCallback,
              SparseArray<WifiAwareClientState> clients) {
          // count the number of clients with the specific uid
          int currentConcurrentCount = 0;
          for (int i = 0; i < clients.size(); ++i) {
              if (clients.valueAt(i).getUid() == uid) {
                  ++currentConcurrentCount;
              }
          }
  
          synchronized (mLock) {
              AttachData data = mAttachDataByUid.get(uid);
              if (data == null) {
                  data = new AttachData();
                  mAttachDataByUid.put(uid, data);
              }
              data.mUsesIdentityCallback |= usesIdentityCallback;
              data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
                      currentConcurrentCount);
              recordAttachStatus(NanStatusType.SUCCESS);
          }
      }
  
      /**
       * Push information about a new attach session status (recorded when attach session is created).
       */
      public void recordAttachStatus(int status) {
          synchronized (mLock) {
              mAttachStatusData.put(status, mAttachStatusData.get(status) + 1);
          }
      }
  
      /**
       * Push duration information of an attach session.
       */
      public void recordAttachSessionDuration(long creationTime) {
          synchronized (mLock) {
              MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                      mHistogramAttachDuration, DURATION_LOG_HISTOGRAM);
          }
      }
  
      /**
       * Push information about the new discovery session.
       */
      public void recordDiscoverySession(int uid, SparseArray<WifiAwareClientState> clients) {
          recordDiscoverySessionInternal(uid, clients, false, -1, -1);
      }
  
      /**
       * Push information about the new discovery session with ranging enabled
       */
      public void recordDiscoverySessionWithRanging(int uid, boolean isSubscriberWithRanging,
              int minRange, int maxRange, SparseArray<WifiAwareClientState> clients) {
          recordDiscoverySessionInternal(uid, clients, isSubscriberWithRanging, minRange, maxRange);
      }
  
      /**
       * Internal combiner of discovery session information.
       */
      private void recordDiscoverySessionInternal(int uid, SparseArray<WifiAwareClientState> clients,
              boolean isRangingEnabledSubscriber, int minRange, int maxRange) {
          // count the number of sessions per uid and overall
          int numPublishesInSystem = 0;
          int numSubscribesInSystem = 0;
          int numPublishesOnUid = 0;
          int numSubscribesOnUid = 0;
  
          int numPublishesWithRangingInSystem = 0;
          int numSubscribesWithRangingInSystem = 0;
          int numPublishesWithRangingOnUid = 0;
          int numSubscribesWithRangingOnUid = 0;
  
          for (int i = 0; i < clients.size(); ++i) {
              WifiAwareClientState client = clients.valueAt(i);
              boolean sameUid = client.getUid() == uid;
  
              SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
              for (int j = 0; j < sessions.size(); ++j) {
                  WifiAwareDiscoverySessionState session = sessions.valueAt(j);
                  boolean isRangingEnabledForThisSession = session.isRangingEnabled();
  
                  if (session.isPublishSession()) {
                      numPublishesInSystem += 1;
                      if (isRangingEnabledForThisSession) {
                          numPublishesWithRangingInSystem += 1;
                      }
                      if (sameUid) {
                          numPublishesOnUid += 1;
                          if (isRangingEnabledForThisSession) {
                              numPublishesWithRangingOnUid += 1;
                          }
                      }
                  } else {
                      numSubscribesInSystem += 1;
                      if (isRangingEnabledForThisSession) {
                          numSubscribesWithRangingInSystem += 1;
                      }
                      if (sameUid) {
                          numSubscribesOnUid += 1;
                          if (isRangingEnabledForThisSession) {
                              numSubscribesWithRangingOnUid += 1;
                          }
                      }
                  }
              }
          }
  
          synchronized (mLock) {
              mMaxPublishInApp = Math.max(mMaxPublishInApp, numPublishesOnUid);
              mMaxSubscribeInApp = Math.max(mMaxSubscribeInApp, numSubscribesOnUid);
              mMaxDiscoveryInApp = Math.max(mMaxDiscoveryInApp,
                      numPublishesOnUid + numSubscribesOnUid);
              mMaxPublishInSystem = Math.max(mMaxPublishInSystem, numPublishesInSystem);
              mMaxSubscribeInSystem = Math.max(mMaxSubscribeInSystem, numSubscribesInSystem);
              mMaxDiscoveryInSystem = Math.max(mMaxDiscoveryInSystem,
                      numPublishesInSystem + numSubscribesInSystem);
  
              mMaxPublishWithRangingInApp = Math.max(mMaxPublishWithRangingInApp,
                      numPublishesWithRangingOnUid);
              mMaxSubscribeWithRangingInApp = Math.max(mMaxSubscribeWithRangingInApp,
                      numSubscribesWithRangingOnUid);
              mMaxPublishWithRangingInSystem = Math.max(mMaxPublishWithRangingInSystem,
                      numPublishesWithRangingInSystem);
              mMaxSubscribeWithRangingInSystem = Math.max(mMaxSubscribeWithRangingInSystem,
                      numSubscribesWithRangingInSystem);
              if (isRangingEnabledSubscriber) {
                  mNumSubscribesWithRanging += 1;
              }
  
              if (minRange != -1) {
                  MetricsUtils.addValueToLinearHistogram(minRange, mHistogramSubscribeGeofenceMin,
                          RANGING_LIMIT_METERS);
              }
              if (maxRange != -1) {
                  MetricsUtils.addValueToLinearHistogram(maxRange, mHistogramSubscribeGeofenceMax,
                          RANGING_LIMIT_METERS);
              }
          }
      }
  
      /**
       * Push information about a new discovery session status (recorded when the discovery session is
       * created).
       */
      public void recordDiscoveryStatus(int uid, int status, boolean isPublish) {
          synchronized (mLock) {
              if (isPublish) {
                  mPublishStatusData.put(status, mPublishStatusData.get(status) + 1);
              } else {
                  mSubscribeStatusData.put(status, mSubscribeStatusData.get(status) + 1);
              }
  
              if (status == NanStatusType.NO_RESOURCES_AVAILABLE) {
                  mAppsWithDiscoverySessionResourceFailure.add(uid);
              }
          }
      }
  
      /**
       * Push duration information of a discovery session.
       */
      public void recordDiscoverySessionDuration(long creationTime, boolean isPublish) {
          synchronized (mLock) {
              MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                      isPublish ? mHistogramPublishDuration : mHistogramSubscribeDuration,
                      DURATION_LOG_HISTOGRAM);
          }
      }
  
      /**
       * Push information about Match indication (aka service discovered) for subscribe sessions
       * which enabled ranging. Collect information about whether or not service discovery was
       * triggered with ranging information or without (i.e. ranging disabled for some reason).
       */
      public void recordMatchIndicationForRangeEnabledSubscribe(boolean rangeProvided) {
          if (rangeProvided) {
              mNumMatchesWithRanging++;
          } else {
              mNumMatchesWithoutRangingForRangingEnabledSubscribes++;
          }
      }
  
      /**
       * Record NDP (and by extension NDI) usage - on successful creation of an NDP.
       */
      public void recordNdpCreation(int uid,
              Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
                      .AwareNetworkRequestInformation> networkRequestCache) {
          int numNdpInApp = 0;
          int numSecureNdpInApp = 0;
          int numNdpInSystem = 0;
          int numSecureNdpInSystem = 0;
  
          Map<String, Integer> ndpPerNdiMap = new HashMap<>();
          Set<String> ndiInApp = new HashSet<>();
          Set<String> ndiInSystem = new HashSet<>();
  
          for (WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri :
                  networkRequestCache.values()) {
              if (anri.state
                      != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
                      .STATE_CONFIRMED) {
                  continue; // only count completed (up-and-running) NDPs
              }
  
              boolean sameUid = anri.uid == uid;
              boolean isSecure = !TextUtils.isEmpty(anri.networkSpecifier.passphrase) || (
                      anri.networkSpecifier.pmk != null && anri.networkSpecifier.pmk.length != 0);
  
              // in-app stats
              if (sameUid) {
                  numNdpInApp += 1;
                  if (isSecure) {
                      numSecureNdpInApp += 1;
                  }
  
                  ndiInApp.add(anri.interfaceName);
              }
  
              // system stats
              numNdpInSystem += 1;
              if (isSecure) {
                  numSecureNdpInSystem += 1;
              }
  
              // ndp/ndi stats
              Integer ndpCount = ndpPerNdiMap.get(anri.interfaceName);
              if (ndpCount == null) {
                  ndpPerNdiMap.put(anri.interfaceName, 1);
              } else {
                  ndpPerNdiMap.put(anri.interfaceName, ndpCount + 1);
              }
  
              // ndi stats
              ndiInSystem.add(anri.interfaceName);
          }
  
          synchronized (mLock) {
              mMaxNdiInApp = Math.max(mMaxNdiInApp, ndiInApp.size());
              mMaxNdpInApp = Math.max(mMaxNdpInApp, numNdpInApp);
              mMaxSecureNdpInApp = Math.max(mMaxSecureNdpInApp, numSecureNdpInApp);
              mMaxNdiInSystem = Math.max(mMaxNdiInSystem, ndiInSystem.size());
              mMaxNdpInSystem = Math.max(mMaxNdpInSystem, numNdpInSystem);
              mMaxSecureNdpInSystem = Math.max(mMaxSecureNdpInSystem, numSecureNdpInSystem);
              mMaxNdpPerNdi = Math.max(mMaxNdpPerNdi, Collections.max(ndpPerNdiMap.values()));
          }
      }
  
      /**
       * Record the completion status of NDP negotiation. There are multiple steps in NDP negotiation
       * a failure on any aborts the process and is recorded. A success on intermediate stages is
       * not recorded - only the final success.
       */
      public void recordNdpStatus(int status, boolean isOutOfBand, long startTimestamp) {
          synchronized (mLock) {
              if (isOutOfBand) {
                  mOutOfBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
              } else {
                  mInBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
              }
  
              if (status == NanStatusType.SUCCESS) {
                  long creationTime = mClock.getElapsedSinceBootMillis() - startTimestamp;
                  MetricsUtils.addValueToLogHistogram(creationTime, mNdpCreationTimeDuration,
                          DURATION_LOG_HISTOGRAM);
                  mNdpCreationTimeMin = (mNdpCreationTimeMin == -1) ? creationTime : Math.min(
                          mNdpCreationTimeMin, creationTime);
                  mNdpCreationTimeMax = Math.max(mNdpCreationTimeMax, creationTime);
                  mNdpCreationTimeSum += creationTime;
                  mNdpCreationTimeSumSq += creationTime * creationTime;
                  mNdpCreationTimeNumSamples += 1;
              }
          }
      }
  
      /**
       * Record the duration of the NDP session. The creation time is assumed to be the time at
       * which a confirm message was received (i.e. the end of the setup negotiation).
       */
      public void recordNdpSessionDuration(long creationTime) {
          synchronized (mLock) {
              MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                      mHistogramNdpDuration, DURATION_LOG_HISTOGRAM);
          }
      }
  
      /**
       * Consolidate all metrics into the proto.
       */
      public WifiMetricsProto.WifiAwareLog consolidateProto() {
          WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
          long now = mClock.getElapsedSinceBootMillis();
          synchronized (mLock) {
              log.histogramAwareAvailableDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramAwareAvailableDurationMs,
                              DURATION_LOG_HISTOGRAM));
              log.availableTimeMs = mAvailableTimeMs;
              if (mLastEnableUsageInThisSampleWindowMs != 0) {
                  log.availableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
              }
  
              log.histogramAwareEnabledDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramAwareEnabledDurationMs,
                              DURATION_LOG_HISTOGRAM));
              log.enabledTimeMs = mEnabledTimeMs;
              if (mLastEnableAwareInThisSampleWindowMs != 0) {
                  log.enabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
              }
  
              log.numApps = mAttachDataByUid.size();
              log.numAppsUsingIdentityCallback = 0;
              log.maxConcurrentAttachSessionsInApp = 0;
              for (AttachData ad: mAttachDataByUid.values()) {
                  if (ad.mUsesIdentityCallback) {
                      ++log.numAppsUsingIdentityCallback;
                  }
                  log.maxConcurrentAttachSessionsInApp = Math.max(
                          log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
              }
              log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
              log.histogramAttachDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramAttachDuration,
                              DURATION_LOG_HISTOGRAM));
  
              log.maxConcurrentPublishInApp = mMaxPublishInApp;
              log.maxConcurrentSubscribeInApp = mMaxSubscribeInApp;
              log.maxConcurrentDiscoverySessionsInApp = mMaxDiscoveryInApp;
              log.maxConcurrentPublishInSystem = mMaxPublishInSystem;
              log.maxConcurrentSubscribeInSystem = mMaxSubscribeInSystem;
              log.maxConcurrentDiscoverySessionsInSystem = mMaxDiscoveryInSystem;
              log.histogramPublishStatus = histogramToProtoArray(mPublishStatusData);
              log.histogramSubscribeStatus = histogramToProtoArray(mSubscribeStatusData);
              log.numAppsWithDiscoverySessionFailureOutOfResources =
                      mAppsWithDiscoverySessionResourceFailure.size();
              log.histogramPublishSessionDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramPublishDuration,
                              DURATION_LOG_HISTOGRAM));
              log.histogramSubscribeSessionDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramSubscribeDuration,
                              DURATION_LOG_HISTOGRAM));
  
              log.maxConcurrentPublishWithRangingInApp = mMaxPublishWithRangingInApp;
              log.maxConcurrentSubscribeWithRangingInApp = mMaxSubscribeWithRangingInApp;
              log.maxConcurrentPublishWithRangingInSystem = mMaxPublishWithRangingInSystem;
              log.maxConcurrentSubscribeWithRangingInSystem = mMaxSubscribeWithRangingInSystem;
              log.histogramSubscribeGeofenceMin = histogramToProtoArray(
                      MetricsUtils.linearHistogramToGenericBuckets(mHistogramSubscribeGeofenceMin,
                              RANGING_LIMIT_METERS));
              log.histogramSubscribeGeofenceMax = histogramToProtoArray(
                      MetricsUtils.linearHistogramToGenericBuckets(mHistogramSubscribeGeofenceMax,
                              RANGING_LIMIT_METERS));
              log.numSubscribesWithRanging = mNumSubscribesWithRanging;
              log.numMatchesWithRanging = mNumMatchesWithRanging;
              log.numMatchesWithoutRangingForRangingEnabledSubscribes =
                      mNumMatchesWithoutRangingForRangingEnabledSubscribes;
  
              log.maxConcurrentNdiInApp = mMaxNdiInApp;
              log.maxConcurrentNdiInSystem = mMaxNdiInSystem;
              log.maxConcurrentNdpInApp = mMaxNdpInApp;
              log.maxConcurrentNdpInSystem = mMaxNdpInSystem;
              log.maxConcurrentSecureNdpInApp = mMaxSecureNdpInApp;
              log.maxConcurrentSecureNdpInSystem = mMaxSecureNdpInSystem;
              log.maxConcurrentNdpPerNdi = mMaxNdpPerNdi;
              log.histogramRequestNdpStatus = histogramToProtoArray(mInBandNdpStatusData);
              log.histogramRequestNdpOobStatus = histogramToProtoArray(mOutOfBandNdpStatusData);
  
              log.histogramNdpCreationTimeMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mNdpCreationTimeDuration,
                              DURATION_LOG_HISTOGRAM));
              log.ndpCreationTimeMsMin = mNdpCreationTimeMin;
              log.ndpCreationTimeMsMax = mNdpCreationTimeMax;
              log.ndpCreationTimeMsSum = mNdpCreationTimeSum;
              log.ndpCreationTimeMsSumOfSq = mNdpCreationTimeSumSq;
              log.ndpCreationTimeMsNumSamples = mNdpCreationTimeNumSamples;
  
              log.histogramNdpSessionDurationMs = histogramToProtoArray(
                      MetricsUtils.logHistogramToGenericBuckets(mHistogramNdpDuration,
                              DURATION_LOG_HISTOGRAM));
          }
          return log;
      }
  
      /**
       * clear Wi-Fi Aware metrics
       */
      public void clear() {
          long now = mClock.getElapsedSinceBootMillis();
          synchronized (mLock) {
              // don't clear mLastEnableUsage since could be valid for next measurement period
              mHistogramAwareAvailableDurationMs.clear();
              mAvailableTimeMs = 0;
              if (mLastEnableUsageInThisSampleWindowMs != 0) {
                  mLastEnableUsageInThisSampleWindowMs = now;
              }
  
              // don't clear mLastEnableAware since could be valid for next measurement period
              mHistogramAwareEnabledDurationMs.clear();
              mEnabledTimeMs = 0;
              if (mLastEnableAwareInThisSampleWindowMs != 0) {
                  mLastEnableAwareInThisSampleWindowMs = now;
              }
  
              mAttachDataByUid.clear();
              mAttachStatusData.clear();
              mHistogramAttachDuration.clear();
  
              mMaxPublishInApp = 0;
              mMaxSubscribeInApp = 0;
              mMaxDiscoveryInApp = 0;
              mMaxPublishInSystem = 0;
              mMaxSubscribeInSystem = 0;
              mMaxDiscoveryInSystem = 0;
              mPublishStatusData.clear();
              mSubscribeStatusData.clear();
              mHistogramPublishDuration.clear();
              mHistogramSubscribeDuration.clear();
              mAppsWithDiscoverySessionResourceFailure.clear();
  
              mMaxPublishWithRangingInApp = 0;
              mMaxSubscribeWithRangingInApp = 0;
              mMaxPublishWithRangingInSystem = 0;
              mMaxSubscribeWithRangingInSystem = 0;
              mHistogramSubscribeGeofenceMin.clear();
              mHistogramSubscribeGeofenceMax.clear();
              mNumSubscribesWithRanging = 0;
              mNumMatchesWithRanging = 0;
              mNumMatchesWithoutRangingForRangingEnabledSubscribes = 0;
  
              mMaxNdiInApp = 0;
              mMaxNdpInApp = 0;
              mMaxSecureNdpInApp = 0;
              mMaxNdiInSystem = 0;
              mMaxNdpInSystem = 0;
              mMaxSecureNdpInSystem = 0;
              mMaxNdpPerNdi = 0;
              mInBandNdpStatusData.clear();
              mOutOfBandNdpStatusData.clear();
  
              mNdpCreationTimeDuration.clear();
              mNdpCreationTimeMin = -1;
              mNdpCreationTimeMax = 0;
              mNdpCreationTimeSum = 0;
              mNdpCreationTimeSumSq = 0;
              mNdpCreationTimeNumSamples = 0;
  
              mHistogramNdpDuration.clear();
          }
      }
  
      /**
       * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
       * serialized metrics (handled by parent WifiMetrics).
       *
       * @param fd   unused
       * @param pw   PrintWriter for writing dump to
       * @param args unused
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          synchronized (mLock) {
              pw.println("mLastEnableUsageMs:" + mLastEnableUsageMs);
              pw.println(
                      "mLastEnableUsageInThisSampleWindowMs:" + mLastEnableUsageInThisSampleWindowMs);
              pw.println("mAvailableTimeMs:" + mAvailableTimeMs);
              pw.println("mHistogramAwareAvailableDurationMs:");
              for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
                  pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
                          + mHistogramAwareAvailableDurationMs.valueAt(i));
              }
  
              pw.println("mLastEnableAwareMs:" + mLastEnableAwareMs);
              pw.println(
                      "mLastEnableAwareInThisSampleWindowMs:" + mLastEnableAwareInThisSampleWindowMs);
              pw.println("mEnabledTimeMs:" + mEnabledTimeMs);
              pw.println("mHistogramAwareEnabledDurationMs:");
              for (int i = 0; i < mHistogramAwareEnabledDurationMs.size(); ++i) {
                  pw.println("  " + mHistogramAwareEnabledDurationMs.keyAt(i) + ": "
                          + mHistogramAwareEnabledDurationMs.valueAt(i));
              }
  
              pw.println("mAttachDataByUid:");
              for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
                  pw.println("  " + "uid=" + ade.getKey() + ": identity="
                          + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
                          + ade.getValue().mMaxConcurrentAttaches);
              }
              pw.println("mAttachStatusData:");
              for (int i = 0; i < mAttachStatusData.size(); ++i) {
                  pw.println("  " + mAttachStatusData.keyAt(i) + ": "
                          + mAttachStatusData.valueAt(i));
              }
              pw.println("mHistogramAttachDuration:");
              for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
                  pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
                          + mHistogramAttachDuration.valueAt(i));
              }
  
              pw.println("mMaxPublishInApp:" + mMaxPublishInApp);
              pw.println("mMaxSubscribeInApp:" + mMaxSubscribeInApp);
              pw.println("mMaxDiscoveryInApp:" + mMaxDiscoveryInApp);
              pw.println("mMaxPublishInSystem:" + mMaxPublishInSystem);
              pw.println("mMaxSubscribeInSystem:" + mMaxSubscribeInSystem);
              pw.println("mMaxDiscoveryInSystem:" + mMaxDiscoveryInSystem);
              pw.println("mPublishStatusData:");
              for (int i = 0; i < mPublishStatusData.size(); ++i) {
                  pw.println("  " + mPublishStatusData.keyAt(i) + ": "
                          + mPublishStatusData.valueAt(i));
              }
              pw.println("mSubscribeStatusData:");
              for (int i = 0; i < mSubscribeStatusData.size(); ++i) {
                  pw.println("  " + mSubscribeStatusData.keyAt(i) + ": "
                          + mSubscribeStatusData.valueAt(i));
              }
              pw.println("mHistogramPublishDuration:");
              for (int i = 0; i < mHistogramPublishDuration.size(); ++i) {
                  pw.println("  " + mHistogramPublishDuration.keyAt(i) + ": "
                          + mHistogramPublishDuration.valueAt(i));
              }
              pw.println("mHistogramSubscribeDuration:");
              for (int i = 0; i < mHistogramSubscribeDuration.size(); ++i) {
                  pw.println("  " + mHistogramSubscribeDuration.keyAt(i) + ": "
                          + mHistogramSubscribeDuration.valueAt(i));
              }
              pw.println("mAppsWithDiscoverySessionResourceFailure:");
              for (Integer uid: mAppsWithDiscoverySessionResourceFailure) {
                  pw.println("  " + uid);
  
              }
  
              pw.println("mMaxPublishWithRangingInApp:" + mMaxPublishWithRangingInApp);
              pw.println("mMaxSubscribeWithRangingInApp:" + mMaxSubscribeWithRangingInApp);
              pw.println("mMaxPublishWithRangingInSystem:" + mMaxPublishWithRangingInSystem);
              pw.println("mMaxSubscribeWithRangingInSystem:" + mMaxSubscribeWithRangingInSystem);
              pw.println("mHistogramSubscribeGeofenceMin:");
              for (int i = 0; i < mHistogramSubscribeGeofenceMin.size(); ++i) {
                  pw.println("  " + mHistogramSubscribeGeofenceMin.keyAt(i) + ": "
                          + mHistogramSubscribeGeofenceMin.valueAt(i));
              }
              pw.println("mHistogramSubscribeGeofenceMax:");
              for (int i = 0; i < mHistogramSubscribeGeofenceMax.size(); ++i) {
                  pw.println("  " + mHistogramSubscribeGeofenceMax.keyAt(i) + ": "
                          + mHistogramSubscribeGeofenceMax.valueAt(i));
              }
              pw.println("mNumSubscribesWithRanging:" + mNumSubscribesWithRanging);
              pw.println("mNumMatchesWithRanging:" + mNumMatchesWithRanging);
              pw.println("mNumMatchesWithoutRangingForRangingEnabledSubscribes:"
                      + mNumMatchesWithoutRangingForRangingEnabledSubscribes);
  
              pw.println("mMaxNdiInApp:" + mMaxNdiInApp);
              pw.println("mMaxNdpInApp:" + mMaxNdpInApp);
              pw.println("mMaxSecureNdpInApp:" + mMaxSecureNdpInApp);
              pw.println("mMaxNdiInSystem:" + mMaxNdiInSystem);
              pw.println("mMaxNdpInSystem:" + mMaxNdpInSystem);
              pw.println("mMaxSecureNdpInSystem:" + mMaxSecureNdpInSystem);
              pw.println("mMaxNdpPerNdi:" + mMaxNdpPerNdi);
              pw.println("mInBandNdpStatusData:");
              for (int i = 0; i < mInBandNdpStatusData.size(); ++i) {
                  pw.println("  " + mInBandNdpStatusData.keyAt(i) + ": "
                          + mInBandNdpStatusData.valueAt(i));
              }
              pw.println("mOutOfBandNdpStatusData:");
              for (int i = 0; i < mOutOfBandNdpStatusData.size(); ++i) {
                  pw.println("  " + mOutOfBandNdpStatusData.keyAt(i) + ": "
                          + mOutOfBandNdpStatusData.valueAt(i));
              }
  
              pw.println("mNdpCreationTimeDuration:");
              for (int i = 0; i < mNdpCreationTimeDuration.size(); ++i) {
                  pw.println("  " + mNdpCreationTimeDuration.keyAt(i) + ": "
                          + mNdpCreationTimeDuration.valueAt(i));
              }
              pw.println("mNdpCreationTimeMin:" + mNdpCreationTimeMin);
              pw.println("mNdpCreationTimeMax:" + mNdpCreationTimeMax);
              pw.println("mNdpCreationTimeSum:" + mNdpCreationTimeSum);
              pw.println("mNdpCreationTimeSumSq:" + mNdpCreationTimeSumSq);
              pw.println("mNdpCreationTimeNumSamples:" + mNdpCreationTimeNumSamples);
  
              pw.println("mHistogramNdpDuration:");
              for (int i = 0; i < mHistogramNdpDuration.size(); ++i) {
                  pw.println("  " + mHistogramNdpDuration.keyAt(i) + ": "
                          + mHistogramNdpDuration.valueAt(i));
              }
          }
      }
  
      // histogram utilities
      /**
       * Convert a generic bucket to Aware HistogramBucket proto.
       */
      @VisibleForTesting
      public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
              MetricsUtils.GenericBucket[] buckets) {
          WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
                  new WifiMetricsProto.WifiAwareLog.HistogramBucket[buckets.length];
  
          for (int i = 0; i < buckets.length; ++i) {
              protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
              protoArray[i].start = buckets[i].start;
              protoArray[i].end = buckets[i].end;
              protoArray[i].count = buckets[i].count;
          }
  
          return protoArray;
      }
  
      /**
       * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
       */
      public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
          int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
          int newValue = histogram.get(protoStatus) + 1;
          histogram.put(protoStatus, newValue);
      }
  
      /**
       * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
       */
      @VisibleForTesting
      public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
              SparseIntArray histogram) {
          WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
                  new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];
  
          for (int i = 0; i < histogram.size(); ++i) {
              protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
              protoArray[i].nanStatusType = histogram.keyAt(i);
              protoArray[i].count = histogram.valueAt(i);
          }
  
          return protoArray;
      }
  
      /**
       * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
       */
      public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
          switch (nanStatusType) {
              case NanStatusType.SUCCESS:
                  return WifiMetricsProto.WifiAwareLog.SUCCESS;
              case NanStatusType.INTERNAL_FAILURE:
                  return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
              case NanStatusType.PROTOCOL_FAILURE:
                  return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
              case NanStatusType.INVALID_SESSION_ID:
                  return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
              case NanStatusType.NO_RESOURCES_AVAILABLE:
                  return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
              case NanStatusType.INVALID_ARGS:
                  return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
              case NanStatusType.INVALID_PEER_ID:
                  return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
              case NanStatusType.INVALID_NDP_ID:
                  return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
              case NanStatusType.NAN_NOT_ALLOWED:
                  return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
              case NanStatusType.NO_OTA_ACK:
                  return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
              case NanStatusType.ALREADY_ENABLED:
                  return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
              case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
                  return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
              case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
                  return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
              default:
                  Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
                  return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
          }
      }
  }
  