refactoring api

add testcase
This commit is contained in:
Woonduk Kang
2016-01-29 21:18:23 +09:00
parent 7e53552437
commit 339ca28bb2
12 changed files with 861 additions and 211 deletions
@@ -0,0 +1,89 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.
*
*/
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.exception.PinpointException;
import com.navercorp.pinpoint.profiler.plugin.PluginConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
/**
* @author Woonduk Kang(emeroad)
*/
public class BootstrapClassLoaderHandler implements ClassInjector {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final PluginConfig pluginConfig;
private final Object lock = new Object();
private boolean injectedToRoot = false;
public BootstrapClassLoaderHandler(PluginConfig pluginConfig) {
if (pluginConfig == null) {
throw new NullPointerException("pluginConfig must not be null");
}
this.pluginConfig = pluginConfig;
}
@Override
@SuppressWarnings("unchecked")
public <T> Class<? extends T> injectClass(ClassLoader classLoader, String className) {
try {
if (classLoader == null) {
return (Class<T>)injectClass0(className);
}
} catch (Exception e) {
logger.warn("Failed to load plugin class {} with classLoader {}", className, classLoader, e);
throw new PinpointException("Failed to load plugin class " + className + " with classLoader " + classLoader, e);
}
throw new PinpointException("invalid ClassLoader");
}
private Class<?> injectClass0(String className) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
synchronized (lock) {
if (this.injectedToRoot == false) {
this.injectedToRoot = true;
pluginConfig.getInstrumentation().appendToBootstrapClassLoaderSearch(pluginConfig.getPluginJarFile());
pluginConfig.getClassPool().appendToBootstrapClassPath(pluginConfig.getPluginJarFile().getName());
}
}
return Class.forName(className, false, null);
}
}
@@ -3,9 +3,9 @@
* 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.
@@ -14,20 +14,9 @@
*/
package com.navercorp.pinpoint.profiler.instrument;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import com.navercorp.pinpoint.profiler.plugin.ClassLoadingChecker;
import com.navercorp.pinpoint.profiler.plugin.PluginConfig;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,37 +29,18 @@ import com.navercorp.pinpoint.exception.PinpointException;
*/
public class JarProfilerPluginClassInjector implements ClassInjector {
private final Logger logger = LoggerFactory.getLogger(JarProfilerPluginClassInjector.class);
private final boolean isDebug = logger.isDebugEnabled();
private static final Method ADD_URL;
private static final Method DEFINE_CLASS;
static {
try {
ADD_URL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
ADD_URL.setAccessible(true);
} catch (Exception e) {
throw new PinpointException("Cannot access URLClassLoader.addURL(URL)", e);
}
try {
DEFINE_CLASS = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
DEFINE_CLASS.setAccessible(true);
} catch (Exception e) {
throw new PinpointException("Cannot access ClassLoader.defineClass(String, byte[], int, int)", e);
}
}
private final PluginConfig pluginConfig;
private final Object lock = new Object();
private boolean injectedToRoot = false;
private final ClassInjector bootstrapClassLoaderHandler;
private final ClassInjector urlClassLoaderHandler;
private final ClassInjector plainClassLoaderHandler;
public JarProfilerPluginClassInjector(PluginConfig pluginConfig) {
if (pluginConfig == null) {
throw new NullPointerException("pluginConfig must not be null");
}
this.pluginConfig = pluginConfig;
this.bootstrapClassLoaderHandler = new BootstrapClassLoaderHandler(pluginConfig);
this.urlClassLoaderHandler = new URLClassLoaderHandler(pluginConfig);
this.plainClassLoaderHandler = new PlainClassLoaderHandler(pluginConfig);
}
@Override
@@ -78,12 +48,12 @@ public class JarProfilerPluginClassInjector implements ClassInjector {
public <T> Class<? extends T> injectClass(ClassLoader classLoader, String className) {
try {
if (classLoader == null) {
return (Class<T>)injectToBootstrapClassLoader(className);
return (Class<T>)bootstrapClassLoaderHandler.injectClass(null, className);
} else if (classLoader instanceof URLClassLoader) {
final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
return (Class<T>)injectToURLClassLoader(urlClassLoader, className);
return (Class<T>)urlClassLoaderHandler.injectClass(urlClassLoader, className);
} else {
return (Class<T>)injectToPlainClassLoader(classLoader, className);
return (Class<T>)plainClassLoaderHandler.injectClass(classLoader, className);
}
} catch (Exception e) {
logger.warn("Failed to load plugin class {} with classLoader {}", className, classLoader, e);
@@ -91,134 +61,4 @@ public class JarProfilerPluginClassInjector implements ClassInjector {
}
}
private Class<?> injectToBootstrapClassLoader(String className) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
synchronized (lock) {
if (this.injectedToRoot == false) {
this.injectedToRoot = true;
pluginConfig.getInstrumentation().appendToBootstrapClassLoaderSearch(pluginConfig.getPluginJarFile());
pluginConfig.getClassPool().appendToBootstrapClassPath(pluginConfig.getPluginJarFile().getName());
}
}
return Class.forName(className, false, null);
}
private Class<?> injectToURLClassLoader(URLClassLoader classLoader, String className) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
final URL[] urls = classLoader.getURLs();
if (urls != null) {
boolean hasPluginJar = false;
for (URL url : urls) {
// if (url.equals(pluginJarURL)) { fix very slow
// http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html
final String externalForm = url.toExternalForm();
if (pluginConfig.getPluginJarURLExternalForm().equals(externalForm)) {
hasPluginJar = true;
break;
}
}
if (!hasPluginJar) {
ADD_URL.invoke(classLoader, pluginConfig.getPluginJar());
}
}
return classLoader.loadClass(className);
}
private Class<?> injectToPlainClassLoader(ClassLoader classLoader, String className) throws NotFoundException, IllegalArgumentException, IOException, CannotCompileException, IllegalAccessException, InvocationTargetException {
if (isDebug) {
logger.debug("injectToPlainClassLoader className:{} cl:{}", className, classLoader);
}
logger.info("bootstrapCoreJarPath:{}", pluginConfig.getBootstrapCoreJarPath());
final ClassPool pool = createClassPool(classLoader);
// TODO ClassLoader + ClassName key?
// TODO concurrent class loading
final ClassLoadingChecker classLoadingChecker = new ClassLoadingChecker();
return injectToPlainClassLoader(pool, classLoader, className, classLoadingChecker);
}
private ClassPool createClassPool(ClassLoader classLoader) throws NotFoundException {
final ClassPool pool = new ClassPool();
pool.appendClassPath(pluginConfig.getBootstrapCoreJarPath());
pool.appendClassPath(new LoaderClassPath(classLoader));
pool.appendClassPath(pluginConfig.getPluginJarFile().getName());
return pool;
}
private Class<?> injectToPlainClassLoader(ClassPool pool, ClassLoader classLoader, String className, ClassLoadingChecker classLoadingChecker) throws NotFoundException, IOException, CannotCompileException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (pluginConfig.getProfilerPackageFilter().accept(className)) {
if (isDebug) {
logger.debug("ProfilerFilter skip class {}", className);
}
return null;
}
if (!pluginConfig.getPluginPackageFilter().accept(className)) {
if (isDebug) {
logger.debug("PluginFilter skip class:{}", className);
}
return null;
}
if (!classLoadingChecker.isFirstLoad(className)) {
if (isDebug) {
logger.debug("skip already loaded class:{}", className);
}
return null;
}
Class<?> c = null;
try {
c = classLoader.loadClass(className);
if (isDebug) {
logger.debug("loadClass:{}", className);
}
} catch (ClassNotFoundException ex) {
if (isDebug) {
logger.debug("ClassNotFound {}", ex.getMessage());
}
}
if (c != null) {
return c;
}
final CtClass ct = pool.getOrNull(className);
if (ct == null) {
throw new NotFoundException(className);
}
final CtClass superClass = ct.getSuperclass();
if (superClass != null) {
if ("java.lang.Object".equals(superClass.getName())) {
return null;
}
injectToPlainClassLoader(pool, classLoader, superClass.getName(), classLoadingChecker);
}
final CtClass[] interfaces = ct.getInterfaces();
for (CtClass ctInterface : interfaces) {
injectToPlainClassLoader(pool, classLoader, ctInterface.getName(), classLoadingChecker);
}
@SuppressWarnings("unchecked")
final Collection<String> referenceClassList = ct.getRefClasses();
if (isDebug) {
logger.debug("target:{} referenceClassList:{}", className, referenceClassList);
}
for (String referenceClass : referenceClassList) {
try {
injectToPlainClassLoader(pool, classLoader, referenceClass, classLoadingChecker);
} catch (NotFoundException e) {
logger.warn("Skip a referenced class because of NotFoundException : {}", e.getMessage(), e);
}
}
if (logger.isInfoEnabled()) {
logger.info("defineClass pluginClass:{} cl:{}", className, classLoader);
}
final byte[] bytes = ct.toBytecode();
return (Class<?>)DEFINE_CLASS.invoke(classLoader, ct.getName(), bytes, 0, bytes.length);
}
}
@@ -0,0 +1,186 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.
*
*/
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.exception.PinpointException;
import com.navercorp.pinpoint.profiler.plugin.ClassLoadingChecker;
import com.navercorp.pinpoint.profiler.plugin.PluginConfig;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* @author Woonduk Kang(emeroad)
*/
public class PlainClassLoaderHandler implements ClassInjector {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final boolean isDebug = logger.isDebugEnabled();
private static final Method DEFINE_CLASS;
static {
try {
DEFINE_CLASS = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
DEFINE_CLASS.setAccessible(true);
} catch (Exception e) {
throw new PinpointException("Cannot access ClassLoader.defineClass(String, byte[], int, int)", e);
}
}
private final PluginConfig pluginConfig;
public PlainClassLoaderHandler(PluginConfig pluginConfig) {
if (pluginConfig == null) {
throw new NullPointerException("pluginConfig must not be null");
}
this.pluginConfig = pluginConfig;
}
@Override
@SuppressWarnings("unchecked")
public <T> Class<? extends T> injectClass(ClassLoader classLoader, String className) {
try {
return (Class<T>)injectClass0(classLoader, className);
} catch (Exception e) {
logger.warn("Failed to load plugin class {} with classLoader {}", className, classLoader, e);
throw new PinpointException("Failed to load plugin class " + className + " with classLoader " + classLoader, e);
}
}
private Class<?> injectClass0(ClassLoader classLoader, String className) throws NotFoundException, IllegalArgumentException, IOException, CannotCompileException, IllegalAccessException, InvocationTargetException {
if (isDebug) {
logger.debug("injectClass0 className:{} cl:{}", className, classLoader);
}
logger.info("bootstrapCoreJarPath:{}", pluginConfig.getBootstrapCoreJarPath());
final ClassPool pool = createClassPool(classLoader);
// TODO ClassLoader + ClassName key?
// TODO concurrent class loading
final ClassLoadingChecker classLoadingChecker = new ClassLoadingChecker();
return injectClass0(pool, classLoader, className, classLoadingChecker);
}
private ClassPool createClassPool(ClassLoader classLoader) throws NotFoundException {
final ClassPool pool = new ClassPool();
final String bootstrapCoreJarPath = pluginConfig.getBootstrapCoreJarPath();
pool.appendClassPath(bootstrapCoreJarPath);
final LoaderClassPath loaderClassPath = new LoaderClassPath(classLoader);
pool.appendClassPath(loaderClassPath);
final String pluginJarFileName = pluginConfig.getPluginJarFile().getName();
pool.appendClassPath(pluginJarFileName);
return pool;
}
private Class<?> injectClass0(ClassPool pool, ClassLoader classLoader, String className, ClassLoadingChecker classLoadingChecker) throws NotFoundException, IOException, CannotCompileException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (!pluginConfig.getPluginPackageFilter().accept(className)) {
if (isDebug) {
logger.debug("PluginFilter skip class:{}", className);
}
return null;
}
if (!classLoadingChecker.isFirstLoad(className)) {
if (isDebug) {
logger.debug("skip already loaded class:{}", className);
}
return null;
}
Class<?> c = null;
try {
c = classLoader.loadClass(className);
if (isDebug) {
logger.debug("loadClass:{}", className);
}
} catch (ClassNotFoundException ex) {
if (isDebug) {
logger.debug("ClassNotFound {}", ex.getMessage());
}
}
if (c != null) {
return c;
}
final CtClass ct = pool.getOrNull(className);
if (ct == null) {
throw new NotFoundException(className);
}
final CtClass superClass = ct.getSuperclass();
if (superClass != null) {
if ("java.lang.Object".equals(superClass.getName())) {
return null;
}
injectClass0(pool, classLoader, superClass.getName(), classLoadingChecker);
}
final CtClass[] interfaces = ct.getInterfaces();
for (CtClass ctInterface : interfaces) {
injectClass0(pool, classLoader, ctInterface.getName(), classLoadingChecker);
}
@SuppressWarnings("unchecked")
final Collection<String> referenceClassList = ct.getRefClasses();
if (isDebug) {
logger.debug("target:{} referenceClassList:{}", className, referenceClassList);
}
for (String referenceClass : referenceClassList) {
try {
injectClass0(pool, classLoader, referenceClass, classLoadingChecker);
} catch (NotFoundException e) {
logger.warn("Skip a referenced class because of NotFoundException : {}", e.getMessage(), e);
}
}
if (logger.isInfoEnabled()) {
logger.info("defineClass pluginClass:{} cl:{}", className, classLoader);
}
final byte[] bytes = ct.toBytecode();
return (Class<?>)DEFINE_CLASS.invoke(classLoader, ct.getName(), bytes, 0, bytes.length);
}
}
@@ -0,0 +1,122 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.
*
*/
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.exception.PinpointException;
import com.navercorp.pinpoint.profiler.plugin.PluginConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* @author Woonduk Kang(emeroad)
*/
public class URLClassLoaderHandler implements ClassInjector {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Method ADD_URL;
static {
try {
ADD_URL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
ADD_URL.setAccessible(true);
} catch (Exception e) {
throw new PinpointException("Cannot access URLClassLoader.addURL(URL)", e);
}
}
private final URL pluginURL;
private final String pluginURLString;
public URLClassLoaderHandler(PluginConfig pluginConfig) {
if (pluginConfig == null) {
throw new NullPointerException("pluginConfig must not be null");
}
this.pluginURL = pluginConfig.getPluginJar();
this.pluginURLString = pluginURL.toExternalForm();
}
@Override
@SuppressWarnings("unchecked")
public <T> Class<? extends T> injectClass(ClassLoader classLoader, String className) {
try {
if (classLoader instanceof URLClassLoader) {
final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
return (Class<T>)injectClass0(urlClassLoader, className);
}
} catch (Exception e) {
logger.warn("Failed to load plugin class {} with classLoader {}", className, classLoader, e);
throw new PinpointException("Failed to load plugin class " + className + " with classLoader " + classLoader, e);
}
throw new PinpointException("invalid ClassLoader");
}
private Class<?> injectClass0(URLClassLoader classLoader, String className) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
final URL[] urls = classLoader.getURLs();
if (urls != null) {
final boolean hasPluginJar = hasPluginJar(urls);
if (!hasPluginJar) {
logger.debug("add Jar:{}", pluginURLString);
ADD_URL.invoke(classLoader, pluginURL);
}
}
return classLoader.loadClass(className);
}
private boolean hasPluginJar(URL[] urls) {
for (URL url : urls) {
// if (url.equals(pluginJarURL)) { fix very slow
// http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html
final String externalForm = url.toExternalForm();
if (pluginURLString.equals(externalForm)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,47 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.plugin;
import java.util.ArrayList;
import java.util.List;
/**
* @author Woonduk Kang(emeroad)
*/
public class ClassNameFilterChain implements ClassNameFilter {
private final List<ClassNameFilter> filterChain;
public ClassNameFilterChain(List<ClassNameFilter> filterChain) {
if (filterChain == null) {
throw new NullPointerException("filterChain must not be null");
}
this.filterChain = new ArrayList<ClassNameFilter>(filterChain);
}
@Override
public boolean accept(String className) {
for (ClassNameFilter classNameFilter : this.filterChain) {
if (!classNameFilter.accept(className)) {
return REJECT;
}
}
return ACCEPT;
}
}
@@ -0,0 +1,74 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* @author Woonduk Kang(emeroad)
*/
public class PinpointProfilerPackageSkipFilter implements ClassNameFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final List<String> packageList;
public PinpointProfilerPackageSkipFilter() {
this(getPinpointPackageList());
}
public PinpointProfilerPackageSkipFilter(List<String> packageList) {
if (packageList == null) {
throw new NullPointerException("packageList must not be null");
}
this.packageList = new ArrayList<String>(packageList);
}
@Override
public boolean accept(String className) {
if (className == null) {
throw new NullPointerException("className must not be null");
}
for (String packageName : packageList) {
if (className.startsWith(packageName)) {
if (logger.isDebugEnabled()) {
logger.info("skip ProfilerPackage:{} Class:{}", packageName, className);
}
return REJECT;
}
}
return ACCEPT;
}
private static List<String> getPinpointPackageList() {
List<String> pinpointPackageList = new ArrayList<String>();
pinpointPackageList.add("com.navercorp.pinpoint.bootstrap");
pinpointPackageList.add("com.navercorp.pinpoint.profiler");
pinpointPackageList.add("com.navercorp.pinpoint.common");
pinpointPackageList.add("com.navercorp.pinpoint.exception");
// TODO move test package
pinpointPackageList.add("com.navercorp.pinpoint.test");
return pinpointPackageList;
}
}
@@ -19,7 +19,6 @@ package com.navercorp.pinpoint.profiler.plugin;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClassPool;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
import com.navercorp.pinpoint.bootstrap.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,9 +30,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* @author Woonduk Kang(emeroad)
@@ -50,15 +47,14 @@ public class PluginConfig {
private String pluginJarURLExternalForm;
private final ProfilerPlugin plugin;
private final Manifest manifest;
private final Instrumentation instrumentation;
private final InstrumentClassPool classPool;
private final String bootstrapCoreJarPath;
private final ClassNameFilter pluginPackageFilter;
private final ClassNameFilter profilerPackageFilter;
public PluginConfig(URL pluginJar, ProfilerPlugin plugin, Instrumentation instrumentation, InstrumentClassPool classPool, String bootstrapCoreJarPath) {
private final ClassNameFilter pluginPackageFilter;
public PluginConfig(URL pluginJar, ProfilerPlugin plugin, Instrumentation instrumentation, InstrumentClassPool classPool, String bootstrapCoreJarPath, ClassNameFilter pluginPackageFilter) {
if (pluginJar == null) {
throw new NullPointerException("pluginJar must not be null");
}
@@ -68,37 +64,16 @@ public class PluginConfig {
this.pluginJar = pluginJar;
this.pluginJarFile = createJarFile(pluginJar);
this.plugin = plugin;
this.manifest = this.getManifest();
this.instrumentation = instrumentation;
this.classPool = classPool;
this.bootstrapCoreJarPath = bootstrapCoreJarPath;
final List<String> pluginPackageList = getPluginPackage(manifest);
if (logger.isInfoEnabled()) {
logger.info("{} Plugin Package:{}", plugin.getClass(), pluginPackageList);
}
this.pluginPackageFilter = new PluginPackageFilter(pluginPackageList);
this.profilerPackageFilter = new PinpointProfilerPackageFilter();
this.pluginPackageFilter = pluginPackageFilter;
}
private Manifest getManifest() {
try {
return pluginJarFile.getManifest();
} catch (IOException e) {
// return empty
return new Manifest();
}
}
public List<String> getPluginPackage(Manifest manifest) {
final Attributes attributes = manifest.getMainAttributes();
final String pluginPackage = attributes.getValue(PINPOINT_PLUGIN_PACKAGE);
if (pluginPackage == null) {
return DEFAULT_PINPOINT_PLUGIN_PACKAGE_NAME;
}
return StringUtils.splitAndTrim(pluginPackage, ",");
}
public ProfilerPlugin getPlugin() {
return plugin;
@@ -146,7 +121,5 @@ public class PluginConfig {
return pluginPackageFilter;
}
public ClassNameFilter getProfilerPackageFilter() {
return profilerPackageFilter;
}
}
}
@@ -14,14 +14,23 @@
*/
package com.navercorp.pinpoint.profiler.plugin;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import com.navercorp.pinpoint.bootstrap.instrument.GuardInstrumentContext;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentContext;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplate;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplateAware;
import com.navercorp.pinpoint.bootstrap.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,6 +48,8 @@ public class ProfilerPluginLoader {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final DefaultAgent agent;
private final ClassNameFilter profilerPackageFilter = new PinpointProfilerPackageSkipFilter();
public ProfilerPluginLoader(DefaultAgent agent) {
if (agent == null) {
throw new NullPointerException("agent must not be null");
@@ -51,31 +62,87 @@ public class ProfilerPluginLoader {
List<String> disabled = agent.getProfilerConfig().getDisabledPlugins();
for (URL jar : pluginJars) {
List<ProfilerPlugin> plugins = PluginLoader.load(ProfilerPlugin.class, new URL[] { jar });
final JarFile pluginJarFile = createJarFile(jar);
final List<String> pluginPackageList = getPluginPackage(pluginJarFile);
final ClassNameFilter pluginFilterChain = createPluginFilterChain(pluginPackageList);
final List<ProfilerPlugin> plugins = PluginLoader.load(ProfilerPlugin.class, new URL[] { jar });
for (ProfilerPlugin plugin : plugins) {
if (disabled.contains(plugin.getClass().getName())) {
logger.info("Skip disabled plugin: {}", plugin.getClass().getName());
continue;
}
if (logger.isInfoEnabled()) {
logger.info("{} Plugin {}:{}", plugin.getClass(), PluginConfig.PINPOINT_PLUGIN_PACKAGE, pluginPackageList);
}
logger.info("Loading plugin: {}", plugin.getClass().getName());
logger.info("Loading plugin:{} pluginPackage:{}", plugin.getClass().getName(), plugin);
PluginConfig pluginConfig = new PluginConfig(jar, plugin, agent.getInstrumentation(), agent.getClassPool(), agent.getBootstrapCoreJar());
PluginConfig pluginConfig = new PluginConfig(jar, plugin, agent.getInstrumentation(), agent.getClassPool(), agent.getBootstrapCoreJar(), pluginFilterChain);
final DefaultProfilerPluginContext context = setupPlugin(pluginConfig);
pluginContexts.add(context);
}
}
return pluginContexts;
}
private ClassNameFilter createPluginFilterChain(List<String> packageList) {
final ClassNameFilter pluginPackageFilter = new PluginPackageFilter(packageList);
final List<ClassNameFilter> chain = Arrays.asList(profilerPackageFilter, pluginPackageFilter);
final ClassNameFilter filterChain = new ClassNameFilterChain(chain);
return filterChain;
}
private JarFile createJarFile(URL pluginJar) {
try {
final URI uri = pluginJar.toURI();
return new JarFile(new File(uri));
} catch (URISyntaxException e) {
throw new RuntimeException("URISyntax error. " + e.getCause(), e);
} catch (IOException e) {
throw new RuntimeException("IO error. " + e.getCause(), e);
}
}
private Manifest getManifest(JarFile pluginJarFile) {
try {
return pluginJarFile.getManifest();
} catch (IOException ex) {
logger.info("{} IoError :{}", pluginJarFile.getName(), ex.getMessage(), ex);
return null;
}
}
public List<String> getPluginPackage(JarFile pluginJarFile) {
final Manifest manifest = getManifest(pluginJarFile);
if (manifest == null) {
return PluginConfig.DEFAULT_PINPOINT_PLUGIN_PACKAGE_NAME;
}
final Attributes attributes = manifest.getMainAttributes();
final String pluginPackage = attributes.getValue(PluginConfig.PINPOINT_PLUGIN_PACKAGE);
if (pluginPackage == null) {
return PluginConfig.DEFAULT_PINPOINT_PLUGIN_PACKAGE_NAME;
}
return StringUtils.splitAndTrim(pluginPackage, ",");
}
private GuardInstrumentContext preparePlugin(ProfilerPlugin plugin, InstrumentContext context) {
final GuardInstrumentContext guardInstrumentContext = new GuardInstrumentContext(context);
if (plugin instanceof TransformTemplateAware) {
if (logger.isDebugEnabled()) {
logger.debug("setTransformTemplate {}", plugin.getClass().getName());
logger.debug("{}.setTransformTemplate", plugin.getClass().getName());
}
final TransformTemplate transformTemplate = new TransformTemplate(guardInstrumentContext);
((TransformTemplateAware) plugin).setTransformTemplate(transformTemplate);
@@ -92,6 +159,9 @@ public class ProfilerPluginLoader {
try {
// WARN external plugin api
final ProfilerPlugin plugin = pluginConfig.getPlugin();
if (logger.isInfoEnabled()) {
logger.info("{} Plugin setup", plugin.getClass().getName());
}
plugin.setup(guardPluginContext);
} finally {
guardPluginContext.close();
@@ -0,0 +1,129 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.
*
*/
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.bootstrap.LibClass;
import com.navercorp.pinpoint.bootstrap.PinpointURLClassLoader;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClassPool;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
import com.navercorp.pinpoint.profiler.plugin.PluginConfig;
import com.navercorp.pinpoint.profiler.plugin.PluginPackageFilter;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.security.CodeSource;
import java.util.Arrays;
/**
* @author Woonduk Kang(emeroad)
*/
public class JarProfilerPluginClassInjectorTest {
public static final String CONTEXT_TYPE_MATCH_CLASS_LOADER = "org.springframework.context.support.ContextTypeMatchClassLoader";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Test
public void testInjectClass() throws Exception {
final URL sampleJar = getSampleJar(Logger.class);
final ClassLoader contextTypeMatchClassLoader = createContextTypeMatchClassLoader(new URL[]{sampleJar});
InstrumentClassPool pool = Mockito.mock(InstrumentClassPool.class);
final Instrumentation instrumentation = Mockito.mock(Instrumentation.class);
final ProfilerPlugin profilerPlugin = Mockito.mock(ProfilerPlugin.class);
// final PluginPackageFilter filter = new PluginPackageFilter(Arrays.asList("test"));
final String packageName = logger.getClass().getPackage().getName();
final PluginPackageFilter filter = new PluginPackageFilter(Arrays.asList(packageName));
PluginConfig pluginConfig = new PluginConfig(sampleJar, profilerPlugin, instrumentation, pool, sampleJar.getPath(), filter);
PlainClassLoaderHandler injector = new PlainClassLoaderHandler(pluginConfig);
final Class<?> loggerClass = injector.injectClass(contextTypeMatchClassLoader, logger.getClass().getName());
logger.debug("ClassLoader{}", loggerClass.getClassLoader());
Assert.assertEquals("check className", loggerClass.getName(), "org.slf4j.impl.Log4jLoggerAdapter");
Assert.assertEquals("check ClassLoader", loggerClass.getClassLoader().getClass().getName(), CONTEXT_TYPE_MATCH_CLASS_LOADER);
}
private ClassLoader createContextTypeMatchClassLoader(URL[] urlArray) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException {
final ClassLoader classLoader = this.getClass().getClassLoader();
final Class<ClassLoader> aClass = (Class<ClassLoader>) classLoader.loadClass(CONTEXT_TYPE_MATCH_CLASS_LOADER);
final Constructor<ClassLoader> constructor = aClass.getConstructor(ClassLoader.class);
ReflectionUtils.makeAccessible(constructor);
final LibClass libClassFilter = new LibClass() {
@Override
public boolean onLoadClass(String clazzName) {
if (clazzName.startsWith("org.slf4j")) {
logger.debug("Loading {}", clazzName);
return ON_LOAD_CLASS;
}
return DELEGATE_PARENT;
}
};
PinpointURLClassLoader testClassLoader = new PinpointURLClassLoader(urlArray, ClassLoader.getSystemClassLoader(), libClassFilter);
final ClassLoader contextTypeMatchClassLoader = constructor.newInstance(testClassLoader);
logger.debug("cl:{}",contextTypeMatchClassLoader);
// final Method excludePackage = aClass.getMethod("excludePackage", String.class);
// ReflectionUtils.invokeMethod(excludePackage, contextTypeMatchClassLoader, "org.slf4j");
return contextTypeMatchClassLoader;
}
private URL getSampleJar(Class clazz) {
final CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
final URL location = codeSource.getLocation();
logger.debug("url:{}", location);
return location;
}
}
@@ -0,0 +1,43 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.plugin;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
/**
* @author Woonduk Kang(emeroad)
*/
public class ClassNameFilterChainTest {
@Test
public void testAccept() throws Exception {
PluginPackageFilter include = new PluginPackageFilter(Arrays.asList("com.include"));
PinpointProfilerPackageSkipFilter exclude = new PinpointProfilerPackageSkipFilter(Arrays.asList("com.exclude"));
ClassNameFilterChain chain = new ClassNameFilterChain(Arrays.asList(include, exclude));
Assert.assertTrue(chain.accept("com.include"));
Assert.assertFalse(chain.accept("com.exclude"));
Assert.assertFalse(chain.accept("unknown"));
}
}
@@ -0,0 +1,37 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.plugin;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Woonduk Kang(emeroad)
*/
public class PinpointProfilerPackageSkipFilterTest {
@Test
public void testAccept() throws Exception {
PinpointProfilerPackageSkipFilter filter = new PinpointProfilerPackageSkipFilter();
Assert.assertFalse("skip", filter.accept("com.navercorp.pinpoint.bootstrap.test.class"));
Assert.assertTrue("include", filter.accept("test"));
}
}
@@ -0,0 +1,40 @@
/*
* *
* * Copyright 2016 NAVER Corp.
* * 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.navercorp.pinpoint.profiler.plugin;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* @author Woonduk Kang(emeroad)
*/
public class PluginPackageFilterTest {
@Test
public void testAccept() throws Exception {
PluginPackageFilter filter = new PluginPackageFilter(Arrays.asList("com.plugin"));
Assert.assertTrue(filter.accept("com.plugin.test.module"));
Assert.assertFalse(filter.accept("test"));
}
}