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.connectivity;
  
  import android.net.LinkProperties;
  import android.net.metrics.DefaultNetworkEvent;
  import android.os.SystemClock;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.util.BitUtils;
  import com.android.internal.util.RingBuffer;
  import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
  
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Tracks events related to the default network for the purpose of default network metrics.
   * {@hide}
   */
  public class DefaultNetworkMetrics {
  
      private static final int ROLLING_LOG_SIZE = 64;
  
      public final long creationTimeMs = SystemClock.elapsedRealtime();
  
      // Event buffer used for metrics upload. The buffer is cleared when events are collected.
      @GuardedBy("this")
      private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
  
      // Rolling event buffer used for dumpsys and bugreports.
      @GuardedBy("this")
      private final RingBuffer<DefaultNetworkEvent> mEventsLog =
              new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE);
  
      // Information about the current status of the default network.
      @GuardedBy("this")
      private DefaultNetworkEvent mCurrentDefaultNetwork;
      // True if the current default network has been validated.
      @GuardedBy("this")
      private boolean mIsCurrentlyValid;
      @GuardedBy("this")
      private long mLastValidationTimeMs;
      // Transport information about the last default network.
      @GuardedBy("this")
      private int mLastTransports;
  
      public DefaultNetworkMetrics() {
          newDefaultNetwork(creationTimeMs, null);
      }
  
      public synchronized void listEvents(PrintWriter pw) {
          pw.println("default network events:");
          long localTimeMs = System.currentTimeMillis();
          long timeMs = SystemClock.elapsedRealtime();
          for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
              printEvent(localTimeMs, pw, ev);
          }
          mCurrentDefaultNetwork.updateDuration(timeMs);
          // When printing default network events for bug reports, update validation time
          // and refresh the last validation timestmap for future validation time updates.
          if (mIsCurrentlyValid) {
              updateValidationTime(timeMs);
              mLastValidationTimeMs = timeMs;
          }
          printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
      }
  
      public synchronized void listEventsAsProto(PrintWriter pw) {
          for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
              pw.print(IpConnectivityEventBuilder.toProto(ev));
          }
      }
  
      public synchronized void flushEvents(List<IpConnectivityEvent> out) {
          for (DefaultNetworkEvent ev : mEvents) {
              out.add(IpConnectivityEventBuilder.toProto(ev));
          }
          mEvents.clear();
      }
  
      public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
          // Transition from valid to invalid: update validity duration since last update
          if (!isValid && mIsCurrentlyValid) {
              mIsCurrentlyValid = false;
              updateValidationTime(timeMs);
          }
  
          // Transition from invalid to valid: simply mark the validation timestamp.
          if (isValid && !mIsCurrentlyValid) {
              mIsCurrentlyValid = true;
              mLastValidationTimeMs = timeMs;
          }
      }
  
      private void updateValidationTime(long timeMs) {
          mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs;
      }
  
      public synchronized void logDefaultNetworkEvent(
              long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) {
          logCurrentDefaultNetwork(timeMs, oldNai);
          newDefaultNetwork(timeMs, newNai);
      }
  
      private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
          if (mIsCurrentlyValid) {
              updateValidationTime(timeMs);
          }
          DefaultNetworkEvent ev = mCurrentDefaultNetwork;
          ev.updateDuration(timeMs);
          ev.previousTransports = mLastTransports;
          // oldNai is null if the system had no default network before the transition.
          if (oldNai != null) {
              // The system acquired a new default network.
              fillLinkInfo(ev, oldNai);
              ev.finalScore = oldNai.getCurrentScore();
          }
          // Only change transport of the previous default network if the event currently logged
          // corresponds to an existing default network, and not to the absence of a default network.
          // This allows to log pairs of transports for successive default networks regardless of
          // whether or not the system experienced a period without any default network.
          if (ev.transports != 0) {
              mLastTransports = ev.transports;
          }
          mEvents.add(ev);
          mEventsLog.append(ev);
      }
  
      private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) {
          DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs);
          ev.durationMs = timeMs;
          // newNai is null if the system has no default network after the transition.
          if (newNai != null) {
              fillLinkInfo(ev, newNai);
              ev.initialScore = newNai.getCurrentScore();
              if (newNai.lastValidated) {
                  mIsCurrentlyValid = true;
                  mLastValidationTimeMs = timeMs;
              }
          } else {
              mIsCurrentlyValid = false;
          }
          mCurrentDefaultNetwork = ev;
      }
  
      private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) {
          LinkProperties lp = nai.linkProperties;
          ev.netId = nai.network().netId;
          ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes());
          ev.ipv4 |= lp.hasIpv4Address() && lp.hasIpv4DefaultRoute();
          ev.ipv6 |= lp.hasGlobalIpv6Address() && lp.hasIpv6DefaultRoute();
      }
  
      private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) {
          long localCreationTimeMs = localTimeMs - ev.durationMs;
          pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev));
      }
  }
  