Loading proto/src/persist_atoms.proto +17 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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: Loading Loading @@ -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; } src/java/com/android/internal/telephony/data/DataNetwork.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -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(); } /** Loading Loading @@ -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"); } Loading Loading @@ -3767,6 +3782,9 @@ public class DataNetwork extends StateMachine { networkValidationStatus)); mNetworkValidationStatus = networkValidationStatus; } mDataNetworkValidationStats.onUpdateNetworkValidationState( mNetworkValidationStatus, getDataNetworkType()); } /** Loading src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java 0 → 100644 +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; } } src/java/com/android/internal/telephony/metrics/MetricsCollector.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 { Loading src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +70 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -223,6 +227,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 5; mMaxOutgoingShortCodeSms = 5; mMaxNumSatelliteStats = 5; mMaxNumDataNetworkValidation = 5; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; Loading @@ -247,6 +252,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 10; mMaxOutgoingShortCodeSms = 10; mMaxNumSatelliteStats = 15; mMaxNumDataNetworkValidation = 15; } mAtoms = loadAtomsFromFile(); Loading Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -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 = Loading Loading @@ -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"); Loading Loading @@ -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. * Loading Loading @@ -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 Loading
proto/src/persist_atoms.proto +17 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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: Loading Loading @@ -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; }
src/java/com/android/internal/telephony/data/DataNetwork.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -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(); } /** Loading Loading @@ -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"); } Loading Loading @@ -3767,6 +3782,9 @@ public class DataNetwork extends StateMachine { networkValidationStatus)); mNetworkValidationStatus = networkValidationStatus; } mDataNetworkValidationStats.onUpdateNetworkValidationState( mNetworkValidationStatus, getDataNetworkType()); } /** Loading
src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java 0 → 100644 +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; } }
src/java/com/android/internal/telephony/metrics/MetricsCollector.java +30 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 { Loading
src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +70 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -223,6 +227,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 5; mMaxOutgoingShortCodeSms = 5; mMaxNumSatelliteStats = 5; mMaxNumDataNetworkValidation = 5; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; Loading @@ -247,6 +252,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 10; mMaxOutgoingShortCodeSms = 10; mMaxNumSatelliteStats = 15; mMaxNumDataNetworkValidation = 15; } mAtoms = loadAtomsFromFile(); Loading Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -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 = Loading Loading @@ -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"); Loading Loading @@ -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. * Loading Loading @@ -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