blob: 8132a5941cff6c509e9f4364048e3cb53669a708 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/sys/fuzzing/libfuzzer/stats.h"
#include <lib/syslog/cpp/macros.h>
#include <sstream>
#include <re2/re2.h>
namespace fuzzing {
using fuchsia::fuzzer::ProcessStats;
bool ParseLibFuzzerStats(std::string_view line, UpdateReason* reason, Status* status) {
re2::StringPiece input(line);
uint32_t runs;
if (!re2::RE2::Consume(&input, "#(\\d+)", &runs)) {
return false;
}
// Parse reason.
std::string reason_str;
if (!re2::RE2::Consume(&input, "\\t(\\S+)", &reason_str)) {
return false;
}
// Is `runs` valid?
uint64_t scaled_runs;
if (runs == 0 || mul_overflow(runs, kOneSecond, &scaled_runs)) {
return false;
}
status->set_runs(runs);
*reason = UpdateReason::PULSE; // By default, assume it's just a status update.
if (reason_str == "INITED") {
*reason = UpdateReason::INIT;
} else if (reason_str == "NEW") {
*reason = UpdateReason::NEW;
} else if (reason_str == "REDUCE") {
*reason = UpdateReason::REDUCE;
} else if (reason_str == "DONE") {
*reason = UpdateReason::DONE;
status->set_running(false);
}
// Parse covered PCs.
size_t covered_pcs;
if (re2::RE2::FindAndConsume(&input, "cov: (\\d+)", &covered_pcs)) {
status->set_covered_pcs(covered_pcs);
}
// Parse covered features.
size_t covered_features;
if (re2::RE2::FindAndConsume(&input, "ft: (\\d+)", &covered_features)) {
status->set_covered_features(covered_features);
}
// Parse corpus stats.
size_t corpus_num_inputs;
if (re2::RE2::FindAndConsume(&input, "corp: (\\d+)", &corpus_num_inputs)) {
size_t corpus_total_size;
status->set_corpus_num_inputs(corpus_num_inputs);
if (re2::RE2::Consume(&input, "/(\\d+)b", &corpus_total_size)) {
status->set_corpus_total_size(corpus_total_size);
} else if (re2::RE2::Consume(&input, "/(\\d+)Kb", &corpus_total_size)) {
status->set_corpus_total_size(corpus_total_size * kOneKb);
} else if (re2::RE2::Consume(&input, "/(\\d+)Mb", &corpus_total_size)) {
status->set_corpus_total_size(corpus_total_size * kOneMb);
}
}
// Best effort: libFuzzer does not track total elapsed time, but it does have number of runs and
// runs per second.
int64_t execs_per_second;
if (re2::RE2::FindAndConsume(&input, "exec/s: (\\d+)", &execs_per_second)) {
if (execs_per_second != 0 && scaled_runs < std::numeric_limits<int64_t>::max()) {
int64_t elapsed = static_cast<int64_t>(scaled_runs) / execs_per_second;
if (!status->has_elapsed() || status->elapsed() < elapsed) {
status->set_elapsed(elapsed);
}
}
}
// Best effort: the libFuzzer output obviously does not have `ZX_INFO_TASK_*` details. Instead,
// this will add a minimal `ProcessStats` with a rough estimate of memory consumed if the status
// does not have more detailed process info. These `ProcessStats` are easily identifiable by
// having an invalid KOID.
if (!status->has_process_stats() ||
(status->process_stats().size() == 1 && status->process_stats()[0].koid == ZX_KOID_INVALID)) {
size_t rss_mb;
if (re2::RE2::FindAndConsume(&input, "rss: (\\d+)Mb", &rss_mb)) {
std::vector<ProcessStats> process_stats(1);
process_stats[0].koid = ZX_KOID_INVALID;
process_stats[0].mem_private_bytes = rss_mb * kOneMb;
status->set_process_stats(std::move(process_stats));
}
}
return true;
}
std::string FormatLibFuzzerStats(UpdateReason reason, const Status& status) {
FX_CHECK(status.has_runs());
int64_t runs = status.runs();
// Since `status.runs` is a `u32`, this scaling should always succeed.
int64_t scaled_runs;
FX_CHECK(!mul_overflow(runs, kOneSecond, &scaled_runs));
std::ostringstream oss;
oss << "#" << runs << "\t";
switch (reason) {
case UpdateReason::INIT:
oss << "INITED";
break;
case UpdateReason::NEW:
oss << "NEW ";
break;
case UpdateReason::PULSE:
oss << "pulse ";
break;
case UpdateReason::REDUCE:
oss << "REDUCE";
break;
case UpdateReason::DONE:
oss << "DONE ";
break;
}
if (status.has_covered_pcs()) {
if (auto covered_pcs = status.covered_pcs()) {
oss << " cov: " << covered_pcs;
}
}
if (status.has_covered_features()) {
if (auto covered_features = status.covered_features()) {
oss << " ft: " << covered_features;
}
}
if (status.has_corpus_num_inputs()) {
if (auto corpus_num_inputs = status.corpus_num_inputs()) {
oss << " corp: " << corpus_num_inputs;
if (status.has_corpus_total_size()) {
auto corpus_total_size = status.corpus_total_size();
oss << "/";
if (corpus_total_size >= kOneMb) {
oss << (corpus_total_size / kOneMb) << "Mb";
} else if (corpus_total_size >= kOneKb) {
oss << (corpus_total_size / kOneKb) << "Kb";
} else if (corpus_total_size) {
oss << corpus_total_size << "b";
}
}
}
}
oss << " exec/s: ";
if (status.has_elapsed()) {
auto elapsed = status.elapsed();
oss << scaled_runs / elapsed;
} else {
oss << 0;
}
oss << " rss: ";
if (status.has_process_stats()) {
size_t mem_bytes = 0;
for (const auto& process_stats : status.process_stats()) {
mem_bytes += process_stats.mem_private_bytes + process_stats.mem_scaled_shared_bytes;
}
oss << ((mem_bytes + kOneMb - 1) / kOneMb);
} else {
oss << 0;
}
oss << "Mb";
return oss.str();
}
} // namespace fuzzing