Add alwaysExcluded flag to quasar-hook, run config for generating exclude pattern

This commit is contained in:
Andras Slemmer 2017-08-30 17:13:25 +01:00
parent 605d142738
commit e9f3b92e97
5 changed files with 67 additions and 22 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
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.
We can take the union of the two generated exclude patterns to get a final one.

View File

@ -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<String>()
val instrumentedClasses = HashSet<String>()
val scannedClasses = HashSet<String>()
val usedInstrumentedClasses = ConcurrentHashMap<String, Unit>()
val instrumentedClasses = ConcurrentHashMap<String, Unit>()
val scannedClasses = ConcurrentHashMap<String, Unit>()
}
/**
@ -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<String>? = null,
val expand: List<String>? = null,
val alwaysExcluded: List<String>? = 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")

View File

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

View File

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