diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2019-03-07 17:02:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-07 17:02:14 +0100 |
commit | d33933d74fa5a5a01529a6b74f1a1a1901904449 (patch) | |
tree | 5e1180aa105c8e8215270b2e740873b59e21e15b | |
parent | 882e4276c0fd191db0ba62f4da5e3e64ab7059ea (diff) | |
parent | 9e410bcd856284367889d288ef8ee17e0d90a569 (diff) |
Merge pull request #8681 from vespa-engine/balder/trace-backend
Balder/trace backend
36 files changed, 1195 insertions, 719 deletions
diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json index 24a46602b60..02f82762d57 100644 --- a/container-search/abi-spec.json +++ b/container-search/abi-spec.json @@ -1752,6 +1752,7 @@ "public java.lang.String toDetailString()", "public int encode(java.nio.ByteBuffer)", "public void trace(java.lang.String, int)", + "public void trace(java.lang.Object, int)", "public void trace(java.lang.String, boolean, int)", "public varargs void trace(boolean, int, java.lang.Object[])", "public void attachContext(com.yahoo.search.Query)", @@ -5370,6 +5371,7 @@ "methods": [ "public void <init>(int, com.yahoo.search.Query)", "public void trace(java.lang.String, int)", + "public void trace(java.lang.Object, int)", "public void logValue(java.lang.String, java.lang.Object)", "public java.util.Iterator logValueIterator()", "public void setProperty(java.lang.String, java.lang.Object)", diff --git a/container-search/src/main/java/com/yahoo/fs4/FS4Properties.java b/container-search/src/main/java/com/yahoo/fs4/FS4Properties.java index 9dec31785c3..f5f1fca0801 100644 --- a/container-search/src/main/java/com/yahoo/fs4/FS4Properties.java +++ b/container-search/src/main/java/com/yahoo/fs4/FS4Properties.java @@ -10,11 +10,12 @@ public class FS4Properties { static public class Entry { public final String key; - public final String val; + private final byte [] val; public Entry(byte[] k, byte[] v) { key = Utf8.toString(k); - val = Utf8.toString(v); + val = v; } + public final byte [] getValue() { return val; } }; private Entry[] entries; diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java index d6e87e58fd8..024dc4a6b06 100644 --- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java +++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java @@ -2,8 +2,11 @@ package com.yahoo.prelude.fastsearch; import com.yahoo.collections.TinyIdentitySet; +import com.yahoo.data.access.simple.Value; +import com.yahoo.data.access.slime.SlimeAdapter; import com.yahoo.fs4.DocsumPacket; import com.yahoo.fs4.DocumentInfo; +import com.yahoo.fs4.FS4Properties; import com.yahoo.fs4.Packet; import com.yahoo.fs4.QueryPacket; import com.yahoo.fs4.QueryPacketData; @@ -29,6 +32,10 @@ import com.yahoo.search.result.Hit; import com.yahoo.search.result.Relevance; import com.yahoo.search.searchchain.Execution; import com.yahoo.searchlib.aggregation.Grouping; +import com.yahoo.slime.BinaryFormat; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.text.Utf8; import com.yahoo.vespa.objects.BufferSerializer; import java.io.IOException; @@ -410,9 +417,23 @@ public abstract class VespaBackEndSearcher extends PingableSearcher { } } + private void addBackendTrace(Query query, QueryResultPacket resultPacket) { + if (resultPacket.propsArray == null) return; + Value.ArrayValue traces = new Value.ArrayValue(); + for (FS4Properties properties : resultPacket.propsArray) { + if ( ! properties.getName().equals("trace")) continue; + for (FS4Properties.Entry entry : properties.getEntries()) { + traces.add(new SlimeAdapter(BinaryFormat.decode(entry.getValue()).get())); + } + } + query.trace(traces, query.getTraceLevel()); + } + void addMetaInfo(Query query, QueryPacketData queryPacketData, QueryResultPacket resultPacket, Result result) { result.setTotalHitCount(resultPacket.getTotalDocumentCount()); + addBackendTrace(query, resultPacket); + // Grouping if (resultPacket.getGroupData() != null) { byte[] data = resultPacket.getGroupData(); diff --git a/container-search/src/main/java/com/yahoo/search/Query.java b/container-search/src/main/java/com/yahoo/search/Query.java index 8aacd194a3f..97a8c35bfa3 100644 --- a/container-search/src/main/java/com/yahoo/search/Query.java +++ b/container-search/src/main/java/com/yahoo/search/Query.java @@ -678,6 +678,11 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { trace(message, false, traceLevel); } + public void trace(Object message, int traceLevel) { + if ( ! isTraceable(traceLevel)) return; + getContext(true).trace(message, 0); + } + /** * Adds a trace message to this query * if the trace level of the query is sufficiently high. @@ -1027,13 +1032,17 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { // TODO: Push down mapCount += ranking.getProperties().encode(buffer, encodeQueryData); - if (encodeQueryData) mapCount += ranking.getFeatures().encode(buffer); + if (encodeQueryData) { + mapCount += ranking.getFeatures().encode(buffer); - // TODO: Push down - if (encodeQueryData && presentation.getHighlight() != null) mapCount += MapEncoder.encodeStringMultiMap(Highlight.HIGHLIGHTTERMS, presentation.getHighlight().getHighlightTerms(), buffer); + // TODO: Push down + if (presentation.getHighlight() != null) { + mapCount += MapEncoder.encodeStringMultiMap(Highlight.HIGHLIGHTTERMS, presentation.getHighlight().getHighlightTerms(), buffer); + } - // TODO: Push down - if (encodeQueryData) mapCount += MapEncoder.encodeSingleValue("model", "searchpath", model.getSearchPath(), buffer); + // TODO: Push down + mapCount += MapEncoder.encodeMap("model", createModelMap(), buffer); + } mapCount += MapEncoder.encodeSingleValue(DocumentDatabase.MATCH_PROPERTY, DocumentDatabase.SEARCH_DOC_TYPE_KEY, model.getDocumentDb(), buffer); mapCount += MapEncoder.encodeMap("caches", createCacheSettingMap(), buffer); @@ -1057,6 +1066,13 @@ public class Query extends com.yahoo.processing.Request implements Cloneable { return Collections.<String,Boolean>emptyMap(); } + private Map<String, String> createModelMap() { + Map<String, String> m = new HashMap<>(); + if (model.getSearchPath() != null) m.put("searchpath", model.getSearchPath()); + if (getTraceLevel() > 0) m.put("tracelevel", String.valueOf(getTraceLevel())); + return m; + } + /** * Prepares this for binary serialization. * <p> diff --git a/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java b/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java index 6b28b166150..1ba30275dc1 100644 --- a/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java +++ b/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java @@ -31,9 +31,12 @@ public class QueryContext implements Cloneable { /** Adds a context message to this context */ public void trace(String message, int traceLevel) { - owner.getModel().getExecution().trace().trace(message,traceLevel); + trace((Object)message, traceLevel); } + public void trace(Object message, int traceLevel) { + owner.getModel().getExecution().trace().trace(message,traceLevel); + } /** * Adds a key-value which will be logged to the access log for this query (by doing toString() on the value * Multiple values may be set to the same key. A value cannot be removed once set. diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java index c6e64a32c48..1754b6cc028 100644 --- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java +++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java @@ -18,7 +18,6 @@ import com.yahoo.document.datatypes.StringFieldValue; import com.yahoo.document.datatypes.TensorFieldValue; import com.yahoo.document.json.JsonWriter; import com.yahoo.lang.MutableBoolean; -import com.yahoo.prelude.hitfield.HitField; import com.yahoo.processing.Response; import com.yahoo.processing.execution.Execution.Trace; import com.yahoo.processing.rendering.AsynchronousSectionedRenderer; @@ -185,7 +184,8 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> { header(); generator.writeStartObject(); } - generator.writeStringField(TRACE_MESSAGE, payload.toString()); + generator.writeFieldName(TRACE_MESSAGE); + fieldConsumer.renderFieldContentsDirect(payload); dirty = true; } if (dirty) { @@ -763,17 +763,32 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> { } private void renderInspector(Inspector data) throws IOException { - StringBuilder intermediate = new StringBuilder(); Inspector asMap = wrapAsMap(data); if (asMap != null) { + StringBuilder intermediate = new StringBuilder(); JsonRender.render(asMap, intermediate, true); + generator.writeRawValue(intermediate.toString()); } else { - JsonRender.render(data, intermediate, true); + renderInspectorDirect(data); } + + } + + private void renderInspectorDirect(Inspector data) throws IOException { + StringBuilder intermediate = new StringBuilder(); + JsonRender.render(data, intermediate, true); generator.writeRawValue(intermediate.toString()); } private void renderFieldContents(Object field) throws IOException { + if (field instanceof Inspectable) { + renderInspector(((Inspectable)field).inspect()); + } else { + renderFieldContentsDirect(field); + } + } + + private void renderFieldContentsDirect(Object field) throws IOException { if (field == null) { generator.writeNull(); } else if (field instanceof Boolean) { @@ -785,7 +800,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> { } else if (field instanceof Tensor) { renderTensor(Optional.of((Tensor)field)); } else if (field instanceof Inspectable) { - renderInspector(((Inspectable)field).inspect()); + renderInspectorDirect(((Inspectable)field).inspect()); } else if (field instanceof JsonProducer) { generator.writeRawValue(((JsonProducer) field).toJson()); } else if (field instanceof StringFieldValue) { diff --git a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java index f3167cceaaf..9fb2e627e9c 100644 --- a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java +++ b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java @@ -95,24 +95,24 @@ public class JsonRendererTestCase { @Test public void testDocumentId() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"documentid\": \"id:unittest:smoke::whee\"\n" - + " },\n" - + " \"id\": \"id:unittest:smoke::whee\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"documentid\": \"id:unittest:smoke::whee\"" + + " }," + + " \"id\": \"id:unittest:smoke::whee\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("docIdTest"); h.setField("documentid", new DocumentId("id:unittest:smoke::whee")); @@ -124,34 +124,34 @@ public class JsonRendererTestCase { @Test public void testDataTypes() throws IOException, InterruptedException, ExecutionException, JSONException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"double\": 0.00390625,\n" - + " \"float\": 14.29,\n" - + " \"integer\": 1,\n" - + " \"long\": 4398046511104,\n" - + " \"bool\": true,\n" - + " \"object\": \"thingie\",\n" - + " \"string\": \"stuff\",\n" - + " \"predicate\": \"a in [b]\",\n" - + " \"tensor1\": { \"cells\": [ { \"address\": {\"x\": \"a\"}, \"value\":2.0 } ] },\n" - + " \"tensor2\": { \"cells\": [] },\n" - + " \"tensor3\": { \"cells\": [ { \"address\": {\"x\": \"a\", \"y\": \"0\"}, \"value\":2.0 }, { \"address\": {\"x\": \"a\", \"y\": \"1\"}, \"value\":-1.0 } ] }\n" - + " },\n" - + " \"id\": \"datatypestuff\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"double\": 0.00390625," + + " \"float\": 14.29," + + " \"integer\": 1," + + " \"long\": 4398046511104," + + " \"bool\": true," + + " \"object\": \"thingie\"," + + " \"string\": \"stuff\"," + + " \"predicate\": \"a in [b]\"," + + " \"tensor1\": { \"cells\": [ { \"address\": {\"x\": \"a\"}, \"value\":2.0 } ] }," + + " \"tensor2\": { \"cells\": [] }," + + " \"tensor3\": { \"cells\": [ { \"address\": {\"x\": \"a\", \"y\": \"0\"}, \"value\":2.0 }, { \"address\": {\"x\": \"a\", \"y\": \"1\"}, \"value\":-1.0 } ] }" + + " }," + + " \"id\": \"datatypestuff\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("datatypestuff"); // the floating point values are chosen to get a deterministic string representation @@ -176,42 +176,42 @@ public class JsonRendererTestCase { @Test public void testTracing() throws IOException, InterruptedException, ExecutionException { // which clearly shows a trace child is created once too often... - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " \"trace\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"No query profile is used\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"something\"\n" - + " },\n" - + " {\n" - + " \"message\": \"something else\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"yellow\"\n" - + " }\n" - + " ]\n" - + " },\n" - + " {\n" - + " \"message\": \"marker\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"something\"" + + " }," + + " {" + + " \"message\": \"something else\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"yellow\"" + + " }" + + " ]" + + " }," + + " {" + + " \"message\": \"marker\"" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=1"); Execution execution = new Execution(Execution.Context.createContextStub()); Result r = new Result(q); @@ -229,16 +229,81 @@ public class JsonRendererTestCase { } @Test + public void testTracingOfSlime() throws IOException, InterruptedException, ExecutionException { + // which clearly shows a trace child is created once too often... + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"something\"" + + " }," + + " {" + + " \"message\": \"something else\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": [" + + " { \"colour\": \"yellow\"}," + + " { \"colour\": \"green\"}" + + " ]" + + " }" + + " ]" + + " }," + + " {" + + " \"message\": \"marker\"" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + "}"; + Query q = new Query("/?query=a&tracelevel=1"); + Execution execution = new Execution(Execution.Context.createContextStub()); + Result r = new Result(q); + + execution.search(q); + q.trace("something", 1); + q.trace("something else", 1); + Execution e2 = new Execution(new Chain<Searcher>(), execution.context()); + Query subQuery = new Query("/?query=b&tracelevel=1"); + e2.search(subQuery); + Value.ArrayValue access = new Value.ArrayValue(); + Slime slime = new Slime(); + slime.setObject().setString("colour","yellow"); + access.add(new SlimeAdapter(slime.get())); + slime = new Slime(); + slime.setObject().setString("colour","green"); + access.add(new SlimeAdapter(slime.get())); + subQuery.trace(access, 1); + q.trace("marker", 1); + String summary = render(execution, r); + assertEqualJson(expected, summary); + } + + @Test public void testEmptyTracing() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=0"); Execution execution = new Execution(Execution.Context.createContextStub()); Result r = new Result(q); @@ -259,31 +324,31 @@ public class JsonRendererTestCase { @SuppressWarnings("unchecked") @Test public void testTracingWithEmptySubtree() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " \"trace\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"No query profile is used\"\n" - + " },\n" - + " {\n" - + " \"message\": \"Resolved properties:\\ntracelevel=10 (value from request)\\nquery=a (value from request)\\n\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"timestamp\": 42\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"message\": \"Resolved properties:\\ntracelevel=10 (value from request)\\nquery=a (value from request)\\n\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"timestamp\": 42" + + " }" + + " ]" + + " }" + + " ]" + + " }" + "}"; Query q = new Query("/?query=a&tracelevel=10"); Execution execution = new Execution(Execution.Context.createContextStub()); @@ -314,14 +379,14 @@ public class JsonRendererTestCase { @Test public void trace_is_not_included_if_tracelevel_0() throws IOException, ExecutionException, InterruptedException { String expected = - "{\n" + - " \"root\": {\n" + - " \"id\": \"toplevel\",\n" + - " \"relevance\": 1.0,\n" + - " \"fields\": {\n" + - " \"totalCount\": 0\n" + - " }\n" + - " }\n" + + "{" + + " \"root\": {" + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0," + + " \"fields\": {" + + " \"totalCount\": 0" + + " }" + + " }" + "}"; Query q = new Query("/?query=a&tracelevel=0"); Execution execution = new Execution(Execution.Context.createContextStub()); @@ -334,37 +399,37 @@ public class JsonRendererTestCase { @Test public void testTracingOfNodesWithBothChildrenAndData() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " \"trace\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"No query profile is used\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"string payload\",\n" + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"string payload\"," + " \"children\": [" - + " {\n" + + " {" + " \"message\": \"leafnode\"" - + " }\n" - + " ]\n" - + " },\n" - + " {\n" - + " \"message\": \"something\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + "}\n"; + + " }" + + " ]" + + " }," + + " {" + + " \"message\": \"something\"" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=1"); Execution execution = new Execution(Execution.Context.createContextStub()); Result r = new Result(q); @@ -380,32 +445,32 @@ public class JsonRendererTestCase { @Test public void testTracingOfNodesWithBothChildrenAndDataAndEmptySubnode() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " \"trace\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"No query profile is used\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"string payload\"\n" - + " },\n" - + " {\n" - + " \"message\": \"something\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"string payload\"" + + " }," + + " {" + + " \"message\": \"something\"" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=1"); Execution execution = new Execution( Execution.Context.createContextStub()); @@ -421,38 +486,38 @@ public class JsonRendererTestCase { @Test public void testTracingOfNestedNodesWithDataAndSubnodes() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " \"trace\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"No query profile is used\"\n" - + " },\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"string payload\",\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"message\": \"in OO languages, nesting is for birds\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }," + + " \"trace\": {" + + " \"children\": [" + + " {" + + " \"message\": \"No query profile is used\"" + + " }," + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"string payload\"," + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"message\": \"in OO languages, nesting is for birds\"" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=1"); Execution execution = new Execution( Execution.Context.createContextStub()); @@ -470,67 +535,67 @@ public class JsonRendererTestCase { @Test public void test() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"c\": \"d\"\n" - + " },\n" - + " \"id\": \"http://localhost/1\",\n" - + " \"relevance\": 0.9,\n" - + " \"types\": [\n" - + " \"summary\"\n" - + " ]\n" - + " }\n" - + " ],\n" - + " \"id\": \"usual\",\n" - + " \"relevance\": 1.0\n" - + " },\n" - + " {\n" - + " \"fields\": {\n" - + " \"e\": \"f\"\n" - + " },\n" - + " \"id\": \"type grouphit\",\n" - + " \"relevance\": 1.0,\n" - + " \"types\": [\n" - + " \"grouphit\"\n" - + " ]\n" - + " },\n" - + " {\n" - + " \"fields\": {\n" - + " \"b\": \"foo\"\n" - + " },\n" - + " \"id\": \"http://localhost/\",\n" - + " \"relevance\": 0.95,\n" - + " \"types\": [\n" - + " \"summary\"\n" - + " ]\n" - + " }\n" - + " ],\n" - + " \"coverage\": {\n" - + " \"coverage\": 100,\n" - + " \"documents\": 500,\n" - + " \"full\": true,\n" - + " \"nodes\": 1,\n" - + " \"results\": 1,\n" - + " \"resultsFull\": 1\n" - + " },\n" - + " \"errors\": [\n" - + " {\n" - + " \"code\": 18,\n" - + " \"message\": \"boom\",\n" - + " \"summary\": \"Internal server error.\"\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"c\": \"d\"" + + " }," + + " \"id\": \"http://localhost/1\"," + + " \"relevance\": 0.9," + + " \"types\": [" + + " \"summary\"" + + " ]" + + " }" + + " ]," + + " \"id\": \"usual\"," + + " \"relevance\": 1.0" + + " }," + + " {" + + " \"fields\": {" + + " \"e\": \"f\"" + + " }," + + " \"id\": \"type grouphit\"," + + " \"relevance\": 1.0," + + " \"types\": [" + + " \"grouphit\"" + + " ]" + + " }," + + " {" + + " \"fields\": {" + + " \"b\": \"foo\"" + + " }," + + " \"id\": \"http://localhost/\"," + + " \"relevance\": 0.95," + + " \"types\": [" + + " \"summary\"" + + " ]" + + " }" + + " ]," + + " \"coverage\": {" + + " \"coverage\": 100," + + " \"documents\": 500," + + " \"full\": true," + + " \"nodes\": 1," + + " \"results\": 1," + + " \"resultsFull\": 1" + + " }," + + " \"errors\": [" + + " {" + + " \"code\": 18," + + " \"message\": \"boom\"," + + " \"summary\": \"Internal server error.\"" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + "}"; Query q = new Query("/?query=a&tracelevel=5"); Execution execution = new Execution(Execution.Context.createContextStub()); @@ -557,28 +622,28 @@ public class JsonRendererTestCase { @Test public void testCoverage() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"coverage\": {\n" - + " \"coverage\": 83,\n" - + " \"documents\": 500,\n" - + " \"degraded\" : {\n" - + " \"match-phase\" : true,\n" - + " \"timeout\" : false,\n" - + " \"adaptive-timeout\" : true,\n" + String expected = "{" + + " \"root\": {" + + " \"coverage\": {" + + " \"coverage\": 83," + + " \"documents\": 500," + + " \"degraded\" : {" + + " \"match-phase\" : true," + + " \"timeout\" : false," + + " \"adaptive-timeout\" : true," + " \"non-ideal-state\" : false" - + " },\n" - + " \"full\": false,\n" - + " \"nodes\": 1,\n" - + " \"results\": 1,\n" - + " \"resultsFull\": 0\n" - + " },\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" + + " }," + + " \"full\": false," + + " \"nodes\": 1," + + " \"results\": 1," + + " \"resultsFull\": 0" + + " }," + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + "}"; Query q = new Query("/?query=a&tracelevel=5"); Execution execution = new Execution(Execution.Context.createContextStub()); @@ -591,27 +656,27 @@ public class JsonRendererTestCase { @Test public void testMoreTypes() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"bigDecimal\": 3.402823669209385e+38,\n" - + " \"bigInteger\": 340282366920938463463374607431768211455,\n" - + " \"byte\": 8,\n" - + " \"short\": 16\n" - + " },\n" - + " \"id\": \"moredatatypestuff\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"bigDecimal\": 3.402823669209385e+38," + + " \"bigInteger\": 340282366920938463463374607431768211455," + + " \"byte\": 8," + + " \"short\": 16" + + " }," + + " \"id\": \"moredatatypestuff\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("moredatatypestuff"); h.setField("byte", Byte.valueOf((byte) 8)); @@ -629,24 +694,24 @@ public class JsonRendererTestCase { @Test public void testNullField() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"null\": null\n" - + " },\n" - + " \"id\": \"nullstuff\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"null\": null" + + " }," + + " \"id\": \"nullstuff\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("nullstuff"); h.setField("null", null); @@ -658,22 +723,22 @@ public class JsonRendererTestCase { @Test public void testHitWithSource() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"id\": \"datatypestuff\",\n" - + " \"relevance\": 1.0,\n" - + " \"source\": \"unit test\"\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"id\": \"datatypestuff\"," + + " \"relevance\": 1.0," + + " \"source\": \"unit test\"" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("datatypestuff"); h.setSource("unit test"); @@ -685,23 +750,23 @@ public class JsonRendererTestCase { @Test public void testErrorWithStackTrace() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"errors\": [\n" - + " {\n" - + " \"code\": 1234,\n" - + " \"message\": \"top of the day\",\n" - + " \"stackTrace\": \"java.lang.Throwable\\n\\tat com.yahoo.search.rendering.JsonRendererTestCase.testErrorWithStackTrace(JsonRendererTestCase.java:732)\\n\",\n" - + " \"summary\": \"hello\"\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"errors\": [" + + " {" + + " \"code\": 1234," + + " \"message\": \"top of the day\"," + + " \"stackTrace\": \"java.lang.Throwable\\n\\tat com.yahoo.search.rendering.JsonRendererTestCase.testErrorWithStackTrace(JsonRendererTestCase.java:732)\\n\"," + + " \"summary\": \"hello\"" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Query q = new Query("/?query=a&tracelevel=5"); Result r = new Result(q); Throwable t = new Throwable(); @@ -724,45 +789,45 @@ public class JsonRendererTestCase { @Test public void testGrouping() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"count()\": 7\n" - + " },\n" - + " \"value\": \"Jones\",\n" - + " \"id\": \"group:string:Jones\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"continuation\": {\n" - + " \"next\": \"CCCC\",\n" - + " \"prev\": \"BBBB\"\n" - + " },\n" - + " \"id\": \"grouplist:customer\",\n" - + " \"label\": \"customer\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"continuation\": {\n" - + " \"this\": \"AAAA\"\n" - + " },\n" - + " \"id\": \"group:root:0\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"count()\": 7" + + " }," + + " \"value\": \"Jones\"," + + " \"id\": \"group:string:Jones\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"continuation\": {" + + " \"next\": \"CCCC\"," + + " \"prev\": \"BBBB\"" + + " }," + + " \"id\": \"grouplist:customer\"," + + " \"label\": \"customer\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"continuation\": {" + + " \"this\": \"AAAA\"" + + " }," + + " \"id\": \"group:root:0\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); RootGroup rg = new RootGroup(0, new Continuation() { @Override @@ -810,44 +875,44 @@ public class JsonRendererTestCase { @Test public void testGroupingWithBucket() throws InterruptedException, ExecutionException, IOException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"something()\": 7\n" - + " },\n" - + " \"limits\": {\n" - + " \"from\": \"1.0\",\n" - + " \"to\": \"2.0\"\n" - + " },\n" - + " \"id\": \"group:double_bucket:1.0:2.0\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"id\": \"grouplist:customer\",\n" - + " \"label\": \"customer\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"continuation\": {\n" - + " \"this\": \"AAAA\"\n" - + " },\n" - + " \"id\": \"group:root:0\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"something()\": 7" + + " }," + + " \"limits\": {" + + " \"from\": \"1.0\"," + + " \"to\": \"2.0\"" + + " }," + + " \"id\": \"group:double_bucket:1.0:2.0\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"id\": \"grouplist:customer\"," + + " \"label\": \"customer\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"continuation\": {" + + " \"this\": \"AAAA\"" + + " }," + + " \"id\": \"group:root:0\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); RootGroup rg = new RootGroup(0, new Continuation() { @Override @@ -873,40 +938,40 @@ public class JsonRendererTestCase { @Test public void testJsonObjects() throws InterruptedException, ExecutionException, IOException, JSONException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"inspectable\": {\n" - + " \"a\": \"b\"\n" - + " },\n" - + " \"jackson\": {\n" - + " \"Nineteen-eighty-four\": 1984\n" - + " },\n" - + " \"json producer\": {\n" - + " \"long in structured\": 7809531904\n" - + " },\n" - + " \"org.json array\": [\n" - + " true,\n" - + " true,\n" - + " false\n" - + " ],\n" - + " \"org.json object\": {\n" - + " \"forty-two\": 42\n" - + " }\n" - + " },\n" - + " \"id\": \"json objects\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 0\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"inspectable\": {" + + " \"a\": \"b\"" + + " }," + + " \"jackson\": {" + + " \"Nineteen-eighty-four\": 1984" + + " }," + + " \"json producer\": {" + + " \"long in structured\": 7809531904" + + " }," + + " \"org.json array\": [" + + " true," + + " true," + + " false" + + " ]," + + " \"org.json object\": {" + + " \"forty-two\": 42" + + " }" + + " }," + + " \"id\": \"json objects\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 0" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("json objects"); JSONObject o = new JSONObject(); @@ -936,24 +1001,24 @@ public class JsonRendererTestCase { @Test public void testFieldValueInHit() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + " \"fromDocumentApi\":{\"integerField\":123, \"stringField\":\"abc\"}" - + " },\n" - + " \"id\": \"fieldValueTest\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + + " }," + + " \"id\": \"fieldValueTest\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("fieldValueTest"); StructDataType structType = new StructDataType("jsonRenderer"); @@ -971,21 +1036,21 @@ public class JsonRendererTestCase { @Test public void testHiddenFields() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"id\": \"hiddenFields\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"id\": \"hiddenFields\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = createHitWithOnlyHiddenFields(); r.hits().add(h); @@ -996,27 +1061,27 @@ public class JsonRendererTestCase { @Test public void testDebugRendering() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"NaN\": \"NaN\",\n" - + " \"emptyString\": \"\",\n" - + " \"emptyStringFieldValue\": \"\",\n" - + " \"$vespaImplementationDetail\": \"Hello, World!\"\n" - + " },\n" - + " \"id\": \"hiddenFields\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"NaN\": \"NaN\"," + + " \"emptyString\": \"\"," + + " \"emptyStringFieldValue\": \"\"," + + " \"$vespaImplementationDetail\": \"Hello, World!\"" + + " }," + + " \"id\": \"hiddenFields\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = new Result(new Query("/?renderer.json.debug=true")); Hit h = createHitWithOnlyHiddenFields(); r.hits().add(h); @@ -1062,24 +1127,24 @@ public class JsonRendererTestCase { @Test public void testJsonCallback() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"documentid\": \"id:unittest:smoke::whee\"\n" - + " },\n" - + " \"id\": \"id:unittest:smoke::whee\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"documentid\": \"id:unittest:smoke::whee\"" + + " }," + + " \"id\": \"id:unittest:smoke::whee\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; String jsonCallback = "some_function_name"; Result r = newEmptyResult(new String[] {"query=a", "jsoncallback="+jsonCallback} ); @@ -1100,28 +1165,28 @@ public class JsonRendererTestCase { @Test public void testMapInField() throws IOException, InterruptedException, ExecutionException { - String expected = "{\n" - + " \"root\": {\n" - + " \"children\": [\n" - + " {\n" - + " \"fields\": {\n" - + " \"structured\": {\n" - + " \"foo\": \"string foo\",\n" - + " \"bar\": [\"array bar elem 1\", \"array bar elem 2\"],\n" - + " \"baz\": {\"f1\": \"object baz field 1\", \"f2\": \"object baz field 2\"}\n" - + " }\n" - + " },\n" - + " \"id\": \"MapInField\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + " ],\n" - + " \"fields\": {\n" - + " \"totalCount\": 1\n" - + " },\n" - + " \"id\": \"toplevel\",\n" - + " \"relevance\": 1.0\n" - + " }\n" - + "}\n"; + String expected = "{" + + " \"root\": {" + + " \"children\": [" + + " {" + + " \"fields\": {" + + " \"structured\": {" + + " \"foo\": \"string foo\"," + + " \"bar\": [\"array bar elem 1\", \"array bar elem 2\"]," + + " \"baz\": {\"f1\": \"object baz field 1\", \"f2\": \"object baz field 2\"}" + + " }" + + " }," + + " \"id\": \"MapInField\"," + + " \"relevance\": 1.0" + + " }" + + " ]," + + " \"fields\": {" + + " \"totalCount\": 1" + + " }," + + " \"id\": \"toplevel\"," + + " \"relevance\": 1.0" + + " }" + + "}"; Result r = newEmptyResult(); Hit h = new Hit("MapInField"); Value.ArrayValue atop = new Value.ArrayValue(); @@ -1164,7 +1229,7 @@ public class JsonRendererTestCase { String summary = render(result); String expected = - "{ \n" + + "{ " + " \"root\":{ " + " \"id\":\"toplevel\"," + " \"relevance\":1.0," + @@ -1176,13 +1241,13 @@ public class JsonRendererTestCase { " \"id\":\"http://abc.html/\"," + " \"relevance\":1.0," + " \"fields\":{ " + - " \"sddocname\":\"one\",\n" + - " \"dynteaser\":\"Feeding <hi>documents</hi> into Vespa is<sep />increment of a set of <hi>documents</hi> fed into Vespa <sep />float in XML when <hi>document</hi> attribute is int<sep />\"\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - "}\n"; + " \"sddocname\":\"one\"," + + " \"dynteaser\":\"Feeding <hi>documents</hi> into Vespa is<sep />increment of a set of <hi>documents</hi> fed into Vespa <sep />float in XML when <hi>document</hi> attribute is int<sep />\"" + + " }" + + " }" + + " ]" + + " }" + + "}"; assertEqualJson(expected, summary); } @@ -1218,12 +1283,13 @@ public class JsonRendererTestCase { @SuppressWarnings("unchecked") private void assertEqualJson(String expected, String generated) throws IOException { + assertEquals("", validateJSON(expected)); + assertEquals("", validateJSON(generated)); + ObjectMapper m = new ObjectMapper(); Map<String, Object> exp = m.readValue(expected, Map.class); Map<String, Object> gen = m.readValue(generated, Map.class); assertEquals(exp, gen); - assertEquals("", validateJSON(expected)); - assertEquals("", validateJSON(generated)); } private String validateJSON(String presumablyValidJson) { diff --git a/documentapi/abi-spec.json b/documentapi/abi-spec.json index 1619d053aa2..663d04e3074 100644 --- a/documentapi/abi-spec.json +++ b/documentapi/abi-spec.json @@ -1054,9 +1054,9 @@ "public com.yahoo.documentapi.messagebus.MessageBusParams setProtocolConfigId(java.lang.String)", "public com.yahoo.documentapi.messagebus.MessageBusParams setRouteName(java.lang.String)", "public com.yahoo.documentapi.messagebus.MessageBusParams setRoute(java.lang.String)", - "public java.lang.String getRoute()", "public com.yahoo.documentapi.messagebus.MessageBusParams setRouteNameForGet(java.lang.String)", "public com.yahoo.documentapi.messagebus.MessageBusParams setRouteForGet(java.lang.String)", + "public java.lang.String getRoute()", "public java.lang.String getRouteForGet()", "public int getTraceLevel()", "public com.yahoo.documentapi.messagebus.MessageBusParams setTraceLevel(int)", diff --git a/processing/abi-spec.json b/processing/abi-spec.json index 464049ec116..78058f1a8b7 100644 --- a/processing/abi-spec.json +++ b/processing/abi-spec.json @@ -127,6 +127,7 @@ "public void setForceTimestamps(boolean)", "public boolean getForceTimestamps()", "public void trace(java.lang.String, int)", + "public void trace(java.lang.Object, int)", "public void logValue(java.lang.String, java.lang.String)", "public java.util.Iterator logValueIterator()", "public com.yahoo.yolean.trace.TraceVisitor accept(com.yahoo.yolean.trace.TraceVisitor)", diff --git a/processing/src/main/java/com/yahoo/processing/execution/Execution.java b/processing/src/main/java/com/yahoo/processing/execution/Execution.java index 957e39bf41f..9aff586ef75 100644 --- a/processing/src/main/java/com/yahoo/processing/execution/Execution.java +++ b/processing/src/main/java/com/yahoo/processing/execution/Execution.java @@ -310,6 +310,9 @@ public class Execution { * Adds a trace message to this trace, if this trace has at most the given trace level */ public void trace(String message, int traceLevel) { + trace((Object)message, traceLevel); + } + public void trace(Object message, int traceLevel) { if (this.traceLevel >= traceLevel) { traceNode.add(new TraceNode(message, timestamp(traceLevel, forceTimestamps))); } diff --git a/searchcore/src/vespa/searchcore/proton/matchengine/matchengine.cpp b/searchcore/src/vespa/searchcore/proton/matchengine/matchengine.cpp index 8fcb30a4143..3a28eeb4dfd 100644 --- a/searchcore/src/vespa/searchcore/proton/matchengine/matchengine.cpp +++ b/searchcore/src/vespa/searchcore/proton/matchengine/matchengine.cpp @@ -2,9 +2,12 @@ #include "matchengine.h" #include <vespa/searchcore/proton/common/state_reporter_utils.h> #include <vespa/vespalib/data/slime/cursor.h> -#include <algorithm> +#include <vespa/vespalib/data/smart_buffer.h> +#include <vespa/vespalib/data/slime/binary_format.h> +#include <vespa/vespalib/stllike/asciistream.h> #include <vespa/log/log.h> + LOG_SETUP(".proton.matchengine.matchengine"); namespace { @@ -47,7 +50,6 @@ MatchEngine::MatchEngine(size_t numThreads, size_t threadsPerSearch, uint32_t di _threadBundlePool(std::max(size_t(1), threadsPerSearch)), _nodeUp(false) { - // empty } MatchEngine::~MatchEngine() @@ -103,9 +105,7 @@ MatchEngine::search(search::engine::SearchRequest::Source request, return ret; } - vespalib::Executor::Task::UP task; - task.reset(new SearchTask(*this, std::move(request), client)); - _executor.execute(std::move(task)); + _executor.execute(std::make_unique<SearchTask>(*this, std::move(request), client)); return search::engine::SearchReply::UP(); } @@ -113,9 +113,9 @@ void MatchEngine::performSearch(search::engine::SearchRequest::Source req, search::engine::SearchClient &client) { - search::engine::SearchReply::UP ret(new search::engine::SearchReply); + auto ret = std::make_unique<search::engine::SearchReply>(); - if (req.get() != NULL) { + if (req.get()) { ISearchHandler::SP searchHandler; vespalib::SimpleThreadBundle::UP threadBundle = _threadBundlePool.obtain(); { // try to find the match handler corresponding to the specified search doc type @@ -123,7 +123,7 @@ MatchEngine::performSearch(search::engine::SearchRequest::Source req, DocTypeName docTypeName(*req.get()); searchHandler = _handlers.getHandler(docTypeName); } - if (searchHandler.get() != NULL) { + if (searchHandler) { ret = searchHandler->match(searchHandler, *req.get(), *threadBundle); } else { HandlerMap<ISearchHandler>::Snapshot::UP snapshot; @@ -140,6 +140,13 @@ MatchEngine::performSearch(search::engine::SearchRequest::Source req, } ret->request = req.release(); ret->setDistributionKey(_distributionKey); + if (ret->request->getTraceLevel() > 0) { + ret->request->trace().getRoot().setLong("distribution-key", _distributionKey); + search::fef::Properties & trace = ret->propertiesMap.lookupCreate("trace"); + vespalib::SmartBuffer output(4096); + vespalib::slime::BinaryFormat::encode(ret->request->trace().getSlime(), output); + trace.add("slime", output.obtain().make_stringref()); + } client.searchDone(std::move(ret)); } diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_tools.h b/searchcore/src/vespa/searchcore/proton/matching/match_tools.h index 0ecf6eb5b78..dfef51bfc5e 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/match_tools.h +++ b/searchcore/src/vespa/searchcore/proton/matching/match_tools.h @@ -132,6 +132,7 @@ public: std::unique_ptr<AttributeOperationTask> createOnSummaryTask() const; const RequestContext & requestContext() const { return _requestContext; } + const Query & query() const { return _query; } }; } diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp index 382e197c16b..4ebf74c373f 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp @@ -14,6 +14,7 @@ #include <vespa/searchlib/engine/searchreply.h> #include <vespa/searchlib/features/setup.h> #include <vespa/searchlib/fef/test/plugin/setup.h> +#include <vespa/vespalib/data/slime/inserter.h> #include <vespa/log/log.h> LOG_SETUP(".proton.matching.matcher"); @@ -203,6 +204,17 @@ Matcher::computeNumThreadsPerSearch(Blueprint::HitEstimate hits, const Propertie return threads; } +namespace { + void traceQuery(const SearchRequest &request, const Query & query) { + if (request.getTraceLevel() > 3) { + if (query.peekRoot()) { + vespalib::slime::ObjectInserter inserter(request.trace().createCursor("blueprint"), "optimized"); + query.peekRoot()->asSlime(inserter); + } + } + } +} + SearchReply::UP Matcher::match(const SearchRequest &request, vespalib::ThreadBundle &threadBundle, ISearchContext &searchContext, IAttributeContext &attrContext, SessionManager &sessionMgr, @@ -240,6 +252,7 @@ Matcher::match(const SearchRequest &request, vespalib::ThreadBundle &threadBundl if (!mtf->valid()) { reply->errorCode = ECODE_QUERY_PARSE_ERROR; reply->errorMessage = "query execution failed (invalid query)"; + traceQuery(request, mtf->query()); return reply; } @@ -274,6 +287,7 @@ Matcher::match(const SearchRequest &request, vespalib::ThreadBundle &threadBundl sessionMgr.insert(std::move(session)); } reply = std::move(result->_reply); + traceQuery(request, mtf->query()); uint32_t numActiveLids = metaStore.getNumActiveLids(); // note: this is actually totalSpace+1, since 0 is reserved diff --git a/searchlib/src/tests/engine/searchapi/searchapi_test.cpp b/searchlib/src/tests/engine/searchapi/searchapi_test.cpp index a517890620c..626435360a0 100644 --- a/searchlib/src/tests/engine/searchapi/searchapi_test.cpp +++ b/searchlib/src/tests/engine/searchapi/searchapi_test.cpp @@ -1,10 +1,12 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/log/log.h> -LOG_SETUP("searchapi_test"); + #include <vespa/vespalib/testkit/testapp.h> #include <vespa/searchlib/common/packets.h> #include <vespa/searchlib/engine/searchapi.h> #include <vespa/searchlib/engine/packetconverter.h> +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/log/log.h> +LOG_SETUP("searchapi_test"); using namespace search::engine; using namespace search::fs4transport; @@ -28,18 +30,8 @@ template <typename T> void copyPacket(T &src, T &dst) { } // namespace <unnamed> -class Test : public vespalib::TestApp -{ -public: - void propertyNames(); - void convertToRequest(); - void convertFromReply(); - int Main() override; -}; - -void -Test::propertyNames() -{ + +TEST("propertyNames") { EXPECT_EQUAL(search::MapNames::RANK, "rank"); EXPECT_EQUAL(search::MapNames::FEATURE, "feature"); EXPECT_EQUAL(search::MapNames::HIGHLIGHTTERMS, "highlightterms"); @@ -47,9 +39,7 @@ Test::propertyNames() EXPECT_EQUAL(search::MapNames::CACHES, "caches"); } -void -Test::convertToRequest() -{ +TEST("convertToReques") { FS4Packet_QUERYX src; src._offset = 2u; src._maxhits = 3u; @@ -116,9 +106,7 @@ Test::convertToRequest() } } -void -Test::convertFromReply() -{ +TEST("convertFromReply") { SearchReply src; src.offset = 1u; src.totalHitCount = 2u; @@ -240,14 +228,46 @@ Test::convertFromReply() } } -int -Test::Main() -{ - TEST_INIT("searchapi_test"); - propertyNames(); - convertToRequest(); - convertFromReply(); - TEST_DONE(); +void verify(vespalib::stringref expected, const vespalib::Slime & slime) { + vespalib::Slime expectedSlime; + vespalib::slime::JsonFormat::decode(expected, expectedSlime); + EXPECT_EQUAL(expectedSlime, slime); +} + +TEST("verify trace") { + Trace t(7); + verify("{" + " traces: [" + " ]," + " creation_time: 7" + "}", + t.getSlime()); + + t.createCursor("tag_a"); + verify("{" + " traces: [" + " {" + " tag: 'tag_a'" + " }" + " ]," + " creation_time: 7" + "}", + t.getSlime()); + Trace::Cursor & tagB = t.createCursor("tag_b"); + tagB.setLong("long", 19); + verify("{" + " traces: [" + " {" + " tag: 'tag_a'" + " }," + " {" + " tag: 'tag_b'," + " long: 19" + " }" + " ]," + " creation_time: 7" + "}", + t.getSlime()); } -TEST_APPHOOK(Test); +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchlib/src/tests/queryeval/blueprint/blueprint_test.cpp b/searchlib/src/tests/queryeval/blueprint/blueprint_test.cpp index e5ee83507ae..108b7b9d20d 100644 --- a/searchlib/src/tests/queryeval/blueprint/blueprint_test.cpp +++ b/searchlib/src/tests/queryeval/blueprint/blueprint_test.cpp @@ -5,6 +5,7 @@ #include <vespa/searchlib/queryeval/intermediate_blueprints.h> #include <vespa/vespalib/objects/objectdumper.h> #include <vespa/vespalib/objects/visit.h> +#include <vespa/vespalib/data/slime/slime.h> #include <algorithm> #include <vespa/log/log.h> @@ -178,6 +179,7 @@ public: void testSearchCreation(); void testBlueprintMakeNew(); void requireThatAsStringWorks(); + void requireThatAsSlimeWorks(); void requireThatVisitMembersWorks(); void requireThatDocIdLimitInjectionWorks(); int Main() override; @@ -698,6 +700,58 @@ getExpectedBlueprint() "}\n"; } +vespalib::string +getExpectedSlimeBlueprint() { + return "{" + " '[type]': '(anonymous namespace)::MyOr'," + " isTermLike: true," + " fields: {" + " '[type]': 'FieldList'," + " '[0]': {" + " '[type]': 'Field'," + " fieldId: 5," + " handle: 7," + " isFilter: false" + " }" + " }," + " estimate: {" + " '[type]': 'HitEstimate'," + " empty: false," + " estHits: 9," + " tree_size: 2," + " allow_termwise_eval: 0" + " }," + " sourceId: 4294967295," + " docid_limit: 0," + " children: {" + " '[type]': 'std::vector'," + " '[0]': {" + " isTermLike: true," + " fields: {" + " '[type]': 'FieldList'," + " '[0]': {" + " '[type]': 'Field'," + " fieldId: 5," + " handle: 7," + " isFilter: false" + " }" + " }," + " '[type]': '(anonymous namespace)::MyTerm'," + " estimate: {" + " '[type]': 'HitEstimate'," + " empty: false," + " estHits: 9," + " tree_size: 1," + " allow_termwise_eval: 1" + " }," + " sourceId: 4294967295," + " docid_limit: 0" + " }" + " }" + "}"; +} + + struct BlueprintFixture { MyOr _blueprint; @@ -714,6 +768,18 @@ Test::requireThatAsStringWorks() } void +Test::requireThatAsSlimeWorks() +{ + BlueprintFixture f; + vespalib::Slime slime; + f._blueprint.asSlime(vespalib::slime::SlimeInserter(slime)); + auto s = slime.toString(); + vespalib::Slime expectedSlime; + vespalib::slime::JsonFormat::decode(getExpectedSlimeBlueprint(), expectedSlime); + EXPECT_EQUAL(expectedSlime, slime); +} + +void Test::requireThatVisitMembersWorks() { BlueprintFixture f; @@ -749,6 +815,7 @@ Test::Main() testSearchCreation(); testBlueprintMakeNew(); requireThatAsStringWorks(); + requireThatAsSlimeWorks(); requireThatVisitMembersWorks(); requireThatDocIdLimitInjectionWorks(); TEST_DONE(); diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.cpp b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.cpp index 0ba4bc814d5..9e3b0bbde3e 100644 --- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.cpp +++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.cpp @@ -39,8 +39,7 @@ ImportedAttributeVector::ImportedAttributeVector(vespalib::stringref name, { } -ImportedAttributeVector::~ImportedAttributeVector() { -} +ImportedAttributeVector::~ImportedAttributeVector() = default; std::unique_ptr<AttributeReadGuard> ImportedAttributeVector::makeReadGuard(bool stableEnumGuard) const diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.h b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.h index e6356866ed9..33afaaf7f1e 100644 --- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.h +++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector.h @@ -6,11 +6,9 @@ #include <vespa/vespalib/stllike/string.h> #include <memory> -namespace search { +namespace search { struct IDocumentMetaStoreContext; } -struct IDocumentMetaStoreContext; - -namespace attribute { +namespace search::attribute { class BitVectorSearchCache; class ReadableAttributeVector; @@ -63,7 +61,7 @@ public: return _name; } - virtual std::unique_ptr<AttributeReadGuard> makeReadGuard(bool stableEnumGuard) const override; + std::unique_ptr<AttributeReadGuard> makeReadGuard(bool stableEnumGuard) const override; protected: vespalib::string _name; @@ -75,4 +73,3 @@ protected: }; } -} diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp index 428b14671cd..50fa1ffded3 100644 --- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp +++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp @@ -6,8 +6,7 @@ #include "reference_attribute.h" #include <vespa/searchlib/query/queryterm.h> -namespace search { -namespace attribute { +namespace search::attribute { ImportedAttributeVectorReadGuard::ImportedAttributeVectorReadGuard( const ImportedAttributeVector &imported_attribute, @@ -24,8 +23,7 @@ ImportedAttributeVectorReadGuard::ImportedAttributeVectorReadGuard( _targetLids = _reference_attribute.getTargetLids(); } -ImportedAttributeVectorReadGuard::~ImportedAttributeVectorReadGuard() { -} +ImportedAttributeVectorReadGuard::~ImportedAttributeVectorReadGuard() = default; const vespalib::string& ImportedAttributeVectorReadGuard::getName() const { return _imported_attribute.getName(); @@ -169,4 +167,3 @@ long ImportedAttributeVectorReadGuard::onSerializeForDescendingSort(DocId doc, } } -} diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h index 4cf4d5b64c1..f130a095006 100644 --- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h +++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h @@ -45,49 +45,48 @@ protected: } public: - ImportedAttributeVectorReadGuard(const ImportedAttributeVector &imported_attribute, - bool stableEnumGuard); - ~ImportedAttributeVectorReadGuard(); + ImportedAttributeVectorReadGuard(const ImportedAttributeVector &imported_attribute, bool stableEnumGuard); + ~ImportedAttributeVectorReadGuard() override; - virtual const vespalib::string &getName() const override; - virtual uint32_t getNumDocs() const override; - virtual uint32_t getValueCount(uint32_t doc) const override; - virtual uint32_t getMaxValueCount() const override; - virtual largeint_t getInt(DocId doc) const override; - virtual double getFloat(DocId doc) const override; - virtual const char *getString(DocId doc, char *buffer, size_t sz) const override; - virtual EnumHandle getEnum(DocId doc) const override; - virtual uint32_t get(DocId docId, largeint_t *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, double *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, const char **buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, EnumHandle *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, WeightedInt *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, WeightedFloat *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, WeightedString *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, WeightedConstChar *buffer, uint32_t sz) const override; - virtual uint32_t get(DocId docId, WeightedEnum *buffer, uint32_t sz) const override; - virtual bool findEnum(const char * value, EnumHandle & e) const override; - virtual std::vector<EnumHandle> findFoldedEnums(const char *value) const override; + const vespalib::string &getName() const override; + uint32_t getNumDocs() const override; + uint32_t getValueCount(uint32_t doc) const override; + uint32_t getMaxValueCount() const override; + largeint_t getInt(DocId doc) const override; + double getFloat(DocId doc) const override; + const char *getString(DocId doc, char *buffer, size_t sz) const override; + EnumHandle getEnum(DocId doc) const override; + uint32_t get(DocId docId, largeint_t *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, double *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, const char **buffer, uint32_t sz) const override; + uint32_t get(DocId docId, EnumHandle *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, WeightedInt *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, WeightedFloat *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, WeightedString *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, WeightedConstChar *buffer, uint32_t sz) const override; + uint32_t get(DocId docId, WeightedEnum *buffer, uint32_t sz) const override; + bool findEnum(const char * value, EnumHandle & e) const override; + std::vector<EnumHandle> findFoldedEnums(const char *value) const override; - virtual const char * getStringFromEnum(EnumHandle e) const override; - virtual std::unique_ptr<ISearchContext> createSearchContext(std::unique_ptr<QueryTermSimple> term, - const SearchContextParams ¶ms) const override; - virtual const IDocumentWeightAttribute *asDocumentWeightAttribute() const override; - virtual const tensor::ITensorAttribute *asTensorAttribute() const override; - virtual BasicType::Type getBasicType() const override; - virtual size_t getFixedWidth() const override; - virtual CollectionType::Type getCollectionType() const override; - virtual bool hasEnum() const override; - virtual bool getIsFilter() const override; - virtual bool getIsFastSearch() const override; - virtual uint32_t getCommittedDocIdLimit() const override; - virtual bool isImported() const override; + const char * getStringFromEnum(EnumHandle e) const override; + std::unique_ptr<ISearchContext> createSearchContext(std::unique_ptr<QueryTermSimple> term, + const SearchContextParams ¶ms) const override; + const IDocumentWeightAttribute *asDocumentWeightAttribute() const override; + const tensor::ITensorAttribute *asTensorAttribute() const override; + BasicType::Type getBasicType() const override; + size_t getFixedWidth() const override; + CollectionType::Type getCollectionType() const override; + bool hasEnum() const override; + bool getIsFilter() const override; + bool getIsFastSearch() const override; + uint32_t getCommittedDocIdLimit() const override; + bool isImported() const override; protected: - virtual long onSerializeForAscendingSort(DocId doc, void * serTo, long available, - const common::BlobConverter * bc) const override; - virtual long onSerializeForDescendingSort(DocId doc, void * serTo, long available, - const common::BlobConverter * bc) const override; + long onSerializeForAscendingSort(DocId doc, void * serTo, long available, + const common::BlobConverter * bc) const override; + long onSerializeForDescendingSort(DocId doc, void * serTo, long available, + const common::BlobConverter * bc) const override; }; } diff --git a/searchlib/src/vespa/searchlib/engine/CMakeLists.txt b/searchlib/src/vespa/searchlib/engine/CMakeLists.txt index 95b47b991af..e475d786a60 100644 --- a/searchlib/src/vespa/searchlib/engine/CMakeLists.txt +++ b/searchlib/src/vespa/searchlib/engine/CMakeLists.txt @@ -13,6 +13,7 @@ vespa_add_library(searchlib_engine OBJECT searchreply.cpp searchrequest.cpp source_description.cpp + trace.cpp transport_metrics.cpp transportserver.cpp DEPENDS diff --git a/searchlib/src/vespa/searchlib/engine/packetconverter.cpp b/searchlib/src/vespa/searchlib/engine/packetconverter.cpp index e6fb37223d6..d87d9888aae 100644 --- a/searchlib/src/vespa/searchlib/engine/packetconverter.cpp +++ b/searchlib/src/vespa/searchlib/engine/packetconverter.cpp @@ -1,9 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "packetconverter.h" +#include <vespa/searchlib/fef/indexproperties.h> + #include <vespa/log/log.h> LOG_SETUP(".engine.packetconverter"); +using search::fef::Property; +using search::fef::Properties; + namespace { bool checkFeature(uint32_t features, uint32_t mask) { @@ -14,8 +19,8 @@ struct FS4PropertiesBuilder : public search::fef::IPropertiesVisitor { uint32_t idx; search::fs4transport::FS4Properties &props; FS4PropertiesBuilder(search::fs4transport::FS4Properties &p) : idx(0), props(p) {} - void visitProperty(const search::fef::Property::Value &key, - const search::fef::Property &values) override + void visitProperty(const Property::Value &key, + const Property &values) override { for (uint32_t i = 0; i < values.size(); ++i) { props.setKey(idx, key.data(), key.size()); @@ -39,7 +44,7 @@ PacketConverter::fillPacketProperties(const PropertiesMap &source, PropsVector& PropertiesMap::ITR end = source.end(); for (uint32_t i = 0; itr != end; ++itr, ++i) { const vespalib::string &name = itr->first; - const search::fef::Properties &values = itr->second; + const Properties &values = itr->second; target[i].setName(name.c_str(), name.size()); target[i].allocEntries(values.numValues()); FS4PropertiesBuilder builder(target[i]); @@ -58,9 +63,8 @@ PacketConverter::toSearchRequest(const QUERYX &packet, SearchRequest &request) request.queryFlags = packet.getQueryFlags(); request.ranking = packet._ranking; - for (uint32_t i = 0; i < packet._propsVector.size(); ++i) { - const FS4Properties &src = packet._propsVector[i]; - search::fef::Properties &dst = request.propertiesMap.lookupCreate(src.getName()); + for (const FS4Properties &src : packet._propsVector) { + Properties &dst = request.propertiesMap.lookupCreate(src.getName()); for (uint32_t e = 0; e < src.size(); ++e) { dst.add(vespalib::stringref(src.getKey(e), src.getKeyLen(e)), vespalib::stringref(src.getValue(e), src.getValueLen(e))); @@ -72,6 +76,7 @@ PacketConverter::toSearchRequest(const QUERYX &packet, SearchRequest &request) request.location = packet._location; request.stackItems = packet._numStackItems; request.stackDump.assign( packet._stackDump.begin(), packet._stackDump.end()); + request.setTraceLevel(search::fef::indexproperties::trace::Level::lookup(request.propertiesMap.modelOverrides())); } void @@ -100,7 +105,7 @@ PacketConverter::fromSearchReply(const SearchReply &reply, QUERYRESULTX &packet) packet._totNumDocs = reply.totalHitCount; packet._maxRank = reply.maxRank; packet.setDistributionKey(reply.getDistributionKey()); - if (reply.sortIndex.size() > 0) { + if ( ! reply.sortIndex.empty()) { packet._features |= QRF_SORTDATA; uint32_t idxCnt = reply.sortIndex.size(); LOG_ASSERT(reply.sortIndex.size() == reply.hits.size()+1); @@ -112,7 +117,7 @@ PacketConverter::fromSearchReply(const SearchReply &reply, QUERYRESULTX &packet) } memcpy(packet._sortData, &(reply.sortData[0]), reply.sortData.size()); } - if (reply.groupResult.size() > 0) { + if ( ! reply.groupResult.empty()) { packet._features |= QRF_GROUPDATA; packet.AllocateGroupData(reply.groupResult.size()); memcpy(packet._groupData, &(reply.groupResult[0]), reply.groupResult.size()); @@ -123,8 +128,10 @@ PacketConverter::fromSearchReply(const SearchReply &reply, QUERYRESULTX &packet) packet._coverageDegradeReason = reply.coverage.getDegradeReason(); packet.setNodesQueried(reply.coverage.getNodesQueried()); packet.setNodesReplied(reply.coverage.getNodesReplied()); - if (reply.request && (reply.request->queryFlags & QFLAG_COVERAGE_NODES)) { - packet._features |= QRF_COVERAGE_NODES; + if (reply.request) { + if (reply.request->queryFlags & QFLAG_COVERAGE_NODES) { + packet._features |= QRF_COVERAGE_NODES; + } } if (reply.useWideHits) { packet._features |= QRF_MLD; @@ -150,9 +157,8 @@ PacketConverter::toDocsumRequest(const GETDOCSUMSX &packet, DocsumRequest &reque request.ranking = packet._ranking; request.queryFlags = packet._qflags; request.resultClassName = packet._resultClassName; - for (uint32_t i = 0; i < packet._propsVector.size(); ++i) { - const FS4Properties &src = packet._propsVector[i]; - search::fef::Properties &dst = request.propertiesMap.lookupCreate(src.getName()); + for (const FS4Properties &src : packet._propsVector) { + Properties &dst = request.propertiesMap.lookupCreate(src.getName()); for (uint32_t e = 0; e < src.size(); ++e) { dst.add(vespalib::stringref(src.getKey(e), src.getKeyLen(e)), vespalib::stringref(src.getValue(e), src.getValueLen(e))); @@ -169,8 +175,7 @@ PacketConverter::toDocsumRequest(const GETDOCSUMSX &packet, DocsumRequest &reque request.hits[i].gid = packet._docid[i]._gid; request.hits[i].path = packet._docid[i]._partid; } - search::fef::Property sessionId = - request.propertiesMap.rankProperties().lookup("sessionId"); + Property sessionId = request.propertiesMap.rankProperties().lookup("sessionId"); if (sessionId.found()) { vespalib::string id = sessionId.get(); request.sessionId.assign(id.begin(), id.end()); @@ -198,7 +203,7 @@ PacketConverter::toDocsumReplyElement(const DOCSUM &packet, DocsumReply::Docsum void PacketConverter::fromDocsumReplyElement(const DocsumReply::Docsum &docsum, DOCSUM &packet) { - if (docsum.data.get() != 0) { + if (docsum.data.get() != nullptr) { packet.SetBuf(docsum.data.c_str(), docsum.data.size()); } packet.setGid(docsum.gid); diff --git a/searchlib/src/vespa/searchlib/engine/propertiesmap.cpp b/searchlib/src/vespa/searchlib/engine/propertiesmap.cpp index b09813c0258..4726a3b9c27 100644 --- a/searchlib/src/vespa/searchlib/engine/propertiesmap.cpp +++ b/searchlib/src/vespa/searchlib/engine/propertiesmap.cpp @@ -11,7 +11,7 @@ PropertiesMap::PropertiesMap() : _propertiesMap() { } -PropertiesMap::~PropertiesMap() { } +PropertiesMap::~PropertiesMap() = default; fef::Properties & PropertiesMap::lookupCreate(vespalib::stringref name) diff --git a/searchlib/src/vespa/searchlib/engine/request.cpp b/searchlib/src/vespa/searchlib/engine/request.cpp index 8f4aa427988..fd4a46ccc43 100644 --- a/searchlib/src/vespa/searchlib/engine/request.cpp +++ b/searchlib/src/vespa/searchlib/engine/request.cpp @@ -8,12 +8,14 @@ namespace search::engine { Request::Request(const fastos::TimeStamp &start_time) : _startTime(start_time), _timeOfDoom(fastos::TimeStamp(fastos::TimeStamp::FUTURE)), - ranking(), + _traceLevel(0), queryFlags(0), + ranking(), location(), propertiesMap(), stackItems(0), - stackDump() + stackDump(), + _trace(start_time) { } diff --git a/searchlib/src/vespa/searchlib/engine/request.h b/searchlib/src/vespa/searchlib/engine/request.h index 02ab75fe509..521a5be39fc 100644 --- a/searchlib/src/vespa/searchlib/engine/request.h +++ b/searchlib/src/vespa/searchlib/engine/request.h @@ -3,6 +3,7 @@ #pragma once #include "propertiesmap.h" +#include "trace.h" #include <vespa/fastos/timestamp.h> namespace search::engine { @@ -11,6 +12,8 @@ class Request { public: Request(const fastos::TimeStamp &start_time); + Request(const Request &) = delete; + Request & operator =(const Request &) = delete; virtual ~Request(); void setTimeout(const fastos::TimeStamp & timeout); fastos::TimeStamp getStartTime() const { return _startTime; } @@ -26,17 +29,24 @@ public: bool should_drop_sort_data() const; + uint32_t getTraceLevel() const { return _traceLevel; } + Request & setTraceLevel(uint32_t traceLevel) { _traceLevel = traceLevel; return *this; } + + Trace & trace() const { return _trace; } private: const fastos::TimeStamp _startTime; fastos::TimeStamp _timeOfDoom; + uint32_t _traceLevel; public: /// Everything here should move up to private section and have accessors - vespalib::string ranking; uint32_t queryFlags; + vespalib::string ranking; vespalib::string location; PropertiesMap propertiesMap; uint32_t stackItems; std::vector<char> stackDump; +private: + mutable Trace _trace; }; } diff --git a/searchlib/src/vespa/searchlib/engine/searchrequest.cpp b/searchlib/src/vespa/searchlib/engine/searchrequest.cpp index 7159c83716b..b558e07480d 100644 --- a/searchlib/src/vespa/searchlib/engine/searchrequest.cpp +++ b/searchlib/src/vespa/searchlib/engine/searchrequest.cpp @@ -17,20 +17,20 @@ SearchRequest::SearchRequest(const fastos::TimeStamp &start_time) sessionId() { } -SearchRequest::~SearchRequest() {} +SearchRequest::~SearchRequest() = default; void SearchRequest::Source::lazyDecode() const { - if ((_request.get() == NULL) && (_fs4Packet != NULL)) { - _request.reset(new SearchRequest(_start)); + if (!_request && (_fs4Packet != nullptr)) { + _request = std::make_unique<SearchRequest>(_start); PacketConverter::toSearchRequest(*_fs4Packet, *_request); _fs4Packet->Free(); - _fs4Packet = NULL; + _fs4Packet = nullptr; } } SearchRequest::Source::~Source() { - if (_fs4Packet != NULL) { + if (_fs4Packet != nullptr) { _fs4Packet->Free(); } } diff --git a/searchlib/src/vespa/searchlib/engine/trace.cpp b/searchlib/src/vespa/searchlib/engine/trace.cpp new file mode 100644 index 00000000000..e12fb9affa6 --- /dev/null +++ b/searchlib/src/vespa/searchlib/engine/trace.cpp @@ -0,0 +1,31 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "trace.h" +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/fastos/timestamp.h> + +namespace search::engine { + +Trace::Trace(const fastos::TimeStamp &start_time) + : _trace(std::make_unique<vespalib::Slime>()), + _root(_trace->setObject()), + _traces(_root.setArray("traces")) +{ + _root.setLong("creation_time", start_time); +} + +Trace::~Trace() = default; + +Trace::Cursor & +Trace::createCursor(vespalib::stringref name) { + Cursor & trace = _traces.addObject(); + trace.setString("tag", name); + return trace; +} + +vespalib::string +Trace::toString() const { + return _trace->toString(); +} + +} diff --git a/searchlib/src/vespa/searchlib/engine/trace.h b/searchlib/src/vespa/searchlib/engine/trace.h new file mode 100644 index 00000000000..09d61c0f199 --- /dev/null +++ b/searchlib/src/vespa/searchlib/engine/trace.h @@ -0,0 +1,39 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/stllike/string.h> + +namespace fastos { class TimeStamp; } +namespace vespalib { class Slime; } +namespace vespalib::slime { class Cursor; } + +namespace search::engine { + + /** + * Used for adding traces to a request. Acquire a new Cursor for everytime you want to trace something. + * Note that it is not thread safe. All use of any cursor aquired must be thread safe. + */ +class Trace +{ +public: + using Cursor = vespalib::slime::Cursor; + Trace(const fastos::TimeStamp &start_time); + ~Trace(); + + /** + * Will give you a trace entry. It will also add a timestamp relative to the creation of the trace. + * @param name + * @return a Cursor to use for further tracing. + */ + Cursor & createCursor(vespalib::stringref name); + vespalib::string toString() const; + Cursor & getRoot() const { return _root; } + vespalib::Slime & getSlime() const { return *_trace; } +private: + std::unique_ptr<vespalib::Slime> _trace; + Cursor & _root; + Cursor & _traces; +}; + +} diff --git a/searchlib/src/vespa/searchlib/fef/indexproperties.cpp b/searchlib/src/vespa/searchlib/fef/indexproperties.cpp index 5cd6c479d24..fd73ce5a0f0 100644 --- a/searchlib/src/vespa/searchlib/fef/indexproperties.cpp +++ b/searchlib/src/vespa/searchlib/fef/indexproperties.cpp @@ -388,6 +388,24 @@ DiversityCutoffStrategy::lookup(const Properties &props, const vespalib::string return lookupString(props, NAME, defaultValue); } +} + +namespace trace { + +const vespalib::string Level::NAME("tracelevel"); +const uint32_t Level::DEFAULT_VALUE(0); + +uint32_t +Level::lookup(const Properties &props) +{ + return lookup(props, DEFAULT_VALUE); +} + +uint32_t +Level::lookup(const Properties &props, uint32_t defaultValue) +{ + return lookupUint32(props, NAME, defaultValue); +} } diff --git a/searchlib/src/vespa/searchlib/fef/indexproperties.h b/searchlib/src/vespa/searchlib/fef/indexproperties.h index 8b78e347a90..23efe514d68 100644 --- a/searchlib/src/vespa/searchlib/fef/indexproperties.h +++ b/searchlib/src/vespa/searchlib/fef/indexproperties.h @@ -310,6 +310,20 @@ namespace matchphase { } // namespace matchphase +namespace trace { + + /** + * Property for the heap size used in the hit collector. + **/ + struct Level { + static const vespalib::string NAME; + static const uint32_t DEFAULT_VALUE; + static uint32_t lookup(const Properties &props); + static uint32_t lookup(const Properties &props, uint32_t defaultValue); + }; + +} + namespace hitcollector { diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp index c99e07cd355..3f3b4a2300e 100644 --- a/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.cpp @@ -6,7 +6,9 @@ #include "equiv_blueprint.h" #include <vespa/vespalib/objects/visit.hpp> #include <vespa/vespalib/objects/objectdumper.h> +#include <vespa/vespalib/objects/object2slime.h> #include <vespa/vespalib/util/classname.h> +#include <vespa/vespalib/data/slime/inserter.h> #include <map> #include <vespa/log/log.h> @@ -117,6 +119,15 @@ Blueprint::asString() const return dumper.toString(); } +vespalib::slime::Cursor & +Blueprint::asSlime(const vespalib::slime::Inserter & inserter) const +{ + vespalib::slime::Cursor & cursor = inserter.insertObject(); + vespalib::Object2Slime dumper(cursor); + visit(dumper, "", this); + return cursor; +} + vespalib::string Blueprint::getClassName() const { diff --git a/searchlib/src/vespa/searchlib/queryeval/blueprint.h b/searchlib/src/vespa/searchlib/queryeval/blueprint.h index 165f592867a..4b60e7d0d8b 100644 --- a/searchlib/src/vespa/searchlib/queryeval/blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/blueprint.h @@ -9,7 +9,11 @@ #include <vespa/searchlib/fef/termfieldmatchdata.h> #include <vespa/searchlib/fef/termfieldmatchdataarray.h> -namespace vespalib { class ObjectVisitor; }; +namespace vespalib { class ObjectVisitor; } +namespace vespalib::slime { + class Cursor; + class Inserter; +} namespace search::queryeval { @@ -173,6 +177,7 @@ public: // for debug dumping vespalib::string asString() const; + vespalib::slime::Cursor & asSlime(const vespalib::slime::Inserter & cursor) const; virtual vespalib::string getClassName() const; virtual void visitMembers(vespalib::ObjectVisitor &visitor) const; virtual bool isEquiv() const { return false; } diff --git a/staging_vespalib/src/vespa/vespalib/objects/CMakeLists.txt b/staging_vespalib/src/vespa/vespalib/objects/CMakeLists.txt index 12970a7d39b..f3afda94fe6 100644 --- a/staging_vespalib/src/vespa/vespalib/objects/CMakeLists.txt +++ b/staging_vespalib/src/vespa/vespalib/objects/CMakeLists.txt @@ -5,6 +5,7 @@ vespa_add_library(staging_vespalib_vespalib_objects OBJECT namedobject.cpp objectvisitor.cpp objectdumper.cpp + object2slime.cpp visit.cpp objectpredicate.cpp objectoperation.cpp diff --git a/staging_vespalib/src/vespa/vespalib/objects/object2slime.cpp b/staging_vespalib/src/vespa/vespalib/objects/object2slime.cpp new file mode 100644 index 00000000000..562f332fb89 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/objects/object2slime.cpp @@ -0,0 +1,76 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "object2slime.h" +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/vespalib/data/slime/cursor.h> + +namespace vespalib { + +Object2Slime::Object2Slime(slime::Cursor & cursor) + : _cursor(cursor), + _stack() +{ +} + +Object2Slime::~Object2Slime() = default; + +//----------------------------------------------------------------------------- + +void +Object2Slime::openStruct(const vespalib::string &name, const vespalib::string &type) +{ + if (name.empty()) { + _cursor.get().setString("[type]", type); + } else { + _stack.push_back(_cursor); + _cursor = _cursor.get().setObject(name); + _cursor.get().setString("[type]", type); + } +} + +void +Object2Slime::closeStruct() +{ + if ( ! _stack.empty()) { + _cursor = _stack.back(); + _stack.pop_back(); + } +} + +void +Object2Slime::visitBool(const vespalib::string &name, bool value) +{ + _cursor.get().setBool(name, value); +} + +void +Object2Slime::visitInt(const vespalib::string &name, int64_t value) +{ + _cursor.get().setLong(name, value); +} + +void +Object2Slime::visitFloat(const vespalib::string &name, double value) +{ + _cursor.get().setDouble(name, value); +} + +void +Object2Slime::visitString(const vespalib::string &name, const vespalib::string &value) +{ + _cursor.get().setString(name, value); +} + +void +Object2Slime::visitNull(const vespalib::string &name) +{ + _cursor.get().setNix(name); +} + +void +Object2Slime::visitNotImplemented() +{ + _cursor.get().setNix("not_implemented"); +} + +} diff --git a/staging_vespalib/src/vespa/vespalib/objects/object2slime.h b/staging_vespalib/src/vespa/vespalib/objects/object2slime.h new file mode 100644 index 00000000000..4c1ee660502 --- /dev/null +++ b/staging_vespalib/src/vespa/vespalib/objects/object2slime.h @@ -0,0 +1,36 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "objectvisitor.h" +#include <vector> + +namespace vespalib { + +namespace slime { class Cursor; } + +/** + * This is a concrete object visitor that will build up a structured + * slime representation of an object. + **/ +class Object2Slime : public ObjectVisitor +{ +private: + std::reference_wrapper<slime::Cursor> _cursor; + std::vector<std::reference_wrapper<slime::Cursor>> _stack; + +public: + Object2Slime(slime::Cursor & cursor); + ~Object2Slime(); + + void openStruct(const vespalib::string &name, const vespalib::string &type) override; + void closeStruct() override; + void visitBool(const vespalib::string &name, bool value) override; + void visitInt(const vespalib::string &name, int64_t value) override; + void visitFloat(const vespalib::string &name, double value) override; + void visitString(const vespalib::string &name, const vespalib::string &value) override; + void visitNull(const vespalib::string &name) override; + void visitNotImplemented() override; +}; + +} // namespace vespalib diff --git a/staging_vespalib/src/vespa/vespalib/objects/objectdumper.cpp b/staging_vespalib/src/vespa/vespalib/objects/objectdumper.cpp index 3237996aafd..67ce43bb0c0 100644 --- a/staging_vespalib/src/vespa/vespalib/objects/objectdumper.cpp +++ b/staging_vespalib/src/vespa/vespalib/objects/objectdumper.cpp @@ -41,8 +41,7 @@ ObjectDumper::ObjectDumper(int indent) { } -ObjectDumper::~ObjectDumper() { -} +ObjectDumper::~ObjectDumper() = default; //----------------------------------------------------------------------------- diff --git a/staging_vespalib/src/vespa/vespalib/objects/objectdumper.h b/staging_vespalib/src/vespa/vespalib/objects/objectdumper.h index 27f37bf839a..e517c75deec 100644 --- a/staging_vespalib/src/vespa/vespalib/objects/objectdumper.h +++ b/staging_vespalib/src/vespa/vespalib/objects/objectdumper.h @@ -60,15 +60,14 @@ public: **/ vespalib::string toString() const { return _str; } - virtual void openStruct(const vespalib::string &name, const vespalib::string &type) override; - virtual void closeStruct() override; - virtual void visitBool(const vespalib::string &name, bool value) override; - virtual void visitInt(const vespalib::string &name, int64_t value) override; - virtual void visitFloat(const vespalib::string &name, double value) override; - virtual void visitString(const vespalib::string &name, const vespalib::string &value) override; - virtual void visitNull(const vespalib::string &name) override; - virtual void visitNotImplemented() override; + void openStruct(const vespalib::string &name, const vespalib::string &type) override; + void closeStruct() override; + void visitBool(const vespalib::string &name, bool value) override; + void visitInt(const vespalib::string &name, int64_t value) override; + void visitFloat(const vespalib::string &name, double value) override; + void visitString(const vespalib::string &name, const vespalib::string &value) override; + void visitNull(const vespalib::string &name) override; + void visitNotImplemented() override; }; } // namespace vespalib - |