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

Commit f7af0707 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Log metrics for vendor vibrations and sessions" into main

parents 5fff9ca8 70fc7286
Loading
Loading
Loading
Loading
+30 −11
Original line number Diff line number Diff line
@@ -86,6 +86,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    @GuardedBy("mLock")
    private Status mEndStatusRequest;
    @GuardedBy("mLock")
    private boolean mEndedByVendor;
    @GuardedBy("mLock")
    private long mStartTime; // for debugging
    @GuardedBy("mLock")
    private long mEndUptime;
@@ -119,14 +121,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    public void finishSession() {
        // Do not abort session in HAL, wait for ongoing vibration requests to complete.
        // This might take a while to end the session, but it can be aborted by cancelSession.
        requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
        requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
    }

    @Override
    public void cancelSession() {
        // Always abort session in HAL while cancelling it.
        // This might be triggered after finishSession was already called.
        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
                /* isVendorRequest= */ true);
    }

    @Override
@@ -158,7 +161,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    public DebugInfo getDebugInfo() {
        synchronized (mLock) {
            return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
                    mEndUptime, mEndTime, mVibrations);
                    mEndUptime, mEndTime, mEndedByVendor, mVibrations);
        }
    }

@@ -172,13 +175,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    @Override
    public void onCancel() {
        Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
        requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
                /* isVendorRequest= */ true);
    }

    @Override
    public void binderDied() {
        Slog.d(TAG, "Binder died, cancelling vibration session...");
        requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
        requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
                /* isVendorRequest= */ false);
    }

    @Override
@@ -207,7 +212,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
        // All requests to end a session should abort it to stop ongoing vibrations, even if
        // immediate flag is false. Only the #finishSession API will not abort and wait for
        // session vibrations to complete, which might take a long time.
        requestEndSession(status, /* shouldAbort= */ true);
        requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
    }

    @Override
@@ -224,7 +229,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    public void notifySessionCallback() {
        synchronized (mLock) {
            // If end was not requested then the HAL has cancelled the session.
            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
                    /* isVendorRequest= */ false);
            maybeSetStatusToRequestedLocked();
            clearVibrationConductor();
        }
