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

Commit 782797c5 authored by Yi Kong's avatar Yi Kong
Browse files

profcollect: Move periodic trace worker to the system server

We used to have the periodic background trace worker in the native side,
and relied on Doze mode to pause the collection when the device is
inactive. This is not very accurate.

Instead, we move the worker to the system server where we know exactly
if the device is interactive, and we can make sure we only trace when
the device is executing workload useful for profiling.

Test: manual
Bug: 381005420
Change-Id: I3c063cb63cb8d54e3e175eeee3118d60ee79714c
parent bc3378bf
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -8909,7 +8909,11 @@
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$PeriodicTraceJobService"
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ReportProcessJobService"
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

+95 −44
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server.profcollect;

import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;

import android.Manifest;
import android.annotation.RequiresPermission;
import android.app.job.JobInfo;
@@ -32,6 +35,7 @@ import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -70,10 +74,11 @@ public final class ProfcollectForwardingService extends SystemService {
    private int mUsageSetting;
    private boolean mUploadEnabled;

    private static boolean sVerityEnforced;
    private boolean mAdbActive;
    static boolean sVerityEnforced;
    static boolean sIsInteractive;
    static boolean sAdbActive;

    private IProfCollectd mIProfcollect;
    private static IProfCollectd sIProfcollect;
    private static ProfcollectForwardingService sSelfService;
    private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());

@@ -86,17 +91,24 @@ public final class ProfcollectForwardingService extends SystemService {
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
            if (ACTION_SCREEN_ON.equals(intent.getAction())) {
                Log.d(LOG_TAG, "Received broadcast that the device became interactive, was "
                        + sIsInteractive);
                sIsInteractive = true;
            } else if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
                Log.d(LOG_TAG, "Received broadcast that the device became noninteractive, was "
                        + sIsInteractive);
                sIsInteractive = false;
            } else if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
                Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
                createAndUploadReport(sSelfService);
            }
            if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
            } else if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
                boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
                if (isADB) {
                    boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
                    Log.d(LOG_TAG, "Received broadcast that ADB became " + connected
                            + ", was " + mAdbActive);
                    mAdbActive = connected;
                            + ", was " + sAdbActive);
                    sAdbActive = connected;
                }
            }
        }
@@ -129,6 +141,8 @@ public final class ProfcollectForwardingService extends SystemService {
            context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);

        final IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_SCREEN_ON);
        filter.addAction(ACTION_SCREEN_OFF);
        filter.addAction(INTENT_UPLOAD_PROFILES);
        filter.addAction(UsbManager.ACTION_USB_STATE);
        context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
