Skip to content

Commit 2181510

Browse files
authored
Merge branch 'alibaba:master' into master
2 parents 14b9df7 + 7518088 commit 2181510

File tree

12 files changed

+710
-13
lines changed

12 files changed

+710
-13
lines changed

core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.SortedSet;
4242
import java.util.TreeMap;
4343
import java.util.TreeSet;
44+
import java.util.regex.Pattern;
4445

4546
@Name("classloader")
4647
@Summary("Show classloader info")
@@ -54,10 +55,15 @@
5455
" classloader -a -c 327a647b\n" +
5556
" classloader -c 659e0bfd --load demo.MathGame\n" +
5657
" classloader -u # url statistics\n" +
58+
" classloader -c 659e0bfd --url-classes\n" +
59+
" classloader -c 659e0bfd --url-classes -d\n" +
60+
" classloader -c 659e0bfd --url-classes --jar spring-core --class org.springframework\n" +
5761
Constants.WIKI + Constants.WIKI_HOME + "classloader")
5862
public class ClassLoaderCommand extends AnnotatedCommand {
5963

6064
private static Logger logger = LoggerFactory.getLogger(ClassLoaderCommand.class);
65+
private static final int DEFAULT_URL_CLASSES_LIMIT = 100;
66+
private static final String UNKNOWN_CODE_SOURCE = "<unknown>";
6167
private boolean isTree = false;
6268
private String hashCode;
6369
private String classLoaderClass;
@@ -68,6 +74,13 @@ public class ClassLoaderCommand extends AnnotatedCommand {
6874

6975
private boolean urlStat = false;
7076

77+
private boolean urlClasses = false;
78+
private boolean urlClassesDetail = false;
79+
private boolean urlClassesRegEx = false;
80+
private int urlClassesLimit = DEFAULT_URL_CLASSES_LIMIT;
81+
private String jarFilter;
82+
private String classFilter;
83+
7184
private String loadClass = null;
7285

7386
private volatile boolean isInterrupted = false;
@@ -126,6 +139,42 @@ public void setUrlStat(boolean urlStat) {
126139
this.urlStat = urlStat;
127140
}
128141

142+
@Option(longName = "url-classes", flag = true)
143+
@Description("Display relationship between jar(URL) and loaded classes in the specified ClassLoader")
144+
public void setUrlClasses(boolean urlClasses) {
145+
this.urlClasses = urlClasses;
146+
}
147+
148+
@Option(shortName = "d", longName = "details", flag = true)
149+
@Description("Display class list for each jar(URL), only works with --url-classes")
150+
public void setUrlClassesDetail(boolean urlClassesDetail) {
151+
this.urlClassesDetail = urlClassesDetail;
152+
}
153+
154+
@Option(shortName = "E", longName = "regex", flag = true)
155+
@Description("Enable regular expression to match for --jar/--class, only works with --url-classes")
156+
public void setUrlClassesRegEx(boolean urlClassesRegEx) {
157+
this.urlClassesRegEx = urlClassesRegEx;
158+
}
159+
160+
@Option(shortName = "n", longName = "limit")
161+
@Description("Maximum number of classes to display per jar(URL) in details mode (100 by default), only works with --url-classes -d")
162+
public void setUrlClassesLimit(int urlClassesLimit) {
163+
this.urlClassesLimit = urlClassesLimit;
164+
}
165+
166+
@Option(longName = "jar")
167+
@Description("Filter jar(URL) by keyword (or regex with -E), only works with --url-classes")
168+
public void setJarFilter(String jarFilter) {
169+
this.jarFilter = jarFilter;
170+
}
171+
172+
@Option(longName = "class")
173+
@Description("Filter classes by keyword/package (or regex with -E), only works with --url-classes")
174+
public void setClassFilter(String classFilter) {
175+
this.classFilter = StringUtils.normalizeClassName(classFilter);
176+
}
177+
129178
@Override
130179
public void process(CommandProcess process) {
131180
// ctrl-C support
@@ -143,6 +192,12 @@ public void process(CommandProcess process) {
143192
process.end();
144193
return;
145194
}
195+
196+
if (!urlClasses && (urlClassesDetail || urlClassesRegEx || jarFilter != null || classFilter != null
197+
|| urlClassesLimit != DEFAULT_URL_CLASSES_LIMIT)) {
198+
process.end(-1, "Options -d/-E/-n/--jar/--class only work with --url-classes.");
199+
return;
200+
}
146201

147202
if (hashCode != null || classLoaderClass != null) {
148203
classLoaderSpecified = true;
@@ -174,6 +229,19 @@ public void process(CommandProcess process) {
174229
}
175230
}
176231

232+
if (urlClasses) {
233+
if (!classLoaderSpecified) {
234+
process.end(-1, "Please specify classloader with '-c <classloader hash>' or '--classLoaderClass <classloader class name>' for --url-classes.");
235+
return;
236+
}
237+
if (targetClassLoader == null) {
238+
process.end(-1, "Can not find classloader by hashcode: " + hashCode + ".");
239+
return;
240+
}
241+
processUrlClasses(process, inst, targetClassLoader);
242+
return;
243+
}
244+
177245
if (all) {
178246
String hashCode = this.hashCode;
179247
if (StringUtils.isBlank(hashCode) && targetClassLoader != null) {
@@ -407,6 +475,172 @@ private boolean checkInterrupted(CommandProcess process) {
407475
}
408476
}
409477

478+
private void processUrlClasses(CommandProcess process, Instrumentation inst, ClassLoader targetClassLoader) {
479+
if (!urlClassesDetail && urlClassesLimit != DEFAULT_URL_CLASSES_LIMIT) {
480+
process.end(-1, "Option -n/--limit only works with --url-classes -d.");
481+
return;
482+
}
483+
if (urlClassesDetail && urlClassesLimit <= 0) {
484+
process.end(-1, "Option -n/--limit must be greater than 0.");
485+
return;
486+
}
487+
488+
Pattern jarPattern = null;
489+
Pattern classPattern = null;
490+
if (urlClassesRegEx) {
491+
try {
492+
if (jarFilter != null) {
493+
jarPattern = Pattern.compile(jarFilter);
494+
}
495+
if (classFilter != null) {
496+
classPattern = Pattern.compile(classFilter);
497+
}
498+
} catch (Throwable e) {
499+
process.end(-1, "Regex compile error: " + e.getMessage());
500+
return;
501+
}
502+
}
503+
504+
Map<String, UrlClassStatBuilder> statsMap = new HashMap<String, UrlClassStatBuilder>();
505+
Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
506+
for (int i = 0; i < allLoadedClasses.length; i++) {
507+
if ((i & 0x3FFF) == 0 && checkInterrupted(process)) {
508+
return;
509+
}
510+
Class<?> clazz = allLoadedClasses[i];
511+
if (clazz == null) {
512+
continue;
513+
}
514+
if (clazz.getClassLoader() != targetClassLoader) {
515+
continue;
516+
}
517+
518+
String url = codeSourceLocation(clazz);
519+
if (!matchJarFilter(url, jarPattern)) {
520+
continue;
521+
}
522+
523+
UrlClassStatBuilder builder = statsMap.get(url);
524+
if (builder == null) {
525+
builder = new UrlClassStatBuilder(url, classFilter != null, urlClassesDetail ? urlClassesLimit : 0);
526+
statsMap.put(url, builder);
527+
}
528+
builder.increaseLoadedCount();
529+
530+
if (classFilter != null) {
531+
if (matchClassFilter(clazz.getName(), classPattern)) {
532+
builder.increaseMatchedCount();
533+
builder.tryAddClass(clazz.getName());
534+
}
535+
} else {
536+
builder.tryAddClass(clazz.getName());
537+
}
538+
}
539+
540+
boolean hasClassFilter = classFilter != null;
541+
List<UrlClassStat> stats = new ArrayList<UrlClassStat>(statsMap.size());
542+
for (UrlClassStatBuilder builder : statsMap.values()) {
543+
if (hasClassFilter && builder.getMatchedClassCount() == 0) {
544+
continue;
545+
}
546+
stats.add(builder.build());
547+
}
548+
549+
Collections.sort(stats, new Comparator<UrlClassStat>() {
550+
@Override
551+
public int compare(UrlClassStat o1, UrlClassStat o2) {
552+
int c1 = hasClassFilter ? safeInt(o1.getMatchedClassCount()) : o1.getLoadedClassCount();
553+
int c2 = hasClassFilter ? safeInt(o2.getMatchedClassCount()) : o2.getLoadedClassCount();
554+
int diff = c2 - c1;
555+
if (diff != 0) {
556+
return diff;
557+
}
558+
return o1.getUrl().compareTo(o2.getUrl());
559+
}
560+
});
561+
562+
RowAffect affect = new RowAffect();
563+
affect.rCnt(stats.size());
564+
ClassLoaderModel model = new ClassLoaderModel()
565+
.setClassLoader(ClassUtils.createClassLoaderVO(targetClassLoader))
566+
.setUrlClassStats(stats)
567+
.setUrlClassStatsDetail(urlClassesDetail);
568+
process.appendResult(model);
569+
process.appendResult(new RowAffectModel(affect));
570+
process.end();
571+
}
572+
573+
private static int safeInt(Integer v) {
574+
return v == null ? 0 : v.intValue();
575+
}
576+
577+
private boolean matchJarFilter(String url, Pattern jarPattern) {
578+
if (jarFilter == null) {
579+
return true;
580+
}
581+
String jarName = guessJarName(url);
582+
if (urlClassesRegEx) {
583+
return jarPattern != null && (jarPattern.matcher(url).find() || jarPattern.matcher(jarName).find());
584+
}
585+
return containsIgnoreCase(url, jarFilter) || containsIgnoreCase(jarName, jarFilter);
586+
}
587+
588+
private boolean matchClassFilter(String className, Pattern classPattern) {
589+
if (classFilter == null) {
590+
return true;
591+
}
592+
if (urlClassesRegEx) {
593+
return classPattern != null && classPattern.matcher(className).find();
594+
}
595+
return containsIgnoreCase(className, classFilter);
596+
}
597+
598+
static boolean containsIgnoreCase(String text, String keyword) {
599+
if (text == null || keyword == null) {
600+
return false;
601+
}
602+
return text.toLowerCase().contains(keyword.toLowerCase());
603+
}
604+
605+
private static String codeSourceLocation(Class<?> clazz) {
606+
try {
607+
ProtectionDomain protectionDomain = clazz.getProtectionDomain();
608+
if (protectionDomain == null) {
609+
return UNKNOWN_CODE_SOURCE;
610+
}
611+
CodeSource codeSource = protectionDomain.getCodeSource();
612+
if (codeSource == null) {
613+
return UNKNOWN_CODE_SOURCE;
614+
}
615+
URL location = codeSource.getLocation();
616+
if (location == null) {
617+
return UNKNOWN_CODE_SOURCE;
618+
}
619+
return location.toString();
620+
} catch (Throwable t) {
621+
return UNKNOWN_CODE_SOURCE;
622+
}
623+
}
624+
625+
static String guessJarName(String url) {
626+
if (url == null) {
627+
return com.taobao.arthas.core.util.Constants.EMPTY_STRING;
628+
}
629+
String s = url;
630+
int bangIndex = s.lastIndexOf('!');
631+
if (bangIndex >= 0) {
632+
s = s.substring(0, bangIndex);
633+
}
634+
while (s.endsWith("/")) {
635+
s = s.substring(0, s.length() - 1);
636+
}
637+
int slash = Math.max(s.lastIndexOf('/'), s.lastIndexOf('\\'));
638+
if (slash >= 0 && slash < s.length() - 1) {
639+
s = s.substring(slash + 1);
640+
}
641+
return s;
642+
}
643+
410644
private Map<ClassLoaderVO, ClassLoaderUrlStat> urlStats(Instrumentation inst) {
411645
Map<ClassLoaderVO, ClassLoaderUrlStat> urlStats = new HashMap<ClassLoaderVO, ClassLoaderUrlStat>();
412646
Map<ClassLoader, Set<String>> usedUrlsMap = new HashMap<ClassLoader, Set<String>>();
@@ -639,6 +873,110 @@ public boolean accept(ClassLoader classLoader) {
639873
}
640874
}
641875

876+
public static class UrlClassStat {
877+
private String url;
878+
private int loadedClassCount;
879+
private Integer matchedClassCount;
880+
private List<String> classes;
881+
private boolean truncated;
882+
883+
public String getUrl() {
884+
return url;
885+
}
886+
887+
public void setUrl(String url) {
888+
this.url = url;
889+
}
890+
891+
public int getLoadedClassCount() {
892+
return loadedClassCount;
893+
}
894+
895+
public void setLoadedClassCount(int loadedClassCount) {
896+
this.loadedClassCount = loadedClassCount;
897+
}
898+
899+
public Integer getMatchedClassCount() {
900+
return matchedClassCount;
901+
}
902+
903+
public void setMatchedClassCount(Integer matchedClassCount) {
904+
this.matchedClassCount = matchedClassCount;
905+
}
906+
907+
public List<String> getClasses() {
908+
return classes;
909+
}
910+
911+
public void setClasses(List<String> classes) {
912+
this.classes = classes;
913+
}
914+
915+
public boolean isTruncated() {
916+
return truncated;
917+
}
918+
919+
public void setTruncated(boolean truncated) {
920+
this.truncated = truncated;
921+
}
922+
}
923+
924+
private static class UrlClassStatBuilder {
925+
private final String url;
926+
private final boolean hasClassFilter;
927+
private final int limit;
928+
private int loadedClassCount;
929+
private int matchedClassCount;
930+
private SortedSet<String> classNames;
931+
private boolean truncated;
932+
933+
UrlClassStatBuilder(String url, boolean hasClassFilter, int limit) {
934+
this.url = url;
935+
this.hasClassFilter = hasClassFilter;
936+
this.limit = limit;
937+
if (limit > 0) {
938+
this.classNames = new TreeSet<String>();
939+
}
940+
}
941+
942+
void increaseLoadedCount() {
943+
loadedClassCount++;
944+
}
945+
946+
void increaseMatchedCount() {
947+
matchedClassCount++;
948+
}
949+
950+
int getMatchedClassCount() {
951+
return matchedClassCount;
952+
}
953+
954+
void tryAddClass(String className) {
955+
if (classNames == null) {
956+
return;
957+
}
958+
if (classNames.size() >= limit) {
959+
truncated = true;
960+
return;
961+
}
962+
classNames.add(className);
963+
}
964+
965+
UrlClassStat build() {
966+
UrlClassStat stat = new UrlClassStat();
967+
stat.setUrl(url);
968+
stat.setLoadedClassCount(loadedClassCount);
969+
if (hasClassFilter) {
970+
stat.setMatchedClassCount(matchedClassCount);
971+
}
972+
if (classNames != null) {
973+
stat.setClasses(new ArrayList<String>(classNames));
974+
}
975+
stat.setTruncated(truncated);
976+
return stat;
977+
}
978+
}
979+
642980
public static class ClassLoaderUrlStat {
643981
private Collection<String> usedUrls;
644982
private Collection<String> unUsedUrls;

0 commit comments

Comments
 (0)