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 android.net.metrics;
  
  import android.net.NetworkCapabilities;
  
  import com.android.internal.util.BitUtils;
  import com.android.internal.util.TokenBucket;
  
  import java.util.StringJoiner;
  
  /**
   * A class accumulating network metrics received from Netd regarding dns queries and
   * connect() calls on a given network.
   *
   * This class also accumulates running sums of dns and connect latency stats and
   * error counts for bug report logging.
   *
   * @hide
   */
  public class NetworkMetrics {
  
      private static final int INITIAL_DNS_BATCH_SIZE = 100;
      private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
  
      // The network id of the Android Network.
      public final int netId;
      // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
      public final long transports;
      // Accumulated metrics for connect events.
      public final ConnectStats connectMetrics;
      // Accumulated metrics for dns events.
      public final DnsEvent dnsMetrics;
      // Running sums of latencies and error counts for connect and dns events.
      public final Summary summary;
      // Running sums of the most recent latencies and error counts for connect and dns events.
      // Starts null until some events are accumulated.
      // Allows to collect periodic snapshot of the running summaries for a given network.
      public Summary pendingSummary;
  
      public NetworkMetrics(int netId, long transports, TokenBucket tb) {
          this.netId = netId;
          this.transports = transports;
          this.connectMetrics =
                  new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
          this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
          this.summary = new Summary(netId, transports);
      }
  
      /**
       * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
       * into the long running Summary statistics of this NetworkMetrics, and also clear them.
       */
      public Summary getPendingStats() {
          Summary s = pendingSummary;
          pendingSummary = null;
          if (s != null) {
              summary.merge(s);
          }
          return s;
      }
  
      /** Accumulate a dns query result reported by netd. */
      public void addDnsResult(int eventType, int returnCode, int latencyMs) {
          if (pendingSummary == null) {
              pendingSummary = new Summary(netId, transports);
          }
          boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
          pendingSummary.dnsLatencies.count(latencyMs);
          pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
      }
  
      /** Accumulate a connect query result reported by netd. */
      public void addConnectResult(int error, int latencyMs, String ipAddr) {
          if (pendingSummary == null) {
              pendingSummary = new Summary(netId, transports);
          }
          boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
          pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
          if (ConnectStats.isNonBlocking(error)) {
              pendingSummary.connectLatencies.count(latencyMs);
          }
      }
  
      /** Accumulate a single netd sock_diag poll result reported by netd. */
      public void addTcpStatsResult(int sent, int lost, int rttUs, int sentAckDiffMs) {
          if (pendingSummary == null) {
              pendingSummary = new Summary(netId, transports);
          }
          pendingSummary.tcpLossRate.count(lost, sent);
          pendingSummary.roundTripTimeUs.count(rttUs);
          pendingSummary.sentAckTimeDiffenceMs.count(sentAckDiffMs);
      }
  
      /** Represents running sums for dns and connect average error counts and average latencies. */
      public static class Summary {
  
          public final int netId;
          public final long transports;
          // DNS latencies measured in milliseconds.
          public final Metrics dnsLatencies = new Metrics();
          // DNS error rate measured in percentage points.
          public final Metrics dnsErrorRate = new Metrics();
          // Blocking connect latencies measured in milliseconds.
          public final Metrics connectLatencies = new Metrics();
          // Blocking and non blocking connect error rate measured in percentage points.
          public final Metrics connectErrorRate = new Metrics();
          // TCP socket packet loss stats collected from Netlink sock_diag.
          public final Metrics tcpLossRate = new Metrics();
          // TCP averaged microsecond round-trip-time stats collected from Netlink sock_diag.
          public final Metrics roundTripTimeUs = new Metrics();
          // TCP stats collected from Netlink sock_diag that averages millisecond per-socket
          // differences between last packet sent timestamp and last ack received timestamp.
          public final Metrics sentAckTimeDiffenceMs = new Metrics();
  
          public Summary(int netId, long transports) {
              this.netId = netId;
              this.transports = transports;
          }
  
          void merge(Summary that) {
              dnsLatencies.merge(that.dnsLatencies);
              dnsErrorRate.merge(that.dnsErrorRate);
              connectLatencies.merge(that.connectLatencies);
              connectErrorRate.merge(that.connectErrorRate);
              tcpLossRate.merge(that.tcpLossRate);
          }
  
          @Override
          public String toString() {
              StringJoiner j = new StringJoiner(", ", "{", "}");
              j.add("netId=" + netId);
              for (int t : BitUtils.unpackBits(transports)) {
                  j.add(NetworkCapabilities.transportNameOf(t));
              }
              j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
                      (int) dnsLatencies.average(), (int) dnsLatencies.max,
                      100 * dnsErrorRate.average(), dnsErrorRate.count));
              j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
                      (int) connectLatencies.average(), (int) connectLatencies.max,
                      100 * connectErrorRate.average(), connectErrorRate.count));
              j.add(String.format("tcp avg_loss=%.1f%% total_sent=%d total_lost=%d",
                      100 * tcpLossRate.average(), tcpLossRate.count, (int) tcpLossRate.sum));
              j.add(String.format("tcp rtt=%dms", (int) (roundTripTimeUs.average() / 1000)));
              j.add(String.format("tcp sent-ack_diff=%dms", (int) sentAckTimeDiffenceMs.average()));
              return j.toString();
          }
      }
  
      /** Tracks a running sum and returns the average of a metric. */
      static class Metrics {
          public double sum;
          public double max = Double.MIN_VALUE;
          public int count;
  
          void merge(Metrics that) {
              this.count += that.count;
              this.sum += that.sum;
              this.max = Math.max(this.max, that.max);
          }
  
          void count(double value) {
              count(value, 1);
          }
  
          void count(double value, int subcount) {
              count += subcount;
              sum += value;
              max = Math.max(max, value);
          }
  
          double average() {
              double a = sum / (double) count;
              if (Double.isNaN(a)) {
                  a = 0;
              }
              return a;
          }
      }
  }
  