forked from apache/skywalking
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix apache#4820, support class cache for ByteBuddy, solve the problem…
… of other javaagent retransform classes enhanced by SkyWalking.
- Loading branch information
Showing
7 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
.../org/apache/skywalking/apm/agent/core/plugin/bytebuddy/CacheableTransformerDecorator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF 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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; | ||
|
||
import net.bytebuddy.agent.builder.AgentBuilder; | ||
import net.bytebuddy.agent.builder.ResettableClassFileTransformer; | ||
import net.bytebuddy.utility.RandomString; | ||
import org.apache.skywalking.apm.agent.core.logging.api.ILog; | ||
import org.apache.skywalking.apm.agent.core.logging.api.LogManager; | ||
import org.apache.skywalking.apm.agent.core.util.IOUtils; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.lang.instrument.IllegalClassFormatException; | ||
import java.nio.file.Files; | ||
import java.security.ProtectionDomain; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
/** | ||
* Wrapper classFileTransformer of ByteBuddy, save the enhanced bytecode to memory cache or file cache, | ||
* and automatically load the previously generated bytecode during the second retransform, | ||
* to solve the problem that ByteBuddy generates auxiliary classes with different random names every time. | ||
* Allow other javaagent to enhance those classes that enhanced by SkyWalking agent. | ||
*/ | ||
public class CacheableTransformerDecorator implements AgentBuilder.TransformerDecorator { | ||
|
||
private static final ILog logger = LogManager.getLogger(CacheableTransformerDecorator.class); | ||
|
||
private String cacheDirBase; | ||
private final ClassCacheMode cacheMode; | ||
private ClassCacheResolver cacheResolver; | ||
|
||
public CacheableTransformerDecorator(ClassCacheMode cacheMode) throws IOException { | ||
this.cacheMode = cacheMode; | ||
initClassCache(); | ||
} | ||
|
||
public CacheableTransformerDecorator(ClassCacheMode cacheMode, String cacheDirBase) throws IOException { | ||
this.cacheDirBase = cacheDirBase; | ||
this.cacheMode = cacheMode; | ||
initClassCache(); | ||
} | ||
|
||
private void initClassCache() throws IOException { | ||
if (this.cacheMode.equals(ClassCacheMode.FILE)) { | ||
File cacheDir; | ||
if (this.cacheDirBase == null) { | ||
cacheDir = Files.createTempDirectory("class-cache").toFile(); | ||
} else { | ||
cacheDir = new File(this.cacheDirBase + "/class-cache-" + RandomString.make()); | ||
} | ||
cacheResolver = new FileCacheResolver(cacheDir); | ||
} else { | ||
cacheResolver = new MemoryCacheResolver(); | ||
} | ||
} | ||
|
||
@Override | ||
public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) { | ||
return new ResettableClassFileTransformer.WithDelegation(classFileTransformer) { | ||
|
||
@Override | ||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { | ||
// load from cache | ||
byte[] classCache = cacheResolver.getClassCache(className); | ||
if (classCache != null) { | ||
return classCache; | ||
} | ||
|
||
//transform class | ||
classfileBuffer = classFileTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); | ||
|
||
// save to cache | ||
if (classfileBuffer != null) { | ||
cacheResolver.putClassCache(className, classfileBuffer); | ||
} | ||
|
||
return classfileBuffer; | ||
} | ||
}; | ||
} | ||
|
||
interface ClassCacheResolver { | ||
|
||
byte[] getClassCache(String className); | ||
|
||
void putClassCache(String className, byte[] classfileBuffer); | ||
} | ||
|
||
static class MemoryCacheResolver implements ClassCacheResolver { | ||
|
||
private Map<String, byte[]> classCacheMap = new ConcurrentHashMap<String, byte[]>(); | ||
|
||
@Override | ||
public byte[] getClassCache(String className) { | ||
return classCacheMap.get(className); | ||
} | ||
|
||
@Override | ||
public void putClassCache(String className, byte[] classfileBuffer) { | ||
classCacheMap.put(className, classfileBuffer); | ||
} | ||
} | ||
|
||
static class FileCacheResolver implements ClassCacheResolver { | ||
|
||
private final File cacheDir; | ||
|
||
FileCacheResolver(File cacheDir) { | ||
this.cacheDir = cacheDir; | ||
if (!cacheDir.exists()) { | ||
cacheDir.mkdirs(); | ||
} | ||
cacheDir.deleteOnExit(); | ||
} | ||
|
||
@Override | ||
public byte[] getClassCache(String className) { | ||
// load from cache | ||
File cacheFile = getCacheFile(className); | ||
if (cacheFile.exists()) { | ||
FileInputStream fileInputStream = null; | ||
try { | ||
fileInputStream = new FileInputStream(cacheFile); | ||
return IOUtils.toByteArray(fileInputStream); | ||
} catch (IOException e) { | ||
logger.error("load class bytes from cache file failure", e); | ||
} finally { | ||
IOUtils.closeQuietly(fileInputStream); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public void putClassCache(String className, byte[] classfileBuffer) { | ||
File cacheFile = getCacheFile(className); | ||
FileOutputStream output = null; | ||
try { | ||
output = new FileOutputStream(cacheFile); | ||
IOUtils.copy(new ByteArrayInputStream(classfileBuffer), output); | ||
} catch (IOException e) { | ||
logger.error("save class bytes to cache file failure", e); | ||
} finally { | ||
IOUtils.closeQuietly(output); | ||
} | ||
} | ||
|
||
private File getCacheFile(String className) { | ||
return new File(cacheDir, className.replace('/', '.') + ".class"); | ||
} | ||
|
||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...e/src/main/java/org/apache/skywalking/apm/agent/core/plugin/bytebuddy/ClassCacheMode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF 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 org.apache.skywalking.apm.agent.core.plugin.bytebuddy; | ||
|
||
/** | ||
* ByteBuddy class cache mode | ||
*/ | ||
public enum ClassCacheMode { | ||
FILE, MEMORY | ||
} |
148 changes: 148 additions & 0 deletions
148
...iffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/util/IOUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF 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 org.apache.skywalking.apm.agent.core.util; | ||
|
||
/** | ||
* Copied from {@link org.apache.commons.io.IOUtils} | ||
*/ | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
public class IOUtils { | ||
|
||
private static final int EOF = -1; | ||
|
||
/** | ||
* The default buffer size ({@value}) to use for | ||
* {@link #copyLarge(InputStream, OutputStream)} | ||
*/ | ||
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; | ||
|
||
/** | ||
* Get the contents of an <code>InputStream</code> as a <code>byte[]</code>. | ||
* <p> | ||
* This method buffers the input internally, so there is no need to use a | ||
* <code>BufferedInputStream</code>. | ||
* | ||
* @param input the <code>InputStream</code> to read from | ||
* @return the requested byte array | ||
* @throws NullPointerException if the input is null | ||
* @throws IOException if an I/O error occurs | ||
*/ | ||
public static byte[] toByteArray(InputStream input) throws IOException { | ||
ByteArrayOutputStream output = new ByteArrayOutputStream(); | ||
copy(input, output); | ||
return output.toByteArray(); | ||
} | ||
|
||
|
||
/** | ||
* Copy bytes from an <code>InputStream</code> to an | ||
* <code>OutputStream</code>. | ||
* <p> | ||
* This method buffers the input internally, so there is no need to use a | ||
* <code>BufferedInputStream</code>. | ||
* <p> | ||
* Large streams (over 2GB) will return a bytes copied value of | ||
* <code>-1</code> after the copy has completed since the correct | ||
* number of bytes cannot be returned as an int. For large streams | ||
* use the <code>copyLarge(InputStream, OutputStream)</code> method. | ||
* | ||
* @param input the <code>InputStream</code> to read from | ||
* @param output the <code>OutputStream</code> to write to | ||
* @return the number of bytes copied, or -1 if > Integer.MAX_VALUE | ||
* @throws NullPointerException if the input or output is null | ||
* @throws IOException if an I/O error occurs | ||
* @since 1.1 | ||
*/ | ||
public static int copy(InputStream input, OutputStream output) throws IOException { | ||
long count = copyLarge(input, output); | ||
if (count > Integer.MAX_VALUE) { | ||
return -1; | ||
} | ||
return (int) count; | ||
} | ||
|
||
/** | ||
* Copy bytes from a large (over 2GB) <code>InputStream</code> to an | ||
* <code>OutputStream</code>. | ||
* <p> | ||
* This method buffers the input internally, so there is no need to use a | ||
* <code>BufferedInputStream</code>. | ||
* <p> | ||
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. | ||
* | ||
* @param input the <code>InputStream</code> to read from | ||
* @param output the <code>OutputStream</code> to write to | ||
* @return the number of bytes copied | ||
* @throws NullPointerException if the input or output is null | ||
* @throws IOException if an I/O error occurs | ||
* @since 1.3 | ||
*/ | ||
public static long copyLarge(InputStream input, OutputStream output) | ||
throws IOException { | ||
return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); | ||
} | ||
|
||
/** | ||
* Copy bytes from a large (over 2GB) <code>InputStream</code> to an | ||
* <code>OutputStream</code>. | ||
* <p> | ||
* This method uses the provided buffer, so there is no need to use a | ||
* <code>BufferedInputStream</code>. | ||
* <p> | ||
* | ||
* @param input the <code>InputStream</code> to read from | ||
* @param output the <code>OutputStream</code> to write to | ||
* @param buffer the buffer to use for the copy | ||
* @return the number of bytes copied | ||
* @throws NullPointerException if the input or output is null | ||
* @throws IOException if an I/O error occurs | ||
* @since 2.2 | ||
*/ | ||
public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) | ||
throws IOException { | ||
long count = 0; | ||
int n = 0; | ||
while (EOF != (n = input.read(buffer))) { | ||
output.write(buffer, 0, n); | ||
count += n; | ||
} | ||
return count; | ||
} | ||
|
||
/** | ||
* close streams | ||
* @param closeable the closeable handler | ||
*/ | ||
public static void closeQuietly(Closeable closeable) { | ||
try { | ||
if (closeable != null) { | ||
closeable.close(); | ||
} | ||
} catch (IOException ex) { | ||
//ignore ex | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.