Home | History | Annotate | Download | only in metrics
      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 
     17 package android.net.metrics;
     18 
     19 import android.net.NetworkCapabilities;
     20 
     21 import com.android.internal.util.BitUtils;
     22 import com.android.internal.util.TokenBucket;
     23 
     24 import java.util.StringJoiner;
     25 
     26 /**
     27  * A class accumulating network metrics received from Netd regarding dns queries and
     28  * connect() calls on a given network.
     29  *
     30  * This class also accumulates running sums of dns and connect latency stats and
     31  * error counts for bug report logging.
     32  *
     33  * @hide
     34  */
     35 public class NetworkMetrics {
     36 
     37     private static final int INITIAL_DNS_BATCH_SIZE = 100;
     38     private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
     39 
     40     // The network id of the Android Network.
     41     public final int netId;
     42     // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
     43     public final long transports;
     44     // Accumulated metrics for connect events.
     45     public final ConnectStats connectMetrics;
     46     // Accumulated metrics for dns events.
     47     public final DnsEvent dnsMetrics;
     48     // Running sums of latencies and error counts for connect and dns events.
     49     public final Summary summary;
     50     // Running sums of the most recent latencies and error counts for connect and dns events.
     51     // Starts null until some events are accumulated.
     52     // Allows to collect periodic snapshot of the running summaries for a given network.
     53     public Summary pendingSummary;
     54 
     55     public NetworkMetrics(int netId, long transports, TokenBucket tb) {
     56         this.netId = netId;
     57         this.transports = transports;
     58         this.connectMetrics =
     59                 new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
     60         this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
     61         this.summary = new Summary(netId, transports);
     62     }
     63 
     64     /**
     65      * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
     66      * into the long running Summary statistics of this NetworkMetrics, and also clear them.
     67      */
     68     public Summary getPendingStats() {
     69         Summary s = pendingSummary;
     70         pendingSummary = null;
     71         if (s != null) {
     72             summary.merge(s);
     73         }
     74         return s;
     75     }
     76 
     77     /** Accumulate a dns query result reported by netd. */
     78     public void addDnsResult(int eventType, int returnCode, int latencyMs) {
     79         if (pendingSummary == null) {
     80             pendingSummary = new Summary(netId, transports);
     81         }
     82         boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
     83         pendingSummary.dnsLatencies.count(latencyMs);
     84         pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
     85     }
     86 
     87     /** Accumulate a connect query result reported by netd. */
     88     public void addConnectResult(int error, int latencyMs, String ipAddr) {
     89         if (pendingSummary == null) {
     90             pendingSummary = new Summary(netId, transports);
     91         }
     92         boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
     93         pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
     94         if (ConnectStats.isNonBlocking(error)) {
     95             pendingSummary.connectLatencies.count(latencyMs);
     96         }
     97     }
     98 
     99     /** Accumulate a single netd sock_diag poll result reported by netd. */
    100     public void addTcpStatsResult(int sent, int lost, int rttUs, int sentAckDiffMs) {
    101         if (pendingSummary == null) {
    102             pendingSummary = new Summary(netId, transports);
    103         }
    104         pendingSummary.tcpLossRate.count(lost, sent);
    105         pendingSummary.roundTripTimeUs.count(rttUs);
    106         pendingSummary.sentAckTimeDiffenceMs.count(sentAckDiffMs);
    107     }
    108 
    109     /** Represents running sums for dns and connect average error counts and average latencies. */
    110     public static class Summary {
    111 
    112         public final int netId;
    113         public final long transports;
    114         // DNS latencies measured in milliseconds.
    115         public final Metrics dnsLatencies = new Metrics();
    116         // DNS error rate measured in percentage points.
    117         public final Metrics dnsErrorRate = new Metrics();
    118         // Blocking connect latencies measured in milliseconds.
    119         public final Metrics connectLatencies = new Metrics();
    120         // Blocking and non blocking connect error rate measured in percentage points.
    121         public final Metrics connectErrorRate = new Metrics();
    122         // TCP socket packet loss stats collected from Netlink sock_diag.
    123         public final Metrics tcpLossRate = new Metrics();
    124         // TCP averaged microsecond round-trip-time stats collected from Netlink sock_diag.
    125         public final Metrics roundTripTimeUs = new Metrics();
    126         // TCP stats collected from Netlink sock_diag that averages millisecond per-socket
    127         // differences between last packet sent timestamp and last ack received timestamp.
    128         public final Metrics sentAckTimeDiffenceMs = new Metrics();
    129 
    130         public Summary(int netId, long transports) {
    131             this.netId = netId;
    132             this.transports = transports;
    133         }
    134 
    135         void merge(Summary that) {
    136             dnsLatencies.merge(that.dnsLatencies);
    137             dnsErrorRate.merge(that.dnsErrorRate);
    138             connectLatencies.merge(that.connectLatencies);
    139             connectErrorRate.merge(that.connectErrorRate);
    140             tcpLossRate.merge(that.tcpLossRate);
    141         }
    142 
    143         @Override
    144         public String toString() {
    145             StringJoiner j = new StringJoiner(", ", "{", "}");
    146             j.add("netId=" + netId);
    147             for (int t : BitUtils.unpackBits(transports)) {
    148                 j.add(NetworkCapabilities.transportNameOf(t));
    149             }
    150             j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
    151                     (int) dnsLatencies.average(), (int) dnsLatencies.max,
    152                     100 * dnsErrorRate.average(), dnsErrorRate.count));
    153             j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
    154                     (int) connectLatencies.average(), (int) connectLatencies.max,
    155                     100 * connectErrorRate.average(), connectErrorRate.count));
    156             j.add(String.format("tcp avg_loss=%.1f%% total_sent=%d total_lost=%d",
    157                     100 * tcpLossRate.average(), tcpLossRate.count, (int) tcpLossRate.sum));
    158             j.add(String.format("tcp rtt=%dms", (int) (roundTripTimeUs.average() / 1000)));
    159             j.add(String.format("tcp sent-ack_diff=%dms", (int) sentAckTimeDiffenceMs.average()));
    160             return j.toString();
    161         }
    162     }
    163 
    164     /** Tracks a running sum and returns the average of a metric. */
    165     static class Metrics {
    166         public double sum;
    167         public double max = Double.MIN_VALUE;
    168         public int count;
    169 
    170         void merge(Metrics that) {
    171             this.count += that.count;
    172             this.sum += that.sum;
    173             this.max = Math.max(this.max, that.max);
    174         }
    175 
    176         void count(double value) {
    177             count(value, 1);
    178         }
    179 
    180         void count(double value, int subcount) {
    181             count += subcount;
    182             sum += value;
    183             max = Math.max(max, value);
    184         }
    185 
    186         double average() {
    187             double a = sum / (double) count;
    188             if (Double.isNaN(a)) {
    189                 a = 0;
    190             }
    191             return a;
    192         }
    193     }
    194 }
    195