aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/tests
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2022-05-18 11:05:54 +0000
committerHenning Baldersheim <balder@yahoo-inc.com>2022-05-18 11:05:54 +0000
commit39443ba7ffe7966fb06555ef832f4eff3756c076 (patch)
tree5e0a2fd6ab79aa6be435551ea307be9750e69227 /vespalib/src/tests
parent36df8bd3d9fd4ee60aadd04af89199a8bc504e68 (diff)
Move state_server, metrivs and some all executors from staging_vespalib too vespalib.
Diffstat (limited to 'vespalib/src/tests')
-rw-r--r--vespalib/src/tests/encoding/.gitignore3
-rw-r--r--vespalib/src/tests/encoding/base64/.gitignore1
-rw-r--r--vespalib/src/tests/encoding/base64/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/encoding/base64/base64_test.cpp83
-rw-r--r--vespalib/src/tests/fileheader/.gitignore6
-rw-r--r--vespalib/src/tests/fileheader/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/fileheader/fileheader.datbin0 -> 56 bytes
-rw-r--r--vespalib/src/tests/fileheader/fileheader_test.cpp694
-rw-r--r--vespalib/src/tests/metrics/CMakeLists.txt17
-rw-r--r--vespalib/src/tests/metrics/mock_tick.cpp6
-rw-r--r--vespalib/src/tests/metrics/mock_tick.h92
-rw-r--r--vespalib/src/tests/metrics/simple_metrics_test.cpp219
-rw-r--r--vespalib/src/tests/metrics/stable_store_test.cpp65
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/.gitignore4
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt31
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp248
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp120
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp73
-rw-r--r--vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp351
-rw-r--r--vespalib/src/tests/singleexecutor/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/singleexecutor/singleexecutor_test.cpp114
-rw-r--r--vespalib/src/tests/state_server/.gitignore1
-rw-r--r--vespalib/src/tests/state_server/CMakeLists.txt8
-rw-r--r--vespalib/src/tests/state_server/state_server_test.cpp516
-rw-r--r--vespalib/src/tests/trace/CMakeLists.txt4
25 files changed, 2678 insertions, 2 deletions
diff --git a/vespalib/src/tests/encoding/.gitignore b/vespalib/src/tests/encoding/.gitignore
new file mode 100644
index 00000000000..a3e9c375723
--- /dev/null
+++ b/vespalib/src/tests/encoding/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+*_test
diff --git a/vespalib/src/tests/encoding/base64/.gitignore b/vespalib/src/tests/encoding/base64/.gitignore
new file mode 100644
index 00000000000..bd63ed7e5cb
--- /dev/null
+++ b/vespalib/src/tests/encoding/base64/.gitignore
@@ -0,0 +1 @@
+vespalib_base64_test_app
diff --git a/vespalib/src/tests/encoding/base64/CMakeLists.txt b/vespalib/src/tests/encoding/base64/CMakeLists.txt
new file mode 100644
index 00000000000..e2bb5d83fbe
--- /dev/null
+++ b/vespalib/src/tests/encoding/base64/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_base64_test_app TEST
+ SOURCES
+ base64_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_base64_test_app COMMAND vespalib_base64_test_app)
diff --git a/vespalib/src/tests/encoding/base64/base64_test.cpp b/vespalib/src/tests/encoding/base64/base64_test.cpp
new file mode 100644
index 00000000000..295aad7ffdd
--- /dev/null
+++ b/vespalib/src/tests/encoding/base64/base64_test.cpp
@@ -0,0 +1,83 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/encoding/base64.h>
+#include <vector>
+
+using namespace vespalib;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("base64_test");
+
+ // Basic test without padding
+ std::string source = "No need to pad this string.";
+ std::string encoded = Base64::encode(source);
+ std::string expected = "Tm8gbmVlZCB0byBwYWQgdGhpcyBzdHJpbmcu";
+ std::string decoded = Base64::decode(encoded);
+
+ EXPECT_EQUAL(expected, encoded);
+ EXPECT_EQUAL(source, decoded);
+
+ EXPECT_TRUE(static_cast<uint32_t>(
+ Base64::getMaximumEncodeLength(source.size())) >= encoded.size());
+ EXPECT_TRUE(static_cast<uint32_t>(
+ Base64::getMaximumDecodeLength(encoded.size())) >= source.size());
+
+ // Basic string that needs padding
+ source = "This string will need to be padded.";
+ encoded = Base64::encode(source);
+ expected = "VGhpcyBzdHJpbmcgd2lsbCBuZWVkIHRvIGJlIHBhZGRlZC4=";
+ decoded = Base64::decode(encoded);
+
+ EXPECT_EQUAL(expected, encoded);
+ EXPECT_EQUAL(source, decoded);
+
+ EXPECT_TRUE(static_cast<uint32_t>(
+ Base64::getMaximumEncodeLength(source.size())) >= encoded.size());
+ EXPECT_TRUE(static_cast<uint32_t>(
+ Base64::getMaximumDecodeLength(encoded.size())) >= source.size());
+
+ // Check that max sizes are good for whatever input sizes
+ source = "";
+ for (uint32_t i=0; i<100; ++i) {
+ source += "a";
+ // Code will assert if -1 is returned from either
+ // getMaximumEncodeLength() or getMaximumDecodeLength().
+ encoded = Base64::encode(source);
+ decoded = Base64::decode(encoded);
+ EXPECT_EQUAL(source, decoded);
+ }
+
+ // Check that -1 is returned on too little space when encoding
+ source = "Checking that -1 is returned when not enough space to encode";
+ std::vector<char> buffer(100, '\0');
+ uint32_t minSizeNeeded = 81;
+ for (uint32_t i=0; i<minSizeNeeded; ++i) {
+ EXPECT_EQUAL(-1, Base64::encode(source.c_str(), source.size(),
+ &buffer[0], i));
+ }
+ EXPECT_EQUAL(80, Base64::encode(source.c_str(), source.size(),
+ &buffer[0], minSizeNeeded));
+ EXPECT_EQUAL(Base64::encode(source), std::string(&buffer[0], 80));
+ EXPECT_TRUE(minSizeNeeded <= static_cast<uint32_t>(
+ Base64::getMaximumEncodeLength(source.size())));
+
+ EXPECT_TRUE(buffer[80] == '\0');
+
+ // Check that -1 is returned on too little space when decoding
+ encoded = Base64::encode(source);
+ minSizeNeeded = 60;
+ for (uint32_t i=0; i<minSizeNeeded; ++i) {
+ EXPECT_EQUAL(-1, Base64::decode(encoded.c_str(), encoded.size(),
+ &buffer[0], i));
+ }
+ EXPECT_EQUAL(60, Base64::decode(encoded.c_str(), encoded.size(),
+ &buffer[0], minSizeNeeded));
+ EXPECT_EQUAL(source, std::string(&buffer[0], 60));
+
+ TEST_DONE();
+}
diff --git a/vespalib/src/tests/fileheader/.gitignore b/vespalib/src/tests/fileheader/.gitignore
new file mode 100644
index 00000000000..f41a6844d34
--- /dev/null
+++ b/vespalib/src/tests/fileheader/.gitignore
@@ -0,0 +1,6 @@
+*.So
+.depend*
+Makefile
+fileheader.tmp
+fileheader_test
+vespalib_fileheader_test_app
diff --git a/vespalib/src/tests/fileheader/CMakeLists.txt b/vespalib/src/tests/fileheader/CMakeLists.txt
new file mode 100644
index 00000000000..a58507e818e
--- /dev/null
+++ b/vespalib/src/tests/fileheader/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_fileheader_test_app TEST
+ SOURCES
+ fileheader_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_fileheader_test_app COMMAND vespalib_fileheader_test_app)
diff --git a/vespalib/src/tests/fileheader/fileheader.dat b/vespalib/src/tests/fileheader/fileheader.dat
new file mode 100644
index 00000000000..90660f64b98
--- /dev/null
+++ b/vespalib/src/tests/fileheader/fileheader.dat
Binary files differ
diff --git a/vespalib/src/tests/fileheader/fileheader_test.cpp b/vespalib/src/tests/fileheader/fileheader_test.cpp
new file mode 100644
index 00000000000..21e374e4f62
--- /dev/null
+++ b/vespalib/src/tests/fileheader/fileheader_test.cpp
@@ -0,0 +1,694 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/data/fileheader.h>
+#include <vespa/vespalib/data/databuffer.h>
+#include <vespa/fastos/file.h>
+
+using namespace vespalib;
+
+class Test : public vespalib::TestApp {
+private:
+ void testTag();
+ void testTagErrors();
+ void testTagIteration();
+ void testGenericHeader();
+ void testBufferReader();
+ void testBufferWriter();
+ void testBufferAccess();
+ void testFileReader();
+ void testFileWriter();
+ void testFileHeader();
+ void testFileAlign();
+ void testFileSize();
+ void testReadErrors();
+ bool testReadError(DataBuffer &buf, const std::string &expected);
+ void testWriteErrors();
+ void testRewriteErrors();
+ void testLayout();
+
+ void testReadSize(bool mapped);
+ void testReadSizeErrors(bool mapped);
+ bool testReadSizeError(DataBuffer &buf, const std::string &expected, bool mapped);
+
+public:
+ int Main() override {
+ TEST_INIT("fileheader_test");
+
+ testTag(); TEST_FLUSH();
+ testTagErrors(); TEST_FLUSH();
+ testTagIteration(); TEST_FLUSH();
+ testGenericHeader(); TEST_FLUSH();
+ testBufferReader(); TEST_FLUSH();
+ testBufferWriter(); TEST_FLUSH();
+ testBufferAccess(); TEST_FLUSH();
+ testFileReader(); TEST_FLUSH();
+ testFileWriter(); TEST_FLUSH();
+ testFileHeader(); TEST_FLUSH();
+ testFileAlign(); TEST_FLUSH();
+ testFileSize(); TEST_FLUSH();
+ testReadErrors(); TEST_FLUSH();
+ testWriteErrors(); TEST_FLUSH();
+ testRewriteErrors(); TEST_FLUSH();
+ testLayout(); TEST_FLUSH();
+ testReadSize(false); TEST_FLUSH();
+ testReadSizeErrors(false); TEST_FLUSH();
+ testReadSize(true); TEST_FLUSH();
+ testReadSizeErrors(true); TEST_FLUSH();
+
+ TEST_DONE();
+ }
+};
+
+TEST_APPHOOK(Test);
+
+void
+Test::testTag()
+{
+ {
+ std::vector<GenericHeader::Tag> tags;
+ tags.push_back(GenericHeader::Tag("foo", 6.9));
+ tags.push_back(GenericHeader::Tag("foo", 6.9f));
+ for (std::vector<GenericHeader::Tag>::iterator it = tags.begin();
+ it != tags.end(); ++it)
+ {
+ GenericHeader::Tag tag = *it;
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_FLOAT, tag.getType());
+ EXPECT_EQUAL("foo", tag.getName());
+ EXPECT_TRUE(tag.asString().empty());
+ EXPECT_APPROX(6.9, tag.asFloat(), 1E-6);
+ EXPECT_EQUAL(0, tag.asInteger());
+
+ uint32_t len = tag.getSize();
+ DataBuffer buf(len);
+ EXPECT_EQUAL(len, tag.write(buf));
+
+ GenericHeader::Tag tmp;
+ EXPECT_EQUAL(len, tmp.read(buf));
+ tag = tmp;
+ }
+ }
+ }
+ {
+ std::vector<GenericHeader::Tag> tags;
+ tags.push_back(GenericHeader::Tag("foo", (int8_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (uint8_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (int16_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (uint16_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (int32_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (uint32_t)69));
+ tags.push_back(GenericHeader::Tag("foo", (int64_t)69));
+ for (std::vector<GenericHeader::Tag>::iterator it = tags.begin();
+ it != tags.end(); ++it)
+ {
+ GenericHeader::Tag tag = *it;
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, tag.getType());
+ EXPECT_EQUAL("foo", tag.getName());
+ EXPECT_TRUE(tag.asString().empty());
+ EXPECT_EQUAL(0.0, tag.asFloat());
+ EXPECT_EQUAL(69l, tag.asInteger());
+
+ uint32_t len = tag.getSize();
+ DataBuffer buf(len);
+ EXPECT_EQUAL(len, tag.write(buf));
+
+ GenericHeader::Tag tmp;
+ EXPECT_EQUAL(len, tmp.read(buf));
+ tag = tmp;
+ }
+ }
+ }
+ {
+ GenericHeader::Tag tag("foo", "bar");
+ for (uint32_t i = 0; i < 2; ++i) {
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_STRING, tag.getType());
+ EXPECT_EQUAL("foo", tag.getName());
+ EXPECT_EQUAL("bar", tag.asString());
+ EXPECT_EQUAL(0.0, tag.asFloat());
+ EXPECT_EQUAL(0, tag.asInteger());
+
+ uint32_t len = tag.getSize();
+ DataBuffer buf(len);
+ EXPECT_EQUAL(len, tag.write(buf));
+
+ GenericHeader::Tag tmp;
+ EXPECT_EQUAL(len, tmp.read(buf));
+ tag = tmp;
+ }
+ }
+ {
+ GenericHeader::Tag trueTag("foo", true);
+ GenericHeader::Tag falseTag("foo", false);
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, trueTag.getType());
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, falseTag.getType());
+ EXPECT_EQUAL(1, trueTag.asInteger());
+ EXPECT_EQUAL(0, falseTag.asInteger());
+ EXPECT_TRUE(trueTag.asBool());
+ EXPECT_FALSE(falseTag.asBool());
+ }
+}
+
+void
+Test::testTagErrors()
+{
+ DataBuffer buf(1024);
+ buf.writeBytes("foo", 3);
+ buf.writeInt8(0);
+ buf.writeInt8((uint8_t)GenericHeader::Tag::TYPE_EMPTY);
+
+ GenericHeader::Tag tag("bar", 6.9);
+ try {
+ tag.read(buf);
+ EXPECT_TRUE(false);
+ } catch (IllegalHeaderException &e) {
+ EXPECT_EQUAL("Can not deserialize empty tag.", e.getMessage());
+ }
+ EXPECT_EQUAL("bar", tag.getName());
+ EXPECT_EQUAL(GenericHeader::Tag::TYPE_FLOAT, tag.getType());
+ EXPECT_EQUAL(6.9, tag.asFloat());
+}
+
+void
+Test::testTagIteration()
+{
+ GenericHeader header;
+ header.putTag(GenericHeader::Tag("foo", 6.9));
+ header.putTag(GenericHeader::Tag("bar", 6699));
+ header.putTag(GenericHeader::Tag("baz", "666999"));
+
+ EXPECT_EQUAL(3u, header.getNumTags());
+ EXPECT_EQUAL("bar", header.getTag(0).getName());
+ EXPECT_EQUAL("baz", header.getTag(1).getName());
+ EXPECT_EQUAL("foo", header.getTag(2).getName());
+}
+
+void
+Test::testGenericHeader()
+{
+ GenericHeader header;
+ EXPECT_TRUE(header.isEmpty());
+ EXPECT_EQUAL(0u, header.getNumTags());
+ EXPECT_TRUE(!header.hasTag("foo"));
+ EXPECT_TRUE(header.getTag("foo").isEmpty());
+ EXPECT_TRUE(!header.hasTag("bar"));
+ EXPECT_TRUE(header.getTag("bar").isEmpty());
+ EXPECT_TRUE(!header.hasTag("baz"));
+ EXPECT_TRUE(header.getTag("baz").isEmpty());
+
+ header.putTag(GenericHeader::Tag("foo", 6.9));
+ EXPECT_TRUE(!header.isEmpty());
+ EXPECT_EQUAL(1u, header.getNumTags());
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(!header.hasTag("bar"));
+ EXPECT_TRUE(header.getTag("bar").isEmpty());
+ EXPECT_TRUE(!header.hasTag("baz"));
+ EXPECT_TRUE(header.getTag("baz").isEmpty());
+
+ header.putTag(GenericHeader::Tag("bar", 6699));
+ EXPECT_TRUE(!header.isEmpty());
+ EXPECT_EQUAL(2u, header.getNumTags());
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(6699, header.getTag("bar").asInteger());
+ EXPECT_TRUE(!header.hasTag("baz"));
+ EXPECT_TRUE(header.getTag("baz").isEmpty());
+
+ header.putTag(GenericHeader::Tag("baz", "666999"));
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(6699, header.getTag("bar").asInteger());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+
+ header.removeTag("bar");
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(!header.hasTag("bar"));
+ EXPECT_TRUE(header.getTag("bar").isEmpty());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+
+ header.removeTag("foo");
+ EXPECT_TRUE(!header.hasTag("foo"));
+ EXPECT_TRUE(header.getTag("foo").isEmpty());
+ EXPECT_TRUE(!header.hasTag("bar"));
+ EXPECT_TRUE(header.getTag("bar").isEmpty());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+
+ header.removeTag("baz");
+ EXPECT_TRUE(!header.hasTag("foo"));
+ EXPECT_TRUE(header.getTag("foo").isEmpty());
+ EXPECT_TRUE(!header.hasTag("bar"));
+ EXPECT_TRUE(header.getTag("bar").isEmpty());
+ EXPECT_TRUE(!header.hasTag("baz"));
+ EXPECT_TRUE(header.getTag("baz").isEmpty());
+}
+
+void
+Test::testBufferReader()
+{
+ DataBuffer src(256);
+ for (uint32_t i = 0; i < 256; ++i) {
+ src.writeInt8((uint8_t)i);
+ }
+
+ GenericHeader::BufferReader reader(src);
+
+ char dst[7];
+ uint32_t sum = 0;
+ while (sum < 256) {
+ uint32_t len = (uint32_t)reader.getData(dst, 7);
+ for (uint32_t i = 0; i < len; ++i) {
+ EXPECT_EQUAL(sum + i, (uint8_t)dst[i]);
+ }
+ sum += len;
+ }
+ EXPECT_EQUAL(256u, sum);
+}
+
+void
+Test::testBufferWriter()
+{
+ DataBuffer dst(256);
+ GenericHeader::BufferWriter writer(dst);
+
+ uint32_t sum = 0;
+ while(sum < 256) {
+ char src[7];
+ for (uint32_t i = 0; i < 7; ++i) {
+ src[i] = (uint8_t)(sum + i);
+ }
+ uint32_t len = std::min(7u, 256 - sum);
+ EXPECT_EQUAL(len, (uint32_t)writer.putData(src, len));
+ sum += len;
+ }
+ EXPECT_EQUAL(256u, sum);
+
+ // flip dst
+ for (uint32_t i = 0; i < 256; ++i) {
+ uint8_t b = dst.readInt8();
+ EXPECT_EQUAL(i, (uint32_t)b);
+ }
+}
+
+void
+Test::testBufferAccess()
+{
+ DataBuffer buf;
+ uint32_t len;
+ {
+ GenericHeader header;
+ header.putTag(GenericHeader::Tag("foo", 6.9));
+ header.putTag(GenericHeader::Tag("bar", 6699));
+ header.putTag(GenericHeader::Tag("baz", "666999"));
+
+ int64_t bval = 0x1234567890abcdefLL;
+ header.putTag(GenericHeader::Tag("big", bval));
+
+ len = header.getSize();
+ buf.ensureFree(len);
+ GenericHeader::BufferWriter writer(buf);
+ EXPECT_EQUAL(len, header.write(writer));
+ }
+ {
+ GenericHeader header;
+ GenericHeader::BufferReader reader(buf);
+ EXPECT_EQUAL(len, header.read(reader));
+
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(6699, header.getTag("bar").asInteger());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+ EXPECT_TRUE(header.hasTag("big"));
+ EXPECT_EQUAL(0x1234567890abcdefLL, header.getTag("big").asInteger());
+ }
+}
+
+void
+Test::testFileReader()
+{
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp"));
+
+ uint8_t buf[256];
+ for (uint32_t i = 0; i < 256; ++i) {
+ buf[i] = (uint8_t)i;
+ }
+ EXPECT_EQUAL(256, file.Write2(buf, 256));
+ }
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp"));
+ FileHeader::FileReader reader(file);
+
+ char buf[7];
+ uint32_t sum = 0;
+ while(sum < 256) {
+ uint32_t len = (uint32_t)reader.getData(buf, 7);
+ for (uint32_t i = 0; i < len; ++i) {
+ EXPECT_EQUAL(sum + i, (uint8_t)buf[i]);
+ }
+ sum += len;
+ }
+ EXPECT_EQUAL(256u, sum);
+
+ ASSERT_TRUE(file.Close());
+ file.Delete();
+ }
+}
+
+void
+Test::testFileWriter()
+{
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp"));
+ FileHeader::FileWriter writer(file);
+
+ uint32_t sum = 0;
+ while(sum < 256) {
+ char src[7];
+ for (uint32_t i = 0; i < 7; ++i) {
+ src[i] = (uint8_t)(sum + i);
+ }
+ uint32_t len = std::min(7u, 256 - sum);
+ EXPECT_EQUAL(len, (uint32_t)writer.putData(src, len));
+ sum += len;
+ }
+ EXPECT_EQUAL(256u, sum);
+ }
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp"));
+
+ uint8_t buf[256];
+ EXPECT_EQUAL(256, file.Read(buf, 256));
+ for (uint32_t i = 0; i < 256; ++i) {
+ EXPECT_EQUAL(i, (uint32_t)buf[i]);
+ }
+
+ ASSERT_TRUE(file.Close());
+ file.Delete();
+ }
+}
+
+void
+Test::testFileHeader()
+{
+ uint32_t len = 0;
+ {
+ FileHeader header;
+ header.putTag(FileHeader::Tag("foo", 6.9));
+ header.putTag(FileHeader::Tag("bar", 6699));
+ header.putTag(FileHeader::Tag("baz", "666999"));
+
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp"));
+ len = header.writeFile(file);
+ EXPECT_EQUAL(len, header.getSize());
+ }
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenReadWrite("fileheader.tmp"));
+
+ FileHeader header;
+ EXPECT_EQUAL(len, header.readFile(file));
+ EXPECT_EQUAL(len, header.getSize());
+
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(6699, header.getTag("bar").asInteger());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+
+ header.putTag(FileHeader::Tag("foo", 9.6));
+ header.putTag(FileHeader::Tag("bar", 9966));
+ header.putTag(FileHeader::Tag("baz", "999666"));
+ EXPECT_EQUAL(len, header.getSize());
+ EXPECT_EQUAL(len, header.rewriteFile(file));
+ }
+ {
+ FileHeader header;
+
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp"));
+ EXPECT_EQUAL(len, header.readFile(file));
+ EXPECT_EQUAL(len, header.getSize());
+ ASSERT_TRUE(file.Close());
+ file.Delete();
+
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(9.6, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(9966, header.getTag("bar").asInteger());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("999666", header.getTag("baz").asString());
+ }
+}
+
+void
+Test::testFileAlign()
+{
+ for (uint32_t alignTo = 1; alignTo < 16; ++alignTo) {
+ FileHeader header(alignTo);
+ header.putTag(FileHeader::Tag("foo", "bar"));
+ EXPECT_EQUAL(0u, header.getSize() % alignTo);
+ }
+}
+
+void
+Test::testFileSize()
+{
+ for (uint32_t minSize = 0; minSize < 512; ++minSize) {
+ FileHeader header(1u, minSize);
+ header.putTag(FileHeader::Tag("foo", "bar"));
+ EXPECT_TRUE(header.getSize() >= minSize);
+ }
+}
+
+void
+Test::testReadErrors()
+{
+ {
+ DataBuffer buf;
+ EXPECT_TRUE(testReadError(buf, "Failed to read header info."));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(0xDEADBEAF);
+ buf.writeInt32(8);
+ EXPECT_TRUE(testReadError(buf, "Failed to verify magic bits."));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(8);
+ EXPECT_TRUE(testReadError(buf, "Failed to verify header size."));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(16);
+ buf.writeInt32(-1);
+ buf.writeInt32(0);
+ EXPECT_TRUE(testReadError(buf, "Failed to verify header version."));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(21);
+ buf.writeInt32(GenericHeader::VERSION);
+ buf.writeInt32(1);
+ buf.writeBytes("foo", 3);
+ buf.writeInt8(0);
+ buf.writeInt8((uint8_t)GenericHeader::Tag::TYPE_EMPTY);
+ EXPECT_TRUE(testReadError(buf, "Can not deserialize empty tag."));
+ }
+}
+
+bool
+Test::testReadError(DataBuffer &buf, const std::string &expected)
+{
+ GenericHeader header;
+ header.putTag(GenericHeader::Tag("foo", "bar"));
+ try {
+ GenericHeader::BufferReader reader(buf);
+ header.read(reader);
+ EXPECT_TRUE(false);
+ return false;
+ } catch (IllegalHeaderException &e) {
+ if (!EXPECT_EQUAL(expected, e.getMessage())) {
+ return false;
+ }
+ }
+ if (!EXPECT_EQUAL(1u, header.getNumTags())) {
+ return false;
+ }
+ if (!EXPECT_EQUAL("bar", header.getTag("foo").asString())) {
+ return false;
+ }
+ return true;
+}
+
+void
+Test::testWriteErrors()
+{
+ GenericHeader header;
+ header.putTag(GenericHeader::Tag("foo", 69));
+
+ DataBuffer buf;
+ buf.ensureFree(4);
+ buf.moveFreeToData(buf.getFreeLen() - 4);
+ EXPECT_TRUE(header.getSize() > buf.getFreeLen());
+ try {
+ GenericHeader::BufferWriter writer(buf);
+ header.write(writer);
+ EXPECT_TRUE(false);
+ } catch (IllegalHeaderException &e) {
+ EXPECT_EQUAL("Failed to write header.", e.getMessage());
+ }
+
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(69, header.getTag("foo").asInteger());
+}
+
+void
+Test::testRewriteErrors()
+{
+ FileHeader header;
+ header.putTag(FileHeader::Tag("foo", "bar"));
+ uint32_t len = header.getSize();
+
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp"));
+ EXPECT_EQUAL(len, header.writeFile(file));
+ }
+ {
+ FastOS_File file;
+ ASSERT_TRUE(file.OpenReadWrite("fileheader.tmp"));
+ header.putTag(FileHeader::Tag("baz", "cox"));
+ EXPECT_TRUE(len != header.getSize());
+ try {
+ header.rewriteFile(file);
+ EXPECT_TRUE(false);
+ } catch (IllegalHeaderException &e) {
+ EXPECT_EQUAL("Failed to rewrite resized header.", e.getMessage());
+ }
+ }
+}
+
+void
+Test::testLayout()
+{
+ FileHeader header;
+ {
+ FastOS_File file;
+ const std::string fileName = TEST_PATH("fileheader.dat");
+ ASSERT_TRUE(file.OpenReadOnly(fileName.c_str()));
+ uint32_t len = header.readFile(file);
+ EXPECT_EQUAL(len, header.getSize());
+ }
+
+ EXPECT_TRUE(header.hasTag("foo"));
+ EXPECT_EQUAL(6.9, header.getTag("foo").asFloat());
+ EXPECT_TRUE(header.hasTag("bar"));
+ EXPECT_EQUAL(6699, header.getTag("bar").asInteger());
+ EXPECT_TRUE(header.hasTag("baz"));
+ EXPECT_EQUAL("666999", header.getTag("baz").asString());
+}
+
+
+void
+Test::testReadSize(bool mapped)
+{
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(21);
+ buf.writeInt32(GenericHeader::VERSION);
+ buf.writeInt32(1);
+ uint32_t headerLen;
+ if (mapped) {
+ GenericHeader::MMapReader reader(buf.getData(), buf.getDataLen());
+ headerLen = FileHeader::readSize(reader);
+ } else {
+ GenericHeader::BufferReader reader(buf);
+ headerLen = FileHeader::readSize(reader);
+ }
+ EXPECT_EQUAL(21u, headerLen);
+}
+
+
+void
+Test::testReadSizeErrors(bool mapped)
+{
+ {
+ DataBuffer buf;
+ EXPECT_TRUE(testReadSizeError(buf, "Failed to read header info.",
+ mapped));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(0xDEADBEAF);
+ buf.writeInt32(8);
+ buf.writeInt32(0);
+ buf.writeInt32(0);
+ EXPECT_TRUE(testReadSizeError(buf, "Failed to verify magic bits.",
+ mapped));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(8);
+ buf.writeInt32(GenericHeader::VERSION);
+ buf.writeInt32(0);
+ EXPECT_TRUE(testReadSizeError(buf, "Failed to verify header size.",
+ mapped));
+ }
+ {
+ DataBuffer buf;
+ buf.writeInt32(GenericHeader::MAGIC);
+ buf.writeInt32(16);
+ buf.writeInt32(-1);
+ buf.writeInt32(0);
+ EXPECT_TRUE(testReadSizeError(buf,
+ "Failed to verify header version.",
+ mapped));
+ }
+}
+
+
+bool
+Test::testReadSizeError(DataBuffer &buf, const std::string &expected,
+ bool mapped)
+{
+ uint32_t headerLen = 0u;
+ try {
+ if (mapped) {
+ GenericHeader::MMapReader reader(buf.getData(), buf.getDataLen());
+ headerLen = FileHeader::readSize(reader);
+ } else {
+ GenericHeader::BufferReader reader(buf);
+ headerLen = FileHeader::readSize(reader);
+ }
+ EXPECT_TRUE(false);
+ return false;
+ } catch (IllegalHeaderException &e) {
+ if (!EXPECT_EQUAL(expected, e.getMessage())) {
+ return false;
+ }
+ }
+ EXPECT_EQUAL(headerLen, 0u);
+ return true;
+}
+
diff --git a/vespalib/src/tests/metrics/CMakeLists.txt b/vespalib/src/tests/metrics/CMakeLists.txt
new file mode 100644
index 00000000000..6019a6c2d4c
--- /dev/null
+++ b/vespalib/src/tests/metrics/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_metrics_test_app TEST
+ SOURCES
+ simple_metrics_test.cpp
+ mock_tick.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_metrics_test_app COMMAND vespalib_metrics_test_app)
+
+vespa_add_executable(vespalib_stablestore_test_app TEST
+ SOURCES
+ stable_store_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_stablestore_test_app COMMAND vespalib_stablestore_test_app)
diff --git a/vespalib/src/tests/metrics/mock_tick.cpp b/vespalib/src/tests/metrics/mock_tick.cpp
new file mode 100644
index 00000000000..c16ef25cfe6
--- /dev/null
+++ b/vespalib/src/tests/metrics/mock_tick.cpp
@@ -0,0 +1,6 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "mock_tick.h"
+
+namespace vespalib::metrics {
+
+} // namespace vespalib::metrics
diff --git a/vespalib/src/tests/metrics/mock_tick.h b/vespalib/src/tests/metrics/mock_tick.h
new file mode 100644
index 00000000000..4d9f6758537
--- /dev/null
+++ b/vespalib/src/tests/metrics/mock_tick.h
@@ -0,0 +1,92 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <vespa/vespalib/metrics/clock.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+
+namespace vespalib::metrics {
+
+// used to test clients of the Tick interface
+// values shared between threads are bounded queues with max size 1
+class MockTick : public Tick {
+private:
+ using Guard = std::unique_lock<std::mutex>;
+ struct Value {
+ Value() noexcept : value(0.0), valid(false) {}
+ TimeStamp value;
+ bool valid;
+ };
+
+ TimeStamp _first_value;
+ std::mutex _lock;
+ std::condition_variable _cond;
+ bool _alive;
+ Value _prev;
+ Value _next;
+
+ void push(Value &dst, TimeStamp value) {
+ Guard guard(_lock);
+ while (_alive && dst.valid) {
+ _cond.wait(guard);
+ }
+ dst.value = value;
+ dst.valid = true;
+ _cond.notify_one();
+ }
+
+ TimeStamp pop(Value &src) {
+ Guard guard(_lock);
+ while (_alive && !src.valid) {
+ _cond.wait(guard);
+ }
+ src.valid = false;
+ _cond.notify_one();
+ return src.value;
+ }
+
+ TimeStamp peek(const Value &src) {
+ Guard guard(_lock);
+ while (_alive && !src.valid) {
+ _cond.wait(guard);
+ }
+ return src.value;
+ }
+
+public:
+ explicit MockTick(TimeStamp first_value) noexcept
+ : _first_value(first_value), _lock(), _cond(), _alive(true), _prev(), _next() {}
+ TimeStamp first() override { return _first_value; }
+ TimeStamp next(TimeStamp prev) override {
+ push(_prev, prev);
+ return pop(_next);
+ }
+ TimeStamp give(TimeStamp next_value) {
+ TimeStamp prev_value = pop(_prev);
+ push(_next, next_value);
+ EXPECT_EQUAL(peek(_prev).count(), next_value.count());
+ return prev_value;
+ }
+ bool alive() const override { return _alive; }
+ void kill() override {
+ Guard guard(_lock);
+ _alive = false;
+ _cond.notify_all();
+ }
+};
+
+// share the MockTick between the tested and the tester.
+class TickProxy : public Tick {
+private:
+ std::shared_ptr<Tick> _tick;
+public:
+ explicit TickProxy(std::shared_ptr<Tick> tick) noexcept : _tick(std::move(tick)) {}
+ TimeStamp first() override { return _tick->first(); }
+ TimeStamp next(TimeStamp prev) override { return _tick->next(prev); }
+ bool alive() const override { return _tick->alive(); }
+ void kill() override { _tick->kill(); }
+};
+
+} // namespace vespalib::metrics
diff --git a/vespalib/src/tests/metrics/simple_metrics_test.cpp b/vespalib/src/tests/metrics/simple_metrics_test.cpp
new file mode 100644
index 00000000000..3006022a43d
--- /dev/null
+++ b/vespalib/src/tests/metrics/simple_metrics_test.cpp
@@ -0,0 +1,219 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/data/slime/json_format.h>
+#include <vespa/vespalib/metrics/simple_metrics.h>
+#include <vespa/vespalib/metrics/simple_metrics_manager.h>
+#include <vespa/vespalib/metrics/stable_store.h>
+#include <vespa/vespalib/metrics/json_formatter.h>
+#include "mock_tick.h"
+#include <stdio.h>
+#include <unistd.h>
+
+using namespace vespalib;
+using namespace vespalib::metrics;
+
+TEST("require that simple metrics gauge merge works")
+{
+ std::pair<MetricId, Point> id(MetricId(42), Point(17));
+ Gauge::Measurement a1(id, 0.0);
+ Gauge::Measurement b1(id, 7.0);
+ Gauge::Measurement b2(id, 9.0);
+ Gauge::Measurement b3(id, 8.0);
+ Gauge::Measurement c1(id, 10.0);
+ Gauge::Measurement c2(id, 1.0);
+
+ GaugeAggregator a(a1), b(b1), c(c1);
+ b.merge(b2);
+ b.merge(b3);
+ c.merge(c2);
+
+ EXPECT_EQUAL(a.observedCount, 1u);
+ EXPECT_EQUAL(a.sumValue, 0.0);
+ EXPECT_EQUAL(a.minValue, 0.0);
+ EXPECT_EQUAL(a.maxValue, 0.0);
+ EXPECT_EQUAL(a.lastValue, 0.0);
+
+ EXPECT_EQUAL(b.observedCount, 3u);
+ EXPECT_EQUAL(b.sumValue, 24.0);
+ EXPECT_EQUAL(b.minValue, 7.0);
+ EXPECT_EQUAL(b.maxValue, 9.0);
+ EXPECT_EQUAL(b.lastValue, 8.0);
+
+ EXPECT_EQUAL(c.observedCount, 2u);
+ EXPECT_EQUAL(c.sumValue, 11.0);
+ EXPECT_EQUAL(c.minValue, 1.0);
+ EXPECT_EQUAL(c.maxValue, 10.0);
+ EXPECT_EQUAL(c.lastValue, 1.0);
+
+ a.minValue = 8;
+
+ a.merge(b);
+ EXPECT_EQUAL(a.observedCount, 4u);
+ EXPECT_EQUAL(a.sumValue, 24.0);
+ EXPECT_EQUAL(a.minValue, 7.0);
+ EXPECT_EQUAL(a.maxValue, 9.0);
+ EXPECT_EQUAL(a.lastValue, 8.0);
+
+ a.merge(b);
+ EXPECT_EQUAL(a.observedCount, 7u);
+ EXPECT_EQUAL(a.sumValue, 48.0);
+ EXPECT_EQUAL(a.minValue, 7.0);
+ EXPECT_EQUAL(a.maxValue, 9.0);
+ EXPECT_EQUAL(a.lastValue, 8.0);
+
+ a.merge(c);
+ EXPECT_EQUAL(a.observedCount, 9u);
+ EXPECT_EQUAL(a.sumValue, 59.0);
+ EXPECT_EQUAL(a.minValue, 1.0);
+ EXPECT_EQUAL(a.maxValue, 10.0);
+ EXPECT_EQUAL(a.lastValue, 1.0);
+}
+
+bool compare_json(const vespalib::string &a, const vespalib::string &b)
+{
+ using vespalib::Memory;
+ using vespalib::slime::JsonFormat;
+
+ Slime slimeA, slimeB;
+ if (! JsonFormat::decode(a, slimeA)) {
+fprintf(stderr, "bad json a:\n>>>%s\n<<<\n", a.c_str());
+ return false;
+ }
+ if (! JsonFormat::decode(b, slimeB)) {
+fprintf(stderr, "bad json b\n");
+ return false;
+ }
+ if (!(slimeA == slimeB)) {
+fprintf(stderr, "compares unequal:\n[A]\n%s\n[B]\n%s\n", a.c_str(), b.c_str());
+ }
+ return slimeA == slimeB;
+}
+
+void check_json(const vespalib::string &actual)
+{
+ vespalib::string expect = "{"
+ " snapshot: { from: 1, to: 4 },"
+ " values: [ { name: 'foo',"
+ " values: { count: 17, rate: 4.85714 }"
+ " }, {"
+ " name: 'foo',"
+ " dimensions: { chain: 'default', documenttype: 'music', thread: '0' },"
+ " values: { count: 4, rate: 1.14286 }"
+ " }, {"
+ " name: 'bar',"
+ " values: { count: 4, rate: 1.14286, average: 42, sum: 168, min: 41, max: 43, last: 42 }"
+ " }, {"
+ " name: 'bar',"
+ " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '1' },"
+ " values: { count: 1, rate: 0.285714, average: 14, sum: 14, min: 14, max: 14, last: 14 }"
+ " }, {"
+ " name: 'bar',"
+ " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '2' },"
+ " values: { count: 1, rate: 0.285714, average: 11, sum: 11, min: 11, max: 11, last: 11 }"
+ " } ]"
+ "}";
+ EXPECT_TRUE(compare_json(expect, actual));
+}
+
+
+TEST("use simple_metrics_collector")
+{
+ using namespace vespalib::metrics;
+ SimpleManagerConfig cf;
+ cf.sliding_window_seconds = 5;
+ std::shared_ptr<MockTick> ticker = std::make_shared<MockTick>(TimeStamp(1.0));
+ auto manager = SimpleMetricsManager::createForTest(cf, std::make_unique<TickProxy>(ticker));
+
+ Counter myCounter = manager->counter("foo", "no description");
+ myCounter.add();
+ myCounter.add(16);
+
+ Gauge myGauge = manager->gauge("bar", "dummy description");
+ myGauge.sample(42.0);
+ myGauge.sample(41.0);
+ myGauge.sample(43.0);
+ myGauge.sample(42.0);
+
+ EXPECT_EQUAL(1.0, ticker->give(TimeStamp(2.0)).count());
+
+ Snapshot snap1 = manager->snapshot();
+ EXPECT_EQUAL(1.0, snap1.startTime());
+ EXPECT_EQUAL(2.0, snap1.endTime());
+
+ EXPECT_EQUAL(1u, snap1.counters().size());
+ EXPECT_EQUAL("foo", snap1.counters()[0].name());
+ EXPECT_EQUAL(17u, snap1.counters()[0].count());
+
+ EXPECT_EQUAL(1u, snap1.gauges().size());
+ EXPECT_EQUAL("bar", snap1.gauges()[0].name());
+ EXPECT_EQUAL(4u, snap1.gauges()[0].observedCount());
+ EXPECT_EQUAL(41.0, snap1.gauges()[0].minValue());
+ EXPECT_EQUAL(43.0, snap1.gauges()[0].maxValue());
+ EXPECT_EQUAL(42.0, snap1.gauges()[0].lastValue());
+
+ Point one = manager->pointBuilder()
+ .bind("chain", "default")
+ .bind("documenttype", "music")
+ .bind("thread", "0").build();
+ PointBuilder b2 = manager->pointBuilder();
+ b2.bind("chain", "vespa")
+ .bind("documenttype", "blogpost");
+ b2.bind("thread", "1");
+ Point two = b2.build();
+ EXPECT_EQUAL(one.id(), 1u);
+ EXPECT_EQUAL(two.id(), 2u);
+
+ Point anotherOne = manager->pointBuilder()
+ .bind("chain", "default")
+ .bind("documenttype", "music")
+ .bind("thread", "0");
+ EXPECT_EQUAL(anotherOne.id(), 1u);
+
+ Point three = manager->pointBuilder(two).bind("thread", "2");
+ EXPECT_EQUAL(three.id(), 3u);
+
+ myCounter.add(3, one);
+ myCounter.add(one);
+ myGauge.sample(14.0, two);
+ myGauge.sample(11.0, three);
+
+ EXPECT_EQUAL(2.0, ticker->give(TimeStamp(4.5)).count());
+
+ Snapshot snap2 = manager->snapshot();
+ EXPECT_EQUAL(1.0, snap2.startTime());
+ EXPECT_EQUAL(4.5, snap2.endTime());
+ EXPECT_EQUAL(2u, snap2.counters().size());
+ EXPECT_EQUAL(3u, snap2.gauges().size());
+
+ JsonFormatter fmt2(snap2);
+ check_json(fmt2.asString());
+
+ // flush sliding window
+ for (int i = 5; i <= 10; ++i) {
+ ticker->give(TimeStamp(i));
+ }
+ Snapshot snap3 = manager->snapshot();
+ EXPECT_EQUAL(5.0, snap3.startTime());
+ EXPECT_EQUAL(10.0, snap3.endTime());
+ EXPECT_EQUAL(2u, snap3.counters().size());
+ EXPECT_EQUAL(0u, snap3.counters()[0].count());
+ EXPECT_EQUAL(0u, snap3.counters()[1].count());
+ EXPECT_EQUAL(3u, snap3.gauges().size());
+ EXPECT_EQUAL(0u, snap3.gauges()[0].observedCount());
+ EXPECT_EQUAL(0u, snap3.gauges()[1].observedCount());
+ EXPECT_EQUAL(0u, snap3.gauges()[2].observedCount());
+
+ Snapshot snap4 = manager->totalSnapshot();
+ EXPECT_EQUAL(1.0, snap4.startTime());
+ EXPECT_EQUAL(10.0, snap4.endTime());
+ EXPECT_EQUAL(2u, snap4.counters().size());
+ EXPECT_NOT_EQUAL(0u, snap4.counters()[0].count());
+ EXPECT_NOT_EQUAL(0u, snap4.counters()[1].count());
+ EXPECT_EQUAL(3u, snap4.gauges().size());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[0].observedCount());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[1].observedCount());
+ EXPECT_NOT_EQUAL(0u, snap4.gauges()[2].observedCount());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/metrics/stable_store_test.cpp b/vespalib/src/tests/metrics/stable_store_test.cpp
new file mode 100644
index 00000000000..cead112069f
--- /dev/null
+++ b/vespalib/src/tests/metrics/stable_store_test.cpp
@@ -0,0 +1,65 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/metrics/simple_metrics.h>
+#include <vespa/vespalib/metrics/simple_metrics_manager.h>
+#include <vespa/vespalib/metrics/stable_store.h>
+#include <vespa/vespalib/metrics/json_formatter.h>
+#include <stdio.h>
+#include <unistd.h>
+
+using namespace vespalib;
+using namespace vespalib::metrics;
+
+struct Foo {
+ int a;
+ char *p;
+ explicit Foo(int v) : a(v), p(nullptr) {}
+ bool operator==(const Foo &other) const {
+ return a == other.a;
+ }
+};
+
+TEST("require that stable_store works")
+{
+ vespalib::StableStore<Foo> bunch;
+ bunch.add(Foo(1));
+ bunch.add(Foo(2));
+ bunch.add(Foo(3));
+ bunch.add(Foo(5));
+ bunch.add(Foo(8));
+ bunch.add(Foo(13));
+ bunch.add(Foo(21));
+ bunch.add(Foo(34));
+ bunch.add(Foo(55));
+ bunch.add(Foo(89));
+
+ EXPECT_EQUAL(bunch.size(), 10u);
+
+ int sum = 0;
+
+ bunch.for_each([&sum](const Foo& value) { sum += value.a; });
+ EXPECT_EQUAL(231, sum);
+
+ std::vector<const Foo *> pointers;
+ bunch.for_each([&pointers](const Foo& value)
+ { pointers.push_back(&value); });
+ EXPECT_EQUAL(1, pointers[0]->a);
+ EXPECT_EQUAL(2, pointers[1]->a);
+ EXPECT_EQUAL(55, pointers[8]->a);
+ EXPECT_EQUAL(89, pointers[9]->a);
+
+ for (int i = 0; i < 20000; ++i) {
+ bunch.add(Foo(i));
+ }
+ bunch.for_each([&sum](const Foo& value) { sum -= value.a; });
+ EXPECT_EQUAL(-199990000, sum);
+
+ std::vector<const Foo *> after;
+ bunch.for_each([&after](const Foo& value)
+ { if (after.size() < 10) after.push_back(&value); });
+
+ EXPECT_EQUAL(pointers[0], after[0]);
+ EXPECT_EQUAL(pointers[9], after[9]);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/sequencedtaskexecutor/.gitignore b/vespalib/src/tests/sequencedtaskexecutor/.gitignore
new file mode 100644
index 00000000000..3b6f7c74a67
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/.gitignore
@@ -0,0 +1,4 @@
+vespalib_sequencedtaskexecutor_test_app
+vespalib_sequencedtaskexecutor_benchmark_app
+vespalib_adaptive_sequenced_executor_test_app
+vespalib_foregroundtaskexecutor_test_app
diff --git a/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt b/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt
new file mode 100644
index 00000000000..6a488b3c716
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_sequencedtaskexecutor_benchmark_app TEST
+ SOURCES
+ sequencedtaskexecutor_benchmark.cpp
+ DEPENDS
+ vespalib
+)
+
+vespa_add_executable(vespalib_sequencedtaskexecutor_test_app TEST
+ SOURCES
+ sequencedtaskexecutor_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_sequencedtaskexecutor_test_app COMMAND vespalib_sequencedtaskexecutor_test_app)
+
+vespa_add_executable(vespalib_adaptive_sequenced_executor_test_app TEST
+ SOURCES
+ adaptive_sequenced_executor_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_adaptive_sequenced_executor_test_app COMMAND vespalib_adaptive_sequenced_executor_test_app)
+
+vespa_add_executable(vespalib_foregroundtaskexecutor_test_app TEST
+ SOURCES
+ foregroundtaskexecutor_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_foregroundtaskexecutor_test_app COMMAND vespalib_foregroundtaskexecutor_test_app)
diff --git a/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp
new file mode 100644
index 00000000000..1a458f86232
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp
@@ -0,0 +1,248 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/adaptive_sequenced_executor.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/test/insertion_operators.h>
+
+#include <condition_variable>
+#include <unistd.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("adaptive_sequenced_executor_test");
+
+namespace vespalib {
+
+
+class Fixture
+{
+public:
+ AdaptiveSequencedExecutor _threads;
+
+ Fixture(bool is_max_pending_hard=true) : _threads(2, 2, 0, 1000, is_max_pending_hard) { }
+};
+
+
+class TestObj
+{
+public:
+ std::mutex _m;
+ std::condition_variable _cv;
+ int _done;
+ int _fail;
+ int _val;
+
+ TestObj() noexcept
+ : _m(),
+ _cv(),
+ _done(0),
+ _fail(0),
+ _val(0)
+ {
+ }
+
+ void
+ modify(int oldValue, int newValue)
+ {
+ {
+ std::lock_guard<std::mutex> guard(_m);
+ if (_val == oldValue) {
+ _val = newValue;
+ } else {
+ ++_fail;
+ }
+ ++_done;
+ _cv.notify_all();
+ }
+ }
+
+ void
+ wait(int wantDone)
+ {
+ std::unique_lock<std::mutex> guard(_m);
+ _cv.wait(guard, [&] { return this->_done >= wantDone; });
+ }
+};
+
+vespalib::stringref ZERO("0");
+
+TEST_F("testExecute", Fixture) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(1, [&]() { tv->modify(0, 42); });
+ tv->wait(1);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+
+TEST_F("require that task with same component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(0, [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+TEST_F("require that task with different component ids are not serialized", Fixture)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < 100; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(1, [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with same string component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ auto test2 = [&]() { tv->modify(14, 42); };
+ f._threads.execute(f._threads.getExecutorIdFromName(ZERO), [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(f._threads.getExecutorIdFromName(ZERO), test2);
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+namespace {
+
+int detectSerializeFailure(Fixture &f, vespalib::stringref altComponentId, int tryLimit)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < tryLimit; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(f._threads.getExecutorIdFromName(ZERO), [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(f._threads.getExecutorIdFromName(altComponentId), [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ return tryCnt;
+}
+
+vespalib::string makeAltComponentId(Fixture &f)
+{
+ int tryCnt = 0;
+ char altComponentId[20];
+ ISequencedTaskExecutor::ExecutorId executorId0 = f._threads.getExecutorIdFromName(ZERO);
+ for (tryCnt = 1; tryCnt < 100; ++tryCnt) {
+ sprintf(altComponentId, "%d", tryCnt);
+ if (f._threads.getExecutorIdFromName(altComponentId) == executorId0) {
+ break;
+ }
+ }
+ EXPECT_TRUE(tryCnt < 100);
+ return altComponentId;
+}
+
+}
+
+TEST_F("require that task with different string component ids are not serialized", Fixture)
+{
+ int tryCnt = detectSerializeFailure(f, "2", 100);
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with different string component ids mapping to the same executor id are serialized",
+ Fixture)
+{
+ vespalib::string altComponentId = makeAltComponentId(f);
+ LOG(info, "second string component id is \"%s\"", altComponentId.c_str());
+ int tryCnt = detectSerializeFailure(f, altComponentId, 100);
+ EXPECT_TRUE(tryCnt == 100);
+}
+
+
+TEST_F("require that execute works with const lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads.execute(0, lambda);
+ f._threads.execute(0, lambda);
+ f._threads.sync_all();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that execute works with reference to lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ auto &lambdaref = lambda;
+ f._threads.execute(0, lambdaref);
+ f._threads.execute(0, lambdaref);
+ f._threads.sync_all();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that executeLambda works", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads.executeLambda(ISequencedTaskExecutor::ExecutorId(0), lambda);
+ f._threads.sync_all();
+ std::vector<int> exp({5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST("require that you get correct number of executors") {
+ AdaptiveSequencedExecutor seven(7, 1, 0, 10, true);
+ EXPECT_EQUAL(7u, seven.getNumExecutors());
+}
+
+TEST("require that you distribute well") {
+ AdaptiveSequencedExecutor seven(7, 1, 0, 10, true);
+ EXPECT_EQUAL(7u, seven.getNumExecutors());
+ for (uint32_t id=0; id < 1000; id++) {
+ EXPECT_EQUAL(id%7, seven.getExecutorId(id).getId());
+ }
+}
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp
new file mode 100644
index 00000000000..56fb570209c
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp
@@ -0,0 +1,120 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/foregroundtaskexecutor.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <condition_variable>
+#include <unistd.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("foregroundtaskexecutor_test");
+
+namespace vespalib {
+
+
+class Fixture
+{
+public:
+ ForegroundTaskExecutor _threads;
+
+ Fixture()
+ : _threads()
+ {
+ }
+};
+
+
+class TestObj
+{
+public:
+ std::mutex _m;
+ std::condition_variable _cv;
+ int _done;
+ int _fail;
+ int _val;
+
+ TestObj() noexcept
+ : _m(),
+ _cv(),
+ _done(0),
+ _fail(0),
+ _val(0)
+ {
+ }
+
+ void
+ modify(int oldValue, int newValue)
+ {
+ {
+ std::lock_guard<std::mutex> guard(_m);
+ if (_val == oldValue) {
+ _val = newValue;
+ } else {
+ ++_fail;
+ }
+ ++_done;
+ }
+ _cv.notify_all();
+ }
+
+ void
+ wait(int wantDone)
+ {
+ std::unique_lock<std::mutex> guard(_m);
+ _cv.wait(guard, [this, wantDone] { return this->_done >= wantDone; });
+ }
+};
+
+TEST_F("testExecute", Fixture) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(1, [=]() { tv->modify(0, 42); });
+ tv->wait(1);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+
+TEST_F("require that task with same id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(0, [=]() { tv->modify(14, 42); });
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+TEST_F("require that task with different ids are serialized", Fixture)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < 100; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(1, [=]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads.sync_all();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ EXPECT_TRUE(tryCnt >= 100);
+}
+
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp
new file mode 100644
index 00000000000..0f7c82ef988
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp
@@ -0,0 +1,73 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/sequencedtaskexecutor.h>
+#include <vespa/vespalib/util/adaptive_sequenced_executor.h>
+#include <vespa/vespalib/util/lambdatask.h>
+#include <vespa/vespalib/util/time.h>
+#include <atomic>
+#include <cinttypes>
+
+using vespalib::ISequencedTaskExecutor;
+using vespalib::SequencedTaskExecutor;
+using vespalib::AdaptiveSequencedExecutor;
+using ExecutorId = vespalib::ISequencedTaskExecutor::ExecutorId;
+
+size_t do_work(size_t size) {
+ size_t ret = 0;
+ for (size_t i = 0; i < size; ++i) {
+ for (size_t j = 0; j < 128; ++j) {
+ ret = (ret + i) * j;
+ }
+ }
+ return ret;
+}
+
+struct SimpleParams {
+ int argc;
+ char **argv;
+ int idx;
+ SimpleParams(int argc_in, char **argv_in) : argc(argc_in), argv(argv_in), idx(0) {}
+ int next(const char *name, int fallback) {
+ ++idx;
+ int value = 0;
+ if (argc > idx) {
+ value = atoi(argv[idx]);
+ } else {
+ value = fallback;
+ }
+ fprintf(stderr, "param %s: %d\n", name, value);
+ return value;
+ }
+};
+
+VESPA_THREAD_STACK_TAG(sequenced_executor)
+
+int main(int argc, char **argv) {
+ SimpleParams params(argc, argv);
+ bool use_adaptive_executor = params.next("use_adaptive_executor", 0);
+ bool optimize_for_throughput = params.next("optimize_for_throughput", 0);
+ size_t num_tasks = params.next("num_tasks", 1000000);
+ size_t num_strands = params.next("num_strands", 4);
+ size_t task_limit = params.next("task_limit", 1000);
+ size_t num_threads = params.next("num_threads", num_strands);
+ size_t max_waiting = params.next("max_waiting", optimize_for_throughput ? 32 : 0);
+ size_t work_size = params.next("work_size", 0);
+ std::atomic<long> counter(0);
+ std::unique_ptr<ISequencedTaskExecutor> executor;
+ if (use_adaptive_executor) {
+ executor = std::make_unique<AdaptiveSequencedExecutor>(num_strands, num_threads, max_waiting, task_limit, true);
+ } else {
+ auto optimize = optimize_for_throughput
+ ? vespalib::Executor::OptimizeFor::THROUGHPUT
+ : vespalib::Executor::OptimizeFor::LATENCY;
+ executor = SequencedTaskExecutor::create(sequenced_executor, num_strands, task_limit, true, optimize);
+ }
+ vespalib::Timer timer;
+ for (size_t task_id = 0; task_id < num_tasks; ++task_id) {
+ executor->executeTask(ExecutorId(task_id % num_strands),
+ vespalib::makeLambdaTask([&counter,work_size] { (void) do_work(work_size); counter++; }));
+ }
+ executor.reset();
+ fprintf(stderr, "\ntotal time: %" PRId64 " ms\n", vespalib::count_ms(timer.elapsed()));
+ return (size_t(counter) == num_tasks) ? 0 : 1;
+}
diff --git a/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp
new file mode 100644
index 00000000000..705d6346e8c
--- /dev/null
+++ b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp
@@ -0,0 +1,351 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/sequencedtaskexecutor.h>
+#include <vespa/vespalib/util/adaptive_sequenced_executor.h>
+#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
+#include <vespa/vespalib/util/singleexecutor.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/test/insertion_operators.h>
+
+#include <condition_variable>
+#include <unistd.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("sequencedtaskexecutor_test");
+
+VESPA_THREAD_STACK_TAG(sequenced_executor)
+
+namespace vespalib {
+
+
+class Fixture
+{
+public:
+ std::unique_ptr<ISequencedTaskExecutor> _threads;
+
+ Fixture(bool is_task_limit_hard = true) :
+ _threads(SequencedTaskExecutor::create(sequenced_executor, 2, 1000, is_task_limit_hard,
+ Executor::OptimizeFor::LATENCY))
+ { }
+};
+
+
+class TestObj
+{
+public:
+ std::mutex _m;
+ std::condition_variable _cv;
+ int _done;
+ int _fail;
+ int _val;
+
+ TestObj() noexcept
+ : _m(),
+ _cv(),
+ _done(0),
+ _fail(0),
+ _val(0)
+ {
+ }
+
+ void
+ modify(int oldValue, int newValue)
+ {
+ {
+ std::lock_guard<std::mutex> guard(_m);
+ if (_val == oldValue) {
+ _val = newValue;
+ } else {
+ ++_fail;
+ }
+ ++_done;
+ }
+ _cv.notify_all();
+ }
+
+ void
+ wait(int wantDone)
+ {
+ std::unique_lock<std::mutex> guard(_m);
+ _cv.wait(guard, [this, wantDone] { return this->_done >= wantDone; });
+ }
+};
+
+vespalib::stringref ZERO("0");
+
+TEST_F("testExecute", Fixture) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads->execute(1, [=]() { tv->modify(0, 42); });
+ tv->wait(1);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+
+TEST_F("require that task with same component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads->execute(0, [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads->execute(0, [=]() { tv->modify(14, 42); });
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+TEST_F("require that task with same component id are serialized when executed with list", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ ISequencedTaskExecutor::ExecutorId executorId = f._threads->getExecutorId(0);
+ ISequencedTaskExecutor::TaskList list;
+ list.template emplace_back(executorId, makeLambdaTask([=]() { usleep(2000); tv->modify(0, 14); }));
+ list.template emplace_back(executorId, makeLambdaTask([=]() { tv->modify(14, 42); }));
+ f._threads->executeTasks(std::move(list));
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+TEST_F("require that task with different component ids are not serialized", Fixture)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < 100; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads->execute(0, [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads->execute(2, [=]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with same string component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ auto test2 = [=]() { tv->modify(14, 42); };
+ f._threads->execute(f._threads->getExecutorIdFromName(ZERO), [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads->execute(f._threads->getExecutorIdFromName(ZERO), test2);
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+namespace {
+
+int
+detectSerializeFailure(Fixture &f, vespalib::stringref altComponentId, int tryLimit)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < tryLimit; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads->execute(f._threads->getExecutorIdFromName(ZERO), [=]() { usleep(2000); tv->modify(0, 14); });
+ f._threads->execute(f._threads->getExecutorIdFromName(altComponentId), [=]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads->sync_all();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ return tryCnt;
+}
+
+vespalib::string
+makeAltComponentId(Fixture &f)
+{
+ int tryCnt = 0;
+ char altComponentId[20];
+ ISequencedTaskExecutor::ExecutorId executorId0 = f._threads->getExecutorIdFromName(ZERO);
+ for (tryCnt = 1; tryCnt < 100; ++tryCnt) {
+ sprintf(altComponentId, "%d", tryCnt);
+ if (f._threads->getExecutorIdFromName(altComponentId) == executorId0) {
+ break;
+ }
+ }
+ EXPECT_TRUE(tryCnt < 100);
+ return altComponentId;
+}
+
+}
+
+TEST_F("require that task with different string component ids are not serialized", Fixture)
+{
+ int tryCnt = detectSerializeFailure(f, "2", 100);
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with different string component ids mapping to the same executor id are serialized",
+ Fixture)
+{
+ vespalib::string altComponentId = makeAltComponentId(f);
+ LOG(info, "second string component id is \"%s\"", altComponentId.c_str());
+ int tryCnt = detectSerializeFailure(f, altComponentId, 100);
+ EXPECT_TRUE(tryCnt == 100);
+}
+
+
+TEST_F("require that execute works with const lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads->execute(0, lambda);
+ f._threads->execute(0, lambda);
+ f._threads->sync_all();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that execute works with reference to lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ auto &lambdaref = lambda;
+ f._threads->execute(0, lambdaref);
+ f._threads->execute(0, lambdaref);
+ f._threads->sync_all();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that executeLambda works", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads->executeLambda(ISequencedTaskExecutor::ExecutorId(0), lambda);
+ f._threads->sync_all();
+ std::vector<int> exp({5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST("require that you get correct number of executors") {
+ auto seven = SequencedTaskExecutor::create(sequenced_executor, 7);
+ EXPECT_EQUAL(7u, seven->getNumExecutors());
+}
+
+void verifyHardLimitForLatency(bool expect_hard) {
+ auto sequenced = SequencedTaskExecutor::create(sequenced_executor, 1, 100, expect_hard, Executor::OptimizeFor::LATENCY);
+ const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*sequenced);
+ EXPECT_EQUAL(expect_hard,nullptr != dynamic_cast<const BlockingThreadStackExecutor *>(seq.first_executor()));
+}
+
+void verifyHardLimitForThroughput(bool expect_hard) {
+ auto sequenced = SequencedTaskExecutor::create(sequenced_executor, 1, 100, expect_hard, Executor::OptimizeFor::THROUGHPUT);
+ const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*sequenced);
+ const SingleExecutor * first = dynamic_cast<const SingleExecutor *>(seq.first_executor());
+ EXPECT_TRUE(first != nullptr);
+ EXPECT_EQUAL(expect_hard, first->isBlocking());
+}
+
+TEST("require that you can get executor with both hard and soft limit") {
+ verifyHardLimitForLatency(true);
+ verifyHardLimitForLatency(false);
+ verifyHardLimitForThroughput(true);
+ verifyHardLimitForThroughput(false);
+}
+
+
+TEST("require that you distribute well") {
+ auto seven = SequencedTaskExecutor::create(sequenced_executor, 7);
+ const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*seven);
+ const uint32_t NUM_EXACT = 8 * seven->getNumExecutors();
+ EXPECT_EQUAL(7u, seven->getNumExecutors());
+ EXPECT_EQUAL(97u, seq.getComponentHashSize());
+ EXPECT_EQUAL(0u, seq.getComponentEffectiveHashSize());
+ for (uint32_t id=0; id < 1000; id++) {
+ if (id < NUM_EXACT) {
+ EXPECT_EQUAL(id % seven->getNumExecutors(), seven->getExecutorId(id).getId());
+ } else {
+ EXPECT_EQUAL(((id - NUM_EXACT) % 97) % seven->getNumExecutors(), seven->getExecutorId(id).getId());
+ }
+ }
+ EXPECT_EQUAL(97u, seq.getComponentHashSize());
+ EXPECT_EQUAL(97u, seq.getComponentEffectiveHashSize());
+}
+
+TEST("require that similar names get perfect distribution with 4 executors") {
+ auto four = SequencedTaskExecutor::create(sequenced_executor, 4);
+ EXPECT_EQUAL(0u, four->getExecutorIdFromName("f1").getId());
+ EXPECT_EQUAL(1u, four->getExecutorIdFromName("f2").getId());
+ EXPECT_EQUAL(2u, four->getExecutorIdFromName("f3").getId());
+ EXPECT_EQUAL(3u, four->getExecutorIdFromName("f4").getId());
+ EXPECT_EQUAL(0u, four->getExecutorIdFromName("f5").getId());
+ EXPECT_EQUAL(1u, four->getExecutorIdFromName("f6").getId());
+ EXPECT_EQUAL(2u, four->getExecutorIdFromName("f7").getId());
+ EXPECT_EQUAL(3u, four->getExecutorIdFromName("f8").getId());
+}
+
+TEST("require that similar names get perfect distribution with 8 executors") {
+ auto four = SequencedTaskExecutor::create(sequenced_executor, 8);
+ EXPECT_EQUAL(0u, four->getExecutorIdFromName("f1").getId());
+ EXPECT_EQUAL(1u, four->getExecutorIdFromName("f2").getId());
+ EXPECT_EQUAL(2u, four->getExecutorIdFromName("f3").getId());
+ EXPECT_EQUAL(3u, four->getExecutorIdFromName("f4").getId());
+ EXPECT_EQUAL(4u, four->getExecutorIdFromName("f5").getId());
+ EXPECT_EQUAL(5u, four->getExecutorIdFromName("f6").getId());
+ EXPECT_EQUAL(6u, four->getExecutorIdFromName("f7").getId());
+ EXPECT_EQUAL(7u, four->getExecutorIdFromName("f8").getId());
+}
+
+TEST("Test creation of different types") {
+ auto iseq = SequencedTaskExecutor::create(sequenced_executor, 1);
+
+ EXPECT_EQUAL(1u, iseq->getNumExecutors());
+ auto * seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get());
+ ASSERT_TRUE(seq != nullptr);
+
+ iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::LATENCY);
+ seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get());
+ ASSERT_TRUE(seq != nullptr);
+
+ iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::THROUGHPUT);
+ seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get());
+ ASSERT_TRUE(seq != nullptr);
+
+ iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::ADAPTIVE, 17);
+ auto aseq = dynamic_cast<AdaptiveSequencedExecutor *>(iseq.get());
+ ASSERT_TRUE(aseq != nullptr);
+}
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/singleexecutor/CMakeLists.txt b/vespalib/src/tests/singleexecutor/CMakeLists.txt
new file mode 100644
index 00000000000..3580a91d114
--- /dev/null
+++ b/vespalib/src/tests/singleexecutor/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_singleexecutor_test_app TEST
+ SOURCES
+ singleexecutor_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_singleexecutor_test_app COMMAND vespalib_singleexecutor_test_app)
diff --git a/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp b/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp
new file mode 100644
index 00000000000..56352ff3c0d
--- /dev/null
+++ b/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp
@@ -0,0 +1,114 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <vespa/vespalib/util/singleexecutor.h>
+#include <vespa/vespalib/util/lambdatask.h>
+#include <vespa/vespalib/util/alloc.h>
+#include <atomic>
+
+using namespace vespalib;
+
+VESPA_THREAD_STACK_TAG(sequenced_executor)
+
+TEST("test that all tasks are executed") {
+
+ std::atomic<uint64_t> counter(0);
+ SingleExecutor executor(sequenced_executor, 10);
+
+ for (uint64_t i(0); i < 10; i++) {
+ executor.execute(makeLambdaTask([&counter] {counter++;}));
+ }
+ executor.sync();
+ EXPECT_EQUAL(10u, counter);
+
+ counter = 0;
+ for (uint64_t i(0); i < 10000; i++) {
+ executor.execute(makeLambdaTask([&counter] {counter++;}));
+ }
+ executor.sync();
+ EXPECT_EQUAL(10000u, counter);
+}
+
+TEST("test that executor can overflow") {
+ constexpr size_t NUM_TASKS = 1000;
+ std::atomic<uint64_t> counter(0);
+ vespalib::Gate gate;
+ SingleExecutor executor(sequenced_executor, 10, false, 1, 1ms);
+ executor.execute(makeLambdaTask([&gate] { gate.await();}));
+
+ for(size_t i(0); i < NUM_TASKS; i++) {
+ executor.execute(makeLambdaTask([&counter, i] {
+ EXPECT_EQUAL(i, counter);
+ counter++;
+ }));
+ }
+ EXPECT_EQUAL(0u, counter);
+ ExecutorStats stats = executor.getStats();
+ EXPECT_EQUAL(NUM_TASKS + 1, stats.acceptedTasks);
+ EXPECT_EQUAL(NUM_TASKS, stats.queueSize.max());
+ gate.countDown();
+ executor.sync();
+ EXPECT_EQUAL(NUM_TASKS, counter);
+}
+
+void verifyResizeTaskLimit(bool up) {
+ std::mutex lock;
+ std::condition_variable cond;
+ std::atomic<uint64_t> started(0);
+ std::atomic<uint64_t> allowed(0);
+ constexpr uint32_t INITIAL = 20;
+ const uint32_t INITIAL_2inN = roundUp2inN(INITIAL);
+ double waterMarkRatio = 0.5;
+ SingleExecutor executor(sequenced_executor, INITIAL, true, INITIAL*waterMarkRatio, 10ms);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ EXPECT_EQUAL(uint32_t(INITIAL_2inN*waterMarkRatio), executor.get_watermark());
+
+ uint32_t targetTaskLimit = up ? 40 : 5;
+ uint32_t roundedTaskLimit = roundUp2inN(targetTaskLimit);
+ EXPECT_NOT_EQUAL(INITIAL_2inN, roundedTaskLimit);
+
+ for (uint64_t i(0); i < INITIAL; i++) {
+ executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] {
+ started++;
+ std::unique_lock guard(lock);
+ while (allowed < started) {
+ cond.wait_for(guard, 1ms);
+ }
+ }));
+ }
+ while (started < 1);
+ EXPECT_EQUAL(1u, started);
+ executor.setTaskLimit(targetTaskLimit);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ EXPECT_EQUAL(INITIAL_2inN*waterMarkRatio, executor.get_watermark());
+ allowed = 5;
+ while (started < 6);
+ EXPECT_EQUAL(6u, started);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ allowed = INITIAL;
+ while (started < INITIAL);
+ EXPECT_EQUAL(INITIAL, started);
+ EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit());
+ executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] {
+ started++;
+ std::unique_lock guard(lock);
+ while (allowed < started) {
+ cond.wait_for(guard, 1ms);
+ }
+ }));
+ while (started < INITIAL + 1);
+ EXPECT_EQUAL(INITIAL + 1, started);
+ EXPECT_EQUAL(roundedTaskLimit, executor.getTaskLimit());
+ EXPECT_EQUAL(roundedTaskLimit*waterMarkRatio, executor.get_watermark());
+ allowed = INITIAL + 1;
+}
+
+TEST("test that resizing up and down works") {
+ TEST_DO(verifyResizeTaskLimit(true));
+ TEST_DO(verifyResizeTaskLimit(false));
+
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/state_server/.gitignore b/vespalib/src/tests/state_server/.gitignore
new file mode 100644
index 00000000000..2d36d34e2ec
--- /dev/null
+++ b/vespalib/src/tests/state_server/.gitignore
@@ -0,0 +1 @@
+vespalib_state_server_test_app
diff --git a/vespalib/src/tests/state_server/CMakeLists.txt b/vespalib/src/tests/state_server/CMakeLists.txt
new file mode 100644
index 00000000000..6d3d762a42b
--- /dev/null
+++ b/vespalib/src/tests/state_server/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_state_server_test_app TEST
+ SOURCES
+ state_server_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_state_server_test_app NO_VALGRIND NO_VALGRIND COMMAND vespalib_state_server_test_app ENVIRONMENT "VESPA_HOME=.")
diff --git a/vespalib/src/tests/state_server/state_server_test.cpp b/vespalib/src/tests/state_server/state_server_test.cpp
new file mode 100644
index 00000000000..f6e614f213a
--- /dev/null
+++ b/vespalib/src/tests/state_server/state_server_test.cpp
@@ -0,0 +1,516 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/net/http/state_server.h>
+#include <vespa/vespalib/net/http/simple_health_producer.h>
+#include <vespa/vespalib/net/http/simple_metrics_producer.h>
+#include <vespa/vespalib/net/http/simple_component_config_producer.h>
+#include <vespa/vespalib/net/http/state_explorer.h>
+#include <vespa/vespalib/net/http/slime_explorer.h>
+#include <vespa/vespalib/net/http/generic_state_handler.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/host_name.h>
+#include <vespa/vespalib/process/process.h>
+#include <sys/stat.h>
+
+using namespace vespalib;
+
+//-----------------------------------------------------------------------------
+
+vespalib::string root_path = "/state/v1/";
+vespalib::string short_root_path = "/state/v1";
+vespalib::string metrics_path = "/state/v1/metrics";
+vespalib::string health_path = "/state/v1/health";
+vespalib::string config_path = "/state/v1/config";
+
+vespalib::string total_metrics_path = "/metrics/total";
+
+vespalib::string unknown_path = "/this/path/is/not/known";
+vespalib::string unknown_state_path = "/state/v1/this/path/is/not/known";
+vespalib::string my_path = "/my/path";
+
+vespalib::string host_tag = "HOST";
+std::map<vespalib::string,vespalib::string> empty_params;
+
+//-----------------------------------------------------------------------------
+
+vespalib::string run_cmd(const vespalib::string &cmd) {
+ vespalib::string out;
+ ASSERT_TRUE(Process::run(cmd.c_str(), out));
+ return out;
+}
+
+vespalib::string getPage(int port, const vespalib::string &path, const vespalib::string &extra_params = "") {
+ return run_cmd(make_string("curl -s %s 'http://localhost:%d%s'", extra_params.c_str(), port, path.c_str()));
+}
+
+vespalib::string getFull(int port, const vespalib::string &path) { return getPage(port, path, "-D -"); }
+
+//-----------------------------------------------------------------------------
+
+struct DummyHandler : JsonGetHandler {
+ vespalib::string result;
+ DummyHandler(const vespalib::string &result_in) : result(result_in) {}
+ vespalib::string get(const vespalib::string &, const vespalib::string &,
+ const std::map<vespalib::string,vespalib::string> &) const override
+ {
+ return result;
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+TEST_F("require that unknown url returns 404 response", HttpServer(0)) {
+ std::string expect("HTTP/1.1 404 Not Found\r\n"
+ "Connection: close\r\n"
+ "\r\n");
+ std::string actual = getFull(f1.port(), unknown_path);
+ EXPECT_EQUAL(expect, actual);
+}
+
+TEST_FF("require that empty known url returns 404 response", DummyHandler(""), HttpServer(0)) {
+ auto token = f2.repo().bind(my_path, f1);
+ std::string expect("HTTP/1.1 404 Not Found\r\n"
+ "Connection: close\r\n"
+ "\r\n");
+ std::string actual = getFull(f2.port(), my_path);
+ EXPECT_EQUAL(expect, actual);
+}
+
+TEST_FF("require that non-empty known url returns expected headers", DummyHandler("[123]"), HttpServer(0)) {
+ auto token = f2.repo().bind(my_path, f1);
+ vespalib::string expect("HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 5\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n"
+ "[123]");
+ std::string actual = getFull(f2.port(), my_path);
+ EXPECT_EQUAL(expect, actual);
+}
+
+TEST_FFFF("require that handler is selected based on longest matching url prefix",
+ DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"),
+ HttpServer(0))
+{
+ auto token2 = f4.repo().bind("/foo/bar", f2);
+ auto token1 = f4.repo().bind("/foo", f1);
+ auto token3 = f4.repo().bind("/foo/bar/baz", f3);
+ int port = f4.port();
+ EXPECT_EQUAL("", getPage(port, "/fox"));
+ EXPECT_EQUAL("[1]", getPage(port, "/foo"));
+ EXPECT_EQUAL("[1]", getPage(port, "/foo/fox"));
+ EXPECT_EQUAL("[2]", getPage(port, "/foo/bar"));
+ EXPECT_EQUAL("[2]", getPage(port, "/foo/bar/fox"));
+ EXPECT_EQUAL("[3]", getPage(port, "/foo/bar/baz"));
+ EXPECT_EQUAL("[3]", getPage(port, "/foo/bar/baz/fox"));
+}
+
+struct EchoHost : JsonGetHandler {
+ ~EchoHost() override;
+ vespalib::string get(const vespalib::string &host, const vespalib::string &,
+ const std::map<vespalib::string,vespalib::string> &) const override
+ {
+ return "[\"" + host + "\"]";
+ }
+};
+
+EchoHost::~EchoHost() = default;
+
+TEST_FF("require that host is passed correctly", EchoHost(), HttpServer(0)) {
+ auto token = f2.repo().bind(my_path, f1);
+ EXPECT_EQUAL(make_string("%s:%d", HostName::get().c_str(), f2.port()), f2.host());
+ vespalib::string default_result = make_string("[\"%s\"]", f2.host().c_str());
+ vespalib::string localhost_result = make_string("[\"%s:%d\"]", "localhost", f2.port());
+ vespalib::string silly_result = "[\"sillyserver\"]";
+ EXPECT_EQUAL(localhost_result, run_cmd(make_string("curl -s http://localhost:%d/my/path", f2.port())));
+ EXPECT_EQUAL(silly_result, run_cmd(make_string("curl -s http://localhost:%d/my/path -H \"Host: sillyserver\"", f2.port())));
+ EXPECT_EQUAL(default_result, run_cmd(make_string("curl -s http://localhost:%d/my/path -H \"Host:\"", f2.port())));
+}
+
+struct SamplingHandler : JsonGetHandler {
+ mutable std::mutex my_lock;
+ mutable vespalib::string my_host;
+ mutable vespalib::string my_path;
+ mutable std::map<vespalib::string,vespalib::string> my_params;
+ ~SamplingHandler() override;
+ vespalib::string get(const vespalib::string &host, const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params) const override
+ {
+ {
+ auto guard = std::lock_guard(my_lock);
+ my_host = host;
+ my_path = path;
+ my_params = params;
+ }
+ return "[]";
+ }
+};
+
+SamplingHandler::~SamplingHandler() = default;
+
+TEST_FF("require that request parameters can be inspected", SamplingHandler(), HttpServer(0))
+{
+ auto token = f2.repo().bind("/foo", f1);
+ EXPECT_EQUAL("[]", getPage(f2.port(), "/foo?a=b&x=y&z"));
+ {
+ auto guard = std::lock_guard(f1.my_lock);
+ EXPECT_EQUAL(f1.my_path, "/foo");
+ EXPECT_EQUAL(f1.my_params.size(), 3u);
+ EXPECT_EQUAL(f1.my_params["a"], "b");
+ EXPECT_EQUAL(f1.my_params["x"], "y");
+ EXPECT_EQUAL(f1.my_params["z"], "");
+ EXPECT_EQUAL(f1.my_params.size(), 3u); // "z" was present
+ }
+}
+
+TEST_FF("require that request path is dequoted", SamplingHandler(), HttpServer(0))
+{
+ auto token = f2.repo().bind("/[foo]", f1);
+ EXPECT_EQUAL("[]", getPage(f2.port(), "/%5bfoo%5D"));
+ {
+ auto guard = std::lock_guard(f1.my_lock);
+ EXPECT_EQUAL(f1.my_path, "/[foo]");
+ EXPECT_EQUAL(f1.my_params.size(), 0u);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+TEST_FFFF("require that the state server wires the appropriate url prefixes",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateServer(0, f1, f2, f3))
+{
+ f2.setTotalMetrics("{}"); // avoid empty result
+ int port = f4.getListenPort();
+ EXPECT_TRUE(getFull(port, short_root_path).find("HTTP/1.1 200 OK") == 0);
+ EXPECT_TRUE(getFull(port, total_metrics_path).find("HTTP/1.1 200 OK") == 0);
+ EXPECT_TRUE(getFull(port, unknown_path).find("HTTP/1.1 404 Not Found") == 0);
+}
+
+TEST_FFFF("require that the state server exposes the state api handler repo",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateServer(0, f1, f2, f3))
+{
+ int port = f4.getListenPort();
+ vespalib::string page1 = getPage(port, root_path);
+ auto token = f4.repo().add_root_resource("state/v1/custom");
+ vespalib::string page2 = getPage(port, root_path);
+ EXPECT_NOT_EQUAL(page1, page2);
+ token.reset();
+ vespalib::string page3 = getPage(port, root_path);
+ EXPECT_EQUAL(page3, page1);
+}
+
+//-----------------------------------------------------------------------------
+
+TEST_FFFF("require that json handlers can be removed from repo",
+ DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"),
+ JsonHandlerRepo())
+{
+ auto token1 = f4.bind("/foo", f1);
+ auto token2 = f4.bind("/foo/bar", f2);
+ auto token3 = f4.bind("/foo/bar/baz", f3);
+ std::map<vespalib::string,vespalib::string> params;
+ EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
+ EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
+ EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params));
+ token2.reset();
+ EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
+ EXPECT_EQUAL("[1]", f4.get("", "/foo/bar", params));
+ EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params));
+}
+
+TEST_FFFF("require that json handlers can be shadowed",
+ DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"),
+ JsonHandlerRepo())
+{
+ auto token1 = f4.bind("/foo", f1);
+ auto token2 = f4.bind("/foo/bar", f2);
+ std::map<vespalib::string,vespalib::string> params;
+ EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
+ EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
+ auto token3 = f4.bind("/foo/bar", f3);
+ EXPECT_EQUAL("[3]", f4.get("", "/foo/bar", params));
+ token3.reset();
+ EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
+}
+
+TEST_F("require that root resources can be tracked", JsonHandlerRepo())
+{
+ EXPECT_TRUE(std::vector<vespalib::string>({}) == f1.get_root_resources());
+ auto token1 = f1.add_root_resource("/health");
+ EXPECT_TRUE(std::vector<vespalib::string>({"/health"}) == f1.get_root_resources());
+ auto token2 = f1.add_root_resource("/config");
+ EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/config"}) == f1.get_root_resources());
+ auto token3 = f1.add_root_resource("/custom/foo");
+ EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/config", "/custom/foo"}) == f1.get_root_resources());
+ token2.reset();
+ EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/custom/foo"}) == f1.get_root_resources());
+}
+
+//-----------------------------------------------------------------------------
+
+TEST_FFFF("require that state api responds to the expected paths",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ f2.setTotalMetrics("{}"); // avoid empty result
+ EXPECT_TRUE(!f4.get(host_tag, short_root_path, empty_params).empty());
+ EXPECT_TRUE(!f4.get(host_tag, root_path, empty_params).empty());
+ EXPECT_TRUE(!f4.get(host_tag, health_path, empty_params).empty());
+ EXPECT_TRUE(!f4.get(host_tag, metrics_path, empty_params).empty());
+ EXPECT_TRUE(!f4.get(host_tag, config_path, empty_params).empty());
+ EXPECT_TRUE(!f4.get(host_tag, total_metrics_path, empty_params).empty());
+ EXPECT_TRUE(f4.get(host_tag, unknown_path, empty_params).empty());
+ EXPECT_TRUE(f4.get(host_tag, unknown_state_path, empty_params).empty());
+}
+
+TEST_FFFF("require that top-level urls are generated correctly",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ EXPECT_EQUAL("{\"resources\":["
+ "{\"url\":\"http://HOST/state/v1/health\"},"
+ "{\"url\":\"http://HOST/state/v1/metrics\"},"
+ "{\"url\":\"http://HOST/state/v1/config\"}]}",
+ f4.get(host_tag, root_path, empty_params));
+ EXPECT_EQUAL(f4.get(host_tag, root_path, empty_params),
+ f4.get(host_tag, short_root_path, empty_params));
+}
+
+TEST_FFFF("require that top-level resource list can be extended",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ auto token = f4.repo().add_root_resource("/state/v1/custom");
+ EXPECT_EQUAL("{\"resources\":["
+ "{\"url\":\"http://HOST/state/v1/health\"},"
+ "{\"url\":\"http://HOST/state/v1/metrics\"},"
+ "{\"url\":\"http://HOST/state/v1/config\"},"
+ "{\"url\":\"http://HOST/state/v1/custom\"}]}",
+ f4.get(host_tag, root_path, empty_params));
+}
+
+TEST_FFFF("require that health resource works as expected",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}",
+ f4.get(host_tag, health_path, empty_params));
+ f1.setFailed("FAIL MSG");
+ EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}",
+ f4.get(host_tag, health_path, empty_params));
+}
+
+TEST_FFFF("require that metrics resource works as expected",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}",
+ f4.get(host_tag, metrics_path, empty_params));
+ f1.setFailed("FAIL MSG");
+ EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}",
+ f4.get(host_tag, metrics_path, empty_params));
+ f1.setOk();
+ f2.setMetrics("{\"foo\":\"bar\"}");
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":{\"foo\":\"bar\"}}",
+ f4.get(host_tag, metrics_path, empty_params));
+}
+
+TEST_FFFF("require that config resource works as expected",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ EXPECT_EQUAL("{\"config\":{}}",
+ f4.get(host_tag, config_path, empty_params));
+ f3.addConfig(SimpleComponentConfigProducer::Config("foo", 3));
+ EXPECT_EQUAL("{\"config\":{\"generation\":3,\"foo\":{\"generation\":3}}}",
+ f4.get(host_tag, config_path, empty_params));
+ f3.addConfig(SimpleComponentConfigProducer::Config("foo", 4));
+ f3.addConfig(SimpleComponentConfigProducer::Config("bar", 4, "error"));
+ EXPECT_EQUAL("{\"config\":{\"generation\":4,\"bar\":{\"generation\":4,\"message\":\"error\"},\"foo\":{\"generation\":4}}}",
+ f4.get(host_tag, config_path, empty_params));
+ f3.removeConfig("bar");
+ EXPECT_EQUAL("{\"config\":{\"generation\":4,\"foo\":{\"generation\":4}}}",
+ f4.get(host_tag, config_path, empty_params));
+}
+
+TEST_FFFF("require that state api also can return total metric",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ f2.setTotalMetrics("{\"foo\":\"bar\"}");
+ EXPECT_EQUAL("{\"foo\":\"bar\"}",
+ f4.get(host_tag, total_metrics_path, empty_params));
+}
+
+TEST_FFFFF("require that custom handlers can be added to the state server",
+ SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3), DummyHandler("[123]"))
+{
+ EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params));
+ auto token = f4.repo().bind(my_path, f5);
+ EXPECT_EQUAL("[123]", f4.get(host_tag, my_path, empty_params));
+ token.reset();
+ EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params));
+}
+
+struct EchoConsumer : MetricsProducer {
+ ~EchoConsumer() override;
+ vespalib::string getMetrics(const vespalib::string &consumer) override {
+ return "[\"" + consumer + "\"]";
+ }
+ vespalib::string getTotalMetrics(const vespalib::string &consumer) override {
+ return "[\"" + consumer + "\"]";
+ }
+};
+
+EchoConsumer::~EchoConsumer() = default;
+
+TEST_FFFF("require that empty v1 metrics consumer defaults to 'statereporter'",
+ SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ std::map<vespalib::string,vespalib::string> my_params;
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"statereporter\"]}", f4.get(host_tag, metrics_path, empty_params));
+}
+
+TEST_FFFF("require that empty total metrics consumer defaults to the empty string",
+ SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ std::map<vespalib::string,vespalib::string> my_params;
+ EXPECT_EQUAL("[\"\"]", f4.get(host_tag, total_metrics_path, empty_params));
+}
+
+TEST_FFFF("require that metrics consumer is passed correctly",
+ SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(),
+ StateApi(f1, f2, f3))
+{
+ std::map<vespalib::string,vespalib::string> my_params;
+ my_params["consumer"] = "ME";
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"ME\"]}", f4.get(host_tag, metrics_path, my_params));
+ EXPECT_EQUAL("[\"ME\"]", f4.get(host_tag, total_metrics_path, my_params));
+}
+
+void check_json(const vespalib::string &expect_json, const vespalib::string &actual_json) {
+ Slime expect_slime;
+ Slime actual_slime;
+ EXPECT_TRUE(slime::JsonFormat::decode(expect_json, expect_slime) > 0);
+ EXPECT_TRUE(slime::JsonFormat::decode(actual_json, actual_slime) > 0);
+ EXPECT_EQUAL(expect_slime, actual_slime);
+}
+
+TEST("require that generic state can be explored") {
+ vespalib::string json_model =
+ "{"
+ " foo: 'bar',"
+ " cnt: 123,"
+ " engine: {"
+ " up: 'yes',"
+ " stats: {"
+ " latency: 5,"
+ " qps: 100"
+ " }"
+ " },"
+ " list: {"
+ " one: {"
+ " size: {"
+ " value: 1"
+ " }"
+ " },"
+ " two: {"
+ " size: 2"
+ " }"
+ " }"
+ "}";
+ vespalib::string json_root =
+ "{"
+ " full: true,"
+ " foo: 'bar',"
+ " cnt: 123,"
+ " engine: {"
+ " up: 'yes',"
+ " url: 'http://HOST/state/v1/engine'"
+ " },"
+ " list: {"
+ " one: {"
+ " size: {"
+ " value: 1,"
+ " url: 'http://HOST/state/v1/list/one/size'"
+ " }"
+ " },"
+ " two: {"
+ " size: 2,"
+ " url: 'http://HOST/state/v1/list/two'"
+ " }"
+ " }"
+ "}";
+ vespalib::string json_engine =
+ "{"
+ " full: true,"
+ " up: 'yes',"
+ " stats: {"
+ " latency: 5,"
+ " qps: 100,"
+ " url: 'http://HOST/state/v1/engine/stats'"
+ " }"
+ "}";
+ vespalib::string json_engine_stats =
+ "{"
+ " full: true,"
+ " latency: 5,"
+ " qps: 100"
+ "}";
+ vespalib::string json_list =
+ "{"
+ " one: {"
+ " size: {"
+ " value: 1,"
+ " url: 'http://HOST/state/v1/list/one/size'"
+ " }"
+ " },"
+ " two: {"
+ " size: 2,"
+ " url: 'http://HOST/state/v1/list/two'"
+ " }"
+ "}";
+ vespalib::string json_list_one =
+ "{"
+ " size: {"
+ " value: 1,"
+ " url: 'http://HOST/state/v1/list/one/size'"
+ " }"
+ "}";
+ vespalib::string json_list_one_size = "{ full: true, value: 1 }";
+ vespalib::string json_list_two = "{ full: true, size: 2 }";
+ //-------------------------------------------------------------------------
+ Slime slime_state;
+ EXPECT_TRUE(slime::JsonFormat::decode(json_model, slime_state) > 0);
+ SlimeExplorer slime_explorer(slime_state.get());
+ GenericStateHandler state_handler(short_root_path, slime_explorer);
+ EXPECT_EQUAL("", state_handler.get(host_tag, unknown_path, empty_params));
+ EXPECT_EQUAL("", state_handler.get(host_tag, unknown_state_path, empty_params));
+ check_json(json_root, state_handler.get(host_tag, root_path, empty_params));
+ check_json(json_engine, state_handler.get(host_tag, root_path + "engine", empty_params));
+ check_json(json_engine_stats, state_handler.get(host_tag, root_path + "engine/stats", empty_params));
+ check_json(json_list, state_handler.get(host_tag, root_path + "list", empty_params));
+ check_json(json_list_one, state_handler.get(host_tag, root_path + "list/one", empty_params));
+ check_json(json_list_one_size, state_handler.get(host_tag, root_path + "list/one/size", empty_params));
+ check_json(json_list_two, state_handler.get(host_tag, root_path + "list/two", empty_params));
+}
+
+TEST_MAIN() {
+ mkdir("var", S_IRWXU);
+ mkdir("var/run", S_IRWXU);
+ TEST_RUN_ALL();
+ rmdir("var/run");
+ rmdir("var");
+}
diff --git a/vespalib/src/tests/trace/CMakeLists.txt b/vespalib/src/tests/trace/CMakeLists.txt
index a632d419b4b..76cb266f230 100644
--- a/vespalib/src/tests/trace/CMakeLists.txt
+++ b/vespalib/src/tests/trace/CMakeLists.txt
@@ -7,10 +7,10 @@ vespa_add_executable(vespalib_trace_test_app TEST
)
vespa_add_test(NAME vespalib_trace_test_app COMMAND vespalib_trace_test_app)
-vespa_add_executable(staging_vespalib_trace_serialization_test_app TEST
+vespa_add_executable(vespalib_trace_serialization_test_app TEST
SOURCES
trace_serialization.cpp
DEPENDS
vespalib
)
-vespa_add_test(NAME staging_vespalib_trace_serialization_test_app COMMAND staging_vespalib_trace_serialization_test_app)
+vespa_add_test(NAME vespalib_trace_serialization_test_app COMMAND vespalib_trace_serialization_test_app)