blob: b423f62e9b6cee6deba5f45d1b063ac433df87bb [file] [log] [blame]
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +01001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.content.Context;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010020import android.database.ContentObserver;
21import android.net.Uri;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010022import android.os.Binder;
23import android.os.Looper;
24import android.os.ResultReceiver;
25import android.os.ShellCallback;
26import android.os.ShellCommand;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010027import android.os.SystemProperties;
28import android.os.UserHandle;
29import android.provider.Settings;
Marcin Oczeretkod464bcf2018-10-19 10:46:21 +010030import android.text.format.DateFormat;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010031import android.util.KeyValueListParser;
32import android.util.Slog;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010033
Marcin Oczeretkoc4c45a82018-12-06 15:09:49 +000034import com.android.internal.os.AppIdToPackageMap;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010035import com.android.internal.os.BackgroundThread;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010036import com.android.internal.os.CachedDeviceState;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010037import com.android.internal.os.LooperStats;
38import com.android.internal.util.DumpUtils;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.Arrays;
43import java.util.Comparator;
44import java.util.List;
45
46/**
47 * @hide Only for use within the system server.
48 */
49public class LooperStatsService extends Binder {
50 private static final String TAG = "LooperStatsService";
51 private static final String LOOPER_STATS_SERVICE_NAME = "looper_stats";
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010052 private static final String SETTINGS_ENABLED_KEY = "enabled";
53 private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000054 private static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state";
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010055 private static final String DEBUG_SYS_LOOPER_STATS_ENABLED =
56 "debug.sys.looper_stats_enabled";
Olivier Gaillard51f669e2019-02-21 16:05:27 +000057 private static final int DEFAULT_SAMPLING_INTERVAL = 1000;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010058 private static final int DEFAULT_ENTRIES_SIZE_CAP = 2000;
Olivier Gaillard2d456062019-01-23 17:56:58 +000059 private static final boolean DEFAULT_ENABLED = true;
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000060 private static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010061
62 private final Context mContext;
63 private final LooperStats mStats;
Olivier Gaillard41589382019-02-04 10:42:34 +000064 // Default should be false so that the first call to #setEnabled installed the looper observer.
65 private boolean mEnabled = false;
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000066 private boolean mTrackScreenInteractive = false;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010067
68 private LooperStatsService(Context context, LooperStats stats) {
69 this.mContext = context;
70 this.mStats = stats;
71 }
72
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010073 private void initFromSettings() {
74 final KeyValueListParser parser = new KeyValueListParser(',');
75
76 try {
77 parser.setString(Settings.Global.getString(mContext.getContentResolver(),
78 Settings.Global.LOOPER_STATS));
79 } catch (IllegalArgumentException e) {
80 Slog.e(TAG, "Bad looper_stats settings", e);
81 }
82
83 setSamplingInterval(
84 parser.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL));
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000085 setTrackScreenInteractive(
86 parser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY,
87 DEFAULT_TRACK_SCREEN_INTERACTIVE));
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010088 // Manually specified value takes precedence over Settings.
89 setEnabled(SystemProperties.getBoolean(
90 DEBUG_SYS_LOOPER_STATS_ENABLED,
91 parser.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED)));
92 }
93
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010094 @Override
95 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
96 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
97 (new LooperShellCommand()).exec(this, in, out, err, args, callback, resultReceiver);
98 }
99
100 @Override
101 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
102 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Marcin Oczeretkoc4c45a82018-12-06 15:09:49 +0000103 AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot();
Marcin Oczeretkod464bcf2018-10-19 10:46:21 +0100104 pw.print("Start time: ");
105 pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis()));
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +0000106 pw.print("On battery time (ms): ");
107 pw.println(mStats.getBatteryTimeMillis());
Marcin Oczeretkod464bcf2018-10-19 10:46:21 +0100108 final List<LooperStats.ExportedEntry> entries = mStats.getEntries();
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100109 entries.sort(Comparator
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100110 .comparing((LooperStats.ExportedEntry entry) -> entry.workSourceUid)
111 .thenComparing(entry -> entry.threadName)
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100112 .thenComparing(entry -> entry.handlerClassName)
113 .thenComparing(entry -> entry.messageName));
114 String header = String.join(",", Arrays.asList(
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100115 "work_source_uid",
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100116 "thread_name",
117 "handler_class",
118 "message_name",
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100119 "is_interactive",
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100120 "message_count",
121 "recorded_message_count",
122 "total_latency_micros",
123 "max_latency_micros",
124 "total_cpu_micros",
125 "max_cpu_micros",
Marcin Oczeretko44272722018-09-19 11:01:32 +0100126 "recorded_delay_message_count",
127 "total_delay_millis",
128 "max_delay_millis",
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100129 "exception_count"));
130 pw.println(header);
131 for (LooperStats.ExportedEntry entry : entries) {
Marcin Oczeretko94501712018-12-17 18:03:45 +0000132 if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
133 // Do not dump debug entries.
134 continue;
135 }
Marcin Oczeretko44272722018-09-19 11:01:32 +0100136 pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
Marcin Oczeretkoc4c45a82018-12-06 15:09:49 +0000137 packageMap.mapUid(entry.workSourceUid),
Marcin Oczeretko44272722018-09-19 11:01:32 +0100138 entry.threadName,
139 entry.handlerClassName,
140 entry.messageName,
141 entry.isInteractive,
142 entry.messageCount,
143 entry.recordedMessageCount,
144 entry.totalLatencyMicros,
145 entry.maxLatencyMicros,
146 entry.cpuUsageMicros,
147 entry.maxCpuUsageMicros,
148 entry.recordedDelayMessageCount,
149 entry.delayMillis,
150 entry.maxDelayMillis,
151 entry.exceptionCount);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100152 }
153 }
154
155 private void setEnabled(boolean enabled) {
156 if (mEnabled != enabled) {
157 mEnabled = enabled;
158 mStats.reset();
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000159 mStats.setAddDebugEntries(enabled);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100160 Looper.setObserver(enabled ? mStats : null);
161 }
162 }
163
Olivier Gaillard36b80ca2019-02-11 11:41:39 +0000164 private void setTrackScreenInteractive(boolean enabled) {
165 if (mTrackScreenInteractive != enabled) {
166 mTrackScreenInteractive = enabled;
167 mStats.reset();
168 }
169 }
170
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100171 private void setSamplingInterval(int samplingInterval) {
Marcin Oczeretko37d41092018-10-02 15:35:44 +0100172 if (samplingInterval > 0) {
173 mStats.setSamplingInterval(samplingInterval);
174 } else {
175 Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
176 + samplingInterval);
177 }
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100178 }
179
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100180 /**
181 * Manages the lifecycle of LooperStatsService within System Server.
182 */
183 public static class Lifecycle extends SystemService {
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100184 private final SettingsObserver mSettingsObserver;
185 private final LooperStatsService mService;
186 private final LooperStats mStats;
187
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100188 public Lifecycle(Context context) {
189 super(context);
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100190 mStats = new LooperStats(DEFAULT_SAMPLING_INTERVAL, DEFAULT_ENTRIES_SIZE_CAP);
191 mService = new LooperStatsService(getContext(), mStats);
192 mSettingsObserver = new SettingsObserver(mService);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100193 }
194
195 @Override
196 public void onStart() {
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100197 publishLocalService(LooperStats.class, mStats);
Marcin Oczeretko1d5d0ec2018-08-30 13:11:27 +0100198 publishBinderService(LOOPER_STATS_SERVICE_NAME, mService);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100199 }
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100200
201 @Override
202 public void onBootPhase(int phase) {
203 if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
204 mService.initFromSettings();
205 Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LOOPER_STATS);
206 getContext().getContentResolver().registerContentObserver(
207 settingsUri, false, mSettingsObserver, UserHandle.USER_SYSTEM);
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100208 mStats.setDeviceState(getLocalService(CachedDeviceState.Readonly.class));
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100209 }
210 }
211 }
212
213 private static class SettingsObserver extends ContentObserver {
214 private final LooperStatsService mService;
215
216 SettingsObserver(LooperStatsService service) {
217 super(BackgroundThread.getHandler());
218 mService = service;
219 }
220
221 @Override
222 public void onChange(boolean selfChange, Uri uri, int userId) {
223 mService.initFromSettings();
224 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100225 }
226
227 private class LooperShellCommand extends ShellCommand {
228 @Override
229 public int onCommand(String cmd) {
230 if ("enable".equals(cmd)) {
231 setEnabled(true);
232 return 0;
233 } else if ("disable".equals(cmd)) {
234 setEnabled(false);
235 return 0;
236 } else if ("reset".equals(cmd)) {
237 mStats.reset();
238 return 0;
Marcin Oczeretko86d07aa2018-09-14 11:51:08 +0100239 } else if ("sampling_interval".equals(cmd)) {
240 int sampling = Integer.parseUnsignedInt(getNextArgRequired());
241 setSamplingInterval(sampling);
242 return 0;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100243 } else {
244 return handleDefaultCommands(cmd);
245 }
246 }
247
248 @Override
249 public void onHelp() {
250 final PrintWriter pw = getOutPrintWriter();
251 pw.println(LOOPER_STATS_SERVICE_NAME + " commands:");
Marcin Oczeretko86d07aa2018-09-14 11:51:08 +0100252 pw.println(" enable: Enable collecting stats.");
253 pw.println(" disable: Disable collecting stats.");
254 pw.println(" sampling_interval: Change the sampling interval.");
255 pw.println(" reset: Reset stats.");
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100256 }
257 }
258}