├── .gitignore ├── .travis.yml ├── BUILDANDDEPLOY.md ├── LICENSE ├── README.md ├── bash-installer ├── README.md ├── build-installer.sh ├── cassandra-diagnostics-installer.sh ├── installer-runner.sh └── installer │ ├── cassandra-diagnostics-jvm-opts-manager.sh │ ├── cassandra-diagnostics-urls.sh │ ├── cassandra-service-manager.sh │ ├── common-functions.sh │ ├── diagnostic-library-files-manager.sh │ ├── find-reporter-modules.awk │ ├── input-arguments-parser.sh │ ├── input-arguments-validator.sh │ ├── installer-messages.sh │ ├── main.sh │ ├── non-interactive-installer.sh │ └── prerequisites-checker.sh ├── cassandra-diagnostics-commons ├── pom.xml └── src │ ├── main │ ├── java-templates │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ProjectInfo.java │ └── java │ │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ ├── GlobalConfiguration.java │ │ ├── Measurement.java │ │ ├── Query.java │ │ ├── connector │ │ ├── AbstractEventProcessor.java │ │ ├── Connector.java │ │ ├── ConnectorConfiguration.java │ │ └── QueryReporter.java │ │ ├── info │ │ ├── CompactionInfo.java │ │ ├── CompactionSettingsInfo.java │ │ ├── InfoProvider.java │ │ ├── NodeInfo.java │ │ └── TPStatsInfo.java │ │ ├── module │ │ ├── AtomicCounter.java │ │ ├── Module.java │ │ └── ModuleConfiguration.java │ │ ├── reporter │ │ ├── Reporter.java │ │ └── ReporterConfiguration.java │ │ └── utils │ │ ├── Option.java │ │ └── Utils.java │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ ├── MeasurementTest.java │ └── connector │ └── ConfigurationTest.java ├── cassandra-diagnostics-connector21 ├── README.md ├── pom.xml └── src │ ├── integration-test │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── connector │ │ │ └── ITConnector.java │ └── resources │ │ ├── cu-cassandra.yaml │ │ └── log4j-embedded-cassandra.properties │ ├── main │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── connector │ │ │ ├── ConnectorImpl.java │ │ │ ├── NodeProbeWrapper.java │ │ │ └── QueryProcessorWrapper.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.smartcat.cassandra.diagnostics.connector.Connector │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── connector │ ├── ConnectorImplTest.java │ └── QueryProcessorWrapperTest.java ├── cassandra-diagnostics-connector30 ├── README.md ├── pom.xml └── src │ ├── integration-test │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── connector │ │ │ └── ITConnector.java │ └── resources │ │ ├── cu-cassandra.yaml │ │ └── log4j-embedded-cassandra.properties │ ├── main │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── connector │ │ │ ├── ConnectorImpl.java │ │ │ ├── NodeProbeWrapper.java │ │ │ └── QueryProcessorWrapper.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.smartcat.cassandra.diagnostics.connector.Connector │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── connector │ ├── ConnectorImplTest.java │ └── QueryProcessorWrapperTest.java ├── cassandra-diagnostics-core ├── COREMODULES.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ ├── ConnectorFactory.java │ │ │ ├── Diagnostics.java │ │ │ ├── DiagnosticsAgent.java │ │ │ ├── DiagnosticsProcessor.java │ │ │ ├── api │ │ │ ├── DiagnosticsApi.java │ │ │ ├── DiagnosticsApiImpl.java │ │ │ └── HttpHandler.java │ │ │ ├── config │ │ │ ├── Configuration.java │ │ │ ├── ConfigurationException.java │ │ │ ├── ConfigurationLoader.java │ │ │ └── YamlConfigurationLoader.java │ │ │ ├── module │ │ │ ├── health │ │ │ │ ├── ClusterHealthConfiguration.java │ │ │ │ └── ClusterHealthModule.java │ │ │ ├── heartbeat │ │ │ │ ├── HeartbeatConfiguration.java │ │ │ │ └── HeartbeatModule.java │ │ │ ├── hiccup │ │ │ │ ├── HiccupConfiguration.java │ │ │ │ ├── HiccupModule.java │ │ │ │ └── HiccupRecorder.java │ │ │ ├── metrics │ │ │ │ ├── MetricsCollector.java │ │ │ │ ├── MetricsConfiguration.java │ │ │ │ ├── MetricsMBean.java │ │ │ │ └── MetricsModule.java │ │ │ ├── requestrate │ │ │ │ ├── RequestRateConfiguration.java │ │ │ │ └── RequestRateModule.java │ │ │ ├── slowquery │ │ │ │ ├── SlowQueryConfiguration.java │ │ │ │ ├── SlowQueryLogDecider.java │ │ │ │ └── SlowQueryModule.java │ │ │ └── status │ │ │ │ ├── StatusConfiguration.java │ │ │ │ └── StatusModule.java │ │ │ └── reporter │ │ │ └── LogReporter.java │ └── resources │ │ └── cassandra-diagnostics-default.yml │ └── test │ ├── java │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ ├── DiagnosticsModuleTest.java │ │ ├── DiagnosticsTest.java │ │ ├── api │ │ └── HttpHandlerTest.java │ │ ├── config │ │ ├── ConfigurationTest.java │ │ └── YamlConfigurationLoaderTest.java │ │ └── module │ │ ├── LatchTestReporter.java │ │ ├── TestMXBean.java │ │ ├── TestMXBeanImpl.java │ │ ├── TestReporter.java │ │ ├── health │ │ ├── ClusterHealthConfigurationTest.java │ │ └── ClusterHealthModuleTest.java │ │ ├── heartbeat │ │ ├── HeartbeatConfigurationTest.java │ │ └── HeartbeatModuleTest.java │ │ ├── hiccup │ │ ├── HiccupConfigurationTest.java │ │ └── HiccupModuleTest.java │ │ ├── metrics │ │ ├── MetricsConfigurationTest.java │ │ └── MetricsModuleTest.java │ │ ├── requestrate │ │ ├── RequestRateConfigurationTest.java │ │ └── RequestRateModuleTest.java │ │ ├── slowquery │ │ ├── SlowQueryConfigurationTest.java │ │ ├── SlowQueryLogDeciderTest.java │ │ └── SlowQueryModuleTest.java │ │ └── status │ │ ├── StatusConfigurationTest.java │ │ └── StatusModuleTest.java │ └── resources │ ├── invalid-cassandra-diagnostics.yml │ └── valid-cassandra-diagnostics.yml ├── cassandra-diagnostics-driver-connector ├── README.md ├── pom.xml └── src │ ├── integration-test │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ ├── diagnostics │ │ │ └── connector │ │ │ │ └── ITConnector.java │ │ │ └── utils │ │ │ └── EmbeddedCassandraServerHelper.java │ └── resources │ │ ├── cu-cassandra.yaml │ │ └── log4j-embedded-cassandra.properties │ ├── main │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── connector │ │ │ ├── ConnectorImpl.java │ │ │ └── ExecuteStatementWrapper.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.smartcat.cassandra.diagnostics.connector.Connector │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── connector │ └── ExecuteStatementWrapperTest.java ├── cassandra-diagnostics-embedded-cassandra ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── utils │ └── EmbeddedCassandraServerHelper.java ├── cassandra-diagnostics-ft ├── basic2 │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── basic2 │ │ │ └── FTBasic2.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ └── cassandra-env.sh ├── basic3 │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── basic3 │ │ │ └── FTBasic3.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ └── cassandra-env.sh ├── influx │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── influx │ │ │ └── FTInflux.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ └── cassandra-env.sh ├── metrics │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── metrics │ │ │ └── FTMetrics.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ └── cassandra-env.sh ├── pom.xml ├── riemann │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── riemann │ │ │ └── FTRiemann.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ ├── cassandra-env.sh │ │ └── riemann.config ├── telegraf │ ├── pom.xml │ └── src │ │ └── functional-test │ │ ├── java │ │ └── io │ │ │ └── smartcat │ │ │ └── cassandra │ │ │ └── diagnostics │ │ │ └── ft │ │ │ └── telegraf │ │ │ └── FTTelegraf.java │ │ └── resources │ │ ├── cassandra-diagnostics.yml │ │ ├── cassandra-env.sh │ │ └── telegraf.conf └── tracing │ ├── pom.xml │ └── src │ └── functional-test │ ├── java │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ └── ft │ │ └── tracing │ │ └── FTTracing.java │ └── resources │ ├── cassandra-diagnostics.yml │ └── cassandra-env.sh ├── cassandra-diagnostics-reporter-datadog ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ └── reporter │ │ └── DatadogReporter.java │ └── test │ └── java │ └── io │ └── smartcat │ └── diagnostics │ └── reporter │ └── DatadogReporterTest.java ├── cassandra-diagnostics-reporter-influx ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── reporter │ └── InfluxReporter.java ├── cassandra-diagnostics-reporter-kafka ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ └── reporter │ │ └── KafkaReporter.java │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── reporter │ ├── KafkaLocal.java │ ├── KafkaReporterTest.java │ └── ZooKeeperLocal.java ├── cassandra-diagnostics-reporter-prometheus ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ └── reporter │ │ └── PrometheusReporter.java │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── reporter │ └── PrometheusReporterTest.java ├── cassandra-diagnostics-reporter-riemann ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── reporter │ └── RiemannReporter.java ├── cassandra-diagnostics-reporter-telegraf ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── smartcat │ │ └── cassandra │ │ └── diagnostics │ │ └── reporter │ │ ├── TcpClient.java │ │ └── TelegrafReporter.java │ └── test │ └── java │ └── io │ └── smartcat │ └── cassandra │ └── diagnostics │ └── reporter │ └── TelegrafReporterTest.java ├── checkstyle-suppressions.xml ├── checkstyle.properties ├── checkstyle.xml ├── diagrams ├── architecture-diagram.png └── cassandra-diagnostics.png ├── pom.xml ├── release └── smartcat-formatter.xml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Common 2 | *~ 3 | **/temp 4 | 5 | ## Java 6 | **/target 7 | *.settings 8 | *.classpath 9 | *.project 10 | *.java-version 11 | 12 | ## Scala 13 | **/.idea 14 | 15 | ## Intellij 16 | *.iml 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | jdk: 4 | - oraclejdk8 5 | matrix: 6 | include: 7 | - jdk: oraclejdk8 8 | script: mvn verify -Pintegration 9 | after_success: 10 | - mvn clean cobertura:cobertura coveralls:report -Pintegration 11 | notifications: 12 | slack: 13 | secure: Vcrg6S2F40pTLir+D4eegQINQm0UKSRdwU5gzXwzfUX4pGLWPv1NzExZb6S8LXXTNU7j5Udop/XelgBps4iverYeDlEOSe+Abr8i7YTBvz1oyRzVj02HMtiVykJ76NbaVsjpdFXacW24FLlApbBjDvCp1E1xnvdVs4CH8qlY8IQE9vJW0YQNi0umOMVGyrQoIDbKha9NT/d+/HGkxSUB7X6t5G5ZAUoJryJQ9bvJrx9Vx0KQdKjYTCpLKzKbXKEv+hYsuUEozUXKwDZM+Xvuf2j0tSiRUqE1g550eoO/gUjfjza4vMG2LKX7B+IP29VNc8wDW3dt373hnk6qTeaBZOFjnf/XXB3vbQeFydFO2wmtF0XY2R2xqr2or+M71s1BjU+/AbbI5NEm5jBWy6x+P2Mb4NXbJ3wxVBH2vkdKJ2LIZL3xQCa86lLsdbdlItdY38ZSbLRTTlt+fatFErfeqj1LfOQFpwbQSyffB475m5WhOZw+iG8oiyuo7MoGoupmP7eQtJGO60mfEP7aYImmxHrX6wYtkyU55TNfYXx7plfpFVU6JUiDmbwqmWptywnY5YcXeIdvnRSfsl0YwpCbeCl+hi6fOV31vIwMhYTUVjA39SznO5vV34R2ep61pxv51lQyGxA4OyKyBGs1H6gOTSuPkwlr2l7H09FohahsFV4= 14 | -------------------------------------------------------------------------------- /bash-installer/README.md: -------------------------------------------------------------------------------- 1 | # linux-installer 2 | 3 | Self-extracting installer used to install cassandra-diagnostics. 4 | 5 | # Dependencies 6 | 7 | This installer should run on any fairly modern linux distribution. 8 | 9 | Requirements for running installer script: 10 | 11 | * bash 4.x 12 | * GNU Awk 4.x 13 | * getopt, enhanced version 14 | * readline 15 | * wget 16 | * mktemp 17 | 18 | Requirements for building installer script: 19 | 20 | * cat 21 | * tar 22 | * gzip 23 | 24 | # Usage 25 | 26 | First checkout _**bash-installer**_ and put it on Cassandra node where you want to install Cassandra diagnostics. 27 | 28 | Prepare _**cassandra-diagnostics.yml**_ (most basic configuration file can be seen on this [link](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-core/src/main/resources/cassandra-diagnostics-default.yml) and some examples are in [core module readme](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-core/COREMODULES.md). 29 | 30 | Run _**cassandra-diagnostics-installer.sh**_ shell script with following switches: 31 | 32 | * _-c, --cassandra-conf-dir_ - location of Cassandra configuration directory 33 | * _-l, --cassandra-lib-dir_ - location of Cassandra library directory 34 | * _-v, --cassandra-version_ - installed Cassandra version number (e.g 3.0.12, 2.1.4, etc.) 35 | * _-V, --cassandra-diagnostics-version_ - desired version of cassandra-diagnostics 36 | * _-C, --cassandra-diagnostics-conf-file_ - location of cassandra-diagnostics configuration (YAML) file 37 | * _-h, --help_ - show help message 38 | 39 | All parameters except "-h, --help" are mandatory. 40 | 41 | When invoked with all of mandatory parameters, installer will extract itself to _**/tmp/cassandra-diagnostics-installer.XXXXXX**_ (XXXXXX is random alphanumeric string), and execute _**main.sh**_ script, which will carry on the installation. 42 | 43 | Here is example of command _**sudo ./cassandra-diagnostics-installer.sh -c /etc/cassandra/ -l /usr/share/cassandra/lib/ -v 3.0.12 -V 1.4.6 -C ./cassandra-diagnostics.yml**_. 44 | 45 | # Building self-extracting installer script 46 | 47 | Run _**build-installer.sh**_. It will tar and gzip scripts from _**installer**_ directory, and append it at the end of _**installer-runner.sh**_, producing _**cassandra-diagnostics-installer.sh**_ script. 48 | -------------------------------------------------------------------------------- /bash-installer/build-installer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Builds cassandra-diagnostics installer script. 4 | 5 | INSTALLER_SCRIPT_NAME="cassandra-diagnostics-installer.sh" 6 | INSTALLER_RUNNER_NAME="installer-runner.sh" 7 | PAYLOAD_DIRECTORY="installer" 8 | PAYLOAD_ARCHIVE_NAME="installer.tar" 9 | PAYLOAD_COMPRESSED_ARCHIVE_NAME="$PAYLOAD_ARCHIVE_NAME.gz" 10 | 11 | # Checks if binary can be found on $PATH. 12 | # 13 | # Exits: 14 | # - with $MISSING_BINARY_EXIT_CODE, if binary is not on $PATH. 15 | function check_if_binary_is_in_path() { 16 | which "$1" >/dev/null 2>&1 17 | 18 | if [ $? != 0 ]; then 19 | print_error "Cannot find $1 in PATH. Please install it to be able to use installer." 20 | 21 | exit $MISSING_BINARY_EXIT_CODE 22 | fi 23 | } 24 | 25 | function check_prerequisites() { 26 | check_if_binary_is_in_path "tar" 27 | check_if_binary_is_in_path "gzip" 28 | } 29 | 30 | check_prerequisites 31 | 32 | if [ -e "$INSTALLER_SCRIPT_NAME" ]; then 33 | echo "Removing previous installer script." 34 | rm -f "$INSTALLER_SCRIPT_NAME" 35 | fi 36 | 37 | cd "$PAYLOAD_DIRECTORY" 38 | tar cf ../"$PAYLOAD_ARCHIVE_NAME" ./* 39 | cd .. 40 | 41 | if [ ! -e "$PAYLOAD_ARCHIVE_NAME" ]; then 42 | echo "Error: cannot find $PAYLOAD_ARCHIVE_NAME." 43 | exit 1 44 | fi 45 | 46 | gzip "$PAYLOAD_ARCHIVE_NAME" 47 | 48 | if [ ! -e "$PAYLOAD_COMPRESSED_ARCHIVE_NAME" ]; then 49 | echo "Error: cannot find $PAYLOAD_COMPRESSED_ARCHIVE_NAME." 50 | exit 1 51 | fi 52 | 53 | cat "$INSTALLER_RUNNER_NAME" "$PAYLOAD_COMPRESSED_ARCHIVE_NAME" > "$INSTALLER_SCRIPT_NAME" 54 | chmod a+x "$INSTALLER_SCRIPT_NAME" 55 | 56 | echo "Installer script \"$INSTALLER_SCRIPT_NAME\" created." 57 | 58 | echo "Cleaning up..." 59 | rm -f "$PAYLOAD_COMPRESSED_ARCHIVE_NAME" 60 | echo "Done." 61 | -------------------------------------------------------------------------------- /bash-installer/cassandra-diagnostics-installer.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcat-labs/cassandra-diagnostics/1fd7b34e5a6c37e1762914f3b62960ef3ee7458a/bash-installer/cassandra-diagnostics-installer.sh -------------------------------------------------------------------------------- /bash-installer/installer-runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Extracts payload directory and starts installer entry script. 4 | 5 | TMPDIR=$(mktemp -d /tmp/cassandra-diagnostics-installer.XXXXXX) 6 | 7 | INSTALLER_ARCHIVE=`awk '/^__INSTALLER_ARCHIVE__/ {print NR + 1; exit 0; }' $0` 8 | 9 | tail -n+$INSTALLER_ARCHIVE $0 | tar xz -C $TMPDIR > /dev/null 10 | 11 | TMPDIR="$TMPDIR" "$TMPDIR"/main.sh $@ 12 | rm -rf $TMPDIR 13 | 14 | exit 0 15 | 16 | # Installer archive will be appended here as binary. 17 | __INSTALLER_ARCHIVE__ 18 | -------------------------------------------------------------------------------- /bash-installer/installer/cassandra-diagnostics-urls.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Contains public URLs to cassandra-diagnostics libraries. 4 | 5 | DIAGNOSTICS_BASE_URL="https://dl.bintray.com/smartcat-labs/maven/io/smartcat" 6 | 7 | DIAGNOSTICS_CORE_URL="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-core/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-core-$CASSANDRA_DIAGNOSTICS_VERSION.jar" 8 | 9 | DIAGNOSTICS_CONNECTOR_URL="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-connector$DIAGNOSTICS_CONNECTOR_VERSION/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-connector$DIAGNOSTICS_CONNECTOR_VERSION-$CASSANDRA_DIAGNOSTICS_VERSION.jar" 10 | 11 | DIAGNOSTICS_DRIVER_CONNECTOR_URL="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-driver-connector/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-driver-connector-$CASSANDRA_DIAGNOSTICS_VERSION.jar" 12 | 13 | declare -A DIAGNOSTICS_REPORTER_URLS 14 | 15 | DIAGNOSTICS_REPORTER_URLS=( 16 | ["InfluxReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-influx/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-influx-$CASSANDRA_DIAGNOSTICS_VERSION-all.jar" \ 17 | ["RiemannReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-riemann/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-riemann-$CASSANDRA_DIAGNOSTICS_VERSION-all.jar" \ 18 | ["TelegrafReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-telegraf/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-telegraf-$CASSANDRA_DIAGNOSTICS_VERSION-all.jar" \ 19 | ["DatadogReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-datadog/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-datadog-$CASSANDRA_DIAGNOSTICS_VERSION-all.jar" \ 20 | ["PrometheusReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-prometheus/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-prometheus-$CASSANDRA_DIAGNOSTICS_VERSION-all.jar" \ 21 | ["KafkaReporter"]="$DIAGNOSTICS_BASE_URL/cassandra-diagnostics-reporter-kafka/$CASSANDRA_DIAGNOSTICS_VERSION/cassandra-diagnostics-reporter-kafka-$CASSANDRA_DIAGNOSTICS_VERSION.jar" 22 | ) 23 | -------------------------------------------------------------------------------- /bash-installer/installer/cassandra-service-manager.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Contains functions to restart Cassandra service. 4 | 5 | CASSANDRA_SERVICE_SYSTEMD_NAME="cassandra" 6 | CASSANDRA_SERVICE_UPSTART_NAME="cassandra" 7 | 8 | # Restarts Cassandra service using systemd or upstart. 9 | function restart_cassandra_service() { 10 | RESTART_SUCCESSFULL=0 11 | 12 | print_info "Restarting Cassandra service..." 13 | if [ $(has_systemd) ]; then 14 | let STATUS=$(restart_using_systemd); 15 | 16 | if [ $STATUS -eq 0 ]; then RESTART_SUCCESSFULL=1; fi 17 | fi 18 | 19 | if [ $RESTART_SUCCESSFULL -eq 0 ]; then 20 | let STATUS=$(restart_using_upstart) 21 | 22 | if [ $STATUS -eq 0 ]; then RESTART_SUCCESSFULL=1; fi 23 | fi 24 | 25 | if [ $RESTART_SUCCESSFULL -eq 0 ]; then 26 | print_warning "Failed to restart Cassandra service, please do it manually to be able to use cassandra-diagnostics." 27 | 28 | return 29 | fi 30 | 31 | print_info "Restarted." 32 | } 33 | 34 | function has_systemd() { 35 | which systemctl > /dev/null 36 | echo $? 37 | } 38 | 39 | function restart_using_systemd() { 40 | systemd restart "$CASSANDRA_SERVICE_SYSTEMD_NAME" > /dev/null 2>&1 41 | echo $? 42 | } 43 | 44 | function restart_using_upstart() { 45 | service "$CASSANDRA_SERVICE_UPSTART_NAME" restart >/dev/null 2<&1 46 | echo $? 47 | } 48 | -------------------------------------------------------------------------------- /bash-installer/installer/common-functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Contains functions shared between the rest of the installer scripts. 4 | 5 | # Checks if string starts with substring. 6 | # 7 | # Parameters: 8 | # $1 - substring with which string is expected to start. 9 | # $2 - string being checked. 10 | function startswith() { 11 | case $2 in 12 | "$1"*) 13 | true 14 | ;; 15 | *) 16 | false 17 | ;; 18 | esac 19 | } 20 | 21 | # Prints message(s) to stdout, using predefined format. 22 | function print_message() { 23 | echo "["`date --rfc-3339='seconds'`"]" "$@" 24 | } 25 | 26 | function print_info() { 27 | print_message "[INFO]" "$@" 28 | } 29 | 30 | function print_error() { 31 | print_message "[ERROR]" "$@" 32 | } 33 | 34 | function absolute_path_of() { 35 | echo $(readlink -f "$1") 36 | } 37 | 38 | function print_debug() { 39 | print_message "[DEBUG]" "$@" 40 | } 41 | 42 | function print_warning() { 43 | print_message "[WARNING]" "$@" 44 | } 45 | -------------------------------------------------------------------------------- /bash-installer/installer/diagnostic-library-files-manager.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | UNABLE_TO_DOWNLOAD_COMPONENT_EXIT_CODE=40 4 | UNKNOWN_REPORTER_MODULE_EXIT_CODE=41 5 | 6 | function set_wget_options() { 7 | WGET_VERSION=$(get_wget_version) 8 | let WGET_MAJOR=$(echo $WGET_VERSION | cut -d"." -f1) 9 | let WGET_MINOR=$(echo $WGET_VERSION | cut -d"." -f2) 10 | 11 | if [ $WGET_MAJOR -ge 1 ] && [ $WGET_MINOR -ge 18 ]; then 12 | WGET_OPTIONS="--progress=bar --show-progress -q" 13 | fi 14 | } 15 | 16 | # Downloads file to specified directory. 17 | # 18 | # Parameters: 19 | # $1 - URL to desired file. 20 | # $2 - path to output directory. 21 | # 22 | # Exits: 23 | # - with $UNABLE_TO_DOWNLOAD_COMPONENT_EXIT_CODE, when download is unsuccessful. 24 | function download() { 25 | if test ${WGET_OPTIONS+1}; then 26 | wget "$1" --directory-prefix="$2" -c $WGET_OPTIONS 27 | else 28 | print_info "Downloading $1 ..." 29 | wget "$1" --directory-prefix="$2" -c -q 30 | fi 31 | 32 | if [ $? != 0 ]; then 33 | print_error "Unable to download $1. Exiting." 34 | exit $UNABLE_TO_DOWNLOAD_COMPONENT_EXIT_CODE 35 | fi 36 | } 37 | 38 | # Downloads cassandra-diagnostics libraries using URLs defined in cassandra-diagnostics-urls.sh to $CASSANDRA_LIB_DIR. 39 | # 40 | # Uses global variables: 41 | # - $DIAGNOSTICS_CORE_URL 42 | # - $CASSANDRA_LIB_DIR 43 | # - $DIAGNOSTICS_CONNECTOR_URL 44 | # - $DIAGNOSTICS_DRIVER_CONNECTOR_URL 45 | # - $REPORTER_MODULES 46 | # - $DIAGNOSTICS_REPORTER_URLS 47 | # 48 | # Exits: 49 | # - with $UNABLE_TO_DOWNLOAD_COMPONENT_EXIT_CODE, when download error occurs. 50 | # - with $UNKNOWN_REPORTER_MODULE_EXIT_CODE, when unknown reporter module is found in $REPORTER_MODULES. 51 | function download_diagnostics_libraries() { 52 | set_wget_options 53 | 54 | # Download diagnostics core. 55 | download "$DIAGNOSTICS_CORE_URL" "$LIBRARIES_DOWNLOAD_DIR" 56 | 57 | # Download diagnostics connector. 58 | download "$DIAGNOSTICS_CONNECTOR_URL" "$LIBRARIES_DOWNLOAD_DIR" 59 | 60 | # Download diagnostics driver connector. 61 | download "$DIAGNOSTICS_DRIVER_CONNECTOR_URL" "$LIBRARIES_DOWNLOAD_DIR" 62 | 63 | # Download diagnostics reporters. 64 | for reporter in $REPORTER_MODULES; do 65 | local reporter_url="${DIAGNOSTICS_REPORTER_URLS[$reporter]}" 66 | 67 | if [ -z $reporter_url ]; then 68 | print_error "Unknown reporter module \"$reporter\". Exiting" 69 | exit $UNKNOWN_REPORTER_MODULE_EXIT_CODE 70 | fi 71 | 72 | download "$reporter_url" "$LIBRARIES_DOWNLOAD_DIR" 73 | done 74 | } 75 | 76 | # Removes installed cassandra-diagnostics libraries. 77 | function remove_installed_cassandra_diagnostics_libraries() { 78 | verbose_remove "$CASSANDRA_LIB_DIR"/cassandra-diagnostics-core-*.jar 79 | verbose_remove "$CASSANDRA_LIB_DIR"/cassandra-diagnostics-connector*.jar 80 | verbose_remove "$CASSANDRA_LIB_DIR"/cassandra-diagnostics-driver-connector-*.jar 81 | verbose_remove "$CASSANDRA_LIB_DIR"/cassandra-diagnostics-reporter-*.jar 82 | } 83 | 84 | # Removes files or directories, if they exist, and prints pre-formatted message. 85 | # 86 | # Parameters: 87 | # $@ - file or directory paths to remove. 88 | function verbose_remove() { 89 | for node in $@; do 90 | if [ -e "$node" ]; then 91 | print_info $(rm -rvf "$node") 92 | fi 93 | done 94 | } 95 | 96 | # Moves downloaded cassandra-diagnostics libraries from temporary directory to Cassandra libraries derectory. 97 | # 98 | # Uses global variables: 99 | # - $CASSANDRA_LIB_DIR 100 | # - $LIBRARIES_DOWNLOAD_DIR 101 | function move_diagnostics_libraries_to_lib_dir() { 102 | mv -f "$LIBRARIES_DOWNLOAD_DIR"/* "$CASSANDRA_LIB_DIR" 103 | } 104 | -------------------------------------------------------------------------------- /bash-installer/installer/find-reporter-modules.awk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | 3 | # Finds reporter classes in cassandra-diagnostics YAML configuration file. 4 | 5 | BEGIN { 6 | reporters_block_indentation = 0 7 | in_reporters_block = 0 8 | reporters_block_items_indentation = 0 9 | should_detect_reporters_block_items_indentation = 0 10 | reporters = "" 11 | ignore_reporter = "LogReporter" 12 | } 13 | 14 | { 15 | gsub(/\t/, " ") # Replace tabs with two spaces. 16 | current_line = $0 17 | 18 | if(is_commented_out(current_line)) { 19 | next 20 | } 21 | 22 | if(reporters_block_starts_on(current_line)) { 23 | in_reporters_block = 1 24 | should_detect_reporters_block_items_indentation = 1 25 | reporters_block_indentation = indentation_level_of(current_line) 26 | } else { 27 | if(should_detect_reporters_block_items_indentation) { 28 | reporters_block_items_indentation = indentation_level_of(current_line) 29 | should_detect_reporters_block_items_indentation = 0 30 | } 31 | 32 | if(in_reporters_block) { 33 | if(indentation_level_of(current_line) <= reporters_block_indentation) { 34 | in_reporters_block = 0 35 | reporters_block_items_indentation = 0 36 | } else if(indentation_level_of(current_line) > reporters_block_items_indentation) { 37 | next 38 | } else if(starts_with_dash(current_line)) { 39 | reporters = find_reporters_using(reporters, current_line) 40 | } else { 41 | next 42 | } 43 | } 44 | } 45 | } 46 | 47 | END { 48 | print reporters 49 | } 50 | 51 | 52 | # Functions 53 | function indentation_level_of(line) { 54 | whitespace_count = 0 55 | 56 | for(i = 0; i < length(line); i++) { 57 | if(char_at(line, i + 1) == " ") { 58 | whitespace_count++ 59 | } else { 60 | break 61 | } 62 | } 63 | 64 | return whitespace_count 65 | } 66 | 67 | function is_commented_out(line) { 68 | return match(current_line, "\\W#.") 69 | } 70 | 71 | function starts_with_dash(line) { 72 | return match(current_line, "\\W-.") 73 | } 74 | 75 | function char_at(string, i) { 76 | return substr(string,i,1); 77 | } 78 | 79 | function reporters_block_starts_on(line) { 80 | return index(line, "reporters:") > 0 81 | } 82 | 83 | function find_reporters_using(reporters_list, reporter_configuration_line) { 84 | tokens_count = split(reporter_configuration_line, tokens, ".") 85 | reporter = tokens[tokens_count] 86 | 87 | if(reporter == ignore_reporter) { 88 | return reporters_list 89 | } 90 | 91 | if(index(reporters_list, reporter) == 0) { 92 | return reporters_list reporter " " 93 | } 94 | 95 | return reporters_list 96 | } 97 | -------------------------------------------------------------------------------- /bash-installer/installer/input-arguments-validator.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Contains functions for validating global variables created from user input. 4 | 5 | UNSUPPORTED_CASSANDRA_VERSION_EXIT_CODE=50 6 | NON_EXISTING_FILE_EXIT_CODE=51 7 | NON_EXISTING_DIRECTORY_EXIT_CODE=52 8 | NON_WRITEABLE_NODE_EXIT_CODE=53 9 | 10 | # Validates mandatory global variables created from user input. 11 | # 12 | # Uses global variables: 13 | # - $CASSANDRA_DIAGNOSTICS_CONF_FILE 14 | # - $CASSANDRA_ENV_SCRIPT 15 | # - $CASSANDRA_CONF_DIR 16 | # - $CASSANDRA_LIB_DIR 17 | # 18 | # Exits: 19 | # - With any of defined exit codes, depending on invalid input. 20 | # Check used functions for more details. 21 | function validate_input_arguments() { 22 | check_supported_cassandra_version 23 | check_if_file_exists "$CASSANDRA_DIAGNOSTICS_CONF_FILE" 24 | check_if_file_exists "$CASSANDRA_ENV_SCRIPT" 25 | check_if_directory_exists "$CASSANDRA_CONF_DIR" 26 | check_if_directory_exists "$CASSANDRA_LIB_DIR" 27 | check_if_node_is_writable "$CASSANDRA_LIB_DIR" 28 | check_if_node_is_writable "$CASSANDRA_ENV_SCRIPT" 29 | } 30 | 31 | # Checks if provided Cassandra version is supported. 32 | # 33 | # Exits: 34 | # - with $UNSUPPORTED_CASSANDRA_VERSION_EXIT_CODE, when Cassandra version is unsupported. 35 | function check_supported_cassandra_version() { 36 | if ! $(startswith "2.1" "$CASSANDRA_VERSION") && ! $(startswith "3.0" "$CASSANDRA_VERSION") ; then 37 | print_error "Unsupported Cassandra version." 38 | print_error "Supported versions are 2.1.x or 3.0.x." 39 | 40 | exit $UNSUPPORTED_CASSANDRA_VERSION_EXIT_CODE 41 | fi 42 | } 43 | 44 | # Checks if file exists. 45 | # 46 | # Parameters: 47 | # $1 - path to file to be checked. 48 | # 49 | # Exits: 50 | # - with NON_EXISTING_FILE_EXIT_CODE, if file does not exist. 51 | function check_if_file_exists() { 52 | if [ ! -f "$1" ]; then 53 | print_error "File $1 does not exist." 54 | 55 | exit $NON_EXISTING_FILE_EXIT_CODE 56 | fi 57 | } 58 | 59 | # Checks if directory exists. 60 | # 61 | # Parameters: 62 | # $1 - path to directory to be checked. 63 | # 64 | # Exits: 65 | # - with NON_EXISTING_DIRECTORY_EXIT_CODE, if directory does not exist. 66 | function check_if_directory_exists() { 67 | if [ ! -d "$1" ]; then 68 | print_error "Directory $1 does not exist." 69 | 70 | exit $NON_EXISTING_DIRECTORY_EXIT_CODE 71 | fi 72 | } 73 | 74 | # Checks if filesystem node is writable. 75 | # 76 | # Parameters: 77 | # $1 - path to filesystem node to be checked. 78 | # 79 | # Exits: 80 | # - with NON_WRITEABLE_NODE_EXIT_CODE, if directory is not writable. 81 | function check_if_node_is_writable() { 82 | if [ ! -w "$1" ]; then 83 | print_error "$1 is not writeable. Do you have correct permissions?" 84 | 85 | exit $NON_WRITEABLE_NODE_EXIT_CODE 86 | fi 87 | } 88 | -------------------------------------------------------------------------------- /bash-installer/installer/installer-messages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | -------------------------------------------------------------------------------- /bash-installer/installer/main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Installer main entry point. 4 | 5 | PAYLOAD_DIRECTORY=$(dirname "$0") 6 | 7 | . "$PAYLOAD_DIRECTORY"/common-functions.sh 8 | . "$PAYLOAD_DIRECTORY"/prerequisites-checker.sh 9 | 10 | check_prerequisites 11 | 12 | print_info "Staring installation of cassandra-diagnostics." 13 | 14 | . "$PAYLOAD_DIRECTORY"/non-interactive-installer.sh 15 | 16 | print_info "Installation finished." 17 | -------------------------------------------------------------------------------- /bash-installer/installer/non-interactive-installer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Non-interactive cassandra-diagnostics installer. 4 | 5 | FILE_COPYING_ERROR_EXIT_CODE=1 6 | 7 | function print_usage() { 8 | echo "Usage: $1 [OPTION]..." 9 | echo "Automates installation of cassandra-diagnostics into existing Cassandra" 10 | echo "installation." 11 | echo "" 12 | echo "Mandatory arguments:" 13 | echo "-c, --cassandra-conf-dir location of Cassandra configuration" 14 | echo " directory" 15 | echo "-l, --cassandra-lib-dir location of Cassandra library directory" 16 | echo "-v, --cassandra-version installed Cassandra version number " 17 | echo " (e.g 3.0.12, 2.1.4, etc.)" 18 | echo "-V, --cassandra-diagnostics-version desired version of cassandra-diagnostics" 19 | echo "-C, --cassandra-diagnostics-conf-file location of cassandra-diagnostics" 20 | echo " configuration (YAML) file" 21 | echo "-h, --help show this message" 22 | echo "" 23 | echo "cassandra-diagnostics source code and documentation URL:" 24 | echo "" 25 | } 26 | 27 | # Show usage if there are no input parameters. 28 | if [ $# == 0 ]; then 29 | print_usage "$INSTALLER_SCRIPT_NAME" 30 | exit 0 31 | fi 32 | 33 | # Include functions for restarting Cassandra service. 34 | . "$PAYLOAD_DIRECTORY"/cassandra-service-manager.sh 35 | 36 | # Include arguments parser functions. 37 | . "$PAYLOAD_DIRECTORY"/input-arguments-parser.sh 38 | 39 | # Parse arguments and create global variables used by installer. 40 | parse_input_arguments "$@" 41 | 42 | # Show usage if help input parameter is passed. 43 | if test ${INSTALLER_SHOW_USAGE+1}; then 44 | print_usage "$INSTALLER_SCRIPT_NAME" 45 | exit 0 46 | fi 47 | 48 | # Include arguments validation functions. 49 | . "$PAYLOAD_DIRECTORY"/input-arguments-validator.sh 50 | 51 | # Validate input arguments. 52 | validate_input_arguments 53 | 54 | # Include scripts that depend on properly set global variables. 55 | . "$PAYLOAD_DIRECTORY"/diagnostic-library-files-manager.sh 56 | . "$PAYLOAD_DIRECTORY"/cassandra-diagnostics-urls.sh 57 | . "$PAYLOAD_DIRECTORY"/cassandra-diagnostics-jvm-opts-manager.sh 58 | 59 | REPORTER_MODULES=$(find_reporters_in "$CASSANDRA_DIAGNOSTICS_CONF_FILE") 60 | 61 | print_info "Downloading cassandra-diagnostics libraries..." 62 | download_diagnostics_libraries 63 | print_info "Done downloading." 64 | 65 | print_info "Removing existing cassandra-diagnostics libraries." 66 | remove_installed_cassandra_diagnostics_libraries 67 | 68 | print_info "Moving cassandra-diagnostics libraries to $CASSANDRA_LIB_DIR..." 69 | move_diagnostics_libraries_to_lib_dir 70 | 71 | print_info "Copying cassandra-diagnostics configuration file to $CASSANDRA_CONF_DIR" 72 | copy_diagnostics_configuration_to_conf_dir 73 | 74 | find_and_remove_diagnostics_configuration_from_cassandra_env 75 | 76 | print_info "Appending cassandra-diagnostics configuration to $CASSANDRA_ENV_SCRIPT_NAME." 77 | append_diagnostics_configuration_to_env_script 78 | 79 | restart_cassandra_service 80 | -------------------------------------------------------------------------------- /bash-installer/installer/prerequisites-checker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Contains functions for checking prerequisites for running installer. 4 | 5 | UNSUPPORTED_BASH_VERSION_EXIT_CODE=20 6 | UNSUPPORTED_GETOPT_VERSION_EXIT_CODE=21 7 | UNSUPPORTED_AWK_VERSION_EXIT_CODE=22 8 | MISSING_BINARY_EXIT_CODE=23 9 | 10 | # Checks if bash version is 4 or newer. 11 | # 12 | # Exits: 13 | # - with $UNSUPPORTED_BASH_VERSION_EXIT_CODE, if bash version is less than 4. 14 | function check_bash_version() { 15 | local bash_version=$(bash --version | head -1 | cut -d " " -f 4) 16 | 17 | if ! startswith "4." "$bash_version"; then 18 | print_error "Unsupported bash version: $bash_version. Version 4 or newer is required." 19 | 20 | exit $UNSUPPORTED_BASH_VERSION_EXIT_CODE 21 | fi 22 | } 23 | 24 | # Checks if getopt is an enhanced version. 25 | # 26 | # Exits: 27 | # - with $UNSUPPORTED_GETOPT_VERSION_EXIT_CODE, if getopt is not an enhanced version. 28 | function check_getopt_version() { 29 | getopt --test >/dev/null 2>&1 30 | 31 | if [ $? != 4 ]; then 32 | print_error "Unsupported getopt version. Enhanced version is required." 33 | fi 34 | } 35 | 36 | # Checks if Awk installation is GNU Awk 4 or newer. 37 | # 38 | # Exits: 39 | # - with $UNSUPPORTED_AWK_VERSION_EXIT_CODE, if Awk version is not supported. 40 | function check_awk_version() { 41 | local awk_version=$(awk --version | head -1 | cut -d "," -f 1) 42 | 43 | if ! startswith "GNU Awk 4." "$awk_version"; then 44 | print_error "Unsupported Awk version: $bash_version. GNU Awk version 4 or newer is required." 45 | 46 | exit $UNSUPPORTED_AWK_VERSION_EXIT_CODE 47 | fi 48 | } 49 | 50 | # Checks if binary can be found on $PATH. 51 | # 52 | # Exits: 53 | # - with $MISSING_BINARY_EXIT_CODE, if binary is not on $PATH. 54 | function check_if_binary_is_in_path() { 55 | which "$1" >/dev/null 2>&1 56 | 57 | if [ $? != 0 ]; then 58 | print_error "Cannot find $1 in PATH. Please install it to be able to use installer." 59 | 60 | exit $MISSING_BINARY_EXIT_CODE 61 | fi 62 | } 63 | 64 | function check_prerequisites() { 65 | check_bash_version 66 | check_getopt_version 67 | check_awk_version 68 | check_if_binary_is_in_path "wget" 69 | check_if_binary_is_in_path "readlink" 70 | } 71 | 72 | function get_wget_version() { 73 | echo $(wget --version | head -1 | cut -d" " -f 3) 74 | } 75 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.smartcat 6 | cassandra-diagnostics 7 | 1.4.11-SNAPSHOT 8 | 9 | cassandra-diagnostics-commons 10 | jar 11 | 12 | 13 | 14 | org.slf4j 15 | slf4j-api 16 | 17 | 18 | junit 19 | junit 20 | test 21 | 22 | 23 | org.mockito 24 | mockito-all 25 | test 26 | 27 | 28 | org.assertj 29 | assertj-core 30 | test 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.codehaus.mojo 38 | templating-maven-plugin 39 | 1.0.0 40 | 41 | 42 | filter-src 43 | 44 | filter-sources 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java-templates/io/smartcat/cassandra/diagnostics/ProjectInfo.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | /** 4 | * Helper class that contains project-wide information. 5 | */ 6 | public final class ProjectInfo { 7 | 8 | private ProjectInfo() { 9 | } 10 | 11 | /** 12 | * Project version injected during maven build. 13 | */ 14 | public static final String VERSION = "${project.parent.version}"; 15 | } 16 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/GlobalConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | import io.smartcat.cassandra.diagnostics.utils.Utils; 4 | 5 | /** 6 | * Global configuration for Cassandra diagnostics. 7 | */ 8 | public class GlobalConfiguration { 9 | 10 | /** 11 | * System wide hostname. Set to override {@code InetAddress} querying. 12 | */ 13 | public String hostname = Utils.resolveHostname(); 14 | 15 | /** 16 | * System wide name. Set to differentiate between systems under observation. 17 | */ 18 | public String systemName = "cassandra-cluster"; 19 | 20 | /** 21 | * Enables diagnostics HTTP API. 22 | */ 23 | public Boolean httpApiEnabled = true; 24 | 25 | /** 26 | * Host name for binding HTTP API listening socket. 27 | */ 28 | public String httpApiHost = "127.0.0.1"; 29 | 30 | /** 31 | * TCP port for diagnostics HTTP API. 32 | */ 33 | public Integer httpApiPort = 8998; 34 | 35 | /** 36 | * Enables HTTP API key-based authentication. 37 | */ 38 | public Boolean httpApiAuthEnabled = false; 39 | 40 | /** 41 | * HTTP API access key. 42 | */ 43 | public String httpApiKey = "diagnostics-api-key"; 44 | 45 | /** 46 | * Returns the default configuration. 47 | * 48 | * @return default configuration 49 | */ 50 | public static GlobalConfiguration getDefault() { 51 | return new GlobalConfiguration(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/connector/AbstractEventProcessor.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import java.util.concurrent.LinkedBlockingQueue; 4 | import java.util.concurrent.ThreadFactory; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * An abstract implementation of diagnostics event processor. It implements event queuing, 14 | * asynchronous execution and throttling. 15 | */ 16 | public abstract class AbstractEventProcessor { 17 | private static final Logger logger = LoggerFactory.getLogger(AbstractEventProcessor.class); 18 | 19 | private static final AtomicLong THREAD_COUNT = new AtomicLong(0); 20 | 21 | /** 22 | * Executor service used for executing query reports. 23 | */ 24 | private ThreadPoolExecutor executor; 25 | 26 | /** 27 | * Query reporter. 28 | */ 29 | protected QueryReporter queryReporter; 30 | 31 | /** 32 | * Connector implementation specific configuration. 33 | */ 34 | protected ConnectorConfiguration configuration; 35 | 36 | private static boolean queueOverflow = false; 37 | 38 | /** 39 | * Constructor. 40 | * 41 | * @param queryReporter QueryReporter used to report queries 42 | * @param configuration Connector configuration 43 | */ 44 | public AbstractEventProcessor(QueryReporter queryReporter, ConnectorConfiguration configuration) { 45 | this.queryReporter = queryReporter; 46 | this.configuration = configuration; 47 | executor = new ThreadPoolExecutor(configuration.numWorkerThreads, 48 | configuration.numWorkerThreads, 49 | 100L, 50 | TimeUnit.MILLISECONDS, 51 | new LinkedBlockingQueue(), 52 | new ThreadFactory() { 53 | @Override 54 | public Thread newThread(Runnable runnable) { 55 | Thread thread = new Thread(runnable); 56 | thread.setName("cassandra-diagnostics-connector-" + THREAD_COUNT.getAndIncrement()); 57 | thread.setDaemon(true); 58 | thread.setPriority(Thread.MIN_PRIORITY); 59 | return thread; 60 | } 61 | }); 62 | } 63 | 64 | /** 65 | * Submits a query reports asynchronously. 66 | * 67 | * @param reportAction action to be executed 68 | */ 69 | protected void report(final Runnable reportAction) { 70 | int numQueuedEvents = executor.getQueue().size(); 71 | 72 | if (!queueOverflow) { 73 | executor.submit(reportAction); 74 | if (numQueuedEvents > configuration.queuedEventsOverflowThreshold) { 75 | queueOverflow = true; 76 | logger.warn("Event queue overflown. Until relaxed, further events will be dropped."); 77 | } 78 | } else { 79 | if (numQueuedEvents <= configuration.queuedEventsRelaxThreshold) { 80 | queueOverflow = false; 81 | logger.info("Event queue relaxed. Further events will be accepted and processed."); 82 | } else { 83 | logger.trace("Event queue overflown. Event is dropped."); 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/connector/Connector.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 6 | import io.smartcat.cassandra.diagnostics.info.InfoProvider; 7 | 8 | /** 9 | * Cassandra Diagnostics Connector. 10 | */ 11 | public interface Connector { 12 | /** 13 | * Performs Cassandra classes instrumentation in order to inject Cassandra Diagnostics interceptors. 14 | * 15 | * @param inst instrumentation reference 16 | * @param reporter query reporter 17 | * @param configuration connector specific configuration 18 | * @param globalConfiguration global configuration general for diagnostics 19 | */ 20 | void init(Instrumentation inst, QueryReporter reporter, ConnectorConfiguration configuration, 21 | GlobalConfiguration globalConfiguration); 22 | 23 | /** 24 | * Blocks the calling thread until the connector's target (e.g. Cassand node) 25 | * is fully setup so the rest of Cassandra Diagnostics initialization can be 26 | * safely completed without colliding with the target code. 27 | */ 28 | void waitForSetupCompleted(); 29 | 30 | /** 31 | * Get an info provider instance. 32 | * 33 | * @return info provider instance 34 | */ 35 | InfoProvider getInfoProvider(); 36 | } 37 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/connector/ConnectorConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | /** 4 | * Connector implementation related configuration. 5 | */ 6 | public class ConnectorConfiguration { 7 | 8 | /** 9 | * The number of worker threads that asynchronously process 10 | * diagnostics events. 11 | */ 12 | public int numWorkerThreads = 2; 13 | 14 | /** 15 | * Configured threshold for queue size, above this threshold all events will be 16 | * dropped until the number of queued events is dropped to 17 | * queuedEventsRelaxThreshold. 18 | */ 19 | public int queuedEventsOverflowThreshold = 1000; 20 | 21 | /** 22 | * Lower threshold bound for event queue size. After the queue was previously 23 | * in overflow state, new events will be queued only when the number of queued 24 | * events drop below this value. 25 | */ 26 | public int queuedEventsRelaxThreshold = 700; 27 | 28 | /** 29 | * Whether to enable tracing or not. It is useful for various modules when debugging. 30 | */ 31 | public boolean enableTracing = false; 32 | 33 | /** 34 | * Returns the default configuration. 35 | * @return default configuration 36 | */ 37 | public static ConnectorConfiguration getDefault() { 38 | return new ConnectorConfiguration(); 39 | } 40 | 41 | /** 42 | * Node JMX host. 43 | */ 44 | public String jmxHost = "127.0.0.1"; 45 | 46 | /** 47 | * Node JMX port. 48 | */ 49 | public Integer jmxPort = 7199; 50 | 51 | /** 52 | * Node JMX authentication enabled. 53 | */ 54 | public Boolean jmxAuthEnabled = false; 55 | 56 | /** 57 | * Node JMX authentication username. 58 | */ 59 | public String jmxUsername = null; 60 | 61 | /** 62 | * Node JMX authentication password. 63 | */ 64 | public String jmxPassword = null; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/connector/QueryReporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import io.smartcat.cassandra.diagnostics.Query; 4 | 5 | /** 6 | * Interface used by {@link Connector} implementation to report an intercepted query. 7 | */ 8 | public interface QueryReporter { 9 | /** 10 | * Reports an intercepted query. 11 | * 12 | * @param query intercepted query 13 | */ 14 | void report(Query query); 15 | } 16 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/info/CompactionInfo.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.info; 2 | 3 | /** 4 | * Compaction info class. 5 | */ 6 | public class CompactionInfo { 7 | 8 | /** 9 | * Total compaction size in {@code unit} units. 10 | */ 11 | public final long total; 12 | 13 | /** 14 | * Compaction completed size in {@code unit} units. 15 | */ 16 | public final long completed; 17 | 18 | /** 19 | * Compaction completed percentage. 20 | */ 21 | public final double completedPercentage; 22 | 23 | /** 24 | * Unit of compaction size. 25 | */ 26 | public final String unit; 27 | 28 | /** 29 | * Compaction type. 30 | */ 31 | public final String taskType; 32 | 33 | /** 34 | * Compaction keyspace. 35 | */ 36 | public final String keyspace; 37 | 38 | /** 39 | * Compaction table. 40 | */ 41 | public final String columnFamily; 42 | 43 | /** 44 | * Compaction id. 45 | */ 46 | public final String compactionId; 47 | 48 | /** 49 | * Compaction class constructor. 50 | * 51 | * @param total total compaction size 52 | * @param completed completed compacted size 53 | * @param unit compaction size unit 54 | * @param taskType compaction type 55 | * @param keyspace compaction keyspace 56 | * @param columnFamily compaction column family 57 | * @param id compaction id 58 | */ 59 | public CompactionInfo(long total, long completed, String unit, String taskType, String keyspace, 60 | String columnFamily, String id) { 61 | this.total = total; 62 | this.completed = completed; 63 | this.completedPercentage = total == 0 ? 0 : (double) completed / total * 100; 64 | this.unit = unit; 65 | this.taskType = taskType; 66 | this.keyspace = keyspace; 67 | this.columnFamily = columnFamily; 68 | this.compactionId = id; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/info/CompactionSettingsInfo.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.info; 2 | 3 | /** 4 | * Info about all compactions on node. 5 | */ 6 | public class CompactionSettingsInfo { 7 | 8 | /** 9 | * Compaction throughput. 10 | */ 11 | public final int compactionThroughput; 12 | 13 | /** 14 | * Core compactor threads. 15 | */ 16 | public final int coreCompactorThreads; 17 | 18 | /** 19 | * Maximum compactor threads. 20 | */ 21 | public final int maximumCompactorThreads; 22 | 23 | /** 24 | * Core validator threads. 25 | */ 26 | public final int coreValidatorThreads; 27 | 28 | /** 29 | * Maximum validator threads. 30 | */ 31 | public final int maximumValidatorThreads; 32 | 33 | /** 34 | * Compaction class constructor. 35 | * 36 | * @param compactionThroughput compaction throughput 37 | * @param coreCompactorThreads core compactor threads 38 | * @param maximumCompactorThreads maximum compactor threads 39 | * @param coreValidatorThreads core validator threads 40 | * @param maximumValidatorThreads maximum validator threads 41 | */ 42 | public CompactionSettingsInfo(int compactionThroughput, int coreCompactorThreads, int maximumCompactorThreads, 43 | int coreValidatorThreads, int maximumValidatorThreads) { 44 | this.compactionThroughput = compactionThroughput; 45 | this.coreCompactorThreads = coreCompactorThreads; 46 | this.maximumCompactorThreads = maximumCompactorThreads; 47 | this.coreValidatorThreads = coreValidatorThreads; 48 | this.maximumValidatorThreads = maximumValidatorThreads; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/info/InfoProvider.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.info; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Info provider interface. 7 | */ 8 | public interface InfoProvider { 9 | 10 | /** 11 | * Gets the list of all active compactions. 12 | * 13 | * @return compaction info list 14 | */ 15 | List getCompactions(); 16 | 17 | /** 18 | * Get the status of all thread pools. 19 | * 20 | * @return thread pools info list 21 | */ 22 | List getTPStats(); 23 | 24 | /** 25 | * Get the number of repair sessions. 26 | * 27 | * @return repair sessions 28 | */ 29 | long getRepairSessions(); 30 | 31 | /** 32 | * Get compaction settings info for specific node. 33 | * 34 | * @return compaction settings info 35 | */ 36 | CompactionSettingsInfo getCompactionSettingsInfo(); 37 | 38 | /** 39 | * Get the list of unreachable nodes. 40 | * 41 | * @return unreachable nodes list 42 | */ 43 | List getUnreachableNodes(); 44 | 45 | /** 46 | * Get the information about node such as which protocols are active and uptime. 47 | * 48 | * @return NodeInfo for node 49 | */ 50 | NodeInfo getNodeInfo(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/info/NodeInfo.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.info; 2 | 3 | /** 4 | * Wrapper around nodetool info command output. Encapsulates node information, such as transport information and uptime. 5 | */ 6 | public class NodeInfo { 7 | 8 | /** 9 | * Information about status of the gossip (active or not). 10 | */ 11 | public final boolean gossipActive; 12 | 13 | /** 14 | * Information about status of the thrift (active or not). 15 | */ 16 | public final boolean thriftActive; 17 | 18 | /** 19 | * Information about status of the native transport (active or not). 20 | */ 21 | public final boolean nativeTransportActive; 22 | 23 | /** 24 | * Node uptime in seconds. 25 | */ 26 | public final long uptimeInSeconds; 27 | 28 | /** 29 | * Count of uncaught exceptions. 30 | */ 31 | public final int exceptionCount; 32 | 33 | /** 34 | * NodeInfo class constructor. 35 | * 36 | * @param gossipActive info if gossip is active 37 | * @param thriftActive info if thrift is active 38 | * @param nativeTransportActive info if native transport is active 39 | * @param uptimeInSeconds uptime in seconds 40 | * @param exceptionCount count of uncaught exceptions 41 | */ 42 | public NodeInfo(boolean gossipActive, boolean thriftActive, boolean nativeTransportActive, long uptimeInSeconds, 43 | int exceptionCount) { 44 | super(); 45 | this.gossipActive = gossipActive; 46 | this.thriftActive = thriftActive; 47 | this.nativeTransportActive = nativeTransportActive; 48 | this.uptimeInSeconds = uptimeInSeconds; 49 | this.exceptionCount = exceptionCount; 50 | } 51 | 52 | /** 53 | * Return info about Gossip protocol. 54 | * 55 | * @return 1 in case gossip is active, 0 otherwise. 56 | */ 57 | public int isGossipActive() { 58 | return gossipActive ? 1 : 0; 59 | } 60 | 61 | /** 62 | * Return info about Thrift protocol. 63 | * 64 | * @return 1 in case thrift is active, 0 otherwise. 65 | */ 66 | public int isThriftActive() { 67 | return thriftActive ? 1 : 0; 68 | } 69 | 70 | /** 71 | * Return info about native transport protocol. 72 | * 73 | * @return 1 in case native transport is active, 0 otherwise. 74 | */ 75 | public int isNativeTransportActive() { 76 | return nativeTransportActive ? 1 : 0; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/info/TPStatsInfo.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.info; 2 | 3 | /** 4 | * Thread pool stats info class. 5 | */ 6 | public class TPStatsInfo { 7 | 8 | /** 9 | * Thread pool name. 10 | */ 11 | public final String threadPool; 12 | 13 | /** 14 | * Thread pool active tasks. 15 | */ 16 | public final long activeTasks; 17 | 18 | /** 19 | * Thread pool pending tasks. 20 | */ 21 | public final long pendingTasks; 22 | 23 | /** 24 | * Thread pool completed tasks. 25 | */ 26 | public final long completedTasks; 27 | 28 | /** 29 | * Thread pool blocked tasks. 30 | */ 31 | public final long currentlyBlockedTasks; 32 | 33 | /** 34 | * Thread pool all time blocked tasks. 35 | */ 36 | public final long totalBlockedTasks; 37 | 38 | /** 39 | * Thread pool stats info. 40 | * 41 | * @param threadPool thread pool name 42 | * @param activeTasks active tasks 43 | * @param pendingTasks pending tasks 44 | * @param completedTasks completed tasks 45 | * @param currentlyBlockedTasks currently blocked tasks 46 | * @param totalBlockedTasks total blocked tasks 47 | */ 48 | public TPStatsInfo(String threadPool, long activeTasks, long pendingTasks, long completedTasks, 49 | long currentlyBlockedTasks, long totalBlockedTasks) { 50 | this.threadPool = threadPool; 51 | this.activeTasks = activeTasks; 52 | this.pendingTasks = pendingTasks; 53 | this.completedTasks = completedTasks; 54 | this.currentlyBlockedTasks = currentlyBlockedTasks; 55 | this.totalBlockedTasks = totalBlockedTasks; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/module/AtomicCounter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** 6 | * Atomic counter implementation. 7 | */ 8 | public class AtomicCounter { 9 | 10 | private AtomicLong counter = new AtomicLong(); 11 | 12 | /** 13 | * Increment value by {@code 1}. 14 | */ 15 | public void increment() { 16 | counter.incrementAndGet(); 17 | } 18 | 19 | /** 20 | * Decrement value by {@code 1}. 21 | */ 22 | public void decrement() { 23 | counter.decrementAndGet(); 24 | } 25 | 26 | /** 27 | * Get sum then reset counter to {@code 0}. 28 | * 29 | * @return Returns sum. 30 | */ 31 | public long sumThenReset() { 32 | return counter.getAndSet(0); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/module/Module.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | import java.util.List; 4 | 5 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 6 | import io.smartcat.cassandra.diagnostics.Measurement; 7 | import io.smartcat.cassandra.diagnostics.Query; 8 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 9 | 10 | /** 11 | * Module abstraction that forces a constructor signature. All valid modules should extend this class. 12 | */ 13 | public abstract class Module { 14 | 15 | /** 16 | * Module configuration. 17 | */ 18 | protected final ModuleConfiguration configuration; 19 | 20 | /** 21 | * Reporter list defined in configuration. 22 | */ 23 | protected final List reporters; 24 | 25 | /** 26 | * Reporter list defined in configuration. 27 | */ 28 | protected final GlobalConfiguration globalConfiguration; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param configuration Module configuration 34 | * @param reporters Reporter list 35 | * @param globalConfiguration Global diagnostics configuration 36 | */ 37 | public Module(ModuleConfiguration configuration, List reporters, 38 | final GlobalConfiguration globalConfiguration) { 39 | this.configuration = configuration; 40 | this.reporters = reporters; 41 | this.globalConfiguration = globalConfiguration; 42 | } 43 | 44 | /** 45 | * Process an intercepted query. 46 | * 47 | * @param query Query object 48 | */ 49 | public void process(Query query) { 50 | 51 | } 52 | 53 | /** 54 | * Report measurement on all configured reporters. 55 | * 56 | * @param measurement Measurement for reporting 57 | */ 58 | protected void report(Measurement measurement) { 59 | for (Reporter reporter : reporters) { 60 | reporter.report(measurement); 61 | } 62 | } 63 | 64 | /** 65 | * Used to stop module with long running tasks. 66 | */ 67 | public void stop() { 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/module/ModuleConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * This class represents the Module configuration. 10 | */ 11 | public class ModuleConfiguration { 12 | 13 | /** 14 | * A fully qualified Java class name of module implementation. 15 | */ 16 | public String module; 17 | 18 | /** 19 | * A measurement name which defines how reporters report it. 20 | */ 21 | public String measurement; 22 | 23 | /** 24 | * List of module specific options. 25 | */ 26 | public Map options = new HashMap<>(); 27 | 28 | /** 29 | * List of reporters for this module defined in the configuration. 30 | */ 31 | public List reporters = new ArrayList<>(); 32 | 33 | /** 34 | * Get measurement name or default provided value. 35 | * 36 | * @param defaultMeasurement Default measurement name value 37 | * @return measurement name value 38 | */ 39 | public String getMeasurementOrDefault(String defaultMeasurement) { 40 | return measurement == null || measurement.isEmpty() ? defaultMeasurement : measurement; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | StringBuilder sb = new StringBuilder(); 46 | sb.append(", module: \"").append(module).append("\", measurement: ").append(measurement) 47 | .append(", moduleOptions: ").append(options).append(", moduleReporters: ").append(reporters) 48 | .append(" }"); 49 | return sb.toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/reporter/Reporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 4 | import io.smartcat.cassandra.diagnostics.Measurement; 5 | 6 | /** 7 | * Reporter abstraction that forces a constructor signature. All valid reporters should extend this class. 8 | */ 9 | public abstract class Reporter { 10 | 11 | /** 12 | * Reporter configuration. 13 | */ 14 | protected ReporterConfiguration configuration; 15 | 16 | /** 17 | * Diagnostics global configuration. 18 | */ 19 | protected GlobalConfiguration globalConfiguration; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param configuration Reporter configuration 25 | * @param globalConfiguration Diagnostics configuration 26 | */ 27 | public Reporter(ReporterConfiguration configuration, GlobalConfiguration globalConfiguration) { 28 | this.configuration = configuration; 29 | this.globalConfiguration = globalConfiguration; 30 | } 31 | 32 | /** 33 | * Reports an intercepted query. 34 | * 35 | * @param measurement processed information about intercepted query 36 | */ 37 | public abstract void report(Measurement measurement); 38 | 39 | /** 40 | * Used to gracefully stop reporter. 41 | */ 42 | public void stop() { 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/reporter/ReporterConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * This class represents the Reporter configuration. 8 | */ 9 | public class ReporterConfiguration { 10 | 11 | /** 12 | * A reporter implementation's fully qualified Java class name. 13 | */ 14 | public String reporter; 15 | 16 | /** 17 | * A map containing optional reporter specific options. 18 | */ 19 | public Map options = new HashMap<>(); 20 | 21 | /** 22 | * Try to get option from list or return default value if option for key not provided. 23 | * 24 | * @param key Option key 25 | * @param defaultValue Default value 26 | * @param Type of the value 27 | * @return either option for key or default value. 28 | */ 29 | @SuppressWarnings("unchecked") 30 | public T getDefaultOption(String key, T defaultValue) { 31 | Object value = options.get(key); 32 | 33 | return value == null ? defaultValue : (T) value; 34 | } 35 | 36 | /** 37 | * Get option value as defined type. 38 | * 39 | * @param key Option key. 40 | * @param Type of return value. 41 | * @return option value 42 | */ 43 | @SuppressWarnings("unchecked") 44 | public T getOption(String key) { 45 | return (T) options.get(key); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | StringBuilder sb = new StringBuilder(); 51 | sb.append(", reporter: \"").append(reporter).append("\", reporterOptions: ").append(options).append(" }"); 52 | return sb.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/main/java/io/smartcat/cassandra/diagnostics/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.utils; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Diagnostics utilities. 11 | */ 12 | public class Utils { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(Utils.class); 15 | 16 | private Utils() { 17 | 18 | } 19 | 20 | /** 21 | * Get system hostname. 22 | * 23 | * @return system hostname 24 | */ 25 | public static String resolveHostname() { 26 | try { 27 | return InetAddress.getLocalHost().getHostName(); 28 | } catch (UnknownHostException e) { 29 | logger.warn("Cannot resolve local host hostname"); 30 | return "UNKNOWN"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/test/java/io/smartcat/cassandra/diagnostics/MeasurementTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.junit.Test; 10 | 11 | public class MeasurementTest { 12 | 13 | @Test 14 | public void should_build_simple_measurement_with_value() { 15 | Map tags = new HashMap<>(); 16 | tags.put("tag1", "tv1"); 17 | tags.put("tag2", "tv2"); 18 | 19 | Map fields = new HashMap<>(); 20 | fields.put("v2", "abc"); 21 | 22 | Measurement measurement = Measurement.createSimple("m1", 1.0, 1434055662, TimeUnit.SECONDS, tags, fields); 23 | 24 | assertThat(measurement.isSimple()).isTrue(); 25 | assertThat(measurement.getValue()).isEqualTo(1.0); 26 | } 27 | 28 | @Test(expected = IllegalArgumentException.class) 29 | public void should_fail_building_simple_measurement_without_value() { 30 | Map tags = new HashMap<>(); 31 | tags.put("tag1", "tv1"); 32 | tags.put("tag2", "tv2"); 33 | 34 | Map fields = new HashMap<>(); 35 | fields.put("v2", "abc"); 36 | 37 | Measurement.createSimple("m1", null, 1434055662, TimeUnit.SECONDS, tags, fields); 38 | } 39 | 40 | @Test 41 | public void should_build_complex_measurement() { 42 | Map tags = new HashMap<>(); 43 | tags.put("tag1", "tv1"); 44 | tags.put("tag2", "tv2"); 45 | 46 | Map fields = new HashMap<>(); 47 | fields.put("v2", "abc"); 48 | 49 | Measurement measurement = Measurement.createComplex("m1", 1434055662, TimeUnit.SECONDS, tags, fields); 50 | 51 | assertThat(measurement.isComplex()).isTrue(); 52 | } 53 | 54 | @Test(expected = IllegalStateException.class) 55 | public void should_fail_fetching_value_from_complex_measurement() { 56 | Map tags = new HashMap<>(); 57 | tags.put("tag1", "tv1"); 58 | tags.put("tag2", "tv2"); 59 | 60 | Map fields = new HashMap<>(); 61 | fields.put("v2", "abc"); 62 | 63 | Measurement measurement = Measurement.createComplex("m1", 1434055662, TimeUnit.SECONDS, tags, fields); 64 | 65 | measurement.getValue(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /cassandra-diagnostics-commons/src/test/java/io/smartcat/cassandra/diagnostics/connector/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import static org.assertj.core.api.Assertions.*; 4 | 5 | import org.junit.Test; 6 | 7 | public class ConfigurationTest { 8 | 9 | @Test 10 | public void deafult_configuration() { 11 | ConnectorConfiguration conf = ConnectorConfiguration.getDefault(); 12 | assertThat(conf.queuedEventsOverflowThreshold).isEqualTo(1000); 13 | assertThat(conf.numWorkerThreads).isEqualTo(2); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector21/README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Diagnostics Connector for Cassandra 2.1.x 2 | 3 | Connector is a module which hooks into the query path and extract information for diagnostics. Bytecode instrumentation is used to augment existing Cassandra code with additional functionality. It uses low priority threads to execute the diagnostics information extraction with minimal performance impact to the target code (Cassandra node or application/driver). 4 | 5 | ## Tracing 6 | 7 | Tracing of queries enables reporting the actual queries for some measurement. Currently only module that supports it is Slow Query Module which is reporting queries above some threshold and you can turn on tracing optionally to view actual query which took longer than configured threshold. In future there might be more modules which use tracing to get the idea which query caused some metric (for example module which is reading too many tombstones, or module which is reading row with number of columns above certain threshold). 8 | 9 | When LogReporter for Slow Query Module is used (which reports the measurements and tracing information in Cassandra's log), slow query tracing is displayed like this: 10 | 11 | ``` 12 | INFO [cassandra-diagnostics-connector-0] 2017-03-23 14:23:58,998 LogReporter.java:35 - Measurement SLOW_QUERY [time=1490275438931, value=50.0, tags={host=SmartCat-Inspiron-5559, statementType=SELECT}, fields={sta 13 | tement=select * from typestest where name = ? and choice = ? LIMIT 100, client=/127.0.0.1:58908}] 14 | ``` 15 | 16 | The slow query, in this example is `select * from typestest where name = ? and choice = ? LIMIT 100` and it came from the 127.0.0.1:58908 client. The query is reported because it is above configured slow query treshlod (configuration option: `slowQueryThresholdInMilliseconds` in SlowQueryModule). 17 | 18 | ## Configuration 19 | 20 | The most important configuration options for the connector are the ones that enable connector to connect to Cassandra JMX: 21 | 22 | - `jmxHost` - Node JMX host 23 | - `jmxPort` - Node JMX port 24 | 25 | If JMX authentication in Cassandra is enabled, `jmxUsername` and `jmxPassword` must also be configured: 26 | 27 | ```yaml 28 | jmxAuthEnabled: true 29 | jmxUsername: "username" # actual jmx username 30 | jmxPassword: "password" # actual jmx password 31 | ``` 32 | 33 | Beside that, there are other configuration options: 34 | 35 | - `queuedEventsOverflowThreshold` - Configured threshold for queue size, above this threshold all events will be dropped until the number of queued events is dropped to `queuedEventsRelaxThreshold`. 36 | - `queuedEventsRelaxThreshold` - Lower threshold bound for event queue size. After the queue was previously in overflow state, new events will be queued only when the number of queued events drop below this value. 37 | - `enableTracing` - Whether to enable tracing or not. It is useful for various modules when debugging. Note that this can impact the performance. The idea is that tracing should be turned on only when needed and turned off once it is not needed anymore. 38 | 39 | The connector comes with sensible default values: 40 | 41 | ```yaml 42 | connector: 43 | jmxHost: "127.0.0.1" # optional 44 | jmxPort: 7199 # optional 45 | jmxAuthEnabled: false # optional 46 | queuedEventsOverflowThreshold: 1000 # optional 47 | queuedEventsRelaxThreshold: 700 # optional 48 | enableTracing: false # optional 49 | ``` 50 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector21/src/integration-test/resources/log4j-embedded-cassandra.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, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # for production, you should probably set the root to INFO 18 | # and the pattern to %c instead of %l. (%l is slower.) 19 | 20 | # output messages into a rolling log file as well as stdout 21 | log4j.rootLogger=ERROR,stdout,HColumnFamilyLogger 22 | 23 | # stdout 24 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 25 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 26 | log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c{3} - %m%n 27 | log4j.appender.stdout.follow=true 28 | 29 | log4j.appender.HColumnFamilyLogger=org.apache.log4j.ConsoleAppender 30 | log4j.appender.HColumnFamilyLogger.layout=org.apache.log4j.PatternLayout 31 | log4j.appender.HColumnFamilyLogger.layout.ConversionPattern=%m%n 32 | log4j.category.HColumnFamilyLogger=DEBUG 33 | #log4j.category.org.apache=INFO, stdout 34 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector21/src/main/resources/META-INF/services/io.smartcat.cassandra.diagnostics.connector.Connector: -------------------------------------------------------------------------------- 1 | io.smartcat.cassandra.diagnostics.connector.ConnectorImpl 2 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector21/src/test/java/io/smartcat/cassandra/diagnostics/connector/ConnectorImplTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import static org.mockito.Matchers.any; 4 | import static org.mockito.Matchers.same; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.verify; 7 | 8 | import java.lang.instrument.Instrumentation; 9 | import java.lang.reflect.Field; 10 | 11 | import org.apache.cassandra.cql3.CQLStatement; 12 | import org.apache.cassandra.cql3.QueryOptions; 13 | import org.apache.cassandra.cql3.QueryProcessor; 14 | import org.apache.cassandra.service.QueryState; 15 | import org.apache.cassandra.transport.messages.ResultMessage; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | import org.springframework.instrument.InstrumentationSavingAgent; 19 | 20 | import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; 21 | 22 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 23 | 24 | public class ConnectorImplTest { 25 | 26 | private static Instrumentation instrumentation; 27 | 28 | @BeforeClass 29 | public static void setUp() { 30 | instrumentation = InstrumentationSavingAgent.getInstrumentation(); 31 | } 32 | 33 | @Test 34 | @SuppressWarnings("unchecked") 35 | public void invokes_wrapper_when_query_processor_process_prepared_activates() throws Exception { 36 | ConnectorConfiguration configuration = new ConnectorConfiguration(); 37 | Connector connector = new ConnectorImpl(); 38 | connector.init(instrumentation, mock(QueryReporter.class), configuration, GlobalConfiguration.getDefault()); 39 | QueryProcessorWrapper queryProcessorWrapper = mock(QueryProcessorWrapper.class); 40 | setStatic(ConnectorImpl.class.getDeclaredField("queryProcessorWrapper"), queryProcessorWrapper); 41 | 42 | QueryProcessor queryProcessor = QueryProcessor.instance; 43 | 44 | CQLStatement statement = mock(CQLStatement.class); 45 | QueryState queryState = mock(QueryState.class); 46 | QueryOptions options = mock(QueryOptions.class); 47 | queryProcessor.processPrepared(statement, queryState, options); 48 | 49 | verify(queryProcessorWrapper) 50 | .processPrepared(same(statement), same(queryState), same(options), any(Long.class), 51 | any(ResultMessage.class), any(ConcurrentLinkedHashMap.class)); 52 | } 53 | 54 | private void setStatic(Field field, Object newValue) throws Exception { 55 | field.setAccessible(true); 56 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 57 | modifiersField.setAccessible(true); 58 | field.set(null, newValue); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector30/README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Diagnostics Connector for Cassandra 3.0.x 2 | 3 | Connector is a module which hooks into the query path and extract information for diagnostics. Bytecode instrumentation is used to augment existing Cassandra code with additional functionality. It uses low priority threads to execute the diagnostics information extraction with minimal performance impact to the target code (Cassandra node or application/driver). 4 | 5 | ## Tracing Slow Queries 6 | 7 | Tracing of queries enables reporting the actual queries for some measurement. Currently only module that supports it is Slow Query Module which is reporting queries above some threshold and you can turn on tracing optionally to view actual query which took longer than configured threshold. In future there might be more modules which use tracing to get the idea which query caused some metric (for example module which is reading too many tombstones, or module which is reading row with number of columns above certain threshold). 8 | 9 | When LogReporter for Slow Query Module is used (which reports the measurements and tracing information in Cassandra's log), slow query tracing is displayed like this: 10 | 11 | ``` 12 | INFO [cassandra-diagnostics-connector-0] 2017-03-23 14:23:58,998 LogReporter.java:35 - Measurement SLOW_QUERY [time=1490275438931, value=50.0, tags={host=SmartCat-Inspiron-5559, statementType=SELECT}, fields={sta 13 | tement=select * from typestest where name = ? and choice = ? LIMIT 100, client=/127.0.0.1:58908}] 14 | ``` 15 | 16 | The slow query, in this example is `select * from typestest where name = ? and choice = ? LIMIT 100` and it came from the 127.0.0.1:58908 client. The query is reported because it is above configured slow query treshlod (configuration option: `slowQueryThresholdInMilliseconds` in SlowQueryModule). 17 | 18 | ## Configuration 19 | 20 | The most important configuration options for the connector are the ones that enable connector to connect to Cassandra JMX: 21 | 22 | - `jmxHost` - Node JMX host 23 | - `jmxPort` - Node JMX port 24 | 25 | If JMX authentication in Cassandra is enabled, `jmxUsername` and `jmxPassword` must also be configured: 26 | 27 | ```yaml 28 | jmxAuthEnabled: true 29 | jmxUsername: "username" # actual jmx username 30 | jmxPassword: "password" # actual jmx password 31 | ``` 32 | 33 | Beside that, there are other configuration options: 34 | 35 | - `queuedEventsOverflowThreshold` - Configured threshold for queue size, above this threshold all events will be dropped until the number of queued events is dropped to `queuedEventsRelaxThreshold`. 36 | - `queuedEventsRelaxThreshold` - Lower threshold bound for event queue size. After the queue was previously in overflow state, new events will be queued only when the number of queued events drop below this value. 37 | - `enableTracing` - Whether to enable tracing or not. It is useful for various modules when debugging. Note that this can impact the performance. The idea is that tracing should be turned on only when needed and turned off once it is not needed anymore. 38 | 39 | The connector comes with sensible default values: 40 | 41 | ```yaml 42 | connector: 43 | jmxHost: "127.0.0.1" # optional 44 | jmxPort: 7199 # optional 45 | jmxAuthEnabled: false # optional 46 | queuedEventsOverflowThreshold: 1000 # optional 47 | queuedEventsRelaxThreshold: 700 # optional 48 | enableTracing: false # optional 49 | ``` 50 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector30/src/integration-test/resources/log4j-embedded-cassandra.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, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # for production, you should probably set the root to INFO 18 | # and the pattern to %c instead of %l. (%l is slower.) 19 | 20 | # output messages into a rolling log file as well as stdout 21 | log4j.rootLogger=ERROR,stdout,HColumnFamilyLogger 22 | 23 | # stdout 24 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 25 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 26 | log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c{3} - %m%n 27 | log4j.appender.stdout.follow=true 28 | 29 | log4j.appender.HColumnFamilyLogger=org.apache.log4j.ConsoleAppender 30 | log4j.appender.HColumnFamilyLogger.layout=org.apache.log4j.PatternLayout 31 | log4j.appender.HColumnFamilyLogger.layout.ConversionPattern=%m%n 32 | log4j.category.HColumnFamilyLogger=DEBUG 33 | #log4j.category.org.apache=INFO, stdout 34 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector30/src/main/resources/META-INF/services/io.smartcat.cassandra.diagnostics.connector.Connector: -------------------------------------------------------------------------------- 1 | io.smartcat.cassandra.diagnostics.connector.ConnectorImpl 2 | -------------------------------------------------------------------------------- /cassandra-diagnostics-connector30/src/test/java/io/smartcat/cassandra/diagnostics/connector/ConnectorImplTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import static org.mockito.Matchers.any; 4 | import static org.mockito.Matchers.same; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.verify; 7 | 8 | import java.lang.instrument.Instrumentation; 9 | import java.lang.reflect.Field; 10 | 11 | import org.apache.cassandra.cql3.CQLStatement; 12 | import org.apache.cassandra.cql3.QueryOptions; 13 | import org.apache.cassandra.cql3.QueryProcessor; 14 | import org.apache.cassandra.service.QueryState; 15 | import org.apache.cassandra.transport.messages.ResultMessage; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | import org.springframework.instrument.InstrumentationSavingAgent; 19 | 20 | import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; 21 | 22 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 23 | 24 | public class ConnectorImplTest { 25 | 26 | private static Instrumentation instrumentation; 27 | 28 | @BeforeClass 29 | public static void setUp() { 30 | instrumentation = InstrumentationSavingAgent.getInstrumentation(); 31 | } 32 | 33 | @Test 34 | @SuppressWarnings("unchecked") 35 | public void invokes_wrapper_when_query_processor_process_prepared_activates() throws Exception { 36 | ConnectorConfiguration configuration = new ConnectorConfiguration(); 37 | Connector connector = new ConnectorImpl(); 38 | connector.init(instrumentation, mock(QueryReporter.class), configuration, GlobalConfiguration.getDefault()); 39 | QueryProcessorWrapper queryProcessorWrapper = mock(QueryProcessorWrapper.class); 40 | setStatic(ConnectorImpl.class.getDeclaredField("queryProcessorWrapper"), queryProcessorWrapper); 41 | 42 | QueryProcessor queryProcessor = QueryProcessor.instance; 43 | 44 | CQLStatement statement = mock(CQLStatement.class); 45 | QueryState queryState = mock(QueryState.class); 46 | QueryOptions options = mock(QueryOptions.class); 47 | queryProcessor.processPrepared(statement, queryState, options); 48 | 49 | verify(queryProcessorWrapper) 50 | .processPrepared(same(statement), same(queryState), same(options), any(Long.class), 51 | any(ResultMessage.class), any(ConcurrentLinkedHashMap.class)); 52 | } 53 | 54 | private void setStatic(Field field, Object newValue) throws Exception { 55 | field.setAccessible(true); 56 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 57 | modifiersField.setAccessible(true); 58 | field.set(null, newValue); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/ConnectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | import io.smartcat.cassandra.diagnostics.connector.Connector; 4 | 5 | import java.util.ServiceLoader; 6 | 7 | /** 8 | * Factory class for obtaining {@link Connector} SPI implementation. 9 | */ 10 | public class ConnectorFactory { 11 | 12 | private ConnectorFactory() { 13 | } 14 | 15 | /** 16 | * Returns SPI {@code Connector} implementation. 17 | * 18 | * @return {@code Connector} implementation 19 | */ 20 | public static Connector getImplementation() { 21 | ServiceLoader loader = ServiceLoader.load(Connector.class); 22 | if (loader.iterator().hasNext()) { 23 | return loader.iterator().next(); 24 | } else { 25 | throw new IllegalStateException("Unable to find Cassandra Connector implementation."); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/DiagnosticsAgent.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import io.smartcat.cassandra.diagnostics.connector.Connector; 9 | import io.smartcat.cassandra.diagnostics.info.InfoProvider; 10 | 11 | /** 12 | * {@code DiagnosticAgent} acts as a Java agent used to instrument original Cassandra classes in order to extend them 13 | * with Cassandra Diagnostics additions. 14 | */ 15 | public class DiagnosticsAgent { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(DiagnosticsAgent.class); 18 | 19 | private static final String INITIALIZATION_THREAD_NAME = "cassandra-diagnostics-agent"; 20 | 21 | private static Diagnostics diagnostics; 22 | 23 | private static Connector connector; 24 | 25 | /** 26 | * Prevents class instantiation. 27 | */ 28 | private DiagnosticsAgent() { 29 | } 30 | 31 | /** 32 | * Entry point for agent when it is started upon VM start. 33 | * 34 | * @param args agent arguments 35 | * @param inst instrumentation handle 36 | */ 37 | public static void premain(final String args, final Instrumentation inst) { 38 | logger.info("Cassandra Diagnostics starting."); 39 | diagnostics = new Diagnostics(); 40 | connector = ConnectorFactory.getImplementation(); 41 | connector.init(inst, diagnostics, diagnostics.getConfiguration().connector, 42 | diagnostics.getConfiguration().global); 43 | Thread th = new Thread(new Runnable() { 44 | @Override 45 | public void run() { 46 | connector.waitForSetupCompleted(); 47 | diagnostics.activate(); 48 | logger.info("Cassandra Diagnostics initialized."); 49 | } 50 | }); 51 | th.setName(INITIALIZATION_THREAD_NAME); 52 | th.setDaemon(true); 53 | th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 54 | @Override 55 | public void uncaughtException(Thread t, Throwable e) { 56 | logger.error(e.getMessage(), e); 57 | } 58 | }); 59 | th.start(); 60 | } 61 | 62 | /** 63 | * Get connector instance. 64 | * 65 | * @return Connector instance 66 | */ 67 | public static InfoProvider getInfoProvider() { 68 | return connector.getInfoProvider(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/api/DiagnosticsApi.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.api; 2 | 3 | /** 4 | * JMX MXBean for monitoring and managing Cassandra Diagnostics module. 5 | */ 6 | public interface DiagnosticsApi { 7 | /** 8 | * Cassandra Diagnostics version. 9 | * @return version string 10 | */ 11 | public String getVersion(); 12 | 13 | /** 14 | * Reload diagnostics configuration. 15 | */ 16 | public void reload(); 17 | } 18 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/api/DiagnosticsApiImpl.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.api; 2 | 3 | import io.smartcat.cassandra.diagnostics.Diagnostics; 4 | import io.smartcat.cassandra.diagnostics.ProjectInfo; 5 | import io.smartcat.cassandra.diagnostics.config.Configuration; 6 | 7 | /** 8 | * Diagnostics JMX MXBean. 9 | */ 10 | public class DiagnosticsApiImpl implements DiagnosticsApi { 11 | 12 | /** 13 | * Module configuration. 14 | */ 15 | private Configuration config; 16 | 17 | private Diagnostics diagnostics; 18 | 19 | /** 20 | * Constructor. 21 | * 22 | * @param config configuration object 23 | * @param diagnostics diagnostics instance 24 | */ 25 | public DiagnosticsApiImpl(Configuration config, Diagnostics diagnostics) { 26 | this.config = config; 27 | this.diagnostics = diagnostics; 28 | } 29 | 30 | @Override 31 | public String getVersion() { 32 | return ProjectInfo.VERSION; 33 | } 34 | 35 | /** 36 | * Diagnostics' configuration reload operation. 37 | */ 38 | public void reload() { 39 | diagnostics.reload(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/api/HttpHandler.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.api; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import fi.iki.elonen.NanoHTTPD; 7 | import fi.iki.elonen.NanoHTTPD.Response.Status; 8 | import io.smartcat.cassandra.diagnostics.config.Configuration; 9 | 10 | /** 11 | * Implements diagnostics HTTP API. 12 | */ 13 | public class HttpHandler extends NanoHTTPD { 14 | private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class); 15 | 16 | private DiagnosticsApi diagnosticsApi; 17 | 18 | private final boolean apiAuthEnabled; 19 | private final String apiKey; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param config diagnostics configuration 25 | * @param diagnosticsApi diagnostics api 26 | */ 27 | public HttpHandler(Configuration config, DiagnosticsApi diagnosticsApi) { 28 | super(config.global.httpApiHost, config.global.httpApiPort); 29 | this.diagnosticsApi = diagnosticsApi; 30 | 31 | apiAuthEnabled = config.global.httpApiAuthEnabled; 32 | apiKey = config.global.httpApiKey; 33 | } 34 | 35 | /* (non-Javadoc) 36 | * @see fi.iki.elonen.NanoHTTPD#serve(fi.iki.elonen.NanoHTTPD.IHTTPSession) 37 | */ 38 | @Override 39 | public Response serve(IHTTPSession session) { 40 | if (!apiAuthEnabled || hasValidCredentials(session)) { 41 | return respond(session); 42 | } 43 | 44 | return newFixedLengthResponse(Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "Invalid API key\r\n"); 45 | } 46 | 47 | private Response respond(IHTTPSession session) { 48 | Method method = session.getMethod(); 49 | String uri = session.getUri(); 50 | 51 | logger.debug("Serving {} {} request.", method, uri); 52 | if (Method.GET.equals(method) && "/version".equalsIgnoreCase(uri)) { 53 | return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_PLAINTEXT, diagnosticsApi.getVersion()); 54 | } else if (Method.POST.equals(method) && "/reload".equalsIgnoreCase(uri)) { 55 | diagnosticsApi.reload(); 56 | return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_PLAINTEXT, 57 | "Configuration reloaded\r\n"); 58 | } else { 59 | return newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, 60 | "Requested URI " + uri + " not found\r\n"); 61 | } 62 | } 63 | 64 | private boolean hasValidCredentials(IHTTPSession session) { 65 | String header = session.getHeaders().get("authorization"); 66 | return apiKey.equals(header); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/config/Configuration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.config; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 10 | import io.smartcat.cassandra.diagnostics.connector.ConnectorConfiguration; 11 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 12 | import io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule; 13 | import io.smartcat.cassandra.diagnostics.reporter.LogReporter; 14 | import io.smartcat.cassandra.diagnostics.reporter.ReporterConfiguration; 15 | 16 | /** 17 | * This class represents the Cassandra Diagnostics configuration. 18 | */ 19 | public class Configuration { 20 | 21 | /** 22 | * Get default configuration for fallback when no configuration is provided. 23 | * 24 | * @return Configuration object with default {@code LogReporter} reporter 25 | */ 26 | public static Configuration getDefaultConfiguration() { 27 | return new Configuration() { 28 | { 29 | final ReporterConfiguration reporter = new ReporterConfiguration(); 30 | reporter.reporter = LogReporter.class.getName(); 31 | reporters.add(reporter); 32 | 33 | Map options = new HashMap<>(); 34 | options.put("period", 15); 35 | options.put("timeunit", TimeUnit.MINUTES.name()); 36 | final ModuleConfiguration module = new ModuleConfiguration(); 37 | module.measurement = "heartbeat"; 38 | module.module = HeartbeatModule.class.getName(); 39 | module.options = options; 40 | modules.add(module); 41 | 42 | connector = ConnectorConfiguration.getDefault(); 43 | 44 | global = GlobalConfiguration.getDefault(); 45 | } 46 | }; 47 | } 48 | 49 | /** 50 | * Connector-related configuration. 51 | */ 52 | public ConnectorConfiguration connector = ConnectorConfiguration.getDefault(); 53 | 54 | /** 55 | * Global configuration. 56 | */ 57 | public GlobalConfiguration global = GlobalConfiguration.getDefault(); 58 | 59 | /** 60 | * Reporters configuration list with reporter specific properties. 61 | */ 62 | public List reporters = new ArrayList<>(); 63 | 64 | /** 65 | * Modules configuration list with module specific properties. 66 | */ 67 | public List modules = new ArrayList<>(); 68 | 69 | @Override 70 | public String toString() { 71 | StringBuilder sb = new StringBuilder(); 72 | sb.append("{ hostname: " + global.hostname); 73 | sb.append("{ systemName: " + global.systemName); 74 | sb.append(", httpApiEnabled: " + global.httpApiEnabled); 75 | sb.append(", httpApiPort: " + global.httpApiPort); 76 | sb.append(", reporters: "); 77 | for (ReporterConfiguration reporter: reporters) { 78 | sb.append(reporter.toString()); 79 | } 80 | sb.append(", modules: "); 81 | for (ModuleConfiguration module: modules) { 82 | sb.append(module.toString()); 83 | } 84 | sb.append(" }"); 85 | return sb.toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/config/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.config; 2 | 3 | /** 4 | * Configuration exception. 5 | */ 6 | @SuppressWarnings("serial") 7 | public class ConfigurationException extends Exception { 8 | 9 | /** 10 | * Default constructor. 11 | */ 12 | public ConfigurationException() { 13 | super(); 14 | } 15 | 16 | /** 17 | * Constructor. 18 | * 19 | * @param message error message 20 | * @param cause exception cause 21 | * @param enableSuppression controls exception suppression 22 | * @param writableStackTrace stack trace 23 | */ 24 | public ConfigurationException(String message, Throwable cause, boolean enableSuppression, 25 | boolean writableStackTrace) { 26 | super(message, cause, enableSuppression, writableStackTrace); 27 | } 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * @param message error message 33 | * @param cause exception cause 34 | */ 35 | public ConfigurationException(String message, Throwable cause) { 36 | super(message, cause); 37 | } 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param message error message 43 | */ 44 | public ConfigurationException(String message) { 45 | super(message); 46 | } 47 | 48 | /** 49 | * Constructor. 50 | * 51 | * @param cause exception cause 52 | */ 53 | public ConfigurationException(Throwable cause) { 54 | super(cause); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/config/ConfigurationLoader.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.config; 2 | 3 | import java.net.URL; 4 | 5 | /** 6 | * Configuration loader. 7 | */ 8 | public interface ConfigurationLoader { 9 | /** 10 | * Loads configuration from an implicit location. 11 | * 12 | * @return loaded configuration 13 | * @throws ConfigurationException in case the configuration cannot be loaded 14 | */ 15 | Configuration loadConfig() throws ConfigurationException; 16 | 17 | /** 18 | * Loads configuration using an explicit location. 19 | * 20 | * @param url configuration location 21 | * @return loaded configuration 22 | * @throws ConfigurationException in case the configuration cannot be loaded 23 | */ 24 | Configuration loadConfig(URL url) throws ConfigurationException; 25 | } 26 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/health/ClusterHealthConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.health; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.yaml.snakeyaml.Yaml; 7 | 8 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 9 | 10 | /** 11 | * Cluster health module's configuration. 12 | */ 13 | public class ClusterHealthConfiguration { 14 | 15 | /** 16 | * A helper class for constructing immutable outer class. 17 | */ 18 | public static class Values { 19 | private static final int DEFAULT_PERIOD = 10; 20 | private static final String DEFAULT_TIMEUNIT = "SECONDS"; 21 | private static final boolean DEFAULT_NUMBER_OF_UNREACHABLE_NODES_ENABLED = false; 22 | 23 | /** 24 | * Cluster health reporting period. 25 | */ 26 | public int period = DEFAULT_PERIOD; 27 | 28 | /** 29 | * Cluster health reporting time unit. 30 | */ 31 | public TimeUnit timeunit = TimeUnit.valueOf(DEFAULT_TIMEUNIT); 32 | 33 | /** 34 | * Number of unreachable nodes. 35 | */ 36 | public boolean numberOfUnreachableNodesEnabled = DEFAULT_NUMBER_OF_UNREACHABLE_NODES_ENABLED; 37 | } 38 | 39 | private Values values = new Values(); 40 | 41 | private ClusterHealthConfiguration() { 42 | 43 | } 44 | 45 | /** 46 | * Create typed configuration for cluster health module out of generic module configuration. 47 | * 48 | * @param options Module configuration options. 49 | * @return typed cluster health module configuration from a generic one 50 | * @throws ConfigurationException in case the provided options are not valid 51 | */ 52 | public static ClusterHealthConfiguration create(Map options) throws ConfigurationException { 53 | ClusterHealthConfiguration conf = new ClusterHealthConfiguration(); 54 | Yaml yaml = new Yaml(); 55 | String str = yaml.dumpAsMap(options); 56 | conf.values = yaml.loadAs(str, ClusterHealthConfiguration.Values.class); 57 | return conf; 58 | } 59 | 60 | /** 61 | * Cluster health reporting period. 62 | * 63 | * @return cluster health reporting period 64 | */ 65 | public int period() { 66 | return values.period; 67 | } 68 | 69 | /** 70 | * Cluster health reporting time unit. 71 | * 72 | * @return cluster health reporting time unit 73 | */ 74 | public TimeUnit timeunit() { 75 | return values.timeunit; 76 | } 77 | 78 | /** 79 | * Reporting rate in milliseconds. 80 | * 81 | * @return reporting rate in milliseconds 82 | */ 83 | public long reportingRateInMillis() { 84 | return timeunit().toMillis(period()); 85 | } 86 | 87 | /** 88 | * Number of unreachable nodes in the cluster. 89 | * 90 | * @return report number of unreachable nodes 91 | */ 92 | public boolean numberOfUnreachableNodesEnabled() { 93 | return values.numberOfUnreachableNodesEnabled; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/health/ClusterHealthModule.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.health; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import io.smartcat.cassandra.diagnostics.DiagnosticsAgent; 14 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 15 | import io.smartcat.cassandra.diagnostics.Measurement; 16 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 17 | import io.smartcat.cassandra.diagnostics.info.InfoProvider; 18 | import io.smartcat.cassandra.diagnostics.module.Module; 19 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 20 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 21 | 22 | /** 23 | * Cluster health module collecting information about the liveness of the nodes in the cluster. 24 | */ 25 | public class ClusterHealthModule extends Module { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(ClusterHealthModule.class); 28 | 29 | private static final String STATUS_THREAD_NAME = "unreachable-nodes-module"; 30 | 31 | private static final String DEFAULT_NUMBER_OF_UNREACHABLE_NODES_MEASUREMENT_NAME = "number_of_unreachable_nodes"; 32 | 33 | private final int period; 34 | 35 | private final TimeUnit timeunit; 36 | 37 | private final boolean numberOfUnreachableNodesEnabled; 38 | 39 | private final Timer timer; 40 | 41 | private final InfoProvider infoProvider; 42 | 43 | /** 44 | * Constructor. 45 | * 46 | * @param configuration Module configuration 47 | * @param reporters Reporter list 48 | * @param globalConfiguration Global diagnostics configuration 49 | * @throws ConfigurationException configuration parsing exception 50 | */ 51 | public ClusterHealthModule(ModuleConfiguration configuration, List reporters, 52 | final GlobalConfiguration globalConfiguration) 53 | throws ConfigurationException { 54 | super(configuration, reporters, globalConfiguration); 55 | 56 | ClusterHealthConfiguration config = ClusterHealthConfiguration.create(configuration.options); 57 | period = config.period(); 58 | timeunit = config.timeunit(); 59 | numberOfUnreachableNodesEnabled = config.numberOfUnreachableNodesEnabled(); 60 | 61 | infoProvider = DiagnosticsAgent.getInfoProvider(); 62 | if (infoProvider == null) { 63 | logger.warn("Failed to initialize StatusModule. Info provider is null"); 64 | timer = null; 65 | } else { 66 | timer = new Timer(STATUS_THREAD_NAME); 67 | timer.scheduleAtFixedRate(new ClusterHealthTask(), 0, config.reportingRateInMillis()); 68 | } 69 | } 70 | 71 | @Override 72 | public void stop() { 73 | logger.trace("Stopping status module."); 74 | timer.cancel(); 75 | } 76 | 77 | /** 78 | * Cluster health collector task that's executed at configured period. 79 | */ 80 | private class ClusterHealthTask extends TimerTask { 81 | @Override 82 | public void run() { 83 | if (numberOfUnreachableNodesEnabled) { 84 | report(createMeasurement(infoProvider.getUnreachableNodes().size())); 85 | } 86 | } 87 | } 88 | 89 | private Measurement createMeasurement(long numberOfUnreachableNode) { 90 | final Map tags = new HashMap<>(1); 91 | tags.put("host", globalConfiguration.hostname); 92 | tags.put("systemName", globalConfiguration.systemName); 93 | 94 | final Map fields = new HashMap<>(); 95 | 96 | return Measurement.createSimple(DEFAULT_NUMBER_OF_UNREACHABLE_NODES_MEASUREMENT_NAME, 97 | (double) numberOfUnreachableNode, System.currentTimeMillis(), TimeUnit.MILLISECONDS, tags, fields); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/heartbeat/HeartbeatConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.heartbeat; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.yaml.snakeyaml.Yaml; 7 | 8 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 9 | 10 | /** 11 | * Heartbeat module's configuration. 12 | */ 13 | public class HeartbeatConfiguration { 14 | 15 | /** 16 | * A helper class for constructing immutable outer class. 17 | */ 18 | public static class Values { 19 | private static final int DEFAULT_PERIOD = 15; 20 | private static final String DEFAULT_TIMEUNIT = "MINUTES"; 21 | 22 | /** 23 | * Heartbeat period. 24 | */ 25 | public int period = DEFAULT_PERIOD; 26 | /** 27 | * Heartbeat period's time unit. 28 | */ 29 | public TimeUnit timeunit = TimeUnit.valueOf(DEFAULT_TIMEUNIT); 30 | } 31 | 32 | private Values values = new Values(); 33 | 34 | private HeartbeatConfiguration() { 35 | } 36 | 37 | /** 38 | * Create typed configuration for heartbeat module out of generic module configuration. 39 | * 40 | * @param options Module configuration options. 41 | * @return typed heartbeat module configuration from a generic one 42 | * @throws ConfigurationException in case the provided options are not valid 43 | */ 44 | public static HeartbeatConfiguration create(Map options) throws ConfigurationException { 45 | HeartbeatConfiguration conf = new HeartbeatConfiguration(); 46 | Yaml yaml = new Yaml(); 47 | String str = yaml.dumpAsMap(options); 48 | conf.values = yaml.loadAs(str, HeartbeatConfiguration.Values.class); 49 | return conf; 50 | } 51 | 52 | /** 53 | * Heartbeat period getter. 54 | * 55 | * @return heartbeat period 56 | */ 57 | public int period() { 58 | return values.period; 59 | } 60 | 61 | /** 62 | * Heartbeat period time unit getter. 63 | * 64 | * @return heartbeat time unit 65 | */ 66 | public TimeUnit timeunit() { 67 | return values.timeunit; 68 | } 69 | 70 | /** 71 | * Reporting rate in milliseconds. 72 | * 73 | * @return Reporting rate in milliseconds 74 | */ 75 | public long reportingRateInMillis() { 76 | return timeunit().toMillis(period()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/heartbeat/HeartbeatModule.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.heartbeat; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 14 | import io.smartcat.cassandra.diagnostics.Measurement; 15 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 16 | import io.smartcat.cassandra.diagnostics.module.Module; 17 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 18 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 19 | 20 | /** 21 | * Heartbeat module providing logged heartbeats at defined intervals. 22 | */ 23 | public class HeartbeatModule extends Module { 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(HeartbeatModule.class); 26 | 27 | private static final String DEFAULT_MEASUREMENT_NAME = "heartbeat"; 28 | 29 | private static final String HEARTBEAT_THREAD_NAME = "heartbeat-timer"; 30 | 31 | private final String service; 32 | 33 | private final Timer timer; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param configuration Module configuration 39 | * @param reporters Reporter list 40 | * @param globalConfiguration Global diagnostics configuration 41 | * @throws ConfigurationException in case the provided module configuration is not valid 42 | */ 43 | public HeartbeatModule(ModuleConfiguration configuration, List reporters, 44 | final GlobalConfiguration globalConfiguration) throws ConfigurationException { 45 | super(configuration, reporters, globalConfiguration); 46 | 47 | HeartbeatConfiguration config = HeartbeatConfiguration.create(configuration.options); 48 | service = configuration.getMeasurementOrDefault(DEFAULT_MEASUREMENT_NAME); 49 | 50 | logger.info("Heartbeat module initialized with {} {} reporting period.", config.period(), 51 | config.timeunit().name()); 52 | timer = new Timer(HEARTBEAT_THREAD_NAME); 53 | timer.schedule(new HeartbeatTask(), 0, config.reportingRateInMillis()); 54 | } 55 | 56 | @Override 57 | public void stop() { 58 | logger.trace("Stopping heartbeat module."); 59 | timer.cancel(); 60 | } 61 | 62 | /** 63 | * Heartbeat task that's executed at configured periods. 64 | */ 65 | private class HeartbeatTask extends TimerTask { 66 | @Override 67 | public void run() { 68 | report(createMeasurement()); 69 | } 70 | } 71 | 72 | private Measurement createMeasurement() { 73 | final Map tags = new HashMap<>(1); 74 | tags.put("host", globalConfiguration.hostname); 75 | tags.put("systemName", globalConfiguration.systemName); 76 | Measurement measurement = Measurement 77 | .createSimple(service, 1.0, System.currentTimeMillis(), TimeUnit.MILLISECONDS, tags, 78 | new HashMap()); 79 | return measurement; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/hiccup/HiccupRecorder.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.hiccup; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.HdrHistogram.SingleWriterRecorder; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * Hiccup recorder class. Taken from https://github.com/giltene/jHiccup. 11 | * All credits go to Gil Tene of Azul Systems. 12 | */ 13 | public class HiccupRecorder extends Thread { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(HiccupRecorder.class); 16 | 17 | private static final String HICCUP_RECORDER_THREAD_NAME = "hiccup-recorder"; 18 | 19 | /** 20 | * Thread running. 21 | */ 22 | public volatile boolean doRun; 23 | 24 | /** 25 | * Allocation object. 26 | */ 27 | public volatile Long lastSleepTimeObj; // public volatile to make sure allocs are not optimized away... 28 | 29 | private final SingleWriterRecorder recorder; 30 | 31 | private final HiccupConfiguration config; 32 | 33 | /** 34 | * Hiccup recorder constructor. 35 | * 36 | * @param config Hiccup configuration 37 | * @param recorder Histogram single writer recorder 38 | */ 39 | public HiccupRecorder(final HiccupConfiguration config, final SingleWriterRecorder recorder) { 40 | this.setDaemon(true); 41 | this.setName(HICCUP_RECORDER_THREAD_NAME); 42 | this.recorder = recorder; 43 | this.config = config; 44 | doRun = true; 45 | } 46 | 47 | /** 48 | * Terminate running thread. 49 | */ 50 | public void terminate() { 51 | doRun = false; 52 | } 53 | 54 | /** 55 | * Current time with delay getter. 56 | * 57 | * @param nextReportingTime next histogram reporting time 58 | * @return current time with delay 59 | * @throws InterruptedException Interrupted exception 60 | */ 61 | public long getCurrentTimeMsecWithDelay(final long nextReportingTime) throws InterruptedException { 62 | final long now = System.currentTimeMillis(); 63 | if (now < nextReportingTime) { 64 | Thread.sleep(nextReportingTime - now); 65 | } 66 | return now; 67 | } 68 | 69 | @Override 70 | public void run() { 71 | final long resolutionNsec = (long) (config.resolutionInMs() * 1000L * 1000L); 72 | try { 73 | long shortestObservedDeltaTimeNsec = Long.MAX_VALUE; 74 | while (doRun) { 75 | final long timeBeforeMeasurement = System.nanoTime(); 76 | if (config.resolutionInMs() != 0) { 77 | TimeUnit.NANOSECONDS.sleep(resolutionNsec); 78 | if (config.allocateObjects()) { 79 | // Allocate an object to make sure potential allocation stalls are measured. 80 | lastSleepTimeObj = new Long(timeBeforeMeasurement); 81 | } 82 | } 83 | final long timeAfterMeasurement = System.nanoTime(); 84 | final long deltaTimeNsec = timeAfterMeasurement - timeBeforeMeasurement; 85 | 86 | if (deltaTimeNsec < shortestObservedDeltaTimeNsec) { 87 | shortestObservedDeltaTimeNsec = deltaTimeNsec; 88 | } 89 | 90 | long hiccupTimeNsec = deltaTimeNsec - shortestObservedDeltaTimeNsec; 91 | 92 | recorder.recordValueWithExpectedInterval(hiccupTimeNsec, resolutionNsec); 93 | } 94 | } catch (InterruptedException e) { 95 | logger.debug("# HiccupRecorder interrupted/terminating..."); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/metrics/MetricsModule.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.metrics; 2 | 3 | import java.util.List; 4 | import java.util.Timer; 5 | import java.util.TimerTask; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 11 | import io.smartcat.cassandra.diagnostics.Measurement; 12 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 13 | import io.smartcat.cassandra.diagnostics.module.Module; 14 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 15 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 16 | 17 | /** 18 | * Metrics module collecting Cassandra metrics exposed over JMX. Requires local JMX connection. 19 | */ 20 | public class MetricsModule extends Module { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(MetricsModule.class); 23 | 24 | private static final String DEFAULT_MEASUREMENT_NAME = "metrics"; 25 | 26 | private static final String METRICS_THREAD_NAME = "metrics-timer"; 27 | 28 | private final MetricsConfiguration config; 29 | 30 | private final MetricsCollector metricsCollector; 31 | 32 | private final Timer timer; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param configuration Module configuration 38 | * @param reporters Reporter list 39 | * @param globalConfiguration Global diagnostics configuration 40 | * @throws ConfigurationException in case the provided module configuration is not valid 41 | */ 42 | public MetricsModule(ModuleConfiguration configuration, List reporters, 43 | final GlobalConfiguration globalConfiguration) throws ConfigurationException { 44 | super(configuration, reporters, globalConfiguration); 45 | 46 | config = MetricsConfiguration.create(configuration.options); 47 | metricsCollector = new MetricsCollector(configuration.getMeasurementOrDefault(DEFAULT_MEASUREMENT_NAME), config, 48 | globalConfiguration); 49 | 50 | logger.info("Metrics module initialized with {} {} reporting period.", config.period(), 51 | config.timeunit().name()); 52 | 53 | timer = new Timer(METRICS_THREAD_NAME); 54 | if (metricsCollector.connect()) { 55 | timer.schedule(new MetricsTask(), 0, config.reportingRateInMillis()); 56 | } 57 | } 58 | 59 | @Override 60 | public void stop() { 61 | logger.trace("Stopping metrics module."); 62 | timer.cancel(); 63 | metricsCollector.close(); 64 | } 65 | 66 | /** 67 | * Metrics reporter task that's executed at configured period. 68 | */ 69 | private class MetricsTask extends TimerTask { 70 | @Override 71 | public void run() { 72 | for (Measurement measurement : metricsCollector.collectMeasurements()) { 73 | report(measurement); 74 | } 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/module/slowquery/SlowQueryLogDecider.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.slowquery; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import io.smartcat.cassandra.diagnostics.Query; 8 | 9 | /** 10 | * Decider which will decide if query should be reported based on configuration. 11 | * 12 | */ 13 | public class SlowQueryLogDecider { 14 | 15 | /** 16 | * Class logger. 17 | */ 18 | private static final Logger logger = LoggerFactory.getLogger(SlowQueryModule.class); 19 | 20 | private final SlowQueryConfiguration slowQueryConfiguration; 21 | 22 | private SlowQueryLogDecider(SlowQueryConfiguration slowQueryConfiguration) { 23 | this.slowQueryConfiguration = slowQueryConfiguration; 24 | } 25 | 26 | /** 27 | * Create SlowQueryLogDecider for provided configuration. 28 | * @param configuration SlowQueryConfiguration which is active. 29 | * @return slow query decider for this configuration. 30 | */ 31 | public static SlowQueryLogDecider create(SlowQueryConfiguration configuration) { 32 | return new SlowQueryLogDecider(configuration); 33 | } 34 | 35 | /** 36 | * Based on defined criteria decide if this query is eligible for reporting. 37 | * @param query Query candidate for report. 38 | * @return if this query is eligible for report. 39 | */ 40 | public boolean isForReporting(Query query) { 41 | if (executionTimeForLogging(query.executionTimeInMilliseconds()) && tableForLogging(query) 42 | && typeForLogging(query)) { 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | private boolean executionTimeForLogging(long executionTimeInMilliseconds) { 50 | if (slowQueryConfiguration.slowQueryThreshold() == 0) { 51 | logger.trace("Slow query threshold turned off, logging all queries."); 52 | return true; 53 | } 54 | 55 | logger.trace("Checking if execution time:{} is above threshold: {}", executionTimeInMilliseconds, 56 | slowQueryConfiguration.slowQueryThreshold()); 57 | if (executionTimeInMilliseconds > slowQueryConfiguration.slowQueryThreshold()) { 58 | return true; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | private boolean typeForLogging(Query query) { 65 | logger.trace("Checking if query type is for logging."); 66 | boolean logAll = slowQueryConfiguration.queryTypesToLog().contains("ALL"); 67 | boolean queryTypeMatches = slowQueryConfiguration.queryTypesToLog().contains(query.statementType().toString()); 68 | return logAll || queryTypeMatches; 69 | } 70 | 71 | private boolean tableForLogging(Query query) { 72 | logger.trace("Checking if table is in tables for logging."); 73 | if (slowQueryConfiguration.tablesForLogging().isEmpty()) { 74 | return true; 75 | } 76 | 77 | if (StringUtils.isBlank(query.fullTableName())) { 78 | logger.debug("Query does not have table name."); 79 | return false; 80 | } 81 | 82 | for (String tableForLogging : slowQueryConfiguration.tablesForLogging()) { 83 | if (tableForLogging.equals(query.fullTableName())) { 84 | logger.debug("Taable {} is eligible for logging.", query.fullTableName()); 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/java/io/smartcat/cassandra/diagnostics/reporter/LogReporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 7 | import io.smartcat.cassandra.diagnostics.Measurement; 8 | 9 | /** 10 | * A SLF4J based {@link Reporter} implementation. This reporter is using {@link Logger} to print query reports to a log 11 | * at {@code INFO} level. 12 | */ 13 | public class LogReporter extends Reporter { 14 | 15 | /** 16 | * Class logger. 17 | */ 18 | private static final Logger logger = LoggerFactory.getLogger(LogReporter.class); 19 | 20 | /** 21 | * String template for logging query report. 22 | */ 23 | private static final String LOG_TEMPLATE = "{} Measurement {} [time={}, value={}, tags={}, fields={}]"; 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param configuration configuration 29 | * @param globalConfiguration Global diagnostics configuration 30 | */ 31 | public LogReporter(ReporterConfiguration configuration, GlobalConfiguration globalConfiguration) { 32 | super(configuration, globalConfiguration); 33 | } 34 | 35 | @Override 36 | public void report(Measurement measurement) { 37 | logger.info(LOG_TEMPLATE, measurement.type().toString(), measurement.name().toUpperCase(), measurement.time(), 38 | measurement.getOrDefault(0d), measurement.tags().toString(), measurement.fields().toString()); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/main/resources/cassandra-diagnostics-default.yml: -------------------------------------------------------------------------------- 1 | # Global diagnostics configuration 2 | global: 3 | systemName: "smartcat-cassandra-cluster" 4 | 5 | # Reporters 6 | reporters: 7 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 8 | 9 | # Modules 10 | modules: 11 | - module: io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule 12 | options: 13 | period: 1 14 | timeunit: MINUTES 15 | reporters: 16 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 17 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/DiagnosticsModuleTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | /** 4 | * Test for diagnostics module. 5 | * 6 | */ 7 | public class DiagnosticsModuleTest { 8 | } 9 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/DiagnosticsTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics; 2 | 3 | import org.junit.Test; 4 | 5 | import io.smartcat.cassandra.diagnostics.config.Configuration; 6 | 7 | public class DiagnosticsTest { 8 | 9 | @Test 10 | public void test_diagnostics_reload() { 11 | Diagnostics diagnostics = new Diagnostics(); 12 | diagnostics.activate(); 13 | Configuration configuration = diagnostics.getConfiguration(); 14 | diagnostics.reload(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/config/YamlConfigurationLoaderTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.config; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Test for yaml configuration loader. 9 | * 10 | */ 11 | public class YamlConfigurationLoaderTest { 12 | 13 | @Test 14 | public void loads_default_configuration() throws ConfigurationException { 15 | YamlConfigurationLoader loader = new YamlConfigurationLoader(); 16 | Configuration configuration = loader.loadConfig(); 17 | assertThat(configuration).isNotNull(); 18 | } 19 | 20 | @Test 21 | public void load_invalid_external_configuratio_uri() { 22 | System.setProperty("cassandra.diagnostics.config", "invalid-cassandra-diagnostics-path.yml"); 23 | YamlConfigurationLoader loader = new YamlConfigurationLoader(); 24 | ConfigurationException exception = null; 25 | try { 26 | loader.loadConfig(); 27 | } catch (ConfigurationException e) { 28 | exception = e; 29 | } 30 | assertThat(exception).isNotNull(); 31 | } 32 | 33 | @Test 34 | public void load_invalid_external_configuratio() { 35 | System.setProperty("cassandra.diagnostics.config", "invalid-cassandra-diagnostics.yml"); 36 | YamlConfigurationLoader loader = new YamlConfigurationLoader(); 37 | ConfigurationException exception = null; 38 | try { 39 | loader.loadConfig(); 40 | } catch (ConfigurationException e) { 41 | exception = e; 42 | } 43 | assertThat(exception).isNotNull(); 44 | } 45 | 46 | @Test 47 | public void load_external_valid_configuration() throws ConfigurationException { 48 | System.setProperty("cassandra.diagnostics.config", "valid-cassandra-diagnostics.yml"); 49 | YamlConfigurationLoader loader = new YamlConfigurationLoader(); 50 | Configuration configuration = loader.loadConfig(); 51 | assertThat(configuration.global.hostname).isEqualTo("test-hostname"); 52 | assertThat(configuration.global.systemName).isEqualTo("smartcat-cassandra-cluster"); 53 | assertThat(configuration.global.httpApiEnabled).isTrue(); 54 | assertThat(configuration.global.httpApiHost).isEqualTo("10.0.0.1"); 55 | assertThat(configuration.global.httpApiPort).isEqualTo(8001); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/LatchTestReporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.CountDownLatch; 6 | 7 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 8 | import io.smartcat.cassandra.diagnostics.Measurement; 9 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 10 | import io.smartcat.cassandra.diagnostics.reporter.ReporterConfiguration; 11 | 12 | public class LatchTestReporter extends Reporter { 13 | 14 | private CountDownLatch latch; 15 | 16 | private final List reported = new ArrayList<>(); 17 | 18 | public LatchTestReporter(ReporterConfiguration configuration, GlobalConfiguration globalConfiguration, 19 | CountDownLatch latch) { 20 | super(configuration, globalConfiguration); 21 | this.latch = latch; 22 | } 23 | 24 | @Override 25 | public void report(Measurement measurement) { 26 | reported.add(measurement); 27 | System.out.println(measurement.toString()); 28 | latch.countDown(); 29 | } 30 | 31 | /** 32 | * Prevent concurrency issues and return always copy of reported values. 33 | * 34 | * @return copy of all reported values. 35 | */ 36 | public List getReported() { 37 | return new ArrayList<>(reported); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/TestMXBean.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | /** 4 | * TestMXBean interface. 5 | */ 6 | public interface TestMXBean { 7 | 8 | int getValue(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/TestMXBeanImpl.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | /** 4 | * TextMXBean interface implementation. 5 | */ 6 | public class TestMXBeanImpl implements TestMXBean { 7 | 8 | private final ModuleConfiguration config; 9 | 10 | public TestMXBeanImpl(ModuleConfiguration config) { 11 | this.config = config; 12 | } 13 | 14 | public boolean called = false; 15 | 16 | @Override 17 | public int getValue() { 18 | this.called = true; 19 | return 1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/TestReporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 7 | import io.smartcat.cassandra.diagnostics.Measurement; 8 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 9 | import io.smartcat.cassandra.diagnostics.reporter.ReporterConfiguration; 10 | 11 | /** 12 | * Test reporter class. 13 | */ 14 | public class TestReporter extends Reporter { 15 | 16 | public final List reported = new ArrayList(); 17 | 18 | public TestReporter(ReporterConfiguration configuration, GlobalConfiguration globalConfiguration) { 19 | super(configuration, globalConfiguration); 20 | } 21 | 22 | @Override 23 | public void report(Measurement measurement) { 24 | reported.add(measurement); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/health/ClusterHealthConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.health; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.junit.Test; 10 | import org.yaml.snakeyaml.constructor.ConstructorException; 11 | 12 | /** 13 | * Cluster health module configuration test. 14 | */ 15 | public class ClusterHealthConfigurationTest { 16 | 17 | @Test 18 | public void loads_default_configuration() throws Exception { 19 | Map options = new HashMap<>(); 20 | ClusterHealthConfiguration conf = ClusterHealthConfiguration.create(options); 21 | assertThat(conf.period()).isEqualTo(10); 22 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 23 | assertThat(conf.numberOfUnreachableNodesEnabled()).isFalse(); 24 | } 25 | 26 | @Test 27 | public void provides_all_values() throws Exception { 28 | Map options = new HashMap<>(); 29 | options.put("period", 2); 30 | options.put("timeunit", "HOURS"); 31 | options.put("numberOfUnreachableNodesEnabled", true); 32 | ClusterHealthConfiguration conf = ClusterHealthConfiguration.create(options); 33 | assertThat(conf.period()).isEqualTo(2); 34 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.HOURS); 35 | assertThat(conf.numberOfUnreachableNodesEnabled()).isTrue(); 36 | } 37 | 38 | @Test 39 | public void fails_when_incorrect_values_provided() { 40 | Map options = new HashMap<>(); 41 | options.put("period", 2); 42 | options.put("timeunit", "ERR"); 43 | options.put("numberOfUnreachableNodesEnabled", "123"); 44 | try { 45 | ClusterHealthConfiguration.create(options); 46 | } catch (Exception e) { 47 | assertThat(e).isInstanceOf(ConstructorException.class); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/heartbeat/HeartbeatConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.heartbeat; 2 | 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.Test; 11 | import org.yaml.snakeyaml.constructor.ConstructorException; 12 | 13 | public class HeartbeatConfigurationTest { 14 | 15 | @Test 16 | public void loads_default_configuration() throws Exception { 17 | Map options = new HashMap<>(); 18 | HeartbeatConfiguration conf = HeartbeatConfiguration.create(options); 19 | assertThat(conf.period()).isEqualTo(15); 20 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.MINUTES); 21 | } 22 | 23 | @Test 24 | public void provides_all_values() throws Exception { 25 | Map options = new HashMap<>(); 26 | options.put("period", 2); 27 | options.put("timeunit", "SECONDS"); 28 | HeartbeatConfiguration conf = HeartbeatConfiguration.create(options); 29 | assertThat(conf.period()).isEqualTo(2); 30 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 31 | } 32 | 33 | @Test 34 | public void fails_when_incorrect_values_provided() { 35 | Map options = new HashMap<>(); 36 | options.put("period", 2); 37 | options.put("timeunit", "ERR"); 38 | try { 39 | HeartbeatConfiguration.create(options); 40 | } catch (Exception e) { 41 | assertThat(e).isInstanceOf(ConstructorException.class); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/heartbeat/HeartbeatModuleTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.heartbeat; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.Test; 11 | 12 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 13 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 14 | import io.smartcat.cassandra.diagnostics.module.LatchTestReporter; 15 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 16 | import io.smartcat.cassandra.diagnostics.module.TestReporter; 17 | import io.smartcat.cassandra.diagnostics.reporter.LogReporter; 18 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 19 | 20 | public class HeartbeatModuleTest { 21 | 22 | @Test 23 | public void should_load_default_configuration_and_initialize() throws ConfigurationException { 24 | final HeartbeatModule module = new HeartbeatModule(testConfiguration(), testReporters(), 25 | GlobalConfiguration.getDefault()); 26 | module.stop(); 27 | } 28 | 29 | @Test 30 | public void should_report_heartbeat_when_started() throws ConfigurationException, InterruptedException { 31 | final CountDownLatch latch = new CountDownLatch(1); 32 | final LatchTestReporter testReporter = new LatchTestReporter(null, GlobalConfiguration.getDefault(), latch); 33 | final List reporters = new ArrayList() { 34 | { 35 | add(testReporter); 36 | } 37 | }; 38 | 39 | final HeartbeatModule module = new HeartbeatModule(testConfiguration(), reporters, 40 | GlobalConfiguration.getDefault()); 41 | boolean wait = latch.await(100, TimeUnit.MILLISECONDS); 42 | module.stop(); 43 | assertThat(wait).isTrue(); 44 | } 45 | 46 | @Test 47 | public void should_report_using_log_reporter() throws ConfigurationException, InterruptedException { 48 | final CountDownLatch latch = new CountDownLatch(1); 49 | final LatchTestReporter latchTestReporter = new LatchTestReporter(null, GlobalConfiguration.getDefault(), 50 | latch); 51 | final List reporters = new ArrayList() { 52 | { 53 | add(latchTestReporter); 54 | add(new LogReporter(null, GlobalConfiguration.getDefault())); 55 | } 56 | }; 57 | 58 | final HeartbeatModule module = new HeartbeatModule(testConfiguration(), reporters, 59 | GlobalConfiguration.getDefault()); 60 | boolean wait = latch.await(200, TimeUnit.MILLISECONDS); 61 | module.stop(); 62 | assertThat(wait).isTrue(); 63 | } 64 | 65 | private ModuleConfiguration testConfiguration() { 66 | final ModuleConfiguration configuration = new ModuleConfiguration(); 67 | configuration.measurement = "test_heartbeat"; 68 | configuration.module = "io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule"; 69 | configuration.options.put("period", 1); 70 | configuration.options.put("timeunit", "SECONDS"); 71 | return configuration; 72 | } 73 | 74 | private List testReporters() { 75 | return new ArrayList() { 76 | { 77 | add(new TestReporter(null, GlobalConfiguration.getDefault())); 78 | } 79 | }; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/hiccup/HiccupConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.hiccup; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.junit.Test; 10 | 11 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 12 | 13 | public class HiccupConfigurationTest { 14 | 15 | @Test 16 | public void loads_default_configuration() throws ConfigurationException { 17 | Map options = new HashMap<>(); 18 | HiccupConfiguration conf = HiccupConfiguration.create(options); 19 | assertThat(conf.resolutionInMs()).isEqualTo(1.0d); 20 | assertThat(conf.startDelayInMs()).isEqualTo(30000); 21 | assertThat(conf.allocateObjects()).isEqualTo(false); 22 | assertThat(conf.lowestTrackableValueInNanos()).isEqualTo(1000L * 20L); 23 | assertThat(conf.highestTrackableValueInNanos()).isEqualTo(3600 * 1000L * 1000L * 1000L); 24 | assertThat(conf.numberOfSignificantValueDigits()).isEqualTo(2); 25 | assertThat(conf.period()).isEqualTo(5); 26 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 27 | } 28 | 29 | @Test 30 | public void provides_all_values() throws Exception { 31 | Map options = new HashMap<>(); 32 | options.put("resolutionInMs", 1000.0d); 33 | options.put("startDelayInMs", 10000); 34 | options.put("allocateObjects", true); 35 | options.put("lowestTrackableValueInNanos", 250L); 36 | options.put("highestTrackableValueInNanos", 100000); 37 | options.put("numberOfSignificantValueDigits", 10); 38 | options.put("period", 2); 39 | options.put("timeunit", "SECONDS"); 40 | HiccupConfiguration conf = HiccupConfiguration.create(options); 41 | assertThat(conf.resolutionInMs()).isEqualTo(1000.0d); 42 | assertThat(conf.startDelayInMs()).isEqualTo(10000); 43 | assertThat(conf.allocateObjects()).isEqualTo(true); 44 | assertThat(conf.lowestTrackableValueInNanos()).isEqualTo(250); 45 | assertThat(conf.highestTrackableValueInNanos()).isEqualTo(100000); 46 | assertThat(conf.numberOfSignificantValueDigits()).isEqualTo(10); 47 | assertThat(conf.period()).isEqualTo(2); 48 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 49 | assertThat(conf.reportingIntervalInMillis()).isEqualTo(2000); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/hiccup/HiccupModuleTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.hiccup; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.Test; 11 | 12 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 13 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 14 | import io.smartcat.cassandra.diagnostics.module.LatchTestReporter; 15 | import io.smartcat.cassandra.diagnostics.module.ModuleConfiguration; 16 | import io.smartcat.cassandra.diagnostics.module.TestReporter; 17 | import io.smartcat.cassandra.diagnostics.reporter.Reporter; 18 | 19 | public class HiccupModuleTest { 20 | 21 | @Test 22 | public void should_initialize_module() throws ConfigurationException, InterruptedException { 23 | final HiccupModule module = new HiccupModule(testConfiguration(), testReporters(), 24 | GlobalConfiguration.getDefault()); 25 | module.stop(); 26 | } 27 | 28 | @Test 29 | public void should_report_hiccup_when_started() throws ConfigurationException, InterruptedException { 30 | final CountDownLatch latch = new CountDownLatch(1); 31 | final LatchTestReporter testReporter = new LatchTestReporter(null, GlobalConfiguration.getDefault(), latch); 32 | final List reporters = new ArrayList() { 33 | { 34 | add(testReporter); 35 | } 36 | }; 37 | 38 | final HiccupModule module = new HiccupModule(testConfiguration(), reporters, GlobalConfiguration.getDefault()); 39 | boolean wait = latch.await(1100, TimeUnit.MILLISECONDS); 40 | module.stop(); 41 | assertThat(wait).isTrue(); 42 | } 43 | 44 | private ModuleConfiguration testConfiguration() { 45 | final ModuleConfiguration configuration = new ModuleConfiguration(); 46 | configuration.measurement = "test_hiccup"; 47 | configuration.module = "io.smartcat.cassandra.diagnostics.module.hiccup.HiccupModule"; 48 | configuration.options.put("startDelayInMs", 0); 49 | configuration.options.put("period", 1); 50 | configuration.options.put("timeunit", "SECONDS"); 51 | return configuration; 52 | } 53 | 54 | private List testReporters() { 55 | return new ArrayList() { 56 | { 57 | add(new TestReporter(null, GlobalConfiguration.getDefault())); 58 | } 59 | }; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/metrics/MetricsConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.metrics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.Test; 11 | import org.yaml.snakeyaml.constructor.ConstructorException; 12 | 13 | public class MetricsConfigurationTest { 14 | 15 | @Test 16 | public void loads_default_configuration() throws Exception { 17 | Map options = new HashMap<>(); 18 | MetricsConfiguration conf = MetricsConfiguration.create(options); 19 | assertThat(conf.period()).isEqualTo(1); 20 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 21 | assertThat(conf.jmxHost()).isEqualTo("127.0.0.1"); 22 | assertThat(conf.jmxPort()).isEqualTo(7199); 23 | assertThat(conf.jmxSslEnabled()).isEqualTo(false); 24 | assertThat(conf.metricsPackageNames()).isEqualTo(Arrays.asList("org.apache.cassandra.metrics")); 25 | assertThat(conf.metricsPatterns()).isNotNull(); 26 | assertThat(conf.metricsPatterns()).isEmpty(); 27 | } 28 | 29 | @Test 30 | public void provides_all_values() throws Exception { 31 | Map options = new HashMap<>(); 32 | options.put("period", 2); 33 | options.put("timeunit", "SECONDS"); 34 | MetricsConfiguration conf = MetricsConfiguration.create(options); 35 | assertThat(conf.period()).isEqualTo(2); 36 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 37 | } 38 | 39 | @Test 40 | public void fails_when_incorrect_values_provided() { 41 | Map options = new HashMap<>(); 42 | options.put("period", 2); 43 | options.put("timeunit", "ERR"); 44 | try { 45 | MetricsConfiguration.create(options); 46 | } catch (Exception e) { 47 | assertThat(e).isInstanceOf(ConstructorException.class); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/requestrate/RequestRateConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.requestrate; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.junit.Test; 11 | 12 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 13 | 14 | public class RequestRateConfigurationTest { 15 | 16 | @Test 17 | public void loads_default_configuration() throws Exception { 18 | Map options = new HashMap<>(); 19 | RequestRateConfiguration conf = RequestRateConfiguration.create(options); 20 | assertThat(conf.period()).isEqualTo(1); 21 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 22 | assertThat(conf.requestsToReport()).hasSize(1).contains("*:*"); 23 | } 24 | 25 | @Test 26 | public void provides_all_values() throws Exception { 27 | Map options = new HashMap<>(); 28 | options.put("period", 2); 29 | options.put("timeunit", "SECONDS"); 30 | options.put("requestsToReport", Arrays.asList("SELECT:ALL")); 31 | RequestRateConfiguration conf = RequestRateConfiguration.create(options); 32 | assertThat(conf.period()).isEqualTo(2); 33 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.SECONDS); 34 | assertThat(conf.requestsToReport()).hasSize(1).contains("SELECT:ALL"); 35 | } 36 | 37 | @Test 38 | public void fails_when_incorrect_values_provided() { 39 | Map options = new HashMap<>(); 40 | options.put("period", 2); 41 | options.put("timeunit", "ERR"); 42 | try { 43 | RequestRateConfiguration.create(options); 44 | } catch (Exception e) { 45 | assertThat(e).isInstanceOf(ConfigurationException.class); 46 | } 47 | } 48 | 49 | @Test 50 | public void fails_when_incorrect_requests_to_report() { 51 | Map options = new HashMap<>(); 52 | options.put("requestsToReport", Arrays.asList("SOMETHING")); 53 | try { 54 | RequestRateConfiguration.create(options); 55 | } catch (Exception e) { 56 | assertThat(e).isInstanceOf(ConfigurationException.class) 57 | .hasMessage("Only two configuration parameters supported, statement type and consistency level."); 58 | } 59 | } 60 | 61 | @Test 62 | public void fails_when_incorrect_statement_type() { 63 | Map options = new HashMap<>(); 64 | options.put("requestsToReport", Arrays.asList("SOMETHING:LOCAL_ONE")); 65 | try { 66 | RequestRateConfiguration.create(options); 67 | } catch (Exception e) { 68 | assertThat(e).isInstanceOf(ConfigurationException.class) 69 | .hasMessage("Illegal statement type configured: SOMETHING"); 70 | } 71 | } 72 | 73 | @Test 74 | public void fails_when_incorrect_consistency_level() { 75 | Map options = new HashMap<>(); 76 | options.put("requestsToReport", Arrays.asList("SELECT:SOMETHING")); 77 | try { 78 | RequestRateConfiguration.create(options); 79 | } catch (Exception e) { 80 | assertThat(e).isInstanceOf(ConfigurationException.class) 81 | .hasMessage("Illegal consistency level configured: SOMETHING"); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/slowquery/SlowQueryConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.slowquery; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.junit.Test; 11 | 12 | import io.smartcat.cassandra.diagnostics.config.ConfigurationException; 13 | 14 | public class SlowQueryConfigurationTest { 15 | 16 | @Test 17 | public void loads_default_configuration() throws Exception { 18 | Map options = new HashMap<>(); 19 | SlowQueryConfiguration conf = SlowQueryConfiguration.create(options); 20 | assertThat(conf.slowQueryThreshold()).isEqualTo(25); 21 | assertThat(conf.tablesForLogging()).isEmpty(); 22 | assertThat(conf.slowQueryReportEnabled()).isFalse(); 23 | assertThat(conf.slowQueryCountReportEnabled()).isTrue(); 24 | assertThat(conf.queryTypesToLog().size()).isEqualTo(1); 25 | assertThat(conf.queryTypesToLog().get(0)).isEqualTo("ALL"); 26 | } 27 | 28 | @Test 29 | public void loads_configuration_with_provided_values() throws Exception { 30 | Map options = new HashMap<>(); 31 | options.put("slowQueryThresholdInMilliseconds", 10); 32 | options.put("tablesForLogging", Arrays.asList("keyspace1.table1", "keyspace2.table2")); 33 | options.put("slowQueryReportEnabled", true); 34 | options.put("slowQueryCountReportEnabled", false); 35 | options.put("queryTypesToLog", Arrays.asList("UPDATE", "SELECT")); 36 | 37 | SlowQueryConfiguration conf = SlowQueryConfiguration.create(options); 38 | assertThat(conf.slowQueryThreshold()).isEqualTo(10); 39 | assertThat(conf.tablesForLogging().size()).isEqualTo(2); 40 | assertThat(conf.tablesForLogging()).contains("keyspace1.table1"); 41 | assertThat(conf.tablesForLogging()).contains("keyspace2.table2"); 42 | assertThat(conf.slowQueryReportEnabled()).isTrue(); 43 | assertThat(conf.slowQueryCountReportEnabled()).isFalse(); 44 | assertThat(conf.queryTypesToLog().size()).isEqualTo(2); 45 | assertThat(conf.queryTypesToLog()).contains("UPDATE", "SELECT"); 46 | } 47 | 48 | @Test 49 | public void fails_when_incorrect_values_provided() { 50 | Map options = new HashMap<>(); 51 | options.put("slowQueryThresholdInMilliseconds", 11); 52 | options.put("tablesForLogging", Arrays.asList("keyspace.table")); 53 | options.put("extra", "value"); 54 | 55 | try { 56 | SlowQueryConfiguration.create(options); 57 | fail("Configuration loading should fail beacuse of incorrect input values."); 58 | } catch (ConfigurationException e) { 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/java/io/smartcat/cassandra/diagnostics/module/status/StatusConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.module.status; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.junit.Test; 10 | import org.yaml.snakeyaml.constructor.ConstructorException; 11 | 12 | /** 13 | * Status module configuration test. 14 | */ 15 | public class StatusConfigurationTest { 16 | 17 | @Test 18 | public void loads_default_configuration() throws Exception { 19 | Map options = new HashMap<>(); 20 | StatusConfiguration conf = StatusConfiguration.create(options); 21 | assertThat(conf.period()).isEqualTo(1); 22 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.MINUTES); 23 | assertThat(conf.compactionsEnabled()).isFalse(); 24 | assertThat(conf.tpStatsEnabled()).isFalse(); 25 | assertThat(conf.repairsEnabled()).isFalse(); 26 | } 27 | 28 | @Test 29 | public void provides_all_values() throws Exception { 30 | Map options = new HashMap<>(); 31 | options.put("period", 2); 32 | options.put("timeunit", "HOURS"); 33 | options.put("compactionsEnabled", true); 34 | options.put("tpStatsEnabled", true); 35 | options.put("repairsEnabled", true); 36 | options.put("nodeInfoEnabled", true); 37 | StatusConfiguration conf = StatusConfiguration.create(options); 38 | assertThat(conf.period()).isEqualTo(2); 39 | assertThat(conf.timeunit()).isEqualTo(TimeUnit.HOURS); 40 | assertThat(conf.compactionsEnabled()).isTrue(); 41 | assertThat(conf.tpStatsEnabled()).isTrue(); 42 | assertThat(conf.repairsEnabled()).isTrue(); 43 | assertThat(conf.nodeInfoEnabled()).isTrue(); 44 | } 45 | 46 | @Test 47 | public void fails_when_incorrect_values_provided() { 48 | Map options = new HashMap<>(); 49 | options.put("period", 2); 50 | options.put("timeunit", "ERR"); 51 | options.put("compactionsEnabled", "123"); 52 | try { 53 | StatusConfiguration.create(options); 54 | } catch (Exception e) { 55 | assertThat(e).isInstanceOf(ConstructorException.class); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/resources/invalid-cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | # Reporters 2 | reporters: 3 | options: 4 | - a 5 | - b 6 | -------------------------------------------------------------------------------- /cassandra-diagnostics-core/src/test/resources/valid-cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | global: 2 | hostname: "test-hostname" 3 | systemName: "smartcat-cassandra-cluster" 4 | 5 | httpApiEnabled: true 6 | httpApiHost: 10.0.0.1 7 | httpApiPort: 8001 8 | 9 | connector: 10 | jmxHost: 10.0.0.1 11 | jmxPort: 8888 12 | jmxAuthEnabled: true 13 | jmxUsername: username #Optional 14 | jmxPassword: password #Optional 15 | 16 | # Reporters 17 | reporters: 18 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 19 | - reporter: io.smartcat.cassandra.diagnostics.reporter.RiemannReporter 20 | options: 21 | riemannHost: 127.0.0.1 22 | riemannPort: 5555 #Optional 23 | - reporter: io.smartcat.cassandra.diagnostics.reporter.InfluxReporter 24 | options: 25 | influxDbAddress: http://127.0.0.1:8086 26 | influxUsername: username #Optional 27 | influxPassword: password #Optional 28 | influxDbName: cassandradb #Optional 29 | influxRetentionPolicy: default #Optional 30 | influxPointsInBatch: 1000 #optional 31 | influxFlushPeriodInSeconds: 5 #optional 32 | 33 | # Modules 34 | modules: 35 | - module: io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule 36 | options: 37 | period: 15 38 | timeunit: MINUTES 39 | reporters: 40 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 41 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 42 | measurement: queryReport 43 | options: 44 | # Slow query threshold 45 | slowQueryThresholdInMilliseconds: 25 46 | - module: io.smartcat.cassandra.diagnostics.module.requestrate.RequestRateModule 47 | measurement: requestRate 48 | options: 49 | period: 1 50 | timeunit: SECONDS 51 | reporters: 52 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 53 | - io.smartcat.cassandra.diagnostics.reporter.InfluxReporter 54 | -------------------------------------------------------------------------------- /cassandra-diagnostics-driver-connector/README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Diagnostics Connector for Cassandra Driver 2 | 3 | Connector is a module which hooks into the query path and extract information for diagnostics. Bytecode instrumentation is used to augment existing Cassandra code with additional functionality. It uses low priority threads to execute the diagnostics information extraction with minimal performance impact to the target code (Cassandra node or application/driver). 4 | 5 | ## Configuration 6 | 7 | Configuration options for driver connector: 8 | 9 | - `numWorkerThreads` - The number of worker threads that asynchronously process diagnostics events. 10 | - `queuedEventsOverflowThreshold` - Configured threshold for queue size, above this threshold all events will be dropped until the number of queued events is dropped to `queuedEventsRelaxThreshold`. 11 | - `queuedEventsRelaxThreshold` - Lower threshold bound for event queue size. After the queue was previously in overflow state, new events will be queued only when the number of queued events drop below this value. 12 | 13 | The connector comes with sensible default values: 14 | 15 | ```yaml 16 | connector: 17 | numWorkerThreads: 2 # optional 18 | queuedEventsOverflowThreshold: 1000 # optional 19 | queuedEventsRelaxThreshold: 700 # optional 20 | ``` 21 | -------------------------------------------------------------------------------- /cassandra-diagnostics-driver-connector/src/integration-test/java/io/smartcat/cassandra/diagnostics/connector/ITConnector.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.connector; 2 | 3 | import java.io.IOException; 4 | import java.lang.instrument.Instrumentation; 5 | import java.util.concurrent.CountDownLatch; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.apache.cassandra.exceptions.ConfigurationException; 9 | import org.apache.thrift.transport.TTransportException; 10 | import org.junit.Assert; 11 | import org.junit.BeforeClass; 12 | import org.junit.Test; 13 | import org.springframework.instrument.InstrumentationSavingAgent; 14 | 15 | import com.datastax.driver.core.Cluster; 16 | import com.datastax.driver.core.Session; 17 | 18 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 19 | import io.smartcat.cassandra.diagnostics.Query; 20 | import io.smartcat.cassandra.utils.EmbeddedCassandraServerHelper; 21 | 22 | public class ITConnector { 23 | 24 | private static Cluster cluster; 25 | private static Session session; 26 | private static CountDownLatch lock = new CountDownLatch(1); 27 | private static boolean queryIntercepted; 28 | 29 | @BeforeClass 30 | public static void setUp() throws ConfigurationException, TTransportException, IOException, InterruptedException { 31 | queryIntercepted = false; 32 | Instrumentation inst = InstrumentationSavingAgent.getInstrumentation(); 33 | ConnectorConfiguration configuration = new ConnectorConfiguration(); 34 | Connector connector = new ConnectorImpl(); 35 | connector.init(inst, new QueryReporter() { 36 | @Override 37 | public void report(Query query) { 38 | if (Query.StatementType.SELECT.equals(query.statementType()) && 39 | query.statement().toLowerCase().startsWith("select * from test_keyspace.test_table")) { 40 | queryIntercepted = true; 41 | lock.countDown(); 42 | } 43 | } 44 | }, configuration, GlobalConfiguration.getDefault()); 45 | EmbeddedCassandraServerHelper.startEmbeddedCassandra(); 46 | cluster = Cluster.builder().addContactPoint("127.0.0.1").withPort(9142).build(); 47 | session = cluster.connect(); 48 | } 49 | 50 | @Test 51 | public void query_is_intercepted_when_connector_is_active() throws InterruptedException { 52 | session.execute("CREATE KEYSPACE IF NOT EXISTS test_keyspace " 53 | + "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); 54 | session.execute("CREATE TABLE IF NOT EXISTS test_keyspace.test_table (uid uuid PRIMARY KEY);"); 55 | session.execute("SELECT * FROM test_keyspace.test_table"); 56 | cluster.close(); 57 | lock.await(2000, TimeUnit.MILLISECONDS); 58 | Assert.assertTrue(queryIntercepted); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /cassandra-diagnostics-driver-connector/src/integration-test/resources/log4j-embedded-cassandra.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, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # for production, you should probably set the root to INFO 18 | # and the pattern to %c instead of %l. (%l is slower.) 19 | 20 | # output messages into a rolling log file as well as stdout 21 | log4j.rootLogger=ERROR,stdout,HColumnFamilyLogger 22 | 23 | # stdout 24 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 25 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 26 | log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c{3} - %m%n 27 | log4j.appender.stdout.follow=true 28 | 29 | log4j.appender.HColumnFamilyLogger=org.apache.log4j.ConsoleAppender 30 | log4j.appender.HColumnFamilyLogger.layout=org.apache.log4j.PatternLayout 31 | log4j.appender.HColumnFamilyLogger.layout.ConversionPattern=%m%n 32 | log4j.category.HColumnFamilyLogger=DEBUG 33 | #log4j.category.org.apache=INFO, stdout 34 | -------------------------------------------------------------------------------- /cassandra-diagnostics-driver-connector/src/main/resources/META-INF/services/io.smartcat.cassandra.diagnostics.connector.Connector: -------------------------------------------------------------------------------- 1 | io.smartcat.cassandra.diagnostics.connector.ConnectorImpl 2 | -------------------------------------------------------------------------------- /cassandra-diagnostics-embedded-cassandra/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.smartcat 6 | cassandra-diagnostics 7 | 1.4.11-SNAPSHOT 8 | 9 | cassandra-diagnostics-embedded-cassandra 10 | jar 11 | 12 | cassandra-diagnostics-embedded-cassandra 13 | 14 | 15 | 2.1.17 16 | 2.1.10.3 17 | 18 | 19 | 20 | 21 | org.apache.cassandra 22 | cassandra-all 23 | ${version.cassandra} 24 | provided 25 | 26 | 27 | com.datastax.cassandra 28 | cassandra-driver-core 29 | ${version.cassandra.driver} 30 | provided 31 | 32 | 33 | junit 34 | junit 35 | 3.8.1 36 | test 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/basic2/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 3 | 4 | modules: 5 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 6 | measurement: queryReport 7 | options: 8 | slowQueryThresholdInMilliseconds: 1 9 | slowQueryReportEnabled: true 10 | slowQueryCountReportEnabled: true 11 | slowQueryCountReportPeriod: 1 12 | slowQueryCountReportTimeunit: SECONDS 13 | tablesForLogging: #optional 14 | - test_keyspace.test_table 15 | queryTypesToLog: 16 | - ALL 17 | reporters: 18 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 19 | - module: io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule 20 | measurement: heartbeat 21 | options: 22 | period: 1 23 | timeunit: SECONDS 24 | reporters: 25 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 26 | - module: io.smartcat.cassandra.diagnostics.module.requestrate.RequestRateModule 27 | measurement: request_rate 28 | options: 29 | period: 1 30 | timeunit: SECONDS 31 | reporters: 32 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 33 | - module: io.smartcat.cassandra.diagnostics.module.status.StatusModule 34 | options: 35 | period: 1 36 | timeunit: SECONDS 37 | compactionsEnabled: true 38 | tpStatsEnabled: true 39 | repairsEnabled: true 40 | nodeInfoEnabled: true 41 | reporters: 42 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 43 | - module: io.smartcat.cassandra.diagnostics.module.health.ClusterHealthModule 44 | options: 45 | period: 1 46 | timeunit: SECONDS 47 | numberOfUnreachableNodesEnabled: true #optional 48 | reporters: 49 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 50 | - module: io.smartcat.cassandra.diagnostics.module.hiccup.HiccupModule 51 | options: 52 | period: 1 53 | timeunit: SECONDS 54 | startDelayInMs: 0 55 | reporters: 56 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 57 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/basic3/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 3 | 4 | modules: 5 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 6 | measurement: queryReport 7 | options: 8 | slowQueryThresholdInMilliseconds: 1 9 | slowQueryReportEnabled: true 10 | slowQueryCountReportEnabled: true 11 | slowQueryCountReportPeriod: 1 12 | slowQueryCountReportTimeunit: SECONDS 13 | tablesForLogging: #optional 14 | - test_keyspace.test_table 15 | queryTypesToLog: 16 | - ALL 17 | reporters: 18 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 19 | - module: io.smartcat.cassandra.diagnostics.module.heartbeat.HeartbeatModule 20 | measurement: heartbeat 21 | options: 22 | period: 1 23 | timeunit: SECONDS 24 | reporters: 25 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 26 | - module: io.smartcat.cassandra.diagnostics.module.requestrate.RequestRateModule 27 | measurement: request_rate 28 | options: 29 | period: 1 30 | timeunit: SECONDS 31 | reporters: 32 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 33 | - module: io.smartcat.cassandra.diagnostics.module.status.StatusModule 34 | options: 35 | period: 1 36 | timeunit: SECONDS 37 | compactionsEnabled: true 38 | tpStatsEnabled: true 39 | repairsEnabled: true 40 | nodeInfoEnabled: true 41 | reporters: 42 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 43 | - module: io.smartcat.cassandra.diagnostics.module.health.ClusterHealthModule 44 | options: 45 | period: 1 46 | timeunit: SECONDS 47 | numberOfUnreachableNodesEnabled: true #optional 48 | reporters: 49 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 50 | - module: io.smartcat.cassandra.diagnostics.module.hiccup.HiccupModule 51 | options: 52 | period: 1 53 | timeunit: SECONDS 54 | startDelayInMs: 0 55 | reporters: 56 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 57 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/influx/src/functional-test/java/io/smartcat/cassandra/diagnostics/ft/influx/FTInflux.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.ft.influx; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileSystem; 5 | import java.nio.file.FileSystems; 6 | import java.nio.file.Path; 7 | import java.nio.file.StandardWatchEventKinds; 8 | import java.nio.file.WatchService; 9 | 10 | import org.apache.cassandra.exceptions.ConfigurationException; 11 | import org.apache.thrift.transport.TTransportException; 12 | import org.assertj.core.api.Assertions; 13 | import org.influxdb.InfluxDB; 14 | import org.influxdb.InfluxDBFactory; 15 | import org.influxdb.dto.Query; 16 | import org.influxdb.dto.QueryResult; 17 | import org.junit.BeforeClass; 18 | import org.junit.Test; 19 | 20 | import com.datastax.driver.core.Cluster; 21 | import com.datastax.driver.core.Session; 22 | 23 | import io.netty.util.internal.SystemPropertyUtil; 24 | 25 | public class FTInflux { 26 | 27 | private static Cluster cluster; 28 | private static Session session; 29 | private static InfluxDB influxdb; 30 | 31 | @BeforeClass 32 | public static void setUp() throws ConfigurationException, TTransportException, IOException, InterruptedException { 33 | cluster = Cluster.builder() 34 | .addContactPoint(SystemPropertyUtil.get("cassandra.host")) 35 | .withPort(Integer.parseInt(SystemPropertyUtil.get("cassandra.port"))) 36 | .build(); 37 | session = cluster.connect(); 38 | 39 | influxdb = InfluxDBFactory.connect(SystemPropertyUtil.get("influxdb.url"), 40 | SystemPropertyUtil.get("influxdb.user"), SystemPropertyUtil.get("influxdb.password")); 41 | } 42 | 43 | @Test 44 | public void test() throws Exception { 45 | FileSystem fileSystem = FileSystems.getDefault(); 46 | WatchService watcher = fileSystem.newWatchService(); 47 | Path logFileDir = fileSystem.getPath(SystemPropertyUtil.get("project.build.directory")); 48 | logFileDir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); 49 | 50 | session.execute("CREATE KEYSPACE IF NOT EXISTS test_keyspace " 51 | + "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); 52 | session.execute("CREATE TABLE IF NOT EXISTS test_keyspace.test_table (uid uuid PRIMARY KEY);"); 53 | 54 | session.execute("SELECT * FROM test_keyspace.test_table"); 55 | 56 | QueryResult result = null; 57 | for (int i = 0; i < 10; i++) { 58 | result = influxdb.query(new Query("SHOW SERIES FROM \"queryReport\"", SystemPropertyUtil.get("influxdb.dbname"))); 59 | if (!result.hasError()) { 60 | break; 61 | } 62 | Thread.sleep(500); 63 | } 64 | 65 | Assertions.assertThat(result.getResults().size()).isEqualTo(1); 66 | cluster.close(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/influx/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.InfluxReporter 3 | options: 4 | influxDbAddress: http://influxdb:8086 5 | influxUsername: admin 6 | influxPassword: secret 7 | influxDbName: diagnostics 8 | influxRetentionPolicy: autogen 9 | influxPointsInBatch: 1 10 | influxFlushPeriodInSeconds: 1 11 | 12 | modules: 13 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 14 | measurement: queryReport 15 | options: 16 | slowQueryThresholdInMilliseconds: 0 17 | reporters: 18 | - io.smartcat.cassandra.diagnostics.reporter.InfluxReporter 19 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/metrics/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 3 | 4 | modules: 5 | - module: io.smartcat.cassandra.diagnostics.module.metrics.MetricsModule 6 | measurement: metrics 7 | options: 8 | period: 1 9 | timeunit: SECONDS 10 | metricsPatterns: 11 | - "^org.apache.cassandra.metrics.Cache.+" 12 | - "^org.apache.cassandra.metrics.Keyspace.rts.+" 13 | - "^org.apache.cassandra.metrics.ThreadPools.+" 14 | - "^org.apache.cassandra.metrics.Compaction.+" 15 | - "^org.apache.cassandra.metrics.CommitLog.TotalCommitLogSize" 16 | - "^org.apache.cassandra.metrics.DroppedMessage.+" 17 | - "^org.apache.cassandra.metrics.Client.+" 18 | reporters: 19 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 20 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/riemann/src/functional-test/java/io/smartcat/cassandra/diagnostics/ft/riemann/FTRiemann.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.ft.riemann; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileSystem; 5 | import java.nio.file.FileSystems; 6 | import java.nio.file.Path; 7 | import java.nio.file.StandardWatchEventKinds; 8 | import java.nio.file.WatchService; 9 | 10 | import org.apache.cassandra.exceptions.ConfigurationException; 11 | import org.apache.thrift.transport.TTransportException; 12 | import org.assertj.core.api.Assertions; 13 | import org.influxdb.InfluxDB; 14 | import org.influxdb.InfluxDBFactory; 15 | import org.influxdb.dto.Query; 16 | import org.influxdb.dto.QueryResult; 17 | import org.junit.BeforeClass; 18 | import org.junit.Test; 19 | 20 | import com.datastax.driver.core.Cluster; 21 | import com.datastax.driver.core.Session; 22 | 23 | import io.netty.util.internal.SystemPropertyUtil; 24 | 25 | public class FTRiemann { 26 | 27 | private static final String INFLUXDB_NAME = "diagnostics-test"; 28 | 29 | private static Cluster cluster; 30 | private static Session session; 31 | private static InfluxDB influxdb; 32 | 33 | @BeforeClass 34 | public static void setUp() throws ConfigurationException, TTransportException, IOException, InterruptedException { 35 | cluster = Cluster.builder() 36 | .addContactPoint(SystemPropertyUtil.get("cassandra.host")) 37 | .withPort(Integer.parseInt(SystemPropertyUtil.get("cassandra.port"))) 38 | .build(); 39 | session = cluster.connect(); 40 | 41 | influxdb = InfluxDBFactory.connect(SystemPropertyUtil.get("influxdb.url"), 42 | SystemPropertyUtil.get("influxdb.user"), SystemPropertyUtil.get("influxdb.password")); 43 | influxdb.createDatabase(INFLUXDB_NAME); 44 | } 45 | 46 | @Test 47 | public void test() throws Exception { 48 | FileSystem fileSystem = FileSystems.getDefault(); 49 | WatchService watcher = fileSystem.newWatchService(); 50 | Path logFileDir = fileSystem.getPath(SystemPropertyUtil.get("project.build.directory")); 51 | logFileDir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); 52 | 53 | session.execute("CREATE KEYSPACE IF NOT EXISTS test_keyspace " 54 | + "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); 55 | session.execute("CREATE TABLE IF NOT EXISTS test_keyspace.test_table (uid uuid PRIMARY KEY);"); 56 | 57 | session.execute("SELECT * FROM test_keyspace.test_table"); 58 | 59 | QueryResult result = null; 60 | for (int i = 0; i < 10; i++) { 61 | result = influxdb.query(new Query("SHOW SERIES FROM \"queryReport\"", INFLUXDB_NAME)); 62 | if (!result.hasError()) { 63 | break; 64 | } 65 | Thread.sleep(500); 66 | } 67 | 68 | Assertions.assertThat(result.getResults().size()).isEqualTo(1); 69 | cluster.close(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/riemann/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.RiemannReporter 3 | options: 4 | riemannHost: riemann 5 | riemannPort: 5555 6 | batchEventSize: 1 7 | 8 | modules: 9 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 10 | measurement: queryReport 11 | options: 12 | slowQueryThresholdInMilliseconds: 0 13 | reporters: 14 | - io.smartcat.cassandra.diagnostics.reporter.RiemannReporter 15 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/riemann/src/functional-test/resources/riemann.config: -------------------------------------------------------------------------------- 1 | ; -*- mode: clojure; -*- 2 | ; vim: filetype=clojure 3 | 4 | (logging/init {:console true}) 5 | 6 | ; Listen on the local interface over TCP (5555), UDP (5555), and websockets 7 | ; (5556) 8 | (let [host "0.0.0.0"] 9 | (tcp-server {:host host}) 10 | (udp-server {:host host}) 11 | (ws-server {:host host})) 12 | 13 | ; Expire old events from the index every 5 seconds. 14 | (periodically-expire 5) 15 | 16 | (def influx 17 | (batch 100 1/10 18 | (async-queue! :agg {:queue-size 1000 19 | :core-pool-size 1 20 | :max-pool-size 4 21 | :keep-alive-time 60000} 22 | 23 | (influxdb {:host "influxdb" 24 | :version :0.9 25 | :port 8086 26 | :db "diagnostics-test" 27 | :username "admin" 28 | :password "secret" 29 | :tag-fields #{:host :id :statementType}})))) 30 | 31 | ; Divide timestamp with 1000 if it is sent as ms to correctly handle ms precision 32 | (defn autoscale [ts] 33 | (if (> ts 1000000000000) (/ ts 1000) ts)) 34 | 35 | (let [index (index)] 36 | (streams 37 | (default :ttl 60 38 | 39 | (adjust [:time autoscale] 40 | index 41 | 42 | influx 43 | 44 | (expired 45 | (fn [event] (info "expired" event))) )))) -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/telegraf/src/functional-test/java/io/smartcat/cassandra/diagnostics/ft/telegraf/FTTelegraf.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.ft.telegraf; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileSystem; 5 | import java.nio.file.FileSystems; 6 | import java.nio.file.Path; 7 | import java.nio.file.StandardWatchEventKinds; 8 | import java.nio.file.WatchService; 9 | 10 | import org.apache.cassandra.exceptions.ConfigurationException; 11 | import org.apache.thrift.transport.TTransportException; 12 | import org.assertj.core.api.Assertions; 13 | import org.influxdb.InfluxDB; 14 | import org.influxdb.InfluxDBFactory; 15 | import org.influxdb.dto.Query; 16 | import org.influxdb.dto.QueryResult; 17 | import org.junit.BeforeClass; 18 | import org.junit.Test; 19 | 20 | import com.datastax.driver.core.Cluster; 21 | import com.datastax.driver.core.Session; 22 | 23 | import io.netty.util.internal.SystemPropertyUtil; 24 | 25 | public class FTTelegraf { 26 | 27 | private static final String INFLUXDB_NAME = "diagnostics-test"; 28 | 29 | private static Cluster cluster; 30 | private static Session session; 31 | private static InfluxDB influxdb; 32 | 33 | @BeforeClass 34 | public static void setUp() throws ConfigurationException, TTransportException, IOException, InterruptedException { 35 | System.out.println("Connecting to " + SystemPropertyUtil.get("cassandra.host") + ":" + SystemPropertyUtil.get("cassandra.port")); 36 | cluster = Cluster.builder() 37 | .addContactPoint(SystemPropertyUtil.get("cassandra.host")) 38 | .withPort(Integer.parseInt(SystemPropertyUtil.get("cassandra.port"))) 39 | .build(); 40 | session = cluster.connect(); 41 | 42 | influxdb = InfluxDBFactory.connect(SystemPropertyUtil.get("influxdb.url"), 43 | SystemPropertyUtil.get("influxdb.user"), SystemPropertyUtil.get("influxdb.password")); 44 | influxdb.createDatabase(INFLUXDB_NAME); 45 | } 46 | 47 | @Test 48 | public void test() throws Exception { 49 | FileSystem fileSystem = FileSystems.getDefault(); 50 | WatchService watcher = fileSystem.newWatchService(); 51 | Path logFileDir = fileSystem.getPath(SystemPropertyUtil.get("project.build.directory")); 52 | logFileDir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); 53 | 54 | session.execute("CREATE KEYSPACE IF NOT EXISTS test_keyspace " 55 | + "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); 56 | session.execute("CREATE TABLE IF NOT EXISTS test_keyspace.test_table (uid uuid PRIMARY KEY);"); 57 | 58 | session.execute("SELECT * FROM test_keyspace.test_table"); 59 | 60 | QueryResult result = null; 61 | for (int i = 0; i < 10; i++) { 62 | result = influxdb.query(new Query("SHOW SERIES FROM \"queryReport\"", INFLUXDB_NAME)); 63 | if (!result.hasError()) { 64 | break; 65 | } 66 | Thread.sleep(500); 67 | } 68 | 69 | Assertions.assertThat(result.getResults().size()).isEqualTo(1); 70 | 71 | cluster.close(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/telegraf/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | reporters: 2 | - reporter: io.smartcat.cassandra.diagnostics.reporter.TelegrafReporter 3 | options: 4 | telegrafHost: telegraf 5 | telegrafPort: 8094 6 | 7 | modules: 8 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 9 | measurement: queryReport 10 | options: 11 | slowQueryThresholdInMilliseconds: 0 12 | reporters: 13 | - io.smartcat.cassandra.diagnostics.reporter.TelegrafReporter 14 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/telegraf/src/functional-test/resources/telegraf.conf: -------------------------------------------------------------------------------- 1 | # Global tags can be specified here in key="value" format. 2 | [global_tags] 3 | # dc = "us-east-1" # will tag all metrics with dc=us-east-1 4 | # rack = "1a" 5 | ## Environment variables can be used as tags, and throughout the config file 6 | # user = "$USER" 7 | 8 | 9 | # Configuration for telegraf agent 10 | [agent] 11 | interval = "10s" 12 | round_interval = true 13 | 14 | metric_batch_size = 100 15 | metric_buffer_limit = 10000 16 | 17 | collection_jitter = "0s" 18 | 19 | flush_interval = "10s" 20 | flush_jitter = "0s" 21 | 22 | precision = "" 23 | debug = true 24 | quiet = false 25 | hostname = "" 26 | omit_hostname = false 27 | 28 | ############################################################################### 29 | # OUTPUT PLUGINS # 30 | ############################################################################### 31 | 32 | [[outputs.influxdb]] 33 | urls = ["http://influx:8086"] 34 | database = "diagnostics" 35 | 36 | retention_policy = "" 37 | write_consistency = "any" 38 | 39 | timeout = "5s" 40 | # username = "telegraf" 41 | # password = "metricsmetricsmetricsmetrics" 42 | ## Set the user agent for HTTP POSTs (can be useful for log differentiation) 43 | # user_agent = "telegraf" 44 | ## Set UDP payload size, defaults to InfluxDB UDP Client default (512 bytes) 45 | # udp_payload = 512 46 | 47 | ############################################################################### 48 | # INPUT PLUGINS # 49 | ############################################################################### 50 | 51 | # # Generic TCP listener 52 | [[inputs.tcp_listener]] 53 | service_address = ":8094" 54 | allowed_pending_messages = 10000 55 | max_tcp_connections = 250 56 | data_format = "influx" 57 | 58 | 59 | -------------------------------------------------------------------------------- /cassandra-diagnostics-ft/tracing/src/functional-test/resources/cassandra-diagnostics.yml: -------------------------------------------------------------------------------- 1 | connector: 2 | enableTracing: true 3 | 4 | reporters: 5 | - reporter: io.smartcat.cassandra.diagnostics.reporter.LogReporter 6 | 7 | modules: 8 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 9 | measurement: queryReport 10 | options: 11 | slowQueryThresholdInMilliseconds: 1 12 | slowQueryReportEnabled: true 13 | slowQueryCountReportEnabled: false 14 | tablesForLogging: #optional 15 | - test_keyspace.test_table 16 | queryTypesToLog: 17 | - ALL 18 | reporters: 19 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 20 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-datadog/README.md: -------------------------------------------------------------------------------- 1 | # Datadog reporter 2 | 3 | [Datadog reporter](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-reporter-datadog/src/main/java/io/smartcat/cassandra/diagnostics/reporter/DatadogReporter.java) sends measurements towards the configured [Datadog agent](https://github.com/DataDog/dd-agent) using UDP transport. 4 | 5 | Datadog reporter has the following configuration parameters (that can be specified using `options`): 6 | 7 | - _statsDHost_ - Datadog statsd server host name (IP address). This parameter is required. 8 | - _statsDPort_ - Datadog statsd server UDP port number (8125 by default). This parameter is optional. 9 | - _keysPrefix_ - Datadog measurement prefix (empty string by default). This parameter is optional. 10 | - _fixedTags_ - Datadog measurement tags applied to all measurements (default none). This parameter is optional. 11 | 12 | Here is an example configuration that uses Datadog reporter: 13 | 14 | ``` 15 | reporters: 16 | - reporter: io.smartcat.cassandra.diagnostics.reporter.DatadogReporter 17 | options: 18 | statsDHost: localhost 19 | statsDPort: 8125 #Optional 20 | keysPrefix: test #Optional 21 | fixedTags: 22 | - tag1:val1 23 | - tag2:val2 24 | - tag3:val3 25 | 26 | modules: 27 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 28 | measurement: slow_query 29 | options: 30 | slowQueryThresholdInMilliseconds: 10 31 | reporters: 32 | - io.smartcat.cassandra.diagnostics.reporter.DatadogReporter 33 | ``` -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-datadog/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cassandra-diagnostics 6 | io.smartcat 7 | 1.4.11-SNAPSHOT 8 | 9 | cassandra-diagnostics-reporter-datadog 10 | 11 | 12 | UTF-8 13 | 2.3 14 | 1.6.5 15 | 16 | 17 | 18 | 19 | io.smartcat 20 | cassandra-diagnostics-commons 21 | ${project.parent.version} 22 | 23 | 24 | com.datadoghq 25 | java-dogstatsd-client 26 | ${dogstatsd.client.version} 27 | 28 | 29 | junit 30 | junit 31 | test 32 | 33 | 34 | org.assertj 35 | assertj-core 36 | test 37 | 38 | 39 | org.mockito 40 | mockito-all 41 | test 42 | 43 | 44 | org.powermock 45 | powermock-module-junit4 46 | ${version.powermock} 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-shade-plugin 56 | 57 | 58 | package 59 | 60 | shade 61 | 62 | 63 | true 64 | all 65 | 66 | 67 | com.datadoghq:java-dogstatsd-client:* 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-influx/README.md: -------------------------------------------------------------------------------- 1 | # Influx reporter 2 | 3 | [InfluxReporter](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-reporter-influx/src/main/java/io/smartcat/cassandra/diagnostics/reporter/InfluxReporter.java) sends measurements to [Influx database](https://www.influxdata.com/time-series-platform/influxdb/). 4 | 5 | Influx DB statement holds name of measurement, tags connected to this measurement, fields and timestamp of measurement in following format: 6 | 7 | ``` 8 | ,id=,statementType= value= 9 | ``` 10 | 11 | Influx reporter has the following configuration parameters: 12 | 13 | - _influxDbAddress_: InfluxDB endpoint address (http://127.0.0.1:8086 by default). This parameter is optional. 14 | - _influxUsername_: Authentication username. This parameter is optional but should be set together with _influxPassword_. 15 | - _influxPassword_: Authentication passowrd. This parameter is optional but should be set together with _influxUsername_. 16 | - _influxDbName_: Database name (cassandradb by default). This parameter is optional. 17 | - _influxRetentionPolicy_: Retention policy (`default` by default). This parameter is optional. 18 | - _influxPointsInBatch_: Max points in batch before flush (1000 by default). This parameter is optional. 19 | - _influxFlushPeriodInSeconds_: Max period in seconds between time based flush (5 by default). This parameter is optional. 20 | 21 | Here is an example configuration that uses Influx reporter: 22 | ``` 23 | reporters: 24 | - reporter: io.smartcat.cassandra.diagnostics.reporter.InfluxReporter 25 | options: 26 | influxDbAddress: http://127.0.0.1:8086 27 | influxUsername: admin 28 | influxPassword: password 29 | influxDbName: cassandradb 30 | influxRetentionPolicy: default 31 | influxPointsInBatch: 1000 32 | influxFlushPeriodInSeconds: 5 33 | 34 | modules: 35 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 36 | measurement: slow_query 37 | options: 38 | slowQueryThresholdInMilliseconds: 10 39 | reporters: 40 | - io.smartcat.cassandra.diagnostics.reporter.TelegrafReporter 41 | ``` -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-influx/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.smartcat 6 | cassandra-diagnostics 7 | 1.4.11-SNAPSHOT 8 | 9 | cassandra-diagnostics-reporter-influx 10 | 11 | 12 | 2.5 13 | 14 | 15 | 16 | 17 | io.smartcat 18 | cassandra-diagnostics-commons 19 | ${project.parent.version} 20 | 21 | 22 | org.slf4j 23 | slf4j-api 24 | 25 | 26 | org.influxdb 27 | influxdb-java 28 | ${version.influxdb-java} 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-shade-plugin 37 | 38 | 39 | package 40 | 41 | shade 42 | 43 | 44 | true 45 | all 46 | 47 | 48 | org.influxdb:influxdb-java:* 49 | com.squareup.retrofit2:retrofit:* 50 | com.squareup.retrofit2:converter-moshi:* 51 | com.google.code.gson:gson:* 52 | com.squareup.okhttp3:okhttp:* 53 | com.squareup.okhttp3:logging-interceptor:* 54 | com.squareup.moshi:moshi:* 55 | com.squareup.okio:okio:* 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-kafka/README.md: -------------------------------------------------------------------------------- 1 | # Kafka reporter 2 | 3 | [KafkaReporter](src/main/java/io/smartcat/cassandra/diagnostics/reporter/KafkaReporter.java) sends measurements to a [Kafka](https://kafka.apache.org/) topic. 4 | 5 | Measurements objects are sent do designated Kafka topic. The message payload is JSON-serialized Measurement object and the message key is created using hostname and systemName config parameters (serialized by Kafka's StringSerializer). 6 | 7 | Kafka reporter has the following configuration parameters: 8 | 9 | - _kafkaBootstrapServers_: A list of Kafka cluster bootstrap servers given as _host:port_. This parameter is mandatory. 10 | - _kafkaTopic_: The name of the destination Kafka topic. This parameter is mandatory. 11 | 12 | Here is an example configuration that uses Kafka reporter: 13 | 14 | ``` 15 | reporters: 16 | - reporter: io.smartcat.cassandra.diagnostics.reporter.KafkaReporter 17 | options: 18 | kafkaBootstrapServers: 10.0.0.20:9092,10.0.0.21:9092 19 | kafkaTopic: measurements 20 | 21 | modules: 22 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 23 | measurement: slow_query 24 | options: 25 | slowQueryThresholdInMilliseconds: 10 26 | reporters: 27 | - io.smartcat.cassandra.diagnostics.reporter.KafkaReporter 28 | ``` 29 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-kafka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.smartcat 8 | cassandra-diagnostics 9 | 1.4.11-SNAPSHOT 10 | 11 | 12 | cassandra-diagnostics-reporter-kafka 13 | 14 | 15 | 0.10.1.1 16 | 4.12 17 | 1.6.5 18 | 19 | 20 | 21 | 22 | io.smartcat 23 | cassandra-diagnostics-commons 24 | ${project.parent.version} 25 | 26 | 27 | org.apache.kafka 28 | kafka-clients 29 | ${version.kafka} 30 | 31 | 32 | junit 33 | junit 34 | ${version.junit} 35 | test 36 | 37 | 38 | org.powermock 39 | powermock-module-junit4 40 | ${version.powermock} 41 | test 42 | 43 | 44 | org.apache.kafka 45 | kafka_2.11 46 | ${version.kafka} 47 | test 48 | 49 | 50 | org.apache.kafka 51 | kafka_2.11 52 | ${version.kafka} 53 | test 54 | test 55 | 56 | 57 | org.apache.kafka 58 | kafka-clients 59 | ${version.kafka} 60 | test 61 | test 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-shade-plugin 70 | 71 | 72 | package 73 | 74 | shade 75 | 76 | 77 | true 78 | all 79 | 80 | 81 | org.apache.kafka:kafka-clients:* 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-kafka/src/main/java/io/smartcat/cassandra/diagnostics/reporter/KafkaReporter.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import java.util.Properties; 4 | 5 | import org.apache.kafka.clients.producer.KafkaProducer; 6 | import org.apache.kafka.clients.producer.Producer; 7 | import org.apache.kafka.clients.producer.ProducerConfig; 8 | import org.apache.kafka.clients.producer.ProducerRecord; 9 | import org.apache.kafka.common.serialization.StringSerializer; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 14 | import io.smartcat.cassandra.diagnostics.Measurement; 15 | 16 | /** 17 | * Apache Kafka based {@link Reporter} implementation. All measurements are written into Kafka based on measurements 18 | * name and time of the measurement. 19 | */ 20 | public class KafkaReporter extends Reporter { 21 | /** 22 | * Class logger. 23 | */ 24 | private static final Logger logger = LoggerFactory.getLogger(KafkaReporter.class); 25 | 26 | private static final String SERVERS_PROP = "kafkaBootstrapServers"; 27 | private static final String TOPIC_PROP = "kafkaTopic"; 28 | 29 | private static Producer producer; 30 | 31 | private String partitionKey; 32 | private String topic; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param configuration Reporter configuration 38 | * @param globalConfiguration Global diagnostics configuration 39 | */ 40 | public KafkaReporter(ReporterConfiguration configuration, GlobalConfiguration globalConfiguration) { 41 | super(configuration, globalConfiguration); 42 | 43 | final String servers = configuration.getDefaultOption(SERVERS_PROP, ""); 44 | if (servers.isEmpty()) { 45 | logger.warn("Missing required property " + SERVERS_PROP + ". Aborting initialization."); 46 | return; 47 | } 48 | 49 | topic = configuration.getDefaultOption(TOPIC_PROP, ""); 50 | if (topic.isEmpty()) { 51 | logger.warn("Missing required property " + TOPIC_PROP + ". Aborting initialization."); 52 | return; 53 | } 54 | 55 | final Properties properties = new Properties(); 56 | properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); 57 | properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 58 | properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 59 | 60 | producer = new KafkaProducer<>(properties); 61 | partitionKey = globalConfiguration.systemName + "_" + globalConfiguration.hostname; 62 | } 63 | 64 | @Override 65 | public void report(Measurement measurement) { 66 | if (producer == null) { 67 | logger.warn("Kafka producer is not initialized."); 68 | return; 69 | } 70 | 71 | producer.send(new ProducerRecord<>(topic, partitionKey, measurement.toJson())); 72 | } 73 | 74 | @Override 75 | public void stop() { 76 | if (producer != null) { 77 | producer.close(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-kafka/src/test/java/io/smartcat/cassandra/diagnostics/reporter/KafkaLocal.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import java.io.IOException; 4 | import java.util.Properties; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import kafka.server.KafkaConfig; 10 | import kafka.server.KafkaServerStartable; 11 | 12 | public class KafkaLocal { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(KafkaLocal.class); 15 | 16 | public final KafkaServerStartable kafka; 17 | public final ZooKeeperLocal zookeeper; 18 | 19 | public KafkaLocal(Properties kafkaProperties, Properties zkProperties) throws IOException, InterruptedException { 20 | KafkaConfig kafkaConfig = new KafkaConfig(kafkaProperties); 21 | 22 | //start local zookeeper 23 | logger.info("starting local zookeeper..."); 24 | zookeeper = new ZooKeeperLocal(zkProperties); 25 | logger.info("done"); 26 | 27 | //start local kafka broker 28 | kafka = new KafkaServerStartable(kafkaConfig); 29 | logger.info("starting local kafka broker..."); 30 | kafka.startup(); 31 | logger.info("done"); 32 | } 33 | 34 | public void stop() { 35 | //stop kafka broker 36 | logger.info("stopping kafka..."); 37 | kafka.shutdown(); 38 | logger.info("done"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-kafka/src/test/java/io/smartcat/cassandra/diagnostics/reporter/ZooKeeperLocal.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import java.io.IOException; 4 | import java.util.Properties; 5 | 6 | import org.apache.zookeeper.server.ServerConfig; 7 | import org.apache.zookeeper.server.ZooKeeperServerMain; 8 | import org.apache.zookeeper.server.quorum.QuorumPeerConfig; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class ZooKeeperLocal { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(ZooKeeperLocal.class); 15 | 16 | private final ZooKeeperServerMain zooKeeperServer; 17 | 18 | public ZooKeeperLocal(Properties zkProperties) throws IOException { 19 | QuorumPeerConfig quorumConfiguration = new QuorumPeerConfig(); 20 | try { 21 | quorumConfiguration.parseProperties(zkProperties); 22 | } catch (Exception e) { 23 | throw new RuntimeException(e); 24 | } 25 | 26 | zooKeeperServer = new ZooKeeperServerMain(); 27 | final ServerConfig configuration = new ServerConfig(); 28 | configuration.readFrom(quorumConfiguration); 29 | 30 | new Thread() { 31 | public void run() { 32 | try { 33 | zooKeeperServer.runFromConfig(configuration); 34 | } catch (IOException e) { 35 | logger.error("ZooKeeper Failed", e); 36 | } 37 | } 38 | }.start(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-prometheus/README.md: -------------------------------------------------------------------------------- 1 | # Prometheus reporter 2 | 3 | Warning: Unlike other popular metrics aggregators, Prometheus server _pulls_ the measurements from hosts that are collecting the measurements. This means that, if not configured properly, it can degrade performance of the system that is being monitored. Note also that is not possible to explicitly set the time of the measurement in the Prometheus client, so there might be small difference between the time when the measurement was created in the monitored system by cassandra-diagnostics and when Prometheus measurement object is created based on the cassandra-diagnostics' Measurement object. 4 | 5 | [Prometheus reporter](https://github.com/smartcat-labs/cassandra-diagnostics/blob/prometheus-reporter-poc/cassandra-diagnostics-reporter-prometheus/src/main/java/io/smartcat/cassandra/diagnostics/reporter/PrometheusReporter.java) exposes measurements on the configured host (`httpServerHost`) and port (`httpServerPort`). Prometheus server, then, scrapes the measurements. 6 | 7 | Prometheus reporter has the following configuration parameters (that can be specified using `options`): 8 | 9 | - _httpServerHost_ - Prometheus client http server's host address for exposing the measurements. This parameter is required. Note that this address needs to be visible to the Prometheus server. 10 | - _httpServerPort_ - Prometheus client http server's port for exposing the measurements (9091 by default). This parameter is optional. 11 | 12 | Here is an example configuration that uses Prometheus reporter: 13 | 14 | ``` 15 | reporters: 16 | - reporter: io.smartcat.cassandra.diagnostics.reporter.PrometheusReporter 17 | options: 18 | httpServerHost: "192.168.34.20" 19 | httpServerPort: 9091 20 | 21 | modules: 22 | - module: io.smartcat.cassandra.diagnostics.module.requestrate.RequestRateModule 23 | measurement: requestRate 24 | options: 25 | period: 10 26 | timeunit: SECONDS 27 | reporters: 28 | - io.smartcat.cassandra.diagnostics.reporter.LogReporter 29 | - io.smartcat.cassandra.diagnostics.reporter.PrometheusReporter 30 | ``` 31 | 32 | # How it works 33 | 34 | Prometheus reporter transforms the Measurements object to Prometheus' Gauge object like this: 35 | - Diagnostics Measurement `name` property is mapped to Gauge object's `name` property 36 | - Dots (`.`) and dashes (`-`) in measurement names are replaced by underscore (`_`) because of restrictions in Prometheus metric's name. 37 | - Diagnostics Measurement tags are mapped to Gauge labels. 38 | - Diagnostics Complex Measurements are mapped to the multiple Gauge objects - one for each field from Complex Measurement. The name of the Prometheus metric created from Complex Measurement is [metric_name]:[field_key]. 39 | 40 | For example: 41 | 42 | ``` 43 | Measurement [ name=node_info, type=COMPLEX, value=null, time=1505396325752, timeUnit=MILLISECONDS, tags: {host=cassandra, 44 | systemName=smartcat-cassandra-cluster}, fields: {nativeTransportActive=1, uptimeInSeconds=12822051, thriftActive=0, gossipActive=1, exceptionCount=0} ] 45 | ``` 46 | is transformed to 5 separate Prometheus metrics, one for each field: 47 | 48 | ``` 49 | node_info:uptimeInSeconds{host="cassandra",instance="192.168.34.20:9091",job="prometheus",systemName="smartcat_cassandra_cluster"} 50 | node_info:uptimeInSeconds{host="cassandra",instance="192.168.34.20:9091",job="prometheus",systemName="smartcat_cassandra_cluster"} 51 | node_info:uptimeInSeconds{host="cassandra",instance="192.168.34.20:9091",job="prometheus",systemName="smartcat_cassandra_cluster"} 52 | node_info:uptimeInSeconds{host="cassandra",instance="192.168.34.20:9091",job="prometheus",systemName="smartcat_cassandra_cluster"} 53 | node_info:uptimeInSeconds{host="cassandra",instance="192.168.34.20:9091",job="prometheus",systemName="smartcat_cassandra_cluster"} 54 | ``` 55 | 56 | Each of the metrics can be graphed separately. 57 | 58 | 59 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-prometheus/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cassandra-diagnostics 6 | io.smartcat 7 | 1.4.11-SNAPSHOT 8 | 9 | cassandra-diagnostics-reporter-prometheus 10 | 11 | 12 | UTF-8 13 | 2.3 14 | 1.6.5 15 | 16 | 17 | 18 | 19 | io.smartcat 20 | cassandra-diagnostics-commons 21 | ${project.parent.version} 22 | 23 | 24 | junit 25 | junit 26 | test 27 | 28 | 29 | org.assertj 30 | assertj-core 31 | test 32 | 33 | 34 | org.mockito 35 | mockito-all 36 | test 37 | 38 | 39 | org.powermock 40 | powermock-module-junit4 41 | ${version.powermock} 42 | test 43 | 44 | 45 | 46 | io.prometheus 47 | simpleclient 48 | 0.0.26 49 | 50 | 51 | io.prometheus 52 | simpleclient_httpserver 53 | 0.0.26 54 | 55 | 56 | 60 | 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-shade-plugin 67 | 68 | 69 | package 70 | 71 | shade 72 | 73 | 74 | true 75 | all 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-riemann/README.md: -------------------------------------------------------------------------------- 1 | # Riemann reporter 2 | 3 | [Riemann reporter](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-reporter-riemann/src/main/java/io/smartcat/cassandra/diagnostics/reporter/RiemannReporter.java) sends measurements as Riemann events towards the configured [Riemann server](http://riemann.io/) using TCP transport. It is using batch Riemann client to save resources and send more events in one network round trip. 4 | 5 | Generated Riemann Events looks like the following: 6 | 7 | ``` 8 | host: 9 | service: 10 | state: "ok" 11 | metric: 12 | ttl: 30 13 | tags: 14 | id: 15 | statementType: 16 | fields: 17 | client: 18 | statement: 19 | ``` 20 | 21 | Riemann reporter has the following configuration parameters (that can be specified using `options`): 22 | 23 | - _riemannHost_ - Riemann server's host name (IP address). This parameter is required. 24 | - _riemannPort_ - Riemann server's TCP port number (5555 by default). This parameter is optional. 25 | - _batchEventSize_ - Riemann events that fit in one batch, this is length that triggers sending of events (10 by default). This parameter is optional. 26 | 27 | Here is an example configuration that uses Riemann reporter: 28 | 29 | ``` 30 | reporters: 31 | - reporter: io.smartcat.cassandra.diagnostics.reporter.RiemannReporter 32 | options: 33 | riemannHost: 127.0.0.1 34 | riemannPort: 5555 #Optional 35 | batchEventSize: 50 #Optional 36 | 37 | modules: 38 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 39 | measurement: slow_query 40 | options: 41 | slowQueryThresholdInMilliseconds: 10 42 | reporters: 43 | - io.smartcat.cassandra.diagnostics.reporter.RiemannReporter 44 | ``` -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-riemann/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.smartcat 6 | cassandra-diagnostics 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | cassandra-diagnostics-reporter-riemann 11 | 12 | 13 | 0.4.1 14 | 15 | 16 | 17 | 18 | io.smartcat 19 | cassandra-diagnostics-commons 20 | ${project.parent.version} 21 | 22 | 23 | com.aphyr 24 | riemann-java-client 25 | ${version.riemann-java-client} 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-shade-plugin 34 | 35 | 36 | package 37 | 38 | shade 39 | 40 | 41 | true 42 | all 43 | 44 | 45 | com.aphyr:riemann-java-client:* 46 | com.google.protobuf:protobuf-java:* 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-telegraf/README.md: -------------------------------------------------------------------------------- 1 | # Telegraf Reporter 2 | 3 | [Telegraf Reporter](https://github.com/smartcat-labs/cassandra-diagnostics/blob/dev/cassandra-diagnostics-reporter-telegraf/src/main/java/io/smartcat/cassandra/diagnostics/reporter/TelegrafReporter.java) is a [Cassandra Diagnostics]() reporter that sends measurements towards an [Telegraf agent](https://github.com/influxdata/telegraf). It is a basic implementation based on Java NIO framework and does not have external dependencies. 4 | 5 | The reporter uses Telegraf's [TCP Listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener) input plugin to transport measurements in the `influx` format (Influx line protocol). Telegraf reporter converts every measurement objects as an Inlufx protocol line. It supports multi-value measurements. A single Influx protocl line looks like this: 6 | 7 | ``` 8 | [,=,...] value=[,=,...] \r\n 9 | ``` 10 | 11 | Telegraf reporter has the following configuration parameters: 12 | 13 | - _telegrafHost_ - Telegraf agent's host name or IP address. This parameter is required. 14 | - _telegrafPort_ - Telegraf agent's TCP port (8084 by default). This parameter is optional. 15 | 16 | Here is an example configuration that uses Telegraf reporter: 17 | 18 | ``` 19 | reporters: 20 | - reporter: io.smartcat.cassandra.diagnostics.reporter.TelegrafReporter 21 | options: 22 | telegrafHost: 127.0.0.1 23 | telegrafPort: 8084 24 | 25 | modules: 26 | - module: io.smartcat.cassandra.diagnostics.module.slowquery.SlowQueryModule 27 | measurement: slow_query 28 | options: 29 | slowQueryThresholdInMilliseconds: 10 30 | reporters: 31 | - io.smartcat.cassandra.diagnostics.reporter.TelegrafReporter 32 | ``` 33 | 34 | Telegraf agent that receives diagnostics measurements should have enabled the TCP Listener plugin in its configuration and to use the `influx` input data format. This is a sample configuration for the plugin: 35 | 36 | ``` 37 | [[inputs.tcp_listener]] 38 | service_address = ":8084" 39 | allowed_pending_messages = 10000 40 | max_tcp_connections = 250 41 | data_format = "influx" 42 | ``` 43 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-telegraf/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.smartcat 8 | cassandra-diagnostics 9 | 1.4.11-SNAPSHOT 10 | 11 | 12 | cassandra-diagnostics-reporter-telegraf 13 | 14 | 15 | 1.6.5 16 | 2.2 17 | 18 | 19 | 20 | 21 | io.smartcat 22 | cassandra-diagnostics-commons 23 | ${project.parent.version} 24 | 25 | 26 | org.influxdb 27 | influxdb-java 28 | ${version.influxdb-java} 29 | 30 | 31 | junit 32 | junit 33 | test 34 | 35 | 36 | org.mockito 37 | mockito-all 38 | test 39 | 40 | 41 | org.assertj 42 | assertj-core 43 | test 44 | 45 | 46 | org.powermock 47 | powermock-module-junit4 48 | ${version.powermock} 49 | test 50 | 51 | 52 | org.powermock 53 | powermock-api-mockito 54 | ${version.powermock} 55 | test 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-shade-plugin 64 | 65 | 66 | package 67 | 68 | shade 69 | 70 | 71 | true 72 | all 73 | 74 | 75 | org.influxdb:influxdb-java:* 76 | com.google.guava:guava:* 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /cassandra-diagnostics-reporter-telegraf/src/test/java/io/smartcat/cassandra/diagnostics/reporter/TelegrafReporterTest.java: -------------------------------------------------------------------------------- 1 | package io.smartcat.cassandra.diagnostics.reporter; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Matchers.any; 5 | import static org.mockito.Mockito.doAnswer; 6 | import static org.mockito.Mockito.doNothing; 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.when; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.charset.Charset; 12 | import java.nio.charset.CharsetDecoder; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.mockito.invocation.InvocationOnMock; 21 | import org.mockito.stubbing.Answer; 22 | import org.powermock.api.support.membermodification.MemberModifier; 23 | import org.powermock.core.classloader.annotations.PrepareForTest; 24 | import org.powermock.modules.junit4.PowerMockRunner; 25 | 26 | import io.smartcat.cassandra.diagnostics.GlobalConfiguration; 27 | import io.smartcat.cassandra.diagnostics.Measurement; 28 | 29 | @RunWith(PowerMockRunner.class) 30 | @PrepareForTest({ TelegrafReporter.class }) 31 | public class TelegrafReporterTest { 32 | 33 | private String line = "dummy"; 34 | 35 | @Test 36 | public void measurement_send() throws Exception { 37 | 38 | TcpClient tcpClientMock = mock(TcpClient.class); 39 | when(tcpClientMock.isConnected()).thenReturn(true); 40 | doNothing().when(tcpClientMock).start(); 41 | doNothing().when(tcpClientMock).stop(); 42 | doAnswer(new Answer() { 43 | @Override 44 | public Void answer(InvocationOnMock invocation) throws Throwable { 45 | ByteBuffer buf = (ByteBuffer) invocation.getArguments()[0]; 46 | Charset charset = StandardCharsets.UTF_8; 47 | CharsetDecoder decoder = charset.newDecoder(); 48 | line = decoder.decode(buf).toString(); 49 | return null; 50 | } 51 | }).when(tcpClientMock).send(any(ByteBuffer.class)); 52 | 53 | ReporterConfiguration configuration = new ReporterConfiguration(); 54 | configuration.options.put("telegrafHost", "localhost"); 55 | 56 | TelegrafReporter reporter = new TelegrafReporter(configuration, GlobalConfiguration.getDefault()); 57 | MemberModifier.field(TelegrafReporter.class, "telegrafClient").set(reporter, tcpClientMock); 58 | 59 | Map tags = new HashMap<>(); 60 | tags.put("tag1", "tv1"); 61 | tags.put("tag2", "tv2"); 62 | 63 | Map fields = new HashMap<>(); 64 | fields.put("v2", "abc"); 65 | Measurement measurement = Measurement.createSimple("m1", 1.0, 1434055662, TimeUnit.SECONDS, tags, fields); 66 | 67 | reporter.report(measurement); 68 | assertThat(line).isEqualTo("m1,tag1=tv1,tag2=tv2,type=SIMPLE v2=\"abc\",value=1.0 1434055662000000000\r\n"); 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /checkstyle.properties: -------------------------------------------------------------------------------- 1 | suppression_file=checkstyle-suppressions.xml -------------------------------------------------------------------------------- /diagrams/architecture-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcat-labs/cassandra-diagnostics/1fd7b34e5a6c37e1762914f3b62960ef3ee7458a/diagrams/architecture-diagram.png -------------------------------------------------------------------------------- /diagrams/cassandra-diagnostics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcat-labs/cassandra-diagnostics/1fd7b34e5a6c37e1762914f3b62960ef3ee7458a/diagrams/cassandra-diagnostics.png --------------------------------------------------------------------------------