From 3e41d57e036863ead13a47c5f4675a490e36174c Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:38:01 -0500 Subject: [PATCH 1/7] clarify opData meaning --- .../nosqlbench/activitytype/cql/core/CqlAsyncAction.java | 6 +++--- .../io/nosqlbench/activitytype/diag/AsyncDiagAction.java | 8 ++++---- .../api/activityapi/core/ops/fluent/opfacets/OpImpl.java | 2 +- .../api/activityapi/core/ops/fluent/opfacets/Payload.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/driver-cql-shaded/src/main/java/io/nosqlbench/activitytype/cql/core/CqlAsyncAction.java b/driver-cql-shaded/src/main/java/io/nosqlbench/activitytype/cql/core/CqlAsyncAction.java index eaa325e2d..43f16f7ef 100644 --- a/driver-cql-shaded/src/main/java/io/nosqlbench/activitytype/cql/core/CqlAsyncAction.java +++ b/driver-cql-shaded/src/main/java/io/nosqlbench/activitytype/cql/core/CqlAsyncAction.java @@ -79,7 +79,7 @@ public class CqlAsyncAction extends BaseAsyncAction { @Override public void startOpCycle(TrackedOp opc) { - CqlOpData cqlop = opc.getData(); + CqlOpData cqlop = opc.getOpData(); long cycle = opc.getCycle(); // bind timer covers all statement selection and binding, skipping, transforming logic @@ -123,7 +123,7 @@ public class CqlAsyncAction extends BaseAsyncAction { public void onSuccess(StartedOp sop) { - CqlOpData cqlop = sop.getData(); + CqlOpData cqlop = sop.getOpData(); HashedCQLErrorHandler.resetThreadStatusCode(); if (cqlop.skipped) { @@ -217,7 +217,7 @@ public class CqlAsyncAction extends BaseAsyncAction { public void onFailure(StartedOp startedOp) { - CqlOpData cqlop = startedOp.getData(); + CqlOpData cqlop = startedOp.getOpData(); long serviceTime = startedOp.getCurrentServiceTimeNanos(); // Even if this is retryable, we expose error events diff --git a/driver-diag/src/main/java/io/nosqlbench/activitytype/diag/AsyncDiagAction.java b/driver-diag/src/main/java/io/nosqlbench/activitytype/diag/AsyncDiagAction.java index 90a233fa9..d2d127167 100644 --- a/driver-diag/src/main/java/io/nosqlbench/activitytype/diag/AsyncDiagAction.java +++ b/driver-diag/src/main/java/io/nosqlbench/activitytype/diag/AsyncDiagAction.java @@ -151,8 +151,8 @@ public class AsyncDiagAction extends BaseAsyncAction i @Override public void startOpCycle(TrackedOp opc) { - opc.getData().log("starting at " + System.nanoTime()); - opc.getData().setSimulatedDelayNanos(delayFunc.applyAsLong(opc.getCycle())); + opc.getOpData().log("starting at " + System.nanoTime()); + opc.getOpData().setSimulatedDelayNanos(delayFunc.applyAsLong(opc.getCycle())); StartedOp started = opc.start(); opQueue.add(started); } @@ -239,7 +239,7 @@ public class AsyncDiagAction extends BaseAsyncAction i try { opc=queue.take(); - DiagOpData op = opc.getData(); + DiagOpData op = opc.getOpData(); long now = System.nanoTime(); long simulatedCompletionTime = opc.getStartedAtNanos() + op.getSimulatedDelayNanos(); @@ -269,7 +269,7 @@ public class AsyncDiagAction extends BaseAsyncAction i logger.info("processing stride output for " + completedOps.get(0).getCycle()); long start = completedOps.get(0).getCycle(); long endPlus = completedOps.get(completedOps.size()-1).getCycle()+1; - String diagLog = completedOps.get(0).getData().getDiagLog().stream().collect(Collectors.joining("\n")); + String diagLog = completedOps.get(0).getOpData().getDiagLog().stream().collect(Collectors.joining("\n")); activity.getSequenceBlocker().awaitAndRun(start, endPlus, () -> logger.info(" => " + start + " -> " + endPlus + ": " + diagLog)); } } diff --git a/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/OpImpl.java b/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/OpImpl.java index f30246302..e4011c75c 100644 --- a/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/OpImpl.java +++ b/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/OpImpl.java @@ -100,7 +100,7 @@ public class OpImpl implements OpFacets { } @Override - public D getData() { + public D getOpData() { return data; } diff --git a/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/Payload.java b/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/Payload.java index 5e193262f..036e8c807 100644 --- a/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/Payload.java +++ b/engine-api/src/main/java/io/nosqlbench/engine/api/activityapi/core/ops/fluent/opfacets/Payload.java @@ -23,6 +23,6 @@ package io.nosqlbench.engine.api.activityapi.core.ops.fluent.opfacets; * @param The type of delegate needed for the implementing protocol */ public interface Payload { - D getData(); + D getOpData(); void setData(D data); } From f4cae1dee22074850a5a6dbe08dc48f7144e754b Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:39:51 -0500 Subject: [PATCH 2/7] add HTTP validity check exceptions --- .../http/InvalidResponseBodyException.java | 13 +++++++++++++ .../http/InvalidStatusCodeException.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidResponseBodyException.java create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidStatusCodeException.java diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidResponseBodyException.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidResponseBodyException.java new file mode 100644 index 000000000..8d1146773 --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidResponseBodyException.java @@ -0,0 +1,13 @@ +package io.nosqlbench.activitytype.http; + +public class InvalidResponseBodyException extends RuntimeException { + private final long cycleValue; + private final String ok_body; + private final String body; + + public InvalidResponseBodyException(long cycleValue, String ok_body, String body) { + this.cycleValue = cycleValue; + this.ok_body = ok_body; + this.body = body; + } +} diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidStatusCodeException.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidStatusCodeException.java new file mode 100644 index 000000000..471250b50 --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/InvalidStatusCodeException.java @@ -0,0 +1,13 @@ +package io.nosqlbench.activitytype.http; + +public class InvalidStatusCodeException extends RuntimeException { + private final long cycleValue; + private final String ok_status; + private final int statusCode; + + public InvalidStatusCodeException(long cycleValue, String ok_status, int statusCode) { + this.cycleValue = cycleValue; + this.ok_status = ok_status; + this.statusCode = statusCode; + } +} From cffe14ba09b0f64f7b7af62e62d119c712053ed0 Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:39:56 -0500 Subject: [PATCH 3/7] rename master to main --- .../io/nosqlbench/engine/core/script/NBCliIntegrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nb/src/test/java/io/nosqlbench/engine/core/script/NBCliIntegrationTests.java b/nb/src/test/java/io/nosqlbench/engine/core/script/NBCliIntegrationTests.java index 07483fcd9..47743eb8d 100644 --- a/nb/src/test/java/io/nosqlbench/engine/core/script/NBCliIntegrationTests.java +++ b/nb/src/test/java/io/nosqlbench/engine/core/script/NBCliIntegrationTests.java @@ -48,7 +48,7 @@ public class NBCliIntegrationTests { assertThat(result.exitStatus).isEqualTo(0); } // disabled till after release - // This is not disabled on testbranch, only on master + // This is not disabled on testbranch, only on main // @Test // public void dockerMetrics() { // ProcessInvoker invoker = new ProcessInvoker(); From b0a629ce87058d1651e1eafa715d136a19a7fb62 Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:40:05 -0500 Subject: [PATCH 4/7] put console format in one place --- .../activitytype/http/HttpConsoleFormats.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpConsoleFormats.java diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpConsoleFormats.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpConsoleFormats.java new file mode 100644 index 000000000..7b6452bd0 --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpConsoleFormats.java @@ -0,0 +1,89 @@ +package io.nosqlbench.activitytype.http; + +import java.io.PrintStream; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class HttpConsoleFormats { + + private final Set includes; + private final long modulo; + + public HttpConsoleFormats(Set includes) { + long mod = 1L; + Set incl = new HashSet<>(); + + for (String include : includes) { + if (include.matches("[0-9]+")) { + mod = Long.parseLong(include); + } else if (include.toLowerCase().equals("all")) { + incl.add("headers"); + incl.add("stats"); + incl.add("content"); + } { + incl.add(include); + } + } + this.includes = incl; + this.modulo = mod; + } + + public void summarizeRequest(HttpRequest request, PrintStream out, long cycle) { + if ((cycle%modulo)!=0) { + return; + } + out.println("---- REQUEST cycle=" + cycle); + out.println(" --- " + request.method() + " " + request.uri() + " " + request.version().orElse(HttpClient.Version.HTTP_2)); + + if (includes.contains("headers")) { + out.println(" -- headers:"); + summariseHeaders(request.headers(),out); + } + + out.println(" -- body length:" + request.bodyPublisher().get().contentLength()); + } + + public void summarizeResponse(HttpResponse response, PrintStream out, long cycle, long nanos) { + if ((cycle%modulo)!=0) { + return; + } + out.println("---- RESPONSE for cycle=" + cycle + " status=" + response.statusCode() + " took=" + (nanos/1_000_000) + "ms"); + + if (includes.contains("stats")) { + int redirects=0; + Optional> walkResponses = response.previousResponse(); + while (walkResponses.isPresent()) { + walkResponses=walkResponses.get().previousResponse(); + redirects++; + } + System.out.println(" redirects = " + redirects); + } + + summariseHeaders(response.headers(),out); + + if (this.includes.contains("content")) { + System.out.println(" -- body:"); + System.out.println(response.body()); + } + } + + private static void summariseHeaders(HttpHeaders headers, PrintStream out) { + out.println(" --- headers:"); + headers.map().forEach((k,v) -> { + out.print(" --- " + k + ":"); + if (v.size()>1) { + out.println(); + v.forEach( h -> { + out.println(" - " + h); + }); + } else { + out.println(" " + v.get(0)); + } + }); + } +} From 4e06e6ffade0a1eb53b814c2e2c102e566024b44 Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:40:53 -0500 Subject: [PATCH 5/7] clarify op data meaning --- .../io/nosqlbench/activitytype/stdout/AsyncStdoutAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-stdout/src/main/java/io/nosqlbench/activitytype/stdout/AsyncStdoutAction.java b/driver-stdout/src/main/java/io/nosqlbench/activitytype/stdout/AsyncStdoutAction.java index 67f2f91b6..89c9363c2 100644 --- a/driver-stdout/src/main/java/io/nosqlbench/activitytype/stdout/AsyncStdoutAction.java +++ b/driver-stdout/src/main/java/io/nosqlbench/activitytype/stdout/AsyncStdoutAction.java @@ -46,7 +46,7 @@ public class AsyncStdoutAction extends BaseAsyncAction started = opc.start(); int result=0; try (Timer.Context executeTime = activity.executeTimer.time()) { - activity.write(opc.getData().statement); + activity.write(opc.getOpData().statement); } catch (Exception e) { result=1; started.fail(result); From 3c6192b9a6a5d1e71743e79066a271a82d117fac Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:41:07 -0500 Subject: [PATCH 6/7] update http docs --- driver-http/src/main/resources/http.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/driver-http/src/main/resources/http.md b/driver-http/src/main/resources/http.md index 6a46a746a..756283fd8 100644 --- a/driver-http/src/main/resources/http.md +++ b/driver-http/src/main/resources/http.md @@ -158,3 +158,23 @@ the defaults for the current HttpClient library that is bundled within the JVM. Presently, this driver only does basic request-response style requests. Thus, adding headers which take TCP socket control away from the HttpClient will likely yield inconsistent (or undefined) results. Support may be added for long-lived connections in a future release. + +## HTTP Activity Parameters + +- **client_scope** - default: activity - One of activity, or thread. This controls how many + clients instances you use with an HTTP activity. By default, all threads will use the same + client instance. +- **follow_redirects** - default: normal - One of never, always, or normal. Normal redirects + are those which do not redirect from HTTPS to HTTP. +- **diagnostics** - default: none - + This setting is a selector for what level of verbosity you will get on console. If you set + this to true, you'll get every request and response logged to console. This is only for + verifying that a test is configured and to spot check services before running higher scale + tests. + If you want finer control over how much information diagnostics provides, you can specify + a comma separated list of the below. + - all - Includes all of the below categories + - stats - Counts of redirects, headers, body length, etc + - headers - include header details + - content - include + - a number, like 3000 - causes the diagnostics to be reported only on this cycle modulo From ba5ec41c5b3e8a065cc120c5eeccaca05720722f Mon Sep 17 00:00:00 2001 From: Jonathan Shook Date: Mon, 13 Jul 2020 09:42:03 -0500 Subject: [PATCH 7/7] partial work. roll this back possibly. --- devdocs/devguide/driver_standards.md | 51 ++++++-- devdocs/sketches/opdata.puml | 8 ++ devdocs/sketches/transforms.dot | 44 +++++++ .../activitytype/cmds/HttpAsyncOp.java | 26 +++++ .../nosqlbench/activitytype/cmds/HttpOp.java | 15 +++ ...ReadyHttpRequest.java => ReadyHttpOp.java} | 21 ++-- .../activitytype/http/ClientScope.java | 6 + .../activitytype/http/HttpAction.java | 78 ++++++++++--- .../activitytype/http/HttpActivity.java | 109 ++++++++---------- .../http/async/HttpAsyncAction.java | 49 ++++++++ ...pRequestTest.java => ReadyHttpOpTest.java} | 22 ++-- .../rawyaml/StatementsOwner.java | 3 +- 12 files changed, 324 insertions(+), 108 deletions(-) create mode 100644 devdocs/sketches/opdata.puml create mode 100644 devdocs/sketches/transforms.dot create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpAsyncOp.java create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpOp.java rename driver-http/src/main/java/io/nosqlbench/activitytype/cmds/{ReadyHttpRequest.java => ReadyHttpOp.java} (84%) create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/http/ClientScope.java create mode 100644 driver-http/src/main/java/io/nosqlbench/activitytype/http/async/HttpAsyncAction.java rename driver-http/src/test/java/io/nosqlbench/activitytype/cmds/{ReadyHttpRequestTest.java => ReadyHttpOpTest.java} (84%) diff --git a/devdocs/devguide/driver_standards.md b/devdocs/devguide/driver_standards.md index 4f3996720..8492ba170 100644 --- a/devdocs/devguide/driver_standards.md +++ b/devdocs/devguide/driver_standards.md @@ -2,10 +2,9 @@ This is a work in progress... - This is the document to read if you want to know if your NoSQLBench driver is complete. Within this document, the phrase `conformant` will be taken to mean that a driver or feature -is implemented according to the design intent and standards of the NoSQLBench driver API. +is implemented according to the design intent and standards of the NoSQLBench driver API standards. While it may be possible to partially implement a driver for basic use, following the guidelines in this document will ensure that contributed drivers for NoSQLBench work in a familiar and @@ -14,19 +13,35 @@ reliable way for users from one driver to another. Over time, the standards in this guide will be programmatically enforced by the NoSQLBench driver API. +## Op Templates + +The core building block of a NoSQLBench driver is the op template. This is the form of a +statement or operation that users add to a yaml or workload editor to represent a single operation. + +For example, in the CQL driver, this is called a "statement template", but going forward, they will +all be called Op Templates and internal API names will reflect that. + +It is the driver's responsibility to create a quick-draw version of an operation. + +## Op Sequencing + +A conformant driver should use the standard method of creating an operational sequence. This means +that a driver simply has to provide a function to map an OpTemplate to a more ready to use form that +is specific to the low level driver in question. + ## Metrics At a minimum, a conformant driver should provide the following metrics: -- bind (timer) - A timer around the code that prepares an executable form of a statement -- execute (timer) - A timer around the code that submits work to a native driver -- result (timer) - A timer around the code that awaits and processes results from a native driver. This +- **bind** (timer) - A timer around the code that prepares an executable form of a statement +- **execute** (timer) - A timer around the code that submits work to a native driver +- **result** (timer) - A timer around the code that awaits and processes results from a native driver. This timer should be included around all operations, successful ones and errors too. -- result-success (timer) - A timer around the code that awaits and processes results from a native driver. +- **result-success** (timer) - A timer around the code that awaits and processes results from a native driver. This timer should only be updated for successful operations. -- errorcounts-... (counters)- Each uniquely named exception or error type that is known to the native driver +- **errorcounts-...** (counters)- Each uniquely named exception or error type that is known to the native driver should be counted. -- tries (histogram) - The number of tries for a given operation. This number is incremented before each +- **tries** (histogram) - The number of tries for a given operation. This number is incremented before each execution of a native operation, and when the result timer is updated, this value should be updated as well (for all operations). This includes errored operations. @@ -38,8 +53,17 @@ error rate exceeds some threshold", and so on. Configurable error handling is essential. +TBD + +## Result Validation + +## Diagnostic Mode + + ## Naming Conventions +TBD + ## Documentation Each activity is required to have a set of markdown documentation in its resource directory. @@ -48,11 +72,16 @@ The name of the driver should also be used as the name of the documentation for Additional documentation can be added beyond this file. However, all documentation for a given driver must start with the drivers name and a hyphen. +These sources of documentation can be wired into the main NoSQLBench documentation system with a set +of content descriptors. + ## Named Scenarios -Confirmant driver implementations should come with one or more examples of a workload under the activities directory path. - -Complete driver implementations should also come with a set of eaxmples under the examples directory path. +Conformant driver implementations should come with one or more examples of a workload under the +activities directory path. ## Examples +Complete driver implementations should also come with a set of examples under the examples +directory path. + diff --git a/devdocs/sketches/opdata.puml b/devdocs/sketches/opdata.puml new file mode 100644 index 000000000..f1092f6bd --- /dev/null +++ b/devdocs/sketches/opdata.puml @@ -0,0 +1,8 @@ +@startuml +actor a as activity +actor nd as "NB Driver" +actor pd as "Native Driver" +actor r as "ReadyOp" +actor op as "op" + +@enduml \ No newline at end of file diff --git a/devdocs/sketches/transforms.dot b/devdocs/sketches/transforms.dot new file mode 100644 index 000000000..423edc445 --- /dev/null +++ b/devdocs/sketches/transforms.dot @@ -0,0 +1,44 @@ + +digraph { +// rankdir=LR; + node [shape = none] + + + cycles[label="read input"] + c_err[shape="none",label="[ERR,null]"] + c_ok[shape="none",label="[OK,cycle] (OpCycle)"] + + cycles -> c_err + cycles -> c_ok + + + bind_template[label="bind template"] + template_ok[label="[OK,template] (OpTemplate)"] + template_err[label="[ERR,template]"] + c_ok -> bind_template + bind_template -> template_err + bind_template -> template_ok + + exec_cmd[label="execute command"] + command_err[label="[ERR,template]"] + command_ok[label="[OK,result] (OpResult)"] + template_ok -> exec_cmd + exec_cmd -> command_err + exec_cmd -> command_ok + + verify_result[label="verify result"] + command_ok -> verify_result + result_invalid[label="[ERR,result]"] + result_ok[label="[OK,result]"] + verify_result -> result_invalid + verify_result -> result_ok + + + + +// Errornone +// ⁅⁆⟦⟧ ⟬⟭ ⟮⟯ ⟨⟩ ⁅⁆ +// { +// } + +} \ No newline at end of file diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpAsyncOp.java b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpAsyncOp.java new file mode 100644 index 000000000..dd644e3ca --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpAsyncOp.java @@ -0,0 +1,26 @@ +package io.nosqlbench.activitytype.cmds; + +import io.nosqlbench.activitytype.http.async.HttpAsyncAction; + +import java.net.http.HttpRequest; + +public class HttpAsyncOp { + public final HttpAsyncAction action; + public final ReadyHttpOp op; + public final long cycle; + + public HttpAsyncOp(HttpAsyncAction action, ReadyHttpOp op, long cycle) { + this.action = action; + this.op = op; + this.cycle = cycle; + } + + public ReadyHttpOp getOp() { + return op; + } + + public HttpRequest getRequest() { + HttpOp httpOp = op.apply(cycle); + } + +} diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpOp.java b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpOp.java new file mode 100644 index 000000000..50893bda1 --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/HttpOp.java @@ -0,0 +1,15 @@ +package io.nosqlbench.activitytype.cmds; + +import java.net.http.HttpRequest; + +public class HttpOp { + public final String ok_status; + public final String ok_body; + public final HttpRequest request; + + public HttpOp(HttpRequest request, String ok_status, String ok_body) { + this.request = request; + this.ok_status = ok_status; + this.ok_body = ok_body; + } +} diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequest.java b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpOp.java similarity index 84% rename from driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequest.java rename to driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpOp.java index 4d4cb1418..af35b7cd1 100644 --- a/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequest.java +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/cmds/ReadyHttpOp.java @@ -12,14 +12,14 @@ import java.util.Map; import java.util.Set; import java.util.function.LongFunction; -public class ReadyHttpRequest implements LongFunction { +public class ReadyHttpOp implements LongFunction { private final CommandTemplate propertyTemplate; // only populated if there is no value which is an actual bindings template - private final HttpRequest cachedRequest; + private final HttpOp cachedOp; - public ReadyHttpRequest(OpTemplate stmtDef) { + public ReadyHttpOp(OpTemplate stmtDef) { propertyTemplate = new CommandTemplate(stmtDef, List.of( HttpFormatParser::parseUrl, @@ -28,18 +28,18 @@ public class ReadyHttpRequest implements LongFunction { ); if (propertyTemplate.isStatic()) { - cachedRequest = apply(0); + cachedOp = apply(0); } else { - cachedRequest = null; + cachedOp = null; } } @Override - public HttpRequest apply(long value) { + public HttpOp apply(long value) { // If the request is invariant, simply return it, since it is thread-safe - if (this.cachedRequest != null) { - return this.cachedRequest; + if (this.cachedOp != null) { + return this.cachedOp; } Map cmd = propertyTemplate.getCommand(value); @@ -76,12 +76,15 @@ public class ReadyHttpRequest implements LongFunction { } } + String ok_status = cmd.remove("ok-status"); + String ok_body = cmd.remove("ok-body"); + if (cmd.size()>0) { throw new BasicError("Some provided request fields were not used: " + cmd.toString()); } HttpRequest request = builder.build(); - return request; + return new HttpOp(request, ok_status, ok_body); } } diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/ClientScope.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/ClientScope.java new file mode 100644 index 000000000..0f1a07f9c --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/ClientScope.java @@ -0,0 +1,6 @@ +package io.nosqlbench.activitytype.http; + +public enum ClientScope { + thread, + activity +} diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpAction.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpAction.java index 7c16b45c0..0d5621de0 100644 --- a/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpAction.java +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpAction.java @@ -1,18 +1,17 @@ package io.nosqlbench.activitytype.http; import com.codahale.metrics.Timer; -import io.nosqlbench.activitytype.cmds.ReadyHttpRequest; +import io.nosqlbench.activitytype.cmds.HttpOp; +import io.nosqlbench.activitytype.cmds.ReadyHttpOp; import io.nosqlbench.engine.api.activityapi.core.SyncAction; import io.nosqlbench.engine.api.activityapi.planning.OpSequence; import io.nosqlbench.engine.api.activityimpl.ActivityDef; -import io.nosqlbench.engine.api.templating.CommandTemplate; import io.nosqlbench.nb.api.errors.BasicError; import io.nosqlbench.virtdata.core.templates.StringBindings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; -import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -20,8 +19,6 @@ import java.nio.file.Path; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class HttpAction implements SyncAction { @@ -33,8 +30,9 @@ public class HttpAction implements SyncAction { private int maxTries = 1; private boolean showstmts; - private OpSequence sequencer; + private OpSequence sequencer; private HttpClient client; + private HttpResponse.BodyHandler bodyreader = HttpResponse.BodyHandlers.ofString(); private long timeoutMillis=30000L; @@ -46,9 +44,19 @@ public class HttpAction implements SyncAction { @Override public void init() { - this.sequencer = httpActivity.getOpSequence(); - this.client = HttpClient.newHttpClient(); + this.sequencer = httpActivity.getSequencer(); + this.client = initClient(httpActivity.getClientScope()); + } + private HttpClient initClient(ClientScope clientScope) { + switch (clientScope) { + case thread: + return httpActivity.newClient(); + case activity: + return httpActivity.getClient(Thread.currentThread()); + default: + throw new RuntimeException("unrecognized client scope: " + clientScope); + } } @Override @@ -62,7 +70,7 @@ public class HttpAction implements SyncAction { // op construction // The request to be used must be constructed from the template each time. - HttpRequest request=null; + HttpOp httpOp=null; // A specifier for what makes a response ok. If this is provided, then it is // either a list of valid http status codes, or if non-numeric, a regex for the body @@ -71,10 +79,19 @@ public class HttpAction implements SyncAction { String ok; try (Timer.Context bindTime = httpActivity.bindTimer.time()) { - ReadyHttpRequest readyHttpRequest = httpActivity.getOpSequence().get(cycleValue); - request =readyHttpRequest.apply(cycleValue); + ReadyHttpOp readHTTPOperation = httpActivity.getSequencer().get(cycleValue); + httpOp =readHTTPOperation.apply(cycleValue); } catch (Exception e) { throw new RuntimeException("while binding request in cycle " + cycleValue + ": " + e.getMessage(),e); + } finally { + if (httpActivity.isDiagnosticMode()) { + System.out.println("==== cycle " + cycleValue + " DIAGNOSTICS ===="); + if (httpOp!=null) { + httpActivity.console.summarizeRequest(httpOp.request,System.out,cycleValue); + } else { + System.out.println("---- REQUEST was null"); + } + } } int tries = 0; @@ -83,18 +100,49 @@ public class HttpAction implements SyncAction { CompletableFuture> responseFuture; try (Timer.Context executeTime = httpActivity.executeTimer.time()) { - responseFuture = client.sendAsync(request, this.bodyreader); + responseFuture = client.sendAsync(httpOp.request, this.bodyreader); } catch (Exception e) { throw new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e); } - HttpResponse response; - try (Timer.Context resultTime = httpActivity.resultTimer.time()) { + HttpResponse response=null; + long startat = System.nanoTime(); + Exception error = null; + try { response = responseFuture.get(httpActivity.getTimeoutMs(), TimeUnit.MILLISECONDS); + if (httpOp.ok_status!=null) { + if (!String.valueOf(response.statusCode()).matches(httpOp.ok_status)) { + throw new InvalidStatusCodeException(cycleValue,httpOp.ok_status,response.statusCode()); + } + } + if (httpOp.ok_body!=null) { + if (!response.body().matches(httpOp.ok_body)) { + throw new InvalidResponseBodyException(cycleValue, httpOp.ok_body, response.body()); + } + } } catch (Exception e) { - throw new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e); + error = new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e); + } finally { + long nanos = System.nanoTime() - startat; + httpActivity.resultTimer.update(nanos, TimeUnit.NANOSECONDS); + if (error==null) { + httpActivity.resultSuccessTimer.update(nanos, TimeUnit.NANOSECONDS); + } + if (httpActivity.isDiagnosticMode()) { + if (response!=null) { + httpActivity.console.summarizeResponse(response,System.out,cycleValue,nanos); + } else { + System.out.println("---- RESPONSE was null"); + } + System.out.println(); + } + + if (error!=null) { + // count and log exception types + } } + // if (ok == null) { // if (response.statusCode() != 200) { // throw new ResponseError("Result had status code " + diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpActivity.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpActivity.java index f998d6dc4..80b30383e 100644 --- a/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpActivity.java +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/HttpActivity.java @@ -1,48 +1,33 @@ package io.nosqlbench.activitytype.http; -import io.nosqlbench.activitytype.cmds.ReadyHttpRequest; -import io.nosqlbench.engine.api.activityconfig.ParsedStmt; -import io.nosqlbench.engine.api.activityconfig.StatementsLoader; -import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef; -import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList; +import io.nosqlbench.activitytype.cmds.ReadyHttpOp; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Timer; import io.nosqlbench.engine.api.activityapi.core.Activity; import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver; import io.nosqlbench.engine.api.activityapi.planning.OpSequence; -import io.nosqlbench.engine.api.activityapi.planning.SequencePlanner; -import io.nosqlbench.engine.api.activityapi.planning.SequencerType; import io.nosqlbench.engine.api.activityimpl.ActivityDef; import io.nosqlbench.engine.api.activityimpl.SimpleActivity; import io.nosqlbench.engine.api.metrics.ActivityMetrics; -import io.nosqlbench.engine.api.templating.CommandTemplate; -import io.nosqlbench.engine.api.templating.StrInterpolator; -import io.nosqlbench.nb.api.errors.BasicError; -import io.nosqlbench.virtdata.core.bindings.BindingsTemplate; -import io.nosqlbench.virtdata.core.templates.StringBindings; -import io.nosqlbench.virtdata.core.templates.StringBindingsTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.net.http.HttpClient; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; public class HttpActivity extends SimpleActivity implements Activity, ActivityDefObserver { private final static Logger logger = LoggerFactory.getLogger(HttpActivity.class); private final ActivityDef activityDef; + public HttpConsoleFormats console; - public StmtsDocList getStmtsDocList() { - return stmtsDocList; - } + // Used when sclientScope == ClientScope.activity + private HttpClient activityClient; + private ClientScope clientScope = ClientScope.activity; - private StmtsDocList stmtsDocList; - - - private int stride; - private Integer maxTries; - private long timeout_ms = 30_000L; - private Boolean showstmnts; public Timer bindTimer; public Timer executeTimer; public Histogram triesHisto; @@ -51,10 +36,7 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe public Histogram skippedTokens; public Timer resultSuccessTimer; - private String[] hosts; - private int port; - - private OpSequence opSequence; + private OpSequence sequencer; public HttpActivity(ActivityDef activityDef) { super(activityDef); @@ -62,59 +44,66 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe } - @Override public void initActivity() { super.initActivity(); -// stride = activityDef.getParams().getOptionalInteger("stride").orElse(1); - maxTries = activityDef.getParams().getOptionalInteger("maxTries").orElse(1); - timeout_ms = activityDef.getParams().getOptionalLong("timeout_ms").orElse(30_000L); -// showstmnts = activityDef.getParams().getOptionalBoolean("showstmnts").orElse(false); - -// hosts = activityDef.getParams().getOptionalString("host").orElse("localhost").split(","); -// port = activityDef.getParams().getOptionalInteger("port").orElse(80); - - this.opSequence = createOpSequence(ReadyHttpRequest::new); - setDefaultsFromOpSequence(opSequence); - bindTimer = ActivityMetrics.timer(activityDef, "bind"); executeTimer = ActivityMetrics.timer(activityDef, "execute"); resultTimer = ActivityMetrics.timer(activityDef, "result"); triesHisto = ActivityMetrics.histogram(activityDef, "tries"); rowCounter = ActivityMetrics.meter(activityDef, "rows"); skippedTokens = ActivityMetrics.histogram(activityDef, "skipped-tokens"); - resultSuccessTimer = ActivityMetrics.timer(activityDef,"result-success"); + resultSuccessTimer = ActivityMetrics.timer(activityDef, "result-success"); + String[] diag = getParams().getOptionalString("diag").orElse("").split(","); + Set diags = new HashSet(Arrays.asList(diag)); + + this.console = new HttpConsoleFormats(diags); + + getParams().getOptionalString("client_scope").map(ClientScope::valueOf).ifPresent(this::setClientScope); + + this.sequencer = createOpSequence(ReadyHttpOp::new); + setDefaultsFromOpSequence(sequencer); onActivityDefUpdate(activityDef); } - @Override - public synchronized void onActivityDefUpdate(ActivityDef activityDef) { - super.onActivityDefUpdate(activityDef); + private void setClientScope(ClientScope clientScope) { + this.clientScope = clientScope; } - public Integer getMaxTries() { - return maxTries; + public ClientScope getClientScope() { + return clientScope; } - public Boolean getShowstmts() { - return showstmnts; + public synchronized Function getClient() { + switch (getClientScope()) { + case thread: + return t -> newClient(); + case activity: + if (this.activityClient == null) { + this.activityClient = newClient(); + } + return t -> this.activityClient; + default: throw new RuntimeException("unable to recoginize client scope: " + getClientScope()); + } } - public String[] getHosts() { - return hosts; + public HttpClient newClient() { + HttpClient.Builder builder = HttpClient.newBuilder(); + getParams().getOptionalString("follow_redirects") + .map(String::toUpperCase) + .map(HttpClient.Redirect::valueOf) + .map(r -> { + logger.debug("follow_redirects=>" + r); + return r; + }) + .ifPresent(builder::followRedirects); + + return builder.build(); } - public int getPort() { - return port; - } - - public OpSequence getOpSequence() { - return opSequence; - } - - public long getTimeoutMs() { - return timeout_ms; + public OpSequence getSequencer() { + return sequencer; } } diff --git a/driver-http/src/main/java/io/nosqlbench/activitytype/http/async/HttpAsyncAction.java b/driver-http/src/main/java/io/nosqlbench/activitytype/http/async/HttpAsyncAction.java new file mode 100644 index 000000000..656b0ae07 --- /dev/null +++ b/driver-http/src/main/java/io/nosqlbench/activitytype/http/async/HttpAsyncAction.java @@ -0,0 +1,49 @@ +package io.nosqlbench.activitytype.http.async; + +import io.nosqlbench.activitytype.cmds.HttpAsyncOp; +import io.nosqlbench.activitytype.cmds.ReadyHttpOp; +import io.nosqlbench.activitytype.http.HttpActivity; +import io.nosqlbench.engine.api.activityapi.core.AsyncAction; +import io.nosqlbench.engine.api.activityapi.core.ops.fluent.opfacets.TrackedOp; +import io.nosqlbench.engine.api.activityapi.planning.OpSequence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.http.HttpClient; +import java.util.function.LongFunction; + +public class HttpAsyncAction implements AsyncAction { + + private final static Logger logger = LoggerFactory.getLogger(HttpAsyncAction.class); + + private final HttpActivity httpActivity; + private final int slot; + + private OpSequence sequencer; + private HttpClient client; + + public HttpAsyncAction(HttpActivity httpActivity, int slot) { + this.httpActivity = httpActivity; + this.slot = slot; + } + + public void init() { + this.sequencer = httpActivity.getSequencer(); + this.client = httpActivity.getClient().apply(Thread.currentThread()); + } + + @Override + public LongFunction getOpInitFunction() { + return l -> { + ReadyHttpOp readyHttpOp = sequencer.get(l); + return new HttpAsyncOp(this,readyHttpOp,l); + }; + } + + @Override + public boolean enqueue(TrackedOp opc) { + HttpAsyncOp opData = opc.getOpData(); + opData.op. + return false; + } +} diff --git a/driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequestTest.java b/driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpOpTest.java similarity index 84% rename from driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequestTest.java rename to driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpOpTest.java index cbea4ee7c..1c85feb1c 100644 --- a/driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpRequestTest.java +++ b/driver-http/src/test/java/io/nosqlbench/activitytype/cmds/ReadyHttpOpTest.java @@ -10,7 +10,7 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -public class ReadyHttpRequestTest { +public class ReadyHttpOpTest { @Test public void testOnelineSpec() { @@ -19,8 +19,8 @@ public class ReadyHttpRequestTest { " - s1: method=get uri=http://localhost/\n"); OpTemplate stmtDef = docs.getStmts().get(0); - ReadyHttpRequest readyReq = new ReadyHttpRequest(stmtDef); - HttpRequest staticReq = readyReq.apply(3); + ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef); + HttpOp staticReq = readyReq.apply(3); } @Test @@ -30,8 +30,8 @@ public class ReadyHttpRequestTest { " - s1: get http://localhost/"); OpTemplate stmtDef = docs.getStmts().get(0); - ReadyHttpRequest readyReq = new ReadyHttpRequest(stmtDef); - HttpRequest staticReq = readyReq.apply(3); + ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef); + HttpOp staticReq = readyReq.apply(3); } @Test @@ -41,8 +41,8 @@ public class ReadyHttpRequestTest { " - s1: get http://localhost/ HTTP/1.1"); OpTemplate stmtDef = docs.getStmts().get(0); - ReadyHttpRequest readyReq = new ReadyHttpRequest(stmtDef); - HttpRequest staticReq = readyReq.apply(3); + ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef); + HttpOp staticReq = readyReq.apply(3); } @Test @@ -55,8 +55,8 @@ public class ReadyHttpRequestTest { ""); OpTemplate stmtDef = docs.getStmts().get(0); - ReadyHttpRequest readyReq = new ReadyHttpRequest(stmtDef); - HttpRequest staticReq = readyReq.apply(3); + ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef); + HttpOp staticReq = readyReq.apply(3); } @Test @@ -69,8 +69,8 @@ public class ReadyHttpRequestTest { " body1"); OpTemplate stmtDef = docs.getStmts().get(0); - ReadyHttpRequest readyReq = new ReadyHttpRequest(stmtDef); - HttpRequest staticReq = readyReq.apply(3); + ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef); + HttpOp staticReq = readyReq.apply(3); } @Test diff --git a/engine-api/src/main/java/io/nosqlbench/engine/api/activityconfig/rawyaml/StatementsOwner.java b/engine-api/src/main/java/io/nosqlbench/engine/api/activityconfig/rawyaml/StatementsOwner.java index 74ba3588e..6edacf61c 100644 --- a/engine-api/src/main/java/io/nosqlbench/engine/api/activityconfig/rawyaml/StatementsOwner.java +++ b/engine-api/src/main/java/io/nosqlbench/engine/api/activityconfig/rawyaml/StatementsOwner.java @@ -92,8 +92,7 @@ public class StatementsOwner extends RawStmtFields { } setStatementsFieldByObjectType(itemizedMaps); } else if (object instanceof String) { - List defs = new ArrayList<>(); - defs.add(new RawStmtDef(null,(String)object)); + setStatementsFieldByObjectType(Map.of("stmt1",(String)object)); } else { throw new RuntimeException("Unknown object type: " + object.getClass()); }