Merge pull request #1279 from nosqlbench/labels_trueup

Labels trueup
This commit is contained in:
Jonathan Shook
2023-05-18 16:40:30 -05:00
committed by GitHub
44 changed files with 937 additions and 474 deletions

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2022 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.activitytype.diag;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongToIntFunction;
public class DiagOpData {
private final String description;
private final List<String> diaglog = new ArrayList<>();
private LongToIntFunction resultFunc;
private long simulatedDelayNanos;
public DiagOpData(String description) {
this.description = description;
}
/**
* If this function is provided, the result will be set to the value of the
* evaluated function with the op cycle.
*
* This is known as "resultfunc" in parameter space.
*
* The function must be thread-safe.
*
* @param resultFunc A function to map the cycle to the result value
* @return this, for method chaining
*/
public DiagOpData withResultFunction(LongToIntFunction resultFunc) {
this.resultFunc = resultFunc;
return this;
}
/**
* If this function is provided, the completion of the operation will be
* delayed until the system nanotime is at least the op start time in
* addition to the provided delay.
*
* This is controlled as "delayfunc" in parameter space.
*
* @param simulatedDelayNanos The amount of nanos ensure as a minimum
* of processing time for this op
*/
public DiagOpData setSimulatedDelayNanos(long simulatedDelayNanos) {
this.simulatedDelayNanos = simulatedDelayNanos;
return this;
}
public long getSimulatedDelayNanos() {
return simulatedDelayNanos;
}
@Override
public String toString() {
return super.toString() + ", description:'" + description;
}
public String getDescription() {
return description;
}
public void log(String logline) {
this.diaglog.add(logline);
}
public List<String> getDiagLog() {
return diaglog;
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright (c) 2022 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.activitytype.diag;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.security.InvalidParameterException;
import java.util.concurrent.atomic.AtomicLong;
public class SequenceBlocker {
private final static Logger logger = LogManager.getLogger(SequenceBlocker.class);
private final AtomicLong sequence;
private final AtomicLong waiting=new AtomicLong(0L);
private final boolean errorsAreFatal;
// private PriorityBlockingQueue<TakeANumber> queue = new PriorityBlockingQueue<>();
private Exception fatalError;
public SequenceBlocker(long start, boolean errorsAreFatal) {
this.sequence = new AtomicLong(start);
this.errorsAreFatal = errorsAreFatal;
}
public synchronized void awaitAndRun(long startAt, long endPlus, Runnable task) {
waiting.incrementAndGet();
if (fatalError != null) {
throw new RuntimeException("There was previously a fatal error, not allowing new tasks. Error=" + fatalError.getMessage());
}
// queue.add(new TakeANumber(startAt, sequencePlusCount, task));
while (sequence.get() != startAt) {
try {
wait(1_000);
} catch (InterruptedException ignored) {
}
}
try {
task.run();
} catch (Exception e) {
logger.error(() -> "Runnable errored in SequenceBlocker: " + e.getMessage());
if (errorsAreFatal) {
this.fatalError = e;
}
throw e;
} finally {
waiting.decrementAndGet();
if (!sequence.compareAndSet(startAt,endPlus)) {
throw new InvalidParameterException("Serious logic error in synchronizer. This should never fail.");
}
}
notifyAll();
}
public synchronized void awaitCompletion() {
while (waiting.get()>0)
try {
wait(60_000);
} catch (InterruptedException ignored) {
}
}
private final static class TakeANumber implements Comparable<TakeANumber> {
private final long start;
private final long endPlus;
private final Runnable task;
public TakeANumber(long start, long endPlus, Runnable task) {
this.start = start;
this.endPlus = endPlus;
this.task = task;
}
@Override
public int compareTo(TakeANumber o) {
return Long.compare(start, o.start);
}
public long getStart() {
return start;
}
public long getEndPlus() {
return endPlus;
}
public String toString() {
return "[" + getStart() + "-" + getEndPlus() + ")";
}
}
}

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");
* you may not use this file except in compliance with the License.
@@ -62,10 +62,13 @@ public class DiagOpDispenser extends BaseOpDispenser<DiagOp,DiagSpace> implement
taskcfg.computeIfAbsent("name",l -> taskname);
taskcfg.computeIfAbsent("type",l -> taskname);
String optype = taskcfg.remove("type").toString();
String opname = taskcfg.get("name").toString();
// Dynamically load the named task instance, based on the op field key AKA the taskname
// and ensure that exactly one is found or throw an error
DiagTask task = ServiceSelector.of(optype, ServiceLoader.load(DiagTask.class)).getOne();
task.setLabelsFrom(op);
task.setName(opname);
// Load the configuration model of the dynamically loaded task for type-safe configuration
NBConfigModel cfgmodel = task.getConfigModel();

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 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.adapter.diag.optasks;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels;
import java.util.Map;
public abstract class BaseDiagTask implements DiagTask {
private NBLabeledElement parentLabels;
private String name;
@Override
public abstract Map<String, Object> apply(Long cycle, Map<String, Object> opstate);
@Override
public NBLabels getLabels() {
return parentLabels.getLabels();
}
public void setName(String name) {
this.name = name;
}
@Override
public void setLabelsFrom(NBLabeledElement labeledElement) {
this.parentLabels = labeledElement;
}
@Override
public NBLabeledElement getParentLabels() {
return parentLabels;
}
public String getName() {
return this.name;
}
}

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");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,9 @@
package io.nosqlbench.adapter.diag.optasks;
import io.nosqlbench.api.config.NBNamedElement;
import io.nosqlbench.api.config.standard.NBReconfigurable;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.standard.NBConfigurable;
import io.nosqlbench.api.config.standard.NBReconfigurable;
import java.util.Map;
import java.util.function.BiFunction;
@@ -44,7 +44,13 @@ import java.util.function.BiFunction;
public interface DiagTask extends
BiFunction<Long,Map<String,Object>, Map<String,Object>>,
NBConfigurable,
NBNamedElement
NBLabeledElement
{
Map<String, Object> apply(Long cycle, Map<String, Object> opstate);
void setName(String opname);
void setLabelsFrom(NBLabeledElement labeledElement);
NBLabeledElement getParentLabels();
}

View File

@@ -16,8 +16,6 @@
package io.nosqlbench.adapter.diag.optasks;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.config.standard.*;
import io.nosqlbench.engine.api.activityapi.ratelimits.RateLimiter;
import io.nosqlbench.engine.api.activityapi.ratelimits.RateLimiters;
@@ -27,7 +25,7 @@ import io.nosqlbench.nb.annotations.Service;
import java.util.Map;
@Service(value = DiagTask.class, selector = "diagrate")
public class DiagTask_diagrate implements DiagTask, NBReconfigurable, NBLabeledElement {
public class DiagTask_diagrate extends BaseDiagTask implements NBReconfigurable {
private String name;
private RateLimiter rateLimiter;
private RateSpec rateSpec;
@@ -73,15 +71,4 @@ public class DiagTask_diagrate implements DiagTask, NBReconfigurable, NBLabeledE
rateLimiter.maybeWaitForOp();
return stringObjectMap;
}
@Override
public String getName() {
return name;
}
@Override
public NBLabels getLabels() {
return NBLabels.forKV("diagop", name);
}
}

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");
* you may not use this file except in compliance with the License.
@@ -29,14 +29,12 @@ import java.util.Map;
* of this owning operation for a number of milliseconds.
*/
@Service(value = DiagTask.class, selector = "erroroncycle")
public class DiagTask_erroroncycle implements DiagTask {
public class DiagTask_erroroncycle extends BaseDiagTask {
private String name;
private long error_on_cycle;
@Override
public void applyConfig(NBConfiguration cfg) {
this.name = cfg.get("name", String.class);
error_on_cycle = cfg.get("erroroncycle", long.class);
}
@@ -55,9 +53,4 @@ public class DiagTask_erroroncycle implements DiagTask {
}
return Map.of();
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.adapter.diag.optasks;
import com.codahale.metrics.Gauge;
import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.config.standard.*;
import io.nosqlbench.api.engine.metrics.ActivityMetrics;
import io.nosqlbench.nb.annotations.Service;
import io.nosqlbench.virtdata.api.bindings.VirtDataConversions;
import io.nosqlbench.virtdata.core.bindings.DataMapper;
import io.nosqlbench.virtdata.core.bindings.VirtData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.Map;
import java.util.function.LongToDoubleFunction;
/**
* <p>A diag gauge task allows you to create a source of metrics data for testing or demonstration.
* You can customize the function used to produce the raw values, the number of buckets to use for
* leavening the values over time, and the basic stat function used to summarize the buckets into
* an aggregate double value.</p>
*
* <H2>Usage Notes</H2>
* The data image for the gauge summary is updated consistently with respect to monotonic (whole step) cycle values.
* There are a few parameters which can be adjusted in order to make the gauge data appear more realistic.
* <UL>
* <LI>label - This determines the metric label, normally used as the metric family name. Default is the task name.</LI>
* <LI>buckets - The number of values to seed incrementally to produce a data image</LI>
* <LI>binding - The binding recipe to use to create the value stored in a bin for a given cycle</LI>
* <LI>modulo - The interval of cycle values at which a new bin value is computed and stored in a bin</LI>
* <LI>stat - The aggregate statistic to use when computing the gauge value: min, avg, or max</LI>
* </UL>
*
* <p>The buckets are updated incrementally and consistently based on the cycle value, modulated by the modulo value.
* When the gauge is observed, the present value of the buckets is converted to a values image and the result is
* summarized according to the selected stat.</p>
*
* <p>Practical values should be selected with awareness of the op rate and the rate of change desired in
* the metrics over time. The buckets allow for the effective rate of change over cycles to be slowed, but it
* is recommended to keep bin counts relative low by increasing modulo instead.</p>
*
* <H2>Examples</H2>
* <p>Suppose you wanted to see a moving average, where a new value is presented every second.
* A new value every second is obviously not needed in practical scenarios, but it makes a useful basis
* for thinking about relative rates, since the rate limiters are specified in ops/s.
* <UL>
* <LI>activity rate=10 modulo=10 - a new update will be visible every second.</LI>
* <LI>activity rate=1000 modulo=1000 - a new gauge value will be visible every second.</LI>
* <LI>activity rate=1000 modulo=60000 - a new gauge value will be visible every minute.</LI>
* <LI>activity rate=100 modulo=100 buckets=50 stat=avg - a new value will be visible every second,
* however the rate of change will be reduced due to the large sample size.</LI>
* </UL>
*
* <H2>Usage Notes</H2>
* Changing the number of buckets has a different effect based on the stat. For avg, the higher the number of buckets,
* the smaller the standard deviation of the results. For min and max, the higher the number of buckets, the more
* extreme the value will become. This is true for uniform bindings and non-uniform binding functions as well,
* although you can tailor the shape of the sample data as you like.
*
*/
@Service(value= DiagTask.class,selector="gauge")
public class DiagTask_gauge extends BaseDiagTask implements Gauge<Double> {
private final static Logger logger = LogManager.getLogger("DIAG");
// TODO: allow for temporal filtering
// TODO: allow for temporal cycles
private String name;
private Gauge<Double> gauge;
private LongToDoubleFunction function;
private Double sampleValue;
private long[] cycleMixer;
private double[] valueMixer;
private long modulo;
private int buckets;
private String label;
private enum Stats {
min,
avg,
max
}
private Stats stat;
@Override
public Map<String, Object> apply(Long cycleValue, Map<String, Object> stringObjectMap) {
long cycle = cycleValue.longValue();
if ((cycle%modulo)==0) {
int bin=(int)(cycle/modulo)%cycleMixer.length;
cycleMixer[bin]=cycleValue;
logger.debug(() -> "updating bin " + bin + " with value " + cycle + ", now:" + Arrays.toString(cycleMixer));
}
return stringObjectMap;
}
@Override
public void applyConfig(NBConfiguration cfg) {
String binding = cfg.get("binding",String.class);
this.buckets = cfg.get("buckets",Integer.class);
this.modulo = cfg.get("modulo",Long.class);
this.label = cfg.getOptional("label").orElse(super.getName());
String stat = cfg.get("stat");
this.cycleMixer=new long[buckets];
this.valueMixer=new double[buckets];
this.stat=Stats.valueOf(stat);
DataMapper<Object> mapper = VirtData.getMapper(binding, Map.of());
Object example = mapper.get(0L);
if (example instanceof Double) {
this.function=l -> (double) mapper.get(l);
} else {
this.function= VirtDataConversions.adaptFunction(mapper,LongToDoubleFunction.class);
}
logger.info("Registering gauge for diag task with labels:" + getParentLabels().getLabels() + " label:" + label);
this.gauge=ActivityMetrics.gauge(this, label, this);
}
@Override
public NBConfigModel getConfigModel() {
return ConfigModel.of(DiagTask_gauge.class)
.add(Param.required("name",String.class))
.add(Param.optional("label",String.class)
.setDescription("A metric family name override. Defaults to the op name."))
.add(Param.defaultTo("binding","HashRange(0L,1000000L)")
.setDescription("A binding function to derive values from"))
.add(Param.defaultTo("buckets", "3")
.setDescription("how many slots to maintain in the mixer to aggregate over"))
.add(Param.defaultTo("stat","avg")
.setRegex("min|avg|max")
.setDescription("min, avg, or max"))
.add(Param.defaultTo("modulo",1L)
.setDescription("A value used to divide down the relative rate of bin updates. 100 means 100x fewer updates"))
.asReadOnly();
}
@Override
public Double getValue() {
for (int idx = 0; idx < valueMixer.length; idx++) {
valueMixer[idx]=function.applyAsDouble(this.cycleMixer[idx]);
}
double sample= switch (this.stat) {
case min -> Arrays.stream(this.valueMixer).reduce(Math::min).getAsDouble();
case avg -> Arrays.stream(this.valueMixer).sum()/(double)this.valueMixer.length;
case max -> Arrays.stream(this.valueMixer).reduce(Math::max).getAsDouble();
};
logger.debug(() -> "sample value for " + getParentLabels().getLabels() + ": " + sample);
return sample;
}
@Override
public NBLabels getLabels() {
return super.getLabels().and("stat",this.stat.toString());
}
}

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");
* you may not use this file except in compliance with the License.
@@ -29,13 +29,10 @@ import java.util.Map;
* of this owning operation for a number of milliseconds.
*/
@Service(value= DiagTask.class,selector = "initdelay")
public class DiagTask_initdelay implements DiagTask {
private String name;
public class DiagTask_initdelay extends BaseDiagTask {
@Override
public void applyConfig(NBConfiguration cfg) {
this.name = cfg.get("name",String.class);
long initdelay = cfg.get("initdelay",long.class);
try {
Thread.sleep(initdelay);
@@ -54,12 +51,6 @@ public class DiagTask_initdelay implements DiagTask {
@Override
public Map<String, Object> apply(Long aLong, Map<String, Object> stringObjectMap) {
return Map.of();
}
@Override
public String getName() {
return name;
}
}

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");
* you may not use this file except in compliance with the License.
@@ -25,12 +25,11 @@ import org.apache.logging.log4j.Logger;
import java.util.Map;
@Service(value= DiagTask.class,selector="log")
public class DiagTask_log implements DiagTask, NBConfigurable {
public class DiagTask_log extends BaseDiagTask {
private final static Logger logger = LogManager.getLogger("DIAG");
private Level level;
private long modulo;
private long interval;
private String name;
@Override
public Map<String, Object> apply(Long aLong, Map<String, Object> stringObjectMap) {
@@ -43,7 +42,6 @@ public class DiagTask_log implements DiagTask, NBConfigurable {
@Override
public void applyConfig(NBConfiguration cfg) {
String level = cfg.getOptional("level").orElse("INFO");
this.name = cfg.get("name");
this.level = Level.valueOf(level);
this.modulo = cfg.get("modulo",long.class);
this.interval = cfg.get("interval",long.class);
@@ -58,9 +56,4 @@ public class DiagTask_log implements DiagTask, NBConfigurable {
.add(Param.defaultTo("interval",1000))
.asReadOnly();
}
@Override
public String getName() {
return name;
}
}

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");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,21 @@
package io.nosqlbench.adapter.diag.optasks;
import io.nosqlbench.nb.annotations.Service;
import io.nosqlbench.api.config.standard.ConfigModel;
import io.nosqlbench.api.config.standard.NBConfigModel;
import io.nosqlbench.api.config.standard.NBConfiguration;
import io.nosqlbench.api.config.standard.Param;
import io.nosqlbench.nb.annotations.Service;
import java.util.Map;
@Service(value= DiagTask.class,selector = "noop")
public class DiagTask_noop implements DiagTask {
public class DiagTask_noop extends BaseDiagTask {
private String name;
@Override
public void applyConfig(NBConfiguration cfg) {
this.name = cfg.get("name",String.class);
}
@Override
@@ -46,8 +45,4 @@ public class DiagTask_noop implements DiagTask {
return Map.of();
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,65 @@
description: |
A diag gauge task allows you to create a source of metrics data
for testing or demonstration. You can customize the binding used
to produce the raw values, the number of buckets to use for leavening
the values over time, and the basic stat function used to summarize
the buckets into an aggregate double value.
Usage Notes
The data image for the gauge summary is updated consistently with
respect to monotonic (whole step) cycle values. There are a few parameters
which can be adjusted in order to make the gauge data appear more realistic.
label - A standard parameter for diag tasks. This determines the metric name as well.
buckets - The number of values to seed incrementally to produce a data image
binding - The binding used to create the value stored in a bin for a given cycle
modulo - The interval of cycle values at which a new bin value is computed and stored in a bin
stat - The aggregate statistic to use when computing the gauge value: min, avg, or max
The buckets are updated incrementally and consistently based on the cycle value,
modulated by the modulo value. When the gauge value is observed, the present
value of the buckets is converted to a values image and the result is summarized
according to the selected stat.
Practical values should be selected with awareness of the op rate and the rate
of change desired in the metrics over time. The buckets allow for the effective
rate of change over cycles to be slowed, but it is recommended to keep bin counts
relative low by increasing modulo instead.
scenarios:
default:
bysecond: start driver=diag tags=block:bysecond rate=10 cycles=6000
byminute: start driver=diag tags=block:stable rate=10 cycles=6000
for100bins: start driver=diag tags=block:byminute rate=10 cycles=6000
for2bins: start driver=diag tags=block:randomish rate=10 cycles=6000
bysecond:
bysecond: start driver=diag tags=block:bysecond rate=10 cycles=6000
byminute:
byminute: start driver=diag tags=block:stable rate=10 cycles=6000
stable:
for100bins: start driver=diag tags=block:byminute rate=10 cycles=6000
randomish:
for2bins: start driver=diag tags=block:randomish rate=10 cycles=6000
blocks:
bysecond: # This assumes you are using a matching rate=10, for one update per second
ops:
tenbins:
lower: type=gauge modulo=10 buckets=10 binding='HashRange(0L,100L)' stat=min
middle: type=gauge modulo=10 buckets=10 binding='HashRange(0L,100L)' stat=avg
higher: type=gauge modulo=10 buckets=10 binding='HashRange(0L,100L)' stat=max
byminute: # This assumes you are using a matching rate=10, for one update per minute
ops:
tenbins:
lower: type=gauge modulo=600 buckets=10 binding='HashRange(0L,100L)' stat=min
middle: type=gauge modulo=600 buckets=10 binding='HashRange(0L,100L)' stat=avg
higher: type=gauge modulo=600 buckets=10 binding='HashRange(0L,100L)' stat=max
stable: # This assumes you are using a matching rate=10, for one update per minute
ops:
hundobins:
lower: type=gauge modulo=600 buckets=100 binding='HashRange(0L,100L)' stat=min
middle: type=gauge modulo=600 buckets=100 binding='HashRange(0L,100L)' stat=avg
higher: type=gauge modulo=600 buckets=100 binding='HashRange(0L,100L)' stat=max
randomish: # This assumes you are using a matching rate=10, for 1 update per second
ops:
threebins:
lower: type=gauge modulo=60 buckets=3 binding='HashRange(0L,100L)' stat=min
middle: type=gauge modulo=60 buckets=3 binding='HashRange(0L,100L)' stat=avg
higher: type=gauge modulo=60 buckets=3 binding='HashRange(0L,100L)' stat=max

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2022 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.activitytype.diag;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
public class SequenceBlockerTest {
private final static Logger logger = LogManager.getLogger(SequenceBlockerTest.class);
@Test
public void await() throws Exception {
SequenceBlocker sb = new SequenceBlocker(234L, true);
new Thread(() -> sb.awaitAndRun(249L,253L, new Printer(logger, "249-253"))).start();
Thread.sleep(100);
new Thread(() -> sb.awaitAndRun(247L,249L, new Printer(logger, "247-249"))).start();
Thread.sleep(100);
new Thread(() -> sb.awaitAndRun(234L,247L, new Printer(logger, "234-247"))).start();
sb.awaitCompletion();
System.out.flush();
}
private final static class Printer implements Runnable {
private final Logger logger;
private final String out;
public Printer(Logger logger, String out) {
this.logger = logger;
this.out = out;
}
@Override
public void run() {
logger.debug(out);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 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.adapter.diag.optasks;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.standard.NBConfiguration;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class DiagTask_gaugeTest {
@Test
public void testAvg() {
DiagTask_gauge gaugeTask = new DiagTask_gauge();
gaugeTask.setName("test");
gaugeTask.setLabelsFrom(NBLabeledElement.EMPTY);
NBConfiguration taskConfig = gaugeTask.getConfigModel().apply(Map.of(
"name","test",
"buckets","5",
"binding", "Identity()"
));
gaugeTask.applyConfig(taskConfig);
for (long i = 0; i < 10; i++) {
gaugeTask.apply(i,Map.of());
}
assertThat(gaugeTask.getValue()).isCloseTo(7.0d, Offset.offset(0.0001d));
}
@Test
public void testMin() {
DiagTask_gauge gaugeTask = new DiagTask_gauge();
gaugeTask.setName("test");
gaugeTask.setLabelsFrom(NBLabeledElement.EMPTY);
NBConfiguration taskConfig = gaugeTask.getConfigModel().apply(Map.of(
"name","test",
"buckets","5",
"stat", "min",
"binding", "Identity()"
));
gaugeTask.applyConfig(taskConfig);
for (long i = 0; i < 10; i++) {
gaugeTask.apply(i,Map.of());
}
assertThat(gaugeTask.getValue()).isCloseTo(5.0d, Offset.offset(0.0001d));
}
@Test
public void testMax() {
DiagTask_gauge gaugeTask = new DiagTask_gauge();
gaugeTask.setName("test");
gaugeTask.setLabelsFrom(NBLabeledElement.EMPTY);
NBConfiguration taskConfig = gaugeTask.getConfigModel().apply(Map.of(
"name","test",
"buckets","5",
"stat", "max",
"binding", "Identity()"
));
gaugeTask.applyConfig(taskConfig);
for (long i = 0; i < 10; i++) {
gaugeTask.apply(i,Map.of());
}
assertThat(gaugeTask.getValue()).isCloseTo(9.0d, Offset.offset(0.0001d));
}
}

View File

@@ -164,7 +164,6 @@ public abstract class BaseDriverAdapter<R extends Op, S> implements DriverAdapte
.add(Param.optional("cycles").setRegex("\\d+[KMBGTPE]?|\\d+[KMBGTPE]?\\.\\.\\d+[KMBGTPE]?").setDescription("cycle interval to use"))
.add(Param.optional("recycles").setDescription("allow cycles to be re-used this many times"))
.add(Param.optional(List.of("cyclerate", "targetrate", "rate"), String.class, "rate limit for cycles per second"))
.add(Param.optional("phaserate", String.class, "rate limit for phases per second"))
.add(Param.optional("seq", String.class, "sequencing algorithm"))
.add(Param.optional("instrument", Boolean.class))
.add(Param.optional(List.of("workload", "yaml"), String.class, "location of workload yaml file"))

View File

@@ -911,6 +911,10 @@ public class ParsedOp implements LongFunction<Map<String, ?>>, NBLabeledElement,
return tmap.parseStaticCmdMap(key, mainField);
}
public List<Map<String, Object>> parseStaticCmdMaps(String key, String mainField) {
return tmap.parseStaticCmdMaps(key, mainField);
}
@Override
public String toString() {
return this.tmap.toString();

View File

@@ -158,33 +158,8 @@ public interface Activity extends Comparable<Activity>, ActivityDefObserver, Pro
*/
RateLimiter getStrideRateLimiter(Supplier<? extends RateLimiter> supplier);
/**
* Get the current phase rate limiter for this activity.
* The phase rate limiter is used to throttle the rate at which
* new phases are dispatched across all threads in an activity.
* @return The stride {@link RateLimiter}
*/
RateLimiter getPhaseLimiter();
Timer getResultTimer();
/**
* Set the phase rate limiter for this activity. This method should only
* be used in a non-concurrent context. Otherwise, the supplier version
* {@link #getPhaseRateLimiter(Supplier)}} should be used.
* @param rateLimiter The phase {@link RateLimiter} for this activity.
*/
void setPhaseLimiter(RateLimiter rateLimiter);
/**
* Get or create the phase {@link RateLimiter} in a concurrent-safe
* way. Implementations should ensure that this method is synchronized or
* that each requester gets the same phase rate limiter for the activity.
* @param supplier A {@link RateLimiter} {@link Supplier}
* @return An extant or newly created phase {@link RateLimiter}
*/
RateLimiter getPhaseRateLimiter(Supplier<? extends RateLimiter> supplier);
/**
* Get or create the instrumentation needed for this activity. This provides
* a single place to find and manage, and document instrumentation that is

View File

@@ -19,6 +19,7 @@ package io.nosqlbench.engine.api.activityimpl;
import com.codahale.metrics.Timer;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.config.params.ParamsParser;
import io.nosqlbench.api.config.standard.NBConfiguration;
import io.nosqlbench.api.engine.activityimpl.ActivityDef;
import io.nosqlbench.api.engine.metrics.ActivityMetrics;
@@ -79,7 +80,6 @@ public class SimpleActivity implements Activity {
private RunState runState = RunState.Uninitialized;
private RateLimiter strideLimiter;
private RateLimiter cycleLimiter;
private RateLimiter phaseLimiter;
private ActivityController activityController;
private ActivityInstrumentation activityInstrumentation;
private PrintWriter console;
@@ -277,30 +277,11 @@ public class SimpleActivity implements Activity {
return strideLimiter;
}
@Override
public RateLimiter getPhaseLimiter() {
return phaseLimiter;
}
@Override
public Timer getResultTimer() {
return ActivityMetrics.timer(this, "result", getParams().getOptionalInteger("hdr_digits").orElse(4));
}
@Override
public void setPhaseLimiter(RateLimiter rateLimiter) {
this.phaseLimiter = rateLimiter;
}
@Override
public synchronized RateLimiter getPhaseRateLimiter(Supplier<? extends RateLimiter> supplier) {
if (null == this.phaseLimiter) {
phaseLimiter = supplier.get();
}
return phaseLimiter;
}
@Override
public synchronized ActivityInstrumentation getInstrumentation() {
if (null == this.activityInstrumentation) {
@@ -350,10 +331,6 @@ public class SimpleActivity implements Activity {
.map(RateSpec::new).ifPresent(
spec -> cycleLimiter = RateLimiters.createOrUpdate(this, "cycles", cycleLimiter, spec));
activityDef.getParams().getOptionalNamedParameter("phaserate")
.map(RateSpec::new)
.ifPresent(spec -> phaseLimiter = RateLimiters.createOrUpdate(this, "phases", phaseLimiter, spec));
}
/**
@@ -675,8 +652,13 @@ public class SimpleActivity implements Activity {
Optional<String> stmt = activityDef.getParams().getOptionalString("op", "stmt", "statement");
Optional<String> op_yaml_loc = activityDef.getParams().getOptionalString("yaml", "workload");
if (stmt.isPresent()) {
String op = stmt.get();
workloadSource = "commandline:" + stmt.get();
return OpsLoader.loadString(stmt.get(), OpTemplateFormat.inline, activityDef.getParams(), null);
if (op.startsWith("{")||op.startsWith("[")) {
return OpsLoader.loadString(stmt.get(), OpTemplateFormat.json, activityDef.getParams(), null);
} else {
return OpsLoader.loadString(stmt.get(), OpTemplateFormat.inline, activityDef.getParams(), null);
}
}
if (op_yaml_loc.isPresent()) {
workloadSource = "yaml:" + op_yaml_loc.get();

View File

@@ -215,17 +215,14 @@ public class CoreMotor<D> implements ActivityDefObserver, Motor<D>, Stoppable {
strideRateLimiter.start();
}
long strideDelay = 0L;
long cycleDelay = 0L;
long phaseDelay = 0L;
// Reviewer Note: This separate of code paths was used to avoid impacting the
// previously logic for the SyncAction type. It may be consolidated later once
// the async action is proven durable
if (action instanceof AsyncAction) {
@SuppressWarnings("unchecked")
AsyncAction<D> async = (AsyncAction) action;
@@ -387,12 +384,7 @@ public class CoreMotor<D> implements ActivityDefObserver, Motor<D>, Stoppable {
long cycleStart = System.nanoTime();
try {
logger.trace(()->"cycle " + cyclenum);
// runCycle
long phaseStart = System.nanoTime();
result = sync.runCycle(cyclenum);
long phaseEnd = System.nanoTime();
} catch (Exception e) {
motorState.enterState(Errored);
throw e;

View File

@@ -18,10 +18,11 @@ package io.nosqlbench.engine.cli;
import io.nosqlbench.api.annotations.Annotation;
import io.nosqlbench.api.annotations.Layer;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.content.Content;
import io.nosqlbench.api.content.NBIO;
import io.nosqlbench.api.engine.metrics.ActivityMetrics;
import io.nosqlbench.api.engine.metrics.reporters.PromPushReporter;
import io.nosqlbench.api.errors.BasicError;
import io.nosqlbench.api.logging.NBLogLevel;
import io.nosqlbench.api.metadata.SessionNamer;
@@ -66,7 +67,7 @@ import java.util.ServiceLoader.Provider;
import java.util.function.Function;
import java.util.stream.Collectors;
public class NBCLI implements Function<String[], Integer> {
public class NBCLI implements Function<String[], Integer>, NBLabeledElement {
private static Logger logger;
private static final LoggerConfig loggerConfig;
@@ -81,6 +82,9 @@ public class NBCLI implements Function<String[], Integer> {
private final String commandName;
private NBLabels labels;
private String sessionName;
public NBCLI(final String commandName) {
this.commandName = commandName;
}
@@ -95,7 +99,7 @@ public class NBCLI implements Function<String[], Integer> {
*/
public static void main(final String[] args) {
try {
final NBCLI cli = new NBCLI("nb");
final NBCLI cli = new NBCLI("nb5");
final int statusCode = cli.apply(args);
System.exit(statusCode);
} catch (final Exception e) {
@@ -115,7 +119,7 @@ public class NBCLI implements Function<String[], Integer> {
@Override
public Integer apply(final String[] args) {
try {
final NBCLI cli = new NBCLI("nb");
final NBCLI cli = new NBCLI("nb5");
final int result = cli.applyDirect(args);
return result;
} catch (final Exception e) {
@@ -149,7 +153,8 @@ public class NBCLI implements Function<String[], Integer> {
NBCLI.loggerConfig.setConsoleLevel(NBLogLevel.ERROR);
final NBCLIOptions globalOptions = new NBCLIOptions(args, Mode.ParseGlobalsOnly);
final String sessionName = SessionNamer.format(globalOptions.getSessionName());
this.labels=NBLabels.forKV("command",commandName).and(globalOptions.getLabelMap());
this.sessionName = SessionNamer.format(globalOptions.getSessionName());
NBCLI.loggerConfig
.setSessionName(sessionName)
@@ -432,7 +437,8 @@ public class NBCLI implements Function<String[], Integer> {
options.getReportSummaryTo(),
String.join("\n", args),
options.getLogsDirectory(),
Maturity.Unspecified);
Maturity.Unspecified,
this);
final ScriptBuffer buffer = new BasicScriptBuffer()
.add(options.getCommands()
@@ -504,4 +510,8 @@ public class NBCLI implements Function<String[], Integer> {
return metrics;
}
@Override
public NBLabels getLabels() {
return labels;
}
}

View File

@@ -51,6 +51,7 @@ public class NBCLIOptions {
private static final String userHome = System.getProperty("user.home");
private static final Map<String,String> DEFAULT_LABELS=Map.of("appname","nosqlbench");
private static final String METRICS_PREFIX = "--metrics-prefix";
private static final String ANNOTATE_EVENTS = "--annotate";
private static final String ANNOTATORS_CONFIG = "--annotators";
@@ -82,6 +83,9 @@ public class NBCLIOptions {
private static final String EXPERIMENTAL = "--experimental";
private static final String MATURITY = "--maturity";
private static final String SET_LABELS = "--set-labels";
private static final String ADD_LABELS = "--add-labels";
// Execution
private static final String EXPORT_CYCLE_LOG = "--export-cycle-log";
private static final String IMPORT_CYCLE_LOG = "--import-cycle-log";
@@ -132,6 +136,7 @@ public class NBCLIOptions {
// private static final String DEFAULT_CONSOLE_LOGGING_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
private final Map<String,String> labels = new LinkedHashMap<>(DEFAULT_LABELS);
private final List<Cmd> cmdList = new ArrayList<>();
private int logsMax;
private boolean wantsVersionShort;
@@ -205,6 +210,9 @@ public class NBCLIOptions {
return this.annotatorsConfig;
}
public Map<String,String> getLabelMap() {
return Collections.unmodifiableMap(this.labels);
}
public String getChartHdrFileName() {
return this.hdrForChartFileName;
@@ -460,6 +468,16 @@ public class NBCLIOptions {
arglist.removeFirst();
final String maturity = this.readWordOrThrow(arglist, "maturity of components to allow");
minMaturity = Maturity.valueOf(maturity.toLowerCase(Locale.ROOT));
case NBCLIOptions.SET_LABELS:
arglist.removeFirst();
String setLabelData = arglist.removeFirst();
setLabels(setLabelData);
break;
case NBCLIOptions.ADD_LABELS:
arglist.removeFirst();
String addLabeldata = arglist.removeFirst();
addLabels(addLabeldata);
break;
default:
nonincludes.addLast(arglist.removeFirst());
}
@@ -468,6 +486,29 @@ public class NBCLIOptions {
return nonincludes;
}
private void setLabels(String labeldata) {
this.labels.clear();
addLabels(labeldata);
}
private void addLabels(String labeldata) {
Map<String,String> newLabels = parseLabels(labeldata);
this.labels.putAll(newLabels);
}
private Map<String, String> parseLabels(String labeldata) {
Map<String,String> setLabelsTo = new LinkedHashMap<>();
for (String component : labeldata.split("[,; ]")) {
String[] parts = component.split("\\W", 2);
if (parts.length!=2) {
throw new BasicError("Unable to parse labels to set:" + labeldata);
}
setLabelsTo.put(parts[0],parts[1]);
}
return setLabelsTo;
}
private Path setStatePath() {
if (0 < statePathAccesses.size())
throw new BasicError("The state dir must be set before it is used by other\n" +

View File

@@ -208,6 +208,21 @@ automatically. It also imports a base dashboard for nosqlbench and configures gr
export to share with a central DataStax grafana instance (grafana can be found on localhost:3000
with the default credentials admin/admin).
### Metrics Labeling
Metrics have attached labels which identify which session, scenario, activity, and operation
they are attached to. Not all labels will be present, as metrics are instanced at different
levels and may or may not be op or activity specific.
By default, labels are added automatically to metrics. You can change this if needed.
# add labels to metrics, in addition to the default ones
--add-labels label1=value1,label2=value2,...
# replace the initial set of labels
--set-labels label1=value1,label2=value2,...
The default labels include appname, command, scenario, activity, op, and name.
### Summary Reporting

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");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,9 @@ public class ActivityProgressIndicator implements Runnable {
}
private void parseProgressSpec(String interval) {
if (interval==null) {
throw new RuntimeException("can't parse progress spec if it is null");
}
String[] parts = interval.split(":");
switch (parts.length) {
case 2:

View File

@@ -74,6 +74,7 @@ public class Scenario implements Callable<ExecutionMetricsResult>, NBLabeledElem
private ScenarioMetadata scenarioMetadata;
private ExecutionMetricsResult result;
private final NBLabeledElement parentComponent;
public Optional<ExecutionMetricsResult> getResultIfComplete() {
return Optional.ofNullable(result);
@@ -82,7 +83,7 @@ public class Scenario implements Callable<ExecutionMetricsResult>, NBLabeledElem
@Override
public NBLabels getLabels() {
return NBLabels.forKV("scenario", this.scenarioName);
return this.parentComponent.getLabels().and("scenario", this.scenarioName);
}
public enum State {
@@ -100,10 +101,10 @@ public class Scenario implements Callable<ExecutionMetricsResult>, NBLabeledElem
private ScenarioContext scriptEnv;
private final String scenarioName;
private ScriptParams scenarioScriptParams;
private String scriptfile;
private final String scriptfile;
private Engine engine = Engine.Graalvm;
private boolean wantsStackTraces;
private boolean wantsCompiledScript;
private final boolean wantsStackTraces;
private final boolean wantsCompiledScript;
private long startedAtMillis = -1L;
private long endedAtMillis = -1L;
@@ -121,7 +122,8 @@ public class Scenario implements Callable<ExecutionMetricsResult>, NBLabeledElem
final String reportSummaryTo,
final String commandLine,
final Path logsPath,
final Maturity minMaturity) {
final Maturity minMaturity,
NBLabeledElement parentComponent) {
this.scenarioName = scenarioName;
this.scriptfile = scriptfile;
@@ -133,17 +135,22 @@ public class Scenario implements Callable<ExecutionMetricsResult>, NBLabeledElem
this.commandLine = commandLine;
this.logsPath = logsPath;
this.minMaturity = minMaturity;
this.parentComponent = parentComponent;
}
public Scenario(final String name, final Engine engine, final String reportSummaryTo, final Maturity minMaturity) {
scenarioName = name;
this.reportSummaryTo = reportSummaryTo;
this.engine = engine;
commandLine = "";
this.minMaturity = minMaturity;
logsPath = Path.of("logs");
public static Scenario forTesting(final String name, final Engine engine, final String reportSummaryTo, final Maturity minMaturity) {
return new Scenario(name,null,engine,"console:10s",true,true,reportSummaryTo,"",Path.of("logs"),minMaturity, NBLabeledElement.forKV("test-name","name"));
}
// public Scenario(final String name, final Engine engine, final String reportSummaryTo, final Maturity minMaturity) {
// scenarioName = name;
// this.reportSummaryTo = reportSummaryTo;
// this.engine = engine;
// commandLine = "";
// this.minMaturity = minMaturity;
// logsPath = Path.of("logs");
// }
//
public Scenario setLogger(final Logger logger) {
this.logger = logger;
return this;

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");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ public class ScenarioTest {
@Test
public void shouldLoadScriptText() {
ScriptEnvBuffer buffer = new ScriptEnvBuffer();
Scenario scenario = new Scenario("testing", Scenario.Engine.Graalvm, "stdout:300", Maturity.Any);
Scenario scenario = Scenario.forTesting("testing", Scenario.Engine.Graalvm, "stdout:300", Maturity.Any);
scenario.addScriptText("print('loaded script environment...');\n");
try {
var result=scenario.call();

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");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ public class ScenariosExecutorTest {
@Disabled
public void testAwaitOnTime() {
ScenariosExecutor e = new ScenariosExecutor(ScenariosExecutorTest.class.getSimpleName(), 1);
Scenario s = new Scenario("testing", Scenario.Engine.Graalvm,"stdout:3000", Maturity.Any);
Scenario s = Scenario.forTesting("testing", Scenario.Engine.Graalvm,"stdout:3000", Maturity.Any);
s.addScriptText("load('classpath:scripts/asyncs.js');\nsetTimeout(\"print('waited')\",5000);\n");
e.execute(s);
ScenariosResults scenariosResults = e.awaitAllResults();

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");
* you may not use this file except in compliance with the License.
@@ -59,21 +59,21 @@ public class ServiceSelector<T> implements Predicate<ServiceLoader.Provider<? ex
return false;
}
public T getOne() {
List<? extends T> services = getAll();
if (services.size() == 0) {
public ServiceLoader.Provider<? extends T> getOneProvider() {
List<? extends ServiceLoader.Provider<? extends T>> providers = getAllProviders();
if (providers.size()==0 || providers.size()>1) {
throw new RuntimeException("You requested exactly one instance of a service by name '" + name + "', but got " +
(services.stream().map(s -> s.getClass().getSimpleName())).collect(Collectors.joining(",")) + " (" + services.stream().count() + ")");
} else if (services.size()==1) {
return services.get(0);
(providers.stream().map(s -> s.getClass().getSimpleName())).collect(Collectors.joining(",")) + " (" + providers.stream().count() + ")");
}
throw new RuntimeException("You requested exactly one instance of a service by name '" + name + "', but got " +
(services.stream().map(s -> s.getClass().getSimpleName())).collect(Collectors.joining(",")) + " (" + services.stream().count() + ")");
return providers.get(0);
}
public List<? extends T> getAll() {
List<? extends T> services = loader
public T getOne() {
return getOneProvider().get();
}
public List<? extends ServiceLoader.Provider<? extends T>> getAllProviders() {
List<? extends ServiceLoader.Provider<? extends T>> providers = loader
.stream()
.peek(l -> {
if (l.type().getAnnotation(Service.class) == null) {
@@ -86,9 +86,14 @@ public class ServiceSelector<T> implements Predicate<ServiceLoader.Provider<? ex
)
.filter(l -> l.type().getAnnotation(Service.class) != null)
.filter(l -> l.type().getAnnotation(Service.class).selector().equals(name))
.toList();
return providers;
}
public List<? extends T> getAll() {
List<? extends ServiceLoader.Provider<? extends T>> providers = getAllProviders();
return providers.stream()
.map(ServiceLoader.Provider::get)
.toList();
return services;
}
public Optional<? extends T> get() {

View File

@@ -123,7 +123,14 @@ public class MapLabels implements NBLabels {
}
public String toString() {
return this.linearize("name");
StringBuilder sb = new StringBuilder("{");
labels.forEach((k,v) -> {
sb.append(k).append(":\\\"").append(v).append("\\\"").append(",");
});
sb.setLength(sb.length()-",".length());
sb.append("}");
return sb.toString();
}
@Override

View File

@@ -19,13 +19,11 @@ package io.nosqlbench.api.engine.metrics.reporters;
import com.codahale.metrics.*;
import io.nosqlbench.api.config.NBLabeledElement;
import io.nosqlbench.api.config.NBLabels;
import io.nosqlbench.api.testutils.Perf;
import java.io.IOException;
import java.io.Writer;
import java.time.Clock;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
/**
@@ -70,7 +68,7 @@ public enum PromExpositionFormat {
final long count = counting.getCount();
buffer
.append(labels.modifyValue("name", n -> n+"_total"))
.append(labels.modifyValue("name", n -> n+"_total").linearize("name"))
.append(' ')
.append(count)
.append(' ')
@@ -84,37 +82,37 @@ public enum PromExpositionFormat {
for (final double quantile : new double[]{0.5, 0.75, 0.90, 0.95, 0.98, 0.99, 0.999}) {
final double value = snapshot.getValue(quantile);
buffer
.append(labels.and("quantile", String.valueOf(quantile)))
.append(labels.and("quantile", String.valueOf(quantile)).linearize("name"))
.append(' ')
.append(value)
.append('\n');
}
final double snapshotCount =snapshot.size();
buffer.append(labels.modifyValue("name",n->n+"_count"))
buffer.append(labels.modifyValue("name",n->n+"_count").linearize("name"))
.append(' ')
.append(snapshotCount)
.append('\n');
buffer.append("# TYPE ").append(labels.only("name")).append("_max").append(" gauge\n");
final long maxValue = snapshot.getMax();
buffer.append(labels.modifyValue("name",n->n+"_max"))
buffer.append(labels.modifyValue("name",n->n+"_max").linearize("name"))
.append(' ')
.append(maxValue)
.append('\n');
buffer.append("# TYPE ").append(labels.only("name")).append("_min").append(" gauge\n");
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_min").only("name")).append(" gauge\n");
final long minValue = snapshot.getMin();
buffer.append(labels.modifyValue("name",n->n+"_min"))
buffer.append(labels.modifyValue("name",n->n+"_min").linearize("name"))
.append(' ')
.append(minValue)
.append('\n');
buffer.append("# TYPE ").append(labels.only("name")).append("_mean").append(" gauge\n");
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_mean").only("name")).append(" gauge\n");
final double meanValue = snapshot.getMean();
buffer.append(labels.modifyValue("name",n->n+"_mean"))
buffer.append(labels.modifyValue("name",n->n+"_mean").linearize("name"))
.append(' ')
.append(meanValue)
.append('\n');
buffer.append("# TYPE ").append(labels.only("name")).append("_stdev").append(" gauge\n");
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_stdev").only("name")).append(" gauge\n");
final double stdDev = snapshot.getStdDev();
buffer.append(labels.modifyValue("name",n->n+"_stdev"))
buffer.append(labels.modifyValue("name",n->n+"_stdev").linearize("name"))
.append(' ')
.append(stdDev)
.append('\n');
@@ -125,18 +123,18 @@ public enum PromExpositionFormat {
final Object value = gauge.getValue();
if (value instanceof final Number number) {
final double doubleValue = number.doubleValue();
buffer.append(labels)
buffer.append(labels.linearize("name"))
.append(' ')
.append(doubleValue)
.append('\n');
} else if (value instanceof final CharSequence sequence) {
final String stringValue = sequence.toString();
buffer.append(labels)
buffer.append(labels.linearize("name"))
.append(' ')
.append(stringValue)
.append('\n');
} else if (value instanceof final String stringValue) {
buffer.append(labels)
buffer.append(labels.linearize("name"))
.append(' ')
.append(stringValue)
.append('\n');
@@ -147,28 +145,28 @@ public enum PromExpositionFormat {
if (metric instanceof final Metered meter) {
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_1mRate").only("name")).append(" gauge\n");
final double oneMinuteRate = meter.getOneMinuteRate();
buffer.append(labels.modifyValue("name",n->n+"_1mRate"))
buffer.append(labels.modifyValue("name",n->n+"_1mRate").linearize("name"))
.append(' ')
.append(oneMinuteRate)
.append('\n');
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_5mRate").only("name")).append(" gauge\n");
final double fiveMinuteRate = meter.getFiveMinuteRate();
buffer.append(labels.modifyValue("name",n->n+"_5mRate"))
buffer.append(labels.modifyValue("name",n->n+"_5mRate").linearize("name"))
.append(' ')
.append(fiveMinuteRate)
.append('\n');
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_15mRate").only("name")).append(" gauge\n");
final double fifteenMinuteRate = meter.getFifteenMinuteRate();
buffer.append(labels.modifyValue("name",n->n+"_15mRate"))
buffer.append(labels.modifyValue("name",n->n+"_15mRate").linearize("name"))
.append(' ')
.append(fifteenMinuteRate)
.append('\n');
buffer.append("# TYPE ").append(labels.modifyValue("name",n->n+"_meanRate").only("name")).append(" gauge\n");
final double meanRate = meter.getMeanRate();
buffer.append(labels.modifyValue("name",n->n+"_meanRate"))
buffer.append(labels.modifyValue("name",n->n+"_meanRate").linearize("name"))
.append(' ')
.append(meanRate)
.append('\n');

View File

@@ -74,6 +74,7 @@ public class PromPushReporter extends ScheduledReporter {
}
PromPushReporter.logger.debug("formatted {} metrics in prom expo format", total);
final String exposition = sb.toString();
logger.trace(() -> "prom exposition format:\n" + exposition);
final double backoffRatio=1.5;
final double maxBackoffSeconds=10;

View File

@@ -56,7 +56,7 @@ public class ScriptExampleTests {
String scenarioName = "scenario " + scriptname;
System.out.println("=".repeat(29) + " Running integration test for example scenario: " + scenarioName);
ScenariosExecutor executor = new ScenariosExecutor(ScriptExampleTests.class.getSimpleName() + ":" + scriptname, 1);
Scenario s = new Scenario(scenarioName, Scenario.Engine.Graalvm,"stdout:300", Maturity.Any);
Scenario s = Scenario.forTesting(scenarioName, Scenario.Engine.Graalvm,"stdout:300", Maturity.Any);
s.addScenarioScriptParams(paramsMap);
@@ -261,7 +261,7 @@ public class ScriptExampleTests {
public void testErrorPropagationFromAdapterOperation() {
ExecutionMetricsResult scenarioResult = runScenario(
"basicdiag",
"type", "diag", "cyclerate", "5", "erroroncycle", "10", "cycles", "2000"
"driver", "diag", "cyclerate", "5", "erroroncycle", "10", "cycles", "2000"
);
}

View File

@@ -15,19 +15,17 @@
*/
activitydef1 = {
"alias" : "activity_error",
"driver" : "diag",
"cycles" : "0..1500000",
"threads" : "1",
"targetrate" : "10",
"op" : {
"log": "type=log modulo=1"
}
"alias": "activity_error",
"driver": "diag",
"cycles": "0..1500000",
"threads": "1",
"targetrate": "10",
"op": "log: modulo=1"
};
print('starting activity activity_error');
scenario.start(activitydef1);
scenario.waitMillis(2000);
activities.activity_error.threads="unparsable";
activities.activity_error.threads = "unparsable";
scenario.awaitActivity("activity_error");
print("awaited activity");

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");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ co_cycle_delay_bursty = {
"cycles": "0..1000000",
"threads": "10",
"cyclerate": "1000,1.5",
"op" : '{"log":{"level":"info","modulo":1000},"diagrate":{"diagrate":"500"}}'
"op" : "diagrate: diagrate=500"
};
print('starting activity co_cycle_delay_bursty');

View File

@@ -973,6 +973,17 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
return new LinkedHashMap<String,Object>(ParamsParser.parseToMap(mapsrc,mainField));
}
public List<Map<String, Object>> parseStaticCmdMaps(String key, String mainField) {
Object mapsSrc = getStaticValue(key);
List<Map<String,Object>> maps = new ArrayList<>();
for (String spec : mapsSrc.toString().split("; +")) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>(ParamsParser.parseToMap(spec, mainField));
maps.add(map);
}
return maps;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("protomap:\n");

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");
* you may not use this file except in compliance with the License.
@@ -29,5 +29,6 @@ public enum Category {
statistics,
general,
objects,
periodic,
experimental
}

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");
* you may not use this file except in compliance with the License.
@@ -90,9 +90,7 @@ public class VirtDataConversions {
List<Class<?>> resultTypes = new ArrayList<>();
resultTypes.add(functionType);
for (Class<?> aClass : resultSignature) {
resultTypes.add(aClass);
}
Collections.addAll(resultTypes, resultSignature);
List<Class<?>> toSignature = linearizeSignature(resultTypes);
signature.addAll(fromSignature);
@@ -335,9 +333,7 @@ public class VirtDataConversions {
Class<?>[] argTypes = new Class[generics.length + 2];
argTypes[0] = fromClass;
argTypes[1] = toClass;
for (int i = 0; i < generics.length; i++) {
argTypes[i + 2] = generics[i];
}
System.arraycopy(generics, 0, argTypes, 2, generics.length);
try {
return hostclass.getMethod("adapt", argTypes);
@@ -354,9 +350,6 @@ public class VirtDataConversions {
if (generics.length < typeParameters.length) {
throw new RuntimeException("You must provide " + typeParameters.length + " generic parameter types for " + toClass.getCanonicalName());
}
// if (generics[i].isPrimitive()) {
// throw new RuntimeException("You must declare non-primitive types in generic parameter placeholders, not " + generics[i].getSimpleName());
// }
genericsBuffer.append(generics[i].getSimpleName());
genericsBuffer.append(",");
}
@@ -385,19 +378,6 @@ public class VirtDataConversions {
}
}
// private static void assertOutputAssignable(Object result, Class<?> clazz) {
// if (!ClassUtils.isAssignable(result.getClass(), clazz, true)) {
// throw new InvalidParameterException("Unable to assign type of " + result.getClass().getCanonicalName()
// + " to " + clazz.getCanonicalName());
// }
//
//// if (!clazz.isAssignableFrom(result.getClass())) {
//// throw new InvalidParameterException("Unable to assign type of " + result.getClass().getCanonicalName()
//// + " to " + clazz.getCanonicalName());
//// }
// }
//
/**
* Given a base object and a wanted type to convert it to, assert that the type of the base object is assignable to
* the wanted type. Further, if the wanted type is a generic type, assert that additional classes are assignable to
@@ -444,16 +424,5 @@ public class VirtDataConversions {
return (T) (base);
}
//
// /**
// * Throw an error indicating a narrowing conversion was attempted for strict conversion.
// * @param func The source function to convert from
// * @param targetClass The target class which was requested
// */
// private static void throwNarrowingError(Object func, Class<?> targetClass) {
// throw new BasicError("Converting from " + func.getClass().getCanonicalName() + " to " + targetClass.getCanonicalName() +
// " is not allowed when strict conversion is requested.");
// }
}

View File

@@ -0,0 +1,47 @@
/*
* 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.virtdata.library.basics.shared.from_double.to_double;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import io.nosqlbench.virtdata.api.bindings.VirtDataConversions;
import java.util.List;
import java.util.function.LongToDoubleFunction;
/**
* Compute the sum of a set of functions.
*/
@ThreadSafeMapper
@Categories({Category.general})
public class SumFunctions implements LongToDoubleFunction {
private final List<LongToDoubleFunction> functions;
public SumFunctions(Object... funcs) {
this.functions = VirtDataConversions.adaptFunctionList(funcs, LongToDoubleFunction.class);
}
@Override
public double applyAsDouble(long value) {
double sum = 0.0d;
for (LongToDoubleFunction function : functions) {
sum+=function.applyAsDouble(value);
}
return sum;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 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.virtdata.library.basics.shared.from_double.to_double;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import io.nosqlbench.virtdata.api.bindings.VirtDataConversions;
import java.util.function.DoubleUnaryOperator;
import java.util.function.LongUnaryOperator;
@ThreadSafeMapper
@Categories(Category.periodic)
public class TriangleWave implements DoubleUnaryOperator {
private final double phaseLength;
private final DoubleUnaryOperator scaleFunc;
private final DoubleUnaryOperator normalizerFunc;
private final double halfWave;
public TriangleWave(double phaseLength, Object scaler) {
this.halfWave = phaseLength*0.5d;
normalizerFunc=d -> d/(phaseLength/2.0);
this.phaseLength=phaseLength;
if (scaler instanceof Number number) {
if (scaler instanceof Double adouble) {
this.scaleFunc=d -> d*adouble;
} else {
this.scaleFunc= d -> d*number.doubleValue();
}
} else {
this.scaleFunc = VirtDataConversions.adaptFunction(scaler, DoubleUnaryOperator.class);
}
}
public TriangleWave(double phaseLength) {
this(phaseLength, LongUnaryOperator.identity());
}
@Override
public double applyAsDouble(double operand) {
double position = operand % phaseLength;
int slot = (int) (4.0d*position/phaseLength);
double sample = switch (slot) {
case 0 -> position;
case 1 -> halfWave-position;
case 2 -> position-halfWave;
case 4 -> phaseLength-position;
default -> Double.NaN;
};
double normalized = normalizerFunc.applyAsDouble(sample);
double scaled = scaleFunc.applyAsDouble(sample);
return sample;
}
}

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.virtdata.library.basics.shared.from_long.to_double;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.util.function.LongToDoubleFunction;
@ThreadSafeMapper
@Categories({Category.general})
public class Mul implements LongToDoubleFunction {
private final double factor;
public Mul(double factor) {
this.factor = factor;
}
@Override
public double applyAsDouble(long value) {
return factor * value;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 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.virtdata.library.basics.shared.from_long.to_long;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import io.nosqlbench.virtdata.api.bindings.VirtDataConversions;
import java.util.function.LongUnaryOperator;
/**
* Computes the distance between the current input value and the
* beginning of the phase, according to a phase length.
* This means that for a phase length of 100, the values will
* range from 0 (for cycle values 0 and 100 or any multiple thereof)
* and 50, when the cycle value falls immediately at the middle
* of the phase.
*/
@ThreadSafeMapper
@Categories(Category.periodic)
public class TriangleWave implements LongUnaryOperator {
private final long phaseLength;
private final LongUnaryOperator scaleFunc;
public TriangleWave(long phaseLength, Object scaleFunc) {
this.phaseLength=phaseLength;
this.scaleFunc = VirtDataConversions.adaptFunction(scaleFunc, LongUnaryOperator.class);
}
public TriangleWave(long phaseLength) {
this(phaseLength, LongUnaryOperator.identity());
}
@Override
public long applyAsLong(long operand) {
long position = operand % phaseLength;
long minDistanceFromEnds = Math.min(Math.abs(phaseLength - position), position);
long result = scaleFunc.applyAsLong(minDistanceFromEnds);
return result;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 nosqlbench
* Copyright (c) 2023 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,20 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.diag;
package io.nosqlbench.virtdata.library.basics.shared.periodic;
public class DiagDummyError extends RuntimeException {
public DiagDummyError(String s) {
super(s);
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.util.function.DoubleUnaryOperator;
@ThreadSafeMapper
@Categories(Category.periodic)
public class Sin implements DoubleUnaryOperator {
@Override
public double applyAsDouble(double operand) {
return Math.sin(operand);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 nosqlbench
* Copyright (c) 2023 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,19 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.diag;
package io.nosqlbench.virtdata.library.basics.shared.temporal;
public class DiagResult {
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.util.function.LongUnaryOperator;
@ThreadSafeMapper
@Categories(Category.datetime)
public class CurrentTimeMillis implements LongUnaryOperator {
@Override
public long applyAsLong(long operand) {
return System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 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.virtdata.library.basics.shared.from_double.to_double;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.Test;
import java.util.function.LongToDoubleFunction;
import static org.assertj.core.api.Assertions.assertThat;
public class SumFunctionsTest {
@Test
public void sumFunctionsTest() {
LongToDoubleFunction f1 = d -> d*3.0d;
LongToDoubleFunction f2 = d -> d+5.0d;
SumFunctions ff = new SumFunctions(f1,f2);
assertThat(ff.applyAsDouble(15L)).isEqualTo(65.0d, Offset.offset(0.0002d));
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 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.virtdata.library.basics.shared.from_long.to_long;/*
* Copyright (c) 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.
*/
import io.nosqlbench.virtdata.library.basics.shared.from_double.to_double.TriangleWave;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TriangleWaveTest {
@Test
public void testLongValues() {
io.nosqlbench.virtdata.library.basics.shared.from_long.to_long.TriangleWave cyclicDistance =
new io.nosqlbench.virtdata.library.basics.shared.from_long.to_long.TriangleWave(100L);
assertThat(cyclicDistance.applyAsLong(0)).isEqualTo(0);
assertThat(cyclicDistance.applyAsLong(100)).isEqualTo(0);
assertThat(cyclicDistance.applyAsLong(49)).isEqualTo(49);
assertThat(cyclicDistance.applyAsLong(50)).isEqualTo(50);
assertThat(cyclicDistance.applyAsLong(51)).isEqualTo(49);
}
/**
* <pre>{@code
* /\ ^0.5
* / \
* ---0----\----0----
* \ /
* \/ _-0.5
* }</pre>
*/
@Test
public void testDoubleValues() {
TriangleWave cyclicDistance =
new TriangleWave(100.0d,50.0d);
assertThat(cyclicDistance.applyAsDouble(0.0d)).isCloseTo(0.0d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(12.5d)).isCloseTo(12.5d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(25.0d)).isCloseTo(25.0d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(37.5d)).isCloseTo(12.5d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(100.0d)).isCloseTo(0.0d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(49.0d)).isCloseTo(1.0d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(50.0d)).isCloseTo(0.0d, Offset.offset(0.0001d));
assertThat(cyclicDistance.applyAsDouble(51.0d)).isCloseTo(1.0d, Offset.offset(0.0001d));
}
}