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

Commit c2b05218 authored by Lais Andrade's avatar Lais Andrade
Browse files

Add telemetry express metrics for vibrator controller

Introducing metrics for the vibrator control service and adaptive
haptics scales applied to vibrations.

Bug: 305963219
Test: VibratorControlServiceTest
      VibratorManagerServiceTest
      VibrationThreadTest
Change-Id: Ia578cd6d7c8a905734c23cf8a9fbedbe537b2023
parent a66816b4
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -208,6 +208,7 @@ abstract class Vibration {
     * potentially expensive or resource-linked objects, such as {@link IBinder}.
     */
    static final class DebugInfo {
        final Status mStatus;
        final long mCreateTime;
        final CallerInfo mCallerInfo;
        @Nullable
@@ -220,7 +221,6 @@ abstract class Vibration {
        private final CombinedVibration mOriginalEffect;
        private final int mScaleLevel;
        private final float mAdaptiveScale;
        private final Status mStatus;

        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
                @Nullable CombinedVibration originalEffect, int scaleLevel,
@@ -253,6 +253,10 @@ abstract class Vibration {
                    + ", callerInfo: " + mCallerInfo;
        }

        void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
        }

        /**
         * Write this info in a compact way into given {@link PrintWriter}.
         *
+18 −4
Original line number Diff line number Diff line
@@ -40,8 +40,10 @@ import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -70,6 +72,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {

    private final DeviceAdapter mDeviceAdapter;
    private final VibrationScaler mVibrationScaler;
    private final VibratorFrameworkStatsLogger mStatsLogger;

    // Not guarded by lock because it's mostly used to read immutable fields by this conductor.
    // This is only modified here at the prepareToStart method which always runs at the vibration
@@ -103,14 +106,15 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
    private int mSuccessfulVibratorOnSteps;

    VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
            DeviceAdapter deviceAdapter,
            VibrationScaler vibrationScaler,
            DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
            VibratorFrameworkStatsLogger statsLogger,
            CompletableFuture<Void> requestVibrationParamsFuture,
            VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
        this.mVibration = vib;
        this.vibrationSettings = vibrationSettings;
        this.mDeviceAdapter = deviceAdapter;
        mVibrationScaler = vibrationScaler;
        mStatsLogger = statsLogger;
        mRequestVibrationParamsFuture = requestVibrationParamsFuture;
        this.vibratorManagerHooks = vibratorManagerHooks;
        this.mSignalVibratorsComplete =
@@ -461,9 +465,19 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        }

        try {
            mRequestVibrationParamsFuture.orTimeout(
            mRequestVibrationParamsFuture.get(
                    vibrationSettings.getRequestVibrationParamsTimeoutMs(),
                    TimeUnit.MILLISECONDS).get();
                    TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            if (DEBUG) {
                Slog.d(TAG, "Request for vibration params timed out", e);
            }
            mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
        } catch (CancellationException e) {
            if (DEBUG) {
                Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
                        + " vibrator controller unregistered. Skipping params...", e);
            }
        } catch (Throwable e) {
            Slog.w(TAG, "Failed to retrieve vibration params.", e);
        }
+46 −25
Original line number Diff line number Diff line
@@ -72,19 +72,21 @@ final class VibratorControlService extends IVibratorControlService.Stub {
    private final VibrationParamsRecords mVibrationParamsRecords;
    private final VibratorControllerHolder mVibratorControllerHolder;
    private final VibrationScaler mVibrationScaler;
    private final VibratorFrameworkStatsLogger mStatsLogger;
    private final Object mLock;
    private final int[] mRequestVibrationParamsForUsages;

    @GuardedBy("mLock")
    private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
    @GuardedBy("mLock")
    private IBinder mRequestVibrationParamsToken;
    @Nullable
    private VibrationParamRequest mVibrationParamRequest = null;

    VibratorControlService(Context context,
            VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
            VibrationSettings vibrationSettings, Object lock) {
            VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
            Object lock) {
        mVibratorControllerHolder = vibratorControllerHolder;
        mVibrationScaler = vibrationScaler;
        mStatsLogger = statsLogger;
        mLock = lock;
        mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();

@@ -180,20 +182,25 @@ final class VibratorControlService extends IVibratorControlService.Stub {
        Objects.requireNonNull(requestToken);

        synchronized (mLock) {
            if (mRequestVibrationParamsToken == null) {
            if (mVibrationParamRequest == null) {
                Slog.wtf(TAG,
                        "New vibration params received but no token was cached in the service. "
                                + "New vibration params ignored.");
                mStatsLogger.logVibrationParamResponseIgnored();
                return;
            }

            if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
            if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
                Slog.w(TAG,
                        "New vibration params received but the provided token does not match the "
                                + "cached one. New vibration params ignored.");
                mStatsLogger.logVibrationParamResponseIgnored();
                return;
            }

            long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
            mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);

            updateAdaptiveHapticsScales(result);
            endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
            recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -222,7 +229,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
     */
    @Nullable
    public CompletableFuture<Void> triggerVibrationParamsRequest(
            @VibrationAttributes.Usage int usage, int timeoutInMillis) {
            int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
        synchronized (mLock) {
            IVibratorController vibratorController =
                    mVibratorControllerHolder.getVibratorController();
@@ -241,16 +248,16 @@ final class VibratorControlService extends IVibratorControlService.Stub {

            try {
                endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
                mRequestVibrationParamsFuture = new CompletableFuture<>();
                mRequestVibrationParamsToken = new Binder();
                mVibrationParamRequest = new VibrationParamRequest(uid);
                vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
                        mRequestVibrationParamsToken);
                        mVibrationParamRequest.token);
                return mVibrationParamRequest.future;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to request vibration params.", e);
                endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
            }

            return mRequestVibrationParamsFuture;
            return null;
        }
    }

@@ -276,13 +283,13 @@ final class VibratorControlService extends IVibratorControlService.Stub {
    }

    /**
     * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
     * Returns the binder token which is used to validate
     * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
     */
    @VisibleForTesting
    public IBinder getRequestVibrationParamsToken() {
        synchronized (mLock) {
            return mRequestVibrationParamsToken;
            return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
        }
    }

@@ -293,7 +300,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
        synchronized (mLock) {
            isVibratorControllerRegistered =
                    mVibratorControllerHolder.getVibratorController() != null;
            hasPendingVibrationParamsRequest = mRequestVibrationParamsFuture != null;
            hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
        }

        pw.println("VibratorControlService:");
@@ -329,18 +336,10 @@ final class VibratorControlService extends IVibratorControlService.Stub {
     */
    @GuardedBy("mLock")
    private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
        mRequestVibrationParamsToken = null;
        if (mRequestVibrationParamsFuture == null) {
            return;
        if (mVibrationParamRequest != null) {
            mVibrationParamRequest.endRequest(wasCancelled);
        }

        if (wasCancelled) {
            mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
        } else {
            mRequestVibrationParamsFuture.complete(null);
        }

        mRequestVibrationParamsFuture = null;
        mVibrationParamRequest = null;
    }

    private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
@@ -423,6 +422,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
     * @param scale The scaling factor that should be applied to the vibrations.
     */
    private void updateAdaptiveHapticsScales(int types, float scale) {
        mStatsLogger.logVibrationParamScale(scale);
        for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
            updateOrRemoveAdaptiveHapticsScale(usage, scale);
        }
@@ -504,6 +504,27 @@ final class VibratorControlService extends IVibratorControlService.Stub {
        }
    }

    /** Represents a request for {@link VibrationParam}. */
    private static final class VibrationParamRequest {
        public final CompletableFuture<Void> future = new CompletableFuture<>();
        public final IBinder token = new Binder();
        public final int uid;
        public final long uptimeMs;

        VibrationParamRequest(int uid) {
            this.uid = uid;
            uptimeMs = SystemClock.uptimeMillis();
        }

        public void endRequest(boolean wasCancelled) {
            if (wasCancelled) {
                future.cancel(/* mayInterruptIfRunning= */ true);
            } else {
                future.complete(null);
            }
        }
    }

    /**
     * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
     * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+45 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.expresslog.Counter;
import com.android.modules.expresslog.Histogram;

import java.util.ArrayDeque;
import java.util.Queue;
@@ -40,6 +41,23 @@ public class VibratorFrameworkStatsLogger {
    // Warning about dropping entries after this amount of atoms were dropped by the throttle.
    private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;

    // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
    // Underflow not expected.
    private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
            "vibrator.value_vibration_param_request_latency",
            new Histogram.UniformOptions(20, 0, 100));

    // Scales in [0, 2), with 2 representing overflow scales >= 2.
    // Underflow expected to represent how many times scales were cleared (set to -1).
    private static final Histogram sVibrationParamScaleHistogram = new Histogram(
            "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));

    // Scales in [0, 2), with 2 representing overflow scales >= 2.
    // Underflow not expected.
    private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
            "vibrator.value_vibration_adaptive_haptic_scale",
            new Histogram.UniformOptions(20, 0, 2));

    private final Object mLock = new Object();
    private final Handler mHandler;
    private final long mVibrationReportedLogIntervalMillis;
@@ -140,6 +158,33 @@ public class VibratorFrameworkStatsLogger {
        }
    }

    /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
    public void logVibrationAdaptiveHapticScale(int uid, float scale) {
        if (Float.compare(scale, 1f) != 0) {
            sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
        }
    }

    /** Logs a vibration param scale value received by the vibrator control service. */
    public void logVibrationParamScale(float scale) {
        sVibrationParamScaleHistogram.logSample(scale);
    }

    /** Logs the latency of a successful vibration params request completed before a vibration. */
    public void logVibrationParamRequestLatency(int uid, long latencyMs) {
        sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
    }

    /** Logs a vibration params request timed out before a vibration. */
    public void logVibrationParamRequestTimeout(int uid) {
        Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
    }

    /** Logs when a response received for a vibration params request is ignored by the service. */
    public void logVibrationParamResponseIgnored() {
        Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
    }

    /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
    public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
        boolean isKeyboard;
+13 −19
Original line number Diff line number Diff line
@@ -212,12 +212,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        mContext = context;
        mInjector = injector;
        mHandler = injector.createHandler(Looper.myLooper());
        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);

        mVibrationSettings = new VibrationSettings(mContext, mHandler);
        mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
        mVibratorControlService = new VibratorControlService(mContext,
                injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
                mLock);
                mFrameworkStatsLogger, mLock);
        mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);

        VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -235,7 +236,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);

        mBatteryStatsService = injector.getBatteryStatsService();
        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);

        mAppOps = mContext.getSystemService(AppOpsManager.class);

