OSDN Git Service

Merge "Add eclipse version to usage stat ping."
[android-x86/sdk.git] / ddms / libs / ddmuilib / src / com / android / ddmuilib / SysinfoPanel.java
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.ddmuilib;
18
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.Client;
21 import com.android.ddmlib.IShellOutputReceiver;
22 import com.android.ddmlib.Log;
23 import com.android.ddmlib.ShellCommandUnresponsiveException;
24 import com.android.ddmlib.TimeoutException;
25
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.events.SelectionAdapter;
28 import org.eclipse.swt.events.SelectionEvent;
29 import org.eclipse.swt.layout.GridData;
30 import org.eclipse.swt.layout.GridLayout;
31 import org.eclipse.swt.layout.RowLayout;
32 import org.eclipse.swt.widgets.Button;
33 import org.eclipse.swt.widgets.Combo;
34 import org.eclipse.swt.widgets.Composite;
35 import org.eclipse.swt.widgets.Control;
36 import org.eclipse.swt.widgets.FileDialog;
37 import org.eclipse.swt.widgets.Label;
38 import org.jfree.chart.ChartFactory;
39 import org.jfree.chart.JFreeChart;
40 import org.jfree.data.general.DefaultPieDataset;
41 import org.jfree.experimental.chart.swt.ChartComposite;
42
43 import java.io.BufferedReader;
44 import java.io.File;
45 import java.io.FileOutputStream;
46 import java.io.FileReader;
47 import java.io.IOException;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50
51 /**
52  * Displays system information graphs obtained from a bugreport file or device.
53  */
54 public class SysinfoPanel extends TablePanel implements IShellOutputReceiver {
55
56     // UI components
57     private Label mLabel;
58     private Button mFetchButton;
59     private Combo mDisplayMode;
60
61     private DefaultPieDataset mDataset;
62
63     // The bugreport file to process
64     private File mDataFile;
65
66     // To get output from adb commands
67     private FileOutputStream mTempStream;
68
69     // Selects the current display: MODE_CPU, etc.
70     private int mMode = 0;
71
72     private static final int MODE_CPU = 0;
73     private static final int MODE_ALARM = 1;
74     private static final int MODE_WAKELOCK = 2;
75     private static final int MODE_MEMINFO = 3;
76     private static final int MODE_SYNC = 4;
77
78     // argument to dumpsys; section in the bugreport holding the data
79     private static final String BUGREPORT_SECTION[] = {"cpuinfo", "alarm",
80             "batteryinfo", "MEMORY INFO", "content"};
81
82     private static final String DUMP_COMMAND[] = {"dumpsys cpuinfo",
83             "dumpsys alarm", "dumpsys batteryinfo", "cat /proc/meminfo ; procrank",
84             "dumpsys content"};
85
86     private static final String CAPTIONS[] = {"CPU load", "Alarms",
87             "Wakelocks", "Memory usage", "Sync"};
88
89     /**
90      * Generates the dataset to display.
91      *
92      * @param file The bugreport file to process.
93      */
94     public void generateDataset(File file) {
95         mDataset.clear();
96         mLabel.setText("");
97         if (file == null) {
98             return;
99         }
100         try {
101             BufferedReader br = getBugreportReader(file);
102             if (mMode == MODE_CPU) {
103                 readCpuDataset(br);
104             } else if (mMode == MODE_ALARM) {
105                 readAlarmDataset(br);
106             } else if (mMode == MODE_WAKELOCK) {
107                 readWakelockDataset(br);
108             } else if (mMode == MODE_MEMINFO) {
109                 readMeminfoDataset(br);
110             } else if (mMode == MODE_SYNC) {
111                 readSyncDataset(br);
112             }
113         } catch (IOException e) {
114             Log.e("DDMS", e);
115         }
116     }
117
118     /**
119      * Sent when a new device is selected. The new device can be accessed with
120      * {@link #getCurrentDevice()}
121      */
122     @Override
123     public void deviceSelected() {
124         if (getCurrentDevice() != null) {
125             mFetchButton.setEnabled(true);
126             loadFromDevice();
127         } else {
128             mFetchButton.setEnabled(false);
129         }
130     }
131
132     /**
133      * Sent when a new client is selected. The new client can be accessed with
134      * {@link #getCurrentClient()}.
135      */
136     @Override
137     public void clientSelected() {
138     }
139
140     /**
141      * Sets the focus to the proper control inside the panel.
142      */
143     @Override
144     public void setFocus() {
145         mDisplayMode.setFocus();
146     }
147
148     /**
149      * Fetches a new bugreport from the device and updates the display.
150      * Fetching is asynchronous.  See also addOutput, flush, and isCancelled.
151      */
152     private void loadFromDevice() {
153         try {
154             initShellOutputBuffer();
155             if (mMode == MODE_MEMINFO) {
156                 // Hack to add bugreport-style section header for meminfo
157                 mTempStream.write("------ MEMORY INFO ------\n".getBytes());
158             }
159             getCurrentDevice().executeShellCommand(
160                     DUMP_COMMAND[mMode], this);
161         } catch (IOException e) {
162             Log.e("DDMS", e);
163         } catch (TimeoutException e) {
164             Log.e("DDMS", e);
165         } catch (AdbCommandRejectedException e) {
166             Log.e("DDMS", e);
167         } catch (ShellCommandUnresponsiveException e) {
168             Log.e("DDMS", e);
169         }
170     }
171
172     /**
173      * Initializes temporary output file for executeShellCommand().
174      *
175      * @throws IOException on file error
176      */
177     void initShellOutputBuffer() throws IOException {
178         mDataFile = File.createTempFile("ddmsfile", ".txt");
179         mDataFile.deleteOnExit();
180         mTempStream = new FileOutputStream(mDataFile);
181     }
182
183     /**
184      * Adds output to the temp file. IShellOutputReceiver method. Called by
185      * executeShellCommand().
186      */
187     public void addOutput(byte[] data, int offset, int length) {
188         try {
189             mTempStream.write(data, offset, length);
190         }
191         catch (IOException e) {
192             Log.e("DDMS", e);
193         }
194     }
195
196     /**
197      * Processes output from shell command. IShellOutputReceiver method. The
198      * output is passed to generateDataset(). Called by executeShellCommand() on
199      * completion.
200      */
201     public void flush() {
202         if (mTempStream != null) {
203             try {
204                 mTempStream.close();
205                 generateDataset(mDataFile);
206                 mTempStream = null;
207                 mDataFile = null;
208             } catch (IOException e) {
209                 Log.e("DDMS", e);
210             }
211         }
212     }
213
214     /**
215      * IShellOutputReceiver method.
216      *
217      * @return false - don't cancel
218      */
219     public boolean isCancelled() {
220         return false;
221     }
222
223     /**
224      * Create our controls for the UI panel.
225      */
226     @Override
227     protected Control createControl(Composite parent) {
228         Composite top = new Composite(parent, SWT.NONE);
229         top.setLayout(new GridLayout(1, false));
230         top.setLayoutData(new GridData(GridData.FILL_BOTH));
231
232         Composite buttons = new Composite(top, SWT.NONE);
233         buttons.setLayout(new RowLayout());
234
235         mDisplayMode = new Combo(buttons, SWT.PUSH);
236         for (String mode : CAPTIONS) {
237             mDisplayMode.add(mode);
238         }
239         mDisplayMode.select(mMode);
240         mDisplayMode.addSelectionListener(new SelectionAdapter() {
241             @Override
242             public void widgetSelected(SelectionEvent e) {
243                 mMode = mDisplayMode.getSelectionIndex();
244                 if (mDataFile != null) {
245                     generateDataset(mDataFile);
246                 } else if (getCurrentDevice() != null) {
247                     loadFromDevice();
248                 }
249             }
250         });
251
252         final Button loadButton = new Button(buttons, SWT.PUSH);
253         loadButton.setText("Load from File");
254         loadButton.addSelectionListener(new SelectionAdapter() {
255             @Override
256             public void widgetSelected(SelectionEvent e) {
257                 FileDialog fileDialog = new FileDialog(loadButton.getShell(),
258                         SWT.OPEN);
259                 fileDialog.setText("Load bugreport");
260                 String filename = fileDialog.open();
261                 if (filename != null) {
262                     mDataFile = new File(filename);
263                     generateDataset(mDataFile);
264                 }
265             }
266         });
267
268         mFetchButton = new Button(buttons, SWT.PUSH);
269         mFetchButton.setText("Update from Device");
270         mFetchButton.setEnabled(false);
271         mFetchButton.addSelectionListener(new SelectionAdapter() {
272             @Override
273             public void widgetSelected(SelectionEvent e) {
274                 loadFromDevice();
275             }
276         });
277
278         mLabel = new Label(top, SWT.NONE);
279         mLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
280
281         mDataset = new DefaultPieDataset();
282         JFreeChart chart = ChartFactory.createPieChart("", mDataset, false
283                 /* legend */, true/* tooltips */, false /* urls */);
284
285         ChartComposite chartComposite = new ChartComposite(top,
286                 SWT.BORDER, chart,
287                 ChartComposite.DEFAULT_HEIGHT,
288                 ChartComposite.DEFAULT_HEIGHT,
289                 ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH,
290                 ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT,
291                 3000,
292                 // max draw width. We don't want it to zoom, so we put a big number
293                 3000,
294                 // max draw height. We don't want it to zoom, so we put a big number
295                 true,  // off-screen buffer
296                 true,  // properties
297                 true,  // save
298                 true,  // print
299                 false,  // zoom
300                 true);
301         chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
302         return top;
303     }
304
305     public void clientChanged(final Client client, int changeMask) {
306         // Don't care
307     }
308
309     /**
310      * Helper to open a bugreport and skip to the specified section.
311      *
312      * @param file File to open
313      * @return Reader to bugreport file
314      * @throws java.io.IOException on file error
315      */
316     private BufferedReader getBugreportReader(File file) throws
317             IOException {
318         BufferedReader br = new BufferedReader(new FileReader(file));
319         // Skip over the unwanted bugreport sections
320         while (true) {
321             String line = br.readLine();
322             if (line == null) {
323                 Log.d("DDMS", "Service not found " + line);
324                 break;
325             }
326             if ((line.startsWith("DUMP OF SERVICE ") || line.startsWith("-----")) &&
327                     line.indexOf(BUGREPORT_SECTION[mMode]) > 0) {
328                 break;
329             }
330         }
331         return br;
332     }
333
334     /**
335      * Parse the time string generated by BatteryStats.
336      * A typical new-format string is "11d 13h 45m 39s 999ms".
337      * A typical old-format string is "12.3 sec".
338      * @return time in ms
339      */
340     private static long parseTimeMs(String s) {
341         long total = 0;
342         // Matches a single component e.g. "12.3 sec" or "45ms"
343         Pattern p = Pattern.compile("([\\d\\.]+)\\s*([a-z]+)");
344         Matcher m = p.matcher(s);
345         while (m.find()) {
346             String label = m.group(2);
347             if ("sec".equals(label)) {
348                 // Backwards compatibility with old time format
349                 total += (long) (Double.parseDouble(m.group(1)) * 1000);
350                 continue;
351             }
352             long value = Integer.parseInt(m.group(1));
353             if ("d".equals(label)) {
354                 total += value * 24 * 60 * 60 * 1000;
355             } else if ("h".equals(label)) {
356                 total += value * 60 * 60 * 1000;
357             } else if ("m".equals(label)) {
358                 total += value * 60 * 1000;
359             } else if ("s".equals(label)) {
360                 total += value * 1000;
361             } else if ("ms".equals(label)) {
362                 total += value;
363             }
364         }
365         return total;
366     }
367     /**
368      * Processes wakelock information from bugreport. Updates mDataset with the
369      * new data.
370      *
371      * @param br Reader providing the content
372      * @throws IOException if error reading file
373      */
374     void readWakelockDataset(BufferedReader br) throws IOException {
375         Pattern lockPattern = Pattern.compile("Wake lock (\\S+): (.+) partial");
376         Pattern totalPattern = Pattern.compile("Total: (.+) uptime");
377         double total = 0;
378         boolean inCurrent = false;
379
380         while (true) {
381             String line = br.readLine();
382             if (line == null || line.startsWith("DUMP OF SERVICE")) {
383                 // Done, or moved on to the next service
384                 break;
385             }
386             if (line.startsWith("Current Battery Usage Statistics")) {
387                 inCurrent = true;
388             } else if (inCurrent) {
389                 Matcher m = lockPattern.matcher(line);
390                 if (m.find()) {
391                     double value = parseTimeMs(m.group(2)) / 1000.;
392                     mDataset.setValue(m.group(1), value);
393                     total -= value;
394                 } else {
395                     m = totalPattern.matcher(line);
396                     if (m.find()) {
397                         total += parseTimeMs(m.group(1)) / 1000.;
398                     }
399                 }
400             }
401         }
402         if (total > 0) {
403             mDataset.setValue("Unlocked", total);
404         }
405     }
406
407     /**
408      * Processes alarm information from bugreport. Updates mDataset with the new
409      * data.
410      *
411      * @param br Reader providing the content
412      * @throws IOException if error reading file
413      */
414     void readAlarmDataset(BufferedReader br) throws IOException {
415         Pattern pattern = Pattern
416                 .compile("(\\d+) alarms: Intent .*\\.([^. ]+) flags");
417
418         while (true) {
419             String line = br.readLine();
420             if (line == null || line.startsWith("DUMP OF SERVICE")) {
421                 // Done, or moved on to the next service
422                 break;
423             }
424             Matcher m = pattern.matcher(line);
425             if (m.find()) {
426                 long count = Long.parseLong(m.group(1));
427                 String name = m.group(2);
428                 mDataset.setValue(name, count);
429             }
430         }
431     }
432
433     /**
434      * Processes cpu load information from bugreport. Updates mDataset with the
435      * new data.
436      *
437      * @param br Reader providing the content
438      * @throws IOException if error reading file
439      */
440     void readCpuDataset(BufferedReader br) throws IOException {
441         Pattern pattern = Pattern
442                 .compile("(\\S+): (\\S+)% = (.+)% user . (.+)% kernel");
443
444         while (true) {
445             String line = br.readLine();
446             if (line == null || line.startsWith("DUMP OF SERVICE")) {
447                 // Done, or moved on to the next service
448                 break;
449             }
450             if (line.startsWith("Load:")) {
451                 mLabel.setText(line);
452                 continue;
453             }
454             Matcher m = pattern.matcher(line);
455             if (m.find()) {
456                 String name = m.group(1);
457                 long both = Long.parseLong(m.group(2));
458                 long user = Long.parseLong(m.group(3));
459                 long kernel = Long.parseLong(m.group(4));
460                 if ("TOTAL".equals(name)) {
461                     if (both < 100) {
462                         mDataset.setValue("Idle", (100 - both));
463                     }
464                 } else {
465                     // Try to make graphs more useful even with rounding;
466                     // log often has 0% user + 0% kernel = 1% total
467                     // We arbitrarily give extra to kernel
468                     if (user > 0) {
469                         mDataset.setValue(name + " (user)", user);
470                     }
471                     if (kernel > 0) {
472                         mDataset.setValue(name + " (kernel)" , both - user);
473                     }
474                     if (user == 0 && kernel == 0 && both > 0) {
475                         mDataset.setValue(name, both);
476                     }
477                 }
478             }
479         }
480     }
481
482     /**
483      * Processes meminfo information from bugreport. Updates mDataset with the
484      * new data.
485      *
486      * @param br Reader providing the content
487      * @throws IOException if error reading file
488      */
489     void readMeminfoDataset(BufferedReader br) throws IOException {
490         Pattern valuePattern = Pattern.compile("(\\d+) kB");
491         long total = 0;
492         long other = 0;
493         mLabel.setText("PSS in kB");
494
495         // Scan meminfo
496         while (true) {
497             String line = br.readLine();
498             if (line == null) {
499                 // End of file
500                 break;
501             }
502             Matcher m = valuePattern.matcher(line);
503             if (m.find()) {
504                 long kb = Long.parseLong(m.group(1));
505                 if (line.startsWith("MemTotal")) {
506                     total = kb;
507                 } else if (line.startsWith("MemFree")) {
508                     mDataset.setValue("Free", kb);
509                     total -= kb;
510                 } else if (line.startsWith("Slab")) {
511                     mDataset.setValue("Slab", kb);
512                     total -= kb;
513                 } else if (line.startsWith("PageTables")) {
514                     mDataset.setValue("PageTables", kb);
515                     total -= kb;
516                 } else if (line.startsWith("Buffers") && kb > 0) {
517                     mDataset.setValue("Buffers", kb);
518                     total -= kb;
519                 } else if (line.startsWith("Inactive")) {
520                     mDataset.setValue("Inactive", kb);
521                     total -= kb;
522                 } else if (line.startsWith("MemFree")) {
523                     mDataset.setValue("Free", kb);
524                     total -= kb;
525                 }
526             } else {
527                 break;
528             }
529         }
530         // Scan procrank
531         while (true) {
532             String line = br.readLine();
533             if (line == null) {
534                 break;
535             }
536             if (line.indexOf("PROCRANK") >= 0 || line.indexOf("PID") >= 0) {
537                 // procrank header
538                 continue;
539             }
540             if  (line.indexOf("----") >= 0) {
541                 //end of procrank section
542                 break;
543             }
544             // Extract pss field from procrank output
545             long pss = Long.parseLong(line.substring(23, 31).trim());
546             String cmdline = line.substring(43).trim().replace("/system/bin/", "");
547             // Arbitrary minimum size to display
548             if (pss > 2000) {
549                 mDataset.setValue(cmdline, pss);
550             } else {
551                 other += pss;
552             }
553             total -= pss;
554         }
555         mDataset.setValue("Other", other);
556         mDataset.setValue("Unknown", total);
557     }
558
559     /**
560      * Processes sync information from bugreport. Updates mDataset with the new
561      * data.
562      *
563      * @param br Reader providing the content
564      * @throws IOException if error reading file
565      */
566     void readSyncDataset(BufferedReader br) throws IOException {
567         while (true) {
568             String line = br.readLine();
569             if (line == null || line.startsWith("DUMP OF SERVICE")) {
570                 // Done, or moved on to the next service
571                 break;
572             }
573             if (line.startsWith(" |") && line.length() > 70) {
574                 String authority = line.substring(3, 18).trim();
575                 String duration = line.substring(61, 70).trim();
576                 // Duration is MM:SS or HH:MM:SS (DateUtils.formatElapsedTime)
577                 String durParts[] = duration.split(":");
578                 if (durParts.length == 2) {
579                     long dur = Long.parseLong(durParts[0]) * 60 + Long
580                             .parseLong(durParts[1]);
581                     mDataset.setValue(authority, dur);
582                 } else if (duration.length() == 3) {
583                     long dur = Long.parseLong(durParts[0]) * 3600
584                             + Long.parseLong(durParts[1]) * 60 + Long
585                             .parseLong(durParts[2]);
586                     mDataset.setValue(authority, dur);
587                 }
588             }
589         }
590     }
591 }