blob: 2fc3755e89338289537025dfeb3c5e5610896176 [file] [log] [blame]
// Copyright 2019 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 "json_formatter.h"
#include <rapidjson/prettywriter.h>
#include <third_party/cobalt/util/crypto_util/base64.h>
#include <locale>
#include "lib/inspect/hierarchy.h"
#include "rapidjson/stringbuffer.h"
using cobalt::crypto::Base64Encode;
namespace inspect {
namespace {
constexpr size_t kMaxDecimalPlaces = 6;
template <typename WriterType>
void FormatNumericValue(WriterType& writer, int64_t value) {
writer->Int64(value);
}
template <typename WriterType>
void FormatNumericValue(WriterType& writer, uint64_t value) {
writer->Uint64(value);
}
template <typename WriterType>
void FormatNumericValue(WriterType& writer, double value) {
if (value == std::numeric_limits<double>::infinity()) {
writer->String("Infinity");
} else if (value == -std::numeric_limits<double>::infinity()) {
writer->String("-Infinity");
} else if (value == std::numeric_limits<double>::quiet_NaN()) {
writer->String("NaN");
} else {
writer->Double(value);
}
}
// Properly formats an array metric based on its display flags.
template <typename WriterType, typename ArrayType>
void FormatArray(WriterType& writer, const ArrayType& array) {
auto buckets = array.GetBuckets();
if (buckets.size() != 0) {
writer->StartObject();
writer->String("buckets");
writer->StartArray();
for (const auto& bucket : buckets) {
writer->StartObject();
writer->String("floor");
FormatNumericValue(writer, bucket.floor);
writer->String("upper_bound");
FormatNumericValue(writer, bucket.upper_limit);
writer->String("count");
FormatNumericValue(writer, bucket.count);
writer->EndObject();
}
writer->EndArray();
writer->EndObject();
} else {
writer->StartArray();
for (const auto& val : array.value()) {
FormatNumericValue(writer, val);
}
writer->EndArray();
}
}
// Properly formats a metric based on its type.
template <typename WriterType>
void FormatMetricValue(WriterType& writer,
const inspect::hierarchy::Metric& metric) {
switch (metric.format()) {
case inspect::hierarchy::MetricFormat::INT_ARRAY:
FormatArray(writer, metric.Get<inspect::hierarchy::IntArray>());
break;
case inspect::hierarchy::MetricFormat::UINT_ARRAY:
FormatArray(writer, metric.Get<inspect::hierarchy::UIntArray>());
break;
case inspect::hierarchy::MetricFormat::DOUBLE_ARRAY:
FormatArray(writer, metric.Get<inspect::hierarchy::DoubleArray>());
break;
case inspect::hierarchy::MetricFormat::INT:
FormatNumericValue(writer,
metric.Get<inspect::hierarchy::IntMetric>().value());
break;
case inspect::hierarchy::MetricFormat::UINT:
FormatNumericValue(writer,
metric.Get<inspect::hierarchy::UIntMetric>().value());
break;
case inspect::hierarchy::MetricFormat::DOUBLE:
FormatNumericValue(
writer, metric.Get<inspect::hierarchy::DoubleMetric>().value());
break;
default:
writer->String("<unknown metric format>");
}
}
// Create a pretty format JSON writer that indents its output.
template <typename OutputStream>
std::unique_ptr<rapidjson::PrettyWriter<OutputStream>> GetPrettyJsonWriter(
OutputStream& os, const JsonFormatter::Options& options) {
auto ret = std::make_unique<rapidjson::PrettyWriter<OutputStream>>(os);
ret->SetMaxDecimalPlaces(kMaxDecimalPlaces);
ret->SetIndent(' ', options.indent);
return ret;
}
// Create a plain JSON writer that doesn't pretty format.
template <typename OutputStream>
std::unique_ptr<rapidjson::Writer<OutputStream>> GetJsonWriter(
OutputStream& os, const JsonFormatter::Options& options) {
auto ret = std::make_unique<rapidjson::Writer<OutputStream>>(os);
ret->SetMaxDecimalPlaces(kMaxDecimalPlaces);
return ret;
}
// Internal function to recursively format a hierarchy rooted at a given source.
template <typename WriterType>
void InternalFormatSource(WriterType& writer, const inspect::Source& source,
const inspect::ObjectHierarchy& root) {
writer->StartObject();
// Properties.
for (const auto& property : root.node().properties()) {
writer->String(property.name());
switch (property.format()) {
case inspect::hierarchy::PropertyFormat::STRING:
writer->String(
property.Get<inspect::hierarchy::StringProperty>().value());
break;
case inspect::hierarchy::PropertyFormat::BYTES: {
auto& val =
property.Get<inspect::hierarchy::ByteVectorProperty>().value();
std::string content;
Base64Encode((uint8_t*)val.data(), val.size(), &content);
writer->String("b64:" + content);
break;
}
default:
writer->String("<Unknown type, format failed>");
break;
}
}
// Metrics.
for (const auto& metric : root.node().metrics()) {
writer->String(metric.name());
FormatMetricValue(writer, metric);
}
for (const auto& child : root.children()) {
writer->String(child.node().name());
InternalFormatSource(writer, source, child);
}
writer->EndObject();
}
} // namespace
template <typename WriterType>
void JsonFormatter::InternalFormatSourceLocations(
WriterType& writer, const std::vector<inspect::Source>& sources) const {
writer->StartArray();
for (const auto& source : sources) {
source.VisitObjectsInHierarchy(
[&](const Path& path, const inspect::ObjectHierarchy& hierarchy) {
writer->String(FormatPathOrName(source.GetLocation(), path,
hierarchy.node().name()));
});
}
writer->EndArray();
}
std::string JsonFormatter::FormatSourceLocations(
const std::vector<inspect::Source>& sources) const {
rapidjson::StringBuffer buffer;
if (options_.indent == 0) {
auto writer = GetJsonWriter(buffer, options_);
InternalFormatSourceLocations(writer, sources);
} else {
auto writer = GetPrettyJsonWriter(buffer, options_);
InternalFormatSourceLocations(writer, sources);
}
return buffer.GetString();
}
template <typename WriterType>
void JsonFormatter::InternalFormatChildListing(
WriterType& writer, const std::vector<inspect::Source>& sources) const {
writer->StartArray();
for (const auto& source : sources) {
const auto& hierarchy = source.GetHierarchy();
for (const auto& child : hierarchy.children()) {
writer->String(FormatPathOrName(
source.GetLocation(), {child.node().name()}, child.node().name()));
}
}
writer->EndArray();
}
std::string JsonFormatter::FormatChildListing(
const std::vector<inspect::Source>& sources) const {
rapidjson::StringBuffer buffer;
if (options_.indent == 0) {
auto writer = GetJsonWriter(buffer, options_);
InternalFormatChildListing(writer, sources);
} else {
auto writer = GetPrettyJsonWriter(buffer, options_);
InternalFormatChildListing(writer, sources);
}
return buffer.GetString();
}
template <typename WriterType>
void JsonFormatter::InternalFormatSourcesRecursive(
WriterType& writer, const std::vector<inspect::Source>& sources) const {
writer->StartArray();
for (const auto& source : sources) {
writer->StartObject();
writer->String("path");
writer->String(FormatPath(source.GetLocation(), {}));
writer->String("contents");
writer->StartObject();
writer->String(source.GetHierarchy().node().name());
InternalFormatSource(writer, source, source.GetHierarchy());
writer->EndObject(); // contents
writer->EndObject(); // source
}
writer->EndArray();
}
std::string JsonFormatter::FormatSourcesRecursive(
const std::vector<inspect::Source>& sources) const {
rapidjson::StringBuffer buffer;
if (options_.indent == 0) {
auto writer = GetJsonWriter(buffer, options_);
InternalFormatSourcesRecursive(writer, sources);
} else {
auto writer = GetPrettyJsonWriter(buffer, options_);
InternalFormatSourcesRecursive(writer, sources);
}
return buffer.GetString();
}
} // namespace inspect