1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.graalvm.compiler.hotspot;
24
25import java.io.File;
26import java.io.IOException;
27import java.lang.reflect.Field;
28import java.net.URI;
29import java.net.URL;
30import java.net.URLClassLoader;
31import java.nio.file.FileSystem;
32import java.nio.file.FileSystems;
33import java.nio.file.FileVisitResult;
34import java.nio.file.Files;
35import java.nio.file.Path;
36import java.nio.file.SimpleFileVisitor;
37import java.nio.file.attribute.BasicFileAttributes;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Collections;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Set;
44import java.util.regex.Pattern;
45import java.util.regex.PatternSyntaxException;
46import java.util.stream.Collectors;
47
48import org.graalvm.compiler.debug.CSVUtil;
49import org.graalvm.compiler.debug.GraalError;
50import org.graalvm.compiler.graph.Node;
51import org.graalvm.compiler.graph.NodeClass;
52import org.graalvm.compiler.graph.spi.Canonicalizable;
53import org.graalvm.compiler.nodes.memory.MemoryCheckpoint;
54import org.graalvm.compiler.nodes.spi.Virtualizable;
55
56public class NodeCostDumpUtil {
57
58    private static final String prefix1 = "com.oracle.";
59    private static final String prefix2 = "org.graalvm.";
60    private static final String FMT = CSVUtil.buildFormatString("%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s");
61
62    private static String getArgumentRegex(String arg) {
63        if (arg.length() == 0) {
64            return null;
65        }
66        try {
67            Pattern.compile(arg);
68            return arg;
69        } catch (PatternSyntaxException e) {
70            // silently ignore
71            System.err.println("Invalid regex given, defaulting to \".*\" regex..");
72            return null;
73        }
74    }
75
76    public static void main(String[] args) {
77        if (args.length != 1) {
78            System.err.println("NodeCostDumpUtil expects exactly one argument, the node name regex to match against.");
79            System.exit(-1);
80        }
81        final String pattern = getArgumentRegex(args[0]);
82        String version = System.getProperty("java.specification.version");
83        if (version.compareTo("1.9") >= 0) {
84            System.err.printf("NodeCostDumpUtil does not support JDK versions greater than 1.8, current version is %s.\n", version);
85            System.exit(-1);
86        }
87        String[] jvmciCP = System.getProperty("jvmci.class.path.append").split(File.pathSeparator);
88        String[] primarySuiteCP = System.getProperty("primary.suite.cp").split(File.pathSeparator);
89        ClassLoader applicationClassLoader = Thread.currentThread().getContextClassLoader();
90        HashSet<Class<?>> classes = new HashSet<>();
91        try {
92            Set<String> uniquePaths = new HashSet<>(Arrays.asList(primarySuiteCP));
93            uniquePaths.addAll(Arrays.asList(jvmciCP));
94            for (String path : uniquePaths) {
95                if (new File(path).exists()) {
96                    if (path.endsWith(".jar")) {
97                        try (FileSystem jarFileSystem = FileSystems.newFileSystem(URI.create("jar:file:" + path), Collections.emptyMap())) {
98                            initAllClasses(jarFileSystem.getPath("/"), applicationClassLoader, classes);
99                        }
100                    } else {
101                        initAllClasses(FileSystems.getDefault().getPath(path), applicationClassLoader, classes);
102                    }
103                }
104            }
105        } catch (IOException ex) {
106            GraalError.shouldNotReachHere();
107        }
108        System.err.printf("Loaded %d classes...\n", classes.size());
109        List<Class<?>> nodeClasses = new ArrayList<>();
110        for (Class<?> loaded : classes) {
111            if (Node.class.isAssignableFrom(loaded) && !loaded.isArray()) {
112                nodeClasses.add(loaded);
113            }
114        }
115        System.err.printf("Loaded %s node classes...\n", nodeClasses.size());
116        List<NodeClass<?>> nc = new ArrayList<>();
117        for (Class<?> nodeClass : nodeClasses) {
118            Field f;
119            try {
120                f = nodeClass.getField("TYPE");
121                f.setAccessible(true);
122                Object val = f.get(null);
123                NodeClass<?> nodeType = (NodeClass<?>) val;
124                nc.add(nodeType);
125            } catch (Throwable t) {
126                // Silently ignore problems here
127            }
128        }
129        System.err.printf("Read TYPE field from %s node classes...\n", nc.size());
130        nc = nc.stream().filter(x -> x != null).collect(Collectors.toList());
131        nc.sort((x, y) -> {
132            String a = x.getJavaClass().getName();
133            String b = y.getJavaClass().getName();
134            return a.compareTo(b);
135        });
136        CSVUtil.Escape.println(System.out, FMT, "NodeName", "Size", "Overrides Size Method", "Cycles", "Overrides Cycles Method", "Canonicalizable", "MemoryCheckPoint", "Virtualizable");
137        for (NodeClass<?> nodeclass : nc) {
138            String packageStrippedName = null;
139            try {
140                packageStrippedName = nodeclass.getJavaClass().getCanonicalName().replace(prefix1, "").replace(prefix2, "");
141            } catch (Throwable t) {
142                // do nothing
143                continue;
144            }
145            if (pattern != null && !packageStrippedName.matches(pattern)) {
146                continue;
147            }
148            boolean overridesSizeMethod = false;
149            boolean overridesCyclesMethod = false;
150            Class<?> c = nodeclass.getJavaClass();
151            try {
152                c.getDeclaredMethod("estimatedNodeSize");
153                overridesSizeMethod = true;
154            } catch (Throwable t) {
155                // do nothing
156            }
157            try {
158                c.getDeclaredMethod("estimatedNodeCycles");
159                overridesCyclesMethod = true;
160            } catch (Throwable t) {
161                // do nothing
162            }
163            CSVUtil.Escape.println(System.out, FMT, packageStrippedName, nodeclass.size(), overridesSizeMethod, nodeclass.cycles(), overridesCyclesMethod, canonicalizable(c), memoryCheckPoint(c),
164                            virtualizable(c));
165        }
166    }
167
168    private static boolean canonicalizable(Class<?> c) {
169        return Canonicalizable.class.isAssignableFrom(c);
170    }
171
172    private static boolean virtualizable(Class<?> c) {
173        return Virtualizable.class.isAssignableFrom(c);
174    }
175
176    private static boolean memoryCheckPoint(Class<?> c) {
177        return MemoryCheckpoint.class.isAssignableFrom(c);
178    }
179
180    private static void initAllClasses(final Path root, ClassLoader classLoader, HashSet<Class<?>> classes) {
181        try {
182            Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
183                @Override
184                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
185                    String className = root.relativize(file).toString();
186                    ClassLoader c = classLoader;
187                    if (className.endsWith(".class")) {
188                        String prefix = prefixed(className);
189                        if (prefix != null) {
190                            String stripped = stripClassName(className);
191                            c = new URLClassLoader(new URL[]{new File(constructURLPart(stripped, className, prefix)).toURI().toURL()}, classLoader);
192                            className = constructClazzPart(stripped, prefix);
193                        } else {
194                            String clazzPart = className.replace('/', '.');
195                            className = clazzPart.substring(0, clazzPart.length() - ".class".length());
196                        }
197                        try {
198                            Class<?> systemClass = Class.forName(className, false, c);
199                            if (systemClass.getEnclosingClass() != null) {
200                                try {
201                                    classes.add(systemClass.getEnclosingClass());
202                                } catch (Throwable t) {
203                                    // do nothing
204                                }
205                            }
206                            classes.add(systemClass);
207                        } catch (Throwable ignored) {
208                        }
209                    }
210                    return FileVisitResult.CONTINUE;
211                }
212            });
213        } catch (IOException ex) {
214            GraalError.shouldNotReachHere();
215        }
216    }
217
218    private static String prefixed(String className) {
219        if (className.contains(prefix1) && className.indexOf(prefix1) > 0) {
220            return prefix1;
221        } else if (className.contains(prefix2) && className.indexOf(prefix2) > 0) {
222            return prefix2;
223        }
224        return null;
225    }
226
227    private static String stripClassName(String className) {
228        return className.replace('/', '.');
229    }
230
231    private static String constructClazzPart(String stripped, String prefix) {
232        String clazzPart = stripped.substring(stripped.lastIndexOf(prefix), stripped.length());
233        return clazzPart.substring(0, clazzPart.length() - ".class".length());
234    }
235
236    private static String constructURLPart(String stripped, String className, String prefix) {
237        return className.substring(0, stripped.lastIndexOf(prefix));
238    }
239
240}
241