@@ -153,14 +167,24 @@ public final class ProfcollectForwardingService extends SystemService {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            UsbManager usbManager = getContext().getSystemService(UsbManager.class);
            if (usbManager == null) {
                mAdbActive = false;
                return;
                sAdbActive = false;
                Log.d(LOG_TAG, "USBManager is not ready");
            } else {
                sAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
                Log.d(LOG_TAG, "ADB is " + sAdbActive + " on system startup");
            }

            PowerManager powerManager = getContext().getSystemService(PowerManager.class);
            if (powerManager == null) {
                sIsInteractive = true;
                Log.d(LOG_TAG, "PowerManager is not ready");
            } else {
                sIsInteractive = powerManager.isInteractive();
                Log.d(LOG_TAG, "Device is interactive " + sIsInteractive + " on system startup");
            }
            mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
            Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup");
        }
        if (phase == PHASE_BOOT_COMPLETED) {
            if (mIProfcollect == null) {
            if (sIProfcollect == null) {
                return;
            }
            BackgroundThread.get().getThreadHandler().post(() -> {
@@ -172,22 +196,22 @@ public final class ProfcollectForwardingService extends SystemService {
    }

    private void registerProviderStatusCallback() {
        if (mIProfcollect == null) {
        if (sIProfcollect == null) {
            return;
        }
        try {
            mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
            sIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage());
        }
    }

    private boolean serviceHasSupportedTraceProvider() {
        if (mIProfcollect == null) {
        if (sIProfcollect == null) {
            return false;
        }
        try {
            return !mIProfcollect.get_supported_provider().isEmpty();
            return !sIProfcollect.get_supported_provider().isEmpty();
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage());
            return false;
@@ -209,7 +233,7 @@ public final class ProfcollectForwardingService extends SystemService {
                    IProfCollectd.Stub.asInterface(
                            ServiceManager.getServiceOrThrow("profcollectd"));
            profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
            mIProfcollect = profcollectd;
            sIProfcollect = profcollectd;
            return true;
        } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
            Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
@@ -233,7 +257,8 @@ public final class ProfcollectForwardingService extends SystemService {
                    break;
                case MESSAGE_REGISTER_SCHEDULERS:
                    registerObservers();
                    ProfcollectBGJobService.schedule(getContext());
                    PeriodicTraceJobService.schedule(getContext());
                    ReportProcessJobService.schedule(getContext());
                    break;
                default:
                    throw new AssertionError("Unknown message: " + message);
@@ -246,27 +271,66 @@ public final class ProfcollectForwardingService extends SystemService {
        public void binderDied() {
            Log.w(LOG_TAG, "profcollectd has died");

            mIProfcollect = null;
            sIProfcollect = null;
            tryConnectNativeService();
        }
    }

    /**
     * Background trace process service.
     * Background report process and upload service.
     */
    public static class ProfcollectBGJobService extends JobService {
        // Unique ID in system service
        private static final int JOB_IDLE_PROCESS = 260817;
    public static class PeriodicTraceJobService extends JobService {
        // Unique ID in system server
        private static final int PERIODIC_TRACE_JOB_ID = 241207;
        private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
                "android",
                ProfcollectBGJobService.class.getName());
                PeriodicTraceJobService.class.getName());

        /**
         * Attach the service to the system job scheduler.
         */
        public static void schedule(Context context) {
            final int interval = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
                    "collection_interval", 600);
            JobScheduler js = context.getSystemService(JobScheduler.class);
            js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
            js.schedule(new JobInfo.Builder(PERIODIC_TRACE_JOB_ID, JOB_SERVICE_NAME)
                    .setPeriodic(TimeUnit.SECONDS.toMillis(interval))
                    // PRIORITY_DEFAULT is the highest priority we can request for a periodic job.
                    .setPriority(JobInfo.PRIORITY_DEFAULT)
                    .build());
        }

        @Override
        public boolean onStartJob(JobParameters params) {
            if (sIProfcollect != null) {
                Utils.traceSystem(sIProfcollect, "periodic");
            }
            jobFinished(params, false);
            return true;
        }

        @Override
        public boolean onStopJob(JobParameters params) {
            return false;
        }
    }

    /**
     * Background report process and upload service.
     */
    public static class ReportProcessJobService extends JobService {
        // Unique ID in system server
        private static final int REPORT_PROCESS_JOB_ID = 260817;
        private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
                "android",
                ReportProcessJobService.class.getName());

        /**
         * Attach the service to the system job scheduler.
         */
        public static void schedule(Context context) {
            JobScheduler js = context.getSystemService(JobScheduler.class);
            js.schedule(new JobInfo.Builder(REPORT_PROCESS_JOB_ID, JOB_SERVICE_NAME)
                    .setRequiresDeviceIdle(true)
                    .setRequiresCharging(true)
                    .setPeriodic(BG_PROCESS_INTERVAL)
@@ -283,7 +347,6 @@ public final class ProfcollectForwardingService extends SystemService {

        @Override
        public boolean onStopJob(JobParameters params) {
            // TODO: Handle this?
            return false;
        }
    }
@@ -311,14 +374,8 @@ public final class ProfcollectForwardingService extends SystemService {
    private class AppLaunchObserver extends ActivityMetricsLaunchObserver {
        @Override
        public void onIntentStarted(Intent intent, long timestampNanos) {
            if (mIProfcollect == null) {
                return;
            }
            if (mAdbActive) {
                return;
            }
            if (Utils.withFrequency("applaunch_trace_freq", 5)) {
                Utils.traceSystem(mIProfcollect, "applaunch");
                Utils.traceSystem(sIProfcollect, "applaunch");
            }
        }
    }
@@ -336,15 +393,9 @@ public final class ProfcollectForwardingService extends SystemService {
    }

    private void traceOnDex2oatStart() {
        if (mIProfcollect == null) {
            return;
        }
        if (mAdbActive) {
            return;
        }
        if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
            // Dex2oat could take a while before it starts. Add a short delay before start tracing.
            Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
            Utils.traceSystem(sIProfcollect, "dex2oat", /* delayMs */ 1000);
        }
    }

@@ -367,12 +418,12 @@ public final class ProfcollectForwardingService extends SystemService {

    private static void createAndUploadReport(ProfcollectForwardingService pfs) {
        BackgroundThread.get().getThreadHandler().post(() -> {
            if (pfs.mIProfcollect == null) {
            if (pfs.sIProfcollect == null) {
                return;
            }
            String reportName;
            try {
                reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
                reportName = pfs.sIProfcollect.report(pfs.mUsageSetting) + ".zip";
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "Failed to create report: " + e.getMessage());
                return;
@@ -411,7 +462,7 @@ public final class ProfcollectForwardingService extends SystemService {
                    return;
                }
                if (Utils.withFrequency("camera_trace_freq", 10)) {
                    Utils.traceProcess(mIProfcollect,
                    Utils.traceProcess(sIProfcollect,
                            "camera",
                            "android.hardware.camera.provider",
                            /* durationMs */ 5000);
+35 −20
Original line number Diff line number Diff line
@@ -28,28 +28,29 @@ import com.android.internal.os.BackgroundThread;
import java.time.Instant;
import java.util.concurrent.ThreadLocalRandom;

public final class Utils {
final class Utils {

    private static Instant lastTraceTime = Instant.EPOCH;
    private static final int TRACE_COOLDOWN_SECONDS = 30;

    public static boolean withFrequency(String configName, int defaultFrequency) {
    static boolean withFrequency(String configName, int defaultFrequency) {
        int threshold = DeviceConfig.getInt(
                DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, configName, defaultFrequency);
        int randomNum = ThreadLocalRandom.current().nextInt(100);
        return randomNum < threshold;
    }

    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName) {
        if (mIProfcollect == null) {
            return false;
        }
        if (isInCooldownOrReset()) {
    /**
     * Request a system-wide trace.
     * Will be ignored if the device does not meet trace criteria or is being rate limited.
     */
    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName) {
        if (!checkPrerequisites(iprofcollectd)) {
            return false;
        }
        BackgroundThread.get().getThreadHandler().post(() -> {
            try {
                mIProfcollect.trace_system(eventName);
                iprofcollectd.trace_system(eventName);
            } catch (RemoteException | ServiceSpecificException e) {
                Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
            }
@@ -57,16 +58,17 @@ public final class Utils {
        return true;
    }

    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName, int delayMs) {
        if (mIProfcollect == null) {
            return false;
        }
        if (isInCooldownOrReset()) {
    /**
     * Request a system-wide trace after a delay.
     * Will be ignored if the device does not meet trace criteria or is being rate limited.
     */
    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName, int delayMs) {
        if (!checkPrerequisites(iprofcollectd)) {
            return false;
        }
        BackgroundThread.get().getThreadHandler().postDelayed(() -> {
            try {
                mIProfcollect.trace_system(eventName);
                iprofcollectd.trace_system(eventName);
            } catch (RemoteException | ServiceSpecificException e) {
                Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
            }
@@ -74,17 +76,18 @@ public final class Utils {
        return true;
    }

    public static boolean traceProcess(IProfCollectd mIProfcollect,
    /**
     * Request a single-process trace.
     * Will be ignored if the device does not meet trace criteria or is being rate limited.
     */
    static boolean traceProcess(IProfCollectd iprofcollectd,
            String eventName, String processName, int durationMs) {
        if (mIProfcollect == null) {
            return false;
        }
        if (isInCooldownOrReset()) {
        if (!checkPrerequisites(iprofcollectd)) {
            return false;
        }
        BackgroundThread.get().getThreadHandler().post(() -> {
            try {
                mIProfcollect.trace_process(eventName,
                iprofcollectd.trace_process(eventName,
                        processName,
                        durationMs);
            } catch (RemoteException | ServiceSpecificException e) {
@@ -105,4 +108,16 @@ public final class Utils {
        }
        return true;
    }

    private static boolean checkPrerequisites(IProfCollectd iprofcollectd) {
        if (iprofcollectd == null) {
            return false;
        }
        if (isInCooldownOrReset()) {
            return false;
        }
        return ProfcollectForwardingService.sVerityEnforced
            && !ProfcollectForwardingService.sAdbActive
            && ProfcollectForwardingService.sIsInteractive;
    }
}