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