Add NBAdvisor Framework (#2070)

* Add NBAdvisor Framework

* Revert "Add NBAdvisor Framework"

This reverts commit f2f448ffc3.

* Reapply "Add NBAdvisor Framework"

This reverts commit 8048402b88.

* Update pom.xml

* Adjust

* Fix tabs

* Revert change including test

* Simplify toString

* Advisor is now a global option

* Added error checking

* NB Advisor runtime scaffold prototype

* fix double dispatch error

* Make NBAdvisorLevel sticky and evaluate error count

* Working towards better encapsulation

* advisor refinements

* document alignment of log levels and advisor levels

* doc updates

* Evaluation refactor and fix integration tests

* Cleanup tabs

* Rename Exception

* Use a logger for output

* Small cleanup

* Remove extraneous dependency

* OF - bug fix

* Add Advisor Exception Test

---------

Co-authored-by: Jonathan Shook <jshook@gmail.com>
This commit is contained in:
Dave Fisher 2024-11-11 10:08:08 -08:00 committed by GitHub
parent 041dafa5c6
commit 7a9b2237de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 869 additions and 46 deletions

View File

@ -20,6 +20,7 @@ import com.amazonaws.util.StringInputStream;
import com.google.gson.GsonBuilder;
import io.nosqlbench.nb.api.nbio.Content;
import io.nosqlbench.nb.api.nbio.NBIO;
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;
@ -129,10 +130,10 @@ public class OpsLoader {
System.err.println(stderrOutput);
if (resultStatus==0 && stderrOutput.isEmpty()) {
logger.info("no errors detected during jsonnet evaluation.");
System.exit(0);
throw new NBAdvisorException("dryrun=jsonnet: No errors detected.", 0);
} else {
logger.error("ERRORS detected during jsonnet evaluation:\n" + stderrOutput);
System.exit(2);
throw new NBAdvisorException("dryrun=jsonnet: Errors detected.", 2);
}
}
if (!stderrOutput.isEmpty()) {

View File

@ -182,6 +182,7 @@ public abstract class BaseDriverAdapter<RESULT
.add(Param.defaultTo("dryrun", "none").setRegex("(op|jsonnet|emit|none)"))
.add(Param.optional("maxtries", Integer.class))
.asReadOnly();
}
@Override

View File

@ -162,7 +162,6 @@
<version>3.46.0.0</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,48 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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;
public abstract class BaseAdvisorBuilder<ELEMENT, T, SelfT extends BaseAdvisorBuilder<ELEMENT, T, SelfT>>
extends NBAdvisorPointOrBuilder<ELEMENT> {
protected NBComponent component;
protected String name;
protected String description;
protected abstract SelfT self();
public SelfT component(NBComponent component) {
this.component = component;
return self();
}
public SelfT name(String name) {
this.name = name;
return self();
}
public SelfT desc(String description) {
this.description = description;
return self();
}
public abstract <PTYPE> NBAdvisorPoint<PTYPE> build();
}

View File

@ -0,0 +1,33 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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 class NBAdvisorBuilder<PTYPE>
extends BaseAdvisorBuilder<PTYPE, NBAdvisorPoint<PTYPE>, NBAdvisorBuilder<PTYPE>> {
@Override
protected NBAdvisorBuilder<PTYPE> self() {
return this;
}
@Override
public NBAdvisorPoint<PTYPE> build() {
return (NBAdvisorPoint<PTYPE>) new NBAdvisorPoint<PTYPE>(name, description == null ? name : description);
}
}

View File

@ -0,0 +1,47 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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.NBNamedElement;
import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.logging.log4j.Level;
public interface NBAdvisorCondition<T> extends Function<T, NBAdvisorPoint.Result<T>>, Predicate<T> {
Function<T, String> okMsg();
Function<T, String> errMsg();
Level level();
@Override
default NBAdvisorPoint.Result<T> apply(T element) {
boolean hasError = test(element);
return new NBAdvisorPoint.Result<>(
this,
element,
hasError ? NBAdvisorPoint.Status.ERROR : NBAdvisorPoint.Status.OK
);
}
String getName();
}

View File

@ -0,0 +1,36 @@
/*
* 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.
*/
package io.nosqlbench.nb.api.advisor;
public class NBAdvisorException extends RuntimeException {
private final String message;
private final int exitCode;
public NBAdvisorException(String message, int exitCode) {
this.message = message;
this.exitCode = exitCode;
}
public String toString() {
return this.message;
}
public int getExitCode() {
return this.exitCode;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.
*/
package io.nosqlbench.nb.api.advisor;
import java.util.Locale;
/**
* This is related to {@link io.nosqlbench.nb.api.advisor.conditions.Conditions}, and the terms
* should be aligned. When possible, the the Conditions class should be used to capture
* re-usable conditions, so that there is only one instance of each distinct type in the runtime,
* regardless of how many components use it in their advisor points.
*
*
*/
public enum NBAdvisorLevel {
/**
* Do not analyze arguments, scenarios, activities, and workloads
*/
none,
/**
* Provide advice about invalid, incorrect, and unused ops
*/
validate,
/**
* Only allow correct operations
*/
enforce;
// Static field to store the last used setting
private static NBAdvisorLevel level = none;
public static NBAdvisorLevel fromString(String advisorStr) {
try {
level = NBAdvisorLevel.valueOf(advisorStr.toLowerCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
System.out.println("--advisor=" + advisorStr + " is invalid. Using 'none'");
level = NBAdvisorLevel.none;
}
return level;
}
public static NBAdvisorLevel get() {
return level;
}
public static boolean isAdvisorActive() {
return level != NBAdvisorLevel.none;
}
public static boolean isEnforcerActive() {
return level == NBAdvisorLevel.enforce;
}
}

View File

@ -0,0 +1,115 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Level;
import java.util.*;
public class NBAdvisorPoint<T> extends NBAdvisorPointOrBuilder<T> {
private final static Logger logger = LogManager.getLogger("ADVISOR");
private final String name;
private final String description;
private NBAdvisorLevel advisorLevel = NBAdvisorLevel.none;
private NBAdvisorCondition<T>[] conditions = new NBAdvisorCondition[0];
private List<Result<?>> resultLog = new ArrayList<Result<?>>();
public NBAdvisorPoint(String name) {
this(name, null);
}
public NBAdvisorPoint(String name, String description) {
this.name = name;
this.description = description == null ? name : description;
this.advisorLevel = NBAdvisorLevel.get();
}
public Result<T>[] validateAll(Collection<T> elements) {
List<Result<T>> buffer = new ArrayList<>();
for (T element : elements) {
Result<T>[] oneElementValidation = validate(element);
for (Result<T> r : oneElementValidation) {
buffer.add(r);
}
}
return buffer.toArray(new Result[0]);
}
public synchronized Result<T>[] validate(T element) {
Result<T>[] results = new Result[conditions.length];
for (int i = 0; i < conditions.length; i++) {
results[i] = conditions[i].apply(element);
resultLog.add(results[i]);
}
return results;
}
public List<Result<?>> getResultLog() {
return this.resultLog;
}
public NBAdvisorPoint<T> add(NBAdvisorCondition<T> condition) {
_addArrayCondition(condition);
return this;
}
public String[] errorMessages(T element) {
Result<T>[] results = this.validate(element);
return Arrays.stream(results).filter(Result::isError).map(Result::rendered).toArray(String[]::new);
}
private void _addArrayCondition(NBAdvisorCondition<T> condition) {
NBAdvisorCondition<T>[] newConditions = new NBAdvisorCondition[conditions.length + 1];
System.arraycopy(conditions, 0, newConditions, 0, conditions.length);
newConditions[newConditions.length - 1] = condition;
conditions = newConditions;
}
public static enum Status {
OK,
ERROR
}
public static record Result<T>(
NBAdvisorCondition<T> condition,
T element,
Status status
) {
public boolean isError() {
return status == Status.ERROR;
}
public Level conditionLevel() {
return condition.level();
}
public String rendered() {
return switch (status) {
case OK -> "OK: " + condition.okMsg().apply(element);
case ERROR -> conditionLevel() + ": " + condition.errMsg().apply(element);
};
}
}
}

View File

@ -0,0 +1,22 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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 class NBAdvisorPointOrBuilder<T> {
}

View File

@ -0,0 +1,95 @@
package io.nosqlbench.nb.api.advisor;
/*
* 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Level;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class NBAdvisorResults {
private final static Logger logger = LogManager.getLogger("ADVISOR");
private final List<NBAdvisorPoint<?>> points = new ArrayList<>();
public NBAdvisorResults(List<NBAdvisorPoint<?>> points) {
this.points.addAll(points);
}
public List<NBAdvisorPoint.Result<?>> getAdvisorResults() {
return points.stream().flatMap(a -> a.getResultLog().stream()).toList();
}
public void render(Level level,String message) {
if (level == Level.INFO) {
logger.info(message);
} else if (level == Level.WARN) {
logger.warn(message);
} else if (level == Level.ERROR) {
logger.error(message);
}
}
public int evaluate() {
List<NBAdvisorPoint.Result<?>> results = getAdvisorResults();
Iterator<NBAdvisorPoint.Result<?>> iterator = results.iterator();
int count = 0;
boolean terminate = false;
Level level = Level.INFO;
while (iterator.hasNext()) {
NBAdvisorPoint.Result<?> result = iterator.next();
level = result.isError() ? result.conditionLevel() : level.INFO;
switch (NBAdvisorLevel.get()) {
case NBAdvisorLevel.none:
if ( level == Level.ERROR ) {
render(level, result.rendered());
count++;
terminate = true;
}
break;
case NBAdvisorLevel.validate:
if ( level == Level.ERROR ) {
render(level, result.rendered());
count++;
terminate = true;
} else {
render(Level.INFO, result.rendered());
}
break;
case NBAdvisorLevel.enforce:
if ( level == Level.ERROR || level == Level.WARN ) {
render(level, result.rendered());
count++;
terminate = true;
}
break;
}
}
if ( terminate ) {
String message = String.format("Advisor found %d actionable %s.",
count,
(count < 2 ? "error" : "errors"));
render(Level.ERROR, message);
throw new NBAdvisorException(message, 2);
}
return count;
}
}

View File

@ -0,0 +1,52 @@
package io.nosqlbench.nb.api.advisor.conditions;
/*
* 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.advisor.NBAdvisorCondition;
import org.apache.logging.log4j.Level;
/**
* <P>The mappings between logging levels for conditions and the advisory
* levels should be consistent, so they will be described here to start.</P>
*
* <P>These are the levels which conditions should use:</P>
* <OL>
* <LI>Level.ERROR (internal to conditions, cause immediate exceptions)</LI>
* <LI>Level.WARN (conditions which should inform the user about a likely issue)</LI>
* <LI>Level.INFO (conditions which may inform the user about a possible issue)</LI>
* </OL>
*
* <P>This means that the following three behaviors are possible:
* <OL>
* <LI>{@link io.nosqlbench.nb.api.advisor.NBAdvisorLevel#none} - Only ERROR level results throw exceptions, no
* results are presented.</LI>
* <LI>{@link io.nosqlbench.nb.api.advisor.NBAdvisorLevel#validate} - Only ERROR level results throw exceptions,
* all results are presented. </LI>
* <LI>{@link io.nosqlbench.nb.api.advisor.NBAdvisorLevel#enforce} - ERROR and WARN levels throw exceptions.</LI>
* </P>
*
*/
public class Conditions {
public static NoHyphens NoHyphensError = new NoHyphens(Level.ERROR);
public static NoHyphens NoHyphensWarning = new NoHyphens(Level.WARN);
public static NoSpaces NoSpacesError = new NoSpaces(Level.ERROR);
public static NoSpaces NoSpacesWarning = new NoSpaces(Level.WARN);
}

View File

@ -0,0 +1,58 @@
package io.nosqlbench.nb.api.advisor.conditions;
/*
* 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.advisor.NBAdvisorCondition;
import org.apache.logging.log4j.Level;
import java.util.function.Function;
public class NoHyphens implements NBAdvisorCondition<String> {
private final Level level;
public NoHyphens(Level level) {
this.level = level;
}
@Override
public boolean test(String string) {
return string.contains("-");
}
@Override
public Function<String, String> okMsg() {
return s -> "String '" + s + "' does not contain hyphens";
}
@Override
public Function<String, String> errMsg() {
return s -> "String '" + s + "' should not contain hyphens";
}
@Override
public Level level() {
return this.level;
}
@Override
public String getName() {
return "no-hyphens";
}
}

View File

@ -0,0 +1,58 @@
package io.nosqlbench.nb.api.advisor.conditions;
/*
* 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.advisor.NBAdvisorCondition;
import org.apache.logging.log4j.Level;
import java.util.function.Function;
public class NoSpaces implements NBAdvisorCondition<String> {
private final Level level;
public NoSpaces(Level level) {
this.level = level;
}
@Override
public Function<String, String> okMsg() {
return string -> "String '" + string + "' does not contain spaces";
}
@Override
public Function<String, String> errMsg() {
return string -> "String '" +string + "' should not contain spaces";
}
@Override
public Level level() {
return level;
}
@Override
public String getName() {
return "no spaces";
}
@Override
public boolean test(String s) {
return s.contains(" ");
}
}

View File

@ -16,6 +16,8 @@
package io.nosqlbench.nb.api.components.core;
import io.nosqlbench.nb.api.advisor.*;
import io.nosqlbench.nb.api.advisor.conditions.Conditions;
import io.nosqlbench.nb.api.components.decorators.NBTokenWords;
import io.nosqlbench.nb.api.components.events.ComponentOutOfScope;
import io.nosqlbench.nb.api.components.events.DownEvent;
@ -24,6 +26,7 @@ import io.nosqlbench.nb.api.components.events.UpEvent;
import io.nosqlbench.nb.api.engine.metrics.MetricsCloseable;
import io.nosqlbench.nb.api.engine.metrics.instruments.NBMetric;
import io.nosqlbench.nb.api.labels.NBLabels;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -32,15 +35,17 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public class NBBaseComponent extends NBBaseComponentMetrics implements NBComponent, NBTokenWords, NBComponentTimeline {
private final static Logger logger = LogManager.getLogger("RUNTIME");
protected final NBComponent parent;
protected final NBLabels labels;
private final List<NBComponent> children = new ArrayList<>();
private final List<NBAdvisorPoint<?>> advisors = new ArrayList<>();
protected NBMetricsBuffer metricsBuffer = new NBMetricsBuffer();
protected boolean bufferOrphanedMetrics = false;
private ConcurrentHashMap<String,String> props = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, String> props = new ConcurrentHashMap<>();
protected Exception error;
protected long started_ns, teardown_ns, closed_ns, errored_ns, started_epoch_ms;
protected NBInvokableState state = NBInvokableState.STARTING;
@ -51,6 +56,19 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
}
public NBBaseComponent(NBComponent parentComponent, NBLabels componentSpecificLabelsOnly) {
NBAdvisorPoint<String> labelsAdvisor = create().advisor(b -> b.name("Check labels"));
// ^ Explicitly name the generic type here
// ^ retain the advisor instance for customization, even though it is already attached to
// the current component
labelsAdvisor.add(Conditions.NoHyphensError);
labelsAdvisor.add(Conditions.NoSpacesWarning);
labelsAdvisor.validateAll(componentSpecificLabelsOnly.asMap().keySet());
labelsAdvisor.validateAll(componentSpecificLabelsOnly.asMap().values());
NBAdvisorResults advisorResults = getAdvisorResults();
advisorResults.evaluate();
this.started_ns = System.nanoTime();
this.started_epoch_ms = System.currentTimeMillis();
this.labels = componentSpecificLabelsOnly;
@ -60,11 +78,11 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
} else {
parent = null;
}
state = (state==NBInvokableState.ERRORED) ? state : NBInvokableState.RUNNING;
state = (state == NBInvokableState.ERRORED) ? state : NBInvokableState.RUNNING;
}
public NBBaseComponent(NBComponent parentComponent, NBLabels componentSpecificLabelsOnly, Map<String, String> props) {
this(parentComponent,componentSpecificLabelsOnly);
this(parentComponent, componentSpecificLabelsOnly);
props.forEach(this::setComponentProp);
}
@ -82,10 +100,8 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
NBLabels eachLabels = extant.getComponentOnlyLabels();
NBLabels newLabels = child.getComponentOnlyLabels();
if (eachLabels!=null && newLabels!=null && !eachLabels.isEmpty() && !newLabels.isEmpty() && child.getComponentOnlyLabels().equals(extant.getComponentOnlyLabels())) {
throw new RuntimeException("Adding second child under already-defined labels is not allowed:\n" +
" extant: (" + extant.getClass().getSimpleName() + ") " + extant.description() + "\n" +
" adding: (" + child.getClass().getSimpleName() + ") " + child.description());
if (eachLabels != null && newLabels != null && !eachLabels.isEmpty() && !newLabels.isEmpty() && child.getComponentOnlyLabels().equals(extant.getComponentOnlyLabels())) {
throw new RuntimeException("Adding second child under already-defined labels is not allowed:\n" + " extant: (" + extant.getClass().getSimpleName() + ") " + extant.description() + "\n" + " adding: (" + child.getClass().getSimpleName() + ") " + child.description());
}
}
@ -129,7 +145,7 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
@Override
public final void close() throws RuntimeException {
state = (state==NBInvokableState.ERRORED) ? state : NBInvokableState.CLOSING;
state = (state == NBInvokableState.ERRORED) ? state : NBInvokableState.CLOSING;
closed_ns = System.nanoTime();
try {
@ -156,8 +172,9 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
RuntimeException wrapped = new RuntimeException("While in state " + this.state + ", an error occured: " + e, e);
logger.error(wrapped);
this.error = wrapped;
state=NBInvokableState.ERRORED;
state = NBInvokableState.ERRORED;
}
/**
* Override this method in your component implementations when you need to do something
* to close out your component.
@ -165,7 +182,7 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
protected void teardown() {
logger.debug("tearing down " + description());
this.teardown_ns = System.nanoTime();
this.state=(state==NBInvokableState.ERRORED) ? state : NBInvokableState.STOPPED;
this.state = (state == NBInvokableState.ERRORED) ? state : NBInvokableState.STOPPED;
}
@Override
@ -240,7 +257,8 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
* This method is called by the engine to report a component going out of scope. The metrics for that component
* will bubble up through the component layers and can be buffered for reporting at multiple levels.
*
* @param m The metric to report
* @param m
* The metric to report
*/
@Override
public void reportExecutionMetric(NBMetric m) {
@ -254,8 +272,8 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
@Override
public long getNanosSinceStart() {
if (teardown_ns ==0) {
return System.nanoTime()- started_ns;
if (teardown_ns == 0) {
return System.nanoTime() - started_ns;
} else {
return teardown_ns - started_ns;
}
@ -263,10 +281,10 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
@Override
public Optional<String> getComponentProp(String name) {
if (this.props!=null && this.props.containsKey(name)) {
if (this.props != null && this.props.containsKey(name)) {
return Optional.ofNullable(this.props.get(name));
} else if (this.getParent()!=null) {
return this.getParent().getComponentProp(name);
} else if (this.getParent() != null) {
return this.getParent().getComponentProp(name);
} else {
return Optional.empty();
}
@ -274,7 +292,7 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
@Override
public NBComponentProps setComponentProp(String name, String value) {
if (this.props==null) {
if (this.props == null) {
this.props = new ConcurrentHashMap<>();
}
props.put(name, value);
@ -315,4 +333,13 @@ public class NBBaseComponent extends NBBaseComponentMetrics implements NBCompone
metricsCloseables.add(metric);
}
@Override
public void addAdvisor(NBAdvisorPoint advisor) {
this.advisors.add(advisor);
}
@Override
public List<NBAdvisorPoint<?>> getAdvisors() {
return advisors;
}
}

View File

@ -43,7 +43,8 @@ public interface NBComponent extends
NBComponentServices,
NBComponentEvents,
NBProviderSearch,
NBComponentProps {
NBComponentProps,
NBComponentAdvisors {
NBComponent EMPTY_COMPONENT = new NBBaseComponent(null);

View File

@ -0,0 +1,36 @@
package io.nosqlbench.nb.api.components.core;
/*
* 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.advisor.NBAdvisorPoint;
import io.nosqlbench.nb.api.advisor.NBAdvisorResults;
import java.util.List;
import java.util.stream.Collectors;
public interface NBComponentAdvisors {
void addAdvisor(NBAdvisorPoint<?> advisor);
List<NBAdvisorPoint<?>> getAdvisors();
default NBAdvisorResults getAdvisorResults() {
return new NBAdvisorResults(getAdvisors());
}
}

View File

@ -16,6 +16,9 @@
package io.nosqlbench.nb.api.components.core;
import io.nosqlbench.nb.api.advisor.NBAdvisorBuilder;
import io.nosqlbench.nb.api.advisor.NBAdvisorPoint;
import io.nosqlbench.nb.api.advisor.NBAdvisorPointOrBuilder;
import io.nosqlbench.nb.api.csvoutput.CsvOutputPluginWriter;
import com.codahale.metrics.Meter;
import io.nosqlbench.nb.api.engine.metrics.*;
@ -42,6 +45,7 @@ import java.util.*;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@ -56,7 +60,7 @@ public class NBCreators {
}
public NBMetricTimer timer(String metricFamilyName, MetricCategory category, String description) {
return timer(metricFamilyName,3, category,description);
return timer(metricFamilyName, 3, category, description);
}
public NBMetricTimer timer(String metricFamilyName, int hdrdigits, MetricCategory category, String description) {
@ -72,7 +76,7 @@ public class NBCreators {
public Meter meter(String metricFamilyName, MetricCategory category, String description) {
NBLabels labels = base.getLabels().and("name", metricFamilyName);
NBMetricMeter meter = new NBMetricMeter(labels,description, category);
NBMetricMeter meter = new NBMetricMeter(labels, description, category);
base.addComponentMetric(meter, category, description);
return meter;
}
@ -113,7 +117,7 @@ public class NBCreators {
for (WindowSummaryGauge.Stat stat : stats) {
anyGauge = new WindowSummaryGauge(
window,
base.getLabels().and(NBLabels.forKV("name", name+"_w"+window, "stat", stat)),
base.getLabels().and(NBLabels.forKV("name", name + "_w" + window, "stat", stat)),
stat,
description,
category
@ -136,7 +140,7 @@ public class NBCreators {
}
public NBMetricHistogram histogram(String metricFamilyName, MetricCategory category, String description) {
return histogram(metricFamilyName,4, category, description);
return histogram(metricFamilyName, 4, category, description);
}
public NBMetricHistogram histogram(String metricFamilyName, int hdrdigits, MetricCategory category, String description) {
@ -486,4 +490,18 @@ public class NBCreators {
() -> new RuntimeException("unable to load extension with name '" + name + "' and type '" + type.getSimpleName() + "'")
);
}
public <PTYPE> NBAdvisorPoint<PTYPE> advisor(Function<NBAdvisorBuilder<PTYPE>, NBAdvisorPointOrBuilder<PTYPE>> builderOrPointF) {
NBAdvisorBuilder<PTYPE> newBuilder = new NBAdvisorBuilder<PTYPE>();
NBAdvisorPointOrBuilder<PTYPE> builderOrPoint = builderOrPointF.apply(newBuilder);
NBAdvisorPoint<PTYPE> point = switch (builderOrPoint) {
case NBAdvisorPoint p -> p;
case NBAdvisorBuilder builder -> builder.build();
default -> throw new RuntimeException(
"unknown type for mapping builder: " + builderOrPoint.getClass().getCanonicalName()
);
};
base.addAdvisor(point);
return point;
}
}

View File

@ -18,6 +18,13 @@ package io.nosqlbench.nb.api.components.decorators;
import java.util.Map;
/**
* <P>This is a canonical way to get the words which are acceptable and valid
* for token transformations in strings, particularly in identifiers for
* external reporting or logging. No other methods should be used to determine
* which tokens are valid across NB components. All tokens which are deemed
* useful or necessary should be included here. (This derives from built-in labels)</P>
*/
public interface NBTokenWords {
Map<String,String> getTokens();
}

View File

@ -51,7 +51,7 @@ public class PromPushReporterComponent extends PeriodicTaskComponent {
private String bearerToken;
public PromPushReporterComponent(NBComponent parent, String endpoint,long intervalMs, NBLabels nbLabels, String prompushApikeyfile) {
super(parent, nbLabels.and("_type", "prom-push"), intervalMs, "REPORT-PROMPUSH",FirstReport.OnInterval, LastReport.OnInterrupt);
super(parent, nbLabels.and("_type", "prom_push"), intervalMs, "REPORT-PROMPUSH",FirstReport.OnInterval, LastReport.OnInterrupt);
String jobname = getLabels().valueOfOptional("jobname").orElse("default");
String instance = getLabels().valueOfOptional("instance").orElse("default");
if (jobname.equals("default") || instance.equals("default")) {

View File

@ -0,0 +1,54 @@
package io.nosqlbench.nb.api.components.core;
/*
* 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.advisor.NBAdvisorPoint;
import io.nosqlbench.nb.api.advisor.conditions.Conditions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class NBAdvisorPointTest {
@Test
public void testAdvisorPointBasics() {
NBAdvisorPoint<String> nap = new NBAdvisorPoint<>("labels should have no hyphens");
nap.add(Conditions.NoHyphensError);
nap.add(Conditions.NoSpacesWarning);
String[] spaceErrors = nap.errorMessages("one two three");
assertThat(spaceErrors)
.containsExactly(new String[]{"WARN: String 'one two three' should not contain spaces"});
String[] hyphenErrors = nap.errorMessages("one-two");
assertThat(hyphenErrors)
.containsExactly(new String[]{"ERROR: String 'one-two' should not contain hyphens"});
String[] bothErrors = nap.errorMessages("one-two three");
assertThat(bothErrors)
.containsExactly(new String[]{
"ERROR: String 'one-two three' should not contain hyphens",
"WARN: String 'one-two three' should not contain spaces"
});
//int count = nap.evaluate();
//assertThat(count).isEqualTo(4);
}
}

View File

@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import static org.assertj.core.api.Assertions.assertThat;
@ -64,7 +65,6 @@ class NBComponentServicesTest {
metricsInTree.forEach(m -> {
System.out.println("metric: " + m.toString());
});
}
}

View File

@ -26,7 +26,7 @@ import java.util.Iterator;
import static org.assertj.core.api.Assertions.assertThat;
class NBComponentTraversalTest {
public class NBComponentTraversalTest {
/**
* <pre>{@code

View File

@ -136,12 +136,12 @@ public class NBCLI implements Function<String[], Integer>, NBLabeledElement {
break;
}
final String error = NBCLIErrorHandler.handle(e, showStackTraces, NBCLI.version);
final int result = NBCLIErrorHandler.handle(e, showStackTraces, NBCLI.version);
// Commented for now, as the above handler should do everything needed.
if (null != error) System.err.println("Scenario stopped due to error. See logs for details.");
if (result != 0) System.err.println("Scenario stopped due to error. See logs for details.");
System.err.flush();
System.out.flush();
return NBCLI.EXIT_ERROR;
return result;
}
}
@ -412,7 +412,8 @@ public class NBCLI implements Function<String[], Integer>, NBLabeledElement {
"logsdir", options.getLogsDirectory().toString(),
"progress", options.getProgressSpec(),
"prompush_cache", "prompush_cache.txt",
"heartbeat", String.valueOf(options.wantsHeartbeatIntervalMs())
"heartbeat", String.valueOf(options.wantsHeartbeatIntervalMs()),
"advisor", String.valueOf(options.getAdvisor())
);
try (

View File

@ -21,6 +21,7 @@ import io.nosqlbench.engine.cli.atfiles.NBAtFile;
import io.nosqlbench.engine.cmdstream.Cmd;
import io.nosqlbench.engine.cmdstream.PathCanonicalizer;
import io.nosqlbench.engine.core.lifecycle.session.CmdParser;
import io.nosqlbench.nb.api.advisor.NBAdvisorLevel;
import io.nosqlbench.nb.api.engine.util.Unit;
import io.nosqlbench.nb.api.errors.BasicError;
import io.nosqlbench.nb.api.labels.NBLabelSpec;
@ -96,6 +97,8 @@ public class NBCLIOptions {
private static final String ADD_LABELS = "--add-labels";
private static final String ADD_LABEL = "--add-label";
private static final String ADVISOR = "--advisor";
// Execution
private static final String EXPORT_CYCLE_LOG = "--export-cycle-log";
private static final String IMPORT_CYCLE_LOG = "--import-cycle-log";
@ -103,7 +106,6 @@ public class NBCLIOptions {
// Execution Options
private static final String SESSION_NAME = "--session-name";
private static final String LOGS_DIR = "--logs-dir";
private static final String WORKSPACES_DIR = "--workspaces-dir";
@ -207,6 +209,7 @@ public class NBCLIOptions {
private String reportSummaryTo = NBCLIOptions.REPORT_SUMMARY_TO_DEFAULT;
private boolean enableAnsi = (null != System.getenv("TERM")) && !System.getenv("TERM").isEmpty();
private Maturity minMaturity = Maturity.Unspecified;
private NBAdvisorLevel advisor = NBAdvisorLevel.none;
private String graphitelogLevel = "info";
private boolean wantsListCommands;
private boolean wantsListApps;
@ -504,6 +507,12 @@ public class NBCLIOptions {
String addLabeldata = arglist.removeFirst();
addLabels(addLabeldata);
break;
case ADVISOR:
arglist.removeFirst();
final String advisorStr = this.readWordOrThrow(arglist, "advisor level for checking");
advisor = NBAdvisorLevel.fromString(advisorStr); // includes error checking. invalid values
// provide a warning.
break;
case NBCLIOptions.ENABLE_LOGGED_METRICS:
arglist.removeFirst();
this.wantsConsoleMetrics = true;
@ -733,7 +742,7 @@ public class NBCLIOptions {
.replaceAll("ARG", cmdParam)
.replaceAll("PROG", "nb5")
.replaceAll("INCLUDES", String.join(",", wantsIncludes()))
+ (arglist.size()>0 && arglist.peekFirst().startsWith("nb") ?
+ (arglist.size() > 0 && arglist.peekFirst().startsWith("nb") ?
"""
(HINT:) It looks like you are starting your command with ARGV0
" which looks like the nb5 command itself. Maybe remove this?
@ -800,6 +809,10 @@ public class NBCLIOptions {
return this.minMaturity;
}
public NBAdvisorLevel getAdvisor() {
return this.advisor;
}
public List<Cmd> getCommands() {
return this.cmdList;
}

View File

@ -20,7 +20,7 @@ package io.nosqlbench.engine.api.activityimpl;
public enum Dryrun {
/**
* Ops are executed normally, no change to the dispenser behavior
* Ops are executed normally, no change to the dispenser behavior.
*/
none,
/**
@ -32,5 +32,12 @@ public enum Dryrun {
* Ops will print the toString version of their result to stdout.
* This is done by wrapping the synthesized op in a post-emit facade.
*/
emit
emit,
/**
* Jsonnet evaluation is a one time dry-run and then exit.
* With this value the run should exit after the first evaluation of jsonnet
* and Ops are not executed, but should processing fall through then processing
* will proceed as for none.
*/
jsonnet
}

View File

@ -43,6 +43,7 @@ public class OpWrappers {
case none -> dispenser;
case op -> new DryCycleOpDispenserWrapper(adapter, pop, dispenser);
case emit -> new EmitterCycleOpDispenserWrapper(adapter, pop, dispenser);
case jsonnet -> dispenser;
};
}
}

View File

@ -34,7 +34,7 @@ public class ClientSystemMetricChecker extends NBBaseComponent {
private List<ClientMetric> clientMetrics;
public ClientSystemMetricChecker(NBComponent parent, NBLabels additionalLabels, int pollIntervalSeconds) {
super(parent,additionalLabels.and("_type","client-metrics"));
super(parent,additionalLabels.and("_type","client_metrics"));
this.pollIntervalSeconds = pollIntervalSeconds;
this.scheduler = Executors.newScheduledThreadPool(1);
this.clientMetrics = new ArrayList<>();

View File

@ -17,6 +17,7 @@
package io.nosqlbench.engine.core.lifecycle.process;
import io.nosqlbench.nb.api.errors.BasicError;
import io.nosqlbench.nb.api.advisor.NBAdvisorException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.graalvm.polyglot.PolyglotException;
@ -43,7 +44,7 @@ public class NBCLIErrorHandler {
private final static Logger logger = LogManager.getLogger("ERRORHANDLER");
public static String handle(Throwable t, boolean wantsStackTraces, String version) {
public static int handle(Throwable t, boolean wantsStackTraces, String version) {
if (wantsStackTraces) {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
@ -63,6 +64,9 @@ public class NBCLIErrorHandler {
} else if (t instanceof BasicError) {
logger.trace("Handling basic error: " + t);
return handleBasicError((BasicError) t, wantsStackTraces, version);
} else if (t instanceof NBAdvisorException) {
logger.trace("Handle processing early exit: " + t);
return handleNBAdvisorException((NBAdvisorException) t, wantsStackTraces, version);
} else if (t instanceof Exception) {
logger.trace("Handling general exception: " + t);
return handleInternalError((Exception) t, wantsStackTraces, version);
@ -72,7 +76,7 @@ public class NBCLIErrorHandler {
}
}
private static String handleInternalError(Exception e, boolean wantsStackTraces, String version) {
private static int handleInternalError(Exception e, boolean wantsStackTraces, String version) {
String prefix = "internal error(" + version + "):";
if (e.getCause() != null && !e.getCause().getClass().getCanonicalName().contains("io.nosqlbench")) {
prefix = "Error from driver or included library(" + version + "):";
@ -87,10 +91,10 @@ public class NBCLIErrorHandler {
logger.error(e.getMessage());
logger.error("for the full stack trace, run with --show-stacktraces");
}
return e.getMessage();
return 2;
}
private static String handleScriptException(ScriptException e, boolean wantsStackTraces, String version) {
private static int handleScriptException(ScriptException e, boolean wantsStackTraces, String version) {
Throwable cause = e.getCause();
if (cause instanceof PolyglotException) {
Throwable hostException = ((PolyglotException) cause).asHostException();
@ -107,17 +111,22 @@ public class NBCLIErrorHandler {
logger.error("for the full stack trace, run with --show-stacktraces");
}
}
return e.getMessage();
return 2;
}
private static String handleBasicError(BasicError e, boolean wantsStackTraces, String version) {
private static int handleBasicError(BasicError e, boolean wantsStackTraces, String version) {
if (wantsStackTraces) {
logger.error(e.getMessage(), e);
} else {
logger.error(e.getMessage());
logger.error("for the full stack trace, run with --show-stacktraces");
}
return e.getMessage();
return 2;
}
private static int handleNBAdvisorException(NBAdvisorException e, boolean wantsStackTraces, String version) {
logger.info(e.toString());
return e.getExitCode();
}
}

View File

@ -18,6 +18,7 @@ package io.nosqlbench.engine.core;
import io.nosqlbench.nb.api.config.standard.TestComponent;
import io.nosqlbench.nb.api.engine.activityimpl.ActivityDef;
import io.nosqlbench.nb.api.advisor.NBAdvisorException;
import io.nosqlbench.engine.api.activityapi.core.*;
import io.nosqlbench.engine.api.activityapi.input.Input;
import io.nosqlbench.engine.api.activityapi.input.InputDispenser;
@ -81,10 +82,24 @@ class ActivityExecutorTest {
//
// }
@Test
synchronized void testAdvisorError() {
try {
ActivityDef activityDef = ActivityDef.parseActivityDef("driver=diag;alias=test-delayed-start;cycles=1000;initdelay=2000;");
new ActivityTypeLoader().load(activityDef, TestComponent.INSTANCE);
Activity activity = new DelayedInitActivity(activityDef);
fail("Expected an Advisor exception");
} catch (NBAdvisorException e) {
assertThat(e.toString().contains("error"));
assertThat(e.getExitCode() == 2);
}
}
@Test
synchronized void testDelayedStartSanity() {
ActivityDef activityDef = ActivityDef.parseActivityDef("driver=diag;alias=test-delayed-start;cycles=1000;initdelay=2000;");
ActivityDef activityDef = ActivityDef.parseActivityDef("driver=diag;alias=test_delayed_start;cycles=1000;initdelay=2000;");
new ActivityTypeLoader().load(activityDef, TestComponent.INSTANCE);
Activity activity = new DelayedInitActivity(activityDef);
@ -118,7 +133,7 @@ class ActivityExecutorTest {
@Test
synchronized void testNewActivityExecutor() {
final ActivityDef activityDef = ActivityDef.parseActivityDef("driver=diag;alias=test-dynamic-params;cycles=1000;initdelay=5000;");
final ActivityDef activityDef = ActivityDef.parseActivityDef("driver=diag;alias=test_dynamic_params;cycles=1000;initdelay=5000;");
new ActivityTypeLoader().load(activityDef,TestComponent.INSTANCE);
Activity simpleActivity = new SimpleActivity(TestComponent.INSTANCE,activityDef);