mirror of
https://github.com/corda/corda.git
synced 2025-02-12 13:45:48 +00:00
Merge pull request #1352 from corda/aslemmer-quasar-exclude
Add exclude pattern to the quasar agent
This commit is contained in:
commit
660d012800
@ -0,0 +1,25 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Quasar exclude pattern extraction from node tests (need not pass, check output at the bottom of console)" type="JUnit" factoryName="JUnit">
|
||||||
|
<module name="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" />
|
||||||
|
<option name="PACKAGE_NAME" value="net.corda.node" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="package" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -javaagent:$PROJECT_DIR$/experimental/quasar-hook/build/libs/quasar-hook.jar="expand=com,de,org,co,io;truncate=net.corda;alwaysExcluded=com.opengamma,io.atomix" -javaagent:lib/quasar.jar -Xmx4G" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
|
||||||
|
<option name="ENV_VARIABLES" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs />
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<patterns />
|
||||||
|
<method>
|
||||||
|
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jar" externalProjectPath="$PROJECT_DIR$/experimental/quasar-hook" vmOptions="" scriptParameters="" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -5,17 +5,30 @@ This is a javaagent that may be used while running applications using quasar. It
|
|||||||
methods are scanned, instrumented and used at runtime, and generates an exclude pattern that may be passed in to quasar
|
methods are scanned, instrumented and used at runtime, and generates an exclude pattern that may be passed in to quasar
|
||||||
to stop it from scanning classes unnecessarily.
|
to stop it from scanning classes unnecessarily.
|
||||||
|
|
||||||
Example usage
|
Arguments
|
||||||
=============
|
===
|
||||||
|
|
||||||
|
`expand`, `alwaysExcluded` and `truncate` tweak the output exclude pattern. `expand` is a list of packages to always expand (for example
|
||||||
|
instead of generating `com.*` generate `com.google.*,com.typesafe.*` etc.), `alwaysExcluded` is a list of packages under
|
||||||
|
which all classes are considered excluded irregardless of instrumentation, `truncate` is a list of packages that should
|
||||||
|
not be included in the exclude pattern. Truncating `net.corda` means nothing should be excluded from instrumentation in
|
||||||
|
Corda.
|
||||||
|
|
||||||
|
How to generate an exclude pattern for Corda
|
||||||
|
====
|
||||||
|
|
||||||
|
In order to generate a good exclude pattern we need to exercise the Corda code so that most classes get loaded and
|
||||||
|
inspected by quasar and quasar-hook. For this we can run tests using the 'Quasar exclude pattern extraction (...)'
|
||||||
|
intellij run configuration, which includes the hook. In addition we should run the tool on a bare corda.jar, as some
|
||||||
|
additional classes are used when the jar is invoked directly. To do this we'll use a node in samples:
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew experimental:quasar-hook:jar
|
./gradlew experimental:quasar-hook:jar
|
||||||
java -javaagent:experimental/quasar-hook/build/libs/quasar-hook.jar="expand=com,de,org,co;truncate=net.corda" -jar path/to/corda.jar
|
./gradlew samples:irs-demo:deployNodes
|
||||||
|
cd samples/irs-demo/build/nodes/NotaryService
|
||||||
|
java -javaagent:../../../../../experimental/quasar-hook/build/libs/quasar-hook.jar=expand=com,de,org,co,io;truncate=net.corda;alwaysExcluded=com.opengamma,io.atomix -jar corda.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
The above will run corda.jar and on exit will print information about what classes were scanned/instrumented.
|
Once the node is started just exit the node.
|
||||||
|
|
||||||
`expand` and `truncate` tweak the output exclude pattern. `expand` is a list of packages to always expand (for example
|
We can take the union of the two generated exclude patterns to get a final one.
|
||||||
instead of generating `com.*` generate `com.google.*,com.typesafe.*` etc.), `truncate` is a list of packages that should
|
|
||||||
not be included in the exclude pattern. Truncating `net.corda` means nothing should be excluded from instrumentation in
|
|
||||||
Corda.
|
|
@ -7,14 +7,15 @@ import java.lang.instrument.ClassFileTransformer
|
|||||||
import java.lang.instrument.Instrumentation
|
import java.lang.instrument.Instrumentation
|
||||||
import java.security.ProtectionDomain
|
import java.security.ProtectionDomain
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to collect classes through instrumentation.
|
* Used to collect classes through instrumentation.
|
||||||
*/
|
*/
|
||||||
class ClassRecorder {
|
class ClassRecorder {
|
||||||
val usedInstrumentedClasses = HashSet<String>()
|
val usedInstrumentedClasses = ConcurrentHashMap<String, Unit>()
|
||||||
val instrumentedClasses = HashSet<String>()
|
val instrumentedClasses = ConcurrentHashMap<String, Unit>()
|
||||||
val scannedClasses = HashSet<String>()
|
val scannedClasses = ConcurrentHashMap<String, Unit>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,13 +40,12 @@ fun recordUsedInstrumentedCallStack() {
|
|||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
index++
|
index++
|
||||||
while (true) {
|
while (index < throwable.stackTrace.size) {
|
||||||
require (index < throwable.stackTrace.size) { "Can't find Fiber call" }
|
|
||||||
val stackElement = throwable.stackTrace[index]
|
val stackElement = throwable.stackTrace[index]
|
||||||
if (stackElement.className.startsWith("co.paralleluniverse")) {
|
if (stackElement.className.startsWith("co.paralleluniverse")) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
classRecorder.usedInstrumentedClasses.add(stackElement.className)
|
classRecorder.usedInstrumentedClasses[stackElement.className] = Unit
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ fun recordUsedInstrumentedCallStack() {
|
|||||||
* instrumentation will happen.
|
* instrumentation will happen.
|
||||||
*/
|
*/
|
||||||
fun recordInstrumentedClass(className: String) {
|
fun recordInstrumentedClass(className: String) {
|
||||||
classRecorder.instrumentedClasses.add(className)
|
classRecorder.instrumentedClasses[className] = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,7 @@ fun recordInstrumentedClass(className: String) {
|
|||||||
*/
|
*/
|
||||||
fun recordScannedClass(className: String?) {
|
fun recordScannedClass(className: String?) {
|
||||||
if (className != null) {
|
if (className != null) {
|
||||||
classRecorder.scannedClasses.add(className)
|
classRecorder.scannedClasses[className] = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +74,13 @@ fun recordScannedClass(className: String?) {
|
|||||||
* @param expand A comma-separated list of packages to expand in the glob output. This is useful for certain top-level
|
* @param expand A comma-separated list of packages to expand in the glob output. This is useful for certain top-level
|
||||||
* domains that we don't want to completely exclude, because later on classes may be loaded from those namespaces
|
* domains that we don't want to completely exclude, because later on classes may be loaded from those namespaces
|
||||||
* that require instrumentation.
|
* that require instrumentation.
|
||||||
|
* @param alwaysExcluded A comma-separated list of packages under which all touched classes will be excluded.
|
||||||
* @param separator The package part separator character used in the above lists.
|
* @param separator The package part separator character used in the above lists.
|
||||||
*/
|
*/
|
||||||
data class Arguments(
|
data class Arguments(
|
||||||
val truncate: List<String>? = null,
|
val truncate: List<String>? = null,
|
||||||
val expand: List<String>? = null,
|
val expand: List<String>? = null,
|
||||||
|
val alwaysExcluded: List<String>? = null,
|
||||||
val separator: Char = '.'
|
val separator: Char = '.'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,6 +100,7 @@ class QuasarInstrumentationHookAgent {
|
|||||||
when (key) {
|
when (key) {
|
||||||
"truncate" -> arguments = arguments.copy(truncate = value.split(","))
|
"truncate" -> arguments = arguments.copy(truncate = value.split(","))
|
||||||
"expand" -> arguments = arguments.copy(expand = value.split(","))
|
"expand" -> arguments = arguments.copy(expand = value.split(","))
|
||||||
|
"alwaysExcluded" -> arguments = arguments.copy(alwaysExcluded = value.split(","))
|
||||||
"separator" -> arguments = arguments.copy(separator = value.toCharArray()[0])
|
"separator" -> arguments = arguments.copy(separator = value.toCharArray()[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,19 +116,21 @@ class QuasarInstrumentationHookAgent {
|
|||||||
println(" $it")
|
println(" $it")
|
||||||
}
|
}
|
||||||
println("Scanned classes: ${classRecorder.scannedClasses.size}")
|
println("Scanned classes: ${classRecorder.scannedClasses.size}")
|
||||||
classRecorder.scannedClasses.take(20).forEach {
|
classRecorder.scannedClasses.keys.take(20).forEach {
|
||||||
println(" $it")
|
println(" $it")
|
||||||
}
|
}
|
||||||
println(" (...)")
|
println(" (...)")
|
||||||
val scannedTree = PackageTree.fromStrings(classRecorder.scannedClasses.toList(), '/')
|
val scannedTree = PackageTree.fromStrings(classRecorder.scannedClasses.keys.toList(), '/')
|
||||||
val instrumentedTree = PackageTree.fromStrings(classRecorder.instrumentedClasses.toList(), '/')
|
val instrumentedTree = PackageTree.fromStrings(classRecorder.instrumentedClasses.keys.toList(), '/')
|
||||||
|
val alwaysExclude = arguments.alwaysExcluded?.let { PackageTree.fromStrings(it, arguments.separator) }
|
||||||
|
val alwaysExcludedTree = alwaysExclude?.let { instrumentedTree.truncate(it) } ?: instrumentedTree
|
||||||
println("Suggested exclude globs:")
|
println("Suggested exclude globs:")
|
||||||
val truncate = arguments.truncate?.let { PackageTree.fromStrings(it, arguments.separator) }
|
val truncate = arguments.truncate?.let { PackageTree.fromStrings(it, arguments.separator) }
|
||||||
// The separator append is a hack, it causes a package with an empty name to be added to the exclude tree,
|
// The separator append is a hack, it causes a package with an empty name to be added to the exclude tree,
|
||||||
// which practically causes that level of the tree to be always expanded in the output globs.
|
// which practically causes that level of the tree to be always expanded in the output globs.
|
||||||
val expand = arguments.expand?.let { PackageTree.fromStrings(it.map { "$it${arguments.separator}" }, arguments.separator) }
|
val expand = arguments.expand?.let { PackageTree.fromStrings(it.map { "$it${arguments.separator}" }, arguments.separator) }
|
||||||
val truncatedTree = truncate?.let { scannedTree.truncate(it)} ?: scannedTree
|
val truncatedTree = truncate?.let { scannedTree.truncate(it)} ?: scannedTree
|
||||||
val expandedTree = expand?.let { instrumentedTree.merge(it) } ?: instrumentedTree
|
val expandedTree = expand?.let { alwaysExcludedTree.merge(it) } ?: alwaysExcludedTree
|
||||||
val globs = truncatedTree.toGlobs(expandedTree)
|
val globs = truncatedTree.toGlobs(expandedTree)
|
||||||
globs.forEach {
|
globs.forEach {
|
||||||
println(" $it")
|
println(" $it")
|
||||||
|
@ -36,10 +36,9 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
|
|||||||
capsuleManifest {
|
capsuleManifest {
|
||||||
applicationVersion = corda_release_version
|
applicationVersion = corda_release_version
|
||||||
appClassPath = ["jolokia-agent-war-${project.rootProject.ext.jolokia_version}.war"]
|
appClassPath = ["jolokia-agent-war-${project.rootProject.ext.jolokia_version}.war"]
|
||||||
// TODO add this once we upgrade quasar to 0.7.8
|
// See experimental/quasar-hook/README.md for how to generate.
|
||||||
// def quasarExcludeExpression = "x(rx**;io**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;javassist**;com.fasterxml**;com.typesafe**;com.google**;com.zaxxer**;com.jcabi**;com.codahale**;com.esotericsoftware**;de.javakaffee**;org.objectweb**;org.slf4j**;org.w3c**;org.codehaus**;org.h2**;org.crsh**;org.fusesource**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.xml**;org.jcp**;org.jetbrains**;org.yaml**;co.paralleluniverse**;net.i2p**)"
|
def quasarExcludeExpression = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)"
|
||||||
// javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"]
|
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"]
|
||||||
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
|
|
||||||
systemProperties['visualvm.display.name'] = 'Corda'
|
systemProperties['visualvm.display.name'] = 'Corda'
|
||||||
minJavaVersion = '1.8.0'
|
minJavaVersion = '1.8.0'
|
||||||
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||||
|
@ -801,12 +801,10 @@ class DriverDSL(
|
|||||||
"net.corda.node.cordapp.scan.package" to callerPackage,
|
"net.corda.node.cordapp.scan.package" to callerPackage,
|
||||||
"java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process
|
"java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process
|
||||||
)
|
)
|
||||||
// TODO Add this once we upgrade to quasar 0.7.8, this causes startup time to halve.
|
// See experimental/quasar-hook/README.md for how to generate.
|
||||||
// val excludePattern = x(rx**;io**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;javassist**;com.fasterxml**;com.typesafe**;com.google**;com.zaxxer**;com.jcabi**;com.codahale**;com.esotericsoftware**;de.javakaffee**;org.objectweb**;org.slf4j**;org.w3c**;org.codehaus**;org.h2**;org.crsh**;org.fusesource**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.xml**;org.jcp**;org.jetbrains**;org.yaml**;co.paralleluniverse**;net.i2p**)"
|
val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)"
|
||||||
// val extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } +
|
|
||||||
// "-javaagent:$quasarJarPath=$excludePattern"
|
|
||||||
val extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } +
|
val extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } +
|
||||||
"-javaagent:$quasarJarPath"
|
"-javaagent:$quasarJarPath=$excludePattern"
|
||||||
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
|
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
|
||||||
|
|
||||||
ProcessUtilities.startCordaProcess(
|
ProcessUtilities.startCordaProcess(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user