api for annotations

This commit is contained in:
Jonathan Shook
2020-11-16 17:35:04 -06:00
parent c2cec2357c
commit 317ffab49c
13 changed files with 624 additions and 107 deletions

View File

@@ -0,0 +1,38 @@
package io.nosqlbench.nb.api;
public enum Layer {
/**
* Events which describe command line arguments, such as parsing,
* named scenario mapping, or critical errors
*/
CLI,
/**
* Events which describe scenario execution, such as parameters,
* lifecycle events, and critical errors
*/
Scenario,
/**
* Events which describe scripting details, such as extensions,
* sending programmatic annotations, or critical errors
*/
Script,
/**
* Events which are associated with a particular activity instance,
* such as parameters, starting and stopping, and critical errors
*/
Activity,
/**
* Events which are associated with a particular activity thread
*/
Motor,
/**
* Events which are associated with a particular operation or op template
*/
Operation
}

View File

@@ -1,43 +0,0 @@
package io.nosqlbench.nb.api.annotation;
import io.nosqlbench.nb.spi.Named;
import java.util.Map;
/**
* An implementation of this type is responsible for taking annotation details and
* logging them in a useful place.
*/
public interface Annotator extends Named {
/**
* Submit an annotation to some type of annotation store or logging or eventing mechanism.
* Implementations of this service are responsible for mapping the scenarioName, target,
* and details into the native schema of the target annotation or logging system in whichever
* way would be the least surprising for a user.
*
* The target is the nominative data which identifies the identity of the annotation. This
* must include enough information to allow the annotation to be homed and located within
* a target system such that is is visible where it should be seen. This includes all
* metadata which may be used to filter or locate the annotation, including timestamps.
*
* The details contain payload information to be displayed within the body of the annotation.
*
* @param sessionName The name of the scenario
* @param startEpochMillis The epoch millisecond instant of the annotation, set this to 0 to have it
* automatically set to the current system time.
* @param endEpochMillis The epoch millisecond instant at the end of the interval. If this is
* equal to the start instant, then this is an annotation for a point in time.
* This will be the default behavior if this value is 0.
* @param target The target of the annotation, fields which are required to associate the
* annotation with the correct instance of a dashboard, metrics, etc
* @param details A map of details
*/
void recordAnnotation(
String sessionName,
long startEpochMillis,
long endEpochMillis,
Map<String, String> target,
Map<String, String> details);
}

View File

@@ -0,0 +1,76 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.api.Layer;
import java.util.Map;
/**
* This is a general purpose representation of an event that describes
* a significant workflow detail to users running tests. It can be
* an event that describes an instant, or it can describe an interval
* in time (being associated with the interval of time between two
* canonical events.)
*
* This view of an annotation event captures the semantics of what
* any reportable annotation should look like from the perspective of
* NoSQLBench. It is up to the downstream consumers to map these
* to concrete fields or identifiers as appropriate.
*/
public interface Annotation {
/**
* @return The named session that the annotation is associated with
*/
String getSession();
/**
* If this is the same as {@link #getEnd()}, then the annotation is
* for an instant in time.
*
* @return The beginning of the interval of time that the annotation describes
*/
long getStart();
/**
* If this is the same as {@link #getStart()}, then the annotation
* is for an instant in time.
*
* @return The end of the interval of time that the annotation describes
*/
long getEnd();
/**
* Annotations must be associated with a processing layer in NoSQLBench.
* For more details on layers, see {@link Layer}
*
* @return
*/
Layer getLayer();
/**
* The labels which identify what this annotation pertains to. The following labels
* should be provided for every annotation, when available:
* <UL>
* <LI>appname: "nosqlbench"</LI>
* <LI>alias: The name of the activity alias, if available</LI>
* <LI>workload: The name of the workload file, if named scenarios are used</LI>
* <LI>scenario: The name of the named scenario, if named scenarios are used</LI>
* <LI>step: The name of the named scenario step, if named scenario are used</LI>
* <LI>usermode: "named_scenario" or "adhoc_activity"</LI>
* </UL>
*
* @return The labels map
*/
Map<String, String> getLabels();
/**
* The details are an ordered map of all the content that you would want the user to see.
*
* @return The details map
*/
Map<String, String> getDetails();
static BuilderFacets.WantsSession newBuilder() {
return new AnnotationBuilder();
}
}

View File

@@ -0,0 +1,77 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.api.Layer;
import java.util.LinkedHashMap;
public class AnnotationBuilder implements BuilderFacets.All {
private String session;
private long start;
private long end;
private final LinkedHashMap<String, String> labels = new LinkedHashMap<>();
private final LinkedHashMap<String, String> details = new LinkedHashMap<>();
private Layer layer;
@Override
public AnnotationBuilder layer(Layer layer) {
this.layer = layer;
this.label("layer", layer.toString());
return this;
}
@Override
public AnnotationBuilder interval(long start, long end) {
start(start);
end(end);
return this;
}
@Override
public AnnotationBuilder now() {
start(System.currentTimeMillis());
end(this.start);
return this;
}
private AnnotationBuilder start(long start) {
this.start = start;
return this;
}
private AnnotationBuilder end(long end) {
this.end = end;
return this;
}
@Override
public AnnotationBuilder at(long at) {
this.start(at);
this.end(at);
return this;
}
@Override
public AnnotationBuilder label(String name, String value) {
this.labels.put(name, value);
return this;
}
@Override
public BuilderFacets.WantsMoreDetailsOrBuild detail(String name, String value) {
this.details.put(name, value);
return this;
}
@Override
public Annotation build() {
return new MutableAnnotation(session, layer, start, end, labels, details).asReadOnly();
}
@Override
public BuilderFacets.WantsInterval session(String session) {
this.session = session;
return this;
}
}

