blob: 775a8fb19e1612b7e7cbc4d34e324e023202601f [file] [log] [blame]
// Copyright 2020 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/developer/forensics/feedback_data/metadata.h"
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <zircon/utc.h>
#include <optional>
#include <set>
#include "src/developer/forensics/feedback_data/constants.h"
#include "src/developer/forensics/feedback_data/errors.h"
#include "src/developer/forensics/utils/cobalt/metrics.h"
#include "src/developer/forensics/utils/errors.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
#include "third_party/rapidjson/include/rapidjson/prettywriter.h"
#include "third_party/rapidjson/include/rapidjson/rapidjson.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
namespace forensics {
namespace feedback_data {
namespace {
using namespace rapidjson;
std::set<std::string> kUtcMonotonicDifferenceAllowlist = {
kAttachmentInspect,
kAttachmentLogKernel,
kAttachmentLogSystem,
};
std::set<std::string> kPreviousBootUtcMonotonicDifferenceAllowlist = {
kAttachmentLogSystemPrevious,
};
std::string ToString(const enum AttachmentValue::State state) {
switch (state) {
case AttachmentValue::State::kComplete:
return "complete";
case AttachmentValue::State::kPartial:
return "partial";
case AttachmentValue::State::kMissing:
return "missing";
}
}
// Create a complete list set of annotations from the collected annotations and the allowlist.
Annotations AllAnnotations(const AnnotationKeys& allowlist,
const ::fit::result<Annotations>& annotations_result) {
Annotations all_annotations;
if (annotations_result.is_ok()) {
all_annotations.insert(annotations_result.value().cbegin(), annotations_result.value().cend());
}
for (const auto& key : allowlist) {
if (all_annotations.find(key) == all_annotations.end()) {
// There is an annotation in the allowlist that was not produced by any provider. This
// indicates a logical error on the Feedback-side.
all_annotations.insert({key, AnnotationOr(Error::kLogicError)});
}
}
return all_annotations;
}
// Create a complete list set of attachments from the collected attachments and the allowlist.
Attachments AllAttachments(const AttachmentKeys& allowlist,
const ::fit::result<Attachments>& attachments_result) {
Attachments all_attachments;
if (attachments_result.is_ok()) {
// Because attachments can contain large blobs of text and we only care about the state of the
// attachment and its associated error, we don't copy the value of the attachment.
for (const auto& [k, v] : attachments_result.value()) {
switch (v.State()) {
case AttachmentValue::State::kComplete:
all_attachments.insert({k, AttachmentValue("")});
break;
case AttachmentValue::State::kPartial:
all_attachments.insert({k, AttachmentValue("", v.Error())});
break;
case AttachmentValue::State::kMissing:
all_attachments.insert({k, v});
break;
}
}
}
for (const auto& key : allowlist) {
if (all_attachments.find(key) == all_attachments.end()) {
all_attachments.insert({key, AttachmentValue(Error::kLogicError)});
}
}
return all_attachments;
}
void AddUtcMonotonicDifference(const std::optional<zx::duration>& utc_monotonic_difference,
Value* file, Document::AllocatorType& allocator) {
if (!utc_monotonic_difference.has_value() || !file->IsObject() ||
file->HasMember("utc_monotonic_difference_nanos")) {
return;
}
file->AddMember("utc_monotonic_difference_nanos",
Value().SetInt64(utc_monotonic_difference.value().get()), allocator);
}
void AddUtcMonotonicDifferences(
const std::optional<zx::duration> utc_monotonic_difference,
const std::optional<zx::duration> previous_boot_utc_monotonic_difference,
Document* metadata_json) {
if (!metadata_json->HasMember("files")) {
return;
}
for (auto& file : (*metadata_json)["files"].GetObject()) {
if (kUtcMonotonicDifferenceAllowlist.find(file.name.GetString()) !=
kUtcMonotonicDifferenceAllowlist.end()) {
AddUtcMonotonicDifference(utc_monotonic_difference, &file.value,
metadata_json->GetAllocator());
}
if (kPreviousBootUtcMonotonicDifferenceAllowlist.find(file.name.GetString()) !=
kPreviousBootUtcMonotonicDifferenceAllowlist.end()) {
AddUtcMonotonicDifference(previous_boot_utc_monotonic_difference, &file.value,
metadata_json->GetAllocator());
}
}
}
void AddAttachments(const AttachmentKeys& attachment_allowlist,
const ::fit::result<Attachments>& attachments_result, Document* metadata_json) {
if (attachment_allowlist.empty()) {
return;
}
auto& allocator = metadata_json->GetAllocator();
auto MakeValue = [&allocator](const std::string& v) { return Value(v, allocator); };
for (const auto& [name, v] : AllAttachments(attachment_allowlist, attachments_result)) {
Value file(kObjectType);
file.AddMember("state", MakeValue(ToString(v.State())), allocator);
if (v.HasError()) {
file.AddMember("error", MakeValue(ToReason(v.Error())), allocator);
}
(*metadata_json)["files"].AddMember(MakeValue(name), file, allocator);
}
}
void AddAnnotationsJson(const AnnotationKeys& annotation_allowlist,
const ::fit::result<Annotations>& annotations_result,
const bool missing_non_platform_annotations, Document* metadata_json) {
const Annotations all_annotations = AllAnnotations(annotation_allowlist, annotations_result);
bool has_non_platform = all_annotations.size() > annotation_allowlist.size();
if (annotation_allowlist.empty() && !(has_non_platform || missing_non_platform_annotations)) {
return;
}
auto& allocator = metadata_json->GetAllocator();
auto MakeValue = [&allocator](const std::string& v) { return Value(v, allocator); };
Value present(kArrayType);
Value missing(kObjectType);
size_t num_present_platform = 0u;
size_t num_missing_platform = 0u;
for (const auto& [k, v] : all_annotations) {
if (annotation_allowlist.find(k) == annotation_allowlist.end()) {
continue;
}
Value key(MakeValue(k));
if (v.HasValue()) {
present.PushBack(key, allocator);
++num_present_platform;
} else {
missing.AddMember(key, MakeValue(ToReason(v.Error())), allocator);
++num_missing_platform;
}
}
if (has_non_platform || missing_non_platform_annotations) {
if (!missing_non_platform_annotations) {
present.PushBack("non-platform annotations", allocator);
} else {
missing.AddMember("non-platform annotations", "too many non-platfrom annotations added",
allocator);
}
}
Value state;
if (num_present_platform == annotation_allowlist.size() && !missing_non_platform_annotations) {
state = "complete";
} else if (num_missing_platform == annotation_allowlist.size() && !has_non_platform &&
missing_non_platform_annotations) {
state = "missing";
} else {
state = "partial";
}
Value annotations_json(kObjectType);
annotations_json.AddMember("state", state, allocator);
annotations_json.AddMember("missing annotations", missing, allocator);
annotations_json.AddMember("present annotations", present, allocator);
(*metadata_json)["files"].AddMember("annotations.json", annotations_json, allocator);
}
} // namespace
Metadata::Metadata(async_dispatcher_t* dispatcher, timekeeper::Clock* clock,
const bool is_first_instance, const AnnotationKeys& annotation_allowlist,
const AttachmentKeys& attachment_allowlist)
: annotation_allowlist_(annotation_allowlist),
attachment_allowlist_(attachment_allowlist),
utc_provider_(dispatcher, zx::unowned_clock(zx_utc_reference_get()), clock,
PreviousBootFile::FromCache(is_first_instance, kUtcMonotonicDifferenceFile)) {}
std::string Metadata::MakeMetadata(const ::fit::result<Annotations>& annotations_result,
const ::fit::result<Attachments>& attachments_result,
bool missing_non_platform_annotations) {
Document metadata_json(kObjectType);
auto& allocator = metadata_json.GetAllocator();
auto MetadataString = [&metadata_json]() {
StringBuffer buffer;
PrettyWriter<StringBuffer> writer(buffer);
metadata_json.Accept(writer);
return std::string(buffer.GetString());
};
// Insert all top-level fields
metadata_json.AddMember("snapshot_version", Value(SnapshotVersion::kString, allocator),
allocator);
metadata_json.AddMember("metadata_version", Value(Metadata::kVersion, allocator), allocator);
metadata_json.AddMember("files", Value(kObjectType), allocator);
const bool has_non_platform_annotations =
annotations_result.is_ok() &&
annotations_result.value().size() > annotation_allowlist_.size();
if (annotation_allowlist_.empty() && attachment_allowlist_.empty() &&
!has_non_platform_annotations && !missing_non_platform_annotations) {
return MetadataString();
}
AddAttachments(attachment_allowlist_, attachments_result, &metadata_json);
AddAnnotationsJson(annotation_allowlist_, annotations_result, missing_non_platform_annotations,
&metadata_json);
AddUtcMonotonicDifferences(utc_provider_.CurrentUtcMonotonicDifference(),
utc_provider_.PreviousBootUtcMonotonicDifference(), &metadata_json);
return MetadataString();
}
} // namespace feedback_data
} // namespace forensics