blob: de40b5207a6eaea06fc802d72342a446849811f0 [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
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;
20import android.database.ContentObserver;
21import android.net.Uri;
22import android.os.Binder;
23import android.os.Looper;
24import android.os.ResultReceiver;
25import android.os.ShellCallback;
26import android.os.ShellCommand;
27import android.os.SystemProperties;
28import android.os.UserHandle;
29import android.provider.Settings;
30import android.text.format.DateFormat;
31import android.util.KeyValueListParser;
32import android.util.Slog;
33
34import com.android.internal.os.AppIdToPackageMap;
35import com.android.internal.os.BackgroundThread;
36import com.android.internal.os.CachedDeviceState;
37import 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";
52 private static final String SETTINGS_ENABLED_KEY = "enabled";
53 private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
54 private static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state";
55 private static final String SETTINGS_IGNORE_BATTERY_STATUS_KEY = "ignore_battery_status";
56 private static final String DEBUG_SYS_LOOPER_STATS_ENABLED =
57 "debug.sys.looper_stats_enabled";
58 private static final int DEFAULT_SAMPLING_INTERVAL = 1000;
59 private static final int DEFAULT_ENTRIES_SIZE_CAP = 1500;
60 private static final boolean DEFAULT_ENABLED = true;
61 private static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;
62
63 private final Context mContext;
64 private final LooperStats mStats;
65 // Default should be false so that the first call to #setEnabled installed the looper observer.
66 private boolean mEnabled = false;
67 private boolean mTrackScreenInteractive = false;
68 private boolean mIgnoreBatteryStatus = LooperStats.DEFAULT_IGNORE_BATTERY_STATUS;
69
70 private LooperStatsService(Context context, LooperStats stats) {
71 this.mContext = context;
72 this.mStats = stats;
73 }
74
75 private void initFromSettings() {
76 final KeyValueListParser parser = new KeyValueListParser(',');
77
78 try {
79 parser.setString(Settings.Global.getString(mContext.getContentResolver(),
80 Settings.Global.LOOPER_STATS));
81 } catch (IllegalArgumentException e) {
82 Slog.e(TAG, "Bad looper_stats settings", e);
83 }
84
85 setSamplingInterval(
86 parser.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL));
87 setTrackScreenInteractive(
88 parser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY,
89 DEFAULT_TRACK_SCREEN_INTERACTIVE));
90 setIgnoreBatteryStatus(
91 parser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY,
92 LooperStats.DEFAULT_IGNORE_BATTERY_STATUS));
93 // Manually specified value takes precedence over Settings.
94 setEnabled(SystemProperties.getBoolean(
95 DEBUG_SYS_LOOPER_STATS_ENABLED,
96 parser.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED)));
97 }
98
99 @Override
100 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
101 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
102 (new LooperShellCommand()).exec(this, in, out, err, args, callback, resultReceiver);
103 }
104
105 @Override
106 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
107 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
108 AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot();
109 pw.print("Start time: ");
110 pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis()));
111 pw.print("On battery time (ms): ");
112 pw.println(mStats.getBatteryTimeMillis());
113 final List<LooperStats.ExportedEntry> entries = mStats.getEntries();
114 entries.sort(Comparator
115 .comparing((LooperStats.ExportedEntry entry) -> entry.workSourceUid)
116 .thenComparing(entry -> entry.threadName)
117 .thenComparing(entry -> entry.handlerClassName)
118 .thenComparing(entry -> entry.messageName));
119 String header = String.join(",", Arrays.asList(
120 "work_source_uid",
121 "thread_name",
122 "handler_class",
123 "message_name",
124 "is_interactive",
125 "message_count",
126 "recorded_message_count",
127 "total_latency_micros",
128 "max_latency_micros",
129 "total_cpu_micros",
130 "max_cpu_micros",
131 "recorded_delay_message_count",
132 "total_delay_millis",
133 "max_delay_millis",
134 "exception_count"));
135 pw.println(header);
136 for (LooperStats.ExportedEntry entry : entries) {
137 if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
138 // Do not dump debug entries.
139 continue;
140 }
141 pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
142 packageMap.mapUid(entry.workSourceUid),
143 entry.threadName,
144 entry.handlerClassName,
145 entry.messageName,
146 entry.isInteractive,
147 entry.messageCount,
148 entry.recordedMessageCount,
149 entry.totalLatencyMicros,
150 entry.maxLatencyMicros,
151 entry.cpuUsageMicros,
152 entry.maxCpuUsageMicros,
153 entry.recordedDelayMessageCount,
154 entry.delayMillis,
155 entry.maxDelayMillis,
156 entry.exceptionCount);
157 }
158 }
159
160 private void setEnabled(boolean enabled) {
161 if (mEnabled != enabled) {
162 mEnabled = enabled;
163 mStats.reset();
164 mStats.setAddDebugEntries(enabled);
165 Looper.setObserver(enabled ? mStats : null);
166 }
167 }
168
169 private void setTrackScreenInteractive(boolean enabled) {
170 if (mTrackScreenInteractive != enabled) {
171 mTrackScreenInteractive = enabled;
172 mStats.reset();
173 }
174 }
175
176 private void setIgnoreBatteryStatus(boolean ignore) {
177 if (mIgnoreBatteryStatus != ignore) {
178 mStats.setIgnoreBatteryStatus(ignore);
179 mIgnoreBatteryStatus = ignore;
180 mStats.reset();
181 }
182 }
183
184 private void setSamplingInterval(int samplingInterval) {
185 if (samplingInterval > 0) {
186 mStats.setSamplingInterval(samplingInterval);
187 } else {
188 Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
189 + samplingInterval);
190 }
191 }
192
193 /**
194 * Manages the lifecycle of LooperStatsService within System Server.
195 */
196 public static class Lifecycle extends SystemService {
197 private final SettingsObserver mSettingsObserver;
198 private final LooperStatsService mService;
199 private final LooperStats mStats;
200
201 public Lifecycle(Context context) {
202 super(context);
203 mStats = new LooperStats(DEFAULT_SAMPLING_INTERVAL, DEFAULT_ENTRIES_SIZE_CAP);
204 mService = new LooperStatsService(getContext(), mStats);
205 mSettingsObserver = new SettingsObserver(mService);
206 }
207
208 @Override
209 public void onStart() {
210 publishLocalService(LooperStats.class, mStats);
211 publishBinderService(LOOPER_STATS_SERVICE_NAME, mService);
212 }
213
214 @Override
215 public void onBootPhase(int phase) {
216 if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
217 mService.initFromSettings();
218 Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LOOPER_STATS);
219 getContext().getContentResolver().registerContentObserver(
220 settingsUri, false, mSettingsObserver, UserHandle.USER_SYSTEM);
221 mStats.setDeviceState(getLocalService(CachedDeviceState.Readonly.class));
222 }
223 }
224 }
225
226 private static class SettingsObserver extends ContentObserver {
227 private final LooperStatsService mService;
228
229 SettingsObserver(LooperStatsService service) {
230 super(BackgroundThread.getHandler());
231 mService = service;
232 }
233
234 @Override
235 public void onChange(boolean selfChange, Uri uri, int userId) {
236 mService.initFromSettings();
237 }
238 }
239
240 private class LooperShellCommand extends ShellCommand {
241 @Override
242 public int onCommand(String cmd) {
243 if ("enable".equals(cmd)) {
244 setEnabled(true);
245 return 0;
246 } else if ("disable".equals(cmd)) {
247 setEnabled(false);
248 return 0;
249 } else if ("reset".equals(cmd)) {
250 mStats.reset();
251 return 0;
252 } else if ("sampling_interval".equals(cmd)) {
253 int sampling = Integer.parseUnsignedInt(getNextArgRequired());
254 setSamplingInterval(sampling);
255 return 0;
256 } else {
257 return handleDefaultCommands(cmd);
258 }
259 }
260
261 @Override
262 public void onHelp() {
263 final PrintWriter pw = getOutPrintWriter();
264 pw.println(LOOPER_STATS_SERVICE_NAME + " commands:");
265 pw.println(" enable: Enable collecting stats.");
266 pw.println(" disable: Disable collecting stats.");
267 pw.println(" sampling_interval: Change the sampling interval.");
268 pw.println(" reset: Reset stats.");
269 }
270 }
271}