From 605d1427385096b866fdd8c20e78f065527a6839 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 29 Aug 2017 16:50:46 +0100 Subject: [PATCH 1/2] Add quasar exclude pattern to the agent --- node/capsule/build.gradle | 6 ++---- .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index ca5d578407..9d69e6f5c9 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -36,10 +36,8 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { capsuleManifest { applicationVersion = corda_release_version appClassPath = ["jolokia-agent-war-${project.rootProject.ext.jolokia_version}.war"] - // TODO add this once we upgrade quasar to 0.7.8 - // 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**)" - // javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"] - javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"] + def quasarExcludeExpression = "x(rx**;io**;antlr**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;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.crsh**;org.fusesource**;org.h2**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.jcp**;org.xml**;co.paralleluniverse**;net.i2p**)" + javaAgents = ["quasar-core-${quasar_version}-jdk8.jar=${quasarExcludeExpression}"] systemProperties['visualvm.display.name'] = 'Corda' minJavaVersion = '1.8.0' minUpdateVersion['1.8'] = java8_minUpdateVersion diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index ab849e220f..cc33d1e77b 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -801,12 +801,9 @@ class DriverDSL( "net.corda.node.cordapp.scan.package" to callerPackage, "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. - // 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 extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } + - // "-javaagent:$quasarJarPath=$excludePattern" + val excludePattern = "x(rx**;io**;antlr**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;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.crsh**;org.fusesource**;org.h2**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.jcp**;org.xml**;co.paralleluniverse**;net.i2p**)" val extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } + - "-javaagent:$quasarJarPath" + "-javaagent:$quasarJarPath=$excludePattern" val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" ProcessUtilities.startCordaProcess( From e9f3b92e97d26cb1a6f07273e1d175a95c78e830 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Wed, 30 Aug 2017 17:13:25 +0100 Subject: [PATCH 2/2] Add alwaysExcluded flag to quasar-hook, run config for generating exclude pattern --- ...check_output_at_the_bottom_of_console_.xml | 25 ++++++++++++++++ experimental/quasar-hook/README.md | 29 ++++++++++++++----- .../quasarhook/QuasarInstrumentationHook.kt | 29 +++++++++++-------- node/capsule/build.gradle | 3 +- .../kotlin/net/corda/testing/driver/Driver.kt | 3 +- 5 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 .idea/runConfigurations/Quasar_exclude_pattern_extraction_from_node_tests__need_not_pass__check_output_at_the_bottom_of_console_.xml diff --git a/.idea/runConfigurations/Quasar_exclude_pattern_extraction_from_node_tests__need_not_pass__check_output_at_the_bottom_of_console_.xml b/.idea/runConfigurations/Quasar_exclude_pattern_extraction_from_node_tests__need_not_pass__check_output_at_the_bottom_of_console_.xml new file mode 100644 index 0000000000..8c8a89624b --- /dev/null +++ b/.idea/runConfigurations/Quasar_exclude_pattern_extraction_from_node_tests__need_not_pass__check_output_at_the_bottom_of_console_.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/experimental/quasar-hook/README.md b/experimental/quasar-hook/README.md index 43a253a5af..b0b1de0c99 100644 --- a/experimental/quasar-hook/README.md +++ b/experimental/quasar-hook/README.md @@ -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 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 -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 -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. \ No newline at end of file +We can take the union of the two generated exclude patterns to get a final one. \ No newline at end of file diff --git a/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt b/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt index 960688379b..c32328ba4a 100644 --- a/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt +++ b/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt @@ -7,14 +7,15 @@ import java.lang.instrument.ClassFileTransformer import java.lang.instrument.Instrumentation import java.security.ProtectionDomain import java.util.* +import java.util.concurrent.ConcurrentHashMap /** * Used to collect classes through instrumentation. */ class ClassRecorder { - val usedInstrumentedClasses = HashSet() - val instrumentedClasses = HashSet() - val scannedClasses = HashSet() + val usedInstrumentedClasses = ConcurrentHashMap() + val instrumentedClasses = ConcurrentHashMap() + val scannedClasses = ConcurrentHashMap() } /** @@ -39,13 +40,12 @@ fun recordUsedInstrumentedCallStack() { index++ } index++ - while (true) { - require (index < throwable.stackTrace.size) { "Can't find Fiber call" } + while (index < throwable.stackTrace.size) { val stackElement = throwable.stackTrace[index] if (stackElement.className.startsWith("co.paralleluniverse")) { break } - classRecorder.usedInstrumentedClasses.add(stackElement.className) + classRecorder.usedInstrumentedClasses[stackElement.className] = Unit index++ } } @@ -55,7 +55,7 @@ fun recordUsedInstrumentedCallStack() { * instrumentation will happen. */ fun recordInstrumentedClass(className: String) { - classRecorder.instrumentedClasses.add(className) + classRecorder.instrumentedClasses[className] = Unit } /** @@ -63,7 +63,7 @@ fun recordInstrumentedClass(className: String) { */ fun recordScannedClass(className: String?) { 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 * domains that we don't want to completely exclude, because later on classes may be loaded from those namespaces * 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. */ data class Arguments( val truncate: List? = null, val expand: List? = null, + val alwaysExcluded: List? = null, val separator: Char = '.' ) @@ -98,6 +100,7 @@ class QuasarInstrumentationHookAgent { when (key) { "truncate" -> arguments = arguments.copy(truncate = value.split(",")) "expand" -> arguments = arguments.copy(expand = value.split(",")) + "alwaysExcluded" -> arguments = arguments.copy(alwaysExcluded = value.split(",")) "separator" -> arguments = arguments.copy(separator = value.toCharArray()[0]) } } @@ -113,19 +116,21 @@ class QuasarInstrumentationHookAgent { println(" $it") } println("Scanned classes: ${classRecorder.scannedClasses.size}") - classRecorder.scannedClasses.take(20).forEach { + classRecorder.scannedClasses.keys.take(20).forEach { println(" $it") } println(" (...)") - val scannedTree = PackageTree.fromStrings(classRecorder.scannedClasses.toList(), '/') - val instrumentedTree = PackageTree.fromStrings(classRecorder.instrumentedClasses.toList(), '/') + val scannedTree = PackageTree.fromStrings(classRecorder.scannedClasses.keys.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:") 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, // 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 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) globs.forEach { println(" $it") diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index 9d69e6f5c9..1898e4453c 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -36,7 +36,8 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { capsuleManifest { applicationVersion = corda_release_version appClassPath = ["jolokia-agent-war-${project.rootProject.ext.jolokia_version}.war"] - def quasarExcludeExpression = "x(rx**;io**;antlr**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;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.crsh**;org.fusesource**;org.h2**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.jcp**;org.xml**;co.paralleluniverse**;net.i2p**)" + // See experimental/quasar-hook/README.md for how to generate. + 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}"] systemProperties['visualvm.display.name'] = 'Corda' minJavaVersion = '1.8.0' diff --git a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt index cc33d1e77b..f89eb04bd8 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -801,7 +801,8 @@ class DriverDSL( "net.corda.node.cordapp.scan.package" to callerPackage, "java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process ) - val excludePattern = "x(rx**;io**;antlr**;kotlin**;jdk**;reflectasm**;groovyjarjarasm**;groovy**;joptsimple**;groovyjarjarantlr**;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.crsh**;org.fusesource**;org.h2**;org.hibernate**;org.dom4j**;org.bouncycastle**;org.apache**;org.objenesis**;org.jboss**;org.jcp**;org.xml**;co.paralleluniverse**;net.i2p**)" + // See experimental/quasar-hook/README.md for how to generate. + 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 loggingLevel = if (debugPort == null) "INFO" else "DEBUG"