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

Commit 2fd3ca6f authored by Chiachang Wang's avatar Chiachang Wang Committed by Gerrit Code Review
Browse files

Merge "Injecting data stall event to statsd"

parents 1c2c1199 f09e3e30
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