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

Commit 735783ee authored by Ryan Zuklie's avatar Ryan Zuklie Committed by Android (Google) Code Review
Browse files

Merge "Add first-class Perfetto wakelock data source." into main

parents 844e023b e76e40d4
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -104,6 +104,16 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T
        mDataSource.stopDoneDataSourceInstance(mInstanceIndex);
    }

    /**
     * Gets the datasource instance for this state with a lock.
     * releaseDataSourceInstanceLocked must be called before this can be called again.
     * @return The data source instance for this state.
     *         Null if the datasource instance no longer exists.
     */
    public DataSourceInstanceType getDataSourceInstanceLocked() {
        return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
    }

    protected byte[][] getAndClearAllPendingTracePackets() {
        byte[][] res = new byte[mTracePackets.size()][];
        for (int i = 0; i < mTracePackets.size(); i++) {
+42 −5
Original line number Diff line number Diff line
@@ -151,6 +151,7 @@ public class Notifier {
    private final Vibrator mVibrator;
    @NonNull private final WakeLockLog mPartialWakeLockLog;
    @NonNull private final WakeLockLog mFullWakeLockLog;
    @Nullable private final WakelockTracer mWakelockTracer;
    private final DisplayManagerInternal mDisplayManagerInternal;

    private final NotifierHandler mHandler;
@@ -256,6 +257,12 @@ public class Notifier {
        mFullWakeLockLog = mInjector.getWakeLockLog(context);
        mPartialWakeLockLog = mInjector.getWakeLockLog(context);

        if (mFlags.isAppWakelockDataSourceEnabled()) {
            mWakelockTracer = mInjector.getWakelockTracer(looper);
        } else {
            mWakelockTracer = null;
        }

        // Initialize interactive state for battery stats.
        try {
            mBatteryStats.noteInteractive(true);
@@ -306,7 +313,8 @@ public class Notifier {
                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                    + ", workSource=" + workSource);
        }
        logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.ACQUIRE);
        logWakelockStateChanged(flags, tag, ownerUid, ownerPid, workSource,
                WakelockEventType.ACQUIRE);
        notifyWakeLockListener(callback, tag, true, ownerUid, ownerPid, flags, workSource,
                packageName, historyTag);
        if (!mFlags.improveWakelockLatency()) {
@@ -411,9 +419,10 @@ public class Notifier {
                        + ", workSource=" + newWorkSource);
            }

            logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.RELEASE);
            logWakelockStateChanged(
                    newFlags, newTag, newOwnerUid, newWorkSource, WakelockEventType.ACQUIRE);
            logWakelockStateChanged(flags, tag, ownerUid, ownerPid, workSource,
                    WakelockEventType.RELEASE);
            logWakelockStateChanged(newFlags, newTag, newOwnerUid, newOwnerPid, newWorkSource,
                    WakelockEventType.ACQUIRE);

            final boolean unimportantForLogging = newOwnerUid == Process.SYSTEM_UID
                    && (newFlags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
@@ -460,7 +469,8 @@ public class Notifier {
                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                    + ", workSource=" + workSource);
        }
        logWakelockStateChanged(flags, tag, ownerUid, workSource, WakelockEventType.RELEASE);
        logWakelockStateChanged(flags, tag, ownerUid, ownerPid, workSource,
                WakelockEventType.RELEASE);
        notifyWakeLockListener(callback, tag, false, ownerUid, ownerPid, flags, workSource,
                packageName, historyTag);
        if (!mFlags.improveWakelockLatency()) {
@@ -1484,8 +1494,15 @@ public class Notifier {
            int flags,
            String tag,
            int ownerUid,
            int ownerPid,
            WorkSource workSource,
            WakelockEventType eventType) {
        if (mWakelockTracer != null) {
            boolean isAcquire = eventType == WakelockEventType.ACQUIRE;
            mWakelockTracer.onWakelockEvent(isAcquire, tag, ownerUid, ownerPid, flags,
                    workSource, mInjector.nanoTime());
        }

        if (mBatteryStatsInternal == null) {
            return;
        }
@@ -1524,11 +1541,21 @@ public class Notifier {
         */
        long currentTimeMillis();

        /**
         * Gets the current time in nanoseconds
         */
        long nanoTime();

        /**
         * Gets the WakeLockLog object
         */
        @NonNull WakeLockLog getWakeLockLog(Context context);

        /**
         * Gets the WakelockTracer object
         */
        @Nullable WakelockTracer getWakelockTracer(Looper looper);

        /**
         * Gets the AppOpsManager system service
         */
@@ -1547,11 +1574,21 @@ public class Notifier {
            return System.currentTimeMillis();
        }

        @Override
        public long nanoTime() {
            return System.nanoTime();
        }

        @Override
        public @NonNull WakeLockLog getWakeLockLog(Context context) {
            return new WakeLockLog(context);
        }

        @Override
        public @Nullable WakelockTracer getWakelockTracer(Looper looper) {
            return new WakelockTracer(looper);
        }

        @Override
        public AppOpsManager getAppOpsManager(Context context) {
            return context.getSystemService(AppOpsManager.class);
+1 −0
Original line number Diff line number Diff line
@@ -8,3 +8,4 @@ wilczynskip@google.com
per-file ThermalManagerService.java=file:/THERMAL_OWNERS
per-file LowPowerStandbyController.java=qingxun@google.com
per-file LowPowerStandbyControllerInternal.java=qingxun@google.com
per-file WakelockTracer.java=file:/PERFORMANCE_OWNERS
+332 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.server.power;

import static android.internal.perfetto.protos.AppWakelockConfig.AppWakelocksConfig.DROP_OWNER_PID;
import static android.internal.perfetto.protos.AppWakelockConfig.AppWakelocksConfig.FILTER_DURATION_BELOW_MS;
import static android.internal.perfetto.protos.AppWakelockConfig.AppWakelocksConfig.WRITE_DELAY_MS;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockBundle.ENCODED_TS;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockBundle.INTERN_ID;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.FLAGS;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.IID;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.OWNER_PID;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.OWNER_UID;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.TAG;
import static android.internal.perfetto.protos.AppWakelockData.AppWakelockInfo.WORK_UID;
import static android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig.APP_WAKELOCKS_CONFIG;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.APP_WAKELOCK_INFO;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.APP_WAKELOCK_BUNDLE;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQUENCE_FLAGS;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_INCREMENTAL_STATE_CLEARED;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_NEEDS_INCREMENTAL_STATE;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;

import android.os.Handler;
import android.os.Looper;
import android.os.WorkSource;
import android.tracing.perfetto.CreateIncrementalStateArgs;
import android.tracing.perfetto.DataSource;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.FlushCallbackArguments;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/** Records wakelock events using the Perfetto SDK. */
final class WakelockTracer
        extends DataSource<WakelockTracer.Instance, Void, WakelockTracer.IncrementalState> {
    private final Handler mHandler;
    private static final long NANOS_PER_MS = 1_000_000L;

    WakelockTracer(Looper looper) {
        this(looper, DataSourceParams.DEFAULTS);
    }

    WakelockTracer(Looper looper, DataSourceParams params) {
        super("android.app_wakelocks");
        mHandler = new Handler(looper);
        if (params != null) {
            register(params);
        }
    }

    /**
     * Creates a new instance of the Wakelock data source.
     *
     * @param stream A ProtoInputStream to read the tracing instance's config.
     * @param instanceIdx The index of the instance being created.
     * @return A new data source instance setup with the provided config.
     */
    @Override
    public Instance createInstance(ProtoInputStream stream, int instanceIdx) {
        return new Instance(this, instanceIdx, stream);
    }

    /**
     * Creates a new incremental state object for the Perfetto data source.
     *
     * @return A new (clean) incremental state instance.
     */
    @Override
    public IncrementalState createIncrementalState(CreateIncrementalStateArgs<Instance> args) {
        return new IncrementalState();
    }

    /**
     * Records a wakelock acquire or release event.
     *
     * Calls to onWakelockEvent are thread-safe. The mutations it applies to the instances
     * are protected by the Perfetto mutex. All other accesses to instance state must be
     * guarded by the Perfetto mutex.
     *
     * Calls to onWakelockEvent should be strictly sequenced (nanoTime should monotonically
     * increase). This is accomplished in Notifier.java by enqueuing the changes on a handler
     * (which is FIFO) while holding the PowerManagerService lock.
     *
     * @param acquired Whether or not this is a acquire or release event.
     * @param tag The wakelock tag provided by the application.
     * @param uid The uid of the process requesting the wakelock.
     * @param pid The pid of the process requesting the wakelock.
     * @param flags The wakelock flag bitmask (such as PowerManager.PARTIAL_WAKE_LOCK).
     * @param ws The Worksource attached to the wakelock (or null if there is none).
     * @param nanoTime The System.nanoTime() when the event occurred.
     */
    public void onWakelockEvent(
            boolean acquired,
            String tag,
            int uid,
            int pid,
            int flags,
            WorkSource ws,
            long nanoTime) {
        trace((ctx) -> {
            try (Instance instance = ctx.getDataSourceInstanceLocked()) {
                if (instance == null) return;
                instance.onWakelockEvent(acquired, tag, uid, pid, flags, ws, nanoTime);
            }
        });
    }

    /** Writes all pending data for the given instance to the Perfetto backend. */
    private void writeInstance(Instance target) {
        trace((ctx) -> {
            try (Instance instance = ctx.getDataSourceInstanceLocked()) {
                if (instance != target) return;
                instance.write(ctx.newTracePacket(), ctx.getIncrementalState(), System.nanoTime());
            }
        });
    }

    /** Wakelock identifies time and event-type independent attributes of a wakelock. */
    private record Wakelock(String tag, int ownerUid, int ownerPid, int flags, int workUid) {}

    /** WakelockEvent records an acquire or release of a wakelock. */
    private record WakelockEvent(Wakelock lock, boolean acquired, long timestampNs) {}

    /** InstanceConfig records the per-instance proto configuration. */
    private record InstanceConfig(long thresholdMs, long delayMs, boolean dropPid) {}

    /** IncrementalState tracks the interned wakelock info already written. */
    public static class IncrementalState {
        // Map from wakelock attributes to interned id.
        public final Map<Wakelock, Integer> iids = new HashMap<Wakelock, Integer>();
    }

    /** Instance corresponds to a single trace data source in a session. */
    public static class Instance extends DataSourceInstance {
        private final InstanceConfig mConfig;
        private final WakelockTracer mParent;

        private ArrayList<WakelockEvent> mEvents = new ArrayList<WakelockEvent>();
        private boolean mHasPendingWrite = false;

        Instance(WakelockTracer dataSource, int index, ProtoInputStream stream) {
            super(dataSource, index);
            mConfig = parseConfig(stream);
            mParent = dataSource;
        }

        @Override
        public void onFlush(FlushCallbackArguments args) {
            mParent.writeInstance(this);
        }

        /** Adds the event to the in-memory buffer. */
        public void onWakelockEvent(
                boolean acquired,
                String tag,
                int uid,
                int pid,
                int flags,
                WorkSource ws,
                long nanoTime) {
            pid = mConfig.dropPid ? -1 : pid;
            int workUid = ws == null ? -1 : ws.getAttributionUid();
            Wakelock lock = new Wakelock(tag, uid, pid, flags, workUid);
            WakelockEvent wle = new WakelockEvent(lock, acquired, nanoTime);

            if (!acquired) {
                // For release events, find the last acquire. If it exists and
                // the duration is less than the cutoff, remove it and don't add
                // the new event. If it is not found, assume that it was already
                // written. mEvents is expected to be sorted, so we only need to
                // look back until the cutoff time.
                long cutoff = nanoTime - (mConfig.thresholdMs * NANOS_PER_MS);
                for (int i = mEvents.size() - 1; i >= 0; i--) {
                    WakelockEvent curr = mEvents.get(i);
                    if (curr.timestampNs < cutoff) break;
                    if (pairsWith(curr, wle)) {
                        mEvents.remove(i);
                        return;
                    }
                }
            }

            mEvents.add(wle);
            scheduleWrite();
        }

        /** Writes pending events to the proto output stream. */
        public void write(ProtoOutputStream stream, IncrementalState state, long nanoTime) {
            ArrayList<WakelockEvent> pending = new ArrayList<WakelockEvent>();

            // Remove events that are ready and add them to pending. Start events are
            // ready only if they're older than the duration threshold. End events
            // are always ready (they aren't added if matching a short wakelock).
            long cutoff = nanoTime - (mConfig.thresholdMs * NANOS_PER_MS);
            mEvents.removeIf((event) -> {
                boolean ready = !event.acquired || event.timestampNs <= cutoff;
                if (ready) pending.add(event);
                return ready;
            });

            mHasPendingWrite = false;
            if (!mEvents.isEmpty()) {
                scheduleWrite();
            }

            writeToPacket(stream, state, pending);
        }

        private void scheduleWrite() {
            if (!mHasPendingWrite) {
                mHasPendingWrite = true;
                mParent.mHandler.postDelayed(
                        () -> {
                            mParent.writeInstance(this);
                        },
                        mConfig.delayMs);
            }
        }

        private static boolean pairsWith(WakelockEvent a, WakelockEvent b) {
            return a.acquired != b.acquired && a.lock.equals(b.lock);
        }

        private static long encodeTs(WakelockEvent e, long referenceTs) {
            return (e.timestampNs - referenceTs) << 1 | (e.acquired ? 1 : 0);
        }

        private static InstanceConfig parseConfig(ProtoInputStream stream) {
            boolean dropPid = false;
            int thresholdMs = 0;
            int delayMs = 5000;

            try {
                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                    if (stream.getFieldNumber() == (int) APP_WAKELOCKS_CONFIG) {
                        final long token = stream.start(APP_WAKELOCKS_CONFIG);
                        while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                            switch (stream.getFieldNumber()) {
                                case (int) FILTER_DURATION_BELOW_MS:
                                    thresholdMs = stream.readInt(FILTER_DURATION_BELOW_MS);
                                    break;
                                case (int) WRITE_DELAY_MS:
                                    delayMs = stream.readInt(WRITE_DELAY_MS);
                                    break;
                                case (int) DROP_OWNER_PID:
                                    dropPid = stream.readBoolean(DROP_OWNER_PID);
                                    break;
                            }
                        }
                        stream.end(token);
                        break;
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException("Failed to read Wakelock DataSource config", e);
            } catch (WireTypeMismatchException e) {
                throw new RuntimeException("Failed to parse Wakelock DataSource config", e);
            }

            return new InstanceConfig(thresholdMs, delayMs, dropPid);
        }

        private static void writeToPacket(
                ProtoOutputStream stream, IncrementalState state, ArrayList<WakelockEvent> events) {
            if (events.isEmpty()) return;

            // Events should be sorted, first event has lowest timestamp.
            long packetTs = events.get(0).timestampNs;

            stream.write(TIMESTAMP, packetTs);
            stream.write(
                    SEQUENCE_FLAGS,
                    state.iids.isEmpty()
                            ? SEQ_INCREMENTAL_STATE_CLEARED
                            : SEQ_NEEDS_INCREMENTAL_STATE);

            int[] iids = new int[events.size()];
            long[] encodedTs = new long[events.size()];
            long internToken = stream.start(INTERNED_DATA);
            for (int i = 0; i < events.size(); i++) {
                WakelockEvent event = events.get(i);
                Wakelock lock = event.lock;

                Integer iid = state.iids.get(lock);
                if (iid == null) {
                    iid = state.iids.size() + 1;
                    state.iids.put(lock, iid);
                    long itemToken = stream.start(APP_WAKELOCK_INFO);
                    stream.write(IID, iid.intValue());
                    stream.write(TAG, lock.tag);
                    stream.write(FLAGS, lock.flags);
                    if (lock.ownerPid >= 0) stream.write(OWNER_PID, lock.ownerPid);
                    if (lock.ownerUid >= 0) stream.write(OWNER_UID, lock.ownerUid);
                    if (lock.workUid >= 0) stream.write(WORK_UID, lock.workUid);
                    stream.end(itemToken);
                }

                iids[i] = iid.intValue();
                encodedTs[i] = encodeTs(event, packetTs);
            }
            stream.end(internToken);

            long bundleToken = stream.start(APP_WAKELOCK_BUNDLE);
            stream.writePackedUInt32(INTERN_ID, iids);
            stream.writePackedUInt64(ENCODED_TS, encodedTs);
            stream.end(bundleToken);
        }
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -68,6 +68,10 @@ public class PowerManagerFlags {
            new FlagState(Flags.FLAG_DISABLE_FROZEN_PROCESS_WAKELOCKS,
                    Flags::disableFrozenProcessWakelocks);

    private final FlagState mEnableAppWakelockDataSource =
            new FlagState(Flags.FLAG_ENABLE_APP_WAKELOCK_DATA_SOURCE,
                    Flags::enableAppWakelockDataSource);

    /** Returns whether early-screen-timeout-detector is enabled on not. */
    public boolean isEarlyScreenTimeoutDetectorEnabled() {
        return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -121,6 +125,13 @@ public class PowerManagerFlags {
        return mDisableFrozenProcessWakelocks.isEnabled();
    }

    /**
     * @return Whether the new Perfetto data source for tracing app wakelocks is enabled
     */
    public boolean isAppWakelockDataSourceEnabled() {
        return mEnableAppWakelockDataSource.isEnabled();
    }

    /**
     * dumps all flagstates
     * @param pw printWriter
@@ -133,6 +144,7 @@ public class PowerManagerFlags {
        pw.println(" " + mMoveWscLoggingToNotifier);
        pw.println(" " + mWakelockAttributionViaWorkchain);
        pw.println(" " + mDisableFrozenProcessWakelocks);
        pw.println(" " + mEnableAppWakelockDataSource);
    }

    private static class FlagState {
Loading