Merge pull request #1251 from nosqlbench/jk-test-eng-95-expected-result-verification

Verify expected result with MVEL
This commit is contained in:
Jonathan Shook 2023-05-18 11:11:32 -05:00 committed by GitHub
commit 97cd593b3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 349 additions and 13 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,7 +28,9 @@ import io.nosqlbench.adapter.cqld4.exceptions.UndefinedResultSetException;
import io.nosqlbench.adapter.cqld4.exceptions.UnexpectedPagingException; import io.nosqlbench.adapter.cqld4.exceptions.UnexpectedPagingException;
import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.*; import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.*;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
@ -42,7 +44,7 @@ import java.util.Map;
// TODO: add rows histogram resultSetSizeHisto // TODO: add rows histogram resultSetSizeHisto
public abstract class Cqld4CqlOp implements CycleOp<ResultSet>, VariableCapture, OpGenerator, OpResultSize { public abstract class Cqld4CqlOp implements CycleOp<List<Row>>, VariableCapture, OpGenerator, OpResultSize {
private final CqlSession session; private final CqlSession session;
private final int maxPages; private final int maxPages;
@ -54,6 +56,8 @@ public abstract class Cqld4CqlOp implements CycleOp<ResultSet>, VariableCapture,
private Cqld4CqlOp nextOp; private Cqld4CqlOp nextOp;
private final RSProcessors processors; private final RSProcessors processors;
private final ThreadLocal<List<Row>> results = new ThreadLocal<>();
public Cqld4CqlOp(CqlSession session, int maxPages, boolean retryReplace, int maxLwtRetries, RSProcessors processors) { public Cqld4CqlOp(CqlSession session, int maxPages, boolean retryReplace, int maxLwtRetries, RSProcessors processors) {
this.session = session; this.session = session;
this.maxPages = maxPages; this.maxPages = maxPages;
@ -71,7 +75,7 @@ public abstract class Cqld4CqlOp implements CycleOp<ResultSet>, VariableCapture,
this.processors = processors; this.processors = processors;
} }
public final ResultSet apply(long cycle) { public final List<Row> apply(long cycle) {
Statement<?> stmt = getStmt(); Statement<?> stmt = getStmt();
rs = session.execute(stmt); rs = session.execute(stmt);
@ -97,22 +101,29 @@ public abstract class Cqld4CqlOp implements CycleOp<ResultSet>, VariableCapture,
Iterator<Row> reader = rs.iterator(); Iterator<Row> reader = rs.iterator();
int pages = 0; int pages = 0;
// TODO/MVEL: An optimization to this would be to collect the results in a result set processor,
// but allow/require this processor to be added to an op _only_ in the event that it would
// be needed by a downstream consumer like the MVEL expected result evaluator
var resultRows = new ArrayList<Row>();
while (true) { while (true) {
int pageRows = rs.getAvailableWithoutFetching(); int pageRows = rs.getAvailableWithoutFetching();
for (int i = 0; i < pageRows; i++) { for (int i = 0; i < pageRows; i++) {
Row row = reader.next(); Row row = reader.next();
resultRows.add(row);
processors.buffer(row); processors.buffer(row);
} }
if (pages++ > maxPages) { if (pages++ > maxPages) {
throw new UnexpectedPagingException(rs, getQueryString(), pages, maxPages, stmt.getPageSize()); throw new UnexpectedPagingException(rs, getQueryString(), pages, maxPages, stmt.getPageSize());
} }
if (rs.isFullyFetched()) { if (rs.isFullyFetched()) {
results.set(resultRows);
break; break;
} }
totalRows += pageRows; totalRows += pageRows;
} }
processors.flush(); processors.flush();
return rs; return results.get();
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,7 +25,7 @@ public class Cqld4CqlPreparedStatement extends Cqld4CqlOp {
private final BoundStatement stmt; private final BoundStatement stmt;
public Cqld4CqlPreparedStatement(CqlSession session, BoundStatement stmt, int maxPages, boolean retryReplace, int maxLwtRetries, RSProcessors processors) { public Cqld4CqlPreparedStatement(CqlSession session, BoundStatement stmt, int maxPages, boolean retryReplace, int maxLwtRetries, RSProcessors processors) {
super(session,maxPages,retryReplace,maxLwtRetries,processors); super(session, maxPages, retryReplace, maxLwtRetries, processors);
this.stmt = stmt; this.stmt = stmt;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -21,11 +21,14 @@ import com.codahale.metrics.Timer;
import io.nosqlbench.api.config.NBLabeledElement; import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels; import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.engine.metrics.ActivityMetrics; import io.nosqlbench.api.engine.metrics.ActivityMetrics;
import io.nosqlbench.api.errors.MVELCompilationError;
import io.nosqlbench.engine.api.activityimpl.uniform.DriverAdapter; import io.nosqlbench.engine.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.Op; import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.Op;
import io.nosqlbench.engine.api.metrics.ThreadLocalNamedTimers; import io.nosqlbench.engine.api.metrics.ThreadLocalNamedTimers;
import io.nosqlbench.engine.api.templating.ParsedOp; import io.nosqlbench.engine.api.templating.ParsedOp;
import org.mvel2.MVEL;
import java.io.Serializable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -40,6 +43,7 @@ import java.util.concurrent.TimeUnit;
public abstract class BaseOpDispenser<T extends Op, S> implements OpDispenser<T>, NBLabeledElement { public abstract class BaseOpDispenser<T extends Op, S> implements OpDispenser<T>, NBLabeledElement {
private final String opName; private final String opName;
private Serializable expectedResultExpression;
protected final DriverAdapter<T, S> adapter; protected final DriverAdapter<T, S> adapter;
private final NBLabels labels; private final NBLabels labels;
private boolean instrument; private boolean instrument;
@ -65,6 +69,27 @@ public abstract class BaseOpDispenser<T extends Op, S> implements OpDispenser<T>
if (null != timerStarts) if (null != timerStarts)
for (final String timerStart : this.timerStarts) ThreadLocalNamedTimers.addTimer(op, timerStart); for (final String timerStart : this.timerStarts) ThreadLocalNamedTimers.addTimer(op, timerStart);
this.configureInstrumentation(op); this.configureInstrumentation(op);
this.configureResultExpectations(op);
}
public Serializable getExpectedResultExpression() {
return expectedResultExpression;
}
private void configureResultExpectations(ParsedOp op) {
op.getOptionalStaticValue("expected-result", String.class)
.map(this::compileExpectedResultExpression)
.ifPresent(result -> this.expectedResultExpression = result);
}
private Serializable compileExpectedResultExpression(String expectedResultExpression) {
try {
return MVEL.compileExpression(expectedResultExpression);
} catch (Exception e) {
throw new MVELCompilationError(
String.format("Failed to compile expected-result expression: \"%s\"", expectedResultExpression), e
);
}
} }
String getOpName() { String getOpName() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package io.nosqlbench.engine.api.activityimpl; package io.nosqlbench.engine.api.activityimpl;
import java.io.Serializable;
import java.util.function.LongFunction; import java.util.function.LongFunction;
/** /**
@ -81,5 +82,6 @@ public interface OpDispenser<T> extends LongFunction<T>, OpResultTracker {
*/ */
T apply(long value); T apply(long value);
Serializable getExpectedResultExpression();
} }

View File

@ -170,6 +170,7 @@ public abstract class BaseDriverAdapter<R extends Op, S> implements DriverAdapte
.add(Param.optional(List.of("workload", "yaml"), String.class, "location of workload yaml file")) .add(Param.optional(List.of("workload", "yaml"), String.class, "location of workload yaml file"))
.add(Param.optional("driver", String.class)) .add(Param.optional("driver", String.class))
.add(Param.defaultTo("dryrun", "none").setRegex("(op|jsonnet|none)")) .add(Param.defaultTo("dryrun", "none").setRegex("(op|jsonnet|none)"))
.add(Param.optional("maxtries", Integer.class))
.asReadOnly(); .asReadOnly();
} }

View File

@ -19,6 +19,7 @@ package io.nosqlbench.engine.api.activityapi.errorhandling;
import io.nosqlbench.api.config.NBLabeledElement; import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.engine.activityimpl.ActivityDef; import io.nosqlbench.api.engine.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.metrics.ExceptionCountMetrics; import io.nosqlbench.engine.api.metrics.ExceptionCountMetrics;
import io.nosqlbench.engine.api.metrics.ExceptionExpectedResultVerificationMetrics;
import io.nosqlbench.engine.api.metrics.ExceptionHistoMetrics; import io.nosqlbench.engine.api.metrics.ExceptionHistoMetrics;
import io.nosqlbench.engine.api.metrics.ExceptionMeterMetrics; import io.nosqlbench.engine.api.metrics.ExceptionMeterMetrics;
import io.nosqlbench.engine.api.metrics.ExceptionTimerMetrics; import io.nosqlbench.engine.api.metrics.ExceptionTimerMetrics;
@ -32,6 +33,7 @@ public class ErrorMetrics {
private ExceptionHistoMetrics exceptionHistoMetrics; private ExceptionHistoMetrics exceptionHistoMetrics;
private ExceptionMeterMetrics exceptionMeterMetrics; private ExceptionMeterMetrics exceptionMeterMetrics;
private ExceptionTimerMetrics exceptionTimerMetrics; private ExceptionTimerMetrics exceptionTimerMetrics;
private ExceptionExpectedResultVerificationMetrics exceptionExpectedResultVerificationMetrics;
public ErrorMetrics(final NBLabeledElement parentLabels) { public ErrorMetrics(final NBLabeledElement parentLabels) {
this.parentLabels = parentLabels; this.parentLabels = parentLabels;
@ -59,6 +61,12 @@ public class ErrorMetrics {
return this.exceptionTimerMetrics; return this.exceptionTimerMetrics;
} }
public synchronized ExceptionExpectedResultVerificationMetrics getExceptionExpectedResultVerificationMetrics() {
if (null == exceptionExpectedResultVerificationMetrics)
this.exceptionExpectedResultVerificationMetrics = new ExceptionExpectedResultVerificationMetrics(this.parentLabels);
return this.exceptionExpectedResultVerificationMetrics;
}
public interface Aware { public interface Aware {
void setErrorMetricsSupplier(Supplier<ErrorMetrics> supplier); void setErrorMetricsSupplier(Supplier<ErrorMetrics> supplier);
} }

View File

@ -0,0 +1,58 @@
/*
* 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.engine.api.activityapi.errorhandling.modular.handlers;
import io.nosqlbench.api.errors.ExpectedResultVerificationError;
import io.nosqlbench.engine.api.activityapi.errorhandling.ErrorMetrics;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorDetail;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorHandler;
import io.nosqlbench.engine.api.metrics.ExceptionExpectedResultVerificationMetrics;
import io.nosqlbench.nb.annotations.Service;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.Supplier;
/**
* The expected result verification error handler will create, if needed, two metric
* objects for error and retry counts.
*/
@Service(value = ErrorHandler.class, selector = "verifyexpected")
public class ExpectedResultVerificationErrorHandler implements ErrorHandler, ErrorMetrics.Aware {
private static final Logger logger = LogManager.getLogger("VERIFY");
private ExceptionExpectedResultVerificationMetrics exceptionExpectedResultVerificationMetrics;
@Override
public ErrorDetail handleError(String name, Throwable t, long cycle, long durationInNanos, ErrorDetail detail) {
if (t instanceof ExpectedResultVerificationError erve) {
if (erve.getTriesLeft() == 0) {
logger.warn("Cycle: {} Verification of result did not pass following expression: {}", cycle, erve.getExpectedResultExpression());
exceptionExpectedResultVerificationMetrics.countVerificationErrors();
} else {
logger.info("Cycle: {} Verification of result did not pass. {} retries left.", cycle, erve.getTriesLeft());
exceptionExpectedResultVerificationMetrics.countVerificationRetries();
}
}
return detail;
}
@Override
public void setErrorMetricsSupplier(Supplier<ErrorMetrics> supplier) {
this.exceptionExpectedResultVerificationMetrics = supplier.get().getExceptionExpectedResultVerificationMetrics();
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package io.nosqlbench.engine.api.activityimpl.uniform.actions;
import com.codahale.metrics.Histogram; import com.codahale.metrics.Histogram;
import com.codahale.metrics.Timer; import com.codahale.metrics.Timer;
import io.nosqlbench.api.errors.ExpectedResultVerificationError;
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver; import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
import io.nosqlbench.engine.api.activityapi.core.SyncAction; import io.nosqlbench.engine.api.activityapi.core.SyncAction;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorDetail; import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorDetail;
@ -29,7 +30,9 @@ import io.nosqlbench.engine.api.activityimpl.uniform.StandardActivity;
import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.*; import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.*;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.mvel2.MVEL;
import java.io.Serializable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -87,7 +90,7 @@ public class StandardAction<A extends StandardActivity<R, ?>, R extends Op> impl
while (op != null) { while (op != null) {
int tries = 0; int tries = 0;
while (tries++ <= maxTries) { while (tries++ < maxTries) {
Throwable error = null; Throwable error = null;
long startedAt = System.nanoTime(); long startedAt = System.nanoTime();
@ -104,6 +107,13 @@ public class StandardAction<A extends StandardActivity<R, ?>, R extends Op> impl
throw new RuntimeException("The op implementation did not implement any active logic. Implement " + throw new RuntimeException("The op implementation did not implement any active logic. Implement " +
"one of [RunnableOp, CycleOp, or ChainingOp]"); "one of [RunnableOp, CycleOp, or ChainingOp]");
} }
var expectedResultExpression = dispenser.getExpectedResultExpression();
if (shouldVerifyExpectedResultFor(op, expectedResultExpression)) {
var verified = MVEL.executeExpression(expectedResultExpression, result, boolean.class);
if (!verified) {
throw new ExpectedResultVerificationError(maxTries - tries, expectedResultExpression);
}
}
} catch (Exception e) { } catch (Exception e) {
error = e; error = e;
} finally { } finally {
@ -139,4 +149,8 @@ public class StandardAction<A extends StandardActivity<R, ?>, R extends Op> impl
@Override @Override
public void onActivityDefUpdate(ActivityDef activityDef) { public void onActivityDefUpdate(ActivityDef activityDef) {
} }
private boolean shouldVerifyExpectedResultFor(Op op, Serializable expectedResultExpression) {
return !(op instanceof RunnableOp) && expectedResultExpression != null;
}
} }

View File

@ -0,0 +1,53 @@
/*
* 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.engine.api.metrics;
import com.codahale.metrics.Counter;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.engine.metrics.ActivityMetrics;
/**
* Use this to provide exception metering during expected result verification.
*/
public class ExceptionExpectedResultVerificationMetrics {
private final NBLabeledElement parentLabels;
private final Counter verificationErrors;
private final Counter verificationRetries;
public ExceptionExpectedResultVerificationMetrics(final NBLabeledElement parentLabels) {
this.parentLabels = parentLabels;
verificationRetries = ActivityMetrics.counter(parentLabels, "verificationcounts.RETRIES");
verificationErrors = ActivityMetrics.counter(parentLabels, "verificationcounts.ERRORS");
}
public void countVerificationRetries() {
verificationRetries.inc();
}
public void countVerificationErrors() {
verificationErrors.inc();
}
public Counter getVerificationErrors() {
return verificationErrors;
}
public Counter getVerificationRetries() {
return verificationRetries;
}
}

View File

@ -21,6 +21,7 @@ import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter; import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer; import com.codahale.metrics.Timer;
import io.nosqlbench.api.config.NBLabeledElement; import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.errors.ExpectedResultVerificationError;
import io.nosqlbench.engine.api.activityapi.errorhandling.ErrorMetrics; import io.nosqlbench.engine.api.activityapi.errorhandling.ErrorMetrics;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.handlers.CountErrorHandler; import io.nosqlbench.engine.api.activityapi.errorhandling.modular.handlers.CountErrorHandler;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.handlers.CounterErrorHandler; import io.nosqlbench.engine.api.activityapi.errorhandling.modular.handlers.CounterErrorHandler;
@ -30,8 +31,12 @@ import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.Logger;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -173,5 +178,54 @@ class NBErrorHandlerTest {
appender.cleanup(logger); appender.cleanup(logger);
} }
@ParameterizedTest(name = "Error with {0}")
@MethodSource
void testExpectedResultVerificationErrorHandler(String name, Exception error, String log, long retriesCount, long errorsCount, Logger logger) {
// given
NBMock.LogAppender appender = NBMock.registerTestLogger(ERROR_HANDLER_APPENDER_NAME, logger, Level.INFO);
var errorMetrics = new ErrorMetrics(NBLabeledElement.forKV("activity","testalias_result_verification_" + name));
var eh = new NBErrorHandler(() -> "verifyexpected", () -> errorMetrics);
var retries = errorMetrics.getExceptionExpectedResultVerificationMetrics().getVerificationRetries();
var errors = errorMetrics.getExceptionExpectedResultVerificationMetrics().getVerificationErrors();
assertThat(retries.getCount()).isEqualTo(0);
assertThat(errors.getCount()).isEqualTo(0);
// when
eh.handleError(error, 1, 2);
// then
assertThat(retries.getCount()).isEqualTo(retriesCount);
assertThat(errors.getCount()).isEqualTo(errorsCount);
logger.getContext().stop(); // force any async appenders to flush
logger.getContext().start(); // resume processing
assertThat(appender.getFirstEntry()).contains(log);
appender.cleanup(logger);
}
@SuppressWarnings("unused")
private static Stream<Arguments> testExpectedResultVerificationErrorHandler() {
Logger logger = (Logger) LogManager.getLogger("VERIFY");
return Stream.of(
Arguments.of(
"retries left",
new ExpectedResultVerificationError(5, "expected"),
"Cycle: 1 Verification of result did not pass. 5 retries left.",
1,
0,
logger
),
Arguments.of(
"no retries left",
new ExpectedResultVerificationError(0, "expected"),
"Cycle: 1 Verification of result did not pass following expression: expected",
0,
1,
logger
)
);
}
} }

View File

@ -161,6 +161,7 @@ public class NBCLI implements Function<String[], Integer> {
.setMaxLogs(globalOptions.getLogsMax()) .setMaxLogs(globalOptions.getLogsMax())
.setLogsDirectory(globalOptions.getLogsDirectory()) .setLogsDirectory(globalOptions.getLogsDirectory())
.setAnsiEnabled(globalOptions.isEnableAnsi()) .setAnsiEnabled(globalOptions.isEnableAnsi())
.setDedicatedVerificationLogger(globalOptions.isDedicatedVerificationLogger())
.activate(); .activate();
ConfigurationFactory.setConfigurationFactory(NBCLI.loggerConfig); ConfigurationFactory.setConfigurationFactory(NBCLI.loggerConfig);

View File

@ -127,6 +127,7 @@ public class NBCLIOptions {
private static final String DEFAULT_CONSOLE_PATTERN = "TERSE"; private static final String DEFAULT_CONSOLE_PATTERN = "TERSE";
private static final String DEFAULT_LOGFILE_PATTERN = "VERBOSE"; private static final String DEFAULT_LOGFILE_PATTERN = "VERBOSE";
private final static String ENABLE_DEDICATED_VERIFICATION_LOGGER = "--enable-dedicated-verification-logging";
// private static final String DEFAULT_CONSOLE_LOGGING_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; // private static final String DEFAULT_CONSOLE_LOGGING_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
@ -190,6 +191,7 @@ public class NBCLIOptions {
private String graphitelogLevel = "info"; private String graphitelogLevel = "info";
private boolean wantsListCommands; private boolean wantsListCommands;
private boolean wantsListApps; private boolean wantsListApps;
private boolean dedicatedVerificationLogger;
public boolean isWantsListApps() { public boolean isWantsListApps() {
return this.wantsListApps; return this.wantsListApps;
@ -232,6 +234,14 @@ public class NBCLIOptions {
return graphitelogLevel; return graphitelogLevel;
} }
public boolean isDedicatedVerificationLogger() {
return this.dedicatedVerificationLogger;
}
public void enableDedicatedVerificationLogger() {
this.dedicatedVerificationLogger = true;
}
public enum Mode { public enum Mode {
ParseGlobalsOnly, ParseGlobalsOnly,
ParseAllOptions ParseAllOptions
@ -340,6 +350,10 @@ public class NBCLIOptions {
showStackTraces = true; showStackTraces = true;
arglist.removeFirst(); arglist.removeFirst();
break; break;
case NBCLIOptions.ENABLE_DEDICATED_VERIFICATION_LOGGER:
enableDedicatedVerificationLogger();
arglist.removeFirst();
break;
case NBCLIOptions.ANNOTATE_EVENTS: case NBCLIOptions.ANNOTATE_EVENTS:
arglist.removeFirst(); arglist.removeFirst();
final String toAnnotate = this.readWordOrThrow(arglist, "annotated events"); final String toAnnotate = this.readWordOrThrow(arglist, "annotated events");

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 nosqlbench * Copyright (c) 2022-2023 nosqlbench
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationFactory;
@ -87,6 +88,7 @@ public class LoggerConfig extends ConfigurationFactory {
private int maxLogfiles = 100; private int maxLogfiles = 100;
private String logfileLocation; private String logfileLocation;
private boolean ansiEnabled; private boolean ansiEnabled;
private boolean isDedicatedVerificationLoggerEnabled = false;
public LoggerConfig() { public LoggerConfig() {
@ -107,6 +109,11 @@ public class LoggerConfig extends ConfigurationFactory {
return this; return this;
} }
public LoggerConfig setDedicatedVerificationLogger(boolean enabled) {
this.isDedicatedVerificationLoggerEnabled = enabled;
return this;
}
/** /**
* Ensure that what is shown in the logfile includes at a minimum, * Ensure that what is shown in the logfile includes at a minimum,
* everything that is shown on console, but allow it to show more * everything that is shown on console, but allow it to show more
@ -209,6 +216,11 @@ public class LoggerConfig extends ConfigurationFactory {
.addComponent(triggeringPolicy); .addComponent(triggeringPolicy);
builder.add(logsAppenderBuilder); builder.add(logsAppenderBuilder);
if (isDedicatedVerificationLoggerEnabled) {
var verificationLogfilePath = loggerDir.resolve(filebase + "_verification.log").toString();
addResultVerificationLoggingChannel(builder, verificationLogfilePath);
}
rootBuilder.add( rootBuilder.add(
builder.newAppenderRef("SCENARIO_APPENDER") builder.newAppenderRef("SCENARIO_APPENDER")
.addAttribute("level", fileLevel) .addAttribute("level", fileLevel)
@ -367,4 +379,23 @@ public class LoggerConfig extends ConfigurationFactory {
this.loggerDir = logsDirectory; this.loggerDir = logsDirectory;
return this; return this;
} }
private void addResultVerificationLoggingChannel(ConfigurationBuilder<BuiltConfiguration> builder, String verificationLogfilePath) {
var appenderName = "RESULTVERIFYLOG";
var appender = builder
.newAppender(appenderName, FileAppender.PLUGIN_NAME)
.addAttribute("append", false)
.addAttribute("fileName", verificationLogfilePath)
.add(builder
.newLayout("PatternLayout")
.addAttribute("pattern", "%d %p %C{1.} [%t] %m%n")
);
var logger = builder
.newLogger("VERIFY", Level.INFO)
.add(builder.newAppenderRef(appenderName))
.addAttribute("additivity", false);
builder.add(appender);
builder.add(logger);
}
} }

View File

@ -0,0 +1,38 @@
/*
* 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.api.errors;
import java.io.Serializable;
public class ExpectedResultVerificationError extends RuntimeException {
private final int triesLeft;
private final Serializable expectedResultExpression;
public ExpectedResultVerificationError(int triesLeft, Serializable expectedResultExpression) {
this.triesLeft = triesLeft;
this.expectedResultExpression = expectedResultExpression;
}
public int getTriesLeft() {
return triesLeft;
}
public Serializable getExpectedResultExpression() {
return expectedResultExpression;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.api.errors;
/**
* MVELCompilationErrors are those known to occur during the compilation of expected results expressions.
*/
public class MVELCompilationError extends RuntimeException {
public MVELCompilationError(String message, Throwable cause) {
super(message, cause);
}
}