mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
many-sessions improvements, type fixes, more javadocs (#2094)
* some javadoc and type alignment improvements * some javadoc and type alignment improvements * example adapter * alignment to previous changes * improvements to many session testing * add missing annotation
This commit is contained in:
@@ -24,6 +24,16 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
|
||||
import io.nosqlbench.nb.api.labels.NBLabels;
|
||||
import io.nosqlbench.nb.api.components.core.NBComponent;
|
||||
|
||||
/**
|
||||
* <P>This service allows for the dynamic instancing of {@link DriverAdapter}s as services,
|
||||
* using a well-defined method signature instead of (just) a no-args constructor. Since all key elements
|
||||
* of the nosqlbench runtime are assembled into a component tree, each one requires an attachment
|
||||
* point (parent) and child identifiers (label names and values) that uniquely describe it. This would typically be
|
||||
* encoded as a constructor signature, however, there is no SPI mechanism which makes this easy to manage across JDPA
|
||||
* and non-JDPA runtimes. So instead, we indirect this to a higher level service which has one and only one
|
||||
* responsibility: to provide an instance through the well-defined API of {@link #load(NBComponent, NBLabels)}.
|
||||
* </P>
|
||||
*/
|
||||
public interface DriverAdapterLoader {
|
||||
public <A extends CycleOp<?>,B extends Space> DriverAdapter<A,B> load(NBComponent parent, NBLabels childLabels);
|
||||
public <A extends CycleOp<?>, B extends Space> DriverAdapter<A, B> load(NBComponent parent, NBLabels childLabels);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ package io.nosqlbench.adapters.api.activityimpl;
|
||||
|
||||
import com.codahale.metrics.Timer;
|
||||
import groovy.lang.Binding;
|
||||
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
|
||||
import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
|
||||
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
|
||||
import io.nosqlbench.adapters.api.evalctx.*;
|
||||
import io.nosqlbench.adapters.api.metrics.ThreadLocalNamedTimers;
|
||||
import io.nosqlbench.adapters.api.templating.ParsedOp;
|
||||
import io.nosqlbench.nb.api.components.core.NBComponent;
|
||||
import io.nosqlbench.nb.api.engine.metrics.instruments.MetricCategory;
|
||||
import io.nosqlbench.nb.api.labels.NBLabels;
|
||||
import io.nosqlbench.nb.api.errors.OpConfigError;
|
||||
@@ -47,7 +47,7 @@ import java.util.function.LongFunction;
|
||||
* @param <OP>
|
||||
* The type of operation
|
||||
*/
|
||||
public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
public abstract class BaseOpDispenser<OP extends CycleOp<?>, SPACE extends Space>
|
||||
extends NBBaseComponent implements OpDispenser<OP> {
|
||||
protected final static Logger logger = LogManager.getLogger(BaseOpDispenser.class);
|
||||
public static final String VERIFIER = "verifier";
|
||||
@@ -58,7 +58,6 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
public static final String STOP_TIMERS = "stop-timers";
|
||||
|
||||
private final String opName;
|
||||
protected final DriverAdapter<? extends OP, ? extends SPACE> adapter;
|
||||
private final NBLabels labels;
|
||||
public final Timer verifierTimer;
|
||||
private boolean instrument;
|
||||
@@ -81,11 +80,9 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
private final CycleFunction<Boolean> _verifier;
|
||||
private final ThreadLocal<CycleFunction<Boolean>> tlVerifier;
|
||||
|
||||
protected BaseOpDispenser(final DriverAdapter<? extends OP, ? extends SPACE> adapter, final ParsedOp op) {
|
||||
super(adapter);
|
||||
protected BaseOpDispenser(final NBComponent parentC, final ParsedOp op, LongFunction<? extends SPACE> spaceF) {
|
||||
super(parentC);
|
||||
opName = op.getName();
|
||||
this.adapter = adapter;
|
||||
this.spaceF = adapter.getSpaceFunc(op);
|
||||
labels = op.getLabels();
|
||||
|
||||
this.timerStarts = op.takeOptionalStaticValue(START_TIMERS, String.class)
|
||||
@@ -140,19 +137,19 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
try {
|
||||
initBlocks.forEach((initName, stringTemplate) -> {
|
||||
GroovyCycleFunction<?> initFunction =
|
||||
new GroovyCycleFunction<>(initName,stringTemplate,verifierImports,verifierStaticImports,variables);
|
||||
new GroovyCycleFunction<>(initName, stringTemplate, verifierImports, verifierStaticImports, variables);
|
||||
logger.info("configured verifier init:" + initFunction);
|
||||
initFunction.setVariable("_parsed_op",op);
|
||||
initFunction.setVariable("_parsed_op", op);
|
||||
initFunction.apply(0L);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new OpConfigError("error in verifier-init:" + e.getMessage(),e);
|
||||
throw new OpConfigError("error in verifier-init:" + e.getMessage(), e);
|
||||
}
|
||||
|
||||
Map<String, ParsedTemplateString> namedVerifiers = op.getTemplateMap().takeAsNamedTemplates(VERIFIER);
|
||||
List<CycleFunction<Boolean>> verifierFunctions = new ArrayList<>();
|
||||
try {
|
||||
namedVerifiers.forEach((verifierName,stringTemplate) -> {
|
||||
namedVerifiers.forEach((verifierName, stringTemplate) -> {
|
||||
GroovyBooleanCycleFunction verifier =
|
||||
new GroovyBooleanCycleFunction(verifierName, stringTemplate, verifierImports, verifierStaticImports, variables);
|
||||
logger.info("configured verifier:" + verifier);
|
||||
@@ -163,13 +160,13 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
}
|
||||
|
||||
try {
|
||||
op.takeAsOptionalStringTemplate(EXPECTED_RESULT)
|
||||
.map(tpl -> new GroovyObjectEqualityFunction(op.getName()+"-"+EXPECTED_RESULT, tpl, verifierImports, verifierStaticImports, variables))
|
||||
op.takeAsOptionalStringTemplate(EXPECTED_RESULT)
|
||||
.map(tpl -> new GroovyObjectEqualityFunction(op.getName() + "-" + EXPECTED_RESULT, tpl, verifierImports, verifierStaticImports, variables))
|
||||
.map(vl -> {
|
||||
logger.info("Configured equality verifier: " + vl);
|
||||
return vl;
|
||||
})
|
||||
.ifPresent(verifierFunctions::add);
|
||||
.ifPresent(verifierFunctions::add);
|
||||
} catch (Exception gre) {
|
||||
throw new OpConfigError("error in verifier:" + gre.getMessage(), gre);
|
||||
}
|
||||
@@ -182,23 +179,20 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
|
||||
return this.opName;
|
||||
}
|
||||
|
||||
public DriverAdapter<? extends OP, ? extends SPACE> getAdapter() {
|
||||
return this.adapter;
|
||||
}
|
||||
|
||||
private void configureInstrumentation(final ParsedOp pop) {
|
||||
instrument = pop.takeStaticConfigOr("instrument", false);
|
||||
if (this.instrument) {
|
||||
final int hdrDigits = pop.getStaticConfigOr("hdr_digits", 4);
|
||||
|
||||
successTimer = create().timer(
|
||||
"successfor_"+getOpName(),
|
||||
"successfor_" + getOpName(),
|
||||
hdrDigits,
|
||||
MetricCategory.Core,
|
||||
"Successful result timer for specific operation '" + pop.getName() + "'"
|
||||
);
|
||||
|
||||
errorTimer = create().timer(
|
||||
"errorsfor_"+getOpName(),
|
||||
"errorsfor_" + getOpName(),
|
||||
hdrDigits,
|
||||
MetricCategory.Core,
|
||||
"Errored result timer for specific operation '" + pop.getName() + "'"
|
||||
|
||||
@@ -78,12 +78,12 @@ public interface OpDispenser<OP extends CycleOp<?>> extends LongFunction<OP>, Op
|
||||
* additional processing if a caller wants to execute the operation
|
||||
* multiple times, as for retries.
|
||||
*
|
||||
* @param value The cycle number which serves as the seed for any
|
||||
* @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
|
||||
*/
|
||||
|
||||
OP getOp(long value);
|
||||
OP getOp(long cycle);
|
||||
|
||||
CycleFunction<Boolean> getVerifier();
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
|
||||
import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
|
||||
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
|
||||
import io.nosqlbench.adapters.api.templating.ParsedOp;
|
||||
import io.nosqlbench.nb.api.components.core.NBComponent;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
@@ -77,24 +77,86 @@ import java.util.function.LongFunction;
|
||||
* matches the driver name.
|
||||
* </p>
|
||||
*
|
||||
* @param <OPTYPE> The parameter type of the actual operation which will be used
|
||||
* to hold all the details for executing an operation,
|
||||
* generally something that implements {@link Runnable}.
|
||||
* @param <OPTYPE>
|
||||
* The parameter type of the actual operation which will be used
|
||||
* to hold all the details for executing an operation,
|
||||
* generally something that implements {@link Runnable}.
|
||||
*/
|
||||
public interface OpMapper<OPTYPE extends CycleOp<?>, SPACETYPE extends Space>
|
||||
extends BiFunction<ParsedOp, LongFunction<SPACETYPE>, OpDispenser<? extends OPTYPE>> {
|
||||
// extends BiFunction<ParsedOp, LongFunction<SPACETYPE>, OpDispenser<? extends OPTYPE>>
|
||||
{
|
||||
|
||||
/**
|
||||
* Interrogate the parsed command, and provide a new
|
||||
* This method is responsible for interrogating the fields in the provided {@link ParsedOp} template object,
|
||||
* determining what adapter-specific operation it maps to, and creating the associated {@link OpDispenser} for
|
||||
* that type.
|
||||
*
|
||||
* @param op
|
||||
* 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.
|
||||
* <H2>Implementation Notes</H2>
|
||||
*
|
||||
* <P>It is important to be familiar with the structure of the {@link ParsedOp}, since this is the runtime model
|
||||
* API for an op template. It provides everything you need to turn various op fields into proper lambdas, which
|
||||
* can then be composed together to make higher-order lambdas. The returned {@link OpDispenser} is essentially
|
||||
* a singular {@link LongFunction} which captures all of the just-in-time construction patterns needed within.</P>
|
||||
*
|
||||
* <H3>Op Mapping</H3>
|
||||
* <p>
|
||||
* Generally speaking, implementations of this method should interrogate the op fields in the ParsedOp to determine
|
||||
* the specific op that matches the user's intentions. It is <EM>Highly</EM> reccommended that each of the valid
|
||||
* op types is presented as an example in the associated adapter documentation. (Each adapter must have a
|
||||
* self-named markdown help file in it's source tree.) Good op mappers are based on specific examples which are
|
||||
* documented, as this is the only way a user knows what op types are available.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* What determines the type of op can be based on something explicit, like the value of a {@code type} field, or it
|
||||
* can be based on whether
|
||||
* certain fields are present or not. Advanced implementations might take into account which fields are provided as
|
||||
* static values and which are specified as (dynamic) bindings. The op mapping phase is meant to qualify and
|
||||
* pre-check that the fields provided are valid and specific for a given type of operation.
|
||||
* </p>
|
||||
*
|
||||
* <p>All of the effective logic for op mapping must be contained within the
|
||||
* {@link #apply(NBComponent, ParsedOp, LongFunction)} method. This includes what happens within the constructor of
|
||||
* any {@link OpDispenser}. What happens within {@link OpDispenser} implementations (the second phase), however,
|
||||
* should do as little qualification of field values as possible, focusing simply on constructing the type of
|
||||
* operation for which they are designed. This suggest the following conventions:
|
||||
* <UL>
|
||||
* <LI>Type-mapping logic (determine which op type) is done in the main body of
|
||||
* {@link #apply(NBComponent, ParsedOp, LongFunction)}, and nothing else. Once the desired op dispenser (based on
|
||||
* the intended op type) is determined, it is immediately constructed and returned.
|
||||
* </LI>
|
||||
* <LI>
|
||||
* Lambda-construction logic is contained in the constructor of the returned op dispenser. This pre-bakes
|
||||
* as much of the op construction behavior as possible, building only a single lambda to do the heavy lifting
|
||||
* later.
|
||||
* </LI>
|
||||
* <LI>When {@link OpDispenser#apply(long)}</LI> is called with a cycle value, it only needs to call the lambda to
|
||||
* return a fully-formed op, ready to be executed via its {@link CycleOp#apply(long)} method.
|
||||
* </UL>
|
||||
* </p>
|
||||
*
|
||||
* @param adapterC
|
||||
* The adapter component. This is passed as an {@link NBComponent} because it is not valid to rely
|
||||
* on the driver adapter instance directly for anything. All logic for an op should be captured
|
||||
* in its mapper and dispenser, and all (other) state for it should be captured within the space.
|
||||
* However, the mapper exists within the nosqlbench runtime as part of the component tree, so it
|
||||
* is included for that reason alone. (You'll need it for super construtors in some cases)
|
||||
* @param pop
|
||||
* 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
|
||||
* @return An OpDispenser which can be used to synthesize real operations.
|
||||
* 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
|
||||
* only within the stack. Using this function to do anything but build more lambdas is probably a
|
||||
* programming error.
|
||||
* @return An OpDispenser which can be used to synthesize directly executable operations.
|
||||
*/
|
||||
|
||||
@Override
|
||||
OpDispenser<OPTYPE> apply(ParsedOp op, LongFunction<SPACETYPE> spaceInitF);
|
||||
OpDispenser<OPTYPE> apply(
|
||||
NBComponent adapterC,
|
||||
ParsedOp pop,
|
||||
LongFunction<SPACETYPE> spaceInitF
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ package io.nosqlbench.adapters.api.activityimpl.uniform;
|
||||
|
||||
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.
|
||||
* @param <SelfT>
|
||||
*/
|
||||
public class BaseSpace<SelfT extends BaseSpace<SelfT> > implements Space {
|
||||
|
||||
private final String spaceName;
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ConcurrentIndexCache<T> implements Iterable<T> {
|
||||
return currentCache.get(key);
|
||||
}
|
||||
}
|
||||
logger.debug(() -> "returning index[ " + key + " ] for [ " + label + " ] cache");
|
||||
logger.trace(() -> "returning index[ " + key + " ] for [ " + label + " ] cache");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -36,70 +36,95 @@ import java.util.function.Function;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* <P>The DriverAdapter interface is the replacement
|
||||
* for ActivityTypes. This interface takes a simpler
|
||||
* approach. Specifically, all of the core logic which was being pasted into each
|
||||
* driver type is centralized, and only the necessary interfaces
|
||||
* needed for construction new operations and shared context are exposed.
|
||||
* This means all drivers can now benefit from cross-cutting enhancements
|
||||
* in the core implementation.
|
||||
* <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>
|
||||
*
|
||||
* @param <OPTYPE> The type of Runnable operation 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.
|
||||
* @param <SPACETYPE> The type of context space used by this driver to hold
|
||||
* cached instances of clients, session, or other native driver
|
||||
* esoterica. This is the shared state which might be needed
|
||||
* during construction of R type operations, or even for individual
|
||||
* operations.
|
||||
* <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>
|
||||
*/
|
||||
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 the parsed
|
||||
* fields in a {@link ParsedOp} and create an OpDispenser.
|
||||
* An OpDispenser is a function that will produce a special
|
||||
* type {@link OPTYPE} that this DriverAdapter implements as its
|
||||
* op implementation.</p>
|
||||
* 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>
|
||||
* The function that is returned is responsible for creating another function.
|
||||
* This might seem counter-intuitive but it is very intentional because
|
||||
* of these design constraints:
|
||||
* <UL>
|
||||
* <LI>Mapping op semantics to a type of operation must be very clear
|
||||
* and flexible. Performance is not important at this layer because this is all done
|
||||
* during initialization time for an activity.</LI>
|
||||
* <LI>Synthesizing executable operations from a known type of operational template
|
||||
* must be done very efficiently. This part is done during activity execution, so
|
||||
* having the details of how you are going to create an op for execution already
|
||||
* sorted out is important.</LI>
|
||||
* </UL>
|
||||
* 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>
|
||||
*
|
||||
* To clarify the distinction between these two phases, the first is canonically
|
||||
* called <em>op mapping</em> in the documentation. The second is called
|
||||
* <em>op synthesis</em>.
|
||||
* <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>
|
||||
* <H2>A note on implementation strategy:</H2>
|
||||
* Generally speaking, implementations of this method should interrogate the op fields
|
||||
* in the ParsedOp and return an OpDispenser that matches the user's intentions.
|
||||
* This can be based on something explicit, like the value of a {@code type} field,
|
||||
* or it can be based on whether certain fields are present or not. Advanced implementations
|
||||
* might take into account which fields are provided as static values and which are
|
||||
* specified as bindings. In any case, the op mapping phase is meant to qualify and
|
||||
* pre-check that the fields provided are valid and specific for a given type of operation.
|
||||
* What happens within {@link OpDispenser} implementations (the second phase), however, should do
|
||||
* as little qualification of field values as possible, focusing simply on constructing
|
||||
* the type of operation for which they are designed.
|
||||
* Conversely, <EM>op dispensing</EM> (the next phase) while an activity is running should be as efficient as
|
||||
* possible.
|
||||
* </p>
|
||||
*
|
||||
* @return a synthesizer function for {@link OPTYPE} op generation
|
||||
* @return a dispensing function for {@link OPTYPE} op generation
|
||||
*/
|
||||
OpMapper<OPTYPE, SPACETYPE> getOpMapper();
|
||||
|
||||
@@ -108,7 +133,8 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
|
||||
* 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.
|
||||
* {@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.
|
||||
*/
|
||||
@@ -121,28 +147,29 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
|
||||
* 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 name.
|
||||
* @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();
|
||||
}
|
||||
|
||||
default List<Function<Map<String,Object>,Map<String,Object>>> getOpFieldRemappers() {
|
||||
default List<Function<Map<String, Object>, Map<String, Object>>> getOpFieldRemappers() {
|
||||
return List.of(f -> f);
|
||||
}
|
||||
|
||||
// ConcurrentSpaceCache<SPACETYPE> getSpaceCache();
|
||||
|
||||
/**
|
||||
* This method allows each driver adapter to create named state which is automatically
|
||||
* <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.
|
||||
* 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.
|
||||
* object state related to retained objects for the lifetime of a native driver.
|
||||
*/
|
||||
default LongFunction<SPACETYPE> getSpaceInitializer(NBConfiguration cfg) {
|
||||
return n -> (SPACETYPE) new Space() {
|
||||
@@ -162,8 +189,11 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
|
||||
* 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>
|
||||
* path <resources>/docs/<adaptername>. Specifically, the file
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
@@ -184,7 +214,7 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
|
||||
|
||||
default String getAdapterName() {
|
||||
Service svc = this.getClass().getAnnotation(Service.class);
|
||||
if (svc==null) {
|
||||
if (svc == null) {
|
||||
throw new RuntimeException("The Service annotation for adapter of type " + this.getClass().getCanonicalName() + " is missing.");
|
||||
}
|
||||
return svc.selector();
|
||||
|
||||
@@ -23,25 +23,25 @@ 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
|
||||
* <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, it is very
|
||||
* likely a candidate for factoring into separate op implementations.
|
||||
* The {@link OpMapper}
|
||||
* and {@link OpDispenser} abstractions are meant to move
|
||||
* op type selection and scheduling to earlier in the activity.
|
||||
* 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> {
|
||||
/**
|
||||
* <p>Run an action for the given cycle.</p>
|
||||
*
|
||||
* @param value The cycle value for which an operation is run
|
||||
* @param value
|
||||
* The cycle value for which an operation is run
|
||||
*/
|
||||
@Override
|
||||
T apply(long value);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class DryCycleOpDispenserWrapper<S extends Space, RESULT> extends BaseOpD
|
||||
ParsedOp pop,
|
||||
OpDispenser<CycleOp<RESULT>> realDispenser
|
||||
) {
|
||||
super(adapter, pop);
|
||||
super(adapter, pop, adapter.getSpaceFunc(pop));
|
||||
this.realDispenser = realDispenser;
|
||||
logger.warn(
|
||||
"initialized {} for dry run only. " +
|
||||
|
||||
@@ -32,7 +32,7 @@ public class EmitterCycleOpDispenserWrapper<O,S extends Space,R> extends BaseOpD
|
||||
ParsedOp pop,
|
||||
OpDispenser<CycleOp<R>> realDispenser
|
||||
) {
|
||||
super(adapter, pop);
|
||||
super(adapter, pop, adapter.getSpaceFunc(pop));
|
||||
this.realDispenser = realDispenser;
|
||||
logger.warn(
|
||||
"initialized {} for to emit the result type to stdout. ",
|
||||
|
||||
@@ -29,7 +29,7 @@ public class EmitterOpDispenserWrapper extends BaseOpDispenser<CycleOp<?>, Space
|
||||
|
||||
public EmitterOpDispenserWrapper(DriverAdapter<CycleOp<?>,Space> adapter, ParsedOp pop,
|
||||
OpDispenser<? extends CycleOp<?>> realDispenser) {
|
||||
super(adapter, pop);
|
||||
super(adapter, pop, adapter.getSpaceFunc(pop));
|
||||
this.realDispenser = realDispenser;
|
||||
}
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user