/* * Copyright (c) 2013-2016 Chris Newland. * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki */ package org.adoptopenjdk.jitwatch.jarscan; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT_CLASS; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SLASH; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_COMMA; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_ASTERISK; import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_EMPTY; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.adoptopenjdk.jitwatch.jarscan.allocationcount.AllocationCountOperation; import org.adoptopenjdk.jitwatch.jarscan.freqinlinesize.FreqInlineSizeOperation; import org.adoptopenjdk.jitwatch.jarscan.instructioncount.InstructionCountOperation; import org.adoptopenjdk.jitwatch.jarscan.invokecount.InvokeCountOperation; import org.adoptopenjdk.jitwatch.jarscan.methodlength.MethodLengthOperation; import org.adoptopenjdk.jitwatch.jarscan.methodsizehisto.MethodSizeHistoOperation; import org.adoptopenjdk.jitwatch.jarscan.nextinstruction.NextInstructionOperation; import org.adoptopenjdk.jitwatch.jarscan.sequencecount.SequenceCountOperation; import org.adoptopenjdk.jitwatch.jarscan.sequencesearch.SequenceSearchOperation; import org.adoptopenjdk.jitwatch.loader.BytecodeLoader; import org.adoptopenjdk.jitwatch.model.bytecode.ClassBC; import org.adoptopenjdk.jitwatch.model.bytecode.MemberBytecode; public class JarScan { private IJarScanOperation operation; private List<String> allowedPackagePrefixes = new ArrayList<>(); public JarScan(IJarScanOperation operation) { this.operation = operation; } public void writeReport() { Writer writer = new PrintWriter(System.out); String report = operation.getReport(); try { writer.write(report); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } public void iterateJar(File jarFile) throws IOException { List<String> classLocations = new ArrayList<>(); classLocations.add(jarFile.getPath()); try (ZipFile zip = new ZipFile(jarFile)) { @SuppressWarnings("unchecked") Enumeration<ZipEntry> list = (Enumeration<ZipEntry>) zip.entries(); while (list.hasMoreElements()) { ZipEntry entry = list.nextElement(); String name = entry.getName(); if (name.endsWith(S_DOT_CLASS)) { String fqName = name.replace(S_SLASH, S_DOT).substring(0, name.length() - S_DOT_CLASS.length()); process(classLocations, fqName); } } } } public void addAllowedPackagePrefix(String prefix) { allowedPackagePrefixes.add(prefix); } private boolean isAllowedPackage(String fqClassName) { boolean allowed = false; if (allowedPackagePrefixes.size() == 0) { allowed = true; } else { for (String allowedPrefix : allowedPackagePrefixes) { if (fqClassName.startsWith(allowedPrefix)) { allowed = true; break; } } } return allowed; } private void process(List<String> classLocations, String fqClassName) { if (!isAllowedPackage(fqClassName)) { return; } boolean cacheBytecode = false; ClassBC classBytecode = BytecodeLoader.fetchBytecodeForClass(classLocations, fqClassName, cacheBytecode); if (classBytecode != null) { for (MemberBytecode memberBytecode : classBytecode.getMemberBytecodeList()) { try { operation.processInstructions(fqClassName, memberBytecode); } catch (Exception e) { System.err.println( "Could not process " + fqClassName + " " + memberBytecode.getMemberSignatureParts().getMemberName()); System.err.println(memberBytecode.toString()); e.printStackTrace(); System.exit(-1); } } } else { System.err.println("An error occurred while parsing " + fqClassName); } } private static void showUsage() { StringBuilder builder = new StringBuilder(); String SEPARATOR = "---------------------------------------------------------------------------------------------------"; builder.append("JarScan --mode=<mode> [options] [params] <jars>").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append("Options:").append(S_NEWLINE); builder.append(" --packages=a,b,c Only include methods from named packages. E.g. --packages=java.util.*") .append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append("Modes:").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" maxMethodSize List every method with bytecode larger than specified limit.").append(S_NEWLINE); builder.append(" --limit=n Report methods larger than n bytes.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" sequenceCount Count instruction sequences.").append(S_NEWLINE); builder.append(" --length=n Report sequences of length n.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" invokeCount Count the most called methods for each invoke instruction.").append(S_NEWLINE); builder.append(" [--limit=n] Limit to top n results per invoke type.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" nextInstructionFreq List the most popular next instruction for each bytecode instruction.") .append(S_NEWLINE); builder.append(" [--limit=n] Limit to top n results per instruction.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" allocationCount Count the most allocated types.").append(S_NEWLINE); builder.append(" [--limit=n] Limit to top n results.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" instructionCount Count occurences of each bytecode instruction.").append(S_NEWLINE); builder.append(" [--limit=n] Limit to top n results.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" sequenceSearch List methods containing the specified bytecode sequence.").append(S_NEWLINE); builder.append(" --sequence=a,b,c,... Comma separated sequence of bytecode instructions.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" methodSizeHisto List frequencies of method bytecode sizes.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); builder.append(" methodLength List methods of the given bytecode size.").append(S_NEWLINE); builder.append(" --length=n Size of methods to find.").append(S_NEWLINE); builder.append(SEPARATOR).append(S_NEWLINE); System.err.println(builder.toString()); } private static final String ARG_PACKAGES = "--packages="; private static final String ARG_MODE = "--mode="; private static final String ARG_LIMIT = "--limit="; private static final String ARG_LENGTH = "--length="; private static final String ARG_SEQUENCE = "--sequence="; private static int getParam(String[] args, String paramName, boolean mandatory) { int result; if (!mandatory) { result = 0; } else { result = -1; } for (String param : args) { if (param.startsWith(paramName)) { String argValue = param.substring(paramName.length(), param.length()); try { result = Integer.parseInt(argValue); } catch (NumberFormatException nfe) { System.err.println("Could not parse parameter " + paramName + " : " + argValue); } break; } } return result; } private static String getParamString(String[] args, String paramName) { String result = null; for (String param : args) { if (param.startsWith(paramName)) { result = param.substring(paramName.length(), param.length()); break; } } return result; } private static IJarScanOperation getJarScanOperation(String[] args) { IJarScanOperation operation = null; String mode = getParamString(args, ARG_MODE); if (mode != null) { String modeParam = mode.toLowerCase(); switch (modeParam) { case "maxmethodsize": { int paramValue = getParam(args, ARG_LIMIT, true); if (paramValue > 0) { operation = new FreqInlineSizeOperation(paramValue); } break; } case "sequencecount": { int paramValue = getParam(args, ARG_LENGTH, true); if (paramValue > 0) { operation = new SequenceCountOperation(paramValue); } break; } case "invokecount": { int paramValue = getParam(args, ARG_LIMIT, false); if (paramValue >= 0) { operation = new InvokeCountOperation(paramValue); } break; } case "nextinstructionfreq": { int paramValue = getParam(args, ARG_LIMIT, false); if (paramValue >= 0) { operation = new NextInstructionOperation(paramValue); } } break; case "allocationcount": { int paramValue = getParam(args, ARG_LIMIT, false); if (paramValue >= 0) { operation = new AllocationCountOperation(paramValue); } break; } case "instructioncount": { int paramValue = getParam(args, ARG_LIMIT, false); if (paramValue >= 0) { operation = new InstructionCountOperation(paramValue); } break; } case "sequencesearch": { String sequence = getParamString(args, ARG_SEQUENCE); if (sequence != null) { operation = new SequenceSearchOperation(sequence); } break; } case "methodsizehisto": { operation = new MethodSizeHistoOperation(); break; } case "methodlength": { int paramValue = getParam(args, ARG_LENGTH, true); if (paramValue > 0) { operation = new MethodLengthOperation(paramValue); } break; } } } return operation; } public static void main(String[] args) throws IOException { IJarScanOperation operation = getJarScanOperation(args); if (operation == null) { showUsage(); System.exit(-1); } JarScan scanner = new JarScan(operation); String packages = getParamString(args, ARG_PACKAGES); if (packages != null) { String[] prefixes = packages.split(S_COMMA); for (String prefix : prefixes) { prefix = prefix.replace(S_ASTERISK, S_EMPTY); scanner.addAllowedPackagePrefix(prefix); } } for (String arg : args) { if (arg.startsWith("--")) { continue; } File jarFile = new File(arg); if (jarFile.exists() && jarFile.isFile()) { scanner.iterateJar(jarFile); } else { System.err.println("Could not scan jar " + jarFile.toString()); } } scanner.writeReport(); } }