├── .gitignore ├── .whitesource ├── ConfigurationExample.conf ├── LICENSE ├── README.md ├── docker ├── Dockerfile └── go.sh ├── pom.xml └── src └── main ├── java └── io │ └── logz │ └── benchmarks │ └── elasticsearch │ ├── ElasticsearchBenchmarkToolMain.java │ ├── benchmark │ ├── BenchmarkPlan.java │ └── BenchmarkStep.java │ ├── configuration │ ├── BaseConfiguration.java │ ├── ConfigurationParser.java │ ├── ElasticsearchConfiguration.java │ ├── IndexingConfiguration.java │ ├── OptimizeConfiguration.java │ └── SearchConfiguration.java │ ├── controllers │ ├── BaseController.java │ ├── IndexingController.java │ ├── NoopController.java │ ├── OptimizeController.java │ └── SearchController.java │ ├── elasticsearch │ └── ElasticsearchController.java │ ├── exceptions │ ├── CouldNotCompleteBulkOperationException.java │ ├── CouldNotExecuteSearchException.java │ ├── CouldNotOptimizeException.java │ └── InvalidConfigurationException.java │ └── metrics │ ├── GeneralMbean.java │ ├── IndexingMbean.java │ └── SearchMbean.java └── resources ├── logback.xml └── templates ├── documents ├── document1.json ├── document2.json ├── document3.json ├── document4.json └── document5.json └── searches ├── datehistogram-with-sub-range-agg-2-hours.json ├── datehistogram-with-terms-and-sub-cardinality-30-minutes.json ├── kibana-discover-last-30-minutes.json ├── simple-search-return-all.json └── terms-10000-size-with-complex-query.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | /elasticsearch-benchmark-tool.iml 14 | /.idea/ 15 | /target/* 16 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "generalSettings": { 3 | "shouldScanRepo": true 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure" 7 | } 8 | } -------------------------------------------------------------------------------- /ConfigurationExample.conf: -------------------------------------------------------------------------------- 1 | # Hocon based configuration 2 | 3 | elasticsearch = { 4 | elasticsearchAddress = "es" 5 | elasticsearchProtocol = "http" 6 | elasticsearchPort = 9200 7 | numberOfShards = 1 8 | numberOfReplicas = 0 9 | userName = "user" 10 | password = "password" 11 | indexPrefix = "index-prefix-" 12 | documentsPath = "/templates/documents" 13 | searchesPath = "/templates/searches" 14 | } 15 | 16 | steps = [ 17 | 18 | { 19 | duration = "10m" 20 | indexing = { 21 | numberOfThreads = 30 22 | } 23 | search = { 24 | numberOfThreads = 5 25 | } 26 | } 27 | { 28 | duration = "5s" 29 | indexing = { 30 | } 31 | search = { 32 | numberOfThreads = 5 33 | } 34 | optimize = { 35 | numberOfSegments = 1 36 | } 37 | } 38 | { 39 | duration = "1s" 40 | noop = { 41 | } 42 | } 43 | { 44 | duration = "10s" 45 | search = { 46 | numberOfThreads = 2 47 | } 48 | } 49 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT IS NO LONGER MAINTAINED 2 | 3 | # elasticsearch-benchmark-tool 4 | Stress test tool that benchmark indexing and searching in Elasticsearch 5 | 6 | 7 | ## How it is working 8 | The tool is getting a configuration file, which represent the test plan. 9 | Each step can have 1 or more of the following controllers: 10 | - Indexing - Index 1k bulks of log like documents (5 different options, with some fields getting different data based on common cardinality factor and always changing timestamp) 11 | - Search - Iterate over 5 different Kibana like searches 12 | - Optimize - Force merge the index 13 | - Noop - Do nothing 14 | 15 | The tool produce metrics as JMX counters, and the [Jmx2Graphite](https://github.com/logzio/jmx2graphite) tool is sending those to your graphite server. 16 | 17 | ## Configuration Example 18 | Can be found under ConfigurationExample.conf 19 | 20 | ## Run 21 | ```bash 22 | docker run --rm -it -e GRAPHITE_SERVER="your-graphite-server.com" \ 23 | -e GRAPHITE_PREFIX="Prefix.under.graphite.root" \ 24 | -e SERVICE_HOST="BENCHMARK_TEST_NAME" \ 25 | -v /your/configuration.conf:/config.conf \ 26 | -v /your/templates:/templates \ 27 | logzio/elasticsearch-benchmark-tool 28 | ``` 29 | 30 | ## Build 31 | ```bash 32 | mvn clean package docker:build 33 | ``` 34 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-alpine 2 | 3 | MAINTAINER roi@logz.io 4 | 5 | RUN apk add --no-cache bash 6 | 7 | ADD go.sh /root 8 | 9 | RUN chmod a+x /root/go.sh 10 | 11 | CMD /root/go.sh 12 | 13 | ADD packages/ /root -------------------------------------------------------------------------------- /docker/go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$GRAPHITE_SERVER" == "" ]; then 4 | 5 | echo "GRAPHITE_SERVER must be passed as environment variable!" 6 | exit 1 7 | fi 8 | 9 | if [ "$GRAPHITE_PREFIX" == "" ]; then 10 | 11 | echo "GRAPHITE_PREFIX must be passed as environment variable!" 12 | exit 1 13 | fi 14 | 15 | if [ "$SERVICE_HOST" == "" ]; then 16 | 17 | echo "SERVICE_HOST must be passed as environment variable!" 18 | exit 1 19 | fi 20 | 21 | if [ "$INTERVAL_IN_SEC" == "" ]; then 22 | INTERVAL_IN_SEC=10 23 | fi 24 | 25 | if [ "$CONFIG_FILE" == "" ]; then 26 | CONFIG_FILE="/config.conf" 27 | fi 28 | 29 | # Find the jar 30 | JAR=`ls /root/ | grep elasticsearch` 31 | JAVA_AGENT="-javaagent:/root/jmx2graphite-1.2.4-javaagent.jar=GRAPHITE_HOSTNAME=$GRAPHITE_SERVER;SERVICE_NAME=$GRAPHITE_PREFIX;SERVICE_HOST=$SERVICE_HOST;INTERVAL_IN_SEC=$INTERVAL_IN_SEC" 32 | 33 | java $JAVA_AGENT -jar /root/$JAR --test-config $CONFIG_FILE -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.logz 8 | elasticsearch-benchmark-tool 9 | 1.0 10 | jar 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 3.5.1 17 | 18 | 1.8 19 | 1.8 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-assembly-plugin 26 | 2.6 27 | 28 | 29 | jar-with-dependencies 30 | 31 | 32 | 33 | io.logz.benchmarks.elasticsearch.ElasticsearchBenchmarkToolMain 34 | 35 | 36 | 37 | 38 | 39 | make-assembly 40 | package 41 | 42 | single 43 | 44 | 45 | 46 | 47 | 48 | 49 | com.spotify 50 | docker-maven-plugin 51 | 0.4.13 52 | 53 | logzio/elasticsearch-benchmark-tool 54 | docker 55 | 56 | 57 | /packages 58 | ${project.build.directory} 59 | ${project.build.finalName}-jar-with-dependencies.jar 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | commons-cli 70 | commons-cli 71 | 1.3.1 72 | 73 | 74 | com.google.guava 75 | guava 76 | 19.0 77 | 78 | 79 | ch.qos.logback 80 | logback-classic 81 | 1.1.7 82 | 83 | 84 | ch.qos.logback 85 | logback-core 86 | 1.1.7 87 | 88 | 89 | org.slf4j 90 | slf4j-api 91 | 1.7.21 92 | 93 | 94 | com.udojava 95 | JMXWrapper 96 | 1.2 97 | 98 | 99 | io.searchbox 100 | jest 101 | 2.0.4 102 | 103 | 104 | org.elasticsearch 105 | elasticsearch 106 | 2.4.0 107 | 108 | 109 | com.typesafe 110 | config 111 | 1.3.0 112 | 113 | 114 | com.fasterxml.jackson.core 115 | jackson-core 116 | 2.9.9 117 | 118 | 119 | com.fasterxml.jackson.core 120 | jackson-databind 121 | 2.9.10.8 122 | 123 | 124 | org.reflections 125 | reflections 126 | 0.9.10 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/ElasticsearchBenchmarkToolMain.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch; 2 | import com.udojava.jmx.wrapper.JMXBeanWrapper; 3 | import io.logz.benchmarks.elasticsearch.benchmark.BenchmarkPlan; 4 | import io.logz.benchmarks.elasticsearch.configuration.ConfigurationParser; 5 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 6 | import io.logz.benchmarks.elasticsearch.metrics.GeneralMbean; 7 | import io.logz.benchmarks.elasticsearch.metrics.IndexingMbean; 8 | import io.logz.benchmarks.elasticsearch.metrics.SearchMbean; 9 | import org.apache.commons.cli.CommandLine; 10 | import org.apache.commons.cli.CommandLineParser; 11 | import org.apache.commons.cli.DefaultParser; 12 | import org.apache.commons.cli.HelpFormatter; 13 | import org.apache.commons.cli.Option; 14 | import org.apache.commons.cli.Options; 15 | import org.apache.commons.cli.ParseException; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import javax.management.InstanceAlreadyExistsException; 20 | import javax.management.IntrospectionException; 21 | import javax.management.MBeanRegistrationException; 22 | import javax.management.MBeanServer; 23 | import javax.management.MalformedObjectNameException; 24 | import javax.management.NotCompliantMBeanException; 25 | import javax.management.ObjectName; 26 | import java.lang.management.ManagementFactory; 27 | 28 | /** 29 | * Created by roiravhon on 9/19/16. 30 | */ 31 | public class ElasticsearchBenchmarkToolMain { 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(ElasticsearchBenchmarkToolMain.class); 34 | 35 | public static void main(String[] args) { 36 | try { 37 | 38 | CommandLine cmd = parseCliArguments(args); 39 | String configFile = cmd.getOptionValue("test-config", null); 40 | 41 | registerMbeans(); 42 | BenchmarkPlan plan = ConfigurationParser.parseConfiguration(configFile); 43 | 44 | // Make sure we cleanup nicely, no matter what 45 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 46 | plan.abortPlan(); 47 | plan.cleanupPlan(); 48 | })); 49 | 50 | plan.execute(); 51 | plan.printStats(); 52 | System.exit(0); 53 | } 54 | catch (RuntimeException e) { 55 | logger.error(e.getMessage(), e); 56 | logger.error("This is fatal, bailing out.."); 57 | System.exit(1); 58 | } catch (InvalidConfigurationException e) { 59 | logger.error("You configuration is invalid!"); 60 | logger.error(e.getMessage(), e); 61 | System.exit(1); 62 | } 63 | } 64 | 65 | private static CommandLine parseCliArguments(String[] args) { 66 | Options options = new Options(); 67 | logger.info("Parsing CLI arguments"); 68 | 69 | Option testConfig = new Option("t", "test-config", true, "The test configuration"); 70 | testConfig.setRequired(true); 71 | options.addOption(testConfig); 72 | 73 | CommandLineParser parser = new DefaultParser(); 74 | HelpFormatter formatter = new HelpFormatter(); 75 | 76 | try { 77 | return parser.parse(options, args); 78 | 79 | } catch (ParseException e) { 80 | System.out.println(e.getMessage()); 81 | formatter.printHelp("elasticsearch-benchmark-tool", options); 82 | 83 | throw new RuntimeException(); 84 | } 85 | } 86 | 87 | private static void registerMbeans() { 88 | try { 89 | MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 90 | 91 | IndexingMbean indexingMbean = IndexingMbean.getInstance(); 92 | SearchMbean searchMbean = SearchMbean.getInstance(); 93 | GeneralMbean generalMbean = GeneralMbean.getInstance(); 94 | 95 | JMXBeanWrapper indexingMbeanWrapper = new JMXBeanWrapper(indexingMbean); 96 | JMXBeanWrapper searchMbeanWrapper = new JMXBeanWrapper(searchMbean); 97 | JMXBeanWrapper generalMbeanWrapper = new JMXBeanWrapper(generalMbean); 98 | 99 | mbs.registerMBean(indexingMbeanWrapper, new ObjectName("io.logz.benchmarks.elasticsearch:type=Indexing,name=Indexing Metrics")); 100 | mbs.registerMBean(searchMbeanWrapper, new ObjectName("io.logz.benchmarks.elasticsearch:type=Search,name=Search Metrics")); 101 | mbs.registerMBean(generalMbeanWrapper, new ObjectName("io.logz.benchmarks.elasticsearch:type=General,name=General Metrics")); 102 | 103 | } catch (IntrospectionException | MalformedObjectNameException | NotCompliantMBeanException | 104 | InstanceAlreadyExistsException | MBeanRegistrationException e) { 105 | throw new RuntimeException("Could not initialize JMX metrics!", e); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/benchmark/BenchmarkPlan.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.benchmark; 2 | 3 | import io.logz.benchmarks.elasticsearch.configuration.ElasticsearchConfiguration; 4 | import io.logz.benchmarks.elasticsearch.elasticsearch.ElasticsearchController; 5 | import io.logz.benchmarks.elasticsearch.metrics.GeneralMbean; 6 | import io.logz.benchmarks.elasticsearch.metrics.IndexingMbean; 7 | import io.logz.benchmarks.elasticsearch.metrics.SearchMbean; 8 | 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by roiravhon on 9/19/16. 14 | */ 15 | public class BenchmarkPlan { 16 | 17 | private final List benchmarkSteps; 18 | private final ElasticsearchController esController; 19 | private final GeneralMbean generalMbean; 20 | private BenchmarkStep currentStep; 21 | private boolean aborting = false; 22 | private boolean done = false; 23 | 24 | public BenchmarkPlan(ElasticsearchConfiguration esConfig) { 25 | benchmarkSteps = new LinkedList<>(); 26 | esController = new ElasticsearchController(esConfig); 27 | generalMbean = GeneralMbean.getInstance(); 28 | } 29 | 30 | public void addStep(BenchmarkStep step) { 31 | benchmarkSteps.add(step); 32 | } 33 | 34 | public void execute() { 35 | prepareForExecution(); 36 | benchmarkSteps.forEach(benchmarkStep -> { 37 | if (!aborting) { 38 | currentStep = benchmarkStep; 39 | benchmarkStep.executeStep(); 40 | generalMbean.incrementStep(); 41 | } 42 | }); 43 | 44 | done = true; 45 | } 46 | 47 | public void printStats() { 48 | 49 | IndexingMbean indexingMbean = IndexingMbean.getInstance(); 50 | SearchMbean searchMbean = SearchMbean.getInstance(); 51 | GeneralMbean generalMbean = GeneralMbean.getInstance(); 52 | 53 | System.out.println("##############################################################################"); 54 | System.out.println(""); 55 | 56 | System.out.println("Benchmark statistics (" + generalMbean.getCurrStep() + " steps):"); 57 | System.out.println(""); 58 | 59 | System.out.println("\t Total documents successfully indexed: " + indexingMbean.getNumberOfSuccessfulDocumentsIndexed()); 60 | System.out.println("\t Total documents that fail to index: " + indexingMbean.getNumberOfFailedDocumentsIndexed()); 61 | System.out.println(""); 62 | 63 | System.out.println("\t Total successful searches " + searchMbean.getNumberOfSuccessfulSearches()); 64 | System.out.println("\t Total failed searches " + searchMbean.getNumberOfFailedSearches()); 65 | System.out.println("\t Total documents fetched " + searchMbean.getNumberOfFetchedDocuments()); 66 | System.out.println("\t Total successful query time, in MS " + searchMbean.getTotalSuccessfulSearchesTimeMs()); 67 | System.out.println("\t Average successful query time, in MS " + searchMbean.getAverageSearchTimeMs()); 68 | System.out.println(""); 69 | 70 | System.out.println(""); 71 | System.out.println("##############################################################################"); 72 | } 73 | 74 | public void cleanupPlan() { 75 | esController.deleteIndex(); 76 | } 77 | 78 | public void abortPlan() { 79 | if (!done) { 80 | aborting = true; 81 | if (currentStep != null) 82 | currentStep.abortStep(); 83 | } 84 | } 85 | 86 | public ElasticsearchController getEsController() { 87 | return esController; 88 | } 89 | 90 | private void prepareForExecution() { 91 | esController.createIndex(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/benchmark/BenchmarkStep.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.benchmark; 2 | 3 | import io.logz.benchmarks.elasticsearch.controllers.BaseController; 4 | import io.logz.benchmarks.elasticsearch.controllers.IndexingController; 5 | import io.logz.benchmarks.elasticsearch.controllers.SearchController; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by roiravhon on 9/19/16. 14 | */ 15 | public class BenchmarkStep { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(BenchmarkStep.class); 18 | private final List controllers; 19 | private final long durationMillis; 20 | 21 | public BenchmarkStep(long durationMillis) { 22 | this.durationMillis = durationMillis; 23 | controllers = new LinkedList<>(); 24 | } 25 | 26 | public void addController(BaseController controller) { 27 | controllers.add(controller); 28 | } 29 | 30 | public void executeStep() { 31 | 32 | logger.info("Starting to execute step: " + toString()); 33 | controllers.forEach(BaseController::run); 34 | 35 | try { 36 | logger.info("Step started. Waiting for {} millis.", durationMillis); 37 | Thread.sleep(durationMillis); 38 | logger.info("Time out!"); 39 | 40 | } catch (InterruptedException e) { 41 | logger.error("Got interrupted while running! gracefully shutting down."); 42 | 43 | } finally { 44 | logger.info("Stopping step: {}", toString()); 45 | controllers.forEach(BaseController::stop); 46 | } 47 | } 48 | 49 | public void abortStep() { 50 | logger.info("Aborting step: {}", toString()); 51 | controllers.forEach(BaseController::stop); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | 57 | StringBuilder allControllersNames = new StringBuilder(); 58 | controllers.forEach(controller -> allControllersNames.append(controller.getControllerName()).append(", ")); 59 | 60 | return "Controllers: " + allControllersNames.toString() + "for: " + durationMillis + " ms"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/BaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 4 | 5 | /** 6 | * Created by roiravhon on 9/21/16. 7 | */ 8 | public interface BaseConfiguration { 9 | 10 | void validateConfig() throws InvalidConfigurationException; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/ConfigurationParser.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.typesafe.config.Config; 5 | import com.typesafe.config.ConfigException; 6 | import com.typesafe.config.ConfigFactory; 7 | import com.typesafe.config.ConfigObject; 8 | import io.logz.benchmarks.elasticsearch.benchmark.BenchmarkPlan; 9 | import io.logz.benchmarks.elasticsearch.benchmark.BenchmarkStep; 10 | import io.logz.benchmarks.elasticsearch.controllers.IndexingController; 11 | import io.logz.benchmarks.elasticsearch.controllers.NoopController; 12 | import io.logz.benchmarks.elasticsearch.controllers.OptimizeController; 13 | import io.logz.benchmarks.elasticsearch.controllers.SearchController; 14 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.io.File; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by roiravhon on 9/19/16. 23 | */ 24 | public class ConfigurationParser { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(ConfigurationParser.class); 27 | 28 | public static BenchmarkPlan parseConfiguration(String configurationFile) throws InvalidConfigurationException { 29 | try { 30 | Config config = ConfigFactory.parseFile(new File(configurationFile)); 31 | ConfigObject elasticsearchConfig = config.getObject("elasticsearch"); 32 | List stepsConfig = config.getObjectList("steps"); 33 | 34 | ObjectMapper objectMapper = new ObjectMapper(); 35 | 36 | // Parse elasticsearch configuration 37 | ElasticsearchConfiguration esConfig = objectMapper.convertValue(elasticsearchConfig.unwrapped(), ElasticsearchConfiguration.class); 38 | esConfig.validateConfig(); 39 | 40 | // Build initial benchmark plan 41 | BenchmarkPlan benchmarkPlan = new BenchmarkPlan(esConfig); 42 | 43 | for (ConfigObject currConfigObject: stepsConfig) { 44 | 45 | Config currConfig = currConfigObject.toConfig(); 46 | 47 | long duration = currConfig.getDuration("duration").toMillis(); 48 | BenchmarkStep step = new BenchmarkStep(duration); 49 | 50 | if (currConfig.hasPath("indexing")) { 51 | IndexingConfiguration indexingConfig = objectMapper.convertValue(currConfig.getObject("indexing").unwrapped(), IndexingConfiguration.class); 52 | indexingConfig.validateConfig(); 53 | step.addController(new IndexingController(indexingConfig, benchmarkPlan.getEsController())); 54 | } 55 | 56 | if (currConfig.hasPath("search")) { 57 | SearchConfiguration searchConfig = objectMapper.convertValue(currConfig.getObject("search").unwrapped(), SearchConfiguration.class); 58 | searchConfig.validateConfig(); 59 | step.addController(new SearchController(searchConfig, benchmarkPlan.getEsController())); 60 | } 61 | 62 | if (currConfig.hasPath("optimize")) { 63 | OptimizeConfiguration optimizeConfig = objectMapper.convertValue(currConfig.getObject("optimize").unwrapped(), OptimizeConfiguration.class); 64 | optimizeConfig.validateConfig(); 65 | step.addController(new OptimizeController(optimizeConfig, benchmarkPlan.getEsController())); 66 | } 67 | 68 | if (currConfig.hasPath("noop")) { 69 | step.addController(new NoopController()); 70 | } 71 | 72 | // Adding the final step 73 | benchmarkPlan.addStep(step); 74 | } 75 | 76 | return benchmarkPlan; 77 | 78 | } catch (ConfigException e) { 79 | throw new RuntimeException("Could not parse configuration file!", e); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/ElasticsearchConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 4 | 5 | /** 6 | * Created by roiravhon on 9/21/16. 7 | */ 8 | public class ElasticsearchConfiguration implements BaseConfiguration { 9 | 10 | private String elasticsearchAddress; 11 | private String elasticsearchProtocol = "http"; 12 | private int elasticsearchPort = 9200; 13 | private int numberOfShards = 1; 14 | private int numberOfReplicas = 0; 15 | private String userName; 16 | private String password; 17 | private String indexPrefix; 18 | private String documentsPath; 19 | private String searchesPath; 20 | 21 | // For Jackson 22 | @SuppressWarnings("unused") 23 | public ElasticsearchConfiguration() { 24 | 25 | } 26 | 27 | @SuppressWarnings("unused") 28 | public ElasticsearchConfiguration(String elasticsearchAddress, String elasticsearchProtocol, Integer elasticsearchPort, Integer numberOfShards, Integer numberOfReplicas, String userName, String password, String indexPrefix) { 29 | this.elasticsearchAddress = elasticsearchAddress; 30 | this.elasticsearchProtocol = elasticsearchProtocol; 31 | this.elasticsearchPort = elasticsearchPort; 32 | this.numberOfShards = numberOfShards; 33 | this.numberOfReplicas = numberOfReplicas; 34 | this.userName = userName; 35 | this.password = password; 36 | this.indexPrefix = indexPrefix; 37 | } 38 | 39 | public String getElasticsearchAddress() { 40 | return elasticsearchAddress; 41 | } 42 | 43 | public String getElasticsearchProtocol() { 44 | return elasticsearchProtocol; 45 | } 46 | 47 | public int getElasticsearchPort() { 48 | return elasticsearchPort; 49 | } 50 | 51 | public int getNumberOfShards() { 52 | return numberOfShards; 53 | } 54 | 55 | public int getNumberOfReplicas() { 56 | return numberOfReplicas; 57 | } 58 | 59 | public String getUserName() { 60 | return userName; 61 | } 62 | 63 | public String getPassword() { 64 | return password; 65 | } 66 | 67 | public String getIndexPrefix() { 68 | return indexPrefix; 69 | } 70 | 71 | public String getDocumentsPath() { 72 | return documentsPath; 73 | } 74 | 75 | public String getSearchesPath() { 76 | return searchesPath; 77 | } 78 | 79 | @Override 80 | public void validateConfig() throws InvalidConfigurationException { 81 | 82 | if (elasticsearchAddress == null || elasticsearchAddress == "") { 83 | throw new InvalidConfigurationException("elasticsearchAddress must be set!"); 84 | } 85 | 86 | if (!elasticsearchProtocol.equals("http")) { 87 | throw new InvalidConfigurationException("Only supporting http protocol for Elasticsearch for now"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/IndexingConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 4 | 5 | /** 6 | * Created by roiravhon on 9/21/16. 7 | */ 8 | public class IndexingConfiguration implements BaseConfiguration{ 9 | 10 | private static final int MAX_NUMBER_OF_THREADS = 10000; 11 | private static final int MIN_NUMBER_OF_THREADS = 1; 12 | private int numberOfThreads = 5; 13 | private int bulkSize = 1000; 14 | 15 | // For Jackson 16 | @SuppressWarnings("unused") 17 | public IndexingConfiguration() { 18 | 19 | } 20 | 21 | @SuppressWarnings("unused") 22 | public IndexingConfiguration(int numberOfThreads, int bulkSize) { 23 | this.numberOfThreads = numberOfThreads; 24 | this.bulkSize = bulkSize; 25 | } 26 | 27 | public int getNumberOfThreads() { 28 | return numberOfThreads; 29 | } 30 | 31 | public int getBulkSize() { 32 | return bulkSize; 33 | } 34 | 35 | @Override 36 | public void validateConfig() throws InvalidConfigurationException { 37 | 38 | if (numberOfThreads < MIN_NUMBER_OF_THREADS || numberOfThreads >= MAX_NUMBER_OF_THREADS) { 39 | throw new InvalidConfigurationException("Indexing numberOfThreads must be between " + MIN_NUMBER_OF_THREADS + " and " + MAX_NUMBER_OF_THREADS); 40 | } 41 | 42 | if (bulkSize <= 0) { 43 | throw new InvalidConfigurationException("Bulk size must be more than 0! can't work with " + bulkSize); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/OptimizeConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 4 | 5 | /** 6 | * Created by roiravhon on 9/21/16. 7 | */ 8 | public class OptimizeConfiguration implements BaseConfiguration { 9 | 10 | private static final int MIN_NUMBER_OF_SEGMENTS = 1; 11 | private static final int MAX_NUMBER_OF_SEGMENTS = 10; 12 | private int numberOfSegments = 1; 13 | 14 | // For Jackson 15 | @SuppressWarnings("unused") 16 | public OptimizeConfiguration() { 17 | 18 | } 19 | 20 | @SuppressWarnings("unused") 21 | public OptimizeConfiguration(int maxNumberOfSegments) { 22 | this.numberOfSegments = maxNumberOfSegments; 23 | } 24 | 25 | public int getNumberOfSegments() { 26 | return numberOfSegments; 27 | } 28 | 29 | @Override 30 | public void validateConfig() throws InvalidConfigurationException { 31 | if (numberOfSegments < MIN_NUMBER_OF_SEGMENTS || numberOfSegments >= MAX_NUMBER_OF_SEGMENTS) { 32 | throw new InvalidConfigurationException("Optimize numberOfSegments must be between " + MIN_NUMBER_OF_SEGMENTS + " and " + MAX_NUMBER_OF_SEGMENTS); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/configuration/SearchConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.configuration; 2 | 3 | import io.logz.benchmarks.elasticsearch.exceptions.InvalidConfigurationException; 4 | 5 | /** 6 | * Created by roiravhon on 9/21/16. 7 | */ 8 | public class SearchConfiguration implements BaseConfiguration { 9 | 10 | private static final int MIN_NUMBER_OF_THREADS = 1; 11 | private static final int MAX_NUMBER_OF_THREADS = 10000; 12 | private int numberOfThreads = 5; 13 | 14 | // For Jackson 15 | @SuppressWarnings("unused") 16 | public SearchConfiguration() { 17 | 18 | } 19 | 20 | @SuppressWarnings("unused") 21 | public SearchConfiguration(int numberOfThreads) { 22 | this.numberOfThreads = numberOfThreads; 23 | } 24 | 25 | public int getNumberOfThreads() { 26 | return numberOfThreads; 27 | } 28 | 29 | @Override 30 | public void validateConfig() throws InvalidConfigurationException { 31 | 32 | if (numberOfThreads < MIN_NUMBER_OF_THREADS || numberOfThreads >= MAX_NUMBER_OF_THREADS) { 33 | throw new InvalidConfigurationException("Search numberOfThreads must be between " + MIN_NUMBER_OF_THREADS + " and " + MAX_NUMBER_OF_THREADS); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/controllers/BaseController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.controllers; 2 | 3 | /** 4 | * Created by roiravhon on 9/19/16. 5 | */ 6 | public interface BaseController { 7 | 8 | String getControllerName(); 9 | void run(); 10 | void stop(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/controllers/IndexingController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.controllers; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.logz.benchmarks.elasticsearch.configuration.ConfigurationParser; 5 | import io.logz.benchmarks.elasticsearch.configuration.ElasticsearchConfiguration; 6 | import io.logz.benchmarks.elasticsearch.configuration.IndexingConfiguration; 7 | import io.logz.benchmarks.elasticsearch.elasticsearch.ElasticsearchController; 8 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotCompleteBulkOperationException; 9 | import io.logz.benchmarks.elasticsearch.metrics.IndexingMbean; 10 | import io.searchbox.core.Bulk; 11 | import io.searchbox.core.Index; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.Arrays; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.ThreadFactory; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.IntStream; 21 | 22 | /** 23 | * Created by roiravhon on 9/19/16. 24 | */ 25 | public class IndexingController implements BaseController { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(IndexingController.class); 28 | 29 | private final IndexingMbean indexingMbean; 30 | private final IndexingConfiguration configuration; 31 | private final ElasticsearchController esController; 32 | private final ExecutorService threadPool; 33 | 34 | public IndexingController(IndexingConfiguration configuration, ElasticsearchController esController) { 35 | 36 | this.configuration = configuration; 37 | this.esController = esController; 38 | indexingMbean = IndexingMbean.getInstance(); 39 | 40 | // Creating the thread pool 41 | ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("indexing-thread-%d").build(); 42 | threadPool = Executors.newFixedThreadPool(configuration.getNumberOfThreads(), namedThreadFactory); 43 | } 44 | 45 | @Override 46 | public String getControllerName() { 47 | return "Indexing"; 48 | } 49 | 50 | @Override 51 | public void run() { 52 | 53 | // Creating indexing threads 54 | IntStream.range(0, configuration.getNumberOfThreads()) 55 | .forEach((threadNumber) -> threadPool.submit(() -> startIndexing(threadNumber))); 56 | } 57 | 58 | @Override 59 | public void stop() { 60 | 61 | threadPool.shutdownNow(); 62 | } 63 | 64 | private void startIndexing(int threadNumber) { 65 | 66 | logger.debug("Starting indexing thread #{}", threadNumber); 67 | while (true) { 68 | try { 69 | 70 | if (Thread.interrupted()) { 71 | logger.debug("Got interrupt, stopping indexing thread #{}", threadNumber); 72 | return; 73 | } 74 | 75 | // Create bulk 76 | Bulk currBulk = new Bulk.Builder() 77 | .defaultIndex(esController.getIndexName()) 78 | .defaultType(esController.getDefaultType()) 79 | .addAction(esController.getMultipleDocuments(configuration.getBulkSize()) 80 | .stream() 81 | .map((doc) -> new Index.Builder(doc).build()) 82 | .collect(Collectors.toList())) 83 | .build(); 84 | 85 | try { 86 | // Lets index the bulk! 87 | int numberOfFailedDocuments = esController.executeBulk(currBulk); 88 | 89 | // Update metrics 90 | indexingMbean.incrementSuccessfulDocuments(configuration.getBulkSize() - numberOfFailedDocuments); 91 | indexingMbean.incrementFailedDocuements(numberOfFailedDocuments); 92 | 93 | } catch (CouldNotCompleteBulkOperationException e) { 94 | 95 | // Assuming all documents failed.. 96 | indexingMbean.incrementFailedDocuements(configuration.getBulkSize()); 97 | } 98 | 99 | } catch (Throwable throwable) { 100 | logger.debug("Got unexpected exception while indexing!", throwable); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/controllers/NoopController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.controllers; 2 | 3 | /** 4 | * Created by roiravhon on 9/19/16. 5 | */ 6 | public class NoopController implements BaseController{ 7 | 8 | @Override 9 | public String getControllerName() { 10 | return "Noop"; 11 | } 12 | 13 | @Override 14 | public void run() { 15 | 16 | } 17 | 18 | @Override 19 | public void stop() { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/controllers/OptimizeController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.controllers; 2 | 3 | import io.logz.benchmarks.elasticsearch.configuration.OptimizeConfiguration; 4 | import io.logz.benchmarks.elasticsearch.elasticsearch.ElasticsearchController; 5 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotOptimizeException; 6 | import io.searchbox.indices.ForceMerge; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Created by roiravhon on 9/19/16. 12 | */ 13 | public class OptimizeController implements BaseController { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(OptimizeController.class); 16 | 17 | private final OptimizeConfiguration configuration; 18 | private final ElasticsearchController esController; 19 | 20 | public OptimizeController(OptimizeConfiguration configuration, ElasticsearchController esController) { 21 | 22 | this.configuration = configuration; 23 | this.esController = esController; 24 | } 25 | 26 | @Override 27 | public String getControllerName() { 28 | return "Optimize"; 29 | } 30 | 31 | @Override 32 | public void run() { 33 | logger.debug("Starting to optimize! (force merge)"); 34 | ForceMerge forceMerge = new ForceMerge.Builder() 35 | .maxNumSegments(configuration.getNumberOfSegments()) 36 | .build(); 37 | try { 38 | esController.executeForceMerge(forceMerge); 39 | } catch (CouldNotOptimizeException e) { 40 | logger.info("Could not optimize!", e); 41 | } 42 | } 43 | 44 | @Override 45 | public void stop() { 46 | // We cant cancel optimize.. 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/controllers/SearchController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.controllers; 2 | 3 | import com.google.common.base.Stopwatch; 4 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 5 | import io.logz.benchmarks.elasticsearch.configuration.SearchConfiguration; 6 | import io.logz.benchmarks.elasticsearch.elasticsearch.ElasticsearchController; 7 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotExecuteSearchException; 8 | import io.logz.benchmarks.elasticsearch.metrics.SearchMbean; 9 | import io.searchbox.core.Search; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.stream.IntStream; 18 | 19 | /** 20 | * Created by roiravhon on 9/19/16. 21 | */ 22 | public class SearchController implements BaseController { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(SearchController.class); 25 | 26 | private final SearchConfiguration configuration; 27 | private final ElasticsearchController esController; 28 | private final SearchMbean searchMbean; 29 | private final ExecutorService threadPool; 30 | 31 | public SearchController(SearchConfiguration configuration, ElasticsearchController esController) { 32 | 33 | this.configuration = configuration; 34 | this.esController = esController; 35 | 36 | searchMbean = SearchMbean.getInstance(); 37 | 38 | // Creating the executor service 39 | ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("search-thread-%d").build(); 40 | threadPool = Executors.newFixedThreadPool(configuration.getNumberOfThreads(), namedThreadFactory); 41 | } 42 | 43 | @Override 44 | public String getControllerName() { 45 | return "Search"; 46 | } 47 | 48 | @Override 49 | public void run() { 50 | 51 | IntStream.range(0, configuration.getNumberOfThreads()) 52 | .forEach((threadNumber) -> threadPool.submit(() -> startSearching(threadNumber))); 53 | } 54 | 55 | @Override 56 | public void stop() { 57 | 58 | threadPool.shutdownNow(); 59 | } 60 | 61 | @SuppressWarnings("WeakerAccess") 62 | public void startSearching(int threadNumber) { 63 | 64 | logger.debug("Starting searching thread #{}", threadNumber); 65 | while (true) { 66 | try { 67 | 68 | if (Thread.interrupted()) 69 | return; 70 | 71 | Stopwatch stopwatch = Stopwatch.createUnstarted(); 72 | 73 | try { 74 | String currSearch = esController.getSearch(); 75 | 76 | Search search = new Search.Builder(currSearch) 77 | .addIndex(esController.getSearchIndex()) 78 | .addType(esController.getDefaultType()) 79 | .build(); 80 | 81 | stopwatch.start(); 82 | int docCount = esController.executeSearch(search); 83 | stopwatch.stop(); 84 | 85 | searchMbean.incrementSuccessfulSearches(); 86 | searchMbean.incrementNumberOfFetchedDocuments(docCount); 87 | searchMbean.incrementTotalSearchTimeMs(stopwatch.elapsed(TimeUnit.MILLISECONDS)); 88 | 89 | } catch (CouldNotExecuteSearchException e) { 90 | stopwatch.stop(); 91 | logger.debug("Could not execute search", e); 92 | searchMbean.incrementNumberOfFailedSearches(); 93 | } 94 | 95 | } catch (Throwable throwable){ 96 | logger.debug("Got unexpected exception while searching!", throwable); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/elasticsearch/ElasticsearchController.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.elasticsearch; 2 | 3 | import com.google.common.io.Files; 4 | import com.google.common.io.Resources; 5 | import io.logz.benchmarks.elasticsearch.configuration.ElasticsearchConfiguration; 6 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotCompleteBulkOperationException; 7 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotExecuteSearchException; 8 | import io.logz.benchmarks.elasticsearch.exceptions.CouldNotOptimizeException; 9 | import io.searchbox.client.JestClient; 10 | import io.searchbox.client.JestClientFactory; 11 | import io.searchbox.client.JestResult; 12 | import io.searchbox.client.config.HttpClientConfig; 13 | import io.searchbox.core.Bulk; 14 | import io.searchbox.core.BulkResult; 15 | import io.searchbox.core.Search; 16 | import io.searchbox.core.SearchResult; 17 | import io.searchbox.indices.CreateIndex; 18 | import io.searchbox.indices.DeleteIndex; 19 | import io.searchbox.indices.ForceMerge; 20 | import io.searchbox.indices.mapping.PutMapping; 21 | import org.apache.commons.lang3.StringUtils; 22 | import org.elasticsearch.common.settings.Settings; 23 | import org.reflections.Reflections; 24 | import org.reflections.scanners.ResourcesScanner; 25 | import org.reflections.util.ClasspathHelper; 26 | import org.reflections.util.ConfigurationBuilder; 27 | import org.reflections.util.FilterBuilder; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.net.URL; 34 | import java.nio.charset.Charset; 35 | import java.nio.charset.StandardCharsets; 36 | import java.util.ArrayList; 37 | import java.util.Optional; 38 | import java.util.Set; 39 | import java.util.UUID; 40 | import java.util.concurrent.ThreadLocalRandom; 41 | import java.util.concurrent.atomic.AtomicInteger; 42 | import java.util.regex.Pattern; 43 | import java.util.stream.IntStream; 44 | 45 | /** 46 | * Created by roiravhon on 9/19/16. 47 | */ 48 | public class ElasticsearchController { 49 | 50 | private static final Logger logger = LoggerFactory.getLogger(ElasticsearchController.class); 51 | private static final int INDEX_LENGTH = 8; 52 | private static final int FIELD_CARDINALITY = 100; 53 | private static final int MAX_CHARS_IN_FIELD = 10; 54 | private static final int MIN_CHARS_IN_FIELD = 5; 55 | private static final int JEST_READ_TIMEOUT = 2 * 60 * 1000; 56 | private static final String TEMPLATE_DOCUMENTS_RESOURCE_PATTERN = ".*templates/documents.*"; 57 | private static final String TEMPLATE_SEARCHES_RESOURCE_PATTERN = ".*templates/searches.*"; 58 | private static final String TIMESTAMP_PLACEHOLDER = "TIMESTAMP"; 59 | private static final String RANDOM_STR_PLACEHOLDER = "RANDSTR"; 60 | private static final String RANDOM_INT_PLACEHOLDER = "RANDINT"; 61 | private static final String DEFAULT_TYPE = "benchmark"; 62 | 63 | private final ElasticsearchConfiguration esConfig; 64 | private final ArrayList randomFieldsList; 65 | private final ArrayList rawDocumentsList; 66 | private final ArrayList searchesList; 67 | private final JestClient client; 68 | private final String indexName; 69 | private final Optional indexPrefix; 70 | private final AtomicInteger lastSelectedDocument; 71 | private final AtomicInteger lastSelectedSearch; 72 | 73 | private boolean indexCreated = false; 74 | 75 | public ElasticsearchController(ElasticsearchConfiguration esConfig) { 76 | this.esConfig = esConfig; 77 | lastSelectedDocument = new AtomicInteger(0); 78 | lastSelectedSearch = new AtomicInteger(0); 79 | 80 | indexPrefix = Optional.ofNullable(esConfig.getIndexPrefix()); 81 | 82 | indexName = indexPrefix.orElse("") + getRandomString(INDEX_LENGTH); 83 | 84 | logger.info("Random test index name set to: {}", indexName); 85 | 86 | client = initializeJestClient(esConfig); 87 | randomFieldsList = generateRandomFieldList(); 88 | rawDocumentsList = loadDocuments(esConfig.getDocumentsPath(), TEMPLATE_DOCUMENTS_RESOURCE_PATTERN); 89 | searchesList = loadDocuments(esConfig.getSearchesPath(), TEMPLATE_SEARCHES_RESOURCE_PATTERN); 90 | } 91 | 92 | private ArrayList loadDocuments(String path, String resource) { 93 | if (path != null) { 94 | try { 95 | return getExternalDirectoryContent(path); 96 | } catch (RuntimeException e) { 97 | logger.error("failed to load documents from path " + path + ". Fallback, going to load default documents"); 98 | } 99 | } 100 | 101 | return getResourceDirectoryContent(resource); 102 | } 103 | 104 | public void createIndex() { 105 | Settings.Builder settingsBuilder = Settings.settingsBuilder(); 106 | settingsBuilder.put("number_of_shards", esConfig.getNumberOfShards()); 107 | settingsBuilder.put("number_of_replicas", esConfig.getNumberOfReplicas()); 108 | 109 | // No nice way to build this with elasticsearch library 110 | String mappings = "{ \"" + DEFAULT_TYPE + "\" : { \"properties\" : { \"@timestamp\" : {\"type\" : \"date\", \"format\" : \"epoch_millis\"}}}}"; 111 | 112 | try { 113 | logger.info("Creating test index on ES, named {} with {} shards and {} replicas", indexName, esConfig.getNumberOfShards(), esConfig.getNumberOfReplicas()); 114 | client.execute(new CreateIndex.Builder(indexName).settings(settingsBuilder.build().getAsMap()).build()); 115 | client.execute(new PutMapping.Builder(indexName, DEFAULT_TYPE, mappings).build()); 116 | 117 | indexCreated = true; 118 | 119 | } catch (IOException e) { 120 | throw new RuntimeException("Could not create index in elasticsearch!", e); 121 | } 122 | } 123 | 124 | public void deleteIndex() { 125 | try { 126 | if (indexCreated) { 127 | logger.info("Deleting test index {} from ES", indexName); 128 | client.execute(new DeleteIndex.Builder(indexName).build()); 129 | } 130 | 131 | } catch (IOException e) { 132 | throw new RuntimeException("Could not delete index from ES!", e); 133 | } 134 | } 135 | 136 | public ArrayList getMultipleDocuments(int numberOfDocument) { 137 | 138 | ArrayList tempList = new ArrayList<>(numberOfDocument + 1); 139 | IntStream.range(0, numberOfDocument).forEach((i) -> tempList.add(getDocument())); 140 | return tempList; 141 | } 142 | 143 | public String getDefaultType() { 144 | return DEFAULT_TYPE; 145 | } 146 | 147 | public String getIndexName() { 148 | return indexName; 149 | } 150 | 151 | public Optional getIndexPrefix() { 152 | return indexPrefix; 153 | } 154 | 155 | public String getSearchIndex() { 156 | return getIndexPrefix().isPresent() ? getIndexPrefix().get() + "*" : getIndexName(); 157 | } 158 | 159 | // Return the number of failed documents 160 | public int executeBulk(Bulk bulk) throws CouldNotCompleteBulkOperationException { 161 | try { 162 | 163 | BulkResult result = client.execute(bulk); 164 | return result.getFailedItems().size(); 165 | 166 | } catch (IOException e) { 167 | throw new CouldNotCompleteBulkOperationException(e); 168 | } 169 | } 170 | 171 | // Returns the number of documents found 172 | public int executeSearch(Search search) throws CouldNotExecuteSearchException { 173 | 174 | try { 175 | SearchResult result = client.execute(search); 176 | 177 | if (result.isSucceeded()) { 178 | return result.getTotal(); 179 | } else { 180 | logger.debug(result.getErrorMessage()); 181 | throw new CouldNotExecuteSearchException(); 182 | } 183 | } catch (IOException e) { 184 | throw new CouldNotExecuteSearchException(e); 185 | } 186 | } 187 | 188 | public void executeForceMerge(ForceMerge forceMerge) throws CouldNotOptimizeException { 189 | try { 190 | JestResult result = client.execute(forceMerge); 191 | if (!result.isSucceeded()) 192 | throw new CouldNotOptimizeException(result.getErrorMessage()); 193 | 194 | } catch (IOException e) { 195 | throw new CouldNotOptimizeException(e); 196 | } 197 | } 198 | 199 | public String getSearch() { 200 | String currSearch = searchesList.get(lastSelectedSearch.get() % searchesList.size()); 201 | lastSelectedSearch.incrementAndGet(); 202 | return currSearch; 203 | } 204 | 205 | private String getDocument() { 206 | 207 | String currDocument = rawDocumentsList.get(lastSelectedDocument.get() % rawDocumentsList.size()); 208 | currDocument = currDocument.replace(TIMESTAMP_PLACEHOLDER, String.valueOf(System.currentTimeMillis())); 209 | 210 | // Replacing all marks with values 211 | while (true) { 212 | 213 | currDocument = currDocument.replaceFirst(RANDOM_STR_PLACEHOLDER, getRandomField()); 214 | currDocument = currDocument.replaceFirst(RANDOM_INT_PLACEHOLDER, String.valueOf(getRandomFieldInt())); 215 | 216 | // Breaking out if we are done replacing 217 | if (!currDocument.contains(RANDOM_STR_PLACEHOLDER) && !currDocument.contains(RANDOM_INT_PLACEHOLDER)) 218 | break; 219 | } 220 | 221 | lastSelectedDocument.incrementAndGet(); 222 | return currDocument; 223 | } 224 | 225 | private String getRandomField() { 226 | int index = ThreadLocalRandom.current().nextInt(0, FIELD_CARDINALITY); 227 | return randomFieldsList.get(index); 228 | } 229 | 230 | private int getRandomFieldInt() { 231 | return ThreadLocalRandom.current().nextInt(0, 10000 + 1); 232 | } 233 | 234 | private String getRandomString(int length) { 235 | try { 236 | return UUID.randomUUID().toString().substring(0, length); 237 | } catch (IndexOutOfBoundsException e) { 238 | throw new RuntimeException("Cannot create random string of size " + length + "! this is probably due to internal parameters changes you made.", e); 239 | } 240 | } 241 | 242 | private String getRandomStringInRandomSize(int minSize, int maxSize) { 243 | int length = ThreadLocalRandom.current().nextInt(minSize, maxSize + 1); 244 | return getRandomString(length); 245 | } 246 | 247 | private JestClient initializeJestClient(ElasticsearchConfiguration esConfig) { 248 | JestClientFactory factory = new JestClientFactory(); 249 | HttpClientConfig.Builder httpClientBuilder = new HttpClientConfig 250 | .Builder(esConfig.getElasticsearchProtocol() + "://" + esConfig.getElasticsearchAddress() + ":" + esConfig.getElasticsearchPort()) 251 | .multiThreaded(true) 252 | .readTimeout(JEST_READ_TIMEOUT); 253 | 254 | if (StringUtils.isNotEmpty(esConfig.getUserName()) && StringUtils.isNotEmpty(esConfig.getPassword())) { 255 | httpClientBuilder.defaultCredentials(esConfig.getUserName(), esConfig.getPassword()); 256 | } 257 | 258 | factory.setHttpClientConfig(httpClientBuilder.build()); 259 | 260 | logger.info("Creating Jest client to handle all ES operations"); 261 | return factory.getObject(); 262 | } 263 | 264 | private ArrayList generateRandomFieldList() { 265 | ArrayList tempRandomFieldList = new ArrayList<>(FIELD_CARDINALITY); 266 | 267 | IntStream.range(0, FIELD_CARDINALITY).forEach((i) -> 268 | tempRandomFieldList.add(getRandomStringInRandomSize(MIN_CHARS_IN_FIELD, MAX_CHARS_IN_FIELD))); 269 | 270 | return tempRandomFieldList; 271 | } 272 | 273 | private ArrayList getResourceDirectoryContent(String resourcePattern) { 274 | ArrayList tempFilesContentList = new ArrayList<>(); 275 | 276 | Reflections reflections = new Reflections(new ConfigurationBuilder() 277 | .setUrls(ClasspathHelper.forPackage("io.logz")) 278 | .setScanners(new ResourcesScanner()) 279 | .filterInputsBy(new FilterBuilder().include(resourcePattern))); 280 | Set properties = reflections.getResources(Pattern.compile(".*\\.json")); 281 | 282 | properties.forEach((resourceName) -> { 283 | 284 | URL resourceUrl = Resources.getResource(resourceName); 285 | try { 286 | tempFilesContentList.add(Resources.toString(resourceUrl, Charset.forName("utf-8")).replace("\n", "")); 287 | 288 | } catch (IOException e) { 289 | logger.info("Could not read file {}", resourceUrl.toString()); 290 | } 291 | }); 292 | 293 | if (tempFilesContentList.isEmpty()) 294 | throw new RuntimeException("Did not find any files under "+ resourcePattern +"!"); 295 | 296 | return tempFilesContentList; 297 | } 298 | 299 | private ArrayList getExternalDirectoryContent(String path) { 300 | ArrayList tempFilesContentList = new ArrayList<>(); 301 | 302 | File directory = new File(path); 303 | if (!directory.isDirectory()) { 304 | throw new RuntimeException(path +" is not a directory!"); 305 | } 306 | 307 | File[] documents = directory.listFiles(); 308 | 309 | if (documents == null || documents.length == 0) { 310 | throw new RuntimeException("Did not find any files under "+ path +"!"); 311 | } 312 | 313 | for (File doc : documents) { 314 | try { 315 | tempFilesContentList.add(String.join("", Files.readLines(doc, StandardCharsets.UTF_8))); 316 | } catch (IOException e) { 317 | logger.info("Could not read file {}", doc.getAbsolutePath()); 318 | } 319 | } 320 | 321 | if (tempFilesContentList.isEmpty()) { 322 | throw new RuntimeException("Failed to load all files under " + path); 323 | } 324 | 325 | return tempFilesContentList; 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/exceptions/CouldNotCompleteBulkOperationException.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.exceptions; 2 | 3 | /** 4 | * Created by roiravhon on 9/26/16. 5 | */ 6 | public class CouldNotCompleteBulkOperationException extends Exception { 7 | 8 | public CouldNotCompleteBulkOperationException() { 9 | } 10 | 11 | public CouldNotCompleteBulkOperationException(String message) { 12 | super(message); 13 | } 14 | 15 | public CouldNotCompleteBulkOperationException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public CouldNotCompleteBulkOperationException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public CouldNotCompleteBulkOperationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/exceptions/CouldNotExecuteSearchException.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.exceptions; 2 | 3 | /** 4 | * Created by roiravhon on 9/27/16. 5 | */ 6 | public class CouldNotExecuteSearchException extends Exception { 7 | public CouldNotExecuteSearchException() { 8 | } 9 | 10 | public CouldNotExecuteSearchException(String message) { 11 | super(message); 12 | } 13 | 14 | public CouldNotExecuteSearchException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public CouldNotExecuteSearchException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public CouldNotExecuteSearchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/exceptions/CouldNotOptimizeException.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.exceptions; 2 | 3 | /** 4 | * Created by roiravhon on 9/27/16. 5 | */ 6 | public class CouldNotOptimizeException extends Exception { 7 | public CouldNotOptimizeException() { 8 | } 9 | 10 | public CouldNotOptimizeException(String message) { 11 | super(message); 12 | } 13 | 14 | public CouldNotOptimizeException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public CouldNotOptimizeException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public CouldNotOptimizeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/exceptions/InvalidConfigurationException.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.exceptions; 2 | 3 | /** 4 | * Created by roiravhon on 9/21/16. 5 | */ 6 | public class InvalidConfigurationException extends Exception { 7 | 8 | public InvalidConfigurationException() { 9 | } 10 | 11 | public InvalidConfigurationException(String message) { 12 | super(message); 13 | } 14 | 15 | public InvalidConfigurationException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public InvalidConfigurationException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public InvalidConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/metrics/GeneralMbean.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.metrics; 2 | 3 | import com.udojava.jmx.wrapper.JMXBean; 4 | import com.udojava.jmx.wrapper.JMXBeanAttribute; 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | /** 9 | * Created by roiravhon on 9/20/16. 10 | */ 11 | @JMXBean(description = "General JMX metrics") 12 | public class GeneralMbean { 13 | 14 | private static GeneralMbean instance; 15 | private final AtomicInteger currStep; 16 | 17 | public static GeneralMbean getInstance() { 18 | if (instance == null) { 19 | instance = new GeneralMbean(); 20 | } 21 | 22 | return instance; 23 | } 24 | 25 | private GeneralMbean() { 26 | currStep = new AtomicInteger(1); 27 | } 28 | 29 | @SuppressWarnings("unused") 30 | @JMXBeanAttribute(name = "currStep", description = "The current step running") 31 | public long getCurrStep() { 32 | return currStep.get(); 33 | } 34 | 35 | public void incrementStep() { 36 | currStep.incrementAndGet(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/metrics/IndexingMbean.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.metrics; 2 | 3 | import com.udojava.jmx.wrapper.JMXBean; 4 | import com.udojava.jmx.wrapper.JMXBeanAttribute; 5 | 6 | import java.util.concurrent.atomic.AtomicLong; 7 | 8 | /** 9 | * Created by roiravhon on 9/20/16. 10 | */ 11 | @JMXBean(description = "Indexing JMX metrics") 12 | public class IndexingMbean { 13 | 14 | private static IndexingMbean instance; 15 | private final AtomicLong numberOfSuccessfulDocumentsIndexed; 16 | private final AtomicLong numberOfFailedDocumentsIndexed; 17 | 18 | public static IndexingMbean getInstance() { 19 | if (instance == null) { 20 | instance = new IndexingMbean(); 21 | } 22 | 23 | return instance; 24 | } 25 | 26 | private IndexingMbean() { 27 | numberOfSuccessfulDocumentsIndexed = new AtomicLong(); 28 | numberOfFailedDocumentsIndexed = new AtomicLong(); 29 | } 30 | 31 | @SuppressWarnings("unused") 32 | @JMXBeanAttribute(name = "numberOfSuccessfulDocumentsIndexed", description = "The accumulated number of successful documents indexed") 33 | public long getNumberOfSuccessfulDocumentsIndexed() { 34 | return numberOfSuccessfulDocumentsIndexed.get(); 35 | } 36 | 37 | @SuppressWarnings("unused") 38 | @JMXBeanAttribute(name = "numberOfFailedDocumentsIndexed", description = "The accumulated number of failed documents indexed") 39 | public long getNumberOfFailedDocumentsIndexed() { 40 | return numberOfFailedDocumentsIndexed.get(); 41 | } 42 | 43 | public void incrementSuccessfulDocuments(long docCount) { 44 | numberOfSuccessfulDocumentsIndexed.addAndGet(docCount); 45 | } 46 | 47 | public void incrementFailedDocuements(long docCount) { 48 | numberOfFailedDocumentsIndexed.addAndGet(docCount); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/io/logz/benchmarks/elasticsearch/metrics/SearchMbean.java: -------------------------------------------------------------------------------- 1 | package io.logz.benchmarks.elasticsearch.metrics; 2 | 3 | import com.udojava.jmx.wrapper.JMXBean; 4 | import com.udojava.jmx.wrapper.JMXBeanAttribute; 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * Created by roiravhon on 9/20/16. 11 | */ 12 | @JMXBean(description = "Search JMX metrics") 13 | public class SearchMbean { 14 | 15 | private static SearchMbean instance; 16 | private final AtomicLong numberOfSuccessfulSearches; 17 | private final AtomicLong totalSuccessfulSearchesTimeMs; 18 | private final AtomicLong numberOfFailedSearches; 19 | private final AtomicLong numberOfFetchedDocuments; 20 | 21 | public static SearchMbean getInstance() { 22 | if (instance == null) { 23 | instance = new SearchMbean(); 24 | } 25 | 26 | return instance; 27 | } 28 | 29 | private SearchMbean() { 30 | numberOfSuccessfulSearches = new AtomicLong(); 31 | totalSuccessfulSearchesTimeMs = new AtomicLong(); 32 | numberOfFailedSearches = new AtomicLong(); 33 | numberOfFetchedDocuments = new AtomicLong(); 34 | } 35 | 36 | @SuppressWarnings("unused") 37 | @JMXBeanAttribute(name = "numberOfSuccessfulSearches", description = "The accumulated number of successful documents searched") 38 | public long getNumberOfSuccessfulSearches() { 39 | return numberOfSuccessfulSearches.get(); 40 | } 41 | 42 | @SuppressWarnings("unused") 43 | @JMXBeanAttribute(name = "totalSuccessfulSearchesTimeMs", description = "The total time all successful queries took, in MS") 44 | public long getTotalSuccessfulSearchesTimeMs() { 45 | return totalSuccessfulSearchesTimeMs.get(); 46 | } 47 | 48 | @SuppressWarnings("unused") 49 | @JMXBeanAttribute(name = "averageSearchTimeMs", description = "The average time each query tool, in MS") 50 | public long getAverageSearchTimeMs() { 51 | if (getNumberOfSuccessfulSearches() > 0) { 52 | return getTotalSuccessfulSearchesTimeMs() / getNumberOfSuccessfulSearches(); 53 | } 54 | return 0; 55 | } 56 | 57 | @SuppressWarnings("unused") 58 | @JMXBeanAttribute(name = "numberOfFailedSearches", description = "The accumulated number of failed searches") 59 | public long getNumberOfFailedSearches() { 60 | return numberOfFailedSearches.get(); 61 | } 62 | 63 | @SuppressWarnings("unused") 64 | @JMXBeanAttribute(name = "numberOfFetchedDocuments", description = "The accumulated number of retrieved documents from searches") 65 | public long getNumberOfFetchedDocuments() { 66 | return numberOfFetchedDocuments.get(); 67 | } 68 | 69 | public void incrementSuccessfulSearches() { 70 | numberOfSuccessfulSearches.incrementAndGet(); 71 | } 72 | 73 | public void incrementTotalSearchTimeMs(long searchTime) { 74 | totalSuccessfulSearchesTimeMs.addAndGet(searchTime); 75 | } 76 | 77 | public void incrementNumberOfFailedSearches() { 78 | numberOfFailedSearches.incrementAndGet(); 79 | } 80 | 81 | public void incrementNumberOfFetchedDocuments(long docCount) { 82 | numberOfFetchedDocuments.addAndGet(docCount); 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%d{HH:mm:ss.SSS}] [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/documents/document1.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": TIMESTAMP, 3 | "message": "RANDSTR", 4 | "itemA": "RANDSTR", 5 | "itemB": "RANDSTR", 6 | "clientId": RANDINT, 7 | "productId": RANDINT 8 | } -------------------------------------------------------------------------------- /src/main/resources/templates/documents/document2.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": TIMESTAMP, 3 | "bookId": RANDINT, 4 | "orderId": RANDINT, 5 | "bookName": "RANDSTR" 6 | } -------------------------------------------------------------------------------- /src/main/resources/templates/documents/document3.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": TIMESTAMP, 3 | "clientPassword": "RANDSTR", 4 | "clientName": "RANDSTR", 5 | "clientAge": RANDINT, 6 | "clientId": RANDINT 7 | } -------------------------------------------------------------------------------- /src/main/resources/templates/documents/document4.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": TIMESTAMP, 3 | "metricName": "RANDSTR", 4 | "value": RANDINT 5 | } -------------------------------------------------------------------------------- /src/main/resources/templates/documents/document5.json: -------------------------------------------------------------------------------- 1 | { 2 | "@timestamp": TIMESTAMP, 3 | "message": "RANDSTR" 4 | } -------------------------------------------------------------------------------- /src/main/resources/templates/searches/datehistogram-with-sub-range-agg-2-hours.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "bool": { 4 | "must": { 5 | "query_string": { 6 | "query": "*", 7 | "analyze_wildcard": true 8 | } 9 | }, 10 | "filter": { 11 | "bool": { 12 | "must": [{ 13 | "query_string": { 14 | "query": "*", 15 | "analyze_wildcard": true 16 | } 17 | }, { 18 | "range": { 19 | "@timestamp": { 20 | "gte": "now-2h", 21 | "lte": "now", 22 | "format": "epoch_millis" 23 | } 24 | } 25 | }], 26 | "must_not": [] 27 | } 28 | } 29 | } 30 | }, 31 | "size": 0, 32 | "aggs": { 33 | "2": { 34 | "date_histogram": { 35 | "field": "@timestamp", 36 | "interval": "1m", 37 | "min_doc_count": 1, 38 | "extended_bounds": { 39 | "min": "now-2h", 40 | "max": "now" 41 | } 42 | }, 43 | "aggs": { 44 | "3": { 45 | "range": { 46 | "field": "value", 47 | "ranges": [{ 48 | "from": 10, 49 | "to": 100 50 | }, { 51 | "from": 100, 52 | "to": 500 53 | }, { 54 | "from": 500, 55 | "to": 1000 56 | }], 57 | "keyed": true 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/resources/templates/searches/datehistogram-with-terms-and-sub-cardinality-30-minutes.json: -------------------------------------------------------------------------------- 1 | { 2 | "size": 0, 3 | "query": { 4 | "bool": { 5 | "must" : { 6 | "query_string": { 7 | "query": "*", 8 | "analyze_wildcard": true 9 | } 10 | }, 11 | "filter": { 12 | "bool": { 13 | "must": [{ 14 | "range": { 15 | "@timestamp": { 16 | "gte": "now-30m", 17 | "lte": "now", 18 | "format": "epoch_millis" 19 | } 20 | } 21 | }], 22 | "must_not": [] 23 | } 24 | } 25 | } 26 | }, 27 | "aggs": { 28 | "2": { 29 | "date_histogram": { 30 | "field": "@timestamp", 31 | "interval": "1m", 32 | "min_doc_count": 1, 33 | "extended_bounds": { 34 | "min": "now-30m", 35 | "max": "now" 36 | } 37 | }, 38 | "aggs": { 39 | "3": { 40 | "terms": { 41 | "field": "clientId", 42 | "size": 100, 43 | "order": { 44 | "1": "desc" 45 | } 46 | }, 47 | "aggs": { 48 | "1": { 49 | "cardinality": { 50 | "field": "clientAge" 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/resources/templates/searches/kibana-discover-last-30-minutes.json: -------------------------------------------------------------------------------- 1 | { 2 | "size": 500, 3 | "sort": [{ 4 | "@timestamp": { 5 | "order": "desc", 6 | "unmapped_type": "boolean" 7 | } 8 | }], 9 | "highlight": { 10 | "pre_tags": ["@kibana-highlighted-field@"], 11 | "post_tags": ["@/kibana-highlighted-field@"], 12 | "require_field_match": false, 13 | "fragment_size": 2147483647 14 | }, 15 | "query": { 16 | "bool": { 17 | "must": { 18 | "query_string": { 19 | "query": "*", 20 | "analyze_wildcard": true 21 | } 22 | }, 23 | "filter": { 24 | "bool": { 25 | "must": [{ 26 | "range": { 27 | "@timestamp": { 28 | "gte": "now-30m", 29 | "lte": "now", 30 | "format": "epoch_millis" 31 | } 32 | } 33 | }], 34 | "must_not": [] 35 | } 36 | } 37 | } 38 | }, 39 | "aggs": { 40 | "2": { 41 | "date_histogram": { 42 | "field": "@timestamp", 43 | "interval": "30s", 44 | "min_doc_count": 0, 45 | "extended_bounds": { 46 | "min": "now-30m", 47 | "max": "now" 48 | } 49 | } 50 | } 51 | }, 52 | "script_fields": {}, 53 | "fielddata_fields": ["@timestamp"] 54 | } -------------------------------------------------------------------------------- /src/main/resources/templates/searches/simple-search-return-all.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "query_string": { 4 | "query": "_type: benchmark" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/main/resources/templates/searches/terms-10000-size-with-complex-query.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "bool": { 4 | "must": { 5 | "query_string": { 6 | "query": "clientId: [100 TO 10000] AND clientAge: [100 TO 150] " 7 | } 8 | }, 9 | "filter": {} 10 | } 11 | }, 12 | "aggs": { 13 | "names": { 14 | "terms": { 15 | "field": "@timestamp", 16 | "size": 10000 17 | } 18 | } 19 | } 20 | } --------------------------------------------------------------------------------