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 
NetworkMetrics(int netId, long transports, TokenBucket tb)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      */
getPendingStats()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. */
addDnsResult(int eventType, int returnCode, int latencyMs)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. */
addConnectResult(int error, int latencyMs, String ipAddr)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. */
addTcpStatsResult(int sent, int lost, int rttUs, int sentAckDiffMs)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 
Summary(int netId, long transports)130         public Summary(int netId, long transports) {
131             this.netId = netId;
132             this.transports = transports;
133         }
134 
merge(Summary that)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
toString()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 
merge(Metrics that)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 
count(double value)176         void count(double value) {
177             count(value, 1);
178         }
179 
count(double value, int subcount)180         void count(double value, int subcount) {
181             count += subcount;
182             sum += value;
183             max = Math.max(max, value);
184         }
185 
average()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