You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ma...@apache.org on 2021/09/09 16:29:25 UTC

[solr] branch main updated: SOLR-15581: New DSL/impl for benchmark random data generation. (#254)

This is an automated email from the ASF dual-hosted git repository.

markrmiller pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 8880139  SOLR-15581: New DSL/impl for benchmark random data generation. (#254)
8880139 is described below

commit 88801396adf702ad394f557cdd610c4928a2e372
Author: Mark Robert Miller <ma...@gmail.com>
AuthorDate: Thu Sep 9 11:29:14 2021 -0500

    SOLR-15581: New DSL/impl for benchmark random data generation. (#254)
---
 .gitignore                                         |      1 +
 gradle/validation/validate-source-patterns.gradle  |     11 +-
 solr/benchmark/README.md                           |    353 +-
 solr/benchmark/build.gradle                        |      2 +
 solr/benchmark/jmh.sh                              |     12 +-
 solr/benchmark/{log4j2.xml => log4j2-bench.xml}    |     38 +-
 .../java/org/apache/solr/bench/BaseBenchState.java |    150 +
 .../src/java/org/apache/solr/bench/DocMaker.java   |    299 -
 .../src/java/org/apache/solr/bench/Docs.java       |    227 +
 .../src/java/org/apache/solr/bench/FieldDef.java   |    170 -
 .../org/apache/solr/bench/MiniClusterState.java    |    259 +-
 .../java/org/apache/solr/bench/SolrGenerate.java   |    382 +
 .../apache/solr/bench/SolrRandomnessSource.java    |     40 +
 .../solr/bench/SplittableRandomGenerator.java      |    119 +
 .../apache/solr/bench/generators/ArraysDSL.java    |    133 +
 .../apache/solr/bench/generators/BooleansDSL.java  |     31 +
 .../org/apache/solr/bench/generators/DatesDSL.java |     97 +
 .../apache/solr/bench/generators/Distribution.java |     27 +
 .../org/apache/solr/bench/generators/Doubles.java  |     66 +
 .../apache/solr/bench/generators/DoublesDSL.java   |    103 +
 .../org/apache/solr/bench/generators/Floats.java   |     58 +
 .../apache/solr/bench/generators/FloatsDSL.java    |    105 +
 .../apache/solr/bench/generators/IntegersDSL.java  |    225 +
 .../org/apache/solr/bench/generators/LazyGen.java  |     59 +
 .../org/apache/solr/bench/generators/Lists.java    |     70 +
 .../org/apache/solr/bench/generators/ListsDSL.java |    172 +
 .../org/apache/solr/bench/generators/LongsDSL.java |     97 +
 .../org/apache/solr/bench/generators/Maps.java     |    110 +
 .../org/apache/solr/bench/generators/MapsDSL.java  |    104 +
 .../apache/solr/bench/generators/MultiString.java  |     20 +
 .../apache/solr/bench/generators/NamedListGen.java |     38 +
 .../solr/bench/generators/RandomDataHistogram.java |    544 +
 .../org/apache/solr/bench/generators/SolrGen.java  |    425 +
 .../apache/solr/bench/generators/SourceDSL.java    |    180 +
 .../org/apache/solr/bench/generators/Strings.java  |    103 +
 .../apache/solr/bench/generators/StringsDSL.java   |    481 +
 .../org/apache/solr/bench/generators/Tracker.java  |    170 +
 .../apache/solr/bench/generators/package-info.java |     18 +
 .../org/apache/solr/bench/index/CloudIndexing.java |    183 +-
 .../org/apache/solr/bench/search/JsonFaceting.java |     88 +-
 .../quicktheories/impl/BenchmarkRandomSource.java  |    126 +
 .../quicktheories/impl/SplittableRandomSource.java |     75 +
 .../configs/cloud-minimal/conf/schema.xml          |     51 +-
 .../configs/cloud-minimal/conf/solrconfig.xml      |     44 +-
 solr/benchmark/src/resources/words.txt             | 466550 ++++++++++++++++++
 solr/benchmark/{ => src/test-files}/log4j2.xml     |     70 +-
 .../test/org/apache/solr/bench/DockMakerTest.java  |    234 +-
 .../solr/bench/MiniClusterBenchStateTest.java      |    133 +
 .../test/org/apache/solr/bench/NestedMapTest.java  |     96 +
 .../solr/index/SimpleMergePolicyFactory.java       |     20 +-
 solr/licenses/jctools-LICENSE-ASL.txt              |    201 +
 solr/licenses/jctools-NOTICE.txt                   |      0
 solr/licenses/jctools-core-3.3.0.jar.sha1          |      1 +
 solr/licenses/quicktheories-0.26.jar.sha1          |      1 +
 solr/licenses/quicktheories-LICENSE-ASL.txt        |    202 +
 solr/licenses/quicktheories-NOTICE.txt             |      0
 .../apache/solr/common/util/BytesOutputStream.java |    141 +
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |      2 +-
 versions.lock                                      |      2 +
 versions.props                                     |      2 +
 60 files changed, 472793 insertions(+), 928 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4ff1ee0..03cd821 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ build/
 
 # IntelliJ IDEA
 /.idea/
+.run
 **/*.iml
 **/*.ipr
 **/*.iws
diff --git a/gradle/validation/validate-source-patterns.gradle b/gradle/validation/validate-source-patterns.gradle
index 009c6d3..f48fcb8 100644
--- a/gradle/validation/validate-source-patterns.gradle
+++ b/gradle/validation/validate-source-patterns.gradle
@@ -78,6 +78,7 @@ subprojects {
 
       // default excludes.
       exclude 'build/**'
+      exclude '**/.idea/**'
     }
   }
 
@@ -113,6 +114,7 @@ configure(rootProject) {
 
       // default excludes.
       exclude '**/build/**'
+      exclude '**/.idea/**'
       exclude 'dev-tools/missing-doclet/src/**/*.java' // <-- TODO: remove once we allow "var" on master
 
       // ourselves :-)
@@ -211,11 +213,12 @@ class ValidateSourcePatternsTask extends DefaultTask {
     progress.start(this.name, this.name)
 
     sourceFiles.each{ f ->
+
       progress.progress("Scanning ${f.name}")
       logger.debug('Scanning source file: {}', f);
 
       def text = f.getText('UTF-8');
-      invalidPatterns.each{ pattern,name ->
+      invalidPatterns.each { pattern, name ->
         if (pattern.matcher(text).find()) {
           reportViolation(f, name);
         }
@@ -231,9 +234,15 @@ class ValidateSourcePatternsTask extends DefaultTask {
       if (f.name.endsWith('.java')) {
         if (text.contains('org.slf4j.LoggerFactory')) {
           if (!validLoggerPattern.matcher(text).find()) {
+            if (text.contains("// nowarn_valid_logger")) {
+              return
+            }
             reportViolation(f, 'invalid logging pattern [not private static final, uses static class name]');
           }
           if (!validLoggerNamePattern.matcher(text).find()) {
+            if (text.contains("// nowarn_valid_logger")) {
+              return
+            }
             reportViolation(f, 'invalid logger name [log, uses static class name, not specialized logger]')
           }
         }
diff --git a/solr/benchmark/README.md b/solr/benchmark/README.md
index 81e0633..e861b19 100644
--- a/solr/benchmark/README.md
+++ b/solr/benchmark/README.md
@@ -1,159 +1,324 @@
-### JMH-Benchmarks module
+JMH-Benchmarks module
+=====================
 
 This module contains benchmarks written using [JMH](https://openjdk.java.net/projects/code-tools/jmh/) from OpenJDK.
-Writing correct micro-benchmarks in Java (or another JVM language) is difficult and there are many non-obvious pitfalls (many
-due to compiler optimizations). JMH is a framework for running and analyzing benchmarks (micro or macro) written in Java (or
-another JVM language).
-
-### Running benchmarks
-
-If you want to set specific JMH flags or only run certain benchmarks, passing arguments via
-gradle tasks is cumbersome. The process has been simplified by the provided `jmh.sh` script.
+Writing correct micro-benchmarks in Java (or another JVM language) is difficult and there are many non-obvious
+pitfalls (many due to compiler optimizations). JMH is a framework for running and analyzing benchmarks (micro or macro)
+written in Java (or another JVM language).
+
+* [JMH-Benchmarks module](#jmh-benchmarks-module)
+  * [Running benchmarks](#running-benchmarks)
+    * [Using JMH with async profiler](#using-jmh-with-async-profiler)
+    * [Using JMH GC profiler](#using-jmh-gc-profiler)
+    * [Using JMH Java Flight Recorder profiler](#using-jmh-java-flight-recorder-profiler)
+    * [JMH Options](#jmh-options)
+  * [Writing benchmarks](#writing-benchmarks)
+    * [SolrCloud MiniCluster Benchmark Setup](#solrcloud-minicluster-benchmark-setup)
+      * [MiniCluster Metrics](#minicluster-metrics)
+    * [Benchmark Repeatability](#benchmark-repeatability)
+
+## Running benchmarks
+
+If you want to set specific JMH flags or only run certain benchmarks, passing arguments via gradle tasks is cumbersome.
+The process has been simplified by the provided `jmh.sh` script.
 
 The default behavior is to run all benchmarks:
 
-    ./jmh.sh
+`./jmh.sh`
 
 Pass a pattern or name after the command to select the benchmarks:
 
-    ./jmh.sh CloudIndexing
+`./jmh.sh CloudIndexing`
 
 Check which benchmarks match the provided pattern:
 
-    ./jmh.sh -l CloudIndexing
+`./jmh.sh -l CloudIndexing`
 
-Run a specific test and override the number of forks, iterations and warm-up iteration to `2`:
+Run a specific test and overrides the number of forks, iterations and sets warm-up iterations to `2`:
 
-    ./jmh.sh -f 2 -i 2 -wi 2 CloudIndexing
+`./jmh.sh -f 2 -i 2 -wi 2 CloudIndexing`
 
 Run a specific test with async and GC profilers on Linux and flame graph output:
 
-    ./jmh.sh -prof gc -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph\;dir=profile-results CloudIndexing
+`./jmh.sh -prof gc -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph\;dir=profile-results CloudIndexing`
 
 ### Using JMH with async profiler
 
 It's good practice to check profiler output for micro-benchmarks in order to verify that they represent the expected
-application behavior and measure what you expect to measure. Some example pitfalls include the use of expensive mocks
-or accidental inclusion of test setup code in the benchmarked code. JMH includes
+application behavior and measure what you expect to measure. Some example pitfalls include the use of expensive mocks or
+accidental inclusion of test setup code in the benchmarked code. JMH includes
 [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) integration that makes this easy:
 
-    ./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;dir=profile-results
+`./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;dir=profile-results`
 
 With flame graph output:
 
-    ./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph\;dir=profile-results
+`./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph\;dir=profile-results`
 
 Simultaneous cpu, allocation and lock profiling with async profiler 2.0 and jfr output:
 
-    ./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=jfr\;alloc\;lock\;dir=profile-results CloudIndexing
+`./jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=jfr\;alloc\;lock\;dir=profile-results CloudIndexing`
 
 A number of arguments can be passed to configure async profiler, run the following for a description:
 
-    ./jmh.sh -prof async:help
+`./jmh.sh -prof async:help`
+
+You can also skip specifying libPath if you place the async profiler lib in a predefined location, such as one of the
+locations in the env variable `LD_LIBRARY_PATH` if it has been set (many Linux distributions set this env variable, Arch
+by default does not), or `/usr/lib` should work.
+
+#### OS Permissions for Async Profiler
+
+Async Profiler uses perf to profile native code in addition to Java code. It will need the following for the necessary
+access.
+
+```bash
+echo 0 > /proc/sys/kernel/kptr_restrict
+echo 1 > /proc/sys/kernel/perf_event_paranoid
+```
+
+or
 
-You can also skip specifying libPath if you place the async profiler lib in a predefined location such as /usr/java/packages/lib
+```bash
+sudo sysctl -w kernel.kptr_restrict=0
+sudo sysctl -w kernel.perf_event_paranoid=1
+```
 
 ### Using JMH GC profiler
 
 You can run a benchmark with `-prof gc` to measure its allocation rate:
 
-    ./jmh.sh -prof gc:dir=profile-results
+`./jmh.sh -prof gc:dir=profile-results`
 
-Of particular importance is the `norm` alloc rates, which measure the allocations per operation rather than allocations per second.
+Of particular importance is the `norm` alloc rates, which measure the allocations per operation rather than allocations
+per second.
 
 ### Using JMH Java Flight Recorder profiler
 
 JMH comes with a variety of built-in profilers. Here is an example of using JFR:
 
-    ./jmh.sh -prof jfr:dir=profile-results\;configName=jfr-profile.jfc
+`./jmh.sh -prof jfr:dir=profile-results\;configName=jfr-profile.jfc`
 
-In this example we point to the included configuration file with configName, but you could also do something like settings=default or settings=profile.
+In this example we point to the included configuration file with configName, but you could also do something like
+settings=default or settings=profile.
 
-### Benchmark Repeatability
+### JMH Options
 
-Indexes created for the benchmarks often involve randomness when generating terms, term length and number of terms in a field. In order to make benchmarks repeatable, a static seed is used for randoms. This allows for generating varying data while ensuring that data is consistent across runs.
+Some common JMH options are:
 
-You can vary that seed by setting a system property to explore a wider range of variation in the benchmark:
+```text
 
-    -jvmArgsAppend -Dsolr.bench.seed=6624420638116043983
+ Usage: ./jmh.sh [regexp*] [options]
+ [opt] means optional argument.
+ <opt> means required argument.
+ "+" means comma-separated list of values.
+ "time" arguments accept time suffixes, like "100ms".
 
-The seed used for a given benchmark run will be printed out near the top of the output.
+Command line options usually take precedence over annotations.
 
-    --> benchmark random seed: 6624420638116043983
+  [arguments]                 Benchmarks to run (regexp+). (default: .*) 
 
-You can also specify where to place the mini-cluster with a system property:
+  -bm <mode>                  Benchmark mode. Available modes are: [Throughput/thrpt, 
+                              AverageTime/avgt, SampleTime/sample, SingleShotTime/ss, 
+                              All/all]. (default: Throughput) 
 
-    -jvmArgsAppend -DminiClusterBaseDir=/benchmark-data/mini-cluster
+  -bs <int>                   Batch size: number of benchmark method calls per 
+                              operation. Some benchmark modes may ignore this 
+                              setting, please check this separately. (default: 
+                              1) 
 
-In this case, new data will not be generated for the benchmark, even if you change parameters. The use case for this if you are running a query based benchmark and want to create a large index for testing and reuse (say hundreds of GB's). Be aware that with this system property set, that same mini-cluster will be reused for any benchmarks run, regardless of if that makes sense or not.
+  -e <regexp+>                Benchmarks to exclude from the run. 
 
-### Writing benchmarks
+  -f <int>                    How many times to fork a single benchmark. Use 0 to 
+                              disable forking altogether. Warning: disabling 
+                              forking may have detrimental impact on benchmark 
+                              and infrastructure reliability, you might want 
+                              to use different warmup mode instead. (default: 
+                              5) 
 
-For help in writing correct JMH tests, the best place to start is the [sample code](https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/) provided
-by the JMH project.
+  -foe <bool>                 Should JMH fail immediately if any benchmark had 
+                              experienced an unrecoverable error? This helps 
+                              to make quick sanity tests for benchmark suites, 
+                              as well as make the automated runs with checking error 
+                              codes. (default: false) 
 
-JMH is highly configurable and users are encouraged to look through the samples for suggestions
-on what options are available. A good tutorial for using JMH can be found [here](http://tutorials.jenkov.com/java-performance/jmh.html#return-value-from-benchmark-method)
+  -gc <bool>                  Should JMH force GC between iterations? Forcing 
+                              the GC may help to lower the noise in GC-heavy benchmarks, 
+                              at the expense of jeopardizing GC ergonomics decisions. 
+                              Use with care. (default: false) 
 
-Many Solr JMH benchmarks are actually closer to a full integration benchmark in that they run a single action against a full Solr mini cluster.
+  -h                          Display help, and exit. 
 
-See org.apache.solr.bench.index.CloudIndexing for an example of this.
+  -i <int>                    Number of measurement iterations to do. Measurement 
+                              iterations are counted towards the benchmark score. 
+                              (default: 1 for SingleShotTime, and 5 for all other 
+                              modes) 
 
-#### MiniCluster Metrics
+  -jvm <string>               Use given JVM for runs. This option only affects forked 
+                              runs. 
 
-After every iteration, the metrics collected by Solr will be dumped to the build/work/metrics-results folder. You can disable metrics collection using the metricsEnabled method of the MiniClusterState, in which case the same output files will be dumped, but the values will all be 0/null.
+  -jvmArgs <string>           Use given JVM arguments. Most options are inherited 
+                              from the host VM options, but in some cases you want 
+                              to pass the options only to a forked VM. Either single 
+                              space-separated option line, or multiple options 
+                              are accepted. This option only affects forked runs. 
 
-### JMH Options
-Some common JMH options are:
+  -jvmArgsAppend <string>     Same as jvmArgs, but append these options after the 
+                              already given JVM args. 
 
-```text
+  -jvmArgsPrepend <string>    Same as jvmArgs, but prepend these options before 
+                              the already given JVM arg. 
+
+  -l                          List the benchmarks that match a filter, and exit. 
+
+  -lp                         List the benchmarks that match a filter, along with 
+                              parameters, and exit. 
+
+  -lprof                      List profilers, and exit. 
+
+  -lrf                        List machine-readable result formats, and exit. 
+
+  -o <filename>               Redirect human-readable output to a given file. 
+
+  -opi <int>                  Override operations per invocation, see @OperationsPerInvocation 
+                              Javadoc for details. (default: 1) 
+
+  -p <param={v,}*>            Benchmark parameters. This option is expected to 
+                              be used once per parameter. Parameter name and parameter 
+                              values should be separated with equals sign. Parameter 
+                              values should be separated with commas. 
+
+  -prof <profiler>            Use profilers to collect additional benchmark data. 
+                              Some profilers are not available on all JVMs and/or 
+                              all OSes. Please see the list of available profilers 
+                              with -lprof. 
+
+  -r <time>                   Minimum time to spend at each measurement iteration. 
+                              Benchmarks may generally run longer than iteration 
+                              duration. (default: 10 s) 
+
+  -rf <type>                  Format type for machine-readable results. These 
+                              results are written to a separate file (see -rff). 
+                              See the list of available result formats with -lrf. 
+                              (default: CSV) 
+
+  -rff <filename>             Write machine-readable results to a given file. 
+                              The file format is controlled by -rf option. Please 
+                              see the list of result formats for available formats. 
+                              (default: jmh-result.<result-format>) 
 
-   -e <regexp+>                Benchmarks to exclude from the run. 
-
-   -f <int>                    How many times to fork a single benchmark. Use 0 to 
-                               disable forking altogether. Warning: disabling 
-                               forking may have detrimental impact on benchmark 
-                               and infrastructure reliability, you might want 
-                               to use different warmup mode instead.
-
-   -i <int>                    Number of measurement iterations to do. Measurement
-                               iterations are counted towards the benchmark score.
-                               (default: 1 for SingleShotTime, and 5 for all other
-                               modes)
-
-   -l                          List the benchmarks that match a filter, and exit.
-
-   -lprof                      List profilers, and exit.
-
-   -o <filename>               Redirect human-readable output to a given file. 
-
-   -prof <profiler>            Use profilers to collect additional benchmark data. 
-                               Some profilers are not available on all JVMs and/or 
-                               all OSes. Please see the list of available profilers 
-                               with -lprof.
-
-   -v <mode>                   Verbosity mode. Available modes are: [SILENT, NORMAL,
-                               EXTRA]
-
-   -wi <int>                   Number of warmup iterations to do. Warmup iterations
-                               are not counted towards the benchmark score. (default:
-                               0 for SingleShotTime, and 5 for all other modes)
-                               
-  -t <int>                    Number of worker threads to run with. 'max' means
-                              the maximum number of hardware threads available
-                              on the machine, figured out by JMH itself.
-                                
-  -rf <type>                  Format type for machine-readable results. These
-                              results are written to a separate file (see -rff).
-                              See the list of available result formats with -lrf.
-
-  -rff <filename>             Write machine-readable results to a given file.
-                              The file format is controlled by -rf option. Please
-                              see the list of result formats for available formats.
-                              
-  -bm <mode>                  Benchmark mode. Available modes are: [Throughput/thrpt,
-                              AverageTime/avgt, SampleTime/sample, SingleShotTime/ss,
-                              All/all]                              
+  -si <bool>                  Should JMH synchronize iterations? This would significantly 
+                              lower the noise in multithreaded tests, by making 
+                              sure the measured part happens only when all workers 
+                              are running. (default: true) 
+
+  -t <int>                    Number of worker threads to run with. 'max' means 
+                              the maximum number of hardware threads available 
+                              on the machine, figured out by JMH itself. (default: 
+                              1) 
+
+  -tg <int+>                  Override thread group distribution for asymmetric 
+                              benchmarks. This option expects a comma-separated 
+                              list of thread counts within the group. See @Group/@GroupThreads 
+                              Javadoc for more information. 
+
+  -to <time>                  Timeout for benchmark iteration. After reaching 
+                              this timeout, JMH will try to interrupt the running 
+                              tasks. Non-cooperating benchmarks may ignore this 
+                              timeout. (default: 10 min) 
+
+  -tu <TU>                    Override time unit in benchmark results. Available 
+                              time units are: [m, s, ms, us, ns]. (default: SECONDS) 
+
+  -v <mode>                   Verbosity mode. Available modes are: [SILENT, NORMAL, 
+                              EXTRA]. (default: NORMAL) 
+
+  -w <time>                   Minimum time to spend at each warmup iteration. Benchmarks 
+                              may generally run longer than iteration duration. 
+                              (default: 10 s) 
+
+  -wbs <int>                  Warmup batch size: number of benchmark method calls 
+                              per operation. Some benchmark modes may ignore this 
+                              setting. (default: 1) 
+
+  -wf <int>                   How many warmup forks to make for a single benchmark. 
+                              All iterations within the warmup fork are not counted 
+                              towards the benchmark score. Use 0 to disable warmup 
+                              forks. (default: 0) 
+
+  -wi <int>                   Number of warmup iterations to do. Warmup iterations 
+                              are not counted towards the benchmark score. (default: 
+                              0 for SingleShotTime, and 5 for all other modes) 
+
+  -wm <mode>                  Warmup mode for warming up selected benchmarks. 
+                              Warmup modes are: INDI = Warmup each benchmark individually, 
+                              then measure it. BULK = Warmup all benchmarks first, 
+                              then do all the measurements. BULK_INDI = Warmup 
+                              all benchmarks first, then re-warmup each benchmark 
+                              individually, then measure it. (default: INDI) 
+
+  -wmb <regexp+>              Warmup benchmarks to include in the run in addition 
+                              to already selected by the primary filters. Harness 
+                              will not measure these benchmarks, but only use them 
+                              for the warmup. 
+```
+
+## Writing benchmarks
+
+For help in writing correct JMH tests, the best place to start is
+the [sample code](https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/)
+provided by the JMH project.
+
+JMH is highly configurable and users are encouraged to look through the samples for suggestions on what options are
+available. A good tutorial for using JMH can be
+found [here](http://tutorials.jenkov.com/java-performance/jmh.html#return-value-from-benchmark-method)
+
+Many Solr JMH benchmarks are actually closer to a full integration benchmark in that they run a single action against a
+full Solr mini cluster.
+
+See [org.apache.solr.bench.index.CloudIndexing](https://github.com/apache/solr/blob/main/solr/benchmark/src/java/org/apache/solr/bench/index/CloudIndexing.java)
+for an example of this.
+
+### SolrCloud MiniCluster Benchmark Setup
+
+#### MiniCluster Setup
+
+- CloudIndexing.java
+
+```java
+    @Setup(Level.Trial)
+    public void doSetup(MiniClusterState.MiniClusterBenchState miniClusterState) throws Exception {
+      System.setProperty("mergePolicyFactory", "org.apache.solr.index.NoMergePolicyFactory");
+      miniClusterState.startMiniCluster(nodeCount);
+      miniClusterState.createCollection(COLLECTION, numShards, numReplicas);
+    }
 ```
 
-To view all options run jmh with the -h flag. 
\ No newline at end of file
+#### MiniCluster Metrics
+
+After every iteration, the metrics collected by Solr will be dumped to the build/work/metrics-results folder. You can
+disable metrics collection using the metricsEnabled method of the MiniClusterState, in which case the same output files
+will be dumped, but the values will all be 0/null.
+
+### Benchmark Repeatability
+
+Indexes created for the benchmarks often involve randomness when generating terms, term length and number of terms in a
+field. In order to make benchmarks repeatable, a static seed is used for randoms. This allows for generating varying
+data while ensuring that data is consistent across runs.
+
+You can vary that seed by setting a system property to explore a wider range of variation in the benchmark:
+
+`-jvmArgsAppend -Dsolr.bench.seed=6624420638116043983`
+
+The seed used for a given benchmark run will be printed out near the top of the output.
+
+> --> benchmark random seed: 6624420638116043983
+
+You can also specify where to place the mini-cluster with a system property:
+
+`-jvmArgsAppend -DminiClusterBaseDir=/benchmark-data/mini-cluster`
+
+In this case, new data will not be generated for the benchmark, even if you change parameters. The use case for this if
+you are running a query based benchmark and want to create a large index for testing and reuse (say hundreds of GB's).
+Be aware that with this system property set, that same mini-cluster will be reused for any benchmarks run, regardless of
+if that makes sense or not.
diff --git a/solr/benchmark/build.gradle b/solr/benchmark/build.gradle
index bf6c9a3..dfbd334 100644
--- a/solr/benchmark/build.gradle
+++ b/solr/benchmark/build.gradle
@@ -46,6 +46,8 @@ task echoCp {
 dependencies {
   implementation project(':solr:test-framework')
 
+  implementation "org.quicktheories:quicktheories"
+  implementation 'org.jctools:jctools-core'
   implementation 'org.openjdk.jmh:jmh-core'
   annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess'
 }
diff --git a/solr/benchmark/jmh.sh b/solr/benchmark/jmh.sh
index 16cbe33..30c72b7 100755
--- a/solr/benchmark/jmh.sh
+++ b/solr/benchmark/jmh.sh
@@ -46,20 +46,22 @@ echo "running JMH with args: $@"
 # -XX:+PreserveFramePointer is not necessary with async profiler and they claim can be up to 10% hit
 
 # -XX:+UseStringDeduplication should experiment with this
-# -XX:MaxMetaspaceExpansion=64M  # and this note:  Avoids triggering full GC when we just allocate a bit more metaspace, and metaspace automatically gets cleaned anyway
+# -XX:MaxMetaspaceExpansion=64M  # and this note:  Avoids triggering full GC when we just allocate a bit more metaspace, and metaspace automatically gets cleaned anyway.
+#                                                  MRM: Metaspace should default to 1GB with compressed ops on I believe, but I think even that is low for Solr in this scenario.
 # -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20  # and this note: Prevents G1 undermining young gen, which otherwise causes a cascade of issues
+#                                                            MRM: I've also seen 15 claimed as a sweet spot.
 
 # -XX:-UseBiasedLocking - should be unreflective in recent JVMs and removed in the latest.
 
-jvmArgs="-jvmArgs -Djmh.shutdownTimeout=5 -jvmArgs -Djmh.separateClassLoader=true -jvmArgs -XX:-UseBiasedLocking -jvmArgs -XX:+UnlockDiagnosticVMOptions -jvmArgs -XX:+DebugNonSafepoints -jvmArgs --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
+jvmArgs="-jvmArgs -Djmh.shutdownTimeout=5 -jvmArgs -Djmh.shutdownTimeout.step=3 -jvmArgs -Djava.security.egd=file:/dev/./urandom  -jvmArgs -XX:-UseBiasedLocking -jvmArgs -XX:+UnlockDiagnosticVMOptions -jvmArgs -XX:+DebugNonSafepoints -jvmArgs --add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
 gcArgs="-jvmArgs -XX:+UseG1GC -jvmArgs -XX:+ParallelRefProcEnabled"
-loggingArgs="-jvmArgs -Dlog4jConfigurationFile=./log4j2.xml -jvmArgs -Dlog4j2.is.webapp=false -jvmArgs -Dlog4j2.garbagefreeThreadContextMap=true -jvmArgs -Dlog4j2.enableDirectEncoders=true -jvmArgs -Dlog4j2.enable.threadlocals=true"
 
-logDir="work/solr-logs"
+# -jvmArgs -Dlog4j2.debug 
+loggingArgs="-jvmArgs -Dlog4jConfigurationFile=./log4j2-bench.xml -jvmArgs -Dlog4j2.is.webapp=false -jvmArgs -Dlog4j2.garbagefreeThreadContextMap=true -jvmArgs -Dlog4j2.enableDirectEncoders=true -jvmArgs -Dlog4j2.enable.threadlocals=true"
 
 #set -x
 
 # shellcheck disable=SC2086
-exec java -cp "$classpath" --add-opens=java.base/java.io=ALL-UNNAMED -Djdk.module.illegalAccess.silent=true org.openjdk.jmh.Main $jvmArgs $loggingArgs $gcArgs -jvmArgs "-Dsolr.log.dir=$logDir" "$@"
+exec java -cp "$classpath" --add-opens=java.base/java.io=ALL-UNNAMED -Djdk.module.illegalAccess.silent=true org.openjdk.jmh.Main $jvmArgs $loggingArgs $gcArgs "$@"
 
 echo "JMH benchmarks done"
diff --git a/solr/benchmark/log4j2.xml b/solr/benchmark/log4j2-bench.xml
similarity index 70%
copy from solr/benchmark/log4j2.xml
copy to solr/benchmark/log4j2-bench.xml
index eba3495..ae4cecb 100644
--- a/solr/benchmark/log4j2.xml
+++ b/solr/benchmark/log4j2-bench.xml
@@ -23,23 +23,25 @@
     <Console name="STDOUT" target="SYSTEM_OUT">
       <PatternLayout>
         <Pattern>
-          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.} %m%notEmpty{ =>%ex{short}}}{10240}%n
+          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.}
+          %m%notEmpty{ =>%ex{short}}}{10240}%n
         </Pattern>
       </PatternLayout>
     </Console>
 
     <RollingRandomAccessFile
         name="MainLogFile"
-        fileName="${sys:solr.log.dir}/${sys:solr.log.name}.log"
-        filePattern="${sys:solr.log.dir}/${sys:solr.log.name}.log.%i" >
+        fileName="${sys:solr.log.dir:-work/solr-logs}/${sys:solr.log.name:-solr}.log"
+        filePattern="${sys:solr.log.dir:-work/solr-logs}/${sys:solr.log.name:-solr}.log.%i">
       <PatternLayout>
         <Pattern>
-          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.} %m%notEmpty{ =>%ex{short}}}{10240}%n
+          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.}
+          %m%notEmpty{ =>%ex{short}}}{10240}%n
         </Pattern>
       </PatternLayout>
       <Policies>
-        <OnStartupTriggeringPolicy />
-        <SizeBasedTriggeringPolicy size="32 MB"/>
+        <OnStartupTriggeringPolicy/>
+        <SizeBasedTriggeringPolicy size="128 MB"/>
       </Policies>
       <DefaultRolloverStrategy max="10"/>
     </RollingRandomAccessFile>
@@ -47,19 +49,35 @@
     <RollingRandomAccessFile
         name="SlowLogFile"
         fileName="${sys:solr.log.dir}/${sys:solr.log.name}_slow_requests.log"
-        filePattern="${sys:solr.log.dir}/${sys:solr.log.name}_slow_requests.log.%i" >
+        filePattern="${sys:solr.log.dir}/${sys:solr.log.name}_slow_requests.log.%i">
       <PatternLayout>
         <Pattern>
-          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.} %m%notEmpty{ =>%ex{short}}}{10240}%n
+          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.}
+          %m%notEmpty{ =>%ex{short}}}{10240}%n
         </Pattern>
       </PatternLayout>
       <Policies>
-        <OnStartupTriggeringPolicy />
-        <SizeBasedTriggeringPolicy size="32 MB"/>
+        <OnStartupTriggeringPolicy/>
+        <SizeBasedTriggeringPolicy size="128 MB"/>
       </Policies>
       <DefaultRolloverStrategy max="10"/>
     </RollingRandomAccessFile>
 
+
+    <RandomAccessFile
+        name="RandomCountsFile"
+        fileName="${sys:solr.log.dir:-build/work/solr-logs}/${sys:solr.log.name:-solr}_random_counts.log">
+      <PatternLayout>
+        <Pattern>
+          %maxLen{%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p (%t) [%X{collection} %X{shard} %X{replica} %X{core}] %c{1.}
+          %m%notEmpty{ =>%ex{short}}}{10240}%n
+        </Pattern>
+      </PatternLayout>
+    </RandomAccessFile>
+    <AsyncLogger name="org.apache.solr.bench.BaseBenchState.RandomCounts" level="info" additivity="false">
+      <AppenderRef ref="RandomCountsFile"/>
+    </AsyncLogger>
+
   </Appenders>
   <Loggers>
     <!-- Use <AsyncLogger/<AsyncRoot and <Logger/<Root for asynchronous logging or synchonous logging respectively -->
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/BaseBenchState.java b/solr/benchmark/src/java/org/apache/solr/bench/BaseBenchState.java
new file mode 100644
index 0000000..8bf2baf
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/BaseBenchState.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.management.ManagementFactory;
+import java.util.SplittableRandom;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.management.MBeanServer;
+import org.apache.commons.io.FileUtils;
+import org.apache.solr.bench.generators.SolrGen;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.infra.BenchmarkParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** The type Base bench state. */
+@State(Scope.Benchmark)
+public class BaseBenchState {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final long RANDOM_SEED = 6624420638116043983L;
+
+  private static final SplittableRandom random = new SplittableRandom(getInitRandomeSeed());
+
+  /**
+   * Gets random seed.
+   *
+   * @return the random seed
+   */
+  public static Long getRandomSeed() {
+    return random.split().nextLong();
+  }
+
+  private static final AtomicBoolean HEAP_DUMPED = new AtomicBoolean();
+
+  /** The constant QUIET_LOG. */
+  public static final boolean QUIET_LOG = Boolean.getBoolean("quietLog");
+
+  /**
+   * Log.
+   *
+   * @param value the value
+   */
+  @SuppressForbidden(reason = "JMH uses std out for user output")
+  public static void log(String value) {
+    if (!QUIET_LOG) {
+      System.out.println((value.isEmpty() ? "" : "--> ") + value);
+    }
+  }
+
+  /** The Work dir. */
+  public String workDir;
+
+  /**
+   * Do setup.
+   *
+   * @param benchmarkParams the benchmark params
+   */
+  @Setup(Level.Trial)
+  public void doSetup(BenchmarkParams benchmarkParams) {
+
+    workDir = System.getProperty("workBaseDir", "build/work");
+
+    System.setProperty("solr.log.dir", workDir + "/logs");
+    System.setProperty("solr.log.name", benchmarkParams.id());
+  }
+
+  /**
+   * Do tear down.
+   *
+   * @param benchmarkParams the benchmark params
+   * @throws Exception the exception
+   */
+  @TearDown(Level.Trial)
+  public static void doTearDown(BenchmarkParams benchmarkParams) throws Exception {
+
+    dumpHeap(benchmarkParams);
+
+    Logger randomCountsLog =
+        LoggerFactory.getLogger(
+            MethodHandles.lookup().lookupClass().getName()
+                + ".RandomCounts"); // nowarn_valid_logger
+
+    SolrGen.countsReport().forEach(randomCountsLog::info);
+    SolrGen.COUNTS.clear();
+  }
+
+  /**
+   * Dump heap.
+   *
+   * @param benchmarkParams the benchmark params
+   * @throws IOException the io exception
+   */
+  @SuppressForbidden(reason = "access to force heapdump")
+  public static void dumpHeap(BenchmarkParams benchmarkParams) throws IOException {
+    String heapDump = System.getProperty("dumpheap");
+    if (heapDump != null) {
+
+      boolean dumpHeap = HEAP_DUMPED.compareAndExchange(false, true);
+      if (dumpHeap) {
+        File file = new File(heapDump);
+        FileUtils.deleteDirectory(file);
+        file.mkdirs();
+        File dumpFile = new File(file, benchmarkParams.id() + ".hprof");
+
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+        HotSpotDiagnosticMXBean mxBean =
+            ManagementFactory.newPlatformMXBeanProxy(
+                server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
+        mxBean.dumpHeap(dumpFile.getAbsolutePath(), true);
+      }
+    }
+  }
+
+  private static Long getInitRandomeSeed() {
+    Long seed = Long.getLong("solr.bench.seed");
+
+    if (seed == null) {
+      seed = RANDOM_SEED;
+    }
+
+    log("benchmark random seed: " + seed);
+
+    return seed;
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/DocMaker.java b/solr/benchmark/src/java/org/apache/solr/bench/DocMaker.java
deleted file mode 100644
index 71d2691..0000000
--- a/solr/benchmark/src/java/org/apache/solr/bench/DocMaker.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.bench;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.Random;
-import java.util.SplittableRandom;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.commons.lang3.Validate;
-import org.apache.lucene.util.RamUsageEstimator;
-import org.apache.lucene.util.TestUtil;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.util.SolrNamedThreadFactory;
-import org.apache.solr.common.util.SuppressForbidden;
-
-/**
- * A tool to generate controlled random data for a benchmark. {@link SolrInputDocument}s are created
- * based on supplied FieldDef definitions.
- *
- * <p>You can call getDocument to build and retrieve one {@link SolrInputDocument} at a time, or you
- * can call {@link #preGenerateDocs} to generate the given number of documents in RAM, and then
- * retrieve them via {@link #getGeneratedDocsIterator}.
- */
-public class DocMaker {
-
-  private Queue<SolrInputDocument> docs = new ConcurrentLinkedQueue<>();
-
-  private final Map<String, FieldDef> fields = new HashMap<>();
-
-  private static final AtomicInteger ID = new AtomicInteger();
-
-  private ExecutorService executorService;
-
-  public DocMaker() {}
-
-  @SuppressForbidden(reason = "This module does not need to deal with logging context")
-  public void preGenerateDocs(int numDocs, SplittableRandom random) throws InterruptedException {
-    MiniClusterState.log("preGenerateDocs " + numDocs + " ...");
-    docs.clear();
-    executorService =
-        Executors.newFixedThreadPool(
-            Runtime.getRuntime().availableProcessors() + 1,
-            new SolrNamedThreadFactory("SolrJMH DocMaker"));
-
-    for (int i = 0; i < numDocs; i++) {
-      executorService.submit(
-          new Runnable() {
-            SplittableRandom threadRandom = random.split();
-
-            @Override
-            public void run() {
-              try {
-                SolrInputDocument doc = DocMaker.this.getDocument(threadRandom);
-                docs.add(doc);
-              } catch (Exception e) {
-                executorService.shutdownNow();
-                throw new RuntimeException(e);
-              }
-            }
-          });
-    }
-
-    executorService.shutdown();
-    boolean result = executorService.awaitTermination(10, TimeUnit.MINUTES);
-    if (!result) {
-      throw new RuntimeException("Timeout waiting for doc adds to finish");
-    }
-    MiniClusterState.log(
-        "done preGenerateDocs docs="
-            + docs.size()
-            + " ram="
-            + RamUsageEstimator.humanReadableUnits(RamUsageEstimator.sizeOfObject(docs)));
-
-    if (numDocs != docs.size()) {
-      throw new IllegalStateException("numDocs != " + docs.size());
-    }
-  }
-
-  public Iterator<SolrInputDocument> getGeneratedDocsIterator() {
-    return docs.iterator();
-  }
-
-  public SolrInputDocument getDocument(SplittableRandom random) {
-    SolrInputDocument doc = new SolrInputDocument();
-
-    for (Map.Entry<String, FieldDef> entry : fields.entrySet()) {
-      doc.addField(entry.getKey(), getValue(entry.getValue(), random));
-    }
-
-    return doc;
-  }
-
-  public void addField(String name, FieldDef.FieldDefBuilder builder) {
-    fields.put(name, builder.build());
-  }
-
-  private Object getValue(FieldDef fieldDef, SplittableRandom threadRandom) {
-
-    switch (fieldDef.getContent()) {
-      case UNIQUE_INT:
-        return ID.incrementAndGet();
-      case INTEGER:
-        if (fieldDef.getMaxCardinality() > 0) {
-          long start = fieldDef.getCardinalityStart();
-          long seed = nextLong(start, start + fieldDef.getMaxCardinality(), threadRandom.split());
-          return nextInt(0, Integer.MAX_VALUE, new SplittableRandom(seed));
-        }
-
-        return threadRandom.nextInt(Integer.MAX_VALUE);
-      case ALPHEBETIC:
-        return getString(
-            fieldDef, value -> getAlphabeticString(fieldDef, threadRandom), threadRandom);
-      case UNICODE:
-        return getString(fieldDef, value -> getUnicodeString(fieldDef, threadRandom), threadRandom);
-      default:
-        throw new UnsupportedOperationException(
-            "Unsupported content type type=" + fieldDef.getContent());
-    }
-  }
-
-  private String getString(
-      FieldDef fieldDef, StringSupplier supplier, SplittableRandom threadRandom) {
-    if (fieldDef.getNumTokens() > 1 || fieldDef.getMaxNumTokens() > 1) {
-      StringBuilder sb =
-          new StringBuilder(
-              fieldDef.getNumTokens()
-                  * (Math.max(fieldDef.getLength(), fieldDef.getMaxLength()) + 1));
-      SplittableRandom random = threadRandom.split();
-      for (int i = 0;
-          i
-              < (fieldDef.getMaxNumTokens() > 1
-                  ? random.nextInt(1, fieldDef.getMaxNumTokens())
-                  : fieldDef.getNumTokens());
-          i++) {
-        if (i > 0) {
-          sb.append(' ');
-        }
-        sb.append(supplier.getString(fieldDef));
-      }
-      return sb.toString();
-    }
-    return supplier.getString(fieldDef);
-  }
-
-  private String getUnicodeString(FieldDef fieldDef, SplittableRandom threadRandom) {
-    try {
-      if (fieldDef.getMaxCardinality() > 0) {
-        long start = fieldDef.getCardinalityStart();
-        long seed = nextLong(start, start + fieldDef.getMaxCardinality(), threadRandom.split());
-        if (fieldDef.getLength() > -1) {
-          return TestUtil.randomRealisticUnicodeString(
-              new Random(seed), fieldDef.getLength(), fieldDef.getLength());
-        } else {
-          return TestUtil.randomRealisticUnicodeString(
-              new Random(seed), 1, fieldDef.getMaxLength());
-        }
-      }
-
-      if (fieldDef.getLength() > -1) {
-        return TestUtil.randomRealisticUnicodeString(
-            new Random(threadRandom.nextLong()), fieldDef.getLength(), fieldDef.getLength());
-      } else {
-        return TestUtil.randomRealisticUnicodeString(
-            new Random(threadRandom.nextLong()), 1, fieldDef.getMaxLength());
-      }
-    } catch (Exception e) {
-      throw new RuntimeException("Failed getting UnicodeString with FieldDef=" + fieldDef, e);
-    }
-  }
-
-  private String getAlphabeticString(FieldDef fieldDef, SplittableRandom threadRandom) {
-    try {
-      if (fieldDef.getMaxCardinality() > 0) {
-        long start = fieldDef.getCardinalityStart();
-        long seed = nextLong(start, start + fieldDef.getMaxCardinality(), threadRandom.split());
-        SplittableRandom random = new SplittableRandom(seed);
-        if (fieldDef.getLength() > -1) {
-          return RandomStringUtils.random(
-              nextInt(fieldDef.getLength(), fieldDef.getLength(), random),
-              0,
-              0,
-              true,
-              false,
-              null,
-              new Random(seed));
-        } else {
-          return RandomStringUtils.random(
-              nextInt(1, fieldDef.getMaxLength(), random),
-              0,
-              0,
-              true,
-              false,
-              null,
-              new Random(seed));
-        }
-      }
-
-      SplittableRandom random = threadRandom.split();
-      Random r = new Random(random.nextLong());
-      if (fieldDef.getLength() > -1) {
-        return RandomStringUtils.random(
-            nextInt(fieldDef.getLength(), fieldDef.getLength(), random),
-            0,
-            0,
-            true,
-            false,
-            null,
-            r);
-      } else {
-        return RandomStringUtils.random(
-            nextInt(1, fieldDef.getMaxLength(), random), 0, 0, true, false, null, r);
-      }
-    } catch (Exception e) {
-      throw new RuntimeException("Failed getting AlphabeticString with FieldDef=" + fieldDef, e);
-    }
-  }
-
-  public void clear() {
-    docs.clear();
-  }
-
-  public enum Content {
-    UNICODE,
-    ALPHEBETIC,
-    INTEGER,
-    UNIQUE_INT
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    DocMaker that = (DocMaker) o;
-    return fields.equals(that.fields);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(fields);
-  }
-
-  public static int nextInt(
-      final int startInclusive, final int endExclusive, SplittableRandom random) {
-    Validate.isTrue(
-        endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
-    Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");
-
-    if (startInclusive == endExclusive) {
-      return startInclusive;
-    }
-
-    return startInclusive + random.nextInt(endExclusive - startInclusive);
-  }
-
-  public static long nextLong(
-      final long startInclusive, final long endExclusive, SplittableRandom random) {
-    Validate.isTrue(
-        endExclusive >= startInclusive, "Start value must be smaller or equal to end value.");
-    Validate.isTrue(
-        startInclusive >= 0,
-        "Both range values must be non-negative startInclusive="
-            + startInclusive
-            + " endExclusive="
-            + endExclusive);
-
-    if (startInclusive == endExclusive) {
-      return startInclusive;
-    }
-
-    return startInclusive + random.nextLong(endExclusive - startInclusive);
-  }
-
-  private interface StringSupplier {
-    String getString(FieldDef value);
-  }
-}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/Docs.java b/solr/benchmark/src/java/org/apache/solr/bench/Docs.java
new file mode 100644
index 0000000..eb96ca3
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/Docs.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench;
+
+import static org.apache.solr.bench.BaseBenchState.log;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.solr.bench.generators.MultiString;
+import org.apache.solr.bench.generators.SolrGen;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.util.SolrNamedThreadFactory;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.quicktheories.core.Gen;
+import org.quicktheories.impl.BenchmarkRandomSource;
+
+/**
+ * A tool to generate controlled random data for a benchmark. {@link SolrInputDocument}s are created
+ * based on supplied FieldDef definitions.
+ *
+ * <p>You can call getDocument to build and retrieve one {@link SolrInputDocument} at a time, or you
+ * can call {@link #preGenerate} to generate the given number of documents in RAM, and then retrieve
+ * them via {@link #generatedDocsIterator}.
+ */
+public class Docs {
+
+  private final ThreadLocal<SolrRandomnessSource> random;
+  private final Queue<SolrInputDocument> docs = new ConcurrentLinkedQueue<>();
+
+  private final Map<String, Gen<?>> fields = Collections.synchronizedMap(new HashMap<>(16));
+
+  private ExecutorService executorService;
+  private int stringFields;
+  private int multiStringFields;
+  private int integerFields;
+  private int longFields;
+  private int booleanFields;
+  private int floatFields;
+  private int dateFields;
+  private int doubleFields;
+
+  /**
+   * Docs docs.
+   *
+   * @return the docs
+   */
+  public static Docs docs() {
+    return new Docs(BaseBenchState.getRandomSeed());
+  }
+
+  /**
+   * Docs docs.
+   *
+   * @param seed the seed
+   * @return the docs
+   */
+  public static Docs docs(Long seed) {
+    return new Docs(seed);
+  }
+
+  private Docs(Long seed) {
+    this.random =
+        ThreadLocal.withInitial(
+            () ->
+                new BenchmarkRandomSource(
+                    new SplittableRandomGenerator(seed))); // TODO: pluggable RandomGenerator
+  }
+
+  /**
+   * Pre generate iterator.
+   *
+   * @param numDocs the num docs
+   * @return the iterator
+   * @throws InterruptedException the interrupted exception
+   */
+  @SuppressForbidden(reason = "This module does not need to deal with logging context")
+  public Iterator<SolrInputDocument> preGenerate(int numDocs) throws InterruptedException {
+    log("preGenerate docs " + numDocs + " ...");
+    docs.clear();
+    executorService =
+        Executors.newFixedThreadPool(
+            Runtime.getRuntime().availableProcessors() + 1,
+            new SolrNamedThreadFactory("SolrJMH DocMaker"));
+
+    for (int i = 0; i < numDocs; i++) {
+      executorService.submit(
+          () -> {
+            try {
+              SolrInputDocument doc = Docs.this.inputDocument();
+              docs.add(doc);
+            } catch (Exception e) {
+              e.printStackTrace();
+              executorService.shutdownNow();
+              throw new RuntimeException(e);
+            }
+          });
+    }
+
+    executorService.shutdown();
+    boolean result = executorService.awaitTermination(10, TimeUnit.MINUTES);
+    if (!result) {
+      throw new RuntimeException("Timeout waiting for doc adds to finish");
+    }
+    log(
+        "done preGenerateDocs docs="
+            + docs.size()
+            + " ram="
+            + RamUsageEstimator.humanReadableUnits(RamUsageEstimator.sizeOfObject(docs)));
+
+    if (numDocs != docs.size()) {
+      throw new IllegalStateException("numDocs != " + docs.size());
+    }
+
+    return docs.iterator();
+  }
+
+  /**
+   * Generated docs iterator iterator.
+   *
+   * @return the iterator
+   */
+  public Iterator<SolrInputDocument> generatedDocsIterator() {
+    return docs.iterator();
+  }
+
+  /**
+   * Input document solr input document.
+   *
+   * @return the solr input document
+   */
+  public SolrInputDocument inputDocument() {
+    SolrInputDocument doc = new SolrInputDocument();
+    SolrRandomnessSource randomSource = random.get();
+    for (Map.Entry<String, Gen<?>> entry : fields.entrySet()) {
+      doc.addField(entry.getKey(), entry.getValue().generate(randomSource));
+    }
+
+    return doc;
+  }
+
+  /**
+   * Document solr document.
+   *
+   * @return the solr document
+   */
+  public SolrDocument document() {
+    SolrDocument doc = new SolrDocument();
+    SolrRandomnessSource randomSource = random.get();
+    for (Map.Entry<String, Gen<?>> entry : fields.entrySet()) {
+      doc.addField(entry.getKey(), entry.getValue().generate(randomSource));
+    }
+
+    return doc;
+  }
+
+  /**
+   * Field docs.
+   *
+   * @param name the name
+   * @param generator the generator
+   * @return the docs
+   */
+  public Docs field(String name, Gen<?> generator) {
+    fields.put(name, generator);
+    return this;
+  }
+
+  /**
+   * Field docs.
+   *
+   * @param generator the generator
+   * @return the docs
+   */
+  public Docs field(SolrGen<?> generator) {
+    Class<?> type = generator.type();
+    if (String.class == type) {
+      fields.put("string" + (stringFields++ > 0 ? stringFields : "") + "_s", generator);
+    } else if (MultiString.class == type) {
+      fields.put("text" + (multiStringFields++ > 0 ? multiStringFields : "") + "_t", generator);
+    } else if (Integer.class == type) {
+      fields.put("int" + (integerFields++ > 0 ? integerFields : "") + "_i", generator);
+    } else if (Long.class == type) {
+      fields.put("long" + (longFields++ > 0 ? longFields : "") + "_l", generator);
+    } else if (Boolean.class == type) {
+      fields.put("boolean" + (booleanFields++ > 0 ? booleanFields : "") + "_b", generator);
+    } else if (Float.class == type) {
+      fields.put("float" + (floatFields++ > 0 ? floatFields : "") + "_f", generator);
+    } else if (Date.class == type) {
+      fields.put("date" + (dateFields++ > 0 ? dateFields : "") + "_dt", generator);
+    } else if (Double.class == type) {
+      fields.put("double" + (doubleFields++ > 0 ? doubleFields : "") + "_d", generator);
+    } else {
+      throw new IllegalArgumentException("Unknown type: " + generator.type());
+    }
+
+    return this;
+  }
+
+  /** Clear. */
+  public void clear() {
+    docs.clear();
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/FieldDef.java b/solr/benchmark/src/java/org/apache/solr/bench/FieldDef.java
deleted file mode 100644
index 933a759..0000000
--- a/solr/benchmark/src/java/org/apache/solr/bench/FieldDef.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.bench;
-
-import java.util.SplittableRandom;
-import org.apache.solr.common.SolrInputDocument;
-
-/**
- * Provides the definition for a randomly generated field in a {@link SolrInputDocument} created by
- * a {@link DocMaker}.
- */
-public class FieldDef {
-  public static final int DEFAULT_MAX_LENGTH = 64;
-
-  private DocMaker.Content content;
-  private int numTokens = 1;
-  private int maxNumTokens = -1;
-  private int maxCardinality = -1;
-  private int maxLength = -1;
-  private int length = -1;
-  private long cardinalityStart;
-
-  public int getNumTokens() {
-    return numTokens;
-  }
-
-  public int getMaxNumTokens() {
-    return maxNumTokens;
-  }
-
-  public int getMaxCardinality() {
-    return maxCardinality;
-  }
-
-  public long getCardinalityStart() {
-    return cardinalityStart;
-  }
-
-  public int getMaxLength() {
-    return maxLength;
-  }
-
-  public int getLength() {
-    return length;
-  }
-
-  public DocMaker.Content getContent() {
-    return content;
-  }
-
-  public static final class FieldDefBuilder {
-
-    private DocMaker.Content content;
-    private int numTokens = 1;
-    private int maxNumTokens = -1;
-    private int maxCardinality = -1;
-    private int maxLength = -1;
-    private int length = -1;
-    private long cardinalityStart;
-
-    private FieldDefBuilder() {}
-
-    public static FieldDefBuilder aFieldDef() {
-      return new FieldDefBuilder();
-    }
-
-    public FieldDefBuilder withContent(DocMaker.Content content) {
-      this.content = content;
-      return this;
-    }
-
-    public FieldDefBuilder withTokenCount(int numTokens) {
-      if (numTokens > 1 && content == DocMaker.Content.UNIQUE_INT) {
-        throw new UnsupportedOperationException(
-            "UNIQUE_INT content type cannot be used with token count > 1");
-      }
-      if (maxCardinality > 1) {
-        throw new UnsupportedOperationException(
-            "tokenCount cannot be used with maxCardinality > 0");
-      }
-      this.numTokens = numTokens;
-      return this;
-    }
-
-    public FieldDefBuilder withMaxTokenCount(int maxNumTokens) {
-      if (numTokens > 1 && content == DocMaker.Content.UNIQUE_INT) {
-        throw new UnsupportedOperationException(
-            "UNIQUE_INT content type cannot be used with token count > 1");
-      }
-      if (maxCardinality > 1) {
-        throw new UnsupportedOperationException(
-            "maxNumTokens cannot be used with maxCardinality > 0");
-      }
-      this.maxNumTokens = maxNumTokens;
-      return this;
-    }
-
-    public FieldDefBuilder withMaxCardinality(int maxCardinality, SplittableRandom random) {
-      if (numTokens > 1) {
-        throw new UnsupportedOperationException(
-            "maxCardinality cannot be used with token count > 1");
-      }
-      this.maxCardinality = maxCardinality;
-      this.cardinalityStart = random.nextLong(0, Long.MAX_VALUE - maxCardinality);
-      return this;
-    }
-
-    public FieldDefBuilder withMaxLength(int maxLength) {
-      if (length > -1) {
-        throw new UnsupportedOperationException("maxLength cannot be used with length");
-      }
-      this.maxLength = maxLength;
-      return this;
-    }
-
-    public FieldDefBuilder withLength(int length) {
-      if (maxLength > -1) {
-        throw new UnsupportedOperationException("length cannot be used with maxLength");
-      }
-      this.length = length;
-      return this;
-    }
-
-    public FieldDef build() {
-      FieldDef fieldDef = new FieldDef();
-      fieldDef.numTokens = this.numTokens;
-      fieldDef.maxNumTokens = this.maxNumTokens;
-      fieldDef.content = this.content;
-      fieldDef.maxCardinality = this.maxCardinality;
-      fieldDef.maxLength = this.maxLength == -1 ? DEFAULT_MAX_LENGTH : this.maxLength;
-      fieldDef.length = this.length;
-      fieldDef.cardinalityStart = this.cardinalityStart;
-      return fieldDef;
-    }
-  }
-
-  @Override
-  public String toString() {
-    return "FieldDef{"
-        + "content="
-        + content
-        + ", numTokens="
-        + numTokens
-        + ", maxNumTokens="
-        + maxNumTokens
-        + ", maxCardinality="
-        + maxCardinality
-        + ", maxLength="
-        + maxLength
-        + ", length="
-        + length
-        + ", cardinalityStart="
-        + cardinalityStart
-        + '}';
-  }
-}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/MiniClusterState.java b/solr/benchmark/src/java/org/apache/solr/bench/MiniClusterState.java
index b9085a9..b2beb12 100755
--- a/solr/benchmark/src/java/org/apache/solr/bench/MiniClusterState.java
+++ b/solr/benchmark/src/java/org/apache/solr/bench/MiniClusterState.java
@@ -17,16 +17,19 @@
 package org.apache.solr.bench;
 
 import static org.apache.commons.io.file.PathUtils.deleteDirectory;
+import static org.apache.solr.bench.BaseBenchState.log;
 
 import com.codahale.metrics.Meter;
+import java.io.File;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.lang.management.ManagementFactory;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 import java.util.SplittableRandom;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -54,55 +57,72 @@ import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.Control;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /** The base class for Solr JMH benchmarks that operate against a {@code MiniSolrCloudCluster}. */
 public class MiniClusterState {
 
+  /** The constant DEBUG_OUTPUT. */
   public static final boolean DEBUG_OUTPUT = false;
 
-  private static final long RANDOM_SEED = 6624420638116043983L;
-
+  /** The constant PROC_COUNT. */
   public static final int PROC_COUNT =
       ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
 
-  private static boolean quietLog = Boolean.getBoolean("quietLog");
-
-  @SuppressForbidden(reason = "JMH uses std out for user output")
-  public static void log(String value) {
-    if (!quietLog) {
-      System.out.println((value.equals("") ? "" : "--> ") + value);
-    }
-  }
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  /** The type Mini cluster bench state. */
   @State(Scope.Benchmark)
   public static class MiniClusterBenchState {
 
+    /** The Metrics enabled. */
     boolean metricsEnabled = true;
 
+    /** The Nodes. */
     public List<String> nodes;
+
+    /** The Cluster. */
     MiniSolrCloudCluster cluster;
+
+    /** The Client. */
     public SolrClient client;
 
+    /** The Run cnt. */
     int runCnt = 0;
 
+    /** The Create collection and index. */
     boolean createCollectionAndIndex = true;
 
+    /** The Delete mini cluster. */
     boolean deleteMiniCluster = true;
 
-    Path baseDir;
+    /** The Mini cluster base dir. */
+    Path miniClusterBaseDir;
+
+    /** The Allow cluster reuse. */
     boolean allowClusterReuse = false;
 
+    /** The Is warmup. */
     boolean isWarmup;
 
     private SplittableRandom random;
-
+    private String workDir;
+
+    /**
+     * Tear down.
+     *
+     * @param benchmarkParams the benchmark params
+     * @throws Exception the exception
+     */
     @TearDown(Level.Iteration)
     public void tearDown(BenchmarkParams benchmarkParams) throws Exception {
 
       // dump Solr metrics
       Path metricsResults =
           Paths.get(
-              "work/metrics-results",
+              workDir,
+              "metrics-results",
               benchmarkParams.id(),
               String.valueOf(runCnt++),
               benchmarkParams.getBenchmark() + ".txt");
@@ -110,50 +130,68 @@ public class MiniClusterState {
         Files.createDirectories(metricsResults.getParent());
       }
 
-      cluster.outputMetrics(
+      cluster.dumpMetrics(
           metricsResults.getParent().toFile(), metricsResults.getFileName().toString());
     }
 
+    /**
+     * Check warm up.
+     *
+     * @param control the control
+     * @throws Exception the exception
+     */
     @Setup(Level.Iteration)
     public void checkWarmUp(Control control) throws Exception {
       isWarmup = control.stopMeasurement;
     }
 
+    /**
+     * Shutdown mini cluster.
+     *
+     * @param benchmarkParams the benchmark params
+     * @throws Exception the exception
+     */
     @TearDown(Level.Trial)
-    public void shutdownMiniCluster() throws Exception {
+    public void shutdownMiniCluster(BenchmarkParams benchmarkParams, BaseBenchState baseBenchState)
+        throws Exception {
+
+      log.info("MiniClusterState tear down - baseBenchState is {}", baseBenchState);
+
+      BaseBenchState.dumpHeap(benchmarkParams);
+
       if (DEBUG_OUTPUT) log("closing client and shutting down minicluster");
       IOUtils.closeQuietly(client);
       cluster.shutdown();
     }
 
+    /**
+     * Do setup.
+     *
+     * @param benchmarkParams the benchmark params
+     * @param baseBenchState the base bench state
+     * @throws Exception the exception
+     */
     @Setup(Level.Trial)
-    public void doSetup(BenchmarkParams benchmarkParams) throws Exception {
+    public void doSetup(BenchmarkParams benchmarkParams, BaseBenchState baseBenchState)
+        throws Exception {
+
+      workDir = System.getProperty("workBaseDir", "build/work");
 
-      MiniClusterState.log("");
+      log("");
       Path currentRelativePath = Paths.get("");
       String s = currentRelativePath.toAbsolutePath().toString();
       log("current relative path is: " + s);
+      log("work path is: " + workDir);
 
-      Long seed = Long.getLong("solr.bench.seed");
-
-      if (seed == null) {
-        seed = RANDOM_SEED;
-      }
-
-      log("benchmark random seed: " + seed);
-
-      // set the seed used by ThreadLocalRandom
-      System.setProperty("randomSeed", Long.toString(new Random(seed).nextLong()));
-
-      this.random = new SplittableRandom(seed);
+      System.setProperty("doNotWaitForMergesOnIWClose", "true");
 
       System.setProperty("pkiHandlerPrivateKeyPath", "");
       System.setProperty("pkiHandlerPublicKeyPath", "");
 
-      System.setProperty("solr.log.name", benchmarkParams.id());
-
       System.setProperty("solr.default.confdir", "../server/solr/configsets/_default");
 
+      this.random = new SplittableRandom(BaseBenchState.getRandomSeed());
+
       // not currently usable, but would enable JettySolrRunner's ill-conceived jetty.testMode and
       // allow using SSL
 
@@ -163,48 +201,58 @@ public class MiniClusterState {
       String baseDirSysProp = System.getProperty("miniClusterBaseDir");
       if (baseDirSysProp != null) {
         deleteMiniCluster = false;
-        baseDir = Paths.get(baseDirSysProp);
-        if (Files.exists(baseDir)) {
+        miniClusterBaseDir = Paths.get(baseDirSysProp);
+        if (Files.exists(miniClusterBaseDir)) {
           createCollectionAndIndex = false;
           allowClusterReuse = true;
         }
       } else {
-        baseDir = Paths.get("work/mini-cluster");
+        miniClusterBaseDir = Paths.get(workDir, "mini-cluster");
       }
 
       System.setProperty("metricsEnabled", String.valueOf(metricsEnabled));
     }
 
+    /**
+     * Metrics enabled.
+     *
+     * @param metricsEnabled the metrics enabled
+     */
     public void metricsEnabled(boolean metricsEnabled) {
       this.metricsEnabled = metricsEnabled;
     }
 
+    /**
+     * Start mini cluster.
+     *
+     * @param nodeCount the node count
+     */
     public void startMiniCluster(int nodeCount) {
-      log("starting mini cluster at base directory: " + baseDir.toAbsolutePath());
+      log("starting mini cluster at base directory: " + miniClusterBaseDir.toAbsolutePath());
 
-      if (!allowClusterReuse && Files.exists(baseDir)) {
+      if (!allowClusterReuse && Files.exists(miniClusterBaseDir)) {
         log("mini cluster base directory exists, removing ...");
         try {
-          deleteDirectory(baseDir);
+          deleteDirectory(miniClusterBaseDir);
         } catch (IOException e) {
           throw new RuntimeException(e);
         }
         createCollectionAndIndex = true;
-      } else if (Files.exists(baseDir)) {
+      } else if (Files.exists(miniClusterBaseDir)) {
         createCollectionAndIndex = false;
         deleteMiniCluster = false;
       }
 
       try {
         cluster =
-            new MiniSolrCloudCluster.Builder(nodeCount, baseDir)
+            new MiniSolrCloudCluster.Builder(nodeCount, miniClusterBaseDir)
                 .formatZkServer(false)
-                .addConfig("conf", Paths.get("src/resources/configs/cloud-minimal/conf"))
+                .addConfig("conf", getFile("src/resources/configs/cloud-minimal/conf").toPath())
                 .configure();
       } catch (Exception e) {
-        if (Files.exists(baseDir)) {
+        if (Files.exists(miniClusterBaseDir)) {
           try {
-            deleteDirectory(baseDir);
+            deleteDirectory(miniClusterBaseDir);
           } catch (IOException ex) {
             e.addSuppressed(ex);
           }
@@ -224,10 +272,23 @@ public class MiniClusterState {
       log("");
     }
 
+    /**
+     * Gets random.
+     *
+     * @return the random
+     */
     public SplittableRandom getRandom() {
       return random;
     }
 
+    /**
+     * Create collection.
+     *
+     * @param collection the collection
+     * @param numShards the num shards
+     * @param numReplicas the num replicas
+     * @throws Exception the exception
+     */
     public void createCollection(String collection, int numShards, int numReplicas)
         throws Exception {
       if (createCollectionAndIndex) {
@@ -242,16 +303,24 @@ public class MiniClusterState {
           cluster.waitForActiveCollection(
               collection, 15, TimeUnit.SECONDS, numShards, numShards * numReplicas);
         } catch (Exception e) {
-          if (Files.exists(baseDir)) {
-            deleteDirectory(baseDir);
+          if (Files.exists(miniClusterBaseDir)) {
+            deleteDirectory(miniClusterBaseDir);
           }
           throw e;
         }
       }
     }
 
+    /**
+     * Index.
+     *
+     * @param collection the collection
+     * @param docs the docs
+     * @param docCount the doc count
+     * @throws Exception the exception
+     */
     @SuppressForbidden(reason = "This module does not need to deal with logging context")
-    public void index(String collection, DocMaker docMaker, int docCount) throws Exception {
+    public void index(String collection, Docs docs, int docCount) throws Exception {
       if (createCollectionAndIndex) {
 
         log("indexing data for benchmark...");
@@ -259,10 +328,10 @@ public class MiniClusterState {
         ExecutorService executorService =
             Executors.newFixedThreadPool(
                 Runtime.getRuntime().availableProcessors(),
-                new SolrNamedThreadFactory("SolrJMH Indexer Progress"));
+                new SolrNamedThreadFactory("SolrJMH Indexer"));
         ScheduledExecutorService scheduledExecutor =
             Executors.newSingleThreadScheduledExecutor(
-                new SolrNamedThreadFactory("SolrJMH Indexer"));
+                new SolrNamedThreadFactory("SolrJMH Indexer Progress"));
         scheduledExecutor.scheduleAtFixedRate(
             () -> {
               if (meter.getCount() == docCount) {
@@ -277,14 +346,14 @@ public class MiniClusterState {
         for (int i = 0; i < docCount; i++) {
           executorService.submit(
               new Runnable() {
-                SplittableRandom threadRandom = random.split();
+                final SplittableRandom threadRandom = random.split();
 
                 @Override
                 public void run() {
                   UpdateRequest updateRequest = new UpdateRequest();
                   updateRequest.setBasePath(
                       nodes.get(threadRandom.nextInt(cluster.getJettySolrRunners().size())));
-                  SolrInputDocument doc = docMaker.getDocument(threadRandom);
+                  SolrInputDocument doc = docs.inputDocument();
                   // log("add doc " + doc);
                   updateRequest.add(doc);
                   meter.mark();
@@ -301,13 +370,13 @@ public class MiniClusterState {
         log("done adding docs, waiting for executor to terminate...");
 
         executorService.shutdown();
-        boolean result = executorService.awaitTermination(600, TimeUnit.MINUTES);
+        boolean result = false;
+        while (!result) {
+          result = executorService.awaitTermination(600, TimeUnit.MINUTES);
+        }
 
         scheduledExecutor.shutdown();
 
-        if (!result) {
-          throw new RuntimeException("Timeout waiting for doc adds to finish");
-        }
         log("done indexing data for benchmark");
 
         log("committing data ...");
@@ -325,18 +394,31 @@ public class MiniClusterState {
 
       NamedList<Object> result = client.request(queryRequest, collection);
 
-      if (DEBUG_OUTPUT) MiniClusterState.log("result: " + result);
+      if (DEBUG_OUTPUT) log("result: " + result);
 
-      MiniClusterState.log("");
+      log("");
 
-      MiniClusterState.log("Dump Core Info");
+      log("Dump Core Info");
       dumpCoreInfo();
     }
 
+    /**
+     * Wait for merges.
+     *
+     * @param collection the collection
+     * @throws Exception the exception
+     */
     public void waitForMerges(String collection) throws Exception {
       forceMerge(collection, Integer.MAX_VALUE);
     }
 
+    /**
+     * Force merge.
+     *
+     * @param collection the collection
+     * @param maxMergeSegments the max merge segments
+     * @throws Exception the exception
+     */
     public void forceMerge(String collection, int maxMergeSegments) throws Exception {
       if (createCollectionAndIndex) {
         // we control segment count for a more informative benchmark *and* because background
@@ -356,16 +438,77 @@ public class MiniClusterState {
       }
     }
 
+    /**
+     * Dump core info.
+     *
+     * @throws IOException the io exception
+     */
     @SuppressForbidden(reason = "JMH uses std out for user output")
     public void dumpCoreInfo() throws IOException {
-      cluster.dumpCoreInfo(!quietLog ? System.out : new NullPrintStream());
+      cluster.dumpCoreInfo(!BaseBenchState.QUIET_LOG ? System.out : new NullPrintStream());
     }
   }
 
+  /**
+   * Params modifiable solr params.
+   *
+   * @param moreParams the more params
+   * @return the modifiable solr params
+   */
+  public static ModifiableSolrParams params(String... moreParams) {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    for (int i = 0; i < moreParams.length; i += 2) {
+      params.add(moreParams[i], moreParams[i + 1]);
+    }
+    return params;
+  }
+
+  /**
+   * Params modifiable solr params.
+   *
+   * @param params the params
+   * @param moreParams the more params
+   * @return the modifiable solr params
+   */
   public static ModifiableSolrParams params(ModifiableSolrParams params, String... moreParams) {
     for (int i = 0; i < moreParams.length; i += 2) {
       params.add(moreParams[i], moreParams[i + 1]);
     }
     return params;
   }
+
+  /**
+   * Gets file.
+   *
+   * @param name the name
+   * @return the file
+   */
+  public static File getFile(String name) {
+    final URL url =
+        MiniClusterState.class.getClassLoader().getResource(name.replace(File.separatorChar, '/'));
+    if (url != null) {
+      try {
+        return new File(url.toURI());
+      } catch (Exception e) {
+        throw new RuntimeException(
+            "Resource was found on classpath, but cannot be resolved to a "
+                + "normal file (maybe it is part of a JAR file): "
+                + name);
+      }
+    }
+    File file = new File(name);
+    if (file.exists()) {
+      return file;
+    } else {
+      file = new File("../../../", name);
+      if (file.exists()) {
+        return file;
+      }
+    }
+    throw new RuntimeException(
+        "Cannot find resource in classpath or in file-system (relative to CWD): "
+            + name
+            + " CWD="
+            + Paths.get("").toAbsolutePath());
+  }
 }
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/SolrGenerate.java b/solr/benchmark/src/java/org/apache/solr/bench/SolrGenerate.java
new file mode 100644
index 0000000..2b2827f
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/SolrGenerate.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import org.apache.solr.bench.generators.SolrGen;
+import org.quicktheories.api.Pair;
+import org.quicktheories.core.DetatchedRandomnessSource;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+
+/** The type Solr generate. */
+public class SolrGenerate {
+
+  private SolrGenerate() {}
+
+  /**
+   * Constant solr gen.
+   *
+   * @param <T> the type parameter
+   * @param constant the constant
+   * @return the solr gen
+   */
+  public static <T> SolrGen<T> constant(T constant) {
+    return new SolrGen<>() {
+      @Override
+      public T generate(RandomnessSource in) {
+        return constant;
+      }
+
+      @Override
+      public T generate(SolrRandomnessSource in) {
+        return constant;
+      }
+    };
+  }
+
+  /**
+   * Range solr gen.
+   *
+   * @param startInclusive the start inclusive
+   * @param endInclusive the end inclusive
+   * @return the solr gen
+   */
+  public static SolrGen<Integer> range(final int startInclusive, final int endInclusive) {
+    return new SolrGen<>() {
+      {
+        this.start = startInclusive;
+        this.end = endInclusive;
+      }
+
+      @Override
+      public Integer generate(RandomnessSource in) {
+        return (int) ((SolrRandomnessSource) in).next(startInclusive, endInclusive);
+      }
+
+      @Override
+      public Integer generate(SolrRandomnessSource in) {
+        return (int) in.next(startInclusive, endInclusive);
+      }
+    };
+  }
+
+  /**
+   * Long range solr gen.
+   *
+   * @param startInclusive the start inclusive
+   * @param endInclusive the end inclusive
+   * @return the solr gen
+   */
+  public static SolrGen<Long> longRange(final long startInclusive, final long endInclusive) {
+    return new SolrGen<>() {
+      {
+        this.start = startInclusive;
+        this.end = endInclusive;
+      }
+
+      @Override
+      public Long generate(RandomnessSource in) {
+        return ((SolrRandomnessSource) in).next(startInclusive, endInclusive);
+      }
+
+      @Override
+      public Long generate(SolrRandomnessSource in) {
+        return in.next(startInclusive, endInclusive);
+      }
+    };
+  }
+
+  /**
+   * Int arrays solr gen.
+   *
+   * @param sizes the sizes
+   * @param contents the contents
+   * @return the solr gen
+   */
+  public static SolrGen<int[]> intArrays(SolrGen<Integer> sizes, SolrGen<Integer> contents) {
+    return new SolrGen<>() {
+
+      @Override
+      public int[] generate(SolrRandomnessSource td) {
+        int size = sizes.generate(td);
+        int[] is = new int[size];
+        for (int i = 0; i != size; i++) {
+          is[i] = contents.generate(td);
+        }
+        return is;
+      }
+    };
+  }
+
+  /**
+   * Code points solr gen.
+   *
+   * @param startInclusive the start inclusive
+   * @param endInclusive the end inclusive
+   * @return the solr gen
+   */
+  public static SolrGen<Integer> codePoints(int startInclusive, int endInclusive) {
+
+    checkArguments(
+        startInclusive >= Character.MIN_CODE_POINT,
+        "(%s) is less than the minimum codepoint (%s)",
+        startInclusive,
+        Character.MIN_CODE_POINT);
+
+    checkArguments(
+        endInclusive <= Character.MAX_CODE_POINT,
+        "%s is greater than the maximum codepoint (%s)",
+        endInclusive,
+        Character.MAX_CODE_POINT);
+
+    return new Retry<>(SolrGenerate.range(startInclusive, endInclusive), Character::isDefined);
+  }
+
+  /**
+   * Returns a generator that provides a value from a generator chosen with probability in
+   * proportion to the weight supplied in the {@link Pair}. Shrinking is towards the first non-zero
+   * weight in the list. At least one generator must have a positive weight and non-positive
+   * generators will never be chosen.
+   *
+   * @param <T> Type to generate
+   * @param mandatory Generator to sample from
+   * @param others Other generators to sample
+   * @return A gen of T
+   */
+  @SafeVarargs
+  public static <T> SolrGen<T> frequency(
+      Pair<Integer, SolrGen<T>> mandatory, Pair<Integer, SolrGen<T>>... others) {
+
+    return frequency(SolrGenerate.FrequencyGen.makeGeneratorList(mandatory, others));
+  }
+
+  /**
+   * Returns a generator that provides a value from a generator chosen with probability in
+   * proportion to the weight supplied in the {@link Pair}. Shrinking is towards the first non-zero
+   * weight in the list. At least one generator must have a positive weight and non-positive
+   * generators will never be chosen.
+   *
+   * @param <T> Type to generate
+   * @param weightedGens pairs of weight and generators to sample in proportion to their weighting
+   * @return A gen of T
+   */
+  public static <T> SolrGen<T> frequency(List<Pair<Integer, SolrGen<T>>> weightedGens) {
+    return SolrGenerate.FrequencyGen.fromList(weightedGens);
+  }
+
+  /**
+   * Booleans solr gen.
+   *
+   * @return the solr gen
+   */
+  public static SolrGen<Boolean> booleans() {
+    return SolrGenerate.pick(Arrays.asList(false, true));
+  }
+
+  /**
+   * Randomly returns one of the supplied values
+   *
+   * @param <T> type of value to generate *
+   * @param ts Values to pick from
+   * @return A Gen of T
+   */
+  public static <T> SolrGen<T> pick(List<T> ts) {
+    Gen<Integer> index = range(0, ts.size() - 1);
+    return new SolrGen<>(prng -> ts.get(index.generate(prng)), null);
+  }
+
+  /**
+   * Returns a generator that provides a value from a random generator provided.
+   *
+   * @param <T> Type to generate
+   * @param mandatory Generator to sample from
+   * @param others Other generators to sample from with equal weighting
+   * @return A gen of T
+   */
+  @SafeVarargs
+  public static <T> SolrGen<T> oneOf(SolrGen<T> mandatory, SolrGen<T>... others) {
+    SolrGen<T>[] generators = Arrays.copyOf(others, others.length + 1);
+    generators[generators.length - 1] = mandatory;
+    SolrGen<Integer> index = range(0, generators.length - 1);
+    return new SolrGen<>(prng -> generators[(index.generate(prng))].generate(prng), null);
+  }
+
+  /**
+   * The type Frequency gen.
+   *
+   * @param <T> the type parameter
+   */
+  static class FrequencyGen<T> extends SolrGen<T> {
+    private final NavigableMap<Integer, SolrGen<T>> weightedMap;
+    private final SolrGen<Integer> indexGen;
+
+    private FrequencyGen(SolrGen<Integer> indexGen, NavigableMap<Integer, SolrGen<T>> weightedMap) {
+      this.weightedMap = weightedMap;
+      this.indexGen = indexGen;
+    }
+
+    /**
+     * Make generator list list.
+     *
+     * @param <T> the type parameter
+     * @param mandatory the mandatory
+     * @param others the others
+     * @return the list
+     */
+    static <T> List<Pair<Integer, SolrGen<T>>> makeGeneratorList(
+        Pair<Integer, SolrGen<T>> mandatory, Pair<Integer, SolrGen<T>>[] others) {
+      List<Pair<Integer, SolrGen<T>>> ts = new ArrayList<>(others.length + 1);
+      ts.add(mandatory);
+      Collections.addAll(ts, others);
+      return ts;
+    }
+
+    /**
+     * From list solr generate . frequency gen.
+     *
+     * @param <T> the type parameter
+     * @param ts the ts
+     * @return the solr generate . frequency gen
+     */
+    /* First the generator normalizes the weights and their total by the greatest common factor
+     * to keep integers small and improve the chance of moving to a new generator as the
+     * index generator shrinks.
+     *
+     * Then assigns each generator a range of integers according to their normalized weight
+     * For example, with three normalized weighted generators {3, g1}, {4, g2}, {5, g3},
+     * total 12 it assigns [0, 2] to g1, [3, 6] to g2, [7, 11] to g3.
+     *
+     * At generation time, the generator picks an integer between [0, total weight) and finds
+     * the generator responsible for the range.
+     */
+    static <T> SolrGenerate.FrequencyGen<T> fromList(List<Pair<Integer, SolrGen<T>>> ts) {
+      if (ts.isEmpty()) {
+        throw new IllegalArgumentException("List of generators must not be empty");
+      }
+      /* Calculate the total unadjusted weights, and the largest common factor
+       * between all the weights and the total, so they can be reduced to the smallest.
+       * It ignores non-positive weights to make it easy to disable generators while developing
+       * properties.
+       */
+      long unadjustedTotalWeights = 0;
+      long commonFactor = 0;
+      for (Pair<Integer, SolrGen<T>> pair : ts) {
+        int weight = pair._1;
+        if (weight <= 0) continue;
+
+        if (unadjustedTotalWeights == 0) {
+          commonFactor = weight;
+        } else {
+          commonFactor = gcd(commonFactor, weight);
+        }
+        unadjustedTotalWeights += weight;
+      }
+      if (unadjustedTotalWeights == 0) {
+        throw new IllegalArgumentException("At least one generator must have a positive weight");
+      }
+      commonFactor = gcd(commonFactor, unadjustedTotalWeights);
+
+      /* Build a tree map with the key as the first integer assigned in the range,
+       * floorEntry will pick the 'the greatest key less than or equal to the given key',
+       * which will find the right generator for the range.
+       */
+      NavigableMap<Integer, SolrGen<T>> weightedMap = new TreeMap<>();
+      int nextStart = 0;
+      for (Pair<Integer, SolrGen<T>> pair : ts) {
+        int weight = pair._1;
+        if (weight <= 0) continue;
+        try {
+          weightedMap.put(nextStart, pair._2);
+        } catch (ClassCastException e) {
+          throw new ClassCastException(
+              "Class cast exception for " + pair._1 + ',' + pair._2 + ' ' + e.getMessage());
+        }
+        nextStart += weight / commonFactor;
+      }
+      final int upperRange = (int) (unadjustedTotalWeights / commonFactor) - 1;
+
+      SolrGen<Integer> indexGen = range(0, upperRange);
+
+      return new SolrGenerate.FrequencyGen<>(indexGen, weightedMap);
+    }
+
+    @Override
+    public T generate(SolrRandomnessSource prng) {
+      return weightedMap.floorEntry(indexGen.generate(prng)).getValue().generate(prng);
+    }
+
+    @Override
+    public String asString(T t) {
+      return weightedMap.get(0).asString(t);
+    }
+
+    private static long gcd(long a, long b) {
+      return BigInteger.valueOf(a).gcd(BigInteger.valueOf(b)).longValue();
+    }
+  }
+}
+
+/**
+ * The type Retry.
+ *
+ * @param <T> the type parameter
+ */
+class Retry<T> extends SolrGen<T> {
+
+  private final Predicate<T> assumption;
+
+  /**
+   * Instantiates a new Retry.
+   *
+   * @param child the child
+   * @param assumption the assumption
+   */
+  Retry(SolrGen<T> child, Predicate<T> assumption) {
+    super(child);
+    this.assumption = assumption;
+  }
+
+  @Override
+  public T generate(RandomnessSource in) {
+    // danger of infinite loop here but space is densely populated enough for
+    // this to be unlikely
+    while (true) {
+      DetatchedRandomnessSource detatched = in.detach();
+      T t = Objects.requireNonNull(child).generate(detatched);
+      if (assumption.test(t)) {
+        detatched.commit();
+        return t;
+      }
+    }
+  }
+
+  @Override
+  public String asString(T t) {
+    return Objects.requireNonNull(child).asString(t);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/SolrRandomnessSource.java b/solr/benchmark/src/java/org/apache/solr/bench/SolrRandomnessSource.java
new file mode 100644
index 0000000..7fb4959
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/SolrRandomnessSource.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench;
+
+import org.apache.solr.bench.generators.Distribution;
+import org.quicktheories.core.RandomnessSource;
+
+/** The interface Solr randomness source. */
+public interface SolrRandomnessSource extends RandomnessSource {
+  /**
+   * Next long.
+   *
+   * @param min the min
+   * @param max the max
+   * @return the long
+   */
+  long next(long min, long max);
+
+  /**
+   * With distribution solr randomness source.
+   *
+   * @param distribution the distribution
+   * @return the solr randomness source
+   */
+  SolrRandomnessSource withDistribution(Distribution distribution);
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/SplittableRandomGenerator.java b/solr/benchmark/src/java/org/apache/solr/bench/SplittableRandomGenerator.java
new file mode 100644
index 0000000..6e60f6a
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/SplittableRandomGenerator.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench;
+
+import java.util.SplittableRandom;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.RandomGeneratorFactory;
+import org.apache.commons.math3.util.FastMath;
+
+/** Extension of {@code java.util.SplittableRandom} to implement {@link RandomGenerator}. */
+public class SplittableRandomGenerator implements RandomGenerator {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = -7745277476784028798L;
+
+  private SplittableRandom random;
+  private double nextGaussian;
+
+  /**
+   * Instantiates a new Splittable random generator.
+   *
+   * @param random the random
+   */
+  public SplittableRandomGenerator(SplittableRandom random) {
+    this.random = random;
+  }
+
+  /**
+   * Instantiates a new Splittable random generator.
+   *
+   * @param seed the seed
+   */
+  public SplittableRandomGenerator(long seed) {
+    this.random = new SplittableRandom(seed);
+  }
+
+  public void setSeed(int seed) {
+    setSeed((long) seed);
+  }
+
+  public void setSeed(int[] seed) {
+    setSeed(RandomGeneratorFactory.convertToLong(seed));
+  }
+
+  @Override
+  public void setSeed(long seed) {
+    this.random = new SplittableRandom(seed);
+  }
+
+  @Override
+  public void nextBytes(byte[] bytes) {
+    random.nextBytes(bytes);
+  }
+
+  @Override
+  public int nextInt() {
+    return random.nextInt();
+  }
+
+  @Override
+  public int nextInt(int n) {
+    return random.nextInt(n);
+  }
+
+  @Override
+  public long nextLong() {
+    return random.nextLong();
+  }
+
+  @Override
+  public boolean nextBoolean() {
+    return random.nextBoolean();
+  }
+
+  @Override
+  public float nextFloat() {
+    return (float) random.nextDouble();
+  }
+
+  @Override
+  public double nextDouble() {
+    return random.nextDouble();
+  }
+
+  @Override
+  public double nextGaussian() {
+
+    final double rnd;
+    if (Double.isNaN(nextGaussian)) {
+      // generate a new pair of gaussian numbers
+      final double x = nextDouble();
+      final double y = nextDouble();
+      final double alpha = 2 * FastMath.PI * x;
+      final double r = FastMath.sqrt(-2 * FastMath.log(y));
+      rnd = r * FastMath.cos(alpha);
+      nextGaussian = r * FastMath.sin(alpha);
+    } else {
+      // use the second element of the pair already generated
+      rnd = nextGaussian;
+      nextGaussian = Double.NaN;
+    }
+
+    return rnd;
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/ArraysDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/ArraysDSL.java
new file mode 100644
index 0000000..4d9ebd3
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/ArraysDSL.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.lang.reflect.Array;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.solr.bench.SolrGenerate;
+import org.quicktheories.api.AsString;
+import org.quicktheories.core.Gen;
+
+public class ArraysDSL {
+
+  /**
+   * Creates an ArrayGeneratorBuilder of Integers that can be used to create an array Source
+   *
+   * @param source a Source of type Integer
+   * @return an ArrayGeneratorBuilder of type Integer
+   */
+  public ArrayGeneratorBuilder<Integer> ofIntegers(Gen<Integer> source) {
+    return new ArrayGeneratorBuilder<>(source, Integer.class);
+  }
+
+  /**
+   * Creates an ArrayGeneratorBuilder of Characters that can be used to create an array Source
+   *
+   * @param source a Source of type Character
+   * @return an ArrayGeneratorBuilder of type Character
+   */
+  public ArrayGeneratorBuilder<Character> ofCharacters(Gen<Character> source) {
+    return new ArrayGeneratorBuilder<>(source, Character.class);
+  }
+
+  /**
+   * Creates an ArrayGeneratorBuilder of Strings that can be used to create an array Source
+   *
+   * @param source a Source of type String
+   * @return an ArrayGeneratorBuilder of type String
+   */
+  public ArrayGeneratorBuilder<String> ofStrings(Gen<String> source) {
+    return new ArrayGeneratorBuilder<>(source, String.class);
+  }
+
+  /**
+   * Creates an ArrayGeneratorBuilder of the given class that can be used to create an array Source
+   *
+   * @param <T> type of value to generate
+   * @param source a Source of type T
+   * @param c a Class of type T
+   * @return an ArrayGeneratorBuilder of type T
+   */
+  public <T> ArrayGeneratorBuilder<T> ofClass(Gen<T> source, Class<T> c) {
+    return new ArrayGeneratorBuilder<>(source, c);
+  }
+
+  public static class ArrayGeneratorBuilder<T> {
+
+    private final Gen<T> source;
+    private final Class<T> c;
+
+    ArrayGeneratorBuilder(Gen<T> source, Class<T> c) {
+      this.source = source;
+      this.c = c;
+    }
+
+    /**
+     * Generates arrays of specified type T of fixed length
+     *
+     * @param length - fixed length
+     * @return a Source of type T[]
+     */
+    public Gen<T[]> withLength(int length) {
+      return withLengthBetween(length, length);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Gen<T[]> withLengths(Gen<Integer> lengths) {
+      return Lists.listsOf(source, Lists.arrayList(), lengths)
+          .map(
+              l -> l.toArray((T[]) Array.newInstance(c, 0)) // will generate
+              // correct size if
+              // zero is less than
+              // the length of the
+              // array
+              )
+          .describedAs(arrayDescriber(source::asString));
+    }
+
+    /**
+     * Generates arrays of specified type T of length bounded inclusively between minimumSize and
+     * maximumSize
+     *
+     * @param minLength - the inclusive minimum size of the array
+     * @param maxLength - the inclusive maximum size of the array
+     * @return a Source of type T[]
+     */
+    public Gen<T[]> withLengthBetween(int minLength, int maxLength) {
+      checkArguments(
+          minLength <= maxLength,
+          "The minLength (%s) is longer than the maxLength(%s)",
+          minLength,
+          maxLength);
+      checkArguments(
+          minLength >= 0,
+          "The length of an array cannot be negative; %s is not an accepted argument",
+          minLength);
+      return withLengths(SolrGenerate.range(minLength, maxLength));
+    }
+
+    private static <T> AsString<T[]> arrayDescriber(Function<T, String> valueDescriber) {
+      return a ->
+          java.util.Arrays.stream(a)
+              .map(valueDescriber)
+              .collect(Collectors.joining(", ", "[", "]"));
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/BooleansDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/BooleansDSL.java
new file mode 100644
index 0000000..0bfd759
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/BooleansDSL.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import org.apache.solr.bench.SolrGenerate;
+
+public class BooleansDSL {
+
+  /**
+   * Generates either true or false randomly.
+   *
+   * @return a Source of booleans
+   */
+  public SolrGen<Boolean> all() {
+    return new SolrGen<>(SolrGenerate.booleans(), Boolean.class);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/DatesDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/DatesDSL.java
new file mode 100644
index 0000000..a63f7b6
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/DatesDSL.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.util.Date;
+import org.apache.solr.bench.SolrGenerate;
+
+/**
+ * A Class for creating Date Sources that will produce Dates based on the number of milliseconds
+ * since epoch
+ */
+public class DatesDSL {
+
+  public SolrGen<Date> all() {
+    return Dates.withMilliSecondsBetween(0, Long.MAX_VALUE);
+  }
+
+  /**
+   * Generates Dates inclusively bounded between January 1, 1970, 00:00:00 GMT and new
+   * Date(milliSecondsFromEpoch). The Source restricts Date generation, so that no Dates before 1970
+   * can be created. The Source is weighted, so it is likely to produce new
+   * Date(millisecondsFromEpoch) one or more times.
+   *
+   * @param millisecondsFromEpoch the number of milliseconds from the epoch such that Dates are
+   *     generated within this interval.
+   * @return a Source of type Date
+   */
+  public SolrGen<Date> withMilliseconds(long millisecondsFromEpoch) {
+    lowerBoundGEQZero(millisecondsFromEpoch);
+    return Dates.withMilliSeconds(millisecondsFromEpoch);
+  }
+
+  /**
+   * Generates Dates inclusively bounded between new Date(millisecondsFromEpochStartInclusive) and
+   * new Date(millisecondsFromEpochEndInclusive).
+   *
+   * @param millisecondsFromEpochStartInclusive the number of milliseconds from epoch for the
+   *     desired older Date
+   * @param millisecondsFromEpochEndInclusive the number of milliseconds from epoch for the desired
+   *     more recent Date
+   * @return a source of Dates
+   */
+  public SolrGen<Date> withMillisecondsBetween(
+      long millisecondsFromEpochStartInclusive, long millisecondsFromEpochEndInclusive) {
+    lowerBoundGEQZero(millisecondsFromEpochStartInclusive);
+    maxGEQMin(millisecondsFromEpochStartInclusive, millisecondsFromEpochEndInclusive);
+    return Dates.withMilliSecondsBetween(
+        millisecondsFromEpochStartInclusive, millisecondsFromEpochEndInclusive);
+  }
+
+  private void lowerBoundGEQZero(long milliSecondsFromEpoch) {
+    checkArguments(
+        milliSecondsFromEpoch >= 0,
+        "A negative long (%s) is not an accepted number of milliseconds",
+        milliSecondsFromEpoch);
+  }
+
+  private void maxGEQMin(long startInclusive, long endInclusive) {
+    checkArguments(
+        startInclusive <= endInclusive,
+        "Cannot have the maximum long (%s) smaller than the minimum long value (%s)",
+        endInclusive,
+        startInclusive);
+  }
+
+  static class Dates {
+
+    private Dates() {}
+
+    static SolrGen<Date> withMilliSeconds(long milliSecondsFromEpoch) {
+      return withMilliSecondsBetween(0, milliSecondsFromEpoch);
+    }
+
+    static SolrGen<Date> withMilliSecondsBetween(
+        long milliSecondsFromEpochStartInclusive, long milliSecondsFromEpochEndInclusive) {
+      return SolrGenerate.longRange(
+              milliSecondsFromEpochStartInclusive, milliSecondsFromEpochEndInclusive)
+          .map(Date::new, Date.class);
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Distribution.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Distribution.java
new file mode 100644
index 0000000..48cf980
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Distribution.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+/** The enum Distribution. */
+public enum Distribution {
+  /** Gaussian distribution. */
+  GAUSSIAN,
+  /** Uniform distribution. */
+  UNIFORM,
+  /** Zipfian distribution. */
+  ZIPFIAN
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Doubles.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Doubles.java
new file mode 100644
index 0000000..519b326
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Doubles.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.util.function.Function;
+import org.apache.solr.bench.SolrGenerate;
+
+final class Doubles {
+
+  private static final long POSITIVE_INFINITY_CORRESPONDING_LONG = 0x7ff0000000000000L;
+  private static final long NEGATIVE_INFINITY_CORRESPONDING_LONG = 0xfff0000000000000L;
+  // fraction portion of double, last 52 bits
+  private static final long FRACTION_BITS = 1L << 53;
+  private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
+  private static final long NEGATIVE_ZERO_CORRESPONDING_LONG = Long.MIN_VALUE;
+
+  private Doubles() {}
+
+  static SolrGen<Double> fromNegativeInfinityToPositiveInfinity() {
+    return negative().mix(positive(), Double.class);
+  }
+
+  static SolrGen<Double> negative() {
+    return range(NEGATIVE_ZERO_CORRESPONDING_LONG, NEGATIVE_INFINITY_CORRESPONDING_LONG);
+  }
+
+  static SolrGen<Double> positive() {
+    return range(0, POSITIVE_INFINITY_CORRESPONDING_LONG);
+  }
+
+  static SolrGen<Double> fromZeroToOne() {
+    return range(0, FRACTION_BITS, l -> l * DOUBLE_UNIT);
+  }
+
+  static SolrGen<Double> between(double min, double max) {
+    checkArguments(min <= max, "Cannot have the maximum (%s) smaller than the min (%s)", max, min);
+    double adjustedMax = max - min;
+    return (SolrGen<Double>) fromZeroToOne().map(d -> (d * adjustedMax) + min);
+  }
+
+  static SolrGen<Double> range(long startInclusive, long endInclusive) {
+    return range(startInclusive, endInclusive, Double::longBitsToDouble);
+  }
+
+  static SolrGen<Double> range(
+      long startInclusive, long endInclusive, Function<Long, Double> conversion) {
+    return (SolrGen<Double>)
+        SolrGenerate.longRange(startInclusive, endInclusive).map(conversion, Double.class);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/DoublesDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/DoublesDSL.java
new file mode 100644
index 0000000..bbd2043
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/DoublesDSL.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+/**
+ * A Class for creating Double Sources that will produce doubles within a set interval and will
+ * shrink within this domain.
+ */
+public class DoublesDSL {
+
+  /**
+   * Generates Doubles inclusively bounded below by Double.NEGATIVE_INFINITY and above by
+   * Double.POSITIVE_INFINITY.
+   *
+   * @return a Source of type Double
+   */
+  public SolrGen<Double> all() {
+    return Doubles.fromNegativeInfinityToPositiveInfinity();
+  }
+
+  /**
+   * Generates Doubles inclusively bounded below by Double.NEGATIVE_INFINITY and above by a value
+   * very close to zero on the negative side.
+   *
+   * @return a Source of type Double
+   */
+  public SolrGen<Double> negative() {
+    return Doubles.negative();
+  }
+
+  /**
+   * Generates Doubles inclusively bounded below by a value very close to zero on the positive side
+   * and above by Double.POSITIVE_INFINITY.
+   *
+   * @return a Source of type Double
+   */
+  public SolrGen<Double> positive() {
+    return Doubles.positive();
+  }
+
+  /**
+   * Generates Doubles inclusively bounded below by zero and above by one.
+   *
+   * @return a Source of type Double
+   */
+  public SolrGen<Double> fromZeroToOne() {
+    return Doubles.fromZeroToOne();
+  }
+
+  /**
+   * Generates Doubles inclusively between two bounds
+   *
+   * @param minInclusive minimum value to generate
+   * @param maxInclusive maximum value to generate
+   * @return a Gen of Doubles between minInclusive and maxInclusive
+   */
+  public SolrGen<Double> between(double minInclusive, double maxInclusive) {
+    return Doubles.between(minInclusive, maxInclusive);
+  }
+
+  /**
+   * Starts a range
+   *
+   * @param startInclusive - lower bound of domain
+   * @return start of range
+   */
+  public DoubleDomainBuilder from(final double startInclusive) {
+    return new DoubleDomainBuilder(startInclusive);
+  }
+
+  public class DoubleDomainBuilder {
+
+    private final double startInclusive;
+
+    private DoubleDomainBuilder(double startInclusive) {
+      this.startInclusive = startInclusive;
+    }
+
+    /**
+     * Generates within the interval specified with an inclusive lower and upper bound.
+     *
+     * @param endInclusive - inclusive upper bound of domain
+     * @return a Source of type Double
+     */
+    public SolrGen<Double> upToAndIncluding(final double endInclusive) {
+      return between(startInclusive, endInclusive);
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Floats.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Floats.java
new file mode 100644
index 0000000..7b5a25a
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Floats.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import org.apache.solr.bench.SolrGenerate;
+
+final class Floats {
+
+  private static final int POSITIVE_INFINITY_CORRESPONDING_INT = 0x7f800000;
+  private static final int NEGATIVE_INFINITY_CORRESPONDING_INT = 0xff800000;
+  private static final int NEGATIVE_ZERO_CORRESPONDING_INT = Integer.MIN_VALUE;
+
+  private Floats() {}
+
+  static SolrGen<Float> fromNegativeInfinityToPositiveInfinity() {
+    return fromNegativeInfinityToNegativeZero().mix(fromZeroToPositiveInfinity(), Float.class);
+  }
+
+  static SolrGen<Float> fromNegativeInfinityToNegativeZero() {
+    return range(NEGATIVE_ZERO_CORRESPONDING_INT, NEGATIVE_INFINITY_CORRESPONDING_INT);
+  }
+
+  static SolrGen<Float> fromZeroToPositiveInfinity() {
+    return range(0, POSITIVE_INFINITY_CORRESPONDING_INT);
+  }
+
+  static SolrGen<Float> fromZeroToOne() {
+    return (SolrGen<Float>)
+        SolrGenerate.range(0, 1 << 24).map(i -> i / (float) (1 << 24), Float.class);
+  }
+
+  static SolrGen<Float> between(float min, float max) {
+    checkArguments(min <= max, "Cannot have the maximum (%s) smaller than the min (%s)", max, min);
+    float adjustedMax = max - min;
+    return (SolrGen<Float>) fromZeroToOne().map(f -> (f * adjustedMax) + min, Float.class);
+  }
+
+  private static SolrGen<Float> range(int startInclusive, int endInclusive) {
+    return (SolrGen<Float>)
+        SolrGenerate.range(startInclusive, endInclusive).map(Float::intBitsToFloat, Float.class);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/FloatsDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/FloatsDSL.java
new file mode 100644
index 0000000..5d6f333
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/FloatsDSL.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import org.quicktheories.core.Gen;
+
+/**
+ * A Class for creating Float Sources that will produce floats within a set interval and will shrink
+ * within this domain.
+ */
+public class FloatsDSL {
+
+  /**
+   * Generates Floats inclusively bounded below by Float.NEGATIVE_INFINITY and above by
+   * Float.POSITIVE_INFINITY.
+   *
+   * @return a Source of type Float
+   */
+  public SolrGen<Float> all() {
+    return Floats.fromNegativeInfinityToPositiveInfinity();
+  }
+
+  /**
+   * Generates Floats inclusively bounded below by Float.NEGATIVE_INFINITY and above by a value very
+   * close to zero on the negative side.
+   *
+   * @return a Source of type Float
+   */
+  public Gen<Float> negative() {
+    return Floats.fromNegativeInfinityToNegativeZero();
+  }
+
+  /**
+   * Generates Floats inclusively bounded below by a value very close to zero on the positive side
+   * and above by Float.POSITIVE_INFINITY.
+   *
+   * @return a Source of type Float
+   */
+  public Gen<Float> positive() {
+    return Floats.fromZeroToPositiveInfinity();
+  }
+
+  /**
+   * Generates Floats inclusively bounded below by zero and above by one.
+   *
+   * @return a Source of type Float
+   */
+  public Gen<Float> fromZeroToOne() {
+    return Floats.fromZeroToOne();
+  }
+
+  /**
+   * Generates Floats inclusively between two bounds
+   *
+   * @param minInclusive minimum value to generate
+   * @param maxInclusive maximum value to generate
+   * @return a Gen of Floats between minInclusive and maxInclusive
+   */
+  public Gen<Float> between(float minInclusive, float maxInclusive) {
+    return Floats.between(minInclusive, maxInclusive);
+  }
+
+  /**
+   * Starts a range
+   *
+   * @param startInclusive - lower bound of domain
+   * @return start of range
+   */
+  public FloatDomainBuilder from(final float startInclusive) {
+    return new FloatDomainBuilder(startInclusive);
+  }
+
+  public class FloatDomainBuilder {
+
+    private final float startInclusive;
+
+    private FloatDomainBuilder(float startInclusive) {
+      this.startInclusive = startInclusive;
+    }
+
+    /**
+     * Generates within the interval specified with an inclusive lower and upper bound.
+     *
+     * @param endInclusive - inclusive upper bound of domain
+     * @return a Source of type Float
+     */
+    public Gen<Float> upToAndIncluding(final float endInclusive) {
+      return between(startInclusive, endInclusive);
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/IntegersDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/IntegersDSL.java
new file mode 100644
index 0000000..b3ffc58
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/IntegersDSL.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.util.SplittableRandom;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.solr.bench.SolrGenerate;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+import org.quicktheories.impl.SplittableRandomSource;
+
+/** The type Integers dsl. */
+public class IntegersDSL {
+
+  /**
+   * Constructs a IntegerDomainBuilder object with an inclusive lower bound
+   *
+   * @param startInclusive - lower bound of domain
+   * @return an IntegerDomainBuilder
+   */
+  public IntegerDomainBuilder from(final int startInclusive) {
+    return new IntegerDomainBuilder(startInclusive);
+  }
+
+  /**
+   * Generates all possible integers in Java bounded below by Integer.MIN_VALUE and above by
+   * Integer.MAX_VALUE.
+   *
+   * @return a Source of type Integer
+   */
+  public SolrGen<Integer> all() {
+    return between(Integer.MIN_VALUE, Integer.MAX_VALUE).describedAs("All Ints");
+  }
+
+  /**
+   * All with max cardinality solr gen.
+   *
+   * @param maxCardinality the max cardinality
+   * @return the solr gen
+   */
+  public SolrGen<Integer> allWithMaxCardinality(int maxCardinality) {
+    return between(Integer.MIN_VALUE, Integer.MAX_VALUE, maxCardinality)
+        .describedAs("All Ints w/ Max Cardinality");
+  }
+
+  /**
+   * Generates all possible positive integers in Java, bounded above by Integer.MAX_VALUE.
+   *
+   * @return a Source of type Integer
+   */
+  public SolrGen<Integer> allPositive() {
+    return between(1, Integer.MAX_VALUE).describedAs("All Positive Ints");
+  }
+
+  /**
+   * All positive with max cardinality solr gen.
+   *
+   * @param maxCardinality the max cardinality
+   * @return the solr gen
+   */
+  public SolrGen<Integer> allPositiveWithMaxCardinality(int maxCardinality) {
+    return between(1, Integer.MAX_VALUE, maxCardinality)
+        .describedAs("All Positive Ints with Max Cardinality");
+  }
+
+  /**
+   * Incrementing SolrGen. Always returns an integer greater than the previous one. You cannot count
+   * on the increment being 1.
+   *
+   * @return a SolrGen that returns an int greater than the previous
+   */
+  public SolrGen<Integer> incrementing() {
+    return new IncrementingIntegerSolrGen();
+  }
+
+  private static class IncrementingIntegerSolrGen extends SolrGen<Integer> {
+    /** The Increment. */
+    AtomicInteger increment = new AtomicInteger();
+
+    /** Instantiates a new Incrementing integer solr gen. */
+    public IncrementingIntegerSolrGen() {
+      super(Integer.class);
+      describedAs("Incrementing Int");
+    }
+
+    @Override
+    public Integer generate(RandomnessSource in) {
+      return increment.getAndIncrement();
+    }
+
+    @Override
+    public Integer generate(SolrRandomnessSource in) {
+      return increment.getAndIncrement();
+    }
+  }
+
+  private static class IntegerMaxCardinalitySolrGen extends SolrGen<Integer> {
+    private final int maxCardinality;
+    private final Gen<Integer> integers;
+    /** The Cardinality start. */
+    Integer cardinalityStart;
+
+    /**
+     * Instantiates a new Integer max cardinality solr gen.
+     *
+     * @param maxCardinality the max cardinality
+     * @param integers the integers
+     */
+    public IntegerMaxCardinalitySolrGen(int maxCardinality, Gen<Integer> integers) {
+      this.maxCardinality = maxCardinality;
+      this.integers = integers;
+      this.describedAs("Max Cardinality Int");
+    }
+
+    @Override
+    public Integer generate(SolrRandomnessSource in) {
+      if (cardinalityStart == null) {
+        cardinalityStart =
+            SolrGenerate.range(0, Integer.MAX_VALUE - maxCardinality - 1).generate(in);
+      }
+
+      long seed =
+          SolrGenerate.range(cardinalityStart, cardinalityStart + maxCardinality - 1).generate(in);
+      return integers.generate(new SplittableRandomSource(new SplittableRandom(seed)));
+    }
+  }
+
+  /** The type Integer domain builder. */
+  public class IntegerDomainBuilder {
+
+    private final int startInclusive;
+
+    private int maxCardinality;
+
+    private IntegerDomainBuilder(int startInclusive) {
+      this.startInclusive = startInclusive;
+    }
+
+    /**
+     * Max cardinality integer domain builder.
+     *
+     * @param max the max
+     * @return the integer domain builder
+     */
+    public IntegerDomainBuilder maxCardinality(int max) {
+      maxCardinality = max;
+      return this;
+    }
+
+    /**
+     * Generates integers within the interval specified with an inclusive lower and upper bound.
+     *
+     * @param endInclusive - inclusive upper bound of domain
+     * @return a Source of type Integer
+     */
+    public SolrGen<Integer> upToAndIncluding(final int endInclusive) {
+      return between(startInclusive, endInclusive, maxCardinality)
+          .describedAs("Int UpToAndIncluding");
+    }
+
+    /**
+     * Generates integers within the interval specified with an inclusive lower bound and exclusive
+     * upper bound.
+     *
+     * @param endExclusive - exclusive upper bound of domain
+     * @return a Source of type Integer
+     */
+    public SolrGen<Integer> upTo(final int endExclusive) {
+      return between(startInclusive, endExclusive - 1, maxCardinality).describedAs("Int UpTo");
+    }
+  }
+
+  /**
+   * Generates Integers within the interval specified with an inclusive lower and upper bound.
+   *
+   * @param startInclusive - inclusive lower bound of domain
+   * @param endInclusive - inclusive upper bound of domain
+   * @return a Source of type Integer
+   */
+  public SolrGen<Integer> between(final int startInclusive, final int endInclusive) {
+    return between(startInclusive, endInclusive, 0).describedAs("Int Between");
+  }
+
+  /**
+   * Generates Integers within the interval specified with an inclusive lower and upper bound.
+   *
+   * @param startInclusive - inclusive lower bound of domain
+   * @param endInclusive - inclusive upper bound of domain
+   * @param maxCardinality the max cardinality
+   * @return a Source of type Integer
+   */
+  public SolrGen<Integer> between(
+      final int startInclusive, final int endInclusive, int maxCardinality) {
+    checkArguments(
+        startInclusive <= endInclusive,
+        "There are no Integer values to be generated between (%s) and (%s)",
+        startInclusive,
+        endInclusive);
+    Gen<Integer> integers = SolrGenerate.range(startInclusive, endInclusive);
+    if (maxCardinality > 0) {
+      return new SolrGen<>(
+              new IntegerMaxCardinalitySolrGen(maxCardinality, integers), Integer.class)
+          .describedAs("Int Between w/ MaxCardinality");
+    } else {
+      return new SolrGen<>(integers, Integer.class).describedAs("Int Between");
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/LazyGen.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/LazyGen.java
new file mode 100644
index 0000000..971dd04
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/LazyGen.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.function.Supplier;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.quicktheories.core.RandomnessSource;
+
+/**
+ * The type Lazy gen.
+ *
+ * @param <T> the type parameter
+ */
+public class LazyGen<T> extends SolrGen<T> {
+
+  private final Supplier<SolrGen<T>> genSupplier;
+  private SolrGen<T> gen;
+
+  /**
+   * Instantiates a new Lazy gen.
+   *
+   * @param genSupplier the gen supplier
+   */
+  public LazyGen(Supplier<SolrGen<T>> genSupplier) {
+    this.genSupplier = genSupplier;
+  }
+
+  @Override
+  public T generate(RandomnessSource in) {
+    if (this.gen == null) {
+      this.gen = genSupplier.get();
+    }
+
+    return gen.generate(in);
+  }
+
+  @Override
+  public T generate(SolrRandomnessSource in) {
+    if (this.gen == null) {
+      this.gen = genSupplier.get();
+    }
+
+    return gen.generate(in);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Lists.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Lists.java
new file mode 100644
index 0000000..a341882
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Lists.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.quicktheories.api.AsString;
+import org.quicktheories.core.Gen;
+
+public final class Lists {
+
+  private Lists() {}
+
+  static <T> Gen<List<T>> listsOf(Gen<T> generator, Gen<Integer> sizes) {
+    return listsOf(generator, arrayList(), sizes).mix(listsOf(generator, linkedList(), sizes));
+  }
+
+  public static <T, A extends List<T>> Collector<T, List<T>, List<T>> arrayList() {
+    return toList(ArrayList::new);
+  }
+
+  public static <T, A extends List<T>> Collector<T, List<T>, List<T>> linkedList() {
+    return toList(LinkedList::new);
+  }
+
+  public static <T, A extends List<T>> Collector<T, A, A> toList(Supplier<A> collectionFactory) {
+    return Collector.of(
+        collectionFactory,
+        List::add,
+        (left, right) -> {
+          left.addAll(right);
+          return left;
+        });
+  }
+
+  static <T> Gen<List<T>> listsOf(
+      Gen<T> values, Collector<T, List<T>, List<T>> collector, Gen<Integer> sizes) {
+
+    Gen<List<T>> gen =
+        prng -> {
+          int size = sizes.generate(prng);
+          return Stream.generate(() -> values.generate(prng)).limit(size).collect(collector);
+        };
+    return gen.describedAs(listDescriber(values::asString));
+  }
+
+  private static <T> AsString<List<T>> listDescriber(Function<T, String> valueDescriber) {
+    return list -> list.stream().map(valueDescriber).collect(Collectors.joining(", ", "[", "]"));
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/ListsDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/ListsDSL.java
new file mode 100644
index 0000000..a8692e8
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/ListsDSL.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.Lists.listsOf;
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import org.apache.solr.bench.SolrGenerate;
+import org.quicktheories.core.Gen;
+import org.quicktheories.generators.Lists;
+
+/**
+ * A Class for creating List Sources that will produce List objects of either fixed or bounded size.
+ */
+public class ListsDSL {
+
+  /**
+   * Creates an appropriate Collector for a type of List by specifying the Supplier used as a
+   * parameter
+   *
+   * @param <T> type to generate
+   * @param <A> list type
+   * @param collectionFactory a supplier of A
+   * @return a Collector
+   */
+  public <T, A extends List<T>> Collector<T, A, A> createListCollector(
+      Supplier<A> collectionFactory) {
+    return Lists.toList(collectionFactory);
+  }
+
+  /**
+   * Creates a ListGeneratorBuilder.
+   *
+   * @param <T> type to generate
+   * @param source a Source of type T for the items in the list
+   * @return a ListGeneratorBuilder of type T
+   */
+  public <T> ListGeneratorBuilder<T> of(Gen<T> source) {
+    return new ListGeneratorBuilder<>(source);
+  }
+
+  /**
+   * ListGeneratorBuilder enables the creation of Sources for Lists of fixed and bounded size, where
+   * no Collector is specified. A ListGeneratorBuilder can be used to create a
+   * TypedListGeneratorBuilder, where the Collector is specified.
+   *
+   * @param <T> type to generate
+   */
+  public static class ListGeneratorBuilder<T> {
+
+    protected final Gen<T> source;
+
+    ListGeneratorBuilder(Gen<T> source) {
+      this.source = source;
+    }
+
+    /**
+     * Generates a List of objects, where the size of the List is fixed
+     *
+     * @param size size of lists to generate
+     * @return a Source of Lists of type T
+     */
+    public Gen<List<T>> ofSize(int size) {
+      return ofSizeBetween(size, size);
+    }
+
+    /**
+     * Generates a List of objects, where the size of the List is bounded by minimumSize and
+     * maximumSize
+     *
+     * @param minimumSize - inclusive minimum size of List
+     * @param maximumSize - inclusive maximum size of List
+     * @return a Source of Lists of type T
+     */
+    public Gen<List<T>> ofSizeBetween(int minimumSize, int maximumSize) {
+      checkBoundedListArguments(minimumSize, maximumSize);
+      return ofSizes(SolrGenerate.range(minimumSize, maximumSize));
+    }
+
+    public Gen<List<T>> ofSizes(Gen<Integer> sizes) {
+      return listsOf(source, sizes);
+    }
+
+    /**
+     * Determines how the Lists will be collected and returns an TypedListGeneratorBuilder with the
+     * Collector specified
+     *
+     * @param collector collector to use to construct list
+     * @return a TypedListGeneratorBuilder
+     */
+    public TypedListGeneratorBuilder<T> ofType(Collector<T, List<T>, List<T>> collector) {
+      return new TypedListGeneratorBuilder<>(source, collector);
+    }
+  }
+
+  /**
+   * TypedListGeneratorBuilder enables the creation of Sources for Lists of fixed and bounded size,
+   * where the Collector is fixed.
+   */
+  public static class TypedListGeneratorBuilder<T> {
+
+    private final Gen<T> source;
+    private final Collector<T, List<T>, List<T>> collector;
+
+    TypedListGeneratorBuilder(Gen<T> source, Collector<T, List<T>, List<T>> collector) {
+      this.source = source;
+      this.collector = collector;
+    }
+
+    /**
+     * Generates a List of objects, where the size of the List is fixed
+     *
+     * @param size size of lists to generate
+     * @return a Source of Lists of type T
+     */
+    public Gen<List<T>> ofSize(int size) {
+      return ofSizeBetween(size, size);
+    }
+
+    /**
+     * Generates a List of objects, where the size of the List is bounded by minimumSize and
+     * maximumSize
+     *
+     * @param minimumSize - inclusive minimum size of List
+     * @param maximumSize - inclusive maximum size of List
+     * @return a Source of Lists of type T
+     */
+    public Gen<List<T>> ofSizeBetween(int minimumSize, int maximumSize) {
+      checkBoundedListArguments(minimumSize, maximumSize);
+      return listsOf(source, collector, SolrGenerate.range(minimumSize, maximumSize));
+    }
+  }
+
+  private static void checkBoundedListArguments(int minimumSize, int maximumSize) {
+    checkArguments(
+        minimumSize <= maximumSize,
+        "The minimumSize (%s) is longer than the maximumSize(%s)",
+        minimumSize,
+        maximumSize);
+    checkSizeNotNegative(minimumSize);
+  }
+
+  private static void checkSizeNotNegative(int size) {
+    checkArguments(
+        size >= 0, "The size of a List cannot be negative; %s is not an accepted argument", size);
+  }
+
+  public <T, A extends List<T>> Collector<T, List<T>, List<T>> arrayList() {
+    return Lists.arrayList();
+  }
+
+  public <T, A extends List<T>> Collector<T, List<T>, List<T>> linkedList() {
+    return Lists.linkedList();
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/LongsDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/LongsDSL.java
new file mode 100644
index 0000000..5f14a2c
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/LongsDSL.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import org.apache.solr.bench.SolrGenerate;
+
+/**
+ * A Class for creating Long Sources that will produce Longs within a set interval and will shrink
+ * within this domain.
+ */
+public class LongsDSL {
+
+  /**
+   * Constructs a LongDomainBuilder object with an inclusive lower bound
+   *
+   * @param startInclusive - lower bound of domain
+   * @return a LongDomainBuilder
+   */
+  public LongDomainBuilder from(final long startInclusive) {
+    return new LongDomainBuilder(startInclusive);
+  }
+
+  /**
+   * Generates all possible Longs in Java bounded below by Long.MIN_VALUE and above by
+   * Long.MAX_VALUE.
+   *
+   * @return a Source of type Long
+   */
+  public SolrGen<Long> all() {
+    return between(Long.MIN_VALUE, Long.MAX_VALUE);
+  }
+
+  public class LongDomainBuilder {
+
+    private final long startInclusive;
+
+    private LongDomainBuilder(long startInclusive) {
+      this.startInclusive = startInclusive;
+    }
+
+    /**
+     * Generates Longs within the interval specified with an inclusive lower and upper bound.
+     *
+     * @param endInclusive - inclusive upper bound of domain
+     * @return a Source of type Long
+     */
+    public SolrGen<Long> upToAndIncluding(final long endInclusive) {
+      return between(startInclusive, endInclusive);
+    }
+
+    /**
+     * Generates Longs within the interval specified with an inclusive lower bound and exclusive
+     * upper bound.
+     *
+     * @param endExclusive - exclusive upper bound of domain
+     * @return a Source of type Long
+     */
+    public SolrGen<Long> upTo(final long endExclusive) {
+      return between(startInclusive, endExclusive - 1);
+    }
+  }
+
+  /**
+   * Generates Longs within the interval specified with an inclusive lower and upper bound.
+   *
+   * <p>The Source is weighted, so it is likely to generate the upper and lower limits of the domain
+   * one or more times.
+   *
+   * @param startInclusive - inclusive lower bound of domain
+   * @param endInclusive - inclusive upper bound of domain
+   * @return a Source of type Long
+   */
+  public SolrGen<Long> between(final long startInclusive, final long endInclusive) {
+    checkArguments(
+        startInclusive <= endInclusive,
+        "There are no Long values to be generated between (%s) and (%s)",
+        startInclusive,
+        endInclusive);
+    return new SolrGen<>(SolrGenerate.longRange(startInclusive, endInclusive), Long.class);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Maps.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Maps.java
new file mode 100644
index 0000000..9ab4327
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Maps.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.quicktheories.api.AsString;
+import org.quicktheories.core.Gen;
+
+/** The type Maps. */
+public class Maps {
+
+  private Maps() {}
+
+  /**
+   * Bounded maps of gen.
+   *
+   * @param <K> the type parameter
+   * @param <V> the type parameter
+   * @param kg the kg
+   * @param vg the vg
+   * @param sizes the sizes
+   * @return the gen
+   */
+  static <K, V> Gen<Map<K, V>> boundedMapsOf(Gen<K> kg, Gen<V> vg, Gen<Integer> sizes) {
+    return mapsOf(kg, vg, defaultMap(), sizes);
+  }
+
+  /**
+   * Default map collector.
+   *
+   * @param <K> the type parameter
+   * @param <V> the type parameter
+   * @return the collector
+   */
+  public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> defaultMap() {
+    return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
+  }
+
+  /**
+   * Maps of gen.
+   *
+   * @param <K> the type parameter
+   * @param <V> the type parameter
+   * @param kg the kg
+   * @param vg the vg
+   * @param collector the collector
+   * @param sizes the sizes
+   * @return the gen
+   */
+  static <K, V> Gen<Map<K, V>> mapsOf(
+      Gen<K> kg,
+      Gen<V> vg,
+      Collector<Map.Entry<K, V>, ?, Map<K, V>> collector,
+      Gen<Integer> sizes) {
+    SolrGen<Map<K, V>> gen =
+        new SolrGen<>() {
+          @Override
+          public Map<K, V> generate(SolrRandomnessSource prng) {
+            int size = sizes.generate(prng);
+            return Stream.generate(() -> kg.generate(prng))
+                .distinct()
+                .map(k -> mapEntry(k, vg.generate(prng)))
+                .limit(size)
+                .collect(collector);
+          }
+        };
+    return gen.describedAs("Maps Of").describedAs(mapDescriber(kg::asString, vg::asString));
+  }
+
+  private static <K, V> AsString<Map<K, V>> mapDescriber(
+      Function<K, String> kd, Function<V, String> vd) {
+    return list ->
+        list.entrySet().stream()
+            .map(e -> '(' + kd.apply(e.getKey()) + ',' + vd.apply(e.getValue()) + ')')
+            .collect(Collectors.joining(", ", "[", "]"));
+  }
+
+  /**
+   * Map entry map . entry.
+   *
+   * @param <K> the type parameter
+   * @param <V> the type parameter
+   * @param k the k
+   * @param v the v
+   * @return the map . entry
+   */
+  static <K, V> Map.Entry<K, V> mapEntry(K k, V v) {
+    return Collections.singletonMap(k, v).entrySet().iterator().next();
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/MapsDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/MapsDSL.java
new file mode 100644
index 0000000..530a9c3
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/MapsDSL.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+
+import java.util.Map;
+import org.apache.solr.bench.SolrGenerate;
+import org.quicktheories.core.Gen;
+
+/**
+ * A Class for creating Map Sources that will produce Map objects of either fixed or bounded size.
+ */
+public class MapsDSL {
+
+  /**
+   * Creates a ListGeneratorBuilder.
+   *
+   * @param keys Gen of keys
+   * @param values Gen of values
+   * @param <K> key type to generate
+   * @param <V> value type to generate
+   * @return a MapGeneratorBuilder of type K,V
+   */
+  public <K, V> MapGeneratorBuilder<K, V> of(SolrGen<K> keys, SolrGen<V> values) {
+    return new MapGeneratorBuilder<>(keys, values);
+  }
+
+  /**
+   * MapGeneratorBuilder enables the creation of Sources for Maps of fixed and bounded size, where
+   * no Collector is specified. A MapGeneratorBuilder can be used to create a
+   * TypedListGeneratorBuilder, where the Collector is specified.
+   *
+   * @param <K> key type to generate
+   * @param <V> value type to generate
+   */
+  public static class MapGeneratorBuilder<K, V> {
+    final Gen<K> kg;
+    final Gen<V> vg;
+
+    public MapGeneratorBuilder(final Gen<K> kg, final Gen<V> vg) {
+      this.kg = kg;
+      this.vg = vg;
+    }
+
+    /**
+     * Generates a Map of objects, where the size of the Map is fixed
+     *
+     * @param size size of lists to generate
+     * @return a Source of Maps of type K,V
+     */
+    public Gen<Map<K, V>> ofSize(int size) {
+      return ofSizeBetween(size, size);
+    }
+
+    /**
+     * Generates a Map of objects, where the size of the Map is bounded by minimumSize and
+     * maximumSize
+     *
+     * @param minSize inclusive minimum size of Map
+     * @param maxSize inclusive maximum size of Map
+     * @return a Source of Maps of type T
+     */
+    public Gen<Map<K, V>> ofSizeBetween(int minSize, int maxSize) {
+      checkBoundedArguments(minSize, maxSize);
+      return ofSizes(SolrGenerate.range(minSize, maxSize));
+    }
+
+    /**
+     * Generates a Map of objects with sizes drawn from sizes gen
+     *
+     * @param sizes Sizes of maps to generate
+     * @return A Source of Maps of Type T
+     */
+    public Gen<Map<K, V>> ofSizes(Gen<Integer> sizes) {
+      return Maps.boundedMapsOf(kg, vg, sizes);
+    }
+
+    private static void checkBoundedArguments(int minSize, int maxSize) {
+      checkArguments(
+          minSize <= maxSize, "The minSize (%s) is longer than the maxSize(%s)", minSize, maxSize);
+      checkSizeNotNegative(minSize);
+    }
+  }
+
+  private static void checkSizeNotNegative(int size) {
+    checkArguments(
+        size >= 0, "The size of a Map cannot be negative; %s is not an accepted argument", size);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/MultiString.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/MultiString.java
new file mode 100644
index 0000000..c05f0df
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/MultiString.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+/** The interface Multi string. */
+public interface MultiString {}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/NamedListGen.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/NamedListGen.java
new file mode 100644
index 0000000..6c1f6c3
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/NamedListGen.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.Map;
+import org.apache.solr.common.util.NamedList;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+
+public class NamedListGen extends SolrGen<NamedList<?>> {
+
+  private final Gen<Map<String, Object>> map;
+
+  public NamedListGen(Gen<Map<String, Object>> map) {
+    this.map = map;
+  }
+
+  /** * Generate random NamedLists. */
+  @Override
+  public NamedList<?> generate(RandomnessSource in) {
+
+    return new NamedList<>(map.generate(in));
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/RandomDataHistogram.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/RandomDataHistogram.java
new file mode 100644
index 0000000..947db5a
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/RandomDataHistogram.java
@@ -0,0 +1,544 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.lang.invoke.MethodHandles;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.quicktheories.api.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** The type Random data histogram. */
+public class RandomDataHistogram {
+
+  /** The constant MAX_TYPES_TO_COLLECT. */
+  public static final int MAX_TYPES_TO_COLLECT = 500;
+
+  /** The constant MAX_ITEMS_TO_PRINT. */
+  public static final int MAX_ITEMS_TO_PRINT = 100;
+
+  /** The constant MAX_LABEL_LENGTH. */
+  public static final int MAX_LABEL_LENGTH = 100;
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /** The constant VERTICAL_BAR. */
+  public static final char VERTICAL_BAR = '│';
+
+  /** The constant COUNT. */
+  public static final String COUNT = "Count";
+
+  /** The constant MAX_WIDTH. */
+  public static final int MAX_WIDTH = 120;
+
+  /** The constant LABEL. */
+  public static final String LABEL = "Label";
+
+  /** The constant HISTO_DOT. */
+  public static final char HISTO_DOT = '⦿';
+
+  private final int bucketCnt;
+
+  private static final MathContext MATH_CONTEXT = new MathContext(4); // 4 precision
+
+  /**
+   * Instantiates a new Random data histogram.
+   *
+   * @param bucketCnt the bucket cnt
+   */
+  public RandomDataHistogram(int bucketCnt) {
+    this.bucketCnt = bucketCnt;
+  }
+
+  /**
+   * Generate random data historgram report.
+   *
+   * @param entries the list of Trackers maintaining the counts
+   * @param label the label for counted objects
+   * @return the string
+   */
+  public String print(List<Tracker> entries, String label) {
+    return print(entries, true, label);
+  }
+
+  /**
+   * Generate random data historgram report.
+   *
+   * @param entries the entries
+   * @param countsComparator the counts comparator
+   * @param label the label
+   * @return the string
+   */
+  public String print(List<Tracker> entries, boolean countsComparator, String label) {
+    entries.sort(
+        countsComparator
+            ? (left, right) -> {
+              try {
+                Integer leftFirst = left.count();
+                Integer rightFirst = right.count();
+                return rightFirst.compareTo(leftFirst);
+              } catch (ClassCastException castException) {
+                return -Integer.compare(left.count(), right.count());
+              }
+            }
+            : comparator());
+    if (bucketCnt > -1) {
+      return print(label, bucket(bucketCnt, entries), countsComparator);
+    } else {
+      List<Bucket> buckets = new ArrayList<>(32);
+      for (Tracker entry : entries) {
+        Bucket bucket = new Bucket(label(entry), entry.count());
+        buckets.add(bucket);
+      }
+      return print(label, buckets, countsComparator);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private Comparator<? super Tracker> comparator() {
+    if (bucketCnt > -1) {
+      return (left, right) -> 0;
+    }
+    return (left, right) -> {
+      try {
+        Comparable<Object> leftFirst = (Comparable<Object>) left.values().get(0);
+        Comparable<Object> rightFirst = (Comparable<Object>) right.values().get(0);
+        return leftFirst.compareTo(rightFirst);
+      } catch (ClassCastException castException) {
+        return -Integer.compare(left.count(), right.count());
+      }
+    };
+  }
+
+  /** Returns the label of the tracker. */
+  private String label(final Tracker tracker) {
+    if (bucketCnt > -1) {
+      return "<--->";
+    }
+    return tracker.name();
+  }
+
+  /**
+   * Bucket list.
+   *
+   * @param bucketCnt the bucket cnt
+   * @param entries the list of Trackers maintaining the counts
+   * @return the list
+   */
+  protected static List<Bucket> bucket(int bucketCnt, final List<Tracker> entries) {
+    Pair<BigInteger, BigInteger> minMax = minMax(entries);
+    BigInteger min = minMax._1;
+    BigInteger max = minMax._2;
+
+    List<Pair<BigInteger, Bucket>> topsAndBuckets = bucketsAndMax(bucketCnt, min, max);
+
+    for (Tracker entry : entries) {
+      Bucket bucket = bucket(topsAndBuckets, new BigDecimal(entry.values().get(0).toString()));
+      bucket.addCount(entry.count());
+    }
+
+    return topsAndBuckets.stream()
+        .map(bigIntegerBucketPair -> bigIntegerBucketPair._2)
+        .collect(Collectors.toList());
+  }
+
+  private String print(String label, final List<Bucket> buckets, boolean countComparator) {
+    int labelWidth =
+        Math.max(
+            label.length(),
+            buckets.stream().mapToInt(bucket2 -> bucket2.label.length()).max().orElse(0));
+    int maxCnt = buckets.stream().mapToInt(bucket1 -> bucket1.count).max().orElse(0);
+    int cntWidth = Math.max(COUNT.length(), (int) Math.max(1, Math.floor(Math.log10(maxCnt)) + 1));
+
+    StringBuilder report = new StringBuilder(32);
+    String header =
+        "%1$4s "
+            + VERTICAL_BAR
+            + " %2$"
+            + labelWidth
+            + "s "
+            + VERTICAL_BAR
+            + " %3$"
+            + cntWidth
+            + "s "
+            + VERTICAL_BAR
+            + " %4$s";
+    String bucket =
+        "%1$4s "
+            + VERTICAL_BAR
+            + " %2$"
+            + labelWidth
+            + "s "
+            + VERTICAL_BAR
+            + " %3$"
+            + cntWidth
+            + "d "
+            + VERTICAL_BAR
+            + " %4$s";
+
+    report.append("\n\n").append(this.getClass().getSimpleName()).append("\n\n");
+    report.append(String.format(Locale.ROOT, header, "", label, COUNT, "")).append('\n');
+    String barRuler = String.valueOf('―').repeat(Math.max(0, maxCnt + 10));
+    report
+        .append(String.format(Locale.ROOT, header, "", "", "", barRuler).replace(" ", "―"))
+        .append('\n');
+
+    int size = buckets.size();
+
+    for (int i = 0; i < size; i++) {
+      report
+          .append(
+              String.format(
+                  Locale.ROOT,
+                  bucket,
+                  i,
+                  buckets.get(i).label,
+                  buckets.get(i).count,
+                  String.valueOf(HISTO_DOT)
+                      .repeat(
+                          Math.max(
+                              0,
+                              (int)
+                                  (buckets.get(i).count
+                                      / Math.max(1.0, maxCnt / (double) MAX_WIDTH))))))
+          .append('\n');
+    }
+    report.append('\n');
+    return report.toString();
+  }
+
+  private static RandomDataHistogram.Bucket bucket(
+      List<Pair<BigInteger, RandomDataHistogram.Bucket>> bucketsAndMax, BigDecimal value) {
+    int size = bucketsAndMax.size();
+    for (int i = 0; i < size; i++) {
+      Pair<BigInteger, RandomDataHistogram.Bucket> bucketAndMax = bucketsAndMax.get(i);
+      BigInteger top = bucketAndMax._1;
+      if (value.compareTo(new BigDecimal(top)) < 0) {
+        return bucketAndMax._2;
+      }
+      if (i == size - 1) {
+        return bucketAndMax._2;
+      }
+    }
+    throw new IllegalArgumentException("No bucket found for value: " + value);
+  }
+
+  private static List<Pair<BigInteger, RandomDataHistogram.Bucket>> bucketsAndMax(
+      int bucketCnt, final BigInteger min, final BigInteger max) {
+    BigInteger range = max.subtract(min);
+    BigInteger numberOfBuckets = BigInteger.valueOf(bucketCnt);
+    BigInteger step = range.divide(numberOfBuckets);
+    BigInteger remainder = range.remainder(numberOfBuckets);
+    if (remainder.compareTo(BigInteger.ZERO) != 0) {
+      step = step.add(BigInteger.ONE);
+    }
+
+    List<Pair<BigInteger, RandomDataHistogram.Bucket>> bucketsAndMax = new ArrayList<>(32);
+    BigInteger left = min;
+    for (BigInteger index = min.add(step); index.compareTo(max) < 0; index = index.add(step)) {
+      String label = String.format(Locale.ROOT, "[%s..%s", left, index) + '[';
+      bucketsAndMax.add(Pair.of(index, new RandomDataHistogram.Bucket(label)));
+      left = index;
+    }
+    String label = String.format(Locale.ROOT, "[%s..%s", left, max) + ']';
+    bucketsAndMax.add(Pair.of(max, new RandomDataHistogram.Bucket(label)));
+    return bucketsAndMax;
+  }
+
+  private static Pair<BigInteger, BigInteger> minMax(final List<Tracker> entries) {
+    BigDecimal min = null;
+    BigDecimal max = null;
+    for (Tracker entry : entries) {
+      try {
+        BigDecimal value = new BigDecimal(entry.values().get(0).toString());
+        if (min == null || value.compareTo(min) < 0) {
+          min = value;
+        }
+        if (max == null || value.compareTo(max) > 0) {
+          max = value;
+        }
+      } catch (NumberFormatException e) {
+        throw new ParseNumberException("Could not parse " + entry.values().get(0), e);
+      }
+    }
+    if (max == null) max = new BigDecimal(0);
+    if (min == null) min = new BigDecimal(0);
+    // TODO: rounds to 4 decimals but leaves tons of trailing 0's
+    // max = max.round(MATH_CONTEXT);
+    // max = max.round(MATH_CONTEXT);
+    BigInteger maxBigInteger = max.setScale(0, RoundingMode.UP).toBigInteger();
+    return Pair.of(min.toBigInteger(), maxBigInteger);
+  }
+
+  /** The type Bucket. */
+  public static class Bucket {
+    private final String label;
+    private int count;
+
+    /**
+     * Instantiates a new Bucket.
+     *
+     * @param label the label
+     */
+    public Bucket(String label) {
+      this(label, 0);
+    }
+
+    /**
+     * Instantiates a new Bucket.
+     *
+     * @param label the label
+     * @param count the count
+     */
+    public Bucket(String label, final int count) {
+      Objects.requireNonNull(label);
+      this.label = label;
+      this.count = count;
+    }
+
+    /**
+     * Add count.
+     *
+     * @param count the count
+     */
+    void addCount(int count) {
+      this.count += count;
+    }
+  }
+
+  /** The type Counts. */
+  public static class Counts {
+
+    /** The constant ITEMS_ONLY_PRINTING. */
+    public static final String ITEMS_ONLY_PRINTING = " items, only printing ";
+
+    /** The constant THERE_ARE_OVER. */
+    public static final String THERE_ARE_OVER = "\n\nThere are over ";
+
+    /** The constant ELIPSE. */
+    public static final String ELIPSE = " ... ";
+
+    private final Map<List<Surrogate>, AtomicInteger> objectToCount = new ConcurrentHashMap<>(64);
+    private final String label;
+    private List<Tracker> entries = null;
+    private volatile boolean limitReached;
+
+    private final boolean bucketed;
+
+    /**
+     * Instantiates a new Counts.
+     *
+     * @param label the label
+     * @param bucketed the bucketed
+     */
+    public Counts(String label, boolean bucketed) {
+      this.label = label;
+      this.bucketed = bucketed;
+    }
+
+    /**
+     * Collect counts.
+     *
+     * @param values the values
+     * @return the counts
+     */
+    public Counts collect(Object... values) {
+      if (values.length == 0) {
+        throw new IllegalArgumentException("values length must be greater than 0: " + label);
+      }
+
+      if (objectToCount.size() > MAX_ITEMS_TO_PRINT) {
+        limitReached = true;
+        return this;
+      }
+
+      List<Surrogate> key;
+      key = surrogates(values);
+
+      AtomicInteger count = objectToCount.computeIfAbsent(key, any -> new AtomicInteger(0));
+      count.incrementAndGet();
+      entries = null;
+      return this;
+    }
+
+    private List<Surrogate> surrogates(Object[] values) {
+      List<Surrogate> surrogates = new ArrayList<>(values.length);
+      for (Object value : values) {
+        String toString = value.toString();
+        surrogates.add(
+            new Surrogate(
+                value.hashCode(),
+                System.identityHashCode(value),
+                toString.length() > MAX_LABEL_LENGTH
+                    ? toString.substring(0, MAX_LABEL_LENGTH) + " ..."
+                    : toString));
+      }
+      return surrogates;
+    }
+
+    /** The type Surrogate. */
+    static class Surrogate {
+      /** The Hash code. */
+      final Integer hashCode;
+
+      /** The Identity hashcode. */
+      final Integer identityHashcode;
+
+      /** The Value. */
+      final String value;
+
+      @Override
+      public String toString() {
+        return value;
+      }
+
+      /**
+       * Instantiates a new Surrogate.
+       *
+       * @param hash the hash
+       * @param identity the identity
+       * @param displayValue the display value
+       */
+      Surrogate(Integer hash, Integer identity, String displayValue) {
+        this.hashCode = hash;
+        this.identityHashcode = identity;
+        this.value = displayValue;
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Surrogate surrogate = (Surrogate) o;
+        return hashCode.equals(surrogate.hashCode)
+            && identityHashcode.equals(surrogate.identityHashcode);
+      }
+
+      @Override
+      public int hashCode() {
+
+        if (hashCode == null && identityHashcode == null) {
+          return 0;
+        }
+
+        if (hashCode == null) {
+          return 0;
+        }
+
+        int result = 1;
+
+        result = 31 * result + hashCode.hashCode();
+
+        result = 31 * result + (identityHashcode == null ? 0 : identityHashcode.hashCode());
+
+        return result;
+      }
+    }
+
+    /** Print a string representation of the random data histogram. @return the string */
+    public String print() {
+      return print(true, !this.bucketed ? -1 : 15);
+    }
+
+    /**
+     * Print a string representation of the random data histogram.
+     *
+     * @param bucketed -1 for no bucketing or the number of buckets
+     * @return the string
+     */
+    public String print(int bucketed) {
+      return print(true, bucketed);
+    }
+
+    /**
+     * Print string.
+     *
+     * @param sortByLabel the sort by label
+     * @param bucketed the bucketed
+     * @return the string
+     */
+    public String print(boolean sortByLabel, int bucketed) {
+      String msg = "";
+      if (entries == null) {
+        Collection<AtomicInteger> values = objectToCount.values();
+        if (limitReached) {
+          msg =
+              THERE_ARE_OVER
+                  + MAX_ITEMS_TO_PRINT
+                  + ITEMS_ONLY_PRINTING
+                  + MAX_ITEMS_TO_PRINT
+                  + ELIPSE;
+        }
+        int sum = values.stream().mapToInt(AtomicInteger::get).sum();
+        entries =
+            objectToCount.entrySet().stream()
+                .sorted(
+                    (e1, e2) -> {
+                      List<Surrogate> k1 = e1.getKey();
+                      List<Surrogate> k2 = e2.getKey();
+
+                      if (k1.size() != k2.size()) {
+                        return Integer.compare(k1.size(), k2.size());
+                      }
+                      return Integer.compare(e2.getValue().get(), e1.getValue().get());
+                    })
+                .filter(entry -> !entry.getKey().equals(Collections.emptyList()))
+                .map(
+                    entry -> {
+                      double percentage = entry.getValue().get() * 100.0 / sum;
+                      return new Tracker(
+                          entry.getKey(),
+                          entry.getKey().stream()
+                              .map(Objects::toString)
+                              .collect(Collectors.joining(" ")),
+                          entry.getValue().get(),
+                          percentage);
+                    })
+                .collect(Collectors.toList());
+      }
+
+      RandomDataHistogram randomDataHistogram = new RandomDataHistogram(bucketed);
+      return msg + '\n' + randomDataHistogram.print(entries, sortByLabel, label);
+    }
+  }
+
+  private static class ParseNumberException extends RuntimeException {
+    /**
+     * Instantiates a new Parse number exception.
+     *
+     * @param s the s
+     * @param e the e
+     */
+    public ParseNumberException(String s, NumberFormatException e) {
+      super(s, e);
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/SolrGen.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/SolrGen.java
new file mode 100644
index 0000000..1661283
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/SolrGen.java
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.jctools.maps.NonBlockingHashMap;
+import org.quicktheories.api.AsString;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+import org.quicktheories.impl.BenchmarkRandomSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The type Solr gen.
+ *
+ * @param <T> the type parameter
+ */
+public class SolrGen<T> implements Gen<T> {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /** The constant OPEN_PAREN. */
+  public static final String OPEN_PAREN = " (";
+
+  /** The constant CLOSE_PAREN. */
+  public static final char CLOSE_PAREN = ')';
+
+  /** The constant COUNT_TYPES_ARE_TRACKED_LIMIT_WAS_REACHED. */
+  public static final String COUNT_TYPES_ARE_TRACKED_LIMIT_WAS_REACHED =
+      " Count types are tracked, limit was reached.\n\n";
+
+  /** The constant RANDOM_DATA_GEN_REPORTS. */
+  public static final String RANDOM_DATA_GEN_REPORTS =
+      "\n\n\n*****  Random Data Gen Reports *****\n\n";
+
+  /** The constant ONLY. */
+  public static final String ONLY = "\n\nOnly ";
+
+  /** The Child. */
+  protected final Gen<T> child;
+
+  private final Class<?> type;
+  private Distribution distribution = Distribution.UNIFORM;
+
+  /** The Start. */
+  protected long start;
+
+  /** The End. */
+  protected long end;
+
+  private static final boolean COLLECT_COUNTS = Boolean.getBoolean("random.counts");
+
+  static {
+    log.info("random.counts is set to {}", COLLECT_COUNTS);
+  }
+
+  private String description;
+
+  private String collectKey;
+  /** The constant COUNTS. */
+  public static final Map<String, RandomDataHistogram.Counts> COUNTS = new NonBlockingHashMap<>(64);
+
+  /**
+   * Counts report list.
+   *
+   * @return the list
+   */
+  public static List<String> countsReport() {
+    List<String> reports = new ArrayList<>(COUNTS.size());
+    reports.add(RANDOM_DATA_GEN_REPORTS);
+    if (COUNTS.size() >= RandomDataHistogram.MAX_TYPES_TO_COLLECT) {
+      reports.add(
+          ONLY
+              + RandomDataHistogram.MAX_TYPES_TO_COLLECT
+              + COUNT_TYPES_ARE_TRACKED_LIMIT_WAS_REACHED);
+    }
+    COUNTS.forEach(
+        (k, v) -> {
+          reports.add(v.print());
+        });
+    return reports;
+  }
+
+  /** Instantiates a new Solr gen. */
+  protected SolrGen() {
+    child = null;
+    this.type = null;
+  }
+
+  /**
+   * Instantiates a new Solr gen.
+   *
+   * @param child the child
+   * @param type the type
+   */
+  public SolrGen(Gen<T> child, Class<?> type) {
+    this.child = child;
+    this.type = type;
+    if (type != null) {
+      this.description = type.getSimpleName();
+    } else if (child instanceof SolrGen) {
+      this.description = ((SolrGen<?>) child).description;
+    }
+    if (COLLECT_COUNTS && description != null) {
+      this.collectKey = description + OPEN_PAREN + distribution + CLOSE_PAREN;
+    }
+  }
+
+  /**
+   * Instantiates a new Solr gen.
+   *
+   * @param type the type
+   */
+  SolrGen(Class<?> type) {
+    child = null;
+    this.type = type;
+    this.description = type.getSimpleName();
+    if (COLLECT_COUNTS) {
+      this.collectKey = description + OPEN_PAREN + distribution + CLOSE_PAREN;
+    }
+  }
+
+  /**
+   * Instantiates a new Solr gen.
+   *
+   * @param child the child
+   */
+  protected SolrGen(Gen<T> child) {
+    this.child = child;
+    this.type = null;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public T generate(RandomnessSource in) {
+    T val;
+    if (child == null && start == end) {
+      val = generate((SolrRandomnessSource) in);
+      processCounts(val);
+      return val;
+    }
+
+    if (child == null) {
+      val = (T) Integer.valueOf((int) ((SolrRandomnessSource) in).next(start, end));
+      processCounts(val);
+      return val;
+    }
+
+    if (child instanceof SolrGen && in instanceof SolrRandomnessSource) {
+      val =
+          (T)
+              ((SolrGen<Object>) child)
+                  .generate(((SolrRandomnessSource) in).withDistribution(distribution));
+      processCounts(val);
+      return val;
+    }
+
+    if (in instanceof BenchmarkRandomSource) {
+      val = child.generate(((BenchmarkRandomSource) in).withDistribution(distribution));
+      processCounts(val);
+      return val;
+    }
+    val = child.generate(in);
+    processCounts(val);
+    return val;
+  }
+
+  private void processCounts(T val) {
+    if (COLLECT_COUNTS) {
+
+      if (collectKey == null || COUNTS.size() > RandomDataHistogram.MAX_TYPES_TO_COLLECT) {
+        return;
+      }
+
+      // System.out.println("Add key " + key);
+      RandomDataHistogram.Counts newCounts = null;
+      RandomDataHistogram.Counts counts;
+      counts = COUNTS.get(collectKey);
+
+      if (counts == null) {
+        newCounts =
+            new RandomDataHistogram.Counts(
+                collectKey,
+                type != null
+                    && Number.class.isAssignableFrom(type)
+                    && (type != Double.class && type != Float.class));
+        counts = COUNTS.putIfAbsent(collectKey, newCounts);
+      }
+
+      if (counts == null) {
+        newCounts.collect(val);
+      } else {
+        counts.collect(val);
+      }
+    }
+  }
+
+  /**
+   * Generate t.
+   *
+   * @param in the in
+   * @return the t
+   */
+  @SuppressWarnings("unchecked")
+  public T generate(SolrRandomnessSource in) {
+    if (child == null && start == end) {
+      T val = generate((RandomnessSource) in);
+      processCounts(val);
+      return val;
+    }
+
+    if (child == null) {
+      T val = (T) Integer.valueOf((int) in.next(start, end));
+      processCounts(val);
+      return val;
+    }
+
+    T val = child.generate(in);
+    processCounts(val);
+    return val;
+  }
+
+  /**
+   * Type class.
+   *
+   * @return the class
+   */
+  @SuppressWarnings("unchecked")
+  public Class<?> type() {
+    SolrGen<?> other = this;
+    while (true) {
+      if (other.type == null && other.child instanceof SolrGen) {
+        other = ((SolrGen<Object>) other.child);
+        continue;
+      }
+      return other.type;
+    }
+  }
+
+  /**
+   * Described as solr gen.
+   *
+   * @param description the description
+   * @return the solr gen
+   */
+  public SolrGen<T> describedAs(String description) {
+    this.description = description;
+    if (COLLECT_COUNTS) {
+      this.collectKey = description + OPEN_PAREN + distribution + CLOSE_PAREN;
+    }
+    return this;
+  }
+
+  @Override
+  public SolrGen<T> describedAs(AsString<T> asString) {
+    return new SolrDescribingGenerator<>(this, asString);
+  }
+
+  /**
+   * Mix solr gen.
+   *
+   * @param rhs the rhs
+   * @param type the type
+   * @return the solr gen
+   */
+  public SolrGen<T> mix(Gen<T> rhs, Class<?> type) {
+    return mix(rhs, 50, type);
+  }
+
+  @Override
+  public Gen<T> mix(Gen<T> rhs, int weight) {
+    return mix(rhs, weight, null);
+  }
+
+  /**
+   * Mix solr gen.
+   *
+   * @param rhs the rhs
+   * @param weight the weight
+   * @param type the type
+   * @return the solr gen
+   */
+  public SolrGen<T> mix(Gen<T> rhs, int weight, Class<?> type) {
+    return new SolrGen<>(type) {
+      @Override
+      public T generate(SolrRandomnessSource in) {
+        while (true) {
+          long picked = in.next(0, 99);
+          if (picked >= weight) {
+            continue;
+          }
+          return ((SolrGen<T>) rhs).generate(in);
+        }
+      }
+    };
+  }
+
+  /**
+   * Flat maps generated values with supplied function
+   *
+   * @param mapper function to map with
+   * @param <R> Type to map to
+   * @return A Gen of R
+   */
+  @Override
+  public <R> Gen<R> flatMap(Function<? super T, Gen<? extends R>> mapper) {
+    return new SolrGen<>(in -> mapper.apply(generate(in)).generate(in), null);
+  }
+
+  @Override
+  public <R> Gen<R> map(Function<? super T, ? extends R> mapper) {
+    return map(mapper, null);
+  }
+
+  /**
+   * Map solr gen.
+   *
+   * @param <R> the type parameter
+   * @param mapper the mapper
+   * @param type the type
+   * @return the solr gen
+   */
+  public <R> SolrGen<R> map(Function<? super T, ? extends R> mapper, Class<?> type) {
+    return new SolrGen<>(in -> mapper.apply(generate(in)), type);
+  }
+
+  /**
+   * With distribution solr gen.
+   *
+   * @param distribution the distribution
+   * @return the solr gen
+   */
+  public SolrGen<T> withDistribution(Distribution distribution) {
+    this.distribution = distribution;
+    if (this.child instanceof SolrGen) {
+      ((SolrGen<?>) this.child).distribution = distribution;
+    }
+    if (COLLECT_COUNTS) {
+      this.collectKey = description + OPEN_PAREN + distribution + CLOSE_PAREN;
+    }
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return "SolrGen{"
+        + "child="
+        + child
+        + ", type="
+        + type
+        + ", distribution="
+        + distribution
+        + '}';
+  }
+
+  /**
+   * Gets distribution.
+   *
+   * @return the distribution
+   */
+  protected Distribution getDistribution() {
+    return this.distribution;
+  }
+}
+
+/**
+ * The type Solr describing generator.
+ *
+ * @param <G> the type parameter
+ */
+class SolrDescribingGenerator<G> extends SolrGen<G> {
+
+  private final AsString<G> toString;
+
+  /**
+   * Instantiates a new Solr describing generator.
+   *
+   * @param child the child
+   * @param toString the to string
+   */
+  public SolrDescribingGenerator(Gen<G> child, AsString<G> toString) {
+    super(child);
+    this.toString = toString;
+  }
+
+  @Override
+  public G generate(RandomnessSource in) {
+    G val;
+    val = Objects.requireNonNull(child).generate(in);
+    return val;
+  }
+
+  @Override
+  public G generate(SolrRandomnessSource in) {
+    G val;
+    val = Objects.requireNonNull(child).generate(in);
+    return val;
+  }
+
+  @Override
+  public String asString(G t) {
+    return toString.asString(t);
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/SourceDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/SourceDSL.java
new file mode 100644
index 0000000..9b54991
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/SourceDSL.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.Locale;
+import org.quicktheories.generators.ArbitraryDSL;
+import org.quicktheories.generators.BigDecimalsDSL;
+import org.quicktheories.generators.BigIntegersDSL;
+import org.quicktheories.generators.CharactersDSL;
+import org.quicktheories.generators.LocalDatesDSL;
+
+/** The type Source dsl. */
+public class SourceDSL {
+
+  private SourceDSL() {}
+
+  /**
+   * Longs longs dsl.
+   *
+   * @return the longs dsl
+   */
+  public static LongsDSL longs() {
+    return new LongsDSL();
+  }
+
+  /**
+   * Integers integers dsl.
+   *
+   * @return the integers dsl
+   */
+  public static IntegersDSL integers() {
+    return new IntegersDSL();
+  }
+
+  /**
+   * Doubles doubles dsl.
+   *
+   * @return the doubles dsl
+   */
+  public static DoublesDSL doubles() {
+    return new DoublesDSL();
+  }
+
+  /**
+   * Floats floats dsl.
+   *
+   * @return the floats dsl
+   */
+  public static FloatsDSL floats() {
+    return new FloatsDSL();
+  }
+
+  /**
+   * Characters characters dsl.
+   *
+   * @return the characters dsl
+   */
+  public static CharactersDSL characters() {
+    return new CharactersDSL();
+  }
+
+  /**
+   * Strings strings dsl.
+   *
+   * @return the strings dsl
+   */
+  public static StringsDSL strings() {
+    return new StringsDSL();
+  }
+
+  /**
+   * Lists lists dsl.
+   *
+   * @return the lists dsl
+   */
+  public static ListsDSL lists() {
+    return new ListsDSL();
+  }
+
+  /**
+   * Maps maps dsl.
+   *
+   * @return the maps dsl
+   */
+  public static MapsDSL maps() {
+    return new MapsDSL();
+  }
+
+  /**
+   * Arrays arrays dsl.
+   *
+   * @return the arrays dsl
+   */
+  public static ArraysDSL arrays() {
+    return new ArraysDSL();
+  }
+
+  /**
+   * Big integers big integers dsl.
+   *
+   * @return the big integers dsl
+   */
+  public static BigIntegersDSL bigIntegers() {
+    return new BigIntegersDSL();
+  }
+
+  /**
+   * Big decimals big decimals dsl.
+   *
+   * @return the big decimals dsl
+   */
+  public static BigDecimalsDSL bigDecimals() {
+    return new BigDecimalsDSL();
+  }
+
+  /**
+   * Arbitrary arbitrary dsl.
+   *
+   * @return the arbitrary dsl
+   */
+  public static ArbitraryDSL arbitrary() {
+    return new ArbitraryDSL();
+  }
+
+  /**
+   * Dates dates dsl.
+   *
+   * @return the dates dsl
+   */
+  public static DatesDSL dates() {
+    return new DatesDSL();
+  }
+
+  /**
+   * Local dates local dates dsl.
+   *
+   * @return the local dates dsl
+   */
+  public static LocalDatesDSL localDates() {
+    return new LocalDatesDSL();
+  }
+
+  /**
+   * Booleans booleans dsl.
+   *
+   * @return the booleans dsl
+   */
+  public static BooleansDSL booleans() {
+    return new BooleansDSL();
+  }
+
+  /**
+   * Check arguments.
+   *
+   * @param expression the expression
+   * @param errorMessageTemplate the error message template
+   * @param errorMessageArgs the error message args
+   */
+  public static void checkArguments(
+      boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
+    if (!expression) {
+      throw new IllegalArgumentException(
+          String.format(Locale.ENGLISH, errorMessageTemplate, errorMessageArgs));
+    }
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Strings.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Strings.java
new file mode 100644
index 0000000..779741e
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Strings.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.function.Function;
+import org.apache.solr.bench.SolrGenerate;
+
+/** The type Strings. */
+final class Strings {
+
+  /** The constant BOUNDED_NUMERIC_STRING. */
+  public static final String BOUNDED_NUMERIC_STRING = "Bounded Numeric String";
+
+  /** The constant POINTS_FROM_CODE_POINT_RANGE. */
+  public static final String POINTS_FROM_CODE_POINT_RANGE = "PointsFromCodePointRange";
+
+  /** The constant BOUNDED_LENGTH_STRING. */
+  public static final String BOUNDED_LENGTH_STRING = "Bounded Length String";
+
+  private Strings() {}
+
+  /**
+   * Bounded numeric strings solr gen.
+   *
+   * @param startInclusive the start inclusive
+   * @param endInclusive the end inclusive
+   * @return the solr gen
+   */
+  static SolrGen<String> boundedNumericStrings(int startInclusive, int endInclusive) {
+    return new SolrGen<>(
+            SolrGenerate.range(startInclusive, endInclusive).map(Object::toString), String.class)
+        .describedAs(BOUNDED_NUMERIC_STRING);
+  }
+
+  /**
+   * With code points solr gen.
+   *
+   * @param minCodePoint the min code point
+   * @param maxCodePoint the max code point
+   * @param numberOfCodePoints the number of code points
+   * @return the solr gen
+   */
+  static SolrGen<String> withCodePoints(
+      int minCodePoint, int maxCodePoint, SolrGen<Integer> numberOfCodePoints) {
+
+    return new SolrGen<>(
+            SolrGenerate.intArrays(
+                    numberOfCodePoints, SolrGenerate.codePoints(minCodePoint, maxCodePoint))
+                .map(is -> new String(is, 0, is.length)),
+            String.class)
+        .describedAs(POINTS_FROM_CODE_POINT_RANGE);
+  }
+
+  /**
+   * Of bounded length strings solr gen.
+   *
+   * @param minCodePoint the min code point
+   * @param maxCodePoint the max code point
+   * @param minLength the min length
+   * @param maxLength the max length
+   * @return the solr gen
+   */
+  static SolrGen<String> ofBoundedLengthStrings(
+      int minCodePoint, int maxCodePoint, int minLength, int maxLength) {
+
+    // generate strings of fixed number of code points then modify any that exceed max length
+    return new SolrGen<>(
+            withCodePoints(minCodePoint, maxCodePoint, SolrGenerate.range(minLength, maxLength))
+                .map(reduceToSize(maxLength)),
+            String.class)
+        .describedAs(BOUNDED_LENGTH_STRING);
+  }
+
+  private static Function<String, String> reduceToSize(int maxLength) {
+    // Reduce size of string by removing characters from start
+    return s -> {
+      if (s.length() <= maxLength) {
+        return s;
+      }
+      String t = s;
+      while (true) {
+        final int length = t.length();
+        if (!(length > maxLength)) break;
+        t = t.substring(1);
+      }
+      return t;
+    };
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/StringsDSL.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/StringsDSL.java
new file mode 100644
index 0000000..33d573c
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/StringsDSL.java
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import static org.apache.solr.bench.generators.SourceDSL.checkArguments;
+import static org.apache.solr.bench.generators.SourceDSL.integers;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Scanner;
+import java.util.SplittableRandom;
+import org.apache.solr.bench.BaseBenchState;
+import org.apache.solr.bench.SolrGenerate;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+import org.quicktheories.impl.SplittableRandomSource;
+
+/** The type Strings dsl. */
+public class StringsDSL {
+
+  private static final int BASIC_LATIN_LAST_CODEPOINT = 0x007E;
+  private static final int BASIC_LATIN_FIRST_CODEPOINT = 0x0020;
+  private static final int ASCII_LAST_CODEPOINT = 0x007F;
+  private static final int LARGEST_DEFINED_BMP_CODEPOINT = 65533;
+
+  private static final List<String> words;
+
+  private static final int WORD_SIZE;
+
+  static {
+    // english word list via https://github.com/dwyl/english-words
+
+    words = new ArrayList<>(1000);
+    InputStream inputStream = StringsDSL.class.getClassLoader().getResourceAsStream("words.txt");
+    try (Scanner scanner =
+        new Scanner(Objects.requireNonNull(inputStream), StandardCharsets.UTF_8.name())) {
+      while (scanner.hasNextLine()) {
+        words.add(scanner.nextLine());
+      }
+    }
+    Collections.shuffle(words, new Random(BaseBenchState.getRandomSeed()));
+    WORD_SIZE = words.size();
+  }
+
+  /**
+   * Word list word list generator builder.
+   *
+   * @return the word list generator builder
+   */
+  public WordListGeneratorBuilder wordList() {
+    return new WordListGeneratorBuilder(
+        new SolrGen<>(new WordListStringSolrGen(), String.class).describedAs("WordList Word"));
+  }
+
+  /**
+   * Generates integers as Strings, and shrinks towards "0".
+   *
+   * @return a Source of type String
+   */
+  public SolrGen<String> numeric() {
+    return new SolrGen<>(numericBetween(Integer.MIN_VALUE, Integer.MAX_VALUE), String.class);
+  }
+
+  /**
+   * Generates integers within the interval as Strings.
+   *
+   * @param startInclusive - lower inclusive bound of integer domain
+   * @param endInclusive - upper inclusive bound of integer domain
+   * @return a Source of type String
+   */
+  public SolrGen<String> numericBetween(int startInclusive, int endInclusive) {
+    checkArguments(
+        startInclusive <= endInclusive,
+        "There are no Integer values to be generated between startInclusive (%s) and endInclusive (%s)",
+        startInclusive,
+        endInclusive);
+    return new SolrGen<>(Strings.boundedNumericStrings(startInclusive, endInclusive), String.class);
+  }
+
+  /**
+   * Constructs a StringGeneratorBuilder which will build Strings composed of all defined code
+   * points
+   *
+   * @return a StringGeneratorBuilder
+   */
+  public StringGeneratorBuilder allPossible() {
+    return betweenCodePoints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT);
+  }
+
+  /**
+   * Realistic unicode realistic unicode generator builder.
+   *
+   * @param minLength the min length
+   * @param maxLength the max length
+   * @return the realistic unicode generator builder
+   */
+  public RealisticUnicodeGeneratorBuilder realisticUnicode(int minLength, int maxLength) {
+    return new RealisticUnicodeGeneratorBuilder(
+        new SolrGen<>() {
+          @Override
+          public String generate(SolrRandomnessSource in) {
+
+            int block =
+                integers()
+                    .between(0, blockStarts.length - 1)
+                    .describedAs("Realistic Unicode BLock Index")
+                    .generate(in);
+
+            return Strings.ofBoundedLengthStrings(
+                    blockStarts[block], blockEnds[block], minLength, maxLength)
+                .describedAs("Realistic Unicode")
+                .generate(in);
+          }
+        });
+  }
+
+  /**
+   * Constructs a StringGeneratorBuilder which will build Strings composed of all defined code
+   * points in the Basic Multilingual Plane
+   *
+   * @return a StringGeneratorBuilder
+   */
+  public StringGeneratorBuilder basicMultilingualPlaneAlphabet() {
+    return betweenCodePoints(Character.MIN_CODE_POINT, LARGEST_DEFINED_BMP_CODEPOINT);
+  }
+
+  /**
+   * Constructs a StringGeneratorBuilder which will build Strings composed of Unicode Basic Latin
+   * Alphabet
+   *
+   * @return a StringGeneratorBuilder
+   */
+  public StringGeneratorBuilder basicLatinAlphabet() {
+    return betweenCodePoints(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT);
+  }
+
+  /**
+   * Alpha string generator builder.
+   *
+   * @return the string generator builder
+   */
+  public StringGeneratorBuilder alpha() {
+    return betweenCodePoints('a', 'z' + 1);
+  }
+
+  /**
+   * Alpha numeric string generator builder.
+   *
+   * @return the string generator builder
+   */
+  public StringGeneratorBuilder alphaNumeric() {
+    return betweenCodePoints(' ', 'z' + 1);
+  }
+
+  /**
+   * Constructs a StringGeneratorBuilder which will build Strings composed of Unicode Ascii Alphabet
+   *
+   * @return a StringGeneratorBuilder
+   */
+  public StringGeneratorBuilder ascii() {
+    return betweenCodePoints(Character.MIN_CODE_POINT, ASCII_LAST_CODEPOINT);
+  }
+
+  /**
+   * Strings with characters between two (inclusive) code points
+   *
+   * @param minInclusive minimum code point
+   * @param maxInclusive max code point
+   * @return Builder for strings
+   */
+  public StringGeneratorBuilder betweenCodePoints(int minInclusive, int maxInclusive) {
+    return new StringGeneratorBuilder(minInclusive, maxInclusive);
+  }
+
+  /** The type Word list generator builder. */
+  public static class WordListGeneratorBuilder {
+    private final SolrGen<String> strings;
+
+    /**
+     * Instantiates a new Word list generator builder.
+     *
+     * @param strings the strings
+     */
+    WordListGeneratorBuilder(SolrGen<String> strings) {
+      this.strings = strings;
+    }
+
+    /**
+     * Of one solr gen.
+     *
+     * @return the solr gen
+     */
+    public SolrGen<String> ofOne() {
+      return strings;
+    }
+
+    /**
+     * Multi solr gen.
+     *
+     * @param count the count
+     * @return the solr gen
+     */
+    public SolrGen<String> multi(int count) {
+      return multiStringGen(strings, count);
+    }
+
+    /**
+     * With distribution word list generator builder.
+     *
+     * @param distribution the distribution
+     * @return the word list generator builder
+     */
+    public WordListGeneratorBuilder withDistribution(Distribution distribution) {
+      this.strings.withDistribution(distribution);
+      return this;
+    }
+  }
+
+  /** The type Realistic unicode generator builder. */
+  public static class RealisticUnicodeGeneratorBuilder {
+    private final SolrGen<String> strings;
+
+    /**
+     * Instantiates a new Realistic unicode generator builder.
+     *
+     * @param strings the strings
+     */
+    RealisticUnicodeGeneratorBuilder(SolrGen<String> strings) {
+      this.strings = strings;
+    }
+
+    /**
+     * Of one solr gen.
+     *
+     * @return the solr gen
+     */
+    public SolrGen<String> ofOne() {
+      return strings;
+    }
+
+    /**
+     * Multi solr gen.
+     *
+     * @param count the count
+     * @return the solr gen
+     */
+    public SolrGen<String> multi(int count) {
+      return multiStringGen(strings, count);
+    }
+  }
+
+  /** The type String generator builder. */
+  public static class StringGeneratorBuilder {
+
+    private final int minCodePoint;
+    private final int maxCodePoint;
+    private Integer cardinalityStart;
+    private Gen<Integer> maxCardinality;
+    private int multi;
+
+    private StringGeneratorBuilder(int minCodePoint, int maxCodePoint) {
+      this.minCodePoint = minCodePoint;
+      this.maxCodePoint = maxCodePoint;
+    }
+
+    /**
+     * Generates Strings of a fixed number of code points.
+     *
+     * @param codePoints - the fixed number of code points for the String
+     * @return a a Source of type String
+     */
+    public SolrGen<String> ofFixedNumberOfCodePoints(int codePoints) {
+      checkArguments(
+          codePoints >= 0,
+          "The number of codepoints cannot be negative; %s is not an accepted argument",
+          codePoints);
+      return new SolrGen<>(
+          Strings.withCodePoints(minCodePoint, maxCodePoint, SolrGenerate.constant(codePoints)),
+          String.class);
+    }
+
+    /**
+     * Generates Strings of a fixed length.
+     *
+     * @param fixedLength - the fixed length for the Strings
+     * @return a Source of type String
+     */
+    public SolrGen<String> ofLength(int fixedLength) {
+      return ofLengthBetween(fixedLength, fixedLength);
+    }
+
+    /**
+     * Max cardinality string generator builder.
+     *
+     * @param max the max
+     * @return the string generator builder
+     */
+    public StringGeneratorBuilder maxCardinality(int max) {
+      maxCardinality = SolrGenerate.constant(max);
+      return this;
+    }
+
+    /**
+     * Max cardinality string generator builder.
+     *
+     * @param max the max
+     * @return the string generator builder
+     */
+    public StringGeneratorBuilder maxCardinality(Gen<Integer> max) {
+      maxCardinality = max;
+      return this;
+    }
+
+    /**
+     * Multi string generator builder.
+     *
+     * @param count the count
+     * @return the string generator builder
+     */
+    public StringGeneratorBuilder multi(int count) {
+      this.multi = count;
+      return this;
+    }
+
+    /**
+     * Generates Strings of length bounded between minLength and maxLength inclusively.
+     *
+     * @param minLength - minimum inclusive length of String
+     * @param maxLength - maximum inclusive length of String
+     * @return a Source of type String
+     */
+    public SolrGen<String> ofLengthBetween(int minLength, int maxLength) {
+      checkArguments(
+          minLength <= maxLength,
+          "The minLength (%s) is longer than the maxLength(%s)",
+          minLength,
+          maxLength);
+      checkArguments(
+          minLength >= 0,
+          "The length of a String cannot be negative; %s is not an accepted argument",
+          minLength);
+      SolrGen<String> strings =
+          Strings.ofBoundedLengthStrings(minCodePoint, maxCodePoint, minLength, maxLength);
+
+      if (maxCardinality != null) {
+        SolrGen<String> gen =
+            new SolrGen<>(
+                new SolrGen<>() {
+                  @Override
+                  public String generate(SolrRandomnessSource in) {
+                    Integer maxCard = maxCardinality.generate(in);
+
+                    if (cardinalityStart == null) {
+                      cardinalityStart =
+                          SolrGenerate.range(0, Integer.MAX_VALUE - maxCard - 1).generate(in);
+                    }
+
+                    long seed =
+                        SolrGenerate.range(cardinalityStart, cardinalityStart + maxCard - 1)
+                            .generate(in);
+                    return strings.generate(
+                        (RandomnessSource) new SplittableRandomSource(new SplittableRandom(seed)));
+                  }
+
+                  @Override
+                  public String generate(RandomnessSource in) {
+                    return generate((SolrRandomnessSource) in);
+                  }
+                },
+                String.class);
+        if (multi > 1) {
+          return multiStringGen(gen, multi);
+        }
+        return new SolrGen<>(gen, String.class);
+      } else {
+        if (multi > 1) {
+          return multiStringGen(strings, multi);
+        }
+        return new SolrGen<>(strings, String.class);
+      }
+    }
+  }
+
+  private static SolrGen<String> multiStringGen(SolrGen<String> strings, int multi) {
+    return new SolrGen<>(MultiString.class) {
+
+      {
+        describedAs("MultiString");
+      }
+
+      @Override
+      public String generate(SolrRandomnessSource in) {
+        StringBuilder sb = new StringBuilder(64);
+        for (int i = 0; i < multi; i++) {
+          sb.append(strings.generate(in));
+          if (i < multi - 1) {
+            sb.append(' ');
+          }
+        }
+        return sb.toString();
+      }
+    };
+  }
+
+  private static class WordListStringSolrGen extends SolrGen<String> {
+    /** Instantiates a new Word list string solr gen. */
+    public WordListStringSolrGen() {
+      super(String.class);
+    }
+
+    @Override
+    public String generate(RandomnessSource in) {
+      return words.get(
+          integers()
+              .between(0, WORD_SIZE - 1)
+              .describedAs("WordList Index")
+              .withDistribution(this.getDistribution())
+              .generate(in));
+    }
+  }
+
+  private static final int[] blockStarts = {
+    0x0000, 0x0080, 0x0100, 0x0180, 0x0250, 0x02B0, 0x0300, 0x0370, 0x0400, 0x0500, 0x0530, 0x0590,
+    0x0600, 0x0700, 0x0750, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0980, 0x0A00, 0x0A80, 0x0B00, 0x0B80,
+    0x0C00, 0x0C80, 0x0D00, 0x0D80, 0x0E00, 0x0E80, 0x0F00, 0x1000, 0x10A0, 0x1100, 0x1200, 0x1380,
+    0x13A0, 0x1400, 0x1680, 0x16A0, 0x1700, 0x1720, 0x1740, 0x1760, 0x1780, 0x1800, 0x18B0, 0x1900,
+    0x1950, 0x1980, 0x19E0, 0x1A00, 0x1A20, 0x1B00, 0x1B80, 0x1C00, 0x1C50, 0x1CD0, 0x1D00, 0x1D80,
+    0x1DC0, 0x1E00, 0x1F00, 0x2000, 0x2070, 0x20A0, 0x20D0, 0x2100, 0x2150, 0x2190, 0x2200, 0x2300,
+    0x2400, 0x2440, 0x2460, 0x2500, 0x2580, 0x25A0, 0x2600, 0x2700, 0x27C0, 0x27F0, 0x2800, 0x2900,
+    0x2980, 0x2A00, 0x2B00, 0x2C00, 0x2C60, 0x2C80, 0x2D00, 0x2D30, 0x2D80, 0x2DE0, 0x2E00, 0x2E80,
+    0x2F00, 0x2FF0, 0x3000, 0x3040, 0x30A0, 0x3100, 0x3130, 0x3190, 0x31A0, 0x31C0, 0x31F0, 0x3200,
+    0x3300, 0x3400, 0x4DC0, 0x4E00, 0xA000, 0xA490, 0xA4D0, 0xA500, 0xA640, 0xA6A0, 0xA700, 0xA720,
+    0xA800, 0xA830, 0xA840, 0xA880, 0xA8E0, 0xA900, 0xA930, 0xA960, 0xA980, 0xAA00, 0xAA60, 0xAA80,
+    0xABC0, 0xAC00, 0xD7B0, 0xE000, 0xF900, 0xFB00, 0xFB50, 0xFE00, 0xFE10, 0xFE20, 0xFE30, 0xFE50,
+    0xFE70, 0xFF00, 0xFFF0, 0x10000, 0x10080, 0x10100, 0x10140, 0x10190, 0x101D0, 0x10280, 0x102A0,
+    0x10300, 0x10330, 0x10380, 0x103A0, 0x10400, 0x10450, 0x10480, 0x10800, 0x10840, 0x10900,
+    0x10920, 0x10A00, 0x10A60, 0x10B00, 0x10B40, 0x10B60, 0x10C00, 0x10E60, 0x11080, 0x12000,
+    0x12400, 0x13000, 0x1D000, 0x1D100, 0x1D200, 0x1D300, 0x1D360, 0x1D400, 0x1F000, 0x1F030,
+    0x1F100, 0x1F200, 0x20000, 0x2A700, 0x2F800, 0xE0000, 0xE0100, 0xF0000, 0x100000
+  };
+
+  private static final int[] blockEnds = {
+    0x007F, 0x00FF, 0x017F, 0x024F, 0x02AF, 0x02FF, 0x036F, 0x03FF, 0x04FF, 0x052F, 0x058F, 0x05FF,
+    0x06FF, 0x074F, 0x077F, 0x07BF, 0x07FF, 0x083F, 0x097F, 0x09FF, 0x0A7F, 0x0AFF, 0x0B7F, 0x0BFF,
+    0x0C7F, 0x0CFF, 0x0D7F, 0x0DFF, 0x0E7F, 0x0EFF, 0x0FFF, 0x109F, 0x10FF, 0x11FF, 0x137F, 0x139F,
+    0x13FF, 0x167F, 0x169F, 0x16FF, 0x171F, 0x173F, 0x175F, 0x177F, 0x17FF, 0x18AF, 0x18FF, 0x194F,
+    0x197F, 0x19DF, 0x19FF, 0x1A1F, 0x1AAF, 0x1B7F, 0x1BBF, 0x1C4F, 0x1C7F, 0x1CFF, 0x1D7F, 0x1DBF,
+    0x1DFF, 0x1EFF, 0x1FFF, 0x206F, 0x209F, 0x20CF, 0x20FF, 0x214F, 0x218F, 0x21FF, 0x22FF, 0x23FF,
+    0x243F, 0x245F, 0x24FF, 0x257F, 0x259F, 0x25FF, 0x26FF, 0x27BF, 0x27EF, 0x27FF, 0x28FF, 0x297F,
+    0x29FF, 0x2AFF, 0x2BFF, 0x2C5F, 0x2C7F, 0x2CFF, 0x2D2F, 0x2D7F, 0x2DDF, 0x2DFF, 0x2E7F, 0x2EFF,
+    0x2FDF, 0x2FFF, 0x303F, 0x309F, 0x30FF, 0x312F, 0x318F, 0x319F, 0x31BF, 0x31EF, 0x31FF, 0x32FF,
+    0x33FF, 0x4DBF, 0x4DFF, 0x9FFF, 0xA48F, 0xA4CF, 0xA4FF, 0xA63F, 0xA69F, 0xA6FF, 0xA71F, 0xA7FF,
+    0xA82F, 0xA83F, 0xA87F, 0xA8DF, 0xA8FF, 0xA92F, 0xA95F, 0xA97F, 0xA9DF, 0xAA5F, 0xAA7F, 0xAADF,
+    0xABFF, 0xD7AF, 0xD7FF, 0xF8FF, 0xFAFF, 0xFB4F, 0xFDFF, 0xFE0F, 0xFE1F, 0xFE2F, 0xFE4F, 0xFE6F,
+    0xFEFF, 0xFFEF, 0xFFFF, 0x1007F, 0x100FF, 0x1013F, 0x1018F, 0x101CF, 0x101FF, 0x1029F, 0x102DF,
+    0x1032F, 0x1034F, 0x1039F, 0x103DF, 0x1044F, 0x1047F, 0x104AF, 0x1083F, 0x1085F, 0x1091F,
+    0x1093F, 0x10A5F, 0x10A7F, 0x10B3F, 0x10B5F, 0x10B7F, 0x10C4F, 0x10E7F, 0x110CF, 0x123FF,
+    0x1247F, 0x1342F, 0x1D0FF, 0x1D1FF, 0x1D24F, 0x1D35F, 0x1D37F, 0x1D7FF, 0x1F02F, 0x1F09F,
+    0x1F1FF, 0x1F2FF, 0x2A6DF, 0x2B73F, 0x2FA1F, 0xE007F, 0xE01EF, 0xFFFFF, 0x10FFFF
+  };
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/Tracker.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/Tracker.java
new file mode 100644
index 0000000..e28a73a
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/Tracker.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.bench.generators;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/** The type Tracker. */
+public class Tracker {
+  private final List<RandomDataHistogram.Counts.Surrogate> values;
+  private final String name;
+  private final int count;
+  private final double percentage;
+
+  /**
+   * Instantiates a new Tracker.
+   *
+   * @param values the values
+   * @param name the name
+   * @param count the count
+   * @param percentage the percentage
+   */
+  public Tracker(
+      List<RandomDataHistogram.Counts.Surrogate> values,
+      String name,
+      int count,
+      double percentage) {
+    this.values = values;
+    this.name = name == null ? "None" : name;
+    this.count = count;
+    this.percentage = percentage;
+  }
+
+  /**
+   * Display string.
+   *
+   * @param object the object
+   * @return the string
+   */
+  public static String display(Object object) {
+    if (object == null) return "None";
+    if (object instanceof Class) {
+      return ((Class<?>) object).getName();
+    }
+    if (object instanceof Collection) {
+      @SuppressWarnings("unchecked")
+      Collection<Object> collection = (Collection<Object>) object;
+      String elements = collection.stream().map(Tracker::display).collect(Collectors.joining(", "));
+      return String.format(Locale.ENGLISH, "[%s]", elements);
+    }
+    if (object.getClass().isArray()) {
+      if (object.getClass().getComponentType().isPrimitive()) {
+        return getString(object);
+      }
+      Object[] array = (Object[]) object;
+      String elements =
+          Arrays.stream(array).map(Tracker::display).collect(Collectors.joining(", "));
+      return String.format(Locale.ENGLISH, "%s{%s}", object.getClass().getSimpleName(), elements);
+    }
+    if (String.class.isAssignableFrom(object.getClass())) {
+      return String.format(Locale.ENGLISH, "\"%s\"", object.toString().replace('\u0000', '\ufffd'));
+    }
+    return object.toString().replace('\u0000', '\ufffd');
+  }
+
+  private static String getString(Object obj) {
+    if (obj == null) {
+      return "None";
+    }
+
+    try {
+      if (obj.getClass().isArray()) {
+        if (obj.getClass().getComponentType().isPrimitive()) {
+          if (obj instanceof boolean[]) {
+            return Arrays.toString((boolean[]) obj);
+          }
+          if (obj instanceof char[]) {
+            return Arrays.toString((char[]) obj);
+          }
+          if (obj instanceof short[]) {
+            return Arrays.toString((short[]) obj);
+          }
+          if (obj instanceof byte[]) {
+            return Arrays.toString((byte[]) obj);
+          }
+          if (obj instanceof int[]) {
+            return Arrays.toString((int[]) obj);
+          }
+          if (obj instanceof long[]) {
+            return Arrays.toString((long[]) obj);
+          }
+          if (obj instanceof float[]) {
+            return Arrays.toString((float[]) obj);
+          }
+          if (obj instanceof double[]) {
+            return Arrays.toString((double[]) obj);
+          }
+        }
+        return Arrays.deepToString((Object[]) obj);
+      }
+
+      return obj.toString();
+    } catch (Throwable t) {
+      if (t instanceof Error) {
+        throw t;
+      }
+
+      return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj));
+    }
+  }
+
+  /**
+   * Name string.
+   *
+   * @return the string
+   */
+  public String name() {
+    return name;
+  }
+
+  /**
+   * Count int.
+   *
+   * @return the int
+   */
+  public int count() {
+    return count;
+  }
+
+  /**
+   * Percentage double.
+   *
+   * @return the double
+   */
+  public double percentage() {
+    return percentage;
+  }
+
+  /**
+   * Values list.
+   *
+   * @return the list
+   */
+  public List<RandomDataHistogram.Counts.Surrogate> values() {
+    return Collections.unmodifiableList(values);
+  }
+
+  public String toString() {
+    return String.format(
+        Locale.ENGLISH, "%s (%s, %s%%): %s", name, count, percentage, Tracker.display(values));
+  }
+}
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/generators/package-info.java b/solr/benchmark/src/java/org/apache/solr/bench/generators/package-info.java
new file mode 100644
index 0000000..0b1c740
--- /dev/null
+++ b/solr/benchmark/src/java/org/apache/solr/bench/generators/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/** Type random generators that are built upon and maintain compatibility with QuickTheories */
+package org.apache.solr.bench.generators;
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/index/CloudIndexing.java b/solr/benchmark/src/java/org/apache/solr/bench/index/CloudIndexing.java
index bac8406..981ce0d 100755
--- a/solr/benchmark/src/java/org/apache/solr/bench/index/CloudIndexing.java
+++ b/solr/benchmark/src/java/org/apache/solr/bench/index/CloudIndexing.java
@@ -16,11 +16,13 @@
  */
 package org.apache.solr.bench.index;
 
+import static org.apache.solr.bench.Docs.docs;
+import static org.apache.solr.bench.generators.SourceDSL.integers;
+import static org.apache.solr.bench.generators.SourceDSL.longs;
+import static org.apache.solr.bench.generators.SourceDSL.strings;
+
 import java.util.Iterator;
-import java.util.SplittableRandom;
 import java.util.concurrent.TimeUnit;
-import org.apache.solr.bench.DocMaker;
-import org.apache.solr.bench.FieldDef;
 import org.apache.solr.bench.MiniClusterState;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.SolrInputDocument;
@@ -39,21 +41,25 @@ import org.openjdk.jmh.annotations.TearDown;
 import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Timeout;
 import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.BenchmarkParams;
 
 @BenchmarkMode(Mode.Throughput)
 @OutputTimeUnit(TimeUnit.SECONDS)
-@Threads(6)
-@Warmup(time = 3, iterations = 5)
-@Measurement(time = 15, iterations = 5)
+@Threads(4)
+@Warmup(time = 10, iterations = 3)
+@Measurement(time = 20, iterations = 4)
 @Fork(value = 1)
 @Timeout(time = 60)
-/** A benchmark to experiment with the performance of distributed indexing. */
+// A benchmark to experiment with the performance of distributed indexing.
 public class CloudIndexing {
 
   @State(Scope.Benchmark)
   public static class BenchState {
 
-    String collection = "testCollection";
+    static final String COLLECTION = "testCollection";
+
+    @Param({"1"})
+    public int scale;
 
     @Param("4")
     int nodeCount;
@@ -61,117 +67,108 @@ public class CloudIndexing {
     @Param("5")
     int numShards;
 
-    @Param({"1", "3", "9"})
+    @Param({"1", "3"})
     int numReplicas;
 
-    @Param({"100000"})
-    public int docCount;
-
-    @Setup(Level.Iteration)
-    public void doSetup(MiniClusterState.MiniClusterBenchState miniClusterState) throws Exception {
-
-      miniClusterState.startMiniCluster(nodeCount);
-
-      miniClusterState.createCollection(collection, numShards, numReplicas);
+    @Param({"0", "15", "30", "70", "100", "500", "1000"})
+    int useStringUtf8Over;
+
+    @Param({"true", "false"})
+    boolean directBuffer;
+
+    private final org.apache.solr.bench.Docs largeDocs;
+    private Iterator<SolrInputDocument> largeDocIterator;
+
+    private final org.apache.solr.bench.Docs smallDocs;
+    private Iterator<SolrInputDocument> smallDocIterator;
+
+    public BenchState() {
+
+      largeDocs =
+          docs()
+              .field("id", integers().incrementing())
+              .field(strings().basicLatinAlphabet().multi(312).ofLengthBetween(30, 64))
+              .field(strings().basicLatinAlphabet().multi(312).ofLengthBetween(30, 64))
+              .field(integers().all())
+              .field(integers().all())
+              .field(integers().all())
+              .field(longs().all())
+              .field(longs().all());
+
+      try {
+        largeDocIterator = largeDocs.preGenerate(50000);
+
+        smallDocs =
+            docs()
+                .field("id", integers().incrementing())
+                .field("text", strings().basicLatinAlphabet().multi(2).ofLengthBetween(20, 32))
+                .field("int1_i", integers().all())
+                .field("int2_i", integers().all())
+                .field("long1_l", longs().all());
+
+        smallDocIterator = smallDocs.preGenerate(50000);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw new RuntimeException(e);
+      }
     }
 
-    @TearDown(Level.Iteration)
-    public void doTearDown(MiniClusterState.MiniClusterBenchState miniClusterState)
-        throws Exception {
-      miniClusterState.shutdownMiniCluster();
+    public SolrInputDocument getLargeDoc() {
+      if (!largeDocIterator.hasNext()) {
+        largeDocIterator = largeDocs.generatedDocsIterator();
+      }
+      return largeDocIterator.next();
     }
 
-    @State(Scope.Thread)
-    public static class Docs {
-
-      private DocMaker largeDocMaker;
-      private Iterator<SolrInputDocument> largeDocIterator;
-
-      private DocMaker smallDocMaker;
-      private Iterator<SolrInputDocument> smallDocIterator;
-
-      private SplittableRandom random;
-
-      @Setup(Level.Trial)
-      public void setupDoc(BenchState state) throws Exception {
-        Long seed = Long.getLong("randomSeed");
-
-        random = new SplittableRandom(seed);
-
-        largeDocMaker = new DocMaker();
-        largeDocMaker.addField(
-            "id", FieldDef.FieldDefBuilder.aFieldDef().withContent(DocMaker.Content.UNIQUE_INT));
-        largeDocMaker.addField(
-            "text",
-            FieldDef.FieldDefBuilder.aFieldDef()
-                .withContent(DocMaker.Content.ALPHEBETIC)
-                .withMaxLength(64)
-                .withTokenCount(random.nextInt(350, 512)));
-
-        largeDocMaker.preGenerateDocs(state.docCount, random);
-
-        largeDocIterator = largeDocMaker.getGeneratedDocsIterator();
-
-        smallDocMaker = new DocMaker();
-        smallDocMaker.addField(
-            "id", FieldDef.FieldDefBuilder.aFieldDef().withContent(DocMaker.Content.UNIQUE_INT));
-        smallDocMaker.addField(
-            "text",
-            FieldDef.FieldDefBuilder.aFieldDef()
-                .withContent(DocMaker.Content.ALPHEBETIC)
-                .withMaxLength(32)
-                .withTokenCount(1));
+    public SolrInputDocument getSmallDoc() {
+      if (!smallDocIterator.hasNext()) {
+        smallDocIterator = smallDocs.generatedDocsIterator();
+      }
+      return smallDocIterator.next();
+    }
 
-        smallDocMaker.preGenerateDocs(state.docCount, random);
+    @Setup(Level.Trial)
+    public void doSetup(MiniClusterState.MiniClusterBenchState miniClusterState) throws Exception {
+      System.setProperty("useStringUtf8Over", Integer.toString(useStringUtf8Over));
+      System.setProperty("httpClientDirectBuffer", Boolean.toString(directBuffer));
 
-        smallDocIterator = smallDocMaker.getGeneratedDocsIterator();
-      }
+      System.setProperty("mergePolicyFactory", "org.apache.solr.index.NoMergePolicyFactory");
+      miniClusterState.startMiniCluster(nodeCount);
+      miniClusterState.createCollection(COLLECTION, numShards, numReplicas);
+    }
 
-      public SolrInputDocument getLargeDoc() {
-        if (!largeDocIterator.hasNext()) {
-          largeDocIterator = largeDocMaker.getGeneratedDocsIterator();
-        }
-        return largeDocIterator.next();
-      }
+    @TearDown(Level.Trial)
+    public void doTearDown(
+        MiniClusterState.MiniClusterBenchState miniClusterState, BenchmarkParams benchmarkParams)
+        throws Exception {
 
-      public SolrInputDocument getSmallDoc() {
-        if (!smallDocIterator.hasNext()) {
-          smallDocIterator = smallDocMaker.getGeneratedDocsIterator();
-        }
-        return smallDocIterator.next();
-      }
+      // miniClusterState.shutdownMiniCluster(benchmarkParams);
     }
   }
 
   @Benchmark
   @Timeout(time = 300)
   public Object indexLargeDoc(
-      MiniClusterState.MiniClusterBenchState miniClusterState,
-      BenchState state,
-      BenchState.Docs docState)
-      throws Exception {
+      MiniClusterState.MiniClusterBenchState miniClusterState, BenchState state) throws Exception {
     UpdateRequest updateRequest = new UpdateRequest();
-    updateRequest.setBasePath(miniClusterState.nodes.get(docState.random.nextInt(state.nodeCount)));
-    SolrInputDocument doc = docState.getLargeDoc();
-
+    updateRequest.setBasePath(
+        miniClusterState.nodes.get(miniClusterState.getRandom().nextInt(state.nodeCount)));
+    SolrInputDocument doc = state.getLargeDoc();
     updateRequest.add(doc);
 
-    return miniClusterState.client.request(updateRequest, state.collection);
+    return miniClusterState.client.request(updateRequest, BenchState.COLLECTION);
   }
 
   @Benchmark
   @Timeout(time = 300)
   public Object indexSmallDoc(
-      MiniClusterState.MiniClusterBenchState miniClusterState,
-      BenchState state,
-      BenchState.Docs docState)
-      throws Exception {
+      MiniClusterState.MiniClusterBenchState miniClusterState, BenchState state) throws Exception {
     UpdateRequest updateRequest = new UpdateRequest();
-    updateRequest.setBasePath(miniClusterState.nodes.get(docState.random.nextInt(state.nodeCount)));
-    SolrInputDocument doc = docState.getSmallDoc();
-
+    updateRequest.setBasePath(
+        miniClusterState.nodes.get(miniClusterState.getRandom().nextInt(state.nodeCount)));
+    SolrInputDocument doc = state.getSmallDoc();
     updateRequest.add(doc);
 
-    return miniClusterState.client.request(updateRequest, state.collection);
+    return miniClusterState.client.request(updateRequest, BenchState.COLLECTION);
   }
 }
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/search/JsonFaceting.java b/solr/benchmark/src/java/org/apache/solr/bench/search/JsonFaceting.java
index 088c703..61e4b87 100755
--- a/solr/benchmark/src/java/org/apache/solr/bench/search/JsonFaceting.java
+++ b/solr/benchmark/src/java/org/apache/solr/bench/search/JsonFaceting.java
@@ -16,10 +16,14 @@
  */
 package org.apache.solr.bench.search;
 
+import static org.apache.solr.bench.Docs.docs;
+import static org.apache.solr.bench.generators.SourceDSL.integers;
+import static org.apache.solr.bench.generators.SourceDSL.strings;
+
 import java.util.SplittableRandom;
 import java.util.concurrent.TimeUnit;
-import org.apache.solr.bench.DocMaker;
-import org.apache.solr.bench.FieldDef;
+import org.apache.solr.bench.BaseBenchState;
+import org.apache.solr.bench.Docs;
 import org.apache.solr.bench.MiniClusterState;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -53,7 +57,7 @@ public class JsonFaceting {
   @State(Scope.Benchmark)
   public static class BenchState {
 
-    public String collection = "testCollection";
+    public static final String collection = "testCollection";
 
     @Param({"100000"})
     public int docCount;
@@ -95,8 +99,6 @@ public class JsonFaceting {
         BenchmarkParams benchmarkParams, MiniClusterState.MiniClusterBenchState miniClusterState)
         throws Exception {
 
-      SplittableRandom random = miniClusterState.getRandom();
-
       System.setProperty("maxMergeAtOnce", "20");
       System.setProperty("segmentsPerTier", "20");
 
@@ -105,52 +107,28 @@ public class JsonFaceting {
       miniClusterState.createCollection(collection, numShards, numReplicas);
 
       // Define random documents
-      DocMaker docMaker = new DocMaker();
-      docMaker.addField(
-          "id", FieldDef.FieldDefBuilder.aFieldDef().withContent(DocMaker.Content.UNIQUE_INT));
-      docMaker.addField(
-          "facet_s",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.ALPHEBETIC)
-              .withMaxLength(64)
-              .withMaxCardinality(facetCard, random));
-      docMaker.addField(
-          "facet2_s",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.ALPHEBETIC)
-              .withMaxLength(16)
-              .withMaxCardinality(facetCard, random));
-      docMaker.addField(
-          "facet3_s",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.UNICODE)
-              .withMaxLength(128)
-              .withMaxCardinality(facetCard2, random));
-      docMaker.addField(
-          "text",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.ALPHEBETIC)
-              .withMaxLength(64)
-              .withTokenCount(random.nextInt(350, 512)));
-      docMaker.addField(
-          "int_i", FieldDef.FieldDefBuilder.aFieldDef().withContent(DocMaker.Content.INTEGER));
-      docMaker.addField(
-          "int2_i",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.INTEGER)
-              .withMaxCardinality(facetCard2, random));
-      docMaker.addField(
-          "int3_i",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.INTEGER)
-              .withMaxCardinality(facetCard2, random));
-      docMaker.addField(
-          "int4_i",
-          FieldDef.FieldDefBuilder.aFieldDef()
-              .withContent(DocMaker.Content.INTEGER)
-              .withMaxCardinality(facetCard2, random));
-
-      miniClusterState.index(collection, docMaker, docCount);
+      Docs docs =
+          docs()
+              .field("id", integers().incrementing())
+              .field(
+                  "facet_s",
+                  strings().basicLatinAlphabet().maxCardinality(facetCard).ofLengthBetween(1, 64))
+              .field(
+                  "facet2_s",
+                  strings().basicLatinAlphabet().maxCardinality(facetCard).ofLengthBetween(1, 16))
+              .field(
+                  "facet3_s",
+                  strings()
+                      .basicMultilingualPlaneAlphabet()
+                      .maxCardinality(facetCard2)
+                      .ofLengthBetween(1, 128))
+              .field(strings().basicLatinAlphabet().multi(512).ofLengthBetween(4, 16))
+              .field(integers().all())
+              .field(integers().allWithMaxCardinality(facetCard2))
+              .field(integers().allWithMaxCardinality(facetCard2))
+              .field(integers().allWithMaxCardinality(facetCard2));
+
+      miniClusterState.index(collection, docs, docCount);
       miniClusterState.forceMerge(collection, 15);
 
       params = new ModifiableSolrParams();
@@ -178,7 +156,7 @@ public class JsonFaceting {
               + " , f6:{type:terms, field:'int3_i', limit:1, sort:'x desc', facet:{x:'hll(int2_i)'}  } "
               + " , f7:{type:terms, field:'facet_s', limit:2, sort:'x desc', facet:{x:'missing(int4_i)'}  } "
               + " , f8:{type:terms, field:'facet_s', limit:2, sort:'x desc', facet:{x:'countvals(int4_i)'}  } "
-              + "}");
+              + '}');
 
       // MiniClusterState.log("params: " + params + "\n");
     }
@@ -189,10 +167,8 @@ public class JsonFaceting {
       private SplittableRandom random;
 
       @Setup(Level.Trial)
-      public void setup() throws Exception {
-        Long seed = Long.getLong("randomSeed");
-
-        this.random = new SplittableRandom(seed);
+      public void setup() {
+        this.random = new SplittableRandom(BaseBenchState.getRandomSeed());
       }
     }
   }
diff --git a/solr/benchmark/src/java/org/quicktheories/impl/BenchmarkRandomSource.java b/solr/benchmark/src/java/org/quicktheories/impl/BenchmarkRandomSource.java
new file mode 100644
index 0000000..8eefcdc
--- /dev/null
+++ b/solr/benchmark/src/java/org/quicktheories/impl/BenchmarkRandomSource.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.quicktheories.impl;
+
+import org.apache.commons.math3.random.RandomDataGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.apache.solr.bench.generators.Distribution;
+import org.quicktheories.core.DetatchedRandomnessSource;
+
+/** The type Benchmark random source. */
+public class BenchmarkRandomSource
+    implements DetatchedRandomnessSource, ExtendedRandomnessSource, SolrRandomnessSource {
+  private final RandomGenerator random;
+  private final RandomDataGenerator rdg;
+
+  private org.apache.solr.bench.generators.Distribution distribution = Distribution.UNIFORM;
+
+  /**
+   * Instantiates a new Benchmark random source.
+   *
+   * @param random the random
+   */
+  public BenchmarkRandomSource(RandomGenerator random) {
+    this.random = random;
+    rdg = new RandomDataGenerator(random);
+  }
+
+  private BenchmarkRandomSource(
+      RandomGenerator random,
+      RandomDataGenerator rdg,
+      org.apache.solr.bench.generators.Distribution distribution) {
+    this.random = random;
+    this.rdg = rdg;
+    this.distribution = distribution;
+  }
+
+  public BenchmarkRandomSource withDistribution(Distribution distribution) {
+    if (this.distribution == distribution) {
+      return this;
+    }
+    return new BenchmarkRandomSource(random, rdg, distribution);
+  }
+
+  @Override
+  public long next(long min, long max) {
+    switch (distribution) {
+      case UNIFORM:
+        return rdg.nextLong(min, max);
+      case ZIPFIAN:
+        return rdg.nextZipf((int) (max - min), 2) + min - 1;
+      case GAUSSIAN:
+        return (int) BenchmarkRandomSource.normalize(rdg.nextGaussian(.5, .125), min, max - 1.0d);
+      default:
+        throw new IllegalStateException("Unknown distribution: " + distribution);
+    }
+  }
+
+  @Override
+  public long next(Constraint constraints) {
+    if (constraints.min() == constraints.max()) {
+      throw new RuntimeException(constraints.min() + " " + constraints.max());
+    }
+    return next(constraints.min(), constraints.max());
+  }
+
+  /**
+   * Normalize double.
+   *
+   * @param value the value
+   * @param normalizationLowerBound the normalization lower bound
+   * @param normalizationUpperBound the normalization upper bound
+   * @return the double
+   */
+  public static double normalize(
+      double value, double normalizationLowerBound, double normalizationUpperBound) {
+    double boundedValue = boundValue(value);
+    // normalize boundedValue to new range
+    double normalizedRange = normalizationUpperBound - normalizationLowerBound;
+    return ((boundedValue - 0) * normalizedRange) + normalizationLowerBound;
+  }
+
+  private static double boundValue(double value) {
+    double boundedValue = value;
+    if (value < 0) {
+      boundedValue = 0;
+    }
+    if (value > 1) {
+      boundedValue = 1;
+    }
+    return boundedValue;
+  }
+
+  @Override
+  public DetatchedRandomnessSource detach() {
+    return this;
+  }
+
+  @Override
+  public void registerFailedAssumption() {}
+
+  @Override
+  public long tryNext(Constraint constraints) {
+    return next(constraints);
+  }
+
+  @Override
+  public void add(Precursor other) {}
+
+  @Override
+  public void commit() {}
+}
diff --git a/solr/benchmark/src/java/org/quicktheories/impl/SplittableRandomSource.java b/solr/benchmark/src/java/org/quicktheories/impl/SplittableRandomSource.java
new file mode 100644
index 0000000..df6f9b0
--- /dev/null
+++ b/solr/benchmark/src/java/org/quicktheories/impl/SplittableRandomSource.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.quicktheories.impl;
+
+import java.util.SplittableRandom;
+import org.apache.solr.bench.SolrRandomnessSource;
+import org.apache.solr.bench.generators.Distribution;
+import org.quicktheories.core.DetatchedRandomnessSource;
+
+/** The type Splittable random source. */
+public class SplittableRandomSource
+    implements DetatchedRandomnessSource, ExtendedRandomnessSource, SolrRandomnessSource {
+  /** The Random. */
+  final SplittableRandom random;
+
+  /**
+   * Instantiates a new Splittable random source.
+   *
+   * @param random the random
+   */
+  public SplittableRandomSource(SplittableRandom random) {
+    this.random = random;
+  }
+
+  @Override
+  public long next(Constraint constraints) {
+    if (constraints.min() == constraints.max()) {
+      throw new RuntimeException(constraints.min() + " " + constraints.max());
+    }
+    return next(constraints.min(), constraints.max());
+  }
+
+  @Override
+  public long next(long min, long max) {
+    return random.nextLong(min, max);
+  }
+
+  @Override
+  public DetatchedRandomnessSource detach() {
+    return this;
+  }
+
+  @Override
+  public void registerFailedAssumption() {}
+
+  @Override
+  public long tryNext(Constraint constraints) {
+    return next(constraints);
+  }
+
+  @Override
+  public void add(Precursor other) {}
+
+  @Override
+  public void commit() {}
+
+  @Override
+  public SolrRandomnessSource withDistribution(Distribution distribution) {
+    return this;
+  }
+}
diff --git a/solr/benchmark/src/resources/configs/cloud-minimal/conf/schema.xml b/solr/benchmark/src/resources/configs/cloud-minimal/conf/schema.xml
index 727f5e2..bc4676c 100644
--- a/solr/benchmark/src/resources/configs/cloud-minimal/conf/schema.xml
+++ b/solr/benchmark/src/resources/configs/cloud-minimal/conf/schema.xml
@@ -16,22 +16,39 @@
  limitations under the License.
 -->
 <schema name="minimal" version="1.1">
-  <fieldType name="string" class="solr.StrField" docValues="true"/>
-  <fieldType name="int" class="org.apache.solr.schema.IntPointField" docValues="true" omitNorms="true" positionIncrementGap="0"/>
-  <fieldType name="long" class="org.apache.solr.schema.LongPointField" docValues="true" omitNorms="true" positionIncrementGap="0"/>
-  <fieldType name="text" class="solr.TextField">
-    <analyzer>
-      <tokenizer class="solr.StandardTokenizerFactory"/>
-      <filter class="solr.LowerCaseFilterFactory"/>
-    </analyzer>
-  </fieldType>
+    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
+    <fieldType name="string" class="solr.StrField" docValues="true"/>
+    <fieldType name="int" class="org.apache.solr.schema.IntPointField" docValues="true" omitNorms="true"
+               positionIncrementGap="0"/>
+    <fieldType name="long" class="org.apache.solr.schema.LongPointField" docValues="true" omitNorms="true"
+               positionIncrementGap="0"/>
+    <fieldType name="float" class="org.apache.solr.schema.FloatPointField" docValues="true" omitNorms="true"
+               positionIncrementGap="0"/>
+    <fieldType name="double" class="org.apache.solr.schema.DoublePointField" docValues="true" omitNorms="true"
+               positionIncrementGap="0"/>
+    <fieldType name="date" class="org.apache.solr.schema.DatePointField" docValues="true" omitNorms="true"
+               positionIncrementGap="0"/>
+    <fieldType name="text" class="solr.TextField">
+        <analyzer>
+            <tokenizer class="solr.StandardTokenizerFactory"/>
+            <filter class="solr.LowerCaseFilterFactory"/>
+        </analyzer>
+    </fieldType>
 
-  <!-- for versioning -->
-  <field name="_version_" type="long" indexed="true" stored="true"/>
-  <field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
-  <field name="id" type="string" indexed="true" stored="true"/>
-  <field name="text" type="text" indexed="true" stored="false"/>
-  <dynamicField name="*_s"  type="string"  indexed="true"  stored="true" />
-  <dynamicField name="*_i"  type="int"  indexed="false"  stored="false" />
-  <uniqueKey>id</uniqueKey>
+    <!-- for versioning -->
+    <field name="_version_" type="long" indexed="true" stored="true"/>
+    <field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
+    <field name="id" type="string" indexed="true" stored="true"/>
+    <field name="text" type="text" indexed="true" stored="false"/>
+
+    <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
+    <dynamicField name="*_s" type="string" indexed="true" stored="false"/>
+    <dynamicField name="*_t" type="text" indexed="true" stored="false"/>
+    <dynamicField name="*_i" type="int" indexed="false" stored="false"/>
+    <dynamicField name="*_l" type="long" indexed="false" stored="false"/>
+    <dynamicField name="*_f" type="float" indexed="false" stored="false"/>
+    <dynamicField name="*_d" type="double" indexed="false" stored="false"/>
+    <dynamicField name="*_dt" type="date" indexed="false" stored="false"/>
+
+    <uniqueKey>id</uniqueKey>
 </schema>
diff --git a/solr/benchmark/src/resources/configs/cloud-minimal/conf/solrconfig.xml b/solr/benchmark/src/resources/configs/cloud-minimal/conf/solrconfig.xml
index 9ddb072..2b8090f 100644
--- a/solr/benchmark/src/resources/configs/cloud-minimal/conf/solrconfig.xml
+++ b/solr/benchmark/src/resources/configs/cloud-minimal/conf/solrconfig.xml
@@ -24,13 +24,13 @@
   <dataDir>${solr.data.dir:}</dataDir>
 
   <directoryFactory name="DirectoryFactory"
-                    class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+                    class="${directoryFactory:solr.NRTCachingDirectoryFactory}"/>
   <schemaFactory class="ClassicIndexSchemaFactory"/>
 
   <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
 
   <indexConfig>
-    <mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
+    <mergePolicyFactory class="${mergePolicyFactory:org.apache.solr.index.TieredMergePolicyFactory}">
       <int name="maxMergeAtOnce">${maxMergeAtOnce:10}</int>
       <int name="segmentsPerTier">${segmentsPerTier:10}</int>
       <double name="noCFSRatio">${noCFSRatio:.1}</double>
@@ -38,7 +38,7 @@
 
     <useCompoundFile>${useCompoundFile:true}</useCompoundFile>
 
-    <ramBufferSizeMB>${solr.ramBufferSizeMB:100}</ramBufferSizeMB>
+    <ramBufferSizeMB>${ramBufferSizeMB:100}</ramBufferSizeMB>
     <!-- <ramPerThreadHardLimitMB>60</ramPerThreadHardLimitMB> -->
 
     <!-- <mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler">
@@ -54,20 +54,20 @@
          use the single process lockType for speed - but tests that explicitly need
          to vary the lockType canset it as needed.
     -->
-    <lockType>${solr.lockType:single}</lockType>
+    <lockType>${lockType:single}</lockType>
 
-    <infoStream>${solr.tests.infostream:false}</infoStream>
+    <infoStream>${infostream:false}</infoStream>
 
   </indexConfig>
 
   <updateHandler class="solr.DirectUpdateHandler2">
     <commitWithin>
-      <softCommit>${solr.commitwithin.softcommit:true}</softCommit>
+      <softCommit>${commitwithin.softcommit:true}</softCommit>
     </commitWithin>
     <autoCommit>
-      <maxTime>${solr.autoCommit.maxTime:60000}</maxTime>
+      <maxTime>${autoCommit.maxTime:60000}</maxTime>
     </autoCommit>
-    <updateLog class="${solr.ulog:solr.UpdateLog}" enable="${enable.update.log:true}"/>
+    <updateLog class="${ulog:solr.UpdateLog}" enable="${enable.update.log:true}"/>
   </updateHandler>
 
   <requestHandler name="/select" class="solr.SearchHandler">
@@ -81,25 +81,25 @@
 
   <query>
     <queryResultCache
-            enabled ="${queryResultCache.enabled:false}"
+            enabled="${queryResultCache.enabled:false}"
             class="${queryResultCache.class:solr.LRUCache}"
             size="${queryResultCache.size:0}"
             initialSize="${queryResultCache.initialSize:0}"
             autowarmCount="${queryResultCache.autowarmCount:0}"/>
-    <documentCache
-            enabled ="${documentCache.enabled:false}"
-            class="${documentCache.class:solr.LRUCache}"
-            size="${documentCache.size:0}"
-            initialSize="${documentCache.initialSize:0}"
-            autowarmCount="${documentCache.autowarmCount:0}"/>
-    <filterCache
-            enabled ="${filterCache.enabled:false}"
-            class="${filterCache.class:solr.LRUCache}"
-            size="${filterCache.size:0}"
-            initialSize="${filterCache.initialSize:0}"
-            autowarmCount="${filterCache.autowarmCount:0}"/>
+      <documentCache
+              enabled="${documentCache.enabled:false}"
+              class="${documentCache.class:solr.LRUCache}"
+              size="${documentCache.size:0}"
+              initialSize="${documentCache.initialSize:0}"
+              autowarmCount="${documentCache.autowarmCount:0}"/>
+      <filterCache
+              enabled="${filterCache.enabled:false}"
+              class="${filterCache.class:solr.LRUCache}"
+              size="${filterCache.size:0}"
+              initialSize="${filterCache.initialSize:0}"
+              autowarmCount="${filterCache.autowarmCount:0}"/>
     <cache name="myPerSegmentCache"
-           enabled ="${myPerSegmentCache.enabled:false}"
+           enabled="${myPerSegmentCache.enabled:false}"
            class="${myPerSegmentCache.class:solr.LRUCache}"
            size="${myPerSegmentCache.size:0}"
            initialSize="${myPerSegmentCache.initialSize:0}"
diff --git a/solr/benchmark/src/resources/words.txt b/solr/benchmark/src/resources/words.txt
new file mode 100644
index 0000000..235a020
--- /dev/null
+++ b/solr/benchmark/src/resources/words.txt
@@ -0,0 +1,466550 @@
+2
+1080
+&c
+10-point
+10th
+11-point
+12-point
+16-point
+18-point
+1st
+2,4,5-t
+2,4-d
+20-point
+2D
+2nd
+30-30
+3D
+3-D
+3M
+3rd
+48-point
+4-D
+4GL
+4H
+4th
+5-point
+5-T
+5th
+6-point
+6th
+7-point
+7th
+8-point
+8th
+9-point
+9th
+a
+a'
+a-
+A&M
+A&P
+A.
+A.A.A.
+A.B.
+A.B.A.
+A.C.
+A.D.
+A.D.C.
+A.F.
+A.F.A.M.
+A.G.
+A.H.
+A.I.
+A.I.A.
+A.I.D.
+A.L.
+A.L.P.
+A.M.
+A.M.A.
+A.M.D.G.
+A.N.
+a.p.
+a.r.
+A.R.C.S.
+A.U.
+A.U.C.
+A.V.
+a.w.
+A.W.O.L.
+A/C
+A/F
+A/O
+A/P
+A/V
+A1
+A-1
+A4
+A5
+AA
+AAA
+AAAA
+AAAAAA
+AAAL
+AAAS
+Aaberg
+Aachen
+AAE
+AAEE
+AAF
+AAG
+aah
+aahed
+aahing
+aahs
+AAII
+aal
+Aalborg
+Aalesund
+aalii
+aaliis
+aals
+Aalst
+Aalto
+AAM
+AAMSI
+Aandahl
+A-and-R
+Aani
+AAO
+AAP
+AAPSS
+Aaqbiye
+Aar
+Aara
+Aarau
+AARC
+aardvark
+aardvarks
+aardwolf
+aardwolves
+Aaren
+Aargau
+aargh
+Aarhus
+Aarika
+Aaron
+Aaronic
+Aaronical
+Aaronite
+Aaronitic
+Aaron's-beard
+Aaronsburg
+Aaronson
+AARP
+aarrgh
+aarrghh
+Aaru
+AAS
+A'asia
+aasvogel
+aasvogels
+AAU
+AAUP
+AAUW
+AAVSO
+AAX
+A-axes
+A-axis
+AB
+ab-
+ABA
+Ababa
+Ababdeh
+Ababua
+abac
+abaca
+abacay
+abacas
+abacate
+abacaxi
+abaci
+abacinate
+abacination
+abacisci
+abaciscus
+abacist
+aback
+abacli
+Abaco
+abacot
+abacterial
+abactinal
+abactinally
+abaction
+abactor
+abaculi
+abaculus
+abacus
+abacuses
+Abad
+abada
+Abadan
+Abaddon
+abadejo
+abadengo
+abadia
+Abadite
+abaff
+abaft
+Abagael
+Abagail
+Abagtha
+abay
+abayah
+Abailard
+abaisance
+abaised
+abaiser
+abaisse
+abaissed
+abaka
+Abakan
+abakas
+Abakumov
+abalation
+abalienate
+abalienated
+abalienating
+abalienation
+abalone
+abalones
+Abama
+abamp
+abampere
+abamperes
+abamps
+Abana
+aband
+abandon
+abandonable
+abandoned
+abandonedly
+abandonee
+abandoner
+abandoners
+abandoning
+abandonment
+abandonments
+abandons
+abandum
+abanet
+abanga
+Abanic
+abannition
+Abantes
+abapical
+abaptiston
+abaptistum
+Abarambo
+Abarbarea
+Abaris
+abarthrosis
+abarticular
+abarticulation
+Abas
+abase
+abased
+abasedly
+abasedness
+abasement
+abasements
+abaser
+abasers
+abases
+Abasgi
+abash
+abashed
+abashedly
+abashedness
+abashes
+abashing
+abashless
+abashlessly
+abashment
+abashments
+abasia
+abasias
+abasic
+abasing
+abasio
+abask
+abassi
+Abassieh
+Abassin
+abastard
+abastardize
+abastral
+abatable
+abatage
+Abate
+abated
+abatement
+abatements
+abater
+abaters
+abates
+abatic
+abating
+abatis
+abatised
+abatises
+abatjour
+abatjours
+abaton
+abator
+abators
+ABATS
+abattage
+abattis
+abattised
+abattises
+abattoir
+abattoirs
+abattu
+abattue
+Abatua
+abature
+abaue
+abave
+abaxial
+abaxile
+abaze
+abb
+Abba
+abbacy
+abbacies
+abbacomes
+Abbadide
+Abbai
+abbaye
+abbandono
+abbas
+abbasi
+Abbasid
+abbassi
+Abbassid
+Abbasside
+Abbate
+abbatial
+abbatical
+abbatie
+Abbe
+Abbey
+abbeys
+abbey's
+abbeystead
+abbeystede
+abbes
+abbess
+abbesses
+abbest
+Abbevilean
+Abbeville
+Abbevillian
+Abbi
+Abby
+Abbie
+Abbye
+Abbyville
+abboccato
+abbogada
+Abbot
+abbotcy
+abbotcies
+abbotnullius
+abbotric
+abbots
+abbot's
+Abbotsen
+Abbotsford
+abbotship
+abbotships
+Abbotson
+Abbotsun
+Abbott
+Abbottson
+Abbottstown
+Abboud
+abbozzo
+ABBR
+abbrev
+abbreviatable
+abbreviate
+abbreviated
+abbreviately
+abbreviates
+abbreviating
+abbreviation
+abbreviations
+abbreviator
+abbreviatory
+abbreviators
+abbreviature
+abbroachment
+ABC
+abcess
+abcissa
+abcoulomb
+ABCs
+abd
+abdal
+abdali
+abdaria
+abdat
+Abdel
+Abd-el-Kadir
+Abd-el-Krim
+Abdella
+Abderhalden
+Abderian
+Abderite
+Abderus
+abdest
+Abdias
+abdicable
+abdicant
+abdicate
+abdicated
+abdicates
+abdicating
+abdication
+abdications
+abdicative
+abdicator
+Abdiel
+abditive
+abditory
+abdom
+abdomen
+abdomens
+abdomen's
+abdomina
+abdominal
+Abdominales
+abdominalia
+abdominalian
+abdominally
+abdominals
+abdominoanterior
+abdominocardiac
+abdominocentesis
+abdominocystic
+abdominogenital
+abdominohysterectomy
+abdominohysterotomy
+abdominoposterior
+abdominoscope
+abdominoscopy
+abdominothoracic
+abdominous
+abdomino-uterotomy
+abdominovaginal
+abdominovesical
+Abdon
+Abdu
+abduce
+abduced
+abducens
+abducent
+abducentes
+abduces
+abducing
+abduct
+abducted
+abducting
+abduction
+abductions
+abduction's
+abductor
+abductores
+abductors
+abductor's
+abducts
+Abdul
+Abdul-Aziz
+Abdul-baha
+Abdulla
+Abe
+a-be
+abeam
+abear
+abearance
+Abebi
+abecedaire
+abecedary
+abecedaria
+abecedarian
+abecedarians
+abecedaries
+abecedarium
+abecedarius
+abed
+abede
+abedge
+Abednego
+abegge
+Abey
+abeyance
+abeyances
+abeyancy
+abeyancies
+abeyant
+abeigh
+ABEL
+Abelard
+abele
+abeles
+Abelia
+Abelian
+Abelicea
+Abelite
+Abell
+Abelmoschus
+abelmosk
+abelmosks
+abelmusk
+Abelonian
+Abelson
+abeltree
+Abencerrages
+abend
+abends
+Abenezra
+abenteric
+Abeokuta
+abepithymia
+ABEPP
+Abercromby
+Abercrombie
+Aberdare
+aberdavine
+Aberdeen
+Aberdeenshire
+aberdevine
+Aberdonian
+aberduvine
+Aberfan
+Aberglaube
+Aberia
+Aberystwyth
+Abernant
+Abernathy
+abernethy
+Abernon
+aberr
+aberrance
+aberrancy
+aberrancies
+aberrant
+aberrantly
+aberrants
+aberrate
+aberrated
+aberrating
+aberration
+aberrational
+aberrations
+aberrative
+aberrator
+aberrometer
+aberroscope
+Abert
+aberuncate
+aberuncator
+abesse
+abessive
+abet
+abetment
+abetments
+abets
+abettal
+abettals
+abetted
+abetter
+abetters
+abetting
+abettor
+abettors
+Abeu
+abevacuation
+abfarad
+abfarads
+ABFM
+Abgatha
+ABHC
+abhenry
+abhenries
+abhenrys
+abhinaya
+abhiseka
+abhominable
+abhor
+abhorred
+abhorrence
+abhorrences
+abhorrency
+abhorrent
+abhorrently
+abhorrer
+abhorrers
+abhorrible
+abhorring
+abhors
+Abhorson
+ABI
+aby
+Abia
+Abiathar
+Abib
+abichite
+abidal
+abidance
+abidances
+abidden
+abide
+abided
+abider
+abiders
+abides
+abidi
+abiding
+abidingly
+abidingness
+Abidjan
+Abydos
+Abie
+abye
+abied
+abyed
+abiegh
+abience
+abient
+Abies
+abyes
+abietate
+abietene
+abietic
+abietin
+Abietineae
+abietineous
+abietinic
+abietite
+Abiezer
+Abigael
+Abigail
+abigails
+abigailship
+Abigale
+abigeat
+abigei
+abigeus
+Abihu
+abying
+Abijah
+Abyla
+abilao
+Abilene
+abiliment
+Abilyne
+abilitable
+ability
+abilities
+ability's
+abilla
+abilo
+abime
+Abimelech
+Abineri
+Abingdon
+Abinger
+Abington
+Abinoam
+Abinoem
+abintestate
+abiogeneses
+abiogenesis
+abiogenesist
+abiogenetic
+abiogenetical
+abiogenetically
+abiogeny
+abiogenist
+abiogenous
+abiology
+abiological
+abiologically
+abioses
+abiosis
+abiotic
+abiotical
+abiotically
+abiotrophy
+abiotrophic
+Abipon
+Abiquiu
+abir
+abirritant
+abirritate
+abirritated
+abirritating
+abirritation
+abirritative
+abys
+Abisag
+Abisha
+Abishag
+Abisia
+abysm
+abysmal
+abysmally
+abysms
+Abyss
+abyssa
+abyssal
+abysses
+Abyssinia
+Abyssinian
+abyssinians
+abyssobenthonic
+abyssolith
+abyssopelagic
+abyss's
+abyssus
+abiston
+abit
+Abitibi
+Abiu
+abiuret
+Abixah
+abject
+abjectedness
+abjection
+abjections
+abjective
+abjectly
+abjectness
+abjectnesses
+abjoint
+abjudge
+abjudged
+abjudging
+abjudicate
+abjudicated
+abjudicating
+abjudication
+abjudicator
+abjugate
+abjunct
+abjunction
+abjunctive
+abjuration
+abjurations
+abjuratory
+abjure
+abjured
+abjurement
+abjurer
+abjurers
+abjures
+abjuring
+abkar
+abkari
+abkary
+Abkhas
+Abkhasia
+Abkhasian
+Abkhaz
+Abkhazia
+Abkhazian
+abl
+abl.
+ablach
+ablactate
+ablactated
+ablactating
+ablactation
+ablaqueate
+ablare
+A-blast
+ablastemic
+ablastin
+ablastous
+ablate
+ablated
+ablates
+ablating
+ablation
+ablations
+ablatitious
+ablatival
+ablative
+ablatively
+ablatives
+ablator
+ablaut
+ablauts
+ablaze
+able
+able-bodied
+able-bodiedness
+ableeze
+ablegate
+ablegates
+ablegation
+able-minded
+able-mindedness
+ablend
+ableness
+ablepharia
+ablepharon
+ablepharous
+Ablepharus
+ablepsy
+ablepsia
+ableptical
+ableptically
+abler
+ables
+ablesse
+ablest
+ablet
+ablewhackets
+ably
+ablings
+ablins
+ablock
+abloom
+ablow
+ABLS
+ablude
+abluent
+abluents
+ablush
+ablute
+abluted
+ablution
+ablutionary
+ablutions
+abluvion
+ABM
+abmho
+abmhos
+abmodality
+abmodalities
+abn
+Abnaki
+Abnakis
+abnegate
+abnegated
+abnegates
+abnegating
+abnegation
+abnegations
+abnegative
+abnegator
+abnegators
+Abner
+abnerval
+abnet
+abneural
+abnormal
+abnormalcy
+abnormalcies
+abnormalise
+abnormalised
+abnormalising
+abnormalism
+abnormalist
+abnormality
+abnormalities
+abnormalize
+abnormalized
+abnormalizing
+abnormally
+abnormalness
+abnormals
+abnormity
+abnormities
+abnormous
+abnumerable
+Abo
+aboard
+aboardage
+Abobra
+abococket
+abodah
+abode
+aboded
+abodement
+abodes
+abode's
+abody
+aboding
+abogado
+abogados
+abohm
+abohms
+aboideau
+aboideaus
+aboideaux
+aboil
+aboiteau
+aboiteaus
+aboiteaux
+abolete
+abolish
+abolishable
+abolished
+abolisher
+abolishers
+abolishes
+abolishing
+abolishment
+abolishments
+abolishment's
+abolition
+abolitionary
+abolitionise
+abolitionised
+abolitionising
+abolitionism
+abolitionist
+abolitionists
+abolitionize
+abolitionized
+abolitionizing
+abolitions
+abolla
+abollae
+aboma
+abomas
+abomasa
+abomasal
+abomasi
+abomasum
+abomasus
+abomasusi
+A-bomb
+abominability
+abominable
+abominableness
+abominably
+abominate
+abominated
+abominates
+abominating
+abomination
+abominations
+abominator
+abominators
+abomine
+abondance
+Abongo
+abonne
+abonnement
+aboon
+aborad
+aboral
+aborally
+abord
+Aboriginal
+aboriginality
+aboriginally
+aboriginals
+aboriginary
+Aborigine
+aborigines
+aborigine's
+Abor-miri
+Aborn
+aborning
+a-borning
+aborsement
+aborsive
+abort
+aborted
+aborter
+aborters
+aborticide
+abortient
+abortifacient
+abortin
+aborting
+abortion
+abortional
+abortionist
+abortionists
+abortions
+abortion's
+abortive
+abortively
+abortiveness
+abortogenic
+aborts
+abortus
+abortuses
+abos
+abote
+Abott
+abouchement
+aboudikro
+abought
+Aboukir
+aboulia
+aboulias
+aboulic
+abound
+abounded
+abounder
+abounding
+aboundingly
+abounds
+Abourezk
+about
+about-face
+about-faced
+about-facing
+abouts
+about-ship
+about-shipped
+about-shipping
+about-sledge
+about-turn
+above
+aboveboard
+above-board
+above-cited
+abovedeck
+above-found
+above-given
+aboveground
+abovementioned
+above-mentioned
+above-named
+aboveproof
+above-quoted
+above-reported
+aboves
+abovesaid
+above-said
+abovestairs
+above-water
+above-written
+abow
+abox
+Abp
+ABPC
+Abqaiq
+abr
+abr.
+Abra
+abracadabra
+abrachia
+abrachias
+abradable
+abradant
+abradants
+abrade
+abraded
+abrader
+abraders
+abrades
+abrading
+Abraham
+Abrahamic
+Abrahamidae
+Abrahamite
+Abrahamitic
+Abraham-man
+Abrahams
+Abrahamsen
+Abrahan
+abray
+abraid
+Abram
+Abramis
+Abramo
+Abrams
+Abramson
+Abran
+abranchial
+abranchialism
+abranchian
+Abranchiata
+abranchiate
+abranchious
+abrasax
+abrase
+abrased
+abraser
+abrash
+abrasing
+abrasiometer
+abrasion
+abrasions
+abrasion's
+abrasive
+abrasively
+abrasiveness
+abrasivenesses
+abrasives
+abrastol
+abraum
+abraxas
+abrazite
+abrazitic
+abrazo
+abrazos
+abreact
+abreacted
+abreacting
+abreaction
+abreactions
+abreacts
+abreast
+abreed
+abrege
+abreid
+abrenounce
+abrenunciate
+abrenunciation
+abreption
+abret
+abreuvoir
+abri
+abrico
+abricock
+abricot
+abridgable
+abridge
+abridgeable
+abridged
+abridgedly
+abridgement
+abridgements
+abridger
+abridgers
+abridges
+abridging
+abridgment
+abridgments
+abrim
+abrin
+abrine
+abris
+abristle
+abroach
+abroad
+Abrocoma
+abrocome
+abrogable
+abrogate
+abrogated
+abrogates
+abrogating
+abrogation
+abrogations
+abrogative
+abrogator
+abrogators
+Abroma
+Abroms
+Abronia
+abrood
+abrook
+abrosia
+abrosias
+abrotanum
+abrotin
+abrotine
+abrupt
+abruptedly
+abrupter
+abruptest
+abruptio
+abruption
+abruptiones
+abruptly
+abruptness
+Abrus
+Abruzzi
+ABS
+abs-
+Absa
+Absalom
+absampere
+Absaraka
+Absaroka
+Absarokee
+absarokite
+ABSBH
+abscam
+abscess
+abscessed
+abscesses
+abscessing
+abscession
+abscessroot
+abscind
+abscise
+abscised
+abscises
+abscisin
+abscising
+abscisins
+abscision
+absciss
+abscissa
+abscissae
+abscissas
+abscissa's
+abscisse
+abscissin
+abscission
+abscissions
+absconce
+abscond
+absconded
+abscondedly
+abscondence
+absconder
+absconders
+absconding
+absconds
+absconsa
+abscoulomb
+abscound
+Absecon
+absee
+absey
+abseil
+abseiled
+abseiling
+abseils
+absence
+absences
+absence's
+absent
+absentation
+absented
+absentee
+absenteeism
+absentees
+absentee's
+absenteeship
+absenter
+absenters
+absentia
+absenting
+absently
+absentment
+absentminded
+absent-minded
+absentmindedly
+absent-mindedly
+absentmindedness
+absent-mindedness
+absentmindednesses
+absentness
+absents
+absfarad
+abshenry
+Abshier
+Absi
+absinth
+absinthe
+absinthes
+absinthial
+absinthian
+absinthiate
+absinthiated
+absinthiating
+absinthic
+absinthiin
+absinthin
+absinthine
+absinthism
+absinthismic
+absinthium
+absinthol
+absinthole
+absinths
+Absyrtus
+absis
+absist
+absistos
+absit
+absmho
+absohm
+absoil
+absolent
+Absolute
+absolutely
+absoluteness
+absoluter
+absolutes
+absolutest
+absolution
+absolutions
+absolutism
+absolutist
+absolutista
+absolutistic
+absolutistically
+absolutists
+absolutive
+absolutization
+absolutize
+absolutory
+absolvable
+absolvatory
+absolve
+absolved
+absolvent
+absolver
+absolvers
+absolves
+absolving
+absolvitor
+absolvitory
+absonant
+absonous
+absorb
+absorbability
+absorbable
+absorbance
+absorbancy
+absorbant
+absorbed
+absorbedly
+absorbedness
+absorbefacient
+absorbency
+absorbencies
+absorbent
+absorbents
+absorber
+absorbers
+absorbing
+absorbingly
+absorbition
+absorbs
+absorbtion
+absorpt
+absorptance
+absorptiometer
+absorptiometric
+absorption
+absorptional
+absorptions
+absorption's
+absorptive
+absorptively
+absorptiveness
+absorptivity
+absquatulate
+absquatulation
+abstain
+abstained
+abstainer
+abstainers
+abstaining
+abstainment
+abstains
+abstemious
+abstemiously
+abstemiousness
+abstention
+abstentionism
+abstentionist
+abstentions
+abstentious
+absterge
+absterged
+abstergent
+absterges
+absterging
+absterse
+abstersion
+abstersive
+abstersiveness
+abstertion
+abstinence
+abstinences
+abstinency
+abstinent
+abstinential
+abstinently
+abstort
+abstr
+abstract
+abstractable
+abstracted
+abstractedly
+abstractedness
+abstracter
+abstracters
+abstractest
+abstracting
+abstraction
+abstractional
+abstractionism
+abstractionist
+abstractionists
+abstractions
+abstraction's
+abstractitious
+abstractive
+abstractively
+abstractiveness
+abstractly
+abstractness
+abstractnesses
+abstractor
+abstractors
+abstractor's
+abstracts
+abstrahent
+abstrict
+abstricted
+abstricting
+abstriction
+abstricts
+abstrude
+abstruse
+abstrusely
+abstruseness
+abstrusenesses
+abstruser
+abstrusest
+abstrusion
+abstrusity
+abstrusities
+absume
+absumption
+absurd
+absurder
+absurdest
+absurdism
+absurdist
+absurdity
+absurdities
+absurdity's
+absurdly
+absurdness
+absurds
+absurdum
+absvolt
+abt
+abterminal
+abthain
+abthainry
+abthainrie
+abthanage
+abtruse
+Abu
+abubble
+Abu-Bekr
+Abucay
+abucco
+abuilding
+Abukir
+abuleia
+Abulfeda
+abulia
+abulias
+abulic
+abulyeit
+abulomania
+abumbral
+abumbrellar
+Abuna
+abundance
+abundances
+abundancy
+abundant
+Abundantia
+abundantly
+abune
+abura
+aburabozu
+aburagiri
+aburban
+Abury
+aburst
+aburton
+abusable
+abusage
+abuse
+abused
+abusedly
+abusee
+abuseful
+abusefully
+abusefulness
+abuser
+abusers
+abuses
+abush
+abusing
+abusion
+abusious
+abusive
+abusively
+abusiveness
+abusivenesses
+abut
+Abuta
+Abutilon
+abutilons
+abutment
+abutments
+abuts
+abuttal
+abuttals
+abutted
+abutter
+abutters
+abutter's
+abutting
+abuzz
+abv
+abvolt
+abvolts
+abwab
+abwatt
+abwatts
+ac
+ac-
+a-c
+AC/DC
+ACAA
+Acacallis
+acacatechin
+acacatechol
+Acacea
+Acaceae
+acacetin
+Acacia
+Acacian
+acacias
+acaciin
+acacin
+acacine
+acad
+academe
+academes
+Academy
+academia
+academial
+academian
+academias
+Academic
+academical
+academically
+academicals
+academician
+academicians
+academicianship
+academicism
+academics
+academie
+academies
+academy's
+academise
+academised
+academising
+academism
+academist
+academite
+academization
+academize
+academized
+academizing
+Academus
+Acadia
+acadialite
+Acadian
+Acadie
+Acaena
+acajou
+acajous
+acal
+acalculia
+acale
+acaleph
+Acalepha
+Acalephae
+acalephan
+acalephe
+acalephes
+acalephoid
+acalephs
+Acalia
+acalycal
+acalycine
+acalycinous
+acalyculate
+Acalypha
+Acalypterae
+Acalyptrata
+Acalyptratae
+acalyptrate
+Acamar
+Acamas
+Acampo
+acampsia
+acana
+acanaceous
+acanonical
+acanth
+acanth-
+acantha
+Acanthaceae
+acanthaceous
+acanthad
+Acantharia
+acanthi
+Acanthia
+acanthial
+acanthin
+acanthine
+acanthion
+acanthite
+acantho-
+acanthocarpous
+Acanthocephala
+acanthocephalan
+Acanthocephali
+acanthocephalous
+Acanthocereus
+acanthocladous
+Acanthodea
+acanthodean
+Acanthodei
+Acanthodes
+acanthodian
+Acanthodidae
+Acanthodii
+Acanthodini
+acanthoid
+Acantholimon
+acantholysis
+acanthology
+acanthological
+acanthoma
+acanthomas
+Acanthomeridae
+acanthon
+Acanthopanax
+Acanthophis
+acanthophorous
+acanthopod
+acanthopodous
+acanthopomatous
+acanthopore
+acanthopteran
+Acanthopteri
+acanthopterygian
+Acanthopterygii
+acanthopterous
+acanthoses
+acanthosis
+acanthotic
+acanthous
+Acanthuridae
+Acanthurus
+acanthus
+acanthuses
+acanthuthi
+acapnia
+acapnial
+acapnias
+acappella
+acapsular
+acapu
+Acapulco
+acara
+Acarapis
+acarari
+acardia
+acardiac
+acardite
+acari
+acarian
+acariasis
+acariatre
+acaricidal
+acaricide
+acarid
+Acarida
+acaridae
+acaridan
+acaridans
+Acaridea
+acaridean
+acaridomatia
+acaridomatium
+acarids
+acariform
+Acarina
+acarine
+acarines
+acarinosis
+Acarnan
+acarocecidia
+acarocecidium
+acarodermatitis
+acaroid
+acarol
+acarology
+acarologist
+acarophilous
+acarophobia
+acarotoxic
+acarpellous
+acarpelous
+acarpous
+Acarus
+ACAS
+acast
+Acastus
+acatalectic
+acatalepsy
+acatalepsia
+acataleptic
+acatallactic
+acatamathesia
+acataphasia
+acataposis
+acatastasia
+acatastatic
+acate
+acategorical
+acater
+acatery
+acates
+acatharsy
+acatharsia
+acatholic
+acaudal
+acaudate
+acaudelescent
+acaulescence
+acaulescent
+acauline
+acaulose
+acaulous
+ACAWS
+ACB
+ACBL
+ACC
+acc.
+acca
+accable
+Accad
+accademia
+Accadian
+Accalia
+acce
+accede
+acceded
+accedence
+acceder
+acceders
+accedes
+acceding
+accel
+accel.
+accelerable
+accelerando
+accelerant
+accelerate
+accelerated
+acceleratedly
+accelerates
+accelerating
+acceleratingly
+acceleration
+accelerations
+accelerative
+accelerator
+acceleratory
+accelerators
+accelerograph
+accelerometer
+accelerometers
+accelerometer's
+accend
+accendibility
+accendible
+accensed
+accension
+accensor
+accent
+accented
+accenting
+accentless
+accentor
+accentors
+accents
+accentuable
+accentual
+accentuality
+accentually
+accentuate
+accentuated
+accentuates
+accentuating
+accentuation
+accentuations
+accentuator
+accentus
+accept
+acceptability
+acceptabilities
+acceptable
+acceptableness
+acceptably
+acceptance
+acceptances
+acceptance's
+acceptancy
+acceptancies
+acceptant
+acceptation
+acceptavit
+accepted
+acceptedly
+acceptee
+acceptees
+accepter
+accepters
+acceptilate
+acceptilated
+acceptilating
+acceptilation
+accepting
+acceptingly
+acceptingness
+acception
+acceptive
+acceptor
+acceptors
+acceptor's
+acceptress
+accepts
+accerse
+accersition
+accersitor
+access
+accessability
+accessable
+accessary
+accessaries
+accessarily
+accessariness
+accessaryship
+accessed
+accesses
+accessibility
+accessibilities
+accessible
+accessibleness
+accessibly
+accessing
+accession
+accessional
+accessioned
+accessioner
+accessioning
+accessions
+accession's
+accessit
+accessive
+accessively
+accessless
+accessor
+accessory
+accessorial
+accessories
+accessorii
+accessorily
+accessoriness
+accessory's
+accessorius
+accessoriusorii
+accessorize
+accessorized
+accessorizing
+accessors
+accessor's
+acciaccatura
+acciaccaturas
+acciaccature
+accidence
+accidency
+accidencies
+accident
+accidental
+accidentalism
+accidentalist
+accidentality
+accidentally
+accidentalness
+accidentals
+accidentary
+accidentarily
+accidented
+accidential
+accidentiality
+accidently
+accident-prone
+accidents
+accidia
+accidias
+accidie
+accidies
+accinge
+accinged
+accinging
+accipenser
+accipient
+Accipiter
+accipitral
+accipitrary
+Accipitres
+accipitrine
+accipter
+accise
+accismus
+accite
+Accius
+acclaim
+acclaimable
+acclaimed
+acclaimer
+acclaimers
+acclaiming
+acclaims
+acclamation
+acclamations
+acclamator
+acclamatory
+acclimatable
+acclimatation
+acclimate
+acclimated
+acclimatement
+acclimates
+acclimating
+acclimation
+acclimations
+acclimatisable
+acclimatisation
+acclimatise
+acclimatised
+acclimatiser
+acclimatising
+acclimatizable
+acclimatization
+acclimatizations
+acclimatize
+acclimatized
+acclimatizer
+acclimatizes
+acclimatizing
+acclimature
+acclinal
+acclinate
+acclivity
+acclivities
+acclivitous
+acclivous
+accloy
+accoast
+accoy
+accoyed
+accoying
+accoil
+Accokeek
+accolade
+accoladed
+accolades
+accolated
+accolent
+accoll
+accolle
+accolled
+accollee
+Accomac
+accombination
+accommodable
+accommodableness
+accommodate
+accommodated
+accommodately
+accommodateness
+accommodates
+accommodating
+accommodatingly
+accommodatingness
+accommodation
+accommodational
+accommodationist
+accommodations
+accommodative
+accommodatively
+accommodativeness
+accommodator
+accommodators
+accomodate
+accompanable
+accompany
+accompanied
+accompanier
+accompanies
+accompanying
+accompanyist
+accompaniment
+accompanimental
+accompaniments
+accompaniment's
+accompanist
+accompanists
+accompanist's
+accomplement
+accompletive
+accompli
+accomplice
+accomplices
+accompliceship
+accomplicity
+accomplis
+accomplish
+accomplishable
+accomplished
+accomplisher
+accomplishers
+accomplishes
+accomplishing
+accomplishment
+accomplishments
+accomplishment's
+accomplisht
+accompt
+accord
+accordable
+accordance
+accordances
+accordancy
+accordant
+accordantly
+accordatura
+accordaturas
+accordature
+accorded
+accorder
+accorders
+according
+accordingly
+accordion
+accordionist
+accordionists
+accordions
+accordion's
+accords
+accorporate
+accorporation
+accost
+accostable
+accosted
+accosting
+accosts
+accouche
+accouchement
+accouchements
+accoucheur
+accoucheurs
+accoucheuse
+accoucheuses
+accounsel
+account
+accountability
+accountabilities
+accountable
+accountableness
+accountably
+accountancy
+accountancies
+accountant
+accountants
+accountant's
+accountantship
+accounted
+accounter
+accounters
+accounting
+accountings
+accountment
+accountrement
+accounts
+accouple
+accouplement
+accourage
+accourt
+accouter
+accoutered
+accoutering
+accouterment
+accouterments
+accouters
+accoutre
+accoutred
+accoutrement
+accoutrements
+accoutres
+accoutring
+Accoville
+ACCRA
+accrease
+accredit
+accreditable
+accreditate
+accreditation
+accreditations
+accredited
+accreditee
+accrediting
+accreditment
+accredits
+accrementitial
+accrementition
+accresce
+accrescence
+accrescendi
+accrescendo
+accrescent
+accretal
+accrete
+accreted
+accretes
+accreting
+accretion
+accretionary
+accretions
+accretion's
+accretive
+accriminate
+Accrington
+accroach
+accroached
+accroaching
+accroachment
+accroides
+accruable
+accrual
+accruals
+accrue
+accrued
+accruement
+accruer
+accrues
+accruing
+ACCS
+ACCT
+acct.
+accts
+accubation
+accubita
+accubitum
+accubitus
+accueil
+accultural
+acculturate
+acculturated
+acculturates
+acculturating
+acculturation
+acculturational
+acculturationist
+acculturative
+acculturize
+acculturized
+acculturizing
+accum
+accumb
+accumbency
+accumbent
+accumber
+accumulable
+accumulate
+accumulated
+accumulates
+accumulating
+accumulation
+accumulations
+accumulativ
+accumulative
+accumulatively
+accumulativeness
+accumulator
+accumulators
+accumulator's
+accupy
+accur
+accuracy
+accuracies
+accurate
+accurately
+accurateness
+accuratenesses
+accurre
+accurse
+accursed
+accursedly
+accursedness
+accursing
+accurst
+accurtation
+accus
+accusable
+accusably
+accusal
+accusals
+accusant
+accusants
+accusation
+accusations
+accusation's
+accusatival
+accusative
+accusative-dative
+accusatively
+accusativeness
+accusatives
+accusator
+accusatory
+accusatorial
+accusatorially
+accusatrix
+accusatrixes
+accuse
+accused
+accuser
+accusers
+accuses
+accusing
+accusingly
+accusive
+accusor
+accustom
+accustomation
+accustomed
+accustomedly
+accustomedness
+accustoming
+accustomize
+accustomized
+accustomizing
+accustoms
+Accutron
+ACD
+ACDA
+AC-DC
+ACE
+acea
+aceacenaphthene
+aceae
+acean
+aceanthrene
+aceanthrenequinone
+acecaffin
+acecaffine
+aceconitic
+aced
+acedy
+acedia
+acediamin
+acediamine
+acedias
+acediast
+ace-high
+Acey
+acey-deucy
+aceite
+aceituna
+Aceldama
+aceldamas
+acellular
+Acemetae
+Acemetic
+acemila
+acenaphthene
+acenaphthenyl
+acenaphthylene
+acenesthesia
+acensuada
+acensuador
+acentric
+acentrous
+aceology
+aceologic
+aceous
+acephal
+Acephala
+acephalan
+Acephali
+acephalia
+Acephalina
+acephaline
+acephalism
+acephalist
+Acephalite
+acephalocyst
+acephalous
+acephalus
+acepots
+acequia
+acequiador
+acequias
+Acer
+Aceraceae
+aceraceous
+Acerae
+Acerata
+acerate
+acerated
+Acerates
+acerathere
+Aceratherium
+aceratosis
+acerb
+Acerbas
+acerbate
+acerbated
+acerbates
+acerbating
+acerber
+acerbest
+acerbic
+acerbically
+acerbity
+acerbityacerose
+acerbities
+acerbitude
+acerbly
+acerbophobia
+acerdol
+aceric
+acerin
+acerli
+acerola
+acerolas
+acerose
+acerous
+acerra
+acers
+acertannin
+acerval
+acervate
+acervately
+acervatim
+acervation
+acervative
+acervose
+acervuli
+acervuline
+acervulus
+aces
+ace's
+acescence
+acescency
+acescent
+acescents
+aceship
+Acesius
+acesodyne
+acesodynous
+Acessamenus
+Acestes
+acestoma
+acet-
+aceta
+acetable
+acetabula
+acetabular
+Acetabularia
+acetabuliferous
+acetabuliform
+acetabulous
+acetabulum
+acetabulums
+acetacetic
+acetal
+acetaldehydase
+acetaldehyde
+acetaldehydrase
+acetaldol
+acetalization
+acetalize
+acetals
+acetamid
+acetamide
+acetamidin
+acetamidine
+acetamido
+acetamids
+acetaminol
+Acetaminophen
+acetanilid
+acetanilide
+acetanion
+acetaniside
+acetanisidide
+acetanisidine
+acetannin
+acetary
+acetarious
+acetars
+acetarsone
+acetate
+acetated
+acetates
+acetation
+acetazolamide
+acetbromamide
+acetenyl
+Acetes
+acethydrazide
+acetiam
+acetic
+acetify
+acetification
+acetified
+acetifier
+acetifies
+acetifying
+acetyl
+acetylacetonates
+acetylacetone
+acetylamine
+acetylaminobenzene
+acetylaniline
+acetylasalicylic
+acetylate
+acetylated
+acetylating
+acetylation
+acetylative
+acetylator
+acetylbenzene
+acetylbenzoate
+acetylbenzoic
+acetylbiuret
+acetylcarbazole
+acetylcellulose
+acetylcholine
+acetylcholinesterase
+acetylcholinic
+acetylcyanide
+acetylenation
+acetylene
+acetylenediurein
+acetylenes
+acetylenic
+acetylenyl
+acetylenogen
+acetylfluoride
+acetylglycin
+acetylglycine
+acetylhydrazine
+acetylic
+acetylid
+acetylide
+acetyliodide
+acetylizable
+acetylization
+acetylize
+acetylized
+acetylizer
+acetylizing
+acetylmethylcarbinol
+acetylperoxide
+acetylphenylhydrazine
+acetylphenol
+acetylrosaniline
+acetyls
+acetylsalicylate
+acetylsalicylic
+acetylsalol
+acetyltannin
+acetylthymol
+acetyltropeine
+acetylurea
+acetimeter
+acetimetry
+acetimetric
+acetin
+acetine
+acetins
+acetite
+acetize
+acetla
+acetmethylanilide
+acetnaphthalide
+aceto-
+acetoacetanilide
+acetoacetate
+acetoacetic
+acetoamidophenol
+acetoarsenite
+Acetobacter
+acetobenzoic
+acetobromanilide
+acetochloral
+acetocinnamene
+acetoin
+acetol
+acetolysis
+acetolytic
+acetometer
+acetometry
+acetometric
+acetometrical
+acetometrically
+acetomorphin
+acetomorphine
+acetonaemia
+acetonaemic
+acetonaphthone
+acetonate
+acetonation
+acetone
+acetonemia
+acetonemic
+acetones
+acetonic
+acetonyl
+acetonylacetone
+acetonylidene
+acetonitrile
+acetonization
+acetonize
+acetonuria
+acetonurometer
+acetophenetide
+acetophenetidin
+acetophenetidine
+acetophenin
+acetophenine
+acetophenone
+acetopiperone
+acetopyrin
+acetopyrine
+acetosalicylic
+acetose
+acetosity
+acetosoluble
+acetostearin
+acetothienone
+acetotoluid
+acetotoluide
+acetotoluidine
+acetous
+acetoveratrone
+acetoxyl
+acetoxyls
+acetoxim
+acetoxime
+acetoxyphthalide
+acetphenetid
+acetphenetidin
+acetract
+acettoluide
+acetum
+aceturic
+ACF
+ACGI
+ac-globulin
+ACH
+Achab
+Achad
+Achaea
+Achaean
+Achaemenes
+Achaemenian
+Achaemenid
+Achaemenidae
+Achaemenides
+Achaemenidian
+Achaemenids
+achaenocarp
+Achaenodon
+Achaeta
+achaetous
+Achaeus
+achafe
+achage
+Achagua
+Achaia
+Achaian
+Achakzai
+achalasia
+Achamoth
+Achan
+Achango
+achape
+achaque
+achar
+acharya
+Achariaceae
+Achariaceous
+acharne
+acharnement
+Acharnians
+achate
+Achates
+Achatina
+Achatinella
+Achatinidae
+achatour
+Achaz
+ache
+acheat
+achech
+acheck
+ached
+acheer
+ACHEFT
+acheilary
+acheilia
+acheilous
+acheiria
+acheirous
+acheirus
+Achelous
+Achen
+achene
+achenes
+achenia
+achenial
+achenium
+achenocarp
+achenodia
+achenodium
+acher
+Acherman
+Achernar
+Acheron
+Acheronian
+Acherontic
+Acherontical
+aches
+Acheson
+achesoun
+achete
+Achetidae
+Acheulean
+Acheulian
+acheweed
+achy
+achier
+achiest
+achievability
+achievable
+achieve
+achieved
+achievement
+achievements
+achievement's
+achiever
+achievers
+achieves
+achieving
+ach-y-fi
+achigan
+achilary
+achylia
+Achill
+Achille
+Achillea
+Achillean
+achilleas
+Achilleid
+achillein
+achilleine
+Achilles
+Achillize
+achillobursitis
+achillodynia
+achilous
+achylous
+Achimaas
+achime
+Achimelech
+Achimenes
+achymia
+achymous
+Achinese
+achiness
+achinesses
+aching
+achingly
+achiote
+achiotes
+achira
+Achyranthes
+achirite
+Achyrodes
+Achish
+Achitophel
+achkan
+achlamydate
+Achlamydeae
+achlamydeous
+achlorhydria
+achlorhydric
+achlorophyllous
+achloropsia
+achluophobia
+Achmed
+Achmetha
+achoke
+acholia
+acholias
+acholic
+Acholoe
+acholous
+acholuria
+acholuric
+Achomawi
+achondrite
+achondritic
+achondroplasia
+achondroplastic
+achoo
+achor
+achordal
+Achordata
+achordate
+Achorion
+Achorn
+Achras
+achree
+achroacyte
+Achroanthes
+achrodextrin
+achrodextrinase
+achroglobin
+achroiocythaemia
+achroiocythemia
+achroite
+achroma
+achromacyte
+achromasia
+achromat
+achromat-
+achromate
+Achromatiaceae
+achromatic
+achromatically
+achromaticity
+achromatin
+achromatinic
+achromatisation
+achromatise
+achromatised
+achromatising
+achromatism
+Achromatium
+achromatizable
+achromatization
+achromatize
+achromatized
+achromatizing
+achromatocyte
+achromatolysis
+achromatope
+achromatophil
+achromatophile
+achromatophilia
+achromatophilic
+achromatopia
+achromatopsy
+achromatopsia
+achromatosis
+achromatous
+achromats
+achromaturia
+achromia
+achromic
+Achromycin
+Achromobacter
+Achromobacterieae
+achromoderma
+achromophilous
+achromotrichia
+achromous
+achronical
+achronychous
+achronism
+achroo-
+achroodextrin
+achroodextrinase
+achroous
+achropsia
+Achsah
+achtehalber
+achtel
+achtelthaler
+achter
+achterveld
+Achuas
+achuete
+acy
+acyanoblepsia
+acyanopsia
+acichlorid
+acichloride
+acyclic
+acyclically
+acicula
+aciculae
+acicular
+acicularity
+acicularly
+aciculas
+aciculate
+aciculated
+aciculum
+aciculums
+acid
+acidaemia
+Acidalium
+Acidanthera
+Acidaspis
+acid-binding
+acidemia
+acidemias
+acider
+acid-fast
+acid-fastness
+acid-forming
+acidhead
+acid-head
+acidheads
+acidy
+acidic
+acidiferous
+acidify
+acidifiable
+acidifiant
+acidific
+acidification
+acidified
+acidifier
+acidifiers
+acidifies
+acidifying
+acidyl
+acidimeter
+acidimetry
+acidimetric
+acidimetrical
+acidimetrically
+acidite
+acidity
+acidities
+acidize
+acidized
+acidizing
+acidly
+acidness
+acidnesses
+acidogenic
+acidoid
+acidolysis
+acidology
+acidometer
+acidometry
+acidophil
+acidophile
+acidophilic
+acidophilous
+acidophilus
+acidoproteolytic
+acidoses
+acidosis
+acidosteophyte
+acidotic
+acidproof
+acids
+acid-treat
+acidulant
+acidulate
+acidulated
+acidulates
+acidulating
+acidulation
+acidulent
+acidulous
+acidulously
+acidulousness
+aciduria
+acidurias
+aciduric
+Acie
+acier
+acierage
+Acieral
+acierate
+acierated
+acierates
+acierating
+acieration
+acies
+acyesis
+acyetic
+aciform
+acyl
+acylal
+acylamido
+acylamidobenzene
+acylamino
+acylase
+acylate
+acylated
+acylates
+acylating
+acylation
+aciliate
+aciliated
+Acilius
+acylogen
+acyloin
+acyloins
+acyloxy
+acyloxymethane
+acyls
+Acima
+acinaceous
+acinaces
+acinacifoliate
+acinacifolious
+acinaciform
+acinacious
+acinacity
+acinar
+acinary
+acinarious
+Acineta
+Acinetae
+acinetan
+Acinetaria
+acinetarian
+acinetic
+acinetiform
+Acinetina
+acinetinan
+acing
+acini
+acinic
+aciniform
+acinose
+acinotubular
+acinous
+acinuni
+acinus
+acious
+Acipenser
+Acipenseres
+acipenserid
+Acipenseridae
+acipenserine
+acipenseroid
+Acipenseroidei
+acyrology
+acyrological
+Acis
+acystia
+acitate
+acity
+aciurgy
+ACK
+ack-ack
+ackee
+ackees
+ackey
+ackeys
+Acker
+Ackerley
+Ackerly
+Ackerman
+Ackermanville
+Ackley
+Ackler
+ackman
+ackmen
+acknew
+acknow
+acknowing
+acknowledge
+acknowledgeable
+acknowledged
+acknowledgedly
+acknowledgement
+acknowledgements
+acknowledger
+acknowledgers
+acknowledges
+acknowledging
+acknowledgment
+acknowledgments
+acknowledgment's
+acknown
+ack-pirate
+ackton
+Ackworth
+ACL
+aclastic
+acle
+acleidian
+acleistocardia
+acleistous
+Aclemon
+aclydes
+aclidian
+aclinal
+aclinic
+aclys
+a-clock
+acloud
+ACLS
+ACLU
+ACM
+Acmaea
+Acmaeidae
+acmaesthesia
+acmatic
+acme
+acmes
+acmesthesia
+acmic
+Acmispon
+acmite
+Acmon
+acne
+acned
+acneform
+acneiform
+acnemia
+acnes
+Acnida
+acnodal
+acnode
+acnodes
+ACO
+acoasm
+acoasma
+a-coast
+Acocanthera
+acocantherin
+acock
+acockbill
+a-cock-bill
+a-cock-horse
+acocotl
+Acoela
+Acoelomata
+acoelomate
+acoelomatous
+Acoelomi
+acoelomous
+acoelous
+Acoemetae
+Acoemeti
+Acoemetic
+acoenaesthesia
+ACOF
... 464798 lines suppressed ...