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