1 /* 2 * Copyright (C) 2020 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 com.android.networkstack.metrics; 18 19 import static android.stats.connectivity.Ipv6ProvisioningMode.IPV6_PROV_MODE_UNKNOWN; 20 21 import android.net.util.Stopwatch; 22 import android.stats.connectivity.DhcpErrorCode; 23 import android.stats.connectivity.DhcpFeature; 24 import android.stats.connectivity.DisconnectCode; 25 import android.stats.connectivity.HostnameTransResult; 26 import android.stats.connectivity.Ipv6ProvisioningMode; 27 28 import com.android.net.module.util.ConnectivityUtils; 29 30 import java.util.HashSet; 31 import java.util.Set; 32 33 /** 34 * Class to record the network IpProvisioning into statsd. 35 * 1. Fill in NetworkIpProvisioningReported proto. 36 * 2. Write the NetworkIpProvisioningReported proto into statsd. 37 * 3. This class is not thread-safe, and should always be accessed from the same thread. 38 * @hide 39 */ 40 41 public class IpProvisioningMetrics { 42 private static final String TAG = IpProvisioningMetrics.class.getSimpleName(); 43 private final NetworkIpProvisioningReported.Builder mStatsBuilder = 44 NetworkIpProvisioningReported.newBuilder(); 45 private final DhcpSession.Builder mDhcpSessionBuilder = DhcpSession.newBuilder(); 46 private final Stopwatch mIpv4Watch = new Stopwatch().start(); 47 private final Stopwatch mIpv6Watch = new Stopwatch().start(); 48 private final Stopwatch mWatch = new Stopwatch().start(); 49 private final Set<DhcpFeature> mDhcpFeatures = new HashSet<DhcpFeature>(); 50 51 // Define a maximum number of the DhcpErrorCode. 52 public static final int MAX_DHCP_ERROR_COUNT = 20; 53 54 /** 55 * reset this all metrics members 56 */ reset()57 public void reset() { 58 mStatsBuilder.clear(); 59 mDhcpSessionBuilder.clear(); 60 mDhcpFeatures.clear(); 61 mIpv4Watch.restart(); 62 mIpv6Watch.restart(); 63 mWatch.restart(); 64 } 65 66 /** 67 * Write the TransportType into mStatsBuilder. 68 * TODO: implement this 69 */ setTransportType()70 public void setTransportType() {} 71 72 /** 73 * Write the IPv4Provisioned latency into mStatsBuilder. 74 */ setIPv4ProvisionedLatencyOnFirstTime(final boolean isIpv4Provisioned)75 public void setIPv4ProvisionedLatencyOnFirstTime(final boolean isIpv4Provisioned) { 76 if (isIpv4Provisioned && !mStatsBuilder.hasIpv4LatencyMicros()) { 77 mStatsBuilder.setIpv4LatencyMicros(ConnectivityUtils.saturatedCast(mIpv4Watch.stop())); 78 } 79 } 80 81 /** 82 * Write the IPv6Provisioned latency into mStatsBuilder. 83 */ setIPv6ProvisionedLatencyOnFirstTime(final boolean isIpv6Provisioned)84 public void setIPv6ProvisionedLatencyOnFirstTime(final boolean isIpv6Provisioned) { 85 if (isIpv6Provisioned && !mStatsBuilder.hasIpv6LatencyMicros()) { 86 mStatsBuilder.setIpv6LatencyMicros(ConnectivityUtils.saturatedCast(mIpv6Watch.stop())); 87 } 88 } 89 90 /** 91 * Write the DhcpFeature proto into mStatsBuilder. 92 */ setDhcpEnabledFeature(final DhcpFeature feature)93 public void setDhcpEnabledFeature(final DhcpFeature feature) { 94 if (feature == DhcpFeature.DF_UNKNOWN) return; 95 mDhcpFeatures.add(feature); 96 } 97 98 /** 99 * Write the DHCPDISCOVER transmission count into DhcpSession. 100 */ incrementCountForDiscover()101 public void incrementCountForDiscover() { 102 mDhcpSessionBuilder.setDiscoverCount(mDhcpSessionBuilder.getDiscoverCount() + 1); 103 } 104 105 /** 106 * Write the DHCPREQUEST transmission count into DhcpSession. 107 */ incrementCountForRequest()108 public void incrementCountForRequest() { 109 mDhcpSessionBuilder.setRequestCount(mDhcpSessionBuilder.getRequestCount() + 1); 110 } 111 112 /** 113 * Write the IPv4 address conflict count into DhcpSession. 114 */ incrementCountForIpConflict()115 public void incrementCountForIpConflict() { 116 mDhcpSessionBuilder.setConflictCount(mDhcpSessionBuilder.getConflictCount() + 1); 117 } 118 119 /** 120 * Write the hostname transliteration result into DhcpSession. 121 */ setHostnameTransinfo(final boolean isOptionEnabled, final boolean transSuccess)122 public void setHostnameTransinfo(final boolean isOptionEnabled, final boolean transSuccess) { 123 mDhcpSessionBuilder.setHtResult(!isOptionEnabled ? HostnameTransResult.HTR_DISABLE : 124 transSuccess ? HostnameTransResult.HTR_SUCCESS : HostnameTransResult.HTR_FAILURE); 125 } 126 dhcpErrorFromNumberSafe(int number)127 private static DhcpErrorCode dhcpErrorFromNumberSafe(int number) { 128 // See DhcpErrorCode.errorCodeWithOption 129 // TODO: add a DhcpErrorCode method to extract the code; 130 // or replace legacy error codes with the new metrics. 131 final DhcpErrorCode error = DhcpErrorCode.forNumber(number & 0xFFFF0000); 132 if (error == null) return DhcpErrorCode.ET_UNKNOWN; 133 return error; 134 } 135 136 /** 137 * write the DHCP error code into DhcpSession. 138 */ addDhcpErrorCode(final int errorCode)139 public void addDhcpErrorCode(final int errorCode) { 140 if (mDhcpSessionBuilder.getErrorCodeCount() >= MAX_DHCP_ERROR_COUNT) return; 141 mDhcpSessionBuilder.addErrorCode(dhcpErrorFromNumberSafe(errorCode)); 142 } 143 144 /** 145 * Write the IP provision disconnect code into DhcpSession. 146 */ setDisconnectCode(final DisconnectCode disconnectCode)147 public void setDisconnectCode(final DisconnectCode disconnectCode) { 148 if (mStatsBuilder.hasDisconnectCode()) return; 149 mStatsBuilder.setDisconnectCode(disconnectCode); 150 } 151 152 /** 153 * Write the IPv6 provisioning mode proto into mStatsBuilder. This API should be called only 154 * once during the provisioning lifetime cycle. 155 */ setIpv6ProvisioningMode(final Ipv6ProvisioningMode mode)156 public void setIpv6ProvisioningMode(final Ipv6ProvisioningMode mode) { 157 if (mode == IPV6_PROV_MODE_UNKNOWN || mStatsBuilder.hasIpv6ProvisioningMode()) return; 158 mStatsBuilder.setIpv6ProvisioningMode(mode); 159 } 160 161 /** 162 * Write the NetworkIpProvisioningReported proto into statsd. 163 */ statsWrite()164 public NetworkIpProvisioningReported statsWrite() { 165 if (!mWatch.isStarted()) return null; 166 for (DhcpFeature feature : mDhcpFeatures) { 167 mDhcpSessionBuilder.addUsedFeatures(feature); 168 } 169 mStatsBuilder.setDhcpSession(mDhcpSessionBuilder); 170 mStatsBuilder.setProvisioningDurationMicros(mWatch.stop()); 171 mStatsBuilder.setRandomNumber((int) (Math.random() * 1000)); 172 final NetworkIpProvisioningReported stats = mStatsBuilder.build(); 173 final byte[] DhcpSession = stats.getDhcpSession().toByteArray(); 174 NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_PROVISIONING_REPORTED, 175 stats.getTransportType().getNumber(), 176 stats.getIpv4LatencyMicros(), 177 stats.getIpv6LatencyMicros(), 178 stats.getProvisioningDurationMicros(), 179 stats.getDisconnectCode().getNumber(), 180 DhcpSession, 181 stats.getRandomNumber(), 182 stats.getIpv6ProvisioningMode().getNumber()); 183 mWatch.reset(); 184 return stats; 185 } 186 } 187