From 1c09650b71c59b42dcbc8d0c8deab0f15417d771 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Thu, 20 Jul 2023 13:13:49 -0400 Subject: [PATCH 01/48] Adds section to README documenting how to enable line number logging in JMXFetch (#454) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc0639ed9..4240adeaa 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ In order to generate the jar artifact, simply run the ```mvn clean compile assem The distribution will be created under ```target/```. -Once the jar is created, you can update the one in the Datadog Agent repo. +To use this jar in the Agent, see [these docs](https://github.com/DataDog/datadog-agent/blob/main/docs/dev/checks/jmxfetch.md). ## Coding standards @@ -46,6 +46,12 @@ mvn checkstyle::check JMXFetch uses [Lombok](https://projectlombok.org/) to modify classes and generate additional code at runtime. You may need to [enable annotation processors](https://projectlombok.org/setup/overview) to compile in your IDE. +## Useful Developer Settings +### Enabling file line numbers in log messages +If you set the system property `-Djmxfetch.filelinelogging=true`, this will enable all log output to +include the line number which emitted a given log. + + ## Testing To run unit test, issue the following command: From ada7190f458a6973f1d89188940946ca27190b36 Mon Sep 17 00:00:00 2001 From: Caleb Metz Date: Mon, 24 Jul 2023 15:26:15 -0400 Subject: [PATCH 02/48] updated tests and misbehaving to work on MacOS --- pom.xml | 2 +- .../src/main/java/org/datadog/misbehavingjmxserver/App.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 42e571fde..1e4ab016f 100644 --- a/pom.xml +++ b/pom.xml @@ -223,7 +223,7 @@ ${maven-surefire-plugin.version} - -Djdk.attach.allowAttachSelf=true + -Djdk.attach.allowAttachSelf=true -Djava.rmi.server.hostname=localhost diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java index ce2e5aeb5..98ffea8cf 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java @@ -83,6 +83,9 @@ public static void main( String[] args ) throws IOException, MalformedObjectName // I don't think this call is actually important for jmx, the below 'env' param to JMXConnectorServerFactory is the important one RMISocketFactory.setSocketFactory(customRMISocketFactory); + // Explicitly set rmi server host, seems to be issues with the default on our corp computers host MacOS + System.setProperty("java.rmi.server.hostname", config.rmiHost); + // Initialize RMI registry at same port as the jmx service LocateRegistry.createRegistry(config.rmiPort, null, customRMISocketFactory); From 9de49197aa5e215c76bbd0b15341c916f272025e Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:31:52 -0400 Subject: [PATCH 03/48] Update tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java Co-authored-by: Scott Opell --- .../src/main/java/org/datadog/misbehavingjmxserver/App.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java index 98ffea8cf..4f52c099a 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java @@ -83,7 +83,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName // I don't think this call is actually important for jmx, the below 'env' param to JMXConnectorServerFactory is the important one RMISocketFactory.setSocketFactory(customRMISocketFactory); - // Explicitly set rmi server host, seems to be issues with the default on our corp computers host MacOS + // Explicitly set RMI hostname to specified argument value System.setProperty("java.rmi.server.hostname", config.rmiHost); // Initialize RMI registry at same port as the jmx service From 18e36c75d6a4177c9449478a539d1d119ea0c25e Mon Sep 17 00:00:00 2001 From: Caleb Metz Date: Mon, 24 Jul 2023 15:34:06 -0400 Subject: [PATCH 04/48] updated pom comments --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e4ab016f..e3c7e12cf 100644 --- a/pom.xml +++ b/pom.xml @@ -222,7 +222,8 @@ maven-surefire-plugin ${maven-surefire-plugin.version} - + + -Djdk.attach.allowAttachSelf=true -Djava.rmi.server.hostname=localhost From 72a392327a933b45fa59ec6ef54ceba09991781c Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:14:54 -0400 Subject: [PATCH 05/48] Update management agent logic and comments for java 7 vs 8 vs 9 (#457) * restructured loadjmxagent * added version log msg * reverted to maven target 1.7 * updated comments * updated formatting * updated format * updated more formatting --- .../datadog/jmxfetch/AttachApiConnection.java | 41 ++++++++++--------- .../datadog/jmxfetch/ConnectionFactory.java | 2 + 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/AttachApiConnection.java b/src/main/java/org/datadog/jmxfetch/AttachApiConnection.java index 6b975ccb9..db05d4c94 100644 --- a/src/main/java/org/datadog/jmxfetch/AttachApiConnection.java +++ b/src/main/java/org/datadog/jmxfetch/AttachApiConnection.java @@ -59,31 +59,32 @@ private String getJmxUrlForProcessRegex(String processRegex) } // management-agent.jar has been removed in java 8+ - // Once JMXFetch drops java7 support, this should be simplified to simply invoke - // vm.startLocalManagementAgent + // Once JMXFetch drops java 7 support, this should be simplified to simply invoke + // vm.startLocalManagementAgent which is accessible in java 8 if tools.jar is added + // to the classpath and java 9+ by default // ref https://bugs.openjdk.org/browse/JDK-8179063 private void loadJmxAgent(com.sun.tools.attach.VirtualMachine vm) throws IOException { - String agent = - vm.getSystemProperties().getProperty("java.home") - + File.separator - + "lib" - + File.separator - + "management-agent.jar"; try { - vm.loadAgent(agent); - } catch (Exception e) { - log.warn("Error initializing JMX agent from management-agent.jar", e); - + Method method = com.sun.tools.attach.VirtualMachine + .class.getMethod("startLocalManagementAgent"); + log.info("Found startLocalManagementAgent API, attempting to use it."); + method.invoke(vm); + } catch (NoSuchMethodException noMethodE) { + log.warn("startLocalManagementAgent method not found, must be on java 7", noMethodE); + String agent = vm.getSystemProperties().getProperty("java.home") + + File.separator + + "lib" + + File.separator + + "management-agent.jar"; try { - Method method = com.sun.tools.attach.VirtualMachine - .class.getMethod("startLocalManagementAgent"); - log.info("Found startLocalManagementAgent API, attempting to use it."); - method.invoke(vm); - } catch (NoSuchMethodException noMethodE) { - log.warn("startLocalManagementAgent method not found, must be on java7", noMethodE); - } catch (Exception reflectionE) { - log.warn("Error invoking the startLocalManagementAgent method", reflectionE); + vm.loadAgent(agent); + } catch (Exception e) { + log.warn("Error initializing JMX agent from management-agent.jar", e); } + } catch (Exception reflectionE) { + log.warn("Error invoking the startLocalManagementAgent method", reflectionE); } + + } } diff --git a/src/main/java/org/datadog/jmxfetch/ConnectionFactory.java b/src/main/java/org/datadog/jmxfetch/ConnectionFactory.java index 61b7e32db..8855e42a6 100644 --- a/src/main/java/org/datadog/jmxfetch/ConnectionFactory.java +++ b/src/main/java/org/datadog/jmxfetch/ConnectionFactory.java @@ -24,6 +24,8 @@ public static Connection createConnection(Map connectionParams) if (connectionParams.get(PROCESS_NAME_REGEX) != null) { try { + // AttachNotSupportedException is accessible in java 7 and 8 through tools.jar + // and java 9+ by default Class.forName("com.sun.tools.attach.AttachNotSupportedException"); } catch (ClassNotFoundException e) { throw new IOException( From d75f44152ea2e1735a5fc23e9a735eeb96df037f Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 2 Aug 2023 16:24:03 +0100 Subject: [PATCH 06/48] Adding CODEOWNERS file (#462) --- .github/CODEOWNERS | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..a58883d76 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# See https://help.github.com/articles/about-codeowners/ for syntax +# Rules are matched bottom-to-top, so one team can own subdirectories +# and another team can own the rest of the directory. + +* @Datadog/agent-metrics-logs + +# Documentation +*.md @DataDog/documentation From f9fef9d04b944e1a5a882a9d1fe795df99288aee Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 4 Aug 2023 15:21:21 +0100 Subject: [PATCH 07/48] Adding Maven Wrapper to project (#463) * Adding Maven Wrapper * Updated build to use Maven Wrapper * Update README.md Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> * Update README.md Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> * Update README.md Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> * Update README.md Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> * Update README.md Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> --------- Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> --- .circleci/config.yml | 8 +- .github/workflows/codeql-analysis.yml | 2 +- .gitlab-ci.yml | 8 +- .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .mvn/wrapper/maven-wrapper.properties | 18 ++ README.md | 47 +++- mvnw | 308 ++++++++++++++++++++++++++ mvnw.cmd | 205 +++++++++++++++++ 8 files changed, 580 insertions(+), 16 deletions(-) create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100755 mvnw create mode 100644 mvnw.cmd diff --git a/.circleci/config.yml b/.circleci/config.yml index c6638c701..1dac557df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,12 +2,12 @@ version: 2 lint_steps: &lint_steps steps: - checkout - - run: mvn verify -B -Dhttps.protocols=TLSv1.2 -DskipTests + - run: ./mvnw verify -B -Dhttps.protocols=TLSv1.2 -DskipTests build_steps: &build_steps steps: - checkout - setup_remote_docker - - run: mvn test -B -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true + - run: ./mvnw test -B -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true - run: when: on_fail command: for log in target/surefire-reports/*.txt; do echo "$log ========================" ; cat $log ; done @@ -15,11 +15,11 @@ build_steps: &build_steps jobs: lint_openjdk8: docker: - - image: circleci/openjdk:8-jdk + - image: cimg/openjdk:8.0 <<: *lint_steps test_openjdk8: docker: - - image: circleci/openjdk:8-jdk + - image: cimg/openjdk:8.0 <<: *build_steps test_openjdk11: docker: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 02217e5d7..3283c27b5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,7 @@ jobs: java-package: jdk - name: Build with Maven - run: mvn clean compile assembly:single + run: ./mvnw clean compile assembly:single - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fc1a8e7e..81ff2e906 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,10 +22,10 @@ run_unit_tests: tags: - "runner:docker" - image: maven:3.9.1-eclipse-temurin-8 + image: eclipse-temurin:8u382-b05-jdk script: - - mvn -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true -B test + - ./mvnw -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true -B test artifacts: expire_in: 1 mos @@ -45,7 +45,7 @@ deploy_to_sonatype: tags: - "runner:docker" - image: maven:3.9.1-eclipse-temurin-8 + image: eclipse-temurin:8u382-b05-jdk script: # Ensure we don't print commands being run to the logs during credential @@ -72,7 +72,7 @@ deploy_to_sonatype: - set -x - echo "Building release..." - - mvn -Djdk.attach.allowAttachSelf=true -DperformRelease=true -Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 --settings ./settings.xml clean deploy + - ./mvnw -Djdk.attach.allowAttachSelf=true -DperformRelease=true -Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 --settings ./settings.xml clean deploy artifacts: expire_in: 12 mos diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..6d3a56651 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/README.md b/README.md index 4240adeaa..73a736238 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,27 @@ pull request. ## Building from source -JMXFetch uses [Maven](http://maven.apache.org) for its build system. +JMXFetch uses [Maven](http://maven.apache.org) for its build system. The repo contains a [Maven Wrapper](https://maven.apache.org/wrapper/), so you don't need to download and install Maven. -In order to generate the jar artifact, simply run the ```mvn clean compile assembly:single``` command in the cloned directory. +In order to generate the JAR artifact, run the `./mvnw clean compile assembly:single` command in the cloned directory. The distribution will be created under ```target/```. -To use this jar in the Agent, see [these docs](https://github.com/DataDog/datadog-agent/blob/main/docs/dev/checks/jmxfetch.md). +To use this JAR in the Agent, see [these docs](https://github.com/DataDog/datadog-agent/blob/main/docs/dev/checks/jmxfetch.md). + +### Note + +If you want build all the JAR files for JMXFetch, you need to use an older JDK version like JDK 8. +There is a known issue where the build can't find `javadoc command` on modern JDKs. +The quickest way to build these JAR files is to use Docker: + +``` +docker run -it --rm \ + --name my-maven-project \ + -v "$(pwd)":/usr/src/app \ + -w /usr/src/app \ + eclipse-temurin:8u382-b05-jdk ./mvnw -DskipTests clean package +``` ## Coding standards @@ -36,7 +50,7 @@ JMXFetch uses [Checkstyle](http://checkstyle.sourceforge.net/) with [Google Java To perform a `Checkstyle` analysis and outputs violations, run: ``` -mvn checkstyle::check +./mvnw checkstyle::check ``` `Checkstyle` analysis is automatically executed prior to compiling the code, testing. @@ -56,7 +70,7 @@ include the line number which emitted a given log. To run unit test, issue the following command: ``` -mvn test +./mvnw test ``` Some tests utilize [TestContainers](https://www.testcontainers.org/) which requires a docker client. @@ -66,7 +80,7 @@ If you're on macOS or Windows, docker desktop is architected to run a linux VM w This makes the networking a bit different and you should use the following command to run the tests. ``` -docker run -it --rm -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal -v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock maven:3.8-eclipse-temurin-8 mvn test +docker run -it --rm -e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal -v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock eclipse-temurin:8-jdk ./mvnw test ``` This version runs the maven jmxfetch tests within a container as well, which works as long as the `TEST_CONTAINERS_HOST_OVERRIDE` env var is set. @@ -127,7 +141,7 @@ export SONATYPE_PASS="" ``` - Run the deploy with the appropriate `skipStaging` flag: ```sh -mvn -DskipTests -DskipStaging=true -DperformRelease=true --settings settings.xml clean deploy +./mvnw -DskipTests -DskipStaging=true -DperformRelease=true --settings settings.xml clean deploy ``` If you do this correctly, the artifact will be available in the Nexus container at @@ -144,3 +158,22 @@ otherwise the subsequent publishes will fail. Get help on usage: java -jar jmxfetch-0.48.0-SNAPSHOT-jar-with-dependencies.jar --help ``` + +## Updating Maven Wrapper + +To upgrade the Maven Wrapper, you need to run: + +``` +./mvn wrapper:wrapper -Dmaven= +``` + +The easiest way to regenerate the wrapper files (`mvnw`, `mvnw.cmd`, `.mvn/wrapper/maven-wrapper.jar` and `.mvn/wrapper/maven-wrapper.properties`) is to use Docker: + +``` +docker run -it --rm \ + --name my-maven-project \ + -v "$(pwd)":/usr/src/app \ + -w /usr/src/app maven:3 mvn wrapper:wrapper -Dmaven= +``` + +Leave out the `-Dmaven=` to get the latest version of Maven. diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..8d937f4c1 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..f80fbad3e --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From 02c071f0d5a7a601144e2a31b9f817aac46523b1 Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:09:47 -0400 Subject: [PATCH 08/48] remove quotes from bean parameter based tags (#456) * Added config option to remove bean param quotes * fixed logging and added unit test * updated gitignore * updated gitignore * changed name to normalize_bean_param_tags * updated tests * reverted compiler version * updated some whitespace formatting * updated tests back to pre java 20 settings * combined sanitize and normalize Params * updated formatting * added ObjectName.unquote() wrapper * updated tests * updated formatting * updated formatting * Added comment on unquote wrapper --- .gitignore | 1 - .../java/org/datadog/jmxfetch/Instance.java | 16 ++++- .../org/datadog/jmxfetch/JmxAttribute.java | 26 +++++++- .../datadog/jmxfetch/JmxComplexAttribute.java | 6 +- .../datadog/jmxfetch/JmxSimpleAttribute.java | 6 +- .../org/datadog/jmxfetch/JmxSubAttribute.java | 6 +- .../datadog/jmxfetch/JmxTabularAttribute.java | 6 +- .../java/org/datadog/jmxfetch/TestApp.java | 62 +++++++++++++++++++ .../jmx_bean_tags_dont_normalize_params.yaml | 18 ++++++ .../jmx_bean_tags_normalize_params.yaml | 19 ++++++ 10 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/test/resources/jmx_bean_tags_dont_normalize_params.yaml create mode 100644 src/test/resources/jmx_bean_tags_normalize_params.yaml diff --git a/.gitignore b/.gitignore index a1a8ed0fb..ac7e58ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ target/* .idea .vscode *.iml - *.ucls *.png diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index e924284b3..9c6778c4e 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -103,6 +103,7 @@ public Yaml initialValue() { private AppConfig appConfig; private Boolean cassandraAliasing; private boolean emptyDefaultHostname; + private Boolean normalizeBeanParamTags; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ public Instance(Instance instance, AppConfig appConfig) { @@ -211,6 +212,12 @@ public Instance( this.serviceCheckPrefix = (String) initConfig.get("service_check_prefix"); } + this.normalizeBeanParamTags = (Boolean) instanceMap.get("normalize_bean_param_tags"); + if (this.normalizeBeanParamTags == null) { + this.normalizeBeanParamTags = false; + } + + // Alternative aliasing for CASSANDRA-4009 metrics // More information: https://issues.apache.org/jira/browse/CASSANDRA-4009 this.cassandraAliasing = (Boolean) instanceMap.get("cassandra_aliasing"); @@ -578,7 +585,8 @@ private void getMatchingAttributes() throws IOException { serviceNameProvider, tags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else if (COMPOSED_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -596,7 +604,8 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else if (MULTI_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -614,7 +623,8 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else { try { log.debug( diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index 68661d82c..38e926790 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -62,6 +62,7 @@ public abstract class JmxAttribute { private List defaultTagsList; private boolean cassandraAliasing; protected String checkName; + private boolean normalizeBeanParamTags; JmxAttribute( MBeanAttributeInfo attribute, @@ -73,7 +74,8 @@ public abstract class JmxAttribute { ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { this.attribute = attribute; this.beanName = beanName; this.className = className; @@ -84,6 +86,7 @@ public abstract class JmxAttribute { this.cassandraAliasing = cassandraAliasing; this.checkName = checkName; this.serviceNameProvider = serviceNameProvider; + this.normalizeBeanParamTags = normalizeBeanParamTags; // A bean name is formatted like that: // org.apache.cassandra.db:type=Caches,keyspace=system,cache=HintsColumnFamilyKeyCache @@ -191,16 +194,35 @@ private List getBeanParametersList( return beanTags; } + /** + * Wrapper for javax.management.ObjectName.unqoute that removes quotes from the bean parameter + * value if possible. If not, it hits the catch block and returns the original parameter. + */ + private String unquote(String beanParameter) { + int valueIndex = beanParameter.indexOf(':') + 1; + try { + return beanParameter.substring(0,valueIndex) + + ObjectName.unquote(beanParameter.substring(valueIndex)); + } catch (IllegalArgumentException e) { + return beanParameter; + } + } + /** * Sanitize MBean parameter names and values, i.e. - Rename parameter names conflicting with * existing tags - Remove illegal characters */ - private static List sanitizeParameters(List beanParametersList) { + private List sanitizeParameters(List beanParametersList) { List defaultTagsList = new ArrayList(beanParametersList.size()); for (String rawBeanParameter : beanParametersList) { // Remove `|` characters String beanParameter = rawBeanParameter.replace("|", ""); + // Unquote bean parameters that have been quoted and escaped by ObjectName.quote() + if (normalizeBeanParamTags == true) { + beanParameter = unquote(beanParameter); + } + // 'host' parameter is renamed to 'bean_host' if (beanParameter.startsWith("host:")) { defaultTagsList.add("bean_host:" + beanParameter.substring("host:".length())); diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index 70ca4cb47..3ea2db51b 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -30,7 +30,8 @@ public JmxComplexAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -41,7 +42,8 @@ public JmxComplexAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } private void populateSubAttributeList(Object attributeValue) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java index c57f16e67..b890613ea 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java @@ -28,7 +28,8 @@ public JmxSimpleAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - Boolean emptyDefaultHostname) { + Boolean emptyDefaultHostname, + Boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -39,7 +40,8 @@ public JmxSimpleAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } @Override diff --git a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java index 0c8899ed4..98a6e1666 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java @@ -21,7 +21,8 @@ public JmxSubAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -32,7 +33,8 @@ public JmxSubAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } public Metric getCachedMetric(String name) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index d36369f6c..efc5a1f35 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -38,7 +38,8 @@ public JmxTabularAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -49,7 +50,8 @@ public JmxTabularAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); subAttributeList = new HashMap>(); } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index a2563ef50..e45a5de3d 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; +import javax.management.ObjectName; + import org.junit.Test; public class TestApp extends TestCommon { @@ -70,6 +72,66 @@ public void testBeanTags() throws Exception { assertMetric("this.is.100", tags, 6); } + /** Tag metrics with MBeans parameters with normalize_bean_param_tags option enabled. */ + @Test + public void testBeanTagsNormalizeParams() throws Exception { + // We expose a few metrics through JMX + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" + + ObjectName.quote(".*example.process.regex.*")); + initApplication("jmx_bean_tags_normalize_params.yaml"); + + // Collecting metrics + run(); + List> metrics = getMetrics(); + + // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file + assertEquals(14, metrics.size()); + + List tags = + Arrays.asList( + "type:SimpleTestJavaApp", + "scope:CoolScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "bean_host:localhost", + "component", + "target_instance:.*example.process.regex.*"); + + assertMetric("this.is.100", tags, 7); + } + + /** Tag metrics with MBeans parameters with normalize_bean_param_tags option disabled. */ + @Test + public void testBeanTagsDontNormalizeParams() throws Exception { + // We expose a few metrics through JMX + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" + + ObjectName.quote(".*example.process.regex.*")); + initApplication("jmx_bean_tags_dont_normalize_params.yaml"); + + // Collecting metrics + run(); + List> metrics = getMetrics(); + + // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file + assertEquals(14, metrics.size()); + + List tags = + Arrays.asList( + "type:\"SimpleTestJavaApp\"", + "scope:\"CoolScope\"", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "bean_host:\"localhost\"", + "component", + "target_instance:\".\\*example.process.regex.\\*\""); + + assertMetric("this.is.100", tags, 7); + } + /** Generate metric aliases from a `alias_match` regular expression. */ @Test public void testRegexpAliasing() throws Exception { diff --git a/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml b/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml new file mode 100644 index 000000000..b9ddb5267 --- /dev/null +++ b/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml @@ -0,0 +1,18 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + - include: + bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= + attribute: + Hashmap.thisis0: + metric_type: gauge + alias: bean.parameters.should.not.match diff --git a/src/test/resources/jmx_bean_tags_normalize_params.yaml b/src/test/resources/jmx_bean_tags_normalize_params.yaml new file mode 100644 index 000000000..b2aee6578 --- /dev/null +++ b/src/test/resources/jmx_bean_tags_normalize_params.yaml @@ -0,0 +1,19 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + - include: + bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= + attribute: + Hashmap.thisis0: + metric_type: gauge + alias: bean.parameters.should.not.match + normalize_bean_param_tags: true From 3c030643b369c32824b220fb0c1312d0bebfe541 Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:33:34 -0400 Subject: [PATCH 09/48] custom jmxfetch bean to track number of ingested beans, attributes, and metrics (#453) * added bean that tracks number of ingested beans * restructured bean creation * reverted compiler version * updated some formatting * cleaned up some formatting * simplified quoting process * removed constructor argument from jmxFetchTelem * fixed formatting * updated spacing * cleaned up debug logs * fixed long lines * updated naming * removed java object debug --- src/main/java/org/datadog/jmxfetch/App.java | 6 +- .../java/org/datadog/jmxfetch/Instance.java | 61 ++++++++++++++++++- .../jmxfetch/util/InstanceTelemetry.java | 44 +++++++++++++ .../jmxfetch/util/InstanceTelemetryMBean.java | 11 ++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java create mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 4f3e3aee5..d80e9fe0f 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -388,11 +388,13 @@ void start() { System.arraycopy(minibuff, 0, buffer, oldLen, len); } } - this.setReinit(processAutoDiscovery(buffer)); + boolean result = processAutoDiscovery(buffer); + this.setReinit(result); } if (this.appConfig.remoteEnabled()) { - this.setReinit(getJsonConfigs()); + boolean result = getJsonConfigs(); + this.setReinit(result); } } catch (IOException e) { log.warn( diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 9c6778c4e..1542869d2 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -4,13 +4,17 @@ import org.datadog.jmxfetch.reporter.Reporter; import org.datadog.jmxfetch.service.ConfigServiceNameProvider; import org.datadog.jmxfetch.service.ServiceNameProvider; +import org.datadog.jmxfetch.util.InstanceTelemetry; import org.yaml.snakeyaml.Yaml; + + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -20,8 +24,15 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import javax.security.auth.login.FailedLoginException; @@ -103,6 +114,9 @@ public Yaml initialValue() { private AppConfig appConfig; private Boolean cassandraAliasing; private boolean emptyDefaultHostname; + private InstanceTelemetry instanceTelemetryBean; + private ObjectName instanceTelemetryBeanName; + private MBeanServer mbs; private Boolean normalizeBeanParamTags; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ @@ -264,8 +278,34 @@ public Instance( } else { log.info("collect_default_jvm_metrics is false - not collecting default JVM metrics"); } + + instanceTelemetryBean = createJmxBean(); } + private ObjectName getObjName(String domain,String instance) + throws MalformedObjectNameException { + return new ObjectName(domain + ":target_instance=" + ObjectName.quote(instance)); + } + + private InstanceTelemetry createJmxBean() { + mbs = ManagementFactory.getPlatformMBeanServer(); + InstanceTelemetry bean = new InstanceTelemetry(); + log.debug("Created jmx bean for instance: " + this.getCheckName()); + + try { + instanceTelemetryBeanName = getObjName("JMXFetch" , this.getName()); + mbs.registerMBean(bean,instanceTelemetryBeanName); + log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName()); + + } catch (MalformedObjectNameException | InstanceAlreadyExistsException + | MBeanRegistrationException | NotCompliantMBeanException e) { + log.warn("Could not register bean for instance: " + this.getCheckName(),e); + } + + return bean; + } + + public static boolean isDirectInstance(Map configInstance) { Object directInstance = configInstance.get(JVM_DIRECT); return directInstance instanceof Boolean && (Boolean) directInstance; @@ -460,7 +500,7 @@ public List getMetrics() throws IOException { ? this.initialRefreshBeansPeriod : this.refreshBeansPeriod; if (isPeriodDue(this.lastRefreshTime, period)) { - log.info("Refreshing bean list"); + log.info("Refreshing bean list for " + this.getCheckName()); this.refreshBeansList(); this.getMatchingAttributes(); } @@ -492,6 +532,13 @@ public List getMetrics() throws IOException { } } } + instanceTelemetryBean.setBeanCount(beans.size()); + instanceTelemetryBean.setAttributeCount(matchingAttributes.size()); + instanceTelemetryBean.setMetricCount(metrics.size()); + log.debug("Updated jmx bean for instance: " + this.getCheckName() + + " With number of beans = " + instanceTelemetryBean.getBeanCount() + + " attributes = " + instanceTelemetryBean.getAttributeCount() + + " metrics = " + instanceTelemetryBean.getMetricCount()); return metrics; } @@ -783,8 +830,18 @@ public boolean isLimitReached() { return this.limitReached; } + private void cleanupTelemetryBean() { + try { + mbs.unregisterMBean(instanceTelemetryBeanName); + log.debug("Successfully unregistered bean for instance: " + this.getCheckName()); + } catch (MBeanRegistrationException | InstanceNotFoundException e) { + log.debug("Unable to unregister bean for instance: " + this.getCheckName()); + } + } + /** Clean up config and close connection. */ public void cleanUp() { + cleanupTelemetryBean(); this.appConfig = null; if (connection != null) { connection.closeConnector(); @@ -797,7 +854,7 @@ public void cleanUp() { * */ public synchronized void cleanUpAsync() { appConfig = null; - + cleanupTelemetryBean(); class AsyncCleaner implements Runnable { Connection conn; diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java new file mode 100644 index 000000000..f8e3b5224 --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java @@ -0,0 +1,44 @@ +package org.datadog.jmxfetch.util; + + +/** Jmxfetch telemetry JMX MBean. */ +public class InstanceTelemetry implements InstanceTelemetryMBean { + + private int beanCount; + private int attributeCount; + private int metricCount; + + /** Jmxfetch telemetry bean constructor. */ + public InstanceTelemetry() { + beanCount = 0; + attributeCount = 0; + metricCount = 0; + } + + public int getBeanCount() { + return beanCount; + } + + public int getAttributeCount() { + return attributeCount; + } + + public int getMetricCount() { + return metricCount; + } + + + public void setBeanCount(int count) { + beanCount = count; + } + + public void setAttributeCount(int count) { + attributeCount = count; + } + + public void setMetricCount(int count) { + metricCount = count; + } + + +} diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java new file mode 100644 index 000000000..9402679ec --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java @@ -0,0 +1,11 @@ +package org.datadog.jmxfetch.util; + +public interface InstanceTelemetryMBean { + + int getBeanCount(); + + int getAttributeCount(); + + int getMetricCount(); + +} From b33e1d30bb863623ce14333b045a7b182244d0e6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 10 Aug 2023 14:02:40 +0100 Subject: [PATCH 10/48] Reverted custom jmxfetch bean to track number of ingested beans, attributes, and metrics (#468) * Revert "custom jmxfetch bean to track number of ingested beans, attributes, and metrics (#453)" This reverts commit 3c030643b369c32824b220fb0c1312d0bebfe541. * Revert "remove quotes from bean parameter based tags (#456)" This reverts commit 02c071f0d5a7a601144e2a31b9f817aac46523b1. --- .gitignore | 1 + src/main/java/org/datadog/jmxfetch/App.java | 6 +- .../java/org/datadog/jmxfetch/Instance.java | 77 ++----------------- .../org/datadog/jmxfetch/JmxAttribute.java | 26 +------ .../datadog/jmxfetch/JmxComplexAttribute.java | 6 +- .../datadog/jmxfetch/JmxSimpleAttribute.java | 6 +- .../org/datadog/jmxfetch/JmxSubAttribute.java | 6 +- .../datadog/jmxfetch/JmxTabularAttribute.java | 6 +- .../jmxfetch/util/InstanceTelemetry.java | 44 ----------- .../jmxfetch/util/InstanceTelemetryMBean.java | 11 --- .../java/org/datadog/jmxfetch/TestApp.java | 62 --------------- .../jmx_bean_tags_dont_normalize_params.yaml | 18 ----- .../jmx_bean_tags_normalize_params.yaml | 19 ----- 13 files changed, 18 insertions(+), 270 deletions(-) delete mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java delete mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java delete mode 100644 src/test/resources/jmx_bean_tags_dont_normalize_params.yaml delete mode 100644 src/test/resources/jmx_bean_tags_normalize_params.yaml diff --git a/.gitignore b/.gitignore index ac7e58ab9..a1a8ed0fb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ target/* .idea .vscode *.iml + *.ucls *.png diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index d80e9fe0f..4f3e3aee5 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -388,13 +388,11 @@ void start() { System.arraycopy(minibuff, 0, buffer, oldLen, len); } } - boolean result = processAutoDiscovery(buffer); - this.setReinit(result); + this.setReinit(processAutoDiscovery(buffer)); } if (this.appConfig.remoteEnabled()) { - boolean result = getJsonConfigs(); - this.setReinit(result); + this.setReinit(getJsonConfigs()); } } catch (IOException e) { log.warn( diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 1542869d2..e924284b3 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -4,17 +4,13 @@ import org.datadog.jmxfetch.reporter.Reporter; import org.datadog.jmxfetch.service.ConfigServiceNameProvider; import org.datadog.jmxfetch.service.ServiceNameProvider; -import org.datadog.jmxfetch.util.InstanceTelemetry; import org.yaml.snakeyaml.Yaml; - - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -24,15 +20,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; - -import javax.management.InstanceAlreadyExistsException; -import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; -import javax.management.MBeanRegistrationException; -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import javax.security.auth.login.FailedLoginException; @@ -114,10 +103,6 @@ public Yaml initialValue() { private AppConfig appConfig; private Boolean cassandraAliasing; private boolean emptyDefaultHostname; - private InstanceTelemetry instanceTelemetryBean; - private ObjectName instanceTelemetryBeanName; - private MBeanServer mbs; - private Boolean normalizeBeanParamTags; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ public Instance(Instance instance, AppConfig appConfig) { @@ -226,12 +211,6 @@ public Instance( this.serviceCheckPrefix = (String) initConfig.get("service_check_prefix"); } - this.normalizeBeanParamTags = (Boolean) instanceMap.get("normalize_bean_param_tags"); - if (this.normalizeBeanParamTags == null) { - this.normalizeBeanParamTags = false; - } - - // Alternative aliasing for CASSANDRA-4009 metrics // More information: https://issues.apache.org/jira/browse/CASSANDRA-4009 this.cassandraAliasing = (Boolean) instanceMap.get("cassandra_aliasing"); @@ -278,34 +257,8 @@ public Instance( } else { log.info("collect_default_jvm_metrics is false - not collecting default JVM metrics"); } - - instanceTelemetryBean = createJmxBean(); - } - - private ObjectName getObjName(String domain,String instance) - throws MalformedObjectNameException { - return new ObjectName(domain + ":target_instance=" + ObjectName.quote(instance)); - } - - private InstanceTelemetry createJmxBean() { - mbs = ManagementFactory.getPlatformMBeanServer(); - InstanceTelemetry bean = new InstanceTelemetry(); - log.debug("Created jmx bean for instance: " + this.getCheckName()); - - try { - instanceTelemetryBeanName = getObjName("JMXFetch" , this.getName()); - mbs.registerMBean(bean,instanceTelemetryBeanName); - log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName()); - - } catch (MalformedObjectNameException | InstanceAlreadyExistsException - | MBeanRegistrationException | NotCompliantMBeanException e) { - log.warn("Could not register bean for instance: " + this.getCheckName(),e); - } - - return bean; } - public static boolean isDirectInstance(Map configInstance) { Object directInstance = configInstance.get(JVM_DIRECT); return directInstance instanceof Boolean && (Boolean) directInstance; @@ -500,7 +453,7 @@ public List getMetrics() throws IOException { ? this.initialRefreshBeansPeriod : this.refreshBeansPeriod; if (isPeriodDue(this.lastRefreshTime, period)) { - log.info("Refreshing bean list for " + this.getCheckName()); + log.info("Refreshing bean list"); this.refreshBeansList(); this.getMatchingAttributes(); } @@ -532,13 +485,6 @@ public List getMetrics() throws IOException { } } } - instanceTelemetryBean.setBeanCount(beans.size()); - instanceTelemetryBean.setAttributeCount(matchingAttributes.size()); - instanceTelemetryBean.setMetricCount(metrics.size()); - log.debug("Updated jmx bean for instance: " + this.getCheckName() - + " With number of beans = " + instanceTelemetryBean.getBeanCount() - + " attributes = " + instanceTelemetryBean.getAttributeCount() - + " metrics = " + instanceTelemetryBean.getMetricCount()); return metrics; } @@ -632,8 +578,7 @@ private void getMatchingAttributes() throws IOException { serviceNameProvider, tags, cassandraAliasing, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } else if (COMPOSED_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -651,8 +596,7 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } else if (MULTI_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -670,8 +614,7 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } else { try { log.debug( @@ -830,18 +773,8 @@ public boolean isLimitReached() { return this.limitReached; } - private void cleanupTelemetryBean() { - try { - mbs.unregisterMBean(instanceTelemetryBeanName); - log.debug("Successfully unregistered bean for instance: " + this.getCheckName()); - } catch (MBeanRegistrationException | InstanceNotFoundException e) { - log.debug("Unable to unregister bean for instance: " + this.getCheckName()); - } - } - /** Clean up config and close connection. */ public void cleanUp() { - cleanupTelemetryBean(); this.appConfig = null; if (connection != null) { connection.closeConnector(); @@ -854,7 +787,7 @@ public void cleanUp() { * */ public synchronized void cleanUpAsync() { appConfig = null; - cleanupTelemetryBean(); + class AsyncCleaner implements Runnable { Connection conn; diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index 38e926790..68661d82c 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -62,7 +62,6 @@ public abstract class JmxAttribute { private List defaultTagsList; private boolean cassandraAliasing; protected String checkName; - private boolean normalizeBeanParamTags; JmxAttribute( MBeanAttributeInfo attribute, @@ -74,8 +73,7 @@ public abstract class JmxAttribute { ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean emptyDefaultHostname) { this.attribute = attribute; this.beanName = beanName; this.className = className; @@ -86,7 +84,6 @@ public abstract class JmxAttribute { this.cassandraAliasing = cassandraAliasing; this.checkName = checkName; this.serviceNameProvider = serviceNameProvider; - this.normalizeBeanParamTags = normalizeBeanParamTags; // A bean name is formatted like that: // org.apache.cassandra.db:type=Caches,keyspace=system,cache=HintsColumnFamilyKeyCache @@ -194,35 +191,16 @@ private List getBeanParametersList( return beanTags; } - /** - * Wrapper for javax.management.ObjectName.unqoute that removes quotes from the bean parameter - * value if possible. If not, it hits the catch block and returns the original parameter. - */ - private String unquote(String beanParameter) { - int valueIndex = beanParameter.indexOf(':') + 1; - try { - return beanParameter.substring(0,valueIndex) - + ObjectName.unquote(beanParameter.substring(valueIndex)); - } catch (IllegalArgumentException e) { - return beanParameter; - } - } - /** * Sanitize MBean parameter names and values, i.e. - Rename parameter names conflicting with * existing tags - Remove illegal characters */ - private List sanitizeParameters(List beanParametersList) { + private static List sanitizeParameters(List beanParametersList) { List defaultTagsList = new ArrayList(beanParametersList.size()); for (String rawBeanParameter : beanParametersList) { // Remove `|` characters String beanParameter = rawBeanParameter.replace("|", ""); - // Unquote bean parameters that have been quoted and escaped by ObjectName.quote() - if (normalizeBeanParamTags == true) { - beanParameter = unquote(beanParameter); - } - // 'host' parameter is renamed to 'bean_host' if (beanParameter.startsWith("host:")) { defaultTagsList.add("bean_host:" + beanParameter.substring("host:".length())); diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index 3ea2db51b..70ca4cb47 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -30,8 +30,7 @@ public JmxComplexAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean emptyDefaultHostname) { super( attribute, beanName, @@ -42,8 +41,7 @@ public JmxComplexAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } private void populateSubAttributeList(Object attributeValue) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java index b890613ea..c57f16e67 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java @@ -28,8 +28,7 @@ public JmxSimpleAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - Boolean emptyDefaultHostname, - Boolean normalizeBeanParamTags) { + Boolean emptyDefaultHostname) { super( attribute, beanName, @@ -40,8 +39,7 @@ public JmxSimpleAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } @Override diff --git a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java index 98a6e1666..0c8899ed4 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java @@ -21,8 +21,7 @@ public JmxSubAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean emptyDefaultHostname) { super( attribute, beanName, @@ -33,8 +32,7 @@ public JmxSubAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); } public Metric getCachedMetric(String name) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index efc5a1f35..d36369f6c 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -38,8 +38,7 @@ public JmxTabularAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean emptyDefaultHostname) { super( attribute, beanName, @@ -50,8 +49,7 @@ public JmxTabularAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname, - normalizeBeanParamTags); + emptyDefaultHostname); subAttributeList = new HashMap>(); } diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java deleted file mode 100644 index f8e3b5224..000000000 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.datadog.jmxfetch.util; - - -/** Jmxfetch telemetry JMX MBean. */ -public class InstanceTelemetry implements InstanceTelemetryMBean { - - private int beanCount; - private int attributeCount; - private int metricCount; - - /** Jmxfetch telemetry bean constructor. */ - public InstanceTelemetry() { - beanCount = 0; - attributeCount = 0; - metricCount = 0; - } - - public int getBeanCount() { - return beanCount; - } - - public int getAttributeCount() { - return attributeCount; - } - - public int getMetricCount() { - return metricCount; - } - - - public void setBeanCount(int count) { - beanCount = count; - } - - public void setAttributeCount(int count) { - attributeCount = count; - } - - public void setMetricCount(int count) { - metricCount = count; - } - - -} diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java deleted file mode 100644 index 9402679ec..000000000 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.datadog.jmxfetch.util; - -public interface InstanceTelemetryMBean { - - int getBeanCount(); - - int getAttributeCount(); - - int getMetricCount(); - -} diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index e45a5de3d..a2563ef50 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -11,8 +11,6 @@ import java.util.List; import java.util.Map; -import javax.management.ObjectName; - import org.junit.Test; public class TestApp extends TestCommon { @@ -72,66 +70,6 @@ public void testBeanTags() throws Exception { assertMetric("this.is.100", tags, 6); } - /** Tag metrics with MBeans parameters with normalize_bean_param_tags option enabled. */ - @Test - public void testBeanTagsNormalizeParams() throws Exception { - // We expose a few metrics through JMX - registerMBean( - new SimpleTestJavaApp(), - "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" - + ObjectName.quote(".*example.process.regex.*")); - initApplication("jmx_bean_tags_normalize_params.yaml"); - - // Collecting metrics - run(); - List> metrics = getMetrics(); - - // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file - assertEquals(14, metrics.size()); - - List tags = - Arrays.asList( - "type:SimpleTestJavaApp", - "scope:CoolScope", - "instance:jmx_test_instance", - "jmx_domain:org.datadog.jmxfetch.test", - "bean_host:localhost", - "component", - "target_instance:.*example.process.regex.*"); - - assertMetric("this.is.100", tags, 7); - } - - /** Tag metrics with MBeans parameters with normalize_bean_param_tags option disabled. */ - @Test - public void testBeanTagsDontNormalizeParams() throws Exception { - // We expose a few metrics through JMX - registerMBean( - new SimpleTestJavaApp(), - "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" - + ObjectName.quote(".*example.process.regex.*")); - initApplication("jmx_bean_tags_dont_normalize_params.yaml"); - - // Collecting metrics - run(); - List> metrics = getMetrics(); - - // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file - assertEquals(14, metrics.size()); - - List tags = - Arrays.asList( - "type:\"SimpleTestJavaApp\"", - "scope:\"CoolScope\"", - "instance:jmx_test_instance", - "jmx_domain:org.datadog.jmxfetch.test", - "bean_host:\"localhost\"", - "component", - "target_instance:\".\\*example.process.regex.\\*\""); - - assertMetric("this.is.100", tags, 7); - } - /** Generate metric aliases from a `alias_match` regular expression. */ @Test public void testRegexpAliasing() throws Exception { diff --git a/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml b/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml deleted file mode 100644 index b9ddb5267..000000000 --- a/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml +++ /dev/null @@ -1,18 +0,0 @@ -init_config: - -instances: - - process_name_regex: .*surefire.* - name: jmx_test_instance - conf: - - include: - bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" - attribute: - ShouldBe100: - metric_type: gauge - alias: this.is.100 - - include: - bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= - attribute: - Hashmap.thisis0: - metric_type: gauge - alias: bean.parameters.should.not.match diff --git a/src/test/resources/jmx_bean_tags_normalize_params.yaml b/src/test/resources/jmx_bean_tags_normalize_params.yaml deleted file mode 100644 index b2aee6578..000000000 --- a/src/test/resources/jmx_bean_tags_normalize_params.yaml +++ /dev/null @@ -1,19 +0,0 @@ -init_config: - -instances: - - process_name_regex: .*surefire.* - name: jmx_test_instance - conf: - - include: - bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" - attribute: - ShouldBe100: - metric_type: gauge - alias: this.is.100 - - include: - bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= - attribute: - Hashmap.thisis0: - metric_type: gauge - alias: bean.parameters.should.not.match - normalize_bean_param_tags: true From 71844534f8674acf0877df8000cbb9b974e4d75d Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 10 Aug 2023 14:10:06 +0100 Subject: [PATCH 11/48] Preparing release 0.47.10 (#466) * Preparing release 0.47.10 * Update CHANGELOG.md Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> * Updated CHANGELOG with jmxfetch.min_tls_version information * Update CHANGELOG.md Co-authored-by: Scott Opell * Adding correct date to CHANGELOG.md --------- Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> Co-authored-by: Scott Opell --- CHANGELOG.md | 9 +++++++++ README.md | 6 +++--- pom.xml | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14417d4f4..b3169557c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ Changelog ========= +# 0.47.10 / 2023-08-10 + +* [IMPROVEMENT] Improvements in how JMXFetch handles communicating back to the Agent. The TLS of the HTTP client used can now be configured, extra logging has been added around the SSL Context, and 'TLS' as min protocol version used in the `dummyTrustManager` (configurable using the flag `jmxfetch.min_tls_version`, e.g. `-Djmxfetch.min_tls_version=TLS`) [#436][] +* [BUGFIX] Fixed issue race condition where an exception is thrown if the Agent hasn't finished initializing before JMXFetch starts to shut down [#449][] +* [OTHER] Update management agent logic and comments for Java 7 vs 8 vs 9 [#457][] + # 0.47.9 / 2023-05-25 * [BUGFIX] Fixes thread leak in situations with persistent connection failures [#432][] @@ -736,7 +742,10 @@ Changelog [#424]: https://github.com/DataDog/jmxfetch/issues/424 [#431]: https://github.com/DataDog/jmxfetch/issues/431 [#432]: https://github.com/DataDog/jmxfetch/issues/432 +[#436]: https://github.com/DataDog/jmxfetch/issues/436 [#437]: https://github.com/DataDog/jmxfetch/issues/437 +[#457]: https://github.com/DataDog/jmxfetch/issues/457 +[#449]: https://github.com/DataDog/jmxfetch/issues/449 [@alz]: https://github.com/alz [@aoking]: https://github.com/aoking [@arrawatia]: https://github.com/arrawatia diff --git a/README.md b/README.md index 73a736238..fdcba8416 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.com/DataDog/jmxfetch.png?branch=master)](https://travis-ci.com/DataDog/jmxfetch) +[![Build Status](https://circleci.com/gh/DataDog/jmxfetch.svg?style=svg)](https://app.circleci.com/pipelines/github/DataDog/jmxfetch) # [Change log](https://github.com/DataDog/jmxfetch/blob/master/CHANGELOG.md) @@ -41,7 +41,7 @@ docker run -it --rm \ --name my-maven-project \ -v "$(pwd)":/usr/src/app \ -w /usr/src/app \ - eclipse-temurin:8u382-b05-jdk ./mvnw -DskipTests clean package + eclipse-temurin:8-jdk ./mvnw -DskipTests clean package ``` ## Coding standards @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.48.0-SNAPSHOT-jar-with-dependencies.jar --help +java -jar jmxfetch-0.47.10-SNAPSHOT-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index e3c7e12cf..557f32213 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.48.0-SNAPSHOT + 0.47.10 jar jmxfetch From c0e9454a788d1bcad65efcfc9ce16f7d7f80a66c Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 10 Aug 2023 14:33:40 +0100 Subject: [PATCH 12/48] Bumping pom.xml to 0.48.0-SNAPSHOT (#470) --- CHANGELOG.md | 2 ++ README.md | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3169557c..8eada0d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ Changelog ========= +# Next / TBD + # 0.47.10 / 2023-08-10 * [IMPROVEMENT] Improvements in how JMXFetch handles communicating back to the Agent. The TLS of the HTTP client used can now be configured, extra logging has been added around the SSL Context, and 'TLS' as min protocol version used in the `dummyTrustManager` (configurable using the flag `jmxfetch.min_tls_version`, e.g. `-Djmxfetch.min_tls_version=TLS`) [#436][] diff --git a/README.md b/README.md index fdcba8416..89c16dcab 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.47.10-SNAPSHOT-jar-with-dependencies.jar --help +java -jar jmxfetch-0.48.0-SNAPSHOT-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index 557f32213..e3c7e12cf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.47.10 + 0.48.0-SNAPSHOT jar jmxfetch From 020425fe21137981128f0c2a422312e5c05cc92e Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 10 Aug 2023 16:50:45 +0100 Subject: [PATCH 13/48] remove quotes from bean parameter based tags (#456) (#469) * remove quotes from bean parameter based tags (#456) * Added config option to remove bean param quotes * fixed logging and added unit test * updated gitignore * updated gitignore * changed name to normalize_bean_param_tags * updated tests * reverted compiler version * updated some whitespace formatting * updated tests back to pre java 20 settings * combined sanitize and normalize Params * updated formatting * added ObjectName.unquote() wrapper * updated tests * updated formatting * updated formatting * Added comment on unquote wrapper * updated changelog --------- Co-authored-by: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Co-authored-by: Caleb Metz --- .gitignore | 1 - CHANGELOG.md | 3 + .../java/org/datadog/jmxfetch/Instance.java | 16 ++++- .../org/datadog/jmxfetch/JmxAttribute.java | 26 +++++++- .../datadog/jmxfetch/JmxComplexAttribute.java | 6 +- .../datadog/jmxfetch/JmxSimpleAttribute.java | 6 +- .../org/datadog/jmxfetch/JmxSubAttribute.java | 6 +- .../datadog/jmxfetch/JmxTabularAttribute.java | 6 +- .../java/org/datadog/jmxfetch/TestApp.java | 62 +++++++++++++++++++ .../jmx_bean_tags_dont_normalize_params.yaml | 18 ++++++ .../jmx_bean_tags_normalize_params.yaml | 19 ++++++ 11 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 src/test/resources/jmx_bean_tags_dont_normalize_params.yaml create mode 100644 src/test/resources/jmx_bean_tags_normalize_params.yaml diff --git a/.gitignore b/.gitignore index a1a8ed0fb..ac7e58ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ target/* .idea .vscode *.iml - *.ucls *.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eada0d22..8b5d75b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Changelog ========= # Next / TBD +* [FEATURE] Added an option to enable removal of extra quotation marks during tag extraction from Java management beans' parameters/attributes [#469][] + # 0.47.10 / 2023-08-10 * [IMPROVEMENT] Improvements in how JMXFetch handles communicating back to the Agent. The TLS of the HTTP client used can now be configured, extra logging has been added around the SSL Context, and 'TLS' as min protocol version used in the `dummyTrustManager` (configurable using the flag `jmxfetch.min_tls_version`, e.g. `-Djmxfetch.min_tls_version=TLS`) [#436][] @@ -748,6 +750,7 @@ Changelog [#437]: https://github.com/DataDog/jmxfetch/issues/437 [#457]: https://github.com/DataDog/jmxfetch/issues/457 [#449]: https://github.com/DataDog/jmxfetch/issues/449 +[#469]: https://github.com/DataDog/jmxfetch/pull/469 [@alz]: https://github.com/alz [@aoking]: https://github.com/aoking [@arrawatia]: https://github.com/arrawatia diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index e924284b3..9c6778c4e 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -103,6 +103,7 @@ public Yaml initialValue() { private AppConfig appConfig; private Boolean cassandraAliasing; private boolean emptyDefaultHostname; + private Boolean normalizeBeanParamTags; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ public Instance(Instance instance, AppConfig appConfig) { @@ -211,6 +212,12 @@ public Instance( this.serviceCheckPrefix = (String) initConfig.get("service_check_prefix"); } + this.normalizeBeanParamTags = (Boolean) instanceMap.get("normalize_bean_param_tags"); + if (this.normalizeBeanParamTags == null) { + this.normalizeBeanParamTags = false; + } + + // Alternative aliasing for CASSANDRA-4009 metrics // More information: https://issues.apache.org/jira/browse/CASSANDRA-4009 this.cassandraAliasing = (Boolean) instanceMap.get("cassandra_aliasing"); @@ -578,7 +585,8 @@ private void getMatchingAttributes() throws IOException { serviceNameProvider, tags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else if (COMPOSED_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -596,7 +604,8 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else if (MULTI_TYPES.contains(attributeType)) { log.debug( ATTRIBUTE @@ -614,7 +623,8 @@ private void getMatchingAttributes() throws IOException { connection, serviceNameProvider, tags, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } else { try { log.debug( diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index 68661d82c..38e926790 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -62,6 +62,7 @@ public abstract class JmxAttribute { private List defaultTagsList; private boolean cassandraAliasing; protected String checkName; + private boolean normalizeBeanParamTags; JmxAttribute( MBeanAttributeInfo attribute, @@ -73,7 +74,8 @@ public abstract class JmxAttribute { ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { this.attribute = attribute; this.beanName = beanName; this.className = className; @@ -84,6 +86,7 @@ public abstract class JmxAttribute { this.cassandraAliasing = cassandraAliasing; this.checkName = checkName; this.serviceNameProvider = serviceNameProvider; + this.normalizeBeanParamTags = normalizeBeanParamTags; // A bean name is formatted like that: // org.apache.cassandra.db:type=Caches,keyspace=system,cache=HintsColumnFamilyKeyCache @@ -191,16 +194,35 @@ private List getBeanParametersList( return beanTags; } + /** + * Wrapper for javax.management.ObjectName.unqoute that removes quotes from the bean parameter + * value if possible. If not, it hits the catch block and returns the original parameter. + */ + private String unquote(String beanParameter) { + int valueIndex = beanParameter.indexOf(':') + 1; + try { + return beanParameter.substring(0,valueIndex) + + ObjectName.unquote(beanParameter.substring(valueIndex)); + } catch (IllegalArgumentException e) { + return beanParameter; + } + } + /** * Sanitize MBean parameter names and values, i.e. - Rename parameter names conflicting with * existing tags - Remove illegal characters */ - private static List sanitizeParameters(List beanParametersList) { + private List sanitizeParameters(List beanParametersList) { List defaultTagsList = new ArrayList(beanParametersList.size()); for (String rawBeanParameter : beanParametersList) { // Remove `|` characters String beanParameter = rawBeanParameter.replace("|", ""); + // Unquote bean parameters that have been quoted and escaped by ObjectName.quote() + if (normalizeBeanParamTags == true) { + beanParameter = unquote(beanParameter); + } + // 'host' parameter is renamed to 'bean_host' if (beanParameter.startsWith("host:")) { defaultTagsList.add("bean_host:" + beanParameter.substring("host:".length())); diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index 70ca4cb47..3ea2db51b 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -30,7 +30,8 @@ public JmxComplexAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -41,7 +42,8 @@ public JmxComplexAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } private void populateSubAttributeList(Object attributeValue) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java index c57f16e67..b890613ea 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java @@ -28,7 +28,8 @@ public JmxSimpleAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - Boolean emptyDefaultHostname) { + Boolean emptyDefaultHostname, + Boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -39,7 +40,8 @@ public JmxSimpleAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } @Override diff --git a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java index 0c8899ed4..98a6e1666 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java @@ -21,7 +21,8 @@ public JmxSubAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean cassandraAliasing, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -32,7 +33,8 @@ public JmxSubAttribute( serviceNameProvider, instanceTags, cassandraAliasing, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); } public Metric getCachedMetric(String name) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index d36369f6c..efc5a1f35 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -38,7 +38,8 @@ public JmxTabularAttribute( Connection connection, ServiceNameProvider serviceNameProvider, Map instanceTags, - boolean emptyDefaultHostname) { + boolean emptyDefaultHostname, + boolean normalizeBeanParamTags) { super( attribute, beanName, @@ -49,7 +50,8 @@ public JmxTabularAttribute( serviceNameProvider, instanceTags, false, - emptyDefaultHostname); + emptyDefaultHostname, + normalizeBeanParamTags); subAttributeList = new HashMap>(); } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index a2563ef50..e45a5de3d 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; +import javax.management.ObjectName; + import org.junit.Test; public class TestApp extends TestCommon { @@ -70,6 +72,66 @@ public void testBeanTags() throws Exception { assertMetric("this.is.100", tags, 6); } + /** Tag metrics with MBeans parameters with normalize_bean_param_tags option enabled. */ + @Test + public void testBeanTagsNormalizeParams() throws Exception { + // We expose a few metrics through JMX + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" + + ObjectName.quote(".*example.process.regex.*")); + initApplication("jmx_bean_tags_normalize_params.yaml"); + + // Collecting metrics + run(); + List> metrics = getMetrics(); + + // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file + assertEquals(14, metrics.size()); + + List tags = + Arrays.asList( + "type:SimpleTestJavaApp", + "scope:CoolScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "bean_host:localhost", + "component", + "target_instance:.*example.process.regex.*"); + + assertMetric("this.is.100", tags, 7); + } + + /** Tag metrics with MBeans parameters with normalize_bean_param_tags option disabled. */ + @Test + public void testBeanTagsDontNormalizeParams() throws Exception { + // We expose a few metrics through JMX + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=\"SimpleTestJavaApp\",scope=\"Co|olScope\",host=\"localhost\",component=,target_instance=" + + ObjectName.quote(".*example.process.regex.*")); + initApplication("jmx_bean_tags_dont_normalize_params.yaml"); + + // Collecting metrics + run(); + List> metrics = getMetrics(); + + // 14 = 13 metrics from java.lang + 1 metric explicitly defined in the yaml config file + assertEquals(14, metrics.size()); + + List tags = + Arrays.asList( + "type:\"SimpleTestJavaApp\"", + "scope:\"CoolScope\"", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "bean_host:\"localhost\"", + "component", + "target_instance:\".\\*example.process.regex.\\*\""); + + assertMetric("this.is.100", tags, 7); + } + /** Generate metric aliases from a `alias_match` regular expression. */ @Test public void testRegexpAliasing() throws Exception { diff --git a/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml b/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml new file mode 100644 index 000000000..b9ddb5267 --- /dev/null +++ b/src/test/resources/jmx_bean_tags_dont_normalize_params.yaml @@ -0,0 +1,18 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + - include: + bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= + attribute: + Hashmap.thisis0: + metric_type: gauge + alias: bean.parameters.should.not.match diff --git a/src/test/resources/jmx_bean_tags_normalize_params.yaml b/src/test/resources/jmx_bean_tags_normalize_params.yaml new file mode 100644 index 000000000..b2aee6578 --- /dev/null +++ b/src/test/resources/jmx_bean_tags_normalize_params.yaml @@ -0,0 +1,19 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + - include: + bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= + attribute: + Hashmap.thisis0: + metric_type: gauge + alias: bean.parameters.should.not.match + normalize_bean_param_tags: true From e30e61501e13515a7770579d364b81fdee08371c Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 10 Aug 2023 19:15:47 +0100 Subject: [PATCH 14/48] custom jmxfetch bean to track number of ingested beans, attributes, and metrics (#453) (#472) * added bean that tracks number of ingested beans * restructured bean creation * reverted compiler version * updated some formatting * cleaned up some formatting * simplified quoting process * removed constructor argument from jmxFetchTelem * fixed formatting * updated spacing * cleaned up debug logs * fixed long lines * updated naming * removed java object debug Co-authored-by: Caleb Metz <135133572+cmetz100@users.noreply.github.com> --- src/main/java/org/datadog/jmxfetch/App.java | 6 +- .../java/org/datadog/jmxfetch/Instance.java | 61 ++++++++++++++++++- .../jmxfetch/util/InstanceTelemetry.java | 44 +++++++++++++ .../jmxfetch/util/InstanceTelemetryMBean.java | 11 ++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java create mode 100644 src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 4f3e3aee5..d80e9fe0f 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -388,11 +388,13 @@ void start() { System.arraycopy(minibuff, 0, buffer, oldLen, len); } } - this.setReinit(processAutoDiscovery(buffer)); + boolean result = processAutoDiscovery(buffer); + this.setReinit(result); } if (this.appConfig.remoteEnabled()) { - this.setReinit(getJsonConfigs()); + boolean result = getJsonConfigs(); + this.setReinit(result); } } catch (IOException e) { log.warn( diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 9c6778c4e..1542869d2 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -4,13 +4,17 @@ import org.datadog.jmxfetch.reporter.Reporter; import org.datadog.jmxfetch.service.ConfigServiceNameProvider; import org.datadog.jmxfetch.service.ServiceNameProvider; +import org.datadog.jmxfetch.util.InstanceTelemetry; import org.yaml.snakeyaml.Yaml; + + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -20,8 +24,15 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import javax.security.auth.login.FailedLoginException; @@ -103,6 +114,9 @@ public Yaml initialValue() { private AppConfig appConfig; private Boolean cassandraAliasing; private boolean emptyDefaultHostname; + private InstanceTelemetry instanceTelemetryBean; + private ObjectName instanceTelemetryBeanName; + private MBeanServer mbs; private Boolean normalizeBeanParamTags; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ @@ -264,8 +278,34 @@ public Instance( } else { log.info("collect_default_jvm_metrics is false - not collecting default JVM metrics"); } + + instanceTelemetryBean = createJmxBean(); } + private ObjectName getObjName(String domain,String instance) + throws MalformedObjectNameException { + return new ObjectName(domain + ":target_instance=" + ObjectName.quote(instance)); + } + + private InstanceTelemetry createJmxBean() { + mbs = ManagementFactory.getPlatformMBeanServer(); + InstanceTelemetry bean = new InstanceTelemetry(); + log.debug("Created jmx bean for instance: " + this.getCheckName()); + + try { + instanceTelemetryBeanName = getObjName("JMXFetch" , this.getName()); + mbs.registerMBean(bean,instanceTelemetryBeanName); + log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName()); + + } catch (MalformedObjectNameException | InstanceAlreadyExistsException + | MBeanRegistrationException | NotCompliantMBeanException e) { + log.warn("Could not register bean for instance: " + this.getCheckName(),e); + } + + return bean; + } + + public static boolean isDirectInstance(Map configInstance) { Object directInstance = configInstance.get(JVM_DIRECT); return directInstance instanceof Boolean && (Boolean) directInstance; @@ -460,7 +500,7 @@ public List getMetrics() throws IOException { ? this.initialRefreshBeansPeriod : this.refreshBeansPeriod; if (isPeriodDue(this.lastRefreshTime, period)) { - log.info("Refreshing bean list"); + log.info("Refreshing bean list for " + this.getCheckName()); this.refreshBeansList(); this.getMatchingAttributes(); } @@ -492,6 +532,13 @@ public List getMetrics() throws IOException { } } } + instanceTelemetryBean.setBeanCount(beans.size()); + instanceTelemetryBean.setAttributeCount(matchingAttributes.size()); + instanceTelemetryBean.setMetricCount(metrics.size()); + log.debug("Updated jmx bean for instance: " + this.getCheckName() + + " With number of beans = " + instanceTelemetryBean.getBeanCount() + + " attributes = " + instanceTelemetryBean.getAttributeCount() + + " metrics = " + instanceTelemetryBean.getMetricCount()); return metrics; } @@ -783,8 +830,18 @@ public boolean isLimitReached() { return this.limitReached; } + private void cleanupTelemetryBean() { + try { + mbs.unregisterMBean(instanceTelemetryBeanName); + log.debug("Successfully unregistered bean for instance: " + this.getCheckName()); + } catch (MBeanRegistrationException | InstanceNotFoundException e) { + log.debug("Unable to unregister bean for instance: " + this.getCheckName()); + } + } + /** Clean up config and close connection. */ public void cleanUp() { + cleanupTelemetryBean(); this.appConfig = null; if (connection != null) { connection.closeConnector(); @@ -797,7 +854,7 @@ public void cleanUp() { * */ public synchronized void cleanUpAsync() { appConfig = null; - + cleanupTelemetryBean(); class AsyncCleaner implements Runnable { Connection conn; diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java new file mode 100644 index 000000000..f8e3b5224 --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java @@ -0,0 +1,44 @@ +package org.datadog.jmxfetch.util; + + +/** Jmxfetch telemetry JMX MBean. */ +public class InstanceTelemetry implements InstanceTelemetryMBean { + + private int beanCount; + private int attributeCount; + private int metricCount; + + /** Jmxfetch telemetry bean constructor. */ + public InstanceTelemetry() { + beanCount = 0; + attributeCount = 0; + metricCount = 0; + } + + public int getBeanCount() { + return beanCount; + } + + public int getAttributeCount() { + return attributeCount; + } + + public int getMetricCount() { + return metricCount; + } + + + public void setBeanCount(int count) { + beanCount = count; + } + + public void setAttributeCount(int count) { + attributeCount = count; + } + + public void setMetricCount(int count) { + metricCount = count; + } + + +} diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java new file mode 100644 index 000000000..9402679ec --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java @@ -0,0 +1,11 @@ +package org.datadog.jmxfetch.util; + +public interface InstanceTelemetryMBean { + + int getBeanCount(); + + int getAttributeCount(); + + int getMetricCount(); + +} From 9a7618b45a33dce785203b080b20b611e3f9487d Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:22:01 -0400 Subject: [PATCH 15/48] add jmxfetch telemetry in code (#467) * failing attempt * removed logic for adding jmx_tele into a init conf * temp debug logs * added logic for in-code check configuration * updated some formatting * simplified process * fixed some formating * changed names * removed tags and added changelog --- CHANGELOG.md | 4 +- src/main/java/org/datadog/jmxfetch/App.java | 37 +++++++++++++++++++ .../java/org/datadog/jmxfetch/AppConfig.java | 10 +++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5d75b8d..e0fcb92de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Changelog ========= # Next / TBD +* [FEATURE] Adds a configurable jmxfetch telemetry check to improve jmxfetch observability [#467][] * [FEATURE] Added an option to enable removal of extra quotation marks during tag extraction from Java management beans' parameters/attributes [#469][] # 0.47.10 / 2023-08-10 @@ -748,9 +749,10 @@ Changelog [#432]: https://github.com/DataDog/jmxfetch/issues/432 [#436]: https://github.com/DataDog/jmxfetch/issues/436 [#437]: https://github.com/DataDog/jmxfetch/issues/437 +[#467]: https://github.com/DataDog/jmxfetch/issues/467 [#457]: https://github.com/DataDog/jmxfetch/issues/457 [#449]: https://github.com/DataDog/jmxfetch/issues/449 -[#469]: https://github.com/DataDog/jmxfetch/pull/469 +[#469]: https://github.com/DataDog/jmxfetch/issues/469 [@alz]: https://github.com/alz [@aoking]: https://github.com/aoking [@arrawatia]: https://github.com/arrawatia diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index d80e9fe0f..653d3ebd3 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -899,6 +900,15 @@ public void init(final boolean forceNewConnection) { } } + // Enables jmxfetch telemetry if there are other checks active and it's been enabled + if (appConfig.getJmxfetchTelemetry() && newInstances.size() >= 1) { + log.info("Adding jmxfetch telemetry check"); + final Instance instance = instantiate(getTelemetryInstanceConfig(), + getTelemetryInitConfig(), "jmxfetch_telemetry_check", + this.appConfig); + newInstances.add(instance); + } + final List> instanceInitTasks = new ArrayList<>(newInstances.size()); for (Instance instance : newInstances) { @@ -947,6 +957,33 @@ public TaskStatusHandler invoke( } } + private Map getTelemetryInitConfig() { + Map config = new HashMap(); + config.put("is_jmx",true); + return config; + } + + private Map getTelemetryInstanceConfig() { + Map config = new HashMap(); + config.put("name","jmxfetch_telemetry_instance"); + config.put("collect_default_jvm_metrics",true); + config.put("new_gc_metrics",true); + config.put("process_name_regex",".*org.datadog.jmxfetch.App.*"); + + List conf = new ArrayList(); + Map confMap = new HashMap(); + Map includeMap = new HashMap(); + includeMap.put("domain","jmxfetch"); + confMap.put("include", includeMap); + conf.add(confMap); + config.put("conf",conf); + + List tags = new ArrayList(); + config.put("tags", tags); + + return config; + } + static TaskStatusHandler processRecoveryResults( final Instance instance, final Future future, diff --git a/src/main/java/org/datadog/jmxfetch/AppConfig.java b/src/main/java/org/datadog/jmxfetch/AppConfig.java index c76a265d1..6685f56e1 100644 --- a/src/main/java/org/datadog/jmxfetch/AppConfig.java +++ b/src/main/java/org/datadog/jmxfetch/AppConfig.java @@ -122,6 +122,12 @@ public class AppConfig { required = false) private boolean statsdTelemetry; + @Parameter( + names = {"--jmxfetch_telemetry", "-jt"}, + description = "Enable additional jmxfetch telemetry reporting", + required = false) + private boolean jmxfetchTelemetry; + @Parameter( names = {"--statsd_queue_size", "-sq"}, description = "Maximum number of unprocessed messages in the StatsD client queue.", @@ -411,6 +417,10 @@ public boolean getStatsdTelemetry() { return statsdTelemetry; } + public boolean getJmxfetchTelemetry() { + return jmxfetchTelemetry; + } + public int getStatsdQueueSize() { return statsdQueueSize; } From 6c17a8b3fc356f1c4e9516e79165ece037433776 Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:44:16 -0400 Subject: [PATCH 16/48] misbehaving jmx server enable configfile support and restructure (#464) * added config, env, and cli init for misbehaving * temp updated dockerfile to not use sup * temp commit * temp fix * restructured manager * removed unused yamlparser * cleaned up old vars * cleaned some formatting * removed rmi.server stuff * added MetricDAO back and reformatted * simplified attribute counting * added test bean * removed some comments * fixed comments * updated formatting * temporary removal for debug * reverted * temp added System.set rmi.server.hostname * updated snakeyaml dependancy * temprary test fix * updated tests and file names * removed uneeded import * added imports * added to readme * Update tools/misbehaving-jmx-server/README.md Co-authored-by: Bryce Eadie * Update tools/misbehaving-jmx-server/README.md Co-authored-by: Bryce Eadie * Update tools/misbehaving-jmx-server/README.md Co-authored-by: Bryce Eadie * updated config path --------- Co-authored-by: Bryce Eadie --- .../datadog/jmxfetch/JmxTabularAttribute.java | 1 - .../jmxfetch/JMXServerControlClient.java | 9 +- .../jmxfetch/TestReconnectContainer.java | 2 +- tools/misbehaving-jmx-server/Dockerfile | 2 +- tools/misbehaving-jmx-server/README.md | 14 +- .../misbehaving-jmx-domains-config.yaml | 13 ++ tools/misbehaving-jmx-server/pom.xml | 7 + .../org/datadog/misbehavingjmxserver/App.java | 92 ++++++++- .../misbehavingjmxserver/BeanManager.java | 110 ++++++----- .../DynamicMBeanMetrics.java | 184 ++++++++++++++++++ .../FourAttributeMetric.java | 34 ---- .../FourAttributeMetricMBean.java | 11 -- .../misbehavingjmxserver/MetricDAO.java | 2 +- .../SingleAttributeMetric.java | 14 -- .../SingleAttributeMetricMBean.java | 5 - 15 files changed, 374 insertions(+), 126 deletions(-) create mode 100644 tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml create mode 100644 tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/DynamicMBeanMetrics.java delete mode 100644 tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetric.java delete mode 100644 tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetricMBean.java delete mode 100644 tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetric.java delete mode 100644 tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetricMBean.java diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index efc5a1f35..0b443de6c 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -151,7 +151,6 @@ public List getMetrics() Metric metric = new Metric(alias, metricType, tags, checkName); double value = castToDouble(getValue(dataKey, metricKey), null); metric.setValue(value); - String fullMetricKey = getAttributeName() + "." + metricKey; if (!subMetrics.containsKey(fullMetricKey)) { subMetrics.put(fullMetricKey, new ArrayList()); diff --git a/src/test/java/org/datadog/jmxfetch/JMXServerControlClient.java b/src/test/java/org/datadog/jmxfetch/JMXServerControlClient.java index 5e7fd29a6..0cc50e21e 100644 --- a/src/test/java/org/datadog/jmxfetch/JMXServerControlClient.java +++ b/src/test/java/org/datadog/jmxfetch/JMXServerControlClient.java @@ -19,9 +19,14 @@ public List getMBeans(String domain) throws IOException { } - public void createMBeans(String domain, int numDesiredBeans) throws IOException { + public void createMBeans(String domain, int numDesiredBeans, int scalarAttributeCount, + int tabularAttributeCount, int compositeValuesPerTabularAttribute) throws IOException { + String endpoint = "/beans/" + domain; - String jsonPayload = "{\"numDesiredBeans\": " + numDesiredBeans + "}"; + String jsonPayload = "{\"beanCount\": " + numDesiredBeans + + ", \"scalarAttributeCount\": " + scalarAttributeCount + + ", \"tabularAttributeCount\": " + tabularAttributeCount + + ", \"compositeValuesPerTabularAttribute\": " + compositeValuesPerTabularAttribute + "}"; sendPostRequestWithPayload(endpoint, jsonPayload); } diff --git a/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java b/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java index 10e886cb2..518bb4b8c 100644 --- a/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java +++ b/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java @@ -158,7 +158,7 @@ public void testJMXFetchManyMetrics() throws IOException, InterruptedException { int numAttributesPerBean = 4; String testDomain = "test-domain"; - this.controlClient.createMBeans(testDomain, numBeans); + this.controlClient.createMBeans(testDomain, numBeans, numAttributesPerBean, 0, 0); this.initApplicationWithYamlLines( "init_config:", " is_jmx: true", diff --git a/tools/misbehaving-jmx-server/Dockerfile b/tools/misbehaving-jmx-server/Dockerfile index 3d46e0f77..cf51ba8c3 100644 --- a/tools/misbehaving-jmx-server/Dockerfile +++ b/tools/misbehaving-jmx-server/Dockerfile @@ -24,4 +24,4 @@ WORKDIR /app COPY --from=build /app/target/misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar . # Run the supervisor class from the jar -CMD ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.supervisor.App"] +CMD ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.supervisor.App"] \ No newline at end of file diff --git a/tools/misbehaving-jmx-server/README.md b/tools/misbehaving-jmx-server/README.md index a749b6166..92e073d3f 100644 --- a/tools/misbehaving-jmx-server/README.md +++ b/tools/misbehaving-jmx-server/README.md @@ -30,7 +30,19 @@ a secondary `init` payload that contains the correct RMI Hostname. It is designe - POST `/cutNetwork` - Denies any requests to create a new socket (ie, no more connections will be 'accept'ed) and then closes existing TCP sockets - POST `/restoreNetwork` - Allows new sockets to be created - GET `/beans/:domain` - Retrieves a list of bean names that are currently registered under the given domain. The length of this array should be exactly the number of beans under that domain -- POST `/beans/:domain` - Declares how many 4-attribute beans should exist with this domain. Beans will either be created or destroyed to reach the desired amount. Payload should be JSON with a single key: `numDesiredBeans`. +- POST `/beans/:domain` - Declares how many beans should exist with this domain. Beans are either created or destroyed to reach the desired amount. Payload should be JSON with four keys: `beanCount`,`scalarAttributeCount`,`tabularAttributeCount`,`compositeValuesPerTabularAttribute`. + +## Bean Configuration +- `beanCount` - Declares how many beans should be present in a specfic domain +- `scalarAttributeCount` - Defines the number of simple attributes in all beans for a given domain +- `tabularAttributeCount` - Defines the number of tabular attributes in each bean for a given domain +- `compositeValuesPerTabularAttribute` - Defines the number of rows of data per tabular attribute +Beans in a given domain must all have the same structure, so updating these values with the HTTP Control Server erases all beans and recreates them to the set beanCount with the same number of attributes per bean. + +## Configuration File +Using the command line options `--config-path` or `-cfp` you can provide a path to a YAML configuration file to create beans automatically upon the start of misbehaving-jmx-server. +An example file can be found at `misbehaving-jmx-domains-config.yaml`. + ## HTTP Control Actions (supervisor) - POST `/init` - Provides `rmiHostname` to be used by jmx-server. jmx server will not be listening until this init payload is sent diff --git a/tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml b/tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml new file mode 100644 index 000000000..ef01bf8fe --- /dev/null +++ b/tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml @@ -0,0 +1,13 @@ +domains: + test_1: + beanCount: 4 + scalarAttributeCount: 5 + tabularAttributeCount: 1 + compositeValuesPerTabularAttribute: 2 + test_2: + beanCount: 10 + scalarAttributeCount: 0 + tabularAttributeCount: 5 + compositeValuesPerTabularAttribute: 10 + + diff --git a/tools/misbehaving-jmx-server/pom.xml b/tools/misbehaving-jmx-server/pom.xml index 2f87511fc..9a8ef20b6 100644 --- a/tools/misbehaving-jmx-server/pom.xml +++ b/tools/misbehaving-jmx-server/pom.xml @@ -17,6 +17,7 @@ 11 11 11 + 1.29 @@ -57,6 +58,12 @@ 4.11 test + + + org.yaml + snakeyaml + ${snakeyaml.version} + diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java index 4270034b5..5e312a704 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java @@ -1,6 +1,9 @@ package org.datadog.misbehavingjmxserver; import java.io.IOException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; @@ -28,9 +31,12 @@ import com.beust.jcommander.Parameter; import io.javalin.*; import lombok.extern.slf4j.Slf4j; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; import org.datadog.Defaults; +@Slf4j class AppConfig { // RMI Port is used for both registry and the JMX service @Parameter(names = {"--rmi-port", "-rp"}) @@ -42,6 +48,11 @@ class AppConfig { // Can only be set via env var public int controlPort = Defaults.JMXSERVER_CONTROL_PORT; + @Parameter(names = {"--config-path", "-cfp"}) + public String config_path = "./misbehaving-jmx-domains-config.yaml"; + + public JmxDomainConfigurations jmxDomainConfigurations; + public void overrideFromEnv() { String val; val = System.getenv("RMI_PORT"); @@ -56,11 +67,68 @@ public void overrideFromEnv() { if (val != null) { this.controlPort = Integer.parseInt(val); } + val = System.getenv("CONFIG_PATH"); + if (val != null) { + this.config_path = val; + } + } + + public void readConfigFileOnDisk () { + File f = new File(config_path); + String yamlPath = f.getPath(); + try{ + FileInputStream yamlInputStream = new FileInputStream(yamlPath); + Yaml yaml = new Yaml(new Constructor(JmxDomainConfigurations.class)); + jmxDomainConfigurations = yaml.load(yamlInputStream); + log.info("JmxDomainConfigurations read from " + config_path + " is:\n" + jmxDomainConfigurations); + } catch (FileNotFoundException e) { + log.warn("Could not find your config file at " + yamlPath); + jmxDomainConfigurations = null; + } } } +class JmxDomainConfigurations { + public Map domains; + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + if (domains != null) { + for (Map.Entry entry: domains.entrySet()) { + result.append("Domain: " + entry.getKey() + entry.getValue().toString() + "\n"); + } + } else { + return "No valid domain configurations"; + } + + return result.toString(); + } +} class BeanSpec { - public int numDesiredBeans; + public int beanCount; + public int scalarAttributeCount; + public int tabularAttributeCount; + public int compositeValuesPerTabularAttribute; + + public BeanSpec() { + + } + + public BeanSpec(int beanCount, int scalarAttributeCount, int tabularAttributeCount, int compositeValuesPerTabularAttribute) { + this.beanCount = beanCount; + this.scalarAttributeCount = scalarAttributeCount; + this.tabularAttributeCount = tabularAttributeCount; + this.compositeValuesPerTabularAttribute = compositeValuesPerTabularAttribute; + } + + @Override + public String toString() { + return "\n\t-beanCount: " + beanCount + + "\n\t-scalarAttributeCount: " + scalarAttributeCount + + "\n\t-tabularAttributeCount: " + tabularAttributeCount + + "\n\t-compositeValuesPerTabularAttribute: " + compositeValuesPerTabularAttribute; + } } @Slf4j @@ -71,6 +139,7 @@ public class App public static void main( String[] args ) throws IOException, MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { AppConfig config = new AppConfig(); + JCommander jCommander = JCommander.newBuilder() .addObject(config) .build(); @@ -82,6 +151,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName jCommander.usage(); System.exit(1); } + config.readConfigFileOnDisk(); InterruptibleRMISocketFactory customRMISocketFactory = new InterruptibleRMISocketFactory(); // I don't think this call is actually important for jmx, the below 'env' param to JMXConnectorServerFactory is the important one @@ -89,7 +159,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName // Explicitly set RMI hostname to specified argument value System.setProperty("java.rmi.server.hostname", config.rmiHost); - + // Initialize RMI registry at same port as the jmx service LocateRegistry.createRegistry(config.rmiPort, null, customRMISocketFactory); @@ -100,10 +170,16 @@ public static void main( String[] args ) throws IOException, MalformedObjectName BeanManager bm = new BeanManager(mbs, mDao); - // Register single static bean under known domain - ObjectName mbeanName = new ObjectName(testDomain + ":name=MyMBean"); - SingleAttributeMetricMBean mbean = new SingleAttributeMetric(mDao); - mbs.registerMBean(mbean, mbeanName); + // Set up test domain + BeanSpec testDomainBeanSpec = new BeanSpec(1, 1, 0, 0); + bm.setMBeanState(testDomain, testDomainBeanSpec); + + // Set up initial beans for all the domains found in config file + if (config.jmxDomainConfigurations != null){ + for (Map.Entry entry: config.jmxDomainConfigurations.domains.entrySet()) { + bm.setMBeanState(entry.getKey(), entry.getValue()); + } + } Javalin controlServer = Javalin.create(); @@ -127,7 +203,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName controlServer.get("/beans/{domain}", ctx -> { String domain = ctx.pathParam("domain"); - Optional> bs = bm.getMBeanState(domain); + Optional> bs = bm.getMBeanState(domain); if (bs.isPresent()) { List metricNames = bs.get().stream().map(metric -> metric.name).collect(Collectors.toList()); @@ -149,7 +225,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName } // This should block until the mbeanserver reaches the desired state - bm.setMBeanState(domain, beanSpec.numDesiredBeans); + bm.setMBeanState(domain, beanSpec); ctx.status(200).result("Received bean request for domain: " + domain); }); diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java index aad2e7bb6..90e96173c 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java @@ -13,76 +13,92 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.Executors; + import lombok.extern.slf4j.Slf4j; @Slf4j public class BeanManager { private final MBeanServer mBeanServer; - private final Map> registeredBeans; + private final Map> registeredBeans; private final MetricDAO mDao; + static final long ATTRIBUTE_REFRESH_INTERVAL = 10; + private final ScheduledExecutorService executor; public BeanManager(MBeanServer mBeanServer, MetricDAO mDao) { this.mBeanServer = mBeanServer; this.registeredBeans = new HashMap<>(); this.mDao = mDao; + this.executor = Executors.newSingleThreadScheduledExecutor(); + runAttributeUpdateLoop(); } - private ObjectName getObjName(String domain, FourAttributeMetric metric) throws MalformedObjectNameException { + private ObjectName getObjName(String domain, DynamicMBeanMetrics metric) throws MalformedObjectNameException { return new ObjectName(domain + ":name=" + metric.name); } - public void setMBeanState(String beanDomain, int numDesiredBeans) { - RandomIdentifier idGen = new RandomIdentifier(); - ArrayList newlyRegisteredBeans = new ArrayList<>(); - int numExistingBeans = 0; - List existingBeans = this.registeredBeans.get(beanDomain); - if (registeredBeans.containsKey(beanDomain)) { - numExistingBeans = existingBeans.size(); - } - if (numExistingBeans == numDesiredBeans) { - // Already have all the beans we want, nothing to do - return; - } else if (numExistingBeans > numDesiredBeans) { - // Too many beans, unregister some - int beansToRemove = numExistingBeans - numDesiredBeans; - - // Pop beans off until we get to desired amount - for (int i = 0; i < beansToRemove; i++) { - FourAttributeMetric m = existingBeans.get(0); - try { - this.mBeanServer.unregisterMBean(getObjName(beanDomain, m)); - existingBeans.remove(0); - } catch (MBeanRegistrationException | InstanceNotFoundException | MalformedObjectNameException e) { - log.warn("Could not unregister bean"); - e.printStackTrace(); - } - } - registeredBeans.put(beanDomain, existingBeans); - } else if (numExistingBeans < numDesiredBeans) { - int newBeansToBeAdded = numDesiredBeans - numExistingBeans; - for (int i = 0; i < newBeansToBeAdded; i++) { - FourAttributeMetric metric = new FourAttributeMetric("Bean-" + idGen.generateIdentifier(), mDao); - try { - ObjectName obj = getObjName(beanDomain, metric); - log.debug("Registering bean with ObjectName: {}", obj); - this.mBeanServer.registerMBean(metric, obj); - newlyRegisteredBeans.add(metric); - } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e) { - log.error("Could not add bean {} for domain {}", metric.name, beanDomain); - e.printStackTrace(); - } + + public void clearDomainBeans(String beanDomain){ + List beansList = this.registeredBeans.getOrDefault(beanDomain,new ArrayList()); + int size = beansList.size(); + + for (int i = 0; i < size; i++){ + DynamicMBeanMetrics metric = beansList.get(0); + try { + ObjectName obj = getObjName(beanDomain, metric); + this.mBeanServer.unregisterMBean(obj); + beansList.remove(0); + } catch (MBeanRegistrationException | InstanceNotFoundException | MalformedObjectNameException e) { + log.warn("Could not unregister bean {} for domain {}", metric.name, beanDomain, e); } - ArrayList totalBeans = new ArrayList<>(newlyRegisteredBeans); - if (existingBeans != null) { - totalBeans.addAll(existingBeans); + } + registeredBeans.put(beanDomain, new ArrayList()); + } + + public void setMBeanState(String beanDomain, BeanSpec domainSpec) { + clearDomainBeans(beanDomain); + RandomIdentifier idGen = new RandomIdentifier(); + ArrayList beansList = new ArrayList(); + + for (int i = 0; i < domainSpec.beanCount; i++) { + DynamicMBeanMetrics metric = new DynamicMBeanMetrics("Bean-" + idGen.generateIdentifier(), domainSpec.scalarAttributeCount, + domainSpec.tabularAttributeCount, domainSpec.compositeValuesPerTabularAttribute, mDao); + try { + ObjectName obj = getObjName(beanDomain, metric); + log.debug("Registering bean with ObjectName: {}", obj); + this.mBeanServer.registerMBean(metric, obj); + beansList.add(metric); + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e) { + log.warn("Could not add bean {} for domain {}", metric.name, beanDomain, e); } - registeredBeans.put(beanDomain, totalBeans); } + registeredBeans.put(beanDomain, beansList); + } - public Optional> getMBeanState(String domain) { + public Optional> getMBeanState(String domain) { return Optional.ofNullable(registeredBeans.get(domain)); } + + public void Do() { + for (Map.Entry> beanDomainEntry : registeredBeans.entrySet()) { + List beansList = beanDomainEntry.getValue(); + for (DynamicMBeanMetrics bean: beansList){ + bean.updateBeanAttributes(); + } + } + } + + void runAttributeUpdateLoop() { + Runnable task = () -> { + this.Do(); + }; + executor.scheduleAtFixedRate(task, 0, ATTRIBUTE_REFRESH_INTERVAL, TimeUnit.SECONDS); + } + + } diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/DynamicMBeanMetrics.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/DynamicMBeanMetrics.java new file mode 100644 index 000000000..a4f0bb69c --- /dev/null +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/DynamicMBeanMetrics.java @@ -0,0 +1,184 @@ +package org.datadog.misbehavingjmxserver; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanException; +import javax.management.ReflectionException; +import javax.management.MBeanInfo; +import javax.management.DynamicMBean; +import javax.management.MBeanAttributeInfo; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DynamicMBeanMetrics implements DynamicMBean { + public final String name; + private Map attributes = new HashMap<>(); + private final MetricDAO metricsDAO; + private int numCompPerTabular; + + public DynamicMBeanMetrics(final String name, int numAttributes, int numTabulars, int numCompPerTabular, final MetricDAO metricsDAO) { + this.name = name; + this.metricsDAO = metricsDAO; + this.numCompPerTabular = numCompPerTabular; + + // Add dummy attributes during object initialization + for (int i = 1; i <= numAttributes; i++){ + attributes.put("Attribute" + String.valueOf(i), getSimpleAttributeValue()); + } + + for (int i = 1; i <= numTabulars; i++) { + try { + + attributes.put("Tabular" + String.valueOf(i),getTabularData()); + } catch (Exception e) { + log.warn("Tabular" + String.valueOf(i) + " threw an exception: ", e); + } + } + + } + + public Number getSimpleAttributeValue() { + return this.metricsDAO.getNumberValue(); + } + + @Override + public Object getAttribute(String attribute) throws AttributeNotFoundException { + if (!attributes.containsKey(attribute)) { + throw new AttributeNotFoundException("Attribute '" + attribute + "' not found."); + } + return attributes.get(attribute); + } + + @Override + public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException { + String attributeName = attribute.getName(); + if (!attributes.containsKey(attributeName)) { + throw new AttributeNotFoundException("Attribute '" + attributeName + "' not found."); + } + attributes.put(attributeName, attribute.getValue()); + } + + @Override + public AttributeList getAttributes(String[] attributes) { + AttributeList result = new AttributeList(); + for (String attr : attributes) { + try { + Object value = getAttribute(attr); + result.add(new Attribute(attr, value)); + } catch (Exception e) { + // Ignore attributes that couldn't be retrieved + } + } + return result; + } + + @Override + public AttributeList setAttributes(AttributeList attributes) { + AttributeList result = new AttributeList(); + for (Object obj : attributes) { + if (obj instanceof Attribute) { + Attribute attr = (Attribute) obj; + try { + setAttribute(attr); + result.add(attr); + } catch (Exception e) { + // Ignore attributes that couldn't be set + } + } + } + return result; + } + + @Override + public Object invoke(String actionName, Object[] params, String[] signature) + throws MBeanException, ReflectionException { + throw new UnsupportedOperationException("Not supported in misbehaving-jmx-server."); + } + + @Override + public MBeanInfo getMBeanInfo() { + MBeanAttributeInfo[] attributeInfos = new MBeanAttributeInfo[attributes.size()]; + int i = 0; + for (Map.Entry entry : attributes.entrySet()) { + String name = entry.getKey(); + String type = entry.getValue().getClass().getName(); + String description = "Dummy attribute " + name; + boolean isReadable = true; + boolean isWritable = true; + boolean isIs = false; + attributeInfos[i++] = new MBeanAttributeInfo(name, type, description, isReadable, isWritable, isIs); + } + + return new MBeanInfo( + this.getClass().getName(), + "Dynamic MBean with dummy attributes", + attributeInfos, + null, + null, + null + ); + } + + public TabularData getTabularData() { + String[] itemNames = {"Id", "Value"}; + String[] itemDescriptions = {"Attribute Id", "Attribute Value"}; + OpenType[] itemTypes = {SimpleType.INTEGER, SimpleType.INTEGER}; + + try { + CompositeType rowType = new CompositeType("AttributeRow", "Attribute Row", itemNames, itemDescriptions, itemTypes); + + TabularType tabularType = new TabularType("AttributeTable", "Table of Attributes", rowType, new String[]{"Id"}); + + TabularDataSupport tabularData = new TabularDataSupport(tabularType); + + Map compositeData = new HashMap<>(); + + for (int i = 1; i <= numCompPerTabular; i++){ + compositeData.put(i, getSimpleAttributeValue()); + } + + // Add dummy data to the TabularData + for (Map.Entry entry : compositeData.entrySet()) { + Object attributeId = entry.getKey(); + Object attributeValue = entry.getValue(); + + CompositeData row = new CompositeDataSupport(rowType, new String[]{"Id", "Value"}, new Object[]{attributeId, attributeValue}); + tabularData.put(row); + } + + return tabularData; + } catch ( OpenDataException e) { + log.warn("Unable to create TabularData", e); + return null; + } + } + + public void updateBeanAttributes() { + log.debug("Updating attribute values for " + this.name); + for (Map.Entry entry : attributes.entrySet()) { + if (entry.getValue() instanceof Integer) { + attributes.put(entry.getKey(), getSimpleAttributeValue()); + } + if(entry.getValue() instanceof TabularDataSupport){ + attributes.put(entry.getKey(), getTabularData()); + } + } + } + + +} diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetric.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetric.java deleted file mode 100644 index 4df3e4fb7..000000000 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetric.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.datadog.misbehavingjmxserver; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class FourAttributeMetric implements FourAttributeMetricMBean { - public final String name; - private final MetricDAO metricsDAO; - - public FourAttributeMetric(final String name, final MetricDAO metricsDAO) { - this.name = name; - this.metricsDAO = metricsDAO; - } - - @Override - public Number getNumberValue() { - return this.metricsDAO.getNumberValue(); - } - - @Override - public Double getDoubleValue() { - return this.metricsDAO.getDoubleValue(); - } - - @Override - public Float getFloatValue() { - return this.metricsDAO.getFloatValue(); - } - - @Override - public Boolean getBooleanValue() { - return this.metricsDAO.getBooleanValue(); - } -} diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetricMBean.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetricMBean.java deleted file mode 100644 index 968bc3924..000000000 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/FourAttributeMetricMBean.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.datadog.misbehavingjmxserver; - -public interface FourAttributeMetricMBean { - Number getNumberValue(); - - Double getDoubleValue(); - - Float getFloatValue(); - - Boolean getBooleanValue(); -} diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/MetricDAO.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/MetricDAO.java index e0f4ca005..d0a58ea60 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/MetricDAO.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/MetricDAO.java @@ -75,4 +75,4 @@ void runTickLoop() { }; executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS); } -} +} \ No newline at end of file diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetric.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetric.java deleted file mode 100644 index e45224589..000000000 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetric.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.datadog.misbehavingjmxserver; - -public class SingleAttributeMetric implements SingleAttributeMetricMBean { - private final MetricDAO metricsDAO; - - public SingleAttributeMetric(final MetricDAO metricsDAO) { - this.metricsDAO = metricsDAO; - } - - public synchronized int getCounter() { - return this.metricsDAO.getNumberValue().intValue(); - } -} - diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetricMBean.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetricMBean.java deleted file mode 100644 index 482c7a4b2..000000000 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/SingleAttributeMetricMBean.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.datadog.misbehavingjmxserver; - -public interface SingleAttributeMetricMBean { - int getCounter(); -} From acd41d2c706a635ca61a8d54e857c17727cbe55e Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:35:48 -0400 Subject: [PATCH 17/48] Update jmxfetch telemetry to include tag normalization (#473) * updated domain matching and add tag norm * updated formatting * added spacing * added constant for jmxfetc telemetry domain * updated line lengths --- src/main/java/org/datadog/jmxfetch/App.java | 3 ++- src/main/java/org/datadog/jmxfetch/AppConfig.java | 6 ++++++ src/main/java/org/datadog/jmxfetch/Instance.java | 6 ++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 653d3ebd3..680003c46 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -969,11 +969,12 @@ private Map getTelemetryInstanceConfig() { config.put("collect_default_jvm_metrics",true); config.put("new_gc_metrics",true); config.put("process_name_regex",".*org.datadog.jmxfetch.App.*"); + config.put("normalize_bean_param_tags",true); List conf = new ArrayList(); Map confMap = new HashMap(); Map includeMap = new HashMap(); - includeMap.put("domain","jmxfetch"); + includeMap.put("domain",appConfig.getJmxfetchTelemetryDomain()); confMap.put("include", includeMap); conf.add(confMap); config.put("conf",conf); diff --git a/src/main/java/org/datadog/jmxfetch/AppConfig.java b/src/main/java/org/datadog/jmxfetch/AppConfig.java index 6685f56e1..a03842d03 100644 --- a/src/main/java/org/datadog/jmxfetch/AppConfig.java +++ b/src/main/java/org/datadog/jmxfetch/AppConfig.java @@ -58,6 +58,8 @@ public class AppConfig { private static final int DEFAULT_RECONNECTION_TO_S = 60; private static final int DEFAULT_STATSD_QUEUE_SIZE = 4096; + private static final String JMXFETCH_TELEMETRY_DOMAIN = "jmx_fetch"; + private Reporter reporter; @Parameter( @@ -421,6 +423,10 @@ public boolean getJmxfetchTelemetry() { return jmxfetchTelemetry; } + public String getJmxfetchTelemetryDomain() { + return JMXFETCH_TELEMETRY_DOMAIN; + } + public int getStatsdQueueSize() { return statsdQueueSize; } diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 1542869d2..2b95bb31e 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -293,9 +293,11 @@ private InstanceTelemetry createJmxBean() { log.debug("Created jmx bean for instance: " + this.getCheckName()); try { - instanceTelemetryBeanName = getObjName("JMXFetch" , this.getName()); + instanceTelemetryBeanName = getObjName(appConfig.getJmxfetchTelemetryDomain(), + this.getName()); mbs.registerMBean(bean,instanceTelemetryBeanName); - log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName()); + log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName() + + " with ObjectName = " + instanceTelemetryBeanName); } catch (MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { From ab0ee2e646698b087977731ddfacb0969f5f4776 Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:15:45 -0400 Subject: [PATCH 18/48] updated instance bean naming (#474) --- .../java/org/datadog/jmxfetch/Instance.java | 8 +++---- .../jmxfetch/util/InstanceTelemetry.java | 24 +++++++++---------- .../jmxfetch/util/InstanceTelemetryMBean.java | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 2b95bb31e..71a315024 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -534,12 +534,12 @@ public List getMetrics() throws IOException { } } } - instanceTelemetryBean.setBeanCount(beans.size()); - instanceTelemetryBean.setAttributeCount(matchingAttributes.size()); + instanceTelemetryBean.setBeansFetched(beans.size()); + instanceTelemetryBean.setTopLevelAttributeCount(matchingAttributes.size()); instanceTelemetryBean.setMetricCount(metrics.size()); log.debug("Updated jmx bean for instance: " + this.getCheckName() - + " With number of beans = " + instanceTelemetryBean.getBeanCount() - + " attributes = " + instanceTelemetryBean.getAttributeCount() + + " With beans fetched = " + instanceTelemetryBean.getBeansFetched() + + " top attributes = " + instanceTelemetryBean.getTopLevelAttributeCount() + " metrics = " + instanceTelemetryBean.getMetricCount()); return metrics; } diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java index f8e3b5224..b216b54f5 100644 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java @@ -4,23 +4,23 @@ /** Jmxfetch telemetry JMX MBean. */ public class InstanceTelemetry implements InstanceTelemetryMBean { - private int beanCount; - private int attributeCount; + private int beansFetched; + private int topLevelAttributeCount; private int metricCount; /** Jmxfetch telemetry bean constructor. */ public InstanceTelemetry() { - beanCount = 0; - attributeCount = 0; + beansFetched = 0; + topLevelAttributeCount = 0; metricCount = 0; } - public int getBeanCount() { - return beanCount; + public int getBeansFetched() { + return beansFetched; } - public int getAttributeCount() { - return attributeCount; + public int getTopLevelAttributeCount() { + return topLevelAttributeCount; } public int getMetricCount() { @@ -28,12 +28,12 @@ public int getMetricCount() { } - public void setBeanCount(int count) { - beanCount = count; + public void setBeansFetched(int count) { + beansFetched = count; } - public void setAttributeCount(int count) { - attributeCount = count; + public void setTopLevelAttributeCount(int count) { + topLevelAttributeCount = count; } public void setMetricCount(int count) { diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java index 9402679ec..b80d126c0 100644 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java @@ -2,9 +2,9 @@ public interface InstanceTelemetryMBean { - int getBeanCount(); + int getBeansFetched(); - int getAttributeCount(); + int getTopLevelAttributeCount(); int getMetricCount(); From 6bc77a8337f2d8d22ef51774f3c2736de2adea98 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Thu, 31 Aug 2023 09:36:40 -0400 Subject: [PATCH 19/48] draft display internal metrics on status --- src/main/java/org/datadog/jmxfetch/App.java | 3 ++- .../java/org/datadog/jmxfetch/Instance.java | 8 ++++++ .../java/org/datadog/jmxfetch/Status.java | 26 ++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 680003c46..5bb6f9499 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -13,6 +13,7 @@ import org.datadog.jmxfetch.util.ByteArraySearcher; import org.datadog.jmxfetch.util.CustomLogger; import org.datadog.jmxfetch.util.FileHelper; +import org.datadog.jmxfetch.util.InstanceTelemetry; import org.datadog.jmxfetch.util.LogLevel; import org.datadog.jmxfetch.util.MetadataHelper; import org.datadog.jmxfetch.util.ServiceCheckHelper; @@ -755,7 +756,7 @@ private void reportStatus( stats.addInstanceStats( checkName, instance.getName(), metricCount, reporter.getServiceCheckCount(checkName), - message, status); + message, status, instance.getInstanceTelemetryBean(), instance.getInstanceTelemetryBeanName()); if (reporter.getHandler() != null) { stats.addErrorStats(reporter.getHandler().getErrors()); } diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 71a315024..a4aed236b 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -827,6 +827,14 @@ public int getMaxNumberOfMetrics() { return this.maxReturnedMetrics; } + public InstanceTelemetry getInstanceTelemetryBean() { + return this.instanceTelemetryBean; + } + + public ObjectName getInstanceTelemetryBeanName() { + return this.instanceTelemetryBeanName; + } + /** Returns whether or not the instance has reached the maximum bean collection limit. */ public boolean isLimitReached() { return this.limitReached; diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index a2d73952e..1c35c979c 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.jr.ob.JSON; import lombok.extern.slf4j.Slf4j; + +import org.datadog.jmxfetch.util.InstanceTelemetry; import org.datadog.jmxfetch.util.MetadataHelper; import org.yaml.snakeyaml.Yaml; @@ -15,6 +17,8 @@ import java.util.List; import java.util.Map; +import javax.management.ObjectName; + @Slf4j public class Status { @@ -73,7 +77,9 @@ public void addInstanceStats( int metricCount, int serviceCheckCount, String message, - String status) { + String status, + InstanceTelemetry instanceTelemetryBean, + ObjectName instanceTelemetryBeanName) { addStats( checkName, instance, @@ -81,7 +87,9 @@ public void addInstanceStats( serviceCheckCount, message, status, - INITIALIZED_CHECKS); + INITIALIZED_CHECKS, + instanceTelemetryBean, + instanceTelemetryBeanName); } public void addErrorStats(int errors) { @@ -96,7 +104,9 @@ private void addStats( int serviceCheckCount, String message, String status, - String key) { + String key, + InstanceTelemetry instanceTelemetryBean, + ObjectName instanceTelemetryBeanName) { List> checkStats; Map initializedChecks; initializedChecks = (Map) this.instanceStats.get(key); @@ -117,6 +127,14 @@ private void addStats( if (serviceCheckCount != -1) { instStats.put("service_check_count", serviceCheckCount); } + if (instanceTelemetryBean != null){ + Map instanceTelemetryBeanStats = new HashMap(); + instanceTelemetryBeanStats.put("name", instanceTelemetryBeanName.toString()); + instanceTelemetryBeanStats.put("beans_fetched_count", instanceTelemetryBean.getBeansFetched()); + instanceTelemetryBeanStats.put("bean_attribute_count", instanceTelemetryBean.getTopLevelAttributeCount()); + instanceTelemetryBeanStats.put("bean_metric_count", instanceTelemetryBean.getMetricCount()); + instStats.put("instanceTelemetryBean", instanceTelemetryBeanStats); + } instStats.put("message", message); instStats.put("status", status); checkStats.add(instStats); @@ -125,7 +143,7 @@ private void addStats( } public void addInitFailedCheck(String checkName, String message, String status) { - addStats(checkName, null, -1, -1, message, status, FAILED_CHECKS); + addStats(checkName, null, -1, -1, message, status, FAILED_CHECKS, null, null); } private String generateYaml() { From f5cd62a7e417fb414dc9018512570aac0e772702 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Thu, 31 Aug 2023 13:41:44 -0400 Subject: [PATCH 20/48] edit stats display --- src/main/java/org/datadog/jmxfetch/App.java | 2 +- .../java/org/datadog/jmxfetch/Instance.java | 4 ---- .../java/org/datadog/jmxfetch/Status.java | 20 +++++++------------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 5bb6f9499..cec8319a2 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -756,7 +756,7 @@ private void reportStatus( stats.addInstanceStats( checkName, instance.getName(), metricCount, reporter.getServiceCheckCount(checkName), - message, status, instance.getInstanceTelemetryBean(), instance.getInstanceTelemetryBeanName()); + message, status, instance.getInstanceTelemetryBean()); if (reporter.getHandler() != null) { stats.addErrorStats(reporter.getHandler().getErrors()); } diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index a4aed236b..a2c5cbce4 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -831,10 +831,6 @@ public InstanceTelemetry getInstanceTelemetryBean() { return this.instanceTelemetryBean; } - public ObjectName getInstanceTelemetryBeanName() { - return this.instanceTelemetryBeanName; - } - /** Returns whether or not the instance has reached the maximum bean collection limit. */ public boolean isLimitReached() { return this.limitReached; diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index 1c35c979c..613ac7d78 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -78,8 +78,7 @@ public void addInstanceStats( int serviceCheckCount, String message, String status, - InstanceTelemetry instanceTelemetryBean, - ObjectName instanceTelemetryBeanName) { + InstanceTelemetry instanceTelemetryBean) { addStats( checkName, instance, @@ -88,8 +87,7 @@ public void addInstanceStats( message, status, INITIALIZED_CHECKS, - instanceTelemetryBean, - instanceTelemetryBeanName); + instanceTelemetryBean); } public void addErrorStats(int errors) { @@ -105,8 +103,7 @@ private void addStats( String message, String status, String key, - InstanceTelemetry instanceTelemetryBean, - ObjectName instanceTelemetryBeanName) { + InstanceTelemetry instanceTelemetryBean) { List> checkStats; Map initializedChecks; initializedChecks = (Map) this.instanceStats.get(key); @@ -128,12 +125,9 @@ private void addStats( instStats.put("service_check_count", serviceCheckCount); } if (instanceTelemetryBean != null){ - Map instanceTelemetryBeanStats = new HashMap(); - instanceTelemetryBeanStats.put("name", instanceTelemetryBeanName.toString()); - instanceTelemetryBeanStats.put("beans_fetched_count", instanceTelemetryBean.getBeansFetched()); - instanceTelemetryBeanStats.put("bean_attribute_count", instanceTelemetryBean.getTopLevelAttributeCount()); - instanceTelemetryBeanStats.put("bean_metric_count", instanceTelemetryBean.getMetricCount()); - instStats.put("instanceTelemetryBean", instanceTelemetryBeanStats); + instStats.put("bean_count", instanceTelemetryBean.getBeansFetched()); + instStats.put("attribute_count", instanceTelemetryBean.getTopLevelAttributeCount()); + instStats.put("bean_metric_count", instanceTelemetryBean.getMetricCount()); } instStats.put("message", message); instStats.put("status", status); @@ -143,7 +137,7 @@ private void addStats( } public void addInitFailedCheck(String checkName, String message, String status) { - addStats(checkName, null, -1, -1, message, status, FAILED_CHECKS, null, null); + addStats(checkName, null, -1, -1, message, status, FAILED_CHECKS, null); } private String generateYaml() { From 3f875cee30bcd34ecebcb63bf1b7ceb76c57d996 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Fri, 1 Sep 2023 10:48:57 -0400 Subject: [PATCH 21/48] java lint --- src/main/java/org/datadog/jmxfetch/App.java | 1 - src/main/java/org/datadog/jmxfetch/Status.java | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index cec8319a2..00ab71d4b 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -13,7 +13,6 @@ import org.datadog.jmxfetch.util.ByteArraySearcher; import org.datadog.jmxfetch.util.CustomLogger; import org.datadog.jmxfetch.util.FileHelper; -import org.datadog.jmxfetch.util.InstanceTelemetry; import org.datadog.jmxfetch.util.LogLevel; import org.datadog.jmxfetch.util.MetadataHelper; import org.datadog.jmxfetch.util.ServiceCheckHelper; diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index 613ac7d78..1358b6004 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -17,8 +17,6 @@ import java.util.List; import java.util.Map; -import javax.management.ObjectName; - @Slf4j public class Status { @@ -124,10 +122,11 @@ private void addStats( if (serviceCheckCount != -1) { instStats.put("service_check_count", serviceCheckCount); } - if (instanceTelemetryBean != null){ - instStats.put("bean_count", instanceTelemetryBean.getBeansFetched()); - instStats.put("attribute_count", instanceTelemetryBean.getTopLevelAttributeCount()); - instStats.put("bean_metric_count", instanceTelemetryBean.getMetricCount()); + if (instanceTelemetryBean != null) { + instStats.put("instance_bean_count", instanceTelemetryBean.getBeansFetched()); + instStats.put("instance_attribute_count", + instanceTelemetryBean.getTopLevelAttributeCount()); + instStats.put("instance_metric_count", instanceTelemetryBean.getMetricCount()); } instStats.put("message", message); instStats.put("status", status); From 21427bfbf0cbef3aef924cfac5133e695ec5f5a0 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Fri, 1 Sep 2023 11:21:17 -0400 Subject: [PATCH 22/48] bump pom.xml for 0.48.1 snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3c7e12cf..01b5f929a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.48.0-SNAPSHOT + 0.48.1-SNAPSHOT jar jmxfetch From 3d0d3f7b06e7e3a129b35397dfef0bb3264b1832 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Fri, 1 Sep 2023 12:49:40 -0400 Subject: [PATCH 23/48] Revert "bump pom.xml for 0.48.1 snapshot" This reverts commit 21427bfbf0cbef3aef924cfac5133e695ec5f5a0. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 01b5f929a..e3c7e12cf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.48.1-SNAPSHOT + 0.48.0-SNAPSHOT jar jmxfetch From 344d79baa781cc6dcd4dfe3de763c0dc92e85e10 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Tue, 5 Sep 2023 01:57:13 -0400 Subject: [PATCH 24/48] attempt status test --- .../java/org/datadog/jmxfetch/TestCommon.java | 1 + .../java/org/datadog/jmxfetch/TestStatus.java | 26 +++++++++++++++++++ .../jmx_status_check_instance_telemetry.yaml | 21 +++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/test/java/org/datadog/jmxfetch/TestStatus.java create mode 100644 src/test/resources/jmx_status_check_instance_telemetry.yaml diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java index e5cadcfac..83eb6ceaf 100644 --- a/src/test/java/org/datadog/jmxfetch/TestCommon.java +++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java @@ -139,6 +139,7 @@ protected void initApplication(String yamlFileName, String autoDiscoveryPipeFile params.add(5, "/foo"); // could be anything we're stubbing it out params.add(6, "--sd_enabled"); } + params.add("--jmxfetch_telemetry"); new JCommander(appConfig, params.toArray(new String[params.size()])); if (sdEnabled) { diff --git a/src/test/java/org/datadog/jmxfetch/TestStatus.java b/src/test/java/org/datadog/jmxfetch/TestStatus.java new file mode 100644 index 000000000..5092ebd6e --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/TestStatus.java @@ -0,0 +1,26 @@ +package org.datadog.jmxfetch; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.Map; + +import org.datadog.jmxfetch.util.InstanceTelemetryMBean; +import org.junit.Test; + +public class TestStatus extends TestCommon { + + @Test + public void testStatusInstanceTelemetry() throws Exception { + registerMBean(new SimpleTestJavaApp(), "org.datadog.jmxfetch.test:type=SimpleTestJavaApp"); + initApplication("jmx_status_check_instance_telemetry.yaml"); + List instances = getInstances(); + assertEquals(1, instances.size()); + Instance inst = instances.get(0); + List metrics = inst.getMetrics(); + InstanceTelemetryMBean instanceTelemetryBean = inst.getInstanceTelemetryBean(); + assertEquals(17, instanceTelemetryBean.getBeansFetched()); + assertEquals(10, instanceTelemetryBean.getTopLevelAttributeCount()); + assertEquals(16, instanceTelemetryBean.getMetricCount()); + } +} diff --git a/src/test/resources/jmx_status_check_instance_telemetry.yaml b/src/test/resources/jmx_status_check_instance_telemetry.yaml new file mode 100644 index 000000000..f646b669b --- /dev/null +++ b/src/test/resources/jmx_status_check_instance_telemetry.yaml @@ -0,0 +1,21 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + tags: + env: stage + newTag: test + conf: + - include: + domain: org.datadog.jmxfetch.test + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100.$foo.$qux + Hashmap.thisis0: + metric_type: gauge + alias: $attribute + Int424242: + metric_type: histogram + alias: test1.histogram From 8c401aea414b95564422bcd1361f908e5e74a3c7 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Tue, 5 Sep 2023 12:44:38 -0400 Subject: [PATCH 25/48] update status test --- .../java/org/datadog/jmxfetch/Status.java | 3 + .../java/org/datadog/jmxfetch/StatusTest.java | 66 +++++++++++++++++++ .../java/org/datadog/jmxfetch/TestCommon.java | 1 - .../java/org/datadog/jmxfetch/TestStatus.java | 26 -------- .../jmx_status_check_instance_telemetry.yaml | 21 ------ 5 files changed, 69 insertions(+), 48 deletions(-) create mode 100644 src/test/java/org/datadog/jmxfetch/StatusTest.java delete mode 100644 src/test/java/org/datadog/jmxfetch/TestStatus.java delete mode 100644 src/test/resources/jmx_status_check_instance_telemetry.yaml diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index 1358b6004..e6ecfc494 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -130,6 +130,9 @@ private void addStats( } instStats.put("message", message); instStats.put("status", status); + // NOTE: jmxfetch template must be updated for any new keys in order for them + // to show up in the datadog-agent status + // https://github.com/DataDog/datadog-agent/blob/main/pkg/status/templates/jmxfetch.tmpl checkStats.add(instStats); initializedChecks.put(checkName, checkStats); this.instanceStats.put(key, initializedChecks); diff --git a/src/test/java/org/datadog/jmxfetch/StatusTest.java b/src/test/java/org/datadog/jmxfetch/StatusTest.java new file mode 100644 index 000000000..fc239ae28 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/StatusTest.java @@ -0,0 +1,66 @@ +package org.datadog.jmxfetch; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.datadog.jmxfetch.util.InstanceTelemetry; +import org.datadog.jmxfetch.util.InstanceTelemetryMBean; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.yaml.snakeyaml.Yaml; + +public class StatusTest { + + @Rule + public TemporaryFolder folder= new TemporaryFolder(); + + @Test + public void TestStatus() throws IOException { + + File tempFile= folder.newFile("tempFile.txt"); + String tempFilePath = tempFile.getAbsolutePath(); + + final Status status = new Status(tempFilePath); + InstanceTelemetry instance = new InstanceTelemetry(); + + int fakeBeansFetched = 11; + int fakeMetricCount = 29; + int fakeAttributeCount = 55; + + instance.setBeansFetched(fakeBeansFetched); + instance.setMetricCount(fakeMetricCount); + instance.setTopLevelAttributeCount(fakeAttributeCount); + + status.addInstanceStats("fake_check", "fake_instance", 10, 3, "fake_message", Status.STATUS_OK, instance); + status.flush(); + + Yaml yaml = new Yaml(); + InputStream inputStream = new FileInputStream(tempFilePath); + + HashMap yamlMap = yaml.load(inputStream); + HashMap checks = (HashMap) yamlMap.get("checks"); + HashMap initializedChecks = (HashMap) checks.get("initialized_checks"); + List> fakeCheck = (List>) initializedChecks.get("fake_check"); + Map stats = fakeCheck.get(0); + assertEquals("fake_instance", stats.get("instance_name")); + assertEquals(10, stats.get("metric_count")); + assertEquals(3, stats.get("service_check_count")); + assertEquals(fakeBeansFetched, stats.get("instance_bean_count")); + assertEquals(fakeAttributeCount, stats.get("instance_attribute_count")); + assertEquals(fakeMetricCount, stats.get("instance_metric_count")); + assertEquals("fake_message", stats.get("message")); + assertEquals(Status.STATUS_OK, stats.get("status")); + } +} \ No newline at end of file diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java index 83eb6ceaf..e5cadcfac 100644 --- a/src/test/java/org/datadog/jmxfetch/TestCommon.java +++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java @@ -139,7 +139,6 @@ protected void initApplication(String yamlFileName, String autoDiscoveryPipeFile params.add(5, "/foo"); // could be anything we're stubbing it out params.add(6, "--sd_enabled"); } - params.add("--jmxfetch_telemetry"); new JCommander(appConfig, params.toArray(new String[params.size()])); if (sdEnabled) { diff --git a/src/test/java/org/datadog/jmxfetch/TestStatus.java b/src/test/java/org/datadog/jmxfetch/TestStatus.java deleted file mode 100644 index 5092ebd6e..000000000 --- a/src/test/java/org/datadog/jmxfetch/TestStatus.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.datadog.jmxfetch; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.Map; - -import org.datadog.jmxfetch.util.InstanceTelemetryMBean; -import org.junit.Test; - -public class TestStatus extends TestCommon { - - @Test - public void testStatusInstanceTelemetry() throws Exception { - registerMBean(new SimpleTestJavaApp(), "org.datadog.jmxfetch.test:type=SimpleTestJavaApp"); - initApplication("jmx_status_check_instance_telemetry.yaml"); - List instances = getInstances(); - assertEquals(1, instances.size()); - Instance inst = instances.get(0); - List metrics = inst.getMetrics(); - InstanceTelemetryMBean instanceTelemetryBean = inst.getInstanceTelemetryBean(); - assertEquals(17, instanceTelemetryBean.getBeansFetched()); - assertEquals(10, instanceTelemetryBean.getTopLevelAttributeCount()); - assertEquals(16, instanceTelemetryBean.getMetricCount()); - } -} diff --git a/src/test/resources/jmx_status_check_instance_telemetry.yaml b/src/test/resources/jmx_status_check_instance_telemetry.yaml deleted file mode 100644 index f646b669b..000000000 --- a/src/test/resources/jmx_status_check_instance_telemetry.yaml +++ /dev/null @@ -1,21 +0,0 @@ -init_config: - -instances: - - process_name_regex: .*surefire.* - name: jmx_test_instance - tags: - env: stage - newTag: test - conf: - - include: - domain: org.datadog.jmxfetch.test - attribute: - ShouldBe100: - metric_type: gauge - alias: this.is.100.$foo.$qux - Hashmap.thisis0: - metric_type: gauge - alias: $attribute - Int424242: - metric_type: histogram - alias: test1.histogram From 71754b217cb023271d3798dcd39f0f0c55cc1d84 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Tue, 5 Sep 2023 12:59:22 -0400 Subject: [PATCH 26/48] remove unnecessary imports --- src/test/java/org/datadog/jmxfetch/StatusTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/org/datadog/jmxfetch/StatusTest.java b/src/test/java/org/datadog/jmxfetch/StatusTest.java index fc239ae28..471aac120 100644 --- a/src/test/java/org/datadog/jmxfetch/StatusTest.java +++ b/src/test/java/org/datadog/jmxfetch/StatusTest.java @@ -2,20 +2,15 @@ import static org.junit.Assert.*; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; import org.datadog.jmxfetch.util.InstanceTelemetry; -import org.datadog.jmxfetch.util.InstanceTelemetryMBean; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; From 808bbe16eab77ff41290481dd1ddfb053b8b1210 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Wed, 6 Sep 2023 10:50:54 -0400 Subject: [PATCH 27/48] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0fcb92de..d6b37054e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog * [FEATURE] Adds a configurable jmxfetch telemetry check to improve jmxfetch observability [#467][] * [FEATURE] Added an option to enable removal of extra quotation marks during tag extraction from Java management beans' parameters/attributes [#469][] +* [FEATURE] Updated status bean to report JMX Telemetry to Agent status [#477][] # 0.47.10 / 2023-08-10 From 81cf78fe3210c067cff254b15cacc9b7a7638161 Mon Sep 17 00:00:00 2001 From: raymond zhao Date: Wed, 6 Sep 2023 13:10:07 -0400 Subject: [PATCH 28/48] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b37054e..9c7889205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -754,6 +754,7 @@ Changelog [#457]: https://github.com/DataDog/jmxfetch/issues/457 [#449]: https://github.com/DataDog/jmxfetch/issues/449 [#469]: https://github.com/DataDog/jmxfetch/issues/469 +[#477]: https://github.com/DataDog/jmxfetch/issues/477 [@alz]: https://github.com/alz [@aoking]: https://github.com/aoking [@arrawatia]: https://github.com/arrawatia From 2cf001a0dc63fa316ec1a23a8ce3f0ce7b2ffb98 Mon Sep 17 00:00:00 2001 From: Caleb Metz <135133572+cmetz100@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:59:11 -0400 Subject: [PATCH 29/48] Update jmxfetch telemetry to jvm_direct (#475) * update instance toString * changed jmxfetch telemetry to jvm direct --- src/main/java/org/datadog/jmxfetch/App.java | 2 +- src/main/java/org/datadog/jmxfetch/Instance.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 00ab71d4b..ddb6f1de8 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -968,7 +968,7 @@ private Map getTelemetryInstanceConfig() { config.put("name","jmxfetch_telemetry_instance"); config.put("collect_default_jvm_metrics",true); config.put("new_gc_metrics",true); - config.put("process_name_regex",".*org.datadog.jmxfetch.App.*"); + config.put("jvm_direct",true); config.put("normalize_bean_param_tags",true); List conf = new ArrayList(); diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index a2c5cbce4..770a8f863 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -479,7 +479,11 @@ public void init(boolean forceNewConnection) @Override public String toString() { if (isDirectInstance(instanceMap)) { - return "jvm_direct"; + if (this.instanceMap.get("name") != null) { + return "jvm_direct - name: `" + (String) this.instanceMap.get("name") + "`"; + } else { + return "jvm_direct"; + } } else if (this.instanceMap.get(PROCESS_NAME_REGEX) != null) { return "process_regex: `" + this.instanceMap.get(PROCESS_NAME_REGEX) + "`"; } else if (this.instanceMap.get("name") != null) { From 09943435a6371ac47635683e4dbc6ad435a0d5d7 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Fri, 22 Sep 2023 11:19:02 -0400 Subject: [PATCH 30/48] Better local docker instructions for misbehaving-jmx-server (#479) * Adds Dockerfile that gets the supervisor out of the picture * Adds better local-run instructions for misbehaving-jmx-server in docker * Update tools/misbehaving-jmx-server/README.md Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> * Update tools/misbehaving-jmx-server/README.md Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> * Update tools/misbehaving-jmx-server/README.md Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> --------- Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> --- tools/misbehaving-jmx-server/README.md | 20 ++++++++++++------- .../docker-compose.yaml | 19 +++++++++++++++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/tools/misbehaving-jmx-server/README.md b/tools/misbehaving-jmx-server/README.md index 92e073d3f..93df69e07 100644 --- a/tools/misbehaving-jmx-server/README.md +++ b/tools/misbehaving-jmx-server/README.md @@ -64,9 +64,18 @@ There are a couple of ways you can get the Agent to pull metrics from this test Copy `misbehaving-jmxfetch-conf.yaml` to `/etc/datadog-agent/conf.d/` and just run the `with-dependencies` jar created by Maven. You will need to restart the Agent to pick up the config. +### Using Docker Compose + +```shell +$ docker compose up +``` + +The Agent will auto-discover the container and begin to collect metrics from it. + ### Using Docker -After building the `misbehaving-jmx-server` you can simply run: +If your container's IP is directly +accessible by your Agent, you can use the following `run` command and use AD. ```shell $ docker run \ @@ -78,10 +87,7 @@ misbehaving-jmx-server The Agent will auto discover the container and begin to collect metrics from it. -### Using Docker Compose - -```shell -$ docker compose up -``` +Note that this implicitly sets the `RMI_HOSTNAME` to `localhost` which is where +the host port mapping comes into play. If this is giving you trouble, consider +using the [docker-compose setup](#using-docker-compose). -The Agent will auto discover the container and begin to collect metrics from it. diff --git a/tools/misbehaving-jmx-server/docker-compose.yaml b/tools/misbehaving-jmx-server/docker-compose.yaml index 0c7062e78..200444826 100644 --- a/tools/misbehaving-jmx-server/docker-compose.yaml +++ b/tools/misbehaving-jmx-server/docker-compose.yaml @@ -2,11 +2,24 @@ version: "3.9" services: - - test-server: + # The docker compose service name is used as the hostname for the misbehaving-jmx-server + # Note it is in the entrypoint as the --rmi-host and in the AD label as the hostname + # that the Agent should reach out to. + jmx-test-server: build: context: . + # Override entrypoint to specify the docker-compose service name as the RMI host + entrypoint: ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.misbehavingjmxserver.App", "--rmi-host", "jmx-test-server"] ports: - "1099:1099" labels: - com.datadoghq.ad.checks: '{"misbehaving":{"init_config":{"is_jmx":true},"instances":[{"host":"%%host%%","port":"1099","collect_default_jvm_metrics":false,"max_returned_metrics":300000,"conf":[{"include":{"domain":"Bohnanza"}}]}]}}' + com.datadoghq.ad.checks: '{"misbehaving":{"init_config":{"is_jmx":true},"instances":[{"host":"jmx-test-server","port":"1099","collect_default_jvm_metrics":false,"max_returned_metrics":300000,"conf":[{"include":{"domain":"Bohnanza"}}]}]}}' + datadog: + image: datadog/agent:7-jmx + pid: host + environment: + - DD_API_KEY=000000001 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /proc/:/host/proc/:ro + - /sys/fs/cgroup:/host/sys/fs/cgroup:ro From fbeee8d72fac06306380ae4c88d743d98297b6a4 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Wed, 27 Sep 2023 14:50:52 -0400 Subject: [PATCH 31/48] Prepare 0.48.0 release (#480) * Prepare for 0.48.0 release of jmxfetch * Update CHANGELOG.md Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> --------- Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> --- CHANGELOG.md | 4 +++- README.md | 2 +- pom.xml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7889205..7c38e8ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,15 @@ Changelog ========= # Next / TBD +# 0.48.0 / 2023-09-26 + * [FEATURE] Adds a configurable jmxfetch telemetry check to improve jmxfetch observability [#467][] * [FEATURE] Added an option to enable removal of extra quotation marks during tag extraction from Java management beans' parameters/attributes [#469][] * [FEATURE] Updated status bean to report JMX Telemetry to Agent status [#477][] # 0.47.10 / 2023-08-10 -* [IMPROVEMENT] Improvements in how JMXFetch handles communicating back to the Agent. The TLS of the HTTP client used can now be configured, extra logging has been added around the SSL Context, and 'TLS' as min protocol version used in the `dummyTrustManager` (configurable using the flag `jmxfetch.min_tls_version`, e.g. `-Djmxfetch.min_tls_version=TLS`) [#436][] +* [IMPROVEMENT] Improvements in how JMXFetch handles communicating back to the Agent. This includes allowing the TLS of the HTTP client to be configured, extra logging around the SSL Context, and 'TLS' as min protocol version used in the `dummyTrustManager` (configurable using the flag `jmxfetch.min_tls_version`, e.g., `-Djmxfetch.min_tls_version=TLS`) [#436][] * [BUGFIX] Fixed issue race condition where an exception is thrown if the Agent hasn't finished initializing before JMXFetch starts to shut down [#449][] * [OTHER] Update management agent logic and comments for Java 7 vs 8 vs 9 [#457][] diff --git a/README.md b/README.md index 89c16dcab..d160dd1d0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.48.0-SNAPSHOT-jar-with-dependencies.jar --help +java -jar jmxfetch-0.48.0-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index e3c7e12cf..8ffde02ad 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.48.0-SNAPSHOT + 0.48.0 jar jmxfetch From 93e6f9093311167a7cf89796d21e7dad3457ee52 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Fri, 29 Sep 2023 16:06:53 -0400 Subject: [PATCH 32/48] Update to next snapshot build 0.49 (#482) --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d160dd1d0..e9bb9cd2e 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.48.0-jar-with-dependencies.jar --help +java -jar jmxfetch-0.49.0-SNAPSHOT-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index 8ffde02ad..3836d3c8c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.48.0 + 0.49.0-SNAPSHOT jar jmxfetch From 195c704dcd4cb35bfc89c51310094714e24e313f Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Thu, 12 Oct 2023 14:49:41 -0400 Subject: [PATCH 33/48] Improves the error message printed when an exception occurs during bean/attribute retrieval (#486) --- src/main/java/org/datadog/jmxfetch/Instance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 770a8f863..54b1274f0 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -601,7 +601,7 @@ private void getMatchingAttributes() throws IOException { // we should not continue throw e; } catch (Exception e) { - log.warn("Cannot get bean attributes or class name: {}", e.getMessage()); + log.warn("Cannot get attributes or class name for bean {}: ", beanName, e); continue; } From 43c6494a7682e174b3eb3dcd8ec24ded3bb5756d Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Thu, 19 Oct 2023 14:44:23 -0400 Subject: [PATCH 34/48] Improves telemetry bean exception handling (#488) * Improves telemetry bean exception handling * Updates log lines for lint length and clarity --- .../java/org/datadog/jmxfetch/Instance.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 54b1274f0..56ef416da 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -279,7 +279,7 @@ public Instance( log.info("collect_default_jvm_metrics is false - not collecting default JVM metrics"); } - instanceTelemetryBean = createJmxBean(); + instanceTelemetryBean = createInstanceTelemetryBean(); } private ObjectName getObjName(String domain,String instance) @@ -287,7 +287,7 @@ private ObjectName getObjName(String domain,String instance) return new ObjectName(domain + ":target_instance=" + ObjectName.quote(instance)); } - private InstanceTelemetry createJmxBean() { + private InstanceTelemetry createInstanceTelemetryBean() { mbs = ManagementFactory.getPlatformMBeanServer(); InstanceTelemetry bean = new InstanceTelemetry(); log.debug("Created jmx bean for instance: " + this.getCheckName()); @@ -295,13 +295,22 @@ private InstanceTelemetry createJmxBean() { try { instanceTelemetryBeanName = getObjName(appConfig.getJmxfetchTelemetryDomain(), this.getName()); - mbs.registerMBean(bean,instanceTelemetryBeanName); - log.debug("Succesfully registered jmx bean for instance: " + this.getCheckName() - + " with ObjectName = " + instanceTelemetryBeanName); + } catch (MalformedObjectNameException e) { + log.warn( + "Could not construct bean name for jmxfetch_telemetry_domain '{}' and name '{}'", + appConfig.getJmxfetchTelemetryDomain(), this.getName()); + return bean; + } - } catch (MalformedObjectNameException | InstanceAlreadyExistsException - | MBeanRegistrationException | NotCompliantMBeanException e) { - log.warn("Could not register bean for instance: " + this.getCheckName(),e); + try { + mbs.registerMBean(bean,instanceTelemetryBeanName); + log.debug("Succesfully registered jmx bean for instance {} with ObjectName = {}", + this.getName(), instanceTelemetryBeanName); + } catch (InstanceAlreadyExistsException + | MBeanRegistrationException + | NotCompliantMBeanException e) { + log.warn("Could not register bean named '{}' for instance: ", + instanceTelemetryBeanName.toString(), e); } return bean; From 36da049e906e32776e95aedc94c0d92563a09861 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Thu, 19 Oct 2023 14:46:12 -0400 Subject: [PATCH 35/48] Adding some composite data tests (#481) * Implements better compositedata support and tests * Adds non-working example of nested data retrieval * Implements runtime check to see if attribute is assignable to any of the classes of attribute types we've declared * Removes runtime class check since tabulardata implements map * Adds compositedatasupport support * Reverts unrelated changes to telemetry bean exception handling * Addresses linter error with import order --- .../java/org/datadog/jmxfetch/Instance.java | 45 ++----------------- .../datadog/jmxfetch/JmxComplexAttribute.java | 27 +++++++---- .../datadog/jmxfetch/JmxSimpleAttribute.java | 33 ++++++++++++++ .../datadog/jmxfetch/JmxTabularAttribute.java | 9 ++++ .../datadog/jmxfetch/SimpleTestJavaApp.java | 43 ++++++++++++++++++ .../jmxfetch/SimpleTestJavaAppMBean.java | 5 +++ .../java/org/datadog/jmxfetch/TestApp.java | 30 +++++++++++++ src/test/resources/jmx_composite_data.yaml | 26 +++++++++++ 8 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 src/test/resources/jmx_composite_data.yaml diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 56ef416da..ded593d60 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -38,44 +38,6 @@ @Slf4j public class Instance { - private static final List SIMPLE_TYPES = - Arrays.asList( - "long", - "java.lang.String", - "int", - "float", - "double", - "java.lang.Double", - "java.lang.Float", - "java.lang.Integer", - "java.lang.Long", - "java.util.concurrent.atomic.AtomicInteger", - "java.util.concurrent.atomic.AtomicLong", - "java.lang.Object", - "java.lang.Boolean", - "boolean", - "java.lang.Number", - //Workaround for jasperserver, which returns attribute types as `class ` - "class java.lang.String", - "class java.lang.Double", - "class java.lang.Float", - "class java.lang.Integer", - "class java.lang.Long", - "class java.util.concurrent.atomic.AtomicInteger", - "class java.util.concurrent.atomic.AtomicLong", - "class java.lang.Object", - "class java.lang.Boolean", - "class java.lang.Number"); - private static final List COMPOSED_TYPES = - Arrays.asList( - "javax.management.openmbean.CompositeData", - "java.util.HashMap", - "java.util.Map"); - private static final List MULTI_TYPES = - Arrays.asList( - "javax.management.openmbean.TabularData", - //Adding TabularDataSupport as it implements TabularData - "javax.management.openmbean.TabularDataSupport"); private static final int MAX_RETURNED_METRICS = 350; private static final int DEFAULT_REFRESH_BEANS_PERIOD = 600; public static final String PROCESS_NAME_REGEX = "process_name_regex"; @@ -629,7 +591,8 @@ private void getMatchingAttributes() throws IOException { } JmxAttribute jmxAttribute; String attributeType = attributeInfo.getType(); - if (SIMPLE_TYPES.contains(attributeType)) { + + if (JmxSimpleAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE + beanName @@ -649,7 +612,7 @@ private void getMatchingAttributes() throws IOException { cassandraAliasing, emptyDefaultHostname, normalizeBeanParamTags); - } else if (COMPOSED_TYPES.contains(attributeType)) { + } else if (JmxComplexAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE + beanName @@ -668,7 +631,7 @@ private void getMatchingAttributes() throws IOException { tags, emptyDefaultHostname, normalizeBeanParamTags); - } else if (MULTI_TYPES.contains(attributeType)) { + } else if (JmxTabularAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE + beanName diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index 3ea2db51b..6849116fa 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -1,9 +1,13 @@ package org.datadog.jmxfetch; + +import lombok.extern.slf4j.Slf4j; + import org.datadog.jmxfetch.service.ServiceNameProvider; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -15,8 +19,15 @@ import javax.management.ReflectionException; import javax.management.openmbean.CompositeData; +@Slf4j @SuppressWarnings("unchecked") public class JmxComplexAttribute extends JmxSubAttribute { + private static final List COMPOSED_TYPES = + Arrays.asList( + "javax.management.openmbean.CompositeData", + "javax.management.openmbean.CompositeDataSupport", + "java.util.HashMap", + "java.util.Map"); private List subAttributeList = new ArrayList(); @@ -47,14 +58,12 @@ public JmxComplexAttribute( } private void populateSubAttributeList(Object attributeValue) { - String attributeType = getAttribute().getType(); - if ("javax.management.openmbean.CompositeData".equals(attributeType)) { + if (attributeValue instanceof javax.management.openmbean.CompositeData) { CompositeData data = (CompositeData) attributeValue; for (String key : data.getCompositeType().keySet()) { this.subAttributeList.add(key); } - } else if (("java.util.HashMap".equals(attributeType)) - || ("java.util.Map".equals(attributeType))) { + } else if (attributeValue instanceof java.util.Map) { Map data = (Map) attributeValue; for (String key : data.keySet()) { this.subAttributeList.add(key); @@ -80,19 +89,21 @@ private Object getValue(String subAttribute) ReflectionException, IOException { Object value = this.getJmxValue(); - String attributeType = getAttribute().getType(); - if ("javax.management.openmbean.CompositeData".equals(attributeType)) { + if (value instanceof CompositeData) { CompositeData data = (CompositeData) value; return data.get(subAttribute); - } else if (("java.util.HashMap".equals(attributeType)) - || ("java.util.Map".equals(attributeType))) { + } else if (value instanceof java.util.Map) { Map data = (Map) value; return data.get(subAttribute); } throw new NumberFormatException(); } + public static boolean matchAttributeType(String attributeType) { + return COMPOSED_TYPES.contains(attributeType); + } + @Override public boolean match(Configuration configuration) { if (!matchDomain(configuration) diff --git a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java index b890613ea..c76676ef5 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java @@ -3,6 +3,7 @@ import org.datadog.jmxfetch.service.ServiceNameProvider; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -15,6 +16,34 @@ @SuppressWarnings("unchecked") public class JmxSimpleAttribute extends JmxAttribute { + private static final List SIMPLE_TYPES = + Arrays.asList( + "long", + "java.lang.String", + "int", + "float", + "double", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.util.concurrent.atomic.AtomicInteger", + "java.util.concurrent.atomic.AtomicLong", + "java.lang.Object", + "java.lang.Boolean", + "boolean", + "java.lang.Number", + //Workaround for jasperserver, which returns attribute types as `class ` + "class java.lang.String", + "class java.lang.Double", + "class java.lang.Float", + "class java.lang.Integer", + "class java.lang.Long", + "class java.util.concurrent.atomic.AtomicInteger", + "class java.util.concurrent.atomic.AtomicLong", + "class java.lang.Object", + "class java.lang.Boolean", + "class java.lang.Number"); private Metric cachedMetric; /** JmxSimpleAttribute constructor. */ @@ -59,6 +88,10 @@ public List getMetrics() return Collections.singletonList(cachedMetric); } + public static boolean matchAttributeType(String attributeType) { + return SIMPLE_TYPES.contains(attributeType); + } + /** Returns whether an attribute matches in a configuration spec. */ public boolean match(Configuration configuration) { return matchDomain(configuration) diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index 0b443de6c..816e0aa29 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -25,6 +25,11 @@ @Slf4j public class JmxTabularAttribute extends JmxSubAttribute { + private static final List MULTI_TYPES = + Arrays.asList( + "javax.management.openmbean.TabularData", + //Adding TabularDataSupport as it implements TabularData + "javax.management.openmbean.TabularDataSupport"); private String instanceName; private Map> subAttributeList; @@ -55,6 +60,10 @@ public JmxTabularAttribute( subAttributeList = new HashMap>(); } + public static boolean matchAttributeType(String attributeType) { + return MULTI_TYPES.contains(attributeType); + } + private String getMultiKey(Collection keys) { StringBuilder sb = new StringBuilder(); boolean first = true; diff --git a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java index 630b9183e..d245cb183 100644 --- a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java +++ b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java @@ -43,6 +43,8 @@ public class SimpleTestJavaApp implements SimpleTestJavaAppMBean { private final TabularDataSupport tabulardata; private final CompositeType compositetype; + private final CompositeData nestedCompositeData; + SimpleTestJavaApp() { hashmap.put("thisis0", 0); hashmap.put("thisis10", 10); @@ -53,6 +55,8 @@ public class SimpleTestJavaApp implements SimpleTestJavaAppMBean { if (tabulardata != null) { tabulardata.put(buildCompositeData(1)); } + + nestedCompositeData = buildNestedCompositeData(); } public int getShouldBe100() { @@ -160,6 +164,45 @@ private CompositeType buildCompositeType() { } } + private CompositeDataSupport buildNestedCompositeData() { + try { + // Define the inner CompositeData + String[] innerNames = { "aLong", "aDouble", "aString" }; + Object[] innerValues = { 123456L, 123.456, "Test String" }; + OpenType[] innerTypes = { SimpleType.LONG, SimpleType.DOUBLE, SimpleType.STRING }; + + CompositeType innerType = new CompositeType( + "InnerType", + "Description for Inner CompositeData", + innerNames, + innerNames, + innerTypes); + + CompositeData innerComposite = new CompositeDataSupport(innerType, innerNames, innerValues); + + // Define the outer CompositeData + String[] outerNames = { "anInt", "aBoolean", "nestedData" }; + Object[] outerValues = { 42, true, innerComposite }; + OpenType[] outerTypes = { SimpleType.INTEGER, SimpleType.BOOLEAN, innerType }; + + CompositeType outerType = new CompositeType( + "OuterType", + "Description for Outer CompositeData", + outerNames, + outerNames, + outerTypes); + + return new CompositeDataSupport(outerType, outerNames, outerValues); + } catch (Exception e) { + // should never happen + return null; + } + } + + public CompositeData getNestedCompositeData() { + return this.nestedCompositeData; + } + private CompositeData buildCompositeData(Integer i) { try { return new CompositeDataSupport( diff --git a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java index 0634957a5..161724721 100644 --- a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java +++ b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java @@ -3,6 +3,9 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; @@ -40,4 +43,6 @@ public interface SimpleTestJavaAppMBean { TabularData getTabulardata(); TabularDataSupport getTabularDataSupport(); + + CompositeData getNestedCompositeData(); } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index e45a5de3d..972b07f9e 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -1114,4 +1114,34 @@ public void testTabularDataTagged() throws Exception { assertCoverage(); } + + @Test + public void testNestedCompositeData() throws Exception { + SimpleTestJavaApp testApp = new SimpleTestJavaApp(); + registerMBean(testApp, "org.datadog.jmxfetch.test:type=SimpleTestJavaApp"); + + // We do a first collection + when(appConfig.isTargetDirectInstances()).thenReturn(true); + initApplication("jmx_composite_data.yaml"); + + run(); + List> metrics = getMetrics(); + + assertEquals(1, metrics.size()); + + List tags = Arrays.asList( + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "type:SimpleTestJavaApp", + "env:stage", + "newTag:test"); + + assertMetric("one_level_int", 42, tags, -1); + + // This assertion currently fails as JMXFetch does not support accessing + // data from a nested compositedata object + //assertMetric("second_level_long", 123456L, tags, -1); + + assertCoverage(); + } } diff --git a/src/test/resources/jmx_composite_data.yaml b/src/test/resources/jmx_composite_data.yaml new file mode 100644 index 000000000..e2426109a --- /dev/null +++ b/src/test/resources/jmx_composite_data.yaml @@ -0,0 +1,26 @@ +--- +init_config: + +instances: + - jvm_direct: true + refresh_beans: 4 + collect_default_jvm_metrics: false + name: jmx_test_instance + tags: + - "env:stage" + - "newTag:test" + conf: + - include: + domain: org.datadog.jmxfetch.test + attribute: + NestedCompositeData.anInt: + metric_type: gauge + alias: one_level_int + - include: + domain: org.datadog.jmxfetch.test + attribute: + # Warning!! This is currently not supported! + # See corresponding unit test + NestedCompositeData.nestedData.aLong: + metric_type: gauge + alias: second_level_long \ No newline at end of file From 2c88ecc9733b5f459bd31edc86df0ab02002a341 Mon Sep 17 00:00:00 2001 From: Carlos Roman Date: Tue, 24 Oct 2023 10:12:19 +0100 Subject: [PATCH 36/48] Attempt to fix misbehaving-jmx-server build --- .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 18 + tools/misbehaving-jmx-server/Dockerfile | 25 +- tools/misbehaving-jmx-server/mvnw | 308 ++++++++++++++++++ tools/misbehaving-jmx-server/mvnw.cmd | 205 ++++++++++++ 5 files changed, 545 insertions(+), 11 deletions(-) create mode 100644 tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.jar create mode 100644 tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.properties create mode 100755 tools/misbehaving-jmx-server/mvnw create mode 100644 tools/misbehaving-jmx-server/mvnw.cmd diff --git a/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.jar b/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.properties b/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..452adac1e --- /dev/null +++ b/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/tools/misbehaving-jmx-server/Dockerfile b/tools/misbehaving-jmx-server/Dockerfile index cf51ba8c3..938e8c112 100644 --- a/tools/misbehaving-jmx-server/Dockerfile +++ b/tools/misbehaving-jmx-server/Dockerfile @@ -1,21 +1,24 @@ -# Use the official Maven image as the base image -FROM maven:latest AS build +# Use the official JDK image as the base image +FROM eclipse-temurin:17 AS base +# Use the base image as the build image +FROM base AS build # Set the working directory to /app WORKDIR /app -# Copy the pom.xml file and install the dependencies -COPY pom.xml . -RUN mvn dependency:resolve +# Copy the pom.xml and Maven files and install the dependencies +COPY .mvn .mvn/ +COPY pom.xml mvnw mvnw.cmd ./ +RUN set -eu && \ + ./mvnw dependency:resolve; # Copy the source code and build the JAR file -COPY src/ /app/src/ -RUN mvn clean package assembly:single +COPY src/ src/ +RUN set -eu && \ + ./mvnw clean package assembly:single; -RUN ls /app/target - -# Use the official OpenJDK image as the base image for the final image -FROM openjdk:latest AS final +# Use the base image as the the final image +FROM base AS final # Set the working directory to /app WORKDIR /app diff --git a/tools/misbehaving-jmx-server/mvnw b/tools/misbehaving-jmx-server/mvnw new file mode 100755 index 000000000..8d937f4c1 --- /dev/null +++ b/tools/misbehaving-jmx-server/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/tools/misbehaving-jmx-server/mvnw.cmd b/tools/misbehaving-jmx-server/mvnw.cmd new file mode 100644 index 000000000..f80fbad3e --- /dev/null +++ b/tools/misbehaving-jmx-server/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From 07c3f705dba1bed6b44c11343e7cfcfb31b9dd8a Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:41:16 -0400 Subject: [PATCH 37/48] Add warning for duplicate instance name (#484) --- src/main/java/org/datadog/jmxfetch/App.java | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index ddb6f1de8..3869e11a7 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -29,11 +29,13 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -828,6 +830,7 @@ public void init(final boolean forceNewConnection) { this.brokenInstanceMap.clear(); final List newInstances = new ArrayList<>(); + final Set instanceNamesSeen = new HashSet<>(); log.info("Dealing with YAML config instances..."); final Iterator> it = this.configs.entrySet().iterator(); @@ -867,7 +870,16 @@ public void init(final boolean forceNewConnection) { isDirectInstance(configInstance)); continue; } - + final String instanceName = (String) configInstance.get("name"); + if (instanceName != null) { + if (instanceNamesSeen.contains(instanceName)) { + log.warn("Found multiple instances with name: '{}'. " + + "Instance names should be unique, " + + "update the 'name' field on your instances to be unique.", + instanceName); + } + instanceNamesSeen.add(instanceName); + } // Create a new Instance object log.info("Instantiating instance for: {}", name); final Instance instance = @@ -893,6 +905,16 @@ public void init(final boolean forceNewConnection) { final String checkName = (String) checkConfig.get("check_name"); for (Map configInstance : configInstances) { log.info("Instantiating instance for: " + checkName); + final String instanceName = (String) configInstance.get("name"); + if (instanceName != null) { + if (instanceNamesSeen.contains(instanceName)) { + log.warn("Found multiple instances with name: '{}'. " + + "Instance names should be unique, " + + "update the 'name' field on your instances to be unique.", + instanceName); + } + instanceNamesSeen.add(instanceName); + } final Instance instance = instantiate(configInstance, initConfig, checkName, this.appConfig); newInstances.add(instance); From 8771182e89eaf046a1010ae7bde62143e11b92ec Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Wed, 1 Nov 2023 13:53:11 -0400 Subject: [PATCH 38/48] Always save the unit test reports for easy debugging (#493) --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 81ff2e906..122018a57 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,7 @@ run_unit_tests: artifacts: expire_in: 1 mos + when: always paths: - ./target/surefire-reports/*.txt From 7c2921b6729c5f18fabc332e4a2761cc6054b02f Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:19:11 -0400 Subject: [PATCH 39/48] Clean up instance telemetry bean in tests (#485) --- src/main/java/org/datadog/jmxfetch/App.java | 4 ++++ src/test/java/org/datadog/jmxfetch/TestApp.java | 6 +++--- src/test/java/org/datadog/jmxfetch/TestCommon.java | 10 ++++++++++ src/test/resources/jmx_alias_match.yaml | 2 +- src/test/resources/jmx_counter_rate.yaml | 4 ++-- src/test/resources/jmx_sd_pipe.txt | 2 +- src/test/resources/jmx_sd_pipe_longname.txt | 4 ++-- 7 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 3869e11a7..6d2d2e16b 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -244,6 +244,10 @@ public TaskStatusHandler invoke( } } + protected void clearAllInstances() { + this.clearInstances(this.instances); + } + /** * Builds an {@link ExecutorService} of the specified fixed size. Threads will be created * and executed as daemons if {@link AppConfig#isDaemon()} is true. Defaults to false. diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 972b07f9e..607329ee4 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -152,7 +152,7 @@ public void testRegexpAliasing() throws Exception { List tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", - "instance:jmx_test_instance", + "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); @@ -963,7 +963,7 @@ public void testServiceDiscovery() throws Exception { List tags = Arrays.asList( "type:SimpleTestJavaApp", "scope:CoolScope", - "instance:jmx_test_instance", + "instance:jmx_test_instance2", "jmx_domain:org.datadog.jmxfetch.test", "bean_host:localhost", "component" @@ -998,7 +998,7 @@ public void testServiceDiscovery() throws Exception { tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", - "instance:jmx_test_instance", + "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java index e5cadcfac..cac345270 100644 --- a/src/test/java/org/datadog/jmxfetch/TestCommon.java +++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java @@ -110,6 +110,16 @@ public void unregisterMBean() throws MBeanRegistrationException, InstanceNotFoun } } + /** + * Clear instances and their instance telemetry bean after execution of every test. + */ + @After + public void clearInstances() { + if (app != null) { + app.clearAllInstances(); + } + } + /** Init JMXFetch with the given YAML configuration file. */ protected void initApplication(String yamlFileName, String autoDiscoveryPipeFile) throws FileNotFoundException, IOException { diff --git a/src/test/resources/jmx_alias_match.yaml b/src/test/resources/jmx_alias_match.yaml index 3d5d51193..8626e89ed 100644 --- a/src/test/resources/jmx_alias_match.yaml +++ b/src/test/resources/jmx_alias_match.yaml @@ -2,7 +2,7 @@ init_config: instances: - process_name_regex: .*surefire.* - name: jmx_test_instance + name: jmx_test_instance1 conf: - include: domain: org.datadog.jmxfetch.test diff --git a/src/test/resources/jmx_counter_rate.yaml b/src/test/resources/jmx_counter_rate.yaml index ab58e7aad..4485994ee 100644 --- a/src/test/resources/jmx_counter_rate.yaml +++ b/src/test/resources/jmx_counter_rate.yaml @@ -3,7 +3,7 @@ init_config: instances: - process_name_regex: .*surefire.* refresh_beans: 4 - name: jmx_test_instance + name: jmx_test_instance1 conf: - include: domain: org.datadog.jmxfetch.test @@ -13,7 +13,7 @@ instances: alias: test.counter - process_name_regex: .*surefire.* refresh_beans: 4 - name: jmx_test_instance + name: jmx_test_instance2 conf: - include: domain: org.datadog.jmxfetch.test diff --git a/src/test/resources/jmx_sd_pipe.txt b/src/test/resources/jmx_sd_pipe.txt index be9ef7688..945fbc4c0 100644 --- a/src/test/resources/jmx_sd_pipe.txt +++ b/src/test/resources/jmx_sd_pipe.txt @@ -25,7 +25,7 @@ init_config: instances: - process_name_regex: .*surefire.* - name: jmx_test_instance + name: jmx_test_instance2 conf: - include: bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= diff --git a/src/test/resources/jmx_sd_pipe_longname.txt b/src/test/resources/jmx_sd_pipe_longname.txt index d1f58c2ee..8e6380525 100644 --- a/src/test/resources/jmx_sd_pipe_longname.txt +++ b/src/test/resources/jmx_sd_pipe_longname.txt @@ -4,7 +4,7 @@ init_config: instances: - process_name_regex: .*surefire.* - name: jmx_test_instance + name: jmx_test_instance2 conf: - include: bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= @@ -25,7 +25,7 @@ init_config: instances: - process_name_regex: .*surefire.* - name: jmx_test_instance + name: jmx_test_instance3 conf: - include: bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= From 1f40ce0bddb25f34c027ec18bc6d111d21420508 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:40:58 -0500 Subject: [PATCH 40/48] AMLII-1173 - Modify JMXFetch to pass back a tag with the jmx_check_name to the Agent (#492) --- .../org/datadog/jmxfetch/JmxAttribute.java | 1 + .../java/org/datadog/jmxfetch/TestApp.java | 324 +++++++++--------- .../java/org/datadog/jmxfetch/TestCommon.java | 4 +- ...onal_tags.yml => jmx_additional_tags.yaml} | 0 ...exclude_tags.yml => jmx_exclude_tags.yaml} | 0 ...=> jmx_exclude_tags_override_service.yaml} | 0 6 files changed, 174 insertions(+), 155 deletions(-) rename src/test/resources/{jmx_additional_tags.yml => jmx_additional_tags.yaml} (100%) rename src/test/resources/{jmx_exclude_tags.yml => jmx_exclude_tags.yaml} (100%) rename src/test/resources/{jmx_exclude_tags_override_service.yml => jmx_exclude_tags_override_service.yaml} (100%) diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index 38e926790..e4fb5b9c4 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -172,6 +172,7 @@ private List getBeanParametersList( List beanTags = new ArrayList(); beanTags.add("instance:" + instanceName); beanTags.add("jmx_domain:" + domain); + beanTags.add("jmx_check_name:" + checkName); if (renameCassandraMetrics()) { beanTags.addAll(getCassandraBeanTags(beanParameters)); diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 607329ee4..03b72caf5 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -35,13 +35,14 @@ public void testBeanRegexTags() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_bean_regex_tags", "bean_host:localhost", "component", "hosttag:localhost", "nonExistantTag:$2", "nonRegexTag:value"); - assertMetric("this.is.100", tags, 9); + assertMetric("this.is.100", tags, 10); } /** Tag metrics with MBeans parameters. */ @@ -66,10 +67,11 @@ public void testBeanTags() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_bean_tags", "bean_host:localhost", "component"); - assertMetric("this.is.100", tags, 6); + assertMetric("this.is.100", tags, 7); } /** Tag metrics with MBeans parameters with normalize_bean_param_tags option enabled. */ @@ -95,11 +97,12 @@ public void testBeanTagsNormalizeParams() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_bean_tags_normalize_params", "bean_host:localhost", "component", "target_instance:.*example.process.regex.*"); - assertMetric("this.is.100", tags, 7); + assertMetric("this.is.100", tags, 8); } /** Tag metrics with MBeans parameters with normalize_bean_param_tags option disabled. */ @@ -125,11 +128,12 @@ public void testBeanTagsDontNormalizeParams() throws Exception { "scope:\"CoolScope\"", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_bean_tags_dont_normalize_params", "bean_host:\"localhost\"", "component", "target_instance:\".\\*example.process.regex.\\*\""); - assertMetric("this.is.100", tags, 7); + assertMetric("this.is.100", tags, 8); } /** Generate metric aliases from a `alias_match` regular expression. */ @@ -152,14 +156,15 @@ public void testRegexpAliasing() throws Exception { List tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_alias_match", "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); - assertMetric("this.is.100.bar.baz", tags, 4); - assertMetric("org.datadog.jmxfetch.test.baz.hashmap.thisis0", tags, 4); - assertMetric("this.is.thousand.1000.0", 1000, tags, 4); - assertMetric("this.is.five.should_be5", 5, tags, 4); + assertMetric("this.is.100.bar.baz", tags, 5); + assertMetric("org.datadog.jmxfetch.test.baz.hashmap.thisis0", tags, 5); + assertMetric("this.is.thousand.1000.0", 1000, tags, 5); + assertMetric("this.is.five.should_be5", 5, tags, 5); } /** @@ -186,10 +191,11 @@ public void testNoAliasOnDetailedAttribute() throws Exception { Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", "instance:jmx_test_instance", + "jmx_check_name:jmx_no_alias", "foo:Bar", "qux:Baz"); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be100", tags, 4); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be100", tags, 5); } /** @@ -220,9 +226,10 @@ public void testCassandraBean() throws Exception { "keyspace:MyKeySpace", "ColumnFamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.metrics", + "jmx_check_name:jmx_cassandra", "instance:jmx_first_instance"); - assertMetric("cassandra.pending_tasks.should_be100", tags, 5); + assertMetric("cassandra.pending_tasks.should_be100", tags, 6); // Default behavior tags = @@ -231,10 +238,11 @@ public void testCassandraBean() throws Exception { "scope:MyColumnFamily", "keyspace:MyKeySpace", "jmx_domain:org.apache.cassandra.metrics", + "jmx_check_name:jmx_cassandra", "instance:jmx_second_instance", "name:PendingTasks"); - assertMetric("cassandra.metrics.should_be1000", tags, 6); + assertMetric("cassandra.metrics.should_be1000", tags, 7); } @Test @@ -258,9 +266,10 @@ public void testCassandraDeprecatedBean() throws Exception { "keyspace:MyKeySpace", "columnfamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.db", + "jmx_check_name:jmx_cassandra_deprecated", "instance:jmx_test_instance"); - assertMetric("cassandra.db.should_be100", tags, 5); + assertMetric("cassandra.db.should_be100", tags, 6); } @Test @@ -526,14 +535,15 @@ public void testMetricTypes() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = - Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test"); + Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test", + "jmx_check_name:jmx_histogram"); // 15 = 13 metrics from java.lang + the 3 collected (gauge and histogram) assertEquals(16, metrics.size()); - assertMetric("test.gauge", 1000.0, commonTags, 5, "gauge"); - assertMetric("test.gauge_by_default", 42.0, commonTags, 5, "gauge"); - assertMetric("test.histogram", 424242, commonTags, 5, "histogram"); + assertMetric("test.gauge", 1000.0, commonTags, 6, "gauge"); + assertMetric("test.gauge_by_default", 42.0, commonTags, 6, "gauge"); + assertMetric("test.histogram", 424242, commonTags, 6, "histogram"); // We run a second collection. The counter should now be present run(); @@ -541,10 +551,10 @@ public void testMetricTypes() throws Exception { // 16 = 13 metrics from java.lang + the 4 collected (gauge, histogram and counter) assertEquals(17, metrics.size()); - assertMetric("test.gauge", 1000.0, commonTags, 5, "gauge"); - assertMetric("test.gauge_by_default", 42.0, commonTags, 5, "gauge"); - assertMetric("test.histogram", 424242, commonTags, 5, "histogram"); - assertMetric("test.counter", 0.0, commonTags, 5, "counter"); + assertMetric("test.gauge", 1000.0, commonTags, 6, "gauge"); + assertMetric("test.gauge_by_default", 42.0, commonTags, 6, "gauge"); + assertMetric("test.histogram", 424242, commonTags, 6, "histogram"); + assertMetric("test.counter", 0.0, commonTags, 6, "counter"); } @Test @@ -554,21 +564,21 @@ public void testExcludeTags() throws Exception { registerMBean(testApp, "org.datadog.jmxfetch.test:type=SimpleTestJavaApp"); // We do a first collection - initApplication("jmx_exclude_tags.yml"); + initApplication("jmx_exclude_tags.yaml"); run(); List> metrics = getMetrics(); // We test for the presence and the value of the metrics we want to collect. // Tags "type", "newTag" and "env" should be excluded List commonTags = - Arrays.asList("instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test"); + Arrays.asList("instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", "jmx_check_name:jmx_exclude_tags"); // 15 = 13 metrics from java.lang + the 2 collected (gauge and histogram) assertEquals(15, metrics.size()); // There should only left 2 tags per metric - assertMetric("test1.gauge", 1000.0, commonTags, 2, "gauge"); - assertMetric("test1.histogram", 424242, commonTags, 2, "histogram"); + assertMetric("test1.gauge", 1000.0, commonTags, 3, "gauge"); + assertMetric("test1.histogram", 424242, commonTags, 3, "histogram"); } @Test @@ -578,7 +588,7 @@ public void testExcludeServiceTagsAndOverride() throws Exception { registerMBean(testApp, "org.datadog.jmxfetch.test:type=SimpleTestJavaApp,service=foo"); // We do a first collection - initApplication("jmx_exclude_tags_override_service.yml"); + initApplication("jmx_exclude_tags_override_service.yaml"); run(); List> metrics = getMetrics(); @@ -586,14 +596,15 @@ public void testExcludeServiceTagsAndOverride() throws Exception { // Tags "type", "newTag" and "env" should be excluded List commonTags = Arrays.asList("instance:jmx_test_service_override_instance", - "jmx_domain:org.datadog.jmxfetch.test","service:test"); + "jmx_domain:org.datadog.jmxfetch.test","service:test", + "jmx_check_name:jmx_exclude_tags_override_service"); // 15 = 13 metrics from java.lang + the 2 collected (gauge and histogram) assertEquals(15, metrics.size()); // There should only left 2 tags per metric - assertMetric("test1.gauge", 1000.0, commonTags, 3, "gauge"); - assertMetric("test1.histogram", 424242, commonTags, 3, "histogram"); + assertMetric("test1.gauge", 1000.0, commonTags, 4, "gauge"); + assertMetric("test1.histogram", 424242, commonTags, 4, "histogram"); } @Test @@ -603,7 +614,7 @@ public void testAdditionalTags() throws Exception { registerMBean(testApp, "org.datadog.jmxfetch.test:type=SimpleTestJavaApp,name=testName"); // We do a first collection - initApplication("jmx_additional_tags.yml"); + initApplication("jmx_additional_tags.yaml"); run(); List> metrics = getMetrics(); @@ -613,6 +624,7 @@ public void testAdditionalTags() throws Exception { Arrays.asList( "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_additional_tags", "type:SimpleTestJavaApp", "name:testName", "simple:SimpleTestJavaApp", @@ -624,8 +636,8 @@ public void testAdditionalTags() throws Exception { assertEquals(15, metrics.size()); // There should only left 2 tags per metric - assertMetric("test1.gauge", 1000.0, commonTags, 8, "gauge"); - assertMetric("test1.histogram", 424242, commonTags, 8, "histogram"); + assertMetric("test1.gauge", 1000.0, commonTags, 9, "gauge"); + assertMetric("test1.histogram", 424242, commonTags, 9, "histogram"); } /** FIXME: Split this test in multiple sub-tests. */ @@ -649,26 +661,26 @@ public void testApp() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = - Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test"); + Arrays.asList("instance:jmx_test_instance", "jmx_check_name:jmx", "env:stage", "newTag:test"); - assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 8); + assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("subattr.defaulted", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("subattr.defaulted", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); assertCoverage(); @@ -682,28 +694,28 @@ public void testApp() throws Exception { // We test for the same metrics but this time, the counter should be here // Previous metrics - assertMetric("this.is.100", 100.0, commonTags, 8); + assertMetric("this.is.100", 100.0, commonTags, 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("subattr.defaulted", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("subattr.defaulted", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); // Counters - assertMetric("subattr.counter", 0.0, commonTags, 5); - assertMetric("test.counter", 0.0, commonTags, 5); + assertMetric("subattr.counter", 0.0, commonTags, 6); + assertMetric("test.counter", 0.0, commonTags, 6); assertCoverage(); // We run a 3rd collection but this time we increment the counter and we sleep @@ -720,28 +732,28 @@ public void testApp() throws Exception { assertEquals(32, metrics.size()); // Previous metrics - assertMetric("this.is.100", 100.0, commonTags, 8); + assertMetric("this.is.100", 100.0, commonTags, 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("subattr.defaulted", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 7); - - // Counter (verify rate metrics within range) - assertMetric("subattr.counter", 0.95, 1, commonTags, 5); - assertMetric("test.counter", 0.95, 1, commonTags, 5); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("subattr.defaulted", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 8); + + // // Counter (verify rate metrics within range) + assertMetric("subattr.counter", 0.95, 1, commonTags, 6); + assertMetric("test.counter", 0.95, 1, commonTags, 6); assertCoverage(); } @@ -766,25 +778,25 @@ public void testAppCanonicalRate() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = - Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test"); + Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test", "jmx_check_name:jmx_canonical"); - assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 8); + assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); assertCoverage(); @@ -798,27 +810,27 @@ public void testAppCanonicalRate() throws Exception { // We test for the same metrics but this time, the counter should be here // Previous metrics - assertMetric("this.is.100", 100.0, commonTags, 8); + assertMetric("this.is.100", 100.0, commonTags, 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 7); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 1.0, commonTags, Arrays.asList("foo:1", "toto:tata"), 8); // Counters - assertMetric("subattr.counter", 0.0, commonTags, 5); - assertMetric("test.counter", 0.0, commonTags, 5); + assertMetric("subattr.counter", 0.0, commonTags, 6); + assertMetric("test.counter", 0.0, commonTags, 6); assertCoverage(); // We run a 3rd collection but this time we decrement the counter @@ -833,7 +845,7 @@ public void testAppCanonicalRate() throws Exception { run(); metrics = getMetrics(); assertEquals(31, metrics.size()); - assertMetric("test.counter", 0.0, commonTags, 5); + assertMetric("test.counter", 0.0, commonTags, 6); // Check that they are working again Thread.sleep(5000); @@ -846,27 +858,27 @@ public void testAppCanonicalRate() throws Exception { assertEquals(31, metrics.size()); // Previous metrics - assertMetric("this.is.100", 100.0, commonTags, 8); + assertMetric("this.is.100", 100.0, commonTags, 9); assertMetric( - "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 5); - assertMetric("test.converted", 5.0, commonTags, 5); - assertMetric("test.boolean", 1.0, commonTags, 5); - assertMetric("test.defaulted", 32.0, commonTags, 5); - assertMetric("subattr.this.is.0", 0.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 5); - assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 5); - assertMetric("multiattr.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 7); - assertMetric("multiattr_supp.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 7); + "jmx.org.datadog.jmxfetch.test.number_big", 1.2345678890123457E20, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.long42424242", 4.2424242E7, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.int424242", 424242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.should_be1000", 1000.0, commonTags, 6); + assertMetric("test.converted", 5.0, commonTags, 6); + assertMetric("test.boolean", 1.0, commonTags, 6); + assertMetric("test.defaulted", 32.0, commonTags, 6); + assertMetric("subattr.this.is.0", 0.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic42", 42.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.atomic4242", 4242.0, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.object1337", 13.37, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.primitive_float", 123.4f, commonTags, 6); + assertMetric("jmx.org.datadog.jmxfetch.test.instance_float", 567.8f, commonTags, 6); + assertMetric("multiattr.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 8); + assertMetric("multiattr_supp.foo", 2.0, commonTags, Arrays.asList("foo:2", "toto:tata"), 8); // Counter (verify rate metrics within range) - assertMetric("subattr.counter", 0.95, 1, commonTags, 5); - assertMetric("test.counter", 0.95, 1, commonTags, 5); + assertMetric("subattr.counter", 0.95, 1, commonTags, 6); + assertMetric("test.counter", 0.95, 1, commonTags, 6); assertCoverage(); } @@ -890,14 +902,14 @@ public void testAppCount() throws Exception { run(); metrics = getMetrics(); assertEquals(14, metrics.size()); - assertMetric("test.counter", 0, Collections.emptyList(), 3); + assertMetric("test.counter", 0, Collections.emptyList(), 4); // For the 3rd collection we increment the count to 5 so we should get a +5 delta testApp.incrementCounter(5); run(); metrics = getMetrics(); assertEquals(14, metrics.size()); - assertMetric("test.counter", 5, Collections.emptyList(), 3); + assertMetric("test.counter", 5, Collections.emptyList(), 4); assertCoverage(); } @@ -922,8 +934,8 @@ public void testAppCounterRate() throws Exception { run(); metrics = getMetrics(); assertEquals(28, metrics.size()); - assertMetric("test.counter", 0, Collections.emptyList(), 3); - assertMetric("test.rate", 0, Collections.emptyList(), 3); + assertMetric("test.counter", 0, Collections.emptyList(), 4); + assertMetric("test.rate", 0, Collections.emptyList(), 4); Thread.sleep(5000); // For the 3rd collection we increment the count to 5 so we should get a +5 delta @@ -931,8 +943,8 @@ public void testAppCounterRate() throws Exception { run(); metrics = getMetrics(); assertEquals(28, metrics.size()); - assertMetric("test.counter", 0.95, 1, Collections.emptyList(), 3); - assertMetric("test.rate", 0.95, 1, Collections.emptyList(), 3); + assertMetric("test.counter", 0.95, 1, Collections.emptyList(), 4); + assertMetric("test.rate", 0.95, 1, Collections.emptyList(), 4); assertCoverage(); } @@ -965,11 +977,12 @@ public void testServiceDiscovery() throws Exception { "scope:CoolScope", "instance:jmx_test_instance2", "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:AD-jmx_0", "bean_host:localhost", "component" ); - assertMetric("this.is.100", tags, 6); + assertMetric("this.is.100", tags, 7); // Assert compliancy with CASSANDRA-4009 tags = @@ -978,9 +991,10 @@ public void testServiceDiscovery() throws Exception { "keyspace:MyKeySpace", "ColumnFamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.metrics", + "jmx_check_name:AD-cassandra_0", "instance:jmx_first_instance"); - assertMetric("cassandra.pending_tasks.should_be100", tags, 5); + assertMetric("cassandra.pending_tasks.should_be100", tags, 6); // Default behavior tags = @@ -989,21 +1003,23 @@ public void testServiceDiscovery() throws Exception { "scope:MyColumnFamily", "keyspace:MyKeySpace", "jmx_domain:org.apache.cassandra.metrics", + "jmx_check_name:AD-cassandra_0", "instance:jmx_second_instance", "name:PendingTasks"); - assertMetric("cassandra.metrics.should_be1000", tags, 6); + assertMetric("cassandra.metrics.should_be1000", tags, 7); // Metric aliases are generated from `alias_match` tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", + "jmx_check_name:jmx_alias_match", "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); - assertMetric("this.is.100.bar.baz", tags, 4); - assertMetric("org.datadog.jmxfetch.test.baz.hashmap.thisis0", tags, 4); + assertMetric("this.is.100.bar.baz", tags, 5); + assertMetric("org.datadog.jmxfetch.test.baz.hashmap.thisis0", tags, 5); } /** Test JMX Service Discovery. */ diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java index cac345270..edb8bde68 100644 --- a/src/test/java/org/datadog/jmxfetch/TestCommon.java +++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java @@ -1,5 +1,7 @@ package org.datadog.jmxfetch; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -280,7 +282,7 @@ public void assertMetric( assertEquals(countTags, mTags.size()); } for (String t : tags) { - assertTrue(mTags.contains(t)); + assertThat(mTags, hasItem(t)); } if (metricType != null) { diff --git a/src/test/resources/jmx_additional_tags.yml b/src/test/resources/jmx_additional_tags.yaml similarity index 100% rename from src/test/resources/jmx_additional_tags.yml rename to src/test/resources/jmx_additional_tags.yaml diff --git a/src/test/resources/jmx_exclude_tags.yml b/src/test/resources/jmx_exclude_tags.yaml similarity index 100% rename from src/test/resources/jmx_exclude_tags.yml rename to src/test/resources/jmx_exclude_tags.yaml diff --git a/src/test/resources/jmx_exclude_tags_override_service.yml b/src/test/resources/jmx_exclude_tags_override_service.yaml similarity index 100% rename from src/test/resources/jmx_exclude_tags_override_service.yml rename to src/test/resources/jmx_exclude_tags_override_service.yaml From 571c44fb1284f32a596b22039237da0cf9f8d44f Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:46:58 -0500 Subject: [PATCH 41/48] rename jmx check name prefix (#495) --- .../org/datadog/jmxfetch/JmxAttribute.java | 2 +- .../java/org/datadog/jmxfetch/TestApp.java | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index e4fb5b9c4..601f6f69e 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -172,7 +172,7 @@ private List getBeanParametersList( List beanTags = new ArrayList(); beanTags.add("instance:" + instanceName); beanTags.add("jmx_domain:" + domain); - beanTags.add("jmx_check_name:" + checkName); + beanTags.add("dd.internal.jmx_check_name:" + checkName); if (renameCassandraMetrics()) { beanTags.addAll(getCassandraBeanTags(beanParameters)); diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 03b72caf5..f857d9673 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -35,7 +35,7 @@ public void testBeanRegexTags() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_bean_regex_tags", + "dd.internal.jmx_check_name:jmx_bean_regex_tags", "bean_host:localhost", "component", "hosttag:localhost", @@ -67,7 +67,7 @@ public void testBeanTags() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_bean_tags", + "dd.internal.jmx_check_name:jmx_bean_tags", "bean_host:localhost", "component"); @@ -97,7 +97,7 @@ public void testBeanTagsNormalizeParams() throws Exception { "scope:CoolScope", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_bean_tags_normalize_params", + "dd.internal.jmx_check_name:jmx_bean_tags_normalize_params", "bean_host:localhost", "component", "target_instance:.*example.process.regex.*"); @@ -128,7 +128,7 @@ public void testBeanTagsDontNormalizeParams() throws Exception { "scope:\"CoolScope\"", "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_bean_tags_dont_normalize_params", + "dd.internal.jmx_check_name:jmx_bean_tags_dont_normalize_params", "bean_host:\"localhost\"", "component", "target_instance:\".\\*example.process.regex.\\*\""); @@ -156,7 +156,7 @@ public void testRegexpAliasing() throws Exception { List tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_alias_match", + "dd.internal.jmx_check_name:jmx_alias_match", "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); @@ -191,7 +191,7 @@ public void testNoAliasOnDetailedAttribute() throws Exception { Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", "instance:jmx_test_instance", - "jmx_check_name:jmx_no_alias", + "dd.internal.jmx_check_name:jmx_no_alias", "foo:Bar", "qux:Baz"); @@ -226,7 +226,7 @@ public void testCassandraBean() throws Exception { "keyspace:MyKeySpace", "ColumnFamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.metrics", - "jmx_check_name:jmx_cassandra", + "dd.internal.jmx_check_name:jmx_cassandra", "instance:jmx_first_instance"); assertMetric("cassandra.pending_tasks.should_be100", tags, 6); @@ -238,7 +238,7 @@ public void testCassandraBean() throws Exception { "scope:MyColumnFamily", "keyspace:MyKeySpace", "jmx_domain:org.apache.cassandra.metrics", - "jmx_check_name:jmx_cassandra", + "dd.internal.jmx_check_name:jmx_cassandra", "instance:jmx_second_instance", "name:PendingTasks"); @@ -266,7 +266,7 @@ public void testCassandraDeprecatedBean() throws Exception { "keyspace:MyKeySpace", "columnfamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.db", - "jmx_check_name:jmx_cassandra_deprecated", + "dd.internal.jmx_check_name:jmx_cassandra_deprecated", "instance:jmx_test_instance"); assertMetric("cassandra.db.should_be100", tags, 6); @@ -536,7 +536,7 @@ public void testMetricTypes() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test", - "jmx_check_name:jmx_histogram"); + "dd.internal.jmx_check_name:jmx_histogram"); // 15 = 13 metrics from java.lang + the 3 collected (gauge and histogram) assertEquals(16, metrics.size()); @@ -571,7 +571,7 @@ public void testExcludeTags() throws Exception { // We test for the presence and the value of the metrics we want to collect. // Tags "type", "newTag" and "env" should be excluded List commonTags = - Arrays.asList("instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", "jmx_check_name:jmx_exclude_tags"); + Arrays.asList("instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", "dd.internal.jmx_check_name:jmx_exclude_tags"); // 15 = 13 metrics from java.lang + the 2 collected (gauge and histogram) assertEquals(15, metrics.size()); @@ -597,7 +597,7 @@ public void testExcludeServiceTagsAndOverride() throws Exception { List commonTags = Arrays.asList("instance:jmx_test_service_override_instance", "jmx_domain:org.datadog.jmxfetch.test","service:test", - "jmx_check_name:jmx_exclude_tags_override_service"); + "dd.internal.jmx_check_name:jmx_exclude_tags_override_service"); // 15 = 13 metrics from java.lang + the 2 collected (gauge and histogram) assertEquals(15, metrics.size()); @@ -624,7 +624,7 @@ public void testAdditionalTags() throws Exception { Arrays.asList( "instance:jmx_test_instance", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_additional_tags", + "dd.internal.jmx_check_name:jmx_additional_tags", "type:SimpleTestJavaApp", "name:testName", "simple:SimpleTestJavaApp", @@ -661,7 +661,7 @@ public void testApp() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = - Arrays.asList("instance:jmx_test_instance", "jmx_check_name:jmx", "env:stage", "newTag:test"); + Arrays.asList("instance:jmx_test_instance", "dd.internal.jmx_check_name:jmx", "env:stage", "newTag:test"); assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 9); assertMetric( @@ -778,7 +778,7 @@ public void testAppCanonicalRate() throws Exception { // We test for the presence and the value of the metrics we want to collect List commonTags = - Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test", "jmx_check_name:jmx_canonical"); + Arrays.asList("instance:jmx_test_instance", "env:stage", "newTag:test", "dd.internal.jmx_check_name:jmx_canonical"); assertMetric("this.is.100", 100.0, commonTags, Arrays.asList("foo", "gorch", "bar:baz"), 9); assertMetric( @@ -977,7 +977,7 @@ public void testServiceDiscovery() throws Exception { "scope:CoolScope", "instance:jmx_test_instance2", "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:AD-jmx_0", + "dd.internal.jmx_check_name:AD-jmx_0", "bean_host:localhost", "component" ); @@ -991,7 +991,7 @@ public void testServiceDiscovery() throws Exception { "keyspace:MyKeySpace", "ColumnFamily:MyColumnFamily", "jmx_domain:org.apache.cassandra.metrics", - "jmx_check_name:AD-cassandra_0", + "dd.internal.jmx_check_name:AD-cassandra_0", "instance:jmx_first_instance"); assertMetric("cassandra.pending_tasks.should_be100", tags, 6); @@ -1003,7 +1003,7 @@ public void testServiceDiscovery() throws Exception { "scope:MyColumnFamily", "keyspace:MyKeySpace", "jmx_domain:org.apache.cassandra.metrics", - "jmx_check_name:AD-cassandra_0", + "dd.internal.jmx_check_name:AD-cassandra_0", "instance:jmx_second_instance", "name:PendingTasks"); @@ -1013,7 +1013,7 @@ public void testServiceDiscovery() throws Exception { tags = Arrays.asList( "jmx_domain:org.datadog.jmxfetch.test", - "jmx_check_name:jmx_alias_match", + "dd.internal.jmx_check_name:jmx_alias_match", "instance:jmx_test_instance1", "foo:Bar", "qux:Baz"); From 33e7e94251302da8b4b57e6ada776f58e0a02ae0 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:49:35 -0500 Subject: [PATCH 42/48] Add JMXFetch telemetry around bean/attribute matching (#487) * add bean attribute instance stats * rename fields * remove throw for MalformedObjectNameException * remove throw for MalformedObjectNameException pt2 * fix status * fix status pt2 * remove domains queried --- .../java/org/datadog/jmxfetch/Instance.java | 47 ++++++++++++++----- .../java/org/datadog/jmxfetch/Status.java | 4 ++ .../jmxfetch/util/InstanceTelemetry.java | 18 +++++++ .../jmxfetch/util/InstanceTelemetryMBean.java | 4 ++ .../java/org/datadog/jmxfetch/StatusTest.java | 6 +++ 5 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index ded593d60..1cc3b91f9 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -509,13 +509,18 @@ public List getMetrics() throws IOException { } } } - instanceTelemetryBean.setBeansFetched(beans.size()); - instanceTelemetryBean.setTopLevelAttributeCount(matchingAttributes.size()); - instanceTelemetryBean.setMetricCount(metrics.size()); - log.debug("Updated jmx bean for instance: " + this.getCheckName() - + " With beans fetched = " + instanceTelemetryBean.getBeansFetched() - + " top attributes = " + instanceTelemetryBean.getTopLevelAttributeCount() - + " metrics = " + instanceTelemetryBean.getMetricCount()); + if (instanceTelemetryBean != null) { + instanceTelemetryBean.setBeansFetched(beans.size()); + instanceTelemetryBean.setTopLevelAttributeCount(matchingAttributes.size()); + instanceTelemetryBean.setMetricCount(metrics.size()); + log.debug("Updated jmx bean for instance: " + this.getCheckName() + + " With beans fetched = " + instanceTelemetryBean.getBeansFetched() + + " top attributes = " + instanceTelemetryBean.getTopLevelAttributeCount() + + " metrics = " + instanceTelemetryBean.getMetricCount() + + " wildcard domain query count = " + + instanceTelemetryBean.getWildcardDomainQueryCount() + + " bean match ratio = " + instanceTelemetryBean.getBeanMatchRatio()); + } return metrics; } @@ -547,11 +552,14 @@ private void getMatchingAttributes() throws IOException { this.failingAttributes.clear(); int metricsCount = 0; + int beansWithAttributeMatch = 0; + if (!action.equals(AppConfig.ACTION_COLLECT)) { reporter.displayInstanceName(this); } for (ObjectName beanName : this.beans) { + boolean attributeMatched = false; if (limitReached) { log.debug("Limit reached"); if (action.equals(AppConfig.ACTION_COLLECT)) { @@ -702,7 +710,17 @@ private void getMatchingAttributes() throws IOException { || action.equals(AppConfig.ACTION_LIST_NOT_MATCHING))) { reporter.displayNonMatchingAttributeName(jmxAttribute); } + if (jmxAttribute.getMatchingConf() != null) { + attributeMatched = true; + } } + if (attributeMatched) { + beansWithAttributeMatch += 1; + } + } + if (instanceTelemetryBean != null) { + instanceTelemetryBean.setBeanMatchRatio((double) + beansWithAttributeMatch / beans.size()); } log.info("Found {} matching attributes", matchingAttributes.size()); } @@ -733,14 +751,21 @@ private void refreshBeansList() throws IOException { ObjectName name = new ObjectName(scope); this.beans.addAll(connection.queryNames(name)); } - } catch (Exception e) { + } catch (MalformedObjectNameException e) { + log.error("Unable to create ObjectName", e); + } catch (IOException e) { log.error( - "Unable to compute a common bean scope, querying all beans as a fallback", - e); + "Unable to query mbean server", e); } } - this.beans = (this.beans.isEmpty()) ? connection.queryNames(null) : this.beans; + if (this.beans.isEmpty()) { + this.beans = connection.queryNames(null); + if (instanceTelemetryBean != null) { + int wildcardQueryCount = instanceTelemetryBean.getWildcardDomainQueryCount(); + instanceTelemetryBean.setWildcardDomainQueryCount(wildcardQueryCount + 1); + } + } this.lastRefreshTime = System.currentTimeMillis(); } diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index e6ecfc494..6a96a9031 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -127,6 +127,10 @@ private void addStats( instStats.put("instance_attribute_count", instanceTelemetryBean.getTopLevelAttributeCount()); instStats.put("instance_metric_count", instanceTelemetryBean.getMetricCount()); + instStats.put("instance_wildcard_domain_query_count", + instanceTelemetryBean.getWildcardDomainQueryCount()); + instStats.put("instance_bean_match_ratio", + instanceTelemetryBean.getBeanMatchRatio()); } instStats.put("message", message); instStats.put("status", status); diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java index b216b54f5..3cbb938fd 100644 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java @@ -7,12 +7,16 @@ public class InstanceTelemetry implements InstanceTelemetryMBean { private int beansFetched; private int topLevelAttributeCount; private int metricCount; + private int wildcardDomainQueryCount; + private double beanMatchRatio; /** Jmxfetch telemetry bean constructor. */ public InstanceTelemetry() { beansFetched = 0; topLevelAttributeCount = 0; metricCount = 0; + wildcardDomainQueryCount = 0; + beanMatchRatio = 0.0; } public int getBeansFetched() { @@ -27,6 +31,13 @@ public int getMetricCount() { return metricCount; } + public int getWildcardDomainQueryCount() { + return wildcardDomainQueryCount; + } + + public double getBeanMatchRatio() { + return beanMatchRatio; + } public void setBeansFetched(int count) { beansFetched = count; @@ -40,5 +51,12 @@ public void setMetricCount(int count) { metricCount = count; } + public void setWildcardDomainQueryCount(int count) { + wildcardDomainQueryCount = count; + } + + public void setBeanMatchRatio(double ratio) { + beanMatchRatio = ratio; + } } diff --git a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java index b80d126c0..4c98b5200 100644 --- a/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java +++ b/src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java @@ -8,4 +8,8 @@ public interface InstanceTelemetryMBean { int getMetricCount(); + int getWildcardDomainQueryCount(); + + double getBeanMatchRatio(); + } diff --git a/src/test/java/org/datadog/jmxfetch/StatusTest.java b/src/test/java/org/datadog/jmxfetch/StatusTest.java index 471aac120..f8a8b06ec 100644 --- a/src/test/java/org/datadog/jmxfetch/StatusTest.java +++ b/src/test/java/org/datadog/jmxfetch/StatusTest.java @@ -33,10 +33,14 @@ public void TestStatus() throws IOException { int fakeBeansFetched = 11; int fakeMetricCount = 29; int fakeAttributeCount = 55; + int fakeWildcardDomainQueryCount = 9; + double fakeBeanMatchRatio = .4; instance.setBeansFetched(fakeBeansFetched); instance.setMetricCount(fakeMetricCount); instance.setTopLevelAttributeCount(fakeAttributeCount); + instance.setWildcardDomainQueryCount(fakeWildcardDomainQueryCount); + instance.setBeanMatchRatio(fakeBeanMatchRatio); status.addInstanceStats("fake_check", "fake_instance", 10, 3, "fake_message", Status.STATUS_OK, instance); status.flush(); @@ -55,6 +59,8 @@ public void TestStatus() throws IOException { assertEquals(fakeBeansFetched, stats.get("instance_bean_count")); assertEquals(fakeAttributeCount, stats.get("instance_attribute_count")); assertEquals(fakeMetricCount, stats.get("instance_metric_count")); + assertEquals(fakeWildcardDomainQueryCount, stats.get("instance_wildcard_domain_query_count")); + assertEquals(fakeBeanMatchRatio, stats.get("instance_bean_match_ratio")); assertEquals("fake_message", stats.get("message")); assertEquals(Status.STATUS_OK, stats.get("status")); } From b6ec32e56a4d92807f075514bb07191aaca8c0e0 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Fri, 10 Nov 2023 11:01:45 -0500 Subject: [PATCH 43/48] Removes periodic re-init of statsd reporter (#422) --- .../datadog/jmxfetch/reporter/StatsdReporter.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/reporter/StatsdReporter.java b/src/main/java/org/datadog/jmxfetch/reporter/StatsdReporter.java index 68e3890cf..8ce63eeae 100644 --- a/src/main/java/org/datadog/jmxfetch/reporter/StatsdReporter.java +++ b/src/main/java/org/datadog/jmxfetch/reporter/StatsdReporter.java @@ -17,7 +17,6 @@ public class StatsdReporter extends Reporter { private int statsdPort; private Boolean telemetry; private int queueSize; - private long initializationTime; private boolean nonBlocking; private int socketBufferSize; private int socketTimeout; @@ -37,8 +36,6 @@ public StatsdReporter(String statsdHost, int statsdPort, boolean telemetry, int } private void init() { - initializationTime = System.currentTimeMillis(); - // Only set the entityId to "none" if UDS communication is activated String entityId = this.statsdPort == 0 ? "none" : null; int defaultUdsDatagramSize = 8192; @@ -99,10 +96,6 @@ private void init() { protected void sendMetricPoint( String metricType, String metricName, double value, String[] tags) { - if (System.currentTimeMillis() - this.initializationTime > 300 * 1000) { - this.statsDClient.stop(); - init(); - } if (metricType.equals("monotonic_count")) { statsDClient.count(metricName, (long) value, tags); } else if (metricType.equals("histogram")) { @@ -115,11 +108,6 @@ protected void sendMetricPoint( /** Submits service check. */ public void doSendServiceCheck( String serviceCheckName, String status, String message, String[] tags) { - if (System.currentTimeMillis() - this.initializationTime > 300 * 1000) { - this.statsDClient.stop(); - init(); - } - ServiceCheck sc = ServiceCheck.builder() .withName(serviceCheckName) .withStatus(this.statusToServiceCheckStatus(status)) From 46425f1544dc25008b7c56b8e278bba68e0f9a76 Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Fri, 10 Nov 2023 13:14:33 -0500 Subject: [PATCH 44/48] Preparing 0.49.0 release (#496) --- CHANGELOG.md | 4 ++++ README.md | 2 +- pom.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c38e8ea2..c225e6d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Changelog ========= # Next / TBD +# 0.49.0 / 2023-11-10 +* [FEATURE] Adds more per-instance telemetry data around bean matching +* [BUGFIX] Removes un-necessary statsd reinit + # 0.48.0 / 2023-09-26 * [FEATURE] Adds a configurable jmxfetch telemetry check to improve jmxfetch observability [#467][] diff --git a/README.md b/README.md index e9bb9cd2e..118e67777 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.49.0-SNAPSHOT-jar-with-dependencies.jar --help +java -jar jmxfetch-0.49.0-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index 3836d3c8c..e5300e3ad 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.49.0-SNAPSHOT + 0.49.0 jar jmxfetch From 990f21dc1ea0a122312616b199f9fbe0515472ee Mon Sep 17 00:00:00 2001 From: Carlos Roman Date: Wed, 15 Nov 2023 12:03:24 +0000 Subject: [PATCH 45/48] Changing release back to a snapshot --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 118e67777..c44ad5332 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ otherwise the subsequent publishes will fail. ``` Get help on usage: -java -jar jmxfetch-0.49.0-jar-with-dependencies.jar --help +java -jar jmxfetch-0.49.1-SNAPSHOT-jar-with-dependencies.jar --help ``` ## Updating Maven Wrapper diff --git a/pom.xml b/pom.xml index e5300e3ad..18cd989b3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.datadoghq jmxfetch - 0.49.0 + 0.49.1-SNAPSHOT jar jmxfetch From deab2441d9cb3da1078a00812ee3573c4c69c420 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <35050708+rayz@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:57:23 -0500 Subject: [PATCH 46/48] Make misbehaving-jmx-server more deterministic (#497) --- ...ns-config.yaml => misbehaving-config.yaml} | 2 +- .../org/datadog/misbehavingjmxserver/App.java | 43 ++++++++++++------- .../misbehavingjmxserver/BeanManager.java | 5 ++- .../RandomIdentifier.java | 6 ++- 4 files changed, 37 insertions(+), 19 deletions(-) rename tools/misbehaving-jmx-server/{misbehaving-jmx-domains-config.yaml => misbehaving-config.yaml} (96%) diff --git a/tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml b/tools/misbehaving-jmx-server/misbehaving-config.yaml similarity index 96% rename from tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml rename to tools/misbehaving-jmx-server/misbehaving-config.yaml index ef01bf8fe..8481a9c32 100644 --- a/tools/misbehaving-jmx-server/misbehaving-jmx-domains-config.yaml +++ b/tools/misbehaving-jmx-server/misbehaving-config.yaml @@ -10,4 +10,4 @@ domains: tabularAttributeCount: 5 compositeValuesPerTabularAttribute: 10 - +seed: 12345 diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java index 5e312a704..2597dbce5 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java @@ -6,14 +6,11 @@ import java.io.FileNotFoundException; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import javax.management.InstanceAlreadyExistsException; @@ -21,7 +18,6 @@ import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; -import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; @@ -45,13 +41,16 @@ class AppConfig { @Parameter(names = {"--rmi-host", "-rh"}) public String rmiHost = Defaults.JMXSERVER_RMI_INTERFACE; + @Parameter(names = {"--rng-seed", "-rs"}) + public Long rngSeed = 54321L; + // Can only be set via env var public int controlPort = Defaults.JMXSERVER_CONTROL_PORT; @Parameter(names = {"--config-path", "-cfp"}) - public String config_path = "./misbehaving-jmx-domains-config.yaml"; + public String config_path = "./misbehaving-config.yaml"; - public JmxDomainConfigurations jmxDomainConfigurations; + public Configuration jmxConfiguration; public void overrideFromEnv() { String val; @@ -71,6 +70,10 @@ public void overrideFromEnv() { if (val != null) { this.config_path = val; } + val = System.getenv("RNG_SEED"); + if (val != null) { + this.rngSeed = Long.parseLong(val); + } } public void readConfigFileOnDisk () { @@ -78,18 +81,20 @@ public void readConfigFileOnDisk () { String yamlPath = f.getPath(); try{ FileInputStream yamlInputStream = new FileInputStream(yamlPath); - Yaml yaml = new Yaml(new Constructor(JmxDomainConfigurations.class)); - jmxDomainConfigurations = yaml.load(yamlInputStream); - log.info("JmxDomainConfigurations read from " + config_path + " is:\n" + jmxDomainConfigurations); + Yaml yaml = new Yaml(new Constructor(Configuration.class)); + jmxConfiguration = yaml.load(yamlInputStream); + log.info("Configuration read from " + config_path + " is:\n" + jmxConfiguration); } catch (FileNotFoundException e) { log.warn("Could not find your config file at " + yamlPath); - jmxDomainConfigurations = null; + jmxConfiguration = null; } } + } -class JmxDomainConfigurations { +class Configuration { public Map domains; + public Long seed; @Override public String toString() { @@ -99,7 +104,11 @@ public String toString() { result.append("Domain: " + entry.getKey() + entry.getValue().toString() + "\n"); } } else { - return "No valid domain configurations"; + result.append("No valid domain configurations\n"); + } + + if (seed != null) { + result.append("RNG Seed: " + seed + "\n"); } return result.toString(); @@ -168,15 +177,19 @@ public static void main( String[] args ) throws IOException, MalformedObjectName MetricDAO mDao = new MetricDAO(); mDao.runTickLoop(); - BeanManager bm = new BeanManager(mbs, mDao); + if (config.jmxConfiguration != null && config.jmxConfiguration.seed != null) { + config.rngSeed = config.jmxConfiguration.seed; + } + log.info("RNG initializing with seed: {}", config.rngSeed); + BeanManager bm = new BeanManager(mbs, mDao, config.rngSeed); // Set up test domain BeanSpec testDomainBeanSpec = new BeanSpec(1, 1, 0, 0); bm.setMBeanState(testDomain, testDomainBeanSpec); // Set up initial beans for all the domains found in config file - if (config.jmxDomainConfigurations != null){ - for (Map.Entry entry: config.jmxDomainConfigurations.domains.entrySet()) { + if (config.jmxConfiguration != null){ + for (Map.Entry entry: config.jmxConfiguration.domains.entrySet()) { bm.setMBeanState(entry.getKey(), entry.getValue()); } } diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java index 90e96173c..92ec83619 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java @@ -25,13 +25,15 @@ public class BeanManager { private final MBeanServer mBeanServer; private final Map> registeredBeans; private final MetricDAO mDao; + private final RandomIdentifier idGen; static final long ATTRIBUTE_REFRESH_INTERVAL = 10; private final ScheduledExecutorService executor; - public BeanManager(MBeanServer mBeanServer, MetricDAO mDao) { + public BeanManager(MBeanServer mBeanServer, MetricDAO mDao, long rngSeed) { this.mBeanServer = mBeanServer; this.registeredBeans = new HashMap<>(); this.mDao = mDao; + this.idGen = new RandomIdentifier(rngSeed); this.executor = Executors.newSingleThreadScheduledExecutor(); runAttributeUpdateLoop(); } @@ -60,7 +62,6 @@ public void clearDomainBeans(String beanDomain){ public void setMBeanState(String beanDomain, BeanSpec domainSpec) { clearDomainBeans(beanDomain); - RandomIdentifier idGen = new RandomIdentifier(); ArrayList beansList = new ArrayList(); for (int i = 0; i < domainSpec.beanCount; i++) { diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/RandomIdentifier.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/RandomIdentifier.java index d899180a7..6a4afa01e 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/RandomIdentifier.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/RandomIdentifier.java @@ -14,7 +14,11 @@ public class RandomIdentifier { private static final List VOWELS = List.of("a", "e", "i", "o", "u"); private static final List CONSONANTS = List.of("b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"); private static final List RARE_SEQUENCES = Arrays.asList("qz", "qp", "qj", "qw", "qx", "qr", "qt", "qy", "qs", "qd", "qf", "qg", "qh", "qk", "ql", "qc", "qv", "qb", "qn", "qm", "wz", "wq", "wk", "wp", "wj", "wy", "wh", "wl"); - private static final Random RANDOM = new Random(); + private final Random RANDOM; + + public RandomIdentifier(long seed) { + this.RANDOM = new Random(seed); + } private String generateWord(int minLength, int maxLength) { int wordLength = minLength + RANDOM.nextInt(maxLength - minLength + 1); From 97afdbfb1f1ace8d29964c4479b6e5f523142dfe Mon Sep 17 00:00:00 2001 From: Scott Opell Date: Mon, 11 Dec 2023 06:08:23 -0500 Subject: [PATCH 47/48] Adds sdkman conf file and usage instructions (#499) * Adds sdkman conf file and usage instructions * Update README.md Co-authored-by: Austin Lai <76412946+alai97@users.noreply.github.com> --------- Co-authored-by: Austin Lai <76412946+alai97@users.noreply.github.com> --- .sdkmanrc | 8 ++++++++ README.md | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 000000000..d633f98b8 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,8 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +# +# Java version currently tracking what is present in -jmx agent builds +# Agent is currently installing 'openjdk-11-jre-headless' from debian +# See https://github.com/DataDog/datadog-agent/blob/3b6f07cb7d097837a7b9247c6119ead3309116ab/Dockerfiles/agent/Dockerfile#L136 +# and https://packages.debian.org/sid/openjdk-11-jre-headless +java=11.0.21-amzn diff --git a/README.md b/README.md index c44ad5332..441072bd9 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,13 @@ JMXFetch uses [Lombok](https://projectlombok.org/) to modify classes and generat You may need to [enable annotation processors](https://projectlombok.org/setup/overview) to compile in your IDE. ## Useful Developer Settings + +### JDK version management +[`sdkman`](https://sdkman.io/install) is recommended to manage multiple versions of Java. +If you are an sdkman user, there is a config file present in this project with +the recommended JDK version for development, use `sdk env` to activate it. + + ### Enabling file line numbers in log messages If you set the system property `-Djmxfetch.filelinelogging=true`, this will enable all log output to include the line number which emitted a given log. From 5b7b83843ae890eeb4cb8e0223dad298612c43c7 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 22 Dec 2023 12:25:11 +0000 Subject: [PATCH 48/48] AMLII-1148 - Adding Unit test to check GC metrics emitted by each type of Java garbage collector (#500) * Added ability to change the final Docker image for misbehaving-jmx-server * Added some tests to verify GC metrics --------- Co-authored-by: DeForest Richards <56796055+drichards-87@users.noreply.github.com> --- pom.xml | 8 +- .../java/org/datadog/jmxfetch/Instance.java | 1 - .../java/org/datadog/jmxfetch/TestCommon.java | 58 +---- .../org/datadog/jmxfetch/TestGCMetrics.java | 214 ++++++++++++++++++ .../jmxfetch/TestReconnectContainer.java | 28 +-- .../datadog/jmxfetch/util/MetricsAssert.java | 99 ++++++++ .../jmxfetch/util/server/JDKImage.java | 19 ++ .../util/server/MisbehavingJMXServer.java | 165 ++++++++++++++ .../jmxfetch/util/server/SimpleApp.java | 95 ++++++++ .../util/server/SimpleAppContainer.java | 68 ++++++ .../jmxfetch/util/server/Dockerfile-SimpleApp | 15 ++ .../jmxfetch/util/server/run-SimpleApp.sh | 26 +++ tools/misbehaving-jmx-server/Dockerfile | 29 ++- tools/misbehaving-jmx-server/README.md | 1 + tools/misbehaving-jmx-server/pom.xml | 14 +- tools/misbehaving-jmx-server/scripts/start.sh | 15 ++ .../org/datadog/misbehavingjmxserver/App.java | 5 +- .../main/java/org/datadog/supervisor/App.java | 18 +- 18 files changed, 790 insertions(+), 88 deletions(-) create mode 100644 src/test/java/org/datadog/jmxfetch/TestGCMetrics.java create mode 100644 src/test/java/org/datadog/jmxfetch/util/MetricsAssert.java create mode 100644 src/test/java/org/datadog/jmxfetch/util/server/JDKImage.java create mode 100644 src/test/java/org/datadog/jmxfetch/util/server/MisbehavingJMXServer.java create mode 100644 src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java create mode 100644 src/test/java/org/datadog/jmxfetch/util/server/SimpleAppContainer.java create mode 100644 src/test/resources/org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp create mode 100755 src/test/resources/org/datadog/jmxfetch/util/server/run-SimpleApp.sh create mode 100755 tools/misbehaving-jmx-server/scripts/start.sh diff --git a/pom.xml b/pom.xml index 18cd989b3..3b1ab826d 100644 --- a/pom.xml +++ b/pom.xml @@ -200,7 +200,13 @@ org.testcontainers testcontainers - 1.18.0 + 1.19.3 + test + + + org.hamcrest + hamcrest-library + 1.3 test diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 1cc3b91f9..9da8de750 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -16,7 +16,6 @@ import java.io.InputStream; import java.lang.management.ManagementFactory; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; diff --git a/src/test/java/org/datadog/jmxfetch/TestCommon.java b/src/test/java/org/datadog/jmxfetch/TestCommon.java index edb8bde68..49fb77a12 100644 --- a/src/test/java/org/datadog/jmxfetch/TestCommon.java +++ b/src/test/java/org/datadog/jmxfetch/TestCommon.java @@ -1,9 +1,5 @@ package org.datadog.jmxfetch; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -19,11 +15,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.MBeanRegistrationException; @@ -31,12 +24,15 @@ import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; + +import org.junit.After; +import org.junit.BeforeClass; + import org.datadog.jmxfetch.reporter.ConsoleReporter; import org.datadog.jmxfetch.reporter.Reporter; import org.datadog.jmxfetch.util.CustomLogger; +import org.datadog.jmxfetch.util.MetricsAssert; import org.datadog.jmxfetch.util.LogLevel; -import org.junit.After; -import org.junit.BeforeClass; final class ConfigUtil { public static Path writeConfigYamlToTemp(String content, String yamlName) throws IOException { @@ -261,49 +257,7 @@ public void assertMetric( List additionalTags, int countTags, String metricType) { - List tags = new ArrayList(commonTags); - tags.addAll(additionalTags); - - for (Map m : metrics) { - String mName = (String) (m.get("name")); - Double mValue = (Double) (m.get("value")); - Set mTags = new HashSet(Arrays.asList((String[]) (m.get("tags")))); - - if (mName.equals(name)) { - - if (!value.equals(-1)) { - assertEquals((Double) value.doubleValue(), mValue); - } else if (!lowerBound.equals(-1) || !upperBound.equals(-1)) { - assertTrue(mValue > (Double) lowerBound.doubleValue()); - assertTrue(mValue < (Double) upperBound.doubleValue()); - } - - if (countTags != -1) { - assertEquals(countTags, mTags.size()); - } - for (String t : tags) { - assertThat(mTags, hasItem(t)); - } - - if (metricType != null) { - assertEquals(metricType, m.get("type")); - } - // Brand the metric - m.put("tested", true); - - return; - } - } - fail( - "Metric assertion failed (name: " - + name - + ", value: " - + value - + ", tags: " - + tags - + ", #tags: " - + countTags - + ")."); + MetricsAssert.assertMetric(name, value, lowerBound, upperBound, commonTags, additionalTags, countTags, metricType, this.metrics); } public void assertMetric( diff --git a/src/test/java/org/datadog/jmxfetch/TestGCMetrics.java b/src/test/java/org/datadog/jmxfetch/TestGCMetrics.java new file mode 100644 index 000000000..fdb5f4d45 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/TestGCMetrics.java @@ -0,0 +1,214 @@ +package org.datadog.jmxfetch; + +import static org.datadog.jmxfetch.util.MetricsAssert.*; +import static org.datadog.jmxfetch.util.server.JDKImage.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.Test; + +import org.datadog.jmxfetch.reporter.ConsoleReporter; +import org.datadog.jmxfetch.util.MetricsAssert; +import org.datadog.jmxfetch.util.server.MisbehavingJMXServer; +import org.datadog.jmxfetch.util.server.SimpleAppContainer; + +@Slf4j +public class TestGCMetrics extends TestCommon { + + @Test + public void testJMXDirectBasic() throws Exception { + try (final SimpleAppContainer container = new SimpleAppContainer()) { + container.start(); + final String ipAddress = container.getIp(); + final String remoteJmxServiceUrl = String.format( + "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", ipAddress, container.getRMIPort()); + final JMXServiceURL jmxUrl = new JMXServiceURL(remoteJmxServiceUrl); + final JMXConnector conn = JMXConnectorFactory.connect(jmxUrl); + final MBeanServerConnection mBeanServerConnection = conn.getMBeanServerConnection(); + assertDomainPresent("java.lang", mBeanServerConnection); + } + } + + @Test + public void testDefaultOldGC() throws IOException { + try (final MisbehavingJMXServer server = new MisbehavingJMXServer.Builder().build()) { + final List> actualMetrics = startAndGetMetrics(server, false); + List gcGenerations = Arrays.asList( + "G1 Old Generation", + "G1 Young Generation"); + assertGCMetric(actualMetrics, "jvm.gc.cms.count", gcGenerations); + assertGCMetric(actualMetrics, "jvm.gc.parnew.time", gcGenerations); + } + } + + @Test + public void testDefaultNewGCMetricsUseParallelGC() throws IOException { + try (final MisbehavingJMXServer server = new MisbehavingJMXServer.Builder().withJDKImage( + JDK_11).appendJavaOpts("-XX:+UseParallelGC").build()) { + final List> actualMetrics = startAndGetMetrics(server, true); + assertThat(actualMetrics, hasSize(13)); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_count", "PS Scavenge", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_time", "PS Scavenge", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_count", "PS MarkSweep", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_time", "PS MarkSweep", "counter"); + } + } + + @Test + public void testDefaultNewGCMetricsUseConcMarkSweepGC() throws IOException { + try (final MisbehavingJMXServer server = new MisbehavingJMXServer.Builder().withJDKImage( + JDK_11).appendJavaOpts("-XX:+UseConcMarkSweepGC").build()) { + final List> actualMetrics = startAndGetMetrics(server, true); + assertThat(actualMetrics, hasSize(13)); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_count", "ParNew", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_time", "ParNew", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_count", "ConcurrentMarkSweep", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_time", "ConcurrentMarkSweep", "counter"); + } + } + + @Test + public void testDefaultNewGCMetricsUseG1GC() throws IOException { + try (final MisbehavingJMXServer server = new MisbehavingJMXServer.Builder().withJDKImage( + JDK_17).appendJavaOpts("-XX:+UseG1GC").build()) { + final List> actualMetrics = startAndGetMetrics(server, true); + assertThat(actualMetrics, hasSize(13)); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_count", "G1 Young Generation", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.minor_collection_time", "G1 Young Generation", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_count", "G1 Old Generation", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.major_collection_time", "G1 Old Generation", "counter"); + } + } + + @Test + public void testDefaultNewGCMetricsUseZGC() throws IOException { + try (final MisbehavingJMXServer server = new MisbehavingJMXServer.Builder().withJDKImage( + JDK_17).appendJavaOpts("-XX:+UseZGC").build()) { + final List> actualMetrics = startAndGetMetrics(server, true); + assertThat(actualMetrics, hasSize(13)); + assertGCMetric(actualMetrics, + "jvm.gc.zgc_pauses_collection_count", "ZGC Pauses", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.zgc_pauses_collection_time", "ZGC Pauses", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.zgc_cycles_collection_count", "ZGC Cycles", "counter"); + assertGCMetric(actualMetrics, + "jvm.gc.zgc_cycles_collection_time", "ZGC Cycles", "counter"); + } + } + + private List> startAndGetMetrics(final MisbehavingJMXServer server, + final boolean newGCMetrics) throws IOException { + server.start(); + this.initApplicationWithYamlLines( + "init_config:", + " is_jmx: true", + " new_gc_metrics: " + newGCMetrics, + "", + "instances:", + " - name: jmxint_container", + " host: " + server.getIp(), + " collect_default_jvm_metrics: true", + " max_returned_metrics: 300000", + " port: " + server.getRMIPort()); + // Run one iteration first + // TODO: Investigate why we have to run this twice - AMLII-1353 + this.app.doIteration(); + // And then pull get the metrics or else reporter does not have correct number of metrics + ((ConsoleReporter) appConfig.getReporter()).getMetrics(); + + // Actual iteration we care about + this.app.doIteration(); + return ((ConsoleReporter) appConfig.getReporter()).getMetrics(); + } + + private static void assertGCMetric(final List> actualMetrics, + final String expectedMetric, + final String gcGeneration, + final String metricType) { + MetricsAssert.assertMetric( + expectedMetric, + -1, + -1, + 10.0, + Collections.singletonList(String.format("name:%s", gcGeneration)), + Arrays.asList( + "instance:jmxint_container", + "jmx_domain:java.lang", + "type:GarbageCollector"), + 5, + metricType, + actualMetrics); + } + + /* + This function is needed as the TestGCMetrics.testDefaultOldGC asserts on two metrics that have + different tags. MetricsAssert.assertMetric expects metrics to have unique names so can't be used + to verify correctly G1 Old Generation and G1 Young Generation for the metric jvm.gc.cms.count and + jvm.gc.parnew.time. + */ + private static void assertGCMetric(final List> actualMetrics, + final String expectedMetric, + final List gcGenerations) { + final List> filteredMetrics = new ArrayList<>(); + for (Map actualMetric : actualMetrics) { + final String name = (String) actualMetric.get("name"); + if(expectedMetric.equals(name)) { + filteredMetrics.add(actualMetric); + } + } + assertThat(filteredMetrics, hasSize(gcGenerations.size())); + for (final String name : gcGenerations) { + log.debug("Asserting for metric '{}'", name); + boolean found = false; + for (Map filteredMetric : filteredMetrics) { + final Set mTags = new HashSet<>( + Arrays.asList((String[]) (filteredMetric.get("tags")))); + + if(mTags.contains(String.format("name:%s", name))) { + assertThat(mTags, not(empty())); + assertThat(mTags, hasSize(5)); + log.debug("mTags '{}' has size: {}\n{}", name, mTags.size(), mTags); + assertThat(mTags, hasItems( + "instance:jmxint_container", + "jmx_domain:java.lang", + "type:GarbageCollector", + String.format("name:%s", name))); + found = true; + } + } + assertThat(String.format("Did not find metric '%s'", name), found, is(true)); + } + } +} diff --git a/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java b/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java index 518bb4b8c..1c964529e 100644 --- a/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java +++ b/src/test/java/org/datadog/jmxfetch/TestReconnectContainer.java @@ -1,6 +1,10 @@ package org.datadog.jmxfetch; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import static org.datadog.jmxfetch.util.MetricsAssert.assertDomainPresent; +import static org.datadog.jmxfetch.util.MetricsAssert.isDomainPresent; import java.io.IOException; import java.util.Collections; @@ -12,7 +16,6 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -39,21 +42,6 @@ public class TestReconnectContainer extends TestCommon { private JMXServerSupervisorClient supervisorClient; private static Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log); - private static boolean isDomainPresent(String domain, MBeanServerConnection mbs) { - boolean found = false; - try { - String[] domains = mbs.getDomains(); - for (int i = 0; i < domains.length; i++) { - if (domains[i].equals(domain)) { - found = true; - } - } - } catch (IOException e) { - found = false; - } - return found; - } - private static ImageFromDockerfile img = new ImageFromDockerfile() .withFileFromPath(".", Paths.get("./tools/misbehaving-jmx-server/")); @@ -101,7 +89,7 @@ public void testJMXDirectBasic() throws Exception { JMXConnector conn = JMXConnectorFactory.connect(jmxUrl); MBeanServerConnection mBeanServerConnection = conn.getMBeanServerConnection(); - assertEquals(true, isDomainPresent("Bohnanza", mBeanServerConnection)); + assertDomainPresent("Bohnanza", mBeanServerConnection); } @Test @@ -117,15 +105,15 @@ public void testJMXDirectReconnect() throws Exception { JMXConnector conn = JMXConnectorFactory.connect(jmxUrl); MBeanServerConnection mBeanServerConnection = conn.getMBeanServerConnection(); - assertEquals(true, isDomainPresent("Bohnanza", mBeanServerConnection)); + assertDomainPresent("Bohnanza", mBeanServerConnection); this.controlClient.jmxCutNetwork(); - assertEquals(false, isDomainPresent("Bohnanza", mBeanServerConnection)); + assertFalse(isDomainPresent("Bohnanza", mBeanServerConnection)); this.controlClient.jmxRestoreNetwork(); - assertEquals(true, isDomainPresent("Bohnanza", mBeanServerConnection)); + assertDomainPresent("Bohnanza", mBeanServerConnection); } @Test diff --git a/src/test/java/org/datadog/jmxfetch/util/MetricsAssert.java b/src/test/java/org/datadog/jmxfetch/util/MetricsAssert.java new file mode 100644 index 000000000..41ca0a8ad --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/util/MetricsAssert.java @@ -0,0 +1,99 @@ +package org.datadog.jmxfetch.util; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.management.MBeanServerConnection; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MetricsAssert { + + public static void assertMetric( + String name, + Number value, + Number lowerBound, + Number upperBound, + List commonTags, + List additionalTags, + int countTags, + String metricType, + List> actualMetrics) { + List tags = new ArrayList<>(commonTags); + tags.addAll(additionalTags); + + for (Map m : actualMetrics) { + String mName = (String) (m.get("name")); + Double mValue = (Double) (m.get("value")); + Set mTags = new HashSet<>(Arrays.asList((String[]) (m.get("tags")))); + + if (mName.equals(name)) { + + if (!value.equals(-1)) { + assertEquals((Double) value.doubleValue(), mValue); + } else if (!lowerBound.equals(-1) || !upperBound.equals(-1)) { + assertTrue(mValue > (Double) lowerBound.doubleValue()); + assertTrue(mValue < (Double) upperBound.doubleValue()); + } + + if (countTags != -1) { + assertEquals(countTags, mTags.size()); + } + for (String t : tags) { + assertThat(mTags, hasItem(t)); + } + + if (metricType != null) { + assertEquals(metricType, m.get("type")); + } + // Brand the metric + m.put("tested", true); + + return; + } + } + fail( + "Metric assertion failed (name: " + + name + + ", value: " + + value + + ", tags: " + + tags + + ", #tags: " + + countTags + + ")."); + + } + + public static void assertDomainPresent(final String domain, final MBeanServerConnection mbs){ + assertThat(String.format("Could not find domain '%s'", domain), + isDomainPresent(domain, mbs), equalTo(true)); + } + + public static boolean isDomainPresent(final String domain, final MBeanServerConnection mbs) { + boolean found = false; + try { + final String[] domains = mbs.getDomains(); + for (String s : domains) { + if(s.equals(domain)) { + found = true; + break; + } + } + } catch (IOException e) { + log.warn("Got an exception checking if domain is present", e); + } + return found; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/util/server/JDKImage.java b/src/test/java/org/datadog/jmxfetch/util/server/JDKImage.java new file mode 100644 index 000000000..365f7bd5b --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/util/server/JDKImage.java @@ -0,0 +1,19 @@ +package org.datadog.jmxfetch.util.server; + +public enum JDKImage { + BASE("base"), + JDK_11("eclipse-temurin:11"), + JDK_17("eclipse-temurin:17"), + JDK_21("eclipse-temurin:21"); + + private final String image; + + private JDKImage(final String image) { + this.image = image; + } + + @Override + public String toString() { + return this.image; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/util/server/MisbehavingJMXServer.java b/src/test/java/org/datadog/jmxfetch/util/server/MisbehavingJMXServer.java new file mode 100644 index 000000000..7ebf69dda --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/util/server/MisbehavingJMXServer.java @@ -0,0 +1,165 @@ +package org.datadog.jmxfetch.util.server; + +import java.io.IOException; +import java.nio.file.Paths; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.lifecycle.Startable; + +import lombok.extern.slf4j.Slf4j; + +import org.datadog.jmxfetch.JMXServerControlClient; +import org.datadog.jmxfetch.JMXServerSupervisorClient; + +@Slf4j +public class MisbehavingJMXServer implements Startable { + + public static final int DEFAULT_RMI_PORT = 9090; + public static final int DEFAULT_CONTROL_PORT = 9091; + public static final int DEFAULT_SUPERVISOR_PORT = 9092; + private static final String DEFAULT_JDK_IMAGE = "base"; + private static final String DEFAULT_MISBEHAVING_OPTS = "-Xmx128M -Xms128M"; + private static final String RMI_PORT = "RMI_PORT"; + private static final String CONTROL_PORT = "CONTROL_PORT"; + private static final String SUPERVISOR_PORT = "SUPERVISOR_PORT"; + public static final String MISBEHAVING_OPTS = "MISBEHAVING_OPTS"; + private final String jdkImage; + + private final String javaOpts; + private final int rmiPort; + private final int controlPort; + private final int supervisorPort; + private final GenericContainer server; + private JMXServerControlClient controlClient; + private JMXServerSupervisorClient supervisorClient; + + public MisbehavingJMXServer( + final String jdkImage, + final String javaOpts, + final int rmiPort, + final int controlPort, + final int supervisorPort) { + this.javaOpts = javaOpts; + this.rmiPort = rmiPort; + this.controlPort = controlPort; + this.supervisorPort = supervisorPort; + this.jdkImage = jdkImage; + final ImageFromDockerfile img = new ImageFromDockerfile() + .withFileFromPath(".", Paths.get("./tools/misbehaving-jmx-server/")) + .withBuildArg("FINAL_JRE_IMAGE", this.jdkImage); + this.server = new GenericContainer<>(img) + .withEnv(RMI_PORT, String.valueOf(rmiPort)) + .withEnv(CONTROL_PORT, String.valueOf(controlPort)) + .withEnv(SUPERVISOR_PORT, String.valueOf(supervisorPort)) + .withEnv(MISBEHAVING_OPTS, this.javaOpts) + .waitingFor(Wait.forLogMessage( + ".*Supervisor HTTP Server Started. Waiting for initialization payload POST to /init.*", + 1)); + } + + @Override + public void start() { + log.info("Starting MisbehavingJMXServer with Docker image '{}' with MISBEHAVING_OPTS '{}'", + this.jdkImage, this.javaOpts); + this.server.start(); + final String ipAddress = this.getIp(); + this.controlClient = new JMXServerControlClient(ipAddress, this.controlPort); + this.supervisorClient = new JMXServerSupervisorClient(ipAddress, this.supervisorPort); + try { + log.debug("Initializing JMXServer"); + this.supervisorClient.initializeJMXServer(ipAddress); + } catch (IOException e) { + log.error("Could not initialize JMX Server", e); + } + } + + @Override + public void stop() { + this.server.stop(); + } + + @Override + public void close() { + this.stop(); + } + + public String getIp() { + return this.server.getContainerInfo().getNetworkSettings().getIpAddress(); + } + + public void cutNetwork() throws IOException { + this.controlClient.jmxCutNetwork(); + } + + public void restoreNetwork() throws IOException { + this.controlClient.jmxRestoreNetwork(); + } + + public int getRMIPort() { + return this.rmiPort; + } + + public static class Builder { + + private String jdkImage; + private String javaOpts; + private int rmiPort; + private int controlPort; + private int supervisorPort; + + public Builder() { + this.jdkImage = MisbehavingJMXServer.DEFAULT_JDK_IMAGE; + this.javaOpts = MisbehavingJMXServer.DEFAULT_MISBEHAVING_OPTS; + this.rmiPort = MisbehavingJMXServer.DEFAULT_RMI_PORT; + this.controlPort = MisbehavingJMXServer.DEFAULT_CONTROL_PORT; + this.supervisorPort = MisbehavingJMXServer.DEFAULT_SUPERVISOR_PORT; + } + + public Builder withJDKImage(final String jdkImage) { + this.jdkImage = jdkImage; + return this; + } + + public Builder withJDKImage(final JDKImage jdkImage) { + this.jdkImage = jdkImage.toString(); + return this; + } + + public Builder withJavaOpts(String javaOpts) { + this.javaOpts = javaOpts; + return this; + } + + public Builder appendJavaOpts(String javaOpts) { + this.javaOpts = String.format("%s %s", javaOpts, this.javaOpts); + return this; + } + + public Builder withRmiPort(int rmiPort) { + this.rmiPort = rmiPort; + return this; + } + + public Builder withControlPort(int controlPort) { + this.controlPort = controlPort; + return this; + } + + public Builder withSupervisorPort(int supervisorPort) { + this.supervisorPort = supervisorPort; + return this; + } + + public MisbehavingJMXServer build() { + return new MisbehavingJMXServer( + this.jdkImage, + this.javaOpts, + this.rmiPort, + this.controlPort, + this.supervisorPort + ); + } + } +} diff --git a/src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java b/src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java new file mode 100644 index 000000000..81c0daee2 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java @@ -0,0 +1,95 @@ +package org.datadog.jmxfetch.util.server; + +import java.lang.management.ManagementFactory; +import java.util.Hashtable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +// TODO: Create tests to check all supported versions of Java work with this server - AMLII-1354 +class SimpleApp { + public interface SampleMBean { + + Integer getShouldBe100(); + + Double getShouldBe1000(); + + Long getShouldBe1337(); + + Float getShouldBe1_1(); + + int getShouldBeCounter(); + } + + public static class Sample implements SampleMBean { + + private final AtomicInteger counter = new AtomicInteger(0); + + @Override + public Integer getShouldBe100() { + return 100; + } + + @Override + public Double getShouldBe1000() { + return 200.0; + } + + @Override + public Long getShouldBe1337() { + return 1337L; + } + + @Override + public Float getShouldBe1_1() { + return 1.1F; + } + + @Override + public int getShouldBeCounter() { + return this.counter.get(); + } + } + + public static void main(String[] args) { + System.out.println("Starting sample app..."); + try { + final Hashtable pairs = new Hashtable<>(); + pairs.put("name", "default"); + pairs.put("type", "simple"); + final Thread daemonThread = getThread(pairs); + daemonThread.start(); + daemonThread.join(); + } catch (MalformedObjectNameException | InstanceAlreadyExistsException | + MBeanRegistrationException | NotCompliantMBeanException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static Thread getThread(final Hashtable pairs) + throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { + final ObjectName objectName = new ObjectName("dd.test.sample", pairs); + final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + final Sample sample = new Sample(); + server.registerMBean(sample, objectName); + final Thread daemonThread = new Thread(new Runnable() { + @Override + public void run() { + while (sample.counter.incrementAndGet() > 0) { + try { + Thread.sleep(TimeUnit.SECONDS.toSeconds(5)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + }); + daemonThread.setDaemon(true); + return daemonThread; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/util/server/SimpleAppContainer.java b/src/test/java/org/datadog/jmxfetch/util/server/SimpleAppContainer.java new file mode 100644 index 000000000..b6702252e --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/util/server/SimpleAppContainer.java @@ -0,0 +1,68 @@ +package org.datadog.jmxfetch.util.server; + +import java.nio.file.Paths; +import java.time.Duration; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.lifecycle.Startable; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SimpleAppContainer implements Startable { + + private static final String JAVA_OPTS = "JAVA_OPTS"; + private static final String RMI_PORT = "RMI_PORT"; + private final String jreDockerImage; + private final String javaOpts; + private final int rmiPort; + private final GenericContainer server; + + public SimpleAppContainer() { + this("eclipse-temurin:17", "", MisbehavingJMXServer.DEFAULT_RMI_PORT); + + } + + public SimpleAppContainer(final String jreDockerImage, final String javaOpts, final int rmiPort) { + this.jreDockerImage = jreDockerImage; + this.javaOpts = javaOpts; + this.rmiPort = rmiPort; + final ImageFromDockerfile img = new ImageFromDockerfile() + .withFileFromPath("app.java", Paths.get("./src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java")) + .withFileFromClasspath("Dockerfile", "org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp") + .withFileFromClasspath("run.sh", "org/datadog/jmxfetch/util/server/run-SimpleApp.sh") + .withBuildArg("JRE_DOCKER_IMAGE", jreDockerImage); + this.server = new GenericContainer<>(img) + .withEnv(JAVA_OPTS, this.javaOpts) + .withEnv(RMI_PORT, Integer.toString(this.rmiPort)) + .withExposedPorts(this.rmiPort) + .waitingFor(Wait.forListeningPorts(this.rmiPort).withStartupTimeout(Duration.ofSeconds(10))); + } + + @Override + public void start() { + log.info("Starting SimpleApp with Docker image '{}' with JAVA_OPTS '{}' in port '{}'", + this.jreDockerImage, this.javaOpts, this.rmiPort); + this.server.start(); + log.info(this.server.getLogs()); + } + + @Override + public void stop() { + this.server.stop(); + } + + public void close() { + this.stop(); + } + + public String getIp() { + return this.server.getContainerInfo().getNetworkSettings().getIpAddress(); + } + + public int getRMIPort() { + return this.rmiPort; + } +} diff --git a/src/test/resources/org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp b/src/test/resources/org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp new file mode 100644 index 000000000..81f60f0ef --- /dev/null +++ b/src/test/resources/org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 + +# Allows to cheange the JDK image used +ARG JRE_DOCKER_IMAGE=eclipse-temurin:11 + +# Use the official JDK image as the base image +FROM ${JRE_DOCKER_IMAGE} + +WORKDIR /app + +COPY run.sh app.java /app/ + +EXPOSE 9010 + +ENTRYPOINT [ "/app/run.sh" ] diff --git a/src/test/resources/org/datadog/jmxfetch/util/server/run-SimpleApp.sh b/src/test/resources/org/datadog/jmxfetch/util/server/run-SimpleApp.sh new file mode 100755 index 000000000..5e1f63b65 --- /dev/null +++ b/src/test/resources/org/datadog/jmxfetch/util/server/run-SimpleApp.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env sh +set -f + +[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx128M -Xms128M" +[ -n "$RMI_PORT" ] || RMI_PORT="9010" + +echo "Using `java --version`" +echo "With JAVA_OPTS '${JAVA_OPTS}'" +CONTAINER_IP=`awk 'END{print $1}' /etc/hosts` + +# shellcheck disable=SC2086 +javac -d app app.java + +echo "Starting app with hostname set to ${CONTAINER_IP}" + +java -cp ./app \ + ${JAVA_OPTS} \ + -Dcom.sun.management.jmxremote=true \ + -Dcom.sun.management.jmxremote.port=${RMI_PORT} \ + -Dcom.sun.management.jmxremote.rmi.port=${RMI_PORT} \ + -Dcom.sun.management.jmxremote.authenticate=false \ + -Dcom.sun.management.jmxremote.ssl=false \ + -Djava.rmi.server.hostname=${CONTAINER_IP} \ + org.datadog.jmxfetch.util.server.SimpleApp + +# java -jar jmxterm-1.0.2-uber.jar -l service:jmx:rmi:///jndi/rmi://localhost:9010/jmxrmi diff --git a/tools/misbehaving-jmx-server/Dockerfile b/tools/misbehaving-jmx-server/Dockerfile index 938e8c112..81fe4e01f 100644 --- a/tools/misbehaving-jmx-server/Dockerfile +++ b/tools/misbehaving-jmx-server/Dockerfile @@ -1,3 +1,6 @@ +# syntax=docker/dockerfile:1 +# Use by default the JDK image used to build the jar +ARG FINAL_JRE_IMAGE=base # Use the official JDK image as the base image FROM eclipse-temurin:17 AS base @@ -9,22 +12,42 @@ WORKDIR /app # Copy the pom.xml and Maven files and install the dependencies COPY .mvn .mvn/ COPY pom.xml mvnw mvnw.cmd ./ + +# TODO: investigate why mount caching does not seem to work Test containers +# Enabling this will speed up tests as the Maven cache can be shared between all builds +# RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ RUN set -eu && \ ./mvnw dependency:resolve; # Copy the source code and build the JAR file COPY src/ src/ + +# TODO: investigate why mount caching does not seem to work Test containers +# RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ RUN set -eu && \ ./mvnw clean package assembly:single; -# Use the base image as the the final image -FROM base AS final +# Use the image specified by FINAL_JRE_IMAGE build arg (default "base") +FROM ${FINAL_JRE_IMAGE} AS final # Set the working directory to /app WORKDIR /app +COPY scripts/start.sh /usr/bin/ + # Copy the JAR file from the Maven image to the final image COPY --from=build /app/target/misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar . +# RMI Port +EXPOSE 9090 + +# Control Port +EXPOSE 9091 + +# Supervisor Port +EXPOSE 9092 + # Run the supervisor class from the jar -CMD ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.supervisor.App"] \ No newline at end of file +ENTRYPOINT [ "/usr/bin/start.sh" ] + +CMD [ "org.datadog.supervisor.App" ] diff --git a/tools/misbehaving-jmx-server/README.md b/tools/misbehaving-jmx-server/README.md index 93df69e07..38c100b2d 100644 --- a/tools/misbehaving-jmx-server/README.md +++ b/tools/misbehaving-jmx-server/README.md @@ -25,6 +25,7 @@ a secondary `init` payload that contains the correct RMI Hostname. It is designe - `RMI_HOST` - hostname for JMX to listen on (default localhost) - `CONTROL_PORT` - HTTP control port (default 8080) - `SUPERVISOR_PORT` - HTTP control port for the supervisor process (if using) (default 8088) +- `MISBEHAVING_OPTS` - Manages memory, GC configurations, and system properties of the Java process running the JMXServer (default `-Xmx128M -Xms128M`) ## HTTP Control Actions (jmx-server) - POST `/cutNetwork` - Denies any requests to create a new socket (ie, no more connections will be 'accept'ed) and then closes existing TCP sockets diff --git a/tools/misbehaving-jmx-server/pom.xml b/tools/misbehaving-jmx-server/pom.xml index 9a8ef20b6..d19bcfd6e 100644 --- a/tools/misbehaving-jmx-server/pom.xml +++ b/tools/misbehaving-jmx-server/pom.xml @@ -53,10 +53,9 @@ - junit - junit - 4.11 - test + org.apache.commons + commons-lang3 + 3.12.0 @@ -64,6 +63,13 @@ snakeyaml ${snakeyaml.version} + + + junit + junit + 4.11 + test + diff --git a/tools/misbehaving-jmx-server/scripts/start.sh b/tools/misbehaving-jmx-server/scripts/start.sh new file mode 100755 index 000000000..87ec5a88c --- /dev/null +++ b/tools/misbehaving-jmx-server/scripts/start.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +set -f + +echo "Running $@" + +[ -n "$MISBEHAVING_OPTS" ] || MISBEHAVING_OPTS="-Xmx128M -Xms128M" + +echo "Using `java --version`" +echo "With MISBEHAVING_OPTS '${MISBEHAVING_OPTS}'" + +# shellcheck disable=SC2086 +java -Xmx64M -Xms64M \ + -cp misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar \ + "$@" diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java index 2597dbce5..ca659dc72 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/App.java @@ -145,8 +145,7 @@ public class App { private static boolean started = false; final static String testDomain = "Bohnanza"; - public static void main( String[] args ) throws IOException, MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException - { + public static void main( String[] args ) throws IOException{ AppConfig config = new AppConfig(); JCommander jCommander = JCommander.newBuilder() @@ -258,7 +257,7 @@ public static void main( String[] args ) throws IOException, MalformedObjectName try { Thread.currentThread().join(); } catch (InterruptedException e) { - e.printStackTrace(); + log.error("Got an InterruptedException", e); } } } diff --git a/tools/misbehaving-jmx-server/src/main/java/org/datadog/supervisor/App.java b/tools/misbehaving-jmx-server/src/main/java/org/datadog/supervisor/App.java index b9d13933e..312dfc760 100644 --- a/tools/misbehaving-jmx-server/src/main/java/org/datadog/supervisor/App.java +++ b/tools/misbehaving-jmx-server/src/main/java/org/datadog/supervisor/App.java @@ -1,16 +1,15 @@ package org.datadog.supervisor; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.datadog.Defaults; import lombok.extern.slf4j.Slf4j; @@ -38,6 +37,8 @@ class SupervisorInitSpec { @Slf4j public class App { + + private static final String MISBEHAVING_OPTS_ENV = "MISBEHAVING_OPTS"; private static Process process = null; // marked when OS process is started private static AtomicBoolean running = new AtomicBoolean(false); @@ -123,12 +124,21 @@ static void stopJMXServer() throws IOException, InterruptedException { } static void startJMXServer() throws IOException { - ProcessBuilder pb = new ProcessBuilder("java", + /* + MISBEHAVING_OPTS_ENV is the environment variable used to pass configuration flags and + system properties to the JVM that runs the JMXServer. This allows you to do such things as + change the garbage collector use by passing "-XX:+UseParallelGC" to it. + */ + final String misbehavingOpts = System.getenv(MISBEHAVING_OPTS_ENV); + final String[] extraOpts = misbehavingOpts !=null ? StringUtils.split(misbehavingOpts) : ArrayUtils.EMPTY_STRING_ARRAY; + final String[] command = ArrayUtils.addAll( ArrayUtils.insert(0, extraOpts, "java"), "-cp", selfJarPath, jmxServerEntrypoint, "--rmi-host", App.config.rmiHostname); + log.info("Running JMXServer with command '{}'", ArrayUtils.toString(command)); + final ProcessBuilder pb = new ProcessBuilder(command); pb.inheritIO(); process = pb.start(); running.set(true);