/*
|
* 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.");
|
}
|
}
|
}
|