package org.jgroups;
import org.jgroups.blocks.MethodCall;
import org.jgroups.jmx.AdditionalJmxObjects;
import org.jgroups.jmx.ResourceDMBean;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.stack.DiagnosticsHandler;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Util;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;
/**
* @author Bela Ban
* @since 4.0
*/
public class JChannelProbeHandler implements DiagnosticsHandler.ProbeHandler {
protected final JChannel ch;
protected final Log log;
public JChannelProbeHandler(JChannel ch) {
this.ch=ch;
log=LogFactory.getLog(ch.getClass());
}
public Map<String, String> handleProbe(String... keys) {
Map<String,String> map=new TreeMap<>();
for(String key : keys) {
if(key.startsWith("jmx")) {
handleJmx(map, key);
continue;
}
if(key.startsWith("reset-stats")) {
resetAllStats();
continue;
}
if(key.startsWith("invoke") || key.startsWith("op")) {
int index=key.indexOf('=');
if(index != -1) {
try {
handleOperation(map, key.substring(index + 1));
}
catch(Throwable throwable) {
log.error(Util.getMessage("OperationInvocationFailure"), key.substring(index + 1), throwable);
}
}
continue;
}
if(key.startsWith("threads")) {
ThreadMXBean bean=ManagementFactory.getThreadMXBean();
boolean cpu_supported=bean.isThreadCpuTimeSupported();
boolean contention_supported=bean.isThreadContentionMonitoringSupported();
int max_name=0;
long[] ids=bean.getAllThreadIds();
List<ThreadEntry> entries=new ArrayList<>(ids.length);
for(long id : ids) {
ThreadInfo info=bean.getThreadInfo(id);
if(info == null) continue;
String thread_name=info.getThreadName();
max_name=Math.max(max_name, thread_name.length());
Thread.State state=info.getThreadState();
long blocked=info.getBlockedCount();
long blocked_time=contention_supported? info.getBlockedTime() : -1;
long waited=info.getWaitedCount();
long waited_time=contention_supported? info.getWaitedTime() : -1;
double cpu_time=cpu_supported? bean.getThreadCpuTime(id) : -1;
if(cpu_time > 0)
cpu_time/=1_000_000;
double user_time=cpu_supported? bean.getThreadUserTime(id) : -1;
if(user_time > 0)
user_time/=1_000_000;
ThreadEntry entry=new ThreadEntry(state, thread_name, blocked, waited, blocked_time, waited_time,
cpu_time, user_time);
entries.add(entry);
}
int index=key.indexOf('=');
if(index >= 0) {
Comparator<ThreadEntry> comp=Comparator.comparing(e -> e.thread_name);
String val=key.substring(index+1);
if(val.startsWith("state"))
comp=Comparator.comparing(e -> e.state);
else if(val.startsWith("cpu"))
comp=Comparator.comparing((ThreadEntry e) -> e.cpu_time).reversed();
else if(val.startsWith("user"))
comp=Comparator.comparing((ThreadEntry e) -> e.user_time).reversed();
else if(val.startsWith("block"))
comp=Comparator.comparing((ThreadEntry e) -> e.blocks).reversed();
else if(val.startsWith("btime"))
comp=Comparator.comparing((ThreadEntry e) -> e.block_time).reversed();
else if(val.startsWith("wait"))
comp=Comparator.comparing((ThreadEntry e) -> e.waits).reversed();
else if(val.startsWith("wtime"))
comp=Comparator.comparing((ThreadEntry e) -> e.wait_time).reversed();
entries.sort(comp);
}
// see if we need to limit the displayed data
index=key.indexOf('=', index+1);
int limit=0;
if(index >= 0) {
String val=key.substring(index+1);
limit=Integer.valueOf(val);
}
max_name=Math.min(max_name, 50)+1;
String title="\n[%s] \t%-" + max_name+"s: %10s %10s %6s %9s %10s %10s\n";
String line="[%s]\t%-"+max_name+"s: %,8.0f %,8.0f %,10d %,9.0f %,10d %,10.0f\n";
StringBuilder sb=new StringBuilder(String.format(title,
"state", "thread-name", "cpu (ms)", "user (ms)",
"block", "btime (ms)", "wait", "wtime (ms)"));
Stream<ThreadEntry> stream=entries.stream();
if(limit > 0)
stream=stream.limit(limit);
stream.forEach(e -> sb.append(e.print(line)));
map.put(key, sb.toString());
continue;
}
if(key.equals("enable-cpu")) {
map.put(key, enable(1, true));
continue;
}
if(key.startsWith("enable-cont")) {
map.put(key, enable(2, true));
continue;
}
if(key.equals("disable-cpu")) {
map.put(key, enable(1, false));
continue;
}
if(key.startsWith("disable-cont")) {
map.put(key, enable(2, false));
}
}
return map;
}
public String[] supportedKeys() {
return new String[]{"reset-stats", "jmx", "op=<operation>[<args>]",
"threads[=<filter>[=<limit>]]", "enable-cpu", "enable-contention", "disable-cpu", "disable-contention"};
}
protected static String enable(int type, boolean flag) {
ThreadMXBean bean=ManagementFactory.getThreadMXBean();
boolean supported=false;
if(type == 1) { // cpu
supported=bean.isThreadCpuTimeSupported();
if(supported)
bean.setThreadCpuTimeEnabled(flag);
}
else if(type == 2) {
supported=bean.isThreadContentionMonitoringSupported();
if(supported)
bean.setThreadContentionMonitoringEnabled(flag);
}
String tmp=type == 1? "CPU" : "contention";
return String.format("%s monitoring supported: %b, %s monitoring enabled: %b", tmp, supported, tmp, supported && flag);
}
protected JChannel resetAllStats() {
List<Protocol> prots=ch.getProtocolStack().getProtocols();
prots.forEach(Protocol::resetStatistics);
return ch.resetStats();
}
protected void handleJmx(Map<String, String> map, String input) {
Map<String, Object> tmp_stats;
int index=input.indexOf('=');
if(index > -1) {
List<String> list=null;
String protocol_name=input.substring(index +1);
index=protocol_name.indexOf('.');
if(index > -1) {
String rest=protocol_name;
protocol_name=protocol_name.substring(0, index);
String attrs=rest.substring(index +1); // e.g. "num_sent,msgs,num_received_msgs"
list=Util.parseStringList(attrs, ",");
// check if there are any attribute-sets in the list
for(Iterator<String> it=list.iterator(); it.hasNext();) {
String tmp=it.next();
index=tmp.indexOf('=');
if(index != -1) {
String attrname=tmp.substring(0, index);
String attrvalue=tmp.substring(index+1);
Object target=ch.getProtocolStack().findProtocol(protocol_name);
Field field=target != null? Util.getField(target.getClass(), attrname) : null;
if(field == null && target instanceof AdditionalJmxObjects) {
Object[] objs=((AdditionalJmxObjects)target).getJmxObjects();
if(objs != null && objs.length > 0) {
for(Object o: objs) {
field=o != null? Util.getField(o.getClass(), attrname) : null;
if(field != null) {
target=o;
break;
}
}
}
}
if(field != null) {
Object value=Util.convert(attrvalue, field.getType());
if(value != null) {
if(target instanceof Protocol)
((Protocol)target).setValue(attrname, value);
else
Util.setField(field, target, value);
}
}
else {
// try to find a setter for X, e.g. x(type-of-x) or setX(type-of-x)
ResourceDMBean.Accessor setter=ResourceDMBean.findSetter(target, attrname); // Util.getSetter(prot.getClass(), attrname);
if(setter == null && target instanceof AdditionalJmxObjects) {
Object[] objs=((AdditionalJmxObjects)target).getJmxObjects();
if(objs != null && objs.length > 0) {
for(Object o: objs) {
setter=o != null? ResourceDMBean.findSetter(target, attrname) : null;
if(setter!= null)
break;
}
}
}
if(setter != null) {
try {
Class<?> type=setter instanceof ResourceDMBean.FieldAccessor?
((ResourceDMBean.FieldAccessor)setter).getField().getType() :
setter instanceof ResourceDMBean.MethodAccessor?
((ResourceDMBean.MethodAccessor)setter).getMethod().getParameterTypes()[0] : null;
Object converted_value=Util.convert(attrvalue, type);
setter.invoke(converted_value);
}
catch(Exception e) {
log.error("unable to invoke %s() on %s: %s", setter, protocol_name, e);
}
}
else
log.warn(Util.getMessage("FieldNotFound"), attrname, protocol_name);
}
it.remove();
}
}
}
tmp_stats=ch.dumpStats(protocol_name, list);
if(tmp_stats != null) {
for(Map.Entry<String,Object> entry : tmp_stats.entrySet()) {
Map<String,Object> tmp_map=(Map<String,Object>)entry.getValue();
String key=entry.getKey();
map.put(key, tmp_map != null? tmp_map.toString() : null);
}
}
}
else {
tmp_stats=ch.dumpStats();
if(tmp_stats != null) {
for(Map.Entry<String,Object> entry : tmp_stats.entrySet()) {
Map<String,Object> tmp_map=(Map<String,Object>)entry.getValue();
String key=entry.getKey();
map.put(key, tmp_map != null? tmp_map.toString() : null);
}
}
}
}
/**
* Invokes an operation and puts the return value into map
* @param map
* @param operation Protocol.OperationName[args], e.g. STABLE.foo[arg1 arg2 arg3]
*/
protected void handleOperation(Map<String, String> map, String operation) throws Exception {
int index=operation.indexOf('.');
if(index == -1)
throw new IllegalArgumentException("operation " + operation + " is missing the protocol name");
String prot_name=operation.substring(0, index);
Protocol prot=ch.getProtocolStack().findProtocol(prot_name);
if(prot == null)
return; // less drastic than throwing an exception...
int args_index=operation.indexOf('[');
String method_name;
if(args_index != -1)
method_name=operation.substring(index +1, args_index).trim();
else
method_name=operation.substring(index+1).trim();
String[] args=null;
if(args_index != -1) {
int end_index=operation.indexOf(']');
if(end_index == -1)
throw new IllegalArgumentException("] not found");
List<String> str_args=Util.parseCommaDelimitedStrings(operation.substring(args_index + 1, end_index));
Object[] strings=str_args.toArray();
args=new String[strings.length];
for(int i=0; i < strings.length; i++)
args[i]=(String)strings[i];
}
Object target=prot;
Method method=MethodCall.findMethod(target.getClass(), method_name, args);
if(method == null) {
if(prot instanceof AdditionalJmxObjects) {
for(Object obj: ((AdditionalJmxObjects)prot).getJmxObjects()) {
method=MethodCall.findMethod(obj.getClass(), method_name, args);
if(method != null) {
target=obj;
break;
}
}
}
if(method == null) {
log.warn(Util.getMessage("MethodNotFound"), ch.getAddress(), target.getClass().getSimpleName(), method_name);
return;
}
}
MethodCall call=new MethodCall(method);
Object[] converted_args=null;
if(args != null) {
converted_args=new Object[args.length];
Class<?>[] types=method.getParameterTypes();
for(int i=0; i < args.length; i++)
converted_args[i]=Util.convert(args[i], types[i]);
}
Object retval=call.invoke(target, converted_args);
if(retval != null)
map.put(prot_name + "." + method_name, retval.toString());
}
protected static class ThreadEntry {
protected final Thread.State state;
protected final String thread_name;
protected final long blocks, waits;
protected final double block_time, wait_time; // ms
protected final double cpu_time, user_time; // ms
public ThreadEntry(Thread.State state, String thread_name, long blocks, long waits, double block_time, double wait_time,
double cpu_time, double user_time) {
this.state=state;
this.thread_name=thread_name;
this.blocks=blocks;
this.waits=waits;
this.block_time=block_time;
this.wait_time=wait_time;
this.cpu_time=cpu_time;
this.user_time=user_time;
}
public String toString() {
StringBuilder sb=new StringBuilder(String.format("[%s] %s:", state, thread_name));
sb.append(String.format(" blocks=%d (%.2f ms) waits=%d (%.2f ms)", blocks, block_time, waits, wait_time));
sb.append(String.format(" sys=%.2f ms user=%.2f ms\n", cpu_time, user_time));
return sb.toString();
}
protected String print(String format) {
return String.format(format, state, thread_name, cpu_time, user_time, blocks, block_time, waits, wait_time);
}
}
}