/* * * Copyright 2011 Performize-IT LTD. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.performizeit.threadtop; import com.performizeit.jmxsupport.JMXConnection; import com.performizeit.threadtop.localext.format.ColumnFormat; import com.performizeit.threadtop.localext.format.TableFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import javax.management.Attribute; import javax.management.openmbean.CompositeData; import static com.performizeit.jmxsupport.JMXConnection.*; public class ThreadTop { // printed columns public static final String TS = "TS"; public static final String PROCID = "PROCID"; public static final String TID = "TID"; public static final String BLOCKED_TIME = "BL"; public static final String BL_PERCENT = "BL%"; public static final String BL_Count = "#BL"; public static final String CPU = "CPU"; public static final String CPU_PERCENT = "CPU%"; public static final String ALLOC = "ALLOC"; public static final String NAME = "Name"; public static final String STACK_TRACE = "Stack Trace"; public enum SortBy { NAME, CPU, CONTENTION, ALLOC_BYTES } private ArrayList<JMXConnection> servers; private final long timeToMeasure; private final long numThreads; SortBy sortBy; String sortByStr; boolean measureCPU; boolean measureBytesAllocated; boolean measureContention; String filterThreadRegExp; int stackTraceEntriesNo=0; // no of thread stack trace entries to print /** * Ctor */ public ThreadTop(ArrayList<JMXConnection> servers, long timeToMeasure, long numThreads, String sortByStr, boolean measureCPU, boolean measureBytesAllocated, boolean measureContention, String filterThreadRegExp, int stackTraceEntriesNo) throws Exception { this.servers = servers; this.timeToMeasure = timeToMeasure; this.numThreads = numThreads; this.measureContention = measureContention; this.measureCPU = measureCPU; this.measureBytesAllocated = measureBytesAllocated; this.filterThreadRegExp = filterThreadRegExp; this.sortByStr = sortByStr; this.stackTraceEntriesNo = stackTraceEntriesNo; } private void sortBy() { if (!measureCPU && !measureContention && !measureBytesAllocated || sortByStr.toLowerCase().startsWith("n")) { sortBy = SortBy.NAME; } else if (!measureCPU && !measureBytesAllocated || sortByStr.toLowerCase().contains("d")) { sortBy = SortBy.CONTENTION; } else if (!measureCPU || sortByStr.toLowerCase().startsWith("a")) { sortBy = SortBy.ALLOC_BYTES; } else { sortBy = SortBy.CPU; } } private void enableContentionMonitoring() throws Exception { Attribute a = new Attribute("ThreadContentionMonitoringEnabled", Boolean.TRUE); for (JMXConnection server : servers) { boolean origValue = (Boolean) server.getServerConnection().getAttribute(THREADING, "ThreadContentionMonitoringEnabled"); server.getServerConnection().setAttribute(THREADING, a); server.setOriginalThreadContentionEnabledValue(origValue); } } private void revertContentionMonitoring() throws Exception { for (JMXConnection server : servers) { Attribute a = new Attribute("ThreadContentionMonitoringEnabled", server.isOriginalThreadContentionEnabledValue()); server.getServerConnection().setAttribute(THREADING, a); } } private long[] getThreadIds(JMXConnection server) throws Exception { long[] thIds = (long[]) server.getServerConnection().getAttribute(THREADING, "AllThreadIds"); return thIds; } private class BlockedTimeComparator implements Comparator<MyThreadInfo> { public int compare(MyThreadInfo o1, MyThreadInfo o2) { if (o1== null && o2 ==null) return 0; if (o1 == null) return 1; if (o2 == null) return -1; if (o1.getBlockedTime() > o2.getBlockedTime()) return -1; if (o1.getBlockedTime() < o2.getBlockedTime()) return 1; return 0; } } private class NameComparator implements Comparator<MyThreadInfo> { public int compare(MyThreadInfo o1, MyThreadInfo o2) { if (o1== null && o2 ==null) return 0; if (o1 == null) return 1; if (o2 == null) return -1; return o1.getName().compareTo(o2.getName()); } } private class CpuTimeComparator implements Comparator<MyThreadInfo> { public int compare(MyThreadInfo o1, MyThreadInfo o2) { if (o1== null && o2 ==null) return 0; if (o1 == null) return 1; if (o2 == null) return -1; if (o1.getCpuTime() > o2.getCpuTime()) return -1; if (o1.getCpuTime() < o2.getCpuTime()) return 1; return 0; } } private class AllocBytesComparator implements Comparator<MyThreadInfo> { public int compare(MyThreadInfo o1, MyThreadInfo o2) { if (o1== null && o2 ==null) return 0; if (o1 == null) return 1; if (o2 == null) return -1; if (o1.getAllocBytes() > o2.getAllocBytes()) return -1; if (o1.getAllocBytes() < o2.getAllocBytes()) return 1; return 0; } } public void getContendedThreads(int numSamples) throws Exception { if (measureContention) { enableContentionMonitoring(); } long st = System.currentTimeMillis(); HashMap <MyThreadInfo, MyThreadInfo> startThreads = new HashMap<MyThreadInfo, MyThreadInfo> (); for (JMXConnection server : servers) { buildThreadInfo(server,startThreads); } // System.out.println("Measuring took " + (System.currentTimeMillis() - st)); for (int nSamp = 0; nSamp < numSamples; nSamp++) { Thread.sleep(timeToMeasure); HashMap <MyThreadInfo, MyThreadInfo> endThreads = new HashMap<MyThreadInfo, MyThreadInfo> (); for (JMXConnection server : servers) { buildThreadInfo(server,endThreads); } ArrayList<MyThreadInfo> diffThreadInfo = new ArrayList<MyThreadInfo>(); for (MyThreadInfo mtie : endThreads.values()) { MyThreadInfo mtis = startThreads.get(mtie); if (mtis != null) { MyThreadInfo diff = new MyThreadInfo(mtis, mtie); diffThreadInfo.add(diff); } } startThreads = endThreads; // start thinking about next iteration MyThreadInfo[] diffThreadInfoArr = diffThreadInfo.toArray(new MyThreadInfo[diffThreadInfo.size()]); Comparator<MyThreadInfo> comp; sortBy(); if (sortBy == SortBy.CPU) { comp = new CpuTimeComparator(); } else if (sortBy == SortBy.CONTENTION) { comp = new BlockedTimeComparator(); } else if (sortBy == SortBy.ALLOC_BYTES) { comp = new AllocBytesComparator(); } else { comp = new NameComparator(); } Arrays.sort(diffThreadInfoArr, comp); // float ts = inSecsTimestamp(server.getUptime()); TableFormat tableFormat = new TableFormat(); tableFormat.addColumn(new ColumnFormat(TS,"%10s ","%10.3f ")); tableFormat.addColumn(new ColumnFormat(PROCID,"%-10s ")); tableFormat.addColumn(new ColumnFormat(TID,"%5s ","%5d ")); if(measureContention) { tableFormat.addColumn(new ColumnFormat(BLOCKED_TIME,sortBy == SortBy.CONTENTION,"%6s ","%6d ")); tableFormat.addColumn(new ColumnFormat(BL_PERCENT,"%4s ","%5.1f ")); tableFormat.addColumn(new ColumnFormat(BL_Count,"%5s ", "%4d ")); } if(measureCPU) { tableFormat.addColumn(new ColumnFormat(CPU,sortBy == SortBy.CPU,"%8s ","%8d ")); tableFormat.addColumn(new ColumnFormat(CPU_PERCENT,sortBy == SortBy.CPU,"%5s ","%5.1f ")); } if(measureBytesAllocated) { tableFormat.addColumn(new ColumnFormat(ALLOC,sortBy == SortBy.ALLOC_BYTES,"%8s ","%8d ")); } tableFormat.addColumn(new ColumnFormat(NAME,sortBy == SortBy.NAME ,"%-50s ")); if(stackTraceEntriesNo >0) { tableFormat.addColumn(new ColumnFormat(STACK_TRACE, "%-50s ")); } if (nSamp == 0) { tableFormat.printHeader(); } tableFormat.clean(); for (int i = 0; i < ((diffThreadInfoArr.length > numThreads) ? numThreads : diffThreadInfoArr.length); i++) { tableFormat.format(TS,0.0); //todo ask Haim tableFormat.format(PROCID,diffThreadInfoArr[i].getProcConnect()); tableFormat.format(TID, diffThreadInfoArr[i].getId()); tableFormat.format(BLOCKED_TIME, diffThreadInfoArr[i].getBlockedTime()); tableFormat.format(BL_PERCENT,(100.0 * diffThreadInfoArr[i].getBlockedTime() / timeToMeasure)); tableFormat.format(BL_Count, diffThreadInfoArr[i].getBlockedCount()); tableFormat.format(CPU, diffThreadInfoArr[i].getCpuTime() / 1000 / 1000); tableFormat.format(CPU_PERCENT, (100.0 * diffThreadInfoArr[i].getCpuTime() / 1000 / 1000 / timeToMeasure)); tableFormat.format(ALLOC, diffThreadInfoArr[i].getAllocBytes()); tableFormat.format(NAME, diffThreadInfoArr[i].getName()); if(stackTraceEntriesNo > 0) { String[] stackTraceEntries = diffThreadInfoArr[i].getStackTrace(); for(int j=0; j<stackTraceEntries.length; j++ ) { // for the second stack trace entry shift to the stack trace column if(j>0) { int columnNums = tableFormat.getColumnNo(); tableFormat.formatEmptyLine(columnNums-1); } tableFormat.format(STACK_TRACE, stackTraceEntries[j]); // do not print the last row if(j != stackTraceEntries.length-1) { tableFormat.printRow(); } } } tableFormat.printRow(); } } if (measureContention ) { revertContentionMonitoring(); } } private void buildThreadInfo(JMXConnection server,HashMap<MyThreadInfo,MyThreadInfo> threadMap) throws Exception { long[] thIds = getThreadIds(server); CompositeData[] threadsBaseData = server.getThreads( thIds, stackTraceEntriesNo); ThreadFilter tf = new ThreadFilterByRegExp(filterThreadRegExp);//".*((RMI)|(JMX)|(ajp)).*" ArrayList<Long> filteredThreadIds = new ArrayList<Long>(); for (int i = 0; i < threadsBaseData.length; i++) { // build map if (threadsBaseData[i] == null) continue; MyThreadInfo mtis = new MyThreadInfo(server.getConnectURL(),threadsBaseData[i]); if (tf.matchFilter(mtis)) { threadMap.put( mtis,mtis); filteredThreadIds.add(mtis.getId()); } } if (server.isJava16_25andAbove()) { try { long[] filteredThreadIdsArr = new long[filteredThreadIds.size()]; for (int i = 0; i < filteredThreadIds.size(); i++) { filteredThreadIdsArr[i] = filteredThreadIds.get(i); } if (measureCPU) { long threadCpus[] = server.getThreadsCPU( filteredThreadIdsArr); for (int i = 0; i < filteredThreadIdsArr.length; i++) { MyThreadInfo mtis = threadMap.get(MyThreadInfo.createProto(server.getConnectURL(),filteredThreadIdsArr[i]) ); mtis.setCpuTime(threadCpus[i]); } } if (measureBytesAllocated) { long threadAllocBytes[] = server.getThreadsAllocBytes( filteredThreadIdsArr); for (int i = 0; i < filteredThreadIdsArr.length; i++) { MyThreadInfo mtis = threadMap.get(MyThreadInfo.createProto(server.getConnectURL(),filteredThreadIdsArr[i]) ); mtis.setAllocBytes(threadAllocBytes[i]); } } } catch (Exception e) { measureBytesAllocated = false; server.unsetJava16_25andAbove(); if (sortByStr.equals("ALLOC")) { sortByStr = ""; } System.err.println("JVM do not support advanced APIs using slower APIs" ); e.printStackTrace(); } } if (!server.isJava16_25andAbove()) { if (measureCPU) { for (int i = 0; i < filteredThreadIds.size(); i++) { MyThreadInfo mtis = threadMap.get(MyThreadInfo.createProto(server.getConnectURL(),filteredThreadIds.get(i))); mtis.setCpuTime(server.getThreadCPU( mtis.getId())); } } } } }