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

Commit f2391e63 authored by Chi Zhang's avatar Chi Zhang
Browse files

Log service state to statsd.

Bug: 161573530
Test: statsd_testdrive, atest FrameworksTelephonyTests
Merged-In: Ic5355bb40a61914a4755cf580f20e55164992847
Change-Id: Ida91f3b6c1372fe7ca74755c38dc6ce1d9d85314
parent 3bb1c983
Loading
Loading
Loading
Loading
+41 −3
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: 13
// Next id: 17
message PersistAtoms {
    /* Aggregated RAT usage during the call. */
    repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1;
@@ -60,6 +60,18 @@ message PersistAtoms {

    /* Timestamp of last data_call_session pull. */
    optional int64 data_call_session_pull_timestamp_millis = 12;

    /* Duration spent in each possible service state. */
    repeated CellularServiceState cellular_service_state = 13;

    /* Timestamp of last cellular_service_state pull. */
    optional int64 cellular_service_state_pull_timestamp_millis = 14;

    /* Switch count between data RATs. */
    repeated CellularDataServiceSwitch cellular_data_service_switch = 15;

    /* Timestamp of last cellular_data_service_switch pull. */
    optional int64 cellular_data_service_switch_pull_timestamp_millis = 16;
}

// The canonical versions of the following enums live in:
@@ -99,11 +111,10 @@ message VoiceCallSession {
    optional int64 setup_begin_millis = 10001;
}

// Internal use only
message RawVoiceCallRatUsage {
    optional int32 carrier_id = 1;
    optional int32 rat = 2;
    optional int64 total_duration_millis = 3;
    optional int64 total_duration_millis = 3; // Duration needs to be rounded when pulled
    optional int64 call_count = 4;
}

@@ -166,3 +177,30 @@ message DataCallSession {
    optional int64 duration_minutes = 17;
    optional bool ongoing = 18;
}

message CellularServiceState {
    optional int32 voice_rat = 1;
    optional int32 data_rat = 2;
    optional int32 voice_roaming_type = 3;
    optional int32 data_roaming_type = 4;
    optional bool is_endc = 5;
    optional int32 sim_slot_index = 6;
    optional bool is_multi_sim = 7;
    optional int32 carrier_id = 8;
    optional int64 total_time_millis = 9; // Duration needs to be rounded when pulled

    // Internal use only
    optional int64 last_used_millis = 10001;
}

message CellularDataServiceSwitch {
    optional int32 rat_from = 1;
    optional int32 rat_to = 2;
    optional int32 sim_slot_index = 3;
    optional bool is_multi_sim = 4;
    optional int32 carrier_id = 5;
    optional int32 switch_count = 6;

    // Internal use only
    optional int64 last_used_millis = 10001;
}
+19 −0
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ import com.android.internal.telephony.cdnr.CarrierDisplayNameResolver;
import com.android.internal.telephony.dataconnection.DataConnection;
import com.android.internal.telephony.dataconnection.DcTracker;
import com.android.internal.telephony.dataconnection.TransportManager;
import com.android.internal.telephony.metrics.ServiceStateStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -481,6 +482,8 @@ public class ServiceStateTracker extends Handler {
    private static final int MS_PER_HOUR = 60 * 60 * 1000;
    private final NitzStateMachine mNitzState;

    private ServiceStateStats mServiceStateStats;

    /**
     * Holds the last NITZ signal received. Used only for trying to determine an MCC from a CDMA
     * SID.
@@ -647,6 +650,8 @@ public class ServiceStateTracker extends Handler {
        mPhone = phone;
        mCi = ci;

        mServiceStateStats = new ServiceStateStats(mPhone);

        mCdnr = new CarrierDisplayNameResolver(mPhone);

        mEriManager = TelephonyComponentFactory.getInstance().inject(EriManager.class.getName())
@@ -1717,6 +1722,7 @@ public class ServiceStateTracker extends Handler {
                        TelephonyMetrics.getInstance().writeServiceStateChanged(
                                mPhone.getPhoneId(), mSS);
                        mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
                        mServiceStateStats.onServiceStateChanged(mSS);
                    }
                }
                break;
@@ -2301,6 +2307,7 @@ public class ServiceStateTracker extends Handler {
                    TelephonyMetrics.getInstance().writeServiceStateChanged(
                            mPhone.getPhoneId(), mSS);
                    mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
                    mServiceStateStats.onServiceStateChanged(mSS);
                }

                if (mPhone.isPhoneTypeGsm()) {
@@ -3590,6 +3597,7 @@ public class ServiceStateTracker extends Handler {
        if (hasChanged || hasNrStateChanged) {
            TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS);
            mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
            mServiceStateStats.onServiceStateChanged(mSS);
        }

        boolean shouldLogAttachedChange = false;
@@ -5900,6 +5908,17 @@ public class ServiceStateTracker extends Handler {
        tm.setDataNetworkTypeForPhone(mPhone.getPhoneId(), type);
    }

    /** Returns the {@link ServiceStateStats} for the phone tracked. */
    public ServiceStateStats getServiceStateStats() {
        return mServiceStateStats;
    }

    /** Replaces the {@link ServiceStateStats} for testing purposes. */
    @VisibleForTesting
    public void setServiceStateStats(ServiceStateStats serviceStateStats) {
        mServiceStateStats = serviceStateStats;
    }

    /**
     * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
     *
+95 −22
Original line number Diff line number Diff line
@@ -16,12 +16,13 @@

package com.android.internal.telephony.metrics;

import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;

import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
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.INCOMING_SMS;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
@@ -38,6 +39,8 @@ import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
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.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
@@ -97,6 +100,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
        mStorage = new PersistAtomsStorage(context);
        mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
        if (mStatsManager != null) {
            registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY);
            registerAtom(CELLULAR_SERVICE_STATE, POLICY_PULL_DAILY);
            registerAtom(SIM_SLOT_STATE, null);
            registerAtom(SUPPORTED_RADIO_ACCESS_FAMILY, null);
            registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
@@ -130,6 +135,10 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
    @Override
    public int onPullAtom(int atomTag, List<StatsEvent> data) {
        switch (atomTag) {
            case CELLULAR_DATA_SERVICE_SWITCH:
                return pullCellularDataServiceSwitch(data);
            case CELLULAR_SERVICE_STATE:
                return pullCellularServiceState(data);
            case SIM_SLOT_STATE:
                return pullSimSlotState(data);
            case SUPPORTED_RADIO_ACCESS_FAMILY:
@@ -178,16 +187,16 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
    }

    private static int pullSupportedRadioAccessFamily(List<StatsEvent> data) {
        long rafSupported = 0L;
        try {
        Phone[] phones = getPhonesIfAny();
        if (phones.length == 0) {
            return StatsManager.PULL_SKIP;
        }

        // The bitmask is defined in android.telephony.TelephonyManager.NetworkTypeBitMask
        long rafSupported = 0L;
        for (Phone phone : PhoneFactory.getPhones()) {
            rafSupported |= phone.getRadioAccessFamily();
        }
        } catch (IllegalStateException e) {
            // Phones have not been made yet
            return StatsManager.PULL_SKIP;
        }

        StatsEvent e =
                StatsEvent.newBuilder()
@@ -199,21 +208,13 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
    }

    private static int pullCarrierIdTableVersion(List<StatsEvent> data) {
        int version = UNKNOWN_CARRIER_ID_LIST_VERSION;
        // All phones should have the same version of the carrier ID table, so only query
        // the first one.
        try {
            Phone phone = PhoneFactory.getPhone(0);
            if (phone != null) {
                version = phone.getCarrierIdListVersion();
            }
        } catch (IllegalStateException e) {
            // Nothing to do
        }

        if (version == UNKNOWN_CARRIER_ID_LIST_VERSION) {
        Phone[] phones = getPhonesIfAny();
        if (phones.length == 0) {
            return StatsManager.PULL_SKIP;
        } else {
            // All phones should have the same version of the carrier ID table, so only query the
            // first one.
            int version = phones[0].getCarrierIdListVersion();
            data.add(
                    StatsEvent.newBuilder()
                            .setAtomId(CARRIER_ID_TABLE_VERSION).writeInt(version).build());
@@ -291,11 +292,73 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
        }
    }

    private int pullCellularDataServiceSwitch(List<StatsEvent> data) {
        CellularDataServiceSwitch[] persistAtoms =
                mStorage.getCellularDataServiceSwitches(MIN_COOLDOWN_MILLIS);
        if (persistAtoms != null) {
            // list is already shuffled when instances were inserted
            Arrays.stream(persistAtoms)
                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
            return StatsManager.PULL_SUCCESS;
        } else {
            Rlog.w(TAG, "CELLULAR_DATA_SERVICE_SWITCH pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
    }

    private int pullCellularServiceState(List<StatsEvent> data) {
        // Include the latest durations
        for (Phone phone : getPhonesIfAny()) {
            phone.getServiceStateTracker().getServiceStateStats().conclude();
        }

        CellularServiceState[] persistAtoms =
                mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
        if (persistAtoms != null) {
            // list is already shuffled when instances were inserted
            Arrays.stream(persistAtoms)
                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
            return StatsManager.PULL_SUCCESS;
        } else {
            Rlog.w(TAG, "CELLULAR_SERVICE_STATE pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
    }

    /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
    private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
        mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
    }

    private static StatsEvent buildStatsEvent(CellularDataServiceSwitch serviceSwitch) {
        return StatsEvent.newBuilder()
                .setAtomId(CELLULAR_DATA_SERVICE_SWITCH)
                .writeInt(serviceSwitch.ratFrom)
                .writeInt(serviceSwitch.ratTo)
                .writeInt(serviceSwitch.simSlotIndex)
                .writeBoolean(serviceSwitch.isMultiSim)
                .writeInt(serviceSwitch.carrierId)
                .writeInt(serviceSwitch.switchCount)
                .build();
    }

    private static StatsEvent buildStatsEvent(CellularServiceState state) {
        return StatsEvent.newBuilder()
                .setAtomId(CELLULAR_SERVICE_STATE)
                .writeInt(state.voiceRat)
                .writeInt(state.dataRat)
                .writeInt(state.voiceRoamingType)
                .writeInt(state.dataRoamingType)
                .writeBoolean(state.isEndc)
                .writeInt(state.simSlotIndex)
                .writeBoolean(state.isMultiSim)
                .writeInt(state.carrierId)
                .writeInt(
                        (int) (round(
                                state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS))
                .build();
    }

    private static StatsEvent buildStatsEvent(RawVoiceCallRatUsage usage) {
        return StatsEvent.newBuilder()
                .setAtomId(VOICE_CALL_RAT_USAGE)
@@ -402,6 +465,16 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                .build();
    }

    /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
    private static Phone[] getPhonesIfAny() {
        try {
            return PhoneFactory.getPhones();
        } catch (IllegalStateException e) {
            // Phones have not been made yet
            return new Phone[0];
        }
    }

    /** Returns the value rounded to the bucket. */
    private static long round(long value, long bucket) {
        return ((value + bucket / 2) / bucket) * bucket;
+193 −40

File changed.

Preview size limit exceeded, changes collapsed.

+188 −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.annotation.Nullable;
import android.os.SystemClock;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.NetworkType;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
import com.android.telephony.Rlog;

import java.util.concurrent.atomic.AtomicReference;

/** Tracks service state duration and switch metrics for each phone. */
public class ServiceStateStats {
    private static final String TAG = ServiceStateStats.class.getSimpleName();

    private final AtomicReference<TimestampedServiceState> mLastState =
            new AtomicReference<>(new TimestampedServiceState(null, 0L));
    private final Phone mPhone;
    private final PersistAtomsStorage mStorage;

    public ServiceStateStats(Phone phone) {
        mPhone = phone;
        mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();
    }

    /** Finalizes the durations of the current service state segment. */
    public void conclude() {
        final long now = getTimeMillis();
        TimestampedServiceState lastState =
                mLastState.getAndUpdate(
                        state -> new TimestampedServiceState(state.mServiceState, now));
        addServiceState(lastState, now);
    }

    /** Updates the current service state. */
    public void onServiceStateChanged(ServiceState serviceState) {
        final long now = getTimeMillis();
        if (isModemOff(serviceState)) {
            // Finish the duration of last service state and mark modem off
            addServiceState(mLastState.getAndSet(new TimestampedServiceState(null, now)), now);
        } else {
            CellularServiceState newState = new CellularServiceState();
            newState.voiceRat = getVoiceRat(mPhone, serviceState);
            newState.dataRat = getDataRat(serviceState);
            newState.voiceRoamingType = serviceState.getVoiceRoamingType();
            newState.dataRoamingType = serviceState.getDataRoamingType();
            newState.isEndc = isEndc(serviceState);
            newState.simSlotIndex = mPhone.getPhoneId();
            newState.isMultiSim = (SimSlotState.getCurrentState().numActiveSims > 1);
            newState.carrierId = mPhone.getCarrierId();

            TimestampedServiceState prevState =
                    mLastState.getAndSet(new TimestampedServiceState(newState, now));
            addServiceStateAndSwitch(
                    prevState, now, getDataServiceSwitch(prevState.mServiceState, newState));
        }
    }

    private void addServiceState(TimestampedServiceState prevState, long now) {
        addServiceStateAndSwitch(prevState, now, null);
    }

    private void addServiceStateAndSwitch(
            TimestampedServiceState prevState,
            long now,
            @Nullable CellularDataServiceSwitch serviceSwitch) {
        if (prevState.mServiceState == null) {
            // Skip duration when modem is off
            return;
        }
        if (now >= prevState.mTimestamp) {
            CellularServiceState state = copyOf(prevState.mServiceState);
            state.totalTimeMillis = now - prevState.mTimestamp;
            mStorage.addCellularServiceStateAndCellularDataServiceSwitch(state, serviceSwitch);
        } else {
            Rlog.e(TAG, "addServiceState: durationMillis<0");
        }
    }

    @Nullable
    private CellularDataServiceSwitch getDataServiceSwitch(
            @Nullable CellularServiceState prevState, CellularServiceState nextState) {
        // Record switch only if multi-SIM state and carrier ID are the same and data RAT differs.
        if (prevState != null
                && prevState.isMultiSim == nextState.isMultiSim
                && prevState.carrierId == nextState.carrierId
                && prevState.dataRat != nextState.dataRat) {
            CellularDataServiceSwitch serviceSwitch = new CellularDataServiceSwitch();
            serviceSwitch.ratFrom = prevState.dataRat;
            serviceSwitch.ratTo = nextState.dataRat;
            serviceSwitch.isMultiSim = nextState.isMultiSim;
            serviceSwitch.simSlotIndex = nextState.simSlotIndex;
            serviceSwitch.carrierId = nextState.carrierId;
            serviceSwitch.switchCount = 1;
            return serviceSwitch;
        } else {
            return null;
        }
    }

    private static CellularServiceState copyOf(CellularServiceState state) {
        // MessageNano does not support clone, have to copy manually
        CellularServiceState copy = new CellularServiceState();
        copy.voiceRat = state.voiceRat;
        copy.dataRat = state.dataRat;
        copy.voiceRoamingType = state.voiceRoamingType;
        copy.dataRoamingType = state.dataRoamingType;
        copy.isEndc = state.isEndc;
        copy.simSlotIndex = state.simSlotIndex;
        copy.isMultiSim = state.isMultiSim;
        copy.carrierId = state.carrierId;
        copy.totalTimeMillis = state.totalTimeMillis;
        return copy;
    }

    private static boolean isModemOff(ServiceState state) {
        // NOTE: Wifi calls can be made in airplane mode, where voice reg state is POWER_OFF but
        // data reg state is IN_SERVICE. In this case, service state should still be tracked.
        return state.getVoiceRegState() == ServiceState.STATE_POWER_OFF
                && state.getDataRegState() == ServiceState.STATE_POWER_OFF;
    }

    private static @NetworkType int getVoiceRat(Phone phone, ServiceState state) {
        boolean isWifiCall =
                phone.getImsPhone() != null
                        && phone.getImsPhone().isWifiCallingEnabled()
                        && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
        return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType();
    }

    private static @NetworkType int getDataRat(ServiceState state) {
        final NetworkRegistrationInfo wwanRegInfo =
                state.getNetworkRegistrationInfo(
                        NetworkRegistrationInfo.DOMAIN_PS,
                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
        return wwanRegInfo != null
                ? wwanRegInfo.getAccessNetworkTechnology()
                : TelephonyManager.NETWORK_TYPE_UNKNOWN;
    }

    private static boolean isEndc(ServiceState state) {
        if (getDataRat(state) != TelephonyManager.NETWORK_TYPE_LTE) {
            return false;
        }
        int nrState = state.getNrState();
        return nrState == NetworkRegistrationInfo.NR_STATE_CONNECTED
                || nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
    }

    @VisibleForTesting
    protected long getTimeMillis() {
        return SystemClock.elapsedRealtime();
    }

    private static final class TimestampedServiceState {
        private final CellularServiceState mServiceState;
        private final long mTimestamp; // Start time of the service state segment

        TimestampedServiceState(CellularServiceState serviceState, long timestamp) {
            mServiceState = serviceState;
            mTimestamp = timestamp;
        }
    }
}
Loading