#Centipede Atomically write CSV lines to fuzzing-stats*.csv reports Before, each field sample would be immediately appended to the report. That created problems: 1. External readers had to be able to tolerate incomplete last lines in reports. 1. If the process was killed in the middle of a line and restarted in the same workdir, the first new line would be appended to the unfinished line. The resulting line was malformed. Also, with network file systems, writing a line at a time means fewer write requests sent to the file server (however, the benefit is likely minor, given the size and frequency of writes here). PiperOrigin-RevId: 617693512
diff --git a/centipede/BUILD b/centipede/BUILD index 9ee4f7a..81a80a6 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -378,6 +378,7 @@ ":logging", ":remote_file", ":workdir", + "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:btree", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/log",
diff --git a/centipede/stats.cc b/centipede/stats.cc index ee5b771..db93370 100644 --- a/centipede/stats.cc +++ b/centipede/stats.cc
@@ -172,7 +172,7 @@ StatsCsvFileAppender::~StatsCsvFileAppender() { for (const auto &[group_name, file] : files_) { - RemoteFileClose(file); + RemoteFileClose(file.file); } } @@ -194,8 +194,8 @@ } void StatsCsvFileAppender::SetCurrGroup(const Environment &master_env) { - RemoteFile *&file = files_[master_env.experiment_name]; - if (file == nullptr) { + BufferedRemoteFile &file = files_[master_env.experiment_name]; + if (file.file == nullptr) { const std::string filename = WorkDir{master_env}.FuzzingStatsPath(master_env.experiment_name); // If a non-empty file already exists and has the same CVS header, then @@ -215,11 +215,11 @@ RemoteFileSetContents(GetBackupFilename(filename), contents); } } - file = RemoteFileOpen(filename, append ? "a" : "w"); - CHECK(file != nullptr) << VV(filename); - if (!append) RemoteFileAppend(file, csv_header_); + file.file = RemoteFileOpen(filename, append ? "a" : "w"); + CHECK(file.file != nullptr) << VV(filename); + if (!append) RemoteFileAppend(file.file, csv_header_); } - curr_file_ = file; + curr_file_ = &file; } void StatsCsvFileAppender::SetCurrField(const Stats::FieldInfo &field_info) { @@ -238,7 +238,8 @@ } double avg = !values.empty() ? (1.0 * sum / values.size()) : 0; - std::string values_str; + CHECK(curr_file_ != nullptr); + std::string &values_str = curr_file_->buffer; if (curr_field_info_.traits & TraitBits::kMin) absl::StrAppendFormat(&values_str, "%" PRIu64 ",", min); if (curr_field_info_.traits & TraitBits::kMax) @@ -247,8 +248,6 @@ absl::StrAppendFormat(&values_str, "%.1lf,", avg); if (curr_field_info_.traits & TraitBits::kSum) absl::StrAppendFormat(&values_str, "%" PRIu64 ",", sum); - - RemoteFileAppend(curr_file_, values_str); } void StatsCsvFileAppender::ReportFlags(const GroupToFlags &group_to_flags) { @@ -257,8 +256,9 @@ } void StatsCsvFileAppender::DoneFieldSamplesBatch() { - for (const auto &[group_name, file] : files_) { - RemoteFileAppend(file, "\n"); + for (auto &&[group_name, file] : files_) { + RemoteFileAppend(file.file, absl::StrCat(file.buffer, "\n")); + file.buffer.clear(); } }
diff --git a/centipede/stats.h b/centipede/stats.h index faf14a6..cd870f7 100644 --- a/centipede/stats.h +++ b/centipede/stats.h
@@ -15,6 +15,7 @@ #ifndef THIRD_PARTY_CENTIPEDE_STATS_H_ #define THIRD_PARTY_CENTIPEDE_STATS_H_ +#include <atomic> #include <cstdint> #include <cstdlib> #include <initializer_list> @@ -24,6 +25,7 @@ #include <string_view> #include <vector> +#include "absl/base/nullability.h" #include "absl/container/btree_map.h" #include "absl/container/flat_hash_map.h" #include "absl/types/span.h" @@ -379,6 +381,11 @@ ~StatsCsvFileAppender() override; private: + struct BufferedRemoteFile { + RemoteFile *file = nullptr; + std::string buffer; + }; + void PreAnnounceFields( std::initializer_list<Stats::FieldInfo> fields) override; void SetCurrGroup(const Environment &master_env) override; @@ -392,8 +399,8 @@ virtual std::string GetBackupFilename(const std::string &filename) const; std::string csv_header_; - absl::flat_hash_map<std::string /*group_name*/, RemoteFile *> files_; - RemoteFile *curr_file_; + absl::flat_hash_map<std::string /*group_name*/, BufferedRemoteFile> files_; + absl::Nullable<BufferedRemoteFile *> curr_file_ = nullptr; Stats::FieldInfo curr_field_info_; };