diff --git a/gradle.properties b/gradle.properties index 9be62a1f..5653d889 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ apacheHttpClientVersion = 5.2.3 aesyDatasizeVersion = 1.0.0 bytebuddyVersion = 1.14.11 -sharedLibsRef = main +sharedLibsRef = feature/premain-java-agent sharedLibsLocalPath = lib-jvm-shared nativeAgentLibName = drill-agent nativeAgentHookEnabled = false diff --git a/java-agent/build.gradle.kts b/java-agent/build.gradle.kts index fd0dfaa8..8c41231e 100644 --- a/java-agent/build.gradle.kts +++ b/java-agent/build.gradle.kts @@ -186,11 +186,14 @@ kotlin { "net.bytebuddy", "mu", ) - project.setProperty("mainClassName", "com.epam.drill.agent.AppArchiveScannerCliKt") + project.setProperty("mainClassName", "com.epam.drill.agent.AgentKt") val runtimeJar by registering(ShadowJar::class) { manifest { attributes(mapOf( - "Main-Class" to "com.epam.drill.agent.AppArchiveScannerCliKt" + "Main-Class" to "com.epam.drill.agent.AgentKt", + "Premain-Class" to "com.epam.drill.agent.AgentKt", + "Can-Redefine-Classes" to "true", + "Can-Retransform-Classes" to "true" )) } mergeServiceFiles() diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt index 8472b1aa..058faaea 100644 --- a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt @@ -30,6 +30,10 @@ object ParameterDefinitions: AgentParameterDefinitionCollection() { name = "apiKey", description = "Drill4J API key. It is recommended to set it with DRILL_API_KEY env variable, rather than using command line argument" ).register() + val MESSAGE_SENDING_MODE = AgentParameterDefinition.forString( + name = "messageSendingMode", + description = "Message sending mode. Possible values: DIRECT, QUEUED", + defaultValue = "QUEUED").register() val MESSAGE_QUEUE_LIMIT = AgentParameterDefinition.forString(name = "messageQueueLimit", defaultValue = "512Mb").register() val MESSAGE_MAX_RETRIES = AgentParameterDefinition.forInt(name = "messageMaxRetries", defaultValue = Int.MAX_VALUE).register() val SSL_TRUSTSTORE = NullableAgentParameterDefinition.forString(name = "sslTruststore").register() diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/configuration/ParameterValidationException.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterValidationException.kt similarity index 100% rename from java-agent/src/nativeMain/kotlin/com/epam/drill/agent/configuration/ParameterValidationException.kt rename to java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterValidationException.kt diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt similarity index 97% rename from java-agent/src/nativeMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt rename to java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt index 2fe47dff..21af8347 100644 --- a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ValidatedParametersProvider.kt @@ -1,149 +1,149 @@ -/** - * Copyright 2020 - 2022 EPAM Systems - * - * Licensed 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 com.epam.drill.agent.configuration - -import kotlin.reflect.KProperty -import mu.KotlinLogging -import com.epam.drill.agent.common.configuration.AgentParameterDefinition -import com.epam.drill.agent.konform.validation.Invalid -import com.epam.drill.agent.konform.validation.Validation -import com.epam.drill.agent.konform.validation.ValidationError -import com.epam.drill.agent.konform.validation.ValidationErrors -import com.epam.drill.agent.konform.validation.ValidationResult -import com.epam.drill.agent.konform.validation.jsonschema.minItems -import com.epam.drill.agent.konform.validation.jsonschema.minLength -import com.epam.drill.agent.konform.validation.jsonschema.minimum -import com.epam.drill.agent.konform.validation.jsonschema.pattern - -class ValidatedParametersProvider( - private val configurationProviders: Set, - override val priority: Int = Int.MAX_VALUE -) : AgentConfigurationProvider { - - private class ValidatingParameters(provider: ValidatedParametersProvider) { - val appId by provider - val groupId by provider - val buildVersion by provider - val commitSha by provider - val envId by provider - val packagePrefixes by provider - val packagePrefixesAsList = packagePrefixes?.split(";") ?: emptyList() - val drillInstallationDir by provider - val apiUrl by provider - val apiKey by provider - val logLevel by provider - val logLevelAsList = logLevel?.split(";") ?: emptyList() - val logLimit by provider - val logLimitAsInt = logLimit?.toIntOrNull() - } - - private val strictValidators = Validation { - ValidatingParameters::drillInstallationDir required { - minLength(1) - } - ValidatingParameters::groupId required { - identifier() - minLength(3) - } - ValidatingParameters::appId required { - identifier() - minLength(3) - } - ValidatingParameters::apiUrl required { - validTransportUrl() - } - ValidatingParameters::packagePrefixesAsList { - minItems(1) - } - ValidatingParameters::packagePrefixesAsList onEach { - isValidPackage() - } - } - - private val softValidators = Validation { - ValidatingParameters::buildVersion ifPresent { - pattern("^\\S*$") hint "must not contain whitespaces" - } - ValidatingParameters::commitSha ifPresent { - pattern("^[a-f0-9]{40}\$") hint "must be a valid full commit SHA" - } - ValidatingParameters::envId ifPresent { - minLength(1) - } - ValidatingParameters::apiKey ifPresent { - minLength(1) - } - ValidatingParameters::logLevelAsList onEach { - isValidLogLevel() - } - ValidatingParameters::logLimitAsInt ifPresent { - minimum(0) - } - } - - private val logger = KotlinLogging.logger("com.epam.drill.agent.configuration.ValidatedParametersProvider") - - private val validatingConfiguration = validatingConfiguration() - - override val configuration - get() = validateConfiguration() - - fun validate(): List { - val strictValidationErrors = strictValidators(ValidatingParameters(this)).takeIf { it is Invalid }?.errors?.toList() ?: emptyList() - val softValidationErrors = softValidators(ValidatingParameters(this)).takeIf { it is Invalid }?.errors?.toList() ?: emptyList() - return strictValidationErrors + softValidationErrors - } - - internal fun validatingConfiguration() = configurationProviders - .sortedBy(AgentConfigurationProvider::priority) - .map(AgentConfigurationProvider::configuration) - .reduce { acc, map -> acc + map } - - private fun validateConfiguration() = mutableMapOf().also { defaultValues -> - val defaultFor: (AgentParameterDefinition) -> Unit = { - defaultValues[it.name] = it.defaultValue.toString() - } - val isInvalid: (ValidationResult<*>) -> Boolean = { it is Invalid } - strictValidators(ValidatingParameters(this)).takeIf(isInvalid)?.let { result -> - val message = "Cannot load the agent because some agent parameters are set incorrectly. " + - convertToMessage(result.errors) - logger.error { message } - throw ParameterValidationException(message) - } - softValidators(ValidatingParameters(this)).takeIf(isInvalid)?.let { result -> - val message = "Some agent parameters were set incorrectly and were replaced with default values. " + - convertToMessage(result.errors) - logger.error { message } - result.errors.forEach { error -> - when (convertToField(error)) { - ValidatingParameters::logLevel.name -> defaultFor(ParameterDefinitions.LOG_LEVEL) - ValidatingParameters::logLimit.name -> defaultFor(ParameterDefinitions.LOG_LIMIT) - } - } - } - } - - private fun convertToMessage(errors: ValidationErrors) = "Please check the following parameters:\n" + - errors.joinToString("\n") { " - ${convertToField(it)} ${it.message}" } - - private fun convertToField(error: ValidationError) = error.dataPath.removePrefix(".") - .substringBeforeLast("AsList") - .removeSuffix("AsInt") - - private operator fun getValue(thisRef: Any, property: KProperty<*>) = - validatingConfiguration[property.name] - -} +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed 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 com.epam.drill.agent.configuration + +import kotlin.reflect.KProperty +import mu.KotlinLogging +import com.epam.drill.agent.common.configuration.AgentParameterDefinition +import com.epam.drill.agent.konform.validation.Invalid +import com.epam.drill.agent.konform.validation.Validation +import com.epam.drill.agent.konform.validation.ValidationError +import com.epam.drill.agent.konform.validation.ValidationErrors +import com.epam.drill.agent.konform.validation.ValidationResult +import com.epam.drill.agent.konform.validation.jsonschema.minItems +import com.epam.drill.agent.konform.validation.jsonschema.minLength +import com.epam.drill.agent.konform.validation.jsonschema.minimum +import com.epam.drill.agent.konform.validation.jsonschema.pattern + +class ValidatedParametersProvider( + private val configurationProviders: Set, + override val priority: Int = Int.MAX_VALUE +) : AgentConfigurationProvider { + + private class ValidatingParameters(provider: ValidatedParametersProvider) { + val appId by provider + val groupId by provider + val buildVersion by provider + val commitSha by provider + val envId by provider + val packagePrefixes by provider + val packagePrefixesAsList = packagePrefixes?.split(";") ?: emptyList() + val drillInstallationDir by provider + val apiUrl by provider + val apiKey by provider + val logLevel by provider + val logLevelAsList = logLevel?.split(";") ?: emptyList() + val logLimit by provider + val logLimitAsInt = logLimit?.toIntOrNull() + } + + private val strictValidators = Validation { + ValidatingParameters::drillInstallationDir required { + minLength(1) + } + ValidatingParameters::groupId required { + identifier() + minLength(3) + } + ValidatingParameters::appId required { + identifier() + minLength(3) + } + ValidatingParameters::apiUrl required { + validTransportUrl() + } + ValidatingParameters::packagePrefixesAsList { + minItems(1) + } + ValidatingParameters::packagePrefixesAsList onEach { + isValidPackage() + } + } + + private val softValidators = Validation { + ValidatingParameters::buildVersion ifPresent { + pattern("^\\S*$") hint "must not contain whitespaces" + } + ValidatingParameters::commitSha ifPresent { + pattern("^[a-f0-9]{40}\$") hint "must be a valid full commit SHA" + } + ValidatingParameters::envId ifPresent { + minLength(1) + } + ValidatingParameters::apiKey ifPresent { + minLength(1) + } + ValidatingParameters::logLevelAsList onEach { + isValidLogLevel() + } + ValidatingParameters::logLimitAsInt ifPresent { + minimum(0) + } + } + + private val logger = KotlinLogging.logger("com.epam.drill.agent.configuration.ValidatedParametersProvider") + + private val validatingConfiguration = validatingConfiguration() + + override val configuration + get() = validateConfiguration() + + fun validate(): List { + val strictValidationErrors = strictValidators(ValidatingParameters(this)).takeIf { it is Invalid }?.errors?.toList() ?: emptyList() + val softValidationErrors = softValidators(ValidatingParameters(this)).takeIf { it is Invalid }?.errors?.toList() ?: emptyList() + return strictValidationErrors + softValidationErrors + } + + internal fun validatingConfiguration() = configurationProviders + .sortedBy(AgentConfigurationProvider::priority) + .map(AgentConfigurationProvider::configuration) + .reduce { acc, map -> acc + map } + + private fun validateConfiguration() = mutableMapOf().also { defaultValues -> + val defaultFor: (AgentParameterDefinition) -> Unit = { + defaultValues[it.name] = it.defaultValue.toString() + } + val isInvalid: (ValidationResult<*>) -> Boolean = { it is Invalid } + strictValidators(ValidatingParameters(this)).takeIf(isInvalid)?.let { result -> + val message = "Cannot load the agent because some agent parameters are set incorrectly. " + + convertToMessage(result.errors) + logger.error { message } + throw ParameterValidationException(message) + } + softValidators(ValidatingParameters(this)).takeIf(isInvalid)?.let { result -> + val message = "Some agent parameters were set incorrectly and were replaced with default values. " + + convertToMessage(result.errors) + logger.error { message } + result.errors.forEach { error -> + when (convertToField(error)) { + ValidatingParameters::logLevel.name -> defaultFor(ParameterDefinitions.LOG_LEVEL) + ValidatingParameters::logLimit.name -> defaultFor(ParameterDefinitions.LOG_LIMIT) + } + } + } + } + + private fun convertToMessage(errors: ValidationErrors) = "Please check the following parameters:\n" + + errors.joinToString("\n") { " - ${convertToField(it)} ${it.message}" } + + private fun convertToField(error: ValidationError) = error.dataPath.removePrefix(".") + .substringBeforeLast("AsList") + .removeSuffix("AsInt") + + private operator fun getValue(thisRef: Any, property: KProperty<*>) = + validatingConfiguration[property.name] + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt new file mode 100644 index 00000000..597cef82 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt @@ -0,0 +1,222 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed 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 com.epam.drill.agent + +import com.epam.drill.agent.configuration.AgentParameterValidationError +import com.epam.drill.agent.configuration.AgentParametersValidator +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.DefaultParameterDefinitions +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.instrument.ApplicationClassTransformer +import com.epam.drill.agent.instrument.TransformerRegistrar +import com.epam.drill.agent.instrument.clients.ApacheHttpClientTransformer +import com.epam.drill.agent.instrument.clients.JavaHttpClientTransformer +import com.epam.drill.agent.instrument.clients.OkHttp3ClientTransformer +import com.epam.drill.agent.instrument.clients.SpringWebClientTransformer +import com.epam.drill.agent.instrument.jetty.Jetty10WsMessagesTransformer +import com.epam.drill.agent.instrument.jetty.Jetty11WsMessagesTransformer +import com.epam.drill.agent.instrument.jetty.Jetty9WsMessagesTransformer +import com.epam.drill.agent.instrument.jetty.JettyHttpServerTransformer +import com.epam.drill.agent.instrument.jetty.JettyWsClientTransformer +import com.epam.drill.agent.instrument.jetty.JettyWsServerTransformer +import com.epam.drill.agent.instrument.netty.NettyHttpServerTransformer +import com.epam.drill.agent.instrument.netty.NettyWsClientTransformer +import com.epam.drill.agent.instrument.netty.NettyWsMessagesTransformer +import com.epam.drill.agent.instrument.netty.NettyWsServerTransformer +import com.epam.drill.agent.instrument.servers.CadenceTransformer +import com.epam.drill.agent.instrument.servers.CompatibilityTestsTransformer +import com.epam.drill.agent.instrument.servers.KafkaTransformer +import com.epam.drill.agent.instrument.servers.ReactorTransformer +import com.epam.drill.agent.instrument.servers.TTLTransformer +import com.epam.drill.agent.instrument.tomcat.TomcatHttpServerTransformer +import com.epam.drill.agent.instrument.tomcat.TomcatWsClientTransformer +import com.epam.drill.agent.instrument.tomcat.TomcatWsMessagesTransformer +import com.epam.drill.agent.instrument.tomcat.TomcatWsServerTransformer +import com.epam.drill.agent.instrument.undertow.UndertowHttpServerTransformer +import com.epam.drill.agent.instrument.undertow.UndertowWsClientTransformer +import com.epam.drill.agent.instrument.undertow.UndertowWsMessagesTransformer +import com.epam.drill.agent.instrument.undertow.UndertowWsServerTransformer +import com.epam.drill.agent.logging.LoggingConfiguration +import com.epam.drill.agent.module.JvmModuleLoader +import com.epam.drill.agent.test2code.Test2Code +import com.epam.drill.agent.test2code.configuration.Test2CodeParameterDefinitions +import com.epam.drill.agent.transport.JvmModuleMessageSender +import jdk.internal.org.objectweb.asm.ClassReader +import mu.KotlinLogging +import java.lang.instrument.ClassFileTransformer +import java.lang.instrument.Instrumentation +import kotlin.system.exitProcess + +private val logo = """ + ____ ____ _ _ _ _ _ + | _"\U | _"\ u ___ |"| |"| | ||"| U |"| u + /| | | |\| |_) |/ |_"_| U | | u U | | u | || |_ _ \| |/ + U| |_| |\| _ < | | \| |/__ \| |/__ |__ _| | |_| |_,-. + |____/ u|_| \_\ U/| |\u |_____| |_____| /|_|\ \___/-(_/ + |||_ // \\_.-,_|___|_,-.// \\ // \\ u_|||_u _// + (__)_) (__) (__)\_)-' '-(_/(_")("_)(_")("_) (__)__) (__) + Java Agent (v${agentVersion}) + """.trimIndent() +private const val DRILL_PACKAGE = "com/epam/drill/agent" + +private val logger = KotlinLogging.logger("com.epam.drill.agent.Agent") +private val transformers = setOf( + ApplicationClassTransformer, + TomcatHttpServerTransformer, + JettyHttpServerTransformer, + UndertowHttpServerTransformer, + NettyHttpServerTransformer, + JavaHttpClientTransformer, + ApacheHttpClientTransformer, + OkHttp3ClientTransformer, + SpringWebClientTransformer, + KafkaTransformer, + CadenceTransformer, + TTLTransformer, + ReactorTransformer, +// SSLEngineTransformer, TODO does not work in JVM due to too early initialization of HeadersRetriever + JettyWsClientTransformer, + JettyWsServerTransformer, + Jetty9WsMessagesTransformer, + Jetty10WsMessagesTransformer, + Jetty11WsMessagesTransformer, + NettyWsClientTransformer, + NettyWsServerTransformer, + NettyWsMessagesTransformer, + TomcatWsClientTransformer, + TomcatWsServerTransformer, + TomcatWsMessagesTransformer, + UndertowWsClientTransformer, + UndertowWsServerTransformer, + UndertowWsMessagesTransformer, + CompatibilityTestsTransformer, +) + +fun premain(agentArgs: String?, inst: Instrumentation) { + try { + println(logo) + LoggingConfiguration.readDefaultConfiguration() + Configuration.initializeNative(agentArgs ?: "") + updateJvmLoggingConfiguration() + validateConfiguration() + TransformerRegistrar.initialize(transformers) + inst.addTransformer(DrillClassFileTransformer, true) + JvmModuleMessageSender.sendAgentMetadata() + JvmModuleLoader.loadJvmModule(Test2Code::class.java.name).load() + } catch (e: Throwable) { + println("Drill4J Initialization Error:\n${e.message ?: e::class.java.name}") + } +} + +fun main(args: Array) { + try { + println(logo) + LoggingConfiguration.readDefaultConfiguration() + Configuration.initializeNative(args.convertToAgentArgs()) + updateJvmLoggingConfiguration() + validateConfiguration() + + val commitSha = Configuration.parameters[DefaultParameterDefinitions.COMMIT_SHA] + val buildVersion = Configuration.parameters[DefaultParameterDefinitions.BUILD_VERSION] + if (commitSha == null && buildVersion == null) + throw AgentParameterValidationError("Either commitSha or buildVersion must be provided") + + JvmModuleMessageSender.sendBuildMetadata() + val test2Code = JvmModuleLoader.loadJvmModule(Test2Code::class.java.name) as Test2Code + test2Code.scanAndSendMetadataClasses() + Runtime.getRuntime().addShutdownHook(Thread { JvmModuleMessageSender.shutdown() }) + exitProcess(0) + } catch (e: Throwable) { + println("Drill4J Initialization Error:\n${e.message ?: e::class.java.name}") + exitProcess(1) + } +} + +private fun validateConfiguration() { + val validator = AgentParametersValidator(Configuration.parameters) + validator.validate( + DefaultParameterDefinitions, + ParameterDefinitions, + Test2CodeParameterDefinitions + ) +} + +private fun Array.convertToAgentArgs(): String = this + .filter { it.startsWith("--") && it.contains("=") } + .associate { + val (key, value) = it.removePrefix("--").split("=", limit = 2) + key to value + }.filter { it.value.isNotEmpty() } + .map { "${it.key}=${it.value}" } + .joinToString(",") + +private fun updateJvmLoggingConfiguration() { + val logLevel = Configuration.parameters[ParameterDefinitions.LOG_LEVEL] + val logFile = Configuration.parameters[ParameterDefinitions.LOG_FILE] + val logLimit = Configuration.parameters[ParameterDefinitions.LOG_LIMIT] + + LoggingConfiguration.setLoggingLevels(logLevel) + if (LoggingConfiguration.getLoggingFilename() != logFile) { + LoggingConfiguration.setLoggingFilename(logFile) + } + if (LoggingConfiguration.getLogMessageLimit() != logLimit) { + LoggingConfiguration.setLogMessageLimit(logLimit) + } +} + +object DrillClassFileTransformer : ClassFileTransformer { + override fun transform( + loader: ClassLoader?, + className: String?, + classBeingRedefined: Class<*>?, + protectionDomain: java.security.ProtectionDomain?, + classfileBuffer: ByteArray? + ): ByteArray? { + val kClassName = className ?: return null + val kClassBytes = classfileBuffer ?: return null + val precheckedTransformers = TransformerRegistrar.enabledTransformers + .filterNot { kClassName.startsWith(DRILL_PACKAGE) } + .filter { it.precheck(kClassName, loader, protectionDomain) } + .takeIf { it.any() } + ?: return null + val (oldClassBytes, reader) = runCatching { + kClassBytes to ClassReader(kClassBytes) + }.onFailure { + logger.error(it) { "Can't read class: $kClassName" } + }.getOrNull() ?: return null + val permittedTransformers = precheckedTransformers.filter { + it.permit( + kClassName, + reader.superName, + reader.interfaces + ) + } + + val newClassBytes = permittedTransformers.fold(oldClassBytes) { bytes, transformer -> + runCatching { + transformer.transform(kClassName, bytes, loader, protectionDomain) + }.onFailure { + logger.warn(it) { "Can't transform class: $kClassName with ${transformer::class.simpleName}" } + }.getOrNull() + ?.takeIf { it !== bytes } + ?.also { + logger.debug { "$kClassName was transformed by ${transformer::class.simpleName}" } + } ?: bytes + } + + return if (newClassBytes !== oldClassBytes) newClassBytes else null + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/AppArchiveScannerCli.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/AppArchiveScannerCli.kt deleted file mode 100644 index 4cacdf49..00000000 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/AppArchiveScannerCli.kt +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2020 - 2022 EPAM Systems - * - * Licensed 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 com.epam.drill.agent - -import com.epam.drill.agent.common.configuration.AgentConfiguration -import com.epam.drill.agent.common.configuration.AgentMetadata -import com.epam.drill.agent.common.configuration.AgentParameterDefinitionCollection -import com.epam.drill.agent.common.configuration.BaseAgentParameterDefinition -import com.epam.drill.agent.common.transport.AgentMessageDestination -import com.epam.drill.agent.configuration.AgentParameterValidationError -import com.epam.drill.agent.configuration.AgentParametersValidator -import com.epam.drill.agent.configuration.DefaultAgentConfiguration -import com.epam.drill.agent.configuration.DefaultParameterDefinitions -import com.epam.drill.agent.configuration.ParameterDefinitions -import com.epam.drill.agent.logging.LoggingConfiguration -import com.epam.drill.agent.test2code.Test2Code -import com.epam.drill.agent.test2code.configuration.Test2CodeParameterDefinitions -import com.epam.drill.agent.transport.HttpAgentMessageDestinationMapper -import com.epam.drill.agent.transport.JsonAgentMessageSerializer -import com.epam.drill.agent.transport.SimpleAgentMessageSender -import com.epam.drill.agent.transport.http.HttpAgentMessageTransport -import java.io.File -import kotlin.system.exitProcess -import kotlin.takeIf - -fun main(args: Array) { - LoggingConfiguration.readDefaultConfiguration() - - val argsMap: Map = args - .filter { it.startsWith("--") && it.contains("=") } - .associate { - val (key, value) = it.removePrefix("--").split("=", limit = 2) - key to value - }.filter { it.value.isNotEmpty() } - val envMap = System.getenv() - .filterKeys { it.startsWith("DRILL_") } - .filterValues { !it.isNullOrEmpty() } - .mapKeys { toParameterName(it) } - val configuration = DefaultAgentConfiguration(envMap + argsMap) - val definitions = collectAgentParameterDefinitions( - DefaultParameterDefinitions, - ParameterDefinitions, - Test2CodeParameterDefinitions - ) - val validator = AgentParametersValidator(configuration.parameters) - try { - validator.validate(*definitions.toTypedArray()) } - catch (e: AgentParameterValidationError) { - println(e.message) - exitProcess(1) - } - - val commitSha = configuration.parameters[DefaultParameterDefinitions.COMMIT_SHA] - val buildVersion = configuration.parameters[DefaultParameterDefinitions.BUILD_VERSION] - if (commitSha == null && buildVersion == null) { - throw IllegalArgumentException("Either commitSha or buildVersion must be provided") - } - - configuration.parameters[ParameterDefinitions.LOG_LEVEL] - .let(LoggingConfiguration::setLoggingLevels) - configuration.parameters[ParameterDefinitions.LOG_FILE] - ?.let(LoggingConfiguration::setLoggingFilename) - configuration.parameters[ParameterDefinitions.LOG_LIMIT].let(LoggingConfiguration::setLogMessageLimit) - - val transport = HttpAgentMessageTransport( - serverAddress = configuration.parameters[ParameterDefinitions.API_URL], - apiKey = configuration.parameters[ParameterDefinitions.API_KEY] ?: "", - sslTruststore = configuration.parameters[ParameterDefinitions.SSL_TRUSTSTORE] - ?.let { resolvePath(configuration, it) } ?: "", - sslTruststorePass = configuration.parameters[ParameterDefinitions.SSL_TRUSTSTORE_PASSWORD] ?: "", - gzipCompression = configuration.parameters[ParameterDefinitions.USE_GZIP_COMPRESSION], - ) - val serializer = JsonAgentMessageSerializer() - val mapper = HttpAgentMessageDestinationMapper() - val sender = SimpleAgentMessageSender(transport, serializer, mapper) - val test2Code = Test2Code( - id = "test2Code", - agentContext = RequestAgentContext, - sender = sender, - configuration = configuration - ) - sender.send(AgentMessageDestination("PUT", "builds"), configuration.agentMetadata, AgentMetadata.serializer()) - test2Code.scanAndSendMetadataClasses() -} - -//TODO: duplicate with JvmModuleMessageSender -private fun resolvePath(configuration: AgentConfiguration, path: String) = File(path).run { - val installationDir = File(configuration.parameters[DefaultParameterDefinitions.INSTALLATION_DIR] ?: "") - val resolved = this.takeIf(File::exists) - ?: this.takeUnless(File::isAbsolute)?.let(installationDir::resolve) - resolved?.takeUnless(File::isDirectory)?.absolutePath ?: path -} - -//TODO: duplicate from ValidatedParametersProvider -internal fun toParameterName(entry: Map.Entry) = entry.key - .removePrefix("DRILL_") - .lowercase() - .split("_") - .joinToString("") { it.replaceFirstChar(Char::uppercase) } - .replaceFirstChar(Char::lowercase) - -private fun collectAgentParameterDefinitions(vararg collections: AgentParameterDefinitionCollection): List> { - return collections.flatMap { it.getAll() } - -} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/Configuration.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/Configuration.kt index 945ba7f8..6569cdad 100644 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/Configuration.kt +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/Configuration.kt @@ -19,19 +19,54 @@ import mu.KotlinLogging import com.epam.drill.agent.common.configuration.AgentConfiguration import com.epam.drill.agent.common.configuration.AgentMetadata import com.epam.drill.agent.common.configuration.AgentParameters +import com.epam.drill.agent.configuration.provider.AgentOptionsProvider +import com.epam.drill.agent.configuration.provider.EnvironmentVariablesProvider actual object Configuration : AgentConfiguration { private val logger = KotlinLogging.logger {} private lateinit var configuration: DefaultAgentConfiguration + private fun inputParameters(configurationProviders: Set) = configurationProviders + .sortedBy(AgentConfigurationProvider::priority) + .map(AgentConfigurationProvider::configuration) + .reduce { acc, map -> acc + map } + + private fun defineDefaults(agentParameters: AgentParameters) { + agentParameters.define( + DefaultParameterDefinitions.APP_ID, + DefaultParameterDefinitions.INSTANCE_ID, + DefaultParameterDefinitions.BUILD_VERSION, + DefaultParameterDefinitions.GROUP_ID, + DefaultParameterDefinitions.COMMIT_SHA, + DefaultParameterDefinitions.ENV_ID, + DefaultParameterDefinitions.INSTALLATION_DIR, + DefaultParameterDefinitions.CONFIG_PATH + ) + agentParameters.define(DefaultParameterDefinitions.PACKAGE_PREFIXES) + } + actual override val agentMetadata: AgentMetadata get() = configuration.agentMetadata actual override val parameters: AgentParameters get() = configuration.parameters - actual fun initializeNative(agentOptions: String): Unit = throw NotImplementedError() + actual fun initializeNative(agentOptions: String) { + val environmentVariablesProvider = EnvironmentVariablesProvider() + logger.debug { "initializeNative: Found environment variables: ${environmentVariablesProvider.configuration}" } + val agentOptionsProvider = AgentOptionsProvider(agentOptions) + logger.debug { "initializeNative: Found agent options: ${agentOptionsProvider.configuration}" } + val runtimeParametersProvider = RuntimeParametersProvider() + val inputParameters = inputParameters(setOf( + environmentVariablesProvider, + agentOptionsProvider, + runtimeParametersProvider + )) + configuration = DefaultAgentConfiguration(inputParameters) + defineDefaults(configuration.parameters) + logger.debug { "initializeNative: Final input parameters: ${configuration.inputParameters}" } + } actual fun initializeJvm(inputParameters: String) { val parameters = inputParameters.split(",") diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/RuntimeParametersProvider.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/RuntimeParametersProvider.kt new file mode 100644 index 00000000..aacc933d --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/configuration/RuntimeParametersProvider.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed 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 com.epam.drill.agent.configuration + +class RuntimeParametersProvider( + override val priority: Int = 100 +) : AgentConfigurationProvider { + + override val configuration = configuration() + + private fun configuration() = mapOf( + Pair(DefaultParameterDefinitions.INSTANCE_ID.name, java.util.UUID.randomUUID().toString()) + ) + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/request/HeadersRetriever.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/request/HeadersRetriever.kt index 355c7954..874c1fa8 100644 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/request/HeadersRetriever.kt +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/request/HeadersRetriever.kt @@ -16,11 +16,28 @@ package com.epam.drill.agent.request import com.epam.drill.agent.common.request.HeadersRetriever +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import kotlin.text.isNotEmpty actual object HeadersRetriever : HeadersRetriever { - actual external override fun adminAddressHeader(): String - actual external override fun adminAddressValue(): String - actual external override fun sessionHeader(): String - actual external override fun agentIdHeader(): String - actual external override fun agentIdHeaderValue(): String + + private val adminAddress by lazy { Configuration.parameters[ParameterDefinitions.API_URL] } + + private val agentIdHeader by lazy { + Configuration.agentMetadata.groupId.takeIf(String::isNotEmpty) + ?.let { "drill-group-id" to Configuration.agentMetadata.groupId } + ?: let { "drill-agent-id" to Configuration.agentMetadata.appId } + } + + actual override fun adminAddressHeader() = "drill-admin-url" + + actual override fun adminAddressValue() = adminAddress + + actual override fun sessionHeader() = "drill-session-id" + + actual override fun agentIdHeader() = agentIdHeader.first + + actual override fun agentIdHeaderValue() = agentIdHeader.second + } diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/JvmModuleMessageSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/JvmModuleMessageSender.kt index 4b0790e9..62b4c39f 100644 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/JvmModuleMessageSender.kt +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/JvmModuleMessageSender.kt @@ -35,13 +35,25 @@ actual object JvmModuleMessageSender : AgentMessageSender { private const val QUEUE_DEFAULT_SIZE: Long = 512L * 1024 * 1024 private val logger = KotlinLogging.logger {} - private val messageSender = messageSender() + private val messageSender by lazy { messageSender() } override fun send(destination: AgentMessageDestination, message: T, serializer: KSerializer) = messageSender.send(destination, message, serializer) actual fun sendAgentMetadata() { - messageSender.send(AgentMessageDestination("PUT", "instances"), Configuration.agentMetadata, AgentMetadata.serializer()) + messageSender.send( + AgentMessageDestination("PUT", "instances"), + Configuration.agentMetadata, + AgentMetadata.serializer() + ) + } + + fun sendBuildMetadata() { + messageSender.send( + AgentMessageDestination("PUT", "builds"), + Configuration.agentMetadata, + AgentMetadata.serializer() + ) } override fun shutdown() { @@ -49,7 +61,7 @@ actual object JvmModuleMessageSender : AgentMessageSender { } @OptIn(InternalSerializationApi::class) - private fun messageSender(): QueuedAgentMessageSender { + private fun messageSender(): AgentMessageSender { val transport = HttpAgentMessageTransport( serverAddress = Configuration.parameters[ParameterDefinitions.API_URL], apiKey = Configuration.parameters[ParameterDefinitions.API_KEY] ?: "", @@ -65,10 +77,25 @@ actual object JvmModuleMessageSender : AgentMessageSender { val queue = InMemoryAgentMessageQueue( capacity = Configuration.parameters[ParameterDefinitions.MESSAGE_QUEUE_LIMIT].let(::parseBytes), ) - return QueuedAgentMessageSender( - transport, serializer, mapper, queue, - maxRetries = Configuration.parameters[ParameterDefinitions.MESSAGE_MAX_RETRIES] - ) + return when (Configuration.parameters[ParameterDefinitions.MESSAGE_SENDING_MODE].uppercase()) { + "DIRECT" -> SimpleAgentMessageSender(transport, serializer, mapper).also { + logger.info { "Using DIRECT message sending mode." } + } + "QUEUED" -> QueuedAgentMessageSender( + transport, serializer, mapper, queue, + maxRetries = Configuration.parameters[ParameterDefinitions.MESSAGE_MAX_RETRIES] + ).also { + logger.info { "Using QUEUED message sending mode." } + } + + else -> SimpleAgentMessageSender(transport, serializer, mapper).also { + logger.warn { + "Unknown message sending mode: ${Configuration.parameters[ParameterDefinitions.MESSAGE_SENDING_MODE]}. " + + "Falling back to DIRECT mode." + + } + } + } } private fun resolvePath(path: String) = File(path).run { diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt index 9e8b6304..02b37c5e 100644 --- a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt @@ -118,7 +118,6 @@ object Agent { Configuration.initializeNative(options) AgentLoggingConfiguration.updateNativeLoggingConfiguration() TransformerRegistrar.initialize(transformers) - addCapabilities() setEventCallbacks() setUnhandledExceptionHook({ error: Throwable -> logger.error(error) { "Unhandled event: $error" }}.freeze()) @@ -141,8 +140,6 @@ object Agent { AgentLoggingConfiguration.defaultJvmLoggingConfiguration() AgentLoggingConfiguration.updateJvmLoggingConfiguration() Configuration.initializeJvm() - - loadJvmModule("com.epam.drill.agent.test2code.Test2Code") JvmModuleMessageSender.sendAgentMetadata() } @@ -170,6 +167,8 @@ object Agent { } private fun loadJvmModule(clazz: String) = runCatching { JvmModuleLoader.loadJvmModule(clazz).load() } - .onFailure { logger.error(it) { "loadJvmModule: Fatal error: id=${clazz}" } } + .onFailure { + logger.error(it) { "loadJvmModule: Fatal error: id=${clazz}" } + } } diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/jvmti/ClassFileLoadHook.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/jvmti/ClassFileLoadHook.kt index affe5f93..3db54803 100644 --- a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/jvmti/ClassFileLoadHook.kt +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/jvmti/ClassFileLoadHook.kt @@ -48,7 +48,6 @@ object ClassFileLoadHook { initRuntimeIfNeeded() val kClassName = clsName?.toKString() ?: return val kClassData = classData ?: return - val precheckedTransformers = TransformerRegistrar.enabledTransformers .filterNot { kClassName.startsWith(DRILL_PACKAGE) } .filter { it.precheck(kClassName, loader, protectionDomain) }