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.util.Slog;
20
21import java.io.*;
22import java.util.Arrays;
23
24/**
25 * Monitors device resources periodically for some period of time. Useful for
26 * tracking down performance problems.
27 */
28class 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