@@ -853,9 +853,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
            boolean shouldWriteStats) {
        vib.end(vibrationEndInfo);
        logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
                vibrationEndInfo.status);
        mVibratorManagerRecords.record(vib);
        logAndRecordVibration(vib.getDebugInfo());
        if (shouldWriteStats) {
            mFrameworkStatsLogger.writeVibrationReportedAsync(
                    vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
@@ -866,9 +864,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
            Vibration.EndInfo vibrationEndInfo) {
        vib.end(vibrationEndInfo);
        logVibrationStatus(vib.externalVibration.getUid(),
                vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
        mVibratorManagerRecords.record(vib);
        logAndRecordVibration(vib.getDebugInfo());
        mFrameworkStatsLogger.writeVibrationReportedAsync(
                vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
    }
@@ -882,12 +878,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                vib.callerInfo.attrs.getUsage())) {
            requestVibrationParamsFuture =
                    mVibratorControlService.triggerVibrationParamsRequest(
                            vib.callerInfo.attrs.getUsage(),
                            vib.callerInfo.uid, vib.callerInfo.attrs.getUsage(),
                            mVibrationSettings.getRequestVibrationParamsTimeoutMs());
        }

        return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
                requestVibrationParamsFuture, mVibrationThreadCallbacks);
                mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
    }

    private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
@@ -903,6 +899,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
    }

    private void logAndRecordVibration(Vibration.DebugInfo info) {
        info.logMetrics(mFrameworkStatsLogger);
        logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
        mVibratorManagerRecords.record(info);
    }

    private void logVibrationStatus(int uid, VibrationAttributes attrs,
            Vibration.Status status) {
        switch (status) {
@@ -1752,15 +1754,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
        }

        synchronized void record(HalVibration vib) {
            record(vib.getDebugInfo());
        }

        synchronized void record(ExternalVibrationHolder vib) {
            record(vib.getDebugInfo());
        }

        private synchronized void record(Vibration.DebugInfo info) {
        synchronized void record(Vibration.DebugInfo info) {
            GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
                    mRecentVibrations.add(new VibrationRecord(info));
            if (droppedRecord != null) {
Loading