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

Commit 7e9bc4f8 authored by Sangyun Yun's avatar Sangyun Yun Committed by Android (Google) Code Review
Browse files

Merge "Logs atom for data network validation" into main

parents 9e0f6c66 d048c8bf
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ option java_outer_classname = "PersistAtomsProto";

// Holds atoms to store on persist storage in case of power cycle or process crash.
// NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
// Next id: 70
// Next id: 72
message PersistAtoms {
    /* Aggregated RAT usage during the call. */
    repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -231,6 +231,12 @@ message PersistAtoms {

    /* Timestamp of last satellite_sos_message_recommender pull. */
    optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69;

    /* Data Network Validation statistics and information. */
    repeated DataNetworkValidation data_network_validation = 70;

    /* Timestamp of last data_network_validation pull. */
    optional int64 data_network_validation_pull_timestamp_millis = 71;
}

// The canonical versions of the following enums live in:
@@ -694,3 +700,13 @@ message SatelliteSosMessageRecommender {
    optional int32 recommending_handover_type = 7;
    optional bool is_satellite_allowed_in_current_location = 8;
}

message DataNetworkValidation {
    optional int32 network_type = 1;
    optional int32 apn_type_bitmask = 2;
    optional int32 signal_strength = 3;
    optional int32 validation_result = 4;
    optional int64 elapsed_time_in_millis = 5;
    optional bool handover_attempted = 6;
    optional int32 network_validation_count = 7;
}
+18 −0
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthE
import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.metrics.DataCallSessionStats;
import com.android.internal.telephony.metrics.DataNetworkValidationStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FunctionalUtils;
@@ -552,6 +553,9 @@ public class DataNetwork extends StateMachine {
    /** Metrics of per data network connection. */
    private final DataCallSessionStats mDataCallSessionStats;

    /** Metrics of per data network validation. */
    private final @NonNull DataNetworkValidationStats mDataNetworkValidationStats;

    /**
     * The unique context id assigned by the data service in {@link DataCallResponse#getId()}. One
     * for {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and one for
@@ -992,6 +996,7 @@ public class DataNetwork extends StateMachine {
                mDataNetworkControllerCallback);
        mDataConfigManager = mDataNetworkController.getDataConfigManager();
        mDataCallSessionStats = new DataCallSessionStats(mPhone);
        mDataNetworkValidationStats = new DataNetworkValidationStats(mPhone);
        mDataNetworkCallback = callback;
        mDataProfile = dataProfile;
        if (dataProfile.getTrafficDescriptor() != null) {
@@ -1925,6 +1930,9 @@ public class DataNetwork extends StateMachine {
                if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                    unregisterForWwanEvents();
                }
                // Since NetworkValidation is able to request only in the Connected state,
                // if ever connected, log for onDataNetworkDisconnected.
                mDataNetworkValidationStats.onDataNetworkDisconnected(getDataNetworkType());
            } else {
                mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                        .onSetupDataFailed(DataNetwork.this,
@@ -3527,6 +3535,8 @@ public class DataNetwork extends StateMachine {
                DataService.REQUEST_REASON_HANDOVER, mLinkProperties, mPduSessionId,
                mNetworkSliceInfo, mHandoverDataProfile.getTrafficDescriptor(), true,
                obtainMessage(EVENT_HANDOVER_RESPONSE, retryEntry));

        mDataNetworkValidationStats.onHandoverAttempted();
    }

    /**
@@ -3721,6 +3731,11 @@ public class DataNetwork extends StateMachine {
        // Request validation directly from the data service.
        mDataServiceManagers.get(mTransport).requestNetworkValidation(
                mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE));

        int apnTypeBitmask = mDataProfile.getApnSetting() != null
                ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE;
        mDataNetworkValidationStats.onRequestNetworkValidation(apnTypeBitmask);

        log("handleDataNetworkValidationRequest, network validation requested");
    }

@@ -3767,6 +3782,9 @@ public class DataNetwork extends StateMachine {
                    networkValidationStatus));
            mNetworkValidationStatus = networkValidationStatus;
        }

        mDataNetworkValidationStats.onUpdateNetworkValidationState(
                mNetworkValidationStatus, getDataNetworkType());
    }

    /**
+205 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.internal.telephony.metrics;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.NetworkType;
import android.telephony.PreciseDataConnectionState;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;

/**
 * DataNetworkValidationStats logs the atoms for response after a validation request from the
 * DataNetwork in framework.
 */
public class DataNetworkValidationStats {

    private static final String TAG = DataNetworkValidationStats.class.getSimpleName();

    @NonNull
    private final Phone mPhone;

    @NonNull
    private final PersistAtomsStorage mAtomsStorage =
            PhoneFactory.getMetricsCollector().getAtomsStorage();

    @Nullable
    private DataNetworkValidation mDataNetworkValidation;

    @ElapsedRealtimeLong
    private long mRequestedTimeInMillis;

    /** constructor */
    public DataNetworkValidationStats(@NonNull Phone phone) {
        mPhone = phone;
    }


    /**
     * Create a new ongoing atom when NetworkValidation requested.
     *
     * Create a data network validation proto for a new atom record and write the start time to
     * calculate the elapsed time required.
     *
     * @param apnTypeBitMask APN type bitmask of DataNetwork.
     */
    public void onRequestNetworkValidation(@ApnType int apnTypeBitMask) {
        if (mDataNetworkValidation == null) {
            mDataNetworkValidation = getDefaultProto(apnTypeBitMask);
            mRequestedTimeInMillis = getTimeMillis();
        }
    }

    /** Mark the Handover Attempt field as true if validation was requested */
    public void onHandoverAttempted() {
        if (mDataNetworkValidation != null) {
            mDataNetworkValidation.handoverAttempted = true;
        }
    }

    /**
     * Called when data network is disconnected.
     *
     * Since network validation is based on the data network, validation must also end when the data
     * network is disconnected. At this time, validation has not been completed, save an atom as
     * unspecified. and clear.
     *
     * @param networkType Current Network Type of the Data Network.
     */
    public void onDataNetworkDisconnected(@NetworkType int networkType) {
        // Nothing to do, if never requested validation
        if (mDataNetworkValidation == null) {
            return;
        }

        // Set data for and atom.
        calcElapsedTime();
        mDataNetworkValidation.networkType = networkType;
        mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel();
        mDataNetworkValidation.validationResult = TelephonyStatsLog
                .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED;

        // Store.
        mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation);

        // clear all values.
        clear();
    }

    /**
     * Store an atom by updated state.
     *
     * Called when the validation status is updated, and saves the atom when a failure or success
     * result is received.
     *
     * @param status Data Network Validation Status.
     * @param networkType Current Network Type of the Data Network.
     */
    public void onUpdateNetworkValidationState(
            @PreciseDataConnectionState.NetworkValidationStatus int status,
            @NetworkType int networkType) {
        // Nothing to do, if never requested validation
        if (mDataNetworkValidation == null) {
            return;
        }

        switch (status) {
            // Immediately after requesting validation, these messages may occur. In this case,
            // ignore it and wait for the next update.
            case PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED: // fall-through
            case PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS:
                return;
            // If status is unsupported, NetworkValidation should not be requested initially. logs
            // this for abnormal tracking.
            case PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED:
                mDataNetworkValidation.validationResult = TelephonyStatsLog
                    .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_NOT_SUPPORTED;
                break;
            // Success or failure corresponds to the result, store an atom.
            case PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS:
            case PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE:
                mDataNetworkValidation.validationResult = status;
                break;
        }

        // Set data for and atom.
        calcElapsedTime();
        mDataNetworkValidation.networkType = networkType;
        mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel();

        // Store.
        mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation);

        // clear all values.
        clear();
    }

    /**
     * Calculate the current time required based on when network validation is requested.
     */
    private void calcElapsedTime() {
        if (mDataNetworkValidation != null && mRequestedTimeInMillis != 0) {
            mDataNetworkValidation.elapsedTimeInMillis = getTimeMillis() - mRequestedTimeInMillis;
        }
    }

    /**
     * Returns current time in millis from boot.
     */
    @VisibleForTesting
    @ElapsedRealtimeLong
    protected long getTimeMillis() {
        return SystemClock.elapsedRealtime();
    }

    /**
     * Clear all values.
     */
    private void clear() {
        mDataNetworkValidation = null;
        mRequestedTimeInMillis = 0;
    }


    /** Creates a DataNetworkValidation proto with default values. */
    @NonNull
    private DataNetworkValidation getDefaultProto(@ApnType int apnTypeBitmask) {
        DataNetworkValidation proto = new DataNetworkValidation();
        proto.networkType =
                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_UNKNOWN;
        proto.apnTypeBitmask = apnTypeBitmask;
        proto.signalStrength =
                TelephonyStatsLog
                        .DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
        proto.validationResult =
                TelephonyStatsLog
                        .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED;
        proto.elapsedTimeInMillis = 0;
        proto.handoverAttempted = false;
        proto.networkValidationCount = 1;
        return proto;
    }
}
+30 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_
import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_NETWORK_VALIDATION;
import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES;
import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO;
import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT;
@@ -72,6 +73,7 @@ import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
import com.android.internal.telephony.nano.PersistAtomsProto.EmergencyNumbersInfo;
import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
@@ -226,6 +228,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
            registerAtom(SATELLITE_OUTGOING_DATAGRAM);
            registerAtom(SATELLITE_PROVISION);
            registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER);
            registerAtom(DATA_NETWORK_VALIDATION);
            Rlog.d(TAG, "registered");
        } else {
            Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -320,6 +323,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                return pullSatelliteProvision(data);
            case SATELLITE_SOS_MESSAGE_RECOMMENDER:
                return pullSatelliteSosMessageRecommender(data);
            case DATA_NETWORK_VALIDATION:
                return pullDataNetworkValidation(data);
            default:
                Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                return StatsManager.PULL_SKIP;
@@ -940,6 +945,19 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
        }
    }

    private int pullDataNetworkValidation(@NonNull List<StatsEvent> data) {
        DataNetworkValidation[] dataNetworkValidations =
                mStorage.getDataNetworkValidation(mPowerCorrelatedMinCooldownMillis);
        if (dataNetworkValidations != null) {
            Arrays.stream(dataNetworkValidations)
                    .forEach(d -> data.add(buildStatsEvent(d)));
            return StatsManager.PULL_SUCCESS;
        } else {
            Rlog.w(TAG, "DATA_NETWORK_VALIDATION pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
    }

    /** Registers a pulled atom ID {@code atomId}. */
    private void registerAtom(int atomId) {
        mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -1395,6 +1413,18 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                stats.isSatelliteAllowedInCurrentLocation);
    }

    private static StatsEvent buildStatsEvent(DataNetworkValidation stats) {
        return TelephonyStatsLog.buildStatsEvent(
                DATA_NETWORK_VALIDATION,
                stats.networkType,
                stats.apnTypeBitmask,
                stats.signalStrength,
                stats.validationResult,
                stats.elapsedTimeInMillis,
                stats.handoverAttempted,
                stats.networkValidationCount);
    }

    /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
    static Phone[] getPhonesIfAny() {
        try {
+70 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
@@ -173,6 +174,9 @@ public class PersistAtomsStorage {
    private final int mMaxNumSatelliteStats;
    private final int mMaxNumSatelliteControllerStats = 1;

    /** Maximum number of data network validation to store during pulls. */
    private final int mMaxNumDataNetworkValidation;

    /** Stores persist atoms and persist states of the puller. */
    @VisibleForTesting protected PersistAtoms mAtoms;

@@ -223,6 +227,7 @@ public class PersistAtomsStorage {
            mMaxNumGbaEventStats = 5;
            mMaxOutgoingShortCodeSms = 5;
            mMaxNumSatelliteStats = 5;
            mMaxNumDataNetworkValidation = 5;
        } else {
            mMaxNumVoiceCallSessions = 50;
            mMaxNumSms = 25;
@@ -247,6 +252,7 @@ public class PersistAtomsStorage {
            mMaxNumGbaEventStats = 10;
            mMaxOutgoingShortCodeSms = 10;
            mMaxNumSatelliteStats = 15;
            mMaxNumDataNetworkValidation = 15;
        }

        mAtoms = loadAtomsFromFile();
@@ -791,6 +797,25 @@ public class PersistAtomsStorage {
        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
    }

    /** Adds a data network validation to the storage. */
    public synchronized void addDataNetworkValidation(DataNetworkValidation dataNetworkValidation) {
        DataNetworkValidation existingStats = find(dataNetworkValidation);
        if (existingStats != null) {
            int count = existingStats.networkValidationCount
                    + dataNetworkValidation.networkValidationCount;
            long elapsedTime = ((dataNetworkValidation.elapsedTimeInMillis
                    * dataNetworkValidation.networkValidationCount) + (
                    existingStats.elapsedTimeInMillis * existingStats.networkValidationCount))
                    / count;
            existingStats.networkValidationCount = count;
            existingStats.elapsedTimeInMillis = elapsedTime;
        } else {
            mAtoms.dataNetworkValidation = insertAtRandomPlace(
                    mAtoms.dataNetworkValidation, dataNetworkValidation, mMaxNumDataCallSessions);
        }
        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
    }

    /**
     * Returns and clears the voice call sessions if last pulled longer than {@code
     * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1449,6 +1474,24 @@ public class PersistAtomsStorage {
        }
    }

    /**
     * Returns and clears the data network validation if last pulled longer than {@code
     * minIntervalMillis} ago, otherwise returns {@code null}.
     */
    @Nullable
    public synchronized DataNetworkValidation[] getDataNetworkValidation(long minIntervalMillis) {
        long wallTime = getWallTimeMillis();
        if (wallTime - mAtoms.dataNetworkValidationPullTimestampMillis > minIntervalMillis) {
            mAtoms.dataNetworkValidationPullTimestampMillis = wallTime;
            DataNetworkValidation[] previousDataNetworkValidation = mAtoms.dataNetworkValidation;
            mAtoms.dataNetworkValidation = new DataNetworkValidation[0];
            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
            return previousDataNetworkValidation;
        } else {
            return null;
        }
    }

    /** Saves {@link PersistAtoms} to a file in private storage immediately. */
    public synchronized void flushAtoms() {
        saveAtomsToFile(0);
@@ -1599,6 +1642,12 @@ public class PersistAtomsStorage {
            atoms.satelliteSosMessageRecommender = sanitizeAtoms(
                    atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class,
                    mMaxNumSatelliteStats);
            atoms.dataNetworkValidation =
                    sanitizeAtoms(
                            atoms.dataNetworkValidation,
                            DataNetworkValidation.class,
                            mMaxNumDataNetworkValidation
                    );

            // out of caution, sanitize also the timestamps
            atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1661,6 +1710,8 @@ public class PersistAtomsStorage {
                    sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis);
            atoms.satelliteSosMessageRecommenderPullTimestampMillis =
                    sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis);
            atoms.dataNetworkValidationPullTimestampMillis =
                    sanitizeTimestamp(atoms.dataNetworkValidationPullTimestampMillis);
            return atoms;
        } catch (NoSuchFileException e) {
            Rlog.d(TAG, "PersistAtoms file not found");
@@ -2083,6 +2134,24 @@ public class PersistAtomsStorage {
        return null;
    }

    /**
     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
     * if it does not exist.
     */
    private @Nullable DataNetworkValidation find(DataNetworkValidation key) {
        for (DataNetworkValidation stats : mAtoms.dataNetworkValidation) {
            if (stats.networkType == key.networkType
                    && stats.apnTypeBitmask == key.apnTypeBitmask
                    && stats.signalStrength == key.signalStrength
                    && stats.validationResult == key.validationResult
                    && stats.handoverAttempted == key.handoverAttempted) {
                return stats;
            }
        }
        return null;
    }


    /**
     * Inserts a new element in a random position in an array with a maximum size.
     *
@@ -2339,6 +2408,7 @@ public class PersistAtomsStorage {
        atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
        atoms.satelliteProvisionPullTimestampMillis = currentTime;
        atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
        atoms.dataNetworkValidationPullTimestampMillis = currentTime;

        Rlog.d(TAG, "created new PersistAtoms");
        return atoms;
Loading