Java程序  |  259行  |  9.87 KB

/*
 * Copyright (C) 2018 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;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.KeyValueListParser;
import android.util.Slog;

import com.android.internal.os.AppIdToPackageMap;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.CachedDeviceState;
import com.android.internal.os.LooperStats;
import com.android.internal.util.DumpUtils;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * @hide Only for use within the system server.
 */
public class LooperStatsService extends Binder {
    private static final String TAG = "LooperStatsService";
    private static final String LOOPER_STATS_SERVICE_NAME = "looper_stats";
    private static final String SETTINGS_ENABLED_KEY = "enabled";
    private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
    private static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state";
    private static final String DEBUG_SYS_LOOPER_STATS_ENABLED =
            "debug.sys.looper_stats_enabled";
    private static final int DEFAULT_SAMPLING_INTERVAL = 1000;
    private static final int DEFAULT_ENTRIES_SIZE_CAP = 1500;
    private static final boolean DEFAULT_ENABLED = true;
    private static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;

    private final Context mContext;
    private final LooperStats mStats;
    // Default should be false so that the first call to #setEnabled installed the looper observer.
    private boolean mEnabled = false;
    private boolean mTrackScreenInteractive = false;

    private LooperStatsService(Context context, LooperStats stats) {
        this.mContext = context;
        this.mStats = stats;
    }

    private void initFromSettings() {
        final KeyValueListParser parser = new KeyValueListParser(',');

        try {
            parser.setString(Settings.Global.getString(mContext.getContentResolver(),
                    Settings.Global.LOOPER_STATS));
        } catch (IllegalArgumentException e) {
            Slog.e(TAG, "Bad looper_stats settings", e);
        }

        setSamplingInterval(
                parser.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL));
        setTrackScreenInteractive(
                parser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY,
                DEFAULT_TRACK_SCREEN_INTERACTIVE));
        // Manually specified value takes precedence over Settings.
        setEnabled(SystemProperties.getBoolean(
                DEBUG_SYS_LOOPER_STATS_ENABLED,
                parser.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED)));
    }

    @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
        (new LooperShellCommand()).exec(this, in, out, err, args, callback, resultReceiver);
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
        AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot();
        pw.print("Start time: ");
        pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis()));
        pw.print("On battery time (ms): ");
        pw.println(mStats.getBatteryTimeMillis());
        final List<LooperStats.ExportedEntry> entries = mStats.getEntries();
        entries.sort(Comparator
                .comparing((LooperStats.ExportedEntry entry) -> entry.workSourceUid)
                .thenComparing(entry -> entry.threadName)
                .thenComparing(entry -> entry.handlerClassName)
                .thenComparing(entry -> entry.messageName));
        String header = String.join(",", Arrays.asList(
                "work_source_uid",
                "thread_name",
                "handler_class",
                "message_name",
                "is_interactive",
                "message_count",
                "recorded_message_count",
                "total_latency_micros",
                "max_latency_micros",
                "total_cpu_micros",
                "max_cpu_micros",
                "recorded_delay_message_count",
                "total_delay_millis",
                "max_delay_millis",
                "exception_count"));
        pw.println(header);
        for (LooperStats.ExportedEntry entry : entries) {
            if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
                // Do not dump debug entries.
                continue;
            }
            pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
                    packageMap.mapUid(entry.workSourceUid),
                    entry.threadName,
                    entry.handlerClassName,
                    entry.messageName,
                    entry.isInteractive,
                    entry.messageCount,
                    entry.recordedMessageCount,
                    entry.totalLatencyMicros,
                    entry.maxLatencyMicros,
                    entry.cpuUsageMicros,
                    entry.maxCpuUsageMicros,
                    entry.recordedDelayMessageCount,
                    entry.delayMillis,
                    entry.maxDelayMillis,
                    entry.exceptionCount);
        }
    }

    private void setEnabled(boolean enabled) {
        if (mEnabled != enabled) {
            mEnabled = enabled;
            mStats.reset();
            mStats.setAddDebugEntries(enabled);
            Looper.setObserver(enabled ? mStats : null);
        }
    }

    private void setTrackScreenInteractive(boolean enabled) {
        if (mTrackScreenInteractive != enabled) {
            mTrackScreenInteractive = enabled;
            mStats.reset();
        }
    }

    private void setSamplingInterval(int samplingInterval) {
        if (samplingInterval > 0) {
            mStats.setSamplingInterval(samplingInterval);
        } else {
            Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
                    + samplingInterval);
        }
    }

    /**
     * Manages the lifecycle of LooperStatsService within System Server.
     */
    public static class Lifecycle extends SystemService {
        private final SettingsObserver mSettingsObserver;
        private final LooperStatsService mService;
        private final LooperStats mStats;

        public Lifecycle(Context context) {
            super(context);
            mStats = new LooperStats(DEFAULT_SAMPLING_INTERVAL, DEFAULT_ENTRIES_SIZE_CAP);
            mService = new LooperStatsService(getContext(), mStats);
            mSettingsObserver = new SettingsObserver(mService);
        }

        @Override
        public void onStart() {
            publishLocalService(LooperStats.class, mStats);
            publishBinderService(LOOPER_STATS_SERVICE_NAME, mService);
        }

        @Override
        public void onBootPhase(int phase) {
            if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
                mService.initFromSettings();
                Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LOOPER_STATS);
                getContext().getContentResolver().registerContentObserver(
                        settingsUri, false, mSettingsObserver, UserHandle.USER_SYSTEM);
                mStats.setDeviceState(getLocalService(CachedDeviceState.Readonly.class));
            }
        }
    }

    private static class SettingsObserver extends ContentObserver {
        private final LooperStatsService mService;

        SettingsObserver(LooperStatsService service) {
            super(BackgroundThread.getHandler());
            mService = service;
        }

        @Override
        public void onChange(boolean selfChange, Uri uri, int userId) {
            mService.initFromSettings();
        }
    }

    private class LooperShellCommand extends ShellCommand {
        @Override
        public int onCommand(String cmd) {
            if ("enable".equals(cmd)) {
                setEnabled(true);
                return 0;
            } else if ("disable".equals(cmd)) {
                setEnabled(false);
                return 0;
            } else if ("reset".equals(cmd)) {
                mStats.reset();
                return 0;
            } else if ("sampling_interval".equals(cmd)) {
                int sampling = Integer.parseUnsignedInt(getNextArgRequired());
                setSamplingInterval(sampling);
                return 0;
            } else {
                return handleDefaultCommands(cmd);
            }
        }

        @Override
        public void onHelp() {
            final PrintWriter pw = getOutPrintWriter();
            pw.println(LOOPER_STATS_SERVICE_NAME + " commands:");
            pw.println("  enable: Enable collecting stats.");
            pw.println("  disable: Disable collecting stats.");
            pw.println("  sampling_interval: Change the sampling interval.");
            pw.println("  reset: Reset stats.");
        }
    }
}