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
17package com.android.preload.classdataretrieval.hprof;
18
19import com.android.ddmlib.Client;
20import com.android.ddmlib.ClientData;
21import com.android.ddmlib.ClientData.IHprofDumpHandler;
22import com.android.preload.classdataretrieval.ClassDataRetriever;
23import com.android.preload.ui.NullProgressMonitor;
24import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
25import com.android.tools.perflib.heap.ClassObj;
26import com.android.tools.perflib.heap.Queries;
27import com.android.tools.perflib.heap.Snapshot;
28
29import java.io.BufferedOutputStream;
30import java.io.File;
31import java.io.FileOutputStream;
32import java.util.HashMap;
33import java.util.Map;
34import java.util.Set;
35
36public class Hprof implements ClassDataRetriever {
37
38    private static GeneralHprofDumpHandler hprofHandler;
39
40    public static void init() {
41        synchronized(Hprof.class) {
42            if (hprofHandler == null) {
43                ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
44            }
45        }
46    }
47
48    public static File doHprof(Client client, int timeout) {
49        GetHprof gh = new GetHprof(client, timeout);
50        return gh.get();
51    }
52
53    /**
54     * Return a map of class names to class-loader names derived from the hprof dump.
55     *
56     * @param hprofLocalFile
57     */
58    public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
59        Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
60
61        Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
62        Map<String, String> retValue = new HashMap<String, String>();
63        for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
64            for (ClassObj c : e.getValue()) {
65                String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
66                String cName = c.getClassName();
67                int aDepth = 0;
68                while (cName.endsWith("[]")) {
69                    cName = cName.substring(0, cName.length()-2);
70                    aDepth++;
71                }
72                String newName = transformPrimitiveClass(cName);
73                if (aDepth > 0) {
74                    // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
75                    if (newName.equals(cName)) {
76                        newName = "L" + newName + ";";
77                    }
78                    for (int i = 0; i < aDepth; i++) {
79                        newName = "[" + newName;
80                    }
81                }
82                retValue.put(newName, cl);
83            }
84        }
85
86        // Free up memory.
87        snapshot.dispose();
88
89        return retValue;
90    }
91
92    private static Map<String, String> primitiveMapping;
93
94    static {
95        primitiveMapping = new HashMap<>();
96        primitiveMapping.put("boolean", "Z");
97        primitiveMapping.put("byte", "B");
98        primitiveMapping.put("char", "C");
99        primitiveMapping.put("double", "D");
100        primitiveMapping.put("float", "F");
101        primitiveMapping.put("int", "I");
102        primitiveMapping.put("long", "J");
103        primitiveMapping.put("short", "S");
104        primitiveMapping.put("void", "V");
105    }
106
107    private static String transformPrimitiveClass(String name) {
108        String rep = primitiveMapping.get(name);
109        if (rep != null) {
110            return rep;
111        }
112        return name;
113    }
114
115    private static class GetHprof implements IHprofDumpHandler {
116
117        private File target;
118        private long timeout;
119        private Client client;
120
121        public GetHprof(Client client, long timeout) {
122            this.client = client;
123            this.timeout = timeout;
124        }
125
126        public File get() {
127            synchronized (this) {
128                hprofHandler.addHandler(this);
129                client.dumpHprof();
130                if (target == null) {
131                    try {
132                        wait(timeout);
133                    } catch (Exception e) {
134                        System.out.println(e);
135                    }
136                }
137            }
138
139            hprofHandler.removeHandler(this);
140            return target;
141        }
142
143        private void wakeUp() {
144            synchronized (this) {
145                notifyAll();
146            }
147        }
148
149        @Override
150        public void onEndFailure(Client arg0, String arg1) {
151            System.out.println("GetHprof.onEndFailure");
152            if (client == arg0) {
153                wakeUp();
154            }
155        }
156
157        private static File createTargetFile() {
158            try {
159                return File.createTempFile("ddms", ".hprof");
160            } catch (Exception e) {
161                throw new RuntimeException(e);
162            }
163        }
164
165        @Override
166        public void onSuccess(String arg0, Client arg1) {
167            System.out.println("GetHprof.onSuccess");
168            if (client == arg1) {
169                try {
170                    target = createTargetFile();
171                    arg1.getDevice().getSyncService().pullFile(arg0,
172                            target.getAbsoluteFile().toString(), new NullProgressMonitor());
173                } catch (Exception e) {
174                    if (target != null) {
175                        target.delete();
176                    }
177                    e.printStackTrace();
178                    target = null;
179                }
180                wakeUp();
181            }
182        }
183
184        @Override
185        public void onSuccess(byte[] arg0, Client arg1) {
186            System.out.println("GetHprof.onSuccess");
187            if (client == arg1) {
188                try {
189                    target = createTargetFile();
190                    BufferedOutputStream out =
191                            new BufferedOutputStream(new FileOutputStream(target));
192                    out.write(arg0);
193                    out.close();
194                } catch (Exception e) {
195                    if (target != null) {
196                        target.delete();
197                    }
198                    e.printStackTrace();
199                    target = null;
200                }
201                wakeUp();
202            }
203        }
204    }
205
206    private int timeout;
207
208    public Hprof(int timeout) {
209        this.timeout = timeout;
210    }
211
212    @Override
213    public Map<String, String> getClassData(Client client) {
214        File hprofLocalFile = Hprof.doHprof(client, timeout);
215        if (hprofLocalFile == null) {
216            throw new RuntimeException("Failed getting dump...");
217        }
218        System.out.println("Dump file is " + hprofLocalFile);
219
220        try {
221            return analyzeHprof(hprofLocalFile);
222        } catch (Exception e) {
223            throw new RuntimeException(e);
224        } finally {
225            hprofLocalFile.delete();
226        }
227    }
228}
229