@@ -335,10 +341,10 @@ final class VendorVibrationSession extends IVibrationSession.Stub
        }
    }

    private void requestEndSession(Status status, boolean shouldAbort) {
    private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
        boolean shouldTriggerSessionHook = false;
        synchronized (mLock) {
            maybeSetEndRequestLocked(status);
            maybeSetEndRequestLocked(status, isVendorRequest);
            if (isStarted()) {
                // Always trigger session hook after it has started, in case new request aborts an
                // already finishing session. Wait for HAL callback before actually ending here.
@@ -354,12 +360,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub
    }

    @GuardedBy("mLock")
    private void maybeSetEndRequestLocked(Status status) {
    private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
        if (mEndStatusRequest != null) {
            // End already requested, keep first requested status and time.
            return;
        }
        mEndStatusRequest = status;
        mEndedByVendor = isVendorRequest;
        mEndTime = System.currentTimeMillis();
        mEndUptime = SystemClock.uptimeMillis();
        if (mConductor != null) {
@@ -442,15 +449,18 @@ final class VendorVibrationSession extends IVibrationSession.Stub
        private final long mStartTime;
        private final long mEndTime;
        private final long mDurationMs;
        private final boolean mEndedByVendor;

        DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
                long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
                long startTime, long endUptime, long endTime, boolean endedByVendor,
                List<DebugInfo> vibrations) {
            mStatus = status;
            mCallerInfo = callerInfo;
            mCreateUptime = createUptime;
            mCreateTime = createTime;
            mStartTime = startTime;
            mEndTime = endTime;
            mEndedByVendor = endedByVendor;
            mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
            mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
        }
@@ -478,6 +488,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub

        @Override
        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            if (mStartTime > 0) {
                // Only log sessions that have started.
                statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
                statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
                        mVibrations.size());
                if (!mEndedByVendor) {
                    statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
                }
            }
            for (DebugInfo vibration : mVibrations) {
                vibration.logMetrics(statsLogger);
            }
+6 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -211,6 +212,11 @@ abstract class Vibration {
        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
            statsLogger.writeVibrationReportedAsync(mStatsInfo);
            if (Flags.vendorVibrationEffects()) {
                // Log effect as it was originally requested.
                statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
                        mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
            }
        }

        @Override
+98 −0
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

package com.android.server.vibrator;

import android.annotation.Nullable;
import android.os.CombinedVibration;
import android.os.Handler;
import android.os.Parcel;
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.util.Slog;
import android.view.HapticFeedbackConstants;

@@ -58,6 +62,16 @@ public class VibratorFrameworkStatsLogger {
            "vibrator.value_vibration_adaptive_haptic_scale",
            new Histogram.UniformOptions(20, 0, 2));

    // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
    private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
            "vibrator.value_vibration_vendor_effect_size",
            new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));

    // Session vibration count in [0, ~840) defined by scaled buckets.
    private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
            "vibrator.value_vibration_vendor_session_vibrations",
            new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));

    private final Object mLock = new Object();
    private final Handler mHandler;
    private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@ public class VibratorFrameworkStatsLogger {
            Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
        }
    }

    /** Logs when a vendor vibration session successfully started. */
    public void logVibrationVendorSessionStarted(int uid) {
        Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
    }

    /**
     * Logs when a vendor vibration session is interrupted by the platform.
     *
     * <p>A vendor session is interrupted if it has successfully started and its end was not
     * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
     * the vibrator HAL triggering the session completed callback early.
     */
    public void logVibrationVendorSessionInterrupted(int uid) {
        Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
    }

    /** Logs the number of vibrations requested for a single vendor vibration session. */
    public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
        sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
    }

    /**
     * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
     *
     * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
     * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
     */
    public void logVibrationCountAndSizeIfVendorEffect(int uid,
            @Nullable CombinedVibration vibration) {
        if (vibration == null) {
            return;
        }
        boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
        if (hasVendorEffects) {
            // Increment CombinedVibration with one or more vendor effects only once.
            Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
        }
    }

    private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
        if (vibration instanceof CombinedVibration.Mono mono) {
            if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
                logVibrationVendorEffectSize(uid, effect);
                return true;
            }
            return false;
        }
        if (vibration instanceof CombinedVibration.Stereo stereo) {
            boolean hasVendorEffects = false;
            for (int i = 0; i < stereo.getEffects().size(); i++) {
                if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
                    logVibrationVendorEffectSize(uid, effect);
                    hasVendorEffects = true;
                }
            }
            return hasVendorEffects;
        }
        if (vibration instanceof CombinedVibration.Sequential sequential) {
            boolean hasVendorEffects = false;
            for (int i = 0; i < sequential.getEffects().size(); i++) {
                hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
                        sequential.getEffects().get(i));
            }
            return hasVendorEffects;
        }
        // Unknown combined vibration, skip metrics.
        return false;
    }

    private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
        int dataSize;
        Parcel vendorData = Parcel.obtain();
        try {
            // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
            // PersistableBundle creates an XML representation for the data in writeToStream, so it
            // might be larger than the actual data that is transferred between processes.
            effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
            dataSize = vendorData.dataSize();
        } finally {
            vendorData.recycle();
        }
        sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1197,7 +1197,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
                        SystemClock.uptimeMillis(), System.currentTimeMillis(),
                        /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
                        /* vibrations= */ null));
                        /* endedByVendor= */ false, /* vibrations= */ null));
    }

    private void logAndRecordVibration(DebugInfo info) {
+55 −0
Original line number Diff line number Diff line
@@ -2796,6 +2796,12 @@ public class VibratorManagerServiceTest {
        stopAutoDispatcherAndDispatchAll();

        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionStarted(anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
        verify(callback, never()).onStarted(any(IVibrationSession.class));
        verify(callback, never()).onFinishing();
        verify(callback, never()).onFinished(anyInt());
@@ -2817,6 +2823,12 @@ public class VibratorManagerServiceTest {

        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionStarted(anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
        verify(callback, never()).onFinishing();
        verify(callback)
                .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2838,6 +2850,12 @@ public class VibratorManagerServiceTest {

        assertThat(session).isNull();
        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionStarted(anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
    }

    @Test
@@ -2860,6 +2878,12 @@ public class VibratorManagerServiceTest {

        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionStarted(anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
        verify(callback, never()).onFinishing();
        verify(callback, times(2))
                .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2886,6 +2910,12 @@ public class VibratorManagerServiceTest {

        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionStarted(anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
        verify(callback, never()).onStarted(any(IVibrationSession.class));
        verify(callback, never()).onFinishing();
        verify(callback)
@@ -2923,6 +2953,12 @@ public class VibratorManagerServiceTest {
        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
        verify(callback).onFinishing();
        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));

        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
        verify(mVibratorFrameworkStatsLoggerMock)
                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
    }

    @Test
@@ -2949,6 +2985,12 @@ public class VibratorManagerServiceTest {
        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
        verify(callback).onFinishing();
        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));

        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
        verify(mVibratorFrameworkStatsLoggerMock)
                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
        verify(mVibratorFrameworkStatsLoggerMock, never())
                .logVibrationVendorSessionInterrupted(anyInt());
    }

    @Test
@@ -3038,6 +3080,11 @@ public class VibratorManagerServiceTest {
        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
        verify(callback).onFinishing();
        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));

        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
        verify(mVibratorFrameworkStatsLoggerMock)
                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
    }

    @Test
@@ -3340,6 +3387,10 @@ public class VibratorManagerServiceTest {
        assertThat(service.isVibrating(2)).isFalse();
        verify(callback).onFinishing();
        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));

        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
        verify(mVibratorFrameworkStatsLoggerMock)
                .logVibrationVendorSessionVibrations(eq(UID), eq(1));
    }

    @Test
@@ -3391,6 +3442,10 @@ public class VibratorManagerServiceTest {
        assertThat(service.isVibrating(2)).isFalse();
        verify(callback).onFinishing();
        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));

        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
        verify(mVibratorFrameworkStatsLoggerMock)
                .logVibrationVendorSessionVibrations(eq(UID), eq(2));
    }

    @Test