View File

@@ -0,0 +1,21 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.spi.Named;
/**
* An implementation of this type is responsible for taking annotation details and
* logging them in a useful place.
*/
public interface Annotator extends Named {
/**
* Submit an annotation to some type of annotation store, logging or eventing mechanism.
* Implementations of this service are responsible for mapping the scenario and labels
* into appropriate key data, and the details in to a native payload. The least surprising
* and most obvious mapping should be used in each case.
*
* For details on constructing a useful annotation to submit to this service, see {@link Annotation#newBuilder()}
*/
void recordAnnotation(Annotation annotation);
}

View File

@@ -0,0 +1,58 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.api.Layer;
public interface BuilderFacets {
interface All extends
WantsSession, WantsInterval, WantsLayer, WantsLabels, WantsMoreDetailsOrBuild, WantsMoreLabelsOrDetails {
}
interface WantsSession {
/**
* The session is the global name of a NoSQLBench process which run a scenario. It is required.
*/
WantsInterval session(String session);
}
interface WantsInterval {
/**
* Specify the instant of the annotated event.
*
* @param epochMillis
*/
WantsLayer at(long epochMillis);
/**
* An interval annotation spans the time between two instants.
*/
WantsLayer interval(long startMillis, long endMillis);
/**
* Use the current UTC time as the annotation instant.
*/
WantsLayer now();
}
interface WantsLayer {
WantsMoreLabelsOrDetails layer(Layer layer);
}
interface WantsLabels {
WantsMoreLabelsOrDetails label(String name, String value);
}
interface WantsMoreLabelsOrDetails {
WantsMoreLabelsOrDetails label(String name, String value);
WantsMoreDetailsOrBuild detail(String name, String value);
}
interface WantsMoreDetailsOrBuild {
WantsMoreDetailsOrBuild detail(String name, String value);
Annotation build();
}
}

View File

@@ -0,0 +1,121 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.api.Layer;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
public class MutableAnnotation implements Annotation {
private String session = "SESSION_UNNAMED";
private Layer layer;
private long start = 0L;
private long end = 0L;
private Map<String, String> labels = new LinkedHashMap<>();
private Map<String, String> details = new LinkedHashMap<>();
public MutableAnnotation(String session, Layer layer, long start, long end, LinkedHashMap<String, String> labels,
LinkedHashMap<String, String> details) {
this.session = session;
this.layer = layer;
this.start = start;
this.end = end;
this.details = details;
this.labels = labels;
}
public void setSession(String sessionName) {
this.session = sessionName;
}
public void setStart(long intervalStart) {
this.start = intervalStart;
}
public void setEnd(long intervalEnd) {
this.end = intervalEnd;
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
public void setLayer(Layer layer) {
this.layer = layer;
this.labels.put("layer", layer.toString());
}
public void setDetails(Map<String, String> details) {
this.details = details;
}
@Override
public String getSession() {
return session;
}
@Override
public long getStart() {
return start;
}
@Override
public long getEnd() {
return end;
}
@Override
public Layer getLayer() {
return this.layer;
}
@Override
public Map<String, String> getLabels() {
return labels;
}
@Override
public Map<String, String> getDetails() {
return details;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("session: ").append(getSession()).append("\n");
sb.append("[").append(new Date(getStart()));
if (getStart() != getEnd()) {
sb.append(" - ").append(new Date(getEnd()));
}
sb.append("]\n");
sb.append("details:\n");
formatMap(sb, getDetails());
sb.append("labels:\n");
formatMap(sb, getLabels());
return sb.toString();
}
private void formatMap(StringBuilder sb, Map<String, String> details) {
details.forEach((k, v) -> {
sb.append(" ").append(k).append(": ");
if (v.contains("\n")) {
sb.append("\n");
String[] lines = v.split("\n+");
for (String line : lines) {
sb.append(" " + line + "\n");
}
// Arrays.stream(lines).sequential().map(s -> " "+s+"\n").forEach(sb::append);
} else {
sb.append(v).append("\n");
}
});
}
public Annotation asReadOnly() {
return this;
}
}

View File

@@ -0,0 +1,45 @@
package io.nosqlbench.nb.api.annotations;
import io.nosqlbench.nb.api.Layer;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class AnnotationBuilderTest {
private static final long time = 1600000000000L;
@Test
public void testBasicAnnotation() {
Annotation an1 = Annotation.newBuilder()
.session("test-session")
.at(time)
.layer(Layer.Scenario)
.label("labelka", "labelvb")
.label("labelkc", "labelvd")
.detail("detailk1", "detailv1")
.detail("detailk2", "detailv21\ndetailv22")
.detail("detailk3", "v1\nv2\nv3\n")
.build();
String represented = an1.toString();
assertThat(represented).isEqualTo("session: test-session\n" +
"[Sun Sep 13 07:26:40 CDT 2020]\n" +
"details:\n" +
" detailk1: detailv1\n" +
" detailk2: \n" +
" detailv21\n" +
" detailv22\n" +
" detailk3: \n" +
" v1\n" +
" v2\n" +
" v3\n" +
"labels:\n" +
" layer: Scenario\n" +
" labelka: labelvb\n" +
" labelkc: labelvd\n");
}
}