patterntesting.runtime.monitor.internal.DoubletDigger Maven / Gradle / Ivy
Show all versions of patterntesting-rt Show documentation
/*
* $Id: DoubletDigger.java,v 1.5 2016/03/15 21:27:21 oboehm Exp $
*
* Copyright (c) 2016 by Oliver Boehm
*
* 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 orimplied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* (c)reated 09.03.2016 by oliver ([email protected])
*/
package patterntesting.runtime.monitor.internal;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import org.slf4j.*;
import patterntesting.runtime.annotation.ProfileMe;
import patterntesting.runtime.monitor.ClasspathMonitorMBean;
import patterntesting.runtime.util.*;
/**
* The DoubletDigger looks for classes which appears more than once in the
* classpath.
*
* Till 0.6.1 this class was located in the ClasspathMonitor itself. Because
* this class gets too big it was extracted to its own class.
*
*
* @author oliver
* @since 1.6 (09.03.2016)
*/
public final class DoubletDigger {
private static final Logger LOG = LoggerFactory.getLogger(DoubletDigger.class);
private final Map, Boolean> doubletClasses = new ConcurrentHashMap, Boolean>();
private final ClassLoader cloader;
private final ClasspathDigger classpathDigger;
private final List> doubletList = new CopyOnWriteArrayList>();
/**
* Instantiates a new doublet digger.
*
* @param classpathDigger the classpath digger
*/
public DoubletDigger(final ClasspathDigger classpathDigger) {
this.classpathDigger = classpathDigger;
this.cloader = classpathDigger.getClassLoader();
}
/**
* Resets the doubletList and maybe later other things.
* At the moment it is only used by ClasspathMonitorTest
* by the testGetDoubletListPerformance() method.
*/
protected final void reset() {
doubletList.clear();
}
/**
* Is the given class a doublet, i.e. can it be found more than once in the
* classpath?
*
* Note: Because this method is needed by other methods like
* getIncompatibleClassList() in the ClasspathMonitor the result of it is
* cached. This speeds up this method about the factor 2000 and more:
*
*
*
* Implementation | Mode | Score | Unit
* ---------------+-------+----------+-------
* old (till 1.4) | thrpt | 35514 | ops/s
* new (cached) | thrpt | 92968300 | ops/s
*
*
* This results were calculated with the help of JMH and was executed on a
* Dell Latitude e6520 with i5 processor (i5-2540M CPU @ 2.60GHz).
*
*
* @param clazz the class to be checked
* @return true if class is found more than once in the classpath
*/
public boolean isDoublet(final Class> clazz) {
Boolean doublet = this.doubletClasses.get(clazz);
if (doublet == null) {
String classname = clazz.getName();
String resource = Converter.classToResource(classname);
try {
doublet = isDoublet(resource);
} catch (NoSuchElementException ex) {
LOG.trace("{} is not found:", ex);
LOG.debug("{} is a proxy or similar class because classloader does not find it:",
clazz);
doublet = false;
}
this.doubletClasses.put(clazz, doublet);
}
return doublet;
}
/**
* Checks if is doublet.
*
* @param name the name
* @return true, if checks if is doublet
*/
public boolean isDoublet(final String name) {
Enumeration resources = this.classpathDigger.getResources(name);
if (!resources.hasMoreElements()) {
throw new NoSuchElementException("resource '" + name + "' not found");
}
resources.nextElement();
if (resources.hasMoreElements()) {
logDoublets(name);
return true;
} else {
return false;
}
}
/**
* Gets the doublet.
*
* @param name the name
* @param nr the nr
* @return the doublet
*/
public URL getDoublet(final String name, final int nr) {
Enumeration resources = this.classpathDigger.getResources(name);
int i = 0;
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
if (i == nr) {
return url;
}
i++;
}
return null;
}
/**
* Gets the doublet.
*
* @param clazz the clazz
* @param nr the nr
* @return the doublet
*/
public URL getDoublet(final Class> clazz, final int nr) {
String resource = Converter.classToResource(clazz.getName());
return this.getDoublet(resource, nr);
}
private void logDoublets(final String name) {
if (LOG.isTraceEnabled()) {
List doublets = new ArrayList();
Enumeration resources = this.classpathDigger.getResources(name);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
doublets.add(url);
}
LOG.trace("{} doublets found: {}", name, doublets);
}
}
/**
* Gets the doublets.
*
* @return the doublets
* @see ClasspathMonitorMBean#getDoublets()
*/
public String[] getDoublets() {
LOG.debug("Calculating doublets...");
List> classes = this.getDoubletList();
String[] doublets = new String[classes.size()];
for (int i = 0; i < doublets.length; i++) {
doublets[i] = classes.get(i).toString();
}
LOG.debug("Calculating doublets successful finished with {} doublet(s) found.", doublets.length);
return doublets;
}
/**
* Gets the doublet list.
*
* @return the doublet list
*/
@ProfileMe
public synchronized List> getDoubletList() {
if (multiThreadingEnabled) {
return getDoubletListParallel();
} else {
return getDoubletListSerial();
}
}
/**
* Looks for each loaded class if it is a doublet or not. For the
* performance reason it looks in the doubletList from the last time if it
* is already found. This is done because normally the number of doublets
* does not decrease.
*
* The method is protetected for testing reasons.
*
*
* @return a sorted list of found doublets
*/
protected List> getDoubletListSerial() {
List> loadedClassList = this.classpathDigger.getLoadedClassList();
synchronized (doubletList) {
for (Class> clazz : loadedClassList) {
if (doubletList.contains(clazz)) {
continue;
}
try {
if (this.isDoublet(clazz)) {
doubletList.add(clazz);
}
} catch (NoSuchElementException nsee) {
LOG.trace("{} not found -> ignored:", clazz, nsee);
}
}
sortDoubletList();
}
return Collections.unmodifiableList(doubletList);
}
/**
* On Java 6 you may get
*
* java.lang.UnsupportedOperationException
* at java.util.concurrent.CopyOnWriteArrayList$COWIterator.set(CopyOnWriteArrayList.java:1013)
* at java.util.Collections.sort(Collections.java:161)
*
* as exception. So we provide a fallback here for this situation.
*/
private void sortDoubletList() {
try {
Collections.sort(doubletList, new ObjectComparator());
} catch (UnsupportedOperationException ex) {
LOG.debug("Will sort doubletList with fallback because Collections.sort(..) failed:", ex);
sortList(doubletList);
}
}
private static void sortList(List> list) {
List> sorted = new ArrayList>(list.size());
Collections.sort(sorted, new ObjectComparator());
list.clear();
list.addAll(sorted);
}
/**
* To string.
*
* @return the string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.getClass().getSimpleName() + " for " + this.cloader;
}
///// M U L T I T H R E A D I N G S E C T I O N ///////////////////
/** The multi threading enabled. */
private boolean multiThreadingEnabled = getMultiThreadingEnabled();
/**
* Multi threading is enabled if more than one processor will be found
* or if property "multiThreadingEnabled=true" is set.
*
* TODO: extract it to a (not yet existing) Config class
*
*
* @return true on multi core processors
*/
private static boolean getMultiThreadingEnabled() {
boolean enabled = Boolean.getBoolean(System.getProperty("multiThreadingEnabled", "false"));
if (enabled) {
LOG.debug("Multi threading is enabled.");
return true;
}
int n = Runtime.getRuntime().availableProcessors();
enabled = (n > 1);
LOG.debug("{} processors found, multi threading is {}enabled.", n, enabled ? "" : "not ");
return enabled;
}
/**
* Is multi threading enabled?
* Multi threading is automatically enabled if more than one processor
* is found. Otherwise you can use set via the system property
* "multiThreadingEnabled=true" to activate it.
*
* @return true if multi threading is enabled for this class.
* @since 0.9.7
*/
public boolean isMultiThreadingEnabled() {
return multiThreadingEnabled;
}
/**
* Here you can enable or disable the (experimental) multi threading mode to
* see if it is really faster on a mult-core machine.
*
* @param enabled the enabled
* @since 0.9.7
*/
public void setMultiThreadingEnabled(final boolean enabled) {
multiThreadingEnabled = enabled;
}
/**
* This was an experiment in v0.9.7: can we tune getDoubleList() by using
* thread techniques like consumer-/producer-pattern? Or is the
* synchronization overhead to big?
*
* Multi threading is automatically activated if property
* "multiThreadingEnabled" is set to true or if more than one available
* processor is found.
*
*
* The method is protetected for testing reasons.
*
*
* @return a list of doublets
*/
@SuppressWarnings("unchecked")
protected List> getDoubletListParallel() {
List> loadedClassList = this.classpathDigger.getLoadedClassList();
if (loadedClassList.isEmpty()) {
return loadedClassList;
}
LOG.trace("Creating queue with {} elements for synchronisation.", loadedClassList.size());
Queue> queue = new ConcurrentLinkedQueue>(loadedClassList);
Class> lastClass = loadedClassList.get(loadedClassList.size() - 1);
if (this.isDoublet(lastClass)) {
doubletList.add(lastClass);
}
LOG.trace("Creating multiple threads.");
int n = 2;
List>[] l = new List[n];
DoubletDiggerRunner[] d = new DoubletDiggerRunner[n];
Thread[] t = new Thread[n];
for (int i = 0; i < n; i++) {
l[i] = new ArrayList>();
d[i] = new DoubletDiggerRunner(queue, lastClass, l[i]);
t[i] = new Thread(d[i]);
t[i].start();
}
LOG.debug("Starting {} threads...", n);
for (int i = 0; i < n; i++) {
try {
LOG.trace("{} started...", t[i]);
t[i].join();
LOG.trace("{} finished.", t[i]);
} catch (InterruptedException ie) {
LOG.info("Waiting for {} threads are interrupted:", n, ie);
Thread.currentThread().interrupt();
}
}
LOG.debug("Starting {} threads finished - will add results to doublet list.", n);
for (int i = 0; i < n; i++) {
doubletList.addAll(l[i]);
}
sortDoubletList();
LOG.trace("Result of {} threads was added to doublet list and sorted.", n);
return Collections.unmodifiableList(doubletList);
}
/**
* This class is needed to realize multi threading.
*/
class DoubletDiggerRunner implements Runnable {
private final Queue> queue;
private final Class> lastClass;
private final List> newDoublets;
/**
* Instantiates a new doublet digger.
*
* @param queue the queue
* @param lastClass the last class
* @param newDoublets the new doublets
*/
public DoubletDiggerRunner(final Queue> queue, final Class> lastClass, final List> newDoublets) {
this.queue = queue;
this.lastClass = lastClass;
this.newDoublets = newDoublets;
}
/**
* Run.
*
* @see java.lang.Runnable#run()
*/
public void run() {
LOG.debug("Running {}...", this);
while (!queue.isEmpty()) {
try {
Class> clazz = queue.remove();
if (clazz.equals(lastClass)) {
LOG.trace("Last {} reached.", clazz);
queue.add(clazz);
break;
}
if (!doubletList.contains(clazz) && isDoublet(clazz)) {
newDoublets.add(clazz);
}
} catch (NoSuchElementException nsee) {
LOG.debug("No element from {} found:", queue, nsee);
}
}
LOG.debug("Running {} finished with {} doublets found.", this, this.newDoublets.size());
}
/**
* To string.
*
* @return the string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "DoubletDigger-" + Thread.currentThread().getId();
}
}
}