optimal & fix file cmake
This commit is contained in:
184
test/runtime_stats_test.cpp
Normal file
184
test/runtime_stats_test.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @file runtime_stats_test.cpp
|
||||
* @brief Kiểm @ref move_base2::RuntimeStats.
|
||||
*
|
||||
* Ba tính chất được khoá lại ở đây, đều là thứ mà một lỗi ở chúng sẽ làm bảng thống kê nói dối chứ
|
||||
* không làm chương trình chết:
|
||||
*
|
||||
* 1. **Tắt là tắt hẳn** — `period <= 0` thì không đăng ký được đoạn nào, không đo, không in.
|
||||
* Telemetry bật ngoài ý muốn trên robot thật nghĩa là log chen vào vòng điều khiển.
|
||||
* 2. **Cửa sổ được reset sau mỗi lần in** — số liệu là của cửa sổ vừa qua, không phải tích luỹ từ
|
||||
* lúc khởi động. Cộng dồn mãi thì mọi đỉnh tức thời sẽ bị pha loãng và không bao giờ thấy lại.
|
||||
* 3. **Thread tự tạo được gán nhãn qua cửa sổ chụp** — đây là cơ chế duy nhất gọi tên được thread
|
||||
* của costmap mà không phải sửa gói costmap.
|
||||
*/
|
||||
#include <move_base2/io/runtime_stats.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using move_base2::RuntimeStats;
|
||||
using move_base2::ScopedSection;
|
||||
|
||||
/// Đốt CPU thật trong khoảng @p ms — sleep không làm tăng bộ đếm CPU của thread.
|
||||
void burnCpu(int ms)
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
|
||||
volatile double sink = 0.0;
|
||||
while (std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
sink += static_cast<double>(i) * 1.000001;
|
||||
}
|
||||
}
|
||||
(void)sink;
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, DisabledMeansNoWork)
|
||||
{
|
||||
RuntimeStats stats(0.0);
|
||||
|
||||
EXPECT_FALSE(stats.enabled());
|
||||
EXPECT_EQ(stats.section("anything"), RuntimeStats::kInvalidSection);
|
||||
EXPECT_FALSE(stats.tick()) << "period = 0 yet it still printed to the terminal";
|
||||
|
||||
// Không được crash dù chỉ số không hợp lệ.
|
||||
stats.record(RuntimeStats::kInvalidSection, 1000);
|
||||
stats.registerCurrentThread("must not be recorded");
|
||||
|
||||
const std::string table = stats.render();
|
||||
EXPECT_EQ(table.find("anything"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, NegativePeriodDisables)
|
||||
{
|
||||
RuntimeStats stats(-1.0);
|
||||
EXPECT_FALSE(stats.enabled());
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, SectionAccumulatesAndResetsPerWindow)
|
||||
{
|
||||
RuntimeStats stats(3600.0); // chu kỳ dài: chỉ render() thủ công mới đóng cửa sổ
|
||||
ASSERT_TRUE(stats.enabled());
|
||||
|
||||
const RuntimeStats::SectionId id = stats.section("test.section");
|
||||
ASSERT_NE(id, RuntimeStats::kInvalidSection);
|
||||
|
||||
stats.record(id, 1'000'000); // 1 ms
|
||||
stats.record(id, 3'000'000); // 3 ms
|
||||
|
||||
const std::string first = stats.render();
|
||||
EXPECT_NE(first.find("test.section"), std::string::npos);
|
||||
EXPECT_NE(first.find("2.00"), std::string::npos) << "average must be 2.00 ms:\n" << first;
|
||||
EXPECT_NE(first.find("3.00"), std::string::npos) << "peak must be 3.00 ms:\n" << first;
|
||||
|
||||
// Cửa sổ mới: đoạn vẫn còn trong bảng nhưng số liệu về 0.
|
||||
const std::string second = stats.render();
|
||||
EXPECT_NE(second.find("test.section"), std::string::npos);
|
||||
EXPECT_NE(second.find("0.00"), std::string::npos) << "a new window must reset:\n" << second;
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, SameNameReturnsSameId)
|
||||
{
|
||||
RuntimeStats stats(3600.0);
|
||||
EXPECT_EQ(stats.section("loop"), stats.section("loop"));
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, TickOnlyPrintsWhenPeriodElapsed)
|
||||
{
|
||||
RuntimeStats stats(3600.0);
|
||||
EXPECT_FALSE(stats.tick()) << "printed before the period elapsed";
|
||||
|
||||
RuntimeStats fast(0.001); // [s]
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
EXPECT_TRUE(fast.tick());
|
||||
EXPECT_FALSE(fast.tick()) << "the window must be reopened right after printing";
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, RegisteredThreadAppearsWithCpuTime)
|
||||
{
|
||||
RuntimeStats stats(3600.0);
|
||||
|
||||
std::atomic<bool> registered{ false };
|
||||
std::atomic<bool> stop{ false };
|
||||
std::thread worker([&stats, ®istered, &stop]() {
|
||||
stats.registerCurrentThread("test/worker");
|
||||
registered.store(true);
|
||||
while (!stop.load())
|
||||
{
|
||||
burnCpu(5);
|
||||
}
|
||||
});
|
||||
|
||||
while (!registered.load())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
burnCpu(60);
|
||||
|
||||
const std::string table = stats.render();
|
||||
stop.store(true);
|
||||
worker.join();
|
||||
|
||||
EXPECT_NE(table.find("test/worker"), std::string::npos) << table;
|
||||
EXPECT_NE(table.find("(unregistered)"), std::string::npos)
|
||||
<< "CPU outside the registered threads must show up, otherwise the table hides the culprit:\n"
|
||||
<< table;
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, ThreadCaptureLabelsThreadsCreatedInsideWindow)
|
||||
{
|
||||
RuntimeStats stats(3600.0);
|
||||
|
||||
std::atomic<bool> stop{ false };
|
||||
std::thread created;
|
||||
|
||||
stats.beginThreadCapture();
|
||||
created = std::thread([&stop]() {
|
||||
while (!stop.load())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
});
|
||||
// Thread phải thực sự tồn tại trong /proc trước khi đóng cửa sổ chụp.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
stats.endThreadCapture("simulated/costmap");
|
||||
|
||||
const std::string table = stats.render();
|
||||
stop.store(true);
|
||||
created.join();
|
||||
|
||||
#ifdef __linux__
|
||||
EXPECT_NE(table.find("simulated/costmap"), std::string::npos) << table;
|
||||
#else
|
||||
GTEST_SKIP() << "thread capture windows rely on /proc, Linux only";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(RuntimeStatsTest, ScopedSectionToleratesNullStats)
|
||||
{
|
||||
// Đây là đường đi bình thường của mọi test dùng cổng giả: runner không được gắn telemetry.
|
||||
{
|
||||
ScopedSection timer(nullptr, RuntimeStats::kInvalidSection);
|
||||
}
|
||||
RuntimeStats stats(0.0);
|
||||
{
|
||||
ScopedSection timer(&stats, stats.section("skipped"));
|
||||
}
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user