You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

629 lines
18 KiB

// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "base/ring_buffer.h"
#include "base/System.h"
#include "base/FunctorThread.h"
#include <gtest/gtest.h>
#include <random>
#include <errno.h>
#ifdef _MSC_VER
#include "base/msvc.h"
#else
#include <sys/time.h>
#endif
namespace android {
namespace base {
TEST(ring_buffer, Init) {
ring_buffer r;
ring_buffer_init(&r);
}
static constexpr size_t kNumElts = 65536;
// Tests that a large buffer can be produced and consumed,
// in a single thread.
TEST(ring_buffer, ProduceConsume) {
std::default_random_engine generator;
generator.seed(0);
std::vector<uint8_t> elements(kNumElts);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
for (size_t i = 0; i < kNumElts; ++i) {
elements[i] =
static_cast<uint8_t>(
eltDistribution(generator));
}
std::vector<uint8_t> result(kNumElts);
ring_buffer r;
ring_buffer_init(&r);
size_t written = 0;
size_t read = 0;
int i = 0;
while (written < kNumElts) {
++i;
// Safety factor; we do not expect the ring buffer
// implementation to be this hangy if used this way.
if (i > kNumElts * 10) {
FAIL() << "Error: too many iterations. Hanging?";
return;
}
uint32_t toWrite = kNumElts - written;
long writtenThisTime =
ring_buffer_write(&r, elements.data() + written, 1, toWrite);
written += writtenThisTime;
if (writtenThisTime < toWrite) {
EXPECT_EQ(-EAGAIN, errno);
}
uint32_t toRead = kNumElts - read;
long readThisTime =
ring_buffer_read(&r, result.data() + read, 1, toRead);
read += readThisTime;
if (readThisTime < toRead) {
EXPECT_EQ(-EAGAIN, errno);
}
}
EXPECT_EQ(elements, result);
}
// General function to pass to FunctorThread to read/write
// data completely to/from a ring buffer.
static void writeTest(ring_buffer* r, const uint8_t* data, size_t stepSize, size_t numSteps) {
size_t stepsWritten = 0;
size_t bytes = stepSize * numSteps;
int i = 0;
while (stepsWritten < numSteps) {
++i;
// Safety factor; we do not expect the ring buffer
// implementation to be this hangy if used this way.
if (i > bytes * 10) {
FAIL() << "Error: too many iterations. Hanging?";
return;
}
uint32_t stepsRemaining = numSteps - stepsWritten;
long stepsWrittenThisTime =
ring_buffer_write(r,
data + stepSize * stepsWritten,
stepSize, stepsRemaining);
stepsWritten += stepsWrittenThisTime;
if (stepsWrittenThisTime < stepsRemaining) {
EXPECT_EQ(-EAGAIN, errno);
}
}
}
static void readTest(ring_buffer* r, uint8_t* data, size_t stepSize, size_t numSteps) {
size_t stepsRead = 0;
size_t bytes = stepSize * numSteps;
int i = 0;
while (stepsRead < numSteps) {
++i;
// Safety factor; we do not expect the ring buffer
// implementation to be this hangy if used this way.
if (i > bytes * 10) {
FAIL() << "Error: too many iterations. Hanging?";
return;
}
uint32_t stepsRemaining = numSteps - stepsRead;
long stepsReadThisTime =
ring_buffer_read(r,
data + stepSize * stepsRead,
stepSize, stepsRemaining);
stepsRead += stepsReadThisTime;
if (stepsReadThisTime < stepsRemaining) {
EXPECT_EQ(-EAGAIN, errno);
}
}
}
// Tests transmission of a large buffer where
// the producer is in one thread
// while the consumer is in another thread.
TEST(ring_buffer, ProduceConsumeMultiThread) {
std::default_random_engine generator;
generator.seed(0);
std::vector<uint8_t> elements(kNumElts);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
for (size_t i = 0; i < kNumElts; ++i) {
elements[i] =
static_cast<uint8_t>(
eltDistribution(generator));
}
std::vector<uint8_t> result(kNumElts, 0);
ring_buffer r;
ring_buffer_init(&r);
FunctorThread producer([&r, &elements]() {
writeTest(&r, (uint8_t*)elements.data(), 1, kNumElts);
});
FunctorThread consumer([&r, &result]() {
readTest(&r, (uint8_t*)result.data(), 1, kNumElts);
});
producer.start();
consumer.start();
consumer.wait();
EXPECT_EQ(elements, result);
}
// Tests various step sizes of ring buffer transmission.
TEST(ring_buffer, ProduceConsumeMultiThreadVaryingStepSize) {
std::default_random_engine generator;
generator.seed(0);
std::vector<uint8_t> elements(kNumElts);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
for (size_t i = 0; i < kNumElts; ++i) {
elements[i] =
static_cast<uint8_t>(
eltDistribution(generator));
}
static constexpr size_t kStepSizesToTest[] = {
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
};
for (auto stepSize : kStepSizesToTest) {
size_t numSteps = kNumElts / stepSize;
std::vector<uint8_t> result(kNumElts, 0);
ring_buffer r;
ring_buffer_init(&r);
FunctorThread producer([&r, &elements, stepSize, numSteps]() {
writeTest(&r, (uint8_t*)elements.data(), stepSize, numSteps);
});
FunctorThread consumer([&r, &result, stepSize, numSteps]() {
readTest(&r, (uint8_t*)result.data(), stepSize, numSteps);
});
producer.start();
consumer.start();
consumer.wait();
EXPECT_EQ(elements, result);
}
}
static void viewWriteTest(ring_buffer* r, ring_buffer_view* v, const uint8_t* data, size_t stepSize, size_t numSteps) {
size_t stepsWritten = 0;
size_t bytes = stepSize * numSteps;
int i = 0;
while (stepsWritten < numSteps) {
++i;
// Safety factor; we do not expect the ring buffer
// implementation to be this hangy if used this way.
if (i > bytes * 10) {
FAIL() << "Error: too many iterations. Hanging?";
return;
}
uint32_t stepsRemaining = numSteps - stepsWritten;
long stepsWrittenThisTime =
ring_buffer_view_write(r, v,
data + stepSize * stepsWritten,
stepSize, stepsRemaining);
stepsWritten += stepsWrittenThisTime;
if (stepsWrittenThisTime < stepsRemaining) {
EXPECT_EQ(-EAGAIN, errno);
}
}
}
static void viewReadTest(ring_buffer* r, ring_buffer_view* v, uint8_t* data, size_t stepSize, size_t numSteps) {
size_t stepsRead = 0;
size_t bytes = stepSize * numSteps;
int i = 0;
while (stepsRead < numSteps) {
++i;
// Safety factor; we do not expect the ring buffer
// implementation to be this hangy if used this way.
if (i > bytes * 10) {
FAIL() << "Error: too many iterations. Hanging?";
return;
}
uint32_t stepsRemaining = numSteps - stepsRead;
long stepsReadThisTime =
ring_buffer_view_read(r, v,
data + stepSize * stepsRead,
stepSize, stepsRemaining);
stepsRead += stepsReadThisTime;
if (stepsReadThisTime < stepsRemaining) {
EXPECT_EQ(-EAGAIN, errno);
}
}
}
// Tests ring_buffer_calc_shift.
TEST(ring_buffer, CalcShift) {
EXPECT_EQ(0, ring_buffer_calc_shift(1));
EXPECT_EQ(1, ring_buffer_calc_shift(2));
EXPECT_EQ(1, ring_buffer_calc_shift(3));
EXPECT_EQ(2, ring_buffer_calc_shift(4));
EXPECT_EQ(2, ring_buffer_calc_shift(5));
EXPECT_EQ(2, ring_buffer_calc_shift(6));
EXPECT_EQ(2, ring_buffer_calc_shift(7));
EXPECT_EQ(3, ring_buffer_calc_shift(8));
}
// Tests usage of ring buffer with view.
TEST(ring_buffer, ProduceConsumeMultiThreadVaryingStepSizeWithView) {
std::default_random_engine generator;
generator.seed(0);
std::vector<uint8_t> elements(kNumElts);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
for (size_t i = 0; i < kNumElts; ++i) {
elements[i] =
static_cast<uint8_t>(
eltDistribution(generator));
}
static constexpr size_t kStepSizesToTest[] = {
1, 2, 4, 8, 16, 32, 64,
1024, 2048, 4096,
};
for (auto stepSize : kStepSizesToTest) {
size_t numSteps = kNumElts / stepSize;
std::vector<uint8_t> result(kNumElts, 0);
// non power of 2
std::vector<uint8_t> buf(8193, 0);
ring_buffer r;
ring_buffer_view v;
ring_buffer_view_init(&r, &v, buf.data(), buf.size());
FunctorThread producer([&r, &v, &elements, stepSize, numSteps]() {
viewWriteTest(&r, &v, (uint8_t*)elements.data(), stepSize, numSteps);
});
FunctorThread consumer([&r, &v, &result, stepSize, numSteps]() {
viewReadTest(&r, &v, (uint8_t*)result.data(), stepSize, numSteps);
});
producer.start();
consumer.start();
consumer.wait();
EXPECT_EQ(elements, result);
}
}
// Tests that wait works as expected
TEST(ring_buffer, Wait) {
ring_buffer r;
ring_buffer_init(&r);
EXPECT_TRUE(ring_buffer_wait_write(&r, nullptr, 1, 0));
EXPECT_FALSE(ring_buffer_wait_read(&r, nullptr, 1, 0));
EXPECT_TRUE(ring_buffer_wait_write(&r, nullptr, 1, 100));
EXPECT_FALSE(ring_buffer_wait_read(&r, nullptr, 1, 100));
}
// Tests the read/write fully operations
TEST(ring_buffer, FullReadWrite) {
ring_buffer r;
ring_buffer_init(&r);
std::default_random_engine generator;
generator.seed(0);
// int for toolchain compatibility
std::uniform_int_distribution<int>
testSizeDistribution(1, 8192);
std::uniform_int_distribution<int>
bufSizeDistribution(256, 8192);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
size_t trials = 1000;
for (size_t i = 0; i < trials; ++i) {
size_t testSize =
testSizeDistribution(generator);
size_t bufSize =
bufSizeDistribution(generator);
std::vector<uint8_t> elements(testSize);
std::vector<uint8_t> result(testSize);
std::vector<uint8_t> buf(bufSize, 0);
ring_buffer r;
ring_buffer_view v;
ring_buffer_view_init(&r, &v, buf.data(), buf.size());
FunctorThread producer([&r, &v, &elements]() {
ring_buffer_write_fully(&r, &v, elements.data(), elements.size());
});
FunctorThread consumer([&r, &v, &result]() {
ring_buffer_read_fully(&r, &v, result.data(), result.size());
});
producer.start();
consumer.start();
consumer.wait();
EXPECT_EQ(elements, result);
}
}
// Tests synchronization with producer driving most things along with
// consumer hangup.
// The test: A producer thread runs and spawns consumer threads on demand. Once
// each consumer thread is done with a bit of traffic, they hang up.
// Currently disabled due to it hanging on Windows.
// TODO(lfy@): figure out why it hangs on windows
TEST(ring_buffer, DISABLED_ProducerDrivenSync) {
std::default_random_engine generator;
generator.seed(0);
std::vector<uint8_t> elements(kNumElts);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
for (size_t i = 0; i < kNumElts; ++i) {
elements[i] =
static_cast<uint8_t>(
eltDistribution(generator));
}
std::vector<uint8_t> result(kNumElts);
ring_buffer r;
ring_buffer_init(&r);
ring_buffer_sync_init(&r);
size_t read = 0;
const size_t totalTestLength = kNumElts * 64;
FunctorThread consumer([&r, &result, &read]() {
while (read < totalTestLength) {
if (ring_buffer_wait_read(&r, nullptr, 1, 1)) {
ring_buffer_read_fully(
&r, nullptr, result.data() + (read % result.size()), 1);
++read;
} else {
if (!ring_buffer_consumer_hangup(&r)) {
EXPECT_NE(RING_BUFFER_SYNC_CONSUMER_HANGING_UP, r.state);
ring_buffer_consumer_wait_producer_idle(&r);
while (ring_buffer_can_read(&r, 1)) {
ring_buffer_read_fully(
&r, nullptr, result.data() + (read % result.size()), 1);
++read;
}
}
ring_buffer_consumer_hung_up(&r);
}
}
});
consumer.start();
FunctorThread producer([&r, &elements]() {
size_t written = 0;
while (written < totalTestLength) {
if (!ring_buffer_producer_acquire(&r)) {
EXPECT_TRUE(
r.state == RING_BUFFER_SYNC_CONSUMER_HANGING_UP ||
r.state == RING_BUFFER_SYNC_CONSUMER_HUNG_UP);
ring_buffer_producer_idle(&r);
ring_buffer_producer_wait_hangup(&r);
EXPECT_TRUE(ring_buffer_producer_acquire_from_hangup(&r));
}
ring_buffer_write_fully(
&r, nullptr,
elements.data() + (written % elements.size()), 1);
++written;
ring_buffer_producer_idle(&r);
}
});
producer.start();
consumer.wait();
EXPECT_EQ(elements, result);
}
// Tests the read/write fully operations
TEST(ring_buffer, SpeedTest) {
std::default_random_engine generator;
generator.seed(0);
// int for toolchain compatibility
std::uniform_int_distribution<int>
eltDistribution(0, 255);
size_t testSize = 1048576 * 8;
size_t bufSize = 16384;
std::vector<uint8_t> elements(testSize);
for (size_t i = 0; i < testSize; ++i) {
elements[i] = static_cast<uint8_t>(eltDistribution(generator));
}
std::vector<uint8_t> result(testSize);
std::vector<uint8_t> buf(bufSize, 0);
ring_buffer r;
ring_buffer_view v;
ring_buffer_view_init(&r, &v, buf.data(), buf.size());
size_t totalCycles = 5;
float mbPerSec = 0.0f;
for (size_t i = 0; i < totalCycles; ++i) {
ring_buffer_view_init(&r, &v, buf.data(), buf.size());
uint64_t start_us = android::base::getHighResTimeUs();
FunctorThread producer([&r, &v, &elements]() {
ring_buffer_write_fully(&r, &v, elements.data(), elements.size());
});
FunctorThread consumer([&r, &v, &result]() {
ring_buffer_read_fully(&r, &v, result.data(), result.size());
});
producer.start();
consumer.start();
consumer.wait();
uint64_t end_us = android::base::getHighResTimeUs();
if (i % 10 == 0) {
fprintf(stderr, "%s: ring stats: live yield sleep %lu %lu %lu\n", __func__,
(unsigned long)r.read_live_count,
(unsigned long)r.read_yield_count,
(unsigned long)r.read_sleep_us_count);
}
mbPerSec += (float(testSize) / (end_us - start_us));
}
mbPerSec = mbPerSec / totalCycles;
fprintf(stderr, "%s: avg mb per sec: %f\n", __func__, mbPerSec);
}
// Tests copying out the contents available for read
// without incrementing the read index.
TEST(ring_buffer, CopyContents) {
std::vector<uint8_t> elements = {
0x1, 0x2, 0x3, 0x4,
0x5, 0x6, 0x7, 0x8,
};
std::vector<uint8_t> buf(4, 0);
std::vector<uint8_t> recv(elements.size(), 0);
ring_buffer r;
ring_buffer_view v;
ring_buffer_view_init(&r, &v, buf.data(), buf.size());
EXPECT_EQ(true, ring_buffer_view_can_write(&r, &v, 3));
EXPECT_EQ(0, ring_buffer_available_read(&r, &v));
uint8_t* elementsPtr = elements.data();
uint8_t* recvPtr = recv.data();
EXPECT_EQ(1, ring_buffer_view_write(&r, &v, elementsPtr, 1, 1));
EXPECT_FALSE(ring_buffer_view_can_write(&r, &v, 3));
EXPECT_TRUE(ring_buffer_view_can_write(&r, &v, 2));
EXPECT_EQ(1, ring_buffer_available_read(&r, &v));
EXPECT_EQ(0, ring_buffer_copy_contents(&r, &v, 1, recvPtr));
EXPECT_EQ(0x1, *recvPtr);
EXPECT_EQ(1, ring_buffer_available_read(&r, &v));
EXPECT_EQ(1, ring_buffer_view_read(&r, &v, recvPtr, 1, 1));
EXPECT_EQ(0, ring_buffer_available_read(&r, &v));
EXPECT_TRUE(ring_buffer_view_can_write(&r, &v, 3));
++elementsPtr;
++recvPtr;
EXPECT_EQ(1, ring_buffer_view_write(&r, &v, elementsPtr, 3, 1));
EXPECT_FALSE(ring_buffer_view_can_write(&r, &v, 3));
EXPECT_EQ(3, ring_buffer_available_read(&r, &v));
EXPECT_EQ(0, ring_buffer_copy_contents(&r, &v, 3, recvPtr));
EXPECT_EQ(0x2, recvPtr[0]);
EXPECT_EQ(0x3, recvPtr[1]);
EXPECT_EQ(0x4, recvPtr[2]);
EXPECT_EQ(3, ring_buffer_available_read(&r, &v));
EXPECT_EQ(1, ring_buffer_view_read(&r, &v, recvPtr, 3, 1));
EXPECT_EQ(0, ring_buffer_available_read(&r, &v));
EXPECT_TRUE(ring_buffer_view_can_write(&r, &v, 3));
elementsPtr += 3;
recvPtr += 3;
EXPECT_EQ(1, ring_buffer_view_write(&r, &v, elementsPtr, 3, 1));
EXPECT_FALSE(ring_buffer_view_can_write(&r, &v, 3));
EXPECT_EQ(3, ring_buffer_available_read(&r, &v));
EXPECT_EQ(0, ring_buffer_copy_contents(&r, &v, 3, recvPtr));
EXPECT_EQ(0x5, recvPtr[0]);
EXPECT_EQ(0x6, recvPtr[1]);
EXPECT_EQ(0x7, recvPtr[2]);
EXPECT_EQ(3, ring_buffer_available_read(&r, &v));
EXPECT_EQ(1, ring_buffer_view_read(&r, &v, recvPtr, 3, 1));
EXPECT_EQ(0, ring_buffer_available_read(&r, &v));
EXPECT_TRUE(ring_buffer_view_can_write(&r, &v, 3));
}
} // namespace android
} // namespace base