Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 853d1ae5 authored by Chiachang Wang's avatar Chiachang Wang Committed by android-build-merger
Browse files

Merge "Injecting data stall event to statsd"

am: 2fd3ca6f

Change-Id: I744a07a2ee10d287f4b38a1698da24e117938222
parents 1b1553fb 2fd3ca6f
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ java_library {
    static_libs: [
        "netd_aidl_interface-java",
        "networkstack-aidl-interfaces-java",
        "datastallprotosnano",
    ]
}

+209 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.annotation.NonNull;
import android.annotation.Nullable;
import android.net.util.NetworkStackUtils;
import android.net.wifi.WifiInfo;

import com.android.internal.util.HexDump;
import com.android.server.connectivity.nano.CellularData;
import com.android.server.connectivity.nano.DataStallEventProto;
import com.android.server.connectivity.nano.DnsEvent;
import com.android.server.connectivity.nano.WifiData;

import com.google.protobuf.nano.MessageNano;

import java.util.ArrayList;
import java.util.List;

/**
 * Class to record the stats of detection level information for data stall.
 *
 * @hide
 */
public final class DataStallDetectionStats {
    private static final int UNKNOWN_SIGNAL_STRENGTH = -1;
    @NonNull
    final byte[] mCellularInfo;
    @NonNull
    final byte[] mWifiInfo;
    @NonNull
    final byte[] mDns;
    final int mEvaluationType;
    final int mNetworkType;

    public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi,
                @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType) {
        mCellularInfo = emptyCellDataIfNull(cell);
        mWifiInfo = emptyWifiInfoIfNull(wifi);

        DnsEvent dns = new DnsEvent();
        dns.dnsReturnCode = returnCode;
        dns.dnsTime = dnsTime;
        mDns = MessageNano.toByteArray(dns);
        mEvaluationType = evalType;
        mNetworkType = netType;
    }

    private byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
        if (cell != null) return cell;

        CellularData data  = new CellularData();
        data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_UNKNOWN;
        data.networkMccmnc = "";
        data.simMccmnc = "";
        data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
        return MessageNano.toByteArray(data);
    }

    private byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
        if (wifi != null) return wifi;

        WifiData data = new WifiData();
        data.wifiBand = DataStallEventProto.AP_BAND_UNKNOWN;
        data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
        return MessageNano.toByteArray(data);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("type: ").append(mNetworkType)
          .append(", evaluation type: ")
          .append(mEvaluationType)
          .append(", wifi info: ")
          .append(HexDump.toHexString(mWifiInfo))
          .append(", cell info: ")
          .append(HexDump.toHexString(mCellularInfo))
          .append(", dns: ")
          .append(HexDump.toHexString(mDns));
        return sb.toString();
    }

    /**
     * Utility to create an instance of {@Link DataStallDetectionStats}
     *
     * @hide
     */
    public static class Builder {
        @Nullable
        private byte[] mCellularInfo;
        @Nullable
        private byte[] mWifiInfo;
        @NonNull
        private final List<Integer> mDnsReturnCode = new ArrayList<Integer>();
        @NonNull
        private final List<Long> mDnsTimeStamp = new ArrayList<Long>();
        private int mEvaluationType;
        private int mNetworkType;

        /**
         * Add a dns event into Builder.
         *
         * @param code the return code of the dns event.
         * @param timeMs the elapsedRealtime in ms that the the dns event was received from netd.
         * @return {@code this} {@link Builder} instance.
         */
        public Builder addDnsEvent(int code, long timeMs) {
            mDnsReturnCode.add(code);
            mDnsTimeStamp.add(timeMs);
            return this;
        }

        /**
         * Set the dns evaluation type into Builder.
         *
         * @param type the return code of the dns event.
         * @return {@code this} {@link Builder} instance.
         */
        public Builder setEvaluationType(int type) {
            mEvaluationType = type;
            return this;
        }

        /**
         * Set the network type into Builder.
         *
         * @param type the network type of the logged network.
         * @return {@code this} {@link Builder} instance.
         */
        public Builder setNetworkType(int type) {
            mNetworkType = type;
            return this;
        }

        /**
         * Set the wifi data into Builder.
         *
         * @param info a {@link WifiInfo} of the connected wifi network.
         * @return {@code this} {@link Builder} instance.
         */
        public Builder setWiFiData(@Nullable final WifiInfo info) {
            WifiData data = new WifiData();
            data.wifiBand = getWifiBand(info);
            data.signalStrength = (info != null) ? info.getRssi() : UNKNOWN_SIGNAL_STRENGTH;
            mWifiInfo = MessageNano.toByteArray(data);
            return this;
        }

        private static int getWifiBand(@Nullable final WifiInfo info) {
            if (info != null) {
                int freq = info.getFrequency();
                // Refer to ScanResult.is5GHz() and ScanResult.is24GHz().
                if (freq > 4900 && freq < 5900) {
                    return DataStallEventProto.AP_BAND_5GHZ;
                } else if (freq > 2400 && freq < 2500) {
                    return DataStallEventProto.AP_BAND_2GHZ;
                }
            }
            return DataStallEventProto.AP_BAND_UNKNOWN;
        }

        /**
         * Set the cellular data into Builder.
         *
         * @param radioType the radio technology of the logged cellular network.
         * @param roaming a boolean indicates if logged cellular network is roaming or not.
         * @param networkMccmnc the mccmnc of the camped network.
         * @param simMccmnc the mccmnc of the sim.
         * @return {@code this} {@link Builder} instance.
         */
        public Builder setCellData(int radioType, boolean roaming,
                @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss) {
            CellularData data  = new CellularData();
            data.ratType = radioType;
            data.isRoaming = roaming;
            data.networkMccmnc = networkMccmnc;
            data.simMccmnc = simMccmnc;
            data.signalStrength = ss;
            mCellularInfo = MessageNano.toByteArray(data);
            return this;
        }

        /**
         * Create a new {@Link DataStallDetectionStats}.
         */
        public DataStallDetectionStats build() {
            return new DataStallDetectionStats(mCellularInfo, mWifiInfo,
                    NetworkStackUtils.convertToIntArray(mDnsReturnCode),
                    NetworkStackUtils.convertToLongArray(mDnsTimeStamp),
                    mEvaluationType, mNetworkType);
        }
    }
}
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.annotation.NonNull;
import android.annotation.Nullable;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.util.Log;

