Home | History | Annotate | Download | only in am
      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