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