Merge "Scouting: add way to view data before submitting"
diff --git a/aos/BUILD b/aos/BUILD
index 0792e67..a5fb6c8 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -776,3 +776,17 @@
"//third_party/cargo:cxx_cc",
],
)
+
+cc_library(
+ name = "sha256",
+ srcs = [
+ "sha256.cc",
+ ],
+ hdrs = ["sha256.h"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@boringssl//:crypto",
+ "@com_google_absl//absl/types:span",
+ ],
+)
diff --git a/aos/configuration.cc b/aos/configuration.cc
index ad0a489..4e1316d 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -338,6 +338,9 @@
if (c->name()->string_view().back() == '/') {
LOG(FATAL) << "Channel names can't end with '/'";
}
+ if (c->name()->string_view().front() != '/') {
+ LOG(FATAL) << "Channel names must start with '/'";
+ }
if (c->name()->string_view().find("//") != std::string_view::npos) {
LOG(FATAL) << ": Invalid channel name " << c->name()->string_view()
<< ", can't use //.";
@@ -984,8 +987,20 @@
}
}
- CHECK(found_schema != nullptr)
- << ": Failed to find schema for " << FlatbufferToJson(c);
+ if (found_schema == nullptr) {
+ std::stringstream ss;
+ for (const aos::FlatbufferVector<reflection::Schema> &schema :
+ schemas) {
+ if (schema.message().root_table() == nullptr) {
+ continue;
+ }
+ auto name = schema.message().root_table()->name()->string_view();
+ ss << "\n\tname: " << name;
+ }
+ LOG(FATAL) << ": Failed to find schema for " << FlatbufferToJson(c)
+ << "\n\tThe following schemas were found:\n"
+ << ss.str();
+ }
// Now copy the message manually.
auto cached_schema = schema_cache.find(c->type()->string_view());
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index 823f43c..cb44f1b 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -136,6 +136,13 @@
LOG(FATAL) << "Foo";
},
"Invalid channel name");
+ EXPECT_DEATH(
+ {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/testdata/invalid_channel_name4.json"));
+ LOG(FATAL) << "Foo";
+ },
+ "Channel names must start with '/'");
}
// Tests that we can modify a config with a json snippet.
diff --git a/aos/events/event_loop.cc b/aos/events/event_loop.cc
index c29d820..2d932cc 100644
--- a/aos/events/event_loop.cc
+++ b/aos/events/event_loop.cc
@@ -34,6 +34,25 @@
}
} // namespace
+std::pair<SharedSpan, absl::Span<uint8_t>> MakeSharedSpan(size_t size) {
+ AlignedOwningSpan *const span = reinterpret_cast<AlignedOwningSpan *>(
+ malloc(sizeof(AlignedOwningSpan) + size + kChannelDataAlignment - 1));
+
+ absl::Span<uint8_t> mutable_span(
+ reinterpret_cast<uint8_t *>(RoundChannelData(span->data(), size)), size);
+ // Use the placement new operator to construct an actual absl::Span in place.
+ new (span) AlignedOwningSpan(mutable_span);
+
+ return std::make_pair(
+ SharedSpan(std::shared_ptr<AlignedOwningSpan>(span,
+ [](AlignedOwningSpan *s) {
+ s->~AlignedOwningSpan();
+ free(s);
+ }),
+ &span->span),
+ mutable_span);
+}
+
std::ostream &operator<<(std::ostream &os, const RawSender::Error err) {
os << ErrorToString(err);
return os;
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 23250e1..8825464 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -133,6 +133,25 @@
Ftrace ftrace_;
};
+using SharedSpan = std::shared_ptr<const absl::Span<const uint8_t>>;
+
+// Holds storage for a span object and the data referenced by that span for
+// compatibility with SharedSpan users. If constructed with MakeSharedSpan, span
+// points to only the aligned segment of the entire data.
+struct AlignedOwningSpan {
+ AlignedOwningSpan(absl::Span<const uint8_t> new_span) : span(new_span) {}
+
+ AlignedOwningSpan(const AlignedOwningSpan &) = delete;
+ AlignedOwningSpan &operator=(const AlignedOwningSpan &) = delete;
+ absl::Span<const uint8_t> span;
+ char *data() { return reinterpret_cast<char *>(this + 1); }
+};
+
+// Constructs a span which owns its data through a shared_ptr. The owning span
+// points to a const view of the data; also returns a temporary mutable span
+// which is only valid while the const shared span is kept alive.
+std::pair<SharedSpan, absl::Span<uint8_t>> MakeSharedSpan(size_t size);
+
// Raw version of sender. Sends a block of data. This is used for reflection
// and as a building block to implement typed senders.
class RawSender {
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index 07bd6d4..2076467 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -2389,6 +2389,68 @@
}
}
+// Tests that the RawSender::Send(SharedSpan) overload works.
+TEST_P(AbstractEventLoopTest, SharedSenderTimingReport) {
+ gflags::FlagSaver flag_saver;
+ FLAGS_timing_report_ms = 1000;
+ auto loop1 = Make();
+ auto loop2 = MakePrimary();
+
+ const FlatbufferDetachedBuffer<TestMessage> kMessage =
+ JsonToFlatbuffer<TestMessage>("{}");
+
+ std::unique_ptr<aos::RawSender> sender =
+ loop2->MakeRawSender(configuration::GetChannel(
+ loop2->configuration(), "/test", "aos.TestMessage", "", nullptr));
+
+ Fetcher<timing::Report> report_fetcher =
+ loop1->MakeFetcher<timing::Report>("/aos");
+ EXPECT_FALSE(report_fetcher.Fetch());
+
+ loop2->OnRun([&]() {
+ for (int ii = 0; ii < TestChannelQueueSize(loop2.get()); ++ii) {
+ auto shared_span = MakeSharedSpan(kMessage.span().size());
+ memcpy(shared_span.second.data(), kMessage.span().data(),
+ kMessage.span().size());
+ EXPECT_EQ(sender->Send(std::move(shared_span.first)),
+ RawSender::Error::kOk);
+ }
+ auto shared_span = MakeSharedSpan(kMessage.span().size());
+ memcpy(shared_span.second.data(), kMessage.span().data(),
+ kMessage.span().size());
+ EXPECT_EQ(sender->Send(std::move(shared_span.first)),
+ RawSender::Error::kMessagesSentTooFast);
+ });
+ // Quit after 1 timing report, mid way through the next cycle.
+ EndEventLoop(loop2.get(), chrono::milliseconds(1500));
+
+ Run();
+
+ if (do_timing_reports() == DoTimingReports::kYes) {
+ // Check that the sent too fast actually got recorded by the timing report.
+ FlatbufferDetachedBuffer<timing::Report> primary_report =
+ FlatbufferDetachedBuffer<timing::Report>::Empty();
+ while (report_fetcher.FetchNext()) {
+ if (report_fetcher->name()->string_view() == "primary") {
+ primary_report = CopyFlatBuffer(report_fetcher.get());
+ }
+ }
+
+ EXPECT_EQ(primary_report.message().name()->string_view(), "primary");
+
+ ASSERT_NE(primary_report.message().senders(), nullptr);
+ EXPECT_EQ(primary_report.message().senders()->size(), 3);
+ EXPECT_EQ(
+ primary_report.message()
+ .senders()
+ ->Get(0)
+ ->error_counts()
+ ->Get(static_cast<size_t>(timing::SendError::MESSAGE_SENT_TOO_FAST))
+ ->count(),
+ 1);
+ }
+}
+
// Tests that senders count correctly in the timing report.
TEST_P(AbstractEventLoopTest, WatcherTimingReport) {
FLAGS_timing_report_ms = 1000;
@@ -2619,9 +2681,10 @@
loop3->configuration(), "/test", "aos.TestMessage", "", nullptr));
loop2->OnRun([&]() {
- EXPECT_EQ(sender->Send(std::make_shared<absl::Span<const uint8_t>>(
- kMessage.span().data(), kMessage.span().size())),
- RawSender::Error::kOk);
+ auto shared_span = MakeSharedSpan(kMessage.span().size());
+ memcpy(shared_span.second.data(), kMessage.span().data(),
+ kMessage.span().size());
+ sender->CheckOk(sender->Send(std::move(shared_span.first)));
});
bool happened = false;
@@ -3191,12 +3254,12 @@
auto sender = event_loop->MakeSender<TestMessage>("/test");
- // We are sending messages at 1 kHz, so we will be sending too fast after
- // queue_size (1600) ms. After this, keep sending messages, and exactly a
- // channel storage duration (2s) after we send the first message we should
- // be able to successfully send a message.
+ // We are sending bunches of messages at 100 Hz, so we will be sending too
+ // fast after queue_size (800) ms. After this, keep sending messages, and
+ // exactly a channel storage duration (2s) after we send the first message we
+ // should be able to successfully send a message.
- const monotonic_clock::duration kInterval = std::chrono::milliseconds(1);
+ const std::chrono::milliseconds kInterval = std::chrono::milliseconds(10);
const monotonic_clock::duration channel_storage_duration =
std::chrono::nanoseconds(
event_loop->configuration()->channel_storage_duration());
@@ -3207,33 +3270,38 @@
auto start = monotonic_clock::min_time;
event_loop->AddPhasedLoop(
- [&](int) {
- const auto actual_err = SendTestMessage(sender);
- const bool done_waiting = (start != monotonic_clock::min_time &&
- sender.monotonic_sent_time() >=
- (start + channel_storage_duration));
- const auto expected_err =
- (msgs_sent < queue_size || done_waiting
- ? RawSender::Error::kOk
- : RawSender::Error::kMessagesSentTooFast);
+ [&](int elapsed_cycles) {
+ // The queue is setup for 800 messages/sec. We want to fill that up at
+ // a rate of 2000 messages/sec so we make sure we fill it up.
+ for (int i = 0; i < 2 * kInterval.count() * elapsed_cycles; ++i) {
+ const auto actual_err = SendTestMessage(sender);
+ const bool done_waiting = (start != monotonic_clock::min_time &&
+ sender.monotonic_sent_time() >=
+ (start + channel_storage_duration));
+ const auto expected_err =
+ (msgs_sent < queue_size || done_waiting
+ ? RawSender::Error::kOk
+ : RawSender::Error::kMessagesSentTooFast);
- if (start == monotonic_clock::min_time) {
- start = sender.monotonic_sent_time();
- }
+ if (start == monotonic_clock::min_time) {
+ start = sender.monotonic_sent_time();
+ }
- ASSERT_EQ(actual_err, expected_err);
- counter.Count(actual_err);
- msgs_sent++;
+ ASSERT_EQ(actual_err, expected_err);
+ counter.Count(actual_err);
+ msgs_sent++;
- EXPECT_EQ(counter.failures(),
- msgs_sent <= queue_size
- ? 0
- : (msgs_sent - queue_size) -
- (actual_err == RawSender::Error::kOk ? 1 : 0));
- EXPECT_EQ(counter.just_failed(), actual_err != RawSender::Error::kOk);
+ EXPECT_EQ(counter.failures(),
+ msgs_sent <= queue_size
+ ? 0
+ : (msgs_sent - queue_size) -
+ (actual_err == RawSender::Error::kOk ? 1 : 0));
+ EXPECT_EQ(counter.just_failed(), actual_err != RawSender::Error::kOk);
- if (done_waiting) {
- Exit();
+ if (done_waiting) {
+ Exit();
+ return;
+ }
}
},
kInterval);
diff --git a/aos/events/logging/BUILD b/aos/events/logging/BUILD
index 6cb3740..bfa51fa 100644
--- a/aos/events/logging/BUILD
+++ b/aos/events/logging/BUILD
@@ -146,6 +146,7 @@
":buffer_encoder",
":logger_fbs",
":log_backend",
+ "//aos:sha256",
"//aos:uuid",
"//aos:configuration",
"//aos:flatbuffer_merge",
@@ -158,7 +159,6 @@
"@com_github_google_flatbuffers//:flatbuffers",
"@com_github_google_glog//:glog",
"@com_google_absl//absl/types:span",
- "@boringssl//:crypto",
] + select({
"//tools:cpu_k8": [
":s3_fetcher",
@@ -380,6 +380,7 @@
deps = [
":log_namer",
"//aos:configuration",
+ "//aos:sha256",
"//aos/events:event_loop",
"//aos/events:simulated_event_loop",
"//aos/network:message_bridge_server_fbs",
@@ -435,6 +436,7 @@
"//aos:configuration",
"//aos:init",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:simulated_event_loop",
"@com_github_gflags_gflags//:gflags",
"@com_github_google_glog//:glog",
@@ -606,6 +608,36 @@
)
aos_config(
+ name = "multinode_pingpong_split4_mixed1_config",
+ src = "multinode_pingpong_split4_mixed1.json",
+ flatbuffers = [
+ "//aos/events:ping_fbs",
+ "//aos/events:pong_fbs",
+ "//aos/network:message_bridge_client_fbs",
+ "//aos/network:message_bridge_server_fbs",
+ "//aos/network:remote_message_fbs",
+ "//aos/network:timestamp_fbs",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = ["//aos/events:aos_config"],
+)
+
+aos_config(
+ name = "multinode_pingpong_split4_mixed2_config",
+ src = "multinode_pingpong_split4_mixed2.json",
+ flatbuffers = [
+ "//aos/events:ping_fbs",
+ "//aos/events:pong_fbs",
+ "//aos/network:message_bridge_client_fbs",
+ "//aos/network:message_bridge_server_fbs",
+ "//aos/network:remote_message_fbs",
+ "//aos/network:timestamp_fbs",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = ["//aos/events:aos_config"],
+)
+
+aos_config(
name = "multinode_pingpong_split4_reliable_config",
src = "multinode_pingpong_split4_reliable.json",
flatbuffers = [
@@ -696,6 +728,8 @@
":multinode_pingpong_combined_config",
":multinode_pingpong_split3_config",
":multinode_pingpong_split4_config",
+ ":multinode_pingpong_split4_mixed1_config",
+ ":multinode_pingpong_split4_mixed2_config",
":multinode_pingpong_split4_reliable_config",
":multinode_pingpong_split_config",
":multinode_pingpong_triangle_split_config",
diff --git a/aos/events/logging/log_backend.cc b/aos/events/logging/log_backend.cc
index a8b7a43..07bb780 100644
--- a/aos/events/logging/log_backend.cc
+++ b/aos/events/logging/log_backend.cc
@@ -16,9 +16,104 @@
"If true, sync data to disk as we go so we don't get too far ahead. Also "
"fadvise that we are done with the memory once it hits disk.");
+DEFINE_uint32(queue_reserve, 32, "Pre-reserved size of write queue.");
+
namespace aos::logger {
namespace {
constexpr const char *kTempExtension = ".tmp";
+
+// Assuming that kSector is power of 2, it aligns address to the left size.
+inline size_t AlignToLeft(size_t value) {
+ return value & (~(FileHandler::kSector - 1));
+}
+
+inline bool IsAligned(size_t value) {
+ return value % FileHandler::kSector == 0;
+}
+
+inline bool IsAlignedStart(const absl::Span<const uint8_t> span) {
+ return (reinterpret_cast<size_t>(span.data()) % FileHandler::kSector) == 0;
+}
+
+inline bool IsAlignedLength(const absl::Span<const uint8_t> span) {
+ return (span.size() % FileHandler::kSector) == 0;
+}
+
+} // namespace
+
+logger::QueueAligner::QueueAligner() {
+ aligned_queue_.reserve(FLAGS_queue_reserve);
+}
+
+void logger::QueueAligner::FillAlignedQueue(
+ const absl::Span<const absl::Span<const uint8_t>> &queue) {
+ aligned_queue_.clear();
+
+ for (const auto &span : queue) {
+ // Generally, every span might have 3 optional parts (i.e. 2^3 cases):
+ // 1. unaligned prefix - from start till first aligned block.
+ // 2. aligned main - block with aligned start and size
+ // 3. unaligned suffix - block with aligned start, and size less than one
+ // sector. If size of the span is less than 1 sector, let's call it prefix.
+
+ auto *data = span.data();
+ size_t size = span.size();
+ const auto start = reinterpret_cast<size_t>(data);
+ VLOG(2) << "Consider span starting at " << std::hex << start
+ << " with size " << size;
+
+ CHECK_GT(size, 0u) << ": Nobody should be sending empty messages.";
+
+ const auto next_aligned =
+ IsAligned(start) ? start : AlignToLeft(start) + FileHandler::kSector;
+ const auto prefix_size = next_aligned - start;
+ VLOG(2) << "Calculated prefix size " << std::hex << prefix_size;
+
+ if (prefix_size >= size) {
+ // size of prefix >= size of span - alignment is not possible, accept the
+ // whole span
+ VLOG(2) << "Only prefix found";
+ CHECK_GT(size, 0u);
+ aligned_queue_.emplace_back(data, size, false);
+ continue;
+ }
+ CHECK_LT(prefix_size, FileHandler::kSector)
+ << ": Wrong calculation of 'next' aligned position";
+ if (prefix_size > 0) {
+ // Cut the prefix and move to the main part.
+ VLOG(2) << "Cutting prefix at " << std::hex << start << " of size "
+ << prefix_size;
+ aligned_queue_.emplace_back(data, prefix_size, false);
+ data += prefix_size;
+ size -= prefix_size;
+ CHECK(data <= span.data() + span.size()) << " :Boundaries after prefix";
+ }
+
+ if (IsAligned(size)) {
+ // the rest is aligned.
+ VLOG(2) << "Returning aligned main part";
+ CHECK_GT(size, 0u);
+ aligned_queue_.emplace_back(data, size, true);
+ continue;
+ }
+
+ const auto aligned_size = AlignToLeft(size);
+ CHECK(aligned_size < size) << ": Wrong calculation of 'main' size";
+ if (aligned_size > 0) {
+ VLOG(2) << "Cutting main part starting " << std::hex
+ << reinterpret_cast<size_t>(data) << " of size " << aligned_size;
+ aligned_queue_.emplace_back(data, aligned_size, true);
+
+ data += aligned_size;
+ size -= aligned_size;
+ CHECK(data <= span.data() + span.size()) << " :Boundaries after main";
+ }
+
+ VLOG(2) << "Cutting suffix part starting " << std::hex
+ << reinterpret_cast<size_t>(data) << " of size " << size;
+ CHECK_GT(size, 0u);
+ aligned_queue_.emplace_back(data, size, false);
+ }
}
FileHandler::FileHandler(std::string filename)
@@ -40,7 +135,7 @@
}
flags_ = fcntl(fd_, F_GETFL, 0);
- PCHECK(flags_ >= 0) << ": Failed to get flags for " << this->filename();
+ PCHECK(flags_ >= 0) << ": Failed to get flags for " << filename_;
EnableDirect();
@@ -56,10 +151,10 @@
// Track if we failed to set O_DIRECT. Note: Austin hasn't seen this call
// fail. The write call tends to fail instead.
if (fcntl(fd_, F_SETFL, new_flags) == -1) {
- PLOG(WARNING) << "Failed to set O_DIRECT on " << filename();
+ PLOG(WARNING) << "Failed to set O_DIRECT on " << filename_;
supports_odirect_ = false;
} else {
- VLOG(1) << "Enabled O_DIRECT on " << filename();
+ VLOG(1) << "Enabled O_DIRECT on " << filename_;
flags_ = new_flags;
}
}
@@ -69,7 +164,7 @@
if (supports_odirect_ && ODirectEnabled()) {
flags_ = flags_ & (~O_DIRECT);
PCHECK(fcntl(fd_, F_SETFL, flags_) != -1) << ": Failed to disable O_DIRECT";
- VLOG(1) << "Disabled O_DIRECT on " << filename();
+ VLOG(1) << "Disabled O_DIRECT on " << filename_;
}
}
@@ -77,9 +172,9 @@
const absl::Span<const absl::Span<const uint8_t>> &queue) {
iovec_.clear();
CHECK_LE(queue.size(), static_cast<size_t>(IOV_MAX));
- iovec_.resize(queue.size());
- // Size of the data currently in iovec_.
- size_t counted_size = 0;
+
+ queue_aligner_.FillAlignedQueue(queue);
+ CHECK_LE(queue_aligner_.aligned_queue().size(), static_cast<size_t>(IOV_MAX));
// Ok, we now need to figure out if we were aligned, and if we were, how much
// of the data we are being asked to write is aligned.
@@ -89,119 +184,47 @@
// kSector in memory, and the length being written is a multiple of kSector.
// Some of the callers use an aligned ResizeableBuffer to generate 512 byte
// aligned buffers for this code to find and use.
- bool aligned = (total_write_bytes_ % kSector) == 0;
+ bool was_aligned = IsAligned(total_write_bytes_);
+ VLOG(1) << "Started " << (was_aligned ? "aligned" : "unaligned")
+ << " at offset " << total_write_bytes_ << " on " << filename();
- // Index we are filling in next. Keeps resetting back to 0 as we write
- // intermediates.
- size_t write_index = 0;
- for (size_t i = 0; i < queue.size(); ++i) {
- iovec_[write_index].iov_base = const_cast<uint8_t *>(queue[i].data());
- iovec_[write_index].iov_len = queue[i].size();
-
- // Make sure the address is aligned, or give up. This should be uncommon,
- // but is always possible.
- if ((reinterpret_cast<size_t>(iovec_[write_index].iov_base) % kSector) !=
- 0) {
- // Flush if we were aligned and have data.
- if (aligned && write_index != 0) {
- VLOG(1) << "Was aligned, now is not, writing previous data";
- const auto code =
- WriteV(iovec_.data(), write_index, true, counted_size);
+ // Walk through aligned queue and batch writes based on aligned flag
+ for (const auto &item : queue_aligner_.aligned_queue()) {
+ if (was_aligned != item.aligned) {
+ // Switching aligned context. Let's flush current batch.
+ if (!iovec_.empty()) {
+ // Flush current queue if we need.
+ const auto code = WriteV(iovec_, was_aligned);
if (code == WriteCode::kOutOfSpace) {
+ // We cannot say anything about what number of messages was written
+ // for sure.
return {
.code = code,
- .messages_written = i,
+ .messages_written = queue.size(),
};
}
-
- // Now, everything before here has been written. Make an iovec out of
- // the rest and keep going.
- write_index = 0;
- counted_size = 0;
-
- iovec_[write_index].iov_base = const_cast<uint8_t *>(queue[i].data());
- iovec_[write_index].iov_len = queue[i].size();
+ iovec_.clear();
}
- aligned = false;
- } else {
- // We are now starting aligned again, and have data worth writing! Flush
- // what was there before.
- if (!aligned && iovec_[write_index].iov_len >= kSector &&
- write_index != 0) {
- VLOG(1) << "Was not aligned, now is, writing previous data";
-
- const auto code =
- WriteV(iovec_.data(), write_index, false, counted_size);
- if (code == WriteCode::kOutOfSpace) {
- return {
- .code = code,
- .messages_written = i,
- };
- }
-
- // Now, everything before here has been written. Make an iovec out of
- // the rest and keep going.
- write_index = 0;
- counted_size = 0;
-
- iovec_[write_index].iov_base = const_cast<uint8_t *>(queue[i].data());
- iovec_[write_index].iov_len = queue[i].size();
- aligned = true;
- }
+ // Write queue is flushed. WriteV updates the total_write_bytes_.
+ was_aligned = IsAligned(total_write_bytes_) && item.aligned;
}
-
- // Now, see if the length is a multiple of kSector. The goal is to figure
- // out if/how much memory we can write out with O_DIRECT so that only the
- // last little bit is done with non-direct IO to keep it fast.
- if ((iovec_[write_index].iov_len % kSector) != 0) {
- VLOG(1) << "Unaligned length " << iovec_[write_index].iov_len << " on "
- << filename();
- // If we've got over a sector of data to write, write it out with O_DIRECT
- // and then continue writing the rest unaligned.
- if (aligned && iovec_[write_index].iov_len > kSector) {
- const size_t aligned_size =
- iovec_[write_index].iov_len & (~(kSector - 1));
- VLOG(1) << "Was aligned, writing last chunk rounded from "
- << queue[i].size() << " to " << aligned_size;
- iovec_[write_index].iov_len = aligned_size;
-
- const auto code =
- WriteV(iovec_.data(), write_index + 1, true, counted_size + aligned_size);
- if (code == WriteCode::kOutOfSpace) {
- return {
- .code = code,
- .messages_written = i,
- };
- }
-
- // Now, everything before here has been written. Make an iovec out of
- // the rest and keep going.
- write_index = 0;
- counted_size = 0;
-
- iovec_[write_index].iov_base =
- const_cast<uint8_t *>(queue[i].data() + aligned_size);
- iovec_[write_index].iov_len = queue[i].size() - aligned_size;
- }
- aligned = false;
- }
- VLOG(1) << "Writing " << iovec_[write_index].iov_len << " to "
- << filename();
- counted_size += iovec_[write_index].iov_len;
- ++write_index;
+ iovec_.push_back(
+ {.iov_base = const_cast<uint8_t *>(item.data), .iov_len = item.size});
}
- // Either write the aligned data if it is all aligned, or write the rest
- // unaligned if we wrote aligned up above.
- const auto code = WriteV(iovec_.data(), write_index, aligned, counted_size);
+ WriteCode result_code = WriteCode::kOk;
+ if (!iovec_.empty()) {
+ // Flush current queue if we need.
+ result_code = WriteV(iovec_, was_aligned);
+ }
return {
- .code = code,
+ .code = result_code,
.messages_written = queue.size(),
};
}
-WriteCode FileHandler::WriteV(struct iovec *iovec_data, size_t iovec_size,
- bool aligned, size_t counted_size) {
+WriteCode FileHandler::WriteV(const std::vector<struct iovec> &iovec,
+ bool aligned) {
// Configure the file descriptor to match the mode we should be in. This is
// safe to over-call since it only does the syscall if needed.
if (aligned) {
@@ -210,42 +233,41 @@
DisableDirect();
}
+ VLOG(2) << "Flushing queue of " << iovec.size() << " elements, "
+ << (aligned ? "aligned" : "unaligned");
+
+ CHECK_GT(iovec.size(), 0u);
const auto start = aos::monotonic_clock::now();
- if (VLOG_IS_ON(2)) {
- size_t to_be_written = 0;
- for (size_t i = 0; i < iovec_size; ++i) {
- VLOG(2) << " iov_base " << static_cast<void *>(iovec_data[i].iov_base)
- << ", iov_len " << iovec_data[i].iov_len;
- to_be_written += iovec_data[i].iov_len;
- }
- CHECK_GT(to_be_written, 0u);
- VLOG(2) << "Going to write " << to_be_written;
- }
+ // Validation of alignment assumptions.
+ if (aligned) {
+ CHECK(IsAligned(total_write_bytes_))
+ << ": Failed after writing " << total_write_bytes_
+ << " to the file, attempting aligned write with unaligned start.";
- const ssize_t written = writev(fd_, iovec_data, iovec_size);
- VLOG(2) << "Wrote " << written << ", for iovec size " << iovec_size;
-
- if (FLAGS_sync && written > 0) {
- // Flush asynchronously and force the data out of the cache.
- sync_file_range(fd_, total_write_bytes_, written, SYNC_FILE_RANGE_WRITE);
- if (last_synced_bytes_ != 0) {
- // Per Linus' recommendation online on how to do fast file IO, do a
- // blocking flush of the previous write chunk, and then tell the kernel to
- // drop the pages from the cache. This makes sure we can't get too far
- // ahead.
- sync_file_range(fd_, last_synced_bytes_,
- total_write_bytes_ - last_synced_bytes_,
- SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE |
- SYNC_FILE_RANGE_WAIT_AFTER);
- posix_fadvise(fd_, last_synced_bytes_,
- total_write_bytes_ - last_synced_bytes_,
- POSIX_FADV_DONTNEED);
-
- last_synced_bytes_ = total_write_bytes_;
+ for (const auto &iovec_item : iovec) {
+ absl::Span<const uint8_t> data(
+ reinterpret_cast<const uint8_t *>(iovec_item.iov_base),
+ iovec_item.iov_len);
+ VLOG(2) << " iov_base " << static_cast<void *>(iovec_item.iov_base)
+ << ", iov_len " << iovec_item.iov_len;
+ CHECK(IsAlignedStart(data) && IsAlignedLength(data));
}
}
+ // Calculation of expected written size.
+ size_t counted_size = 0;
+ for (const auto &iovec_item : iovec) {
+ CHECK_GT(iovec_item.iov_len, 0u);
+ counted_size += iovec_item.iov_len;
+ }
+
+ VLOG(2) << "Going to write " << counted_size;
+ CHECK_GT(counted_size, 0u);
+
+ const ssize_t written = writev(fd_, iovec.data(), iovec.size());
+ VLOG(2) << "Wrote " << written << ", for iovec size " << iovec.size();
+
const auto end = aos::monotonic_clock::now();
if (written == -1 && errno == ENOSPC) {
return WriteCode::kOutOfSpace;
@@ -259,8 +281,30 @@
return WriteCode::kOutOfSpace;
}
+ if (FLAGS_sync) {
+ // Flush asynchronously and force the data out of the cache.
+ sync_file_range(fd_, total_write_bytes_, written, SYNC_FILE_RANGE_WRITE);
+ if (last_synced_bytes_ != 0) {
+ // Per Linus' recommendation online on how to do fast file IO, do a
+ // blocking flush of the previous write chunk, and then tell the kernel to
+ // drop the pages from the cache. This makes sure we can't get too far
+ // ahead.
+ sync_file_range(fd_, last_synced_bytes_,
+ total_write_bytes_ - last_synced_bytes_,
+ SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE |
+ SYNC_FILE_RANGE_WAIT_AFTER);
+ posix_fadvise(fd_, last_synced_bytes_,
+ total_write_bytes_ - last_synced_bytes_,
+ POSIX_FADV_DONTNEED);
+ }
+ last_synced_bytes_ = total_write_bytes_;
+ }
+
total_write_bytes_ += written;
- write_stats_.UpdateStats(end - start, written, iovec_size);
+ if (aligned) {
+ written_aligned_ += written;
+ }
+ WriteStatistics()->UpdateStats(end - start, written, iovec.size());
return WriteCode::kOk;
}
@@ -284,7 +328,7 @@
FileBackend::FileBackend(std::string_view base_name)
: base_name_(base_name), separator_(base_name_.back() == '/' ? "" : "_") {}
-std::unique_ptr<FileHandler> FileBackend::RequestFile(std::string_view id) {
+std::unique_ptr<LogSink> FileBackend::RequestFile(std::string_view id) {
const std::string filename = absl::StrCat(base_name_, separator_, id);
return std::make_unique<FileHandler>(filename);
}
@@ -292,7 +336,7 @@
RenamableFileBackend::RenamableFileBackend(std::string_view base_name)
: base_name_(base_name), separator_(base_name_.back() == '/' ? "" : "_") {}
-std::unique_ptr<FileHandler> RenamableFileBackend::RequestFile(
+std::unique_ptr<LogSink> RenamableFileBackend::RequestFile(
std::string_view id) {
const std::string filename =
absl::StrCat(base_name_, separator_, id, temp_suffix_);
@@ -408,4 +452,5 @@
}
return WriteCode::kOk;
}
+
} // namespace aos::logger
diff --git a/aos/events/logging/log_backend.h b/aos/events/logging/log_backend.h
index 20ade91..1b1417e 100644
--- a/aos/events/logging/log_backend.h
+++ b/aos/events/logging/log_backend.h
@@ -77,6 +77,71 @@
size_t messages_written = 0;
};
+// Interface that abstract writing to log from media.
+class LogSink {
+ public:
+ LogSink() = default;
+ virtual ~LogSink() = default;
+
+ LogSink(const LogSink &) = delete;
+ LogSink &operator=(const LogSink &) = delete;
+
+ // Try to open file. App will crash if there are other than out-of-space
+ // problems with backend media.
+ virtual WriteCode OpenForWrite() = 0;
+
+ // Close the file handler.
+ virtual WriteCode Close() = 0;
+
+ // Returns true if sink is open and need to be closed.
+ virtual bool is_open() const = 0;
+
+ // Peeks messages from queue and writes it to file. Returns code when
+ // out-of-space problem occurred along with number of messages from queue that
+ // was written.
+ virtual WriteResult Write(
+ const absl::Span<const absl::Span<const uint8_t>> &queue) = 0;
+
+ // Get access to statistics related to the write operations.
+ WriteStats *WriteStatistics() { return &write_stats_; }
+
+ // Name of the log sink.
+ virtual std::string_view name() const = 0;
+
+ private:
+ WriteStats write_stats_;
+};
+
+// Source for iovec with an additional flag that pointer and size of data is
+// aligned and be ready for O_DIRECT operation.
+struct AlignedIovec {
+ const uint8_t *data;
+ size_t size;
+ bool aligned;
+
+ AlignedIovec(const uint8_t *data, size_t size, bool aligned)
+ : data(data), size(size), aligned(aligned) {}
+};
+
+// Converts queue of pieces to write to the disk to the queue where every
+// element is either aligned for O_DIRECT operation or marked as not aligned.
+class QueueAligner {
+ public:
+ QueueAligner();
+
+ // Reads input queue and fills with aligned and unaligned pieces. It is easy
+ // to deal with smaller pieces and batch it during the write operation.
+ void FillAlignedQueue(
+ const absl::Span<const absl::Span<const uint8_t>> &queue);
+
+ const std::vector<AlignedIovec> &aligned_queue() const {
+ return aligned_queue_;
+ }
+
+ private:
+ std::vector<AlignedIovec> aligned_queue_;
+};
+
// FileHandler is a replacement for bare filename in log writing and reading
// operations.
//
@@ -94,30 +159,28 @@
// 4) Not all filesystems support O_DIRECT, and different sizes may be optimal
// for different machines. The defaults should work decently anywhere and
// be tunable for faster systems.
-// TODO (Alexei): need 2 variations, to support systems with and without
-// O_DIRECT
-class FileHandler {
+class FileHandler : public LogSink {
public:
// Size of an aligned sector used to detect when the data is aligned enough to
// use O_DIRECT instead.
static constexpr size_t kSector = 512u;
explicit FileHandler(std::string filename);
- virtual ~FileHandler();
+ ~FileHandler() override;
FileHandler(const FileHandler &) = delete;
FileHandler &operator=(const FileHandler &) = delete;
// Try to open file. App will crash if there are other than out-of-space
// problems with backend media.
- virtual WriteCode OpenForWrite();
+ WriteCode OpenForWrite() override;
// Close the file handler.
- virtual WriteCode Close();
+ WriteCode Close() override;
// This will be true until Close() is called, unless the file couldn't be
// created due to running out of space.
- bool is_open() const { return fd_ != -1; }
+ bool is_open() const override { return fd_ != -1; }
// Peeks messages from queue and writes it to file. Returns code when
// out-of-space problem occurred along with number of messages from queue that
@@ -127,18 +190,19 @@
// write faster if the spans passed in start at aligned addresses, and are
// multiples of kSector long (and the data written so far is also a multiple
// of kSector length).
- virtual WriteResult Write(
- const absl::Span<const absl::Span<const uint8_t>> &queue);
+ WriteResult Write(
+ const absl::Span<const absl::Span<const uint8_t>> &queue) override;
- // TODO (Alexei): it is rather leaked abstraction.
- // Path to the concrete log file.
+ // Name of the log sink mostly for informational purposes.
+ std::string_view name() const override { return filename_; }
+
+ // Number of bytes written in aligned mode. It is mostly for testing.
+ size_t written_aligned() const { return written_aligned_; }
+
+ protected:
+ // This is used by subclasses who need to access filename.
std::string_view filename() const { return filename_; }
- int fd() const { return fd_; }
-
- // Get access to statistics related to the write operations.
- WriteStats *WriteStatistics() { return &write_stats_; }
-
private:
// Enables O_DIRECT on the open file if it is supported. Cheap to call if it
// is already enabled.
@@ -149,11 +213,9 @@
bool ODirectEnabled() const { return !!(flags_ & O_DIRECT); }
- // Writes a chunk of iovecs. aligned is true if all the data is kSector byte
- // aligned and multiples of it in length, and counted_size is the sum of the
- // sizes of all the chunks of data.
- WriteCode WriteV(struct iovec *iovec_data, size_t iovec_size, bool aligned,
- size_t counted_size);
+ // Writes a chunk of iovecs. aligned is true if all the data is kSector byte
+ // aligned and multiples of it in length.
+ WriteCode WriteV(const std::vector<struct iovec> &iovec, bool aligned);
const std::string filename_;
@@ -163,13 +225,15 @@
// churn.
std::vector<struct iovec> iovec_;
+ QueueAligner queue_aligner_;
+
int total_write_bytes_ = 0;
int last_synced_bytes_ = 0;
+ size_t written_aligned_ = 0;
+
bool supports_odirect_ = true;
int flags_ = 0;
-
- WriteStats write_stats_;
};
// Class that decouples log writing and media (file system or memory). It is
@@ -181,7 +245,7 @@
// Request file-like object from the log backend. It maybe a file on a disk or
// in memory. id is usually generated by log namer and looks like name of the
// file within a log folder.
- virtual std::unique_ptr<FileHandler> RequestFile(std::string_view id) = 0;
+ virtual std::unique_ptr<LogSink> RequestFile(std::string_view id) = 0;
};
// Implements requests log files from file system.
@@ -192,7 +256,7 @@
~FileBackend() override = default;
// Request file from a file system. It is not open yet.
- std::unique_ptr<FileHandler> RequestFile(std::string_view id) override;
+ std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
private:
const std::string base_name_;
@@ -210,7 +274,7 @@
: FileHandler(std::move(filename)), owner_(owner) {}
~RenamableFileHandler() final = default;
- // Returns false if not enough memory, true otherwise.
+ // Closes and if needed renames file.
WriteCode Close() final;
private:
@@ -221,11 +285,11 @@
~RenamableFileBackend() = default;
// Request file from a file system. It is not open yet.
- std::unique_ptr<FileHandler> RequestFile(std::string_view id) override;
+ std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
// TODO (Alexei): it is called by Logger, and left here for compatibility.
// Logger should not call it.
- std::string_view base_name() { return base_name_; }
+ std::string_view base_name() const { return base_name_; }
// If temp files are enabled, then this will write files with the .tmp
// suffix, and then rename them to the desired name after they are fully
diff --git a/aos/events/logging/log_backend_test.cc b/aos/events/logging/log_backend_test.cc
index e940948..928bb24 100644
--- a/aos/events/logging/log_backend_test.cc
+++ b/aos/events/logging/log_backend_test.cc
@@ -14,13 +14,24 @@
#include "gtest/gtest.h"
namespace aos::logger::testing {
+namespace {
+// Helper to write simple string to the log sink
+WriteResult Write(LogSink *log_sink, std::string_view content) {
+ auto span = absl::Span<const uint8_t>(
+ reinterpret_cast<const unsigned char *>(content.data()), content.size());
+ auto queue = absl::Span<const absl::Span<const uint8_t>>(&span, 1);
+ return log_sink->Write(queue);
+}
+} // namespace
+
TEST(LogBackendTest, CreateSimpleFile) {
const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
FileBackend backend(logevent);
auto file = backend.RequestFile("test.log");
ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
- auto result = write(file->fd(), "test", 4);
- EXPECT_GT(result, 0);
+ auto result = Write(file.get(), "test");
+ EXPECT_EQ(result.code, WriteCode::kOk);
+ EXPECT_EQ(result.messages_written, 1);
EXPECT_EQ(file->Close(), WriteCode::kOk);
EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
}
@@ -30,8 +41,9 @@
RenamableFileBackend backend(logevent);
auto file = backend.RequestFile("test.log");
ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
- auto result = write(file->fd(), "testtest", 8);
- EXPECT_GT(result, 0);
+ auto result = Write(file.get(), "test");
+ EXPECT_EQ(result.code, WriteCode::kOk);
+ EXPECT_EQ(result.messages_written, 1);
EXPECT_EQ(file->Close(), WriteCode::kOk);
EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
}
@@ -42,8 +54,9 @@
backend.EnableTempFiles();
auto file = backend.RequestFile("test.log");
ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
- auto result = write(file->fd(), "testtest", 8);
- EXPECT_GT(result, 0);
+ auto result = Write(file.get(), "test");
+ EXPECT_EQ(result.code, WriteCode::kOk);
+ EXPECT_EQ(result.messages_written, 1);
EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
EXPECT_EQ(file->Close(), WriteCode::kOk);
@@ -56,8 +69,9 @@
RenamableFileBackend backend(logevent);
auto file = backend.RequestFile("test.log");
ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
- auto result = write(file->fd(), "testtest", 8);
- EXPECT_GT(result, 0);
+ auto result = Write(file.get(), "test");
+ EXPECT_EQ(result.code, WriteCode::kOk);
+ EXPECT_EQ(result.messages_written, 1);
EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
@@ -77,8 +91,9 @@
backend.EnableTempFiles();
auto file = backend.RequestFile("test.log");
ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
- auto result = write(file->fd(), "testtest", 8);
- EXPECT_GT(result, 0);
+ auto result = Write(file.get(), "test");
+ EXPECT_EQ(result.code, WriteCode::kOk);
+ EXPECT_EQ(result.messages_written, 1);
EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
@@ -92,6 +107,109 @@
EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
}
+TEST(QueueAlignmentTest, Cases) {
+ QueueAligner aligner;
+ uint8_t *start = nullptr;
+ {
+ // Only prefix
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start + 1;
+ queue.emplace_back(current, FileHandler::kSector - 2);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 1);
+ const auto &prefix = aligner.aligned_queue()[0];
+ EXPECT_FALSE(prefix.aligned);
+ EXPECT_EQ(prefix.size, FileHandler::kSector - 2);
+ }
+ {
+ // Only main
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start;
+ queue.emplace_back(current, FileHandler::kSector);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 1);
+ const auto &main = aligner.aligned_queue()[0];
+ EXPECT_TRUE(main.aligned);
+ EXPECT_EQ(main.size, FileHandler::kSector);
+ }
+ {
+ // Empty
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start;
+ queue.emplace_back(current, 0);
+ EXPECT_DEATH(aligner.FillAlignedQueue(queue),
+ "Nobody should be sending empty messages");
+ }
+ {
+ // Main and suffix
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start;
+ queue.emplace_back(current, FileHandler::kSector + 1);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 2);
+
+ const auto &main = aligner.aligned_queue()[0];
+ EXPECT_TRUE(main.aligned);
+ EXPECT_EQ(main.size, FileHandler::kSector);
+
+ const auto &suffix = aligner.aligned_queue()[1];
+ EXPECT_FALSE(suffix.aligned);
+ EXPECT_EQ(suffix.size, 1);
+ }
+ {
+ // Prefix, main
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start + 1;
+ queue.emplace_back(current, 2 * FileHandler::kSector - 1);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 2);
+
+ const auto &prefix = aligner.aligned_queue()[0];
+ EXPECT_FALSE(prefix.aligned);
+ EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
+
+ const auto &main = aligner.aligned_queue()[1];
+ EXPECT_TRUE(main.aligned);
+ EXPECT_EQ(main.size, FileHandler::kSector);
+ }
+ {
+ // Prefix and suffix
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start + 1;
+ queue.emplace_back(current, 2 * FileHandler::kSector - 2);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 2);
+
+ const auto &prefix = aligner.aligned_queue()[0];
+ EXPECT_FALSE(prefix.aligned);
+ EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
+
+ const auto &suffix = aligner.aligned_queue()[1];
+ EXPECT_FALSE(suffix.aligned);
+ EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
+ }
+ {
+ // Prefix, main and suffix
+ std::vector<absl::Span<const uint8_t>> queue;
+ const uint8_t *current = start + 1;
+ queue.emplace_back(current, 3 * FileHandler::kSector - 2);
+ aligner.FillAlignedQueue(queue);
+ ASSERT_EQ(aligner.aligned_queue().size(), 3);
+
+ const auto &prefix = aligner.aligned_queue()[0];
+ EXPECT_FALSE(prefix.aligned);
+ EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
+
+ const auto &main = aligner.aligned_queue()[1];
+ EXPECT_TRUE(main.aligned);
+ EXPECT_EQ(main.size, FileHandler::kSector);
+
+ const auto &suffix = aligner.aligned_queue()[2];
+ EXPECT_FALSE(suffix.aligned);
+ EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
+ }
+}
+
// It represents calls to Write function (batching of calls and messages) where
// int values are sizes of each message in the queue.
using WriteRecipe = std::vector<std::vector<int>>;
@@ -209,7 +327,7 @@
std::uniform_int_distribution<int> lengths_distribution{
0, static_cast<int>(lengths.size() - 1)};
- for (int i = 0; i < 100000; ++i) {
+ for (int i = 0; i < 1000; ++i) {
WriteRecipe recipe;
int number_of_writes = count_distribution(engine2);
for (int j = 0; j < number_of_writes; ++j) {
@@ -261,6 +379,8 @@
auto result = handler->Write(queue);
EXPECT_EQ(result.code, WriteCode::kOk);
EXPECT_EQ(result.messages_written, queue.size());
+ FileHandler *file_handler = reinterpret_cast<FileHandler *>(handler.get());
+ EXPECT_GT(file_handler->written_aligned(), 0);
ASSERT_EQ(handler->Close(), WriteCode::kOk);
EXPECT_TRUE(std::filesystem::exists(file));
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index b9e940c..b06cf20 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -13,6 +13,7 @@
#include "aos/events/simulated_event_loop.h"
#include "aos/init.h"
#include "aos/json_to_flatbuffer.h"
+#include "aos/sha256.h"
#include "gflags/gflags.h"
DEFINE_string(
@@ -131,9 +132,8 @@
.max_vector_size = static_cast<size_t>(
FLAGS_max_vector_size)})
<< std::endl;
- CHECK_EQ(
- full_header->configuration_sha256()->string_view(),
- aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
+ CHECK_EQ(full_header->configuration_sha256()->string_view(),
+ aos::Sha256(raw_header_reader->raw_log_file_header().span()));
full_header = raw_header_reader->log_file_header();
}
diff --git a/aos/events/logging/log_edit.cc b/aos/events/logging/log_edit.cc
index 55b7666..7e822ca 100644
--- a/aos/events/logging/log_edit.cc
+++ b/aos/events/logging/log_edit.cc
@@ -48,12 +48,13 @@
aos::logger::SpanReader span_reader(orig_path);
CHECK(!span_reader.ReadMessage().empty()) << ": Empty header, aborting";
- aos::logger::DetachedBufferFileWriter buffer_writer(
- FLAGS_logfile,
+ aos::logger::FileBackend file_backend("/");
+ aos::logger::DetachedBufferWriter buffer_writer(
+ file_backend.RequestFile(FLAGS_logfile),
std::make_unique<aos::logger::DummyEncoder>(FLAGS_max_message_size));
{
- aos::logger::DataEncoder::SpanCopier coppier(header.span());
- buffer_writer.CopyMessage(&coppier, aos::monotonic_clock::min_time);
+ aos::logger::DataEncoder::SpanCopier copier(header.span());
+ buffer_writer.CopyMessage(&copier, aos::monotonic_clock::min_time);
}
while (true) {
@@ -63,8 +64,8 @@
}
{
- aos::logger::DataEncoder::SpanCopier coppier(msg_data);
- buffer_writer.CopyMessage(&coppier, aos::monotonic_clock::min_time);
+ aos::logger::DataEncoder::SpanCopier copier(msg_data);
+ buffer_writer.CopyMessage(&copier, aos::monotonic_clock::min_time);
}
}
} else {
diff --git a/aos/events/logging/log_namer.cc b/aos/events/logging/log_namer.cc
index 8ec1e70..4b23268 100644
--- a/aos/events/logging/log_namer.cc
+++ b/aos/events/logging/log_namer.cc
@@ -45,7 +45,7 @@
void NewDataWriter::Rotate() {
// No need to rotate if nothing has been written.
if (header_written_) {
- VLOG(1) << "Rotated " << filename();
+ VLOG(1) << "Rotated " << name();
++parts_index_;
reopen_(this);
header_written_ = false;
@@ -77,7 +77,7 @@
state_[node_index_].boot_uuid = source_node_boot_uuid;
- VLOG(1) << "Rebooted " << filename();
+ VLOG(1) << "Rebooted " << name();
}
void NewDataWriter::UpdateBoot(const UUID &source_node_boot_uuid) {
@@ -101,7 +101,7 @@
// Did the remote boot UUID change?
if (state.boot_uuid != remote_node_boot_uuid) {
- VLOG(1) << filename() << " Remote " << remote_node_index << " updated to "
+ VLOG(1) << name() << " Remote " << remote_node_index << " updated to "
<< remote_node_boot_uuid << " from " << state.boot_uuid;
state.boot_uuid = remote_node_boot_uuid;
state.oldest_remote_monotonic_timestamp = monotonic_clock::max_time;
@@ -124,7 +124,7 @@
if (!reliable) {
if (state.oldest_remote_unreliable_monotonic_timestamp >
monotonic_remote_time) {
- VLOG(1) << filename() << " Remote " << remote_node_index
+ VLOG(1) << name() << " Remote " << remote_node_index
<< " oldest_remote_unreliable_monotonic_timestamp updated from "
<< state.oldest_remote_unreliable_monotonic_timestamp << " to "
<< monotonic_remote_time;
@@ -136,7 +136,7 @@
} else {
if (state.oldest_remote_reliable_monotonic_timestamp >
monotonic_remote_time) {
- VLOG(1) << filename() << " Remote " << remote_node_index
+ VLOG(1) << name() << " Remote " << remote_node_index
<< " oldest_remote_reliable_monotonic_timestamp updated from "
<< state.oldest_remote_reliable_monotonic_timestamp << " to "
<< monotonic_remote_time;
@@ -153,7 +153,7 @@
if (monotonic_event_time <
logger_state.oldest_logger_remote_unreliable_monotonic_timestamp) {
VLOG(1)
- << filename() << " Remote " << node_index_
+ << name() << " Remote " << node_index_
<< " oldest_logger_remote_unreliable_monotonic_timestamp updated "
"from "
<< logger_state.oldest_logger_remote_unreliable_monotonic_timestamp
@@ -169,7 +169,7 @@
// Did any of the timestamps change?
if (state.oldest_remote_monotonic_timestamp > monotonic_remote_time) {
- VLOG(1) << filename() << " Remote " << remote_node_index
+ VLOG(1) << name() << " Remote " << remote_node_index
<< " oldest_remote_monotonic_timestamp updated from "
<< state.oldest_remote_monotonic_timestamp << " to "
<< monotonic_remote_time;
@@ -205,7 +205,7 @@
CHECK_EQ(state_[node_index_].boot_uuid, source_node_boot_uuid);
CHECK(writer);
CHECK(header_written_) << ": Attempting to write message before header to "
- << writer->filename();
+ << writer->name();
writer->CopyMessage(coppier, now);
}
@@ -214,7 +214,7 @@
const size_t logger_node_index = log_namer_->logger_node_index();
const UUID &logger_node_boot_uuid = log_namer_->logger_node_boot_uuid();
if (state_[logger_node_index].boot_uuid == UUID::Zero()) {
- VLOG(1) << filename() << " Logger node is " << logger_node_index
+ VLOG(1) << name() << " Logger node is " << logger_node_index
<< " and uuid is " << logger_node_boot_uuid;
state_[logger_node_index].boot_uuid = logger_node_boot_uuid;
} else {
@@ -227,7 +227,7 @@
void NewDataWriter::QueueHeader(
aos::SizePrefixedFlatbufferDetachedBuffer<LogFileHeader> &&header) {
CHECK(!header_written_) << ": Attempting to write duplicate header to "
- << writer->filename();
+ << writer->name();
CHECK(header.message().has_source_node_boot_uuid());
CHECK_EQ(state_[node_index_].boot_uuid,
UUID::FromString(header.message().source_node_boot_uuid()));
@@ -245,7 +245,7 @@
reopen_(this);
}
- VLOG(1) << "Writing to " << filename() << " "
+ VLOG(1) << "Writing to " << name() << " "
<< aos::FlatbufferToJson(
header, {.multi_line = false, .max_vector_size = 100});
@@ -560,14 +560,14 @@
return result;
}
-MultiNodeLogNamer::MultiNodeLogNamer(
- std::unique_ptr<RenamableFileBackend> log_backend, EventLoop *event_loop)
+MultiNodeLogNamer::MultiNodeLogNamer(std::unique_ptr<LogBackend> log_backend,
+ EventLoop *event_loop)
: MultiNodeLogNamer(std::move(log_backend), event_loop->configuration(),
event_loop, event_loop->node()) {}
-MultiNodeLogNamer::MultiNodeLogNamer(
- std::unique_ptr<RenamableFileBackend> log_backend,
- const Configuration *configuration, EventLoop *event_loop, const Node *node)
+MultiNodeLogNamer::MultiNodeLogNamer(std::unique_ptr<LogBackend> log_backend,
+ const Configuration *configuration,
+ EventLoop *event_loop, const Node *node)
: LogNamer(configuration, event_loop, node),
log_backend_(std::move(log_backend)),
encoder_factory_([](size_t max_message_size) {
@@ -714,9 +714,13 @@
return data_writer_.get();
}
-void MultiNodeLogNamer::Close() {
+WriteCode MultiNodeLogNamer::Close() {
data_writers_.clear();
data_writer_.reset();
+ if (ran_out_of_space_) {
+ return WriteCode::kOutOfSpace;
+ }
+ return WriteCode::kOk;
}
void MultiNodeLogNamer::ResetStatistics() {
@@ -822,7 +826,6 @@
return;
}
DetachedBufferWriter *const writer = writer_pointer->get();
- const bool was_open = writer->is_open();
writer->Close();
const auto *stats = writer->WriteStatistics();
@@ -840,11 +843,6 @@
ran_out_of_space_ = true;
writer->acknowledge_out_of_space();
}
-
- if (!was_open) {
- CHECK(access(std::string(writer->filename()).c_str(), F_OK) == -1)
- << ": File should not exist: " << writer->filename();
- }
}
} // namespace logger
diff --git a/aos/events/logging/log_namer.h b/aos/events/logging/log_namer.h
index 3b54025..8842f4e 100644
--- a/aos/events/logging/log_namer.h
+++ b/aos/events/logging/log_namer.h
@@ -43,7 +43,8 @@
void UpdateMaxMessageSize(size_t new_size) {
if (new_size > max_message_size_) {
- CHECK(!header_written_);
+ CHECK(!header_written_) << ": Tried to update to " << new_size << ", was "
+ << max_message_size_ << " for " << name();
max_message_size_ = new_size;
}
}
@@ -77,10 +78,8 @@
// update the remote timestamps.
void UpdateBoot(const UUID &source_node_boot_uuid);
- // Returns the filename of the writer.
- std::string_view filename() const {
- return writer ? writer->filename() : "(closed)";
- }
+ // Returns the name of the writer. It may be a filename, but assume it is not.
+ std::string_view name() const { return writer ? writer->name() : "(closed)"; }
void Close();
@@ -176,17 +175,6 @@
}
virtual ~LogNamer() = default;
- virtual std::string_view base_name() const = 0;
-
- // Rotate should be called at least once in between calls to set_base_name.
- // Otherwise temporary files will not be recoverable.
- // Rotate is called by Logger::RenameLogBase, which is currently the only user
- // of this method.
- // Only renaming the folder is supported, not the file base name.
- // TODO (Alexei): it should not be in interface, since it is not applied to
- // files.
- virtual void set_base_name(std::string_view base_name) = 0;
-
// Returns a writer for writing data from messages on this channel (on the
// primary node).
//
@@ -216,6 +204,10 @@
// Returns all the nodes that data is being written for.
const std::vector<const Node *> &nodes() const { return nodes_; }
+ // Closes all existing log data writers. No more data may be written after
+ // this.
+ virtual WriteCode Close() = 0;
+
// Returns the node the logger is running on.
const Node *node() const { return node_; }
const UUID &logger_node_boot_uuid() const { return logger_node_boot_uuid_; }
@@ -302,28 +294,13 @@
// Log namer which uses a config to name a bunch of files.
class MultiNodeLogNamer : public LogNamer {
public:
- MultiNodeLogNamer(std::unique_ptr<RenamableFileBackend> log_backend,
+ MultiNodeLogNamer(std::unique_ptr<LogBackend> log_backend,
EventLoop *event_loop);
- MultiNodeLogNamer(std::unique_ptr<RenamableFileBackend> log_backend,
+ MultiNodeLogNamer(std::unique_ptr<LogBackend> log_backend,
const Configuration *configuration, EventLoop *event_loop,
const Node *node);
~MultiNodeLogNamer() override;
- std::string_view base_name() const final { return log_backend_->base_name(); }
-
- void set_base_name(std::string_view base_name) final {
- log_backend_->RenameLogBase(base_name);
- }
-
- // When enabled, this will write files under names beginning
- // with the .tmp suffix, and then rename them to the desired name after
- // they are fully written.
- //
- // This is useful to enable incremental copying of the log files.
- //
- // Defaults to writing directly to the final filename.
- void EnableTempFiles() { log_backend_->EnableTempFiles(); }
-
// Sets the function for creating encoders. The argument is the max message
// size (including headers) that will be written into this encoder.
//
@@ -387,7 +364,7 @@
// Closes all existing log files. No more data may be written after this.
//
// This may set ran_out_of_space().
- void Close();
+ WriteCode Close() override;
// Accessors for various statistics. See the identically-named methods in
// DetachedBufferWriter for documentation. These are aggregated across all
@@ -458,6 +435,12 @@
void ResetStatistics();
+ protected:
+ // TODO (Alexei): consider to move ownership of log_namer to concrete sub
+ // class and make log_backend_ raw pointer.
+ LogBackend *log_backend() { return log_backend_.get(); }
+ const LogBackend *log_backend() const { return log_backend_.get(); }
+
private:
// Opens up a writer for timestamps forwarded back.
void OpenForwardedTimestampWriter(const Channel *channel,
@@ -488,7 +471,7 @@
return t;
}
- std::unique_ptr<RenamableFileBackend> log_backend_;
+ std::unique_ptr<LogBackend> log_backend_;
bool ran_out_of_space_ = false;
std::vector<std::string> all_filenames_;
@@ -524,6 +507,36 @@
: MultiNodeLogNamer(std::make_unique<RenamableFileBackend>(base_name),
configuration, event_loop, node) {}
~MultiNodeFilesLogNamer() override = default;
+
+ std::string_view base_name() const {
+ return renamable_file_backend()->base_name();
+ }
+
+ // Rotate should be called at least once in between calls to set_base_name.
+ // Otherwise, temporary files will not be recoverable.
+ // Rotate is called by Logger::RenameLogBase, which is currently the only user
+ // of this method.
+ // Only renaming the folder is supported, not the file base name.
+ void set_base_name(std::string_view base_name) {
+ renamable_file_backend()->RenameLogBase(base_name);
+ }
+
+ // When enabled, this will write files under names beginning
+ // with the .tmp suffix, and then rename them to the desired name after
+ // they are fully written.
+ //
+ // This is useful to enable incremental copying of the log files.
+ //
+ // Defaults to writing directly to the final filename.
+ void EnableTempFiles() { renamable_file_backend()->EnableTempFiles(); }
+
+ private:
+ RenamableFileBackend *renamable_file_backend() {
+ return reinterpret_cast<RenamableFileBackend *>(log_backend());
+ }
+ const RenamableFileBackend *renamable_file_backend() const {
+ return reinterpret_cast<const RenamableFileBackend *>(log_backend());
+ }
};
} // namespace logger
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 0af69c7..a299c61 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -1,5 +1,6 @@
#include "aos/events/logging/log_reader.h"
+#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -729,6 +730,7 @@
// running until the last node.
for (std::unique_ptr<State> &state : states_) {
+ CHECK(state);
VLOG(1) << "Start time is " << state->monotonic_start_time(0)
<< " for node " << MaybeNodeName(state->node()) << "now "
<< state->monotonic_now();
@@ -1977,9 +1979,8 @@
// Send! Use the replayed queue index here instead of the logged queue index
// for the remote queue index. This makes re-logging work.
- const auto err = sender->Send(
- RawSender::SharedSpan(timestamped_message.data,
- ×tamped_message.data->span),
+ const RawSender::Error err = sender->Send(
+ SharedSpan(timestamped_message.data, ×tamped_message.data->span),
timestamped_message.monotonic_remote_time.time,
timestamped_message.realtime_remote_time, remote_queue_index,
(channel_source_state_[timestamped_message.channel_index] != nullptr
diff --git a/aos/events/logging/log_reader.h b/aos/events/logging/log_reader.h
index 6a0fcea..e536c46 100644
--- a/aos/events/logging/log_reader.h
+++ b/aos/events/logging/log_reader.h
@@ -289,6 +289,9 @@
}
std::string_view name() const { return log_files_[0].name; }
+ std::string_view log_event_uuid() const {
+ return log_files_[0].log_event_uuid;
+ }
// Set whether to exit the SimulatedEventLoopFactory when we finish reading
// the logfile.
@@ -543,6 +546,7 @@
}
monotonic_clock::time_point monotonic_now() const {
+ CHECK_NOTNULL(event_loop_);
return event_loop_->monotonic_now();
}
@@ -729,8 +733,8 @@
std::vector<message_bridge::NoncausalOffsetEstimator *> filters_;
message_bridge::MultiNodeNoncausalOffsetEstimator *multinode_filters_;
- // List of NodeEventLoopFactorys (or nullptr if it isn't a forwarded
- // channel) which correspond to the originating node.
+ // List of States (or nullptr if it isn't a forwarded channel) which
+ // correspond to the originating node.
std::vector<State *> channel_source_state_;
// This is a cache for channel, connection mapping to the corresponding
diff --git a/aos/events/logging/log_writer.cc b/aos/events/logging/log_writer.cc
index 1a333e7..17c8da2 100644
--- a/aos/events/logging/log_writer.cc
+++ b/aos/events/logging/log_writer.cc
@@ -11,6 +11,7 @@
#include "aos/network/message_bridge_server_generated.h"
#include "aos/network/team_number.h"
#include "aos/network/timestamp_channel.h"
+#include "aos/sha256.h"
namespace aos {
namespace logger {
@@ -230,17 +231,6 @@
}
}
-bool Logger::RenameLogBase(std::string new_base_name) {
- // TODO(Naman): Got a crash in RenameLogBase. Putting in a CHECK_NOTNULL to
- // catch the bug if it happens again
- if (new_base_name == CHECK_NOTNULL(log_namer_)->base_name()) {
- return true;
- }
- log_namer_->set_base_name(new_base_name);
- Rotate();
- return true;
-}
-
std::string Logger::WriteConfiguration(LogNamer *log_namer) {
std::string config_sha256;
@@ -348,11 +338,44 @@
VLOG(1) << "Restarting logger for " << FlatbufferToJson(node_);
- // Force out every currently pending message, pointing all fetchers at the
- // last (currently available) records. Note that LogUntil() updates
- // last_synchronized_time_ to the time value that it receives.
- while (LogUntil(last_synchronized_time_ + polling_period_))
- ;
+ // Make sure not to write past now so we don't risk out of order problems. We
+ // don't want to get into a situation where we write out up to now + 0.1 sec,
+ // and that operation takes ~0.1 seconds, so we end up writing a different
+ // amount of the early and late channels. That would then result in the next
+ // go around finding more than 0.1 sec of data on the early channels.
+ //
+ // Make sure we read up until "now" and log it. This sets us up so that we
+ // are unlikely to fetch a message far in the future and have a ton of data
+ // before the offical start time.
+ monotonic_clock::time_point newest_record = monotonic_clock::min_time;
+ while (true) {
+ aos::monotonic_clock::time_point next_time =
+ last_synchronized_time_ + polling_period_;
+ const aos::monotonic_clock::time_point monotonic_now =
+ event_loop_->monotonic_now();
+ if (next_time > monotonic_now) {
+ next_time = monotonic_now;
+ }
+
+ bool wrote_messages = false;
+ std::tie(wrote_messages, newest_record) = LogUntil(next_time);
+
+ if (next_time == monotonic_now &&
+ (!wrote_messages || newest_record < monotonic_now + polling_period_)) {
+ // If we stopped writing messages, then we probably have stopped making
+ // progress. If the newest record (unwritten or written) on a channel is
+ // very close to the current time, then there won't be much data
+ // officially after the end of the last log but before the start of the
+ // current one. We need to pick the start of the current log to be after
+ // the last message on record so we don't have holes in the log.
+ break;
+ }
+ }
+
+ // We are now synchronized up to last_synchronized_time_. Our start time can
+ // safely be "newest_record". But, we need to guarentee that the start time
+ // is after the newest message we have a record of, and that we don't skip any
+ // messages as we rotate. This means we can't call Fetch anywhere.
std::unique_ptr<LogNamer> old_log_namer = std::move(log_namer_);
log_namer_ = std::move(log_namer);
@@ -371,13 +394,11 @@
<< "ns to swap log_namer";
}
- // Since we are going to log all in 1 big go, we need our log start time to
- // be after the previous LogUntil call finished, but before 1 period after
- // it. The best way to guarentee that is to pick a start time that is the
- // earliest of the two. That covers the case where the OS puts us to sleep
- // between when we finish LogUntil and capture beginning_time.
- const aos::monotonic_clock::time_point monotonic_start_time =
- std::min(last_synchronized_time_, beginning_time);
+ // Our start time is now the newest message we have a record of. We will
+ // declare the old log "done", and start in on the new one, double-logging
+ // anything we have a record of so we have all the messages from before the
+ // start.
+ const aos::monotonic_clock::time_point monotonic_start_time = newest_record;
const aos::realtime_clock::time_point realtime_start_time =
(beginning_time_rt + (monotonic_start_time.time_since_epoch() -
((beginning_time.time_since_epoch() +
@@ -402,66 +423,32 @@
const aos::monotonic_clock::time_point header_time =
event_loop_->monotonic_now();
- // Write the transition record(s) for each channel ...
+ // Close out the old writers to free up memory to be used by the new writers.
+ old_log_namer->Close();
+
for (FetcherStruct &f : fetchers_) {
// Create writers from the new namer
- NewDataWriter *next_writer = nullptr;
- NewDataWriter *next_timestamp_writer = nullptr;
- NewDataWriter *next_contents_writer = nullptr;
if (f.wants_writer) {
- next_writer = log_namer_->MakeWriter(f.channel);
+ f.writer = log_namer_->MakeWriter(f.channel);
}
if (f.wants_timestamp_writer) {
- next_timestamp_writer = log_namer_->MakeTimestampWriter(f.channel);
+ f.timestamp_writer = log_namer_->MakeTimestampWriter(f.channel);
}
if (f.wants_contents_writer) {
- next_contents_writer = log_namer_->MakeForwardedTimestampWriter(
+ f.contents_writer = log_namer_->MakeForwardedTimestampWriter(
f.channel, CHECK_NOTNULL(f.timestamp_node));
}
- if (f.fetcher->context().data != nullptr) {
- // Write the last message fetched as the first of the new log of this
- // type. The timestamps on these will all be before the new start time.
- WriteData(next_writer, f);
- WriteTimestamps(next_timestamp_writer, f);
- WriteContent(next_contents_writer, f);
-
- // It is possible that a few more snuck in. Write them all out also,
- // including any that should also be in the old log.
- while (true) {
- // Get the next message ...
- const auto start = event_loop_->monotonic_now();
- const bool got_new = f.fetcher->FetchNext();
- const auto end = event_loop_->monotonic_now();
- RecordFetchResult(start, end, got_new, &f);
-
- if (got_new) {
- if (f.fetcher->context().monotonic_event_time <=
- last_synchronized_time_) {
- WriteFetchedRecord(f);
- WriteData(next_writer, f);
- WriteTimestamps(next_timestamp_writer, f);
- WriteContent(next_contents_writer, f);
-
- } else {
- f.written = false;
- break;
- }
-
- } else {
- f.written = true;
- break;
- }
- }
- }
-
- // Switch fully over to the new writers.
- f.writer = next_writer;
- f.timestamp_writer = next_timestamp_writer;
- f.contents_writer = next_contents_writer;
+ // Mark each channel with data as not written. That triggers each channel
+ // to be re-logged.
+ f.written = f.fetcher->context().data == nullptr;
}
+ // And now make sure to log everything up to the start time in 1 big go so we
+ // make sure we have it before we let the world start logging normally again.
+ LogUntil(monotonic_start_time);
+
const aos::monotonic_clock::time_point channel_time =
event_loop_->monotonic_now();
@@ -498,6 +485,8 @@
log_event_uuid_ = UUID::Zero();
log_start_uuid_ = std::nullopt;
+ log_namer_->Close();
+
return std::move(log_namer_);
}
@@ -746,7 +735,7 @@
VLOG(2) << "Wrote data as node " << FlatbufferToJson(node_)
<< " for channel "
<< configuration::CleanedChannelToString(f.fetcher->channel())
- << " to " << writer->filename();
+ << " to " << writer->name();
}
}
@@ -771,7 +760,7 @@
VLOG(2) << "Wrote timestamps as node " << FlatbufferToJson(node_)
<< " for channel "
<< configuration::CleanedChannelToString(f.fetcher->channel())
- << " to " << timestamp_writer->filename() << " timestamp";
+ << " to " << timestamp_writer->name() << " timestamp";
}
}
@@ -828,8 +817,10 @@
WriteContent(f.contents_writer, f);
}
-bool Logger::LogUntil(monotonic_clock::time_point t) {
- bool has_pending_messages = false;
+std::pair<bool, monotonic_clock::time_point> Logger::LogUntil(
+ monotonic_clock::time_point t) {
+ bool wrote_messages = false;
+ monotonic_clock::time_point newest_record = monotonic_clock::min_time;
// Grab the latest ServerStatistics message. This will always have the
// oppertunity to be >= to the current time, so it will always represent any
@@ -838,6 +829,11 @@
// Write each channel to disk, one at a time.
for (FetcherStruct &f : fetchers_) {
+ if (f.fetcher->context().data != nullptr) {
+ newest_record =
+ std::max(newest_record, f.fetcher->context().monotonic_event_time);
+ }
+
while (true) {
if (f.written) {
const auto start = event_loop_->monotonic_now();
@@ -850,23 +846,25 @@
f.fetcher->channel());
break;
}
+ newest_record =
+ std::max(newest_record, f.fetcher->context().monotonic_event_time);
f.written = false;
}
// TODO(james): Write tests to exercise this logic.
if (f.fetcher->context().monotonic_event_time >= t) {
- has_pending_messages = true;
break;
}
WriteFetchedRecord(f);
+ wrote_messages = true;
f.written = true;
}
}
last_synchronized_time_ = t;
- return has_pending_messages;
+ return std::make_pair(wrote_messages, newest_record);
}
void Logger::DoLogData(const monotonic_clock::time_point end_time,
diff --git a/aos/events/logging/log_writer.h b/aos/events/logging/log_writer.h
index 0063c9b..65c68f4 100644
--- a/aos/events/logging/log_writer.h
+++ b/aos/events/logging/log_writer.h
@@ -139,11 +139,6 @@
std::unique_ptr<LogNamer> log_namer,
std::optional<UUID> log_start_uuid = std::nullopt);
- // Moves the current log location to the new name. Returns true if a change
- // was made, false otherwise.
- // Only renaming the folder is supported, not the file base name.
- bool RenameLogBase(std::string new_base_name);
-
// Stops logging. Ensures any messages through end_time make it into the log.
//
// If you want to stop ASAP, pass min_time to avoid reading any more messages.
@@ -250,9 +245,12 @@
// Fetches from each channel until all the data is logged. This is dangerous
// because it lets you log for more than 1 period. All calls need to verify
// that t isn't greater than 1 period in the future.
- // Returns true if there is at least one message that has been fetched but
- // not yet written.
- bool LogUntil(monotonic_clock::time_point t);
+ //
+ // Returns true if there is at least one message written, and also returns the
+ // timestamp of the newest record that any fetcher is pointing to, or min_time
+ // if there are no messages published on any logged channels.
+ std::pair<bool, monotonic_clock::time_point> LogUntil(
+ monotonic_clock::time_point t);
void RecordFetchResult(aos::monotonic_clock::time_point start,
aos::monotonic_clock::time_point end, bool got_new,
diff --git a/aos/events/logging/logfile_sorting.cc b/aos/events/logging/logfile_sorting.cc
index 89801b7..f0161c9 100644
--- a/aos/events/logging/logfile_sorting.cc
+++ b/aos/events/logging/logfile_sorting.cc
@@ -11,9 +11,9 @@
#include "aos/events/logging/logfile_utils.h"
#include "aos/flatbuffer_merge.h"
#include "aos/flatbuffers.h"
+#include "aos/sha256.h"
#include "aos/time/time.h"
#include "dirent.h"
-#include "openssl/sha.h"
#include "sys/stat.h"
#if ENABLE_S3
@@ -1276,6 +1276,21 @@
// .oldest_remote_unreliable_monotonic_timestamp=9223372036.854775807sec,
// .oldest_local_unreliable_monotonic_timestamp=9223372036.854775807sec
// }
+ // 4) One reliable, one unreliable, local times don't match. 1 < 2
+ // same message got sent, and with reliable timestamps, we don't
+ // know how long it took to cross the network.
+ // {
+ // .oldest_remote_reliable_monotonic_timestamp=9223372036.854775807sec,
+ // .oldest_local_reliable_monotonic_timestamp=9223372036.854775807sec
+ // .oldest_remote_unreliable_monotonic_timestamp=10.122999611sec,
+ // .oldest_local_unreliable_monotonic_timestamp=9.400951024sec,
+ // }
+ // {
+ // .oldest_remote_reliable_monotonic_timestamp=11.798054208sec,
+ // .oldest_local_reliable_monotonic_timestamp=23457.772660691sec,
+ // .oldest_remote_unreliable_monotonic_timestamp=9223372036.854775807sec,
+ // .oldest_local_unreliable_monotonic_timestamp=9223372036.854775807sec
+ // }
//
// Writing all this out for which timestamps we have out of all 32
// combinations, and which cases each of the correspond to:
@@ -1286,10 +1301,10 @@
// { }, {ru} no match -> fail, won't be in the list
// { u}, { } no match -> fail, won't be in the list
// { u}, { u} no match -> 1
- // { u}, {r } no match -> fail
+ // { u}, {r } no match -> 4
// { u}, {ru} no match -> 1
// {r }, { } no match -> fail, won't be in the list
- // {r }, { u} no match -> fail
+ // {r }, { u} no match -> 4
// {r }, {r } no match -> 2
// {r }, {ru} no match -> 2
// {ru}, { } no match -> fail, won't be in the list
@@ -1353,11 +1368,13 @@
aos::monotonic_clock::max_time;
if (both_unreliable) {
+ VLOG(1) << "Both Unreliable";
return std::get<1>(a)
.oldest_local_unreliable_monotonic_timestamp <
std::get<1>(b)
.oldest_local_unreliable_monotonic_timestamp;
} else if (both_reliable) {
+ VLOG(1) << "Both Reliable";
CHECK_NE(
std::get<1>(a).oldest_local_reliable_monotonic_timestamp,
std::get<1>(b).oldest_local_reliable_monotonic_timestamp)
@@ -1367,6 +1384,42 @@
return std::get<1>(a)
.oldest_local_reliable_monotonic_timestamp <
std::get<1>(b).oldest_local_reliable_monotonic_timestamp;
+
+ } else if (std::get<1>(a)
+ .oldest_local_reliable_monotonic_timestamp !=
+ aos::monotonic_clock::max_time &&
+ std::get<1>(b)
+ .oldest_local_unreliable_monotonic_timestamp !=
+ aos::monotonic_clock::max_time) {
+ VLOG(1)
+ << " Comparing Reliable "
+ << std::get<1>(a).oldest_local_reliable_monotonic_timestamp;
+ VLOG(1) << " Versus Uneliable "
+ << std::get<1>(b)
+ .oldest_local_unreliable_monotonic_timestamp;
+
+ return std::get<1>(a)
+ .oldest_local_reliable_monotonic_timestamp <
+ std::get<1>(b)
+ .oldest_local_unreliable_monotonic_timestamp;
+
+ } else if (std::get<1>(a)
+ .oldest_local_unreliable_monotonic_timestamp !=
+ aos::monotonic_clock::max_time &&
+ std::get<1>(b)
+ .oldest_local_reliable_monotonic_timestamp !=
+ aos::monotonic_clock::max_time) {
+ VLOG(1) << " Comparing Unreliable "
+ << std::get<1>(a)
+ .oldest_local_unreliable_monotonic_timestamp;
+ VLOG(1)
+ << " Versus Reliable "
+ << std::get<1>(b).oldest_local_reliable_monotonic_timestamp;
+
+ return std::get<1>(a)
+ .oldest_local_unreliable_monotonic_timestamp <
+ std::get<1>(b).oldest_local_reliable_monotonic_timestamp;
+
} else {
LOG(FATAL) << "Broken logic, unable to compare timestamps "
<< std::get<1>(a) << ", " << std::get<1>(b);
@@ -2143,19 +2196,5 @@
return stream;
}
-std::string Sha256(const absl::Span<const uint8_t> str) {
- unsigned char hash[SHA256_DIGEST_LENGTH];
- SHA256_CTX sha256;
- SHA256_Init(&sha256);
- SHA256_Update(&sha256, str.data(), str.size());
- SHA256_Final(hash, &sha256);
- std::stringstream ss;
- for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
- ss << std::hex << std::setw(2) << std::setfill('0')
- << static_cast<int>(hash[i]);
- }
- return ss.str();
-}
-
} // namespace logger
} // namespace aos
diff --git a/aos/events/logging/logfile_sorting.h b/aos/events/logging/logfile_sorting.h
index 9e99bb2..6f895f1 100644
--- a/aos/events/logging/logfile_sorting.h
+++ b/aos/events/logging/logfile_sorting.h
@@ -153,9 +153,6 @@
// Recursively searches for logfiles in argv[1] and onward.
std::vector<std::string> FindLogs(int argc, char **argv);
-// Returns the sha256 of a span.
-std::string Sha256(const absl::Span<const uint8_t> str);
-
} // namespace logger
} // namespace aos
diff --git a/aos/events/logging/logfile_utils.cc b/aos/events/logging/logfile_utils.cc
index 990e742..490393d 100644
--- a/aos/events/logging/logfile_utils.cc
+++ b/aos/events/logging/logfile_utils.cc
@@ -80,12 +80,11 @@
}
} // namespace
-DetachedBufferWriter::DetachedBufferWriter(
- std::unique_ptr<FileHandler> file_handler,
- std::unique_ptr<DataEncoder> encoder)
- : file_handler_(std::move(file_handler)), encoder_(std::move(encoder)) {
- CHECK(file_handler_);
- ran_out_of_space_ = file_handler_->OpenForWrite() == WriteCode::kOutOfSpace;
+DetachedBufferWriter::DetachedBufferWriter(std::unique_ptr<LogSink> log_sink,
+ std::unique_ptr<DataEncoder> encoder)
+ : log_sink_(std::move(log_sink)), encoder_(std::move(encoder)) {
+ CHECK(log_sink_);
+ ran_out_of_space_ = log_sink_->OpenForWrite() == WriteCode::kOutOfSpace;
if (ran_out_of_space_) {
LOG(WARNING) << "And we are out of space";
}
@@ -108,7 +107,7 @@
// (because that data will then be its data).
DetachedBufferWriter &DetachedBufferWriter::operator=(
DetachedBufferWriter &&other) {
- std::swap(file_handler_, other.file_handler_);
+ std::swap(log_sink_, other.log_sink_);
std::swap(encoder_, other.encoder_);
std::swap(ran_out_of_space_, other.ran_out_of_space_);
std::swap(acknowledge_ran_out_of_space_, other.acknowledge_ran_out_of_space_);
@@ -147,14 +146,15 @@
}
void DetachedBufferWriter::Close() {
- if (!file_handler_->is_open()) {
+ if (!log_sink_->is_open()) {
return;
}
encoder_->Finish();
while (encoder_->queue_size() > 0) {
Flush(monotonic_clock::max_time);
}
- ran_out_of_space_ = file_handler_->Close() == WriteCode::kOutOfSpace;
+ encoder_.reset();
+ ran_out_of_space_ = log_sink_->Close() == WriteCode::kOutOfSpace;
}
void DetachedBufferWriter::Flush(aos::monotonic_clock::time_point now) {
@@ -177,7 +177,7 @@
return;
}
- const WriteResult result = file_handler_->Write(queue);
+ const WriteResult result = log_sink_->Write(queue);
encoder_->Clear(result.messages_written);
ran_out_of_space_ = result.code == WriteCode::kOutOfSpace;
}
diff --git a/aos/events/logging/logfile_utils.h b/aos/events/logging/logfile_utils.h
index c75c8fb..0bf60a0 100644
--- a/aos/events/logging/logfile_utils.h
+++ b/aos/events/logging/logfile_utils.h
@@ -51,7 +51,7 @@
// Marker struct for one of our constructor overloads.
struct already_out_of_space_t {};
- DetachedBufferWriter(std::unique_ptr<FileHandler> file_handler,
+ DetachedBufferWriter(std::unique_ptr<LogSink> log_sink,
std::unique_ptr<DataEncoder> encoder);
// Creates a dummy instance which won't even open a file. It will act as if
// opening the file ran out of space immediately.
@@ -64,11 +64,11 @@
DetachedBufferWriter &operator=(DetachedBufferWriter &&other);
DetachedBufferWriter &operator=(const DetachedBufferWriter &) = delete;
- std::string_view filename() const { return file_handler_->filename(); }
+ std::string_view name() const { return log_sink_->name(); }
// This will be true until Close() is called, unless the file couldn't be
// created due to running out of space.
- bool is_open() const { return file_handler_->is_open(); }
+ bool is_open() const { return log_sink_->is_open(); }
// Queues up a finished FlatBufferBuilder to be encoded and written.
//
@@ -106,7 +106,7 @@
return encoder_->total_bytes();
}
- WriteStats* WriteStatistics() const { return file_handler_->WriteStatistics(); }
+ WriteStats *WriteStatistics() const { return log_sink_->WriteStatistics(); }
private:
// Performs a single writev call with as much of the data we have queued up as
@@ -124,7 +124,7 @@
// the current time. It just needs to be close.
void FlushAtThreshold(aos::monotonic_clock::time_point now);
- std::unique_ptr<FileHandler> file_handler_;
+ std::unique_ptr<LogSink> log_sink_;
std::unique_ptr<DataEncoder> encoder_;
bool ran_out_of_space_ = false;
@@ -134,17 +134,6 @@
aos::monotonic_clock::min_time;
};
-// Specialized writer to single file
-class DetachedBufferFileWriter : public FileBackend,
- public DetachedBufferWriter {
- public:
- DetachedBufferFileWriter(std::string_view filename,
- std::unique_ptr<DataEncoder> encoder)
- : FileBackend("/"),
- DetachedBufferWriter(FileBackend::RequestFile(filename),
- std::move(encoder)) {}
-};
-
// Repacks the provided RemoteMessage into fbb.
flatbuffers::Offset<MessageHeader> PackRemoteMessage(
flatbuffers::FlatBufferBuilder *fbb,
diff --git a/aos/events/logging/logfile_utils_out_of_space_test_runner.cc b/aos/events/logging/logfile_utils_out_of_space_test_runner.cc
index 03bfd0a..71c537f 100644
--- a/aos/events/logging/logfile_utils_out_of_space_test_runner.cc
+++ b/aos/events/logging/logfile_utils_out_of_space_test_runner.cc
@@ -18,8 +18,9 @@
std::array<uint8_t, 10240> data;
data.fill(0);
- aos::logger::DetachedBufferFileWriter writer(
- FLAGS_tmpfs + "/file",
+ aos::logger::FileBackend file_backend("/");
+ aos::logger::DetachedBufferWriter writer(
+ file_backend.RequestFile(FLAGS_tmpfs + "/file"),
std::make_unique<aos::logger::DummyEncoder>(data.size()));
for (int i = 0; i < 8; ++i) {
aos::logger::DataEncoder::SpanCopier coppier(data);
diff --git a/aos/events/logging/logfile_utils_test.cc b/aos/events/logging/logfile_utils_test.cc
index e2dc8aa..8d5b0fb 100644
--- a/aos/events/logging/logfile_utils_test.cc
+++ b/aos/events/logging/logfile_utils_test.cc
@@ -30,13 +30,15 @@
// Adapter class to make it easy to test DetachedBufferWriter without adding
// test only boilerplate to DetachedBufferWriter.
-class TestDetachedBufferWriter : public DetachedBufferFileWriter {
+class TestDetachedBufferWriter : public FileBackend,
+ public DetachedBufferWriter {
public:
// Pick a max size that is rather conservative.
static constexpr size_t kMaxMessageSize = 128 * 1024;
TestDetachedBufferWriter(std::string_view filename)
- : DetachedBufferFileWriter(
- filename, std::make_unique<DummyEncoder>(kMaxMessageSize)) {}
+ : FileBackend("/"),
+ DetachedBufferWriter(FileBackend::RequestFile(filename),
+ std::make_unique<DummyEncoder>(kMaxMessageSize)) {}
void WriteSizedFlatbuffer(flatbuffers::DetachedBuffer &&buffer) {
QueueSpan(absl::Span<const uint8_t>(buffer.data(), buffer.size()));
}
diff --git a/aos/events/logging/multinode_logger_test.cc b/aos/events/logging/multinode_logger_test.cc
index 18337be..9e2d3a3 100644
--- a/aos/events/logging/multinode_logger_test.cc
+++ b/aos/events/logging/multinode_logger_test.cc
@@ -1,3 +1,5 @@
+#include <algorithm>
+
#include "aos/events/logging/log_reader.h"
#include "aos/events/logging/multinode_logger_test_lib.h"
#include "aos/events/message_counter.h"
@@ -1814,8 +1816,16 @@
logfile_base1_ = tmp_dir_ + "/new-good/multi_logfile1";
logfile_base2_ = tmp_dir_ + "/new-good/multi_logfile2";
logfiles_ = MakeLogFiles(logfile_base1_, logfile_base2_);
- ASSERT_TRUE(pi1_logger.logger->RenameLogBase(logfile_base1_));
- ASSERT_TRUE(pi2_logger.logger->RenameLogBase(logfile_base2_));
+
+ // Sequence of set_base_name and Rotate simulates rename operation. Since
+ // rename is not supported by all namers, RenameLogBase moved from logger to
+ // the higher level abstraction, yet log_namers support rename, and it is
+ // legal to test it here.
+ pi1_logger.log_namer->set_base_name(logfile_base1_);
+ pi1_logger.logger->Rotate();
+ pi2_logger.log_namer->set_base_name(logfile_base2_);
+ pi2_logger.logger->Rotate();
+
for (auto &file : logfiles_) {
struct stat s;
EXPECT_EQ(0, stat(file.c_str(), &s));
@@ -1834,7 +1844,7 @@
StartLogger(&pi1_logger);
event_loop_factory_.RunFor(chrono::milliseconds(10000));
logfile_base1_ = tmp_dir_ + "/new-renamefile/new_multi_logfile1";
- EXPECT_DEATH({ pi1_logger.logger->RenameLogBase(logfile_base1_); },
+ EXPECT_DEATH({ pi1_logger.log_namer->set_base_name(logfile_base1_); },
"Rename of file base from");
}
@@ -3593,6 +3603,146 @@
ConfirmReadable(filenames);
}
+// Tests that we properly handle only one direction ever existing after a reboot
+// with mixed unreliable vs reliable, where reliable has an earlier timestamp
+// than unreliable.
+TEST(MissingDirectionTest, OneDirectionAfterRebootMixedCase1) {
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig(ArtifactPath(
+ "aos/events/logging/multinode_pingpong_split4_mixed1_config.json"));
+ message_bridge::TestingTimeConverter time_converter(
+ configuration::NodesCount(&config.message()));
+ SimulatedEventLoopFactory event_loop_factory(&config.message());
+ event_loop_factory.SetTimeConverter(&time_converter);
+
+ NodeEventLoopFactory *const pi1 =
+ event_loop_factory.GetNodeEventLoopFactory("pi1");
+ const size_t pi1_index = configuration::GetNodeIndex(
+ event_loop_factory.configuration(), pi1->node());
+ NodeEventLoopFactory *const pi2 =
+ event_loop_factory.GetNodeEventLoopFactory("pi2");
+ const size_t pi2_index = configuration::GetNodeIndex(
+ event_loop_factory.configuration(), pi2->node());
+ std::vector<std::string> filenames;
+
+ {
+ CHECK_EQ(pi1_index, 0u);
+ CHECK_EQ(pi2_index, 1u);
+
+ time_converter.AddNextTimestamp(
+ distributed_clock::epoch(),
+ {BootTimestamp::epoch(), BootTimestamp::epoch()});
+
+ const chrono::nanoseconds reboot_time = chrono::milliseconds(5000);
+ time_converter.AddNextTimestamp(
+ distributed_clock::epoch() + reboot_time,
+ {BootTimestamp{.boot = 1, .time = monotonic_clock::epoch()},
+ BootTimestamp::epoch() + reboot_time});
+ }
+
+ const std::string kLogfile2_1 =
+ aos::testing::TestTmpDir() + "/multi_logfile2.1/";
+ util::UnlinkRecursive(kLogfile2_1);
+
+ // The following sequence using the above reference config creates
+ // a reliable message timestamp < unreliable message timestamp.
+ {
+ pi1->DisableStatistics();
+ pi2->DisableStatistics();
+
+ event_loop_factory.RunFor(chrono::milliseconds(95));
+
+ pi1->AlwaysStart<Ping>("ping");
+
+ event_loop_factory.RunFor(chrono::milliseconds(5250));
+
+ pi1->EnableStatistics();
+
+ event_loop_factory.RunFor(chrono::milliseconds(1000));
+
+ LoggerState pi2_logger = MakeLoggerState(
+ pi2, &event_loop_factory, SupportedCompressionAlgorithms()[0]);
+
+ pi2_logger.StartLogger(kLogfile2_1);
+
+ event_loop_factory.RunFor(chrono::milliseconds(5000));
+ pi2_logger.AppendAllFilenames(&filenames);
+ }
+
+ const std::vector<LogFile> sorted_parts = SortParts(filenames);
+ ConfirmReadable(filenames);
+}
+
+// Tests that we properly handle only one direction ever existing after a reboot
+// with mixed unreliable vs reliable, where unreliable has an earlier timestamp
+// than reliable.
+TEST(MissingDirectionTest, OneDirectionAfterRebootMixedCase2) {
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig(ArtifactPath(
+ "aos/events/logging/multinode_pingpong_split4_mixed2_config.json"));
+ message_bridge::TestingTimeConverter time_converter(
+ configuration::NodesCount(&config.message()));
+ SimulatedEventLoopFactory event_loop_factory(&config.message());
+ event_loop_factory.SetTimeConverter(&time_converter);
+
+ NodeEventLoopFactory *const pi1 =
+ event_loop_factory.GetNodeEventLoopFactory("pi1");
+ const size_t pi1_index = configuration::GetNodeIndex(
+ event_loop_factory.configuration(), pi1->node());
+ NodeEventLoopFactory *const pi2 =
+ event_loop_factory.GetNodeEventLoopFactory("pi2");
+ const size_t pi2_index = configuration::GetNodeIndex(
+ event_loop_factory.configuration(), pi2->node());
+ std::vector<std::string> filenames;
+
+ {
+ CHECK_EQ(pi1_index, 0u);
+ CHECK_EQ(pi2_index, 1u);
+
+ time_converter.AddNextTimestamp(
+ distributed_clock::epoch(),
+ {BootTimestamp::epoch(), BootTimestamp::epoch()});
+
+ const chrono::nanoseconds reboot_time = chrono::milliseconds(5000);
+ time_converter.AddNextTimestamp(
+ distributed_clock::epoch() + reboot_time,
+ {BootTimestamp{.boot = 1, .time = monotonic_clock::epoch()},
+ BootTimestamp::epoch() + reboot_time});
+ }
+
+ const std::string kLogfile2_1 =
+ aos::testing::TestTmpDir() + "/multi_logfile2.1/";
+ util::UnlinkRecursive(kLogfile2_1);
+
+ // The following sequence using the above reference config creates
+ // an unreliable message timestamp < reliable message timestamp.
+ {
+ pi1->DisableStatistics();
+ pi2->DisableStatistics();
+
+ event_loop_factory.RunFor(chrono::milliseconds(95));
+
+ pi1->AlwaysStart<Ping>("ping");
+
+ event_loop_factory.RunFor(chrono::milliseconds(5250));
+
+ pi1->EnableStatistics();
+
+ event_loop_factory.RunFor(chrono::milliseconds(1000));
+
+ LoggerState pi2_logger = MakeLoggerState(
+ pi2, &event_loop_factory, SupportedCompressionAlgorithms()[0]);
+
+ pi2_logger.StartLogger(kLogfile2_1);
+
+ event_loop_factory.RunFor(chrono::milliseconds(5000));
+ pi2_logger.AppendAllFilenames(&filenames);
+ }
+
+ const std::vector<LogFile> sorted_parts = SortParts(filenames);
+ ConfirmReadable(filenames);
+}
+
// Tests that we properly handle what used to be a time violation in one
// direction. This can occur when one direction goes down after sending some
// data, but the other keeps working. The down direction ends up resolving to a
@@ -3802,6 +3952,52 @@
auto result = ConfirmReadable(filenames);
}
+// Tests that RestartLogging works in the simple case. Unfortunately, the
+// failure cases involve simulating time elapsing in callbacks, which is really
+// hard. The best we can reasonably do is make sure 2 back to back logs are
+// parseable together.
+TEST_P(MultinodeLoggerTest, RestartLogging) {
+ time_converter_.AddMonotonic(
+ {BootTimestamp::epoch(), BootTimestamp::epoch() + chrono::seconds(1000)});
+ std::vector<std::string> filenames;
+ {
+ LoggerState pi1_logger = MakeLogger(pi1_);
+
+ event_loop_factory_.RunFor(chrono::milliseconds(95));
+
+ StartLogger(&pi1_logger, logfile_base1_);
+ aos::monotonic_clock::time_point last_rotation_time =
+ pi1_logger.event_loop->monotonic_now();
+ pi1_logger.logger->set_on_logged_period([&] {
+ const auto now = pi1_logger.event_loop->monotonic_now();
+ if (now > last_rotation_time + std::chrono::seconds(5)) {
+ pi1_logger.AppendAllFilenames(&filenames);
+ std::unique_ptr<MultiNodeFilesLogNamer> namer =
+ pi1_logger.MakeLogNamer(logfile_base2_);
+ pi1_logger.log_namer = namer.get();
+
+ pi1_logger.logger->RestartLogging(std::move(namer));
+ last_rotation_time = now;
+ }
+ });
+
+ event_loop_factory_.RunFor(chrono::milliseconds(7000));
+
+ pi1_logger.AppendAllFilenames(&filenames);
+ }
+
+ for (const auto &x : filenames) {
+ LOG(INFO) << x;
+ }
+
+ EXPECT_GE(filenames.size(), 2u);
+
+ ConfirmReadable(filenames);
+
+ // TODO(austin): It would be good to confirm that any one time messages end up
+ // in both logs correctly.
+}
+
} // namespace testing
} // namespace logger
} // namespace aos
diff --git a/aos/events/logging/multinode_logger_test_lib.cc b/aos/events/logging/multinode_logger_test_lib.cc
index 22822e7..fdee4d8 100644
--- a/aos/events/logging/multinode_logger_test_lib.cc
+++ b/aos/events/logging/multinode_logger_test_lib.cc
@@ -29,6 +29,16 @@
params};
}
+std::unique_ptr<MultiNodeFilesLogNamer> LoggerState::MakeLogNamer(
+ std::string logfile_base) {
+ std::unique_ptr<MultiNodeFilesLogNamer> namer =
+ std::make_unique<MultiNodeFilesLogNamer>(logfile_base, configuration,
+ event_loop.get(), node);
+ namer->set_extension(params.extension);
+ namer->set_encoder_factory(params.encoder_factory);
+ return namer;
+}
+
void LoggerState::StartLogger(std::string logfile_base) {
CHECK(!logfile_base.empty());
@@ -41,11 +51,7 @@
logger->set_logger_version(
absl::StrCat("logger_version_", event_loop->node()->name()->str()));
event_loop->OnRun([this, logfile_base]() {
- std::unique_ptr<MultiNodeFilesLogNamer> namer =
- std::make_unique<MultiNodeFilesLogNamer>(logfile_base, configuration,
- event_loop.get(), node);
- namer->set_extension(params.extension);
- namer->set_encoder_factory(params.encoder_factory);
+ std::unique_ptr<MultiNodeFilesLogNamer> namer = MakeLogNamer(logfile_base);
log_namer = namer.get();
logger->StartLogging(std::move(namer));
diff --git a/aos/events/logging/multinode_logger_test_lib.h b/aos/events/logging/multinode_logger_test_lib.h
index 40b5933..7502e4d 100644
--- a/aos/events/logging/multinode_logger_test_lib.h
+++ b/aos/events/logging/multinode_logger_test_lib.h
@@ -43,11 +43,14 @@
struct LoggerState {
void StartLogger(std::string logfile_base);
+ std::unique_ptr<MultiNodeFilesLogNamer> MakeLogNamer(
+ std::string logfile_base);
+
std::unique_ptr<EventLoop> event_loop;
std::unique_ptr<Logger> logger;
const Configuration *configuration;
const Node *node;
- MultiNodeLogNamer *log_namer;
+ MultiNodeFilesLogNamer *log_namer;
CompressionParams params;
void AppendAllFilenames(std::vector<std::string> *filenames);
@@ -56,13 +59,13 @@
};
constexpr std::string_view kCombinedConfigSha1() {
- return "c8cd3762e42a4e19b2155f63ccec97d1627a2fbd34d3da3ea6541128ca22b899";
+ return "e630fdd5533159ddad89075f93d9df90ae93a5a5841d6af7e1ec86875792bf27";
}
constexpr std::string_view kSplitConfigSha1() {
- return "0ee6360b3e82a46f3f8b241661934abac53957d494a81ed1938899c220334954";
+ return "7ed547b800f84e5b56825d11d39d3686fb770c2021658c3a9031f2cbf94e82a4";
}
constexpr std::string_view kReloggedSplitConfigSha1() {
- return "cc31e1a644dd7bf65d72247aea3e09b3474753e01921f3b6272f8233f288a16b";
+ return "7b17a3349852133aa56790fce93650b82455bad36ac669a4adebf33419c8ece9";
}
LoggerState MakeLoggerState(NodeEventLoopFactory *node,
diff --git a/aos/events/logging/multinode_pingpong_split4_mixed1.json b/aos/events/logging/multinode_pingpong_split4_mixed1.json
new file mode 100644
index 0000000..d376b00
--- /dev/null
+++ b/aos/events/logging/multinode_pingpong_split4_mixed1.json
@@ -0,0 +1,176 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ /* Logged on pi1 locally */
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi2"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi2"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi2"],
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi1"],
+ "source_node": "pi2",
+ "destination_nodes": [
+ {
+ "name": "pi1"
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi1",
+ "frequency": 150
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/test/aos-examples-Pong",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi2",
+ "frequency": 150
+ },
+ /* Forwarded to pi2 */
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi2"],
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1
+ }
+ ],
+ "frequency": 150
+ },
+ /* Forwarded back to pi1.
+ * The message is logged both on the sending node and the receiving node
+ * (to make it easier to look at the results for now).
+ *
+ * The timestamps are logged on the receiving node.
+ */
+ {
+ "name": "/test",
+ "type": "aos.examples.Pong",
+ "source_node": "pi2",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi1"],
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1
+ }
+ ],
+ "frequency": 150
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1",
+ "hostname": "raspberrypi",
+ "port": 9971
+ },
+ {
+ "name": "pi2",
+ "hostname": "raspberrypi2",
+ "port": 9971
+ }
+ ]
+}
diff --git a/aos/events/logging/multinode_pingpong_split4_mixed2.json b/aos/events/logging/multinode_pingpong_split4_mixed2.json
new file mode 100644
index 0000000..57235e0
--- /dev/null
+++ b/aos/events/logging/multinode_pingpong_split4_mixed2.json
@@ -0,0 +1,176 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ /* Logged on pi1 locally */
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi2"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "logger": "LOCAL_LOGGER",
+ "source_node": "pi2"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi2"],
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2"
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi1"],
+ "source_node": "pi2",
+ "destination_nodes": [
+ {
+ "name": "pi1"
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi1",
+ "frequency": 150
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/test/aos-examples-Pong",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "num_senders": 2,
+ "source_node": "pi2",
+ "frequency": 150
+ },
+ /* Forwarded to pi2 */
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi2"],
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "time_to_live": 5000000
+ }
+ ],
+ "frequency": 150
+ },
+ /* Forwarded back to pi1.
+ * The message is logged both on the sending node and the receiving node
+ * (to make it easier to look at the results for now).
+ *
+ * The timestamps are logged on the receiving node.
+ */
+ {
+ "name": "/test",
+ "type": "aos.examples.Pong",
+ "source_node": "pi2",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi1"],
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1
+ }
+ ],
+ "frequency": 150
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1",
+ "hostname": "raspberrypi",
+ "port": 9971
+ },
+ {
+ "name": "pi2",
+ "hostname": "raspberrypi2",
+ "port": 9971
+ }
+ ]
+}
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index c679b21..c021a84 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -49,41 +49,6 @@
const bool prior_;
};
-// Holds storage for a span object and the data referenced by that span for
-// compatibility with RawSender::SharedSpan users. If constructed with
-// MakeSharedSpan, span points to only the aligned segment of the entire data.
-struct AlignedOwningSpan {
- AlignedOwningSpan(const AlignedOwningSpan &) = delete;
- AlignedOwningSpan &operator=(const AlignedOwningSpan &) = delete;
- absl::Span<const uint8_t> span;
- char data[];
-};
-
-// Constructs a span which owns its data through a shared_ptr. The owning span
-// points to a const view of the data; also returns a temporary mutable span
-// which is only valid while the const shared span is kept alive.
-std::pair<RawSender::SharedSpan, absl::Span<uint8_t>> MakeSharedSpan(
- size_t size) {
- AlignedOwningSpan *const span = reinterpret_cast<AlignedOwningSpan *>(
- malloc(sizeof(AlignedOwningSpan) + size + kChannelDataAlignment - 1));
-
- absl::Span<uint8_t> mutable_span(
- reinterpret_cast<uint8_t *>(RoundChannelData(&span->data[0], size)),
- size);
- // Use the placement new operator to construct an actual absl::Span in place.
- new (&span->span) absl::Span(mutable_span);
-
- return std::make_pair(
- RawSender::SharedSpan(
- std::shared_ptr<AlignedOwningSpan>(span,
- [](AlignedOwningSpan *s) {
- s->~AlignedOwningSpan();
- free(s);
- }),
- &span->span),
- mutable_span);
-}
-
// Container for both a message, and the context for it for simulation. This
// makes tracking the timestamps associated with the data easy.
struct SimulatedMessage final {
@@ -93,8 +58,8 @@
// Creates a SimulatedMessage with size bytes of storage.
// This is a shared_ptr so we don't have to implement refcounting or copying.
- static std::shared_ptr<SimulatedMessage> Make(
- SimulatedChannel *channel, const RawSender::SharedSpan data);
+ static std::shared_ptr<SimulatedMessage> Make(SimulatedChannel *channel,
+ const SharedSpan data);
// Context for the data.
Context context;
@@ -103,7 +68,7 @@
// Owning span to this message's data. Depending on the sender may either
// represent the data of just the flatbuffer, or max channel size.
- RawSender::SharedSpan data;
+ SharedSpan data;
// Mutable view of above data. If empty, this message is not mutable.
absl::Span<uint8_t> mutable_data;
@@ -336,7 +301,7 @@
namespace {
std::shared_ptr<SimulatedMessage> SimulatedMessage::Make(
- SimulatedChannel *channel, RawSender::SharedSpan data) {
+ SimulatedChannel *channel, SharedSpan data) {
// The allocations in here are due to infrastructure and don't count in the no
// mallocs in RT code.
ScopedNotRealtime nrt;
@@ -1165,8 +1130,7 @@
}
RawSender::Error SimulatedSender::DoSend(
- const RawSender::SharedSpan data,
- monotonic_clock::time_point monotonic_remote_time,
+ const SharedSpan data, monotonic_clock::time_point monotonic_remote_time,
realtime_clock::time_point realtime_remote_time,
uint32_t remote_queue_index, const UUID &source_boot_uuid) {
CHECK_LE(data->size(), this->size())
diff --git a/aos/ipc_lib/lockless_queue.cc b/aos/ipc_lib/lockless_queue.cc
index f380848..8e0e3d8 100644
--- a/aos/ipc_lib/lockless_queue.cc
+++ b/aos/ipc_lib/lockless_queue.cc
@@ -1063,11 +1063,12 @@
"Retrying.";
continue;
} else {
- VLOG(3) << "Messages sent too fast. Returning. Attempted index: "
+ VLOG(1) << "Messages sent too fast. Returning. Attempted index: "
<< decremented_queue_index.index()
<< " message sent time: " << message->header.monotonic_sent_time
<< " message to replace sent time: "
<< to_replace_monotonic_sent_time;
+
// Since we are not using the message obtained from scratch_index
// and we are not retrying, we need to invalidate its queue_index.
message->header.queue_index.Invalidate();
diff --git a/aos/libc/aos_strsignal.cc b/aos/libc/aos_strsignal.cc
index 0f9b065..cf1aad1 100644
--- a/aos/libc/aos_strsignal.cc
+++ b/aos/libc/aos_strsignal.cc
@@ -15,9 +15,23 @@
return buffer;
}
+// sys_strsignal depricated in glibc2.32
+#ifdef __GLIBC__
+ #if __GLIBC_PREREQ(2, 32)
+ if (signal > 0 && signal < NSIG && sigdescr_np(signal) != nullptr) {
+ return sigdescr_np(signal);
+ }
+ #else
if (signal > 0 && signal < NSIG && sys_siglist[signal] != nullptr) {
return sys_siglist[signal];
}
+ #endif
+// If not using GLIBC assume we can use sys_siglist
+#else
+ if (signal > 0 && signal < NSIG && sys_siglist[signal] != nullptr) {
+ return sys_siglist[signal];
+ }
+#endif
CHECK_GT(snprintf(buffer, sizeof(buffer), "Unknown signal %d", signal), 0);
return buffer;
diff --git a/aos/network/BUILD b/aos/network/BUILD
index 88df785..527fd46 100644
--- a/aos/network/BUILD
+++ b/aos/network/BUILD
@@ -274,6 +274,7 @@
":message_bridge_server_lib",
"//aos:init",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:shm_event_loop",
"//aos/logging:dynamic_logging",
],
@@ -356,6 +357,7 @@
":message_bridge_client_lib",
"//aos:init",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:shm_event_loop",
"//aos/logging:dynamic_logging",
],
@@ -422,6 +424,7 @@
":message_bridge_client_lib",
":message_bridge_server_lib",
"//aos:json_to_flatbuffer",
+ "//aos:sha256",
"//aos/events:ping_fbs",
"//aos/events:pong_fbs",
"//aos/events:shm_event_loop",
diff --git a/aos/network/connect.fbs b/aos/network/connect.fbs
index 7ff2fbe..5c7fb21 100644
--- a/aos/network/connect.fbs
+++ b/aos/network/connect.fbs
@@ -13,6 +13,9 @@
// The UUID that this node booted with.
boot_uuid: string (id: 2);
+
+ // Sha256 of the AOS config that this node is running with.
+ config_sha256: string (id: 3);
}
root_type Connect;
diff --git a/aos/network/message_bridge_client.cc b/aos/network/message_bridge_client.cc
index 8aad867..c3f55ba 100644
--- a/aos/network/message_bridge_client.cc
+++ b/aos/network/message_bridge_client.cc
@@ -2,6 +2,7 @@
#include "aos/init.h"
#include "aos/logging/dynamic_logging.h"
#include "aos/network/message_bridge_client_lib.h"
+#include "aos/sha256.h"
DEFINE_string(config, "aos_config.json", "Path to the config.");
DEFINE_int32(rt_priority, -1, "If > 0, run as this RT priority");
@@ -18,7 +19,7 @@
event_loop.SetRuntimeRealtimePriority(FLAGS_rt_priority);
}
- MessageBridgeClient app(&event_loop);
+ MessageBridgeClient app(&event_loop, Sha256(config.span()));
logging::DynamicLogging dynamic_logging(&event_loop);
// TODO(austin): Save messages into a vector to be logged. One file per
diff --git a/aos/network/message_bridge_client_lib.cc b/aos/network/message_bridge_client_lib.cc
index b7ee3cc..0aefb5c 100644
--- a/aos/network/message_bridge_client_lib.cc
+++ b/aos/network/message_bridge_client_lib.cc
@@ -99,11 +99,11 @@
aos::ShmEventLoop *const event_loop, std::string_view remote_name,
const Node *my_node, std::string_view local_host,
std::vector<SctpClientChannelState> *channels, int client_index,
- MessageBridgeClientStatus *client_status)
+ MessageBridgeClientStatus *client_status, std::string_view config_sha256)
: event_loop_(event_loop),
connect_message_(MakeConnectMessage(event_loop->configuration(), my_node,
- remote_name,
- event_loop->boot_uuid())),
+ remote_name, event_loop->boot_uuid(),
+ config_sha256)),
message_reception_reply_(MakeMessageHeaderReply()),
remote_node_(CHECK_NOTNULL(
configuration::GetNode(event_loop->configuration(), remote_name))),
@@ -128,22 +128,35 @@
event_loop_->OnRun(
[this]() { connect_timer_->Setup(event_loop_->monotonic_now()); });
- int max_size = connect_message_.span().size();
+ size_t max_write_size =
+ std::max(kHeaderSizeOverhead(), connect_message_.span().size());
+ size_t max_read_size = 0u;
for (const Channel *channel : *event_loop_->configuration()->channels()) {
CHECK(channel->has_source_node());
if (configuration::ChannelIsSendableOnNode(channel, remote_node_) &&
configuration::ChannelIsReadableOnNode(channel, event_loop_->node())) {
- LOG(INFO) << "Receiving channel "
- << configuration::CleanedChannelToString(channel);
- max_size = std::max(channel->max_size(), max_size);
+ VLOG(1) << "Receiving channel "
+ << configuration::CleanedChannelToString(channel);
+ max_read_size = std::max(
+ static_cast<size_t>(channel->max_size() + kHeaderSizeOverhead()),
+ max_read_size);
}
}
// Buffer up the max size a bit so everything fits nicely.
- LOG(INFO) << "Max message size for all servers is " << max_size;
- client_.SetMaxSize(max_size + 100);
+ LOG(INFO) << "Max read message size for all servers is " << max_read_size;
+ LOG(INFO) << "Max write message size for all servers is " << max_write_size;
+ // RemoteMessage header appears to be between 100 and 204 bytes of overhead
+ // from the vector of data. No need to get super tight to that bound.
+ client_.SetMaxReadSize(max_read_size);
+ client_.SetMaxWriteSize(max_write_size);
+
+ // 1 client talks to 1 server. With interleaving support 1 turned on, we'll
+ // at most see 1 partial message, and 1 incoming part, for a total of 2
+ // messages in flight.
+ client_.SetPoolSize(2u);
event_loop_->epoll()->OnReadable(client_.fd(),
[this]() { MessageReceived(); });
@@ -193,6 +206,7 @@
} else if (message->message_type == Message::kMessage) {
HandleData(message.get());
}
+ client_.FreeMessage(std::move(message));
}
void SctpClientConnection::SendConnect() {
@@ -262,7 +276,7 @@
monotonic_clock::time_point(
chrono::nanoseconds(remote_data->monotonic_sent_time())) ==
channel_state->last_timestamp) {
- LOG(INFO) << "Duplicate message from " << message->PeerAddress();
+ VLOG(1) << "Duplicate message from " << message->PeerAddress();
connection_->mutate_duplicate_packets(connection_->duplicate_packets() + 1);
// Duplicate message, ignore.
} else {
@@ -342,8 +356,11 @@
<< " cumtsn=" << message->header.rcvinfo.rcv_cumtsn << ")";
}
-MessageBridgeClient::MessageBridgeClient(aos::ShmEventLoop *event_loop)
- : event_loop_(event_loop), client_status_(event_loop_) {
+MessageBridgeClient::MessageBridgeClient(aos::ShmEventLoop *event_loop,
+ std::string config_sha256)
+ : event_loop_(event_loop),
+ client_status_(event_loop_),
+ config_sha256_(std::move(config_sha256)) {
std::string_view node_name = event_loop->node()->name()->string_view();
// Find all the channels which are supposed to be delivered to us.
@@ -390,7 +407,8 @@
// Open an unspecified connection (:: in ipv6 terminology)
connections_.emplace_back(new SctpClientConnection(
event_loop, source_node, event_loop->node(), "", &channels_,
- client_status_.FindClientIndex(source_node), &client_status_));
+ client_status_.FindClientIndex(source_node), &client_status_,
+ config_sha256_));
}
}
diff --git a/aos/network/message_bridge_client_lib.h b/aos/network/message_bridge_client_lib.h
index 23c982d..6e108df 100644
--- a/aos/network/message_bridge_client_lib.h
+++ b/aos/network/message_bridge_client_lib.h
@@ -37,7 +37,8 @@
std::string_view local_host,
std::vector<SctpClientChannelState> *channels,
int client_index,
- MessageBridgeClientStatus *client_status);
+ MessageBridgeClientStatus *client_status,
+ std::string_view config_sha256);
~SctpClientConnection() { event_loop_->epoll()->DeleteFd(client_.fd()); }
@@ -101,7 +102,7 @@
// node.
class MessageBridgeClient {
public:
- MessageBridgeClient(aos::ShmEventLoop *event_loop);
+ MessageBridgeClient(aos::ShmEventLoop *event_loop, std::string config_sha256);
~MessageBridgeClient() {}
@@ -116,6 +117,8 @@
// List of connections. These correspond to the nodes in source_node_names_
std::vector<std::unique_ptr<SctpClientConnection>> connections_;
+
+ std::string config_sha256_;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_protocol.cc b/aos/network/message_bridge_protocol.cc
index 87114ed..38e16b2 100644
--- a/aos/network/message_bridge_protocol.cc
+++ b/aos/network/message_bridge_protocol.cc
@@ -13,7 +13,8 @@
aos::FlatbufferDetachedBuffer<aos::message_bridge::Connect> MakeConnectMessage(
const Configuration *config, const Node *my_node,
- std::string_view remote_name, const UUID &boot_uuid) {
+ std::string_view remote_name, const UUID &boot_uuid,
+ std::string_view config_sha256) {
CHECK(config->has_nodes()) << ": Config must have nodes to transfer.";
flatbuffers::FlatBufferBuilder fbb;
@@ -33,11 +34,16 @@
if (connection->name()->string_view() == node_name &&
channel->source_node()->string_view() == remote_name) {
// Remove the schema to save some space on the wire.
- aos::FlatbufferDetachedBuffer<Channel> cleaned_channel =
- RecursiveCopyFlatBuffer<Channel>(channel);
- cleaned_channel.mutable_message()->clear_schema();
- channel_offsets.emplace_back(
- CopyFlatBuffer<Channel>(&cleaned_channel.message(), &fbb));
+ flatbuffers::Offset<flatbuffers::String> name_offset =
+ fbb.CreateSharedString(channel->name()->string_view());
+ flatbuffers::Offset<flatbuffers::String> type_offset =
+ fbb.CreateSharedString(channel->type()->string_view());
+
+ // We only really care about name, type, and max size.
+ Channel::Builder channel_builder(fbb);
+ channel_builder.add_name(name_offset);
+ channel_builder.add_type(type_offset);
+ channel_offsets.emplace_back(channel_builder.Finish());
}
}
}
@@ -46,10 +52,14 @@
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
channels_offset = fbb.CreateVector(channel_offsets);
+ flatbuffers::Offset<flatbuffers::String> config_sha256_offset =
+ fbb.CreateString(config_sha256);
+
Connect::Builder connect_builder(fbb);
connect_builder.add_channels_to_transfer(channels_offset);
connect_builder.add_node(node_offset);
connect_builder.add_boot_uuid(boot_uuid_offset);
+ connect_builder.add_config_sha256(config_sha256_offset);
fbb.Finish(connect_builder.Finish());
return fbb.Release();
diff --git a/aos/network/message_bridge_protocol.h b/aos/network/message_bridge_protocol.h
index d0a28a1..23a6e0f 100644
--- a/aos/network/message_bridge_protocol.h
+++ b/aos/network/message_bridge_protocol.h
@@ -31,10 +31,15 @@
// The stream on which timestamp replies are sent.
constexpr size_t kTimestampStream() { return 1; }
+// Overhead constant for headers. Both remote timestamps and the extra context
+// inside RemoteData need to fit inside this.
+constexpr size_t kHeaderSizeOverhead() { return 208u; }
+
// Builds up a subscription request for my_node to remote_name.
aos::FlatbufferDetachedBuffer<aos::message_bridge::Connect> MakeConnectMessage(
const Configuration *config, const Node *my_node,
- std::string_view remote_name, const UUID &boot_uuid);
+ std::string_view remote_name, const UUID &boot_uuid,
+ std::string_view config_sha256);
} // namespace message_bridge
} // namespace aos
diff --git a/aos/network/message_bridge_server.cc b/aos/network/message_bridge_server.cc
index 3b5d30b..ec3cdc4 100644
--- a/aos/network/message_bridge_server.cc
+++ b/aos/network/message_bridge_server.cc
@@ -2,6 +2,7 @@
#include "aos/init.h"
#include "aos/logging/dynamic_logging.h"
#include "aos/network/message_bridge_server_lib.h"
+#include "aos/sha256.h"
#include "gflags/gflags.h"
#include "glog/logging.h"
@@ -20,7 +21,7 @@
event_loop.SetRuntimeRealtimePriority(FLAGS_rt_priority);
}
- MessageBridgeServer app(&event_loop);
+ MessageBridgeServer app(&event_loop, Sha256(config.span()));
logging::DynamicLogging dynamic_logging(&event_loop);
diff --git a/aos/network/message_bridge_server.fbs b/aos/network/message_bridge_server.fbs
index 031f801..30017c4 100644
--- a/aos/network/message_bridge_server.fbs
+++ b/aos/network/message_bridge_server.fbs
@@ -41,6 +41,10 @@
// Number of times we've established a connection to the server.
connection_count:uint (id: 8);
+
+ // Number of times we've had an invalid connection with something wrong in
+ // the connection message, but we were able to match which node it was.
+ invalid_connection_count:uint (id: 9);
}
// Statistics for all connections to all the clients.
@@ -49,6 +53,11 @@
// Count of timestamp send failures
timestamp_send_failures:uint64 (id: 1);
+
+ // Number of times we've had an invalid connection with something wrong in
+ // the connection message. The most likely cause is that the config sha256
+ // doesn't match between nodes.
+ invalid_connection_count:uint (id: 2);
}
root_type ServerStatistics;
diff --git a/aos/network/message_bridge_server_lib.cc b/aos/network/message_bridge_server_lib.cc
index b0973ac..df6d4b1 100644
--- a/aos/network/message_bridge_server_lib.cc
+++ b/aos/network/message_bridge_server_lib.cc
@@ -13,23 +13,23 @@
#include "aos/network/sctp_server.h"
#include "aos/network/timestamp_channel.h"
#include "glog/logging.h"
+#include "glog/raw_logging.h"
namespace aos {
namespace message_bridge {
namespace chrono = std::chrono;
bool ChannelState::Matches(const Channel *other_channel) {
- // Confirm the normal tuple, plus make sure that the other side isn't going to
- // send more data over than we expect with a mismatching size.
- return (
- channel_->name()->string_view() == other_channel->name()->string_view() &&
- channel_->type()->string_view() == other_channel->type()->string_view() &&
- channel_->max_size() == other_channel->max_size());
+ return channel_->name()->string_view() ==
+ other_channel->name()->string_view() &&
+ channel_->type()->string_view() ==
+ other_channel->type()->string_view();
}
flatbuffers::FlatBufferBuilder ChannelState::PackContext(
- const Context &context) {
- flatbuffers::FlatBufferBuilder fbb(channel_->max_size() + 100);
+ FixedAllocator *allocator, const Context &context) {
+ flatbuffers::FlatBufferBuilder fbb(
+ channel_->max_size() + kHeaderSizeOverhead(), allocator);
fbb.ForceDefaults(true);
VLOG(2) << "Found " << peers_.size() << " peers on channel "
<< channel_->name()->string_view() << " "
@@ -59,10 +59,11 @@
return fbb;
}
-void ChannelState::SendData(SctpServer *server, const Context &context) {
+void ChannelState::SendData(SctpServer *server, FixedAllocator *allocator,
+ const Context &context) {
// TODO(austin): I don't like allocating this buffer when we are just freeing
// it at the end of the function.
- flatbuffers::FlatBufferBuilder fbb = PackContext(context);
+ flatbuffers::FlatBufferBuilder fbb = PackContext(allocator, context);
// TODO(austin): Track which connections need to be reliable and handle
// resending properly.
@@ -194,6 +195,7 @@
int ChannelState::NodeConnected(const Node *node, sctp_assoc_t assoc_id,
int stream, SctpServer *server,
+ FixedAllocator *allocator,
aos::monotonic_clock::time_point monotonic_now,
std::vector<sctp_assoc_t> *reconnected) {
VLOG(1) << "Channel " << channel_->name()->string_view() << " "
@@ -211,14 +213,18 @@
peer.sac_assoc_id) == reconnected->end())) {
reconnected->push_back(peer.sac_assoc_id);
if (peer.sac_assoc_id == assoc_id) {
- LOG_EVERY_T(WARNING, 0.025)
- << "Node " << node->name()->string_view() << " reconnecting on "
- << assoc_id << " with the same ID, something got lost";
+ if (VLOG_IS_ON(1)) {
+ LOG_EVERY_T(WARNING, 0.025)
+ << "Node " << node->name()->string_view() << " reconnecting on "
+ << assoc_id << " with the same ID, something got lost";
+ }
} else {
- LOG_EVERY_T(WARNING, 0.025)
- << "Node " << node->name()->string_view() << " "
- << " already connected on " << peer.sac_assoc_id
- << " aborting old connection and switching to " << assoc_id;
+ if (VLOG_IS_ON(1)) {
+ LOG_EVERY_T(WARNING, 0.025)
+ << "Node " << node->name()->string_view() << " "
+ << " already connected on " << peer.sac_assoc_id
+ << " aborting old connection and switching to " << assoc_id;
+ }
server->Abort(peer.sac_assoc_id);
}
}
@@ -237,10 +243,8 @@
<< (last_message_fetcher_->context().data != nullptr);
if (last_message_fetcher_->context().data != nullptr) {
// SendData sends to all... Only send to the new one.
- // TODO(austin): I don't like allocating this buffer when we are just
- // freeing it at the end of the function.
flatbuffers::FlatBufferBuilder fbb =
- PackContext(last_message_fetcher_->context());
+ PackContext(allocator, last_message_fetcher_->context());
if (server->Send(std::string_view(reinterpret_cast<const char *>(
fbb.GetBufferPointer()),
@@ -261,18 +265,26 @@
return -1;
}
-MessageBridgeServer::MessageBridgeServer(aos::ShmEventLoop *event_loop)
+MessageBridgeServer::MessageBridgeServer(aos::ShmEventLoop *event_loop,
+ std::string config_sha256)
: event_loop_(event_loop),
timestamp_loggers_(event_loop_),
server_(max_channels() + kControlStreams(), "",
event_loop->node()->port()),
- server_status_(event_loop, [this](const Context &context) {
- timestamp_state_->SendData(&server_, context);
- }) {
+ server_status_(event_loop,
+ [this](const Context &context) {
+ timestamp_state_->SendData(&server_, &allocator_,
+ context);
+ }),
+ config_sha256_(std::move(config_sha256)),
+ allocator_(0) {
+ CHECK_EQ(config_sha256_.size(), 64u) << ": Wrong length sha256sum";
CHECK(event_loop_->node() != nullptr) << ": No nodes configured.";
- size_t max_size = 0;
+ // Start out with a decent size big enough to hold timestamps.
+ size_t max_size = 204;
+ size_t destination_nodes = 0u;
// Seed up all the per-node connection state.
// We are making the assumption here that every connection is bidirectional
// (data is being sent both ways). This is pretty safe because we are
@@ -280,6 +292,7 @@
for (std::string_view destination_node_name :
configuration::DestinationNodeNames(event_loop->configuration(),
event_loop->node())) {
+ ++destination_nodes;
// Find the largest connection message so we can size our buffers big enough
// to receive a connection message. The connect message comes from the
// client to the server, so swap the node arguments.
@@ -288,7 +301,7 @@
configuration::GetNode(event_loop->configuration(),
destination_node_name),
event_loop->node()->name()->string_view(),
- UUID::Zero())
+ UUID::Zero(), config_sha256_)
.span()
.size();
VLOG(1) << "Connection to " << destination_node_name << " has size "
@@ -303,6 +316,7 @@
LOG(INFO) << "Hostname: " << event_loop_->node()->hostname()->string_view();
int channel_index = 0;
+ size_t max_channel_size = 0u;
const Channel *const timestamp_channel = configuration::GetChannel(
event_loop_->configuration(), "/aos", Timestamp::GetFullyQualifiedName(),
event_loop_->name(), event_loop_->node());
@@ -324,10 +338,10 @@
any_reliable = true;
}
}
- max_size =
- std::max(static_cast<size_t>(channel->max_size() *
- channel->destination_nodes()->size()),
- max_size);
+
+ max_channel_size =
+ std::max(static_cast<size_t>(channel->max_size()), max_channel_size);
+
std::unique_ptr<ChannelState> state(new ChannelState{
channel, channel_index,
any_reliable ? event_loop_->MakeRawFetcher(channel) : nullptr});
@@ -361,7 +375,7 @@
event_loop_->MakeRawWatcher(
channel, [this, state_ptr](const Context &context,
const void * /*message*/) {
- state_ptr->SendData(&server_, context);
+ state_ptr->SendData(&server_, &allocator_, context);
});
} else {
for (const Connection *connection : *channel->destination_nodes()) {
@@ -387,8 +401,24 @@
CHECK(timestamp_state_ != nullptr);
// Buffer up the max size a bit so everything fits nicely.
- LOG(INFO) << "Max message size for all clients is " << max_size;
- server_.SetMaxSize(max_size + 100u);
+ LOG(INFO) << "Max message read size for all clients is " << max_size;
+ LOG(INFO) << "Max message write size for all clients is "
+ << max_channel_size + kHeaderSizeOverhead();
+ server_.SetMaxReadSize(max_size);
+ server_.SetMaxWriteSize(max_channel_size + kHeaderSizeOverhead());
+
+ // Since we are doing interleaving mode 1, we will see at most 1 message being
+ // delivered at a time for an association. That means, if a message is
+ // started to be delivered, all the following parts will be from the same
+ // message in the same stream. The server can have at most 1 association per
+ // client active, and can then (reasonably) have 1 new client connecting
+ // trying to talk. And 2 messages per association (one partially filled one,
+ // and 1 new one with more of the data).
+ server_.SetPoolSize((destination_nodes + 1) * 2);
+
+ allocator_ = FixedAllocator(max_channel_size + kHeaderSizeOverhead());
+
+ reconnected_.reserve(max_channels());
}
void MessageBridgeServer::NodeConnected(sctp_assoc_t assoc_id) {
@@ -465,6 +495,35 @@
} else if (message->message_type == Message::kMessage) {
HandleData(message.get());
}
+ server_.FreeMessage(std::move(message));
+}
+
+void MessageBridgeServer::MaybeIncrementInvalidConnectionCount(
+ const Node *node) {
+ server_status_.increment_invalid_connection_count();
+
+ if (node == nullptr) {
+ return;
+ }
+
+ if (!node->has_name()) {
+ return;
+ }
+
+ const aos::Node *client_node = configuration::GetNode(
+ event_loop_->configuration(), node->name()->string_view());
+
+ if (client_node == nullptr) {
+ return;
+ }
+
+ const int node_index =
+ configuration::GetNodeIndex(event_loop_->configuration(), client_node);
+
+ ServerConnection *connection = server_status_.server_connection()[node_index];
+
+ connection->mutate_invalid_connection_count(
+ connection->invalid_connection_count() + 1);
}
void MessageBridgeServer::HandleData(const Message *message) {
@@ -475,13 +534,54 @@
const Connect *connect = flatbuffers::GetRoot<Connect>(message->data());
{
flatbuffers::Verifier verifier(message->data(), message->size);
- CHECK(connect->Verify(verifier));
+ if (!connect->Verify(verifier)) {
+ if (VLOG_IS_ON(1)) {
+ LOG_EVERY_T(WARNING, 1.0)
+ << "Failed to verify message, disconnecting client";
+ }
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(nullptr);
+ return;
+ }
}
VLOG(1) << FlatbufferToJson(connect);
- CHECK_LE(connect->channels_to_transfer()->size(),
- static_cast<size_t>(max_channels()))
- << ": Client has more channels than we do";
+ if (!connect->has_config_sha256()) {
+ if (VLOG_IS_ON(1)) {
+ LOG(WARNING) << "Client missing config_sha256, disconnecting client";
+ }
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
+ if (connect->config_sha256()->string_view() != config_sha256_) {
+ if (VLOG_IS_ON(1)) {
+ LOG(WARNING) << "Client config sha256 of "
+ << connect->config_sha256()->string_view()
+ << " doesn't match our config sha256 of " << config_sha256_
+ << ", disconnecting client";
+ }
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
+ if (connect->channels_to_transfer()->size() >
+ static_cast<size_t>(max_channels())) {
+ if (VLOG_IS_ON(1)) {
+ LOG(WARNING)
+ << "Client has more channels than we do, disconnecting client";
+ }
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
+ }
+
monotonic_clock::time_point monotonic_now = event_loop_->monotonic_now();
// Account for the control channel and delivery times channel.
@@ -493,8 +593,7 @@
// number of messages is overwhelming right now at first boot. This also
// should mean that we only send a single abort per association change,
// which is more correct behavior.
- std::vector<sctp_assoc_t> reconnected;
- reconnected.reserve(connect->channels_to_transfer()->size());
+ reconnected_.clear();
for (const Channel *channel : *connect->channels_to_transfer()) {
bool matched = false;
for (std::unique_ptr<ChannelState> &channel_state : channels_) {
@@ -504,15 +603,25 @@
if (channel_state->Matches(channel)) {
node_index = channel_state->NodeConnected(
connect->node(), message->header.rcvinfo.rcv_assoc_id,
- channel_index, &server_, monotonic_now, &reconnected);
- CHECK_NE(node_index, -1);
+ channel_index, &server_, &allocator_, monotonic_now,
+ &reconnected_);
+ CHECK_NE(node_index, -1)
+ << ": Failed to find node "
+ << aos::FlatbufferToJson(connect->node()) << " for connection "
+ << aos::FlatbufferToJson(connect);
matched = true;
break;
}
}
if (!matched) {
- LOG(ERROR) << "Remote tried registering for unknown channel "
- << FlatbufferToJson(channel);
+ if (VLOG_IS_ON(1)) {
+ LOG(ERROR) << "Remote tried registering for unknown channel "
+ << FlatbufferToJson(channel);
+ }
+ server_.Abort(message->header.rcvinfo.rcv_assoc_id);
+
+ MaybeIncrementInvalidConnectionCount(connect->node());
+ return;
}
++channel_index;
}
@@ -551,11 +660,13 @@
message->LogRcvInfo();
}
} else {
- message->LogRcvInfo();
- // TODO(sarah.newman): add some versioning concept such that if this was a
- // fatal error, we would never get here.
- LOG_FIRST_N(ERROR, 20) << "Unexpected stream id "
- << message->header.rcvinfo.rcv_sid;
+ // We should never see the client sending us something on the wrong stream.
+ // Just explode... In theory, this could let a client DOS us, but we trust
+ // the client.
+ if (VLOG_IS_ON(2)) {
+ message->LogRcvInfo();
+ }
+ LOG(FATAL) << "Unexpected stream id " << message->header.rcvinfo.rcv_sid;
}
}
diff --git a/aos/network/message_bridge_server_lib.h b/aos/network/message_bridge_server_lib.h
index 6f2be08..98e1dd0 100644
--- a/aos/network/message_bridge_server_lib.h
+++ b/aos/network/message_bridge_server_lib.h
@@ -68,7 +68,7 @@
// This will potentially grow to the number of associations as we find reconnects.
int NodeDisconnected(sctp_assoc_t assoc_id);
int NodeConnected(const Node *node, sctp_assoc_t assoc_id, int stream,
- SctpServer *server,
+ SctpServer *server, FixedAllocator *allocator,
aos::monotonic_clock::time_point monotonic_now,
std::vector<sctp_assoc_t> *reconnected);
@@ -83,10 +83,12 @@
bool Matches(const Channel *other_channel);
// Sends the data in context using the provided server.
- void SendData(SctpServer *server, const Context &context);
+ void SendData(SctpServer *server, FixedAllocator *allocator,
+ const Context &context);
// Packs a context into a size prefixed message header for transmission.
- flatbuffers::FlatBufferBuilder PackContext(const Context &context);
+ flatbuffers::FlatBufferBuilder PackContext(FixedAllocator *allocator,
+ const Context &context);
// Handles reception of delivery times.
void HandleDelivery(sctp_assoc_t rcv_assoc_id, uint16_t ssn,
@@ -109,7 +111,7 @@
// node. It handles the session and dispatches data to the ChannelState.
class MessageBridgeServer {
public:
- MessageBridgeServer(aos::ShmEventLoop *event_loop);
+ MessageBridgeServer(aos::ShmEventLoop *event_loop, std::string config_sha256);
~MessageBridgeServer() { event_loop_->epoll()->DeleteFd(server_.fd()); }
@@ -126,6 +128,10 @@
// received.
void HandleData(const Message *message);
+ // Increments the invalid connection count overall, and per node if we know
+ // which node (ie, node is not nullptr).
+ void MaybeIncrementInvalidConnectionCount(const Node *node);
+
// The maximum number of channels we support on a single connection. We need
// to configure the SCTP socket with this before any clients connect, so we
// need an upper bound on the number of channels any of them will use.
@@ -147,6 +153,14 @@
// List of channels. The entries that aren't sent from this node are left
// null.
std::vector<std::unique_ptr<ChannelState>> channels_;
+
+ const std::string config_sha256_;
+
+ // List of assoc_id's that have been found already when connecting. This is a
+ // member variable so the memory is allocated in the constructor.
+ std::vector<sctp_assoc_t> reconnected_;
+
+ FixedAllocator allocator_;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_server_status.cc b/aos/network/message_bridge_server_status.cc
index 4f6abff..df87134 100644
--- a/aos/network/message_bridge_server_status.cc
+++ b/aos/network/message_bridge_server_status.cc
@@ -40,6 +40,7 @@
connection_builder.add_connected_since_time(
monotonic_clock::min_time.time_since_epoch().count());
connection_builder.add_connection_count(0);
+ connection_builder.add_invalid_connection_count(0);
connection_offsets.emplace_back(connection_builder.Finish());
}
flatbuffers::Offset<
@@ -87,6 +88,7 @@
send_data_(send_data) {
server_connection_offsets_.reserve(
statistics_.message().connections()->size());
+ client_offsets_.reserve(statistics_.message().connections()->size());
filters_.resize(event_loop->configuration()->nodes()->size());
partial_deliveries_.resize(event_loop->configuration()->nodes()->size());
@@ -231,6 +233,11 @@
connection->connection_count());
}
+ if (connection->invalid_connection_count() != 0) {
+ server_connection_builder.add_invalid_connection_count(
+ connection->invalid_connection_count());
+ }
+
// TODO(austin): If it gets stale, drop it too.
if (!filters_[node_index].MissingSamples()) {
server_connection_builder.add_monotonic_offset(
@@ -254,6 +261,8 @@
server_statistics_builder.add_connections(server_connections_offset);
server_statistics_builder.add_timestamp_send_failures(
timestamp_failure_counter_.failures());
+ server_statistics_builder.add_invalid_connection_count(
+ invalid_connection_count_);
builder.CheckOk(builder.Send(server_statistics_builder.Finish()));
}
@@ -282,7 +291,7 @@
if (client_statistics_fetcher_.get()) {
// Build up the list of client offsets.
- std::vector<flatbuffers::Offset<ClientOffset>> client_offsets;
+ client_offsets_.clear();
// Iterate through the connections this node has made.
for (const ClientConnection *connection :
@@ -369,10 +378,10 @@
client_offset_builder.add_node(node_offset);
client_offset_builder.add_monotonic_offset(
connection->monotonic_offset());
- client_offsets.emplace_back(client_offset_builder.Finish());
+ client_offsets_.emplace_back(client_offset_builder.Finish());
}
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<ClientOffset>>>
- offsets_offset = fbb->CreateVector(client_offsets);
+ offsets_offset = fbb->CreateVector(client_offsets_);
Timestamp::Builder builder(*fbb);
builder.add_offsets(offsets_offset);
diff --git a/aos/network/message_bridge_server_status.h b/aos/network/message_bridge_server_status.h
index 327930c..624b376 100644
--- a/aos/network/message_bridge_server_status.h
+++ b/aos/network/message_bridge_server_status.h
@@ -68,7 +68,7 @@
ServerConnection *FindServerConnection(std::string_view node_name);
ServerConnection *FindServerConnection(const Node *node);
- std::vector<ServerConnection *> server_connection() {
+ const std::vector<ServerConnection *> &server_connection() {
return server_connection_;
}
@@ -77,6 +77,10 @@
// Enables sending out any statistics messages.
void EnableStatistics();
+ // Increments invalid_connection_count_, marking that we had another bad
+ // connection that got rejected.
+ void increment_invalid_connection_count() { ++invalid_connection_count_; }
+
private:
static constexpr std::chrono::nanoseconds kStatisticsPeriod =
std::chrono::seconds(1);
@@ -126,6 +130,10 @@
bool send_ = true;
std::vector<uint32_t> partial_deliveries_;
+
+ size_t invalid_connection_count_ = 0u;
+
+ std::vector<flatbuffers::Offset<ClientOffset>> client_offsets_;
};
} // namespace message_bridge
diff --git a/aos/network/message_bridge_test.cc b/aos/network/message_bridge_test.cc
index 226daf7..681001e 100644
--- a/aos/network/message_bridge_test.cc
+++ b/aos/network/message_bridge_test.cc
@@ -8,6 +8,7 @@
#include "aos/network/message_bridge_client_lib.h"
#include "aos/network/message_bridge_server_lib.h"
#include "aos/network/team_number.h"
+#include "aos/sha256.h"
#include "aos/testing/path.h"
#include "aos/util/file.h"
#include "gtest/gtest.h"
@@ -53,6 +54,7 @@
MessageBridgeParameterizedTest()
: config(aos::configuration::ReadConfig(
ArtifactPath(absl::StrCat("aos/network/", GetParam().config)))),
+ config_sha256(Sha256(config.span())),
pi1_boot_uuid_(UUID::Random()),
pi2_boot_uuid_(UUID::Random()) {
util::UnlinkRecursive(ShmBase("pi1"));
@@ -73,14 +75,16 @@
FLAGS_boot_uuid = pi2_boot_uuid_.ToString();
}
- void MakePi1Server() {
+ void MakePi1Server(std::string server_config_sha256 = "") {
OnPi1();
FLAGS_application_name = "pi1_message_bridge_server";
pi1_server_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi1_server_event_loop->SetRuntimeRealtimePriority(1);
- pi1_message_bridge_server =
- std::make_unique<MessageBridgeServer>(pi1_server_event_loop.get());
+ pi1_message_bridge_server = std::make_unique<MessageBridgeServer>(
+ pi1_server_event_loop.get(), server_config_sha256.size() == 0
+ ? config_sha256
+ : server_config_sha256);
}
void RunPi1Server(chrono::nanoseconds duration) {
@@ -118,8 +122,8 @@
pi1_client_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi1_client_event_loop->SetRuntimeRealtimePriority(1);
- pi1_message_bridge_client =
- std::make_unique<MessageBridgeClient>(pi1_client_event_loop.get());
+ pi1_message_bridge_client = std::make_unique<MessageBridgeClient>(
+ pi1_client_event_loop.get(), config_sha256);
}
void StartPi1Client() {
@@ -183,8 +187,8 @@
pi2_server_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi2_server_event_loop->SetRuntimeRealtimePriority(1);
- pi2_message_bridge_server =
- std::make_unique<MessageBridgeServer>(pi2_server_event_loop.get());
+ pi2_message_bridge_server = std::make_unique<MessageBridgeServer>(
+ pi2_server_event_loop.get(), config_sha256);
}
void RunPi2Server(chrono::nanoseconds duration) {
@@ -222,8 +226,8 @@
pi2_client_event_loop =
std::make_unique<aos::ShmEventLoop>(&config.message());
pi2_client_event_loop->SetRuntimeRealtimePriority(1);
- pi2_message_bridge_client =
- std::make_unique<MessageBridgeClient>(pi2_client_event_loop.get());
+ pi2_message_bridge_client = std::make_unique<MessageBridgeClient>(
+ pi2_client_event_loop.get(), config_sha256);
}
void RunPi2Client(chrono::nanoseconds duration) {
@@ -297,6 +301,8 @@
}
aos::FlatbufferDetachedBuffer<aos::Configuration> config;
+ std::string config_sha256;
+
const UUID pi1_boot_uuid_;
const UUID pi2_boot_uuid_;
@@ -351,6 +357,8 @@
MakePi1Server();
MakePi1Client();
+ const std::string long_data = std::string(10000, 'a');
+
// And build the app which sends the pings.
FLAGS_application_name = "ping";
aos::ShmEventLoop ping_event_loop(&config.message());
@@ -416,7 +424,8 @@
int ping_count = 0;
int pi1_server_statistics_count = 0;
ping_event_loop.MakeWatcher("/pi1/aos", [this, &ping_count, &ping_sender,
- &pi1_server_statistics_count](
+ &pi1_server_statistics_count,
+ &long_data](
const ServerStatistics &stats) {
VLOG(1) << "/pi1/aos ServerStatistics " << FlatbufferToJson(&stats);
@@ -454,6 +463,7 @@
if (connected) {
VLOG(1) << "Connected! Sent ping.";
auto builder = ping_sender.MakeBuilder();
+ builder.fbb()->CreateString(long_data);
examples::Ping::Builder ping_builder =
builder.MakeBuilder<examples::Ping>();
ping_builder.add_value(ping_count + 971);
@@ -1342,6 +1352,109 @@
pi1_remote_timestamp_thread.join();
}
+// Test that differing config sha256's result in no connection.
+TEST_P(MessageBridgeParameterizedTest, MismatchedSha256) {
+ // This is rather annoying to set up. We need to start up a client and
+ // server, on the same node, but get them to think that they are on different
+ // nodes.
+ //
+ // We need the client to not post directly to "/test" like it would in a
+ // real system, otherwise we will re-send the ping message... So, use an
+ // application specific map to have the client post somewhere else.
+ //
+ // To top this all off, each of these needs to be done with a ShmEventLoop,
+ // which needs to run in a separate thread... And it is really hard to get
+ // everything started up reliably. So just be super generous on timeouts and
+ // hope for the best. We can be more generous in the future if we need to.
+ //
+ // We are faking the application names by passing in --application_name=foo
+ OnPi1();
+
+ MakePi1Server(
+ "dummy sha256 ");
+ MakePi1Client();
+
+ // And build the app for testing.
+ MakePi1Test();
+ aos::Fetcher<ServerStatistics> pi1_server_statistics_fetcher =
+ pi1_test_event_loop->MakeFetcher<ServerStatistics>("/pi1/aos");
+ aos::Fetcher<ClientStatistics> pi1_client_statistics_fetcher =
+ pi1_test_event_loop->MakeFetcher<ClientStatistics>("/pi1/aos");
+
+ // Now do it for "raspberrypi2", the client.
+ OnPi2();
+ MakePi2Server();
+
+ // And build the app for testing.
+ MakePi2Test();
+ aos::Fetcher<ServerStatistics> pi2_server_statistics_fetcher =
+ pi2_test_event_loop->MakeFetcher<ServerStatistics>("/pi2/aos");
+ aos::Fetcher<ClientStatistics> pi2_client_statistics_fetcher =
+ pi2_test_event_loop->MakeFetcher<ClientStatistics>("/pi2/aos");
+
+ // Wait until we are connected, then send.
+
+ StartPi1Test();
+ StartPi2Test();
+ StartPi1Server();
+ StartPi1Client();
+ StartPi2Server();
+
+ {
+ MakePi2Client();
+
+ RunPi2Client(chrono::milliseconds(3050));
+
+ // Now confirm we are synchronized.
+ EXPECT_TRUE(pi1_server_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi1_client_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi2_server_statistics_fetcher.Fetch());
+ EXPECT_TRUE(pi2_client_statistics_fetcher.Fetch());
+
+ const ServerConnection *const pi1_connection =
+ pi1_server_statistics_fetcher->connections()->Get(0);
+ const ClientConnection *const pi1_client_connection =
+ pi1_client_statistics_fetcher->connections()->Get(0);
+ const ServerConnection *const pi2_connection =
+ pi2_server_statistics_fetcher->connections()->Get(0);
+ const ClientConnection *const pi2_client_connection =
+ pi2_client_statistics_fetcher->connections()->Get(0);
+
+ // Make sure one direction is disconnected with a bunch of connection
+ // attempts and failures.
+ EXPECT_EQ(pi1_connection->state(), State::DISCONNECTED);
+ EXPECT_EQ(pi1_connection->connection_count(), 0u);
+ EXPECT_GT(pi1_connection->invalid_connection_count(), 10u);
+
+ EXPECT_EQ(pi2_client_connection->state(), State::DISCONNECTED);
+ EXPECT_GT(pi2_client_connection->connection_count(), 10u);
+
+ // And the other direction is happy.
+ EXPECT_EQ(pi2_connection->state(), State::CONNECTED);
+ EXPECT_EQ(pi2_connection->connection_count(), 1u);
+ EXPECT_TRUE(pi2_connection->has_connected_since_time());
+ EXPECT_FALSE(pi2_connection->has_monotonic_offset());
+ EXPECT_TRUE(pi2_connection->has_boot_uuid());
+
+ EXPECT_EQ(pi1_client_connection->state(), State::CONNECTED);
+ EXPECT_EQ(pi1_client_connection->connection_count(), 1u);
+
+ VLOG(1) << aos::FlatbufferToJson(pi2_server_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi1_server_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi2_client_statistics_fetcher.get());
+ VLOG(1) << aos::FlatbufferToJson(pi1_client_statistics_fetcher.get());
+
+ StopPi2Client();
+ }
+
+ // Shut everyone else down
+ StopPi1Server();
+ StopPi1Client();
+ StopPi2Server();
+ StopPi1Test();
+ StopPi2Test();
+}
+
INSTANTIATE_TEST_SUITE_P(
MessageBridgeTests, MessageBridgeParameterizedTest,
::testing::Values(
diff --git a/aos/network/message_bridge_test_combined_timestamps_common.json b/aos/network/message_bridge_test_combined_timestamps_common.json
index 5d82965..13a0514 100644
--- a/aos/network/message_bridge_test_combined_timestamps_common.json
+++ b/aos/network/message_bridge_test_combined_timestamps_common.json
@@ -110,7 +110,8 @@
"timestamp_logger": "REMOTE_LOGGER",
"timestamp_logger_nodes": ["pi1"]
}
- ]
+ ],
+ "max_size": 20480
},
{
"name": "/test",
@@ -125,7 +126,8 @@
"timestamp_logger": "REMOTE_LOGGER",
"timestamp_logger_nodes": ["pi1"]
}
- ]
+ ],
+ "max_size": 20480
},
{
"name": "/unreliable",
@@ -139,7 +141,8 @@
"timestamp_logger_nodes": ["pi1"],
"time_to_live": 5000000
}
- ]
+ ],
+ "max_size": 20480
}
],
"maps": [
diff --git a/aos/network/message_bridge_test_common.json b/aos/network/message_bridge_test_common.json
index a30734d..9bb0863 100644
--- a/aos/network/message_bridge_test_common.json
+++ b/aos/network/message_bridge_test_common.json
@@ -75,28 +75,33 @@
{
"name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
"type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
"source_node": "pi1",
"frequency": 15
},
{
"name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
"type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
"source_node": "pi2",
"frequency": 15
},
{
"name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
"type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
"source_node": "pi1"
},
{
"name": "/pi2/aos/remote_timestamps/pi1/test/aos-examples-Pong",
"type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
"source_node": "pi2"
},
{
"name": "/pi1/aos/remote_timestamps/pi2/unreliable/aos-examples-Ping",
"type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
"source_node": "pi1"
},
{
@@ -126,7 +131,8 @@
"timestamp_logger": "REMOTE_LOGGER",
"timestamp_logger_nodes": ["pi1"]
}
- ]
+ ],
+ "max_size": 20480
},
{
"name": "/test",
@@ -141,7 +147,8 @@
"timestamp_logger": "REMOTE_LOGGER",
"timestamp_logger_nodes": ["pi2"]
}
- ]
+ ],
+ "max_size": 20480
},
{
"name": "/unreliable",
@@ -155,7 +162,8 @@
"timestamp_logger_nodes": ["pi1"],
"time_to_live": 5000000
}
- ]
+ ],
+ "max_size": 20480
}
],
"maps": [
diff --git a/aos/network/sctp_client.cc b/aos/network/sctp_client.cc
index e3da03a..ab70c20 100644
--- a/aos/network/sctp_client.cc
+++ b/aos/network/sctp_client.cc
@@ -51,7 +51,9 @@
message_bridge::LogSctpStatus(fd(), assoc_id);
}
-void SctpClient::SetPriorityScheduler(sctp_assoc_t assoc_id) {
+void SctpClient::SetPriorityScheduler([[maybe_unused]] sctp_assoc_t assoc_id) {
+// Kernel 4.9 does not have SCTP_SS_PRIO
+#ifdef SCTP_SS_PRIO
struct sctp_assoc_value scheduler;
memset(&scheduler, 0, sizeof(scheduler));
scheduler.assoc_id = assoc_id;
@@ -61,6 +63,7 @@
LOG_FIRST_N(WARNING, 1) << "Failed to set scheduler: " << strerror(errno)
<< " [" << errno << "]";
}
+#endif
}
} // namespace message_bridge
diff --git a/aos/network/sctp_client.h b/aos/network/sctp_client.h
index 1ba32ff..d7a2b43 100644
--- a/aos/network/sctp_client.h
+++ b/aos/network/sctp_client.h
@@ -47,12 +47,18 @@
void LogSctpStatus(sctp_assoc_t assoc_id);
- void SetMaxSize(size_t max_size) { sctp_.SetMaxSize(max_size); }
+ void SetMaxReadSize(size_t max_size) { sctp_.SetMaxReadSize(max_size); }
+ void SetMaxWriteSize(size_t max_size) { sctp_.SetMaxWriteSize(max_size); }
+ void SetPoolSize(size_t pool_size) { sctp_.SetPoolSize(pool_size); }
void SetAssociationId(sctp_assoc_t sac_assoc_id) {
sac_assoc_id_ = sac_assoc_id;
}
+ void FreeMessage(aos::unique_c_ptr<Message> &&message) {
+ sctp_.FreeMessage(std::move(message));
+ }
+
private:
struct sockaddr_storage sockaddr_remote_;
struct sockaddr_storage sockaddr_local_;
diff --git a/aos/network/sctp_lib.cc b/aos/network/sctp_lib.cc
index 9632d3c..3f482c3 100644
--- a/aos/network/sctp_lib.cc
+++ b/aos/network/sctp_lib.cc
@@ -334,25 +334,52 @@
return true;
}
+void SctpReadWrite::FreeMessage(aos::unique_c_ptr<Message> &&message) {
+ if (use_pool_) {
+ free_messages_.emplace_back(std::move(message));
+ }
+}
+
+void SctpReadWrite::SetPoolSize(size_t pool_size) {
+ CHECK(!use_pool_);
+ free_messages_.reserve(pool_size);
+ for (size_t i = 0; i < pool_size; ++i) {
+ free_messages_.emplace_back(AcquireMessage());
+ }
+ use_pool_ = true;
+}
+
+aos::unique_c_ptr<Message> SctpReadWrite::AcquireMessage() {
+ if (!use_pool_) {
+ constexpr size_t kMessageAlign = alignof(Message);
+ const size_t max_message_size =
+ ((sizeof(Message) + max_read_size_ + 1 + (kMessageAlign - 1)) /
+ kMessageAlign) *
+ kMessageAlign;
+ aos::unique_c_ptr<Message> result(reinterpret_cast<Message *>(
+ aligned_alloc(kMessageAlign, max_message_size)));
+ return result;
+ } else {
+ CHECK_GT(free_messages_.size(), 0u);
+ aos::unique_c_ptr<Message> result = std::move(free_messages_.back());
+ free_messages_.pop_back();
+ return result;
+ }
+}
+
// We read each fragment into a fresh Message, because most of them won't be
// fragmented. If we do end up with a fragment, then we copy the data out of it.
aos::unique_c_ptr<Message> SctpReadWrite::ReadMessage() {
CHECK(fd_ != -1);
while (true) {
- constexpr size_t kMessageAlign = alignof(Message);
- const size_t max_message_size =
- ((sizeof(Message) + max_size_ + 1 + (kMessageAlign - 1)) /
- kMessageAlign) *
- kMessageAlign;
- aos::unique_c_ptr<Message> result(reinterpret_cast<Message *>(
- aligned_alloc(kMessageAlign, max_message_size)));
+ aos::unique_c_ptr<Message> result = AcquireMessage();
struct msghdr inmessage;
memset(&inmessage, 0, sizeof(struct msghdr));
struct iovec iov;
- iov.iov_len = max_size_ + 1;
+ iov.iov_len = max_read_size_ + 1;
iov.iov_base = result->mutable_data();
inmessage.msg_iov = &iov;
@@ -377,7 +404,7 @@
CHECK(!(inmessage.msg_flags & MSG_CTRUNC))
<< ": Control message truncated.";
- CHECK_LE(size, static_cast<ssize_t>(max_size_))
+ CHECK_LE(size, static_cast<ssize_t>(max_read_size_))
<< ": Message overflowed buffer on stream "
<< result->header.rcvinfo.rcv_sid << ".";
@@ -445,7 +472,7 @@
<< result->header.rcvinfo.rcv_assoc_id;
// Now copy the data over and update the size.
- CHECK_LE(partial_message->size + result->size, max_size_)
+ CHECK_LE(partial_message->size + result->size, max_read_size_)
<< ": Assembled fragments overflowed buffer on stream "
<< result->header.rcvinfo.rcv_sid << ".";
memcpy(partial_message->mutable_data() + partial_message->size,
@@ -541,10 +568,10 @@
}
void SctpReadWrite::DoSetMaxSize() {
- size_t max_size = max_size_;
+ size_t max_size = max_write_size_;
// This sets the max packet size that we can send.
- CHECK_GE(ReadWMemMax(), max_size)
+ CHECK_GE(ReadWMemMax(), max_write_size_)
<< "wmem_max is too low. To increase wmem_max temporarily, do sysctl "
"-w net.core.wmem_max="
<< max_size;
diff --git a/aos/network/sctp_lib.h b/aos/network/sctp_lib.h
index 6cd11a3..a852365 100644
--- a/aos/network/sctp_lib.h
+++ b/aos/network/sctp_lib.h
@@ -105,17 +105,35 @@
int fd() const { return fd_; }
- void SetMaxSize(size_t max_size) {
+ void SetMaxReadSize(size_t max_size) {
CHECK(partial_messages_.empty())
<< ": May not update size with queued fragments because we do not "
"track individual message sizes";
- max_size_ = max_size;
+ max_read_size_ = max_size;
if (fd_ != -1) {
DoSetMaxSize();
}
}
+ void SetMaxWriteSize(size_t max_size) {
+ CHECK(partial_messages_.empty())
+ << ": May not update size with queued fragments because we do not "
+ "track individual message sizes";
+ max_write_size_ = max_size;
+ if (fd_ != -1) {
+ DoSetMaxSize();
+ }
+ }
+
+ // Returns a message returned from ReadMessage back to the pool.
+ void FreeMessage(aos::unique_c_ptr<Message> &&message);
+
+ // Allocates messages for the pool. SetMaxSize must be set first.
+ void SetPoolSize(size_t pool_size);
+
private:
+ aos::unique_c_ptr<Message> AcquireMessage();
+
void CloseSocket();
void DoSetMaxSize();
@@ -128,9 +146,13 @@
// We use this as a unique identifier that just increments for each message.
uint32_t send_ppid_ = 0;
- size_t max_size_ = 1000;
+ size_t max_read_size_ = 1000;
+ size_t max_write_size_ = 1000;
std::vector<aos::unique_c_ptr<Message>> partial_messages_;
+
+ bool use_pool_ = false;
+ std::vector<aos::unique_c_ptr<Message>> free_messages_;
};
// Returns the max network buffer available for reading for a socket.
diff --git a/aos/network/sctp_server.cc b/aos/network/sctp_server.cc
index 2f6a041..a78aa34 100644
--- a/aos/network/sctp_server.cc
+++ b/aos/network/sctp_server.cc
@@ -64,12 +64,15 @@
PCHECK(listen(fd(), 100) == 0);
- SetMaxSize(1000);
+ SetMaxReadSize(1000);
+ SetMaxWriteSize(1000);
break;
}
}
-void SctpServer::SetPriorityScheduler(sctp_assoc_t assoc_id) {
+void SctpServer::SetPriorityScheduler([[maybe_unused]] sctp_assoc_t assoc_id) {
+// Kernel 4.9 does not have SCTP_SS_PRIO
+#ifdef SCTP_SS_PRIO
struct sctp_assoc_value scheduler;
memset(&scheduler, 0, sizeof(scheduler));
scheduler.assoc_id = assoc_id;
@@ -79,10 +82,14 @@
LOG_FIRST_N(WARNING, 1) << "Failed to set scheduler: " << strerror(errno)
<< " [" << errno << "]";
}
+#endif
}
-void SctpServer::SetStreamPriority(sctp_assoc_t assoc_id, int stream_id,
- uint16_t priority) {
+void SctpServer::SetStreamPriority([[maybe_unused]] sctp_assoc_t assoc_id,
+ [[maybe_unused]] int stream_id,
+ [[maybe_unused]] uint16_t priority) {
+// Kernel 4.9 does not have SCTP_STREAM_SCHEDULER_VALUE
+#ifdef SCTP_STREAM_SCHEDULER_VALUE
struct sctp_stream_value sctp_priority;
memset(&sctp_priority, 0, sizeof(sctp_priority));
sctp_priority.assoc_id = assoc_id;
@@ -93,6 +100,7 @@
LOG_FIRST_N(WARNING, 1) << "Failed to set scheduler: " << strerror(errno)
<< " [" << errno << "]";
}
+#endif
}
} // namespace message_bridge
diff --git a/aos/network/sctp_server.h b/aos/network/sctp_server.h
index dbfd1ac..800c2c1 100644
--- a/aos/network/sctp_server.h
+++ b/aos/network/sctp_server.h
@@ -30,6 +30,11 @@
// Receives the next packet from the remote.
aos::unique_c_ptr<Message> Read() { return sctp_.ReadMessage(); }
+ // Frees the message returned by Read();
+ void FreeMessage(aos::unique_c_ptr<Message> &&message) {
+ sctp_.FreeMessage(std::move(message));
+ }
+
// Sends a block of data to a client on a stream with a TTL. Returns true on
// success.
bool Send(std::string_view data, sctp_assoc_t snd_assoc_id, int stream,
@@ -52,7 +57,10 @@
void SetStreamPriority(sctp_assoc_t assoc_id, int stream_id,
uint16_t priority);
- void SetMaxSize(size_t max_size) { sctp_.SetMaxSize(max_size); }
+ void SetMaxReadSize(size_t max_size) { sctp_.SetMaxReadSize(max_size); }
+ void SetMaxWriteSize(size_t max_size) { sctp_.SetMaxWriteSize(max_size); }
+
+ void SetPoolSize(size_t pool_size) { sctp_.SetPoolSize(pool_size); }
private:
struct sockaddr_storage sockaddr_local_;
diff --git a/aos/realtime.cc b/aos/realtime.cc
index 2f299e6..2f5e1d9 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -20,7 +20,7 @@
#include "glog/raw_logging.h"
DEFINE_bool(
- die_on_malloc, false,
+ die_on_malloc, true,
"If true, die when the application allocates memory in a RT section.");
DEFINE_bool(skip_realtime_scheduler, false,
"If true, skip changing the scheduler. Pretend that we changed "
@@ -337,7 +337,9 @@
has_malloc_hook = false;
}
} else {
- RAW_LOG(INFO, "Replacing glibc malloc");
+ if (VLOG_IS_ON(1)) {
+ RAW_LOG(INFO, "Replacing glibc malloc");
+ }
if (&malloc != &aos_malloc_hook) {
has_malloc_hook = false;
}
diff --git a/aos/sha256.cc b/aos/sha256.cc
new file mode 100644
index 0000000..ae83792
--- /dev/null
+++ b/aos/sha256.cc
@@ -0,0 +1,26 @@
+#include "aos/sha256.h"
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "absl/types/span.h"
+#include "openssl/sha.h"
+
+namespace aos {
+
+std::string Sha256(const absl::Span<const uint8_t> str) {
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+ SHA256_CTX sha256;
+ SHA256_Init(&sha256);
+ SHA256_Update(&sha256, str.data(), str.size());
+ SHA256_Final(hash, &sha256);
+ std::stringstream ss;
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
+ ss << std::hex << std::setw(2) << std::setfill('0')
+ << static_cast<int>(hash[i]);
+ }
+ return ss.str();
+}
+
+} // namespace aos
diff --git a/aos/sha256.h b/aos/sha256.h
new file mode 100644
index 0000000..7fb9b2f
--- /dev/null
+++ b/aos/sha256.h
@@ -0,0 +1,15 @@
+#ifndef AOS_SHA256_H_
+#define AOS_SHA256_H_
+
+#include <string>
+
+#include "absl/types/span.h"
+
+namespace aos {
+
+// Returns the sha256 of a span.
+std::string Sha256(const absl::Span<const uint8_t> str);
+
+} // namespace aos
+
+#endif // AOS_SHA256_H_
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index 9068caa..7ef3777 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -118,6 +118,7 @@
"//aos/events:ping_fbs",
"//aos/events:pong_fbs",
"//aos/events:simulated_event_loop",
+ "//aos/ipc_lib:event",
"//aos/testing:googletest",
"//aos/testing:path",
"//aos/testing:tmpdir",
diff --git a/aos/starter/starter_rpc_lib.cc b/aos/starter/starter_rpc_lib.cc
index 15132ec..5f14e21 100644
--- a/aos/starter/starter_rpc_lib.cc
+++ b/aos/starter/starter_rpc_lib.cc
@@ -225,10 +225,12 @@
// Clear commands prior to calling handlers to allow the handler to call
// SendCommands() again if desired.
current_commands_.clear();
+ // Clear the timer before calling success handler, in case the success
+ // handler needs to modify timeout handler.
+ timeout_timer_->Disable();
if (success_handler_) {
success_handler_();
}
- timeout_timer_->Disable();
}
bool SendCommandBlocking(aos::starter::Command command, std::string_view name,
diff --git a/aos/starter/starter_test.cc b/aos/starter/starter_test.cc
index 87cb544..94566ae 100644
--- a/aos/starter/starter_test.cc
+++ b/aos/starter/starter_test.cc
@@ -1,9 +1,11 @@
+#include <chrono>
#include <csignal>
#include <future>
#include <thread>
#include "aos/events/ping_generated.h"
#include "aos/events/pong_generated.h"
+#include "aos/ipc_lib/event.h"
#include "aos/network/team_number.h"
#include "aos/testing/path.h"
#include "aos/testing/tmpdir.h"
@@ -19,11 +21,9 @@
class StarterdTest : public ::testing::Test {
public:
- StarterdTest() : shm_dir_(aos::testing::TestTmpDir() + "/aos") {
- FLAGS_shm_base = shm_dir_;
-
+ StarterdTest() {
// Nuke the shm dir:
- aos::util::UnlinkRecursive(shm_dir_);
+ aos::util::UnlinkRecursive(FLAGS_shm_base);
}
protected:
@@ -35,11 +35,10 @@
}
})
->Setup(starter->event_loop()->monotonic_now(),
- std::chrono::seconds(1));
+ std::chrono::milliseconds(100));
}
gflags::FlagSaver flag_saver_;
- std::string shm_dir_;
// Used to track when the test completes so that we can clean up the starter
// in its thread.
std::atomic<bool> test_done_{false};
@@ -79,8 +78,8 @@
"args": ["--shm_base", "%s", "--config", "%s", "--override_hostname", "%s"]
}
]})",
- ArtifactPath("aos/events/ping"), shm_dir_, config_file,
- GetParam().hostname, ArtifactPath("aos/events/pong"), shm_dir_,
+ ArtifactPath("aos/events/ping"), FLAGS_shm_base, config_file,
+ GetParam().hostname, ArtifactPath("aos/events/pong"), FLAGS_shm_base,
config_file, GetParam().hostname));
const aos::Configuration *config_msg = &new_config.message();
@@ -161,12 +160,25 @@
SetupStarterCleanup(&starter);
- std::thread starterd_thread([&starter] { starter.Run(); });
- std::thread client_thread([&client_loop] { client_loop.Run(); });
- watcher_loop.Run();
+ Event starter_started;
+ std::thread starterd_thread([&starter, &starter_started] {
+ starter.event_loop()->OnRun(
+ [&starter_started]() { starter_started.Set(); });
+ starter.Run();
+ });
+ starter_started.Wait();
+ Event client_started;
+ std::thread client_thread([&client_loop, &client_started] {
+ client_loop.OnRun([&client_started]() { client_started.Set(); });
+ client_loop.Run();
+ });
+ client_started.Wait();
+
+ watcher_loop.Run();
test_done_ = true;
client_thread.join();
+ ASSERT_TRUE(success);
starterd_thread.join();
}
@@ -197,8 +209,8 @@
"args": ["--shm_base", "%s"]
}
]})",
- ArtifactPath("aos/events/ping"), shm_dir_,
- ArtifactPath("aos/events/pong"), shm_dir_));
+ ArtifactPath("aos/events/ping"), FLAGS_shm_base,
+ ArtifactPath("aos/events/pong"), FLAGS_shm_base));
const aos::Configuration *config_msg = &new_config.message();
@@ -257,7 +269,13 @@
SetupStarterCleanup(&starter);
- std::thread starterd_thread([&starter] { starter.Run(); });
+ Event starter_started;
+ std::thread starterd_thread([&starter, &starter_started] {
+ starter.event_loop()->OnRun(
+ [&starter_started]() { starter_started.Set(); });
+ starter.Run();
+ });
+ starter_started.Wait();
watcher_loop.Run();
test_done_ = true;
@@ -287,8 +305,8 @@
"args": ["--shm_base", "%s"]
}
]})",
- ArtifactPath("aos/events/ping"), shm_dir_,
- ArtifactPath("aos/events/pong"), shm_dir_));
+ ArtifactPath("aos/events/ping"), FLAGS_shm_base,
+ ArtifactPath("aos/events/pong"), FLAGS_shm_base));
const aos::Configuration *config_msg = &new_config.message();
@@ -346,7 +364,13 @@
SetupStarterCleanup(&starter);
- std::thread starterd_thread([&starter] { starter.Run(); });
+ Event starter_started;
+ std::thread starterd_thread([&starter, &starter_started] {
+ starter.event_loop()->OnRun(
+ [&starter_started]() { starter_started.Set(); });
+ starter.Run();
+ });
+ starter_started.Wait();
watcher_loop.Run();
test_done_ = true;
@@ -362,25 +386,23 @@
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(config_file);
- const std::string test_dir = aos::testing::TestTmpDir();
-
auto new_config = aos::configuration::MergeWithConfig(
&config.message(), absl::StrFormat(
R"({"applications": [
{
"name": "ping",
"executable_name": "%s",
- "args": ["--shm_base", "%s/aos"],
+ "args": ["--shm_base", "%s"],
"autorestart": false
},
{
"name": "pong",
"executable_name": "%s",
- "args": ["--shm_base", "%s/aos"]
+ "args": ["--shm_base", "%s"]
}
]})",
- ArtifactPath("aos/events/ping"), test_dir,
- ArtifactPath("aos/events/pong"), test_dir));
+ ArtifactPath("aos/events/ping"), FLAGS_shm_base,
+ ArtifactPath("aos/events/pong"), FLAGS_shm_base));
const aos::Configuration *config_msg = &new_config.message();
@@ -439,7 +461,13 @@
SetupStarterCleanup(&starter);
- std::thread starterd_thread([&starter] { starter.Run(); });
+ Event starter_started;
+ std::thread starterd_thread([&starter, &starter_started] {
+ starter.event_loop()->OnRun(
+ [&starter_started]() { starter_started.Set(); });
+ starter.Run();
+ });
+ starter_started.Wait();
watcher_loop.Run();
test_done_ = true;
@@ -447,5 +475,121 @@
starterd_thread.join();
}
+TEST_F(StarterdTest, StarterChainTest) {
+ // This test was written in response to a bug that was found
+ // in StarterClient::Succeed. The bug caused the timeout handler
+ // to be reset after the success handler was called.
+ // the bug has been fixed, and this test will ensure it does
+ // not regress.
+ const std::string config_file =
+ ArtifactPath("aos/events/pingpong_config.json");
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig(config_file);
+ auto new_config = aos::configuration::MergeWithConfig(
+ &config.message(), absl::StrFormat(
+ R"({"applications": [
+ {
+ "name": "ping",
+ "executable_name": "%s",
+ "args": ["--shm_base", "%s"],
+ "autorestart": false
+ },
+ {
+ "name": "pong",
+ "executable_name": "%s",
+ "args": ["--shm_base", "%s"]
+ }
+ ]})",
+ ArtifactPath("aos/events/ping"), FLAGS_shm_base,
+ ArtifactPath("aos/events/pong"), FLAGS_shm_base));
+
+ const aos::Configuration *config_msg = &new_config.message();
+ // Set up starter with config file
+ aos::starter::Starter starter(config_msg);
+ aos::ShmEventLoop client_loop(config_msg);
+ client_loop.SkipAosLog();
+ StarterClient client(&client_loop);
+ bool success = false;
+ auto client_node = client_loop.node();
+
+ // limit the amount of time we will wait for the test to finish.
+ client_loop
+ .AddTimer([&client_loop] {
+ client_loop.Exit();
+ FAIL() << "ERROR: The test has failed, the watcher has timed out. "
+ "The chain of stages defined below did not complete "
+ "within the time limit.";
+ })
+ ->Setup(client_loop.monotonic_now() + std::chrono::seconds(20));
+
+ // variables have been defined, here we define the body of the test.
+ // We want stage1 to succeed, triggering stage2.
+ // We want stage2 to timeout, triggering stage3.
+
+ auto stage3 = [&client_loop, &success]() {
+ LOG(INFO) << "Begin stage3.";
+ SUCCEED();
+ success = true;
+ client_loop.Exit();
+ LOG(INFO) << "End stage3.";
+ };
+ auto stage2 = [this, &starter, &client, &client_node, &stage3] {
+ LOG(INFO) << "Begin stage2";
+ test_done_ = true; // trigger `starter` to exit.
+
+ // wait for the starter event loop to close, so we can
+ // intentionally trigger a timeout.
+ int attempts = 0;
+ while (starter.event_loop()->is_running()) {
+ ++attempts;
+ if (attempts > 5) {
+ LOG(INFO) << "Timeout while waiting for starter to exit";
+ return;
+ }
+ LOG(INFO) << "Waiting for starter to close.";
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ client.SetTimeoutHandler(stage3);
+ client.SetSuccessHandler([]() {
+ LOG(INFO) << "stage3 success handler called.";
+ FAIL() << ": Command should not have succeeded here.";
+ });
+ // we want this command to timeout
+ client.SendCommands({{Command::START, "ping", {client_node}}},
+ std::chrono::seconds(5));
+ LOG(INFO) << "End stage2";
+ };
+ auto stage1 = [&client, &client_node, &stage2] {
+ LOG(INFO) << "Begin stage1";
+ client.SetTimeoutHandler(
+ []() { FAIL() << ": Command should not have timed out."; });
+ client.SetSuccessHandler(stage2);
+ client.SendCommands({{Command::STOP, "ping", {client_node}}},
+ std::chrono::seconds(5));
+ LOG(INFO) << "End stage1";
+ };
+ // start the test body
+ client_loop.AddTimer(stage1)->Setup(client_loop.monotonic_now() +
+ std::chrono::milliseconds(1));
+
+ // prepare the cleanup for starter. This will finish when we call
+ // `test_done_ = true;`.
+ SetupStarterCleanup(&starter);
+
+ // run `starter.Run()` in a thread to simulate it running on
+ // another process.
+ Event started;
+ std::thread starterd_thread([&starter, &started] {
+ starter.event_loop()->OnRun([&started]() { started.Set(); });
+ starter.Run();
+ });
+
+ started.Wait();
+ client_loop.Run();
+ EXPECT_TRUE(success);
+ ASSERT_FALSE(starter.event_loop()->is_running());
+ starterd_thread.join();
+}
+
} // namespace starter
} // namespace aos
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index b8b7343..30e0887 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -35,7 +35,10 @@
SendStatus();
status_count_ = 0;
})),
- cleanup_timer_(event_loop_.AddTimer([this] { event_loop_.Exit(); })),
+ cleanup_timer_(event_loop_.AddTimer([this] {
+ event_loop_.Exit();
+ LOG(INFO) << "Starter event loop exit finished.";
+ })),
max_status_count_(
event_loop_.GetChannel<aos::starter::Status>("/aos")->frequency() -
1),
diff --git a/aos/testdata/BUILD b/aos/testdata/BUILD
index 82e67f9..8b87682 100644
--- a/aos/testdata/BUILD
+++ b/aos/testdata/BUILD
@@ -20,6 +20,7 @@
"invalid_channel_name1.json",
"invalid_channel_name2.json",
"invalid_channel_name3.json",
+ "invalid_channel_name4.json",
"invalid_destination_node.json",
"invalid_logging_configuration.json",
"invalid_nodes.json",
diff --git a/aos/testdata/invalid_channel_name4.json b/aos/testdata/invalid_channel_name4.json
new file mode 100644
index 0000000..44ed8db
--- /dev/null
+++ b/aos/testdata/invalid_channel_name4.json
@@ -0,0 +1,9 @@
+{
+ "channels": [
+ {
+ "name": "foo",
+ "type": ".aos.bar",
+ "max_size": 5
+ }
+ ]
+}
diff --git a/aos/util/BUILD b/aos/util/BUILD
index 2f10a70..8ce96f0 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -1,10 +1,10 @@
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
-load("config_validator_macro.bzl", "config_validator_rule")
+load("config_validator_macro.bzl", "config_validator_test")
package(default_visibility = ["//visibility:public"])
-config_validator_rule(
+config_validator_test(
name = "config_validator_test",
config = "//aos/events:pingpong_config",
)
@@ -499,17 +499,14 @@
],
)
-cc_binary(
+cc_library(
name = "config_validator",
testonly = True,
srcs = ["config_validator.cc"],
target_compatible_with = ["@platforms//os:linux"],
deps = [
- "//aos:init",
+ ":config_validator_lib",
"//aos:json_to_flatbuffer",
- "//aos/events:simulated_event_loop",
- "//aos/events/logging:log_reader",
- "//aos/events/logging:log_writer",
"//aos/testing:googletest",
"@com_github_gflags_gflags//:gflags",
"@com_github_google_glog//:glog",
@@ -528,3 +525,59 @@
"@com_github_gflags_gflags//:gflags",
],
)
+
+cc_library(
+ name = "simulation_logger",
+ srcs = ["simulation_logger.cc"],
+ hdrs = ["simulation_logger.h"],
+ deps = [
+ "//aos/events:simulated_event_loop",
+ "//aos/events/logging:log_writer",
+ ],
+)
+
+flatbuffer_cc_library(
+ name = "config_validator_config_fbs",
+ srcs = ["config_validator_config.fbs"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "config_validator_lib",
+ testonly = True,
+ srcs = ["config_validator_lib.cc"],
+ hdrs = ["config_validator_lib.h"],
+ deps = [
+ ":config_validator_config_fbs",
+ ":simulation_logger",
+ "//aos/events:simulated_event_loop",
+ "//aos/events/logging:log_reader",
+ "//aos/events/logging:log_writer",
+ "//aos/network:timestamp_channel",
+ "//aos/testing:tmpdir",
+ "@com_github_google_glog//:glog",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "config_validator_lib_test",
+ srcs = ["config_validator_lib_test.cc"],
+ data = [
+ "//aos/util/test_data:multinode_common_logger",
+ "//aos/util/test_data:multinode_extraneous_timestamp",
+ "//aos/util/test_data:multinode_invalid_timestamp_logger_list",
+ "//aos/util/test_data:multinode_no_logged_timestamps",
+ "//aos/util/test_data:multinode_no_statistics",
+ "//aos/util/test_data:multinode_timestamp_typo",
+ "//aos/util/test_data:valid_multinode_config",
+ "//aos/util/test_data:valid_singlenode_config",
+ ],
+ deps = [
+ ":config_validator_lib",
+ "//aos:json_to_flatbuffer",
+ "//aos/testing:googletest",
+ "//aos/testing:path",
+ ],
+)
diff --git a/aos/util/config_validator.cc b/aos/util/config_validator.cc
index d5bd6ba..df21ba0 100644
--- a/aos/util/config_validator.cc
+++ b/aos/util/config_validator.cc
@@ -1,16 +1,9 @@
-#include <chrono>
-
-#include "aos/configuration.h"
-#include "aos/events/logging/log_reader.h"
-#include "aos/events/logging/log_writer.h"
-#include "aos/events/simulated_event_loop.h"
-#include "aos/init.h"
#include "aos/json_to_flatbuffer.h"
-#include "aos/network/team_number.h"
-#include "gflags/gflags.h"
-#include "gtest/gtest.h"
+#include "aos/util/config_validator_lib.h"
DEFINE_string(config, "", "Name of the config file to replay using.");
+DEFINE_string(validation_config, "{}",
+ "JSON config to use to validate the config.");
/* This binary is used to validate that all of the
needed remote timestamps channels are in the config
to log the timestamps.
@@ -26,9 +19,11 @@
const aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(FLAGS_config);
- aos::SimulatedEventLoopFactory factory(&config.message());
-
- factory.RunFor(std::chrono::seconds(1));
+ const aos::FlatbufferDetachedBuffer<aos::util::ConfigValidatorConfig>
+ validator_config =
+ aos::JsonToFlatbuffer<aos::util::ConfigValidatorConfig>(
+ FLAGS_validation_config);
+ aos::util::ConfigIsValid(&config.message(), &validator_config.message());
}
// TODO(milind): add more tests, the above one doesn't
diff --git a/aos/util/config_validator_config.fbs b/aos/util/config_validator_config.fbs
new file mode 100644
index 0000000..cda84b2
--- /dev/null
+++ b/aos/util/config_validator_config.fbs
@@ -0,0 +1,55 @@
+namespace aos.util;
+
+// This file defines a schema for what to validate when we run the
+// config_validator against an AOS config.
+// The primary purpose of this config is to allow the user to specify what
+// sets of nodes they expect to be able to log on so that we can validate the
+// logging configurations. In the future this may also include flags to indicate
+// how aggressively to do certain checks.
+//
+// This flatbuffer should not exist in serialized form anywhere, and so is
+// safe to modify in non-backwards-compatible ways.
+
+// Species a set of nodes that you should be able to combine the logs from and
+// subsequently replay. E.g., this allows you to write a check that says
+// "If you combine logs from pi2 & pi4, you should be able to replay data from
+// nodes pi2, pi4, and pi6"; or
+// "When logs from all nodes are combined, you should be able to replay data
+// for all nodes;" or
+// "Each node should log all the data needed to replay its own data"
+// (this would require muliple LoggerNodeSetValidation's).
+//
+// Each LoggerNodeSetValidation table represents a single set of logging nodes
+// that should be able to replay data on some number of other nodes. An empty
+// list of loggers or replay_nodes indicates "all nodes." The above examples
+// could then be represented by, e.g.:
+// "pi2 & pi4 -> pi2, pi4, & pi6":
+// {"loggers": ["pi2", "pi4"], "replay_nodes": ["pi2", "pi4", "pi6"]}
+// "all -> all": {"logger": [], "replay_nodes": []}
+// "each node -> itself": [
+// {"logger": ["pi1"], "replay_nodes": ["pi1"]},
+// {"logger": ["pi2"], "replay_nodes": ["pi2"]},
+// {"logger": ["pi3"], "replay_nodes": ["pi3"]},
+// {"logger": ["pi4"], "replay_nodes": ["pi4"]}]
+table LoggerNodeSetValidation {
+ loggers:[string] (id: 0);
+ replay_nodes:[string] (id: 1);
+}
+
+// This table specifies which
+table LoggingConfigValidation {
+ // If true, all channels should be logged by some valid set of loggers.
+ // Essentially, this is checking that no channels are configured to be
+ // NOT_LOGGED except for remote timestamp channels.
+ all_channels_logged:bool = true (id: 0);
+ // A list of all the sets of logger nodes that we care about. Typically this
+ // should at least include an entry that says that "logs from all nodes should
+ // combine to allow you to replay all nodes."
+ logger_sets:[LoggerNodeSetValidation] (id: 1);
+}
+
+table ConfigValidatorConfig {
+ logging:LoggingConfigValidation (id: 0);
+}
+
+root_type ConfigValidatorConfig;
diff --git a/aos/util/config_validator_lib.cc b/aos/util/config_validator_lib.cc
new file mode 100644
index 0000000..0f90ed6
--- /dev/null
+++ b/aos/util/config_validator_lib.cc
@@ -0,0 +1,292 @@
+#include "aos/util/config_validator_lib.h"
+
+#include <chrono>
+
+#include "aos/events/logging/log_reader.h"
+#include "aos/events/logging/log_writer.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/network/remote_message_generated.h"
+#include "aos/network/timestamp_channel.h"
+#include "aos/testing/tmpdir.h"
+#include "aos/util/simulation_logger.h"
+
+DECLARE_bool(validate_timestamp_logger_nodes);
+
+namespace aos::util {
+
+namespace {
+void RunSimulationAndExit(const aos::Configuration *config) {
+ aos::SimulatedEventLoopFactory factory(config);
+
+ factory.RunFor(std::chrono::seconds(1));
+
+ std::exit(EXIT_SUCCESS);
+}
+
+// Checks if either the node is in the specified list of node names or if the
+// list is empty (in which case it is treated as matching all nodes).
+bool NodeInList(
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *list,
+ const aos::Node *node) {
+ if (list == nullptr || list->size() == 0) {
+ return true;
+ }
+ for (const flatbuffers::String *name : *list) {
+ if (name->string_view() == node->name()->string_view()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+void ConfigIsValid(const aos::Configuration *config,
+ const ConfigValidatorConfig *validation_config) {
+ ASSERT_TRUE(config->has_channels())
+ << "An AOS config must have channels. If you have a valid use-case for "
+ "channels with no channels, please write a design proposal.";
+
+ // First, we do some sanity checks--these are likely to indicate a malformed
+ // config, and so catching them early with a clear error message is likely to
+ // help.
+
+ // The set of all channels that are required by the channels that are
+ // configured--these are the remote timestamp channels that *must* be present,
+ // and ideally there are no other channels present.
+ std::set<const Channel *> required_timestamp_channels;
+ // The set of all channels that *look* like remote timestamp channels. This
+ // may include channels that are improperly configured and thus have typos &
+ // aren't actually going to do anything at runtime.
+ std::set<const Channel *> configured_timestamp_channels;
+ bool validation_failed = false;
+ for (size_t channel_index = 0; channel_index < config->channels()->size();
+ ++channel_index) {
+ const aos::Channel *channel = config->channels()->Get(channel_index);
+ ASSERT_TRUE(channel->has_name()) << "All AOS channels must have a name.";
+ ASSERT_TRUE(channel->has_type()) << "All AOS channels must have a type.";
+
+ const bool channel_looks_like_remote_message_channel =
+ channel->type()->string_view() ==
+ message_bridge::RemoteMessage::GetFullyQualifiedName();
+
+ const bool check_for_not_logged_channels =
+ !validation_config->has_logging() ||
+ validation_config->logging()->all_channels_logged();
+ const bool channel_is_not_logged =
+ channel->logger() == aos::LoggerConfig::NOT_LOGGED;
+ if (check_for_not_logged_channels) {
+ if (channel_looks_like_remote_message_channel != channel_is_not_logged) {
+ LOG(WARNING)
+ << "Channel " << configuration::StrippedChannelToString(channel)
+ << " is " << EnumNameLoggerConfig(channel->logger()) << " but "
+ << (channel_looks_like_remote_message_channel ? "is" : "is not")
+ << " a remote timestamp channel. This is almost certainly wrong.";
+ validation_failed = true;
+ }
+ }
+
+ if (channel_looks_like_remote_message_channel) {
+ configured_timestamp_channels.insert(channel);
+ } else {
+ if (channel->has_destination_nodes()) {
+ // TODO(james): Technically the timestamp finder should receive a
+ // non-empty application name. However, there are no known users that
+ // care at this moment.
+ message_bridge::ChannelTimestampFinder timestamp_finder(
+ config, "",
+ configuration::GetNode(config,
+ channel->source_node()->string_view()));
+ for (const Connection *connection : *channel->destination_nodes()) {
+ switch (connection->timestamp_logger()) {
+ case LoggerConfig::NOT_LOGGED:
+ case LoggerConfig::LOCAL_LOGGER:
+ if (connection->has_timestamp_logger_nodes()) {
+ LOG(WARNING)
+ << "Connections that are "
+ << EnumNameLoggerConfig(connection->timestamp_logger())
+ << " should not have remote timestamp logger nodes "
+ "populated. This is for the connection to "
+ << connection->name()->string_view() << " on "
+ << configuration::StrippedChannelToString(channel);
+ validation_failed = true;
+ }
+ break;
+ case LoggerConfig::REMOTE_LOGGER:
+ case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
+ if (!connection->has_timestamp_logger_nodes() ||
+ connection->timestamp_logger_nodes()->size() != 1 ||
+ connection->timestamp_logger_nodes()->Get(0)->string_view() !=
+ channel->source_node()->string_view()) {
+ LOG(WARNING)
+ << "Connections that are "
+ << EnumNameLoggerConfig(connection->timestamp_logger())
+ << " should have exactly 1 remote timestamp logger node "
+ "populated, and that node should be the source_node ("
+ << channel->source_node()->string_view()
+ << "). This is for the connection to "
+ << connection->name()->string_view() << " on "
+ << configuration::StrippedChannelToString(channel);
+ validation_failed = true;
+ }
+ // TODO(james): This will be overly noisy, as it ends up
+ // CHECK-failing.
+ required_timestamp_channels.insert(CHECK_NOTNULL(
+ timestamp_finder.ForChannel(channel, connection)));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Check that all of the things that look like timestamp channels are indeed
+ // required.
+ // Note: Because ForChannel() will die if a required channel is not present,
+ // we do not do a separate check that all the required channels exist.
+ for (const auto &channel : configured_timestamp_channels) {
+ if (required_timestamp_channels.count(channel) == 0) {
+ LOG(WARNING) << "Timestamp channel "
+ << configuration::StrippedChannelToString(channel)
+ << " was specified in the config but is not used.";
+ validation_failed = true;
+ }
+ }
+
+ if (validation_failed) {
+ FAIL() << "Remote timestamp linting failed.";
+ return;
+ }
+
+ // Because the most common way for simulation to fail involves it dying, force
+ // it to fail in a slightly more controlled manner.
+ ASSERT_EXIT(RunSimulationAndExit(config),
+ ::testing::ExitedWithCode(EXIT_SUCCESS), "");
+
+ if (!validation_config->has_logging() || !configuration::MultiNode(config)) {
+ return;
+ }
+
+ // We will run all the logger configs in two modes:
+ // 1) We don't send any data on any non-infrastructure channels; this confirms
+ // that the logs are readable in the absence of any user applications being
+ // present.
+ // 2) We confirm that we can generate a good logfile that actually has data
+ // on every channel (some checks in the LogReader may not get hit if there
+ // is no data on a given channel).
+ const std::string log_path = aos::testing::TestTmpDir() + "/logs/";
+ for (const bool send_data_on_channels : {false, true}) {
+ SCOPED_TRACE(send_data_on_channels);
+ for (const LoggerNodeSetValidation *logger_set :
+ *validation_config->logging()->logger_sets()) {
+ SCOPED_TRACE(aos::FlatbufferToJson(logger_set));
+ aos::SimulatedEventLoopFactory factory(config);
+ std::vector<std::unique_ptr<LoggerState>> loggers;
+ if (logger_set->has_loggers() && logger_set->loggers()->size() > 0) {
+ std::vector<std::string> logger_nodes;
+ for (const auto &node : *logger_set->loggers()) {
+ logger_nodes.push_back(node->str());
+ }
+ loggers = MakeLoggersForNodes(&factory, logger_nodes, log_path);
+ } else {
+ loggers = MakeLoggersForAllNodes(&factory, log_path);
+ }
+
+ std::vector<std::unique_ptr<EventLoop>> test_loops;
+ std::map<std::string, std::vector<std::unique_ptr<RawSender>>>
+ test_senders;
+
+ if (send_data_on_channels) {
+ // Make a sender on every non-infrastructure channel on every node
+ // (including channels that may not be observable by the current logger
+ // set).
+ for (const aos::Node *node : configuration::GetNodes(config)) {
+ test_loops.emplace_back(factory.MakeEventLoop("", node));
+ for (const aos::Channel *channel : *config->channels()) {
+ // TODO(james): Make a more sophisticated check for "infrastructure"
+ // channels than just looking for a "/aos" in the channel--we don't
+ // accidentally want to spam nonsense data onto any timestamp
+ // channels, though.
+ if (configuration::ChannelIsSendableOnNode(channel, node) &&
+ channel->name()->str().find("/aos") == std::string::npos &&
+ channel->logger() != LoggerConfig::NOT_LOGGED) {
+ test_senders[node->name()->str()].emplace_back(
+ test_loops.back()->MakeRawSender(channel));
+ RawSender *sender =
+ test_senders[node->name()->str()].back().get();
+ test_loops.back()->OnRun([sender, channel]() {
+ flatbuffers::DetachedBuffer buffer =
+ JsonToFlatbuffer("{}", channel->schema());
+ sender->CheckOk(sender->Send(buffer.data(), buffer.size()));
+ });
+ }
+ }
+ }
+ }
+
+ factory.RunFor(std::chrono::seconds(2));
+
+ // Get all of the loggers to close before trying to read the logfiles.
+ loggers.clear();
+
+ // Confirm that we can read the log, and that if we put data in it that we
+ // can find data on all the nodes that the user cares about.
+ logger::LogReader reader(logger::SortParts(logger::FindLogs(log_path)));
+ SimulatedEventLoopFactory replay_factory(reader.configuration());
+ reader.RegisterWithoutStarting(&replay_factory);
+
+ // Find every channel we deliberately sent data on, and if it is for a
+ // node that we care about, confirm that we get it during replay.
+ std::vector<std::unique_ptr<EventLoop>> replay_loops;
+ std::vector<std::unique_ptr<RawFetcher>> fetchers;
+ for (const aos::Node *node :
+ configuration::GetNodes(replay_factory.configuration())) {
+ // If the user doesn't care about this node, don't check it.
+ if (!NodeInList(logger_set->replay_nodes(), node)) {
+ continue;
+ }
+ replay_loops.emplace_back(replay_factory.MakeEventLoop("", node));
+ for (const auto &sender : test_senders[node->name()->str()]) {
+ const aos::Channel *channel = configuration::GetChannel(
+ replay_factory.configuration(), sender->channel(), "", node);
+ fetchers.emplace_back(replay_loops.back()->MakeRawFetcher(channel));
+ }
+ }
+
+ std::vector<std::pair<const aos::Node *, std::unique_ptr<RawFetcher>>>
+ remote_fetchers;
+ for (const auto &fetcher : fetchers) {
+ for (auto &loop : replay_loops) {
+ const Connection *connection =
+ configuration::ConnectionToNode(fetcher->channel(), loop->node());
+ if (connection != nullptr) {
+ remote_fetchers.push_back(std::make_pair(
+ loop->node(), loop->MakeRawFetcher(fetcher->channel())));
+ }
+ }
+ }
+
+ replay_factory.Run();
+
+ for (auto &fetcher : fetchers) {
+ EXPECT_TRUE(fetcher->Fetch())
+ << "Failed to log or replay any data on "
+ << configuration::StrippedChannelToString(fetcher->channel());
+ }
+
+ for (auto &pair : remote_fetchers) {
+ EXPECT_TRUE(pair.second->Fetch())
+ << "Failed to log or replay any data on "
+ << configuration::StrippedChannelToString(pair.second->channel())
+ << " from remote node " << logger::MaybeNodeName(pair.first) << ".";
+ }
+
+ reader.Deregister();
+
+ // Clean up the logs.
+ UnlinkRecursive(log_path);
+ }
+ }
+}
+
+} // namespace aos::util
diff --git a/aos/util/config_validator_lib.h b/aos/util/config_validator_lib.h
new file mode 100644
index 0000000..61658e5
--- /dev/null
+++ b/aos/util/config_validator_lib.h
@@ -0,0 +1,12 @@
+#ifndef AOS_UTIL_CONFIG_VALIDATOR_H_
+#define AOS_UTIL_CONFIG_VALIDATOR_H_
+
+#include "aos/configuration.h"
+#include "aos/util/config_validator_config_generated.h"
+#include "gtest/gtest.h"
+namespace aos::util {
+
+void ConfigIsValid(const aos::Configuration *config,
+ const ConfigValidatorConfig *validation_config);
+} // namespace aos::util
+#endif // AOS_UTIL_CONFIG_VALIDATOR_H_
diff --git a/aos/util/config_validator_lib_test.cc b/aos/util/config_validator_lib_test.cc
new file mode 100644
index 0000000..c68695c
--- /dev/null
+++ b/aos/util/config_validator_lib_test.cc
@@ -0,0 +1,189 @@
+#include "aos/util/config_validator_lib.h"
+
+#include "aos/json_to_flatbuffer.h"
+#include "aos/testing/path.h"
+#include "gtest/gtest-spi.h"
+
+using aos::testing::ArtifactPath;
+namespace aos::util::testing {
+
+// Check that a reasonably normal config passes the config validator with a
+// reasonable set of checks turned on.
+TEST(ConfigValidatorTest, NoErrorOnValidConfigs) {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(
+ ArtifactPath("aos/util/test_data/valid_multinode_config.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": [],
+ "replay_nodes": []
+ },
+ {
+ "loggers": ["pi1"],
+ "replay_nodes": ["pi1"]
+ },
+ {
+ "loggers": ["pi2"],
+ "replay_nodes": ["pi2"]
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+}
+
+// Check that a reasonably normal single-node config passes the config validator
+// with a reasonable set of checks turned on.
+TEST(ConfigValidatorTest, NoErrorOnValidSingleNodeConfig) {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(
+ ArtifactPath("aos/util/test_data/valid_singlenode_config.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": [],
+ "replay_nodes": []
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+}
+
+// Checks that the validator fails if the message bridge statistics channels are
+// missing.
+TEST(ConfigValidatorTest, FailOnMissingStatisticsChannels) {
+ EXPECT_FATAL_FAILURE(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(ArtifactPath(
+ "aos/util/test_data/multinode_no_statistics.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>("{}");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ "Statistics");
+}
+
+// Checks that the validator fails if a timestamp channel has a typo and so
+// doesn't exist.
+TEST(ConfigValidatorTest, FailOnTimestampTypo) {
+ EXPECT_DEATH(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(ArtifactPath(
+ "aos/util/test_data/multinode_timestamp_typo.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>("{}");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ "not found in config");
+}
+
+// Checks that the validator fails if there is a RemoteMessage channel that is
+// *not* a timestamp channel (Since this is almost always a typo).
+TEST(ConfigValidatorTest, FailOnExtraneousTimestampChannel) {
+ EXPECT_FATAL_FAILURE(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(ArtifactPath(
+ "aos/util/test_data/multinode_extraneous_timestamp.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>("{}");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ "linting failed");
+}
+
+// Checks that the validator fails on timestamp logger nodes that won't really
+// log the timestamps.
+TEST(ConfigValidatorTest, FailOnInvalidRemoteTimestampLogger) {
+ EXPECT_FATAL_FAILURE(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(
+ ArtifactPath("aos/util/test_data/"
+ "multinode_invalid_timestamp_logger_list.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": [],
+ "replay_nodes": []
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ "linting failed");
+}
+
+// Checks that if you attempt to log on pi2 but expect it to have data for pi1
+// then the test fails (at least, for a config which does not forward all the
+// channels between the nodes).
+TEST(ConfigValidatorTest, FailOnNormalInsufficientLogging) {
+ EXPECT_NONFATAL_FAILURE(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(
+ ArtifactPath("aos/util/test_data/valid_multinode_config.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": ["pi2"],
+ "replay_nodes": ["pi1"]
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ "Failed to log");
+}
+
+// Checks that if we have a node that is configured to log all the data from all
+// the nodes that the test passes.
+TEST(ConfigValidatorTest, PassCommonLoggerNode) {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(
+ ArtifactPath("aos/util/test_data/multinode_common_logger.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": ["pi2"],
+ "replay_nodes": ["pi1"]
+ },
+ {
+ "loggers": [],
+ "replay_nodes": []
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+}
+
+// Sets up a config that will not actually log sufficient timestamp data to
+// support full replay, and ensures that we identify that.
+TEST(ConfigValidatorTest, FailOnInsufficientConfiguredTimestampData) {
+ EXPECT_NONFATAL_FAILURE(
+ {
+ const FlatbufferDetachedBuffer<Configuration> config =
+ configuration::ReadConfig(ArtifactPath(
+ "aos/util/test_data/multinode_no_logged_timestamps.json"));
+ const FlatbufferDetachedBuffer<ConfigValidatorConfig> validator_config =
+ JsonToFlatbuffer<ConfigValidatorConfig>(R"json({"logging": {
+ "all_channels_logged": true,
+ "logger_sets": [
+ {
+ "loggers": [],
+ "replay_nodes": []
+ }
+ ]}})json");
+ ConfigIsValid(&config.message(), &validator_config.message());
+ },
+ R"json(Failed to log or replay any data on { "name": "/test", "type": "aos.examples.Ping" } from remote node pi2)json");
+}
+
+} // namespace aos::util::testing
diff --git a/aos/util/config_validator_macro.bzl b/aos/util/config_validator_macro.bzl
index 453c5c2..cd05bab 100644
--- a/aos/util/config_validator_macro.bzl
+++ b/aos/util/config_validator_macro.bzl
@@ -1,20 +1,17 @@
-def config_validator_rule(name, config, extension = ".bfbs", visibility = None):
+def config_validator_test(name, config, logger_sets = [{}], check_for_not_logged_channels = False, extension = ".bfbs", visibility = None):
'''
Macro to take a config and pass it to the config validator to validate that it will work on a real system.
- Currently just checks that the system can startup, but will check that timestamp channels are properly logged in the future.
-
Args:
name: name that the config validator uses, e.g. "test_config",
config: config rule that needs to be validated, e.g. "//aos/events:pingpong_config",
'''
config_file = config + extension
- native.genrule(
+ config_json = json.encode({"logging": {"all_channels_logged": check_for_not_logged_channels, "logger_sets": logger_sets}})
+ native.cc_test(
name = name,
- outs = [name + ".txt"],
- cmd = "$(location //aos/util:config_validator) --config $(location %s) > $@" % config_file,
- srcs = [config_file],
- tools = ["//aos/util:config_validator"],
- testonly = True,
+ deps = ["//aos/util:config_validator"],
+ args = ["--config=$(location %s)" % config_file, "--validation_config='%s'" % config_json],
+ data = [config_file],
visibility = visibility,
)
diff --git a/aos/util/file_test.cc b/aos/util/file_test.cc
index d4382c4..ba03ea5 100644
--- a/aos/util/file_test.cc
+++ b/aos/util/file_test.cc
@@ -7,8 +7,6 @@
#include "aos/testing/tmpdir.h"
#include "gtest/gtest.h"
-DECLARE_bool(die_on_malloc);
-
namespace aos {
namespace util {
namespace testing {
@@ -52,9 +50,6 @@
FileReader reader(test_file);
- gflags::FlagSaver flag_saver;
- FLAGS_die_on_malloc = true;
- RegisterMallocHook();
aos::ScopedRealtime realtime;
{
std::array<char, 20> contents;
@@ -79,9 +74,6 @@
FileWriter writer(test_file);
- gflags::FlagSaver flag_saver;
- FLAGS_die_on_malloc = true;
- RegisterMallocHook();
FileWriter::WriteResult result;
{
aos::ScopedRealtime realtime;
@@ -104,9 +96,6 @@
// Mess up the file management by closing the file descriptor.
PCHECK(0 == close(writer.fd()));
- gflags::FlagSaver flag_saver;
- FLAGS_die_on_malloc = true;
- RegisterMallocHook();
FileWriter::WriteResult result;
{
aos::ScopedRealtime realtime;
diff --git a/aos/util/log_to_mcap.cc b/aos/util/log_to_mcap.cc
index 38a3be7..d4e240f 100644
--- a/aos/util/log_to_mcap.cc
+++ b/aos/util/log_to_mcap.cc
@@ -74,33 +74,31 @@
std::unique_ptr<aos::EventLoop> clock_event_loop;
std::unique_ptr<aos::ClockPublisher> clock_publisher;
if (FLAGS_include_clocks) {
- reader.OnStart(node, [&clock_event_loop, &reader, &clock_publisher,
- &factory, node]() {
- clock_event_loop =
- reader.event_loop_factory()->MakeEventLoop("clock", node);
- clock_publisher = std::make_unique<aos::ClockPublisher>(
- &factory, clock_event_loop.get());
- });
+ reader.OnStart(
+ node, [&clock_event_loop, &reader, &clock_publisher, &factory, node]() {
+ clock_event_loop =
+ reader.event_loop_factory()->MakeEventLoop("clock", node);
+ clock_publisher = std::make_unique<aos::ClockPublisher>(
+ &factory, clock_event_loop.get());
+ });
}
std::unique_ptr<aos::EventLoop> mcap_event_loop;
CHECK(!FLAGS_output_path.empty());
std::unique_ptr<aos::McapLogger> relogger;
- factory.GetNodeEventLoopFactory(node)
- ->OnStartup([&relogger, &mcap_event_loop, &reader, node]() {
- mcap_event_loop =
- reader.event_loop_factory()->MakeEventLoop("mcap", node);
- relogger = std::make_unique<aos::McapLogger>(
- mcap_event_loop.get(), FLAGS_output_path,
- FLAGS_mode == "flatbuffer"
- ? aos::McapLogger::Serialization::kFlatbuffer
- : aos::McapLogger::Serialization::kJson,
- FLAGS_canonical_channel_names
- ? aos::McapLogger::CanonicalChannelNames::kCanonical
- : aos::McapLogger::CanonicalChannelNames::kShortened,
- FLAGS_compress ? aos::McapLogger::Compression::kLz4
- : aos::McapLogger::Compression::kNone);
- });
+ factory.GetNodeEventLoopFactory(node)->OnStartup([&relogger, &mcap_event_loop,
+ &reader, node]() {
+ mcap_event_loop = reader.event_loop_factory()->MakeEventLoop("mcap", node);
+ relogger = std::make_unique<aos::McapLogger>(
+ mcap_event_loop.get(), FLAGS_output_path,
+ FLAGS_mode == "flatbuffer" ? aos::McapLogger::Serialization::kFlatbuffer
+ : aos::McapLogger::Serialization::kJson,
+ FLAGS_canonical_channel_names
+ ? aos::McapLogger::CanonicalChannelNames::kCanonical
+ : aos::McapLogger::CanonicalChannelNames::kShortened,
+ FLAGS_compress ? aos::McapLogger::Compression::kLz4
+ : aos::McapLogger::Compression::kNone);
+ });
reader.event_loop_factory()->Run();
reader.Deregister();
}
diff --git a/aos/util/simulation_logger.cc b/aos/util/simulation_logger.cc
new file mode 100644
index 0000000..1f55ece
--- /dev/null
+++ b/aos/util/simulation_logger.cc
@@ -0,0 +1,40 @@
+#include "aos/util/simulation_logger.h"
+#include "aos/events/logging/logfile_utils.h"
+
+namespace aos::util {
+LoggerState::LoggerState(aos::SimulatedEventLoopFactory *factory,
+ const aos::Node *node, std::string_view output_folder)
+ : event_loop_(factory->MakeEventLoop("logger", node)),
+ namer_(std::make_unique<aos::logger::MultiNodeFilesLogNamer>(
+ absl::StrCat(output_folder, "/", logger::MaybeNodeName(node), "/"),
+ event_loop_.get())),
+ logger_(std::make_unique<aos::logger::Logger>(event_loop_.get())) {
+ event_loop_->SkipTimingReport();
+ event_loop_->SkipAosLog();
+ event_loop_->OnRun([this]() { logger_->StartLogging(std::move(namer_)); });
+}
+
+std::vector<std::unique_ptr<LoggerState>> MakeLoggersForNodes(
+ aos::SimulatedEventLoopFactory *factory,
+ const std::vector<std::string> &nodes_to_log,
+ std::string_view output_folder) {
+ std::vector<std::unique_ptr<LoggerState>> loggers;
+ for (const std::string &node : nodes_to_log) {
+ loggers.emplace_back(std::make_unique<LoggerState>(
+ factory, aos::configuration::GetNode(factory->configuration(), node),
+ output_folder));
+ }
+ return loggers;
+}
+
+std::vector<std::unique_ptr<LoggerState>> MakeLoggersForAllNodes(
+ aos::SimulatedEventLoopFactory *factory, std::string_view output_folder) {
+ std::vector<std::unique_ptr<LoggerState>> loggers;
+ for (const aos::Node *node : configuration::GetNodes(factory->configuration())) {
+ loggers.emplace_back(
+ std::make_unique<LoggerState>(factory, node, output_folder));
+ }
+ return loggers;
+}
+
+} // namespace aos::util
diff --git a/aos/util/simulation_logger.h b/aos/util/simulation_logger.h
new file mode 100644
index 0000000..f431b4c
--- /dev/null
+++ b/aos/util/simulation_logger.h
@@ -0,0 +1,33 @@
+#ifndef AOS_UTIL_SIMULATION_LOGGER_H_
+#define AOS_UTIL_SIMULATION_LOGGER_H_
+#include <string_view>
+
+#include "aos/events/logging/log_writer.h"
+#include "aos/events/simulated_event_loop.h"
+namespace aos::util {
+
+class LoggerState {
+ public:
+ LoggerState(aos::SimulatedEventLoopFactory *factory, const aos::Node *node,
+ std::string_view output_folder);
+
+ private:
+ std::unique_ptr<aos::EventLoop> event_loop_;
+ std::unique_ptr<aos::logger::LogNamer> namer_;
+ std::unique_ptr<aos::logger::Logger> logger_;
+};
+
+// Creates a logger for each of the specified nodes. This makes it so that you
+// can easily setup some number of loggers in simulation or log replay without
+// needing to redo all the boilerplate every time.
+std::vector<std::unique_ptr<LoggerState>> MakeLoggersForNodes(
+ aos::SimulatedEventLoopFactory *factory,
+ const std::vector<std::string> &nodes_to_log,
+ std::string_view output_folder);
+
+// Creates loggers for all of the nodes.
+std::vector<std::unique_ptr<LoggerState>> MakeLoggersForAllNodes(
+ aos::SimulatedEventLoopFactory *factory, std::string_view output_folder);
+
+} // namespace aos::util
+#endif // AOS_UTIL_SIMULATION_LOGGER_H_
diff --git a/aos/util/test_data/BUILD b/aos/util/test_data/BUILD
new file mode 100644
index 0000000..016dd07
--- /dev/null
+++ b/aos/util/test_data/BUILD
@@ -0,0 +1,28 @@
+load("//aos:config.bzl", "aos_config")
+
+[
+ aos_config(
+ name = name,
+ src = name + "_source.json",
+ flatbuffers = [
+ "//aos/network:remote_message_fbs",
+ "//aos/events:ping_fbs",
+ "//aos/network:message_bridge_client_fbs",
+ "//aos/network:message_bridge_server_fbs",
+ "//aos/network:timestamp_fbs",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = ["//aos/events:aos_config"],
+ )
+ for name in [
+ "valid_multinode_config",
+ "valid_singlenode_config",
+ "multinode_no_statistics",
+ "multinode_timestamp_typo",
+ "multinode_extraneous_timestamp",
+ "multinode_invalid_timestamp_logger_list",
+ "multinode_common_logger",
+ "multinode_no_logged_timestamps",
+ ]
+]
diff --git a/aos/util/test_data/multinode_common_logger_source.json b/aos/util/test_data/multinode_common_logger_source.json
new file mode 100644
index 0000000..81aa065
--- /dev/null
+++ b/aos/util/test_data/multinode_common_logger_source.json
@@ -0,0 +1,156 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": ["pi2"],
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/multinode_extraneous_timestamp_source.json b/aos/util/test_data/multinode_extraneous_timestamp_source.json
new file mode 100644
index 0000000..2e889d0
--- /dev/null
+++ b/aos/util/test_data/multinode_extraneous_timestamp_source.json
@@ -0,0 +1,161 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/os-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/multinode_invalid_timestamp_logger_list_source.json b/aos/util/test_data/multinode_invalid_timestamp_logger_list_source.json
new file mode 100644
index 0000000..e501437
--- /dev/null
+++ b/aos/util/test_data/multinode_invalid_timestamp_logger_list_source.json
@@ -0,0 +1,154 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/multinode_no_logged_timestamps_source.json b/aos/util/test_data/multinode_no_logged_timestamps_source.json
new file mode 100644
index 0000000..a956f73
--- /dev/null
+++ b/aos/util/test_data/multinode_no_logged_timestamps_source.json
@@ -0,0 +1,147 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "NOT_LOGGED"
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/multinode_no_statistics_source.json b/aos/util/test_data/multinode_no_statistics_source.json
new file mode 100644
index 0000000..44fa5d8
--- /dev/null
+++ b/aos/util/test_data/multinode_no_statistics_source.json
@@ -0,0 +1,130 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/multinode_timestamp_typo_source.json b/aos/util/test_data/multinode_timestamp_typo_source.json
new file mode 100644
index 0000000..afb4275
--- /dev/null
+++ b/aos/util/test_data/multinode_timestamp_typo_source.json
@@ -0,0 +1,154 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestam",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/valid_multinode_config_source.json b/aos/util/test_data/valid_multinode_config_source.json
new file mode 100644
index 0000000..0c35251
--- /dev/null
+++ b/aos/util/test_data/valid_multinode_config_source.json
@@ -0,0 +1,154 @@
+{
+ "channels": [
+ {
+ "name": "/pi1/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi1",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "pi2",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi1",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "pi2",
+ "frequency": 15,
+ "max_size": 200,
+ "destination_nodes": [
+ {
+ "name": "pi1",
+ "priority": 1,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi2"],
+ "time_to_live": 5000000
+ }
+ ]
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi1",
+ "frequency": 2
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "pi2",
+ "frequency": 2
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/pi1/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1",
+ "frequency": 15
+ },
+ {
+ "name": "/pi2/aos/remote_timestamps/pi1/pi2/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi2",
+ "frequency": 15
+ },
+ {
+ "name": "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping",
+ "type": "aos.message_bridge.RemoteMessage",
+ "logger": "NOT_LOGGED",
+ "source_node": "pi1"
+ },
+ {
+ "name": "/pi1/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi1",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/pi2/aos",
+ "type": "aos.timing.Report",
+ "source_node": "pi2",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "source_node": "pi1",
+ "destination_nodes": [
+ {
+ "name": "pi2",
+ "priority": 1,
+ "timestamp_logger": "REMOTE_LOGGER",
+ "timestamp_logger_nodes": ["pi1"]
+ }
+ ],
+ "max_size": 20480
+ }
+ ],
+ "maps": [
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi1"
+ },
+ "rename": {
+ "name": "/pi1/aos"
+ }
+ },
+ {
+ "match": {
+ "name": "/aos*",
+ "source_node": "pi2"
+ },
+ "rename": {
+ "name": "/pi2/aos"
+ }
+ }
+ ],
+ "nodes": [
+ {
+ "name": "pi1"
+ },
+ {
+ "name": "pi2"
+ }
+ ]
+}
diff --git a/aos/util/test_data/valid_singlenode_config_source.json b/aos/util/test_data/valid_singlenode_config_source.json
new file mode 100644
index 0000000..736eace
--- /dev/null
+++ b/aos/util/test_data/valid_singlenode_config_source.json
@@ -0,0 +1,23 @@
+{
+ "channels": [
+ {
+ "name": "/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "frequency": 200,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/aos",
+ "type": "aos.timing.Report",
+ "frequency": 50,
+ "num_senders": 20,
+ "max_size": 2048
+ },
+ {
+ "name": "/test",
+ "type": "aos.examples.Ping",
+ "max_size": 20480
+ }
+ ]
+}
diff --git a/frc971/control_loops/drivetrain/localization/puppet_localizer_test.cc b/frc971/control_loops/drivetrain/localization/puppet_localizer_test.cc
index d64c419..e45f880 100644
--- a/frc971/control_loops/drivetrain/localization/puppet_localizer_test.cc
+++ b/frc971/control_loops/drivetrain/localization/puppet_localizer_test.cc
@@ -16,7 +16,6 @@
DEFINE_string(output_folder, "",
"If set, logs all channels to the provided logfile.");
-DECLARE_bool(die_on_malloc);
namespace frc971 {
namespace control_loops {
@@ -78,7 +77,6 @@
drivetrain_plant_(drivetrain_plant_event_loop_.get(),
drivetrain_plant_imu_event_loop_.get(), dt_config_,
std::chrono::microseconds(500)) {
- FLAGS_die_on_malloc = true;
set_team_id(frc971::control_loops::testing::kTeamNumber);
set_battery_voltage(12.0);
diff --git a/frc971/solvers/BUILD b/frc971/solvers/BUILD
new file mode 100644
index 0000000..fc89616
--- /dev/null
+++ b/frc971/solvers/BUILD
@@ -0,0 +1,22 @@
+cc_library(
+ name = "convex",
+ hdrs = ["convex.h"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@com_github_google_glog//:glog",
+ "@org_tuxfamily_eigen//:eigen",
+ ],
+)
+
+cc_test(
+ name = "convex_test",
+ srcs = [
+ "convex_test.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ ":convex",
+ "//aos/testing:googletest",
+ ],
+)
diff --git a/frc971/solvers/convex.h b/frc971/solvers/convex.h
new file mode 100644
index 0000000..aa14c1a
--- /dev/null
+++ b/frc971/solvers/convex.h
@@ -0,0 +1,381 @@
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <Eigen/Dense>
+#include <iomanip>
+
+#include "absl/strings/str_join.h"
+#include "glog/logging.h"
+
+namespace frc971 {
+namespace solvers {
+
+// TODO(austin): Steal JET from Ceres to generate the derivatives easily and
+// quickly?
+//
+// States is the number of inputs to the optimization problem.
+// M is the number of inequality constraints.
+// N is the number of equality constraints.
+template <size_t States, size_t M, size_t N>
+class ConvexProblem {
+ public:
+ // Returns the function to minimize and it's derivatives.
+ virtual double f0(
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X) const = 0;
+ virtual Eigen::Matrix<double, States, 1> df0(
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X) const = 0;
+ virtual Eigen::Matrix<double, States, States> ddf0(
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X) const = 0;
+
+ // Returns the constraints f(X) < 0, and their derivative.
+ virtual Eigen::Matrix<double, M, 1> f(
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X) const = 0;
+ virtual Eigen::Matrix<double, M, States> df(
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X) const = 0;
+
+ // Returns the equality constraints of the form A x = b
+ virtual Eigen::Matrix<double, N, States> A() const = 0;
+ virtual Eigen::Matrix<double, N, 1> b() const = 0;
+};
+
+// Implements a Primal-Dual Interior point method convex solver.
+// See 11.7 of https://web.stanford.edu/~boyd/cvxbook/bv_cvxbook.pdf
+//
+// States is the number of inputs to the optimization problem.
+// M is the number of inequality constraints.
+// N is the number of equality constraints.
+template <size_t States, size_t M, size_t N>
+class Solver {
+ public:
+ // Ratio to require the cost to decrease when line searching.
+ static constexpr double kAlpha = 0.05;
+ // Line search step parameter.
+ static constexpr double kBeta = 0.5;
+ static constexpr double kMu = 2.0;
+ // Terminal condition for the primal problem (equality constraints) and dual
+ // (gradient + inequality constraints).
+ static constexpr double kEpsilonF = 1e-6;
+ // Terminal condition for nu, the surrogate duality gap.
+ static constexpr double kEpsilon = 1e-6;
+
+ // Solves the problem given a feasible initial solution.
+ Eigen::Matrix<double, States, 1> Solve(
+ const ConvexProblem<States, M, N> &problem,
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X_initial);
+
+ private:
+ // Class to hold all the derivataves and function evaluations.
+ struct Derivatives {
+ Eigen::Matrix<double, States, 1> gradient;
+ Eigen::Matrix<double, States, States> hessian;
+
+ // Inequality function f
+ Eigen::Matrix<double, M, 1> f;
+ // df
+ Eigen::Matrix<double, M, States> df;
+
+ // ddf is assumed to be 0 because for the linear constraint distance
+ // function we are using, it is actually 0, and by assuming it is zero
+ // rather than passing it through as 0 to the solver, we can save enough CPU
+ // to make it worth it.
+
+ // A
+ Eigen::Matrix<double, N, States> A;
+ // Ax - b
+ Eigen::Matrix<double, N, 1> Axmb;
+ };
+
+ // Computes all the values for the given problem at the given state.
+ Derivatives ComputeDerivative(
+ const ConvexProblem<States, M, N> &problem,
+ const Eigen::Ref<const Eigen::Matrix<double, States + M + N, 1>> y);
+
+ // Computes Rt at the given state and with the given t_inverse. See 11.53 of
+ // cvxbook.pdf.
+ Eigen::Matrix<double, States + M + N, 1> Rt(
+ const Derivatives &derivatives,
+ Eigen::Matrix<double, States + M + N, 1> y, double t_inverse);
+
+ // Prints out all the derivatives with VLOG at the provided verbosity.
+ void PrintDerivatives(
+ const Derivatives &derivatives,
+ const Eigen::Ref<const Eigen::Matrix<double, States + M + N, 1>> y,
+ std::string_view prefix, int verbosity);
+};
+
+template <size_t States, size_t M, size_t N>
+Eigen::Matrix<double, States + M + N, 1> Solver<States, M, N>::Rt(
+ const Derivatives &derivatives, Eigen::Matrix<double, States + M + N, 1> y,
+ double t_inverse) {
+ Eigen::Matrix<double, States + M + N, 1> result;
+
+ Eigen::Ref<Eigen::Matrix<double, States, 1>> r_dual =
+ result.template block<States, 1>(0, 0);
+ Eigen::Ref<Eigen::Matrix<double, M, 1>> r_cent =
+ result.template block<M, 1>(States, 0);
+ Eigen::Ref<Eigen::Matrix<double, N, 1>> r_pri =
+ result.template block<N, 1>(States + M, 0);
+
+ Eigen::Ref<const Eigen::Matrix<double, M, 1>> lambda =
+ y.template block<M, 1>(States, 0);
+ Eigen::Ref<const Eigen::Matrix<double, N, 1>> v =
+ y.template block<N, 1>(States + M, 0);
+
+ r_dual = derivatives.gradient + derivatives.df.transpose() * lambda +
+ derivatives.A.transpose() * v;
+ r_cent = -(Eigen::DiagonalMatrix<double, M>(lambda) * derivatives.f +
+ t_inverse * Eigen::Matrix<double, M, 1>::Ones());
+ r_pri = derivatives.Axmb;
+
+ return result;
+}
+
+template <size_t States, size_t M, size_t N>
+Eigen::Matrix<double, States, 1> Solver<States, M, N>::Solve(
+ const ConvexProblem<States, M, N> &problem,
+ Eigen::Ref<const Eigen::Matrix<double, States, 1>> X_initial) {
+ const Eigen::IOFormat kHeavyFormat(Eigen::StreamPrecision, 0, ", ",
+ ",\n "
+ " ",
+ "[", "]", "[", "]");
+
+ Eigen::Matrix<double, States + M + N, 1> y =
+ Eigen::Matrix<double, States + M + N, 1>::Constant(1.0);
+ y.template block<States, 1>(0, 0) = X_initial;
+
+ Derivatives derivatives = ComputeDerivative(problem, y);
+
+ for (size_t i = 0; i < M; ++i) {
+ CHECK_LE(derivatives.f(i, 0), 0.0)
+ << ": Initial state " << X_initial.transpose().format(kHeavyFormat)
+ << " not feasible";
+ }
+
+ PrintDerivatives(derivatives, y, "", 1);
+
+ size_t iteration = 0;
+ while (true) {
+ // Solve for the primal-dual search direction by solving the newton step.
+ Eigen::Ref<const Eigen::Matrix<double, M, 1>> lambda =
+ y.template block<M, 1>(States, 0);
+
+ const double nu = -(derivatives.f.transpose() * lambda)(0, 0);
+ const double t_inverse = nu / (kMu * lambda.rows());
+ Eigen::Matrix<double, States + M + N, 1> rt_orig =
+ Rt(derivatives, y, t_inverse);
+
+ Eigen::Matrix<double, States + M + N, States + M + N> m1;
+ m1.setZero();
+ m1.template block<States, States>(0, 0) = derivatives.hessian;
+ m1.template block<States, M>(0, States) = derivatives.df.transpose();
+ m1.template block<States, N>(0, States + M) = derivatives.A.transpose();
+ m1.template block<M, States>(States, 0) =
+ -(Eigen::DiagonalMatrix<double, M>(lambda) * derivatives.df);
+ m1.template block<M, M>(States, States) -=
+ Eigen::DiagonalMatrix<double, M>(derivatives.f);
+ m1.template block<N, States>(States + M, 0) = derivatives.A;
+
+ Eigen::Matrix<double, States + M + N, 1> dy =
+ m1.colPivHouseholderQr().solve(-rt_orig);
+
+ Eigen::Ref<Eigen::Matrix<double, M, 1>> dlambda =
+ dy.template block<M, 1>(States, 0);
+
+ double s = 1.0;
+
+ // Now, time to do line search.
+ //
+ // Start by keeping lambda positive. Make sure our step doesn't let
+ // lambda cross 0.
+ for (int i = 0; i < dlambda.rows(); ++i) {
+ if (lambda(i) + s * dlambda(i) < 0.0) {
+ // Ignore tiny steps in lambda. They cause issues when we get really
+ // close to having our constraints met but haven't converged the rest
+ // of the problem and start to run into rounding issues in the matrix
+ // solve portion.
+ if (dlambda(i) < 0.0 && dlambda(i) > -1e-12) {
+ VLOG(1) << " lambda(" << i << ") " << lambda(i) << " + " << s
+ << " * " << dlambda(i) << " -> s would be now "
+ << -lambda(i) / dlambda(i);
+ dlambda(i) = 0.0;
+ VLOG(1) << " dy -> " << std::setprecision(12) << std::fixed
+ << std::setfill(' ') << dy.transpose().format(kHeavyFormat);
+ continue;
+ }
+ VLOG(1) << " lambda(" << i << ") " << lambda(i) << " + " << s << " * "
+ << dlambda(i) << " -> s now " << -lambda(i) / dlambda(i);
+ s = -lambda(i) / dlambda(i);
+ }
+ }
+
+ VLOG(1) << " After lambda line search, s is " << s;
+
+ VLOG(3) << " Initial step " << iteration << " -> " << std::setprecision(12)
+ << std::fixed << std::setfill(' ')
+ << dy.transpose().format(kHeavyFormat);
+ VLOG(3) << " rt -> "
+ << std::setprecision(12) << std::fixed << std::setfill(' ')
+ << rt_orig.transpose().format(kHeavyFormat);
+
+ const double rt_orig_squared_norm = rt_orig.squaredNorm();
+
+ Eigen::Matrix<double, States + M + N, 1> next_y;
+ Eigen::Matrix<double, States + M + N, 1> rt;
+ Derivatives next_derivatives;
+ while (true) {
+ next_y = y + s * dy;
+ next_derivatives = ComputeDerivative(problem, next_y);
+ rt = Rt(next_derivatives, next_y, t_inverse);
+
+ const Eigen::Ref<const Eigen::VectorXd> next_x =
+ next_y.block(0, 0, next_derivatives.hessian.rows(), 1);
+ const Eigen::Ref<const Eigen::VectorXd> next_lambda =
+ next_y.block(next_x.rows(), 0, next_derivatives.f.rows(), 1);
+
+ const Eigen::Ref<const Eigen::VectorXd> next_v = next_y.block(
+ next_x.rows() + next_lambda.rows(), 0, next_derivatives.A.rows(), 1);
+
+ VLOG(1) << " next_rt(" << iteration << ") is " << rt.norm() << " -> "
+ << std::setprecision(12) << std::fixed << std::setfill(' ')
+ << rt.transpose().format(kHeavyFormat);
+
+ PrintDerivatives(next_derivatives, next_y, "next_", 3);
+
+ if (next_derivatives.f.maxCoeff() > 0.0) {
+ VLOG(1) << " f_next > 0.0 -> " << next_derivatives.f.maxCoeff()
+ << ", continuing line search.";
+ s *= kBeta;
+ } else if (next_derivatives.Axmb.squaredNorm() < 0.1 &&
+ rt.squaredNorm() >
+ std::pow(1.0 - kAlpha * s, 2.0) * rt_orig_squared_norm) {
+ VLOG(1) << " |Rt| > |Rt+1| " << rt.norm() << " > " << rt_orig.norm()
+ << ", drt -> " << std::setprecision(12) << std::fixed
+ << std::setfill(' ')
+ << (rt_orig - rt).transpose().format(kHeavyFormat);
+ s *= kBeta;
+ } else {
+ break;
+ }
+ }
+
+ VLOG(1) << " Terminated line search with s " << s << ", " << rt.norm()
+ << "(|Rt+1|) < " << rt_orig.norm() << "(|Rt|)";
+ y = next_y;
+
+ const Eigen::Ref<const Eigen::VectorXd> next_lambda =
+ y.template block<M, 1>(States, 0);
+
+ // See if we hit our convergence criteria.
+ const double r_primal_squared_norm =
+ rt.template block<N, 1>(States + M, 0).squaredNorm();
+ VLOG(1) << " rt_next(" << iteration << ") is " << rt.norm() << " -> "
+ << std::setprecision(12) << std::fixed << std::setfill(' ')
+ << rt.transpose().format(kHeavyFormat);
+ if (r_primal_squared_norm < kEpsilonF * kEpsilonF) {
+ const double r_dual_squared_norm =
+ rt.template block<States, 1>(0, 0).squaredNorm();
+ if (r_dual_squared_norm < kEpsilonF * kEpsilonF) {
+ const double next_nu =
+ -(next_derivatives.f.transpose() * next_lambda)(0, 0);
+ if (next_nu < kEpsilon) {
+ VLOG(1) << " r_primal(" << iteration << ") -> "
+ << std::sqrt(r_primal_squared_norm) << " < " << kEpsilonF
+ << ", r_dual(" << iteration << ") -> "
+ << std::sqrt(r_dual_squared_norm) << " < " << kEpsilonF
+ << ", nu(" << iteration << ") -> " << next_nu << " < "
+ << kEpsilon;
+ break;
+ } else {
+ VLOG(1) << " nu(" << iteration << ") -> " << next_nu << " < "
+ << kEpsilon << ", not done yet";
+ }
+
+ } else {
+ VLOG(1) << " r_dual(" << iteration << ") -> "
+ << std::sqrt(r_dual_squared_norm) << " < " << kEpsilonF
+ << ", not done yet";
+ }
+ } else {
+ VLOG(1) << " r_primal(" << iteration << ") -> "
+ << std::sqrt(r_primal_squared_norm) << " < " << kEpsilonF
+ << ", not done yet";
+ }
+ VLOG(1) << " step(" << iteration << ") " << std::setprecision(12)
+ << (s * dy).transpose().format(kHeavyFormat);
+ VLOG(1) << " y(" << iteration << ") is now " << std::setprecision(12)
+ << y.transpose().format(kHeavyFormat);
+
+ // Very import, use the last set of derivatives we picked for our new y
+ // for the next iteration. This avoids re-computing it.
+ derivatives = std::move(next_derivatives);
+
+ ++iteration;
+ if (iteration > 100) {
+ LOG(FATAL) << "Too many iterations";
+ }
+ }
+
+ return y.template block<States, 1>(0, 0);
+}
+
+template <size_t States, size_t M, size_t N>
+typename Solver<States, M, N>::Derivatives
+Solver<States, M, N>::ComputeDerivative(
+ const ConvexProblem<States, M, N> &problem,
+ const Eigen::Ref<const Eigen::Matrix<double, States + M + N, 1>> y) {
+ const Eigen::Ref<const Eigen::Matrix<double, States, 1>> x =
+ y.template block<States, 1>(0, 0);
+
+ Derivatives derivatives;
+ derivatives.gradient = problem.df0(x);
+ derivatives.hessian = problem.ddf0(x);
+ derivatives.f = problem.f(x);
+ derivatives.df = problem.df(x);
+ derivatives.A = problem.A();
+ derivatives.Axmb =
+ derivatives.A * y.template block<States, 1>(0, 0) - problem.b();
+ return derivatives;
+}
+
+template <size_t States, size_t M, size_t N>
+void Solver<States, M, N>::PrintDerivatives(
+ const Derivatives &derivatives,
+ const Eigen::Ref<const Eigen::Matrix<double, States + M + N, 1>> y,
+ std::string_view prefix, int verbosity) {
+ const Eigen::Ref<const Eigen::VectorXd> x =
+ y.block(0, 0, derivatives.hessian.rows(), 1);
+ const Eigen::Ref<const Eigen::VectorXd> lambda =
+ y.block(x.rows(), 0, derivatives.f.rows(), 1);
+
+ if (VLOG_IS_ON(verbosity)) {
+ Eigen::IOFormat heavy(Eigen::StreamPrecision, 0, ", ",
+ ",\n "
+ " ",
+ "[", "]", "[", "]");
+ heavy.rowSeparator =
+ heavy.rowSeparator +
+ std::string(absl::StrCat(getpid()).size() + prefix.size(), ' ');
+
+ const Eigen::Ref<const Eigen::VectorXd> v =
+ y.block(x.rows() + lambda.rows(), 0, derivatives.A.rows(), 1);
+ VLOG(verbosity) << " " << prefix << "x: " << x.transpose().format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "lambda: " << lambda.transpose().format(heavy);
+ VLOG(verbosity) << " " << prefix << "v: " << v.transpose().format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "hessian: " << derivatives.hessian.format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "gradient: " << derivatives.gradient.format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "A: " << derivatives.A.format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "Ax-b: " << derivatives.Axmb.format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "f: " << derivatives.f.format(heavy);
+ VLOG(verbosity) << " " << prefix
+ << "df: " << derivatives.df.format(heavy);
+ }
+}
+
+}; // namespace solvers
+}; // namespace frc971
diff --git a/frc971/solvers/convex_test.cc b/frc971/solvers/convex_test.cc
new file mode 100644
index 0000000..213e70b
--- /dev/null
+++ b/frc971/solvers/convex_test.cc
@@ -0,0 +1,106 @@
+#include "frc971/solvers/convex.h"
+
+#include "gtest/gtest.h"
+
+namespace frc971 {
+namespace solvers {
+namespace testing {
+
+const Eigen::IOFormat kHeavyFormat(Eigen::StreamPrecision, 0, ", ",
+ ",\n "
+ " ",
+ "[", "]", "[", "]");
+
+class SimpleQP : public ConvexProblem<2, 4, 1> {
+ public:
+ // QP of the for 0.5 * X^t Q_ X + p.T * X
+ SimpleQP(Eigen::Matrix<double, 2, 2> Q, Eigen::Matrix<double, 2, 1> p,
+ double x0_max, double x0_min, double x1_max, double x1_min)
+ : Q_(Q), p_(p) {
+ C_ << 1, 0, -1, 0, 0, 1, 0, -1;
+ c_ << x0_max, -x0_min, x1_max, -x1_min;
+ }
+
+ double f0(Eigen::Ref<const Eigen::Matrix<double, 2, 1>> X) const override {
+ return 0.5 * (X.transpose() * Q_ * X)(0, 0);
+ }
+
+ Eigen::Matrix<double, 2, 1> df0(
+ Eigen::Ref<const Eigen::Matrix<double, 2, 1>> X) const override {
+ return Q_ * X + p_;
+ }
+
+ Eigen::Matrix<double, 2, 2> ddf0(
+ Eigen::Ref<const Eigen::Matrix<double, 2, 1>> /*X*/) const override {
+ return Q_;
+ }
+
+ // Returns the constraints f(X) < 0, and their derivitive.
+ Eigen::Matrix<double, 4, 1> f(
+ Eigen::Ref<const Eigen::Matrix<double, 2, 1>> X) const override {
+ return C_ * X - c_;
+ }
+ Eigen::Matrix<double, 4, 2> df(
+ Eigen::Ref<const Eigen::Matrix<double, 2, 1>> /*X*/) const override {
+ return C_;
+ }
+
+ // Returns the equality constraints of the form A x = b
+ Eigen::Matrix<double, 1, 2> A() const override {
+ return Eigen::Matrix<double, 1, 2>(1, -1);
+ }
+ Eigen::Matrix<double, 1, 1> b() const override {
+ return Eigen::Matrix<double, 1, 1>(0);
+ }
+
+ private:
+ Eigen::Matrix<double, 2, 2> Q_;
+ Eigen::Matrix<double, 2, 1> p_;
+
+ Eigen::Matrix<double, 4, 2> C_;
+ Eigen::Matrix<double, 4, 1> c_;
+};
+
+// Test a constrained quadratic problem where the constraints aren't active.
+TEST(SolverTest, SimpleQP) {
+ Eigen::Matrix<double, 2, 2> Q = Eigen::DiagonalMatrix<double, 2>(1.0, 1.0);
+ Eigen::Matrix<double, 2, 1> p(-4, -6);
+
+ SimpleQP qp(Q, p, 6, -1, 6, -1);
+ Solver<2, 4, 1> s;
+ Eigen::Vector2d result = s.Solve(qp, Eigen::Matrix<double, 2, 1>(0, 0));
+ LOG(INFO) << "Result is " << std::setprecision(12)
+ << result.transpose().format(kHeavyFormat);
+ EXPECT_NEAR((result - Eigen::Vector2d(5.0, 5.0)).norm(), 0.0, 1e-6);
+}
+
+// Test a constrained quadratic problem where the constraints are active.
+TEST(SolverTest, Constrained) {
+ Eigen::Matrix<double, 2, 2> Q = Eigen::DiagonalMatrix<double, 2>(1.0, 2.0);
+ Eigen::Matrix<double, 2, 1> p(-5, -10);
+
+ SimpleQP qp(Q, p, 4, -1, 5, -1);
+ Solver<2, 4, 1> s;
+ Eigen::Vector2d result = s.Solve(qp, Eigen::Matrix<double, 2, 1>(3, 4));
+ LOG(INFO) << "Result is " << std::setprecision(12)
+ << result.transpose().format(kHeavyFormat);
+ EXPECT_NEAR((result - Eigen::Vector2d(4.0, 4.0)).norm(), 0.0, 1e-6);
+}
+
+// Test a constrained quadratic problem where the constraints are active and the
+// initial value is the solution.
+TEST(SolverTest, ConstrainedFromSolution) {
+ Eigen::Matrix<double, 2, 2> Q = Eigen::DiagonalMatrix<double, 2>(1.0, 2.0);
+ Eigen::Matrix<double, 2, 1> p(-5, -10);
+
+ SimpleQP qp(Q, p, 4, -1, 5, -1);
+ Solver<2, 4, 1> s;
+ Eigen::Vector2d result = s.Solve(qp, Eigen::Matrix<double, 2, 1>(4, 4));
+ LOG(INFO) << "Result is " << std::setprecision(12)
+ << result.transpose().format(kHeavyFormat);
+ EXPECT_NEAR((result - Eigen::Vector2d(4.0, 4.0)).norm(), 0.0, 1e-6);
+}
+
+} // namespace testing
+} // namespace solvers
+} // namespace frc971
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 18d54a4..ac9a6e8 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -62,9 +62,8 @@
CompLevel string `gorm:"primaryKey"`
// This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
CompletedAction []byte
- // TODO(phil): Get all the spellings of "timestamp" to be the same.
- TimeStamp int64 `gorm:"primaryKey"`
- CollectedBy string
+ Timestamp int64 `gorm:"primaryKey"`
+ CollectedBy string
}
type NotesData struct {
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 9d1184a..8a4c0bc 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -813,27 +813,27 @@
correct := []Action{
Action{
TeamNumber: "1235", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0000, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0000, CollectedBy: "",
},
Action{
TeamNumber: "1236", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0321, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0321, CollectedBy: "",
},
Action{
TeamNumber: "1237", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0222, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0222, CollectedBy: "",
},
Action{
TeamNumber: "1238", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0110, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0110, CollectedBy: "",
},
Action{
TeamNumber: "1239", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0004, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0004, CollectedBy: "",
},
Action{
TeamNumber: "1233", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0005, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0005, CollectedBy: "",
},
}
diff --git a/scouting/webserver/requests/messages/request_shift_schedule_response.fbs b/scouting/webserver/requests/messages/request_shift_schedule_response.fbs
index 611db49..fe44ef0 100644
--- a/scouting/webserver/requests/messages/request_shift_schedule_response.fbs
+++ b/scouting/webserver/requests/messages/request_shift_schedule_response.fbs
@@ -2,16 +2,16 @@
table MatchAssignment {
match_number:int (id:0);
- R1scouter:string (id:1);
- R2scouter:string (id:2);
- R3scouter:string (id:3);
- B1scouter:string (id:4);
- B2scouter:string (id:5);
- B3scouter:string (id:6);
+ r1_scouter:string (id:1);
+ r2_scouter:string (id:2);
+ r3_scouter:string (id:3);
+ b1_scouter:string (id:4);
+ b2_scouter:string (id:5);
+ b3_scouter:string (id:6);
}
table RequestShiftScheduleResponse {
shift_schedule:[MatchAssignment] (id:0);
}
-root_type RequestShiftScheduleResponse;
\ No newline at end of file
+root_type RequestShiftScheduleResponse;
diff --git a/scouting/webserver/requests/messages/submit_shift_schedule.fbs b/scouting/webserver/requests/messages/submit_shift_schedule.fbs
index 1f1833e..5292544 100644
--- a/scouting/webserver/requests/messages/submit_shift_schedule.fbs
+++ b/scouting/webserver/requests/messages/submit_shift_schedule.fbs
@@ -2,16 +2,16 @@
table MatchAssignment {
match_number:int (id:0);
- R1scouter:string (id:1);
- R2scouter:string (id:2);
- R3scouter:string (id:3);
- B1scouter:string (id:4);
- B2scouter:string (id:5);
- B3scouter:string (id:6);
+ r1_scouter:string (id:1);
+ r2_scouter:string (id:2);
+ r3_scouter:string (id:3);
+ b1_scouter:string (id:4);
+ b2_scouter:string (id:5);
+ b3_scouter:string (id:6);
}
table SubmitShiftSchedule {
shift_schedule:[MatchAssignment] (id:0);
}
-root_type SubmitShiftSchedule;
\ No newline at end of file
+root_type SubmitShiftSchedule;
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 86a09d1..37a6ff2 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -628,12 +628,12 @@
for _, shifts := range shiftData {
response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
MatchNumber: shifts.MatchNumber,
- R1scouter: shifts.R1scouter,
- R2scouter: shifts.R2scouter,
- R3scouter: shifts.R3scouter,
- B1scouter: shifts.B1scouter,
- B2scouter: shifts.B2scouter,
- B3scouter: shifts.B3scouter,
+ R1Scouter: shifts.R1scouter,
+ R2Scouter: shifts.R2scouter,
+ R3Scouter: shifts.R3scouter,
+ B1Scouter: shifts.B1scouter,
+ B2Scouter: shifts.B2scouter,
+ B3Scouter: shifts.B3scouter,
})
}
@@ -668,12 +668,12 @@
request.ShiftSchedule(&match_assignment, i)
current_shift := db.Shift{
MatchNumber: match_assignment.MatchNumber(),
- R1scouter: string(match_assignment.R1scouter()),
- R2scouter: string(match_assignment.R2scouter()),
- R3scouter: string(match_assignment.R3scouter()),
- B1scouter: string(match_assignment.B1scouter()),
- B2scouter: string(match_assignment.B2scouter()),
- B3scouter: string(match_assignment.B3scouter()),
+ R1scouter: string(match_assignment.R1Scouter()),
+ R2scouter: string(match_assignment.R2Scouter()),
+ R3scouter: string(match_assignment.R3Scouter()),
+ B1scouter: string(match_assignment.B1Scouter()),
+ B2scouter: string(match_assignment.B2Scouter()),
+ B3scouter: string(match_assignment.B3Scouter()),
}
err = handler.db.AddToShift(current_shift)
if err != nil {
@@ -834,7 +834,7 @@
CompLevel: string(request.CompLevel()),
//TODO: Serialize CompletedAction
CompletedAction: []byte{},
- TimeStamp: action.Timestamp(),
+ Timestamp: action.Timestamp(),
CollectedBy: username,
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index ae017d2..c1fe860 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -553,21 +553,21 @@
ShiftSchedule: []*request_shift_schedule_response.MatchAssignmentT{
{
MatchNumber: 1,
- R1scouter: "Bob",
- R2scouter: "James",
- R3scouter: "Robert",
- B1scouter: "Alice",
- B2scouter: "Mary",
- B3scouter: "Patricia",
+ R1Scouter: "Bob",
+ R2Scouter: "James",
+ R3Scouter: "Robert",
+ B1Scouter: "Alice",
+ B2Scouter: "Mary",
+ B3Scouter: "Patricia",
},
{
MatchNumber: 2,
- R1scouter: "Liam",
- R2scouter: "Noah",
- R3scouter: "Oliver",
- B1scouter: "Emma",
- B2scouter: "Charlotte",
- B3scouter: "Amelia",
+ R1Scouter: "Liam",
+ R2Scouter: "Noah",
+ R3Scouter: "Oliver",
+ B1Scouter: "Emma",
+ B2Scouter: "Charlotte",
+ B3Scouter: "Amelia",
},
},
}
@@ -592,12 +592,12 @@
builder.Finish((&submit_shift_schedule.SubmitShiftScheduleT{
ShiftSchedule: []*submit_shift_schedule.MatchAssignmentT{
{MatchNumber: 1,
- R1scouter: "Bob",
- R2scouter: "James",
- R3scouter: "Robert",
- B1scouter: "Alice",
- B2scouter: "Mary",
- B3scouter: "Patricia"},
+ R1Scouter: "Bob",
+ R2Scouter: "James",
+ R3Scouter: "Robert",
+ B1Scouter: "Alice",
+ B2Scouter: "Mary",
+ B3Scouter: "Patricia"},
},
}).Pack(builder))
@@ -869,7 +869,7 @@
CompLevel: "qual",
CollectedBy: "debug_cli",
CompletedAction: []byte{},
- TimeStamp: 2400,
+ Timestamp: 2400,
},
{
PreScouting: true,
@@ -879,7 +879,7 @@
CompLevel: "qual",
CollectedBy: "debug_cli",
CompletedAction: []byte{},
- TimeStamp: 1009,
+ Timestamp: 1009,
},
}
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index d6d6799..ada96ff 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -151,15 +151,33 @@
<!-- 'Balancing' during auto. -->
<div *ngIf="autoPhase" class="d-grid gap-2">
<label>
- <input #docked type="checkbox" />
+ <input
+ #docked
+ type="radio"
+ id="option1"
+ name="docked_engaged"
+ value="docked"
+ />
Docked (on the charging station)
</label>
<label>
- <input #engaged type="checkbox" />
- Engaged (level & station lights on)
+ <input
+ #engaged
+ type="radio"
+ id="option2"
+ name="docked_engaged"
+ value="dockedengaged"
+ />
+ Docked & Engaged (level & station lights on)
</label>
<label>
- <input #attempted type="checkbox" />
+ <input
+ #attempted
+ type="radio"
+ id="option3"
+ name="docked_engaged"
+ value="failed"
+ />
Attempted to dock and engage but failed
</label>
<button
@@ -233,15 +251,33 @@
<!-- 'Balancing' during auto. -->
<div *ngIf="autoPhase" class="d-grid gap-1">
<label>
- <input #docked type="checkbox" />
+ <input
+ #docked
+ type="radio"
+ id="option1"
+ name="docked_engaged"
+ value="docked"
+ />
Docked (on the charging station)
</label>
<label>
- <input #engaged type="checkbox" />
- Engaged (level & station lights on)
+ <input
+ #engaged
+ type="radio"
+ id="option2"
+ name="docked_engaged"
+ value="dockedengaged"
+ />
+ Docked & Engaged (level & station lights on)
</label>
<label>
- <input #attempted type="checkbox" />
+ <input
+ #attempted
+ type="radio"
+ id="option3"
+ name="docked_engaged"
+ value="failed"
+ />
Attempted to dock and engage but failed
</label>
<button
@@ -273,15 +309,33 @@
DEAD
</button>
<label>
- <input #docked type="checkbox" />
+ <input
+ #docked
+ type="radio"
+ id="option1"
+ name="docked_engaged"
+ value="docked"
+ />
Docked (on the charging station)
</label>
<label>
- <input #engaged type="checkbox" />
- Engaged (level & station lights on)
+ <input
+ #engaged
+ type="radio"
+ id="option2"
+ name="docked_engaged"
+ value="dockedengaged"
+ />
+ Docked & Engaged (level & station lights on)
</label>
<label>
- <input #attempted type="checkbox" />
+ <input
+ #attempted
+ type="radio"
+ id="option3"
+ name="docked_engaged"
+ value="failed"
+ />
Attempted to dock and engage but failed
</label>
<button
diff --git a/scouting/www/index.html b/scouting/www/index.html
index afc589e..46779cc 100644
--- a/scouting/www/index.html
+++ b/scouting/www/index.html
@@ -12,7 +12,8 @@
/>
<link
rel="stylesheet"
- href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.c"
+ href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
+ integrity="d8824f7067cdfea38afec7e9ffaf072125266824206d69ef1f112d72153a505e"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</head>
diff --git a/y2019/control_loops/drivetrain/localizer.h b/y2019/control_loops/drivetrain/localizer.h
index c55ccc9..818e1b9 100644
--- a/y2019/control_loops/drivetrain/localizer.h
+++ b/y2019/control_loops/drivetrain/localizer.h
@@ -8,6 +8,15 @@
#include "frc971/control_loops/drivetrain/hybrid_ekf.h"
#include "frc971/control_loops/pose.h"
+#if !defined(__clang__) && defined(__GNUC__)
+// GCC miss-detects that when zero is set to true, the member variables could be
+// uninitialized. Rather than spend the CPU to initialize them in addition to
+// the memory for no good reason, tell GCC to stop doing that. Clang appears to
+// get it.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+
namespace y2019 {
namespace control_loops {
@@ -80,37 +89,71 @@
// those mappings for all the remaining corrections.
// As such, we need to store the EKF functions that the remaining targets
// will need in arrays:
- ::aos::SizedArray<::std::function<Output(const State &, const Input &)>,
+ ::aos::SizedArray<HFunction, max_targets_per_frame> h_functions;
+ ::aos::SizedArray<Eigen::Matrix<Scalar, kNOutputs, kNStates>,
max_targets_per_frame>
- h_functions;
- ::aos::SizedArray<::std::function<Eigen::Matrix<Scalar, kNOutputs,
- kNStates>(const State &)>,
- max_targets_per_frame>
- dhdx_functions;
+ dhdx;
make_h_queue_.CorrectKnownHBuilder(
z, nullptr,
ExpectedObservationBuilder(this, camera, targets, &h_functions,
- &dhdx_functions),
+ &dhdx),
R, t);
// Fetch cache:
for (size_t ii = 1; ii < targets.size(); ++ii) {
TargetViewToMatrices(targets[ii], &z, &R);
h_queue_.CorrectKnownH(
z, nullptr,
- ExpectedObservationFunctor(h_functions[ii], dhdx_functions[ii]), R,
+ ExpectedObservationFunctor(h_functions[ii], dhdx[ii]), R,
t);
}
}
private:
+ class HFunction {
+ public:
+ HFunction() : zero_(true) {}
+ HFunction(const Camera *camera, const TargetView &best_view,
+ const TargetView &target_view, TypedLocalizer *localizer)
+ : zero_(false),
+ camera_(camera),
+ best_view_(best_view),
+ target_view_(target_view),
+ localizer_(localizer) {}
+ Output operator()(const State &X, const Input &) {
+ if (zero_) {
+ return Output::Zero();
+ }
+
+ // This function actually handles determining what the Output should
+ // be at a given state, now that we have chosen the target that
+ // we want to match to.
+ *localizer_->robot_pose_->mutable_pos() << X(0, 0), X(1, 0), 0.0;
+ localizer_->robot_pose_->set_theta(X(2, 0));
+ const Pose relative_pose =
+ best_view_.target->pose().Rebase(&camera_->pose());
+ const Scalar heading = relative_pose.heading();
+ const Scalar distance = relative_pose.xy_norm();
+ const Scalar skew =
+ ::aos::math::NormalizeAngle(relative_pose.rel_theta() - heading);
+ return Output(heading, distance, skew);
+ }
+
+ private:
+ bool zero_ = false;
+
+ const Camera *camera_;
+ TargetView best_view_;
+ TargetView target_view_;
+ TypedLocalizer *localizer_;
+ };
+
+ friend class HFunction;
+
class ExpectedObservationFunctor
: public HybridEkf::ExpectedObservationFunctor {
public:
- ExpectedObservationFunctor(
- ::std::function<Output(const State &, const Input &)> h,
- ::std::function<
- Eigen::Matrix<Scalar, kNOutputs, kNStates>(const State &)>
- dhdx)
+ ExpectedObservationFunctor(const HFunction &h,
+ Eigen::Matrix<Scalar, kNOutputs, kNStates> dhdx)
: h_(h), dhdx_(dhdx) {}
Output H(const State &state, const Input &input) final {
@@ -118,14 +161,13 @@
}
virtual Eigen::Matrix<Scalar, kNOutputs, kNStates> DHDX(
- const State &state) final {
- return dhdx_(state);
+ const State &) final {
+ return dhdx_;
}
private:
- ::std::function<Output(const State &, const Input &)> h_;
- ::std::function<Eigen::Matrix<Scalar, kNOutputs, kNStates>(const State &)>
- dhdx_;
+ HFunction h_;
+ Eigen::Matrix<Scalar, kNOutputs, kNStates> dhdx_;
};
class ExpectedObservationBuilder
: public HybridEkf::ExpectedObservationBuilder {
@@ -134,23 +176,20 @@
TypedLocalizer *localizer, const Camera &camera,
const ::aos::SizedArray<TargetView, max_targets_per_frame>
&target_views,
- ::aos::SizedArray<::std::function<Output(const State &, const Input &)>,
- max_targets_per_frame> *h_functions,
- ::aos::SizedArray<::std::function<Eigen::Matrix<
- Scalar, kNOutputs, kNStates>(const State &)>,
- max_targets_per_frame> *dhdx_functions)
+ ::aos::SizedArray<HFunction, max_targets_per_frame> *h_functions,
+ ::aos::SizedArray<Eigen::Matrix<Scalar, kNOutputs, kNStates>,
+ max_targets_per_frame> *dhdx)
: localizer_(localizer),
camera_(camera),
target_views_(target_views),
h_functions_(h_functions),
- dhdx_functions_(dhdx_functions) {}
+ dhdx_(dhdx) {}
virtual ExpectedObservationFunctor *MakeExpectedObservations(
const State &state, const StateSquare &P) {
- ::std::function<Output(const State &, const Input &)> h;
- ::std::function<Eigen::Matrix<Scalar, kNOutputs, kNStates>(const State &)>
- dhdx;
- localizer_->MakeH(camera_, target_views_, h_functions_, dhdx_functions_,
+ HFunction h;
+ Eigen::Matrix<Scalar, kNOutputs, kNStates> dhdx;
+ localizer_->MakeH(camera_, target_views_, h_functions_, dhdx_,
state, P, &h, &dhdx);
functor_.emplace(h, dhdx);
return &functor_.value();
@@ -160,13 +199,12 @@
TypedLocalizer *localizer_;
const Camera &camera_;
const ::aos::SizedArray<TargetView, max_targets_per_frame> &target_views_;
- ::aos::SizedArray<::std::function<Output(const State &, const Input &)>,
- max_targets_per_frame> *h_functions_;
- ::aos::SizedArray<::std::function<Eigen::Matrix<Scalar, kNOutputs,
- kNStates>(const State &)>,
- max_targets_per_frame> *dhdx_functions_;
+ ::aos::SizedArray<HFunction, max_targets_per_frame> *h_functions_;
+ ::aos::SizedArray<Eigen::Matrix<Scalar, kNOutputs, kNStates>,
+ max_targets_per_frame> *dhdx_;
std::optional<ExpectedObservationFunctor> functor_;
};
+
// The threshold to use for completely rejecting potentially bad target
// matches.
// TODO(james): Tune
@@ -220,15 +258,11 @@
void MakeH(
const Camera &camera,
const ::aos::SizedArray<TargetView, max_targets_per_frame> &target_views,
- ::aos::SizedArray<::std::function<Output(const State &, const Input &)>,
- max_targets_per_frame> *h_functions,
- ::aos::SizedArray<::std::function<Eigen::Matrix<Scalar, kNOutputs,
- kNStates>(const State &)>,
- max_targets_per_frame> *dhdx_functions,
- const State &X_hat, const StateSquare &P,
- ::std::function<Output(const State &, const Input &)> *h,
- ::std::function<Eigen::Matrix<Scalar, kNOutputs, kNStates>(const State &)>
- *dhdx) {
+ ::aos::SizedArray<HFunction, max_targets_per_frame> *h_functions,
+ ::aos::SizedArray<Eigen::Matrix<Scalar, kNOutputs, kNStates>,
+ max_targets_per_frame> *dhdx,
+ const State &X_hat, const StateSquare &P, HFunction *h,
+ Eigen::Matrix<Scalar, kNOutputs, kNStates> *current_dhdx) {
// Because we need to match camera targets ("views") to actual field
// targets, and because we want to take advantage of the correlations
// between the targets (i.e., if we see two targets in the image, they
@@ -358,13 +392,11 @@
AOS_LOG(DEBUG, "Unable to identify potential target matches.\n");
// If we can't get a match, provide H = zero, which will make this
// correction step a nop.
- *h = [](const State &, const Input &) { return Output::Zero(); };
- *dhdx = [](const State &) {
- return Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero();
- };
+ *h = HFunction();
+ *current_dhdx = Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero();
for (size_t ii = 0; ii < target_views.size(); ++ii) {
h_functions->push_back(*h);
- dhdx_functions->push_back(*dhdx);
+ dhdx->push_back(*current_dhdx);
}
} else {
// Go through and brute force the issue of what the best combination of
@@ -377,11 +409,9 @@
size_t view_idx = best_frames[ii];
if (view_idx >= camera_views.size()) {
AOS_LOG(ERROR, "Somehow, the view scorer failed.\n");
- h_functions->push_back(
- [](const State &, const Input &) { return Output::Zero(); });
- dhdx_functions->push_back([](const State &) {
- return Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero();
- });
+ h_functions->emplace_back();
+ dhdx->push_back(
+ Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero());
continue;
}
const Eigen::Matrix<Scalar, kNOutputs, kNStates> best_H =
@@ -394,38 +424,18 @@
"Rejecting target at (%f, %f, %f, %f) due to high score.\n",
target_view.reading.heading, target_view.reading.distance,
target_view.reading.skew, target_view.reading.height);
- h_functions->push_back(
- [](const State &, const Input &) { return Output::Zero(); });
- dhdx_functions->push_back([](const State &) {
- return Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero();
- });
+ h_functions->emplace_back();
+ dhdx->push_back(Eigen::Matrix<Scalar, kNOutputs, kNStates>::Zero());
} else {
- h_functions->push_back([this, &camera, best_view, target_view](
- const State &X, const Input &) {
- // This function actually handles determining what the Output should
- // be at a given state, now that we have chosen the target that
- // we want to match to.
- *robot_pose_->mutable_pos() << X(0, 0), X(1, 0), 0.0;
- robot_pose_->set_theta(X(2, 0));
- const Pose relative_pose =
- best_view.target->pose().Rebase(&camera.pose());
- const Scalar heading = relative_pose.heading();
- const Scalar distance = relative_pose.xy_norm();
- const Scalar skew = ::aos::math::NormalizeAngle(
- relative_pose.rel_theta() - heading);
- return Output(heading, distance, skew);
- });
+ h_functions->emplace_back(&camera, best_view, target_view, this);
// TODO(james): Experiment to better understand whether we want to
// recalculate H or not.
- dhdx_functions->push_back(
- [best_H](const Eigen::Matrix<Scalar, kNStates, 1> &) {
- return best_H;
- });
+ dhdx->push_back(best_H);
}
}
*h = h_functions->at(0);
- *dhdx = dhdx_functions->at(0);
+ *current_dhdx = dhdx->at(0);
}
}
@@ -563,7 +573,11 @@
make_h_queue_;
friend class ExpectedObservationBuilder;
-}; // class TypedLocalizer
+};
+
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
} // namespace control_loops
} // namespace y2019
diff --git a/y2020/BUILD b/y2020/BUILD
index 2f6f963..32d392a 100644
--- a/y2020/BUILD
+++ b/y2020/BUILD
@@ -2,6 +2,12 @@
load("//aos:config.bzl", "aos_config")
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("//tools/build_rules:template.bzl", "jinja2_template")
+load("//aos/util:config_validator_macro.bzl", "config_validator_test")
+
+config_validator_test(
+ name = "config_validator_test",
+ config = "//y2020:aos_config",
+)
robot_downloader(
binaries = [
diff --git a/y2020/control_loops/drivetrain/BUILD b/y2020/control_loops/drivetrain/BUILD
index fa153f0..8191404 100644
--- a/y2020/control_loops/drivetrain/BUILD
+++ b/y2020/control_loops/drivetrain/BUILD
@@ -207,6 +207,7 @@
"//aos/events:simulated_event_loop",
"//aos/events/logging:log_reader",
"//aos/events/logging:log_writer",
+ "//aos/util:simulation_logger",
"//frc971/control_loops/drivetrain:drivetrain_lib",
"//frc971/control_loops/drivetrain:trajectory_generator",
"//y2020:constants",
diff --git a/y2020/control_loops/drivetrain/drivetrain_replay.cc b/y2020/control_loops/drivetrain/drivetrain_replay.cc
index 57feabb..8373258 100644
--- a/y2020/control_loops/drivetrain/drivetrain_replay.cc
+++ b/y2020/control_loops/drivetrain/drivetrain_replay.cc
@@ -11,6 +11,7 @@
#include "aos/init.h"
#include "aos/json_to_flatbuffer.h"
#include "aos/network/team_number.h"
+#include "aos/util/simulation_logger.h"
#include "frc971/control_loops/drivetrain/drivetrain.h"
#include "frc971/control_loops/drivetrain/trajectory_generator.h"
#include "gflags/gflags.h"
@@ -26,27 +27,6 @@
DEFINE_int32(team, 971, "Team number to use for logfile replay.");
DEFINE_bool(log_all_nodes, false, "Whether to rerun the logger on every node.");
-class LoggerState {
- public:
- LoggerState(aos::logger::LogReader *reader, const aos::Node *node)
- : event_loop_(
- reader->event_loop_factory()->MakeEventLoop("logger", node)),
- namer_(std::make_unique<aos::logger::MultiNodeFilesLogNamer>(
- absl::StrCat(FLAGS_output_folder, "/", node->name()->string_view(),
- "/"),
- event_loop_.get())),
- logger_(std::make_unique<aos::logger::Logger>(event_loop_.get())) {
- event_loop_->SkipTimingReport();
- event_loop_->SkipAosLog();
- event_loop_->OnRun([this]() { logger_->StartLogging(std::move(namer_)); });
- }
-
- private:
- std::unique_ptr<aos::EventLoop> event_loop_;
- std::unique_ptr<aos::logger::LogNamer> namer_;
- std::unique_ptr<aos::logger::Logger> logger_;
-};
-
// TODO(james): Currently, this replay produces logfiles that can't be read due
// to time estimation issues. Pending the active refactorings of the
// timestamp-related code, fix this.
@@ -82,21 +62,17 @@
"y2020.control_loops.superstructure.Output");
reader.Register();
- std::vector<std::unique_ptr<LoggerState>> loggers;
+ std::vector<std::unique_ptr<aos::util::LoggerState>> loggers;
if (FLAGS_log_all_nodes) {
- for (const aos::Node *node :
- aos::configuration::GetNodes(reader.configuration())) {
- loggers.emplace_back(std::make_unique<LoggerState>(&reader, node));
- }
+ loggers = aos::util::MakeLoggersForAllNodes(reader.event_loop_factory(),
+ FLAGS_output_folder);
} else {
// List of nodes to create loggers for (note: currently just roborio; this
// code was refactored to allow easily adding new loggers to accommodate
// debugging and potential future changes).
const std::vector<std::string> nodes_to_log = {"roborio"};
- for (const std::string &node : nodes_to_log) {
- loggers.emplace_back(std::make_unique<LoggerState>(
- &reader, aos::configuration::GetNode(reader.configuration(), node)));
- }
+ loggers = aos::util::MakeLoggersForNodes(reader.event_loop_factory(),
+ nodes_to_log, FLAGS_output_folder);
}
const aos::Node *node = nullptr;
diff --git a/y2020/control_loops/drivetrain/localizer_test.cc b/y2020/control_loops/drivetrain/localizer_test.cc
index d280523..9e9e7dd 100644
--- a/y2020/control_loops/drivetrain/localizer_test.cc
+++ b/y2020/control_loops/drivetrain/localizer_test.cc
@@ -15,7 +15,6 @@
DEFINE_string(output_file, "",
"If set, logs all channels to the provided logfile.");
-DECLARE_bool(die_on_malloc);
// This file tests that the full 2020 localizer behaves sanely.
@@ -147,7 +146,6 @@
CHECK_EQ(aos::configuration::GetNodeIndex(configuration(), pi1_), 1);
set_team_id(frc971::control_loops::testing::kTeamNumber);
set_battery_voltage(12.0);
- FLAGS_die_on_malloc = true;
if (!FLAGS_output_file.empty()) {
logger_event_loop_ = MakeEventLoop("logger", roborio_);
diff --git a/y2020/y2020_pi_template.json b/y2020/y2020_pi_template.json
index cf54c23..c609632 100644
--- a/y2020/y2020_pi_template.json
+++ b/y2020/y2020_pi_template.json
@@ -92,12 +92,19 @@
"time_to_live": 5000000,
"timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
"timestamp_logger_nodes": [
- "roborio"
+ "pi{{ NUM }}"
]
}
]
},
{
+ "name": "/pi{{ NUM }}/aos/remote_timestamps/roborio/pi{{ NUM }}/aos/aos-message_bridge-Timestamp",
+ "type": "aos.message_bridge.RemoteMessage",
+ "frequency": 20,
+ "source_node": "pi{{ NUM }}",
+ "max_size": 208
+ },
+ {
"name": "/pi{{ NUM }}/camera",
"type": "frc971.vision.CameraImage",
"source_node": "pi{{ NUM }}",
diff --git a/y2022/BUILD b/y2022/BUILD
index 1a04c2e..f8bf59f 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -2,6 +2,12 @@
load("//aos:config.bzl", "aos_config")
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("//tools/build_rules:template.bzl", "jinja2_template")
+load("//aos/util:config_validator_macro.bzl", "config_validator_test")
+
+config_validator_test(
+ name = "config_validator_test",
+ config = "//y2022:aos_config",
+)
robot_downloader(
binaries = [
diff --git a/y2022/localizer/BUILD b/y2022/localizer/BUILD
index dd0fb67..7f076e8 100644
--- a/y2022/localizer/BUILD
+++ b/y2022/localizer/BUILD
@@ -150,6 +150,7 @@
"//aos/events:simulated_event_loop",
"//aos/events/logging:log_reader",
"//aos/events/logging:log_writer",
+ "//aos/util:simulation_logger",
"//y2022/control_loops/drivetrain:drivetrain_base",
],
)
diff --git a/y2022/localizer/localizer_replay.cc b/y2022/localizer/localizer_replay.cc
index 0c09535..6dcbb1e 100644
--- a/y2022/localizer/localizer_replay.cc
+++ b/y2022/localizer/localizer_replay.cc
@@ -1,6 +1,7 @@
#include "aos/configuration.h"
#include "aos/events/logging/log_reader.h"
#include "aos/events/logging/log_writer.h"
+#include "aos/util/simulation_logger.h"
#include "aos/events/simulated_event_loop.h"
#include "aos/init.h"
#include "aos/json_to_flatbuffer.h"
@@ -16,27 +17,6 @@
DEFINE_string(output_folder, "/tmp/replayed",
"Name of the folder to write replayed logs to.");
-class LoggerState {
- public:
- LoggerState(aos::logger::LogReader *reader, const aos::Node *node)
- : event_loop_(
- reader->event_loop_factory()->MakeEventLoop("logger", node)),
- namer_(std::make_unique<aos::logger::MultiNodeFilesLogNamer>(
- absl::StrCat(FLAGS_output_folder, "/", node->name()->string_view(),
- "/"),
- event_loop_.get())),
- logger_(std::make_unique<aos::logger::Logger>(event_loop_.get())) {
- event_loop_->SkipTimingReport();
- event_loop_->SkipAosLog();
- event_loop_->OnRun([this]() { logger_->StartLogging(std::move(namer_)); });
- }
-
- private:
- std::unique_ptr<aos::EventLoop> event_loop_;
- std::unique_ptr<aos::logger::LogNamer> namer_;
- std::unique_ptr<aos::logger::Logger> logger_;
-};
-
// TODO(james): Currently, this replay produces logfiles that can't be read due
// to time estimation issues. Pending the active refactorings of the
// timestamp-related code, fix this.
@@ -71,15 +51,13 @@
reader.Register(factory.get());
- std::vector<std::unique_ptr<LoggerState>> loggers;
// List of nodes to create loggers for (note: currently just roborio; this
// code was refactored to allow easily adding new loggers to accommodate
// debugging and potential future changes).
const std::vector<std::string> nodes_to_log = {"imu"};
- for (const std::string &node : nodes_to_log) {
- loggers.emplace_back(std::make_unique<LoggerState>(
- &reader, aos::configuration::GetNode(reader.configuration(), node)));
- }
+ std::vector<std::unique_ptr<aos::util::LoggerState>> loggers =
+ aos::util::MakeLoggersForNodes(reader.event_loop_factory(), nodes_to_log,
+ FLAGS_output_folder);
const aos::Node *node = nullptr;
if (aos::configuration::MultiNode(reader.configuration())) {
diff --git a/y2022/y2022_logger.json b/y2022/y2022_logger.json
index 01800bf..65a6e98 100644
--- a/y2022/y2022_logger.json
+++ b/y2022/y2022_logger.json
@@ -35,7 +35,7 @@
"priority": 2,
"timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
"timestamp_logger_nodes": [
- "roborio"
+ "logger"
],
"time_to_live": 5000000
}
@@ -46,7 +46,7 @@
"type": "aos.message_bridge.RemoteMessage",
"source_node": "logger",
"logger": "NOT_LOGGED",
- "frequency": 20,
+ "frequency": 200,
"num_senders": 2,
"max_size": 200
},
diff --git a/y2022/y2022_pi_template.json b/y2022/y2022_pi_template.json
index 4d3c427..cb90fee 100644
--- a/y2022/y2022_pi_template.json
+++ b/y2022/y2022_pi_template.json
@@ -104,7 +104,7 @@
"time_to_live": 5000000,
"timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
"timestamp_logger_nodes": [
- "roborio"
+ "pi{{ NUM }}"
]
},
{
@@ -113,7 +113,7 @@
"time_to_live": 5000000,
"timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
"timestamp_logger_nodes": [
- "imu"
+ "pi{{ NUM }}"
]
}
]
diff --git a/y2022/y2022_roborio.json b/y2022/y2022_roborio.json
index 068c543..d046568 100644
--- a/y2022/y2022_roborio.json
+++ b/y2022/y2022_roborio.json
@@ -459,7 +459,7 @@
"priority": 5,
"timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
"timestamp_logger_nodes": [
- "imu"
+ "roborio"
],
"time_to_live": 0
}
diff --git a/y2023/BUILD b/y2023/BUILD
index 6be5cac..667b59d 100644
--- a/y2023/BUILD
+++ b/y2023/BUILD
@@ -2,9 +2,9 @@
load("//aos:config.bzl", "aos_config")
load("//tools/build_rules:template.bzl", "jinja2_template")
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
-load("//aos/util:config_validator_macro.bzl", "config_validator_rule")
+load("//aos/util:config_validator_macro.bzl", "config_validator_test")
-config_validator_rule(
+config_validator_test(
name = "config_validator_test",
config = "//y2023:aos_config",
)
diff --git a/y2023/autonomous/splines/spline.1.json b/y2023/autonomous/splines/spline.1.json
index a98ac6e..5240ad8 100644
--- a/y2023/autonomous/splines/spline.1.json
+++ b/y2023/autonomous/splines/spline.1.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [1.609310857796625, 2.5819120488556946, 3.506443404506549, 5.556354709601551, 5.990235974918718, 6.419541332061575], "spline_y": [0.6043502546533336, 0.69141924611354, 1.0213742193777775, 0.22424951902207307, 0.24557526406496255, 0.24557526406496255], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [1.609310857796625, 2.5819120488556946, 3.506443404506549, 5.555694235956169, 5.989575501273337, 6.418880858416194], "spline_y": [0.6043502546533336, 0.69141924611354, 1.0213742193777775, 0.38712949808092717, 0.40845524312381665, 0.40845524312381665], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline.2.json b/y2023/autonomous/splines/spline.2.json
index 641b973..297966c 100644
--- a/y2023/autonomous/splines/spline.2.json
+++ b/y2023/autonomous/splines/spline.2.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [6.419541332061575, 6.028411344095072, 5.2762663069267655, 2.8053451665928835, 2.37026593061867, 1.5260719060059573], "spline_y": [0.24557526406496255, 0.24557526406496255, 0.3018576793840364, 1.3287637699876067, -0.026954142728124464, -0.5547144640218522], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [6.418880858416194, 6.02775087044969, 5.275605833281384, 2.8053451665928835, 2.37026593061867, 1.5260719060059573], "spline_y": [0.40845524312381665, 0.40845524312381665, 0.4647376584428905, 1.3287637699876067, -0.026954142728124464, -0.5547144640218522], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline.3.json b/y2023/autonomous/splines/spline.3.json
index f5651e7..6ac5412 100644
--- a/y2023/autonomous/splines/spline.3.json
+++ b/y2023/autonomous/splines/spline.3.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [1.5260719060059573, 3.487962685989344, 3.140736946266447, 5.361661826393136, 4.912898328525625, 6.376744631564399], "spline_y": [-0.5547144640218522, 0.6717904367469538, 0.6138773092943139, 0.6864204635819386, 0.27660573272799804, 0.2788534917250134], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.6}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.65, "start_distance": 4.1, "end_distance": 10.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [1.5260719060059573, 3.487962685989344, 3.140736946266447, 5.366541573420288, 4.917778075552777, 6.381624378591551], "spline_y": [-0.5547144640218522, 0.6717904367469538, 0.6138773092943139, 0.7392105412041843, 0.3293958103502437, 0.331643569347259], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.6}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.65, "start_distance": 4.1, "end_distance": 10.0}]}
\ No newline at end of file
diff --git a/y2023/constants.cc b/y2023/constants.cc
index a6c0396..4ef5670 100644
--- a/y2023/constants.cc
+++ b/y2023/constants.cc
@@ -101,7 +101,7 @@
arm_distal->zeroing.one_revolution_distance =
M_PI * 2.0 * constants::Values::kDistalEncoderRatio();
- roll_joint->zeroing.measured_absolute_position = 0.419144048980465;
+ roll_joint->zeroing.measured_absolute_position = 0.424187348328397;
roll_joint->potentiometer_offset =
-(3.87038557084874 - 0.0241774522172967 + 0.0711345168020632 -
0.866186131631967 - 0.0256788357596952 + 0.18101759154572017 -
@@ -109,10 +109,11 @@
0.5935210745062 + 0.166256655718334 - 0.12591438680483 +
0.11972765117321 - 0.318724743041507) +
0.0201047336425017 - 1.0173426655158 - 0.186085272847293 -
- 0.0317706563397807 - 2.6357823523782 + 0.871932806570122;
+ 0.0317706563397807 - 2.6357823523782 + 0.871932806570122 +
+ 1.09682107821155;
wrist->subsystem_params.zeroing_constants.measured_absolute_position =
- 0.868820879549023;
+ 0.886183343417664;
break;
@@ -131,7 +132,7 @@
arm_distal->zeroing.one_revolution_distance =
M_PI * 2.0 * constants::Values::kDistalEncoderRatio() *
- //3.11964893168338 / 3.148;
+ // 3.11964893168338 / 3.148;
(3.12725165289659 + 0.002) / 3.1485739705977704;
roll_joint->zeroing.measured_absolute_position = 1.79390317510529;
diff --git a/y2023/control_loops/python/graph_paths.py b/y2023/control_loops/python/graph_paths.py
index 4b8de27..eb92ec8 100644
--- a/y2023/control_loops/python/graph_paths.py
+++ b/y2023/control_loops/python/graph_paths.py
@@ -333,7 +333,7 @@
))
points['HPPickupBackConeUp'] = to_theta_with_circular_index_and_roll(
- -1.1200539, 1.325, np.pi / 2.0, circular_index=0)
+ -1.1200539, 1.335, np.pi / 2.0, circular_index=0)
named_segments.append(
ThetaSplineSegment(
diff --git a/y2023/joystick_reader.cc b/y2023/joystick_reader.cc
index a8f540f..874e691 100644
--- a/y2023/joystick_reader.cc
+++ b/y2023/joystick_reader.cc
@@ -71,7 +71,7 @@
const ButtonLocation kSuck(2, 3);
const ButtonLocation kBack(2, 4);
-const ButtonLocation kStayIn(2, 12);
+const ButtonLocation kStayIn(3, 2);
const ButtonLocation kConeDownTip(1, 9);
const ButtonLocation kConeDownBase(1, 10);
diff --git a/y2023/localizer/BUILD b/y2023/localizer/BUILD
index 3ef024c..f9d0d28 100644
--- a/y2023/localizer/BUILD
+++ b/y2023/localizer/BUILD
@@ -220,6 +220,7 @@
"//aos/events:simulated_event_loop",
"//aos/events/logging:log_reader",
"//aos/events/logging:log_writer",
+ "//aos/util:simulation_logger",
"//y2023/control_loops/drivetrain:drivetrain_base",
],
)
diff --git a/y2023/localizer/localizer_replay.cc b/y2023/localizer/localizer_replay.cc
index c74b708..27f0d18 100644
--- a/y2023/localizer/localizer_replay.cc
+++ b/y2023/localizer/localizer_replay.cc
@@ -5,9 +5,10 @@
#include "aos/init.h"
#include "aos/json_to_flatbuffer.h"
#include "aos/network/team_number.h"
-#include "y2023/localizer/localizer.h"
+#include "aos/util/simulation_logger.h"
#include "gflags/gflags.h"
#include "y2023/control_loops/drivetrain/drivetrain_base.h"
+#include "y2023/localizer/localizer.h"
DEFINE_string(config, "y2023/aos_config.json",
"Name of the config file to replay using.");
@@ -15,27 +16,6 @@
DEFINE_string(output_folder, "/tmp/replayed",
"Name of the folder to write replayed logs to.");
-class LoggerState {
- public:
- LoggerState(aos::logger::LogReader *reader, const aos::Node *node)
- : event_loop_(
- reader->event_loop_factory()->MakeEventLoop("logger", node)),
- namer_(std::make_unique<aos::logger::MultiNodeFilesLogNamer>(
- absl::StrCat(FLAGS_output_folder, "/", node->name()->string_view(),
- "/"),
- event_loop_.get())),
- logger_(std::make_unique<aos::logger::Logger>(event_loop_.get())) {
- event_loop_->SkipTimingReport();
- event_loop_->SkipAosLog();
- event_loop_->OnRun([this]() { logger_->StartLogging(std::move(namer_)); });
- }
-
- private:
- std::unique_ptr<aos::EventLoop> event_loop_;
- std::unique_ptr<aos::logger::LogNamer> namer_;
- std::unique_ptr<aos::logger::Logger> logger_;
-};
-
int main(int argc, char **argv) {
aos::InitGoogle(&argc, &argv);
@@ -69,15 +49,13 @@
reader.Register(factory.get());
- std::vector<std::unique_ptr<LoggerState>> loggers;
// List of nodes to create loggers for (note: currently just roborio; this
// code was refactored to allow easily adding new loggers to accommodate
// debugging and potential future changes).
const std::vector<std::string> nodes_to_log = {"imu"};
- for (const std::string &node : nodes_to_log) {
- loggers.emplace_back(std::make_unique<LoggerState>(
- &reader, aos::configuration::GetNode(reader.configuration(), node)));
- }
+ std::vector<std::unique_ptr<aos::util::LoggerState>> loggers =
+ aos::util::MakeLoggersForNodes(reader.event_loop_factory(), nodes_to_log,
+ FLAGS_output_folder);
const aos::Node *node = nullptr;
if (aos::configuration::MultiNode(reader.configuration())) {
diff --git a/y2023/localizer/localizer_test.cc b/y2023/localizer/localizer_test.cc
index e8b7d56..2cb5756 100644
--- a/y2023/localizer/localizer_test.cc
+++ b/y2023/localizer/localizer_test.cc
@@ -14,7 +14,6 @@
DEFINE_string(output_folder, "",
"If set, logs all channels to the provided logfile.");
-DECLARE_bool(die_on_malloc);
DECLARE_double(max_distance_to_target);
namespace y2023::localizer::testing {
@@ -75,7 +74,6 @@
status_fetcher_(
imu_test_event_loop_->MakeFetcher<Status>("/localizer")) {
FLAGS_max_distance_to_target = 100.0;
- FLAGS_die_on_malloc = true;
{
aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() {
{
diff --git a/y2023/vision/camera_reader.cc b/y2023/vision/camera_reader.cc
index b526086..3001c04 100644
--- a/y2023/vision/camera_reader.cc
+++ b/y2023/vision/camera_reader.cc
@@ -12,7 +12,7 @@
DEFINE_string(config, "aos_config.json", "Path to the config file to use.");
DEFINE_bool(lowlight_camera, true, "Switch to use imx462 image sensor.");
-DEFINE_int32(gain, 200, "analogue_gain");
+DEFINE_int32(gain, 150, "analogue_gain");
DEFINE_double(red, 1.252, "Red gain");
DEFINE_double(green, 1, "Green gain");
diff --git a/y2023/vision/maps/johnson.json b/y2023/vision/maps/johnson.json
new file mode 100644
index 0000000..b0aef36
--- /dev/null
+++ b/y2023/vision/maps/johnson.json
@@ -0,0 +1,152 @@
+{
+ "target_poses": [
+{
+ "id": 1,
+ "position": {
+ "x": 7.243997790506,
+ "y": -2.933106182702,
+ "z": 0.462948446708
+ },
+ "orientation": {
+ "w": 0.50135307601,
+ "x": -0.503852469447,
+ "y": 0.496681622743,
+ "z": -0.498081467069
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 2,
+ "position": {
+ "x": 7.233087584818,
+ "y": -1.266935526895,
+ "z": 0.458422750798
+ },
+ "orientation": {
+ "w": 0.503123586992,
+ "x": -0.498312711799,
+ "y": 0.4917162543,
+ "z": -0.506721050214
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 3,
+ "position": {
+ "x": 7.226895621731,
+ "y": 0.399357924781,
+ "z": 0.451036794292
+ },
+ "orientation": {
+ "w": 0.509600379834,
+ "x": -0.495432434727,
+ "y": 0.487971656326,
+ "z": -0.506693021577
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 4,
+ "position": {
+ "x": 7.909,
+ "y": 2.74,
+ "z": 0.695
+ },
+ "orientation": {
+ "w": -0.5,
+ "x": 0.5,
+ "y": -0.5,
+ "z": 0.5
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 5,
+ "position": {
+ "x": -7.908,
+ "y": 2.74,
+ "z": 0.695
+ },
+ "orientation": {
+ "w": 0.5,
+ "x": -0.5,
+ "y": -0.5,
+ "z": 0.5
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 6,
+ "position": {
+ "x": -7.239001740201,
+ "y": 0.422111705663,
+ "z": 0.464955016203
+ },
+ "orientation": {
+ "w": 0.498420780853,
+ "x": -0.499263204799,
+ "y": -0.500023026083,
+ "z": 0.502284730939
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 7,
+ "position": {
+ "x": -7.241093280921,
+ "y": -1.270115952319,
+ "z": 0.463955457197
+ },
+ "orientation": {
+ "w": -0.499112246574,
+ "x": 0.499432412584,
+ "y": 0.501866879868,
+ "z": -0.49958369216
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ },
+ {
+ "id": 8,
+ "position": {
+ "x": -7.240827110532,
+ "y": -2.95806661736,
+ "z": 0.458620705581
+ },
+ "orientation": {
+ "w": -0.499747319421,
+ "x": 0.499461045816,
+ "y": 0.501623538198,
+ "z": -0.499164333279
+ },
+ "confidence": 0.0,
+ "pose_error": 0.0,
+ "distortion_factor": 0.0,
+ "pose_error_ratio": 0.0
+ }
+ ],
+ "field_name": "johnson",
+ "monotonic_timestamp_ns": 0,
+ "rejections": 0
+}
+
diff --git a/y2023/y2023_roborio.json b/y2023/y2023_roborio.json
index 13e48dc..172d11c 100644
--- a/y2023/y2023_roborio.json
+++ b/y2023/y2023_roborio.json
@@ -18,15 +18,6 @@
]
},
{
- "name": "/roborio/aos/remote_timestamps/imu/roborio/aos/aos-JoystickState",
- "type": "aos.message_bridge.RemoteMessage",
- "source_node": "roborio",
- "logger": "NOT_LOGGED",
- "frequency": 300,
- "num_senders": 2,
- "max_size": 200
- },
- {
"name": "/roborio/aos",
"type": "aos.RobotState",
"source_node": "roborio",
@@ -234,15 +225,6 @@
]
},
{
- "name": "/roborio/aos/remote_timestamps/imu/drivetrain/frc971-control_loops-drivetrain-Output",
- "type": "aos.message_bridge.RemoteMessage",
- "source_node": "roborio",
- "logger": "NOT_LOGGED",
- "frequency": 400,
- "num_senders": 2,
- "max_size": 200
- },
- {
"name": "/drivetrain",
"type": "frc971.control_loops.drivetrain.Status",
"source_node": "roborio",
@@ -339,9 +321,6 @@
{
"name": "drivetrain",
"executable_name": "drivetrain",
- "args": [
- "--die_on_malloc"
- ],
"nodes": [
"roborio"
]
@@ -349,9 +328,6 @@
{
"name": "trajectory_generator",
"executable_name": "trajectory_generator",
- "args": [
- "--die_on_malloc"
- ],
"nodes": [
"roborio"
]
@@ -359,9 +335,6 @@
{
"name": "superstructure",
"executable_name": "superstructure",
- "args": [
- "--die_on_malloc"
- ],
"nodes": [
"roborio"
]
@@ -389,6 +362,9 @@
{
"name": "wpilib_interface",
"executable_name": "wpilib_interface",
+ "args": [
+ "--nodie_on_malloc"
+ ],
"nodes": [
"roborio"
]