| /* |
| * 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); |
| } |
| } |
| |
| /** 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(); |
| |
| 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); |
| } |
| |
| @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)); |
| 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++; |
| sum += value; |
| max = Math.max(max, value); |
| } |
| |
| double average() { |
| double a = sum / (double) count; |
| if (Double.isNaN(a)) { |
| a = 0; |
| } |
| return a; |
| } |
| } |
| } |