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