Loading proto/src/persist_atoms.proto +28 −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: 11 // Next id: 13 message PersistAtoms { /* Aggregated RAT usage during the call. */ repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1; Loading Loading @@ -54,6 +54,12 @@ message PersistAtoms { /* Last version of carrier ID table sent. */ optional int32 carrier_id_table_version = 10; /* Data Call session statistics and information. */ repeated DataCallSession data_call_session = 11; /* Timestamp of last data_call_session pull. */ optional int64 data_call_session_pull_timestamp_millis = 12; } // The canonical versions of the following enums live in: Loading Loading @@ -139,3 +145,24 @@ message CarrierIdMismatch { optional string gid1 = 2; optional string spn = 3; } message DataCallSession { optional int32 dimension = 1; optional bool is_multi_sim = 2; optional bool is_esim = 3; optional int32 profile = 4; optional int32 apn_type_bitmask = 5; optional int32 carrier_id = 6; optional bool is_roaming = 7; optional int32 rat_at_end = 8; optional bool oos_at_end = 9; optional int64 rat_switch_count = 10; optional bool is_opportunistic = 11; optional int32 ip_type = 12; optional bool setup_failed = 13; optional int32 failure_cause = 14; optional int32 suggested_retry_millis = 15; optional int32 deactivate_reason = 16; optional int64 duration_minutes = 17; optional bool ongoing = 18; } src/java/com/android/internal/telephony/dataconnection/DataConnection.java +29 −1 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType; import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType; import com.android.internal.telephony.metrics.DataCallSessionStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.TelephonyProto.RilDataCall; import com.android.internal.util.AsyncChannel; Loading Loading @@ -190,6 +191,9 @@ public class DataConnection extends StateMachine { private int[] mAdministratorUids = new int[0]; // stats per data call private DataCallSessionStats mDataCallSessionStats; /** * Used internally for saving connecting parameters. */ Loading Loading @@ -663,6 +667,7 @@ public class DataConnection extends StateMachine { mCid = -1; mDataRegState = mPhone.getServiceState().getDataRegistrationState(); mIsSuspended = false; mDataCallSessionStats = new DataCallSessionStats(mPhone); int networkType = getNetworkType(); mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType); Loading Loading @@ -866,6 +871,7 @@ public class DataConnection extends StateMachine { if (apnContext != null) apnContext.requestLog(str); mDataServiceManager.deactivateDataCall(mCid, discReason, obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o)); mDataCallSessionStats.setDeactivateDataCallReason(discReason); } private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) { Loading Loading @@ -1802,6 +1808,8 @@ public class DataConnection extends StateMachine { if (DBG) log("DcDefaultState EVENT_TEAR_DOWN_NOW"); mDataServiceManager.deactivateDataCall(mCid, DataService.REQUEST_REASON_NORMAL, null); mDataCallSessionStats.setDeactivateDataCallReason( DataService.REQUEST_REASON_NORMAL); break; case EVENT_LOST_CONNECTION: if (DBG) { Loading @@ -1824,6 +1832,10 @@ public class DataConnection extends StateMachine { + " drs=" + mDataRegState + " mRilRat=" + mRilRat); } // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed if (mRilRat != 0) { mDataCallSessionStats.onRatChanged(mRilRat); } break; default: if (DBG) { Loading Loading @@ -2052,6 +2064,7 @@ public class DataConnection extends StateMachine { .registerCarrierPrivilegesListener( getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null); notifyDataConnectionState(); mDataCallSessionStats.onSetupDataCall(); } @Override public boolean processMessage(Message msg) { Loading Loading @@ -2142,6 +2155,9 @@ public class DataConnection extends StateMachine { throw new RuntimeException("Unknown SetupResult, should not happen"); } retVal = HANDLED; mDataCallSessionStats .onSetupDataCallResponse(dataCallResponse, cp.mRilRat, cp.mProfileId, mApnSetting.getApnTypeBitmask(), mApnSetting.getProtocol()); break; case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED: AsyncResult asyncResult = (AsyncResult) msg.obj; Loading Loading @@ -2289,8 +2305,9 @@ public class DataConnection extends StateMachine { getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null); } notifyDataConnectionState(); int apnBitMask = mApnSetting.getApnTypeBitmask(); TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(), mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.CONNECTED); mCid, apnBitMask, RilDataCall.State.CONNECTED); } @Override Loading Loading @@ -2318,6 +2335,7 @@ public class DataConnection extends StateMachine { TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(), mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.DISCONNECTED); mDataCallSessionStats.onDataCallDisconnected(mCid); mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler()); } Loading Loading @@ -2429,6 +2447,10 @@ public class DataConnection extends StateMachine { mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this); } retVal = HANDLED; // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed if (mRilRat != 0) { mDataCallSessionStats.onRatChanged(mRilRat); } break; } case EVENT_NR_FREQUENCY_CHANGED: Loading Loading @@ -2945,6 +2967,12 @@ public class DataConnection extends StateMachine { } } /** Sets the {@link DataCallSessionStats} mock for this phone ID during unit testing. */ @VisibleForTesting public void setDataCallSessionStats(DataCallSessionStats dataCallSessionStats) { mDataCallSessionStats = dataCallSessionStats; } /** * @return the string for msg.what as our info. */ Loading src/java/com/android/internal/telephony/dataconnection/DcTracker.java +6 −1 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ import com.android.internal.telephony.SubscriptionInfoUpdater; import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType; import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType; import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason; import com.android.internal.telephony.metrics.DataStallRecoveryStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.telephony.util.TelephonyUtils; Loading Loading @@ -329,6 +330,9 @@ public class DcTracker extends Handler { private boolean mNrSaSub6Unmetered = false; private boolean mRoamingUnmetered = false; // stats per data call recovery event private DataStallRecoveryStats mDataStallRecoveryStats; /* List of SubscriptionPlans, updated when initialized and when plans are changed. */ private List<SubscriptionPlan> mSubscriptionPlans = null; Loading Loading @@ -4683,7 +4687,7 @@ public class DcTracker extends Handler { RECOVERY_ACTION_RADIO_RESTART }) @Retention(RetentionPolicy.SOURCE) private @interface RecoveryAction {}; public @interface RecoveryAction {}; private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST = 0; private static final int RECOVERY_ACTION_CLEANUP = 1; private static final int RECOVERY_ACTION_REREGISTER = 2; Loading Loading @@ -4785,6 +4789,7 @@ public class DcTracker extends Handler { mPhone.getPhoneId(), signalStrength); TelephonyMetrics.getInstance().writeDataStallEvent( mPhone.getPhoneId(), recoveryAction); DataStallRecoveryStats.onDataStallEvent(recoveryAction, mPhone); broadcastDataStallDetected(recoveryAction); switch (recoveryAction) { Loading src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; import android.telephony.DataFailCause; import android.telephony.ServiceState; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ProtocolType; import android.telephony.data.DataCallResponse; import android.telephony.data.DataService; import android.telephony.data.DataService.DeactivateDataReason; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.uicc.UiccSlot; import com.android.telephony.Rlog; import java.util.Random; /** Collects data call change events per DcTracker for the pulled atom. */ public class DataCallSessionStats { private static final String TAG = DataCallSessionStats.class.getSimpleName(); private final Phone mPhone; private long mStartTime; DataCallSession mOngoingDataCall; private final PersistAtomsStorage mAtomsStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); private static final Random RANDOM = new Random(); public DataCallSessionStats(Phone phone) { if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { mPhone = phone.getDefaultPhone(); } else { mPhone = phone; } } /** * create a new ongoing atom when data cal is set up */ public synchronized void onSetupDataCall() { // there shouldn't be an ongoing dataCall here, if that's the case, it means that // deactivateDataCall hasn't been processed properly, so we save the previous atom here // and move on. if (mOngoingDataCall != null && mOngoingDataCall.ongoing) { mOngoingDataCall.failureCause = DataFailCause.UNKNOWN; mOngoingDataCall.durationMinutes = convertMillisToMinutes(System.currentTimeMillis() - mStartTime); mOngoingDataCall.ongoing = false; mAtomsStorage.addDataCallSession(mOngoingDataCall); } mOngoingDataCall = getDefaultProto(); } /** * update the ongoing dataCall's atom for data call response event * * @param response setup Data call response * @param radioTechnology The data call RAT * @param profileId Data profile id * @param apnTypeBitmask APN type bitmask * @param protocol Data connection protocol */ public synchronized void onSetupDataCallResponse(DataCallResponse response, @NetworkType int radioTechnology, int profileId, @ApnType int apnTypeBitmask, @ProtocolType int protocol) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no metric will be logged if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } mOngoingDataCall.ratAtEnd = ServiceState.rilRadioTechnologyToAccessNetworkType(radioTechnology); mOngoingDataCall.profile = profileId; mOngoingDataCall.apnTypeBitmask = apnTypeBitmask; mOngoingDataCall.ipType = protocol; mStartTime = System.currentTimeMillis(); if (response != null) { if (response.getCause() == 0) { mOngoingDataCall.failureCause = DataFailCause.NONE; } else { mOngoingDataCall.failureCause = response.getCause(); mOngoingDataCall.setupFailed = true; // set dataCall as inactive mOngoingDataCall.ongoing = false; // store it only if setup has failed mAtomsStorage.addDataCallSession(mOngoingDataCall); } mOngoingDataCall.suggestedRetryMillis = response.getSuggestedRetryTime(); } } /** * update the ongoing dataCall's atom when data call is deactivated * * @param reason Deactivate reason */ public void setDeactivateDataCallReason(@DeactivateDataReason int reason) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no metric will be logged if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } switch (reason) { case DataService.REQUEST_REASON_NORMAL: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; break; case DataService.REQUEST_REASON_SHUTDOWN: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; break; case DataService.REQUEST_REASON_HANDOVER: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; break; default: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; mOngoingDataCall.oosAtEnd = true; } } /** * store the atom, when DataConnection reaches DISCONNECTED state * @param cid Context Id, uniquely identifies the call */ public void onDataCallDisconnected(int cid) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no atom will be saved if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } mOngoingDataCall.carrierId = cid; mOngoingDataCall.ongoing = false; mOngoingDataCall.durationMinutes = convertMillisToMinutes(System.currentTimeMillis() - mStartTime); // store for the data call list event, after DataCall is disconnected and entered into // inactive mode mAtomsStorage.addDataCallSession(mOngoingDataCall); } /** Updates this RAT when it changes. */ public synchronized void onRatChanged(@NetworkType int rat) { // if no data call is initiated, or we have a new data call while the last one has ended // because onRatChanged might be called before onSetupDataCall if (mOngoingDataCall == null || !mOngoingDataCall.ongoing) { mOngoingDataCall = getDefaultProto(); } if (mOngoingDataCall.ratAtEnd != rat) { mOngoingDataCall.ratSwitchCount++; mOngoingDataCall.ratAtEnd = rat; } } private static long convertMillisToMinutes(long millis) { return Math.round(millis / 60000); } /** Creates a proto for a normal {@code DataCallSession} with default values. */ private DataCallSession getDefaultProto() { DataCallSession proto = new DataCallSession(); proto.dimension = RANDOM.nextInt(); proto.isMultiSim = isMultiSim(); proto.isEsim = isEsim(); proto.profile = DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT; proto.apnTypeBitmask = ApnSetting.TYPE_NONE; proto.carrierId = getCarrierId(); proto.isRoaming = getIsRoaming(); proto.oosAtEnd = false; proto.ratSwitchCount = 0L; proto.isOpportunistic = getIsOpportunistic(); proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; proto.setupFailed = false; proto.failureCause = DataFailCause.NONE; proto.suggestedRetryMillis = 0; proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; proto.durationMinutes = 0; proto.ongoing = true; return proto; } private boolean isMultiSim() { return SimSlotState.getCurrentState().numActiveSims > 1; } private boolean isEsim() { UiccController uiccController = UiccController.getInstance(); int slotId = uiccController.getSlotIdFromPhoneId(mPhone.getPhoneId()); UiccSlot slot = uiccController.getUiccSlot(slotId); if (slot != null) { return slot.isEuicc(); } else { // should not happen, but assume we are not using eSIM loge("isEsim: slot %d is null", slotId); return false; } } private boolean getIsRoaming() { ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); ServiceState serviceState = serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; return serviceState != null ? serviceState.getRoaming() : false; } private boolean getIsOpportunistic() { SubscriptionController subController = SubscriptionController.getInstance(); return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false; } private int getCarrierId() { return mPhone.getCarrierId(); } private void loge(String format, Object... args) { Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); } } src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.telephony.Annotation.NetworkType; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.dataconnection.DcTracker; /** Generates metrics related to data stall recovery events per phone ID for the pushed atom. */ public class DataStallRecoveryStats { /** * Create and push new atom when there is a data stall recovery event * * @param recoveryAction Data stall recovery action * @param phone */ public static void onDataStallEvent(@DcTracker.RecoveryAction int recoveryAction, Phone phone) { if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { phone = phone.getDefaultPhone(); } int carrierId = phone.getCarrierId(); int rat = getRat(phone); // the number returned here matches the SignalStrength enum we have int signalStrength = phone.getSignalStrength().getLevel(); boolean isOpportunistic = getIsOpportunistic(phone); boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1; TelephonyStatsLog.write(TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED, carrierId, rat, signalStrength, recoveryAction, isOpportunistic, isMultiSim); } private static @NetworkType int getRat(Phone phone) { ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker(); ServiceState serviceState = serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; return serviceState != null ? serviceState.getVoiceNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN; } private static boolean getIsOpportunistic(Phone phone) { SubscriptionController subController = SubscriptionController.getInstance(); return subController != null ? subController.isOpportunistic(phone.getSubId()) : false; } } Loading
proto/src/persist_atoms.proto +28 −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: 11 // Next id: 13 message PersistAtoms { /* Aggregated RAT usage during the call. */ repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1; Loading Loading @@ -54,6 +54,12 @@ message PersistAtoms { /* Last version of carrier ID table sent. */ optional int32 carrier_id_table_version = 10; /* Data Call session statistics and information. */ repeated DataCallSession data_call_session = 11; /* Timestamp of last data_call_session pull. */ optional int64 data_call_session_pull_timestamp_millis = 12; } // The canonical versions of the following enums live in: Loading Loading @@ -139,3 +145,24 @@ message CarrierIdMismatch { optional string gid1 = 2; optional string spn = 3; } message DataCallSession { optional int32 dimension = 1; optional bool is_multi_sim = 2; optional bool is_esim = 3; optional int32 profile = 4; optional int32 apn_type_bitmask = 5; optional int32 carrier_id = 6; optional bool is_roaming = 7; optional int32 rat_at_end = 8; optional bool oos_at_end = 9; optional int64 rat_switch_count = 10; optional bool is_opportunistic = 11; optional int32 ip_type = 12; optional bool setup_failed = 13; optional int32 failure_cause = 14; optional int32 suggested_retry_millis = 15; optional int32 deactivate_reason = 16; optional int64 duration_minutes = 17; optional bool ongoing = 18; }
src/java/com/android/internal/telephony/dataconnection/DataConnection.java +29 −1 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType; import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType; import com.android.internal.telephony.metrics.DataCallSessionStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.nano.TelephonyProto.RilDataCall; import com.android.internal.util.AsyncChannel; Loading Loading @@ -190,6 +191,9 @@ public class DataConnection extends StateMachine { private int[] mAdministratorUids = new int[0]; // stats per data call private DataCallSessionStats mDataCallSessionStats; /** * Used internally for saving connecting parameters. */ Loading Loading @@ -663,6 +667,7 @@ public class DataConnection extends StateMachine { mCid = -1; mDataRegState = mPhone.getServiceState().getDataRegistrationState(); mIsSuspended = false; mDataCallSessionStats = new DataCallSessionStats(mPhone); int networkType = getNetworkType(); mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType); Loading Loading @@ -866,6 +871,7 @@ public class DataConnection extends StateMachine { if (apnContext != null) apnContext.requestLog(str); mDataServiceManager.deactivateDataCall(mCid, discReason, obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o)); mDataCallSessionStats.setDeactivateDataCallReason(discReason); } private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) { Loading Loading @@ -1802,6 +1808,8 @@ public class DataConnection extends StateMachine { if (DBG) log("DcDefaultState EVENT_TEAR_DOWN_NOW"); mDataServiceManager.deactivateDataCall(mCid, DataService.REQUEST_REASON_NORMAL, null); mDataCallSessionStats.setDeactivateDataCallReason( DataService.REQUEST_REASON_NORMAL); break; case EVENT_LOST_CONNECTION: if (DBG) { Loading @@ -1824,6 +1832,10 @@ public class DataConnection extends StateMachine { + " drs=" + mDataRegState + " mRilRat=" + mRilRat); } // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed if (mRilRat != 0) { mDataCallSessionStats.onRatChanged(mRilRat); } break; default: if (DBG) { Loading Loading @@ -2052,6 +2064,7 @@ public class DataConnection extends StateMachine { .registerCarrierPrivilegesListener( getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null); notifyDataConnectionState(); mDataCallSessionStats.onSetupDataCall(); } @Override public boolean processMessage(Message msg) { Loading Loading @@ -2142,6 +2155,9 @@ public class DataConnection extends StateMachine { throw new RuntimeException("Unknown SetupResult, should not happen"); } retVal = HANDLED; mDataCallSessionStats .onSetupDataCallResponse(dataCallResponse, cp.mRilRat, cp.mProfileId, mApnSetting.getApnTypeBitmask(), mApnSetting.getProtocol()); break; case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED: AsyncResult asyncResult = (AsyncResult) msg.obj; Loading Loading @@ -2289,8 +2305,9 @@ public class DataConnection extends StateMachine { getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null); } notifyDataConnectionState(); int apnBitMask = mApnSetting.getApnTypeBitmask(); TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(), mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.CONNECTED); mCid, apnBitMask, RilDataCall.State.CONNECTED); } @Override Loading Loading @@ -2318,6 +2335,7 @@ public class DataConnection extends StateMachine { TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(), mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.DISCONNECTED); mDataCallSessionStats.onDataCallDisconnected(mCid); mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler()); } Loading Loading @@ -2429,6 +2447,10 @@ public class DataConnection extends StateMachine { mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this); } retVal = HANDLED; // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed if (mRilRat != 0) { mDataCallSessionStats.onRatChanged(mRilRat); } break; } case EVENT_NR_FREQUENCY_CHANGED: Loading Loading @@ -2945,6 +2967,12 @@ public class DataConnection extends StateMachine { } } /** Sets the {@link DataCallSessionStats} mock for this phone ID during unit testing. */ @VisibleForTesting public void setDataCallSessionStats(DataCallSessionStats dataCallSessionStats) { mDataCallSessionStats = dataCallSessionStats; } /** * @return the string for msg.what as our info. */ Loading
src/java/com/android/internal/telephony/dataconnection/DcTracker.java +6 −1 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ import com.android.internal.telephony.SubscriptionInfoUpdater; import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType; import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType; import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason; import com.android.internal.telephony.metrics.DataStallRecoveryStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.telephony.util.TelephonyUtils; Loading Loading @@ -329,6 +330,9 @@ public class DcTracker extends Handler { private boolean mNrSaSub6Unmetered = false; private boolean mRoamingUnmetered = false; // stats per data call recovery event private DataStallRecoveryStats mDataStallRecoveryStats; /* List of SubscriptionPlans, updated when initialized and when plans are changed. */ private List<SubscriptionPlan> mSubscriptionPlans = null; Loading Loading @@ -4683,7 +4687,7 @@ public class DcTracker extends Handler { RECOVERY_ACTION_RADIO_RESTART }) @Retention(RetentionPolicy.SOURCE) private @interface RecoveryAction {}; public @interface RecoveryAction {}; private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST = 0; private static final int RECOVERY_ACTION_CLEANUP = 1; private static final int RECOVERY_ACTION_REREGISTER = 2; Loading Loading @@ -4785,6 +4789,7 @@ public class DcTracker extends Handler { mPhone.getPhoneId(), signalStrength); TelephonyMetrics.getInstance().writeDataStallEvent( mPhone.getPhoneId(), recoveryAction); DataStallRecoveryStats.onDataStallEvent(recoveryAction, mPhone); broadcastDataStallDetected(recoveryAction); switch (recoveryAction) { Loading
src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; import android.telephony.DataFailCause; import android.telephony.ServiceState; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ProtocolType; import android.telephony.data.DataCallResponse; import android.telephony.data.DataService; import android.telephony.data.DataService.DeactivateDataReason; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.uicc.UiccSlot; import com.android.telephony.Rlog; import java.util.Random; /** Collects data call change events per DcTracker for the pulled atom. */ public class DataCallSessionStats { private static final String TAG = DataCallSessionStats.class.getSimpleName(); private final Phone mPhone; private long mStartTime; DataCallSession mOngoingDataCall; private final PersistAtomsStorage mAtomsStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); private static final Random RANDOM = new Random(); public DataCallSessionStats(Phone phone) { if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { mPhone = phone.getDefaultPhone(); } else { mPhone = phone; } } /** * create a new ongoing atom when data cal is set up */ public synchronized void onSetupDataCall() { // there shouldn't be an ongoing dataCall here, if that's the case, it means that // deactivateDataCall hasn't been processed properly, so we save the previous atom here // and move on. if (mOngoingDataCall != null && mOngoingDataCall.ongoing) { mOngoingDataCall.failureCause = DataFailCause.UNKNOWN; mOngoingDataCall.durationMinutes = convertMillisToMinutes(System.currentTimeMillis() - mStartTime); mOngoingDataCall.ongoing = false; mAtomsStorage.addDataCallSession(mOngoingDataCall); } mOngoingDataCall = getDefaultProto(); } /** * update the ongoing dataCall's atom for data call response event * * @param response setup Data call response * @param radioTechnology The data call RAT * @param profileId Data profile id * @param apnTypeBitmask APN type bitmask * @param protocol Data connection protocol */ public synchronized void onSetupDataCallResponse(DataCallResponse response, @NetworkType int radioTechnology, int profileId, @ApnType int apnTypeBitmask, @ProtocolType int protocol) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no metric will be logged if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } mOngoingDataCall.ratAtEnd = ServiceState.rilRadioTechnologyToAccessNetworkType(radioTechnology); mOngoingDataCall.profile = profileId; mOngoingDataCall.apnTypeBitmask = apnTypeBitmask; mOngoingDataCall.ipType = protocol; mStartTime = System.currentTimeMillis(); if (response != null) { if (response.getCause() == 0) { mOngoingDataCall.failureCause = DataFailCause.NONE; } else { mOngoingDataCall.failureCause = response.getCause(); mOngoingDataCall.setupFailed = true; // set dataCall as inactive mOngoingDataCall.ongoing = false; // store it only if setup has failed mAtomsStorage.addDataCallSession(mOngoingDataCall); } mOngoingDataCall.suggestedRetryMillis = response.getSuggestedRetryTime(); } } /** * update the ongoing dataCall's atom when data call is deactivated * * @param reason Deactivate reason */ public void setDeactivateDataCallReason(@DeactivateDataReason int reason) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no metric will be logged if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } switch (reason) { case DataService.REQUEST_REASON_NORMAL: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; break; case DataService.REQUEST_REASON_SHUTDOWN: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; break; case DataService.REQUEST_REASON_HANDOVER: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; break; default: mOngoingDataCall.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; mOngoingDataCall.oosAtEnd = true; } } /** * store the atom, when DataConnection reaches DISCONNECTED state * @param cid Context Id, uniquely identifies the call */ public void onDataCallDisconnected(int cid) { // there should've been another call to initiate the atom, // so this method is being called out of order -> no atom will be saved if (mOngoingDataCall == null) { loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); return; } mOngoingDataCall.carrierId = cid; mOngoingDataCall.ongoing = false; mOngoingDataCall.durationMinutes = convertMillisToMinutes(System.currentTimeMillis() - mStartTime); // store for the data call list event, after DataCall is disconnected and entered into // inactive mode mAtomsStorage.addDataCallSession(mOngoingDataCall); } /** Updates this RAT when it changes. */ public synchronized void onRatChanged(@NetworkType int rat) { // if no data call is initiated, or we have a new data call while the last one has ended // because onRatChanged might be called before onSetupDataCall if (mOngoingDataCall == null || !mOngoingDataCall.ongoing) { mOngoingDataCall = getDefaultProto(); } if (mOngoingDataCall.ratAtEnd != rat) { mOngoingDataCall.ratSwitchCount++; mOngoingDataCall.ratAtEnd = rat; } } private static long convertMillisToMinutes(long millis) { return Math.round(millis / 60000); } /** Creates a proto for a normal {@code DataCallSession} with default values. */ private DataCallSession getDefaultProto() { DataCallSession proto = new DataCallSession(); proto.dimension = RANDOM.nextInt(); proto.isMultiSim = isMultiSim(); proto.isEsim = isEsim(); proto.profile = DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT; proto.apnTypeBitmask = ApnSetting.TYPE_NONE; proto.carrierId = getCarrierId(); proto.isRoaming = getIsRoaming(); proto.oosAtEnd = false; proto.ratSwitchCount = 0L; proto.isOpportunistic = getIsOpportunistic(); proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; proto.setupFailed = false; proto.failureCause = DataFailCause.NONE; proto.suggestedRetryMillis = 0; proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; proto.durationMinutes = 0; proto.ongoing = true; return proto; } private boolean isMultiSim() { return SimSlotState.getCurrentState().numActiveSims > 1; } private boolean isEsim() { UiccController uiccController = UiccController.getInstance(); int slotId = uiccController.getSlotIdFromPhoneId(mPhone.getPhoneId()); UiccSlot slot = uiccController.getUiccSlot(slotId); if (slot != null) { return slot.isEuicc(); } else { // should not happen, but assume we are not using eSIM loge("isEsim: slot %d is null", slotId); return false; } } private boolean getIsRoaming() { ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); ServiceState serviceState = serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; return serviceState != null ? serviceState.getRoaming() : false; } private boolean getIsOpportunistic() { SubscriptionController subController = SubscriptionController.getInstance(); return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false; } private int getCarrierId() { return mPhone.getCarrierId(); } private void loge(String format, Object... args) { Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); } }
src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.telephony.Annotation.NetworkType; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.dataconnection.DcTracker; /** Generates metrics related to data stall recovery events per phone ID for the pushed atom. */ public class DataStallRecoveryStats { /** * Create and push new atom when there is a data stall recovery event * * @param recoveryAction Data stall recovery action * @param phone */ public static void onDataStallEvent(@DcTracker.RecoveryAction int recoveryAction, Phone phone) { if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { phone = phone.getDefaultPhone(); } int carrierId = phone.getCarrierId(); int rat = getRat(phone); // the number returned here matches the SignalStrength enum we have int signalStrength = phone.getSignalStrength().getLevel(); boolean isOpportunistic = getIsOpportunistic(phone); boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1; TelephonyStatsLog.write(TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED, carrierId, rat, signalStrength, recoveryAction, isOpportunistic, isMultiSim); } private static @NetworkType int getRat(Phone phone) { ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker(); ServiceState serviceState = serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; return serviceState != null ? serviceState.getVoiceNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN; } private static boolean getIsOpportunistic(Phone phone) { SubscriptionController subController = SubscriptionController.getInstance(); return subController != null ? subController.isOpportunistic(phone.getSubId()) : false; } }