├── tools └── misbehaving-jmx-server │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── src │ └── main │ │ └── java │ │ └── org │ │ └── datadog │ │ ├── Defaults.java │ │ └── misbehavingjmxserver │ │ ├── InterceptibleSocketServer.java │ │ ├── MetricDAO.java │ │ ├── RandomIdentifier.java │ │ ├── InterruptibleRMISocketFactory.java │ │ └── BeanManager.java │ ├── misbehaving-jmxfetch-conf.yaml │ ├── misbehaving-config.yaml │ ├── scripts │ └── start.sh │ ├── docker-compose.yaml │ ├── Dockerfile │ └── README.md ├── src ├── main │ ├── resources │ │ ├── project.properties │ │ └── org │ │ │ └── datadog │ │ │ └── jmxfetch │ │ │ └── old-gc-default-jmx-metrics.yaml │ ├── config │ │ └── classworlds.conf │ ├── java │ │ └── org │ │ │ └── datadog │ │ │ └── jmxfetch │ │ │ ├── service │ │ │ ├── ServiceNameProvider.java │ │ │ └── ConfigServiceNameProvider.java │ │ │ ├── tasks │ │ │ ├── TaskProcessException.java │ │ │ ├── TaskMethod.java │ │ │ ├── TaskStatusHandler.java │ │ │ └── TaskProcessor.java │ │ │ ├── util │ │ │ ├── AppTelemetryMBean.java │ │ │ ├── InstanceTelemetryMBean.java │ │ │ ├── StdoutConsoleHandler.java │ │ │ ├── FileHelper.java │ │ │ ├── MetadataHelper.java │ │ │ ├── ServiceCheckHelper.java │ │ │ ├── AppTelemetry.java │ │ │ ├── StringUtils.java │ │ │ ├── InstanceTelemetry.java │ │ │ ├── ByteArraySearcher.java │ │ │ └── LogLevel.java │ │ │ ├── AppShutdownHook.java │ │ │ ├── ConnectionFactory.java │ │ │ ├── converter │ │ │ └── ExitWatcherConverter.java │ │ │ ├── InstanceCleanupTask.java │ │ │ ├── InstanceInitializingTask.java │ │ │ ├── JvmDirectConnection.java │ │ │ ├── InstanceTask.java │ │ │ ├── reporter │ │ │ ├── LoggingErrorHandler.java │ │ │ ├── ReporterFactory.java │ │ │ ├── JsonReporter.java │ │ │ └── ConsoleReporter.java │ │ │ ├── MetricCollectionTask.java │ │ │ ├── validator │ │ │ ├── ReporterValidator.java │ │ │ ├── PositiveIntegerValidator.java │ │ │ └── LogLevelValidator.java │ │ │ ├── YamlParser.java │ │ │ ├── Metric.java │ │ │ ├── ExitWatcher.java │ │ │ ├── JmxSubAttribute.java │ │ │ ├── DefaultConnectionFactory.java │ │ │ ├── JmxFetch.java │ │ │ ├── Connection.java │ │ │ ├── AttachApiConnection.java │ │ │ └── DynamicTag.java │ └── assembly │ │ └── uber.xml └── test │ ├── resources │ ├── jmx_empty_filters.yaml │ ├── non_running_process.yaml │ ├── jmx_check_no_prefix.yaml │ ├── jmx_class_include.yaml │ ├── jmx_check_prefix.yaml │ ├── jmx_domain_include.yaml │ ├── jmx_non_parsable_max_returned_metrics_string.yaml │ ├── jmx_parsable_max_returned_metrics_string.yaml │ ├── too_many_metrics.yaml │ ├── org │ │ └── datadog │ │ │ └── jmxfetch │ │ │ ├── dd-java-agent-jmx.yaml │ │ │ ├── util │ │ │ └── server │ │ │ │ ├── Dockerfile-SimpleApp │ │ │ │ └── run-SimpleApp.sh │ │ │ ├── remote-jmx.yaml │ │ │ ├── default-jmx-metrics.yaml │ │ │ └── sample-metrics.yaml │ ├── jmx_telemetry_tags.yaml │ ├── jmx_no_alias.yaml │ ├── jmx_cassandra_deprecated.yaml │ ├── jmx_count.yaml │ ├── jmx_refresh_beans.yaml │ ├── jmx_domain_exclude.yaml │ ├── jmx_baseline_default_hostname.yaml │ ├── jmx_class_regex.yaml │ ├── jmx_cast.yaml │ ├── jmx_domain_regex.yaml │ ├── jmx_class_exclude.yaml │ ├── jmx_empty_default_hostname.yaml │ ├── jmx_tabular_data_tagless.yaml │ ├── jmx_jee_data.yaml │ ├── jmx_list_beans_regex_include.yaml │ ├── jmx_min_collection_period.yml │ ├── jmx_list_beans_regex_exclude.yaml │ ├── jmx_tabular_data_tagged.yaml │ ├── jmx_list_beans_exclude.yaml │ ├── jmx_bean_regex_tags.yaml │ ├── jmx_list_beans_include.yaml │ ├── jmx_counter_rate.yaml │ ├── jmx_additional_tags.yaml │ ├── jmx_exclude_tags.yaml │ ├── jmx_cassandra.yaml │ ├── jmx_filter_list_support.yaml │ ├── jmx_list_params_include.yaml │ ├── jmx_bean_tags.yaml │ ├── jmx_list_params_exclude.yaml │ ├── jmx_filter_issues.yaml │ ├── jmx_composite_data.yaml │ ├── jmx_service_tag_global.yaml │ ├── jmx_histogram.yaml │ ├── jmx_exclude_tags_override_service.yaml │ ├── jmx_bean_tags_dont_normalize_params.yaml │ ├── jmx_config_dynamic_tags_bean_params.yaml │ ├── jmx_bean_tags_normalize_params.yaml │ ├── jmx_service_tag_global_list.yaml │ ├── jmx_service_tag_instance_override.yaml │ ├── jmx_config_dynamic_tags.yaml │ ├── jmx_alias_match.yaml │ ├── jmx_filtering_test.yaml │ ├── jmx_bean_scope.yaml │ ├── jmx_config_dynamic_tags_multiple_per_conf.yaml │ ├── jmx_config_dynamic_tags_caching.yaml │ ├── jmx_config_dynamic_tags_multi.yaml │ ├── jmx_config_dynamic_tags_invalid.yaml │ ├── jmx_sd_pipe.txt │ ├── jmx_sd_pipe_longname.txt │ ├── jmx_canonical.yaml │ ├── jmx.yaml │ └── auto_discovery_configs.json │ └── java │ └── org │ └── datadog │ └── jmxfetch │ ├── DynamicTagTestAppMBean.java │ ├── jee │ ├── UnsupportedStatisticImpl.java │ ├── CountStatisticImpl.java │ ├── BoundaryStatisticImpl.java │ ├── BoundedRangeStatisticImpl.java │ ├── BaseStatistic.java │ ├── RangeStatisticImpl.java │ ├── JeeStats.java │ └── TimeStatisticImpl.java │ ├── JMXServerSupervisorClient.java │ ├── util │ ├── server │ │ ├── JDKImage.java │ │ ├── WaitOrStrategy.java │ │ ├── SimpleApp.java │ │ └── SimpleAppContainer.java │ ├── ServiceCheckHelperTest.java │ ├── TimerWaitStrategy.java │ ├── ByteArraySearcherTest.java │ ├── StringUtilsTest.java │ ├── LogLevelTest.java │ └── MetricsAssert.java │ ├── reporter │ └── ReporterTest.java │ ├── TestJsonPrinter.java │ ├── DynamicTagTestApp.java │ ├── JMXServerControlClient.java │ ├── SimpleTestJavaAppMBean.java │ ├── JMXServerClient.java │ ├── StatusTest.java │ └── service │ └── ConfigServiceNameProviderTest.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── repository.datadog.yml ├── .github ├── CODEOWNERS └── workflows │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── .sdkmanrc ├── Dockerfile.agentbase ├── .vscode └── settings.json ├── settings.xml ├── .devcontainer └── devcontainer.json ├── docker-compose.yaml ├── LICENSE └── .gitlab-ci.yml /tools/misbehaving-jmx-server/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /src/main/resources/project.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | -------------------------------------------------------------------------------- /src/main/config/classworlds.conf: -------------------------------------------------------------------------------- 1 | main is org.datadog.jmxfetch.app from app 2 | [app] -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/jmxfetch/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /repository.datadog.yml: -------------------------------------------------------------------------------- 1 | --- 2 | schema-version: v1 3 | kind: mergequeue 4 | team: agent-metricslogs 5 | gitlab_check_enable: false 6 | -------------------------------------------------------------------------------- /src/test/resources/jmx_empty_filters.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - name: jmx_test_default_hostname 5 | conf: 6 | - include: 7 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/jmxfetch/HEAD/tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/service/ServiceNameProvider.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.service; 2 | 3 | public interface ServiceNameProvider { 4 | Iterable getServiceNames(); 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/non_running_process.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*non_running_process_test.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | -------------------------------------------------------------------------------- /src/test/resources/jmx_check_no_prefix.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | service_check_prefix: null 3 | 4 | instances: 5 | - jvm_direct: true 6 | name: jmx_test_instance 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | -------------------------------------------------------------------------------- /src/test/resources/jmx_class_include.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | class: org.datadog.jmxfetch.SimpleTestJavaApp 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ for syntax 2 | # Rules are matched bottom-to-top, so one team can own subdirectories 3 | # and another team can own the rest of the directory. 4 | 5 | * @Datadog/agent-metric-pipelines 6 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/tasks/TaskProcessException.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.tasks; 2 | 3 | public class TaskProcessException extends Exception { 4 | public TaskProcessException(String errorMessage) { 5 | super(errorMessage); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/jmx_check_prefix.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | service_check_prefix: myprefix 3 | 4 | instances: 5 | - jvm_direct: true 6 | name: jmx_test_instance 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/DynamicTagTestAppMBean.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | public interface DynamicTagTestAppMBean { 4 | String getClusterId(); 5 | String getVersion(); 6 | int getPort(); 7 | Double getMetric(); 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/jmx_domain_include.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.includeme 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *~ 3 | *.DS_Store 4 | target/* 5 | .euml2 6 | .umlproject 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings/ 11 | /target 12 | .idea 13 | *.iml 14 | *.ucls 15 | 16 | *.png 17 | 18 | # jenv 19 | .java_version 20 | .java-version 21 | !.vscode 22 | .env 23 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/AppTelemetryMBean.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | public interface AppTelemetryMBean { 4 | 5 | int getRunningInstanceCount(); 6 | 7 | int getBrokenInstanceCount(); 8 | 9 | int getBrokenInstanceEventCount(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/Defaults.java: -------------------------------------------------------------------------------- 1 | package org.datadog; 2 | 3 | public class Defaults { 4 | public static final int JMXSERVER_CONTROL_PORT = 8080; 5 | public static final int JMXSERVER_RMI_PORT = 1099; 6 | public static final String JMXSERVER_RMI_INTERFACE = "localhost"; 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/jmx_non_parsable_max_returned_metrics_string.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | max_returned_metrics: "foo" 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | -------------------------------------------------------------------------------- /src/test/resources/jmx_parsable_max_returned_metrics_string.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | max_returned_metrics: "500" 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/too_many_metrics.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | 13 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/UnsupportedStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | public class UnsupportedStatisticImpl extends BaseStatistic { 4 | 5 | public UnsupportedStatisticImpl(String name) { 6 | super(name); 7 | } 8 | 9 | public long getUnsupportedValue() { 10 | return 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/dd-java-agent-jmx.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | is_jmx: true 3 | new_gc_metrics: true 4 | 5 | instances: 6 | - jmx_url: service:jmx:local:/// 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.log_init_test 10 | attribute: 11 | Triggered: 12 | metric_type: gauge 13 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/misbehaving-jmxfetch-conf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | is_jmx: true 4 | 5 | instances: 6 | - host: localhost 7 | port: 1099 8 | collect_default_jvm_metrics: false 9 | max_returned_metrics: 300000 10 | conf: 11 | - include: 12 | domain: Bohnanza 13 | tags: 14 | - jmx:fetch 15 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/AppShutdownHook.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | public class AppShutdownHook extends Thread { 4 | 5 | private App app; 6 | 7 | public AppShutdownHook(App app) { 8 | this.app = app; 9 | } 10 | 11 | @Override 12 | public void run() { 13 | app.stop(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/jmx_telemetry_tags.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | 4 | instances: 5 | - jvm_direct: true 6 | refresh_beans: 4 7 | collect_default_jvm_metrics: false 8 | name: jmx_test_instance 9 | tags: 10 | - "env:stage" 11 | - "newTag:test" 12 | conf: 13 | - include: 14 | domain: org.datadog.jmxfetch.test -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/tasks/TaskMethod.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.tasks; 2 | 3 | import org.datadog.jmxfetch.Instance; 4 | import org.datadog.jmxfetch.reporter.Reporter; 5 | 6 | import java.util.concurrent.Future; 7 | 8 | public interface TaskMethod { 9 | TaskStatusHandler invoke(Instance instance, Future future, Reporter reporter); 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/jmx_no_alias.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/InstanceTelemetryMBean.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | public interface InstanceTelemetryMBean { 4 | 5 | int getBeansFetched(); 6 | 7 | int getTopLevelAttributeCount(); 8 | 9 | int getMetricCount(); 10 | 11 | int getWildcardDomainQueryCount(); 12 | 13 | double getBeanMatchRatio(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/org/datadog/jmxfetch/old-gc-default-jmx-metrics.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Old GC metrics for compatibility 3 | - include: 4 | domain: java.lang 5 | type: GarbageCollector 6 | attribute: 7 | CollectionCount: 8 | alias: jvm.gc.cms.count 9 | metric_type: gauge 10 | CollectionTime: 11 | alias: jvm.gc.parnew.time 12 | metric_type: gauge -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/ConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | public interface ConnectionFactory { 7 | /** Factory method to create connections, both remote and local to the target JVM. */ 8 | Connection createConnection(Map connectionParams) throws IOException; 9 | } 10 | -------------------------------------------------------------------------------- /src/test/resources/jmx_cassandra_deprecated.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | bean: org.apache.cassandra.db:type=ColumnFamilies,keyspace=MyKeySpace,columnfamily=MyColumnFamily 9 | attribute: 10 | - ShouldBe100 11 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Allows to cheange the JDK image used 4 | ARG JRE_DOCKER_IMAGE=eclipse-temurin:11 5 | 6 | # Use the official JDK image as the base image 7 | FROM ${JRE_DOCKER_IMAGE} 8 | 9 | WORKDIR /app 10 | 11 | COPY run.sh app.java /app/ 12 | 13 | EXPOSE 9010 14 | 15 | ENTRYPOINT [ "/app/run.sh" ] 16 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/converter/ExitWatcherConverter.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.converter; 2 | 3 | import com.beust.jcommander.IStringConverter; 4 | import org.datadog.jmxfetch.ExitWatcher; 5 | 6 | public class ExitWatcherConverter implements IStringConverter { 7 | 8 | public ExitWatcher convert(String value) { 9 | return new ExitWatcher(value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/misbehaving-config.yaml: -------------------------------------------------------------------------------- 1 | domains: 2 | test_1: 3 | beanCount: 4 4 | scalarAttributeCount: 5 5 | tabularAttributeCount: 1 6 | compositeValuesPerTabularAttribute: 2 7 | test_2: 8 | beanCount: 10 9 | scalarAttributeCount: 0 10 | tabularAttributeCount: 5 11 | compositeValuesPerTabularAttribute: 10 12 | 13 | seed: 12345 14 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/remote-jmx.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | is_jmx: true 3 | new_gc_metrics: true 4 | 5 | instances: 6 | # this is expected to fail with a socket connection error 7 | - jmx_url: service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi 8 | conf: 9 | - include: 10 | domain: org.datadog.jmxfetch.log_init_test 11 | attribute: 12 | Triggered: 13 | metric_type: gauge 14 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -f 4 | 5 | echo "Running $@" 6 | 7 | [ -n "$MISBEHAVING_OPTS" ] || MISBEHAVING_OPTS="-Xmx128M -Xms128M" 8 | 9 | echo "Using `java --version`" 10 | echo "With MISBEHAVING_OPTS '${MISBEHAVING_OPTS}'" 11 | 12 | # shellcheck disable=SC2086 13 | java -Xmx64M -Xms64M \ 14 | -cp misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar \ 15 | "$@" 16 | -------------------------------------------------------------------------------- /src/test/resources/jmx_count.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | refresh_beans: 4 6 | name: jmx_test_instance 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | attribute: 11 | ShouldBeCounter: 12 | metric_type: monotonic_count 13 | alias: test.counter -------------------------------------------------------------------------------- /src/test/resources/jmx_refresh_beans.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | min_collection_interval: 1 6 | refresh_beans_initial: 1 7 | refresh_beans: 3 8 | name: jmx_test_instance 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | attribute: 13 | - ShouldBe100 14 | - ShouldBe1000 15 | -------------------------------------------------------------------------------- /src/test/resources/jmx_domain_exclude.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | attribute: 9 | ShouldBe100: 10 | metric_type: gauge 11 | alias: this.is.100 12 | exclude: 13 | domain: org.datadog.jmxfetch.excludeme 14 | -------------------------------------------------------------------------------- /src/test/resources/jmx_baseline_default_hostname.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_default_hostname 6 | tags: 7 | - jmx:fetch 8 | conf: 9 | - include: 10 | domain: org.datadog.jmxfetch.test 11 | attribute: 12 | ShouldBe100: 13 | metric_type: gauge 14 | alias: this.is.100.$foo.$qux -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | # 4 | # Java version currently tracking what is present in -jmx agent builds 5 | # Agent is currently installing 'openjdk-11-jre-headless' from debian 6 | # See https://github.com/DataDog/datadog-agent/blob/3b6f07cb7d097837a7b9247c6119ead3309116ab/Dockerfiles/agent/Dockerfile#L136 7 | # and https://packages.debian.org/sid/openjdk-11-jre-headless 8 | java=11.0.21-amzn 9 | -------------------------------------------------------------------------------- /src/test/resources/jmx_class_regex.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | class_regex: .*IncludeMe.* 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | exclude: 14 | class_regex: .*Me$ 15 | -------------------------------------------------------------------------------- /src/test/resources/jmx_cast.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | refresh_beans: 4 6 | name: jmx_test_instance 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | attribute: 11 | ShouldBe100: 12 | metric_type: gauge 13 | ShouldBeDefaulted: 14 | metric_type: gauge 15 | -------------------------------------------------------------------------------- /src/test/resources/jmx_domain_regex.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain_regex: .*includeme.* 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | exclude: 14 | domain_regex: .*\.me$ 15 | -------------------------------------------------------------------------------- /src/test/resources/jmx_class_exclude.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | attribute: 9 | ShouldBe100: 10 | metric_type: gauge 11 | alias: this.is.100 12 | exclude: 13 | class: org.datadog.jmxfetch.TestApp$1SimpleTestJavaAnotherApp 14 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/CountStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.CountStatistic; 4 | 5 | public class CountStatisticImpl extends BaseStatistic implements CountStatistic { 6 | private final long count; 7 | 8 | public CountStatisticImpl(String name, long count) { 9 | super(name); 10 | this.count = count; 11 | } 12 | 13 | @Override 14 | public long getCount() { 15 | return count; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/jmx_empty_default_hostname.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | empty_default_hostname: true 6 | name: jmx_test_no_hostname 7 | tags: 8 | - jmx:fetch 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | attribute: 13 | ShouldBe100: 14 | metric_type: gauge 15 | alias: this.is.100.$foo.$qux 16 | -------------------------------------------------------------------------------- /src/test/resources/jmx_tabular_data_tagless.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | 4 | instances: 5 | - jvm_direct: true 6 | refresh_beans: 4 7 | name: jmx_test_instance 8 | tags: 9 | - "env:stage" 10 | - "newTag:test" 11 | conf: 12 | - include: 13 | domain: org.datadog.jmxfetch.test 14 | attribute: 15 | Tabulardata.foo: 16 | metric_type: gauge 17 | alias: multiattr.foo_tagless 18 | limit: 1 19 | sort: desc 20 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/StdoutConsoleHandler.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.io.OutputStream; 4 | import java.lang.SecurityException; 5 | import java.util.logging.ConsoleHandler; 6 | 7 | public class StdoutConsoleHandler extends ConsoleHandler { 8 | protected void setOutputStream(OutputStream out) throws SecurityException { 9 | // force ConsoleHandler to set its output to System.out 10 | // instead of System.err 11 | super.setOutputStream(System.out); 12 | } 13 | } -------------------------------------------------------------------------------- /src/test/resources/jmx_jee_data.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | 4 | instances: 5 | - jvm_direct: true 6 | refresh_beans: 4 7 | name: jmx_test_instance 8 | conf: 9 | - include: 10 | domain: org.datadog.jmxfetch.test 11 | attribute: 12 | - JeeCounter # 1 metric 13 | - JeeTime # 4 metrics 14 | - JeeRange # 3 metrics 15 | - JeeBoundary # 2 metrics 16 | - JeeBoundedRange # 5 metrics 17 | - JeeUnsupported # 0 metrics 18 | - JeeStat # 1 child metric 19 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_beans_regex_include.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | bean_regex: 12 | - .*type=\w*WrongType 13 | - .*type=Include.* 14 | attribute: 15 | ShouldBe100: 16 | metric_type: gauge 17 | alias: this.is.100 18 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/InstanceCleanupTask.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | @Slf4j 6 | class InstanceCleanupTask extends InstanceTask { 7 | 8 | InstanceCleanupTask(Instance instance) { 9 | super(instance); 10 | setWarning("Unable to cleanup instance " + instance); 11 | } 12 | 13 | @Override 14 | public Void call() throws Exception { 15 | log.info("Trying to cleanup: " + instance); 16 | 17 | instance.cleanUp(); 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/jmx_min_collection_period.yml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | min_collection_interval: 5 6 | name: jmx_test_instance 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | attribute: 11 | ShouldBe100: 12 | metric_type: gauge 13 | alias: this.is.100.$foo.$qux 14 | Hashmap.thisis0: 15 | metric_type: gauge 16 | alias: $domain.$qux.$attribute 17 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/JMXServerSupervisorClient.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import java.io.IOException; 4 | 5 | public class JMXServerSupervisorClient extends JMXServerClient { 6 | public JMXServerSupervisorClient(String host, int port) { 7 | super(host, port); 8 | } 9 | 10 | public void initializeJMXServer(String rmiHostname) throws IOException { 11 | String endpoint = "/init"; 12 | String jsonPayload = "{\"rmiHostname\": \"" + rmiHostname + "\"}"; 13 | sendPostRequestWithPayload(endpoint, jsonPayload); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_beans_regex_exclude.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | attribute: 12 | ShouldBe100: 13 | metric_type: gauge 14 | alias: this.is.100 15 | exclude: 16 | bean_regex: 17 | - .*[,:]type=ExcludeMe.* 18 | - .*[,:]scope=Out.* 19 | -------------------------------------------------------------------------------- /src/test/resources/jmx_tabular_data_tagged.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | 4 | instances: 5 | - jvm_direct: true 6 | refresh_beans: 4 7 | name: jmx_test_instance 8 | tags: 9 | - "env:stage" 10 | - "newTag:test" 11 | conf: 12 | - include: 13 | domain: org.datadog.jmxfetch.test 14 | attribute: 15 | Tabulardata.foo: 16 | metric_type: gauge 17 | alias: multiattr.foo_tagged 18 | tags: 19 | foo: $foo 20 | toto: $toto 21 | limit: 1 22 | sort: desc 23 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/server/JDKImage.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util.server; 2 | 3 | public enum JDKImage { 4 | BASE("base"), 5 | JDK_8("eclipse-temurin:8"), 6 | JDK_11("eclipse-temurin:11"), 7 | JDK_11_OPENJ9("adoptopenjdk/openjdk11-openj9:latest"), 8 | JDK_17("eclipse-temurin:17"), 9 | JDK_21("eclipse-temurin:21"); 10 | 11 | private final String image; 12 | 13 | private JDKImage(final String image) { 14 | this.image = image; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return this.image; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_beans_exclude.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | attribute: 12 | ShouldBe100: 13 | metric_type: gauge 14 | alias: this.is.100 15 | exclude: 16 | bean: 17 | - org.datadog.jmxfetch.test:type=ExcludeMe 18 | - org.datadog.jmxfetch.test:type=ExcludeMeToo 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/InstanceInitializingTask.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | class InstanceInitializingTask extends InstanceTask { 4 | boolean reconnect; 5 | 6 | InstanceInitializingTask(Instance instance, boolean reconnect) { 7 | super(instance); 8 | this.reconnect = reconnect; 9 | setWarning("Unable to instantiate or initialize instance " + instance); 10 | } 11 | 12 | @Override 13 | public Void call() throws Exception { 14 | // Try to reinit the connection and force to renew it 15 | instance.init(reconnect); 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/jmx_bean_regex_tags.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | bean_regex: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co\|olScope,host=(.*),component=.* 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | tags: 14 | hosttag: $1 15 | nonExistantTag: $2 16 | nonRegexTag: value 17 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/BoundaryStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.BoundaryStatistic; 4 | 5 | public class BoundaryStatisticImpl extends BaseStatistic implements BoundaryStatistic { 6 | private final long low; 7 | private final long high; 8 | 9 | public BoundaryStatisticImpl(String name, long low, long high) { 10 | super(name); 11 | this.low = low; 12 | this.high = high; 13 | } 14 | 15 | @Override 16 | public long getUpperBound() { 17 | return high; 18 | } 19 | 20 | @Override 21 | public long getLowerBound() { 22 | return low; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_beans_include.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | bean: 12 | - org.datadog.jmxfetch.test:type=WrongType 13 | - org.datadog.jmxfetch.test:type=IncludeMe 14 | - org.datadog.jmxfetch.test:type=AnotherWrongType 15 | attribute: 16 | ShouldBe100: 17 | metric_type: gauge 18 | alias: this.is.100 19 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/JvmDirectConnection.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.lang.management.ManagementFactory; 7 | 8 | @Slf4j 9 | public class JvmDirectConnection extends Connection { 10 | 11 | public JvmDirectConnection() throws IOException { 12 | createConnection(); 13 | } 14 | 15 | protected void createConnection() throws IOException { 16 | mbs = ManagementFactory.getPlatformMBeanServer(); 17 | } 18 | 19 | public void closeConnector() { 20 | // ignore 21 | } 22 | 23 | public boolean isAlive() { 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/FileHelper.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | 7 | public class FileHelper { 8 | /** Touches file. */ 9 | public static void touch(File file) throws IOException { 10 | long timestamp = System.currentTimeMillis(); 11 | touch(file, timestamp); 12 | } 13 | 14 | /** Touches file and set last modified timestamp. */ 15 | public static void touch(File file, long timestamp) throws IOException { 16 | if (!file.exists()) { 17 | new FileOutputStream(file).close(); 18 | } 19 | 20 | file.setLastModified(timestamp); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/jmx_counter_rate.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | refresh_beans: 4 6 | name: jmx_test_instance1 7 | conf: 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | attribute: 11 | ShouldBeCounter: 12 | metric_type: counter 13 | alias: test.counter 14 | - process_name_regex: .*surefire.* 15 | refresh_beans: 4 16 | name: jmx_test_instance2 17 | conf: 18 | - include: 19 | domain: org.datadog.jmxfetch.test 20 | attribute: 21 | ShouldBeCounter: 22 | metric_type: rate 23 | alias: test.rate 24 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/BoundedRangeStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.BoundedRangeStatistic; 4 | 5 | public class BoundedRangeStatisticImpl extends RangeStatisticImpl implements BoundedRangeStatistic { 6 | private final long low; 7 | private final long high; 8 | 9 | public BoundedRangeStatisticImpl( 10 | String name, long min, long max, long current, long low, long high) { 11 | super(name, min, max, current); 12 | this.low = low; 13 | this.high = high; 14 | } 15 | 16 | @Override 17 | public long getUpperBound() { 18 | return high; 19 | } 20 | 21 | @Override 22 | public long getLowerBound() { 23 | return low; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/jmx_additional_tags.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | tags: 10 | simple: $type 11 | raw_value: value 12 | unknown_tag: $does-not-exist 13 | multiple: $type-$name 14 | attribute: 15 | ShouldBe1000: 16 | metric_type: gauge 17 | alias: test1.gauge 18 | Int424242: 19 | metric_type: histogram 20 | alias: test1.histogram 21 | -------------------------------------------------------------------------------- /src/test/resources/jmx_exclude_tags.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | exclude_tags: 13 | - env 14 | - type 15 | - newTag 16 | attribute: 17 | ShouldBe1000: 18 | metric_type: gauge 19 | alias: test1.gauge 20 | Int424242: 21 | metric_type: histogram 22 | alias: test1.histogram 23 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/InstanceTask.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.concurrent.Callable; 6 | 7 | @Slf4j 8 | public abstract class InstanceTask implements Callable { 9 | protected Instance instance; 10 | protected String warning; 11 | 12 | public InstanceTask(Instance instance) { 13 | this.instance = instance; 14 | } 15 | 16 | public Instance getInstance() { 17 | return instance; 18 | } 19 | 20 | public void setWarning(String warning) { 21 | this.warning = warning; 22 | } 23 | 24 | public String getWarning() { 25 | return warning; 26 | } 27 | 28 | public abstract T call() throws Exception; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/reporter/LoggingErrorHandler.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.reporter; 2 | 3 | import com.timgroup.statsd.StatsDClientErrorHandler; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | 9 | /** An error handler class to track errors as required. */ 10 | @Slf4j 11 | public class LoggingErrorHandler implements StatsDClientErrorHandler { 12 | private AtomicInteger errors = new AtomicInteger(); 13 | 14 | @Override 15 | public void handle(Exception exception) { 16 | errors.incrementAndGet(); 17 | log.error("statsd client error:", exception); 18 | } 19 | 20 | public int getErrors() { 21 | return errors.get(); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/jmx_cassandra.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_first_instance 6 | cassandra_aliasing: true 7 | conf: 8 | - include: 9 | bean: org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks 10 | attribute: 11 | - ShouldBe100 12 | - process_name_regex: .*surefire.* 13 | name: jmx_second_instance 14 | conf: 15 | - include: 16 | bean: org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks 17 | attribute: 18 | - ShouldBe1000 19 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.Statistic; 4 | 5 | public abstract class BaseStatistic implements Statistic { 6 | private final String name; 7 | 8 | public BaseStatistic(String name) { 9 | this.name = name; 10 | } 11 | 12 | @Override 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | @Override 18 | public String getUnit() { 19 | return ""; 20 | } 21 | 22 | @Override 23 | public String getDescription() { 24 | return ""; 25 | } 26 | 27 | @Override 28 | public long getStartTime() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public long getLastSampleTime() { 34 | return 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/reporter/ReporterTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.reporter; 2 | 3 | import org.datadog.jmxfetch.Status; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class ReporterTest { 9 | 10 | @Test 11 | public void statusToInteger() { 12 | JsonReporter jsonReporter = new JsonReporter(); 13 | 14 | assertEquals(0, jsonReporter.statusToServiceCheckStatusValue(Status.STATUS_OK)); 15 | assertEquals(1, jsonReporter.statusToServiceCheckStatusValue(Status.STATUS_WARNING)); 16 | assertEquals(2, jsonReporter.statusToServiceCheckStatusValue(Status.STATUS_ERROR)); 17 | assertEquals(3, jsonReporter.statusToServiceCheckStatusValue("XX_UNKNOWN__XX")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/jmx_filter_list_support.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | bean: singleBeanName 13 | parameter: singleParameterValue 14 | - include: 15 | domain: org.datadog.jmxfetch.test 16 | bean: 17 | - firstBeanName 18 | - secondBeanName 19 | parameter: 20 | - firstParameterValue 21 | - secondParameterValue 22 | - thirdParameterValue 23 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_params_include.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | foo: bar 13 | type: WrongType 14 | - include: 15 | domain: org.datadog.jmxfetch.test 16 | type: 17 | - WrongType 18 | - RightType 19 | - AnotherWrongType 20 | attribute: 21 | ShouldBe100: 22 | metric_type: gauge 23 | alias: this.is.100 24 | -------------------------------------------------------------------------------- /src/test/resources/jmx_bean_tags.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | - include: 14 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 15 | attribute: 16 | Hashmap.thisis0: 17 | metric_type: gauge 18 | alias: bean.parameters.should.not.match 19 | -------------------------------------------------------------------------------- /Dockerfile.agentbase: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:8-jdk-noble AS build 2 | 3 | WORKDIR /app 4 | 5 | # Copy the pom.xml and Maven files and install the dependencies 6 | COPY .mvn .mvn/ 7 | COPY pom.xml mvnw mvnw.cmd ./ 8 | 9 | # Use a maven cache to speed up iteration 10 | RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ 11 | set -eu && \ 12 | ./mvnw dependency:resolve; 13 | 14 | # Copy the source code and build the JAR file 15 | COPY src/ src/ 16 | 17 | RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ 18 | set -eu && \ 19 | ./mvnw -DskipTests clean package assembly:single; 20 | 21 | 22 | FROM datadog/agent:7-jmx AS final 23 | 24 | COPY --from=build /app/target/jmxfetch-*-jar-with-dependencies.jar /opt/datadog-agent/bin/agent/dist/jmx/jmxfetch.jar 25 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/MetadataHelper.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Properties; 6 | 7 | public class MetadataHelper { 8 | /** Returns our own version number. */ 9 | public static String getVersion() { 10 | final Properties properties = new Properties(); 11 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 12 | try (InputStream stream = classLoader.getResourceAsStream("project.properties")) { 13 | properties.load(stream); 14 | return properties.getProperty("version"); 15 | } catch (IOException e) { 16 | e.printStackTrace(); 17 | return "?.?.?"; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/jmx_list_params_exclude.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | attribute: 13 | ShouldBe100: 14 | metric_type: gauge 15 | alias: this.is.100 16 | Hashmap.thisis0: 17 | metric_type: gauge 18 | alias: subattr.this.is.0 19 | exclude: 20 | type: 21 | - WrongType 22 | - RightType 23 | - AnotherWrongType 24 | -------------------------------------------------------------------------------- /src/test/resources/jmx_filter_issues.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | subType: 10 | - Uris 11 | resource: 12 | - "\"/v1/MyMixUser/{userGuid}/{channelGuid}/tune\"" 13 | executionTimes: 14 | - MethodTimes 15 | 16 | - include: 17 | domain: org.datadog.jmxfetch.test 18 | subType: 19 | - Uris 20 | resource: 21 | - "\"/v1/MyMixUser/{userGuid}/{channelGuid}/clips/play\"" 22 | executionTimes: 23 | - MethodTimes 24 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/RangeStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.RangeStatistic; 4 | 5 | public class RangeStatisticImpl extends BaseStatistic implements RangeStatistic { 6 | private final long current; 7 | private final long max; 8 | private final long min; 9 | 10 | public RangeStatisticImpl(String name, long min, long max, long current) { 11 | super(name); 12 | this.current = current; 13 | this.max = max; 14 | this.min = min; 15 | } 16 | 17 | @Override 18 | public long getHighWaterMark() { 19 | return max; 20 | } 21 | 22 | @Override 23 | public long getLowWaterMark() { 24 | return min; 25 | } 26 | 27 | @Override 28 | public long getCurrent() { 29 | return current; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/jmx_composite_data.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | init_config: 3 | 4 | instances: 5 | - jvm_direct: true 6 | refresh_beans: 4 7 | collect_default_jvm_metrics: false 8 | name: jmx_test_instance 9 | tags: 10 | - "env:stage" 11 | - "newTag:test" 12 | conf: 13 | - include: 14 | domain: org.datadog.jmxfetch.test 15 | attribute: 16 | NestedCompositeData.anInt: 17 | metric_type: gauge 18 | alias: one_level_int 19 | - include: 20 | domain: org.datadog.jmxfetch.test 21 | attribute: 22 | # Warning!! This is currently not supported! 23 | # See corresponding unit test 24 | NestedCompositeData.nestedData.aLong: 25 | metric_type: gauge 26 | alias: second_level_long -------------------------------------------------------------------------------- /src/test/resources/jmx_service_tag_global.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | service: global 3 | 4 | instances: 5 | - process_name_regex: .*surefire.* 6 | name: jmx_test_1 7 | tags: 8 | - jmx:fetch 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | attribute: 13 | ShouldBe100: 14 | metric_type: gauge 15 | alias: this.is.100.$foo.$qux 16 | 17 | - process_name_regex: .*surefire.* 18 | name: jmx_test_2 19 | tags: 20 | - jmx:fetch 21 | conf: 22 | - include: 23 | domain: org.datadog.jmxfetch.test 24 | attribute: 25 | ShouldBe100: 26 | metric_type: gauge 27 | alias: this.is.100.$foo.$qux 28 | -------------------------------------------------------------------------------- /src/test/resources/jmx_histogram.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | tags: 7 | env: stage 8 | newTag: test 9 | conf: 10 | - include: 11 | domain: org.datadog.jmxfetch.test 12 | attribute: 13 | Atomic42: 14 | alias: test.gauge_by_default 15 | ShouldBe100: 16 | metric_type: counter 17 | alias: test.counter 18 | ShouldBe1000: 19 | metric_type: gauge 20 | alias: test.gauge 21 | Int424242: 22 | metric_type: histogram 23 | alias: test.histogram 24 | -------------------------------------------------------------------------------- /src/test/resources/jmx_exclude_tags_override_service.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_service_override_instance 6 | service: test 7 | tags: 8 | env: stage 9 | newTag: test 10 | conf: 11 | - include: 12 | domain: org.datadog.jmxfetch.test 13 | exclude_tags: 14 | - env 15 | - type 16 | - newTag 17 | - service 18 | attribute: 19 | ShouldBe1000: 20 | metric_type: gauge 21 | alias: test1.gauge 22 | Int424242: 23 | metric_type: histogram 24 | alias: test1.histogram 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "disabled", 3 | "java.format.settings.url": "style.xml", 4 | "java.debug.settings.onBuildFailureProceed": true, 5 | "cSpell.words": [ 6 | "adoptopenjdk", 7 | "barbarfoo", 8 | "barbarfoobar", 9 | "confd", 10 | "confs", 11 | "fofofofofo", 12 | "foofoo", 13 | "hamcrest", 14 | "jasperserver", 15 | "javadocs", 16 | "jmxchecknoprefix", 17 | "jmxfetch", 18 | "jmxremote", 19 | "jmxrmi", 20 | "jmxtree", 21 | "jndi", 22 | "JVMS", 23 | "keyspace", 24 | "mbean", 25 | "minibuff", 26 | "NADA", 27 | "nonblocking", 28 | "openj9", 29 | "openmbean", 30 | "pqrst", 31 | "reinit", 32 | "statsd", 33 | "temurin", 34 | "thisis", 35 | "thisiscounter", 36 | "uripath" 37 | ], 38 | } -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | central 7 | ${env.SONATYPE_USER} 8 | ${env.SONATYPE_PASS} 9 | 10 | 11 | github 12 | datadog 13 | ${env.GITHUB_TOKEN} 14 | 15 | 16 | gpg.passphrase 17 | ${env.GPG_PASSPHRASE} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/jmx_bean_tags_dont_normalize_params.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | - include: 14 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 15 | attribute: 16 | Hashmap.thisis0: 17 | metric_type: gauge 18 | alias: bean.parameters.should.not.match 19 | -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags_bean_params.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_bean_params_test 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | type: DynamicTagTestApp 10 | tags: 11 | bean_type: $type 12 | bean_name: $name 13 | attribute: 14 | Metric: 15 | metric_type: gauge 16 | alias: test.bean.params.metric 17 | dynamic_tags: 18 | - tag_name: cluster_id 19 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp,name=TestBean 20 | attribute: ClusterId 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/ServiceCheckHelperTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | public class ServiceCheckHelperTest { 8 | 9 | @Test 10 | public void testFormatServiceCheckPrefix() { 11 | // Let's get a list of Strings to test (add real versionned check names 12 | // here when you add new versionned check) 13 | String[][] data = { 14 | {"activemq_58.foo.bar12", "activemq.foo.bar12"}, 15 | {"test_package-X86_64-VER1:0.weird.metric_name", "testpackage.weird.metric_name"} 16 | }; 17 | 18 | // Let's test them all 19 | for (String[] datum : data) { 20 | assertEquals(datum[1], ServiceCheckHelper.formatServiceCheckPrefix(datum[0])); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/MetricCollectionTask.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | @Slf4j 9 | class MetricCollectionTask extends InstanceTask> { 10 | MetricCollectionTask(Instance instance) { 11 | super(instance); 12 | setWarning("Unable to collect metrics or refresh bean list."); 13 | } 14 | 15 | @Override 16 | public List call() throws Exception { 17 | 18 | if (!instance.timeToCollect()) { 19 | log.debug( 20 | "it is not time to collect, skipping run for instance: " + instance.getName()); 21 | 22 | // Maybe raise an exception here instead... 23 | return Collections.emptyList(); 24 | } 25 | 26 | return instance.getMetrics(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/jmx_bean_tags_normalize_params.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | bean: org.datadog.jmxfetch.test:type="SimpleTestJavaApp",scope="Co|olScope",host="localhost",component=,target_instance=".\*example.process.regex.\*" 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100 13 | - include: 14 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 15 | attribute: 16 | Hashmap.thisis0: 17 | metric_type: gauge 18 | alias: bean.parameters.should.not.match 19 | normalize_bean_param_tags: true 20 | -------------------------------------------------------------------------------- /src/test/resources/jmx_service_tag_global_list.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | service: 3 | - global 4 | - foo 5 | - bar 6 | - test 7 | 8 | instances: 9 | - process_name_regex: .*surefire.* 10 | name: jmx_test_1 11 | tags: 12 | - jmx:fetch 13 | conf: 14 | - include: 15 | domain: org.datadog.jmxfetch.test 16 | attribute: 17 | ShouldBe100: 18 | metric_type: gauge 19 | alias: this.is.100.$foo.$qux 20 | 21 | - process_name_regex: .*surefire.* 22 | name: jmx_test_2 23 | tags: 24 | - jmx:fetch 25 | conf: 26 | - include: 27 | domain: org.datadog.jmxfetch.test 28 | attribute: 29 | ShouldBe100: 30 | metric_type: gauge 31 | alias: this.is.100.$foo.$qux 32 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/JeeStats.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.Statistic; 4 | import javax.management.j2ee.statistics.Stats; 5 | import java.util.Map; 6 | 7 | public class JeeStats implements Stats { 8 | private final Map statistics; 9 | 10 | public JeeStats(Map statistics) { 11 | this.statistics = statistics; 12 | } 13 | 14 | @Override 15 | public Statistic getStatistic(String statisticName) { 16 | return statistics.get(statisticName); 17 | } 18 | 19 | @Override 20 | public String[] getStatisticNames() { 21 | return statistics.keySet().toArray(new String[0]); 22 | } 23 | 24 | @Override 25 | public Statistic[] getStatistics() { 26 | return statistics.values().toArray(new Statistic[0]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/jmx_service_tag_instance_override.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | service: global 3 | 4 | instances: 5 | - process_name_regex: .*surefire.* 6 | name: jmx_test_1 7 | service: override 8 | tags: 9 | - jmx:fetch 10 | conf: 11 | - include: 12 | domain: org.datadog.jmxfetch.test 13 | attribute: 14 | ShouldBe100: 15 | metric_type: gauge 16 | alias: this.is.100.$foo.$qux 17 | 18 | - process_name_regex: .*surefire.* 19 | name: jmx_test_2 20 | service: override 21 | tags: 22 | - jmx:fetch 23 | conf: 24 | - include: 25 | domain: org.datadog.jmxfetch.test 26 | attribute: 27 | ShouldBe100: 28 | metric_type: gauge 29 | alias: this.is.100.$foo.$qux 30 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/validator/ReporterValidator.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.validator; 2 | 3 | import com.beust.jcommander.IParameterValidator; 4 | import com.beust.jcommander.ParameterException; 5 | 6 | public class ReporterValidator implements IParameterValidator { 7 | 8 | /** Validates a reporter configurations (console, statsd). */ 9 | public void validate(String name, String value) throws ParameterException { 10 | if (!value.matches("^statsd:.+$") && !value.equals("console") && !value.equals("json")) { 11 | throw new ParameterException( 12 | "Parameter " 13 | + name 14 | + " should be either 'console', 'json'," 15 | + " 'statsd:[STATSD_HOST]:[STATSD_PORT]'" 16 | + " or 'statsd:unix://[STATSD_UNIX_SOCKET_PATH]'"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/validator/PositiveIntegerValidator.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.validator; 2 | 3 | import com.beust.jcommander.IParameterValidator; 4 | import com.beust.jcommander.ParameterException; 5 | 6 | public class PositiveIntegerValidator implements IParameterValidator { 7 | 8 | /** Validates whether an integer is positive. */ 9 | public void validate(String name, String value) throws ParameterException { 10 | try { 11 | int num = Integer.parseInt(value); 12 | if (num <= 0) { 13 | throw new ParameterException( 14 | "Parameter " + name + " should be positive (found " + value + ")"); 15 | } 16 | } catch (NumberFormatException e) { 17 | throw new ParameterException( 18 | "Parameter " + name + " should be an integer (found " + value + ")"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/jee/TimeStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.jee; 2 | 3 | import javax.management.j2ee.statistics.TimeStatistic; 4 | 5 | public class TimeStatisticImpl extends BaseStatistic implements TimeStatistic { 6 | private final long count; 7 | private final long min; 8 | private final long max; 9 | private final long total; 10 | 11 | public TimeStatisticImpl(String name, long min, long max, long total, long count) { 12 | super(name); 13 | this.count = count; 14 | this.min = min; 15 | this.max = max; 16 | this.total = total; 17 | } 18 | 19 | @Override 20 | public long getCount() { 21 | return count; 22 | } 23 | 24 | @Override 25 | public long getMaxTime() { 26 | return max; 27 | } 28 | 29 | @Override 30 | public long getMinTime() { 31 | return min; 32 | } 33 | 34 | @Override 35 | public long getTotalTime() { 36 | return total; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_test 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | type: DynamicTagTestApp 10 | tags: 11 | env: test 12 | attribute: 13 | Metric: 14 | metric_type: gauge 15 | alias: test.config.dynamic.tags.metric 16 | dynamic_tags: 17 | - tag_name: cluster_id 18 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 19 | attribute: ClusterId 20 | - tag_name: kafka_version 21 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 22 | attribute: Version 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/YamlParser.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import org.yaml.snakeyaml.Yaml; 4 | 5 | import java.io.InputStream; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @SuppressWarnings("unchecked") 10 | class YamlParser { 11 | 12 | private Map parsedYaml; 13 | 14 | public YamlParser(InputStream yamlInputStream) { 15 | parsedYaml = (Map) new Yaml().load(yamlInputStream); 16 | } 17 | 18 | public YamlParser(YamlParser other) { 19 | parsedYaml = new HashMap((Map) other.getParsedYaml()); 20 | } 21 | 22 | public Object getYamlInstances() { 23 | return parsedYaml.get("instances"); 24 | } 25 | 26 | public Object getInitConfig() { 27 | return parsedYaml.get("init_config"); 28 | } 29 | 30 | public Object getParsedYaml() { 31 | return parsedYaml; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/jmx_alias_match.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance1 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | attribute: 10 | ShouldBe100: 11 | metric_type: gauge 12 | alias: this.is.100.$foo.$qux 13 | ShouldBe1000: 14 | metric_type: gauge 15 | alias: this.is.thousand.$value 16 | ShouldBeConverted: 17 | metric_type: gauge 18 | alias: this.is.five.$value 19 | values: 20 | ShouldBe0: 0 21 | ShouldBe5: 5 22 | Hashmap.thisis0: 23 | metric_type: gauge 24 | alias: $domain.$qux.$attribute 25 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/util/server/run-SimpleApp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -f 3 | 4 | [ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx128M -Xms128M" 5 | [ -n "$RMI_PORT" ] || RMI_PORT="9010" 6 | 7 | echo "Using `java --version`" 8 | echo "With JAVA_OPTS '${JAVA_OPTS}'" 9 | CONTAINER_IP=`awk 'END{print $1}' /etc/hosts` 10 | 11 | # shellcheck disable=SC2086 12 | javac -d app app.java 13 | 14 | echo "Starting app with hostname set to ${CONTAINER_IP}" 15 | 16 | java -cp ./app \ 17 | ${JAVA_OPTS} \ 18 | -Dcom.sun.management.jmxremote=true \ 19 | -Dcom.sun.management.jmxremote.port=${RMI_PORT} \ 20 | -Dcom.sun.management.jmxremote.rmi.port=${RMI_PORT} \ 21 | -Dcom.sun.management.jmxremote.authenticate=false \ 22 | -Dcom.sun.management.jmxremote.ssl=false \ 23 | -Djava.rmi.server.hostname=${CONTAINER_IP} \ 24 | org.datadog.jmxfetch.util.server.SimpleApp 25 | 26 | # java -jar jmxterm-1.0.2-uber.jar -l service:jmx:rmi:///jndi/rmi://localhost:9010/jmxrmi 27 | -------------------------------------------------------------------------------- /src/test/resources/jmx_filtering_test.yaml: -------------------------------------------------------------------------------- 1 | instances: 2 | - process_name_regex: .*surefire.* 3 | name: jmx_test_instance 4 | 5 | 6 | init_config: 7 | conf: 8 | - include: 9 | domain: org.glassfish.jersey 10 | subType: 11 | - Uris 12 | resource: 13 | - "\"/v1/MyMixUser/{userGuid}/{channelGuid}/tune\"" 14 | executionTimes: 15 | - MethodTimes 16 | # attribute: 17 | # "AverageTime[ms]_1m": 18 | # alias: jmx.siriusxm.sequencer.tune.ave_time 19 | # metric_type: gauge 20 | - include: 21 | domain: org.glassfish.jersey 22 | subType: 23 | - Uris 24 | resource: 25 | - "\"/v1/MyMixUser/{userGuid}/{channelGuid}/clips/play\"" 26 | executionTimes: 27 | - MethodTimes 28 | # attribute: 29 | # "AverageTime[ms]_1m": 30 | # alias: jmx.siriusxm.sequencer.play.ave_time 31 | # metric_type: gauge 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/ServiceCheckHelper.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | public class ServiceCheckHelper { 4 | /** 5 | * Formats the service check prefix. 6 | * First implemented here: 7 | * https://github.com/DataDog/jmxfetch/commit/0428c41ebf7a14404ae50928e3ecfc229701c042 8 | * 9 | *

The formatted service check name is kept for backward compatibility only. 10 | * Previously there were 2 JMXFetch integrations for activemq: one called activemq 11 | * for older versions of activemq, the other called activemq_58 for v5.8+ of activemq, 12 | * see https://github.com/DataDog/dd-agent/tree/5.10.x/conf.d 13 | * And we still wanted both integrations to send the service check with the activemq prefix. 14 | * */ 15 | public static String formatServiceCheckPrefix(String fullname) { 16 | String[] chunks = fullname.split("\\."); 17 | chunks[0] = chunks[0].replaceAll("[A-Z0-9:_\\-]", ""); 18 | return StringUtils.join(".", chunks); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/TestJsonPrinter.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.StringWriter; 6 | import java.io.ByteArrayOutputStream; 7 | import java.util.Map; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Arrays; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | 14 | public class TestJsonPrinter { 15 | String print(Object obj) throws Exception { 16 | StringWriter sw = new StringWriter(); 17 | JsonPrinter.prettyPrint(sw, obj); 18 | return sw.toString(); 19 | } 20 | 21 | @Test 22 | public void printer() throws Exception { 23 | assertEquals("null", print(null)); 24 | assertEquals("[ 1, 2, 3.5, null, \"abc\" ]", print(Arrays.asList(1, 2, 3.5, null, "abc"))); 25 | assertEquals("\"お茶\"", print("お茶")); 26 | assertEquals("\"\\\\/\\\"\\u0001\\b\\t\\n\\u000b\\f\\r\"", print("\\/\"\u0001\u0008\u0009\n\u000b\u000c\r")); 27 | assertEquals("\"☀️😎🏖️\"", print("☀️😎🏖️")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/resources/jmx_bean_scope.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_test_instance 6 | conf: 7 | - include: 8 | domain: org.datadog.jmxfetch.test 9 | scope: sameScope 10 | type: sameType 11 | param: sameParam 12 | additional: additionalParam 13 | - include: 14 | domain: org.datadog.jmxfetch.test 15 | scope: sameScope 16 | type: sameType 17 | param: sameParam 18 | - include: 19 | domain: org.datadog.jmxfetch.test 20 | scope: sameScope 21 | type: 22 | - sameType 23 | - notTheSameType 24 | param: sameParam 25 | - include: 26 | bean: 27 | - org.datadog.jmxfetch.test:scope=sameScope,param=sameParam,type=sameType 28 | - org.datadog.jmxfetch.test:scope=sameScope,param=notTheSameParam,type=sameType 29 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/TimerWaitStrategy.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import org.testcontainers.containers.wait.strategy.WaitStrategy; 4 | import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; 5 | import java.time.Duration; 6 | 7 | public class TimerWaitStrategy implements WaitStrategy { 8 | 9 | private final long sleepTimeMillis; 10 | 11 | public TimerWaitStrategy(long sleepTimeMillis) { 12 | this.sleepTimeMillis = sleepTimeMillis; 13 | } 14 | 15 | @Override 16 | public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { 17 | try { 18 | Thread.sleep(sleepTimeMillis); 19 | } catch (InterruptedException e) { 20 | Thread.currentThread().interrupt(); 21 | throw new RuntimeException("TimerWaitStrategy was interrupted", e); 22 | } 23 | } 24 | 25 | @Override 26 | public WaitStrategy withStartupTimeout(Duration startupTimeout) { 27 | return this; // This method can be a no-op for this strategy, as the timeout is fixed 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/Metric.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | /** 4 | * Metric carrier class. 5 | */ 6 | public class Metric { 7 | private final String alias; 8 | private final String metricType; 9 | private final String[] tags; 10 | private final String checkName; 11 | private double value; 12 | 13 | 14 | /** 15 | * Metric constructor. 16 | */ 17 | public Metric(String alias, String metricType, String[] tags, String checkName) { 18 | this.alias = alias; 19 | this.metricType = metricType; 20 | this.tags = tags; 21 | this.checkName = checkName; 22 | } 23 | 24 | public String getAlias() { 25 | return alias; 26 | } 27 | 28 | public String getMetricType() { 29 | return metricType; 30 | } 31 | 32 | public double getValue() { 33 | return value; 34 | } 35 | 36 | public String[] getTags() { 37 | return tags; 38 | } 39 | 40 | public String getCheckName() { 41 | return checkName; 42 | } 43 | 44 | public void setValue(double value) { 45 | this.value = value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/DynamicTagTestApp.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | public class DynamicTagTestApp implements DynamicTagTestAppMBean { 4 | private final String clusterId; 5 | private final String version; 6 | private final int port; 7 | private double metric; 8 | 9 | public DynamicTagTestApp() { 10 | this("local-kafka-cluster", "2.8.0", 9092); 11 | } 12 | 13 | public DynamicTagTestApp(String clusterId, String version, int port) { 14 | this.clusterId = clusterId; 15 | this.version = version; 16 | this.port = port; 17 | this.metric = 100.0; 18 | } 19 | 20 | @Override 21 | public String getClusterId() { 22 | return clusterId; 23 | } 24 | 25 | @Override 26 | public String getVersion() { 27 | return version; 28 | } 29 | 30 | @Override 31 | public int getPort() { 32 | return port; 33 | } 34 | 35 | @Override 36 | public Double getMetric() { 37 | return metric; 38 | } 39 | 40 | public void setMetric(double metric) { 41 | this.metric = metric; 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/ExitWatcher.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import java.io.File; 4 | 5 | public class ExitWatcher { 6 | 7 | private String exitFileLocation; 8 | private boolean isEnabled; 9 | 10 | /** Default constructor. */ 11 | public ExitWatcher() { 12 | this(null); 13 | } 14 | 15 | public ExitWatcher(String exitFileLocation) { 16 | this.exitFileLocation = exitFileLocation; 17 | this.isEnabled = this.exitFileLocation != null; 18 | } 19 | 20 | public String getExitFileLocation() { 21 | return exitFileLocation; 22 | } 23 | 24 | public boolean isEnabled() { 25 | return isEnabled; 26 | } 27 | 28 | /** Evaluates if its time to exit according to exit-file presence. */ 29 | public boolean shouldExit() { 30 | if (isEnabled()) { 31 | File file = new File(exitFileLocation); 32 | if (file.exists() && !file.isDirectory()) { 33 | return true; 34 | } 35 | } 36 | return false; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return exitFileLocation; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.9" 3 | 4 | services: 5 | # The docker compose service name is used as the hostname for the misbehaving-jmx-server 6 | # Note it is in the entrypoint as the --rmi-host and in the AD label as the hostname 7 | # that the Agent should reach out to. 8 | jmx-test-server: 9 | build: 10 | context: . 11 | # Override entrypoint to specify the docker-compose service name as the RMI host 12 | entrypoint: ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.misbehavingjmxserver.App", "--rmi-host", "jmx-test-server"] 13 | ports: 14 | - "1099:1099" 15 | labels: 16 | 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"}}]}]}}' 17 | datadog: 18 | image: datadog/agent:7-jmx 19 | pid: host 20 | environment: 21 | - DD_API_KEY=000000001 22 | volumes: 23 | - /var/run/docker.sock:/var/run/docker.sock 24 | - /proc/:/host/proc/:ro 25 | - /sys/fs/cgroup:/host/sys/fs/cgroup:ro 26 | -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags_multiple_per_conf.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_multiple_per_conf_test 6 | conf: 7 | # Config with multiple dynamic tags (cluster_id AND version) on the same metric 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | type: DynamicTagTestApp 11 | attribute: 12 | Metric: 13 | metric_type: gauge 14 | alias: test.metric.with.multiple.tags 15 | tags: 16 | env: test 17 | dynamic_tags: 18 | - tag_name: cluster_id 19 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 20 | attribute: ClusterId 21 | - tag_name: version 22 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 23 | attribute: Version 24 | - tag_name: port 25 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 26 | attribute: Port 27 | 28 | 29 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/java 3 | { 4 | "name": "Java", 5 | "image": "mcr.microsoft.com/devcontainers/java:8", 6 | 7 | "features": { 8 | "ghcr.io/devcontainers/features/java:1": { 9 | "additionalVersions": "21-tem, 17.0.15-tem, 11.0.27-tem", 10 | "installGradle": "true", 11 | "installMaven": "true", 12 | }, 13 | "ghcr.io/devcontainers/features/docker-in-docker:2": { // Needed for Testcontainers for Java 14 | "disableIp6tables": true 15 | } 16 | }, 17 | 18 | // Configure tool-specific properties. 19 | "customizations": { 20 | // Configure properties specific to VS Code. 21 | "vscode": { 22 | "settings": {}, 23 | "extensions": [ 24 | "streetsidesoftware.code-spell-checker" 25 | ] 26 | } 27 | } 28 | 29 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 30 | // "forwardPorts": [], 31 | 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | // "postCreateCommand": "java -version", 34 | 35 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 36 | // "remoteUser": "root" 37 | } 38 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/default-jmx-metrics.yaml: -------------------------------------------------------------------------------- 1 | # This is a reduced set of 'default' metrics to make tests more predictable 2 | 3 | # Memory 4 | - include: 5 | domain: java.lang 6 | type: Memory 7 | attribute: 8 | HeapMemoryUsage.used: 9 | alias: jvm.heap_memory 10 | metric_type: gauge 11 | HeapMemoryUsage.committed: 12 | alias: jvm.heap_memory_committed 13 | metric_type: gauge 14 | HeapMemoryUsage.init: 15 | alias: jvm.heap_memory_init 16 | metric_type: gauge 17 | HeapMemoryUsage.max: 18 | alias: jvm.heap_memory_max 19 | metric_type: gauge 20 | NonHeapMemoryUsage.used: 21 | alias: jvm.non_heap_memory 22 | metric_type: gauge 23 | NonHeapMemoryUsage.committed: 24 | alias: jvm.non_heap_memory_committed 25 | metric_type: gauge 26 | NonHeapMemoryUsage.init: 27 | alias: jvm.non_heap_memory_init 28 | metric_type: gauge 29 | NonHeapMemoryUsage.max: 30 | alias: jvm.non_heap_memory_max 31 | metric_type: gauge 32 | 33 | # Threads 34 | - include: 35 | domain: java.lang 36 | type: Threading 37 | attribute: 38 | ThreadCount: 39 | alias: jvm.thread_count 40 | metric_type: gauge 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags_caching.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_caching_test 6 | conf: 7 | # Config 1: Uses cluster_id 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | type: DynamicTagTestApp 11 | attribute: 12 | Metric: 13 | metric_type: gauge 14 | alias: test.metric1 15 | dynamic_tags: 16 | - tag_name: cluster_id 17 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 18 | attribute: ClusterId 19 | 20 | # Config 2: Uses same cluster_id (should be cached) 21 | - include: 22 | domain: org.datadog.jmxfetch.test 23 | type: DynamicTagTestApp 24 | attribute: 25 | Port: 26 | metric_type: gauge 27 | alias: test.metric2 28 | dynamic_tags: 29 | - tag_name: cluster_id 30 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 31 | attribute: ClusterId 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/tasks/TaskStatusHandler.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.tasks; 2 | 3 | public class TaskStatusHandler { 4 | Object data; 5 | Throwable throwableStatus; 6 | 7 | /** 8 | * Default constructor. 9 | * */ 10 | public TaskStatusHandler() {} 11 | 12 | /** 13 | * Constructor sets throwable for status reporting. 14 | * */ 15 | public TaskStatusHandler(Throwable throwable) { 16 | throwableStatus = throwable; 17 | } 18 | 19 | /** 20 | * Constructor sets throwable for status reporting, and 21 | * generic object if return data is expected. 22 | * */ 23 | public TaskStatusHandler(Object obj, Throwable throwable) { 24 | data = obj; 25 | throwableStatus = throwable; 26 | } 27 | 28 | public void setData(Object data) { 29 | this.data = data; 30 | } 31 | 32 | public Object getData() { 33 | return data; 34 | } 35 | 36 | public void setThrowableStatus(Throwable throwable) { 37 | throwableStatus = throwable; 38 | } 39 | 40 | /** 41 | * Throws the throwable status set in the handler if not null. 42 | * */ 43 | public void raiseForStatus() throws Throwable { 44 | if (throwableStatus != null) { 45 | throw throwableStatus; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.9" 3 | 4 | services: 5 | # The docker compose service name is used as the hostname for the misbehaving-jmx-server 6 | # Note it is in the entrypoint as the --rmi-host and in the AD label as the hostname 7 | # that the Agent should reach out to. 8 | jmx-test-server: 9 | build: 10 | context: ./tools/misbehaving-jmx-server/ 11 | # Override entrypoint to specify the docker-compose service name as the RMI host 12 | entrypoint: ["java", "-cp", "misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar", "org.datadog.misbehavingjmxserver.App", "--rmi-host", "jmx-test-server"] 13 | ports: 14 | - "1099:1099" 15 | labels: 16 | 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"}}]}]}}' 17 | datadog: 18 | build: 19 | context: . 20 | dockerfile: ./Dockerfile.agentbase 21 | pid: host 22 | environment: 23 | - DD_API_KEY=00000000001 # Remove me if you specify a valid API key in .env 24 | - DD_JMX_TELEMETRY_ENABLED=true 25 | env_file: .env 26 | volumes: 27 | - /var/run/docker.sock:/var/run/docker.sock 28 | - /proc/:/host/proc/:ro 29 | - /sys/fs/cgroup:/host/sys/fs/cgroup:ro 30 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/AppTelemetry.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** Jmxfetch telemetry JMX MBean. */ 6 | public class AppTelemetry implements AppTelemetryMBean { 7 | private AtomicInteger runningInstanceCount; 8 | private AtomicInteger brokenInstanceCount; 9 | private AtomicInteger brokenInstanceEventCount; 10 | 11 | /** Jmxfetch telemetry bean constructor. */ 12 | public AppTelemetry() { 13 | runningInstanceCount = new AtomicInteger(0); 14 | brokenInstanceCount = new AtomicInteger(0); 15 | brokenInstanceEventCount = new AtomicInteger(0); 16 | } 17 | 18 | public int getRunningInstanceCount() { 19 | return runningInstanceCount.get(); 20 | } 21 | 22 | public int getBrokenInstanceCount() { 23 | return brokenInstanceCount.get(); 24 | } 25 | 26 | public int getBrokenInstanceEventCount() { 27 | return brokenInstanceEventCount.get(); 28 | } 29 | 30 | public void setRunningInstanceCount(int count) { 31 | this.runningInstanceCount.set(count); 32 | } 33 | 34 | public void setBrokenInstanceCount(int count) { 35 | brokenInstanceCount.set(count); 36 | } 37 | 38 | public void incrementBrokenInstanceEventCount() { 39 | brokenInstanceEventCount.incrementAndGet(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/resources/org/datadog/jmxfetch/sample-metrics.yaml: -------------------------------------------------------------------------------- 1 | # Same as default-jmx-metrics.yaml but with the top level jmx_metrics to match standard metrics.yaml file structure. 2 | 3 | jmx_metrics: 4 | 5 | # Memory 6 | - include: 7 | domain: java.lang 8 | type: Memory 9 | attribute: 10 | HeapMemoryUsage.used: 11 | alias: jvm.heap_memory 12 | metric_type: gauge 13 | HeapMemoryUsage.committed: 14 | alias: jvm.heap_memory_committed 15 | metric_type: gauge 16 | HeapMemoryUsage.init: 17 | alias: jvm.heap_memory_init 18 | metric_type: gauge 19 | HeapMemoryUsage.max: 20 | alias: jvm.heap_memory_max 21 | metric_type: gauge 22 | NonHeapMemoryUsage.used: 23 | alias: jvm.non_heap_memory 24 | metric_type: gauge 25 | NonHeapMemoryUsage.committed: 26 | alias: jvm.non_heap_memory_committed 27 | metric_type: gauge 28 | NonHeapMemoryUsage.init: 29 | alias: jvm.non_heap_memory_init 30 | metric_type: gauge 31 | NonHeapMemoryUsage.max: 32 | alias: jvm.non_heap_memory_max 33 | metric_type: gauge 34 | 35 | # Threads 36 | - include: 37 | domain: java.lang 38 | type: Threading 39 | attribute: 40 | ThreadCount: 41 | alias: jvm.thread_count 42 | metric_type: gauge 43 | 44 | -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags_multi.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_multi_test 6 | conf: 7 | # Config 1: Has cluster_id dynamic tag 8 | - include: 9 | domain: org.datadog.jmxfetch.test 10 | type: DynamicTagTestApp 11 | name: Instance1 12 | attribute: 13 | Metric: 14 | metric_type: gauge 15 | alias: test.instance1.metric 16 | dynamic_tags: 17 | - tag_name: cluster_id 18 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp,name=Instance1 19 | attribute: ClusterId 20 | 21 | # Config 2: Has version dynamic tag 22 | - include: 23 | domain: org.datadog.jmxfetch.test 24 | type: DynamicTagTestApp 25 | name: Instance2 26 | attribute: 27 | Metric: 28 | metric_type: gauge 29 | alias: test.instance2.metric 30 | dynamic_tags: 31 | - tag_name: version 32 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp,name=Instance2 33 | attribute: Version 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/validator/LogLevelValidator.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.validator; 2 | 3 | 4 | import com.beust.jcommander.IParameterValidator; 5 | import com.beust.jcommander.ParameterException; 6 | import org.datadog.jmxfetch.util.StringUtils; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public class LogLevelValidator implements IParameterValidator { 12 | // for history, there is a `FATAL` log level supported here as we were supporting it 13 | // before moving to the `java.util.logging` log system. 14 | // we keep it here since we still want it to be valid, but we consider it as ERROR 15 | // in jmxfetch 16 | // FIXME: remove the "LEVEL" log level, which was introduced by mistake early in JMXFetch's 17 | // development (currently defaults to INFO). 18 | public static final List LOGLEVELS = 19 | Arrays.asList( 20 | "ALL", "DEBUG", "ERROR", "FATAL", "INFO", "OFF", "TRACE", "LEVEL", "WARN"); 21 | 22 | /** Validates a string as a valid logging level. */ 23 | public void validate(String name, String value) throws ParameterException { 24 | 25 | if (!LOGLEVELS.contains(value.toUpperCase())) { 26 | String message = 27 | "Parameter " 28 | + name 29 | + " should be in (" 30 | + StringUtils.join(",", LOGLEVELS) 31 | + ")"; 32 | throw new ParameterException(message); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/JMXServerControlClient.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | @Slf4j 10 | public class JMXServerControlClient extends JMXServerClient{ 11 | public JMXServerControlClient(String host, int port) { 12 | super(host, port); 13 | } 14 | 15 | public List getMBeans(String domain) throws IOException { 16 | String endpoint = "/beans/" + domain; 17 | String response = sendGetRequest(endpoint); 18 | return Arrays.asList(response.toString().split(",")); 19 | 20 | } 21 | 22 | public void createMBeans(String domain, int numDesiredBeans, int scalarAttributeCount, 23 | int tabularAttributeCount, int compositeValuesPerTabularAttribute) throws IOException { 24 | 25 | String endpoint = "/beans/" + domain; 26 | String jsonPayload = "{\"beanCount\": " + numDesiredBeans 27 | + ", \"scalarAttributeCount\": " + scalarAttributeCount 28 | + ", \"tabularAttributeCount\": " + tabularAttributeCount 29 | + ", \"compositeValuesPerTabularAttribute\": " + compositeValuesPerTabularAttribute + "}"; 30 | sendPostRequestWithPayload(endpoint, jsonPayload); 31 | } 32 | 33 | public void jmxCutNetwork() throws IOException { 34 | sendPostRequestWithPayload("/cutNetwork", ""); 35 | } 36 | 37 | public void jmxRestoreNetwork() throws IOException { 38 | sendPostRequestWithPayload("/restoreNetwork", ""); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/resources/jmx_config_dynamic_tags_invalid.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - process_name_regex: .*surefire.* 5 | name: jmx_config_dynamic_tags_invalid_test 6 | conf: 7 | # Invalid dynamic tag configurations (should be handled gracefully) 8 | # Normal tags should still be applied, but invalid dynamic tags should be ignored 9 | - include: 10 | domain: org.datadog.jmxfetch.test 11 | type: DynamicTagTestApp 12 | attribute: 13 | Metric: 14 | metric_type: gauge 15 | alias: test.metric.with.invalid.tags 16 | tags: 17 | env: test 18 | region: us-east-1 19 | dynamic_tags: 20 | # Missing tag_name - should be ignored with warning 21 | - bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 22 | attribute: ClusterId 23 | # Missing bean_name - should be ignored with warning 24 | - tag_name: missing_bean 25 | attribute: Version 26 | # Missing attribute - should be ignored with warning 27 | - tag_name: missing_attr 28 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 29 | # Valid one - should work 30 | - tag_name: valid_port 31 | bean_name: org.datadog.jmxfetch.test:type=DynamicTagTestApp 32 | attribute: Port 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | 6 | public class StringUtils { 7 | 8 | /** 9 | * Joins the parts together delimitd by the delimiter. 10 | * @param delimiter the delimiter 11 | * @param parts the parts to join 12 | * @return a new string composed of the parts delimited by the delimiter 13 | */ 14 | public static String join(String delimiter, Collection parts) { 15 | StringBuilder sb = new StringBuilder(); 16 | Iterator it = parts.iterator(); 17 | if (it.hasNext()) { 18 | sb.append(it.next()); 19 | } 20 | while (it.hasNext()) { 21 | sb.append(delimiter).append(it.next()); 22 | } 23 | return sb.toString(); 24 | } 25 | 26 | /** 27 | * Joins the parts together delimitd by the delimiter. 28 | * @param delimiter the delimiter 29 | * @param parts the parts to join 30 | * @return a new string composed of the parts delimited by the delimiter 31 | */ 32 | public static String join(String delimiter, String... parts) { 33 | if (parts.length > 1) { 34 | StringBuilder sb = new StringBuilder(); 35 | sb.append(parts[0]); 36 | for (int i = 1; i < parts.length; ++i) { 37 | sb.append(delimiter).append(parts[i]); 38 | } 39 | return sb.toString(); 40 | } 41 | if (parts.length == 1) { 42 | return parts[0]; 43 | } 44 | return ""; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2013-2016, Datadog 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of Datadog nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/test/resources/jmx_sd_pipe.txt: -------------------------------------------------------------------------------- 1 | #### SERVICE-DISCOVERY #### 2 | # cassandra_0 3 | init_config: 4 | 5 | instances: 6 | - process_name_regex: .*surefire.* 7 | name: jmx_first_instance 8 | cassandra_aliasing: true 9 | conf: 10 | - include: 11 | bean: org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks 12 | attribute: 13 | - ShouldBe100 14 | - process_name_regex: .*surefire.* 15 | name: jmx_second_instance 16 | conf: 17 | - include: 18 | bean: org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks 19 | attribute: 20 | - ShouldBe1000 21 | 22 | #### SERVICE-DISCOVERY #### 23 | # jmx_0 24 | init_config: 25 | 26 | instances: 27 | - process_name_regex: .*surefire.* 28 | name: jmx_test_instance2 29 | conf: 30 | - include: 31 | bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= 32 | attribute: 33 | ShouldBe100: 34 | metric_type: gauge 35 | alias: this.is.100 36 | - include: 37 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 38 | attribute: 39 | Hashmap.thisis0: 40 | metric_type: gauge 41 | alias: bean.parameters.should.not.match 42 | 43 | 44 | #### AUTO-DISCOVERY TERM #### 45 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | import javax.management.j2ee.statistics.Statistic; 8 | import javax.management.j2ee.statistics.Stats; 9 | import javax.management.openmbean.CompositeData; 10 | import javax.management.openmbean.CompositeDataSupport; 11 | import javax.management.openmbean.TabularData; 12 | import javax.management.openmbean.TabularDataSupport; 13 | 14 | public interface SimpleTestJavaAppMBean { 15 | 16 | int getShouldBe100(); 17 | 18 | Double getShouldBe1000(); 19 | 20 | int getShouldBeCounter(); 21 | 22 | String getShouldBeConverted(); 23 | 24 | String getShouldBeDefaulted(); 25 | 26 | boolean getShouldBeBoolean(); 27 | 28 | Map getHashmap(); 29 | 30 | AtomicInteger getAtomic42(); 31 | 32 | AtomicLong getAtomic4242(); 33 | 34 | Object getObject1337(); 35 | 36 | Number getNumberBig(); 37 | 38 | Long getLong42424242(); 39 | 40 | Integer getInt424242(); 41 | 42 | float getPrimitiveFloat(); 43 | 44 | Float getInstanceFloat(); 45 | 46 | TabularData getTabulardata(); 47 | 48 | TabularDataSupport getTabularDataSupport(); 49 | 50 | CompositeData getNestedCompositeData(); 51 | 52 | Statistic getJeeCounter(); 53 | 54 | Statistic getJeeRange(); 55 | 56 | Statistic getJeeTime(); 57 | 58 | Statistic getJeeBoundary(); 59 | 60 | Statistic getJeeBoundedRange(); 61 | 62 | Statistic getJeeUnsupported(); 63 | 64 | Stats getJeeStat(); 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/ByteArraySearcherTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.Arrays; 9 | import java.util.UUID; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | @RunWith(Parameterized.class) 14 | public class ByteArraySearcherTest { 15 | 16 | private final byte[] term; 17 | private final byte[] content; 18 | private final boolean matches; 19 | 20 | public ByteArraySearcherTest(String content, String term, boolean matches) { 21 | this.content = content.getBytes(Charset.forName("UTF-8")); 22 | this.term = term.getBytes(Charset.forName("UTF-8")); 23 | this.matches = matches; 24 | } 25 | 26 | @Parameterized.Parameters 27 | public static Iterable testCases() { 28 | return Arrays.asList( 29 | new Object[][] { 30 | {"foobar", "foo", true}, 31 | {"foofoo", "foo", true}, 32 | {"", "foo", false}, 33 | {"bar", "foo", false}, 34 | {"barbarfoo", "foo", true}, 35 | {"barbarfoobar", "foo", true}, 36 | {"fofofofofo", "foo", false}, 37 | {UUID.randomUUID().toString(), "pqrst", false}, 38 | } 39 | ); 40 | } 41 | 42 | 43 | @Test 44 | public void testFindTermInContent() { 45 | assertEquals(matches, new ByteArraySearcher(term).matches(content)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/assembly/uber.xml: -------------------------------------------------------------------------------- 1 | 2 | uber 3 | 4 | jar 5 | 6 | / 7 | 8 | 9 | src/main/config/classworlds.conf 10 | WORLDS-INF/conf 11 | 12 | 13 | 14 | 15 | WORLDS-INF/lib 16 | 17 | ${artifact.artifactId}.jar 18 | 19 | compile 20 | false 21 | 22 | *:* 23 | 24 | 25 | jline:jline 26 | 27 | 28 | 29 | / 30 | true 31 | 32 | classworlds:classworlds-boot 33 | jline:jline 34 | 35 | 36 | 37 | WORLDS-INF 38 | 39 | ${artifact.artifactId}.jar 40 | 41 | 42 | classworlds:classworlds 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | @RunWith(Parameterized.class) 14 | public class StringUtilsTest { 15 | 16 | 17 | @Parameterized.Parameters 18 | public static Iterable testCases() { 19 | return Arrays.asList(new Object[][] { 20 | {Collections.emptyList(), ":", ""}, 21 | {Collections.singletonList("foo"), ":", "foo"}, 22 | {Arrays.asList("foo", "bar"), ":", "foo:bar"}, 23 | {Arrays.asList("foo", "bar"), "::", "foo::bar"}, 24 | {Arrays.asList("foo", "bar", "qux"), ":", "foo:bar:qux"}, 25 | {Arrays.asList("foo", "bar", "qux"), "::", "foo::bar::qux"} 26 | }); 27 | } 28 | 29 | private final Collection parts; 30 | private final String delimiter; 31 | private final String expected; 32 | 33 | public StringUtilsTest(Collection parts, String delimiter, String expected) { 34 | this.parts = parts; 35 | this.delimiter = delimiter; 36 | this.expected = expected; 37 | } 38 | 39 | @Test 40 | public void testJoinCollection() { 41 | assertEquals(expected, StringUtils.join(delimiter, parts)); 42 | } 43 | 44 | @Test 45 | public void testJoinArray() { 46 | assertEquals(expected, StringUtils.join(delimiter, parts.toArray(new String[0]))); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/InstanceTelemetry.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | 4 | /** Jmxfetch telemetry JMX MBean. */ 5 | public class InstanceTelemetry implements InstanceTelemetryMBean { 6 | 7 | private int beansFetched; 8 | private int topLevelAttributeCount; 9 | private int metricCount; 10 | private int wildcardDomainQueryCount; 11 | private double beanMatchRatio; 12 | 13 | /** Jmxfetch telemetry bean constructor. */ 14 | public InstanceTelemetry() { 15 | beansFetched = 0; 16 | topLevelAttributeCount = 0; 17 | metricCount = 0; 18 | wildcardDomainQueryCount = 0; 19 | beanMatchRatio = 0.0; 20 | } 21 | 22 | public int getBeansFetched() { 23 | return beansFetched; 24 | } 25 | 26 | public int getTopLevelAttributeCount() { 27 | return topLevelAttributeCount; 28 | } 29 | 30 | public int getMetricCount() { 31 | return metricCount; 32 | } 33 | 34 | public int getWildcardDomainQueryCount() { 35 | return wildcardDomainQueryCount; 36 | } 37 | 38 | public double getBeanMatchRatio() { 39 | return beanMatchRatio; 40 | } 41 | 42 | public void setBeansFetched(int count) { 43 | beansFetched = count; 44 | } 45 | 46 | public void setTopLevelAttributeCount(int count) { 47 | topLevelAttributeCount = count; 48 | } 49 | 50 | public void setMetricCount(int count) { 51 | metricCount = count; 52 | } 53 | 54 | public void setWildcardDomainQueryCount(int count) { 55 | wildcardDomainQueryCount = count; 56 | } 57 | 58 | public void setBeanMatchRatio(double ratio) { 59 | beanMatchRatio = ratio; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # Use by default the JDK image used to build the jar 3 | ARG FINAL_JRE_IMAGE=base 4 | # Use the official JDK image as the base image 5 | FROM eclipse-temurin:17 AS base 6 | 7 | # Use the base image as the build image 8 | FROM base AS build 9 | # Set the working directory to /app 10 | WORKDIR /app 11 | 12 | # Copy the pom.xml and Maven files and install the dependencies 13 | COPY .mvn .mvn/ 14 | COPY pom.xml mvnw mvnw.cmd ./ 15 | 16 | # TODO: investigate why mount caching does not seem to work Test containers 17 | # Enabling this will speed up tests as the Maven cache can be shared between all builds 18 | # RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ 19 | RUN set -eu && \ 20 | ./mvnw dependency:resolve; 21 | 22 | # Copy the source code and build the JAR file 23 | COPY src/ src/ 24 | 25 | # TODO: investigate why mount caching does not seem to work Test containers 26 | # RUN --mount=type=cache,id=mavenCache,target=/root/.m2,sharing=locked \ 27 | RUN set -eu && \ 28 | ./mvnw clean package assembly:single; 29 | 30 | # Use the image specified by FINAL_JRE_IMAGE build arg (default "base") 31 | FROM ${FINAL_JRE_IMAGE} AS final 32 | 33 | # Set the working directory to /app 34 | WORKDIR /app 35 | 36 | COPY scripts/start.sh /usr/bin/ 37 | 38 | # Copy the JAR file from the Maven image to the final image 39 | COPY --from=build /app/target/misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar . 40 | 41 | # RMI Port 42 | EXPOSE 9090 43 | 44 | # Control Port 45 | EXPOSE 9091 46 | 47 | # Supervisor Port 48 | EXPOSE 9092 49 | 50 | # Run the supervisor class from the jar 51 | ENTRYPOINT [ "/usr/bin/start.sh" ] 52 | 53 | CMD [ "org.datadog.supervisor.App" ] 54 | -------------------------------------------------------------------------------- /src/test/resources/jmx_sd_pipe_longname.txt: -------------------------------------------------------------------------------- 1 | #### SERVICE-DISCOVERY #### 2 | # jmx_0 3 | init_config: 4 | 5 | instances: 6 | - process_name_regex: .*surefire.* 7 | name: jmx_test_instance2 8 | conf: 9 | - include: 10 | bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= 11 | attribute: 12 | ShouldBe100: 13 | metric_type: gauge 14 | alias: this.is.100 15 | - include: 16 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 17 | attribute: 18 | Hashmap.thisis0: 19 | metric_type: gauge 20 | alias: bean.parameters.should.not.match 21 | 22 | #### SERVICE-DISCOVERY #### 23 | # jmxbutthisisincrediblylongandshouldneverbeavalidnamebecausewhoneedsanamelikethishonestly_0 24 | init_config: 25 | 26 | instances: 27 | - process_name_regex: .*surefire.* 28 | name: jmx_test_instance3 29 | conf: 30 | - include: 31 | bean: org.datadog.jmxfetch.test:type=SimpleTestJavaApp,scope=Co|olScope,host=localhost,component= 32 | attribute: 33 | ShouldBe100: 34 | metric_type: gauge 35 | alias: this.is.100 36 | - include: 37 | bean: org.datadog.jmxfetch.test:type=WrongType,scope=WrongScope,host=localhost,component= 38 | attribute: 39 | Hashmap.thisis0: 40 | metric_type: gauge 41 | alias: bean.parameters.should.not.match 42 | 43 | 44 | #### AUTO-DISCOVERY TERM #### 45 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/ByteArraySearcher.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | /** 4 | * Employs the literal version of the Bitap/shift-and algorithm 5 | * to match short search terms in worst-case linear time. 6 | * 7 | *

The masks are bit-sliced to reduce spatial requirement from ~2KB 8 | * per matcher to ~256 bytes. 9 | */ 10 | public final class ByteArraySearcher { 11 | 12 | private final long[] high; 13 | private final long[] low; 14 | private final long termination; 15 | 16 | /** 17 | * Constructs a simple literal matcher from the input term. 18 | * @param term the input term, must be shorter than 64 bytes. 19 | */ 20 | public ByteArraySearcher(byte[] term) { 21 | if (term.length > 64) { 22 | throw new IllegalArgumentException("term must be shorter than 64 characters"); 23 | } 24 | this.high = new long[16]; 25 | this.low = new long[16]; 26 | int mask = 1; 27 | for (byte b : term) { 28 | low[b & 0xF] |= mask; 29 | high[b >>> 4] |= mask; 30 | mask <<= 1; 31 | } 32 | this.termination = 1 << (term.length - 1); 33 | } 34 | 35 | 36 | /** 37 | * Returns true if the array contains a literal match of this searcher's term. 38 | * @param array the input array 39 | * @return whether the array matches or not 40 | */ 41 | public boolean matches(byte[] array) { 42 | long state = 0; 43 | for (byte symbol : array) { 44 | long highMask = high[symbol >>> 4]; 45 | long lowMask = low[symbol & 0xF]; 46 | state = ((state << 1) | 1) & highMask & lowMask; 47 | if ((state & termination) == termination) { 48 | return true; 49 | } 50 | } 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import org.datadog.jmxfetch.service.ServiceNameProvider; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import javax.management.MBeanAttributeInfo; 9 | import javax.management.ObjectName; 10 | 11 | abstract class JmxSubAttribute extends JmxAttribute { 12 | private Map cachedMetrics = new HashMap(); 13 | 14 | public JmxSubAttribute( 15 | MBeanAttributeInfo attribute, 16 | ObjectName beanName, 17 | String className, 18 | String instanceName, 19 | String checkName, 20 | Connection connection, 21 | ServiceNameProvider serviceNameProvider, 22 | Map instanceTags, 23 | boolean cassandraAliasing, 24 | boolean emptyDefaultHostname, 25 | boolean normalizeBeanParamTags) { 26 | super( 27 | attribute, 28 | beanName, 29 | className, 30 | instanceName, 31 | checkName, 32 | connection, 33 | serviceNameProvider, 34 | instanceTags, 35 | cassandraAliasing, 36 | emptyDefaultHostname, 37 | normalizeBeanParamTags); 38 | } 39 | 40 | public Metric getCachedMetric(String name) { 41 | Metric metric = cachedMetrics.get(name); 42 | if (metric != null) { 43 | return metric; 44 | } 45 | String alias = getAlias(name); 46 | String metricType = getMetricType(name); 47 | String[] tags = getTags(); 48 | metric = new Metric(alias, metricType, tags, checkName); 49 | cachedMetrics.put(name, metric); 50 | return metric; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/LogLevelTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.Arrays; 8 | import java.util.logging.Level; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | @RunWith(Parameterized.class) 15 | public class LogLevelTest { 16 | 17 | @Parameterized.Parameters 18 | public static Iterable testCases() { 19 | return Arrays.asList(new Object[][] { 20 | {"ALL", LogLevel.ALL, Level.ALL}, 21 | {"DEBUG", LogLevel.DEBUG, Level.FINE}, 22 | {"TRACE", LogLevel.TRACE, Level.FINEST}, 23 | {"INFO", LogLevel.INFO, Level.INFO}, 24 | {"WARN", LogLevel.WARN, Level.WARNING}, 25 | {"ERROR", LogLevel.ERROR, Level.SEVERE}, 26 | {"FATAL", LogLevel.ERROR, Level.SEVERE}, 27 | {"OFF", LogLevel.OFF, Level.OFF}, 28 | {"LEVEL", LogLevel.INFO, Level.INFO}, 29 | {"some_unknown_level", LogLevel.INFO, Level.INFO}, 30 | }); 31 | } 32 | 33 | private final String configLogLevel; 34 | private final LogLevel expectedLogLevel; 35 | private final Level expectedJulLevel; 36 | 37 | public LogLevelTest(String configLogLevel, LogLevel expectedLogLevel, Level expectedJulLevel) { 38 | this.configLogLevel = configLogLevel; 39 | this.expectedLogLevel = expectedLogLevel; 40 | this.expectedJulLevel = expectedJulLevel; 41 | } 42 | 43 | @Test 44 | public void testFromStringToJulLevel() { 45 | LogLevel logLevel = LogLevel.fromString(configLogLevel); 46 | assertEquals(expectedLogLevel, logLevel); 47 | assertEquals(expectedJulLevel, logLevel.toJulLevel()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/DefaultConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import static org.datadog.jmxfetch.Instance.isDirectInstance; 4 | 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.io.IOException; 8 | import java.util.Map; 9 | 10 | /** Singleton used to create connections to the MBeanServer. */ 11 | @Slf4j 12 | public class DefaultConnectionFactory implements ConnectionFactory { 13 | public static final String PROCESS_NAME_REGEX = "process_name_regex"; 14 | 15 | /** Factory method to create connections, both remote and local to the target JVM. */ 16 | public Connection createConnection(Map connectionParams) 17 | throws IOException { 18 | // This is used by dd-java-agent to enable directly connecting to the mbean server. 19 | // This works since jmxfetch is being run as a library inside the process being monitored. 20 | if (isDirectInstance(connectionParams)) { 21 | log.info("Connecting to JMX directly on the JVM"); 22 | return new JvmDirectConnection(); 23 | } 24 | 25 | if (connectionParams.get(PROCESS_NAME_REGEX) != null) { 26 | try { 27 | // AttachNotSupportedException is accessible in java 7 and 8 through tools.jar 28 | // and java 9+ by default 29 | Class.forName("com.sun.tools.attach.AttachNotSupportedException"); 30 | } catch (ClassNotFoundException e) { 31 | throw new IOException( 32 | "Unable to find tools.jar." 33 | + " Are you using a JDK and did you set the path to tools.jar ?"); 34 | } 35 | log.info("Connecting using Attach API"); 36 | return new AttachApiConnection(connectionParams); 37 | } 38 | 39 | log.info("Connecting using JMX Remote"); 40 | return new RemoteConnection(connectionParams); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ master ] 9 | 10 | jobs: 11 | analyze: 12 | name: Analyze 13 | runs-on: ubuntu-latest 14 | permissions: 15 | actions: read 16 | contents: read 17 | security-events: write 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | language: [ 'java' ] 23 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 24 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 33 | with: 34 | languages: ${{ matrix.language }} 35 | # If you wish to specify custom queries, you can do so here or in a config file. 36 | # By default, queries listed here will override any specified in a config file. 37 | # Prefix the list here with "+" to use these queries and those in the config file. 38 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 39 | 40 | - name: Checkout code 41 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 42 | - name: Set up JDK 8 43 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 44 | with: 45 | distribution: 'zulu' 46 | java-version: '8' 47 | java-package: jdk 48 | 49 | - name: Build with Maven 50 | run: ./mvnw clean compile assembly:single 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 54 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/reporter/ReporterFactory.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.reporter; 2 | 3 | import org.datadog.jmxfetch.AppConfig; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | public class ReporterFactory { 9 | 10 | /** Gets the reporter for the corresponding app config. */ 11 | public static Reporter getReporter(AppConfig appConfig) { 12 | String type = appConfig.getReporterString(); 13 | if (type == null || type.length() <= 0) { 14 | throw new IllegalArgumentException("Null or empty reporter type"); 15 | } 16 | if ("console".equals(type)) { 17 | return new ConsoleReporter(); 18 | } else if ("json".equals(type)) { 19 | return new JsonReporter(); 20 | } else if (type.startsWith("statsd:")) { 21 | 22 | Matcher matcher = Pattern.compile("^statsd:(.*):(\\d+)$").matcher(type); 23 | if (matcher.find() && matcher.groupCount() == 2) { 24 | String host = matcher.group(1); 25 | Integer port = Integer.valueOf(matcher.group(2)); 26 | return new StatsdReporter( 27 | host, 28 | port, 29 | appConfig.getStatsdTelemetry(), 30 | appConfig.getStatsdQueueSize(), 31 | appConfig.isStatsdNonBlocking(), 32 | appConfig.getStatsdBufferSize(), 33 | appConfig.getSocketTimeout()); 34 | } 35 | 36 | matcher = Pattern.compile("^statsd:unix://(.*)$").matcher(type); 37 | if (matcher.find() && matcher.groupCount() == 1) { 38 | String socketPath = matcher.group(1); 39 | return new StatsdReporter( 40 | socketPath, 41 | 0, 42 | appConfig.getStatsdTelemetry(), 43 | appConfig.getStatsdQueueSize(), 44 | appConfig.isStatsdNonBlocking(), 45 | appConfig.getStatsdBufferSize(), 46 | appConfig.getSocketTimeout()); 47 | } 48 | } 49 | throw new IllegalArgumentException("Invalid reporter type: " + type); 50 | } 51 | 52 | private ReporterFactory() { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/resources/jmx_canonical.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | canonical_rate: true 3 | 4 | instances: 5 | - process_name_regex: .*surefire.* 6 | refresh_beans: 4 7 | name: jmx_test_instance 8 | tags: 9 | - "env:stage" 10 | - "newTag:test" 11 | conf: 12 | - include: 13 | domain: org.datadog.jmxfetch.test 14 | attribute: 15 | ShouldBe100: 16 | metric_type: gauge 17 | alias: this.is.100 18 | tags: 19 | - "foo" 20 | - "bar:baz" 21 | - gorch 22 | ShouldBeCounter: 23 | metric_type: counter 24 | alias: test.counter 25 | ShouldBeBoolean: 26 | metric_type: gauge 27 | alias: test.boolean 28 | Hashmap.thisis0: 29 | metric_type: gauge 30 | alias: subattr.this.is.0 31 | Hashmap.thisiscounter: 32 | metric_type: counter 33 | alias: subattr.counter 34 | ShouldBeConverted: 35 | metric_type: gauge 36 | alias: test.converted 37 | values: 38 | ShouldBe0: 0 39 | ShouldBe5: 5 40 | ShouldBeDefaulted: 41 | metric_type: gauge 42 | alias: test.defaulted 43 | values: 44 | default: 32 45 | Tabulardata.foo: 46 | metric_type: gauge 47 | alias: multiattr.foo 48 | tags: 49 | foo: $foo 50 | toto: $toto 51 | limit: 1 52 | sort: desc 53 | TabularDataSupport.foo: 54 | metric_type: gauge 55 | alias: multiattr_supp.foo 56 | tags: 57 | foo: $foo 58 | toto: $toto 59 | limit: 1 60 | sort: desc 61 | - include: 62 | domain: org.datadog.jmxfetch.test 63 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/server/WaitOrStrategy.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util.server; 2 | 3 | import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; 4 | import org.testcontainers.containers.wait.strategy.WaitAllStrategy; 5 | import org.testcontainers.containers.wait.strategy.WaitStrategy; 6 | import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.concurrent.*; 12 | 13 | public class WaitOrStrategy implements WaitStrategy { 14 | 15 | private final List waitStrategies; 16 | 17 | public WaitOrStrategy(WaitStrategy... strategies) { 18 | this.waitStrategies = Arrays.asList(strategies); 19 | } 20 | 21 | @Override 22 | public WaitOrStrategy withStartupTimeout(java.time.Duration startupTimeout) { 23 | return this; 24 | } 25 | 26 | @Override 27 | public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { 28 | final WaitStrategyTarget target = waitStrategyTarget; 29 | ExecutorService executor = Executors.newCachedThreadPool(); 30 | List> futures = new ArrayList<>(); 31 | 32 | for (final WaitStrategy strategy : waitStrategies) { 33 | Future future = executor.submit(new Callable() { 34 | @Override 35 | public Object call() throws Exception { 36 | strategy.waitUntilReady(target); 37 | return null; 38 | } 39 | }); 40 | futures.add(future); 41 | } 42 | 43 | try { 44 | for (Future future : futures) { 45 | try { 46 | future.get(1, TimeUnit.SECONDS); 47 | break; // One of the strategies has completed, we can stop waiting 48 | } catch (TimeoutException ignored) { 49 | // This strategy did not complete yet, try the next one 50 | } 51 | } 52 | } catch (InterruptedException e) { 53 | Thread.currentThread().interrupt(); 54 | throw new RuntimeException("WaitOrStrategy was interrupted", e); 55 | } catch (ExecutionException e) { 56 | throw new RuntimeException("Exception during WaitOrStrategy execution", e); 57 | } finally { 58 | executor.shutdownNow(); // Make sure to shutdown the executor 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/test/resources/jmx.yaml: -------------------------------------------------------------------------------- 1 | init_config: 2 | 3 | instances: 4 | - jvm_direct: true 5 | refresh_beans: 4 6 | name: jmx_test_instance 7 | tags: 8 | - "env:stage" 9 | - "newTag:test" 10 | conf: 11 | - include: 12 | domain: org.datadog.jmxfetch.test 13 | attribute: 14 | ShouldBe100: 15 | metric_type: gauge 16 | alias: this.is.100 17 | tags: 18 | - "foo" 19 | - "bar:baz" 20 | - gorch 21 | ShouldBeCounter: 22 | metric_type: counter 23 | alias: test.counter 24 | ShouldBeBoolean: 25 | metric_type: gauge 26 | alias: test.boolean 27 | Hashmap.thisis0: 28 | metric_type: gauge 29 | alias: subattr.this.is.0 30 | Hashmap.thisiscounter: 31 | metric_type: counter 32 | alias: subattr.counter 33 | Hashmap.shouldBeDefaulted: 34 | metric_type: gauge 35 | alias: subattr.defaulted 36 | values: 37 | default: 42 38 | ShouldBeConverted: 39 | metric_type: gauge 40 | alias: test.converted 41 | values: 42 | ShouldBe0: 0 43 | ShouldBe5: 5 44 | ShouldBeDefaulted: 45 | metric_type: gauge 46 | alias: test.defaulted 47 | values: 48 | default: 32 49 | Tabulardata.foo: 50 | metric_type: gauge 51 | alias: multiattr.foo 52 | tags: 53 | foo: $foo 54 | toto: $toto 55 | limit: 1 56 | sort: desc 57 | TabularDataSupport.foo: 58 | metric_type: gauge 59 | alias: multiattr_supp.foo 60 | tags: 61 | foo: $foo 62 | toto: $toto 63 | limit: 1 64 | sort: desc 65 | - include: 66 | domain: org.datadog.jmxfetch.test 67 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/InterceptibleSocketServer.java: -------------------------------------------------------------------------------- 1 | package org.datadog.misbehavingjmxserver; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | 14 | @Slf4j 15 | class InterceptibleSocketServer extends ServerSocket { 16 | private List acceptedSockets; 17 | private AtomicBoolean closed; 18 | 19 | public InterceptibleSocketServer(int port) throws IOException { 20 | super(port); 21 | this.acceptedSockets = new ArrayList<>(); 22 | this.closed = new AtomicBoolean(false); 23 | } 24 | 25 | public InterceptibleSocketServer(int port, int backlog) throws IOException { 26 | super(port, backlog); 27 | this.acceptedSockets = new ArrayList<>(); 28 | this.closed = new AtomicBoolean(false); 29 | } 30 | 31 | public InterceptibleSocketServer(int port, int backlog, InetAddress bindAddr) throws IOException { 32 | super(port, backlog, bindAddr); 33 | this.acceptedSockets = new ArrayList<>(); 34 | this.closed = new AtomicBoolean(false); 35 | } 36 | 37 | @Override 38 | public Socket accept() throws IOException { 39 | Socket socket = super.accept(); 40 | if (this.closed.get()) { 41 | socket.close(); 42 | throw new IOException("JMX Network is closed, try again later"); 43 | } 44 | log.debug("Accepted socket localPort {} port {} localAddress {} inetAddress {}", socket.getLocalPort(), socket.getPort(), socket.getLocalAddress(), socket.getInetAddress(), socket.getRemoteSocketAddress()); 45 | acceptedSockets.add(socket); 46 | return socket; 47 | } 48 | 49 | public void setClosed(boolean closed) { 50 | this.closed.set(closed); 51 | log.debug("[{}] Socket server is now {}", this.toString(), closed); 52 | } 53 | 54 | public int closeAcceptedSockets() { 55 | int closed = 0; 56 | for (Socket socket : acceptedSockets) { 57 | try { 58 | log.debug("Closing socket localPort {} port {} localAddress {} inetAddress {}", socket.getLocalPort(), socket.getPort(), socket.getLocalAddress(), socket.getInetAddress(), socket.getRemoteSocketAddress()); 59 | socket.close(); 60 | closed++; 61 | } catch (IOException e) { 62 | log.error("Error closing socket ", e); 63 | } 64 | } 65 | return closed; 66 | } 67 | } -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/MetricDAO.java: -------------------------------------------------------------------------------- 1 | package org.datadog.misbehavingjmxserver; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.atomic.AtomicBoolean; 6 | import java.util.concurrent.atomic.AtomicReference; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class MetricDAO { 10 | private final AtomicReference doubleValue; 11 | private final AtomicReference floatValue; 12 | private final AtomicBoolean boolValue; 13 | private final AtomicReference numberValue; 14 | private final ScheduledExecutorService executor; 15 | 16 | 17 | public MetricDAO() { 18 | this.doubleValue = new AtomicReference<>(0.0); 19 | this.floatValue = new AtomicReference<>((float) 0); 20 | this.boolValue = new AtomicBoolean(); 21 | this.numberValue = new AtomicReference(0); 22 | this.executor = Executors.newSingleThreadScheduledExecutor(); 23 | } 24 | 25 | public Number getNumberValue() { 26 | return this.numberValue.get(); 27 | } 28 | 29 | public Double getDoubleValue() { 30 | return this.doubleValue.get(); 31 | } 32 | 33 | public Float getFloatValue() { 34 | return this.floatValue.get(); 35 | } 36 | 37 | public Boolean getBooleanValue() { 38 | return this.boolValue.get(); 39 | } 40 | 41 | private void incDoubleValue() { 42 | final Double current = this.doubleValue.get(); 43 | final Double next = current + 1; 44 | this.doubleValue.compareAndSet(current, next); 45 | } 46 | 47 | private void incFloatValue() { 48 | final Float current = this.floatValue.get(); 49 | final Float next = current + 1; 50 | this.floatValue.compareAndSet(current, next); 51 | } 52 | 53 | private void incBooleanValue() { 54 | final boolean current = this.boolValue.get(); 55 | final boolean next = !current; 56 | this.boolValue.compareAndSet(current, next); 57 | } 58 | 59 | private void incNumberValue() { 60 | final Number current = this.numberValue.get(); 61 | final Number next = current.intValue() + 1; 62 | this.numberValue.compareAndSet(current, next); 63 | } 64 | 65 | public void Do() { 66 | this.incDoubleValue(); 67 | this.incFloatValue(); 68 | this.incBooleanValue(); 69 | this.incNumberValue(); 70 | } 71 | 72 | void runTickLoop() { 73 | Runnable task = () -> { 74 | this.Do(); 75 | }; 76 | executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/JmxFetch.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import com.beust.jcommander.ParameterException; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.datadog.jmxfetch.util.CustomLogger; 8 | import org.datadog.jmxfetch.util.LogLevel; 9 | import org.datadog.jmxfetch.util.MetadataHelper; 10 | 11 | @Slf4j 12 | public class JmxFetch { 13 | /** 14 | * Main entry of JMXFetch. 15 | * 16 | *

See AppConfig class for more details on the args 17 | */ 18 | public static void main(String[] args) { 19 | 20 | // Load the config from the args 21 | AppConfig config = AppConfig.builder().build(); 22 | JCommander commander = null; 23 | try { 24 | // Try to parse the args using JCommander 25 | commander = new JCommander(config, args); 26 | } catch (ParameterException e) { 27 | System.out.println(e.getMessage()); 28 | System.exit(1); 29 | } 30 | 31 | // Display the version and quit 32 | if (config.isVersion() || AppConfig.ACTION_VERSION.equals(config.getAction())) { 33 | JCommander.getConsole().println("JMX Fetch " + MetadataHelper.getVersion()); 34 | System.exit(0); 35 | } 36 | 37 | // Display the help and quit 38 | if (config.isHelp() || AppConfig.ACTION_HELP.equals(config.getAction())) { 39 | commander.usage(); 40 | System.exit(0); 41 | } 42 | 43 | { 44 | // Running these commands here because they are logging specific, 45 | // not needed in dd-java-agent, which calls run directly. 46 | 47 | // Set up the logger to add file handler 48 | CustomLogger.setup(LogLevel.fromString(config.getLogLevel()), 49 | config.getLogLocation(), 50 | config.isLogFormatRfc3339()); 51 | 52 | // Set up the shutdown hook to properly close resources 53 | attachShutdownHook(); 54 | } 55 | 56 | App app = new App(config); 57 | System.exit(app.run()); 58 | } 59 | 60 | /** Attach a Shutdown Hook that will be called when SIGTERM is sent to JMXFetch. */ 61 | private static void attachShutdownHook() { 62 | Runtime.getRuntime().addShutdownHook( 63 | new Thread() { 64 | @Override 65 | public void run() { 66 | log.info("JMXFetch is closing"); 67 | // make sure log handlers are properly closed 68 | CustomLogger.shutdown(); 69 | } 70 | } 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/RandomIdentifier.java: -------------------------------------------------------------------------------- 1 | package org.datadog.misbehavingjmxserver; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | /* 8 | * Just for fun, most of this was generated 9 | * Can also just use a random number. 10 | * Sample output: 11 | * eyajutewe-icajudiwiq, ozujirimugo-uxuwivico, ubapabituz-taxesujuji, bowabucoyaqa-royedeyujap, uxayihifaf-rurobawifu, sicaqulorube-jeninojusom 12 | */ 13 | public class RandomIdentifier { 14 | private static final List VOWELS = List.of("a", "e", "i", "o", "u"); 15 | 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"); 16 | 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"); 17 | private final Random RANDOM; 18 | 19 | public RandomIdentifier(long seed) { 20 | this.RANDOM = new Random(seed); 21 | } 22 | 23 | private String generateWord(int minLength, int maxLength) { 24 | int wordLength = minLength + RANDOM.nextInt(maxLength - minLength + 1); 25 | StringBuilder wordBuilder = new StringBuilder(); 26 | 27 | boolean hasVowel = false; 28 | for (int j = 0; j < wordLength; j++) { 29 | String letter; 30 | if (j % 2 == 0) { 31 | letter = getRandomLetter(CONSONANTS); 32 | } else { 33 | letter = getRandomLetter(VOWELS); 34 | hasVowel = true; 35 | } 36 | 37 | if (j > 0 && isRareSequence(wordBuilder.charAt(j - 1), letter)) { 38 | j--; 39 | continue; 40 | } 41 | 42 | wordBuilder.append(letter); 43 | } 44 | 45 | if (!hasVowel) { 46 | wordBuilder.setCharAt(RANDOM.nextInt(wordLength), VOWELS.get(RANDOM.nextInt(VOWELS.size())).charAt(0)); 47 | } 48 | 49 | return wordBuilder.toString(); 50 | } 51 | 52 | public String generateIdentifier() { 53 | int minLength = 9; 54 | int maxLength = 13; 55 | return generateWord(minLength, maxLength) + "-" + generateWord(minLength, maxLength); 56 | } 57 | 58 | private String getRandomLetter(List letterSet) { 59 | return letterSet.get(RANDOM.nextInt(letterSet.size())); 60 | } 61 | 62 | private boolean isRareSequence(char prev, String next) { 63 | return RARE_SEQUENCES.contains(prev + next) || CONSONANTS.contains(prev + "") && CONSONANTS.contains(next); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/service/ConfigServiceNameProvider.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** 8 | * ConfigServiceNameProvider provides the list of service names based on the config maps passed to 9 | * its constructor. 10 | */ 11 | public class ConfigServiceNameProvider implements ServiceNameProvider { 12 | private List serviceNames; 13 | private ServiceNameProvider additionalServiceNames; 14 | 15 | 16 | /** 17 | * Builds a ConfigServiceNameProvider based on the maps of an instance config and init config, 18 | * returning the initialized ServiceNameProvider. 19 | * 20 | * @param instanceMap The instance config map 21 | * @param initConfig The init config map 22 | */ 23 | public ConfigServiceNameProvider( 24 | Map instanceMap, 25 | Map initConfig, 26 | ServiceNameProvider additionalServiceNames) { 27 | List services = compileServiceList(instanceMap); 28 | if (services.size() == 0) { 29 | services = compileServiceList(initConfig); 30 | } 31 | this.serviceNames = services; 32 | this.additionalServiceNames = additionalServiceNames; 33 | } 34 | 35 | /** 36 | * Returns a String Iterable with the relevant services defined in the init config and instance 37 | * maps supplied in the constructor, as well as any additional services derived from the 38 | * additional ServiceNameProvider (if any). 39 | * 40 | * @return the service String Iterable. 41 | */ 42 | public Iterable getServiceNames() { 43 | if (this.additionalServiceNames == null) { 44 | return this.serviceNames; 45 | } 46 | 47 | List names = new ArrayList(this.serviceNames); 48 | for (String service : this.additionalServiceNames.getServiceNames()) { 49 | names.add(service); 50 | } 51 | return names; 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | private static List compileServiceList(Map config) { 56 | List services = new ArrayList<>(); 57 | if (config == null || !config.containsKey("service")) { 58 | return services; 59 | } 60 | 61 | try { 62 | String svc = (String) config.get("service"); 63 | if (svc != null && !svc.isEmpty()) { 64 | services.add(svc); 65 | } 66 | } catch (ClassCastException e) { 67 | // must be a list then... 68 | services = (List) config.get("service"); 69 | } 70 | 71 | return services; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/JMXServerClient.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.OutputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | @Slf4j 13 | public abstract class JMXServerClient { 14 | private final String host; 15 | private final int port; 16 | JMXServerClient(String host, int port) { 17 | this.host = host; 18 | this.port = port; 19 | } 20 | 21 | protected void sendPostRequestWithPayload(String endpoint, String jsonPayload) throws IOException { 22 | URL url = new URL("http://" + host + ":" + port + endpoint); 23 | log.info("Sending POST to {} with payload {}", url, jsonPayload); 24 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 25 | con.setRequestMethod("POST"); 26 | con.setRequestProperty("Content-Type", "application/json"); 27 | con.setDoOutput(true); 28 | 29 | OutputStream os = con.getOutputStream(); 30 | os.write(jsonPayload.getBytes("UTF-8")); 31 | os.flush(); 32 | os.close(); 33 | 34 | int responseCode = con.getResponseCode(); 35 | 36 | BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); 37 | String inputLine; 38 | StringBuffer response = new StringBuffer(); 39 | while ((inputLine = in.readLine()) != null) { 40 | response.append(inputLine); 41 | } 42 | in.close(); 43 | if (responseCode == HttpURLConnection.HTTP_OK) { 44 | log.info("HTTP Resp: {}", response.toString()); 45 | } else { 46 | log.warn("HTTP POST request failed with status code: {} err: {}", responseCode, response.toString()); 47 | } 48 | } 49 | 50 | protected String sendGetRequest(String endpoint) throws IOException { 51 | URL url = new URL("http://" + host + ":" + port + endpoint); 52 | log.info("Sending GET to {} ", url); 53 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 54 | con.setRequestMethod("GET"); 55 | 56 | int responseCode = con.getResponseCode(); 57 | 58 | if (responseCode == HttpURLConnection.HTTP_OK) { 59 | BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); 60 | String inputLine; 61 | StringBuffer response = new StringBuffer(); 62 | while ((inputLine = in.readLine()) != null) { 63 | response.append(inputLine); 64 | } 65 | in.close(); 66 | return response.toString(); 67 | } else { 68 | log.warn("HTTP GET request failed with status code: {}", responseCode); 69 | return ""; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/StatusTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.datadog.jmxfetch.util.InstanceTelemetry; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.junit.rules.TemporaryFolder; 17 | import org.yaml.snakeyaml.Yaml; 18 | 19 | public class StatusTest { 20 | 21 | @Rule 22 | public TemporaryFolder folder= new TemporaryFolder(); 23 | 24 | @Test 25 | public void TestStatus() throws IOException { 26 | 27 | File tempFile= folder.newFile("tempFile.txt"); 28 | String tempFilePath = tempFile.getAbsolutePath(); 29 | 30 | final Status status = new Status(tempFilePath); 31 | InstanceTelemetry instance = new InstanceTelemetry(); 32 | 33 | int fakeBeansFetched = 11; 34 | int fakeMetricCount = 29; 35 | int fakeAttributeCount = 55; 36 | int fakeWildcardDomainQueryCount = 9; 37 | double fakeBeanMatchRatio = .4; 38 | 39 | instance.setBeansFetched(fakeBeansFetched); 40 | instance.setMetricCount(fakeMetricCount); 41 | instance.setTopLevelAttributeCount(fakeAttributeCount); 42 | instance.setWildcardDomainQueryCount(fakeWildcardDomainQueryCount); 43 | instance.setBeanMatchRatio(fakeBeanMatchRatio); 44 | 45 | status.addInstanceStats("fake_check", "fake_instance", 10, 3, "fake_message", Status.STATUS_OK, instance); 46 | status.flush(); 47 | 48 | Yaml yaml = new Yaml(); 49 | InputStream inputStream = new FileInputStream(tempFilePath); 50 | 51 | HashMap yamlMap = yaml.load(inputStream); 52 | HashMap checks = (HashMap) yamlMap.get("checks"); 53 | HashMap initializedChecks = (HashMap) checks.get("initialized_checks"); 54 | List> fakeCheck = (List>) initializedChecks.get("fake_check"); 55 | Map stats = fakeCheck.get(0); 56 | assertEquals("fake_instance", stats.get("instance_name")); 57 | assertEquals(10, stats.get("metric_count")); 58 | assertEquals(3, stats.get("service_check_count")); 59 | assertEquals(fakeBeansFetched, stats.get("instance_bean_count")); 60 | assertEquals(fakeAttributeCount, stats.get("instance_attribute_count")); 61 | assertEquals(fakeMetricCount, stats.get("instance_metric_count")); 62 | assertEquals(fakeWildcardDomainQueryCount, stats.get("instance_wildcard_domain_query_count")); 63 | assertEquals(fakeBeanMatchRatio, stats.get("instance_bean_match_ratio")); 64 | assertEquals("fake_message", stats.get("message")); 65 | assertEquals(Status.STATUS_OK, stats.get("status")); 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/reporter/JsonReporter.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.reporter; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import org.datadog.jmxfetch.Instance; 6 | import org.datadog.jmxfetch.JmxAttribute; 7 | import org.datadog.jmxfetch.JsonPrinter; 8 | 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @Slf4j 16 | public class JsonReporter extends Reporter { 17 | 18 | private List> metrics = new ArrayList>(); 19 | 20 | protected void sendMetricPoint( 21 | String metricType, String metricName, double value, String[] tags) { 22 | long currentTime = System.currentTimeMillis() / 1000L; 23 | List point = new ArrayList(2); 24 | point.add(currentTime); 25 | point.add(value); 26 | List points = new ArrayList(1); 27 | points.add(point); 28 | Map metric = new HashMap(); 29 | metric.put("host", "default"); 30 | metric.put("interval", 0); 31 | metric.put("source_type_name", "JMX"); 32 | metric.put("metric", metricName); 33 | metric.put("points", points); 34 | metric.put("tags", tags); 35 | metric.put("type", metricType); 36 | metrics.add(metric); 37 | } 38 | 39 | /** Use the service check callback to display the JSON. */ 40 | public void doSendServiceCheck( 41 | String serviceCheckName, String status, String message, String[] tags) { 42 | log.debug("Displaying JSON output"); 43 | Map sc = new HashMap(); 44 | sc.put("check", serviceCheckName); 45 | sc.put("host_name", "default"); 46 | sc.put("timestamp", System.currentTimeMillis() / 1000); 47 | sc.put("status", this.statusToServiceCheckStatusValue(status)); 48 | sc.put("message", message); 49 | sc.put("tags", tags); 50 | 51 | Map aggregator = new HashMap(); 52 | aggregator.put("metrics", metrics); 53 | List serviceChecks = new ArrayList(); 54 | serviceChecks.add(sc); 55 | aggregator.put("service_checks", serviceChecks); 56 | Map serie = new HashMap(); 57 | serie.put("aggregator", aggregator); 58 | List> series = new ArrayList>(1); 59 | series.add(serie); 60 | 61 | System.out.println("=== JSON ==="); 62 | JsonPrinter.prettyPrint(System.out, series); 63 | System.out.println(); 64 | metrics.clear(); 65 | } 66 | 67 | public void displayMetricReached() { 68 | } 69 | 70 | public void displayMatchingAttributeName(JmxAttribute jmxAttribute, int rank, int limit) { 71 | } 72 | 73 | public void displayNonMatchingAttributeName(JmxAttribute jmxAttribute) { 74 | } 75 | 76 | public void displayInstanceName(Instance instance) { 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/InterruptibleRMISocketFactory.java: -------------------------------------------------------------------------------- 1 | package org.datadog.misbehavingjmxserver; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.net.Socket; 6 | import java.rmi.server.RMISocketFactory; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | 14 | @Slf4j 15 | class InterruptibleRMISocketFactory extends RMISocketFactory { 16 | private List socketServers; 17 | private List sockets; 18 | private AtomicBoolean closed; 19 | 20 | public InterruptibleRMISocketFactory() { 21 | this.socketServers = new ArrayList<>(); 22 | this.sockets = new ArrayList<>(); 23 | this.closed = new AtomicBoolean(false); 24 | } 25 | 26 | @Override 27 | public synchronized ServerSocket createServerSocket(int port) throws IOException { 28 | if (this.closed.get()) { 29 | log.warn("Denied request for a serverSocket on :{} (network closed)", port); 30 | throw new IOException("JMX Network is closed, sorry, try again later."); 31 | } 32 | InterceptibleSocketServer serverSocket = new InterceptibleSocketServer(port); 33 | socketServers.add(serverSocket); 34 | log.debug("Creating serverSocket on :{}", port); 35 | return serverSocket; 36 | } 37 | 38 | @Override 39 | public Socket createSocket(String host, int port) throws IOException { 40 | if (this.closed.get()) { 41 | log.warn("Denied request for a socket to {}:{} (network closed)", host, port); 42 | throw new IOException("JMX Network is closed, sorry, try again later."); 43 | } 44 | Socket socket = new Socket(host, port); 45 | sockets.add(socket); 46 | log.debug("Creating socket to {}:{}, now have {} sockets", host, port, sockets.size()); 47 | return socket; 48 | } 49 | 50 | public void setClosed(boolean closed) { 51 | this.closed.set(closed); 52 | for (InterceptibleSocketServer socketServer : this.socketServers) { 53 | socketServer.setClosed(closed); 54 | } 55 | } 56 | 57 | public void closeClientSockets() { 58 | int closed = 0; 59 | for (Socket socket : this.sockets) { 60 | try { 61 | socket.close(); 62 | closed++; 63 | } catch (IOException e) { 64 | log.error("Error closing socket ", e); 65 | } 66 | } 67 | log.info("Closed {} client sockets", closed); 68 | this.sockets.clear(); 69 | } 70 | 71 | public void closeServerSockets() { 72 | int closed = 0; 73 | for (InterceptibleSocketServer socketServer : this.socketServers) { 74 | closed += socketServer.closeAcceptedSockets(); 75 | // Closing the socketServer itself fubars JMX 76 | // Only close 'accepted' sockets from the socketservers 77 | } 78 | log.info("Closed {} accepted sockets", closed); 79 | } 80 | 81 | public void closeAllSockets() { 82 | this.closeClientSockets(); 83 | this.closeServerSockets(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/service/ConfigServiceNameProviderTest.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.service; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | 15 | @RunWith(Parameterized.class) 16 | public class ConfigServiceNameProviderTest { 17 | 18 | private static class CustomConfigServiceNameProvider implements ServiceNameProvider { 19 | public Iterable getServiceNames() { 20 | return new ArrayList(Arrays.asList("foo-provider")); 21 | } 22 | } 23 | 24 | @Parameterized.Parameters 25 | public static Iterable testCases() { 26 | return Arrays.asList(new Object[][] { 27 | { 28 | Collections.singletonMap("service", "foo-instance"), 29 | Collections.singletonMap("service", "foo-init"), 30 | null, 31 | Arrays.asList("foo-instance") 32 | }, 33 | { 34 | Collections.singletonMap("not-service", "foo-instance"), 35 | Collections.singletonMap("service", "foo-init"), 36 | null, 37 | Arrays.asList("foo-init") 38 | }, 39 | { 40 | Collections.singletonMap("not-service", "foo-instance"), 41 | Collections.singletonMap("not-service", "foo-init"), 42 | null, 43 | new ArrayList<>() 44 | }, 45 | { 46 | Collections.singletonMap("service", Arrays.asList("foo1-instance", "foo2-instance")), 47 | Collections.singletonMap("service", "foo-init"), 48 | null, 49 | Arrays.asList("foo1-instance", "foo2-instance") 50 | }, 51 | { 52 | Collections.singletonMap("service", Arrays.asList("foo1-instance", "foo2-instance")), 53 | Collections.singletonMap("service", "foo-init"), 54 | new ConfigServiceNameProviderTest.CustomConfigServiceNameProvider(), 55 | Arrays.asList("foo1-instance", "foo2-instance", "foo-provider") 56 | }, 57 | }); 58 | } 59 | 60 | private final Map instanceMap; 61 | private final Map initConfigMap; 62 | private final ServiceNameProvider provider; 63 | private final List expected; 64 | 65 | public ConfigServiceNameProviderTest(Map instanceMap, Map initConfigMap, ServiceNameProvider provider, List expected) { 66 | this.instanceMap = instanceMap; 67 | this.initConfigMap = initConfigMap; 68 | this.provider = provider; 69 | this.expected = expected; 70 | } 71 | 72 | @Test 73 | public void testConfigServiceNameProvider() { 74 | ConfigServiceNameProvider configServiceNameProvider = new ConfigServiceNameProvider( 75 | instanceMap, 76 | initConfigMap, 77 | provider 78 | ); 79 | 80 | assertEquals(expected, configServiceNameProvider.getServiceNames()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util.server; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.util.Hashtable; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import javax.management.InstanceAlreadyExistsException; 8 | import javax.management.MBeanRegistrationException; 9 | import javax.management.MBeanServer; 10 | import javax.management.MalformedObjectNameException; 11 | import javax.management.NotCompliantMBeanException; 12 | import javax.management.ObjectName; 13 | 14 | // TODO: Create tests to check all supported versions of Java work with this server - AMLII-1354 15 | class SimpleApp { 16 | public interface SampleMBean { 17 | 18 | Integer getShouldBe100(); 19 | 20 | Double getShouldBe1000(); 21 | 22 | Long getShouldBe1337(); 23 | 24 | Float getShouldBe1_1(); 25 | 26 | int getShouldBeCounter(); 27 | } 28 | 29 | public static class Sample implements SampleMBean { 30 | 31 | private final AtomicInteger counter = new AtomicInteger(0); 32 | 33 | @Override 34 | public Integer getShouldBe100() { 35 | return 100; 36 | } 37 | 38 | @Override 39 | public Double getShouldBe1000() { 40 | return 200.0; 41 | } 42 | 43 | @Override 44 | public Long getShouldBe1337() { 45 | return 1337L; 46 | } 47 | 48 | @Override 49 | public Float getShouldBe1_1() { 50 | return 1.1F; 51 | } 52 | 53 | @Override 54 | public int getShouldBeCounter() { 55 | return this.counter.get(); 56 | } 57 | } 58 | 59 | public static void main(String[] args) { 60 | System.out.println("Starting sample app..."); 61 | try { 62 | final Hashtable pairs = new Hashtable<>(); 63 | pairs.put("name", "default"); 64 | pairs.put("type", "simple"); 65 | final Thread daemonThread = getThread(pairs); 66 | daemonThread.start(); 67 | System.out.println("Sample app started."); 68 | daemonThread.join(); 69 | } catch (MalformedObjectNameException | InstanceAlreadyExistsException | 70 | MBeanRegistrationException | NotCompliantMBeanException | InterruptedException e) { 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | 75 | private static Thread getThread(final Hashtable pairs) 76 | throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 77 | final ObjectName objectName = new ObjectName("dd.test.sample", pairs); 78 | final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 79 | final Sample sample = new Sample(); 80 | server.registerMBean(sample, objectName); 81 | final Thread daemonThread = new Thread(new Runnable() { 82 | @Override 83 | public void run() { 84 | while (sample.counter.incrementAndGet() > 0) { 85 | try { 86 | Thread.sleep(TimeUnit.SECONDS.toSeconds(5)); 87 | } catch (InterruptedException e) { 88 | throw new RuntimeException(e); 89 | } 90 | } 91 | } 92 | }); 93 | daemonThread.setDaemon(true); 94 | return daemonThread; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/util/LogLevel.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import java.util.logging.Level; 4 | 5 | /** 6 | * LogLevel used for internal logging to match Datadog Agent levels. 7 | * Comparison table with java.util.logging: 8 | *
  9 |  *  JUL     |  JMXFetch LogLevel
 10 |  * ----------------------------
 11 |  *  OFF     |  OFF
 12 |  *  SEVERE  |  ERROR
 13 |  *  WARNING |  WARN
 14 |  *  INFO    |  INFO
 15 |  *  CONFIG  |  DEBUG
 16 |  *  FINE    |  DEBUG
 17 |  *  FINER   |  TRACE
 18 |  *  FINEST  |  TRACE
 19 |  *  ALL     |  ALL
 20 |  * 
21 | *

22 | * `FATAL` from previous bindings used by JMXFetch is now converted 23 | * to `LogLevel.ERROR`. 24 | *

25 | */ 26 | public enum LogLevel { 27 | OFF(0, "OFF"), 28 | ERROR(1, "ERROR"), 29 | WARN(2, "WARN"), 30 | INFO(3, "INFO"), 31 | DEBUG(4, "DEBUG"), 32 | TRACE(5, "TRACE"), 33 | ALL(6, "ALL"); 34 | 35 | private int level; 36 | private String label; 37 | private LogLevel(int level, String label) { 38 | this.level = level; 39 | this.label = label; 40 | } 41 | 42 | // -- 43 | 44 | /** fromJulLevel converts a java.util.logging.Level into a LogLevel. */ 45 | public static LogLevel fromJulLevel(Level julLevel) { 46 | if (julLevel == Level.ALL) { 47 | return ALL; 48 | } else if (julLevel == Level.SEVERE) { 49 | return ERROR; 50 | } else if (julLevel == Level.WARNING) { 51 | return WARN; 52 | } else if (julLevel == Level.INFO) { 53 | return INFO; 54 | } else if (julLevel == Level.CONFIG) { 55 | return DEBUG; 56 | } else if (julLevel == Level.FINE) { 57 | return DEBUG; 58 | } else if (julLevel == Level.FINER) { 59 | return TRACE; 60 | } else if (julLevel == Level.FINEST) { 61 | return TRACE; 62 | } else if (julLevel == Level.OFF) { 63 | return OFF; 64 | } 65 | 66 | // should never happen but defaults to INFO 67 | return INFO; 68 | } 69 | 70 | /** fromString converts a string into a LogLevel, when not possible, it returns `INFO`. */ 71 | public static LogLevel fromString(String str) { 72 | // compatibility 73 | if (str.toUpperCase().equals("FATAL")) { 74 | return ERROR; 75 | } 76 | for (LogLevel l : LogLevel.class.getEnumConstants()) { 77 | if (str.toUpperCase().equals(l.toString().toUpperCase())) { 78 | return l; 79 | } 80 | } 81 | 82 | // default to INFO 83 | return INFO; 84 | } 85 | 86 | /** 87 | * toJulLevel converts a LogLevel to a `java.util.logging.Level`. 88 | * This mapping needs to match http://slf4j.org/api/org/slf4j/impl/JDK14LoggerAdapter.html 89 | **/ 90 | public Level toJulLevel() { 91 | switch (this) { 92 | case ALL: 93 | return Level.ALL; 94 | case ERROR: 95 | return Level.SEVERE; 96 | case WARN: 97 | return Level.WARNING; 98 | case INFO: 99 | return Level.INFO; 100 | case DEBUG: 101 | return Level.FINE; 102 | case TRACE: 103 | return Level.FINEST; 104 | case OFF: 105 | return Level.OFF; 106 | default: 107 | return Level.INFO; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/resources/auto_discovery_configs.json: -------------------------------------------------------------------------------- 1 | { 2 | "configs": { 3 | "jmx": { 4 | "instances": [ 5 | { 6 | "process_name_regex": ".*surefire.*", 7 | "name": "jmx_test_instance", 8 | "conf": [ 9 | { 10 | "include": { 11 | "scope": "sameScope", 12 | "domain": "org.datadog.jmxfetch.test", 13 | "type": "sameType", 14 | "additional": "additionalParam", 15 | "param": "sameParam" 16 | } 17 | }, 18 | { 19 | "include": { 20 | "scope": "sameScope", 21 | "domain": "org.datadog.jmxfetch.test", 22 | "type": "sameType", 23 | "param": "sameParam" 24 | } 25 | }, 26 | { 27 | "include": { 28 | "scope": "sameScope", 29 | "domain": "org.datadog.jmxfetch.test", 30 | "type": [ 31 | "sameType", 32 | "notTheSameType" 33 | ], 34 | "param": "sameParam" 35 | } 36 | }, 37 | { 38 | "include": { 39 | "bean": [ 40 | "org.datadog.jmxfetch.test:scope=sameScope,param=sameParam,type=sameType", 41 | "org.datadog.jmxfetch.test:scope=sameScope,param=notTheSameParam,type=sameType" 42 | ] 43 | } 44 | } 45 | ] 46 | } 47 | ], 48 | "init_config": null 49 | }, 50 | "cassandra": { 51 | "instances": [ 52 | { 53 | "process_name_regex": ".*surefire.*", 54 | "name": "jmx_first_instance", 55 | "conf": [ 56 | { 57 | "include": { 58 | "attribute": [ 59 | "ShouldBe100" 60 | ], 61 | "bean": "org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks" 62 | } 63 | } 64 | ], 65 | "cassandra_aliasing": true 66 | }, 67 | { 68 | "process_name_regex": ".*surefire.*", 69 | "name": "jmx_second_instance", 70 | "conf": [ 71 | { 72 | "include": { 73 | "attribute": [ 74 | "ShouldBe1000" 75 | ], 76 | "bean": "org.apache.cassandra.metrics:keyspace=MyKeySpace,type=ColumnFamily,scope=MyColumnFamily,name=PendingTasks" 77 | } 78 | } 79 | ] 80 | } 81 | ], 82 | "init_config": null 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/server/SimpleAppContainer.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util.server; 2 | 3 | import java.nio.file.Paths; 4 | 5 | import org.testcontainers.containers.GenericContainer; 6 | import org.testcontainers.containers.wait.strategy.Wait; 7 | import org.testcontainers.images.builder.ImageFromDockerfile; 8 | import org.testcontainers.lifecycle.Startable; 9 | import org.testcontainers.containers.wait.strategy.WaitAllStrategy; 10 | 11 | import org.datadog.jmxfetch.util.TimerWaitStrategy; 12 | 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | @Slf4j 16 | public class SimpleAppContainer implements Startable { 17 | 18 | private static final String JAVA_OPTS = "JAVA_OPTS"; 19 | private static final String RMI_PORT = "RMI_PORT"; 20 | private final String jreDockerImage; 21 | private final String javaOpts; 22 | private final int rmiPort; 23 | private final GenericContainer server; 24 | 25 | public SimpleAppContainer() { 26 | this(JDKImage.JDK_17.toString(), "", MisbehavingJMXServer.DEFAULT_RMI_PORT); 27 | 28 | } 29 | 30 | public SimpleAppContainer(final String jreDockerImage, final String javaOpts, final int rmiPort) { 31 | this.jreDockerImage = jreDockerImage; 32 | this.javaOpts = javaOpts; 33 | this.rmiPort = rmiPort; 34 | final ImageFromDockerfile img = new ImageFromDockerfile() 35 | .withFileFromPath("app.java", Paths.get("./src/test/java/org/datadog/jmxfetch/util/server/SimpleApp.java")) 36 | .withFileFromClasspath("Dockerfile", "org/datadog/jmxfetch/util/server/Dockerfile-SimpleApp") 37 | .withFileFromClasspath("run.sh", "org/datadog/jmxfetch/util/server/run-SimpleApp.sh") 38 | .withBuildArg("JRE_DOCKER_IMAGE", jreDockerImage); 39 | this.server = new GenericContainer<>(img) 40 | .withEnv(JAVA_OPTS, this.javaOpts) 41 | .withEnv(RMI_PORT, Integer.toString(this.rmiPort)) 42 | .withExposedPorts(this.rmiPort) 43 | // Waiting is a bit tricky here, so lets explain 44 | // There are two cases that need to be supported by this code 45 | // 1. Environments where port checks work correctly 46 | // 2. Environments where port checks never succeed 47 | // If the listening port is ever detected, then that is a valid signal 48 | // that the container has started. 49 | // If the log message is observed, we impose an artificial 5s delay 50 | // to allow the networking stack to "catch up" to the container logs 51 | // This is the fix for observed flakey tests in CI. 52 | .waitingFor(new WaitOrStrategy( 53 | new WaitAllStrategy() 54 | .withStrategy(Wait.forLogMessage(".*Sample app started..*", 1)) 55 | .withStrategy(new TimerWaitStrategy(5000)), 56 | Wait.forListeningPorts(this.rmiPort) 57 | )); 58 | } 59 | 60 | @Override 61 | public void start() { 62 | log.info("Starting SimpleApp with Docker image '{}' with JAVA_OPTS '{}' in port '{}'", 63 | this.jreDockerImage, this.javaOpts, this.rmiPort); 64 | this.server.start(); 65 | log.info(this.server.getLogs()); 66 | } 67 | 68 | @Override 69 | public void stop() { 70 | this.server.stop(); 71 | } 72 | 73 | public void close() { 74 | this.stop(); 75 | } 76 | 77 | public String getIp() { 78 | return this.server.getContainerInfo().getNetworkSettings().getIpAddress(); 79 | } 80 | 81 | public int getRMIPort() { 82 | return this.rmiPort; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/datadog/jmxfetch/util/MetricsAssert.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.util; 2 | 3 | import static org.hamcrest.CoreMatchers.hasItem; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.equalTo; 6 | import static org.junit.Assert.assertEquals; 7 | import static org.junit.Assert.assertTrue; 8 | import static org.junit.Assert.fail; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | import javax.management.MBeanServerConnection; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | @Slf4j 21 | public class MetricsAssert { 22 | 23 | public static void assertMetric( 24 | String name, 25 | Number value, 26 | Number lowerBound, 27 | Number upperBound, 28 | List commonTags, 29 | List additionalTags, 30 | int countTags, 31 | String metricType, 32 | List> actualMetrics) { 33 | List tags = new ArrayList<>(commonTags); 34 | tags.addAll(additionalTags); 35 | 36 | for (Map m : actualMetrics) { 37 | String mName = (String) (m.get("name")); 38 | Double mValue = (Double) (m.get("value")); 39 | Set mTags = new HashSet<>(Arrays.asList((String[]) (m.get("tags")))); 40 | 41 | if (mName.equals(name)) { 42 | 43 | if (!value.equals(-1)) { 44 | assertEquals((Double) value.doubleValue(), mValue); 45 | } else if (!lowerBound.equals(-1) || !upperBound.equals(-1)) { 46 | assertTrue(mValue > (Double) lowerBound.doubleValue()); 47 | assertTrue(mValue < (Double) upperBound.doubleValue()); 48 | } 49 | 50 | if (countTags != -1) { 51 | assertEquals("Tag count didn't match", countTags, mTags.size()); 52 | } 53 | for (String t : tags) { 54 | assertThat("Did not contain tag", mTags, hasItem(t)); 55 | } 56 | 57 | if (metricType != null) { 58 | assertEquals("Metric types not equal", metricType, m.get("type")); 59 | } 60 | // Brand the metric 61 | m.put("tested", true); 62 | 63 | return; 64 | } 65 | } 66 | fail( 67 | "Metric assertion failed (name: " 68 | + name 69 | + ", value: " 70 | + value 71 | + ", tags: " 72 | + tags 73 | + ", #tags: " 74 | + countTags 75 | + ")."); 76 | 77 | } 78 | 79 | public static void assertDomainPresent(final String domain, final MBeanServerConnection mbs){ 80 | assertThat(String.format("Could not find domain '%s'", domain), 81 | isDomainPresent(domain, mbs), equalTo(true)); 82 | } 83 | 84 | public static boolean isDomainPresent(final String domain, final MBeanServerConnection mbs) { 85 | boolean found = false; 86 | try { 87 | final String[] domains = mbs.getDomains(); 88 | for (String s : domains) { 89 | if(s.equals(domain)) { 90 | found = true; 91 | break; 92 | } 93 | } 94 | } catch (IOException e) { 95 | log.warn("Got an exception checking if domain is present", e); 96 | } 97 | return found; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/Connection.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.io.InterruptedIOException; 7 | import java.net.SocketTimeoutException; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ArrayBlockingQueue; 12 | import java.util.concurrent.BlockingQueue; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.TimeUnit; 17 | import javax.management.Attribute; 18 | import javax.management.AttributeNotFoundException; 19 | import javax.management.InstanceNotFoundException; 20 | import javax.management.IntrospectionException; 21 | import javax.management.MBeanAttributeInfo; 22 | import javax.management.MBeanException; 23 | import javax.management.MBeanInfo; 24 | import javax.management.MBeanServerConnection; 25 | import javax.management.ObjectName; 26 | import javax.management.ReflectionException; 27 | import javax.management.remote.JMXConnector; 28 | import javax.management.remote.JMXConnectorFactory; 29 | import javax.management.remote.JMXServiceURL; 30 | 31 | @Slf4j 32 | public class Connection { 33 | private static final long CONNECTION_TIMEOUT = 10000; 34 | private JMXConnector connector; 35 | protected MBeanServerConnection mbs; 36 | protected Map env; 37 | protected JMXServiceURL address; 38 | 39 | /** Gets attributes for matching bean name. */ 40 | public MBeanInfo getMBeanInfo(ObjectName beanName) 41 | throws InstanceNotFoundException, IntrospectionException, ReflectionException, 42 | IOException { 43 | return mbs.getMBeanInfo(beanName); 44 | } 45 | 46 | /** Queries beans on specific scope. Returns set of matching query names.. */ 47 | public Set queryNames(ObjectName name) throws IOException { 48 | String scope = (name != null) ? name.toString() : "*:*"; 49 | log.debug("Querying bean names on scope: " + scope); 50 | return mbs.queryNames(name, null); 51 | } 52 | 53 | protected void createConnection() throws IOException { 54 | this.env.put("attribute.remote.x.request.waiting.timeout", CONNECTION_TIMEOUT); 55 | closeConnector(); 56 | log.info("Connecting to: " + this.address); 57 | connector = JMXConnectorFactory.connect(this.address, this.env); 58 | mbs = connector.getMBeanServerConnection(); 59 | } 60 | 61 | /** Gets attribute for matching bean and attribute name. */ 62 | public Object getAttribute(ObjectName objectName, String attributeName) 63 | throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, 64 | ReflectionException, IOException { 65 | Object attr = mbs.getAttribute(objectName, attributeName); 66 | if (attr instanceof javax.management.Attribute) { 67 | return ((Attribute) attr).getValue(); 68 | } 69 | return attr; 70 | } 71 | 72 | /** Closes the connector. */ 73 | public void closeConnector() { 74 | if (connector != null) { 75 | try { 76 | connector.close(); 77 | } catch (IOException e) { 78 | // ignore 79 | } 80 | } 81 | } 82 | 83 | /** Returns a boolean describing if the connection is still alive. */ 84 | public boolean isAlive() { 85 | if (connector == null) { 86 | return false; 87 | } 88 | try { 89 | connector.getConnectionId(); 90 | } catch (IOException e) { // the connection is closed or broken 91 | return false; 92 | } 93 | return true; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - deploy_to_sonatype 3 | - create_key 4 | - run_unit_tests 5 | 6 | variables: 7 | REGISTRY: registry.ddbuild.io 8 | # ryuk is a privileged container started by testcontainers which cleans up containers at the end of testing 9 | # It is not necessary for our gitlab CI env as #ci-cd infra tears everything down at the end of a gitlab run 10 | TESTCONTAINERS_RYUK_DISABLED: "true" 11 | 12 | 13 | # Testing is handled by circleCI for PRs, but we also run maven tests as part of the deploy process 14 | # This run_unit_tests job is useful for ensuring the tests run fine without any of the deployment bits 15 | run_unit_tests: 16 | stage: run_unit_tests 17 | 18 | rules: 19 | - when: manual 20 | allow_failure: true 21 | 22 | tags: 23 | - "runner:docker" 24 | 25 | image: &jdk-image eclipse-temurin:8u452-b09-jdk 26 | 27 | script: 28 | - ./mvnw -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true -B test 29 | 30 | artifacts: 31 | expire_in: 1 mos 32 | when: always 33 | paths: 34 | - ./target/surefire-reports/*.txt 35 | 36 | 37 | # From the tagged repo, push the release artifact 38 | deploy_to_sonatype: 39 | stage: deploy_to_sonatype 40 | 41 | rules: 42 | # All releases are manual 43 | - when: manual 44 | allow_failure: true 45 | 46 | tags: 47 | - "runner:docker" 48 | 49 | image: *jdk-image 50 | 51 | script: 52 | # Ensure we don't print commands being run to the logs during credential 53 | # operations 54 | - set +x 55 | 56 | - echo "Setting up Python virtual environment..." 57 | - apt update 58 | - apt install -y python3 python3-pip python3-venv 59 | - python3 -m venv venv 60 | - source venv/bin/activate 61 | - python3 -m pip install --upgrade pip 62 | - echo "Installing AWSCLI..." 63 | - python3 -m pip install awscli 64 | 65 | - echo "Fetching Sonatype user..." 66 | - export SONATYPE_USER=$(aws ssm get-parameter --region us-east-1 --name ci.jmxfetch.publishing.sonatype_username --with-decryption --query "Parameter.Value" --out text) 67 | - echo "Fetching Sonatype password..." 68 | - export SONATYPE_PASS=$(aws ssm get-parameter --region us-east-1 --name ci.jmxfetch.publishing.sonatype_password --with-decryption --query "Parameter.Value" --out text) 69 | 70 | - echo "Fetching signing key password..." 71 | - export GPG_PASSPHRASE=$(aws ssm get-parameter --region us-east-1 --name ci.jmxfetch.signing.gpg_passphrase --with-decryption --query "Parameter.Value" --out text) 72 | 73 | - echo "Fetching signing key..." 74 | - gpg_key=$(aws ssm get-parameter --region us-east-1 --name ci.jmxfetch.signing.gpg_private_key --with-decryption --query "Parameter.Value" --out text) 75 | - printf -- "$gpg_key" | gpg --import --batch 76 | 77 | - set -x 78 | 79 | - echo "Building release..." 80 | - ./mvnw -Djdk.attach.allowAttachSelf=true -DperformRelease=true -Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 --settings ./settings.xml clean deploy 81 | 82 | - sha256sum ./target/*-jar-with-dependencies.jar 83 | 84 | artifacts: 85 | expire_in: 12 mos 86 | paths: 87 | - ./target/*.jar 88 | - ./target/*.pom 89 | - ./target/*.asc 90 | - ./target/*.md5 91 | - ./target/*.sha1 92 | - ./target/*.sha256 93 | - ./target/*.sha512 94 | 95 | # This job creates the GPG key used to sign the releases 96 | create_key: 97 | stage: create_key 98 | when: manual 99 | 100 | tags: 101 | - "runner:docker" 102 | 103 | image: $REGISTRY/ci/agent-key-management-tools/gpg:1 104 | 105 | variables: 106 | PROJECT_NAME: "jmxfetch" 107 | 108 | script: 109 | - /create.sh 110 | 111 | artifacts: 112 | expire_in: 13 mos 113 | paths: 114 | - ./pubkeys/ 115 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/reporter/ConsoleReporter.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.reporter; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import org.datadog.jmxfetch.Instance; 6 | import org.datadog.jmxfetch.JmxAttribute; 7 | import org.datadog.jmxfetch.util.StringUtils; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @Slf4j 15 | public class ConsoleReporter extends Reporter { 16 | 17 | private List> metrics = new ArrayList>(); 18 | private List> serviceChecks = new ArrayList>(); 19 | 20 | @Override 21 | protected void sendMetricPoint( 22 | String metricType, String metricName, double value, String[] tags) { 23 | String tagString = "[" + StringUtils.join(",", tags) + "]"; 24 | log.info( 25 | metricName + tagString + " - " + System.currentTimeMillis() / 1000 + " = " + value); 26 | 27 | Map metric = new HashMap(); 28 | metric.put("name", metricName); 29 | metric.put("value", value); 30 | metric.put("tags", tags); 31 | metric.put("type", metricType); 32 | metrics.add(metric); 33 | } 34 | 35 | /** Returns list of metrics to report and clears stored metric map. */ 36 | public List> getMetrics() { 37 | List> returnedMetrics = 38 | new ArrayList>(metrics.size()); 39 | for (Map map : metrics) { 40 | returnedMetrics.add(new HashMap(map)); 41 | } 42 | metrics.clear(); 43 | return returnedMetrics; 44 | } 45 | 46 | /** Adds service check to report on. */ 47 | public void doSendServiceCheck( 48 | String serviceCheckName, String status, String message, String[] tags) { 49 | String tagString = ""; 50 | if (tags != null && tags.length > 0) { 51 | tagString = "[" + StringUtils.join(",", tags) + "]"; 52 | } 53 | log.info(serviceCheckName + tagString + " - " + System.currentTimeMillis() / 1000 54 | + " = " + status); 55 | 56 | Map sc = new HashMap(); 57 | sc.put("name", serviceCheckName); 58 | sc.put("status", status); 59 | sc.put("message", message); 60 | sc.put("tags", tags); 61 | serviceChecks.add(sc); 62 | } 63 | 64 | /** Returns list of service checks to report and clears stored service check map.. */ 65 | public List> getServiceChecks() { 66 | List> returnedServiceChecks = 67 | new ArrayList>(serviceChecks.size()); 68 | for (Map map : serviceChecks) { 69 | returnedServiceChecks.add(new HashMap(map)); 70 | } 71 | serviceChecks.clear(); 72 | return returnedServiceChecks; 73 | } 74 | 75 | @Override 76 | public void displayMetricReached() { 77 | log.info( 78 | " ------- METRIC LIMIT REACHED: ATTRIBUTES BELOW WON'T BE COLLECTED -------"); 79 | } 80 | 81 | @Override 82 | public void displayMatchingAttributeName(JmxAttribute jmxAttribute, int rank, int limit) { 83 | log.info(" Matching: " + rank + "/" + limit + ". " + jmxAttribute); 84 | } 85 | 86 | @Override 87 | public void displayNonMatchingAttributeName(JmxAttribute jmxAttribute) { 88 | log.info(" Not Matching: " + jmxAttribute); 89 | } 90 | 91 | @Override 92 | public void displayInstanceName(Instance instance) { 93 | log.info("#####################################"); 94 | log.info("Instance: " + instance); 95 | log.info("#####################################"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: read-all 8 | 9 | jobs: 10 | lint: 11 | name: Lint (OpenJDK 8) 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 18 | 19 | - name: Set up JDK 8 20 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 21 | with: 22 | java-version: '8' 23 | distribution: 'temurin' 24 | 25 | - name: Cache Maven packages 26 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 27 | with: 28 | path: ~/.m2 29 | key: ${{ runner.os }}-m2-${{ github.ref }}-${{ hashFiles('**/pom.xml') }}-lint 30 | restore-keys: | 31 | ${{ runner.os }}-m2-${{ github.ref }}- 32 | ${{ runner.os }}-m2- 33 | 34 | - name: Run lint 35 | run: ./mvnw verify -B -Dhttps.protocols=TLSv1.2 -DskipTests 36 | 37 | jdk7-verification: 38 | name: JDK 7 verification 39 | runs-on: ubuntu-latest 40 | timeout-minutes: 5 41 | 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 45 | 46 | - name: Set up JDK 8 47 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 48 | with: 49 | java-version: '8' 50 | distribution: 'temurin' 51 | 52 | - name: Cache Maven packages 53 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 54 | with: 55 | path: ~/.m2 56 | key: ${{ runner.os }}-m2-${{ github.ref }}-${{ hashFiles('**/pom.xml') }}-jdk7 57 | restore-keys: | 58 | ${{ runner.os }}-m2-${{ github.ref }}- 59 | ${{ runner.os }}-m2- 60 | 61 | - name: Build JMXFetch 62 | run: ./mvnw clean package -DskipTests 63 | 64 | - name: Run using JDK 7 65 | run: docker run -i --rm -v $(pwd):/app -w /app openjdk:7-jdk java -jar ./target/jmxfetch-0.51.1-SNAPSHOT-jar-with-dependencies.jar --help 66 | 67 | test: 68 | name: Test (OpenJDK ${{ matrix.java-version }}) 69 | runs-on: ubuntu-latest 70 | timeout-minutes: 15 71 | strategy: 72 | matrix: 73 | java-version: 74 | - 8 75 | - 11 76 | - 17 77 | - 21 78 | - 24 # Latest JDK version 79 | 80 | steps: 81 | - name: Checkout code 82 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 83 | 84 | - name: Set up JDK ${{ matrix.java-version }} 85 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 86 | with: 87 | java-version: ${{ matrix.java-version }} 88 | distribution: 'temurin' 89 | 90 | - name: Cache Maven packages 91 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 92 | with: 93 | path: ~/.m2 94 | key: ${{ runner.os }}-m2-${{ github.ref }}-${{ hashFiles('**/pom.xml') }} 95 | restore-keys: | 96 | ${{ runner.os }}-m2-${{ github.ref }}- 97 | ${{ runner.os }}-m2- 98 | 99 | - name: Set up Docker 100 | uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0 101 | 102 | - name: Run tests 103 | run: ./mvnw test -B -Dhttps.protocols=TLSv1.2 -Dcheckstyle.skip=true -Dtests.log_level=info -Djdk.attach.allowAttachSelf=true 104 | 105 | - name: Show test logs on failure 106 | if: failure() 107 | run: | 108 | echo "Test failed. Showing surefire reports:" 109 | for log in target/surefire-reports/*.txt; do 110 | if [ -f "$log" ]; then 111 | echo "$log ========================" 112 | cat "$log" 113 | fi 114 | done 115 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/AttachApiConnection.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.lang.reflect.Method; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import javax.management.remote.JMXServiceURL; 11 | 12 | @Slf4j 13 | public class AttachApiConnection extends Connection { 14 | private static final String CONNECTOR_ADDRESS = 15 | "com.sun.management.jmxremote.localConnectorAddress"; 16 | private String processRegex; 17 | 18 | /** AttachApiConnection constructor for specified connection parameters. */ 19 | public AttachApiConnection(Map connectionParams) throws IOException { 20 | processRegex = (String) connectionParams.get("process_name_regex"); 21 | this.env = new HashMap(); 22 | this.address = getAddress(connectionParams); 23 | createConnection(); 24 | } 25 | 26 | private JMXServiceURL getAddress(Map connectionParams) 27 | throws IOException { 28 | JMXServiceURL address; 29 | try { 30 | address = new JMXServiceURL(getJmxUrlForProcessRegex(processRegex)); 31 | } catch (com.sun.tools.attach.AttachNotSupportedException e) { 32 | throw new IOException("Unable to attach to process regex: " + processRegex, e); 33 | } 34 | return address; 35 | } 36 | 37 | private String getJmxUrlForProcessRegex(String processRegex) 38 | throws com.sun.tools.attach.AttachNotSupportedException, IOException { 39 | for (com.sun.tools.attach.VirtualMachineDescriptor vmd : 40 | com.sun.tools.attach.VirtualMachine.list()) { 41 | if (vmd.displayName().matches(processRegex)) { 42 | com.sun.tools.attach.VirtualMachine vm = 43 | com.sun.tools.attach.VirtualMachine.attach(vmd); 44 | String connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS); 45 | // If jmx agent is not running in VM, load it and return the connector url 46 | if (connectorAddress == null) { 47 | loadJmxAgent(vm); 48 | 49 | // agent is started, get the connector address 50 | connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS); 51 | } 52 | 53 | return connectorAddress; 54 | } 55 | } 56 | 57 | throw new IOException( 58 | "No match found. Available JVMs can be listed with the `list_jvms` command."); 59 | } 60 | 61 | // management-agent.jar has been removed in java 8+ 62 | // Once JMXFetch drops java 7 support, this should be simplified to simply invoke 63 | // vm.startLocalManagementAgent which is accessible in java 8 if tools.jar is added 64 | // to the classpath and java 9+ by default 65 | // ref https://bugs.openjdk.org/browse/JDK-8179063 66 | private void loadJmxAgent(com.sun.tools.attach.VirtualMachine vm) throws IOException { 67 | try { 68 | Method method = com.sun.tools.attach.VirtualMachine 69 | .class.getMethod("startLocalManagementAgent"); 70 | log.info("Found startLocalManagementAgent API, attempting to use it."); 71 | method.invoke(vm); 72 | } catch (NoSuchMethodException noMethodE) { 73 | log.warn("startLocalManagementAgent method not found, must be on java 7", noMethodE); 74 | String agent = vm.getSystemProperties().getProperty("java.home") 75 | + File.separator 76 | + "lib" 77 | + File.separator 78 | + "management-agent.jar"; 79 | try { 80 | vm.loadAgent(agent); 81 | } catch (Exception e) { 82 | log.warn("Error initializing JMX agent from management-agent.jar", e); 83 | } 84 | } catch (Exception reflectionE) { 85 | log.warn("Error invoking the startLocalManagementAgent method", reflectionE); 86 | } 87 | 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/DynamicTag.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import javax.management.MalformedObjectNameException; 8 | import javax.management.ObjectName; 9 | 10 | @Slf4j 11 | public class DynamicTag { 12 | private final String tagName; 13 | private final String beanName; 14 | private final String attributeName; 15 | 16 | /** Parse dynamic tag from configuration map (list entry format). */ 17 | public static DynamicTag parse(Object tagConfig) { 18 | if (tagConfig == null) { 19 | return null; 20 | } 21 | 22 | if (!(tagConfig instanceof Map)) { 23 | log.warn("Invalid dynamic tag config: expected map with 'tag_name', 'bean_name' and " 24 | + "'attribute' keys"); 25 | return null; 26 | } 27 | 28 | Map config = (Map) tagConfig; 29 | Object tagNameObj = config.get("tag_name"); 30 | Object beanObj = config.get("bean_name"); 31 | Object attrObj = config.get("attribute"); 32 | 33 | if (tagNameObj == null || beanObj == null || attrObj == null) { 34 | String missing = "Invalid dynamic tag config: missing" 35 | + (tagNameObj == null ? " tag_name" : "") 36 | + (beanObj == null ? " bean_name" : "") 37 | + (attrObj == null ? " attribute" : ""); 38 | log.warn(missing); 39 | return null; 40 | } 41 | 42 | String tagName = tagNameObj.toString(); 43 | String beanName = beanObj.toString(); 44 | String attributeName = attrObj.toString(); 45 | 46 | return new DynamicTag(tagName, beanName, attributeName); 47 | } 48 | 49 | private DynamicTag(String tagName, String beanName, String attributeName) { 50 | this.tagName = tagName; 51 | this.beanName = beanName; 52 | this.attributeName = attributeName; 53 | } 54 | 55 | public String getTagName() { 56 | return tagName; 57 | } 58 | 59 | public String getBeanName() { 60 | return beanName; 61 | } 62 | 63 | public String getAttributeName() { 64 | return attributeName; 65 | } 66 | 67 | /** Gets a unique key for the bean and attribute combination. */ 68 | public String getBeanAttributeKey() { 69 | return beanName + "#" + attributeName; 70 | } 71 | 72 | /** Resolve the dynamic tag by fetching the attribute value from JMX. */ 73 | public Map.Entry resolve(Connection connection) { 74 | try { 75 | ObjectName objectName = new ObjectName(beanName); 76 | Object value = connection.getAttribute(objectName, attributeName); 77 | 78 | if (value == null) { 79 | log.warn("Dynamic tag '{}' resolved to null for bean '{}' attribute '{}'", 80 | tagName, beanName, attributeName); 81 | return null; 82 | } 83 | 84 | String stringValue = value.toString(); 85 | log.info("Resolved dynamic tag '{}' to value '{}' from bean '{}' attribute '{}'", 86 | tagName, stringValue, beanName, attributeName); 87 | 88 | return new HashMap.SimpleEntry<>(tagName, stringValue); 89 | 90 | } catch (MalformedObjectNameException e) { 91 | log.error("Invalid bean name '{}' for dynamic tag '{}': {}", 92 | beanName, tagName, e.getMessage()); 93 | return null; 94 | } catch (Exception e) { 95 | log.warn("Failed to resolve dynamic tag '{}' from bean '{}' attribute '{}': {}", 96 | tagName, beanName, attributeName, e.getMessage()); 97 | log.debug("Dynamic tag resolution error details", e); 98 | return null; 99 | } 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return String.format("DynamicTag{name='%s', bean='%s', attribute='%s'}", 105 | tagName, beanName, attributeName); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/src/main/java/org/datadog/misbehavingjmxserver/BeanManager.java: -------------------------------------------------------------------------------- 1 | package org.datadog.misbehavingjmxserver; 2 | 3 | import javax.management.InstanceAlreadyExistsException; 4 | import javax.management.InstanceNotFoundException; 5 | import javax.management.MBeanRegistrationException; 6 | import javax.management.MBeanServer; 7 | import javax.management.MalformedObjectNameException; 8 | import javax.management.NotCompliantMBeanException; 9 | import javax.management.ObjectName; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.concurrent.ScheduledExecutorService; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.Executors; 19 | 20 | 21 | import lombok.extern.slf4j.Slf4j; 22 | 23 | @Slf4j 24 | public class BeanManager { 25 | private final MBeanServer mBeanServer; 26 | private final Map> registeredBeans; 27 | private final MetricDAO mDao; 28 | private final RandomIdentifier idGen; 29 | static final long ATTRIBUTE_REFRESH_INTERVAL = 10; 30 | private final ScheduledExecutorService executor; 31 | 32 | public BeanManager(MBeanServer mBeanServer, MetricDAO mDao, long rngSeed) { 33 | this.mBeanServer = mBeanServer; 34 | this.registeredBeans = new HashMap<>(); 35 | this.mDao = mDao; 36 | this.idGen = new RandomIdentifier(rngSeed); 37 | this.executor = Executors.newSingleThreadScheduledExecutor(); 38 | runAttributeUpdateLoop(); 39 | } 40 | 41 | private ObjectName getObjName(String domain, DynamicMBeanMetrics metric) throws MalformedObjectNameException { 42 | return new ObjectName(domain + ":name=" + metric.name); 43 | } 44 | 45 | 46 | public void clearDomainBeans(String beanDomain){ 47 | List beansList = this.registeredBeans.getOrDefault(beanDomain,new ArrayList()); 48 | int size = beansList.size(); 49 | 50 | for (int i = 0; i < size; i++){ 51 | DynamicMBeanMetrics metric = beansList.get(0); 52 | try { 53 | ObjectName obj = getObjName(beanDomain, metric); 54 | this.mBeanServer.unregisterMBean(obj); 55 | beansList.remove(0); 56 | } catch (MBeanRegistrationException | InstanceNotFoundException | MalformedObjectNameException e) { 57 | log.warn("Could not unregister bean {} for domain {}", metric.name, beanDomain, e); 58 | } 59 | } 60 | registeredBeans.put(beanDomain, new ArrayList()); 61 | } 62 | 63 | public void setMBeanState(String beanDomain, BeanSpec domainSpec) { 64 | clearDomainBeans(beanDomain); 65 | ArrayList beansList = new ArrayList(); 66 | 67 | for (int i = 0; i < domainSpec.beanCount; i++) { 68 | DynamicMBeanMetrics metric = new DynamicMBeanMetrics("Bean-" + idGen.generateIdentifier(), domainSpec.scalarAttributeCount, 69 | domainSpec.tabularAttributeCount, domainSpec.compositeValuesPerTabularAttribute, mDao); 70 | try { 71 | ObjectName obj = getObjName(beanDomain, metric); 72 | log.debug("Registering bean with ObjectName: {}", obj); 73 | this.mBeanServer.registerMBean(metric, obj); 74 | beansList.add(metric); 75 | } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e) { 76 | log.warn("Could not add bean {} for domain {}", metric.name, beanDomain, e); 77 | } 78 | } 79 | registeredBeans.put(beanDomain, beansList); 80 | 81 | 82 | } 83 | 84 | public Optional> getMBeanState(String domain) { 85 | return Optional.ofNullable(registeredBeans.get(domain)); 86 | } 87 | 88 | public void Do() { 89 | for (Map.Entry> beanDomainEntry : registeredBeans.entrySet()) { 90 | List beansList = beanDomainEntry.getValue(); 91 | for (DynamicMBeanMetrics bean: beansList){ 92 | bean.updateBeanAttributes(); 93 | } 94 | } 95 | } 96 | 97 | void runAttributeUpdateLoop() { 98 | Runnable task = () -> { 99 | this.Do(); 100 | }; 101 | executor.scheduleAtFixedRate(task, 0, ATTRIBUTE_REFRESH_INTERVAL, TimeUnit.SECONDS); 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/datadog/jmxfetch/tasks/TaskProcessor.java: -------------------------------------------------------------------------------- 1 | package org.datadog.jmxfetch.tasks; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import org.datadog.jmxfetch.Instance; 6 | import org.datadog.jmxfetch.InstanceTask; 7 | import org.datadog.jmxfetch.reporter.Reporter; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.Callable; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Future; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | @Slf4j 18 | public class TaskProcessor { 19 | private Reporter reporter; 20 | private ExecutorService threadPoolExecutor; 21 | 22 | /** 23 | * TaskProcess constructor. 24 | * */ 25 | public TaskProcessor(ExecutorService executor, Reporter reporter) { 26 | threadPoolExecutor = executor; 27 | this.reporter = reporter; 28 | } 29 | 30 | public void setThreadPoolExecutor(ExecutorService executor) { 31 | threadPoolExecutor = executor; 32 | } 33 | 34 | /** 35 | * Returns whether or not the executor service has threads available 36 | * for work on tasks. 37 | * */ 38 | public boolean ready() { 39 | if (threadPoolExecutor == null) { 40 | // assumes we are in embedded mode and tasks will process by the calling thread 41 | return true; 42 | } 43 | ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadPoolExecutor; 44 | return !tpe.isTerminated() && !(tpe.getMaximumPoolSize() == tpe.getActiveCount()); 45 | } 46 | 47 | /** 48 | * Processes the list of InstanceTasks within a set timeout deadline. 49 | * */ 50 | public List processTasks( 51 | List> tasks, int timeout, TimeUnit timeUnit, TaskMethod processor) 52 | throws Exception { 53 | List statuses = new ArrayList(); 54 | try { 55 | if (threadPoolExecutor != null) { 56 | List> callables = new ArrayList>(tasks); 57 | List> results = threadPoolExecutor.invokeAll(callables, timeout, 58 | timeUnit); 59 | for (int i = 0; i < results.size(); i++) { 60 | Instance instance = tasks.get(i).getInstance(); 61 | try { 62 | Future future = results.get(i); 63 | statuses.add(processor.invoke(instance, future, reporter)); 64 | } catch (Exception e) { 65 | log.warn("There was an error processing concurrent instance: " 66 | + instance, e); 67 | statuses.add(new TaskStatusHandler(e)); 68 | } 69 | } 70 | } else { 71 | for (InstanceTask task : tasks) { 72 | T result = task.call(); 73 | statuses.add(processor.invoke(task.getInstance(), new SimpleFuture(result), 74 | reporter)); 75 | } 76 | } 77 | 78 | } catch (Exception e) { 79 | // Should we do anything else here? 80 | log.warn("JMXFetch internal TaskProcessor error invoking concurrent tasks: ", e); 81 | throw e; 82 | } 83 | return statuses; 84 | } 85 | 86 | /** 87 | * Stops the excutor service. 88 | * */ 89 | public void stop() { 90 | if (threadPoolExecutor != null) { 91 | threadPoolExecutor.shutdownNow(); 92 | } 93 | } 94 | 95 | /** 96 | * used to wrap the result in embedded mode when not using executors. 97 | */ 98 | private static class SimpleFuture implements Future { 99 | private final T result; 100 | 101 | public SimpleFuture(T result) { 102 | this.result = result; 103 | } 104 | 105 | @Override 106 | public boolean cancel(boolean mayInterruptIfRunning) { 107 | return false; 108 | } 109 | 110 | @Override 111 | public boolean isCancelled() { 112 | return false; 113 | } 114 | 115 | @Override 116 | public boolean isDone() { 117 | return true; 118 | } 119 | 120 | @Override 121 | public T get() { 122 | return result; 123 | } 124 | 125 | @Override 126 | public T get(long timeout, TimeUnit unit) { 127 | return result; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /tools/misbehaving-jmx-server/README.md: -------------------------------------------------------------------------------- 1 | # Misbehaving JMX Server 2 | This project exists to have a JMX server that is easily controllable from an external process 3 | to intentionally cause connection issues. 4 | 5 | ## Current features 6 | - cut and restore the TCP network connections between the JMX server and any clients attached 7 | 8 | ## Implementation 9 | - `org.datadog.misbehavingjmxserver.App` is a JMX server exposing custom mbeans 10 | as well as an HTTP control interface to allow injection of network errors. Entrypoint class for the jar. 11 | - `org.datadog.supervisor.App` is a secondary class (non-entrypoint) for the JAR and its job is to wait for 12 | a secondary `init` payload that contains the correct RMI Hostname. It is designed for use in a container where you may not know the hostname before starting the container. 13 | 14 | ## Build 15 | `mvn clean compile assembly:single` 16 | 17 | ## Run standalone JMX server 18 | `java -jar target/misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar` 19 | 20 | ## Run with supervisor 21 | `java -cp target/misbehavingjmxserver-1.0-SNAPSHOT-jar-with-dependencies.jar org.datadog.supervisor.App` 22 | 23 | ## Configure 24 | - `RMI_PORT` - RMI port for jmx failure server to use (default 1099) 25 | - `RMI_HOST` - hostname for JMX to listen on (default localhost) 26 | - `CONTROL_PORT` - HTTP control port (default 8080) 27 | - `SUPERVISOR_PORT` - HTTP control port for the supervisor process (if using) (default 8088) 28 | - `MISBEHAVING_OPTS` - Manages memory, GC configurations, and system properties of the Java process running the JMXServer (default `-Xmx128M -Xms128M`) 29 | 30 | ## HTTP Control Actions (jmx-server) 31 | - POST `/cutNetwork` - Denies any requests to create a new socket (ie, no more connections will be 'accept'ed) and then closes existing TCP sockets 32 | - POST `/restoreNetwork` - Allows new sockets to be created 33 | - 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 34 | - 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`. 35 | 36 | ## Bean Configuration 37 | - `beanCount` - Declares how many beans should be present in a specfic domain 38 | - `scalarAttributeCount` - Defines the number of simple attributes in all beans for a given domain 39 | - `tabularAttributeCount` - Defines the number of tabular attributes in each bean for a given domain 40 | - `compositeValuesPerTabularAttribute` - Defines the number of rows of data per tabular attribute 41 | 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. 42 | 43 | ## Configuration File 44 | 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. 45 | An example file can be found at `misbehaving-jmx-domains-config.yaml`. 46 | 47 | 48 | ## HTTP Control Actions (supervisor) 49 | - POST `/init` - Provides `rmiHostname` to be used by jmx-server. jmx server will not be listening until this init payload is sent 50 | 51 | ## Docker 52 | ``` 53 | $ docker build -t misbehaving-jmx-server . 54 | $ docker run --rm -p :1099 misbehaving-jmx-server 55 | ``` 56 | 57 | Can connect via jmxterm ` java -jar ~/jmxterm-1.0.2-uber.jar --url localhost:` 58 | 59 | ## Testing the server with the Agent 60 | 61 | There are a couple of ways you can get the Agent to pull metrics from this test server. 62 | 63 | ### JMX integration config 64 | 65 | Copy `misbehaving-jmxfetch-conf.yaml` to `/etc/datadog-agent/conf.d/` and just run the `with-dependencies` jar created by Maven. 66 | You will need to restart the Agent to pick up the config. 67 | 68 | ### Using Docker Compose 69 | 70 | ```shell 71 | $ docker compose up 72 | ``` 73 | 74 | The Agent will auto-discover the container and begin to collect metrics from it. 75 | 76 | ### Using Docker 77 | 78 | If your container's IP is directly 79 | accessible by your Agent, you can use the following `run` command and use AD. 80 | 81 | ```shell 82 | $ docker run \ 83 | --rm \ 84 | -p 1099:1099 \ 85 | --label "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"}}]}]}}' \ 86 | misbehaving-jmx-server 87 | ``` 88 | 89 | The Agent will auto discover the container and begin to collect metrics from it. 90 | 91 | Note that this implicitly sets the `RMI_HOSTNAME` to `localhost` which is where 92 | the host port mapping comes into play. If this is giving you trouble, consider 93 | using the [docker-compose setup](#using-docker-compose). 94 | 95 | --------------------------------------------------------------------------------