mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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++);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <T>. 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();
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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><resources>/<adaptername>.md - A single markdown file which is the named top-level
|
||||
* markdown file for this driver adapter.</li>
|
||||
* <li><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 {@link Service#selector()}</li>
|
||||
* <li><resources>/docs/<adaptername>.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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user