Jshook/verification (#2107)

* docs update

* typos

* disable noisy prom exposition dump

* fix refkey bug

* move specs into non-test namespace

* remove unimplemented rainbow ops

* docs updates

* give credit to maintainers

* update milvus module to build clean on new APIs

* remove var keyword

* API remapping

* enable new op behaviors
This commit is contained in:
Jonathan Shook
2024-12-10 15:41:05 -06:00
committed by GitHub
parent 5fcc3b27fd
commit ca38a710c3
184 changed files with 4052 additions and 2209 deletions

View File

@@ -91,7 +91,15 @@
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.md</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@@ -18,6 +18,7 @@ package io.nosqlbench.adapters.api.activityconfig;
import com.amazonaws.util.StringInputStream;
import com.google.gson.GsonBuilder;
import io.nosqlbench.adapters.api.activityconfig.yaml.*;
import io.nosqlbench.nb.api.nbio.Content;
import io.nosqlbench.nb.api.nbio.NBIO;
import io.nosqlbench.nb.api.nbio.ResolverChain;
@@ -25,8 +26,6 @@ import io.nosqlbench.nb.api.advisor.NBAdvisorException;
import io.nosqlbench.nb.api.errors.BasicError;
import io.nosqlbench.adapters.api.activityconfig.rawyaml.RawOpsDocList;
import io.nosqlbench.adapters.api.activityconfig.rawyaml.RawOpsLoader;
import io.nosqlbench.adapters.api.activityconfig.yaml.OpTemplateFormat;
import io.nosqlbench.adapters.api.activityconfig.yaml.OpsDocList;
import io.nosqlbench.adapters.api.templating.StrInterpolator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -46,6 +45,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/// This class is responsible for loading op templates.
public class OpsLoader {
private final static Logger logger = LogManager.getLogger(OpsLoader.class);
@@ -58,7 +58,7 @@ public class OpsLoader {
}
public static OpsDocList loadPath(String path, Map<String, ?> params, String... searchPaths) {
String[] extensions = path.indexOf('.')>-1 ? new String[]{} : YAML_EXTENSIONS;
String[] extensions = path.indexOf('.') > -1 ? new String[]{} : YAML_EXTENSIONS;
ResolverChain chain = new ResolverChain(path);
Content<?> foundPath = NBIO.chain(chain.getChain()).searchPrefixes(searchPaths).pathname(chain.getPath()).extensionSet(extensions).first()
.orElseThrow(() -> new RuntimeException("Unable to load path '" + path + "'"));
@@ -66,10 +66,11 @@ public class OpsLoader {
return loadString(foundPath.asString(), fmt, params, foundPath.getURI());
}
public static OpsDocList loadString(final String sourceData, OpTemplateFormat fmt, Map<String, ?> params, URI srcuri) {
public static OpsDocList loadString(
final String sourceData, OpTemplateFormat fmt, Map<String, ?> params, URI srcuri) {
logger.trace(() -> "Applying string transformer to data:" + sourceData);
if (srcuri!=null) {
if (srcuri != null) {
logger.info("workload URI: '" + srcuri + "'");
}
StrInterpolator transformer = new StrInterpolator(params);
@@ -84,7 +85,6 @@ public class OpsLoader {
// TODO: itemize inline to support ParamParser
OpsDocList layered = new OpsDocList(rawOpsDocList);
transformer.checkpointAccesses().forEach((k, v) -> {
layered.addTemplateVariable(k, v);
params.remove(k);
@@ -95,9 +95,9 @@ public class OpsLoader {
private static String evaluateJsonnet(URI uri, Map<String, ?> params) {
List<String> injected = new LinkedList<>(List.of(Path.of(uri).toString()));
params.forEach((k,v) -> {
params.forEach((k, v) -> {
if (v instanceof CharSequence cs) {
injected.addAll(List.of("--ext-str",k+"="+cs));
injected.addAll(List.of("--ext-str", k + "=" + cs));
}
});
@@ -113,15 +113,9 @@ public class OpsLoader {
}
int resultStatus = SjsonnetMain.main0(
injected.toArray(new String[0]),
new DefaultParseCache(),
inputStream,
stdoutStream,
stderrStream,
new os.Path(Path.of(System.getProperty("user.dir"))),
Option.empty(),
Option.empty(),
null
injected.toArray(new String[0]), new DefaultParseCache(), inputStream, stdoutStream,
stderrStream, new os.Path(Path.of(System.getProperty("user.dir"))), Option.empty(),
Option.empty(), null
);
String stdoutOutput = stdoutBuffer.toString(StandardCharsets.UTF_8);
@@ -130,7 +124,7 @@ public class OpsLoader {
logger.info("dryrun=jsonnet, dumping result to stdout and stderr:");
System.out.println(stdoutOutput);
System.err.println(stderrOutput);
if (resultStatus==0 && stderrOutput.isEmpty()) {
if (resultStatus == 0 && stderrOutput.isEmpty()) {
logger.info("no errors detected during jsonnet evaluation.");
throw new NBAdvisorException("dryrun=jsonnet: No errors detected.", 0);
} else {
@@ -139,14 +133,16 @@ public class OpsLoader {
}
}
if (!stderrOutput.isEmpty()) {
BasicError error = new BasicError("stderr output from jsonnet preprocessing: " + stderrOutput);
if (resultStatus!=0) {
BasicError error = new BasicError(
"stderr output from jsonnet preprocessing: " + stderrOutput);
if (resultStatus != 0) {
throw error;
} else {
logger.warn(error.toString(),error);
logger.warn(error.toString(), error);
}
}
logger.info("jsonnet processing read '" + uri +"', rendered " + stdoutOutput.split("\n").length + " lines.");
logger.info("jsonnet processing read '" + uri + "', rendered " + stdoutOutput.split(
"\n").length + " lines.");
logger.trace("jsonnet result:\n" + stdoutOutput);
return stdoutOutput;

View File

@@ -24,19 +24,20 @@ import org.apache.logging.log4j.Logger;
import java.util.*;
/**
* See specification for what this should do in UniformWorkloadSpecificationTest
*/
See specification for what this should do in UniformWorkloadSpecificationTest */
public class RawOpDef extends RawOpFields {
private final static Logger logger = LogManager.getLogger(RawOpDef.class);
/**
* Contains all the op fields. If the key <em>params</em> is used, then fields are divided
* between the op fields map and the params map, with the non-specified one soaking up the dangling
* op fields. (Those not under 'op' or 'params' and which are not reserverd words)
Contains all the op fields. If the key <em>params</em> is used, then fields are divided
between the op fields map and the params map, with the non-specified one soaking up the dangling
op fields. (Those not under 'op' or 'params' and which are not reserverd words)
*/
private Object op;
private final static List<String> opFieldSynonyms = List.of("stmt", "statement", "op", "operation");
private final static List<String> opFieldSynonyms = List.of(
"stmt", "statement", "op", "operation");
private int refKey;
public RawOpDef() {
}
@@ -71,14 +72,15 @@ public class RawOpDef extends RawOpFields {
// if (!keyName.equals("stmt")) {
// logger.info("Used implied stmt field under name '" + keyName + "'. You can just use 'stmt: ... "+ s +"' or the equivalent to avoid this warning.");
// }
map.put("stmt",s.toString());
map.put("stmt", s.toString());
// setOp(new LinkedHashMap<String,Object>(Map.of("stmt",s.toString())));
} else {
setOp(op);
}
}
if (found.size() > 1) {
throw new BasicError("You used " + found + " as an op name, but only one of these is allowed at a time.");
throw new BasicError(
"You used " + found + " as an op name, but only one of these is allowed at a time.");
} else if ((getName() == null || getName().isEmpty()) && op == null && !map.isEmpty()) {
Map.Entry<String, Object> first = map.entrySet().iterator().next();
setName(first.getKey());
@@ -91,7 +93,8 @@ public class RawOpDef extends RawOpFields {
if (_op) {
if (_params) {
if (!map.isEmpty()) {
throw new OpConfigError("If you have scoped op and params, you may not have dangling fields. Op template named '" + this.getName() + "' is invalid. Move dangling params ("+ map.keySet() +") under another field.");
throw new OpConfigError(
"If you have scoped op and params, you may not have dangling fields. Op template named '" + this.getName() + "' is invalid. Move dangling params (" + map.keySet() + ") under another field.");
}
} else { // no params. Op was a scoped field and there are dangling fields, so assume they belong to params
getParams().putAll(map);
@@ -136,12 +139,11 @@ public class RawOpDef extends RawOpFields {
private void checkForUnintendedJsonMap(Object m, List<String> path) {
if (m instanceof Map) {
((Map)m).forEach((k,v) -> {
((Map) m).forEach((k, v) -> {
if (v == null) {
throw new OpConfigError("A map key '" + k.toString() + "' with a null value was encountered. This is not" +
" allowed, and may be the result of using an unquoted binding, like {" + k + "}. You can simply wrap this in quotes" +
" like \"{"+ k +"}\" to avoid interpreting this as a JSON map." +
(path.size()>0 ? String.join(".",path):""));
throw new OpConfigError(
"A map key '" + k.toString() + "' with a null value was encountered. This is not" + " allowed, and may be the result of using an unquoted binding, like {" + k + "}. You can simply wrap this in quotes" + " like \"{" + k + "}\" to avoid interpreting this as a JSON map." + (path.size() > 0 ? String.join(
".", path) : ""));
} else {
if (v instanceof Map) {
path.add(k.toString());
@@ -152,4 +154,17 @@ public class RawOpDef extends RawOpFields {
}
}
public void setRefKey(int refKey) {
this.refKey = refKey;
}
/**
* Get an integer key for the op template for this workload template, based on enumeration of all
* active op templates. This value is stable within the current instance of the workload template only.
* @return a unique integer key for this op template within the workload template
*/
public int getRefKey() {
return this.refKey;
}
}

View File

@@ -34,7 +34,7 @@ import java.util.function.Function;
public class RawOpsLoader {
private final static Logger logger = LogManager.getLogger(RawOpsLoader.class);
public static String[] YAML_EXTENSIONS = new String[]{"yaml","yml"};
public static String[] YAML_EXTENSIONS = new String[]{"yaml", "yml"};
private final ArrayList<Function<String,String>> transformers = new ArrayList<>();
@@ -113,6 +113,15 @@ public class RawOpsLoader {
}
}
RawOpsDocList rawOpsDocList = new RawOpsDocList(newDocList);
int refkey = 0;
for (RawOpsDoc rawOpsBlocks : rawOpsDocList) {
for (RawOpsBlock rawOpsBlock : rawOpsBlocks) {
for (RawOpDef rawOpDef : rawOpsBlock.getRawOpDefs()) {
rawOpDef.setRefKey(refkey++);
}
}
}
return rawOpsDocList;
}

View File

@@ -16,6 +16,8 @@
package io.nosqlbench.adapters.api.activityconfig.yaml;
import org.jetbrains.annotations.NotNull;
import java.security.InvalidParameterException;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -29,47 +31,61 @@ public class OpData extends OpTemplate {
private Map<String, Object> params = Map.of();
private Map<String, String> bindings = Map.of();
private Map<String, String> tags = new LinkedHashMap<>();
private int refKey;
private int refkey;
public OpData(String desc, String name, Map<String, String> tags, Map<String, String> bindings, Map<String, Object> params, Map<String, Object> op) {
public OpData(
String desc, String name, Map<String, String> tags, Map<String, String> bindings,
Map<String, Object> params, Map<String, Object> op, int refkey
) {
this.desc = desc;
this.name = name;
this.tags = tags;
this.bindings = bindings;
this.params = params;
this.op = op;
this.refkey = refkey;
}
public OpData() {}
public OpData() {
}
public OpData(Map<String,Object> opdata) {
public OpData(Map<String, Object> opdata) {
applyFields(opdata);
if (opdata.size()>0) {
throw new RuntimeException("Unconsumed fields in construction of op data from map: " + opdata);
if (opdata.size() > 0) {
throw new RuntimeException(
"Unconsumed fields in construction of op data from map: " + opdata);
}
}
public OpData applyFields(Map<String,Object> opdata) {
LinkedHashMap<String,Object> toapply = new LinkedHashMap<>(opdata);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_DESC)).ifPresent(v -> this.setDesc(v.toString()));
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_NAME)).ifPresent(v -> this.setName(v.toString()));
public OpData applyFields(Map<String, Object> opdata) {
LinkedHashMap<String, Object> toapply = new LinkedHashMap<>(opdata);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_DESC)).ifPresent(
v -> this.setDesc(v.toString()));
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_NAME)).ifPresent(
v -> this.setName(v.toString()));
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_BINDINGS)).ifPresent(this::setBindings);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_OP)).ifPresent(this::setOp);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_PARAMS)).ifPresent(this::setParams);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_TAGS)).ifPresent(this::setTags);
Optional.ofNullable(toapply.remove(OpTemplate.FIELD_REFKEY)).map(
v -> (Integer) v).ifPresent(this::setRefKey);
if (toapply.size()>0) {
if (toapply.size() > 0) {
throw new InvalidParameterException("Fields were not applied to OpData:" + toapply);
}
return this;
}
private void setRefKey(int refkey) {
this.refkey = refkey;
}
private void setTags(Object o) {
if (o instanceof Map) {
((Map<?, ?>) o).forEach((k,v) -> {
this.tags.put(k.toString(),v.toString());
((Map<?, ?>) o).forEach((k, v) -> {
this.tags.put(k.toString(), v.toString());
});
} else {
throw new RuntimeException("Invalid type for tags: " + o.getClass().getSimpleName());
@@ -79,8 +95,8 @@ public class OpData extends OpTemplate {
private void setParams(Object o) {
if (o instanceof Map) {
this.params = new LinkedHashMap<>();
((Map<?, ?>) o).forEach((k,v) -> {
this.params.put(k.toString(),v);
((Map<?, ?>) o).forEach((k, v) -> {
this.params.put(k.toString(), v);
});
} else {
throw new RuntimeException("Invalid type for params: " + op.getClass().getSimpleName());
@@ -89,9 +105,9 @@ public class OpData extends OpTemplate {
private void setOp(Object o) {
if (o instanceof CharSequence) {
this.op = new LinkedHashMap<>(Map.of("stmt",o.toString()));
this.op = new LinkedHashMap<>(Map.of("stmt", o.toString()));
} else if (o instanceof Map) {
this.op = new LinkedHashMap<>((Map)o);
this.op = new LinkedHashMap<>((Map) o);
} else {
throw new RuntimeException("Invalid type for op:" + op.getClass().getSimpleName());
}
@@ -100,17 +116,18 @@ public class OpData extends OpTemplate {
private void setBindings(Object bindings) {
if (bindings instanceof Map) {
this.bindings = new LinkedHashMap<>();
((Map<?, ?>) bindings).forEach((k,v) -> {
this.bindings.put(k.toString(),v.toString());
((Map<?, ?>) bindings).forEach((k, v) -> {
this.bindings.put(k.toString(), v.toString());
});
} else if (bindings!=null) {
throw new RuntimeException("Invalid type for bindings: " + bindings.getClass().getSimpleName());
} else if (bindings != null) {
throw new RuntimeException(
"Invalid type for bindings: " + bindings.getClass().getSimpleName());
}
}
private void setName(String name) {
this.name = name;
this.tags.put("name",name);
this.tags.put("name", name);
}
private void setDesc(String desc) {
@@ -142,16 +159,6 @@ public class OpData extends OpTemplate {
return this.params;
}
@Override
public int getRefKey() {
return refKey;
}
@Override
public void setRefKey(int refKey) {
this.refKey = refKey;
}
@Override
public Optional<Map<String, Object>> getOp() {
return Optional.of(this.op);
@@ -161,4 +168,9 @@ public class OpData extends OpTemplate {
public Optional<String> getStmt() {
return Optional.empty();
}
@Override
public int getRefKey() {
return refkey;
}
}

View File

@@ -32,7 +32,7 @@ public class OpDef extends OpTemplate {
private final LinkedHashMap<String, Object> params;
private final LinkedHashMap<String, String> bindings;
private final LinkedHashMap<String, String> tags;
private int refKey;
private int refkey;
public OpDef(OpsBlock block, RawOpDef rawOpDef) {
this.block = block;
@@ -40,6 +40,7 @@ public class OpDef extends OpTemplate {
this.params = composeParams();
this.bindings = composeBindings();
this.tags = composeTags();
this.refkey = rawOpDef.getRefKey();
}
@Override
@@ -69,6 +70,11 @@ public class OpDef extends OpTemplate {
return Optional.of(newmap);
}
@Override
public int getRefKey() {
return this.refkey;
}
@Override
public LinkedHashMap<String, String> getBindings() {
return bindings;
@@ -85,24 +91,12 @@ public class OpDef extends OpTemplate {
return params;
}
@Override
public int getRefKey() {
return this.refKey;
}
@Override
public void setRefKey(int refKey) {
this.refKey = refKey;
}
private LinkedHashMap<String, Object> composeParams() {
MultiMapLookup<Object> lookup = new MultiMapLookup<>(rawOpDef.getParams(), block.getParams());
LinkedHashMap<String, Object> params = new LinkedHashMap<>(lookup);
return params;
}
@Override
public Map<String, String> getTags() {
return tags;

View File

@@ -45,7 +45,7 @@ import java.util.function.Function;
* for UniformWorkloadSpecification directly to see how this specification is tested and documented.
* </p>
*/
public abstract class OpTemplate implements Tagged {
public abstract class OpTemplate implements Tagged, OpTemplateProps {
private final static Gson gson = new GsonBuilder().setPrettyPrinting().create();
// TODO: coalesce Gson instances to a few statics on a central NB API class
@@ -57,16 +57,7 @@ public abstract class OpTemplate implements Tagged {
public final static String FIELD_PARAMS = "params";
public final static String FIELD_TAGS = "tags";
public final static String FIELD_REFKEY = "refkey";
/**
* @return a description for the op template, or an empty string
*/
public abstract String getDesc();
/**
* @return a name for the op template, user-specified or auto-generated
*/
public abstract String getName();
private int refKey = -1;
/**
* Return a map of tags for this statement. Implementations are required to
@@ -74,12 +65,9 @@ public abstract class OpTemplate implements Tagged {
*
* @return A map of assigned tags for the op, with the name added as an auto-tag.
*/
@Override
public abstract Map<String, String> getTags();
public abstract Map<String, String> getBindings();
public abstract Map<String, Object> getParams();
public <T> Map<String, T> getParamsAsValueType(Class<? extends T> type) {
Map<String, T> map = new LinkedHashMap<>();
for (String pname : getParams().keySet()) {
@@ -130,13 +118,7 @@ public abstract class OpTemplate implements Tagged {
}
}
/**
* Get an integer key for the op template for this workload template, based on enumeration of all
* active op templates. This value is stable within the current instance of the workload template only.
* @return a unique integer key for this op template within the workload template
*/
public abstract int getRefKey();
public abstract void setRefKey(int refKey);
public <V> V getParam(String name, Class<? extends V> type) {
Object object = getParams().get(name);
@@ -196,11 +178,11 @@ public abstract class OpTemplate implements Tagged {
return getStmt().map(s -> new ParsedTemplateString(s, getBindings()));
}
public abstract Optional<Map<String, Object>> getOp();
public Map<String, Object> asData() {
LinkedHashMap<String, Object> fields = new LinkedHashMap<>();
// fields.put(FIELD_REFKEY, this.getRefKey());
if (this.getDesc() != null && !this.getDesc().isBlank()) {
fields.put(FIELD_DESC, this.getDesc());
}
@@ -266,4 +248,5 @@ public abstract class OpTemplate implements Tagged {
throw new OpConfigError("The op template named '" + getName() + "' was not fully consumed. These fields are not being applied:" + remainingFields());
}
}
}

View File

@@ -0,0 +1,46 @@
package io.nosqlbench.adapters.api.activityconfig.yaml;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/
import java.util.Map;
import java.util.Optional;
public interface OpTemplateProps {
/**
* @return a description for the op template, or an empty string
*/
String getDesc();
/**
* @return a name for the op template, user-specified or auto-generated
*/
String getName();
Map<String, String> getTags();
Map<String, String> getBindings();
Map<String, Object> getParams();
Optional<Map<String, Object>> getOp();
Optional<String> getStmt();
int getRefKey();
}

View File

@@ -40,7 +40,7 @@ public class OpsDocList implements Iterable<OpsDoc> {
public OpsDocList(RawOpsDocList rawOpsDocList) {
this.rawOpsDocList = rawOpsDocList;
this.applyModifier(new enumerator());
// this.applyModifier(new enumerator());
}
public static OpsDocList none() {
@@ -196,12 +196,4 @@ public class OpsDocList implements Iterable<OpsDoc> {
return count;
}
public static class enumerator implements Consumer<OpTemplate> {
private int count=0;
@Override
public void accept(OpTemplate opTemplate) {
opTemplate.setRefKey(count++);
}
}
}

View File

@@ -18,72 +18,93 @@ package io.nosqlbench.adapters.api.activityimpl;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.evalctx.CycleFunction;
import io.nosqlbench.adapters.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import java.util.function.LongFunction;
/**
* <p>
* <H2>Synopsis</H2>
* An OpDispenser is responsible for mapping a cycle number into
* an executable operation. This is where <i>Op Synthesis</i> occurs
* in NoSQLBench.</p>
* <hr/>
* <p>
* <H2>BaseOpDispenser</H2>
* </p>
* Some common behaviors which are intended to be portable across all op
* dispenser types are implemented in {@link BaseOpDispenser}. It is
* <em>strongly</em> recommended that you use this as your base type when
* implementing op dispensers.
* <p>
* <H2>Concepts</H2>
* Op Synthesis is the process of building a specific executable
* operation for some (low level driver) API by combining the
* static and dynamic elements of the operation together.
* In most cases, implementations of OpDispenser will be constructed
* within the logic of an {@link OpMapper} which is responsible for
* determining the type of OpDispenser to use as associated with a specific
* type &lt;T&gt;. The OpMapper is called for each type of operation
* that is active during activity initialization. It's primary responsibility
* is figuring out what types of {@link OpDispenser}s to create based
* on the op templates provided by users. Once the activity is initialized,
* a set of op dispensers is held as live dispensers to use as needed
* to synthesize new operations from generated data in real time.
* </p>
*
* <hr/>
* <h2>Implementation Strategy</h2>
* <p>OpDispenser implementations are intended to be implemented
* for each type of distinct operation that is supported by a
* DriverAdapter.
* That is not to say that an OpDispenser can't be responsible for
* producing multiple types of operations. Operations which are similar
* in what they need and how they are constructed make sense to be implemented
* in the same op dispenser. Those which need different construction
* logic or fundamentally different types of field values should be implemented
* separately. The rule of thumb is to ensure that op construction patterns
* are easy to understand at the mapping level ({@link OpMapper}),
* and streamlined for fast execution at the synthesis level ({@link OpDispenser}).
*
* @param <OP> The parameter type of the actual operation which will be used
* to hold all the details for executing an operation,
* something that implements {@link io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp}.
*/
public interface OpDispenser<OP extends CycleOp<?>> extends LongFunction<OP>, OpResultTracker {
/// ## Synopsis
///
/// An OpDispenser is responsible for producing an executable operation
/// for a given cycle number. OpDispenser's are __NOT__ responsible for determine what kind of
/// operation the user intended. These two roles are separated by design so that when an
/// OpDispenser is called, all of the work of figuring out what specific type of operations to run
/// has already been done. [OpDispenser]s are produced by [OpMapper]s. Thus the role of an OpMapper
/// is to construct a `cycle->op` function.
///
/// There is an important relationship between op templates which are interpreted by op mapping and
/// the executable operations which are produced by op dispensers. At a high level, the chain looks
/// like this:
/// ```
/// op template -> op mapper -> op dispenser -> executable op
///```
/// The types and roles associated with op synthesis are:
/// - [OpTemplate] - the raw op template data structure
/// - [ParsedOp] - an API around op template; a lambda construction kit
/// - [OpMapper] - interprets user intent by looking at the op template and constructs an
/// associated op dispenser
/// - [OpDispenser] - Applies a cycle number to a lambda to produce an executable op, and
/// keeps track of template-specific metrics.
/// - [CycleOp] - The base type of executable operations.
///
/// ----
///
/// ## BaseOpDispenser
///
/// Some common behaviors which are intended to be portable across all op
/// dispenser types are implemented in [BaseOpDispenser]. It is
/// __strongly__ recommended that you use this as your base type when
/// implementing op dispensers. (__refactoring will make this mandatory__)
///
/// ## Concepts
///
/// REWRITE BELOW
/// Op Synthesis is the process of building a specific executable
/// operation for some (low level driver) API by combining the
/// static and dynamic elements of the operation together.
/// In most cases, implementations of OpDispenser will be constructed
/// within the logic of an [OpMapper] which is responsible for
/// determining the type of OpDispenser to use as associated with a specific
/// type `<OPTYPE>`. The OpMapper is called for each op template
/// that is active (not excluded by tag filtering) during activity
/// initialization. It's primary responsibility is figuring out what types of
/// [OpDispenser]s to create based
/// on the op templates provided by users. Once the activity is initialized,
/// a set of op dispensers is held as live dispensers to use as needed
/// to synthesize new operations from generated data in real time.
///
/// ---
///
/// ## Implementation Strategy
///
/// OpDispenser implementations are intended to be implemented
/// for each type of distinct operation that is supported by a
/// DriverAdapter.
/// That is not to say that an OpDispenser can't be responsible for
/// producing multiple types of operations. Operations which are similar
/// in what they need and how they are constructed make sense to be implemented
/// in the same op dispenser. Those which need different construction
/// logic or fundamentally different types of field values should be implemented
/// separately. The rule of thumb is to ensure that op construction patterns
/// are easy to understand at the mapping level ([OpMapper])
/// and streamlined for fast execution at the synthesis level ([OpDispenser])
/// @param <OPTYPE>
/// The parameter type of the actual operation which will be used to hold all the details for
/// executing an
/// operation, something that implements [CycleOp]
/**
* The apply method in an op dispenser should do all the work of
* creating an operation that is executable by some other caller.
* The value produced by the apply method should not require
* additional processing if a caller wants to execute the operation
* multiple times, as for retries.
*
* @param cycle The cycle number which serves as the seed for any
* generated op fields to be bound into an operation.
* @return an executable operation
*/
public interface OpDispenser<OPTYPE extends CycleOp<?>> extends LongFunction<OPTYPE>, OpResultTracker {
OP getOp(long cycle);
/// This method should do all the work of
/// creating an operation that is executable by some other caller.
/// The value produced by the apply method should not require
/// additional processing if a caller wants to execute the operation
/// multiple times, as for retries.
/// @param cycle
/// The cycle number which serves as the seed for any
/// generated op fields to be bound into an operation.
/// @return an executable operation
OPTYPE getOp(long cycle);
CycleFunction<Boolean> getVerifier();

View File

@@ -145,7 +145,7 @@ public interface OpMapper<OPTYPE extends CycleOp<?>, SPACETYPE extends Space>
* The {@link ParsedOp} which is the parsed version of the user-provided op template. This contains all the
* fields provided by the user, as well as explicit knowledge of which ones are static and dynamic. It provides
* convenient lambda-construction methods to streamline the effort of creating the top-level op lambda.
* @param spaceInitF
* @param spaceF
* This is the pre-baked lambda needed to access the specific {@link SPACETYPE} for a given cycle, if or when it
* is needed. Not all op types need this, since they may have all the state needed fully captured within the
* native type. For those that do, ensure that you are accessing the value through this function lazily and
@@ -154,9 +154,9 @@ public interface OpMapper<OPTYPE extends CycleOp<?>, SPACETYPE extends Space>
* @return An OpDispenser which can be used to synthesize directly executable operations.
*/
OpDispenser<OPTYPE> apply(
OpDispenser<? extends OPTYPE> apply(
NBComponent adapterC,
ParsedOp pop,
LongFunction<SPACETYPE> spaceInitF
LongFunction<SPACETYPE> spaceF
);
}

View File

@@ -23,6 +23,9 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
/**
* This example of a space uses the <EM>SelfT</EM> technique to enable
* the self type to be used in method signatures and return types.
*
* {@inheritDoc}
*
* @param <SelfT>
*/
public class BaseSpace<SelfT extends BaseSpace<SelfT> > implements Space {

View File

@@ -35,120 +35,177 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.function.LongFunction;
/**
* <P>The DriverAdapter interface is the top level API for implementing
* operations in NoSQLBench. It defines the related APIs needed to fully realized an adapter
* at runtime. A driver adapter can map op templates from YAML form to a fully executable
* form in native Java code, just as an application might do with a native driver. It can also
* do trivial operations, like simply calling {@code System.out.println(...)}. When you specify an adapter by name,
* you are choosing both the available operations, and the rules for converting a YAML op template into those
* operations. This is a two-step process: The adapter provides mapping logic for converting high-level templates from
* users into op types, and separate dispensing logic which can efficiently create these ops at runtime. When used
* together, they power <EM>op synthesis</EM> -- efficient and deterministic construction of runtime operations using
* procedural generation methods.
* </p>
*
* <p>
* Generally speaking, a driver adapter is responsible for
* <UL>
* <LI>Defining a class type for holding related state known as a {@link Space}.
* <UL><LI>The type of this is specified as
* generic parameter {@link SPACETYPE}.</LI></UL></LI>
* <LI>Defining a factory method for constructing an instance of the space.</LI>
* <LI>Recognizing the op templates that are documented for it via an {@link OpMapper}
* and assigning them to an op implementation.
* <UL><LI>The base type of these ops is specified as generic
* parameter {@link OPTYPE}.</LI></UL>
* </LI>
* <LI>Constructing dispensers for each matching op implementation with a matching {@link OpDispenser}</LI>
* implementation.
* </UL>
* <P>At runtime, the chain of these together ({@code cycle -> op mapping -> op dispensing -> op}) is cached
* as a look-up table of op dispensers. This results in the simpler logical form {@code cycle -> op synthesis ->
* operation}.
* </P>
*
* <H3>Variable Naming Conventions</H3>
* <p>
* Within the related {@link DriverAdapter} APIs, the following conventions are (more often) used, and will be found
* everywhere:
* <UL>
* <LI>{@code namedF} describes a namedFunction variable. Functional patterns are used everywhere in these APIs
* .</LI>
* <LI>{@code namedC} describes a namedComponent variable. All key elements of the nosqlbench runtime are
* part of a component tree.</LI>
* <LI>{@code pop} describes a {@link ParsedOp} instance.</LI>
* </UL>
* </P>
* <H3>Generic Parameters</H3>
* <p>
* When a new driver adapter is defined with the generic parameters below, it becomes easy to build out a matching
* DriverAdapter with any modern IDE.</P>
*
* @param <OPTYPE>
* The type of {@link CycleOp} which will be used to wrap all operations for this driver adapter. This allows you
* to add context or features common to all operations of this type. This can be a simple <a
* href="https://en.wikipedia.org/wiki/Marker_interface_pattern">Marker</a> interface, or it can be something more
* concrete that captures common logic or state across all the operations used for a given adapter. It is highly
* advised to <EM>NOT</EM> leave it as simply {@code CycleOp<?>}, since specific op implementations offer much
* better performance.
* @param <SPACETYPE>
* The type of context space used by this driver to hold cached instances of clients, session, or other native
* driver state. This is the shared state which might be needed during construction operations for an adapter.
* <EM>No other mechanism is provided nor intended for holding adapter-specific state. You must store it in
* this type. This includes client instances, codec mappings, or anything else that a single instance of an
* application would need to effectively use a given native driver.</EM>
*/
import io.nosqlbench.adapters.api.activityconfig.yaml.OpTemplate;
/// The DriverAdapter interface is the top level API for implementing operations of any kind in
/// NoSQLBench. It defines the related APIs needed to fully realize an adapter at runtime. A
/// driver adapter can map op templates from YAML form to a fully executable form in native Java
/// code, and then execute those native operations just as an application might do directly
/// with a native driver. It can also do trivial operations, like simply calling
/// `System.out.println(...)`. What a particular driver adapter does is open-ended, but this
/// usually means wrapping a native driver when supporting specific protocols.
///
/// Every DriverAdapter has a simple name, as indicated on it's [Service] annotation. When you
/// specify an adapter by name, you are choosing both the available operations, and the rules
/// for converting a YAML op template into those operations. This is a two-step process: Mapping
/// user intentions, and generating executable operations, called op mapping and op dispensing,
/// respectively. Each adapter provides implementations for both of these phases for all the op
/// types is supports. When used together, they power _op synthesis_ -- efficient and
/// deterministic construction of runtime operations using procedural generation methods.
///
/// An overview of all the key elements of the adapter API is given here. Understanding this
/// section means you know how the core op generator logic of NoSQLBench works.
///
/// Generally speaking, a driver adapter is responsible for
/// - Implementing a type of adapter-specific [Space] to hold related state.
/// - The type of this is specified as generic parameter [SPACETYPE] on [DriverAdapter].
/// - This is the primary state holder for any thing an application would typically need in order
/// to use a
/// (specific) native driver. Space instances are analogous to application instances, although
/// they only hold the essential state needed to enable native driver usage. By default, an
/// adapter only provides a single space, but users can override this to achieve higher native
/// driver concurrency for some specialized types of testing.
/// - In [DriverAdapter#getSpaceInitializer(NBConfiguration)], defining a factory method for
/// constructing an instance of the space when needed.
/// - The [NBConfiguration] is provided to configure app or driver settings as specified by
/// the user in activity parameters.
/// - in [OpMapper#apply(NBComponent, ParsedOp, LongFunction)], recognizing the op template
/// that is documented for it and constructing a matching [OpDispenser]`<`[OPTYPE]`>`.
/// - The [NBComponent] is part of the runtime component tree, and can be used to attach
/// user-visible component and naming structure as needed. The component tree supports
/// runtime event propogation, and automatic dimensional-labeling within the NoSQLBench
/// runtime. It also provides services for creating context-anchored metrics instruments,
/// configuration linting, and so on.
/// - The [ParsedOp] is a fully normalized view of an [OpTemplate], adhering to all the rules
/// of the _Uniform_ [workload_definition]. The parsed op API provides methods to assist in
/// constructing the lambdas which are the backbone of op synthesis. Lambdas constructed in
/// this way are highly efficient in modern JVM implementations, and serve effectively as
/// defered compilation in NoSQLBench.
/// - The [LongFunction]`<`[SPACETYPE]`>` provides functional access to the space needed for an
/// operation. It is a long function, since the space instance may be specific for each
/// (long) cycle value
/// - in [OpDispenser#apply(long)]), creating an executable operation in the form of a [CycleOp].
/// - Op dispenser logic should call a previously constructed lambda, having been built either
/// in
/// the body of [OpMapper#apply(NBComponent, ParsedOp, LongFunction)] or in the constructor of
/// [OpDispenser]. In either case, the op synthesis function is realized before the op
/// dispenser is fully constructed, ensuring that op generation is streamlined.
/// - The long value provided here is the cycle value, and is the primary _coordinate_ provided
/// to the op synthesis lambda. Any properties or features of the operation should be fully
/// determined, and a matching immutable op implementation should be returned.
/// - implement [CycleOp#apply(long)] for each unique type of operation which can be dispensed.
/// - CycleOps should be immutable, since they may be retried when necessary.
/// Subsequent calls should exactly the same thing for a given op instance.
/// - the long cycle value is provided here for special cases like error handling, logging,
/// or debugging, but should not be needed for normal op execution.
///
/// ---
///
/// At a high level, the following sequence is executed for every cycle in an activity:
/// ```
///[cycle value] -> op synthesis -> [executable op] -> op execution
///```
/// or, as a functional sketch, `opExecution(opFunction(cycle))`. This is a simplified view of the
/// detailed steps, most of which are handled automatically by the nosqlbench runtime engine:
///
/// ```
/// cycle value
/// -> op template sequencing # memoized
/// -> op template selection # memoized
/// -> op template parsing # memoized
/// -> op template normalization # memoized
/// -> op type mapping # memoized
/// -> op dispensing
/// -> op execution
///```
///
/// Notice that some stages are optimized via a form of memoization, for efficient execution.
///
/// ---
///
/// ### Variable Naming Conventions
///
/// Within the related [DriverAdapter] APIs, the following conventions are (more often) used, and will be
/// found everywhere:
/// - `namedF` describes a namedFunction variable. Functional patterns are used everywhere in these APIs.
/// - `namedC` describes a namedComponent variable. All key elements of the nosqlbench runtime are part of a
/// component tree.
/// - `pop` describes a [ParsedOp] instance.
///
/// ---
///
/// ### Generic Parameters
///
/// When a new driver adapter is defined with the generic parameters below, it becomes easy to build out a matching
/// [DriverAdapter] with any modern IDE by using the _implement interface ..._ and similar features.
///
/// @param OPTYPE
/// The type of [CycleOp] which will be used to wrap all operations for this driver adapter.
/// This allows you to add context or features common to all operations of this type. This can
/// be a simple
/// [marker](https://en.wikipedia.org/wiki/Marker_interface_pattern) interface,
/// or it can be something more concrete that captures common logic or state across all the
/// operations used for a given adapter. It is highly advised to _NOT_ leave it as simply
/// `CycleOp<?>`, since specific op implementations offer much better performance.
/// @param SPACETYPE
/// The type of context space used by this driver to hold cached instances of clients,
/// session, or other native driver state. This is the shared state which might be needed
/// during construction operations for an adapter. No other mechanism is provided nor intended
/// for holding adapter-specific state. You must store it in this type. This includes client
/// instances, codec mappings, or anything else that a single instance of an application would
/// need to effectively use a given native driver.
///
public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Space> extends NBComponent {
/**
* <p>
* <H2>Op Mapping</H2>
* An Op Mapper is a function which can look at a {@link ParsedOp} and create a matching {@link OpDispenser}.
* An OpDispenser is a function that will produce a special type {@link OPTYPE} that this DriverAdapter implements
* as its op implementation. There may be many different ops supported by an adapter, thus there may be similarly
* many dispensers.</p>
*
* <p>
* Both {@link OpMapper} and {@link OpDispenser} are functions. The role of {@link OpMapper} is to
* map the op template provided by the user to an op implementation provided by the driver adapter,
* and then to create a factor function for it (the {@link OpDispenser}).</p>
*
* <p>These roles are split for a very good reason: Mapping what the user wants to do with an op template
* is resource intenstive, and should be as pre-baked as possible. This phase is the <EM>op mapping</EM> phase.
* It is essential that the mapping logic be very clear and maintainable. Performance is not as important
* at this phase, because all of the mapping logic is run during initialization of an activity.
* </p>
* <p>
* Conversely, <EM>op dispensing</EM> (the next phase) while an activity is running should be as efficient as
* possible.
* </p>
*
* @return a dispensing function for {@link OPTYPE} op generation
*/
/// ## Op Mapping
///
/// An Op Mapper is a function which can look at a [ParsedOp] and create a matching
/// [OpDispenser].
/// An OpDispenser is a function that will produce a special type [OPTYPE] that this
/// DriverAdapter implements.
/// There may be many different ops supported by an adapter, thus there may be similarly many
/// dispensers.
///
/// Both [OpMapper] and [OpDispenser] are functions. The role of [OpMapper] is to map the op
/// template provided
/// by the user to an op implementation provided by the driver adapter, and then to create a
/// suitable function for
/// creating that type of operations, known as the [OpDispenser].
///
/// These roles are split for a very good reason: Mapping what the user wants to do with an op
/// template
/// is resource intensive, and should be as pre-baked as possible. This phase is the _op
/// mapping_ phase.
/// It is essential that the mapping logic be very clear and maintainable. Performance is not as
/// important
/// at this phase, because all of the mapping logic is run during initialization of an
/// activity.
///
/// Conversely, _op dispensing_ (the next phase) while an activity is running should be as
/// efficient as possible.
/// @return a dispensing function for an [OPTYPE] op generation
OpMapper<OPTYPE, SPACETYPE> getOpMapper();
/**
* The preprocessor function allows the driver adapter to remap
* the fields in the op template before they are interpreted canonically.
* At this level, the transform is applied once to the input map
* (once per op template) to yield the map that is provided to
* {@link OpMapper} implementations. <EM>This is here to make backwards compatibility
* possible for op templates which have changed. Avoid using it unless necessary.</EM>
*
* @return A function to pre-process the op template fields.
*/
/// The preprocessor function allows the driver adapter to remap the fields in the op template
/// before they are
/// interpreted canonically. At this level, the transform is applied once to the input map (once
/// per op
/// template) to yield the map that is provided to [OpMapper] implementations. _This is here to
/// make
/// backwards compatibility possible for op templates which have changed. Avoid using it unless
/// necessary._
/// @return A function to pre-process the op template fields.
default Function<Map<String, Object>, Map<String, Object>> getPreprocessor() {
return f -> f;
}
/**
* When a driver needs to identify an error uniquely for the purposes of
* routing it to the correct error handler, or naming it in logs, or naming
* metrics, override this method in your activity.
*
* @return A function that can reliably and safely map an instance of Throwable to a stable adapter-specific name.
*/
/// When a driver needs to identify an error uniquely for the purposes of routing it to the
/// correct error
/// handler, or naming it in logs, or naming metrics, override this method in your activity.
/// @return A function that can reliably and safely map an instance of Throwable to a stable
/// adapter-specific name.
default Function<Throwable, String> getErrorNameMapper() {
return t -> t.getClass().getSimpleName();
}
@@ -157,20 +214,22 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
return List.of(f -> f);
}
/**
* <P>This method allows each driver adapter to create named state which is automatically
* cached and re-used by name. For each (driver,space) combination in an activity,
* a distinct space instance will be created. In general, adapter developers will
* use the space type associated with an adapter to wrap native driver instances
* one-to-one. As such, if the space implementation is a {@link AutoCloseable},
* it will be explicitly shutdown as part of the activity shutdown.</P>
*
* <p>It is not necessary to implement a space for a stateless driver adapter, or one
* which puts all state into each op instance.</p>
*
* @return A function which can initialize a new Space, which is a place to hold
* object state related to retained objects for the lifetime of a native driver.
*/
/// This method allows each driver adapter to create named state which is automatically cached
/// and re-used by
/// name. For each (driver,space) combination in an activity, a distinct space instance will be
/// created. In
/// general, adapter developers will use the space type associated with an adapter to wrap
/// native driver
/// instances one-to-one. As such, if the space implementation is an [AutoCloseable], it will
/// be
/// explicitly shutdown as part of the activity shutdown.
///
/// It is not necessary to implement a space for a stateless driver adapter, or one which
/// injects all necessary
/// state into each op instance.
/// @return A function which can initialize a new Space, which is a place to hold object state
/// related to
/// retained objects for the lifetime of a native driver.
default LongFunction<SPACETYPE> getSpaceInitializer(NBConfiguration cfg) {
return n -> (SPACETYPE) new Space() {
@Override
@@ -180,71 +239,109 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
};
}
/// Provides the configuration for this driver adapter, which comes from the superset of
/// activity parameters given for the owning activity. Presently, the driver adapter acts
/// as a proxy to set these parameters on the space, but this will likely be updated.
/// Instead, the configuratin will be properly attached to the space directly, and the APIs
/// supporting it will enforce this.
NBConfiguration getConfiguration();
/**
* The standard way to provide docs for a driver adapter is to put them in one of two common places:
* <ul>
* <li>&lt;resources&gt;/&lt;adaptername&gt;.md - A single markdown file which is the named top-level
* markdown file for this driver adapter.</li>
* <li>&lt;resources&gt;/docs/&lt;adaptername&gt;/ - A directory containing any type of file which
* is to be included in docs under the adapter name, otherwise known as the {@link Service#selector()}</li>
* <li>&lt;resources&gt;/docs/&lt;adaptername&gt;.md</li>
* </ul>
*
* <P><EM>A build will fail if any driver adapter implementation is missing at least one self-named
* markdown doc file.</EM></P>
*
* @return A {@link DocsBinder} which describes docs to include for a given adapter.
*/
/// The standard way to provide docs for a driver adapter is to put them in one of two common
/// places:
/// - `resources/<adaptername>.md`
/// - A single markdown file which is the named top-level markdown file for this driver
/// adapter.
/// - `resources/docs/<adaptername>/`
/// - A directory containing any type of file which is to be included in docs under the
/// adapter name, otherwise
/// known as the [Service#selector()]
/// - `resources/docs/<adaptername>.md`
/// - An alternate location for the main doc file for an adapter, assuming you are using the
/// docs/ path.
///
/// _A build will fail if any driver adapter implementation is missing at least one self-named
/// markdown doc file._
/// @return A [DocsBinder] which describes docs to include for a given adapter.
default DocsBinder getBundledDocs() {
Docs docs = new Docs().namespace("drivers");
String dev_docspath = "adapter-" + this.getAdapterName() + "/src/main/resources/docs/" + this.getAdapterName();
String cp_docspath = "docs/" + this.getAdapterName();
Optional<Content<?>> bundled_docs = NBIO.local().pathname(dev_docspath, cp_docspath).first();
Optional<Content<?>> bundled_docs = NBIO.local().pathname(
dev_docspath, cp_docspath).first();
bundled_docs.map(Content::asPath).ifPresent(docs::addContentsOf);
Optional<Content<?>> maindoc = NBIO.local().pathname("/src/main/resources/" + this.getAdapterName() + ".md", this.getAdapterName() + ".md").first();
Optional<Content<?>> maindoc = NBIO.local().pathname(
"/src/main/resources/" + this.getAdapterName() + ".md",
this.getAdapterName() + ".md"
).first();
maindoc.map(Content::asPath).ifPresent(docs::addPath);
return docs.asDocsBinder();
}
/// Provide the simple name for this [DriverAdapter] implementation, derived from the
/// required [Service] annotation.
default String getAdapterName() {
Service svc = this.getClass().getAnnotation(Service.class);
if (svc == null) {
throw new RuntimeException("The Service annotation for adapter of type " + this.getClass().getCanonicalName() + " is missing.");
throw new RuntimeException(
"The Service annotation for adapter of type " + this.getClass().getCanonicalName() + " is missing.");
}
return svc.selector();
}
/// Indicate the level of testing and muturity for the current adapter. This is not actively
/// used
/// and may be removed.
/// @deprecated
default Maturity getAdapterMaturity() {
return this.getClass().getAnnotation(Service.class).maturity();
}
/**
* <p>The cache of all objects needed within a single instance
* of a DriverAdapter which are not operations. These are generally
* things needed by operations, or things needed during the
* construction of operations.</p>
*
* <p>During Adapter Initialization, Op Mapping, Op Synthesis, or Op Execution,
* you may need access to the objects in (the or a) space cache. You can build the
* type of context needed and then provide this function to provide new instances
* when needed.</p>
*
* <p>The function returned by this method is specialized to the space mapping
* logic in the op template. Specifically, it uses whatever binding is set on a given
* op template for the <em>space</em> op field. If none are provided, then this
* becomes a short-circuit for the default '0'. If a non-numeric binding is provided,
* then an interstitial mapping is added which converts the {@link Object#toString()}
* value to ordinals using a hash map. This is less optimal by far than using
* any binding that produces a {@link Number}.</p>
*
* @return A cache of named objects
*/
/// The function returned by [#getSpaceFunc(ParsedOp)] provides access to a cache of all
/// stateful objects needed
/// within a single instance of a DriverAdapter. These are generally things needed by
/// operations, or things
/// needed during the construction of operations. Typically, a space is where you store a native
/// driver instance
/// which is expected to be created/initialized once and reused within an application.
/// Generally, users can
/// think of __space__ as __driver instance__, or __client instance__, although there are driver
/// adapters that
/// do things other than wrap native drivers and clients.
///
/// The value of the op field `space` is used to customize the instancing behavior of spaces. If
/// none is provided
/// by the user, then only a singular space will be created for a given adapter in an activity.
/// This is normal,
/// and what most users will expect to do. However, customizing the space selector can be a
/// powerful way to test
/// any system with high logical concurrency. For example, each instance of a native driver will
/// typically
/// maintain its own thread or connection pools, cached resources and so on. ( Unless the
/// developers of
/// said native driver are breaking encapsulation by using global singletons in the runtime,
/// which is highly
/// frowned upon.) The spaces feature allows any nosqlbench workload to be easily converted into
/// an unreasonably parallel
/// client topology test with a single change. It works the same way for any adapter or protocol
/// supported by
/// nosqlbench.
///
/// The value of the op field `space` should be a [Number] type. [Number#intValue()] is used to
/// determine which space instance is, if needed, initialized first, and then returned. If users provide a
/// non-[Number] type,
/// then an enumerating layer is added inline to convert any such value to an integer, which is
/// less optimal.
///
/// During op mapping or dispensing, you may need access to state held by a driver-specific
/// implementation of
/// [SPACETYPE]. In the initialization phase, you only have access to the space function
/// itself.
/// This is important to maintain a boundary betwen the explicitly stateless and stateful parts
/// of the
/// runtime. To use the space, incorporate the space function into the lambdas which produce the
/// operation to be
/// executed. This is typically done in the construtor of the related [OpDispenser].
/// @return A cache of named objects
public LongFunction<SPACETYPE> getSpaceFunc(ParsedOp pop);
}

View File

@@ -26,8 +26,9 @@ import io.nosqlbench.nb.api.components.core.NBNamedElement;
* in testing scenarios. Within the operations for an adapter, the space
* may be needed, for example, to construct prepared statements, or other
* 'session-attached' objects. Put any state that you would normally
* associate with an instance of a native driver into a space, and use
* the {@link DriverAdapter#getSpaceCache()} to access it when needed.</P>
* associate with an instance of a native driver into a space.
* A function to access the cycle-specific space instance is provided where
* you might needed, such as in the mapping or dispensing APIs.
*/
public interface Space extends NBNamedElement, AutoCloseable {

View File

@@ -0,0 +1,23 @@
package io.nosqlbench.adapters.api.activityimpl.uniform;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/
public interface Validator<RESULT> {
public void validate(RESULT result);
}

View File

@@ -0,0 +1,29 @@
package io.nosqlbench.adapters.api.activityimpl.uniform;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/
import io.nosqlbench.adapters.api.templating.ParsedOp;
import java.util.Optional;
/// A [DriverAdapter] may implement this interface to provide adapter-specific
/// validators.
public interface ValidatorSource {
Optional<Validator> getValidator(String name, ParsedOp pop);
}

View File

@@ -21,22 +21,52 @@ import io.nosqlbench.adapters.api.activityimpl.OpMapper;
import java.util.function.LongFunction;
/**
* <H2>CycleOp: f(cycle) -> T</H2>
* <p>A CycleOp of T is an operation which takes a long input value and produces a value of type T. It is implemented as
* {@link LongFunction} of T.</p>
*
* <h2>Designer Notes</h2>
* <p>
* If you are using the value in this call to select a specific type of behavior, i.e.
* among a variety of operation types, it is very likely a candidate for factoring
* into separate op implementations. By using the <pre>cycle -> mapper -> dispenser -> op</pre>
* pattern to move as much of the initialization logic forward, you will have
* much more efficient operations and much cleaner code. The {@link OpMapper}
* and {@link OpDispenser} abstractions are meant to move op type selection and scheduling to earlier in the activity.
* </p>
*/
public interface CycleOp<T> extends LongFunction<T> {
/// The [CycleOp] is the core interface for any executable operation within
/// the NoSQLBench runtime. When [#apply(long)] is called, the long value
/// is the value of a specific cycle. It is effectively `cycleop.apply(cycle) -> RESULT`.
///
/// [CycleOp]s may or may not actually use the long input. Generally speaking,
/// once you have an instance of a cycle op, you have already synthesized all
/// concrete values needed to instantiate it. These values are derived from
/// the cycle within the op dispensing layer, thus the [CycleOp] should not need
/// them again to define the core operation. However, it is often helpful
/// to have the cycle value for debugging, diagnostics, or other instrumentation,
/// particularly in cases where special error handling is needed. In some rare
/// cases, special dispensers may use the value to reconstruct determinstic op
/// data for the purposes of troubleshooting or similar.
///
/// A given [CycleOp] instance should execute the exact same operation if called again.
/// This is used to _retry_ operations in some cases. Some operations which may be
/// spawned as a result of previous operations may never use the cycle op value, as the
/// fields and behavior of those secondary operations may be fully determined by the
/// results or fields of the previous operation. This may be the case for linearized
/// operations which, for example, read further data from a data source as a client-side
/// join.
///
/// ## Designer Notes
///
/// If you are using the value in this call to select a specific type of behavior, i.e. among a variety of
/// operation types, it is very likely a candidate for factoring into separate op implementations. In general,
/// you should move as much of the initialization logic forward as possible, to keep op synthesis fast.
///
/// If you derive from [CycleOp] to create subtypes within your own
/// [io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter] implementation, it is better to
/// keep the [RESULT] generic parameter within the arity of the derived type. For example, a
/// derived
/// ```
/// public ... MyCycleOp<? extends MyBaseRequestType, RESULT extends MyBaseResultType>
/// extends CycleOp<RESULT>
/// ```
/// is much more understandable and reusable than
/// ```
/// public ... MyCycleOp<? extends MyBaseRequestType>
/// extends CycleOp<Object> # confusing!
/// ```
/// since the latter version replaces the generic result type with some other adapter-specific type.
/// At least in the first case, a clear specialization is carved out for a new generic parameter
/// without hiding the original result parameter.
///
public interface CycleOp<RESULT> extends LongFunction<RESULT> {
/**
* <p>Run an action for the given cycle.</p>
*
@@ -44,7 +74,7 @@ public interface CycleOp<T> extends LongFunction<T> {
* The cycle value for which an operation is run
*/
@Override
T apply(long value);
RESULT apply(long value);
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2022-2023 nosqlbench
*
* Licensed 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 io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes;
import io.nosqlbench.virtdata.core.templates.ParsedTemplateString;
import java.util.Map;
/**
* If an op implements VariableCapture, then it is known to be able to
* extract variables from its result. Generally speaking, this should
* be implemented within an Op according to the standard format
* of {@link ParsedTemplateString#getCaptures()}. Any op implementing
* this should use the interface below to support interop between adapters
* and to allow for auto documentation tha the feature is supported for
* a given adapter.
*/
public interface VariableCapture {
Map<String,?> capture();
}

View File

@@ -16,19 +16,23 @@
package io.nosqlbench.adapters.api.activityimpl.uniform.opwrappers;
import io.nosqlbench.adapters.api.activityimpl.uniform.Validator;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
public class EmitterCycleOp<T> implements CycleOp<T> {
public class AssertingOp<T> implements CycleOp<T> {
private final CycleOp<T> cycleOp;
public EmitterCycleOp(CycleOp<T> cycleOp) {
this.cycleOp = cycleOp;
private final CycleOp<T> op;
private final Validator<T> validator;
public AssertingOp(CycleOp<T> op, Validator<T> validator) {
this.op = op;
this.validator = validator;
}
@Override
public T apply(long value) {
T result = cycleOp.apply(value);
System.out.println("result from cycle " + value + ":\n"+result);
return result;
T result = op.apply(value);
validator.validate(result);
return result;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-2024 nosqlbench
*
* Licensed 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 io.nosqlbench.adapters.api.activityimpl.uniform.opwrappers;
import io.nosqlbench.adapters.api.activityimpl.BaseOpDispenser;
import io.nosqlbench.adapters.api.activityimpl.OpDispenser;
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.Validator;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
public class AssertingOpDispenser<S extends Space, RESULT> extends BaseOpDispenser<CycleOp<RESULT>, S> {
private final OpDispenser<CycleOp<RESULT>> realDispenser;
private final Validator<RESULT> validator;
public AssertingOpDispenser(
DriverAdapter<CycleOp<RESULT>, S> adapter,
ParsedOp pop,
OpDispenser<CycleOp<RESULT>> realDispenser,
Validator<RESULT> validator
) {
super(adapter, pop, adapter.getSpaceFunc(pop));
this.realDispenser = realDispenser;
this.validator = validator;
logger.debug("initialized {} for result validation", pop.getName());
}
@Override
public CycleOp<RESULT> getOp(long cycle) {
CycleOp<RESULT> op = realDispenser.getOp(cycle);
return new AssertingOp<>(op, validator);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-2024 nosqlbench
*
* Licensed 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 io.nosqlbench.adapters.api.activityimpl.uniform.opwrappers;
import io.nosqlbench.adapters.api.activityimpl.uniform.Validator;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import java.util.Map;
import java.util.function.Function;
public class CapturingOp<T> implements CycleOp<Map<String,?>> {
private final CycleOp<T> op;
private final Function<T, Map<String, ?>> extractorF;
public CapturingOp(CycleOp<T> op, Function<T, Map<String,?>> extractorF) {
this.op = op;
this.extractorF = extractorF;
}
@Override
public Map<String,?> apply(long value) {
T result = op.apply(value);
Map<String, ?> map = extractorF.apply(result);
return map;
}
}

View File

@@ -23,18 +23,28 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
public class EmitterOpDispenserWrapper extends BaseOpDispenser<CycleOp<?>, Space> {
import java.util.Map;
import java.util.function.Function;
private final OpDispenser<? extends CycleOp<?>> realDispenser;
public class CapturingOpDispenser<S extends Space, RESULT> extends BaseOpDispenser<CycleOp<Map<String,?>>, S> {
public EmitterOpDispenserWrapper(DriverAdapter<CycleOp<?>,Space> adapter, ParsedOp pop,
OpDispenser<? extends CycleOp<?>> realDispenser) {
private final OpDispenser<CycleOp<RESULT>> realDispenser;
private final Function<RESULT, Map<String, ?>> extractorF;
public CapturingOpDispenser(
DriverAdapter<CycleOp<RESULT>, S> adapter,
ParsedOp pop,
OpDispenser<CycleOp<RESULT>> realDispenser,
Function<RESULT, Map<String,?>> extractorF
) {
super(adapter, pop, adapter.getSpaceFunc(pop));
this.realDispenser = realDispenser;
this.extractorF = extractorF;
}
@Override
public EmitterOp getOp(long cycle) {
CycleOp<?> cycleOp = realDispenser.getOp(cycle);
return new EmitterOp(cycleOp);
public CycleOp<Map<String,?>> getOp(long cycle) {
CycleOp<RESULT> op = realDispenser.getOp(cycle);
return new CapturingOp<>(op,extractorF);
}
}

View File

@@ -18,11 +18,11 @@ package io.nosqlbench.adapters.api.activityimpl.uniform.opwrappers;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
public class DryCycleOp<T> implements CycleOp<T> {
public class DryrunOp<T> implements CycleOp<T> {
private final CycleOp<T> op;
public DryCycleOp(CycleOp<T> op) {
public DryrunOp(CycleOp<T> op) {
this.op = op;
}

View File

@@ -23,11 +23,11 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
public class DryCycleOpDispenserWrapper<S extends Space, RESULT> extends BaseOpDispenser<CycleOp<RESULT>, S> {
public class DryrunOpDispenser<S extends Space, RESULT> extends BaseOpDispenser<CycleOp<RESULT>, S> {
private final OpDispenser<CycleOp<RESULT>> realDispenser;
public DryCycleOpDispenserWrapper(
public DryrunOpDispenser(
DriverAdapter<CycleOp<RESULT>, S> adapter,
ParsedOp pop,
OpDispenser<CycleOp<RESULT>> realDispenser
@@ -45,6 +45,6 @@ public class DryCycleOpDispenserWrapper<S extends Space, RESULT> extends BaseOpD
@Override
public CycleOp<RESULT> getOp(long cycle) {
CycleOp<RESULT> op = realDispenser.getOp(cycle);
return new DryCycleOp<>(op);
return new DryrunOp<>(op);
}
}

View File

@@ -18,10 +18,10 @@ package io.nosqlbench.adapters.api.activityimpl.uniform.opwrappers;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
public class EmitterOp<T> implements CycleOp<T> {
public class ResultPrintingOp<T> implements CycleOp<T> {
private final CycleOp<T> cycleOp;
public EmitterOp(CycleOp<T> cycleOp) {
public ResultPrintingOp(CycleOp<T> cycleOp) {
this.cycleOp = cycleOp;
}

View File

@@ -23,11 +23,11 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
public class EmitterCycleOpDispenserWrapper<O,S extends Space,R> extends BaseOpDispenser<CycleOp<R>, S> {
public class ResultPrintingOpDispenser<O,S extends Space,R> extends BaseOpDispenser<CycleOp<R>, S> {
private final OpDispenser<CycleOp<R>> realDispenser;
public EmitterCycleOpDispenserWrapper(
public ResultPrintingOpDispenser(
DriverAdapter<? extends CycleOp<R>, S> adapter,
ParsedOp pop,
OpDispenser<CycleOp<R>> realDispenser
@@ -42,8 +42,8 @@ public class EmitterCycleOpDispenserWrapper<O,S extends Space,R> extends BaseOpD
}
@Override
public EmitterCycleOp<R> getOp(long cycle) {
public ResultPrintingOp<R> getOp(long cycle) {
CycleOp<R> cycleOp = realDispenser.getOp(cycle);
return new EmitterCycleOp(cycleOp);
return new ResultPrintingOp(cycleOp);
}
}

View File

@@ -0,0 +1,53 @@
package io.nosqlbench.adapters.api.evalctx.comparators;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/
public enum DiffType {
/// Verify nothing for this result
none(0),
/// Verify that keys named in the result are present in the reference map.
result_fields(0x1),
/// Verify that keys in the reference map are present in the result map.
reference_fields(0x1 << 1),
/// Verify that all fields present in either the reference map or the result map
/// are also present in the other. (set equality)
fields(0x1 | 0x1 << 1),
/// Verify that all values of the same named key are equal, according to
/// {@link Object#equals(Object)}}.
values(0x1 << 2),
/// Cross-verify all names and values between the reference map and result map.
all(0x1 | 0x1 << 1 | 0x1 << 2);
public int bitmask;
DiffType(int bit) {
this.bitmask = bit;
}
public boolean is(DiffType option) {
return (bitmask & option.bitmask) > 0;
}
}

View File

@@ -0,0 +1,339 @@
package io.nosqlbench.adapters.api.evalctx.comparators;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/
import io.nosqlbench.nb.api.components.core.NBComponent;
import io.nosqlbench.nb.api.engine.metrics.instruments.MetricCategory;
import io.nosqlbench.nb.api.engine.metrics.instruments.NBMetricCounter;
import io.nosqlbench.virtdata.core.bindings.Bindings;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.ToLongBiFunction;
import java.util.stream.Collectors;
public class MapDifferencer {
private final StringBuilder logbuffer = new StringBuilder();
private final Map<String, Object> refMap = new HashMap<>();
private final DiffType difftype;
private final Object metrics;
private final NBComponent scope;
private final NBMetricCounter verifiedResultsCounter;
private final NBMetricCounter verifiedValuesCounter;
private final NBMetricCounter unverifiedResultsCounter;
private final NBMetricCounter unverifiedValuesCounter;
private MapDifferencer(NBComponent parent, Object metrics, Bindings bindings, DiffType diffType) {
this.scope = parent;
this.metrics = metrics;
this.difftype = diffType;
this.verifiedResultsCounter = parent.create().counter(
"verified_results",
MetricCategory.Verification,
"Number of results which were verified without error"
);
this.verifiedValuesCounter = parent.create().counter(
"verified_values",
MetricCategory.Verification,
"Number of values in results which were verified"
);
this.unverifiedResultsCounter = parent.create().counter(
"unverified_results",
MetricCategory.Verification,
"Number of results which were unverified (threw an error)"
);
this.unverifiedValuesCounter = parent.create().counter(
"unverified_values",
MetricCategory.Verification,
"Number of values in results which were unverified (threw an error)"
);
}
// /**
// * see {@link DataType}
// *
// * @param typeName The DataType.Name of the field in question
// * @param row The row to read the field value from
// * @param fieldName The field name to read
// * @param genValue the generated value to compare against
// * @return true, if the value is equal
// */
// private static boolean isEqual(DataType.Name typeName, Row row, String fieldName, Object genValue) {
// switch (typeName) {
// case ASCII: // ASCII(1, String.class)
// case VARCHAR: // VARCHAR(13, String.class)
// case TEXT: // TEXT(10, String.class)
// String textValue = row.getString(fieldName);
// return textValue.equals(genValue);
// case BIGINT: // BIGINT(2, Long.class)
// case COUNTER: // COUNTER(5, Long.class)
// long longValue = row.getLong(fieldName);
// return longValue == (long) genValue;
// case BLOB: // BLOB(3, ByteBuffer.class)
// // TODO: How do we test this one?
// case CUSTOM: // CUSTOM(0, ByteBuffer.class)
// ByteBuffer blobValue = row.getBytes(fieldName);
// return blobValue.equals(genValue);
// case BOOLEAN: // BOOLEAN(4, Boolean.class)
// boolean boolValue = row.getBool(fieldName);
// return boolValue == (boolean) genValue;
// case DECIMAL: // DECIMAL(6, BigDecimal.class)
// BigDecimal bigDecimalValue = row.getDecimal(fieldName);
// return bigDecimalValue.equals(genValue);
// case DOUBLE: // DOUBLE(7, Double.class)
// double doubleValue = row.getDouble(fieldName);
// return doubleValue == (double) genValue;
// case FLOAT: // FLOAT(8, Float.class)
// float floatValue = row.getFloat(fieldName);
// return floatValue == (float) genValue;
// case INET: // INET(16, InetAddress.class)
// InetAddress inetAddressValue = row.getInet(fieldName);
// return inetAddressValue.equals(genValue);
// case INT: // INT(9, Integer.class)
// int intValue = row.getInt(fieldName);
// return intValue == (int) genValue;
// case TIMESTAMP: // TIMESTAMP(11, Date.class)
// Date timestamp = row.getTimestamp(fieldName);
// return timestamp.equals(genValue);
// case UUID: // UUID(12, UUID.class)
// case TIMEUUID: // TIMEUUID(15, UUID.class)
// UUID uuidValue = row.getUUID(fieldName);
// return uuidValue.equals(genValue);
// case VARINT: // VARINT(14, BigInteger.class)
// BigInteger bigIntValue = row.getVarint(fieldName);
// return bigIntValue.equals(genValue);
// case LIST: // LIST(32, List.class)
// // TODO: How do we make getCollection methods work with non-String CQL types?
// List<?> list = row.getList(fieldName, String.class);
// return list.equals(genValue);
// case SET: // SET(34, Set.class)
// Set<?> set = row.getSet(fieldName, String.class);
// return set.equals(genValue);
// case MAP: // MAP(33, Map.class)
// Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
// return map.equals(genValue);
// case UDT: // UDT(48, UDTValue.class)
// UDTValue udtValue = row.getUDTValue(fieldName);
// return udtValue.equals(genValue);
// case TUPLE: // TUPLE(49, TupleValue.class)
// TupleValue tupleValue = row.getTupleValue(fieldName);
// return tupleValue.equals(genValue);
// case SMALLINT:
// short shortVal = row.getShort(fieldName);
// return shortVal == (Short) genValue;
// case TINYINT:
// byte byteValue = row.getByte(fieldName);
// return byteValue == (byte) genValue;
// case DATE:
// LocalDate dateValue = row.getDate(fieldName);
// return dateValue.equals(genValue);
// case TIME:
// long timeValue = row.getTime(fieldName);
// return timeValue == (long) genValue;
// default:
// throw new RuntimeException("Unrecognized type:" + typeName);
// }
// }
//
// private static String prettyPrint(DataType.Name typeName, Row row, String fieldName) {
// switch (typeName) {
// case ASCII: // ASCII(1, String.class)
// case VARCHAR: // VARCHAR(13, String.class)
// case TEXT: // TEXT(10, String.class)
// return row.getString(fieldName);
// case BIGINT: // BIGINT(2, Long.class)
// case COUNTER: // COUNTER(5, Long.class)
// long counterValue = row.getLong(fieldName);
// return String.valueOf(counterValue);
// case BLOB: // BLOB(3, ByteBuffer.class)
// case CUSTOM: // CUSTOM(0, ByteBuffer.class)
// ByteBuffer blobValue = row.getBytes(fieldName);
// return String.valueOf(blobValue);
// case BOOLEAN: // BOOLEAN(4, Boolean.class)
// boolean boolValue = row.getBool(fieldName);
// return String.valueOf(boolValue);
// case DECIMAL: // DECIMAL(6, BigDecimal.class)
// BigDecimal bigDecimalValue = row.getDecimal(fieldName);
// return String.valueOf(bigDecimalValue);
// case DOUBLE: // DOUBLE(7, Double.class)
// double doubleValue = row.getDouble(fieldName);
// return String.valueOf(doubleValue);
// case FLOAT: // FLOAT(8, Float.class)
// float floatValue = row.getFloat(fieldName);
// return String.valueOf(floatValue);
// case INET: // INET(16, InetAddress.class)
// InetAddress inetAddressValue = row.getInet(fieldName);
// return String.valueOf(inetAddressValue);
// case INT: // INT(9, Integer.class)
// int intValue = row.getInt(fieldName);
// return String.valueOf(intValue);
// case TIMESTAMP: // TIMESTAMP(11, Date.class)
// Date timestamp = row.getTimestamp(fieldName);
// return String.valueOf(timestamp);
// case UUID: // UUID(12, UUID.class)
// case TIMEUUID: // TIMEUUID(15, UUID.class)
// UUID uuidValue = row.getUUID(fieldName);
// return String.valueOf(uuidValue);
// case VARINT: // VARINT(14, BigInteger.class)
// BigInteger bigIntValue = row.getVarint(fieldName);
// return String.valueOf(bigIntValue);
// case LIST: // LIST(32, List.class)
// List<?> list = row.getList(fieldName, String.class);
// return String.valueOf(list);
// case SET: // SET(34, Set.class)
// Set<?> set = row.getSet(fieldName, String.class);
// return String.valueOf(set);
// case MAP: // MAP(33, Map.class)
// Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
// return String.valueOf(map);
// case UDT: // UDT(48, UDTValue.class)
// UDTValue udtValue = row.getUDTValue(fieldName);
// return String.valueOf(udtValue);
// case TUPLE: // TUPLE(49, TupleValue.class)
// TupleValue tupleValue = row.getTupleValue(fieldName);
// return String.valueOf(tupleValue);
// case SMALLINT:
// short val = row.getShort(fieldName);
// return String.valueOf(val);
// case TINYINT:
// byte byteValue = row.getByte(fieldName);
// return String.valueOf(byteValue);
// case DATE:
// LocalDate dateValue = row.getDate(fieldName);
// return String.valueOf(dateValue);
// case TIME:
// long timeValue = row.getTime(fieldName);
// return String.valueOf(timeValue);
// default:
// throw new RuntimeException("Type not recognized:" + typeName);
// }
// }
/**
Compare the values of the result with the reference map.
<p>
Specifically,
<ol>
<li>Ensure the same number of fields.</li>
<li>Ensure the same pair-wise field names.</li>
<li>Ensure that each pair of same-named fields has the same data type.</li>
<li>Ensure that the value of each pair of fields is equal according to the equals
operator for the respective type.</li>
</ol>
@return a count of differences between the row and the reference values
*/
private int compare(Map<String, Object> result, Map<String, Object> ref) {
int diff = 0;
// ColumnDefinitions cdefs = row.getColumnDefinitions();
logbuffer.setLength(0);
if (difftype.is(DiffType.reference_fields)) {
List<String> missingRowFields = ref.keySet().stream().filter(gk -> !result.containsKey(gk)).collect(
Collectors.toList());
if (missingRowFields.size() > 0) {
diff += missingRowFields.size();
logbuffer.append("\nexpected fields '");
logbuffer.append(String.join("','", missingRowFields));
logbuffer.append("' not in row.");
}
}
if (difftype.is(DiffType.result_fields)) {
List<String> missingRefFields = result.keySet().stream().filter(k -> !ref.containsKey(k)).collect(
Collectors.toList());
if (missingRefFields.size() > 0) {
diff += missingRefFields.size();
logbuffer.append("\nexpected fields '");
logbuffer.append(String.join("','", missingRefFields));
logbuffer.append("' not in reference data: " + ref);
}
}
if (difftype.is(DiffType.values)) {
for (String name : result.keySet()) {
if (ref.containsKey(name)) {
if (!result.get(name).equals(ref.get(name))) {
logbuffer.append("\nvalue differs for '").append(name).append("' ");
logbuffer.append("expected:'").append(ref.get(name).toString()).append("'");
logbuffer.append(" actual:'").append(result.get(name).toString()).append("'");
diff++;
this.unverifiedValuesCounter.inc();
} else {
this.verifiedValuesCounter.inc();
}
}
}
}
if (diff == 0) {
this.verifiedResultsCounter.inc();
} else {
this.unverifiedResultsCounter.inc();
}
return diff;
}
/**
Get the most recent detail log recorded by this thread.
@return a logbuffer string, with one entry per line
*/
public String getDetail() {
return this.logbuffer.toString();
}
public int verify(long cycle, Object result) {
// refMap.clear();
// bindings.setMap(refMap, cycle);
// int diffs = compare(row, refMap);
// if (diffs > 0) {
// HashMap<String, Object> mapcopy = new HashMap<>();
// mapcopy.putAll(refMap);
// throw new RowVerificationException(cycle, row, mapcopy, getDetail());
// } else {
return 0;
// }
}
public static class ThreadLocalWrapper {
private final NBComponent parent;
private final Bindings bindings;
private final DiffType diffType;
private final ThreadLocal<MapDifferencer> tl;
public ThreadLocalWrapper(NBComponent parent, Bindings bindings, DiffType diffType) {
this.parent = parent;
this.bindings = bindings;
this.diffType = diffType;
tl = ThreadLocal.withInitial(() -> new MapDifferencer(parent, bindings, bindings, diffType));
}
public int apply(Map<String,Object> result, long cycle) {
return tl.get().verify(cycle,result);
}
}
}

View File

@@ -0,0 +1,20 @@
/// This package contains the API needed to implement a [io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter]
package io.nosqlbench.adapters.api;
/*
* Copyright (c) nosqlbench
*
* Licensed 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.
*/

View File

@@ -112,7 +112,7 @@ public class PromPushReporterComponent extends PeriodicTaskComponent {
}
PromPushReporterComponent.logger.debug("formatted {} metrics in prom expo format", total);
final String exposition = sb.toString();
logger.trace(() -> "prom exposition format:\n" + exposition);
// logger.trace(() -> "prom exposition format:\n" + exposition);
final double backoffRatio = 1.5;
final double maxBackoffSeconds = 10;

View File

@@ -47,21 +47,21 @@ public final class StatBucket {
mean = value;
dSquared = 0.0d;
} else if (Double.isNaN(popped)) {
var newMean = mean + ((value - mean) / ringbuf.count());
var dSquaredIncrement = ((value - newMean) * (value - mean));
double newMean = mean + ((value - mean) / ringbuf.count());
double dSquaredIncrement = ((value - newMean) * (value - mean));
// If this value is too small to be interpreted as a double it gets converted to
// zero, which is not what we want. So we use the smallest possible double value
if (dSquaredIncrement == 0) dSquaredIncrement = Double.MIN_VALUE;
dSquared += dSquaredIncrement;
mean = newMean;
} else {
var meanIncrement = (value - popped) / ringbuf.count();
var newMean = mean + meanIncrement;
var dSquaredIncrement = ((value - popped) * (value - newMean + popped - mean));
double meanIncrement = (value - popped) / ringbuf.count();
double newMean = mean + meanIncrement;
double dSquaredIncrement = ((value - popped) * (value - newMean + popped - mean));
// If this value is too small to be interpreted as a double it gets converted to
// zero, which is not what we want. So we use the smallest possible double value
if (dSquaredIncrement == 0) dSquaredIncrement = Double.MIN_VALUE;
var newDSquared = this.dSquared + dSquaredIncrement;
double newDSquared = this.dSquared + dSquaredIncrement;
mean = newMean;
dSquared = newDSquared;
}
@@ -89,7 +89,7 @@ public final class StatBucket {
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (StatBucket) obj;
StatBucket that = (StatBucket) obj;
return this.ringbuf.count() == that.ringbuf.count() &&
Double.doubleToLongBits(this.mean) == Double.doubleToLongBits(that.mean);
}