segment: segments) {
+ segment.unlock();
+ }
+ }
+ if (sum > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ } else {
+ return (int) sum;
+ }
+ }
+
+ /**
+ * Returns the value to which the specified key is mapped, or {@code null}
+ * if this map contains no mapping for the key.
+ *
+ * More formally, if this map contains a mapping from a key {@code k} to
+ * a value {@code v} such that {@code key.equals(k)}, then this method
+ * returns {@code v}; otherwise it returns {@code null}. (There can be at
+ * most one such mapping.)
+ *
+ * @throws NullPointerException if the specified key is null
+ */
+ @Override
+ public V get(Object key) {
+ int hash = hashOf(key);
+ return segmentFor(hash).get(key, hash);
+ }
+
+ /**
+ * Tests if the specified object is a key in this table.
+ *
+ * @param key possible key
+ * @return true if and only if the specified object is a key in
+ * this table, as determined by the equals method;
+ * false otherwise.
+ * @throws NullPointerException if the specified key is null
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ int hash = hashOf(key);
+ return segmentFor(hash).containsKey(key, hash);
+ }
+
+ /**
+ * Returns true if this map maps one or more keys to the specified
+ * value. Note: This method requires a full internal traversal of the hash
+ * table, and so is much slower than method containsKey.
+ *
+ * @param value value whose presence in this map is to be tested
+ * @return true if this map maps one or more keys to the specified
+ * value
+ * @throws NullPointerException if the specified value is null
+ */
+
+ @Override
+ public boolean containsValue(Object value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+
+ // See explanation of modCount use above
+
+ final Segment[] segments = this.segments;
+ int[] mc = new int[segments.length];
+
+ // Try a few times without locking
+ for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) {
+ int mcsum = 0;
+ for (int i = 0; i < segments.length; ++ i) {
+ mcsum += mc[i] = segments[i].modCount;
+ if (segments[i].containsValue(value)) {
+ return true;
+ }
+ }
+ boolean cleanSweep = true;
+ if (mcsum != 0) {
+ for (int i = 0; i < segments.length; ++ i) {
+ if (mc[i] != segments[i].modCount) {
+ cleanSweep = false;
+ break;
+ }
+ }
+ }
+ if (cleanSweep) {
+ return false;
+ }
+ }
+ // Resort to locking all segments
+ for (Segment segment: segments) {
+ segment.lock();
+ }
+ boolean found = false;
+ try {
+ for (Segment segment: segments) {
+ if (segment.containsValue(value)) {
+ found = true;
+ break;
+ }
+ }
+ } finally {
+ for (Segment segment: segments) {
+ segment.unlock();
+ }
+ }
+ return found;
+ }
+
+ /**
+ * Legacy method testing if some key maps into the specified value in this
+ * table. This method is identical in functionality to
+ * {@link #containsValue}, and exists solely to ensure full compatibility
+ * with class {@link Hashtable}, which supported this method prior to
+ * introduction of the Java Collections framework.
+ *
+ * @param value a value to search for
+ * @return true if and only if some key maps to the value
+ * argument in this table as determined by the equals
+ * method; false otherwise
+ * @throws NullPointerException if the specified value is null
+ */
+ public boolean contains(Object value) {
+ return containsValue(value);
+ }
+
+ /**
+ * Maps the specified key to the specified value in this table. Neither the
+ * key nor the value can be null.
+ *
+ * The value can be retrieved by calling the get method with a
+ * key that is equal to the original key.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with key, or null
+ * if there was no mapping for key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ @Override
+ public V put(K key, V value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ int hash = hashOf(key);
+ return segmentFor(hash).put(key, hash, value, false);
+ }
+
+ /**
+ * @return the previous value associated with the specified key, or
+ * null if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V putIfAbsent(K key, V value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ int hash = hashOf(key);
+ return segmentFor(hash).put(key, hash, value, true);
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this one. These
+ * mappings replace any mappings that this map had for any of the keys
+ * currently in the specified map.
+ *
+ * @param m mappings to be stored in this map
+ */
+ @Override
+ public void putAll(Map extends K, ? extends V> m) {
+ for (Map.Entry extends K, ? extends V> e: m.entrySet()) {
+ put(e.getKey(), e.getValue());
+ }
+ }
+
+ /**
+ * Removes the key (and its corresponding value) from this map. This method
+ * does nothing if the key is not in the map.
+ *
+ * @param key the key that needs to be removed
+ * @return the previous value associated with key, or null
+ * if there was no mapping for key
+ * @throws NullPointerException if the specified key is null
+ */
+ @Override
+ public V remove(Object key) {
+ int hash = hashOf(key);
+ return segmentFor(hash).remove(key, hash, null, false);
+ }
+
+ /**
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean remove(Object key, Object value) {
+ int hash = hashOf(key);
+ if (value == null) {
+ return false;
+ }
+ return segmentFor(hash).remove(key, hash, value, false) != null;
+ }
+
+ /**
+ * @throws NullPointerException if any of the arguments are null
+ */
+ public boolean replace(K key, V oldValue, V newValue) {
+ if (oldValue == null || newValue == null) {
+ throw new NullPointerException();
+ }
+ int hash = hashOf(key);
+ return segmentFor(hash).replace(key, hash, oldValue, newValue);
+ }
+
+ /**
+ * @return the previous value associated with the specified key, or
+ * null if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V replace(K key, V value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ int hash = hashOf(key);
+ return segmentFor(hash).replace(key, hash, value);
+ }
+
+ /**
+ * Removes all of the mappings from this map.
+ */
+ @Override
+ public void clear() {
+ for (Segment segment: segments) {
+ segment.clear();
+ }
+ }
+
+ /**
+ * Removes any stale entries whose keys have been finalized. Use of this
+ * method is normally not necessary since stale entries are automatically
+ * removed lazily, when blocking operations are required. However, there are
+ * some cases where this operation should be performed eagerly, such as
+ * cleaning up old references to a ClassLoader in a multi-classloader
+ * environment.
+ *
+ * Note: this method will acquire locks, one at a time, across all segments
+ * of this table, so if it is to be used, it should be used sparingly.
+ */
+ public void purgeStaleEntries() {
+ for (Segment segment: segments) {
+ segment.removeStale();
+ }
+ }
+
+ /**
+ * Returns a {@link Set} view of the keys contained in this map. The set is
+ * backed by the map, so changes to the map are reflected in the set, and
+ * vice-versa. The set supports element removal, which removes the
+ * corresponding mapping from this map, via the Iterator.remove,
+ * Set.remove, removeAll, retainAll, and
+ * clear operations. It does not support the add or
+ * addAll operations.
+ *
+ * The view's iterator is a "weakly consistent" iterator that
+ * will never throw {@link ConcurrentModificationException}, and guarantees
+ * to traverse elements as they existed upon construction of the iterator,
+ * and may (but is not guaranteed to) reflect any modifications subsequent
+ * to construction.
+ */
+ @Override
+ public Set keySet() {
+ Set ks = keySet;
+ return ks != null? ks : (keySet = new KeySet());
+ }
+
+ /**
+ * Returns a {@link Collection} view of the values contained in this map.
+ * The collection is backed by the map, so changes to the map are reflected
+ * in the collection, and vice-versa. The collection supports element
+ * removal, which removes the corresponding mapping from this map, via the
+ * Iterator.remove, Collection.remove, removeAll,
+ * retainAll, and clear operations. It does not support
+ * the add or addAll operations.
+ *
+ * The view's iterator is a "weakly consistent" iterator that
+ * will never throw {@link ConcurrentModificationException}, and guarantees
+ * to traverse elements as they existed upon construction of the iterator,
+ * and may (but is not guaranteed to) reflect any modifications subsequent
+ * to construction.
+ */
+ @Override
+ public Collection values() {
+ Collection vs = values;
+ return vs != null? vs : (values = new Values());
+ }
+
+ /**
+ * Returns a {@link Set} view of the mappings contained in this map.
+ * The set is backed by the map, so changes to the map are reflected in the
+ * set, and vice-versa. The set supports element removal, which removes the
+ * corresponding mapping from the map, via the Iterator.remove,
+ * Set.remove, removeAll, retainAll, and
+ * clear operations. It does not support the add or
+ * addAll operations.
+ *
+ * The view's iterator is a "weakly consistent" iterator that
+ * will never throw {@link ConcurrentModificationException}, and guarantees
+ * to traverse elements as they existed upon construction of the iterator,
+ * and may (but is not guaranteed to) reflect any modifications subsequent
+ * to construction.
+ */
+ @Override
+ public Set> entrySet() {
+ Set> es = entrySet;
+ return es != null? es : (entrySet = new EntrySet());
+ }
+
+ /**
+ * Returns an enumeration of the keys in this table.
+ *
+ * @return an enumeration of the keys in this table
+ * @see #keySet()
+ */
+ public Enumeration keys() {
+ return new KeyIterator();
+ }
+
+ /**
+ * Returns an enumeration of the values in this table.
+ *
+ * @return an enumeration of the values in this table
+ * @see #values()
+ */
+ public Enumeration elements() {
+ return new ValueIterator();
+ }
+
+ /* ---------------- Iterator Support -------------- */
+
+ abstract class HashIterator {
+ int nextSegmentIndex;
+ int nextTableIndex;
+ HashEntry[] currentTable;
+ HashEntry nextEntry;
+ HashEntry lastReturned;
+ K currentKey; // Strong reference to weak key (prevents gc)
+
+ HashIterator() {
+ nextSegmentIndex = segments.length - 1;
+ nextTableIndex = -1;
+ advance();
+ }
+
+ public void rewind() {
+ nextSegmentIndex = segments.length - 1;
+ nextTableIndex = -1;
+ currentTable = null;
+ nextEntry = null;
+ lastReturned = null;
+ currentKey = null;
+ advance();
+ }
+
+ public boolean hasMoreElements() {
+ return hasNext();
+ }
+
+ final void advance() {
+ if (nextEntry != null && (nextEntry = nextEntry.next) != null) {
+ return;
+ }
+
+ while (nextTableIndex >= 0) {
+ if ((nextEntry = currentTable[nextTableIndex --]) != null) {
+ return;
+ }
+ }
+
+ while (nextSegmentIndex >= 0) {
+ Segment seg = segments[nextSegmentIndex --];
+ if (seg.count != 0) {
+ currentTable = seg.table;
+ for (int j = currentTable.length - 1; j >= 0; -- j) {
+ if ((nextEntry = currentTable[j]) != null) {
+ nextTableIndex = j - 1;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ public boolean hasNext() {
+ while (nextEntry != null) {
+ if (nextEntry.key() != null) {
+ return true;
+ }
+ advance();
+ }
+
+ return false;
+ }
+
+ HashEntry nextEntry() {
+ do {
+ if (nextEntry == null) {
+ throw new NoSuchElementException();
+ }
+
+ lastReturned = nextEntry;
+ currentKey = lastReturned.key();
+ advance();
+ } while (currentKey == null); // Skip GC'd keys
+
+ return lastReturned;
+ }
+
+ public void remove() {
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ ConcurrentWeakKeyHashMap.this.remove(currentKey);
+ lastReturned = null;
+ }
+ }
+
+ final class KeyIterator
+ extends HashIterator implements ReusableIterator, Enumeration {
+
+ public K next() {
+ return nextEntry().key();
+ }
+
+ public K nextElement() {
+ return nextEntry().key();
+ }
+ }
+
+ final class ValueIterator
+ extends HashIterator implements ReusableIterator, Enumeration {
+
+ public V next() {
+ return nextEntry().value();
+ }
+
+ public V nextElement() {
+ return nextEntry().value();
+ }
+ }
+
+ /*
+ * This class is needed for JDK5 compatibility.
+ */
+ static class SimpleEntry implements Entry {
+
+ private final K key;
+
+ private V value;
+
+ public SimpleEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public SimpleEntry(Entry extends K, ? extends V> entry) {
+ key = entry.getKey();
+ value = entry.getValue();
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry, ?>)) {
+ return false;
+ }
+ @SuppressWarnings("rawtypes")
+ Map.Entry e = (Map.Entry) o;
+ return eq(key, e.getKey()) && eq(value, e.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return (key == null? 0 : key.hashCode()) ^ (value == null? 0 : value.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return key + "=" + value;
+ }
+
+ private static boolean eq(Object o1, Object o2) {
+ return o1 == null? o2 == null : o1.equals(o2);
+ }
+ }
+
+ /**
+ * Custom Entry class used by EntryIterator.next(), that relays setValue
+ * changes to the underlying map.
+ */
+ final class WriteThroughEntry extends SimpleEntry {
+
+ WriteThroughEntry(K k, V v) {
+ super(k, v);
+ }
+
+ /**
+ * Set our entry's value and write through to the map. The value to
+ * return is somewhat arbitrary here. Since a WriteThroughEntry does not
+ * necessarily track asynchronous changes, the most recent "previous"
+ * value could be different from what we return (or could even have been
+ * removed in which case the put will re-establish). We do not and can
+ * not guarantee more.
+ */
+ @Override
+ public V setValue(V value) {
+
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ V v = super.setValue(value);
+ put(getKey(), value);
+ return v;
+ }
+ }
+
+ final class EntryIterator extends HashIterator implements
+ ReusableIterator> {
+ public Map.Entry next() {
+ HashEntry e = nextEntry();
+ return new WriteThroughEntry(e.key(), e.value());
+ }
+ }
+
+ final class KeySet extends AbstractSet {
+ @Override
+ public Iterator iterator() {
+ return new KeyIterator();
+ }
+
+ @Override
+ public int size() {
+ return ConcurrentWeakKeyHashMap.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ConcurrentWeakKeyHashMap.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return ConcurrentWeakKeyHashMap.this.remove(o) != null;
+ }
+
+ @Override
+ public void clear() {
+ ConcurrentWeakKeyHashMap.this.clear();
+ }
+ }
+
+ final class Values extends AbstractCollection {
+ @Override
+ public Iterator iterator() {
+ return new ValueIterator();
+ }
+
+ @Override
+ public int size() {
+ return ConcurrentWeakKeyHashMap.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ConcurrentWeakKeyHashMap.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return containsValue(o);
+ }
+
+ @Override
+ public void clear() {
+ ConcurrentWeakKeyHashMap.this.clear();
+ }
+ }
+
+ final class EntrySet extends AbstractSet> {
+ @Override
+ public Iterator> iterator() {
+ return new EntryIterator();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ if (!(o instanceof Map.Entry, ?>)) {
+ return false;
+ }
+ Map.Entry, ?> e = (Map.Entry, ?>) o;
+ V v = get(e.getKey());
+ return v != null && v.equals(e.getValue());
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (!(o instanceof Map.Entry, ?>)) {
+ return false;
+ }
+ Map.Entry, ?> e = (Map.Entry, ?>) o;
+ return ConcurrentWeakKeyHashMap.this.remove(e.getKey(), e.getValue());
+ }
+
+ @Override
+ public int size() {
+ return ConcurrentWeakKeyHashMap.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ConcurrentWeakKeyHashMap.this.isEmpty();
+ }
+
+ @Override
+ public void clear() {
+ ConcurrentWeakKeyHashMap.this.clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java b/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java
new file mode 100644
index 000000000..f146a029c
--- /dev/null
+++ b/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project 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 com.taobao.arthas.common.concurrent;
+
+import java.util.Iterator;
+
+public interface ReusableIterator extends Iterator {
+ void rewind();
+}
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index 8190e7ead..53fb17147 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -221,6 +221,12 @@
mockito-core
test
+
+ com.taobao.arthas
+ arthas-demo
+ ${project.version}
+ test
+
org.benf
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java
new file mode 100644
index 000000000..0143e390f
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java
@@ -0,0 +1,203 @@
+package com.taobao.arthas.core.advisor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap;
+import com.taobao.arthas.core.server.ArthasBootstrap;
+import com.taobao.arthas.core.shell.system.ExecStatus;
+import com.taobao.arthas.core.shell.system.ProcessAware;
+
+/**
+ *
+ * TODO line 的记录 listener方式? 还是有string为key,不过 classname|method|desc|num 这样子? 判断是否已插入了,可以在两行中间查询,有没有 SpyAPI 的invoke?
+ *
+ * TODO trace的怎么搞? trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到 SPY的invoke ?直接忽略?
+ *
+ * TODO trace命令可以动态的增加 新的函数进去不?只要关联上同一个 Listener应该是可以的。
+ *
+ * TODO 在SPY里放很多的 Object数组,然后动态的设置进去? 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成 存放这些 Listener数组的类?
+ * 这样子的话,只要有 Binding那里,查询到一个具体分配好的类, 这样子就可以了? 甚至每个ClassLoader里都动态生成这样子的 存放类,那么这样子不可以避免查 ClassLoader了么?
+ *
+ * 动态为每一个增强类,生成一个新的类,新的类里,有各种的 ID 数组,保存每一个类的每一种 trace 点的信息??
+ *
+ * 多个 watch命令 对同一个类,现在的逻辑是,每个watch都有一个自己的 TransForm,但不会重复增强,因为做了判断。 watch命令停止时,也没有去掉增强的代码。
+ * 只有reset时 才会去掉。
+ *
+ * 其实用户想查看局部变量,并不是想查看哪一行! 而是想看某个函数里子调用时的 局部变量的值! 所以实际上是想要一个新的命令,比如 watchinmethod ,
+ * 可以 在某个子调用里,
+ *
+ * TODO 现在的trace 可以输出行号,可能不是很精确,但是可以对应上的。 这个在新的方式里怎么支持? 增加一个 linenumber binding? 从mehtodNode,向上查找到最近的行号?
+ *
+ * TODO 防止重复增强,最重要的应该还是动态增加 annotation,这个才是真正可以做到某一行,某一个子 invoke 都能识别出来的! 无论是 transform多少次!
+ * 字节码怎么动态加 annotation ? annotation里签名用 url ?的key/value方式表达! 这样子可以有效还原信息
+ *
+ * TODO 是否考虑一个 trace /watch命令之后,得到一个具体的 Listener ID, 允许在另外的窗口里,再次 trace/watch时指定这个ID,就会查找到,并处理。
+ * 这样子的话,真正达到了动态灵活的,一层一层增加的trace !
+ *
+ *
+ * @author hengyunabc 2020-04-24
+ *
+ */
+public class AdviceListenerManager {
+
+ private static Timer timer = ArthasBootstrap.getInstance().getTimer();
+
+ static {
+ timer.scheduleAtFixedRate(new TimerTask() {
+
+ @Override
+ public void run() {
+ if (adviceListenerMap != null) {
+ for (Entry entry : adviceListenerMap.entrySet()) {
+ ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
+ synchronized (adviceListenerManager) {
+ for (Entry> eee : adviceListenerManager.map.entrySet()) {
+ List listeners = eee.getValue();
+ List newResult = new ArrayList();
+ for (AdviceListener listener : listeners) {
+ if (listener instanceof ProcessAware) {
+ ProcessAware processAware = (ProcessAware) listener;
+ ExecStatus status = processAware.getProcess().status();
+ if (!status.equals(ExecStatus.TERMINATED)) {
+ newResult.add(listener);
+ }
+ }
+ }
+
+ if (newResult.size() != listeners.size()) {
+ adviceListenerManager.map.put(eee.getKey(), newResult);
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ }, 3000, 3000);
+ }
+
+ static private ConcurrentWeakKeyHashMap adviceListenerMap = new ConcurrentWeakKeyHashMap();
+
+ static class ClassLoaderAdviceListenerManager {
+ private ConcurrentHashMap> map = new ConcurrentHashMap>();
+
+ private String key(String className, String methodName, String methodDesc) {
+ return className + methodName + methodDesc;
+ }
+
+ private String keyForTrace(String className, String owner, String methodName, String methodDesc) {
+ return className + owner + methodName + methodDesc;
+ }
+
+ public void registerAdviceListener(String className, String methodName, String methodDesc,
+ AdviceListener listener) {
+ synchronized (this) {
+ className = className.replace('/', '.');
+ String key = key(className, methodName, methodDesc);
+
+ List listeners = map.get(key);
+ if (listeners == null) {
+ listeners = new ArrayList();
+ map.put(key, listeners);
+ }
+ if(!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+ }
+
+ public List queryAdviceListeners(String className, String methodName, String methodDesc) {
+ className = className.replace('/', '.');
+ String key = key(className, methodName, methodDesc);
+
+ List listeners = map.get(key);
+
+ return listeners;
+ }
+
+
+ public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc,
+ AdviceListener listener) {
+
+ className = className.replace('/', '.');
+ String key = keyForTrace(className, owner, methodName, methodDesc);
+
+ List listeners = map.get(key);
+ if (listeners == null) {
+ listeners = new ArrayList();
+ map.put(key, listeners);
+ }
+ if(!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+
+ public List queryTraceAdviceListeners(String className, String owner, String methodName, String methodDesc) {
+ className = className.replace('/', '.');
+ String key = keyForTrace(className, owner, methodName, methodDesc);
+
+ List listeners = map.get(key);
+
+ return listeners;
+ }
+ }
+
+ public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName,
+ String methodDesc, AdviceListener listener) {
+ className = className.replace('/', '.');
+
+ ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
+
+ if (manager == null) {
+ manager = new ClassLoaderAdviceListenerManager();
+ adviceListenerMap.put(classLoader, manager);
+ }
+ manager.registerAdviceListener(className, methodName, methodDesc, listener);
+ }
+
+ public static void updateAdviceListeners() {
+
+ }
+
+ public static List queryAdviceListeners(ClassLoader classLoader, String className,
+ String methodName, String methodDesc) {
+ className = className.replace('/', '.');
+ ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
+
+ if (manager != null) {
+ return manager.queryAdviceListeners(className, methodName, methodDesc);
+ }
+
+ return null;
+ }
+
+ public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner, String methodName,
+ String methodDesc, AdviceListener listener) {
+ className = className.replace('/', '.');
+
+ ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
+
+ if (manager == null) {
+ manager = new ClassLoaderAdviceListenerManager();
+ adviceListenerMap.put(classLoader, manager);
+ }
+ manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener);
+ }
+ public static List queryTraceAdviceListeners(ClassLoader classLoader, String className, String owner,
+ String methodName, String methodDesc) {
+ className = className.replace('/', '.');
+ ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
+
+ if (manager != null) {
+ return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc);
+ }
+
+ return null;
+ }
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
index 9fedcdeca..8a0becfd4 100644
--- a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
@@ -23,6 +23,7 @@ import java.util.WeakHashMap;
import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes;
import com.alibaba.arthas.deps.org.objectweb.asm.Type;
+import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode;
@@ -44,10 +45,9 @@ import com.taobao.arthas.bytekit.asm.location.filter.GroupLocationFilter;
import com.taobao.arthas.bytekit.asm.location.filter.InvokeCheckLocationFilter;
import com.taobao.arthas.bytekit.asm.location.filter.InvokeContainLocationFilter;
import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter;
+import com.taobao.arthas.bytekit.utils.AsmOpUtils;
import com.taobao.arthas.bytekit.utils.AsmUtils;
import com.taobao.arthas.core.GlobalOptions;
-import com.taobao.arthas.core.bytecode.AdviceListenerManager;
-import com.taobao.arthas.core.bytecode.SpyImpl;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.util.ArthasCheckUtils;
import com.taobao.arthas.core.util.FileUtils;
@@ -248,25 +248,47 @@ public class Enhancer implements ClassFileTransformer {
groupLocationFilter.addFilter(invokeExceptionFilter);
for (MethodNode methodNode : matchedMethods) {
- MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
- for (InterceptorProcessor interceptor : interceptorProcessors) {
- try {
- List locations = interceptor.process(methodProcessor);
- for (Location location : locations) {
- if (location instanceof MethodInsnNodeWare) {
- MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
- MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
-
- AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
- methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
+ // 先查找是否有 atBeforeInvoke 函数,如果有,则说明已经有trace了,则直接不再尝试增强,直接插入 listener
+ if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) {
+ for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
+ .getNext()) {
+ if (insnNode instanceof MethodInsnNode) {
+ final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
+ if(this.skipJDKTrace) {
+ if(methodInsnNode.owner.startsWith("java/")) {
+ continue;
+ }
}
+ // 原始类型的box类型相关的都跳过
+ if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) {
+ continue;
+ }
+ AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
+ methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
}
+ }
+ }else {
+ MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
+ for (InterceptorProcessor interceptor : interceptorProcessors) {
+ try {
+ List locations = interceptor.process(methodProcessor);
+ for (Location location : locations) {
+ if (location instanceof MethodInsnNodeWare) {
+ MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
+ MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
- } catch (Throwable e) {
- e.printStackTrace();
+ AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
+ methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
+ }
+ }
+
+ } catch (Throwable e) {
+ logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e);
+ }
}
}
+ // enter/exist 总是要插入 listener
AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc,
listener);
affect.mCnt(1);
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java
index 2723cb3a7..308a9366d 100644
--- a/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java
@@ -3,6 +3,8 @@ package com.taobao.arthas.core.advisor;
import com.taobao.arthas.core.command.express.ExpressException;
import com.taobao.arthas.core.command.express.ExpressFactory;
import com.taobao.arthas.core.shell.command.CommandProcess;
+import com.taobao.arthas.core.shell.system.Process;
+import com.taobao.arthas.core.shell.system.ProcessAware;
import com.taobao.arthas.core.util.ArthasCheckUtils;
import com.taobao.arthas.core.util.Constants;
import com.taobao.arthas.core.util.StringUtils;
@@ -16,7 +18,8 @@ import java.lang.reflect.Method;
* 通过反射拿到对应的Class/Method类,而不是原始的ClassName/MethodName
* 当然性能开销要比普通监听器高许多
*/
-public abstract class ReflectAdviceListenerAdapter implements AdviceListener {
+public abstract class ReflectAdviceListenerAdapter implements AdviceListener , ProcessAware{
+ private Process process;
@Override
public void create() {
@@ -28,6 +31,14 @@ public abstract class ReflectAdviceListenerAdapter implements AdviceListener {
// default no-op
}
+ public Process getProcess() {
+ return process;
+ }
+
+ public void setProcess(Process process) {
+ this.process = process;
+ }
+
private ClassLoader toClassLoader(ClassLoader loader) {
return null != loader
? loader
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java
new file mode 100644
index 000000000..175ed9f1d
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java
@@ -0,0 +1,210 @@
+package com.taobao.arthas.core.advisor;
+
+import java.arthas.SpyAPI.AbstractSpy;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import com.alibaba.arthas.deps.org.slf4j.Logger;
+import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
+import com.taobao.arthas.core.shell.system.ExecStatus;
+import com.taobao.arthas.core.shell.system.ProcessAware;
+
+/**
+ *
+ * 怎么从 className|methodDesc 到 id 对应起来??
+ * 当id少时,可以id自己来判断是否符合?
+ *
+ * 如果是每个 className|methodDesc 为 key ,是否
+ *
+ *
+ * @author hengyunabc 2020-04-24
+ *
+ */
+public class SpyImpl extends AbstractSpy {
+ private static final Logger logger = LoggerFactory.getLogger(SpyImpl.class);
+
+ @Override
+ public void atEnter(Class> clazz, String methodInfo, Object target, Object[] args) {
+ ClassLoader classLoader = clazz.getClassLoader();
+
+ String[] info = splitMethodInfo(methodInfo);
+ String methodName = info[0];
+ String methodDesc = info[1];
+ // TODO listener 只用查一次,放到 thread local里保存起来就可以了!
+ List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
+ methodName, methodDesc);
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ adviceListener.before(classLoader, clazz.getName(), methodName, methodDesc, target, args);
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void atExit(Class> clazz, String methodInfo, Object target, Object[] args, Object returnObject) {
+ ClassLoader classLoader = clazz.getClassLoader();
+
+ String[] info = splitMethodInfo(methodInfo);
+ String methodName = info[0];
+ String methodDesc = info[1];
+
+ List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
+ methodName, methodDesc);
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ adviceListener.afterReturning(classLoader, clazz.getName(), methodName, methodDesc, target, args,
+ returnObject);
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void atExceptionExit(Class> clazz, String methodInfo, Object target, Object[] args, Throwable throwable) {
+ ClassLoader classLoader = clazz.getClassLoader();
+
+ String[] info = splitMethodInfo(methodInfo);
+ String methodName = info[0];
+ String methodDesc = info[1];
+
+ List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
+ methodName, methodDesc);
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ adviceListener.afterThrowing(classLoader, clazz.getName(), methodName, methodDesc, target, args,
+ throwable);
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void atBeforeInvoke(Class> clazz, String invokeInfo, Object target) {
+ ClassLoader classLoader = clazz.getClassLoader();
+ String[] info = splitInvokeInfo(invokeInfo);
+ String owner = info[0];
+ String methodName = info[1];
+ String methodDesc = info[2];
+
+ List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
+ owner, methodName, methodDesc);
+
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ final InvokeTraceable listener = (InvokeTraceable) adviceListener;
+ listener.invokeBeforeTracing(owner, methodName, methodDesc, Integer.parseInt(info[3]));
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void atAfterInvoke(Class> clazz, String invokeInfo, Object target) {
+ ClassLoader classLoader = clazz.getClassLoader();
+ String[] info = splitInvokeInfo(invokeInfo);
+ String owner = info[0];
+ String methodName = info[1];
+ String methodDesc = info[2];
+ List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
+ owner, methodName, methodDesc);
+
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ final InvokeTraceable listener = (InvokeTraceable) adviceListener;
+ listener.invokeAfterTracing(owner, methodName, methodDesc, Integer.parseInt(info[3]));
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void atInvokeException(Class> clazz, String invokeInfo, Object target, Throwable throwable) {
+ ClassLoader classLoader = clazz.getClassLoader();
+ String[] info = splitInvokeInfo(invokeInfo);
+ String owner = info[0];
+ String methodName = info[1];
+ String methodDesc = info[2];
+
+ List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
+ owner, methodName, methodDesc);
+
+ if (listeners != null) {
+ for (AdviceListener adviceListener : listeners) {
+ try {
+ if (skipAdviceListener(adviceListener)) {
+ continue;
+ }
+ final InvokeTraceable listener = (InvokeTraceable) adviceListener;
+ listener.invokeThrowTracing(owner, methodName, methodDesc, Integer.parseInt(info[3]));
+ } catch (Throwable e) {
+ if (logger.isDebugEnabled()) {
+ logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
+ }
+ }
+ }
+ }
+ }
+
+ private String[] splitMethodInfo(String methodInfo) {
+ return methodInfo.split(Pattern.quote("|"));
+ }
+
+ private String[] splitInvokeInfo(String invokeInfo) {
+ return invokeInfo.split(Pattern.quote("|"));
+ }
+
+ private boolean skipAdviceListener(AdviceListener adviceListener) {
+ if (adviceListener instanceof ProcessAware) {
+ ProcessAware processAware = (ProcessAware) adviceListener;
+ ExecStatus status = processAware.getProcess().status();
+ if (status.equals(ExecStatus.TERMINATED) || status.equals(ExecStatus.STOPPED)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java
new file mode 100644
index 000000000..492cfb085
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java
@@ -0,0 +1,72 @@
+package com.taobao.arthas.core.advisor;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ *
+ * @author hengyunabc 2020-05-18
+ *
+ */
+public class TransformerManager {
+
+ private Instrumentation instrumentation;
+ private List watchTransformers = new CopyOnWriteArrayList();
+ private List traceTransformers = new CopyOnWriteArrayList();
+
+ private ClassFileTransformer classFileTransformer;
+
+ public TransformerManager(Instrumentation instrumentation) {
+ this.instrumentation = instrumentation;
+
+ classFileTransformer = new ClassFileTransformer() {
+
+ @Override
+ public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
+
+ for (ClassFileTransformer classFileTransformer : watchTransformers) {
+ byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
+ protectionDomain, classfileBuffer);
+ if (transformResult != null) {
+ classfileBuffer = transformResult;
+ }
+ }
+
+ for (ClassFileTransformer classFileTransformer : traceTransformers) {
+ byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
+ protectionDomain, classfileBuffer);
+ if (transformResult != null) {
+ classfileBuffer = transformResult;
+ }
+ }
+
+ return classfileBuffer;
+ }
+
+ };
+ instrumentation.addTransformer(classFileTransformer, true);
+ }
+
+ public void addTransformer(ClassFileTransformer transformer, boolean isTracing) {
+ if (isTracing) {
+ traceTransformers.add(transformer);
+ } else {
+ watchTransformers.add(transformer);
+ }
+ }
+
+ public void removeTransformer(ClassFileTransformer transformer) {
+ watchTransformers.remove(transformer);
+ traceTransformers.remove(transformer);
+ }
+
+ public void destroy() {
+ instrumentation.removeTransformer(classFileTransformer);
+ }
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java
index cebd52c54..e7b1e8ec0 100644
--- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java
+++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java
@@ -133,7 +133,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand {
// 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃
if (session.getLock() == lock) {
// 注册通知监听器
- process.register(lock, listener);
+ process.register(lock, listener, effect.getTransformer());
if (process.isForeground()) {
process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n");
}
diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java
index c86153d8c..314cff3ee 100644
--- a/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java
+++ b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java
@@ -18,8 +18,6 @@ package com.taobao.arthas.core.env;
import java.util.Arrays;
-import com.sun.tools.javac.util.Log;
-
/**
* Abstract base class representing a source of name/value property pairs. The
* underlying {@linkplain #getSource() source object} may be of any type
diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java
index 0e45762fe..33d3a9ade 100644
--- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java
+++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java
@@ -27,6 +27,7 @@ import com.alibaba.arthas.tunnel.client.TunnelClient;
import com.taobao.arthas.common.AnsiLog;
import com.taobao.arthas.common.PidUtils;
import com.taobao.arthas.core.advisor.AdviceWeaver;
+import com.taobao.arthas.core.advisor.TransformerManager;
import com.taobao.arthas.core.command.BuiltinCommandPack;
import com.taobao.arthas.core.config.BinderUtils;
import com.taobao.arthas.core.config.Configure;
@@ -80,6 +81,8 @@ public class ArthasBootstrap {
private Timer timer = new Timer("arthas-timer", true);
+ private TransformerManager transformerManager;
+
private ArthasBootstrap(Instrumentation instrumentation, String args) throws Throwable {
this.instrumentation = instrumentation;
@@ -114,6 +117,7 @@ public class ArthasBootstrap {
}
};
+ transformerManager = new TransformerManager(instrumentation);
Runtime.getRuntime().addShutdownHook(shutdown);
}
@@ -330,6 +334,7 @@ public class ArthasBootstrap {
}
}
executorService.shutdownNow();
+ transformerManager.destroy();
UserStatUtil.destroy();
// clear the reference in Spy class.
cleanUpSpyReference();
@@ -395,6 +400,14 @@ public class ArthasBootstrap {
return this.timer;
}
+ public Instrumentation getInstrumentation() {
+ return this.instrumentation;
+ }
+
+ public TransformerManager getTransformerManager() {
+ return this.transformerManager;
+ }
+
private Logger logger() {
return LoggerFactory.getLogger(this.getClass());
}
diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java
index 6f85ca0b4..b3a12dbef 100644
--- a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java
+++ b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java
@@ -7,6 +7,7 @@ import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.shell.term.Tty;
import com.taobao.middleware.cli.CommandLine;
+import java.lang.instrument.ClassFileTransformer;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -125,7 +126,7 @@ public interface CommandProcess extends Tty {
* @param lock the lock for enhance class
* @param listener
*/
- void register(int lock, AdviceListener listener);
+ void register(int lock, AdviceListener listener, ClassFileTransformer transformer);
/**
* Unregister listener
diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java b/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java
new file mode 100644
index 000000000..9dc4baf2e
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java
@@ -0,0 +1,14 @@
+package com.taobao.arthas.core.shell.system;
+
+/**
+ *
+ * @author hengyunabc 2020-05-18
+ *
+ */
+public interface ProcessAware {
+
+ public Process getProcess();
+
+ public void setProcess(Process process);
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java
index 606c67a7d..b28b7875a 100644
--- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java
+++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java
@@ -14,8 +14,8 @@ import com.taobao.arthas.core.shell.handlers.Handler;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.shell.system.ExecStatus;
import com.taobao.arthas.core.shell.system.Process;
+import com.taobao.arthas.core.shell.system.ProcessAware;
import com.taobao.arthas.core.shell.term.Tty;
-import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer;
import com.taobao.arthas.core.util.usage.StyledUsageFormatter;
import com.taobao.middleware.cli.CLIException;
import com.taobao.middleware.cli.CommandLine;
@@ -24,6 +24,7 @@ import com.taobao.text.Color;
import io.termd.core.function.Function;
+import java.lang.instrument.ClassFileTransformer;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -348,7 +349,7 @@ public class ProcessImpl implements Process {
return;
}
- process = new CommandProcessImpl(args2, tty, cl);
+ process = new CommandProcessImpl(this, args2, tty, cl);
if (cacheLocation() != null) {
process.echoTips("job id : " + this.jobId + "\n");
process.echoTips("cache location : " + cacheLocation() + "\n");
@@ -379,15 +380,17 @@ public class ProcessImpl implements Process {
}
private class CommandProcessImpl implements CommandProcess {
-
+ private final Process process;
private final List args2;
private final Tty tty;
private final CommandLine commandLine;
private int enhanceLock = -1;
private AtomicInteger times = new AtomicInteger();
private AdviceListener suspendedListener = null;
+ private ClassFileTransformer transformer;
- public CommandProcessImpl(List args2, Tty tty, CommandLine commandLine) {
+ public CommandProcessImpl(Process process, List args2, Tty tty, CommandLine commandLine) {
+ this.process = process;
this.args2 = args2;
this.tty = tty;
this.commandLine = commandLine;
@@ -525,13 +528,23 @@ public class ProcessImpl implements Process {
}
@Override
- public void register(int enhanceLock, AdviceListener listener) {
+ public void register(int enhanceLock, AdviceListener listener, ClassFileTransformer transformer) {
this.enhanceLock = enhanceLock;
+
+ if (listener instanceof ProcessAware) {
+ ((ProcessAware) listener).setProcess(this.process);
+ }
AdviceWeaver.reg(enhanceLock, listener);
+
+ this.transformer = transformer;
}
@Override
public void unregister() {
+ if (transformer != null) {
+ ArthasBootstrap.getInstance().getTransformerManager().removeTransformer(transformer);
+ }
+
AdviceWeaver.unReg(enhanceLock);
}
diff --git a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java
index 131ec24df..81111c444 100644
--- a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java
+++ b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java
@@ -3,6 +3,7 @@ package com.taobao.arthas.core.util.affect;
import com.taobao.arthas.core.GlobalOptions;
import java.io.File;
+import java.lang.instrument.ClassFileTransformer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
@@ -18,19 +19,13 @@ public final class EnhancerAffect extends Affect {
private final AtomicInteger cCnt = new AtomicInteger();
private final AtomicInteger mCnt = new AtomicInteger();
-
+ private ClassFileTransformer transformer;
/**
* dumpClass的文件存放集合
*/
private final Collection classDumpFiles = new ArrayList();
public EnhancerAffect() {
-
- }
-
- public EnhancerAffect(int cCnt, int mCnt) {
- this.cCnt(cCnt);
- this.mCnt(mCnt);
}
/**
@@ -80,6 +75,14 @@ public final class EnhancerAffect extends Affect {
return classDumpFiles;
}
+ public ClassFileTransformer getTransformer() {
+ return transformer;
+ }
+
+ public void setTransformer(ClassFileTransformer transformer) {
+ this.transformer = transformer;
+ }
+
@Override
public String toString() {
final StringBuilder infoSB = new StringBuilder();