import com.android.internal.util.HexDump;
import com.android.server.connectivity.nano.DataStallEventProto;

/**
 * Collection of utilities for data stall metrics.
 *
 * To see if the logs are properly sent to statsd, execute following command.
 *
 * $ adb shell cmd stats print-logs
 * $ adb logcat | grep statsd  OR $ adb logcat -b stats
 *
 * @hide
 */
public class DataStallStatsUtils {
    private static final String TAG = DataStallStatsUtils.class.getSimpleName();
    private static final boolean DBG = false;

    private static int probeResultToEnum(@Nullable final CaptivePortalProbeResult result) {
        if (result == null) return DataStallEventProto.INVALID;

        // TODO: Add partial connectivity support.
        if (result.isSuccessful()) {
            return DataStallEventProto.VALID;
        } else if (result.isPortal()) {
            return DataStallEventProto.PORTAL;
        } else {
            return DataStallEventProto.INVALID;
        }
    }

    /**
     * Write the metric to {@link StatsLog}.
     */
    public static void write(@NonNull final DataStallDetectionStats stats,
            @NonNull final CaptivePortalProbeResult result) {
        int validationResult = probeResultToEnum(result);
        if (DBG) {
            Log.d(TAG, "write: " + stats + " with result: " + validationResult
                    + ", dns: " + HexDump.toHexString(stats.mDns));
        }
        // TODO(b/124613085): Send to Statsd once the public StatsLog API is ready.
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -16,8 +16,11 @@

package android.net.util;

import android.annotation.NonNull;

import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;

/**
 * Collection of utilities for the network stack.
@@ -40,4 +43,26 @@ public class NetworkStackUtils {
        } catch (IOException ignored) {
        }
    }

    /**
     * Returns an int array from the given Integer list.
     */
    public static int[] convertToIntArray(@NonNull List<Integer> list) {
        int[] array = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }

    /**
     * Returns a long array from the given long list.
     */
    public static long[] convertToLongArray(@NonNull List<Long> list) {
        long[] array = new long[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }
}
+79 −3
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
import static android.net.util.NetworkStackUtils.isEmpty;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -50,6 +51,8 @@ import android.net.TrafficStats;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.captiveportal.CaptivePortalProbeSpec;
import android.net.metrics.DataStallDetectionStats;
import android.net.metrics.DataStallStatsUtils;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.metrics.ValidationProbeEvent;
@@ -66,8 +69,10 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.CellSignalStrength;
import android.telephony.NetworkRegistrationState;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -126,6 +131,9 @@ public class NetworkMonitor extends StateMachine {
    private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
    private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
            (1 << DATA_STALL_EVALUATION_TYPE_DNS);
    // Reevaluate it as intending to increase the number. Larger log size may cause statsd
    // log buffer bust and have stats log lost.
    private static final int DEFAULT_DNS_LOG_SIZE = 20;

    enum EvaluationResult {
        VALIDATED(true),
@@ -244,6 +252,7 @@ public class NetworkMonitor extends StateMachine {
    private final ConnectivityManager mCm;
    private final IpConnectivityLog mMetricsLog;
    private final Dependencies mDependencies;
    private final DataStallStatsUtils mDetectionStatsUtils;

    // Configuration values for captive portal detection probes.
    private final String mCaptivePortalUserAgent;
@@ -302,17 +311,19 @@ public class NetworkMonitor extends StateMachine {
    private final int mDataStallEvaluationType;
    private final DnsStallDetector mDnsStallDetector;
    private long mLastProbeTime;
    // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
    private boolean mCollectDataStallMetrics = false;

    public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
            SharedLog validationLog) {
        this(context, cb, network, new IpConnectivityLog(), validationLog,
                Dependencies.DEFAULT);
                Dependencies.DEFAULT, new DataStallStatsUtils());
    }

    @VisibleForTesting
    protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
            IpConnectivityLog logger, SharedLog validationLogs,
            Dependencies deps) {
            Dependencies deps, DataStallStatsUtils detectionStatsUtils) {
        // Add suffix indicating which NetworkMonitor we're talking about.
        super(TAG + "/" + network.toString());

@@ -325,6 +336,7 @@ public class NetworkMonitor extends StateMachine {
        mValidationLogs = validationLogs;
        mCallback = cb;
        mDependencies = deps;
        mDetectionStatsUtils = detectionStatsUtils;
        mNonPrivateDnsBypassNetwork = network;
        mNetwork = deps.getPrivateDnsBypassNetwork(network);
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -656,6 +668,7 @@ public class NetworkMonitor extends StateMachine {
                case EVENT_DNS_NOTIFICATION:
                    mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
                    if (isDataStall()) {
                        mCollectDataStallMetrics = true;
                        validationLog("Suspecting data stall, reevaluate");
                        transitionTo(mEvaluatingState);
                    }
@@ -667,6 +680,65 @@ public class NetworkMonitor extends StateMachine {
        }
    }

    private void writeDataStallStats(@NonNull final CaptivePortalProbeResult result) {
        /*
         * Collect data stall detection level information for each transport type. Collect type
         * specific information for cellular and wifi only currently. Generate
         * DataStallDetectionStats for each transport type. E.g., if a network supports both
         * TRANSPORT_WIFI and TRANSPORT_VPN, two DataStallDetectionStats will be generated.
         */
        final int[] transports = mNetworkCapabilities.getTransportTypes();

        for (int i = 0; i < transports.length; i++) {
            DataStallStatsUtils.write(buildDataStallDetectionStats(transports[i]), result);
        }
        mCollectDataStallMetrics = false;
    }

    @VisibleForTesting
    protected DataStallDetectionStats buildDataStallDetectionStats(int transport) {
        final DataStallDetectionStats.Builder stats = new DataStallDetectionStats.Builder();
        if (VDBG_STALL) log("collectDataStallMetrics: type=" + transport);
        stats.setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
        stats.setNetworkType(transport);
        switch (transport) {
            case NetworkCapabilities.TRANSPORT_WIFI:
                // TODO: Update it if status query in dual wifi is supported.
                final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
                stats.setWiFiData(wifiInfo);
                break;
            case NetworkCapabilities.TRANSPORT_CELLULAR:
                final boolean isRoaming = !mNetworkCapabilities.hasCapability(
                        NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
                final SignalStrength ss = mTelephonyManager.getSignalStrength();
                // TODO(b/120452078): Support multi-sim.
                stats.setCellData(
                        mTelephonyManager.getDataNetworkType(),
                        isRoaming,
                        mTelephonyManager.getNetworkOperator(),
                        mTelephonyManager.getSimOperator(),
                        (ss != null)
                        ? ss.getLevel() : CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
                break;
            default:
                // No transport type specific information for the other types.
                break;
        }
        addDnsEvents(stats);

        return stats.build();
    }

    private void addDnsEvents(@NonNull final DataStallDetectionStats.Builder stats) {
        final int size = mDnsStallDetector.mResultIndices.size();
        for (int i = 1; i <= DEFAULT_DNS_LOG_SIZE && i <= size; i++) {
            final int index = mDnsStallDetector.mResultIndices.indexOf(size - i);
            stats.addDnsEvent(mDnsStallDetector.mDnsEvents[index].mReturnCode,
                    mDnsStallDetector.mDnsEvents[index].mTimeStamp);
        }
    }


    // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
    // is required.  This State takes care to clear the notification upon exit from the State.
    private class MaybeNotifyState extends State {
@@ -972,6 +1044,11 @@ public class NetworkMonitor extends StateMachine {
                    final CaptivePortalProbeResult probeResult =
                            (CaptivePortalProbeResult) message.obj;
                    mLastProbeTime = SystemClock.elapsedRealtime();

                    if (mCollectDataStallMetrics) {
                        writeDataStallStats(probeResult);
                    }

                    if (probeResult.isSuccessful()) {
                        // Transit EvaluatingPrivateDnsState to get to Validated
                        // state (even if no Private DNS validation required).
@@ -1617,7 +1694,6 @@ public class NetworkMonitor extends StateMachine {
     */
    @VisibleForTesting
    protected class DnsStallDetector {
        private static final int DEFAULT_DNS_LOG_SIZE = 50;
        private int mConsecutiveTimeoutCount = 0;
        private int mSize;
        final DnsResult[] mDnsEvents;
Loading