/**
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat licenses this file to you 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 io.fabric8.apmagent.metrics;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import io.fabric8.apmagent.ApmAgent;
import io.fabric8.apmagent.ApmConfiguration;
import io.fabric8.apmagent.ClassInfo;
import io.fabric8.apmagent.MethodDescription;
import org.jolokia.jmx.JolokiaMBeanServerUtil;
import org.jolokia.jvmagent.JolokiaServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ApmAgentContext {
private static final Logger LOG = LoggerFactory.getLogger(ApmAgent.class);
private final String DEFAULT_DOMAIN = "io.fabric8.apmagent";
private final long HOUSE_KEEPING_TIME = TimeUnit.SECONDS.toMillis(2);
private final ConcurrentMap<String, ClassInfo> allMethods = new ConcurrentHashMap<>();
private AtomicBoolean initialized = new AtomicBoolean();
private AtomicBoolean started = new AtomicBoolean();
private ConcurrentMap<Thread, ThreadMetrics> threadMetricsMap = new ConcurrentHashMap<>();
private ConcurrentMap<String, MethodMetrics> methodMetricsMap = new ConcurrentHashMap<>();
private ConcurrentMap<Object, ObjectName> objectNameMap = new ConcurrentHashMap<>();
private MBeanServer mBeanServer;
private JolokiaServer jolokiaServer;
private final ApmAgent apmAgent;
private ObjectName agentObjectName;
private ObjectName configurationObjectName;
private final ApmConfiguration configuration;
private final MonitoredMethodMetrics monitoredMethodMetrics;
private AtomicBoolean doHouseKeeping = new AtomicBoolean();
private Thread backgroundThread;
private boolean monitorByDefault = true;
public ApmAgentContext(ApmAgent agent) {
this.apmAgent = agent;
this.configuration = agent.getConfiguration();
this.monitoredMethodMetrics = new MonitoredMethodMetrics(this);
this.monitoredMethodMetrics.setMonitorSize(configuration.getMethodMetricDepth());
}
public void enterMethod(Thread currentThread, String fullMethodName, boolean alwaysActive) {
if (isInitialized()) {
ThreadMetrics threadMetrics = threadMetricsMap.get(currentThread);
if (threadMetrics == null) {
threadMetrics = new ThreadMetrics(this, currentThread);
threadMetricsMap.put(currentThread, threadMetrics);
}
threadMetrics.enter(fullMethodName, alwaysActive);
MethodMetrics methodMetrics = methodMetricsMap.get(fullMethodName);
if (methodMetrics == null) {
methodMetrics = new MethodMetrics(fullMethodName);
methodMetrics.setActive(isMonitorByDefault());
methodMetricsMap.putIfAbsent(fullMethodName, methodMetrics);
}
}
}
public void exitMethod(Thread currentThread, String methodName, boolean alwaysActive) {
if (isInitialized()) {
ThreadMetrics threadMetrics = threadMetricsMap.get(currentThread);
long elapsed = -1;
if (threadMetrics != null) {
elapsed = threadMetrics.exit(methodName, alwaysActive);
}
if (elapsed >= 0) {
MethodMetrics methodMetrics = methodMetricsMap.get(methodName);
if (methodMetrics != null) {
methodMetrics.update(elapsed);
}
}
doHouseKeeping();
}
}
public void initialize() {
if (initialized.compareAndSet(false, true)) {
try {
agentObjectName = new ObjectName(DEFAULT_DOMAIN, "type", "apmAgent");
registerMBean(agentObjectName, apmAgent);
configurationObjectName = new ObjectName(DEFAULT_DOMAIN, "type", "configuration");
registerMBean(configurationObjectName, configuration);
} catch (Throwable e) {
LOG.warn("Failed to register ApmAgent mbeans with mBeanServer due " + e.getMessage(), e);
}
}
}
public void start() {
if (initialized.get()) {
if (started.compareAndSet(false, true)) {
backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
while (started.get()) {
try {
Thread.sleep(HOUSE_KEEPING_TIME);
doHouseKeeping.set(true);
} catch (Throwable e) {
}
}
}
}, "Fabric8-ApmAgent-BackgroundThread");
backgroundThread.setDaemon(true);
backgroundThread.start();
}
}
}
void doHouseKeeping() {
//the time is going to be the elapsed time from the latest method call
//its not going to be terribly accurate - but then it doesn't really need to be
if (doHouseKeeping.compareAndSet(true, false)) {
try {
List<ThreadMetrics> threadMetricsList = getThreadMetrics();
for (ThreadMetrics tm : threadMetricsList) {
if (tm.isDead()) {
tm.destroy();
threadMetricsMap.remove(tm.getThread());
}
}
monitoredMethodMetrics.calculateMethodMetrics(getMethodMetrics());
for (ThreadMetrics threadMetrics : threadMetricsList) {
threadMetrics.calculateMethodMetrics();
}
} catch (Throwable e) {
LOG.warn("Error during housekeeping due " + e.getMessage() + ". This exception is ignored.", e);
}
}
}
public void stop() {
if (initialized.get() && started.compareAndSet(true, false)) {
for (ObjectName objectName : objectNameMap.values()) {
unregisterMBean(objectName);
}
objectNameMap.clear();
methodMetricsMap.clear();
threadMetricsMap.clear();
}
}
public void shutDown() {
if (initialized.compareAndSet(true, false)) {
stop();
unregisterMBean(configurationObjectName);
unregisterMBean(agentObjectName);
if (jolokiaServer != null) {
jolokiaServer.stop();
jolokiaServer = null;
}
mBeanServer = null;
}
}
public ClassInfo getClassInfo(String className) {
String key = className.replace('/', '.');
ClassInfo result = allMethods.get(key);
if (result == null) {
ClassInfo classInfo = new ClassInfo();
classInfo.setClassName(key);
result = allMethods.putIfAbsent(key, classInfo);
if (result == null) {
result = classInfo;
}
}
return result;
}
public List<String> getTransformedMethods() {
List<String> result = new ArrayList<>();
for (ClassInfo classInfo : allMethods.values()) {
for (String methodName : classInfo.getAllTransformedMethodNames()) {
result.add(classInfo.getClassName() + "@" + methodName);
}
}
return result;
}
public List<String> getAllMethods() {
List<String> result = new ArrayList<>();
for (ClassInfo classInfo : allMethods.values()) {
for (String methodName : classInfo.getAllMethodNames()) {
result.add(classInfo.getClassName() + "@" + methodName);
}
}
return result;
}
public List<ThreadMetrics> getThreadMetrics() {
List<ThreadMetrics> result = new ArrayList<>(threadMetricsMap.values());
Collections.sort(result, new Comparator<ThreadMetrics>() {
@Override
public int compare(ThreadMetrics threadMetrics1, ThreadMetrics threadMetrics2) {
return (int) (threadMetrics2.getCpuTime() - threadMetrics1.getCpuTime());
}
});
return result;
}
public List<? extends MethodMetrics> getMethodMetrics() {
return MethodMetrics.sortedMetrics(methodMetricsMap.values());
}
public boolean isInitialized() {
return initialized.get();
}
public ApmConfiguration getConfiguration() {
return configuration;
}
public boolean isMonitorByDefault() {
return monitorByDefault;
}
public void setMonitorByDefault(boolean monitorByDefault) {
this.monitorByDefault = monitorByDefault;
}
public void setActive(String fullMethodName, boolean flag) {
if (isInitialized()) {
for (ThreadMetrics threadMetrics : threadMetricsMap.values()) {
threadMetrics.setActive(fullMethodName, flag);
}
MethodMetrics methodMetrics = methodMetricsMap.get(fullMethodName);
if (methodMetrics != null) {
methodMetrics.setActive(flag);
}
}
}
public List<ClassInfo> buildDeltaList() {
List<ClassInfo> result = new ArrayList<>();
for (ClassInfo classInfo : allMethods.values()) {
if (classInfo.isTransformed()) {
//check to see its still should be audited
if (configuration.isAudit(classInfo.getClassName())) {
boolean retransform = false;
//check to see if there's a change to methods that should be transformed
Set<String> transformedMethodNames = classInfo.getAllTransformedMethodNames();
for (String methodName : transformedMethodNames) {
if (!configuration.isAudit(classInfo.getClassName(), methodName)) {
retransform = true;
break;
}
}
if (!retransform) {
//check to see if there are methods that should now be audited but weren't
Set<String> allMethodNames = classInfo.getAllMethodNames();
for (String methodName : allMethodNames) {
if (!transformedMethodNames.contains(methodName) && configuration.isAudit(classInfo.getClassName(), methodName)) {
retransform = true;
break;
}
}
}
if (retransform) {
result.add(classInfo);
}
} else {
//we were once audited - but now need to be removed
result.add(classInfo);
}
} else if (configuration.isAudit(classInfo.getClassName())) {
if (classInfo.isCanTransform()) {
result.add(classInfo);
}
}
}
return result;
}
public void resetMethods(ClassInfo classInfo) {
Collection<MethodDescription> list = classInfo.getTransformedMethodDescriptions();
for (MethodDescription methodDescription : list) {
if (!configuration.isAudit(classInfo.getClassName(), methodDescription.getMethodName())) {
remove(methodDescription);
}
}
}
public void resetAll(ClassInfo classInfo) {
Collection<MethodDescription> list = classInfo.getTransformedMethodDescriptions();
for (MethodDescription methodDescription : list) {
remove(methodDescription);
}
classInfo.resetTransformed();
}
public void methodMetricsDepthChanged() {
monitoredMethodMetrics.setMonitorSize(configuration.getMethodMetricDepth());
}
public void threadMetricsDepthChanged() {
for (ThreadMetrics threadMetrics : threadMetricsMap.values()) {
threadMetrics.setMonitorSize(configuration.getThreadMetricDepth());
}
}
private void remove(MethodDescription methodDescription) {
methodMetricsMap.remove(methodDescription.getFullMethodName());
for (ThreadMetrics threadMetrics : threadMetricsMap.values()) {
threadMetrics.remove(methodDescription.getFullMethodName());
}
}
protected ObjectInstance registerMBean(ObjectName objectName, Object object) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
MBeanServer server = getMBeanServer();
if (server != null && !server.isRegistered(objectName)) {
return server.registerMBean(object, objectName);
}
return null;
}
protected void unregisterMBean(ObjectName objectName) {
MBeanServer beanServer = getMBeanServer();
if (objectName != null && beanServer != null && beanServer.isRegistered(objectName)) {
try {
beanServer.unregisterMBean(objectName);
} catch (Throwable e) {
LOG.warn("Failed to unregister " + objectName + " due " + e.getMessage() + ". This exception is ignored.", e);
}
}
}
void registerMethodMetricsMBean(int rank, MethodMetricsProxy methodMetrics) {
try {
ObjectName objectName = new ObjectName(DEFAULT_DOMAIN + ":" +
"type=MethodMetrics" +
",rank=" + ObjectName.quote("rank" + rank));
LOG.debug("registered {}", objectName);
registerMBean(objectName, methodMetrics);
objectNameMap.put(methodMetrics, objectName);
} catch (Throwable e) {
LOG.warn("Failed to register mbean " + methodMetrics.toString() + " due " + e.getMessage() + ". This exception is ignored.", e);
}
}
void registerMethodMetricsMBean(String threadName, long threadId, int rank, MethodMetricsProxy threadMetrics) {
try {
String threadIdentity = threadName + "[" + threadId + "]";
ObjectName objectName = new ObjectName(DEFAULT_DOMAIN + ":"
+ "type=ThreadContextMetrics"
+ ",threadName=" + ObjectName.quote(threadIdentity)
+ ",rank=" + ObjectName.quote("rank" + rank));
registerMBean(objectName, threadMetrics);
objectNameMap.put(threadMetrics, objectName);
} catch (Throwable e) {
LOG.warn("Failed to register mbean " + threadMetrics.toString() + " due " + e.getMessage() + ". This exception is ignored.", e);
}
}
void unregisterMethodMetricsMBean(MethodMetricsProxy methodMetrics) {
ObjectName objectName = objectNameMap.remove(methodMetrics);
unregisterMBean(objectName);
}
private synchronized MBeanServer getMBeanServer() {
if (mBeanServer == null) {
// return platform mbean server if the option is specified.
if (configuration.isUsePlatformMBeanServer()) {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
} else {
mBeanServer = JolokiaMBeanServerUtil.getJolokiaMBeanServer();
}
}
return mBeanServer;
}
}