| // Copyright 2022 The Centipede Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "./centipede/stats.h" |
| |
| #include <atomic> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> // NOLINT |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/log/log_entry.h" |
| #include "absl/log/log_sink.h" |
| #include "absl/log/log_sink_registry.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/civil_time.h" |
| #include "absl/time/time.h" |
| #include "./centipede/environment.h" |
| #include "./centipede/util.h" |
| #include "./common/logging.h" // IWYU pragma: keep |
| #include "./common/test_util.h" |
| |
| namespace fuzztest::internal { |
| namespace { |
| |
| using ::testing::ElementsAreArray; |
| |
| namespace { |
| |
| class LogCapture : public absl::LogSink { |
| public: |
| LogCapture() { absl::AddLogSink(this); } |
| ~LogCapture() override { absl::RemoveLogSink(this); } |
| void Send(const absl::LogEntry &entry) override { |
| captured_log_.emplace_back(entry.text_message()); |
| } |
| std::vector<std::string> CapturedLogLines() const { |
| // Join->Split normalizes multi-line messages. |
| return absl::StrSplit(absl::StrJoin(captured_log_, "\n"), '\n'); |
| } |
| |
| private: |
| std::vector<std::string> captured_log_; |
| }; |
| |
| uint64_t CivilTimeToUnixMicros( // |
| int64_t y, int64_t m, int64_t d, int64_t hh, int64_t mm, int64_t ss) { |
| return absl::ToUnixMicros(absl::FromCivil( |
| absl::CivilSecond{y, m, d, hh, mm, ss}, absl::LocalTimeZone())); |
| } |
| |
| Environment CreateEnv(std::string_view workdir, |
| std::string_view experiment_name = "", |
| std::string_view experiment_flags = "") { |
| Environment env; |
| env.workdir = std::string(workdir); |
| env.experiment_name = std::string(experiment_name); |
| env.experiment_flags = std::string(experiment_flags); |
| return env; |
| } |
| |
| } // namespace |
| |
| TEST(Stats, PrintStatsToLog) { |
| std::vector<std::atomic<Stats>> stats_vec(4); |
| for (uint64_t i = 0; i < stats_vec.size(); ++i) { |
| const uint64_t j = i + 1; |
| |
| CovStats cov_stats = {/*num_covered_pcs=*/21 * j, |
| /*num_8bit_counter_features=*/22 * j, |
| /*num_data_flow_features=*/23 * j, |
| /*num_cmp_features=*/24 * j, |
| /*num_call_stack_features=*/25 * j, |
| /*num_bounded_path_features=*/26 * j, |
| /*num_pc_pair_features=*/27 * j, |
| /*num_user_features=*/28 * j}; |
| // NOTE: These two don't add up to `num_user_features`, but that's |
| // ok for the purposes of this test. |
| cov_stats.num_user0_features = 28 * j + 1; |
| cov_stats.num_user10_features = 28 * j + 2; |
| cov_stats.num_unknown_features = 29 * j; |
| cov_stats.num_funcs_in_frontier = 31 * j; |
| |
| stats_vec[i].store(Stats{ |
| StatsMeta{/*timestamp_unix_micros=*/CivilTimeToUnixMicros(1970, 1, 1, 0, |
| 0, i)}, |
| ExecStats{ |
| /*fuzz_time_sec=*/10 * j, |
| /*num_executions=*/12 * j, |
| /*num_target_crashes=*/13 * j, |
| }, |
| cov_stats, |
| CorpusStats{ |
| /*active_corpus_size=*/101 * j, |
| /*total_corpus_size=*/102 * j, |
| /*max_corpus_element_size=*/103 * j, |
| /*avg_corpus_element_size=*/104 * j, |
| }, |
| RusageStats{ |
| /*engine_rusage_avg_millicores=*/201 * j, |
| /*engine_rusage_cpu_percent=*/202 * j, |
| /*engine_rusage_rss_mb=*/203 * j, |
| /*engine_rusage_vsize_mb=*/204 * j, |
| }, |
| }); |
| } |
| |
| const std::vector<Environment> env_vec = { |
| CreateEnv( |
| /*workdir=*/"", /*experiment_name=*/"Experiment A", |
| /*experiment_flags=*/"AAA"), |
| CreateEnv(/*workdir=*/"", /*experiment_name=*/"Experiment B", |
| /*experiment_flags=*/"BBB"), |
| CreateEnv( |
| /*workdir=*/"", /*experiment_name=*/"Experiment A", |
| /*experiment_flags=*/"AAA"), |
| CreateEnv(/*workdir=*/"", /*experiment_name=*/"Experiment B", |
| /*experiment_flags=*/"BBB")}; |
| |
| StatsLogger stats_logger{stats_vec, env_vec}; |
| |
| { |
| const std::vector<std::string_view> kExpectedLogLines = { |
| "Current stats:", |
| "Coverage:", |
| " Experiment A: min:\t21\tmax:\t63\tavg:\t42.0\t--\t21\t63", |
| " Experiment B: min:\t42\tmax:\t84\tavg:\t63.0\t--\t42\t84", |
| "Number of executions:", |
| " Experiment A: min:\t12\tmax:\t36\tavg:\t24.0\t--\t12\t36", |
| " Experiment B: min:\t24\tmax:\t48\tavg:\t36.0\t--\t24\t48", |
| "Active corpus size:", |
| " Experiment A: min:\t101\tmax:\t303\tavg:\t202.0\t--\t101\t303", |
| " Experiment B: min:\t202\tmax:\t404\tavg:\t303.0\t--\t202\t404", |
| "Max element size:", |
| " Experiment A: min:\t103\tmax:\t309\tavg:\t206.0\t--\t103\t309", |
| " Experiment B: min:\t206\tmax:\t412\tavg:\t309.0\t--\t206\t412", |
| "Avg element size:", |
| " Experiment A: min:\t104\tmax:\t312\tavg:\t208.0\t--\t104\t312", |
| " Experiment B: min:\t208\tmax:\t416\tavg:\t312.0\t--\t208\t416", |
| "Fuzz time (sec):", |
| " Experiment A: min:\t10\tmax:\t30\tavg:\t20.0\t--\t10\t30", |
| " Experiment B: min:\t20\tmax:\t40\tavg:\t30.0\t--\t20\t40", |
| "Num proxy crashes:", |
| " Experiment A: min:\t13\tmax:\t39\tsum:\t52\t--\t13\t39", |
| " Experiment B: min:\t26\tmax:\t52\tsum:\t78\t--\t26\t52", |
| "Total corpus size:", |
| " Experiment A: min:\t102\tmax:\t306\tsum:\t408\t--\t102\t306", |
| " Experiment B: min:\t204\tmax:\t408\tsum:\t612\t--\t204\t408", |
| "Num 8-bit counter features:", |
| " Experiment A: min:\t22\tmax:\t66\tavg:\t44.0\t--\t22\t66", |
| " Experiment B: min:\t44\tmax:\t88\tavg:\t66.0\t--\t44\t88", |
| "Num data flow features:", |
| " Experiment A: min:\t23\tmax:\t69\tavg:\t46.0\t--\t23\t69", |
| " Experiment B: min:\t46\tmax:\t92\tavg:\t69.0\t--\t46\t92", |
| "Num cmp features:", |
| " Experiment A: min:\t24\tmax:\t72\tavg:\t48.0\t--\t24\t72", |
| " Experiment B: min:\t48\tmax:\t96\tavg:\t72.0\t--\t48\t96", |
| "Num call stack features:", |
| " Experiment A: min:\t25\tmax:\t75\tavg:\t50.0\t--\t25\t75", |
| " Experiment B: min:\t50\tmax:\t100\tavg:\t75.0\t--\t50\t100", |
| "Num bounded path features:", |
| " Experiment A: min:\t26\tmax:\t78\tavg:\t52.0\t--\t26\t78", |
| " Experiment B: min:\t52\tmax:\t104\tavg:\t78.0\t--\t52\t104", |
| "Num PC pair features:", |
| " Experiment A: min:\t27\tmax:\t81\tavg:\t54.0\t--\t27\t81", |
| " Experiment B: min:\t54\tmax:\t108\tavg:\t81.0\t--\t54\t108", |
| "Num user features:", |
| " Experiment A: min:\t28\tmax:\t84\tavg:\t56.0\t--\t28\t84", |
| " Experiment B: min:\t56\tmax:\t112\tavg:\t84.0\t--\t56\t112", |
| "Num unknown features:", |
| " Experiment A: min:\t29\tmax:\t87\tavg:\t58.0\t--\t29\t87", |
| " Experiment B: min:\t58\tmax:\t116\tavg:\t87.0\t--\t58\t116", |
| "Num funcs in frontier:", |
| " Experiment A: min:\t31\tmax:\t93\tavg:\t62.0\t--\t31\t93", |
| " Experiment B: min:\t62\tmax:\t124\tavg:\t93.0\t--\t62\t124", |
| "Num user0 features:", |
| " Experiment A: min:\t29\tmax:\t85\tavg:\t57.0\t--\t29\t85", |
| " Experiment B: min:\t57\tmax:\t113\tavg:\t85.0\t--\t57\t113", |
| "Num user1 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user2 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user3 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user4 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user5 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user6 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user7 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user8 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user9 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user10 features:", |
| " Experiment A: min:\t30\tmax:\t86\tavg:\t58.0\t--\t30\t86", |
| " Experiment B: min:\t58\tmax:\t114\tavg:\t86.0\t--\t58\t114", |
| "Num user11 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user12 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user13 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user14 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user15 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Flags:", |
| " Experiment A: AAA", |
| " Experiment B: BBB", |
| }; |
| |
| LogCapture log_capture; |
| stats_logger.ReportCurrStats(); |
| |
| const auto log_lines = log_capture.CapturedLogLines(); |
| EXPECT_THAT(log_lines, ElementsAreArray(kExpectedLogLines)) |
| << "\nActual:" << absl::StrJoin(log_lines, "\n") << "\nExpected:\n" |
| << absl::StrJoin(kExpectedLogLines, "\n"); |
| } |
| |
| { |
| for (auto &stats : stats_vec) { |
| auto new_stats = stats.load(); |
| |
| new_stats.timestamp_unix_micros += 1000000; |
| |
| new_stats.fuzz_time_sec += 1; |
| new_stats.num_executions += 1; |
| new_stats.num_target_crashes += 1; |
| |
| new_stats.num_covered_pcs += 1; |
| new_stats.num_8bit_counter_features += 1; |
| new_stats.num_data_flow_features += 1; |
| new_stats.num_cmp_features += 1; |
| new_stats.num_call_stack_features += 1; |
| new_stats.num_bounded_path_features += 1; |
| new_stats.num_pc_pair_features += 1; |
| new_stats.num_user_features += 1; |
| new_stats.num_user0_features += 1; |
| new_stats.num_user10_features += 1; |
| new_stats.num_unknown_features += 1; |
| new_stats.num_funcs_in_frontier += 1; |
| |
| new_stats.active_corpus_size += 1; |
| new_stats.total_corpus_size += 1; |
| new_stats.max_corpus_element_size += 1; |
| new_stats.avg_corpus_element_size += 1; |
| |
| new_stats.engine_rusage_avg_millicores += 1; |
| new_stats.engine_rusage_cpu_percent += 1; |
| new_stats.engine_rusage_rss_mb += 1; |
| new_stats.engine_rusage_vsize_mb += 1; |
| |
| stats.store(new_stats); |
| } |
| |
| const std::vector<std::string_view> kExpectedLogLines = { |
| "Current stats:", |
| "Coverage:", |
| " Experiment A: min:\t22\tmax:\t64\tavg:\t43.0\t--\t22\t64", |
| " Experiment B: min:\t43\tmax:\t85\tavg:\t64.0\t--\t43\t85", |
| "Number of executions:", |
| " Experiment A: min:\t13\tmax:\t37\tavg:\t25.0\t--\t13\t37", |
| " Experiment B: min:\t25\tmax:\t49\tavg:\t37.0\t--\t25\t49", |
| "Active corpus size:", |
| " Experiment A: min:\t102\tmax:\t304\tavg:\t203.0\t--\t102\t304", |
| " Experiment B: min:\t203\tmax:\t405\tavg:\t304.0\t--\t203\t405", |
| "Max element size:", |
| " Experiment A: min:\t104\tmax:\t310\tavg:\t207.0\t--\t104\t310", |
| " Experiment B: min:\t207\tmax:\t413\tavg:\t310.0\t--\t207\t413", |
| "Avg element size:", |
| " Experiment A: min:\t105\tmax:\t313\tavg:\t209.0\t--\t105\t313", |
| " Experiment B: min:\t209\tmax:\t417\tavg:\t313.0\t--\t209\t417", |
| "Fuzz time (sec):", |
| " Experiment A: min:\t11\tmax:\t31\tavg:\t21.0\t--\t11\t31", |
| " Experiment B: min:\t21\tmax:\t41\tavg:\t31.0\t--\t21\t41", |
| "Num proxy crashes:", |
| " Experiment A: min:\t14\tmax:\t40\tsum:\t54\t--\t14\t40", |
| " Experiment B: min:\t27\tmax:\t53\tsum:\t80\t--\t27\t53", |
| "Total corpus size:", |
| " Experiment A: min:\t103\tmax:\t307\tsum:\t410\t--\t103\t307", |
| " Experiment B: min:\t205\tmax:\t409\tsum:\t614\t--\t205\t409", |
| "Num 8-bit counter features:", |
| " Experiment A: min:\t23\tmax:\t67\tavg:\t45.0\t--\t23\t67", |
| " Experiment B: min:\t45\tmax:\t89\tavg:\t67.0\t--\t45\t89", |
| "Num data flow features:", |
| " Experiment A: min:\t24\tmax:\t70\tavg:\t47.0\t--\t24\t70", |
| " Experiment B: min:\t47\tmax:\t93\tavg:\t70.0\t--\t47\t93", |
| "Num cmp features:", |
| " Experiment A: min:\t25\tmax:\t73\tavg:\t49.0\t--\t25\t73", |
| " Experiment B: min:\t49\tmax:\t97\tavg:\t73.0\t--\t49\t97", |
| "Num call stack features:", |
| " Experiment A: min:\t26\tmax:\t76\tavg:\t51.0\t--\t26\t76", |
| " Experiment B: min:\t51\tmax:\t101\tavg:\t76.0\t--\t51\t101", |
| "Num bounded path features:", |
| " Experiment A: min:\t27\tmax:\t79\tavg:\t53.0\t--\t27\t79", |
| " Experiment B: min:\t53\tmax:\t105\tavg:\t79.0\t--\t53\t105", |
| "Num PC pair features:", |
| " Experiment A: min:\t28\tmax:\t82\tavg:\t55.0\t--\t28\t82", |
| " Experiment B: min:\t55\tmax:\t109\tavg:\t82.0\t--\t55\t109", |
| "Num user features:", |
| " Experiment A: min:\t29\tmax:\t85\tavg:\t57.0\t--\t29\t85", |
| " Experiment B: min:\t57\tmax:\t113\tavg:\t85.0\t--\t57\t113", |
| "Num unknown features:", |
| " Experiment A: min:\t30\tmax:\t88\tavg:\t59.0\t--\t30\t88", |
| " Experiment B: min:\t59\tmax:\t117\tavg:\t88.0\t--\t59\t117", |
| "Num funcs in frontier:", |
| " Experiment A: min:\t32\tmax:\t94\tavg:\t63.0\t--\t32\t94", |
| " Experiment B: min:\t63\tmax:\t125\tavg:\t94.0\t--\t63\t125", |
| "Num user0 features:", |
| " Experiment A: min:\t30\tmax:\t86\tavg:\t58.0\t--\t30\t86", |
| " Experiment B: min:\t58\tmax:\t114\tavg:\t86.0\t--\t58\t114", |
| "Num user1 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user2 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user3 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user4 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user5 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user6 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user7 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user8 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user9 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user10 features:", |
| " Experiment A: min:\t31\tmax:\t87\tavg:\t59.0\t--\t31\t87", |
| " Experiment B: min:\t59\tmax:\t115\tavg:\t87.0\t--\t59\t115", |
| "Num user11 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user12 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user13 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user14 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Num user15 features:", |
| " Experiment A: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| " Experiment B: min:\t0\tmax:\t0\tavg:\t0.0\t--\t0\t0", |
| "Flags:", |
| " Experiment A: AAA", |
| " Experiment B: BBB", |
| }; |
| |
| LogCapture log_capture; |
| stats_logger.ReportCurrStats(); |
| |
| const auto log_lines = log_capture.CapturedLogLines(); |
| EXPECT_THAT(log_lines, ElementsAreArray(kExpectedLogLines)); |
| } |
| } |
| |
| TEST(Stats, DumpStatsToCsvFile) { |
| const std::filesystem::path workdir = GetTestTempDir(test_info_->name()); |
| |
| std::vector<std::atomic<Stats>> stats_vec(4); |
| for (uint64_t i = 0; i < stats_vec.size(); ++i) { |
| const uint64_t j = i + 1; |
| |
| CovStats cov_stats{/*num_covered_pcs=*/ |
| 21 * j, |
| /*num_8bit_counter_features=*/22 * j, |
| /*num_data_flow_features=*/23 * j, |
| /*num_cmp_features=*/24 * j, |
| /*num_call_stack_features=*/25 * j, |
| /*num_bounded_path_features=*/26 * j, |
| /*num_pc_pair_features=*/27 * j, |
| /*num_user_features=*/28 * j}; |
| // NOTE: These two don't add up to `num_user_features`, but that's |
| // ok for the purposes of this test. |
| cov_stats.num_user0_features = 28 * j + 1; |
| cov_stats.num_user10_features = 28 * j + 2; |
| cov_stats.num_unknown_features = 29 * j; |
| cov_stats.num_funcs_in_frontier = 31 * j; |
| |
| stats_vec[i].store(Stats{ |
| StatsMeta{/*timestamp_unix_micros=*/1000000 * j}, |
| ExecStats{ |
| /*fuzz_time_sec=*/10 * j, |
| /*.num_executions=*/12 * j, |
| /*.num_target_crashes=*/13 * j, |
| }, |
| cov_stats, |
| CorpusStats{ |
| /*active_corpus_size=*/101 * j, |
| /*total_corpus_size=*/102 * j, |
| /*max_corpus_element_size=*/103 * j, |
| /*avg_corpus_element_size=*/104 * j, |
| }, |
| RusageStats{ |
| /*engine_rusage_avg_millicores=*/201 * j, |
| /*engine_rusage_cpu_percent=*/202 * j, |
| /*engine_rusage_rss_mb=*/203 * j, |
| /*engine_rusage_vsize_mb=*/204 * j, |
| }, |
| }); |
| } |
| |
| const std::vector<Environment> env_vec = { |
| CreateEnv(workdir.c_str(), /*experiment_name=*/"ExperimentA", |
| /*experiment_flags=*/"AAA"), |
| CreateEnv(workdir.c_str(), /*experiment_name=*/"ExperimentB", |
| /*experiment_flags=*/"BBB"), |
| CreateEnv(workdir.c_str(), /*experiment_name=*/"ExperimentA", |
| /*experiment_flags=*/"AAA"), |
| CreateEnv(workdir.c_str(), /*experiment_name=*/"ExperimentB", |
| /*experiment_flags=*/"BBB")}; |
| |
| { |
| StatsCsvFileAppender stats_csv_appender{stats_vec, env_vec}; |
| stats_csv_appender.ReportCurrStats(); |
| |
| // Emulate progress in shard #2 of each experiment. In the second line of |
| // each CSV, min's shouldn't change, max's should increase by 1, avg's |
| // should increase by 0.5. |
| for (int i = 2; i < stats_vec.size(); ++i) { |
| auto &stats = stats_vec[i]; |
| auto new_stats = stats.load(); |
| |
| new_stats.timestamp_unix_micros += 1; |
| |
| new_stats.fuzz_time_sec += 1; |
| new_stats.num_executions += 1; |
| new_stats.num_target_crashes += 1; |
| |
| new_stats.num_covered_pcs += 1; |
| new_stats.num_8bit_counter_features += 1; |
| new_stats.num_data_flow_features += 1; |
| new_stats.num_cmp_features += 1; |
| new_stats.num_call_stack_features += 1; |
| new_stats.num_bounded_path_features += 1; |
| new_stats.num_pc_pair_features += 1; |
| new_stats.num_user_features += 1; |
| new_stats.num_user0_features += 1; |
| new_stats.num_user10_features += 1; |
| new_stats.num_unknown_features += 1; |
| new_stats.num_funcs_in_frontier += 1; |
| |
| new_stats.active_corpus_size += 1; |
| new_stats.total_corpus_size += 1; |
| new_stats.max_corpus_element_size += 1; |
| new_stats.avg_corpus_element_size += 1; |
| |
| new_stats.engine_rusage_avg_millicores += 1; |
| new_stats.engine_rusage_cpu_percent += 1; |
| new_stats.engine_rusage_rss_mb += 1; |
| new_stats.engine_rusage_vsize_mb += 1; |
| |
| stats.store(new_stats); |
| } |
| |
| stats_csv_appender.ReportCurrStats(); |
| } |
| |
| const std::vector<std::string> kExpectedCsvs = { |
| workdir / "fuzzing-stats-.000000.ExperimentA.csv", |
| workdir / "fuzzing-stats-.000000.ExperimentB.csv", |
| }; |
| const std::vector<std::vector<std::string>> kExpectedCsvLines = { |
| // CSV #1. |
| { |
| // clang-format off |
| // Header. |
| "NumCoveredPcs_Min,NumCoveredPcs_Max,NumCoveredPcs_Avg," |
| "NumExecs_Min,NumExecs_Max,NumExecs_Avg," |
| "ActiveCorpusSize_Min,ActiveCorpusSize_Max,ActiveCorpusSize_Avg," |
| "MaxEltSize_Min,MaxEltSize_Max,MaxEltSize_Avg," |
| "AvgEltSize_Min,AvgEltSize_Max,AvgEltSize_Avg," |
| "UnixMicros_Min,UnixMicros_Max," |
| "FuzzTimeSec_Min,FuzzTimeSec_Max,FuzzTimeSec_Avg," |
| "NumProxyCrashes_Min,NumProxyCrashes_Max,NumProxyCrashes_Sum," |
| "TotalCorpusSize_Min,TotalCorpusSize_Max,TotalCorpusSize_Sum," |
| "Num8BitCounterFts_Min,Num8BitCounterFts_Max,Num8BitCounterFts_Avg," |
| "NumDataFlowFts_Min,NumDataFlowFts_Max,NumDataFlowFts_Avg," |
| "NumCmpFts_Min,NumCmpFts_Max,NumCmpFts_Avg," |
| "NumCallStackFts_Min,NumCallStackFts_Max,NumCallStackFts_Avg," |
| "NumBoundedPathFts_Min,NumBoundedPathFts_Max,NumBoundedPathFts_Avg," |
| "NumPcPairFts_Min,NumPcPairFts_Max,NumPcPairFts_Avg," |
| "NumUserFts_Min,NumUserFts_Max,NumUserFts_Avg," |
| "NumUnknownFts_Min,NumUnknownFts_Max,NumUnknownFts_Avg," |
| "NumFuncsInFrontier_Min,NumFuncsInFrontier_Max,NumFuncsInFrontier_Avg," // NOLINT |
| "EngineRusageAvgCores_Max,EngineRusageCpuPct_Max," |
| "EngineRusageRssMb_Max,EngineRusageVSizeMb_Max," |
| "NumUser0Fts_Min,NumUser0Fts_Max,NumUser0Fts_Avg," |
| "NumUser1Fts_Min,NumUser1Fts_Max,NumUser1Fts_Avg," |
| "NumUser2Fts_Min,NumUser2Fts_Max,NumUser2Fts_Avg," |
| "NumUser3Fts_Min,NumUser3Fts_Max,NumUser3Fts_Avg," |
| "NumUser4Fts_Min,NumUser4Fts_Max,NumUser4Fts_Avg," |
| "NumUser5Fts_Min,NumUser5Fts_Max,NumUser5Fts_Avg," |
| "NumUser6Fts_Min,NumUser6Fts_Max,NumUser6Fts_Avg," |
| "NumUser7Fts_Min,NumUser7Fts_Max,NumUser7Fts_Avg," |
| "NumUser8Fts_Min,NumUser8Fts_Max,NumUser8Fts_Avg," |
| "NumUser9Fts_Min,NumUser9Fts_Max,NumUser9Fts_Avg," |
| "NumUser10Fts_Min,NumUser10Fts_Max,NumUser10Fts_Avg," |
| "NumUser11Fts_Min,NumUser11Fts_Max,NumUser11Fts_Avg," |
| "NumUser12Fts_Min,NumUser12Fts_Max,NumUser12Fts_Avg," |
| "NumUser13Fts_Min,NumUser13Fts_Max,NumUser13Fts_Avg," |
| "NumUser14Fts_Min,NumUser14Fts_Max,NumUser14Fts_Avg," |
| "NumUser15Fts_Min,NumUser15Fts_Max,NumUser15Fts_Avg,", |
| // Line 1. |
| "21,63,42.0," |
| "12,36,24.0," |
| "101,303,202.0," |
| "103,309,206.0," |
| "104,312,208.0," |
| "1000000,3000000," |
| "10,30,20.0," |
| "13,39,52," |
| "102,306,408," |
| "22,66,44.0," |
| "23,69,46.0," |
| "24,72,48.0," |
| "25,75,50.0," |
| "26,78,52.0," |
| "27,81,54.0," |
| "28,84,56.0," |
| "29,87,58.0," |
| "31,93,62.0," |
| "603,606," |
| "609,612," |
| "29,85,57.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "30,86,58.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0,", |
| // Line 2. |
| "21,64,42.5," |
| "12,37,24.5," |
| "101,304,202.5," |
| "103,310,206.5," |
| "104,313,208.5," |
| "1000000,3000001," |
| "10,31,20.5," |
| "13,40,53," |
| "102,307,409," |
| "22,67,44.5," |
| "23,70,46.5," |
| "24,73,48.5," |
| "25,76,50.5," |
| "26,79,52.5," |
| "27,82,54.5," |
| "28,85,56.5," |
| "29,88,58.5," |
| "31,94,62.5," |
| "604,607," |
| "610,613," |
| "29,86,57.5," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "30,87,58.5," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0,", |
| "", // empty line at EOF |
| // clang-format on |
| }, |
| // CSV #2. |
| { |
| // clang-format off |
| // Header. |
| "NumCoveredPcs_Min,NumCoveredPcs_Max,NumCoveredPcs_Avg," |
| "NumExecs_Min,NumExecs_Max,NumExecs_Avg," |
| "ActiveCorpusSize_Min,ActiveCorpusSize_Max,ActiveCorpusSize_Avg," |
| "MaxEltSize_Min,MaxEltSize_Max,MaxEltSize_Avg," |
| "AvgEltSize_Min,AvgEltSize_Max,AvgEltSize_Avg," |
| "UnixMicros_Min,UnixMicros_Max," |
| "FuzzTimeSec_Min,FuzzTimeSec_Max,FuzzTimeSec_Avg," |
| "NumProxyCrashes_Min,NumProxyCrashes_Max,NumProxyCrashes_Sum," |
| "TotalCorpusSize_Min,TotalCorpusSize_Max,TotalCorpusSize_Sum," |
| "Num8BitCounterFts_Min,Num8BitCounterFts_Max,Num8BitCounterFts_Avg," |
| "NumDataFlowFts_Min,NumDataFlowFts_Max,NumDataFlowFts_Avg," |
| "NumCmpFts_Min,NumCmpFts_Max,NumCmpFts_Avg," |
| "NumCallStackFts_Min,NumCallStackFts_Max,NumCallStackFts_Avg," |
| "NumBoundedPathFts_Min,NumBoundedPathFts_Max,NumBoundedPathFts_Avg," |
| "NumPcPairFts_Min,NumPcPairFts_Max,NumPcPairFts_Avg," |
| "NumUserFts_Min,NumUserFts_Max,NumUserFts_Avg," |
| "NumUnknownFts_Min,NumUnknownFts_Max,NumUnknownFts_Avg," |
| "NumFuncsInFrontier_Min,NumFuncsInFrontier_Max,NumFuncsInFrontier_Avg," // NOLINT |
| "EngineRusageAvgCores_Max,EngineRusageCpuPct_Max," |
| "EngineRusageRssMb_Max,EngineRusageVSizeMb_Max," |
| "NumUser0Fts_Min,NumUser0Fts_Max,NumUser0Fts_Avg," |
| "NumUser1Fts_Min,NumUser1Fts_Max,NumUser1Fts_Avg," |
| "NumUser2Fts_Min,NumUser2Fts_Max,NumUser2Fts_Avg," |
| "NumUser3Fts_Min,NumUser3Fts_Max,NumUser3Fts_Avg," |
| "NumUser4Fts_Min,NumUser4Fts_Max,NumUser4Fts_Avg," |
| "NumUser5Fts_Min,NumUser5Fts_Max,NumUser5Fts_Avg," |
| "NumUser6Fts_Min,NumUser6Fts_Max,NumUser6Fts_Avg," |
| "NumUser7Fts_Min,NumUser7Fts_Max,NumUser7Fts_Avg," |
| "NumUser8Fts_Min,NumUser8Fts_Max,NumUser8Fts_Avg," |
| "NumUser9Fts_Min,NumUser9Fts_Max,NumUser9Fts_Avg," |
| "NumUser10Fts_Min,NumUser10Fts_Max,NumUser10Fts_Avg," |
| "NumUser11Fts_Min,NumUser11Fts_Max,NumUser11Fts_Avg," |
| "NumUser12Fts_Min,NumUser12Fts_Max,NumUser12Fts_Avg," |
| "NumUser13Fts_Min,NumUser13Fts_Max,NumUser13Fts_Avg," |
| "NumUser14Fts_Min,NumUser14Fts_Max,NumUser14Fts_Avg," |
| "NumUser15Fts_Min,NumUser15Fts_Max,NumUser15Fts_Avg,", |
| // Line 1. |
| "42,84,63.0," |
| "24,48,36.0," |
| "202,404,303.0," |
| "206,412,309.0," |
| "208,416,312.0," |
| "2000000,4000000," |
| "20,40,30.0," |
| "26,52,78," |
| "204,408,612," |
| "44,88,66.0," |
| "46,92,69.0," |
| "48,96,72.0," |
| "50,100,75.0," |
| "52,104,78.0," |
| "54,108,81.0," |
| "56,112,84.0," |
| "58,116,87.0," |
| "62,124,93.0," |
| "804,808," |
| "812,816," |
| "57,113,85.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "58,114,86.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0,", |
| // Line 2. |
| "42,85,63.5," |
| "24,49,36.5," |
| "202,405,303.5," |
| "206,413,309.5," |
| "208,417,312.5," |
| "2000000,4000001," |
| "20,41,30.5," |
| "26,53,79," |
| "204,409,613," |
| "44,89,66.5," |
| "46,93,69.5," |
| "48,97,72.5," |
| "50,101,75.5," |
| "52,105,78.5," |
| "54,109,81.5," |
| "56,113,84.5," |
| "58,117,87.5," |
| "62,125,93.5," |
| "805,809," |
| "813,817," |
| "57,114,85.5," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "58,115,86.5," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0," |
| "0,0,0.0,", |
| "", // empty line at EOF |
| // clang-format on |
| }, |
| }; |
| |
| for (int i = 0; i < 2; ++i) { |
| ASSERT_TRUE(std::filesystem::exists(kExpectedCsvs[i])); |
| std::string csv_contents; |
| ReadFromLocalFile(kExpectedCsvs[i], csv_contents); |
| const std::vector<std::string_view> csv_lines = |
| absl::StrSplit(csv_contents, '\n'); |
| ASSERT_EQ(csv_lines.size(), kExpectedCsvLines[i].size()); |
| for (size_t j = 0; j < csv_lines.size(); ++j) { |
| EXPECT_EQ(csv_lines[j], kExpectedCsvLines[i][j]) << VV(i) << VV(j); |
| } |
| } |
| } |
| |
| TEST(Stats, DumpStatsToExistingCsvFile) { |
| class TestStatsCsvFileAppender : public StatsCsvFileAppender { |
| using StatsCsvFileAppender::StatsCsvFileAppender; |
| |
| private: |
| std::string GetBackupFilename(const std::string &filename) const override { |
| return absl::StrCat(filename, ".bak"); |
| } |
| }; |
| |
| const std::filesystem::path workdir = GetTestTempDir(test_info_->name()); |
| const std::vector<Environment> env_vec = {CreateEnv(workdir.c_str()), |
| CreateEnv(workdir.c_str())}; |
| |
| std::vector<std::atomic<Stats>> stats_vec(2); |
| stats_vec[0].store({StatsMeta{/*timestamp_unix_micros=*/1000000}}); |
| stats_vec[1].store({StatsMeta{/*timestamp_unix_micros=*/2000000}}); |
| |
| const std::string kExpectedCsv = workdir / "fuzzing-stats-.000000.csv"; |
| const std::string kExpectedCsvBak = workdir / "fuzzing-stats-.000000.csv.bak"; |
| |
| // `StatsCsvFileAppender` creates a brand-new fuzzing-stats file and writes |
| // the CSV header and a 1st stats line to it. |
| { |
| TestStatsCsvFileAppender stats_csv_appender{stats_vec, env_vec}; |
| stats_csv_appender.ReportCurrStats(); |
| |
| ASSERT_TRUE(std::filesystem::exists(kExpectedCsv)); |
| ASSERT_FALSE(std::filesystem::exists(kExpectedCsvBak)); |
| |
| std::string contents; |
| ReadFromLocalFile(kExpectedCsv, contents); |
| const std::vector<std::string> lines = absl::StrSplit(contents, '\n'); |
| // Header + 1 stats line + empty line at EOF. |
| EXPECT_EQ(lines.size(), 3); |
| } |
| |
| // `StatsCsvFileAppender` finds an existing file with a CSV header matching |
| // the current version and appends a 2nd stats line to it. |
| { |
| stats_vec[0].store({StatsMeta{/*timestamp_unix_micros=*/3000000}}); |
| stats_vec[1].store({StatsMeta{/*timestamp_unix_micros=*/4000000}}); |
| |
| TestStatsCsvFileAppender stats_csv_appender{stats_vec, env_vec}; |
| stats_csv_appender.ReportCurrStats(); |
| |
| ASSERT_TRUE(std::filesystem::exists(kExpectedCsv)); |
| ASSERT_FALSE(std::filesystem::exists(kExpectedCsvBak)); |
| |
| std::string contents; |
| ReadFromLocalFile(kExpectedCsv, contents); |
| const std::vector<std::string> lines = absl::StrSplit(contents, '\n'); |
| // Header + 2 stats lines + empty line at EOF. |
| EXPECT_EQ(lines.size(), 4); |
| } |
| |
| // `StatsCsvFileAppender` finds an existing file with a CSV header not |
| // matching the current version (ostensibly created by a previous version of |
| // Centipede), creates a backup copy of it, and starts a new file scratch, |
| // writing the new CSV header and a 1st line to it. |
| { |
| // Fake a CSV with an outdated header and contents. |
| const std::vector<std::string> kFakeOldCsvLines = { |
| "Old,Csv,Header,", "0,1,2,", "10,11,12,", "20,21,22,", "", |
| }; |
| WriteToLocalFile(kExpectedCsv, absl::StrJoin(kFakeOldCsvLines, "\n")); |
| |
| stats_vec[0].store({StatsMeta{/*timestamp_unix_micros=*/5000000}}); |
| stats_vec[1].store({StatsMeta{/*timestamp_unix_micros=*/6000000}}); |
| |
| TestStatsCsvFileAppender stats_csv_appender{stats_vec, env_vec}; |
| stats_csv_appender.ReportCurrStats(); |
| |
| ASSERT_TRUE(std::filesystem::exists(kExpectedCsv)); |
| ASSERT_TRUE(std::filesystem::exists(kExpectedCsvBak)); |
| |
| std::string contents; |
| ReadFromLocalFile(kExpectedCsv, contents); |
| const std::vector<std::string> lines = absl::StrSplit(contents, '\n'); |
| // Header + 1 stats line + empty line at EOF. |
| EXPECT_EQ(lines.size(), 3); |
| |
| std::string contents_bak; |
| ReadFromLocalFile(kExpectedCsvBak, contents_bak); |
| const std::vector<std::string> lines_bak = |
| absl::StrSplit(contents_bak, '\n'); |
| FUZZTEST_CHECK_NE(lines.size(), |
| kFakeOldCsvLines.size()); // Prevent false positives |
| EXPECT_EQ(lines_bak.size(), kFakeOldCsvLines.size()); |
| } |
| } |
| |
| TEST(Stats, PrintRewardValues) { |
| std::stringstream ss; |
| std::vector<std::atomic<Stats>> stats_vec(4); |
| stats_vec[0].store( |
| {StatsMeta{}, ExecStats{}, CovStats{/*num_covered_pcs=*/15}}); |
| stats_vec[1].store( |
| {StatsMeta{}, ExecStats{}, CovStats{/*num_covered_pcs=*/10}}); |
| stats_vec[2].store( |
| {StatsMeta{}, ExecStats{}, CovStats{/*num_covered_pcs=*/40}}); |
| stats_vec[3].store( |
| {StatsMeta{}, ExecStats{}, CovStats{/*num_covered_pcs=*/25}}); |
| PrintRewardValues(stats_vec, ss); |
| const char *expected = |
| "REWARD_MAX 40\n" |
| "REWARD_SECOND_MAX 25\n" |
| "REWARD_MIN 10\n" |
| "REWARD_MEDIAN 25\n" |
| "REWARD_AVERAGE 22.5\n"; |
| EXPECT_EQ(ss.str(), expected); |
| } |
| |
| } // namespace |
| } // namespace fuzztest::internal |