1/* 2 * Copyright (C) 2008 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.am; 18 19import android.os.SELinux; 20import android.util.Slog; 21 22import java.io.*; 23import java.util.Arrays; 24 25/** 26 * Monitors device resources periodically for some period of time. Useful for 27 * tracking down performance problems. 28 */ 29class DeviceMonitor { 30 31 private static final String LOG_TAG = DeviceMonitor.class.getName(); 32 33 /** Number of samples to take. */ 34 private static final int SAMPLE_COUNT = 10; 35 36 /** Time to wait in ms between samples. */ 37 private static final int INTERVAL = 1000; 38 39 /** Time to wait in ms between samples. */ 40 private static final int MAX_FILES = 30; 41 42 private final byte[] buffer = new byte[1024]; 43 44 /** Is the monitor currently running? */ 45 private boolean running = false; 46 47 private DeviceMonitor() { 48 new Thread() { 49 public void run() { 50 monitor(); 51 } 52 }.start(); 53 } 54 55 /** 56 * Loops continuously. Pauses until someone tells us to start monitoring. 57 */ 58 @SuppressWarnings("InfiniteLoopStatement") 59 private void monitor() { 60 while (true) { 61 waitForStart(); 62 63 purge(); 64 65 for (int i = 0; i < SAMPLE_COUNT; i++) { 66 try { 67 dump(); 68 } catch (IOException e) { 69 Slog.w(LOG_TAG, "Dump failed.", e); 70 } 71 pause(); 72 } 73 74 stop(); 75 } 76 } 77 78 private static final File PROC = new File("/proc"); 79 private static final File BASE = new File("/data/anr/"); 80 static { 81 if (!BASE.isDirectory() && !BASE.mkdirs()) { 82 throw new AssertionError("Couldn't create " + BASE + "."); 83 } 84 if (!SELinux.restorecon(BASE)) { 85 throw new AssertionError("Couldn't restorecon " + BASE + "."); 86 } 87 } 88 89 private static final File[] PATHS = { 90 new File(PROC, "zoneinfo"), 91 new File(PROC, "interrupts"), 92 new File(PROC, "meminfo"), 93 new File(PROC, "slabinfo"), 94 }; 95 96 97 /** 98 * Deletes old files. 99 */ 100 private void purge() { 101 File[] files = BASE.listFiles(); 102 int count = files.length - MAX_FILES; 103 if (count > 0) { 104 Arrays.sort(files); 105 for (int i = 0; i < count; i++) { 106 if (!files[i].delete()) { 107 Slog.w(LOG_TAG, "Couldn't delete " + files[i] + "."); 108 } 109 } 110 } 111 } 112 113 /** 114 * Dumps the current device stats to a new file. 115 */ 116 private void dump() throws IOException { 117 OutputStream out = new FileOutputStream( 118 new File(BASE, String.valueOf(System.currentTimeMillis()))); 119 try { 120 // Copy /proc/*/stat 121 for (File processDirectory : PROC.listFiles()) { 122 if (isProcessDirectory(processDirectory)) { 123 dump(new File(processDirectory, "stat"), out); 124 } 125 } 126 127 // Copy other files. 128 for (File file : PATHS) { 129 dump(file, out); 130 } 131 } finally { 132 closeQuietly(out); 133 } 134 } 135 136 /** 137 * Returns true if the given file represents a process directory. 138 */ 139 private static boolean isProcessDirectory(File file) { 140 try { 141 Integer.parseInt(file.getName()); 142 return file.isDirectory(); 143 } catch (NumberFormatException e) { 144 return false; 145 } 146 } 147 148 /** 149 * Copies from a file to an output stream. 150 */ 151 private void dump(File from, OutputStream out) throws IOException { 152 writeHeader(from, out); 153 154 FileInputStream in = null; 155 try { 156 in = new FileInputStream(from); 157 int count; 158 while ((count = in.read(buffer)) != -1) { 159 out.write(buffer, 0, count); 160 } 161 } finally { 162 closeQuietly(in); 163 } 164 } 165 166 /** 167 * Writes a header for the given file. 168 */ 169 private static void writeHeader(File file, OutputStream out) 170 throws IOException { 171 String header = "*** " + file.toString() + "\n"; 172 out.write(header.getBytes()); 173 } 174 175 /** 176 * Closes the given resource. Logs exceptions. 177 * @param closeable 178 */ 179 private static void closeQuietly(Closeable closeable) { 180 try { 181 if (closeable != null) { 182 closeable.close(); 183 } 184 } catch (IOException e) { 185 Slog.w(LOG_TAG, e); 186 } 187 } 188 189 /** 190 * Pauses momentarily before we start the next dump. 191 */ 192 private void pause() { 193 try { 194 Thread.sleep(INTERVAL); 195 } catch (InterruptedException e) { /* ignore */ } 196 } 197 198 /** 199 * Stops dumping. 200 */ 201 private synchronized void stop() { 202 running = false; 203 } 204 205 /** 206 * Waits until someone starts us. 207 */ 208 private synchronized void waitForStart() { 209 while (!running) { 210 try { 211 wait(); 212 } catch (InterruptedException e) { /* ignore */ } 213 } 214 } 215 216 /** 217 * Instructs the monitoring to start if it hasn't already. 218 */ 219 private synchronized void startMonitoring() { 220 if (!running) { 221 running = true; 222 notifyAll(); 223 } 224 } 225 226 private static DeviceMonitor instance = new DeviceMonitor(); 227 228 /** 229 * Starts monitoring if it hasn't started already. 230 */ 231 static void start() { 232 instance.startMonitoring(); 233 } 234} 235