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