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