1 /*
2  * Copyright (C) 2015 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.ahat;
18 
19 import com.android.ahat.heapdump.AhatSnapshot;
20 import com.android.ahat.heapdump.Diff;
21 import com.android.ahat.heapdump.HprofFormatException;
22 import com.android.ahat.heapdump.Parser;
23 import com.android.ahat.heapdump.Reachability;
24 import com.android.ahat.progress.Progress;
25 import com.android.ahat.proguard.ProguardMap;
26 import com.sun.net.httpserver.HttpServer;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.PrintStream;
30 import java.net.InetAddress;
31 import java.net.InetSocketAddress;
32 import java.text.ParseException;
33 import java.util.concurrent.Executors;
34 
35 /**
36  * Contains the main entry point for the ahat heap dump viewer.
37  */
38 public class Main {
Main()39   private Main() {
40   }
41 
help(PrintStream out)42   private static void help(PrintStream out) {
43     out.println("java -jar ahat.jar [OPTIONS] FILE");
44     out.println("  Launch an http server for viewing the given Android heap dump FILE.");
45     out.println("");
46     out.println("OPTIONS:");
47     out.println("  -p <port>");
48     out.println("     Serve pages on the given port. Defaults to 7100.");
49     out.println("  --proguard-map FILE");
50     out.println("     Use the proguard map FILE to deobfuscate the heap dump.");
51     out.println("  --baseline FILE");
52     out.println("     Diff the heap dump against the given baseline heap dump FILE.");
53     out.println("  --baseline-proguard-map FILE");
54     out.println("     Use the proguard map FILE to deobfuscate the baseline heap dump.");
55     out.println("  --retained [strong | soft | finalizer | weak | phantom | unreachable]");
56     out.println("     The weakest reachability of instances to treat as retained.");
57     out.println("     Defaults to soft");
58     out.println("");
59   }
60 
61   /**
62    * Load the given heap dump file.
63    * Prints an error message and exits the application on failure to load the
64    * heap dump.
65    */
loadHeapDump(File hprof, ProguardMap map, Progress progress, Reachability retained)66   private static AhatSnapshot loadHeapDump(File hprof,
67       ProguardMap map, Progress progress, Reachability retained) {
68     System.out.println("Processing '" + hprof + "' ...");
69     try {
70       return new Parser(hprof).map(map).progress(progress).retained(retained).parse();
71     } catch (IOException e) {
72       System.err.println("Unable to load '" + hprof + "':");
73       e.printStackTrace();
74     } catch (HprofFormatException e) {
75       System.err.println("'" + hprof + "' does not appear to be a valid Java heap dump:");
76       e.printStackTrace();
77     }
78     System.exit(1);
79     throw new AssertionError("Unreachable");
80   }
81 
82   /**
83    * Main entry for ahat heap dump viewer.
84    * Launches an http server on localhost for viewing a given heap dump.
85    * See the ahat README or pass "--help" as one of the arguments to see a
86    * description of what arguments and options are expected.
87    *
88    * @param args the command line arguments
89    */
main(String[] args)90   public static void main(String[] args) {
91     int port = 7100;
92     for (String arg : args) {
93       if (arg.equals("--help")) {
94         help(System.out);
95         return;
96       }
97     }
98 
99     File hprof = null;
100     File hprofbase = null;
101     ProguardMap map = new ProguardMap();
102     ProguardMap mapbase = new ProguardMap();
103     Reachability retained = Reachability.SOFT;
104     for (int i = 0; i < args.length; i++) {
105       if ("-p".equals(args[i]) && i + 1 < args.length) {
106         i++;
107         port = Integer.parseInt(args[i]);
108       } else if ("--proguard-map".equals(args[i]) && i + 1 < args.length) {
109         i++;
110         try {
111           map.readFromFile(new File(args[i]));
112         } catch (IOException | ParseException ex) {
113           System.out.println("Unable to read proguard map: " + ex);
114           System.out.println("The proguard map will not be used.");
115         }
116       } else if ("--baseline-proguard-map".equals(args[i]) && i + 1 < args.length) {
117         i++;
118         try {
119           mapbase.readFromFile(new File(args[i]));
120         } catch (IOException | ParseException ex) {
121           System.out.println("Unable to read baseline proguard map: " + ex);
122           System.out.println("The proguard map will not be used.");
123         }
124       } else if ("--baseline".equals(args[i]) && i + 1 < args.length) {
125         i++;
126         if (hprofbase != null) {
127           System.err.println("multiple baseline heap dumps.");
128           help(System.err);
129           return;
130         }
131         hprofbase = new File(args[i]);
132       } else if ("--retained".equals(args[i]) && i + 1 < args.length) {
133         i++;
134         switch (args[i]) {
135           case "strong": retained = Reachability.STRONG; break;
136           case "soft": retained = Reachability.SOFT; break;
137           case "finalizer": retained = Reachability.FINALIZER; break;
138           case "weak": retained = Reachability.WEAK; break;
139           case "phantom": retained = Reachability.PHANTOM; break;
140           case "unreachable": retained = Reachability.UNREACHABLE; break;
141           default:
142             System.err.println("Invalid retained reference type: " + args[i]);
143             help(System.err);
144             return;
145         }
146       } else {
147         if (hprof != null) {
148           System.err.println("multiple input files.");
149           help(System.err);
150           return;
151         }
152         hprof = new File(args[i]);
153       }
154     }
155 
156     if (hprof == null) {
157       System.err.println("no input file.");
158       help(System.err);
159       return;
160     }
161 
162     // Launch the server before parsing the hprof file so we get
163     // BindExceptions quickly.
164     InetAddress loopback = InetAddress.getLoopbackAddress();
165     InetSocketAddress addr = new InetSocketAddress(loopback, port);
166     System.out.println("Preparing " + addr + " ...");
167     HttpServer server = null;
168     try {
169       server = HttpServer.create(addr, 0);
170     } catch (IOException e) {
171       System.err.println("Unable to setup ahat server:");
172       e.printStackTrace();
173       System.exit(1);
174     }
175 
176     AhatSnapshot ahat = loadHeapDump(hprof, map, new AsciiProgress(), retained);
177     if (hprofbase != null) {
178       AhatSnapshot base = loadHeapDump(hprofbase, mapbase, new AsciiProgress(), retained);
179 
180       System.out.println("Diffing heap dumps ...");
181       Diff.snapshots(ahat, base);
182     }
183 
184     server.createContext("/",
185         new AhatHttpHandler(new OverviewHandler(ahat, hprof, hprofbase, retained)));
186     server.createContext("/rooted", new AhatHttpHandler(new RootedHandler(ahat)));
187     server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat)));
188     server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat)));
189     server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat)));
190     server.createContext("/bitmap", new BitmapHandler(ahat));
191     server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
192     server.setExecutor(Executors.newFixedThreadPool(1));
193     System.out.println("Server started on localhost:" + port);
194 
195     server.start();
196   }
197 }
198 
199