http release fixes

This commit is contained in:
Jonathan Shook 2020-10-23 02:35:58 -05:00
parent 3fa89ebc14
commit 71195d0a0b
10 changed files with 406 additions and 111 deletions

View File

@ -11,7 +11,6 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.LongFunction;
public class ReadyHttpOp implements LongFunction<HttpOp> {
@ -69,15 +68,6 @@ public class ReadyHttpOp implements LongFunction<HttpOp> {
builder.uri(uri);
}
Set<String> headers = cmd.keySet();
for (String header : headers) {
if (header.charAt(0) >= 'A' && header.charAt(0) <= 'Z') {
builder.header(header, cmd.remove(header));
} else {
throw new BasicError("HTTP request parameter '" + header + "' was not recognized as a basic request parameter, and it is not capitalized to indicate that it is a header.");
}
}
String ok_status = cmd.remove("ok-status");
String ok_body = cmd.remove("ok-body");
@ -86,10 +76,21 @@ public class ReadyHttpOp implements LongFunction<HttpOp> {
builder.timeout(Duration.of(Long.parseLong(timeoutStr), ChronoUnit.MILLIS));
}
if (cmd.size()>0) {
throw new BasicError("Some provided request fields were not used: " + cmd.toString());
// At this point, the only things left in the list must be headers,
// but we check them for upper-case conventions as a sanity check for the user
for (String headerName : cmd.keySet()) {
if (headerName.charAt(0) >= 'A' && headerName.charAt(0) <= 'Z') {
String headerValue = cmd.get(headerName);
builder = builder.header(headerName, headerValue);
} else {
throw new BasicError("HTTP request parameter '" + headerName + "' was not recognized as a basic request parameter, and it is not capitalized to indicate that it is a header.");
}
}
// cmd.clear();
// if (cmd.size()>0) {
// throw new BasicError("Some provided request fields were not used: " + cmd.toString());
// }
//
HttpRequest request = builder.build();
return new HttpOp(request, ok_status, ok_body);
}

View File

@ -75,16 +75,16 @@ public class HttpAction implements SyncAction {
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);
httpActivity.console.summarizeRequest("ERRORED REQUEST", e, httpOp.request, System.out, cycleValue,
System.nanoTime());
} else {
System.out.println("---- REQUEST was null");
}
}
throw new RuntimeException("while binding request in cycle " + cycleValue + ": " + e.getMessage(), e);
} finally {
}
int tries = 0;
@ -123,7 +123,7 @@ public class HttpAction implements SyncAction {
}
if (httpActivity.isDiagnosticMode()) {
if (response!=null) {
httpActivity.console.summarizeResponse(response,System.out,cycleValue,nanos);
httpActivity.console.summarizeResponseChain(null, response, System.out, cycleValue, nanos);
} else {
System.out.println("---- RESPONSE was null");
}

View File

@ -14,9 +14,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 {
@ -45,7 +42,6 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
this.activityDef = activityDef;
}
@Override
public void initActivity() {
super.initActivity();
@ -65,10 +61,13 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
@Override
public synchronized void onActivityDefUpdate(ActivityDef activityDef) {
super.onActivityDefUpdate(activityDef);
String[] diag = getParams().getOptionalString("diag").orElse("").split(",");
Set<String> diags = new HashSet<String>(Arrays.asList(diag));
this.console = new HttpConsoleFormats(diags);
this.console = getParams().getOptionalString("diag")
.map(s -> HttpConsoleFormats.apply(s, this.console))
.orElseGet(() -> HttpConsoleFormats.apply(null, null));
this.diagnosticsEnabled = console.isDiagnosticMode();
this.timeout = getParams().getOptionalLong("timeout").orElse(Long.MAX_VALUE);
getParams().getOptionalString("client_scope")
@ -104,14 +103,14 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
public HttpClient newClient() {
HttpClient.Builder builder = HttpClient.newBuilder();
getParams().getOptionalString("follow_redirects")
HttpClient.Redirect follow_redirects = getParams().getOptionalString("follow_redirects")
.map(String::toUpperCase)
.map(HttpClient.Redirect::valueOf)
.map(r -> {
logger.debug("follow_redirects=>" + r);
return r;
})
.ifPresent(builder::followRedirects);
}).orElse(HttpClient.Redirect.NORMAL);
builder = builder.followRedirects(follow_redirects);
return builder.build();
}

View File

@ -5,81 +5,252 @@ 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;
import java.util.*;
public class HttpConsoleFormats {
private final Set<String> includes;
private final long modulo;
private static final String CYCLE_CUE = "==== ";
private static final String REQUEST_CUE = " ==> ";
private static final String RESPONSE_CUE = " <== ";
private static final String MESSAGE_CUE = "---- ";
private static final String COMPONENT_CUE = " === ";
private static final String DETAIL_CUE = " - ";
private static final String ENTRY_CUE = " - ";
private static final String PAYLOAD_CUE = "data:";
private final long mask;
private final long modulo;
private final String enabled;
private String filter;
public String getFilter() {
return this.filter;
}
private final static long _STATS = 1L;
private final static long _REQUESTS = 1L << 1;
private final static long _RESPONSES = 1L << 2;
private final static long _HEADERS = 1L << 3;
private final static long _REDIRECTS = 1L << 4;
private final static long _DATA = 1L << 5;
private final static long _DATA10 = 1L << 6;
private final static long _DATA100 = 1L << 7;
private final static long _DATA1000 = 1L << 8;
enum Diag {
headers(_HEADERS),
stats(_STATS),
data(_DATA),
data10(_DATA10),
data100(_DATA100),
data1000(_DATA1000),
redirects(_REDIRECTS),
requests(_REQUESTS),
responses(_RESPONSES),
brief(_HEADERS | _STATS | _REQUESTS | _RESPONSES | _DATA10),
all(_HEADERS | _STATS | _REDIRECTS | _REQUESTS | _RESPONSES | _DATA);
private final long mask;
Diag(long mask) {
this.mask = mask;
}
public long addTo(long othermask) {
return othermask | this.mask;
}
public boolean includedIn(long component) {
return (mask & component) > 0L;
}
public static boolean anyIncluded(long mask, Diag... levels) {
for (Diag level : levels) {
if (level.includedIn(mask)) {
return true;
}
}
return false;
}
public static String enabledSummary(long includedMask) {
if (includedMask <= 0L) {
return "";
}
StringBuilder sb = new StringBuilder();
for (Diag d : values()) {
if ((d.mask & includedMask) == d.mask) {
sb.append(d).append(",");
}
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
}
public static HttpConsoleFormats apply(String spec, HttpConsoleFormats extant) {
if (extant == null || (extant.getFilter() != null && !extant.getFilter().equals(spec))) {
return new HttpConsoleFormats(spec);
} else {
return extant;
}
}
private HttpConsoleFormats(String spec) {
Set<String> filterSet = Set.of();
if (spec != null) {
filterSet = new HashSet<>(Arrays.asList(spec.split(",")));
}
public HttpConsoleFormats(Set<String> includes) {
long mod = 1L;
Set<String> incl = new HashSet<>();
long mask = 0L;
for (String include : includes) {
for (String include : filterSet) {
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);
} else {
Diag diag = null;
try {
diag = Diag.valueOf(include);
mask = diag.addTo(mask);
} catch (Exception e) {
throw new RuntimeException("Invalid http diagnostic filter '" + include + "', choose from " +
Arrays.toString(Diag.values()));
}
}
this.includes = incl;
}
this.mask = mask;
this.modulo = mod;
this.enabled = Diag.enabledSummary(mask);
}
public void summarizeRequest(HttpRequest request, PrintStream out, long cycle) {
public void summarizeResponseChain(Exception e, HttpResponse<String> lastResponse, PrintStream out, long cycle, long nanos) {
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:");
out.println(CYCLE_CUE + "DIAGNOSTICS (cycle " + cycle + ") (filters " + enabled + ")");
LinkedList<HttpResponse<String>> responses = new LinkedList<>();
HttpResponse<String> walking = lastResponse;
while (lastResponse != null) {
responses.add(lastResponse);
lastResponse = lastResponse.previousResponse().orElse(null);
}
Iterator<HttpResponse<String>> iter = responses.descendingIterator();
int index = 0;
while (iter.hasNext()) {
index++;
HttpResponse<String> resp = iter.next();
if (Diag.requests.includedIn(mask) && (Diag.redirects.includedIn(mask) || index == 1)) {
summarizeRequest("REQUEST [" + index + "]", null, resp.request(), out, cycle, nanos);
}
if (Diag.stats.includedIn(mask) && index == 1) {
out.println(DETAIL_CUE + "redirects = " + (responses.size() - 1));
}
if (Diag.responses.includedIn(mask) && (Diag.redirects.includedIn(mask) || index == responses.size())) {
summarizeResponse("RESPONSE[" + index + "]", null, resp, out, cycle, nanos);
}
}
}
public void summarizeRequest(String caption, Exception e, HttpRequest request, PrintStream out, long cycle, long nanos) {
if ((cycle % modulo) != 0) {
return;
}
out.println(REQUEST_CUE + (caption != null ? caption : " REQUEST"));
if (e != null) {
out.println(CYCLE_CUE + " EXCEPTION: " + e.getMessage());
}
out.println(COMPONENT_CUE + request.method() + " " + request.uri() + " " + request.version().orElse(HttpClient.Version.HTTP_2));
summariseHeaders(request.headers(), out);
out.println(DETAIL_CUE + "body length:" + request.bodyPublisher().get().contentLength());
}
out.println(" -- body length:" + request.bodyPublisher().get().contentLength());
}
public void summarizeResponse(HttpResponse<String> response, PrintStream out, long cycle, long nanos) {
public void summarizeResponse(String caption, Exception e, HttpResponse<String> 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<HttpResponse<String>> walkResponses = response.previousResponse();
while (walkResponses.isPresent()) {
walkResponses=walkResponses.get().previousResponse();
redirects++;
}
System.out.println(" redirects = " + redirects);
out.println(RESPONSE_CUE + (caption != null ? caption : " RESPONSE") +
" status=" + response.statusCode() + " took=" + (nanos / 1_000_000) + "ms");
if (e != null) {
out.println(MESSAGE_CUE + " EXCEPTION: " + e.getMessage());
}
summariseHeaders(response.headers(), out);
summarizeContent(response, out);
if (this.includes.contains("content")) {
System.out.println(" -- body:");
System.out.println(response.body());
}
private void summarizeContent(HttpResponse<String> response, PrintStream out) {
if (Diag.anyIncluded(mask, Diag.data, Diag.data10, Diag.data100, Diag.data1000)) {
String contentLenStr = response.headers().map().getOrDefault("content-length", List.of("0")).get(0);
Long contentLength = Long.parseLong(contentLenStr);
if (contentLength == 0L) {
return;
}
System.out.println(PAYLOAD_CUE);
List<String> contentTypeList = response.headers().map().getOrDefault("content-type", List.of("text/html"));
String printable = "<non-printable>";
if (contentTypeList.size() > 1) {
printable = "non-printable/multiple content types provided";
} else {
String contentType = contentTypeList.get(0).toLowerCase();
if (!contentType.contains("text") && !contentType.contains("json")) {
printable = "non-printable content type:" + contentTypeList.get(0);
} else {
printable = response.body();
if (printable == null) {
printable = "content-length was " + contentLength + ", but body was null";
}
if (Diag.data1000.includedIn(mask)) {
if (printable.length() > 1000) {
printable = printable.substring(0, 1000) + "\n--truncated at 1000 characters--\n";
}
} else if (Diag.data100.includedIn(mask)) {
if (printable.length() > 100) {
printable = printable.substring(0, 100) + "\ntruncated at 100 characters\n";
}
} else if (Diag.data10.includedIn(mask)) {
if (printable.length() > 10) {
printable = printable.substring(0, 10) + "\ntruncated at 10 characters\n";
}
}
}
}
System.out.println(printable);
}
}
private static void summariseHeaders(HttpHeaders headers, PrintStream out) {
out.println(" --- headers:");
private void summariseHeaders(HttpHeaders headers, PrintStream out) {
if (!Diag.headers.includedIn(mask)) {
return;
}
out.println(COMPONENT_CUE + "headers(" + headers.map().keySet().size() + ")");
headers.map().forEach((k, v) -> {
out.print(" --- " + k + ":");
out.print(DETAIL_CUE + k + ":");
if (v.size() > 1) {
out.println();
v.forEach(h -> {
out.println(" - " + h);
out.println(ENTRY_CUE + h);
});
} else {
out.println(" " + v.get(0));
@ -88,6 +259,6 @@ public class HttpConsoleFormats {
}
public boolean isDiagnosticMode() {
return this.includes.size()>0;
return this.mask > 0L;
}
}

View File

@ -35,7 +35,9 @@ statements:
- s2: https://www.amazon.com/s?k={query}
ratio: 2
bindings:
query: WeightedStrings('function generator;backup generator;static generator');
query: >
WeightedStrings('function generator;backup generator;static generator');
UrlEncode();
```
You can even make a detailed request with custom headers and result verification conditions:
@ -43,12 +45,13 @@ You can even make a detailed request with custom headers and result verification
```yaml
# Require that the result be status code 200-299 match regex "OK, account id is .*" in the body
statements:
- method: GET
uri: https://google.com/
version: HTTP/1.1
"Content-Type": "application/json"
ok-status: 2[0-9][0-9]
ok-body: ^(OK, account id is .*)$
- name: 'get-from-google'
method: GET
uri: "https://google.com/"
version: "HTTP/1.1"
Content-Type: "application/json"
ok-status: "2[0-9][0-9]"
ok-body: "^(OK, account id is .*)$"
```
For those familiar with what an HTTP request looks like on the wire, the format below may be
@ -163,19 +166,53 @@ results. Support may be added for long-lived connections in a future release.
- **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
- **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
- **diagnostics** - default: none - synonym: **diag**
example: `diag=brief,1000` - print diagnostics for every 1000th
cycle, including only brief details as explained below.
This setting is a selector for what level of verbosity you will get
on the 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
All of the data shown in diagnostics is post-hoc, directly from
the response provided by the internal HTTP client in the Java runtime.
If you want finer control over how much information diagnostics
provides, you can specify a comma separated list of the below.
- headers - show headers
- stats - show basic stats of each request
- data10 - show only the first 10 characters of each response body
- data100 - show only the first 100 characters of each response body
this setting supersedes `data10`
- data1000 - show only the first 1000 characters of each response body
this setting supersedes `data100`
- data - show all of each response body
this setting supersedes `data1000`
- redirects - show details for interstitial request which are made
when the client follows a redirect directive like a `location` header.
- requests - show details for requests
- responses - show details for responses
- brief - Show headers, stats, requests, responses, and 10 characters
- all - Show everything, including full payloads and redirects
- a modulo - any number, like 3000 - causes the diagnostics to be
reported only on this cycle modulo. If you set `diag=300,brief`
then you will get the brief diagnostic output for every 300th
response.
The requests, responses, and redirects setting work intersectionally.
For example, if you specify responses, and redirect, but not requests,
then you will only see the response portion of all calls made by the
client.
All of the diagnostic filters are incrementally added.
- **timeout** - default: forever -
Sets the timeout of each request in milliseconds.

View File

@ -1,8 +1,10 @@
# A list of named statements with variable fields and specific ratios:
statements:
- s1: http://google.com/search?query={query}
- s1: "http://google.com/search?query={query}"
ratio: 3
- s2: https://www.amazon.com/s?k={query}
- s2: "https://www.amazon.com/s?k={query}"
ratio: 2
bindings:
query: WeightedStrings('function generator;backup generator;static generator');
query: >
WeightedStrings('function generator;backup generator;static generator');
URLEncode();

View File

@ -1,8 +1,9 @@
# Require that the result be status code 200-299 match regex "OK, account id is .*" in the body
statements:
- method: GET
uri: https://google.com/
version: HTTP/1.1
"Content-Type": "application/json"
ok-status: 2[0-9][0-9]
ok-body: ^(OK, account id is .*)$
- name: 'get-from-google'
method: GET
uri: "https://google.com/"
version: "HTTP/1.1"
Content-Type: "application/json"
ok-status: "2[0-9][0-9]"
ok-body: "^(OK, account id is .*)$"

View File

@ -76,7 +76,9 @@ public class MultiMapLookup<V> implements Map<String, V> {
@Override
public V remove(Object key) {
throw immutable();
V result = get(key);
maps.stream().forEach(m -> m.remove(String.valueOf(key)));
return result;
}
@Override

View File

@ -0,0 +1,41 @@
package io.nosqlbench.virtdata.library.basics.shared.unary_string;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.Example;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
/**
* URLDecode string data
*/
@ThreadSafeMapper
@Categories({Category.conversion})
public class URLDecode implements Function<String, String> {
private final Charset charset;
/**
* URLDecode any incoming string using the specified charset.
*
* @param charset A valid character set name from {@link Charset}
*/
@Example({"URLDecode('UTF-16')", "URLDecode using the UTF-16 charset."})
public URLDecode(String charset) {
this.charset = Charset.forName(charset);
}
@Example({"URLDecode()", "URLDecode using the default UTF-8 charset."})
public URLDecode() {
this.charset = StandardCharsets.UTF_8;
}
@Override
public String apply(String s) {
return URLDecoder.decode(s, charset);
}
}

View File

@ -0,0 +1,41 @@
package io.nosqlbench.virtdata.library.basics.shared.unary_string;
import io.nosqlbench.virtdata.api.annotations.Categories;
import io.nosqlbench.virtdata.api.annotations.Category;
import io.nosqlbench.virtdata.api.annotations.Example;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
/**
* URLEncode string data
*/
@ThreadSafeMapper
@Categories({Category.conversion})
public class URLEncode implements Function<String, String> {
private final Charset charset;
/**
* UrlEncode any incoming string using the specified charset.
*
* @param charset A valid character set name from {@link Charset}
*/
@Example({"URLEncode('UTF-16')", "URLEncode using the UTF-16 charset."})
public URLEncode(String charset) {
this.charset = Charset.forName(charset);
}
@Example({"URLEncode()", "URLEncode using the default UTF-8 charset."})
public URLEncode() {
this.charset = StandardCharsets.UTF_8;
}
@Override
public String apply(String s) {
return URLEncoder.encode(s, charset);
}
}