Merge pull request #1352 from corda/aslemmer-quasar-exclude

Add exclude pattern to the quasar agent
This commit is contained in:
Andras Slemmer 2017-08-30 17:39:05 +01:00 committed by GitHub
commit 660d012800
5 changed files with 69 additions and 29 deletions

View File

@ -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=&quot;expand=com,de,org,co,io;truncate=net.corda;alwaysExcluded=com.opengamma,io.atomix&quot; -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>

View File

@ -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.

View File

@ -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")

View File

@ -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

View File

@ -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(